Natcha Luang - Aroonchai

Trust me I'm Petdo

Bangkok, Thailand

[Kubernetes] Deploy Docker Container บน Google Container Engine

ช่วงนี้ผมกำลังทดลองใช้งาน Google Container Engine อยู่ ซึ่งเป็นบริการที่ให้เราสามารถสร้าง group ของ Google Compute Engine แล้วเอามา join กันเป็น cluster สำหรับใช้รันงานบนระบบ container ได้

บริการ Google Container Engine จะมีเครื่องมือสำหรับจัดการ container มาด้วยชื่อว่า Kubernetes สามารถช่วยจัดการเรื่อง start/stop service รวมไปถึงการทำ replicate หรือทำ scale ของระบบได้อย่างง่ายดาย

ซึ่งหลังจากใช้งานมาได้สักระยะก็คิดว่าน่าจะเขียนบทความเกี่ยวกับวิธีการเริ่มใช้งาน ปัญหาที่พบเจอระหว่างการติดตั้ง/ใช้งาน ลองมาดูขั้นตอนกันดีกว่าครับว่า ถ้าเราต้องการ deploy ระบบง่าย ๆ สักอันจะต้องทำอะไรบ้าง

สารบัญ

รู้จักกับ Kubernetes

Kubernetes เป็นเครื่องมือช่วยบริหารจัดการ container ที่ทำงานอยู่บน cloud ซึ่งเป็นเครื่องมือที่ Google พัฒนาขึ้นมาและเปิดเป็น open source เป้าหมายคือผลักดันให้เป็นมาตรฐานของวงการ container ที่ช่วยในการจัดการ container ที่ตอนนี้มีหลากหลายมาตรฐานเหลือเกิน

Kubernetes ช่วยในเรื่องของการ build, deploy และ scale ทำให้เรื่องยาก ๆ อย่างการสร้าง replica เป็นเรื่องง่ายดายมากขึ้น และยิ่งง่ายเข้าไปอีกเมื่อทำงานร่วมกันกับ Google Container Engine

การติดตั้ง Kubernetes สำหรับใช้จัดการ container บน Google Container Engine นั้นง่ายมาก ก่อนอื่นต้องดาวน์โหลด Google Cloud SDK มาติดตั้งก่อน ที่นี่ เลือกติดตั้งตามระบบปฏิบัติการ

เมื่อติดตั้งเสร็จแล้วให้ทดลองเรียกใช้งานคำสั่งด้านล่างเพื่อติดตั้ง kubectl ลงในเครื่อง

$ gcloud components install kubectl

รอจนการติดตั้งเสร็จเรียบร้อยให้ทดลองเรียกใช้คำสั่ง kubectl version จะได้ผลลัพธ์ออกมาประมาณนี้

Client Version: version.Info{Major:"1", Minor:"1", GitVersion:"v1.1.7", GitCommit:"e4e6878293a339e4087dae684647c9e53f1cf9f0", GitTreeState:"clean"}

สร้าง Container cluster

หลังจากติดตั้ง gcloud และ kubectl กันเรียบร้อยแล้ว ขั้นตอนต่อไปเราจะต้องสร้าง container cluster กัน โดยสามารถทำได้ 2 วิธีคือที่หน้า console.cloud.google.com และด้วยคำสั่ง gcloud

ซึ่งจากตรงนี้ผมคิดว่าทุกคนคงมี project อยู่บน Google Cloud กันอยู่แล้ว จากนี้เราจะใช้ project-id เป็นตัวกำหนดการทำงานทุก ๆ อย่างดังนั้นถ้าใครที่ยังไม่มีหรือยังไม่ได้สร้าง project สามารถดูวิธีการได้จากบทความก่อน ๆ

[Hugo] รู้จักกับ Hugo เครื่องมือสำหรับสร้าง static website

การที่จะ create/delete Container Engine ได้เราต้องเปิดใช้งาน Container Engine API ก่อน สามารถทำตามคู่มือได้จากลิงค์นี้ Before You Begin

เลือกทำขั้นตอนใดอันใดอันนึงเท่านั้นน่ะครับ

