อัปเดต: 1 ตุลาคม 2026 · ตัวอย่างคำสั่งใช้ Bash บน macOS, Linux หรือ Windows ผ่าน WSL2

บทความนี้อธิบายแนวคิดและให้ตัวอย่างสำหรับทดลองทำตาม คำสั่งสร้างคลัสเตอร์และ Load Balancer จะสร้างทรัพยากรที่มีค่าใช้จ่าย ตรวจไวยากรณ์ JavaScript/Bash, โครงสร้าง YAML และการตอบ HTTP ของแอปบนเครื่องแล้ว ส่วนการ Build ด้วย Docker และ Deploy บน DigitalOcean ยังไม่ได้รันจริง

info-docker-docr-doks-container-digitalocean-image-deploy

เมื่อพัฒนาแอปพลิเคชันด้วย Container เราต้องจัดการทั้งการสร้างแพ็กเกจ การเก็บแพ็กเกจ และการนำแพ็กเกจไปรันบนระบบจริง บน DigitalOcean สามารถเชื่อมงานเหล่านี้ด้วย Docker, DigitalOcean Container Registry (DOCR) และ DigitalOcean Kubernetes (DOKS)

Docker ช่วยสร้าง Container Image, DOCR เก็บและแจกจ่าย Image ส่วนตัว ส่วน DOKS เป็นบริการ Kubernetes ที่นำ Image มารันและจัดการแอปพลิเคชันตามการตั้งค่าของเรา ทั้งสามส่วนทำงานร่วมกันได้ แต่ยังต้องกำหนดสิทธิ์ดึง Image, ทรัพยากร, เครือข่าย และวิธีเผยแพร่บริการให้ถูกต้อง [1][2]

#1. เข้าใจหน้าที่ของแต่ละส่วน

ส่วนประกอบ หน้าที่หลัก สิ่งที่นักพัฒนากำหนด ตัวอย่าง
Docker สร้าง Image และรัน Container เพื่อพัฒนา/ทดสอบ Dockerfile, Base Image, ไฟล์แอป, สถาปัตยกรรม CPU สร้าง Image ของ REST API
DOCR เก็บและแจกจ่าย Image ใน Private Registry ชื่อ Registry, Repository, Tag, สิทธิ์เข้าถึง registry.digitalocean.com/my-registry/demo-api:v1
DOKS ให้บริการ Kubernetes ที่ DigitalOcean จัดการ Control Plane Node Pool, Deployment, Service, จำนวน Pod, Resource Limits รัน API สอง Pod และทำ Rolling Update

Image คือแพ็กเกจที่ใช้สร้าง Container ส่วน Container คืออินสแตนซ์ที่กำลังรันจาก Image เดียวกัน Image หนึ่งตัวจึงใช้สร้าง Container ได้หลายตัว

Registry เป็นบริการเก็บ Image ขณะที่ Repository เป็นกลุ่ม Image ของแอปหนึ่ง เช่น demo-api ภายใน Registry เดียวกัน และ Tag เป็นชื่ออ้างอิงเวอร์ชัน เช่น v1, v2 หรือ Git commit SHA

ชื่อ Image แบบเต็มมีรูปแบบดังนี้:

registry.digitalocean.com/<registry-name>/<repository>:<tag>

ตัวอย่าง:

registry.digitalocean.com/se-container-lab/demo-api:v1

Docker เป็นเครื่องมือที่ใช้ในเวิร์กช็อปนี้ เราสามารถสร้าง Image ที่เข้ากันได้ด้วยเครื่องมืออื่นเช่นกัน และ Kubernetes ไม่จำเป็นต้องใช้ Docker Engine เป็น Container Runtime เพราะรองรับ Runtime ผ่าน CRI เช่น containerd หรือ CRI-O [3]

#2. ภาพรวมการทำงาน

ลำดับที่นักพัฒนามักใช้งานคือ เขียนโค้ด → Build Image → Push ไป DOCR → Deploy บน DOKS → ตรวจสอบและปรับขนาด

flowchart TD
    Source["Source Code และ Dockerfile"] --> Build["Docker Build"]
    Build -->|"Push Image"| Registry["DOCR: Private Image Registry"]
    Manifest["Kubernetes Manifest"] --> Deploy
    Registry -->|"Worker Node ดึง Image"| Pods
    Users["ผู้ใช้งาน"] --> LB["DigitalOcean Load Balancer"]
    LB --> Service
    subgraph Cluster["DOKS"]
        Deploy["Deployment"] -->|"กำหนดเวอร์ชันและจำนวน"| Pods["Application Pods"]
        Service["Kubernetes Service"] -->|"ส่งต่อ Request"| Pods
    end

