พัฒนาแอพบน Kubernetes ให้เป็นเรื่องง่ายด้วย Skaffold

Nuttavut Thongjor

เกือบ 4 ปีที่เหล่าออเจ้าได้รู้จักหนทางแห่งการ deploy แอพพลิเคชันที่เลิศ scale ระบบได้ดี และจัดการแอพพลิเคชันได้ชิค ๆ คูล ๆ กับเครื่องมือที่ชื่อว่า Kubernetes ที่ดังกระฉ่อนไปทั่วทั้งพระนคร

ทว่า deployment โดยทั่วไปยังฝากชะตากรรมนี้ไว้กับ devops ทีม โปรแกรมเมอร์นั้นฤางานกรรมกรมีหน้าที่โค้ดก็โค้ดไปซิ จะกระสันไปจุ้นส่วนอื่นทำไม

โลกเปลี่ยนคนเปลี่ยน เมื่อสภาพแวดล้อมในการพัฒนาเช่นระบบปฏิบัติการไม่ตรงกับการทำงานบน production บางครั้งปัญหาจึงเกิด โปรแกรมเมอร์จึงต้องขายวิญญาณด้วยการสร้าง containerized app ผ่าน Docker ส่วนงาน deploy นั้นไซร์ให้ทีมอาวุโสอย่าง devops ทำต่อไปเถอะ

ถึงกระนั้นปัญหายังคงราวี แม้ตอนนี้โปรแกรมเมอร์จะควบคุมและจัดการ dependencies ในการพัฒนาให้ใกล้เคียงกับการใช้งานจริงได้แล้วก็ตาม แต่ขั้นตอนของการ deploy นั้นก็ไม่การันตีว่าจะทำให้แอพพลิเคชันของเราไม่นิพพานก่อนวัยอันควร โปรแกรมเมอร์ที่น่าสงสารจึงต้องเรียนรู้เพื่อใช้งาน Kubernetes จะได้ทดสอบและตรวจสอบการทำงานให้ถูกต้องก่อนการส่งมอบงาน

กระบวนการพัฒนาซอฟต์แวร์ด้วย Kubernetes

โดยทั่วไปกระบวนการพัฒนาจึงเป็นเช่นนี้ 1

  1. จัดหาคลัสเตอร์ของ Kubernetes ซะก่อนไม่ว่าจะเป็น GKE AKS หรือเครื่องมืออย่าง kops
  2. Build Docker Image และทำการอัพโหลดไปยัง registry เพื่อใช้งานบนคลัสเตอร์
  3. สร้าง Kubernetes manifest เพื่อใช้งานตามเอกสารของ Kubernetes
  4. Deploy แอพพลิเคชันผ่าน kubectl CLI หรือ Kubernetes Dashboard
  5. ทำซ้ำข้อ 2 - 4 จนกว่ากาพัฒนาฟีเจอร์หรือการแก้ข้อผิดพลาดจะเสร็จสิ้น
  6. โยนโครมลง CI เพื่อทำ Unit testing Integration testing และ deploy ลงสภาพแวดล้อมแบบ test หรือ staging

แค่อ่านก็หาวแล้วใช่ไหม โดยปกติขั้นตอนที่ 2 - 5 มักเป็นกระบวนการทำมือผ่านเครื่องมือต่าง ๆ และบางอย่างก็เป็นเรื่องที่ต้องตบตีกันเอาเองว่าจะใช้ท่าไหน เช่น บางคนกำหนดเลข tag ด้วยการนับเพิ่มเรื่อยๆ จาก 1 ไปถึงไหนก็ไม่รู้ แต่ถ้าวันนึง deploy เยอะมากนับเลขคงไม่ไหว เอา tag เป็นเลข commit ของ GIT แล้วกัน เป็นต้น

รู้จัก Skaffold เครื่องมือที่จะทำให้การพัฒนาเป็นเรื่องง่าย

ทั้งหมดทั้งมวลจะเห็นว่าขั้นตอนที่ 2 - 5 นั้นยุ่งยากและเป็นส่วนที่สามารถ automate ได้ จึงเกิดเครื่องมือสนอง need ต่าง ๆ มากมาย เช่น Draft จาก Microsoft Forge จาก Datawire และ Flux จาก Weavework