ผ่านทางคำสั่ง gcloud

ขั้นตอนการสร้างด้วยคำสั่ง gcloud สามารถทำได้ดังนี้

$ gcloud container cluster create NAME --zone ZONE

สิ่งที่ต้องเปลี่ยนคือ NAME และ ZONE โดยที่ NAME คือชื่อของ cluster ที่เราต้องการสร้างโดยต้องเป็นภาษาอังกฤษตัวพิมพ์เล็กทั้งหมดและสามารถใช้ - และตัวเลขผสมได้

ส่วน ZONE เป็นที่อยู่ของ cluster ที่เราสร้าง (ซึ่ง cluster นี้ก็เป็น group ของ Compute Engine instances นั่นเอง) ซึ่งมีรายละเอียดอยู่ ที่นี่

ซึ่งผมจะสร้าง nodes ทั้งหมด 3 nodes และเลือกประเภทของเครื่องเป็น f1-micro ที่ค่าใช้จ่ายถูกที่สุด ดังนั้นคำสั่งในการสร้างทั้งหมดจะได้ดังนี้ โดยผมตั้งชื่อว่า golang-hello-world และตั้งอยู่ที่ us-central1-a

$ gcloud container cluster create golang-hello-world \
  --zone us-central1-a \
  --machine-type f1-micro \
  --num-nodes 3

ส่วนคำสั่งเพิ่มเติมที่เหลือสามารถดูได้ ที่นี่

ผ่านทางหน้าเว็บ console.cloud.google.com

วิธีนี้จะง่ายกว่าขั้นตอนแรกโดยเข้าไปที่ https://console.cloud.google.com จากนั้นเลือกที่ project ที่เราต้องการจะสร้าง cluster จากนั้นคลิกที่เมนู Container Cluster จะเจอกับหน้าจัดการ Container Engine

Google Cloud Console

คลิกที่ปุ่ม Create a container cluster จากนั้นตั้งค่าตามที่ต้องการ ผมเลือกให้เหมือนกันกับที่ตั้งค่าไว้ตอนใช้คำสั่ง gcloud

Create a container cluster

สร้าง image และทดสอบการทำงาน

หลังจากที่เราสร้าง cluster กันเสร็จเรียบร้อยแล้ว ขั้นตอนต่อมาคือการสร้าง Docker image โดยในตัวอย่างนี้ผมจะใช้ Golang สร้าง HTTP server จากนั้นให้ response คำว่า "Hello, world!"

Dockerfile

FROM golang:1.6-alpine

RUN mkdir -p $GOPATH/src/golang/helloworld
ADD . $GOPATH/src/golang/helloworld

WORKDIR $GOPATH/src/golang/helloworld
RUN go build -o $GOPATH/bin/helloworld .

EXPOSE 80

CMD ["helloworld"]

main.go

package main

import (
    "net/http"
    "fmt"
)

func main() {
    http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(rw, "Hello, world!")
    })

    http.ListenAndServe(":80", nil)
}

จัดการสร้างไฟล์ Dockerfile และ main.go จากนั้นเอาใส่ไว้ใน directory เดียวกัน เสร็จแล้วรันคำสั่ง

$ docker build -t gcr.io/PROJECT_ID/golang-hello-world:latest .

โดยให้เปลี่ยน PROJECT_ID เป็น project ID ที่เราจะใช้ deploy และถ้าใครที่สร้าง cluster ไว้ที่อื่นนอกเหนือจาก US ต้องใส่ sub domain ตรง gcr.io เช่น ถ้าสร้างไว้ที่ Asia ต้องใช่ domain เป็น asia.gcr.io เป็นต้น

ถ้าการ build เรียบร้อย เราจะได้ผลลัพธ์แบบนี้ถ้าเรียกดูรายการ images ทั้งหมด

$ docker images
REPOSITORY                              TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
gcr.io/PROJECT_ID/golang-hello-world   latest              74450e4abf55        2 minutes ago       245.6 MB

สามารถทดลองรันดูได้ด้วยคำสั่ง