DOCR อยู่ในเส้นทางแจกจ่าย Image ส่วน Request ของผู้ใช้เข้าผ่าน Load Balancer และ Service ไปยัง Pod การ Push Image ใหม่เข้า Registry โดยลำพังไม่ได้เปลี่ยน Deployment ในเวิร์กช็อปนี้ เราจะอัปเดตค่า image ของ Deployment อย่างชัดเจน

#3. DOKS จัดการอะไร และเรายังต้องจัดการอะไร

DigitalOcean ดูแล Control Plane ของบริการ DOKS และให้การเชื่อมต่อกับผลิตภัณฑ์ เช่น Load Balancer และ Storage ส่วนผู้ใช้ยังต้องดูแลการตั้งค่าแอปและ Worker Node Pool [2]

DigitalOcean จัดการในบริการ ผู้ใช้กำหนดและดูแล
โครงสร้างพื้นฐานของ Managed Control Plane ขนาดและจำนวน Worker Node
กลไกบริการสำหรับสร้างและอัปเกรดคลัสเตอร์ แผนอัปเกรดและตรวจสอบความเข้ากันได้ของแอป
การเชื่อมต่อกับบริการ Cloud ที่รองรับ Manifest, สิทธิ์, Network Policy และข้อมูลลับ
บริการ Registry และการยืนยันตัวตนที่รองรับ การออกเวอร์ชัน Image, การสแกน และการเก็บเวอร์ชันย้อนหลัง

การใช้ Managed Kubernetes ช่วยลดงานดูแล Control Plane แต่คุณภาพของแอปยังขึ้นกับ Health Checks, Resource Requests/Limits, การกระจาย Pod, การสำรองข้อมูล และการเฝ้าระวังระบบ

#4. เครื่องมือและสิ่งที่ต้องเตรียม

  • บัญชี DigitalOcean ที่สามารถสร้าง Container Registry และ Kubernetes Cluster ได้
  • Docker Desktop หรือ Docker Engine พร้อม Buildx
  • doctl รุ่นปัจจุบันที่รองรับคำสั่ง registries
  • kubectl รุ่นที่เข้ากันได้กับ Kubernetes ของคลัสเตอร์
  • Terminal แบบ Bash และ curl
  • API Token ที่มีสิทธิ์เพียงพอสำหรับทรัพยากรที่ต้องสร้าง

บทความนี้ใช้คำสั่ง doctl registries แบบพหูพจน์ เพื่อระบุ Registry ที่ต้องการอย่างชัดเจน เอกสารเก่าบางชุดอาจใช้ doctl registry โดยเอกสาร CLI ปัจจุบันระบุให้ registries login รับชื่อ Registry [4][5]

ตรวจสอบเครื่องมือ:

docker version
docker buildx version
doctl version
doctl registries --help
kubectl version --client

บน macOS ที่ติดตั้ง Homebrew แล้ว สามารถติดตั้ง doctl ด้วย:

brew install doctl

บน Ubuntu ที่ใช้ Snap:

sudo snap install doctl
sudo snap connect doctl:kube-config
sudo snap connect doctl:dot-docker

การเชื่อมต่อ Snap สองรายการนี้ช่วยให้ doctl เข้าถึงไฟล์ตั้งค่า Kubernetes และ Docker ตามที่เอกสารระบุ สำหรับระบบอื่นใช้วิธีติดตั้งจากเอกสารทางการ [6]

ยืนยันตัวตนผ่าน Prompt เพื่อไม่ต้องใส่ Token ไว้ในคำสั่ง:

doctl auth init --context container-lab
doctl auth switch --context container-lab
doctl account get

หากเปิด Terminal ใหม่ ให้กำหนดตัวแปรในเวิร์กช็อปอีกครั้ง ตรวจสอบทั้งบัญชี DigitalOcean และ Kubernetes Context ก่อนสร้างหรือแก้ไขทรัพยากร

#5. สร้างแอปตัวอย่าง

เราจะสร้าง HTTP API ด้วย Node.js โดยใช้โมดูลมาตรฐาน จึงไม่มี Dependency ที่ต้องติดตั้งเพิ่มเติม แอปรองรับ:

Endpoint ผลลัพธ์
GET / ข้อความ, เวอร์ชันแอป และชื่อ Pod/Container
GET /healthz สถานะสำหรับ Health Check
เส้นทางอื่น HTTP 404
Method อื่น HTTP 405

สร้างโฟลเดอร์:

mkdir digitalocean-container-lab
cd digitalocean-container-lab
mkdir k8s