และล่าสุดกับ Skaffold เครื่องมือสุดเจ๋งที่จะช่วยเหล่าออเจ้าลดความยุ่งยากในการพัฒนาแอพพลิเคชันและ deploy ด้วย Kubernetes

ไม่ต้องพูดพร่ำทำเพลงเยอะ ลอง build push และ deploy แอพพลิเคชัน Node.js ด้วย Skaffold ดูซิว่ามันเจ๋งแค่ไหนแล้วค่อยกลับมาสรุปกันอีกครั้ง

build/push/deploy แอพพลิเคชัน Node.js ด้วย Skaffold

ก่อนที่จะทดลองใช้งาน Skaffold ขอให้มั่นใจก่อนว่าเพื่อน ๆ ได้ทำการติดตั้ง Docker Minikube และ kubectl เป็นที่เรียบร้อยแล้ว หลังจากนั้นจึงทำการติดตั้ง Skaffold ในลำดับถัดไป สำหรับระบบปฏิบัติการ MacOS ดำเนินการติดตั้งผ่านคำสั่งนี้

Code
1curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-darwin-amd64 && chmod +x skaffold && sudo mv skaffold /usr/local/bin

เมื่อทุกอย่างพร้อม เราก็พร้อมไปต่อกับการสร้างเซิฟเวอร์อย่างง่าย ๆ ด้วย express.js ผ่าน package.json ดังนี้

Code
1{
2 "name": "skaffold",
3 "version": "1.0.0",
4 "description": "",
5 "main": "index.js",
6 "scripts": {
7 "start": "node index.js"
8 },
9 "author": "",
10 "license": "ISC",
11 "dependencies": {
12 "express": "^4.16.3"
13 }
14}

และนี่คือโค้ดหลักของโปรแกรมเราใต้ index.js

Code
1const express = require('express')
2const app = express()
3
4app.get('/', function(req, res) {
5 res.send('Babel Coder!')
6})
7
8app.listen(3000, err => {
9 if (err) throw err
10
11 console.log('server is listening');
12})

แอพพลิเคชันพร้อม! ต่อไปก็เตรียมความพร้อมสำหรับการสร้าง Docker Image ไว้ใช้กับคลัสเตอร์ผ่านการนิยามด้วย Dockerfile

Code
1FROM node:8.10.0-alpine
2
3WORKDIR /usr/src/app
4
5COPY package.json .
6COPY package-lock.json .
7RUN npm install
8
9COPY . .
10
11EXPOSE 3000
12
13CMD npm start

โดยปกติเมื่อถึงขั้นตอนนี้เราต้องทำการ build และแปะ tag เพื่อสร้าง Docker Image สำหรับนำไปใช้ต่อ แต่ตอนนี้เราฉลาดขึ้นแล้วผลักภาระไปให้ Skaffold ทำเองเถอะแม่หญิง

ถัดไปจึงสร้าง deployment และ service ผ่านไฟล์ k8s.yml ดังนี้ 2

Code
1apiVersion: extensions/v1beta1
2kind: Deployment
3metadata:
4 name: node-app
5spec:
6 replicas: 1
7 template:
8 metadata:
9 labels:
10 app: node-app
11 spec:
12 containers:
13 - name: node-app
14 image: IMAGE_NAME
15 ports:
16 - containerPort: 3000
17---
18apiVersion: v1
19kind: Service
20metadata:
21 name: node-app
22 labels:
23 app: node-app
24spec:
25 selector:
26 app: node-app
27 ports:
28 - port: 3000
29 protocol: TCP
30 nodePort: 30003
31 type: LoadBalancer

โปรดสังเกตว่าเราจะไม่ระบุ Docker image ลงไปในส่วนของ containers นั่นเพราะถ้าเราทำมือเองเมื่อ tag เปลี่ยนเราก็ต้องเปลี่ยนส่วนนี้เองตลอด แต่เราจะระบุเป็น IMAGE_NAME แทนเพื่อให้ Skaffold จัดการสร้าง image พร้อม tag ใหม่ให้กับเราทุกครั้งที่โค้ดเราเปลี่ยน และทำการแทนที่ IMAGE_NAME ด้วยชื่อ image นั้น