$ docker run -d -p 8080:80 gcr.io/PROJECT_ID/golang-hello-world:latest

เข้าไปที่ http://192.168.99.100:8080 จะเจอกับข้อความ "Hello world!" ตรง IP นี้จะเป็นของเครื่องใครเครื่องมันถ้าไม่รู้ IP ก็สามารถใช้โปรแกรม Kitemetic เพื่อดูผลลัพธ์ได้เช่นกันครับ

ถ้าอยากรู้จัก Docker ให้มากกว่านี้แนะนำบทความเดิมที่เคยเขียนไปแล้วครับ

[Docker] รู้จักกับ Docker และวิธีใช้งานเบื้องต้น

Push ขึ้น Google Container Registry

จากนั้นเราจะต้อง push image ขึ้นไปยัง Google Container Registry (GCR) ก่อนจึงจะสามารถ deploy ได้ ซึ่งขั้นตอนนี้เราจะต้องระวังนิดนึงเพราะว่าเวลาเรา push image มันจะส่งไปยัง region เดียวกันกับ sub domain ของ gcr.io ตามที่เราตั้ง ซึ่งถ้าเกิดว่าเราสร้าง cluster ไว้คนละที่กับ GCR อาจจะต้องเสียเงินค่าแบนวิธเพิ่มขึ้นได้น่ะครับ

ให้ใช้คำสั่งต่อไปนี้เพื่อ push image ขึ้น GCR

$ gcloud docker push gcr.io/PROJECT_ID/golang-hello-world:latest

รอจนเสร็จเรียบร้อย เราจะสามารถเรียกดูรายการ images ทั้งหมดที่เคย push ขึ้นไปแล้วได้จากเมนู Container Registry ข้างล่างเมนู​ Container clusters

Deploy pod

หลังจากที่เรา push image ขึ้น GCR เป็นที่เรียบร้อยแล้ว ขั้นตอนถัดไปคือการสร้าง pod จาก image ที่อยู่ใน GCR ด้วยคำสั่ง

$ kubectl run golang-hello-world --image=gcr.io/PROJECT_ID/golang-hello-world:latest --port=80
deployment "golang-hello-world" created

จากคำสั่งจะเป็นการสั่งให้ Kubernetes สร้าง application ใน Replication Controller (RC) โดย pull source image จาก gcr.io ซึ่งเจ้า RC จะทำหน้าที่ส่งคำสั่งนี้ไปรันใน worker cluster ของเราอีกทีหนึ่ง ซึ่ง container ที่รับหน้าที่รัน image นี้เราจะเรียกว่า Pod ครับ

ถ้าทุกอย่างเรียบร้อยดีเราจะสามารถดูรายการ Pod ได้ด้วยคำสั่ง

$ kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
golang-hello-world-29397364-3jzrz   1/1       Running   0          1m

และสามารถดูรายละเอียดของ Pod ได้ด้วยคำสั่ง

$ kubectl describe pod golang-hello-world-29397364-3jzrz
Name:        golang-hello-world-29397364-3jzrz
Namespace:    default
Node:        172.17.4.203/172.17.4.203
Start Time:    Tue, 12 Apr 2016 08:41:55 +0700
Labels:        pod-template-hash=29397364,run=golang-hello-world
Status:        Running
IP:        10.2.53.2
Controllers:    ReplicaSet/golang-hello-world-29397364
Containers:
  golang-hello-world:
    Container ID:    docker://2a446d90244ab958a8ce478741b1a8316c17465f6a9d432fb5c1709f45b2fd2f
    Image:        gcr.io/PROJECT_ID/golang-hello-world:latest
    Image ID:        docker://sha256:0da714848a6f3e14cba0b28ebb31a82df2c9ae1267cd4a839e53f867903fa743
    Port:        80/TCP
    QoS Tier:
      cpu:        BestEffort
      memory:        BestEffort
    State:        Running
      Started:        Tue, 12 Apr 2016 08:42:09 +0700
    Ready:        True
    Restart Count:    0
    Environment Variables:
Conditions:
  Type        Status
  Ready     True
Volumes:
  default-token-h7kbd:
    Type:    Secret (a volume populated by a Secret)
    SecretName:    default-token-h7kbd
Events:
  FirstSeen    LastSeen    Count    From            SubobjectPath                Type        Reason        Message
  ---------    --------    -----    ----            -------------                --------    ------        -------
  44s        44s        1    {default-scheduler }                        Normal        Scheduled    Successfully assigned golang-hello-world-29397364-3jzrz to 172.17.4.203
  44s        44s        1    {kubelet 172.17.4.203}    spec.containers{golang-hello-world}    Normal        Pulling        pulling image "gcr.io/PROJECT_ID/golang-hello-world:latest"
  30s        30s        1    {kubelet 172.17.4.203}    spec.containers{golang-hello-world}    Normal        Pulled        Successfully pulled image "gcr.io/PROJECT_ID/golang-hello-world:latest"
  30s        30s        1    {kubelet 172.17.4.203}    spec.containers{golang-hello-world}    Normal        Created        Created container with docker id 2a446d90244a
  30s        30s        1    {kubelet 172.17.4.203}    spec.containers{golang-hello-world}    Normal        Started        Started container with docker id 2a446d90244a

ตอนนี้ Pod รันแล้วก็จริงแต่เราจะยังไม่สามารถใช้งานได้ เนื่องจาก container ที่รันอยู่ใน cluster จะมีเพียง internal IP เราจำเป็นต้องใส่ค่า external IP ให้เพื่อให้สามารถเรียกใช้งานจากภายนอกได้

ก่อนอื่นเรียกดูรายการ deployments

$ kubectl get deployments
NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
golang-hello-world   1         1         1            1           3m

จากนั้นสั่ง expose deployment โดยที่ --type จะต้องเป็น "LoadBalancer"

$ kubectl expose deployment golang-hello-world --type="LoadBalancer"
service "golang-hello-world" exposed

จากนั้นรอสักพักกว่าระบบจะ setup external IP ให้ สามารถใช้คำสั่งเรียกดูรายการ services ได้แบบนี้

$ kubectl get services golang-hello-world
NAME                 CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
golang-hello-world   10.3.0.221                 80/TCP    23s

ซึ่งถ้าการ setup เสร็จแล้วจะมี IP ตรง external แสดง

ทำ replicate Pod

หลังจากที่เราสามารถสร้าง Pod ได้แล้ว ขั้นตอนต่อมาเราจะทดลองทำ replica กัน โดยใช้คำสั่งนี้

$ kubectl scale deployment golang-hello-world --replicas=3
deployment "golang-hello-world" scaled

เราจะได้ replica ของ deployment เราทั้งหมด 3 อันรันอยู่ใน cluster แบบกระจายกัน สามารถเรียกดูรายการ replicas ได้ด้วยคำสั่ง

$ kubectl get deployment
NAME                 DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
golang-hello-world   3         3         3            3           6m

หรือต้องการดูรายการ Pod ที่สร้างขึ้นให้ใช้คำสั่ง

$ kubectl get pods
NAME                                READY     STATUS    RESTARTS   AGE
golang-hello-world-29397364-3jzrz   1/1       Running   0          7m
golang-hello-world-29397364-65eij   1/1       Running   0          1m
golang-hello-world-29397364-c3cbv   1/1       Running   0          1m

การทำ replica เป็นความสามารถหลักของ Kubernetes ที่ถูกออกแบบมาให้ง่ายต่อการทำ scale ซึ่งจากตัวอย่างผมใช้เพียงแค่คำสั่ง kubectl scale ระบบก็ทำงานให้ได้โดยอัตโนมัติ​ ซึ่งถ้าสมมติมี Pod ตัวใดตัวหนึ่งล่มไปมันจะสร้างตัวใหม่ให้ทันที

ผมจะทดลองลบ Pod ออกหนึ่งอันแล้วมาดูผลลัพธ์กันครับ

$ kubectl delete pod golang-hello-world-29397364-3jzrz
pod "golang-hello-world-29397364-3jzrz" deleted