สร้างไฟล์ server.js:

const http = require('node:http');
const os = require('node:os');

const port = Number(process.env.PORT || 8080);
const version = process.env.APP_VERSION || 'development';

function json(res, status, payload) {
  res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });
  res.end(JSON.stringify(payload));
}

const server = http.createServer((req, res) => {
  if (req.method !== 'GET') {
    res.setHeader('Allow', 'GET');
    return json(res, 405, { error: 'Method not allowed' });
  }

  const pathname = new URL(req.url, 'http://localhost').pathname;

  if (pathname === '/healthz') {
    return json(res, 200, { status: 'ok' });
  }

  if (pathname === '/') {
    return json(res, 200, {
      message: 'Hello from Docker, DOCR and DOKS',
      version,
      hostname: os.hostname()
    });
  }

  return json(res, 404, { error: 'Not found' });
});

server.listen(port, '0.0.0.0', () => {
  console.log(`API listening on port ${port}, version=${version}`);
});

process.once('SIGTERM', () => {
  server.close(() => process.exit(0));
  setTimeout(() => process.exit(1), 10000).unref();
});

การ Listen ที่ 0.0.0.0 ช่วยให้ระบบภายนอก Container ติดต่อแอปได้ ส่วนการจัดการ SIGTERM ช่วยให้แอปปิดตัวหลังรับสัญญาณหยุดจาก Kubernetes

สร้าง Dockerfile:

FROM node:24-alpine

WORKDIR /app
ARG APP_VERSION=v1
ENV NODE_ENV=production
ENV PORT=8080
ENV APP_VERSION=${APP_VERSION}

COPY --chown=node:node server.js ./server.js

USER node
EXPOSE 8080
CMD ["node", "server.js"]

ตัวอย่างใช้ Official Node Image และรันด้วยผู้ใช้ node แทน root สำหรับระบบจริงควรเลือก Patch Version หรือ Digest ของ Base Image ที่ผ่านการตรวจสอบแล้ว [7]

สร้าง .dockerignore:

.git
.env
.env.*
node_modules
k8s
*.log

#6. Build และทดสอบบนเครื่อง

docker build --build-arg APP_VERSION=v1 -t demo-api:v1 .
docker run -d --name demo-api-local -p 127.0.0.1:8080:8080 demo-api:v1
curl -fsS http://localhost:8080/
curl -fsS http://localhost:8080/healthz

ผลลัพธ์จาก / ควรมีลักษณะดังนี้ โดย hostname จะแตกต่างกัน:

{
  "message": "Hello from Docker, DOCR and DOKS",
  "version": "v1",
  "hostname": "example-container"
}

ตรวจ Log และหยุด Container หลังทดสอบ:

docker logs demo-api-local
docker rm -f demo-api-local

#7. สร้าง DOCR และ Push Image

กำหนดชื่อ Registry ให้ไม่ซ้ำกับที่มีอยู่ เปลี่ยนค่า your-unique-registry ก่อนรัน:

export REGISTRY_NAME="your-unique-registry"
export APP_NAME="demo-api"
export IMAGE_TAG="v1"
export IMAGE="registry.digitalocean.com/${REGISTRY_NAME}/${APP_NAME}:${IMAGE_TAG}"

doctl registries options available-regions
doctl registries options subscription-tiers
read -r -p "Registry region slug: " DOCR_REGION
export DOCR_REGION

เลือก Region ที่มีในผลลัพธ์ หากมี Region ร่วมกับ DOKS ที่ต้องการใช้งานให้พิจารณาใช้ Region เดียวกัน

สร้าง Registry สำหรับทดลองด้วย Starter Plan:

doctl registries create "$REGISTRY_NAME" \
  --region "$DOCR_REGION" \
  --subscription-tier starter

doctl registries get "$REGISTRY_NAME"
doctl registries login "$REGISTRY_NAME" --expiry-seconds 3600

Starter มีข้อจำกัดจำนวน Repository และพื้นที่เก็บ Image หากบัญชีใช้ Registry หรือแผนอยู่แล้ว ให้ใช้ Registry เดิมตามสิทธิ์และแผนของบัญชี แล้วข้ามคำสั่งสร้างใหม่ การสร้าง Registry ใช้ชื่อที่ประกอบด้วยตัวพิมพ์เล็ก ตัวเลข และขีดกลาง [4][8]

#ตรวจสถาปัตยกรรมก่อนส่ง Image ขึ้น Cloud

Image ที่ Build บน Mac Apple Silicon อาจเป็น linux/arm64 ขณะที่ Worker Node ที่เราเลือกอาจเป็น linux/amd64 ต้องสร้าง Image ให้ตรงกับปลายทาง [9]

