มีเวลาว่าง เลยมาเขียนบทความเกี่ยวกับพื้นฐาน Kubernetes ไว้สักหน่อย
Kubernetes แบบสั้นๆ
มัน Container Orchestration System คือ ระบบจัดการ Container อัตโนมัติ ซึ่งภายใน Container จะมี Application รันอยู่โดนจะทำงานร่วมกัน
Kubernetes Cluster
ภายใน K8S Cluster จะประกอบไปด้วย ส่วนที่เป็น Worker Node และ Master Node
Worker Node
กลุ่มของ Worker Node นึกถึง Computer 1 ตัว หรือมากกว่านั้น ที่ติดตั้ง Kubernetes ไว้
โดยภายในจะประกอบด้วย 2 ส่วน คือ
- Kube Management: ที่ทำหน้าที่ในการบริหารจัดการภายใน Node นั้นๆ โดยจะมีการทำงานย่อยๆ อยู่ 3 ตัว คือ
- Kubelet (ทำหน้าที่ติดต่อกับ APIs หลัก)
- Kube Proxy (ทำหน้าที่จัดการกับ Networks)
- Docker (ทำหน้าที่ Run Container)
- Application: เป็นส่วนการทำงานของระบบที่เราต้องการติดตั้ง ซึ่งจะประกอบไปด้วย Pod หลายๆ Pod ขึ้นอยู่กับว่าเราใช้งานกับอะไร และต้องการใช้งานแบบใน ภายใน Pod ก็จะถูกรันผ่าน Container อีกทีนึง
- ใน Node สามารถมี Pod ได้มากกว่า 1 Pod
Master Node
มีหน้าที่บริหารจัดการ Worker Node โดยเราในฐานะที่เป็น Developer ก็จะทำงานผ่านการใส่คำสั่งและส่งไปที่ Master Node ให้มันทำงาน โดยใน Master Node จะมีส่วนประกอบ 3 ส่วนด้วยกัน คือ
- APIs Server: ทำหน้าที่รับคำสั่งจาก Developer และ API Server จะติดต่อกับ Kubelet เพื่อบอกว่าต้องการให้ทำอะไร
- Scheduler: ทำหน้าที่ตรวจสอบและจัดการ Worker Node เช่น ถ้าเราต้องการเพิ่ม Application ใหม่เข้าไป Scheduler จะทำการตรวจสอบว่า Pod ที่จะเพิ่มใหม่เข้าไป จะเพิ่มเข้าไปใน Worker Node ตัวไหน อาจจะไว้ที่เดียวกัน หรือแยก Node ใหม่เข้าไปก็ได้
- Controller Managements: ทำหน้าที่สอดส่องทรัพยากร เช่น ดูว่าระบบยังทำงานได้เหมือนเดิมหรือเปล่า มี Pod หายหรือไม่ หรือ Resource ไม่เพียงพอ ถ้าหากตัว Controller Manager พบ ก็จะแจ้งไปยัง Scheduler เพื่อทำการสร้าง/ลด Pod ใหม่
ลองเรียนรู้และสร้างมันไปด้วยกัน
เรามาลองสร้าง Kubernetes ไปด้วยกัน ซึ่งบทความนี้ขอข้ามเรื่องของการติดตั้ง Docker กับ Kubernetes ไปเลย
สมมติว่าเราออกแบบระบบไว้ดังนี้
kubectl
ก่อนจะเริ่มสร้าง มาเรียนรู้คำสั่งพื้นฐานของ Kubectl กันก่อน
#ดู Resource ทั้งหมด
kubectl get all
#ดู Pods ทั้งหมด
kubectl get pods
#ใช้งานไฟล์
kubectl apply -f myfile.yml
#ดู Services ทั้งหมด
kubectl get services
#ดู Log ของ Pod
kubectl logs mypod
#ลบ Pod
kubectl delete pod mypod
#ลบ Service
kubectl delete service myservice
#ลบ Pod/Service ทั้งหมด
kubectl delete pod/service --all
kubectl delete all --all
สามารถเข้าไปดูทั้งหมดได้ที่ https://kubernetes.io/docs/reference/kubectl/quick-reference
Pods
เป็นหน่วยที่เล็กที่สุดใน K8S ซึ่งภายในก็จะมี Container ประกอบอยู่ เริ่มสร้าง Pod สำหรับ Web App กัน
สร้างไฟล์ sample-pod.yml
ของเราขึ้นมา
ลอง run ไฟล์ sample-pod.yml
ในการสร้าง Pod
เมื่อสร้าง pod แล้ว ลองดูว่ามีการสร้าง pod ใหม่ขึ้นมาไหมด้วย
โดยตัว application ของเราจะ run อยู่บน pod :80 ถ้าหากเราลองเข้าไปดู http://localhost:80 ก็จะเข้าไม่ได้ เนื่องจากตัว pod นั้นเป็นระบบปิด จะต้องไปเปิดให้สามารถเข้าถึงจากภายนอกได้ก่อน
ถ้าหากเราอยากดูว่า pod ของเราทำงานได้ไหม ก็ให้ใช้คำสั่ง logs เข้าไปดูก่อนก็ได้
Deployment
เป็นการบริหาร pod โดยให้ระบบ Kubernetes ทำการควบคุมให้ โดยการสร้าง deployment ขึ้นมา และให้ deployment จัดการให้เรา
สร้างไฟล์ client-deployment.yml
แล้วเขียน spec ลงไป
อธิบายคำสั่ง:
- replicas: จำนวน pod ที่เราต้องการให้สร้างขึ้น ในไฟล์ตัวอย่างนี้ให้สร้าง pods ขึ้นมา 3 ตัว
- selector: สำหรับทำงานร่วมกับ service โดยภายในจะใช้
matchLabels
เพื่อ match เข้าหา pod หลายๆ ตัว - template: เป็นการกำหนด spec สำหรับ pod ที่เราต้องการสร้าง (สังเกตว่าจะเป็นตัวเดียวกับตัว
sample-pod.yml
ที่เข้าเขียนขึ้นมา)
แต่ก่อน run ไฟล์นี้ ให้ลบ pods เก่าที่เราเคยสร้างขึ้นมาก่อน
จากนั้นลอง สร้าง Pod ใหม่กัน
เท่านี้ pod ของเราก็จะถูกสร้างขึ้นแล้ว ถ้าลง get pod ดู ก็จะพบว่ามี Pod ใหม่เกิดขึ้นมา 3 ตัว
ทีนี้ลอง get all ดู
kubectl get all
ก็จะได้ผลลัพธ์ประมาณนี้
service/kubernetes
เป็น service ของ Kubernetes ที่สร้างขั้นมาเองdeployment.apps/client-deployment
เป็น deployment ที่เราสร้างขึ้นมา บอกว่า มีทั้งหมดกี่ Pod และ Available อยู่เท่าไหร่replicaset.apps/client-deployment-666d95b674
บอกถึง replicas set ที่สร้างขึ้นมา
อย่างที่บอกไว้ว่า ตัว deployment จะเป็นตัวจัดการ pod ทั้งหมดให้
หากเราลองลบ pod ออกสักตัวนึงที่ชื่อ client-deployment-666d95b674-rjjrg
จากนั้นลอง kubectl get all
มาดูใหม่ ก็จะพบว่า pods ยังมีอยู่ 3 ตัวเท่าเดิม แต่ตัว pod ที่ชื่อ client-deployment-666d95b674-rjjrg
หายไปแล้ว และมี pod ใหม่เข้ามาแทน pod/client-deployment-666d95b674-jlf8q
นี่คือความสามารถของ deployment ของ Kubernetes
Services
ใน services จะประกอบไปด้วย 4 ตัว
- Node Port: ทำหน้าที่เปิด port ให้สามารถเข้าใช้งานจากภายนอกได้ user สามารถเข้ามาใช้งาน application ของเราผ่าน port ที่เราเปิดไว้
- Cluster IP: จะคล้ายๆ node port แต่จะเปิดให้ใช้ภายใน Kubernetes Clusters ของเราเท่านั้น จะไม่สามารถเข้าใช้งานจากภายนอกได้
- Load Balancer: มีหน้าที่ทำการ load balance ระหว่าง cluster ซึ่งส่วนนี้ตอนใช้งานจริงอาจจะต้องดูหน่อยว่า cloud service ที่เราใช้งานอยู่รองรับหรือเปล่า (ตัวขอข้ามไปก่อน ไม่เอาเข้ามาใส่ไว้ใน)
- External (Ingress): เป็นการจัดการ traffic ที่เรียกใช้จากภายนอก (เดี๋ยวค่อยลงรายละเอียดในหัวข้อ Ingress อีกทีหนึ่ง)
Node Port
ใช้สำหรับเปิด pod ที่ง่ายที่สุด เพื่อให้ผู้ใช้หรือ application อื่นๆ เข้าถึง pod ภายใน cluster ของเราได้
แต่จะสามารถเปิดได้แค่ 1 pod ต่อ 1 application เท่าไหร่
โดย node port นั้นมีคุณสมบัติคล้ายกับ Load balance
เช่น เรามีผู้ใช้งาน เข้ามาใช้งานผ่านทาง node port ของเรา มันก็จะดูว่าเปิด port นั้นไว้หรือเปล่า ถ้าเปิดไว้แล้วมันจะให้เราเข้าถึงได้ และถ้าหากเรามีหลายๆ pod ตัว node port ก็จะ route ไปหา pod ทุกๆ ตัวที่เราสร้างไว้ เป็นต้น
มาลองกันเลยดีกว่า... เริ่มจากสร้างไฟล์ client-service.yml
อธิบายคำสั่ง
selector -> app
: เป็นการอ้างอิงถึงตัว labels ของ app ที่เราตั้งชื่อไว้ในclient-deployment.yml
ซึ่งในตัวอย่างชื่อclient
type
: เป็นตัวบอกว่าเรากำลังจำใช้ service ประเภทไหน ซึ่งในตัวอย่างนี้เราจะใช้NodePort
port
: เป็น port ภายในของตัว containertargetPort
: เป็น port ภายในของ ApplicationnodePort
: เป็น port ภายนอกที่อยากให้เข้าถึง ซึ่งเราสามารถใช้งานได้ ตั้งแต่ 30000 - 32767
จากนั้นลอง run คำสั่งกันดู โดยจะใช้วิธีการสร้างไฟล์หลายๆ ไฟล์ พร้อมกัน โดยการระบบ .
แทนการอ้างอิงไฟล์แบบไฟล์เดียว
ซึ่งเมื่อใช้คำสั่งนี้แล้วจะเป็นการเรียกใช้งานไฟล์ทุกไฟล์ที่อยู่ภายใต้ directory นั้น
ลอง kubectl get all
ดู ก็จะพบว่า มีตัว service/client-service
ที่เป็น NodePort
ถูกสร้างขึ้นมาแล้ว
NodePort
ที่ถูกสร้างมานั้นจะมี Cluster IP ที่เป็น IP ที่ใช้ภายใน Node ของ Kubernetes และมี port ที่เพิ่มให้ผู้ใช้ได้เข้ามาใช้งานคือ 30001
ลองเข้าไปดูได้ผ่านทาง http://localhost:30001/
Cluster IP
ใช้สำหรับเปิดการสื่อสารระหว่าง Pod โดยการสื่อสารจะเป็นเฉพาะภายใน Node เท่านั้น โดยทั้งระบบจะมี DNS Service เป็นผู้บริหาร IP ภายในให้อัตโนมัติ
เราจะสร้างตามนี้
เราจะใช้ตัว Cluster IP ในการสื่อสารระหว่าง API กับตัว Mongo DB
สร้างไฟล์ server-deployment
เริ่มต้นที่เราจะสร้างไฟล์ server-deployment.yml
ใหม่ขึ้นมาก่อน ซึ่งไฟล์นี้จะเป็นไฟล์สำหรับ api ของเรา
จากไฟล์ได้บนถ้าหากเราต้องการสร้าง NodePort หรืออื่นๆ แทนทีจะสร้างไฟล์แยก เราสามารถรวมกันไว้ในไฟล์เดียวกันได้เลย โดยขั้นด้วย ---
ปรับไฟล์ client-deployment
ก่อนที่จะไปทำตัว mongo ผมขอปรับไฟล์ client-deployment.yml
ใหม่ก่อน โดยรวมไฟล์ service มาไว้ในไฟล์ deployment ด้วย ก็จะได้แบบนี้
เมื่อเรียบร้อยแล้วลองสร้างกัน
เมื่อลอง kubectl get all
ก็จะได้ประมาณนี้
ซึ่งถ้าลองเข้าไปที่ http://localhost:30001/ กับ http://localhost:30002/api/health ถ้าแสดง ready แสดงว่ามันสามารถทำงานได้แล้ว
แต่ถ้าเราลองเข้าไปที่ http://localhost:30002/api/todo ดูมันจะเข้าไม่ได้ เพราะว่าเรายังไม่ได้สร้างตัว database ที่เป็น mongo db
สร้างไฟล์ mongo-deployment
ต่อไปเราจะสร้าง Pod สำหรับ Mongo DB เริ่มจากสร้างไฟล์ชื่อ mongo-deployment.yml
สำหรับ mongo db ที่เป็น database นั้น เราจะใช้ pod แค่ตัวเดียวเท่านั้น replicas:1
และกำหนดประเภทของ spec -> type
เป็น ClusterIP
อีกส่วนหนึ่งที่ต่างจากไฟล์ api-deployment.yml
และ client-deployment.yml
เราจะไม่มีการกำหนด nodePort
ให้แก่ pod ของ mongo db ของเรา เพราะเราไม่อยากให้ pod นี้เข้าถึงได้จากภายนอก
เมื่อเสร็จแล้ว ก็ให้ลองสร้างไฟล์ mongo-deployment.yml
ดู
ถ้าหากเราสร้าง client กับ api ไว้แล้ว ก็สร้างแค่ mongo-deployment.yml
เท่านั้น
แต่ถ้ายังไม่ได้สร้างอะไรไว้เลยก็ใช้
ถ้าอยากดูว่าทุกอย่างถูกสร้างไว้หรือยังก็ให้ใช้ kubectl get all
เพื่อดูเหมือนเดิม แต่ผมจะไม่เอา result มาแปะไว้แล้ว เพราะมันเริ่มเยอะแล้ว เดียวจะอ่านยากเปล่าๆ
ทดสอบของเข้าไป http://localhost:30002/api/todo ตอนนี้เราจะได้ array เปล่าๆ กลับมา แสดงว่า เราสามารถสื่อสารกับ mongo db ได้แล้ว
อธิบายการทำงาน ภายใน Kubernetes จะมี DNS service ที่จะเก็บข้อมูล IP ไว้ เมื่อมีการเปลี่ยนแปลง IP ภายใน Cluster ก็จะทำการอัพเดท IP อัตโนมัติ
ในการเข้าถึงได้ผ่านชื่อของ cluster ที่ตั้งไว้ได้เลย เช่น mongo-service
, api-service
โดยที่ DNS ทำการบริหาร IP ให้อัตโนมัติ
สร้าง Cluster IP สำหรับ Client กับ API
ตอนนี้เรามี Cluster IP ที่ไว้สำหรับสื่อสารกันระหว่าง mongo กับ api แล้ว แต่เรายังไม่มี Cluster IP ที่ไว้สำหรับสื่อสารระหว่าง API กับ Client
ดังนั้นเราจะเข้าไปปรับในไฟล์ server-deployment.yml
โดยการเพิ่ม Cluster IP เข้าไปแทน
เมื่อเสร็จแล้ว ก็จะได้แบบนี้
ลองสร้างดู
เมื่อทุกอย่าง run ขึ้นมาพร้อมแล้ว เราก็จะสามารถ เพิ่มลด todo ได้ผ่าน http://localhost:30001 และสามารถเรียกดูข้อมูลผ่าน API ได้เหมือนกัน http://localhost:30002
Ingress
ใช้สำหรับกระจาย traffic ไปยัง Service ต่างๆ โดยขึ้นอยู่กับ routing rules (ต้นทาง) ที่กำหนด โดยมันจะทำหน้าที่คล้ายๆ กับ Application Load Balancer
เช่น เรามี xxx.com
ที่จะมี route path ต่างๆ ไว้ ดังนี้
- ถ้าเข้าผ่าน
xxx.com/api
ให้วิ่งไปที่API Service
- ถ้าเข้าผ่าน
xxx.com/image
ให้วิ่งไปที่Storage Service
- ถ้าเข้าผ่าน
xxx.com
ให้วิ่งไปที่Web Service
กลับมาที่ Pod ที่เรามีอยู่ตอนนี้
ในขั้นต่อไปเราจะใช้ Ingress โดยที่เอาจะเอา NodePort
ออกจาก server-deployment.yml
และเปลี่ยนไปเป็น Cluster IP อีกตัวหนึ่ง
จากนั้นก็จะไปสร้าง Ingress Controller เข้าไป เมื่อมีการเรียก http://localhost:30001 ก็จะไปยังเว็บ application ของเรา
ถ้าเข้าผ่าน http://localhost:30001/api ก็จะเข้าผ่าน API Service แทน ตามภาพเลย
เรามาเริ่มกันเลย....
ลบ NodePort
เริ่มต้นด้วยการลบ NodePort ของ server-service-nodeport ในไฟล์ server-deployment.yml
ออกไปก่อน เพราะเราจะไม่ใช้มันแล้ว
ก็จะได้แบบนี้
ติดตั้ง Ingress Nginx Controller
ก่อนที่เราจะไปสร้าง Ingress เราต้องติดตั้งตัว Ingress Service ขึ้นมาก่อน โดยในบทความนี้ เราจะใช้ Ingress-Nginx Controller
จากนั้นก็เลือกติดตั้งตามเครื่องมือที่เราใช้ได้เลย เช่น Docker Desktop, Minikube, AWS, GCE เป็นต้น
จริงๆ เวลาเราใช้งาน Kubernetes เราจะใช้ Helm เข้ามาช่วย แต่ในบทความนี้ เราใช้ Kubectl กันไปก่อนนะ
เมื่อกด run เสร็จแล้ว ลองเข้าไปที่ http://localhost มันจะแสดงหน้า nginx ขึ้นมาแล้ว
สร้าง Ingress
สร้างไฟล์ ingress.yml
จากนั้นให้ใส่คำสั่งดังนี้
kubernetes.io/ingress.class
ตรงนี้เป็นตัวบอกว่า เวลาที่เราต้องการใช้ ingress จะให้ไปใช้ engine ตัวไหนมา run ซึ่งในตัวอย่างจะใช้ nginxnginx.ingress.kubernetes.io/use-regex
: บอกว่าเราจะใช้ regular expression ในการ match path จากต้นทาง
ลอง run ดู
ถ้าเราลอง get all ดู เราจะไม่เจอ ingress แสดงไว้ในนั้น อย่าพึ่งตกใจไป ให้ลองเข้า http://localhost/ ถ้าทำถูกต้องเราก็จะเข้าเว็บ todo ที่เราสร้างไว้
ทีนี้เราลองเข้าผ่าน http://localhost/api/todo ก็จะได้ ข้อมูลกลับมาเช่นเดียวกัน
Storage (Persistent Volume)
ตอนนี้เรามี mongo db อยู่ใน pod แล้ว ภายใน pod จะมี volume อยู่ในตัวของมันเองด้วย ซึ่งในที่นี้จะเก็บไว้นใน data/db
จากที่เราลองสร้างและลบ เราจะเห็นว่าตัวข้อมูลมันหายไปด้วย เมื่อเราลบ pod นั้นทิ้ง ซึ่งในการใช้งานจริงๆ เวลาที่ pod หายไป ข้อมูลก็จะหายไปด้วยเช่นกัน
ดังนั้นเราจำเป็นต้องเปลี่ยนที่เก็บข้อมูลใหม่ เพื่อให้ข้อมูลที่เราต้องการเก็บไว้ยังอยู่
มาออกแบบกันใหม่ โดยการเพิ่ม Persistent Volume Claim (PVC) เข้าไป
สร้าง pvc
เริ่มที่เราจะสร้างไฟล์ mongo-pvc.yml
ขึ้นมา
accessModes
: กำหนดสิทธิ์ในการเข้าถึง PVC ซึ่งในตัวอย่างนี้จะกำหนดให้ใครเข้าไปอ่านก็ได้ReadWriteMany
สามารถเข้าดูเพิ่มเติมได้ที่ https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modesrequests
: เป็นการบอกว่าต้องการ resource เท่าไหร่
เสร็จแล้วก็สร้าง PVC ของเรา
ถ้าอยากเข้าไปดูให้ใช้คำสั่ง
เราก็จะเห็น PVC ทั้งหมดที่สร้างขึ้นมา
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
mongo-pvc Bound pvc-839a0c1c-03c4-4a20-ac9b-7d6119fafb3e 1Gi RWX hostpath <unset> 50s
เชื่อมต่อ PVC กับ Containers
ไปที่ไฟล์ mongo-development.yml
จากนั้น ไปเพิ่ม volumeMounts
เข้ากับตัวไหน แต่เราจะไม่สามารถต่อเข้า PVC ได้โดยตรง จำเป็นต้องระบุ volume กันก่อน
ตั้งชื่อให้กับ volume ใน mongo โดยเราจะตั้งชื่อว่า mongo-storage
และระบุ mountPath เป็น /data/db
จากนั้น เพิ่มข้อมูล volumes
เข้าไป โดย name เป็นตัวเดียวกับที่พึ่งตั้งชื่อไป (mongo-storage
)
เพิ่ม PersistentVolumeClaim
เข้าไปที่ volumes
เพื่อต่อเข้าไปหา PVC ที่เราได้สร้างไว้
ลอง run ดู
เมื่อ run ผ่านแล้ว เท่านี้เราก็สามารถเก็บข้อมูลโดยที่ไม่หายได้แล้ว
หากต้องการลด PVC ทิ้งทั้งหมด ให้ใช้คำสั่งนี้
เรื่องพื้นๆ ของ Kubernetes จบไว้เท่านี้ก่อน ไว้มีเวลาจะมาเขียนเพิ่มเติมต่อไป
หรือถ้าอยากลองเล่น ผมมี repo ของ Kubernetes อยู่ ลองไป clone แล้วลองเล่นกันได้