จากนั้นลองเรียกดูรายการ Pod ทั้งหมด

$ kubectl get pods
NAME                                READY     STATUS        RESTARTS   AGE
golang-hello-world-29397364-2slgx   1/1       Running       0          29s
golang-hello-world-29397364-3jzrz   1/1       Terminating   0          10m
golang-hello-world-29397364-65eij   1/1       Running       0          3m
golang-hello-world-29397364-donzz   0/1       Pending       0          2s

จะเห็นว่าทันทีที่มีการลบ Pod เกิดขึ้นระบบจะตรวจสอบจำนวน replica ที่เหลืออยู่ทั้งหมดก่อนจะสร้างตัวใหม่ขึ้นมาทันทีถ้าไม่ตรงกับจำนวนที่กำหนดไว้

เสร็จสิ้นการทดลอง

สุดท้ายแล้วหลังจากที่ทดลองจนเป็นที่พอใจก็มาถึงส่วนของการลบ cluster ทิ้ง ก่อนอื่นต้องลบ service, deployment ทั้งหมดที่เราได้สร้างเอาไว้ด้วยคำสั่ง

$ kubectl delete service,deployment golang-hello-world
service "golang-hello-world" deleted
deployment "golang-hello-world" deleted

หลังจากลบเสร็จสิ้นแล้วถ้าเราสั่งเรียกรายการ RC ดูจะเจอกับ error แบบนี้

$ kubectl get rc/golang-hello-world
Error from server: replicationcontrollers "golang-hello-world" not found

จากนั้นเราจะลบ cluster ที่สร้างไว้บน Google Container Engine กัน

$ gcloud container clusters delete golang-hello-world
Waiting for cluster deletion...done.
name: operation-xxxxxxxxxxxxxxxx
operationType: deleteCluster
status: done
target: /projects/kubernetes-codelab/zones/us-central1-a/clusters/golang-hello-world
zone: us-central1-a

จากนั้นเราต้องลบไฟล์ที่อัพโหลดขึ้น Google Container Registry ด้วย (มันจะอัพไปไว้ใน Bucket ของ Cloud Storage เราครับ)

เรียกดูรายการ image ด้วยคำสั่ง

$ gsutil ls
gs://artifacts.PROJECT_ID.appspot.com/

จากนั้นลบรายการอิมเมจที่เราต้องการ

$ gsutil rm -r gs://artifacts.PROJECT_ID.appspot.com/
Removing gs://artifacts.PROJECT_ID.appspot.com/...

Error ที่เจอและแนวทางแก้ไข

นี้เป็น error ที่ผมเจอระหว่างการใช้งานซึ่งถ้ามีอัพเดทก็จะรวบรวมคำตอบเอาไว้ตรงนี้ครับ

error: couldn't read version from server: Get http://localhost:8080/api: dial tcp 127.0.0.1:8080: connection refused

อันนี้เกิดจากเรายังไม่ได้ตั้งค่า config ของ Kubernetes ทำให้เวลาเรียก API เซิฟเวอร์มันจะเรียกไปที่ default คือ http://localhost:8080/api ซึ่งถ้าเราไม่ได้รัน API ใน localhost ก็จะเจอกับ error แบบนี้

วิธีแก้ไขคือให้ตั้งค่า config ของ Kubernetes โดยให้ชี้ไปที่ Google Container Engine

ก่อนอื่นให้ตั้งค่า project ที่เราจะทำงานก่อน

$ gcloud config set project PROJECT_ID

จากนั้นตั้งค่า compute zone ที่เราสร้าง cluster ไว้

$ gcloud config set compute/zone ZONE

จากนั้นตั้งค่า default cluster

$ gcloud config set container/cluster CLUSTER_NAME

สุดท้ายดึงข้อมูล clusters credentials ด้วยคำสั่ง

$ gcloud container clusters get-credentials CLUSTER_NAME

ทีนี้เราก็จะได้ config สำหรับเชื่อมต่อกับ Google Container Engine เราแล้วครับ


ที่มา

Comments