ตัวอย่างต่อไปนี้ตั้งใจเลือก Worker Node แบบ x86-64 ในขั้นตอนสร้างคลัสเตอร์ จึง Build เป็น linux/amd64:

docker buildx build \
  --platform linux/amd64 \
  --build-arg APP_VERSION="$IMAGE_TAG" \
  --tag "$IMAGE" \
  --push .

คำสั่งนี้ Build และ Push โดยตรง จึงไม่จำเป็นต้องใช้ docker push ซ้ำ หากใช้ Builder ที่รองรับ สามารถสร้างหลายสถาปัตยกรรมได้ด้วย --platform linux/amd64,linux/arm64 แต่ควรทำเมื่อมีความต้องการใช้งานทั้งสองแบบ

ตรวจข้อมูล Image ที่เผยแพร่แล้ว:

docker buildx imagetools inspect "$IMAGE"

การ Push สำเร็จเป็นเพียงการจัดเก็บ Image ขั้นตอนถัดไปจึงเป็นการสร้างคลัสเตอร์และสั่ง Deployment ใช้ Image นี้

#8. สร้างคลัสเตอร์ DOKS

ตรวจตัวเลือกปัจจุบันก่อนเลือก Region, Machine Size และ Kubernetes Version:

doctl kubernetes options regions
doctl kubernetes options sizes
doctl kubernetes options versions

กำหนดค่าตามผลลัพธ์ เลือก Machine Size แบบ x86-64 เพื่อให้ตรงกับ Image linux/amd64 ในขั้นตอนก่อนหน้า:

export CLUSTER_NAME="container-lab"
export APP_NAMESPACE="container-lab"

read -r -p "DOKS region slug: " DOKS_REGION
read -r -p "Worker size slug (x86-64): " NODE_SIZE
read -r -p "Kubernetes version slug: " K8S_VERSION
export DOKS_REGION NODE_SIZE K8S_VERSION

สร้างคลัสเตอร์สำหรับทดลองหนึ่ง Worker Node:

doctl kubernetes cluster create "$CLUSTER_NAME" \
  --region "$DOKS_REGION" \
  --version "$K8S_VERSION" \
  --size "$NODE_SIZE" \
  --count 1 \
  --ha=false \
  --wait

คำสั่งนี้มีค่าใช้จ่ายสำหรับ Worker Node ใช้หนึ่ง Node เพื่อศึกษาลำดับงาน และปิด HA Control Plane อย่างชัดเจนตามขอบเขตการทดลอง คลัสเตอร์นี้ยังไม่มีความทนทานต่อการเสียของ Worker Node แม้จะรันหลาย Pod [10]

เมื่อสร้างเสร็จ เชื่อมต่อและตรวจสถานะ:

doctl kubernetes cluster kubeconfig save "$CLUSTER_NAME"
kubectl config current-context
kubectl get nodes -o wide
kubectl get nodes -L kubernetes.io/arch
kubectl create namespace "$APP_NAMESPACE"

หาก Node แสดง arm64 ต้อง Build Image สำหรับ linux/arm64 หรือใช้ Image ที่รองรับทั้งสองสถาปัตยกรรมก่อน Deploy

#9. ให้ DOKS ดึง Private Image จาก DOCR

การ Login Docker บนเครื่องของเราไม่ได้ทำให้ Worker Node มีสิทธิ์ดึง Image โดยอัตโนมัติ ต้องให้คลัสเตอร์มี Credentials ของ Registry ด้วย

DigitalOcean มีการเชื่อมต่อผ่าน Control Panel ที่ช่วยกระจาย Secret และตั้งค่า Default Service Account ส่วนเวิร์กช็อปนี้ใช้ Secret เฉพาะ Namespace และอ้างชื่อใน Deployment เพื่อให้เห็นกลไกชัดเจน [1][11]

doctl registries kubernetes-manifest "$REGISTRY_NAME" \
  --namespace "$APP_NAMESPACE" \
  --name docr-pull-secret \
  | kubectl apply -f -

kubectl -n "$APP_NAMESPACE" get secret docr-pull-secret

คำสั่งสร้าง Manifest สำหรับให้ Kubernetes ดึง Image แล้วส่งเข้า kubectl โดยตรง หลีกเลี่ยงการบันทึกไฟล์ที่มี Credentials ลง Git และจัดการอายุ/การหมุนเวียน Credentials ตามนโยบายของระบบ