คำถาม แล้ว Skaffold รู้ได้อย่างไรว่า IMAGE_NAME ควรมีชื่อเป็นอะไร? เราจึงต้องสร้างไฟล์ชื่อ skaffold.yaml เพื่อนิยามการทำงาน ดังนี้

Code
1apiVersion: skaffold/v1alpha1
2kind: Config
3build:
4 artifacts:
5 - imageName: node-app
6 workspace: .
7 local: {}
8deploy:
9 kubectl:
10 manifests:
11 - paths:
12 - k8s.yml
13 parameters:
14 IMAGE_NAME: node-app

เมื่อโค้ดของเราเกิดการเปลี่ยนแปลง Skaffold จะทำการ build image ใหม่ทุกครั้งด้วยชื่อที่เราระบุใน IMAGE_NAME พร้อมแปะ tag ให้กับเราอัตโนมัติ ส่วนโปรแกรมเมอร์นั้นหรือ จิบกาแฟไปซิ~

เอาหละตอนนี้ก็ถึงเวลาทดลองกันแล้ว เมื่อเป็นการพัฒนาบนเครื่องจงอย่าลืมที่จะปลุกชีพ Minikube ขึ้นมาก่อนด้วยคำสั่ง minikube start จากนั้นจึงสั่ง Skaffold ให้ทำการ build/push/deploy ให้กับเราด้วยคำสั่ง skaffold dev

Code
1Starting build...
2Found minikube or Docker for Desktop context, using local docker daemon.
3Sending build context to Docker daemon 1.944MB
4Step 1/8 : FROM node:8.10.0-alpine
5 ---> adc4b0f5bc53
6Step 2/8 : WORKDIR /usr/src/app
7 ---> Using cache
8 ---> 484a011055f1
9Step 3/8 : COPY package.json .
10 ---> 48c848a33ca8
11Step 4/8 : COPY package-lock.json .
12 ---> b467f6dff7ee
13Step 5/8 : RUN npm install
14 ---> Running in 9da273042e8f
15added 49 packages in 1.165s
16 ---> b6e8c2d170b4
17Step 6/8 : COPY . .
18 ---> 2d977d626ad8
19Step 7/8 : EXPOSE 3000
20 ---> Running in f32b155a225e
21 ---> 44b426f66723
22Step 8/8 : CMD npm start
23 ---> Running in 521fc2ea13ca
24 ---> 68e3745c9338
25Successfully built 68e3745c9338
26Successfully tagged 05cd3c1f9adb2e24f0660a3eb51071ea:latest
27Successfully tagged node-app:68e3745c933892673713541009428c3f1e4e7437f6f2c33ddd7aa5f706e2bfc7
28Build complete.
29Starting deploy...
30Deploying k8s.yml...
31Deploy complete.

จะสังเกตได้ว่า Skaffold ทำการแปะ tag ให้กับเราอัตโนมัติด้วยชื่อจาก IMAGE_NAME และค่า hash ออกคำสั่งล่างนี้เพื่อดูผลลัพธ์เป็นการแสดงผล Babel Coder! ออกหน้าจอ

minikube service node-app

ยังไม่จบแค่นั้นครับ Skaffold ยังตรวจจับการเปลี่ยนแปลงโค้ดได้ด้วย เมื่อไหร่ที่โค้ดของเราเปลี่ยนมันจะทำการ build/push/deploy ให้กับเราอย่างอัตโนมัติ ลองเปลี่ยนโค้ดของ index.js จาก

JavaScript
1app.get('/', function (req, res) {
2 res.send('Babel Coder!')
3})

เป็น

JavaScript
1app.get('/', function (req, res) {
2 res.send('Babel Coder Rocks!')
3})

แล้วชะโงกหน้าดูที่ terminal ก็จะพบความจริงว่า...