Secret ที่อ้างผ่าน imagePullSecrets ต้องอยู่ใน Namespace เดียวกับ Pod เสมอ การมี Secret ใน default ไม่ได้ทำให้ Pod ใน container-lab ใช้งานได้ [12]

#10. สร้าง Deployment และ Service

สร้างไฟล์ k8s/app.yaml ด้วยคำสั่งต่อไปนี้ ตัวแปร $IMAGE และ $APP_NAMESPACE จะถูก Bash แทนค่าก่อนเขียนไฟล์ Kubernetes ไม่แทนตัวแปรเหล่านี้เอง:

cat > k8s/app.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-api
  namespace: ${APP_NAMESPACE}
spec:
  replicas: 2
  revisionHistoryLimit: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0
      maxSurge: 1
  selector:
    matchLabels:
      app: demo-api
  template:
    metadata:
      labels:
        app: demo-api
    spec:
      automountServiceAccountToken: false
      terminationGracePeriodSeconds: 20
      imagePullSecrets:
        - name: docr-pull-secret
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        runAsGroup: 1000
        seccompProfile:
          type: RuntimeDefault
      containers:
        - name: api
          image: ${IMAGE}
          imagePullPolicy: IfNotPresent
          ports:
            - name: http
              containerPort: 8080
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              cpu: 500m
              memory: 128Mi
          readinessProbe:
            httpGet:
              path: /healthz
              port: http
            initialDelaySeconds: 2
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /healthz
              port: http
            initialDelaySeconds: 10
            periodSeconds: 10
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop: ["ALL"]
---
apiVersion: v1
kind: Service
metadata:
  name: demo-api
  namespace: ${APP_NAMESPACE}
spec:
  type: ClusterIP
  selector:
    app: demo-api
  ports:
    - name: http
      port: 80
      targetPort: http
EOF

เวิร์กช็อปเริ่มจาก Service แบบ ClusterIP เพื่อทดสอบแอปก่อนสร้าง Public Load Balancer

การตั้งค่า ผลที่ต้องการ
replicas: 2 รันสอง Pod ของแอป
imagePullSecrets ใช้ Credentials ของ DOCR
requests ระบุทรัพยากรที่ใช้ในการจัดตาราง Pod
limits กำหนดเพดาน CPU และ Memory ของ Container
readinessProbe หยุดส่ง Traffic ผ่าน Service ไปยัง Pod ที่ยังไม่พร้อม
livenessProbe ให้ Kubernetes เริ่ม Container ใหม่เมื่อ Probe ล้มเหลวถึงเกณฑ์
maxUnavailable: 0 ระหว่าง Rolling Update รักษาจำนวน Pod พร้อมใช้งานตามเป้าหมาย
maxSurge: 1 เพิ่ม Pod ชั่วคราวได้หนึ่งตัวระหว่าง Update

Probe ของตัวอย่างตรวจว่า HTTP Server ตอบได้ แอปจริงอาจต้องแยก Readiness กับ Liveness โดยไม่ทำให้ Liveness ล้มเหลวเพียงเพราะบริการภายนอกขัดข้อง [13][14]

#11. Deploy และตรวจสอบ

kubectl apply -f k8s/app.yaml
kubectl -n "$APP_NAMESPACE" rollout status deployment/demo-api --timeout=180s
kubectl -n "$APP_NAMESPACE" get pods -o wide
kubectl -n "$APP_NAMESPACE" get services
kubectl -n "$APP_NAMESPACE" logs deployment/demo-api --all-pods=true

Pod ควรแสดง Running และ READY เป็น 1/1 หาก Pod เป็น Pending หรือดึง Image ไม่สำเร็จ ให้ใช้ส่วนแก้ปัญหาด้านท้าย

ทดสอบผ่าน Port Forward:

kubectl -n "$APP_NAMESPACE" port-forward service/demo-api 8080:80

เปิด Terminal อีกหน้าต่างแล้วทดสอบ:

curl -fsS http://localhost:8080/
curl -fsS http://localhost:8080/healthz

Port Forward ผูกกับ Pod ที่เลือกหนึ่งตัว จึงใช้ตรวจการเชื่อมต่อได้ แต่ไม่ใช่วิธีพิสูจน์การกระจาย Request ระหว่างทุก Pod กด Ctrl+C เมื่อทดสอบเสร็จ

#12. เปิดบริการผ่าน DigitalOcean Load Balancer

ขั้นตอนนี้เป็นทางเลือกสำหรับทดลองเข้าถึงผ่าน Public IP และมีค่าใช้จ่ายของ Load Balancer เพิ่มจาก Worker Node [2][15]

แก้ type: ClusterIP ในไฟล์ k8s/app.yaml ให้เป็น:

  type: LoadBalancer

จากนั้น Apply และติดตามสถานะ:

kubectl apply -f k8s/app.yaml
kubectl -n "$APP_NAMESPACE" get service demo-api --watch

รอ EXTERNAL-IP เปลี่ยนจาก <pending> เป็น IP แล้วกด Ctrl+C กำหนด IP ตามค่าจริงเพื่อทดสอบ:

read -r -p "Load Balancer external IP: " LB_IP
curl -fsS "http://${LB_IP}/"
curl -fsS "http://${LB_IP}/healthz"

เส้นทางการเชื่อมต่อคือ ผู้ใช้ → DigitalOcean Load Balancer → Kubernetes Service → Ready Pod ตัวอย่างเปิด HTTP สำหรับแอปสาธิต เมื่อใช้งานจริงให้กำหนด Domain, TLS และรูปแบบ Ingress/Gateway หรือการตั้งค่า Load Balancer ให้ตรงกับความต้องการ

#13. อัปเดตแอปด้วย Rolling Update

ออก Image ใหม่เป็น v2 โดยเปลี่ยนค่าที่ฝังใน Image ผ่าน Build Argument:

export IMAGE_TAG="v2"
export IMAGE="registry.digitalocean.com/${REGISTRY_NAME}/${APP_NAME}:${IMAGE_TAG}"

doctl registries login "$REGISTRY_NAME" --expiry-seconds 3600
docker buildx build \
  --platform linux/amd64 \
  --build-arg APP_VERSION="$IMAGE_TAG" \
  --tag "$IMAGE" \
  --push .

แก้ค่า image ใน k8s/app.yaml ให้ตรงกับค่าใหม่ ก่อน Apply:

          image: registry.digitalocean.com/your-unique-registry/demo-api:v2

ใช้ชื่อ Registry จริง จากนั้น:

kubectl apply -f k8s/app.yaml
kubectl -n "$APP_NAMESPACE" rollout status deployment/demo-api --timeout=180s
kubectl -n "$APP_NAMESPACE" rollout history deployment/demo-api

เมื่อ Rollout เสร็จ / ควรแสดง "version":"v2" หากใช้ Port Forward ให้เริ่มคำสั่งใหม่หลัง Update เพื่อเชื่อมต่อกับ Pod ใหม่

Deployment จะค่อย ๆ เพิ่ม Pod รุ่นใหม่และลด Pod รุ่นเดิมตาม Strategy ที่กำหนด โดยต้องมีทรัพยากรสำหรับ Pod ชั่วคราวและมี Readiness ที่ตรวจสถานะได้ถูกต้อง การตั้ง Rolling Update อย่างเดียวไม่ได้รับประกันว่า Request ทุกตัวจะสำเร็จ [13]

#Rollback

หากรุ่นใหม่มีปัญหา:

kubectl -n "$APP_NAMESPACE" rollout undo deployment/demo-api
kubectl -n "$APP_NAMESPACE" rollout status deployment/demo-api --timeout=180s

แก้ image ใน k8s/app.yaml กลับเป็น v1 ด้วย เพื่อให้ไฟล์ตรงกับสถานะที่ต้องการ การ Apply ไฟล์ที่ยังอ้าง v2 จะนำรุ่นที่มีปัญหากลับมาอีก และควรเก็บ Image รุ่นก่อนใน DOCR ให้พร้อมสำหรับ Rollback

#14. ขยายจำนวน Pod

แก้ replicas: 2 ใน k8s/app.yaml เป็น replicas: 3 แล้ว Apply:

kubectl apply -f k8s/app.yaml
kubectl -n "$APP_NAMESPACE" rollout status deployment/demo-api --timeout=180s
kubectl -n "$APP_NAMESPACE" get pods -o wide

การเพิ่ม Pod ไม่ได้เพิ่ม Worker Node โดยอัตโนมัติ หากทรัพยากรของ Node ไม่พอ Pod ใหม่อาจเป็น Pending สำหรับระบบจริงสามารถพิจารณา:

  • Horizontal Pod Autoscaler (HPA): ปรับจำนวน Pod ตาม Metric และเงื่อนไขที่กำหนด
  • Cluster Autoscaler: ปรับจำนวน Worker Node ของ Node Pool ตามการจัดตาราง Pod
  • การกระจาย Pod ข้าม Node เช่น Topology Spread Constraints หรือ Pod Anti-Affinity

เวิร์กช็อปนี้ใช้ Manual Scaling และไม่ได้เปิด Autoscaler ทั้งสองชนิด หลาย Pod บน Node เดียวช่วยศึกษาการจัดการ Replica แต่ยังสูญเสียบริการได้เมื่อ Node นั้นหยุดทำงาน