Code
1Starting build...
2Found minikube or Docker for Desktop context, using local docker daemon.
3Sending build context to Docker daemon 1.944MB
4Step 1/8 : FROM node:8.10.0-alpine
5 ---> adc4b0f5bc53
6Step 2/8 : WORKDIR /usr/src/app
7 ---> Using cache
8 ---> 484a011055f1
9Step 3/8 : COPY package.json .
10 ---> Using cache
11 ---> 48c848a33ca8
12Step 4/8 : COPY package-lock.json .
13 ---> Using cache
14 ---> b467f6dff7ee
15Step 5/8 : RUN npm install
16 ---> Using cache
17 ---> b6e8c2d170b4
18Step 6/8 : COPY . .
19 ---> 41d5b84d7feb
20Step 7/8 : EXPOSE 3000
21 ---> Running in 12da84409ecb
22 ---> 8831c0755173
23Step 8/8 : CMD npm start
24 ---> Running in ebe7b8e815d0
25 ---> b4a1e92c24b7
26Successfully built b4a1e92c24b7
27Successfully tagged 8d090121e01a29734b5b997f9bc114fb:latest
28Successfully tagged node-app:b4a1e92c24b76f7507713806e02496e27e8d67236130dbf72e22246e569585ca
29Build complete.
30Starting deploy...
31Deploying k8s.yml...
32Deploy complete.

เมื่อ Skaffold ตรวจพบการเปลี่ยนแปลงจึงทำการ build/push/deploy ใหม่อีกครั้ง หากออกคำสั่ง minikube service node-app เราก็จะพบผลลัพธ์ที่เปลี่ยนไป

Skaffold ดีอย่างไร

หลังจากลองเล่น Skaffold กันไปแล้ว ถึงเวลาแล้วหละที่จะสรุปว่า Skaffold นั้นควรค่าแก่การขึ้นหิ้งอย่างไร

  • Skaffold นั้นตรวจจับการเปลี่ยนแปลงแล้วทำการ build/push/deploy ให้ใหม่ ไม่กวนเวลาจิบกาแฟยามเที่ยงและงีบหลับในเวลางานของเรา
  • จัดการ image tag ให้กับเราผ่าน sha256 จากโค้ดของเรา แต่ถ้าเป็นบน CI/CD ก็จะใช้ git commit แทน

Skaffold นั้นให้สิทธิ์ในการเลือกเครื่องมือที่เหมาะสมสำหรับเรา เช่น deploy บน Minikube บน development และใช้ GKE กับ Helm บน production

Pluggability

สรุป

Skaffold นั้นเป็นอีกหนึ่งเครื่องมือที่ช่วยให้งานพัฒนาและการส่งมอบเป็นเรื่องง่าย จากงานมือทำซ้ำซากให้เป็นงานหน่อมแน้มทำอัตโนมัติ หากเพื่อน ๆ คนไหนสนใจสามารถอ่านเพิ่มเติมได้จาก Repo ของ Skaffold ครับ

เอกสารอ้างอิง

Daniel Bryant. (2018) Google Releases “Skaffold”, a Tool That Facilitates Continuous Development with Kubernetes. Retrieved Mar, 23, 2018, https://www.infoq.com/news/2018/03/skaffold-kubernetes

Vic Iglesias. (2018) Introducing Skaffold: Easy and repeatable Kubernetes development. Retrieved Mar, 23, 2018, from https://cloudplatform.googleblog.com/2018/03/introducing-Skaffold-Easy-and-repeatable-Kubernetes-development.html

Gergely Nemeth. (2018) Using Kubernetes for Local Development. Retrieved Mar, 23, 2018, https://nemethgergely.com/using-kubernetes-for-local-development/

สารบัญ

สารบัญ

  • กระบวนการพัฒนาซอฟต์แวร์ด้วย Kubernetes
  • รู้จัก Skaffold เครื่องมือที่จะทำให้การพัฒนาเป็นเรื่องง่าย
  • build/push/deploy แอพพลิเคชัน Node.js ด้วย Skaffold
  • Skaffold ดีอย่างไร
  • สรุป
  • เอกสารอ้างอิง