#15. ต่อเติมเป็น CI/CD

เมื่อทำเวิร์กช็อปได้แล้ว สามารถนำลำดับเดียวกันไปใช้ใน GitHub Actions, GitLab CI หรือ Jenkins:

  1. Checkout โค้ดและตรวจสอบคุณภาพ
  2. รัน Unit Test และ API Test
  3. Build Image ตามสถาปัตยกรรมปลายทาง
  4. สแกน Image และเผยแพร่เข้า DOCR ด้วย Tag ที่ผูกกับ Commit
  5. อัปเดต Manifest หรือค่าที่ระบบ GitOps ใช้
  6. รอ Rollout และรัน Smoke Test

เลือกวิธี Deploy ให้ชัดเจน หาก Pipeline ใช้ kubectl ต้องมีสิทธิ์เข้าคลัสเตอร์ที่เหมาะสม หากใช้ GitOps เช่น Argo CD ให้ Pipeline อัปเดต Manifest ใน Git แล้วให้ Controller ทำการ Reconcile

แยก Credentials สำหรับ Push Image ออกจาก Credentials สำหรับ Pull Image และสิทธิ์ Deploy เท่าที่ระบบรองรับ เก็บข้อมูลลับใน Secret Store ของระบบ CI/CD

#16. ปัญหาที่พบบ่อย

อาการ สิ่งที่ควรตรวจ แนวทางแก้
unknown command "registries" รุ่นของ doctl อัปเดต doctl และตรวจ Help ของคำสั่งใหม่
สร้าง Registry ไม่ได้ ชื่อซ้ำ, สิทธิ์, จำนวน Registry ตามแผน เปลี่ยนชื่อหรือใช้ Registry ที่มีอยู่
Push ได้ unauthorized สิทธิ์และอายุ Credentials บนเครื่อง Login ใหม่โดยระบุชื่อ Registry และอย่าใช้ Read-only Credentials สำหรับ Push
ImagePullBackOff Image Path, Tag, Secret, สิทธิ์, การเชื่อมต่อ ตรวจว่า Image มีจริงและ Secret อยู่ใน Namespace ของ Pod
exec format error สถาปัตยกรรมของ Image และ Node Build สำหรับ linux/amd64 หรือ linux/arm64 ให้ตรงกัน
CrashLoopBackOff Log, คำสั่งเริ่มแอป, Probe อ่าน Log ของ Container ปัจจุบันและก่อนหน้า
Pod Pending CPU/Memory ที่ร้องขอ, Taint, Node Capacity อ่าน Events และเพิ่มทรัพยากรตามสาเหตุ
Service ติดต่อ Pod ไม่ได้ Selector, Labels, Readiness, Port ให้ Selector ตรงกับ Pod และตรวจ EndpointSlice
EXTERNAL-IP เป็น <pending> นาน การสร้าง Load Balancer, Quota, Region, Events อ่าน Events ของ Service และสถานะใน Control Panel
Push Tag เดิมแล้วแอปยังเป็นรุ่นเก่า Deployment และ Image Cache ออก Tag ใหม่แล้วอัปเดต Deployment หรืออ้าง Digest

คำสั่งวิเคราะห์:

kubectl -n "$APP_NAMESPACE" get pods -o wide
kubectl -n "$APP_NAMESPACE" get events --sort-by=.metadata.creationTimestamp
kubectl -n "$APP_NAMESPACE" describe deployment demo-api
kubectl -n "$APP_NAMESPACE" describe service demo-api
kubectl -n "$APP_NAMESPACE" get endpointslices \
  -l kubernetes.io/service-name=demo-api

สำหรับ Pod ที่มีปัญหา ใส่ชื่อจริงแทน <pod-name>:

kubectl -n "$APP_NAMESPACE" describe pod <pod-name>
kubectl -n "$APP_NAMESPACE" logs <pod-name> -c api
kubectl -n "$APP_NAMESPACE" logs <pod-name> -c api --previous

ใช้ --previous เมื่อ Container เคย Restart แล้วเท่านั้น สำหรับแอปจริง Log อาจมีข้อมูลลับ ควรตรวจและปกปิดข้อมูลก่อนเผยแพร่

#17. ค่าใช้จ่ายและการเก็บ Image

DOCR มีแผน Starter, Basic และ Professional โดยต่างกันด้านจำนวน Registry, Repository และพื้นที่เก็บ Image ตรวจราคาและข้อจำกัดปัจจุบันก่อนเลือกแผน [8]

ทรัพยากร สิ่งที่ทำให้เกิดค่าใช้จ่าย
DOCR แผนที่เลือกและพื้นที่เก็บที่ใช้
DOKS Worker Node ขนาด, จำนวนและระยะเวลาของ Node ที่รัน
HA Control Plane การเปิด HA ตามเงื่อนไขราคาปัจจุบัน
Load Balancer การเปิด Service ที่สร้าง Cloud Load Balancer
Storage Volumes, Snapshot หรือบริการเก็บข้อมูลที่สร้างเพิ่ม
เครือข่าย การรับส่งข้อมูลตามโควตาและเงื่อนไขของบริการ

การลบ Deployment ไม่ได้หยุดค่าใช้จ่ายของ Worker Node และการลบ Tag อาจยังเหลือ Image Layer ที่ไม่ถูกอ้างอิง ซึ่งยังใช้พื้นที่ Registry ควรวางแผนเก็บ Image ที่ใช้ Production และ Rollback แล้วทำ Cleanup/Garbage Collection ตามวิธีที่บริการรองรับ [2][8]

#18. ล้างทรัพยากรหลังเวิร์กช็อป

ทำส่วนนี้เมื่อจบการทดลองและแน่ใจว่าทรัพยากรเป็นของเวิร์กช็อปเท่านั้น ตรวจ Context และชื่อก่อนลบ:

kubectl config current-context
doctl kubernetes cluster list
doctl registries list

ลบ Service ก่อนเพื่อให้ Kubernetes ส่งคำขอลบ Load Balancer ที่เกี่ยวข้อง จากนั้นตรวจใน Control Panel ว่า Load Balancer ถูกลบแล้วก่อนลบคลัสเตอร์:

kubectl -n "$APP_NAMESPACE" delete service demo-api
kubectl delete namespace "$APP_NAMESPACE"

ลบคลัสเตอร์ที่สร้างเพื่อเวิร์กช็อป:

doctl kubernetes cluster delete "$CLUSTER_NAME"

คำสั่งลบคลัสเตอร์ตามปกติลบ Worker Node แต่ไม่รับประกันการลบทรัพยากรอื่นทั้งหมด เช่น Load Balancer, Volume หรือ Snapshot จึงต้องตรวจทรัพยากรที่เหลือด้วย [16]

หาก Registry นี้สร้างสำหรับเวิร์กช็อปโดยเฉพาะและไม่ต้องเก็บ Image แล้ว:

doctl registries delete "$REGISTRY_NAME"

คำสั่งนี้ลบ Registry อย่างถาวร อย่าใช้กับ Registry ที่แชร์กับโครงการอื่น [17]

#สิ่งที่ได้จากเวิร์กช็อป

เราสร้าง API และ Container Image ด้วย Docker, เผยแพร่ Image ส่วนตัวเข้า DOCR, ตั้งสิทธิ์ให้ DOKS ดึง Image, Deploy แอปพร้อม Health Checks และ Service รวมถึงทดลอง Rolling Update, Rollback และขยายจำนวน Pod

เมื่อนำไปใช้จริง ให้ต่อเติมการออกเวอร์ชันด้วย Commit SHA หรือ Digest, การทดสอบและสแกน Image, TLS, การกระจาย Pod ข้าม Node, การติดตาม Metric/Log และการสำรองข้อมูลตามความต้องการของระบบ

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

ตรวจสอบจากเอกสารทางการ ณ วันที่จัดทำ ตัวเลือก Region, Version, แผนและราคาสามารถเปลี่ยนแปลงได้

  1. DigitalOcean: Use Your Private Container Registry with Docker and Kubernetes
  2. DigitalOcean: Kubernetes Pricing และขอบเขตบริการ
  3. Kubernetes: Dockershim Removal FAQ
  4. DigitalOcean CLI: doctl registries create
  5. DigitalOcean CLI: doctl registries login
  6. DigitalOcean: Install and Configure doctl
  7. Docker Hub: Official Node Image
  8. DigitalOcean: Container Registry Pricing
  9. Docker: Multi-platform Builds
  10. DigitalOcean CLI: doctl kubernetes cluster create
  11. DigitalOcean CLI: doctl registries kubernetes-manifest
  12. Kubernetes: Images และ imagePullSecrets
  13. Kubernetes: Deployments
  14. Kubernetes: Liveness, Readiness and Startup Probes
  15. DigitalOcean: Add Load Balancers to Kubernetes Clusters
  16. DigitalOcean CLI: doctl kubernetes cluster delete
  17. DigitalOcean CLI: doctl registries delete