#Deploy Web ด้วย GitOps บน DigitalOcean ด้วย Argo CD และ GitHub Actions

การ Deploy Web Application ในระบบ Cloud Native สามารถทำให้เป็นอัตโนมัติ ปลอดภัย และตรวจสอบย้อนหลังได้มากขึ้นด้วยแนวคิด GitOps

แทนที่จะให้ CI Pipeline เชื่อมต่อ Kubernetes แล้วรัน kubectl apply เข้า Production โดยตรง เราสามารถให้ Git Repository เป็น Source of Truth และใช้ Argo CD ทำหน้าที่ตรวจสอบและปรับสถานะของ Kubernetes Cluster ให้ตรงกับ Configuration ที่กำหนดไว้ใน Git

บทความนี้ใช้ Stack หลักดังนี้

  • GitHub
  • GitHub Actions
  • Docker
  • DigitalOcean Container Registry (DOCR)
  • DigitalOcean Kubernetes (DOKS)
  • Argo CD
  • Kustomize

infographic-deploy-digital-ocean-argocd

#GitOps คืออะไร?

GitOps คือแนวทางในการจัดการ Application และ Infrastructure โดยเก็บ Desired State ของระบบไว้ใน Git

ตัวอย่างเช่น Kubernetes Manifest กำหนดจำนวน Pod

replicas: 2

และกำหนด Container Image

image: registry.digitalocean.com/my-registry/my-web:a12bc34

Argo CD จะเปรียบเทียบ

Desired State in Git
        VS
Actual State in Kubernetes

หากสถานะไม่ตรงกัน Argo CD สามารถ Sync และ Reconcile ระบบให้กลับมาตรงกับ Git ได้


#CI/CD แบบทั่วไป

Developer
   |
   v
GitHub
   |
   v
GitHub Actions
   |
   +-- Test
   +-- Build Docker Image
   +-- Push Image
   +-- kubectl apply
           |
           v
       Kubernetes

วิธีนี้ทำงานได้ แต่ CI Pipeline ต้องถือ Credential ที่สามารถ Deploy เข้า Kubernetes Cluster โดยตรง


#GitOps Architecture

Developer
   |
   v
Application Repository
   |
   v
GitHub Actions
   |
   +-- Test
   +-- Build
   +-- Push Image --------> DOCR
   |
   +-- Update GitOps Repository
                   |
                   v
                Argo CD
                   |
                   v
                  DOKS

GitHub Actions ทำหน้าที่ด้าน CI ส่วน Argo CD ทำหน้าที่ด้าน Continuous Delivery


#1. เตรียมเครื่องมือ

ตรวจสอบเครื่องมือที่จำเป็น

docker --version
kubectl version --client
doctl version
git --version
argocd version --client

ควรมี

  • DigitalOcean Account
  • GitHub Account
  • Application Repository
  • GitOps Repository
  • DigitalOcean Kubernetes Cluster
  • DigitalOcean Container Registry
  • Domain Name สำหรับ Production

#2. สร้าง DigitalOcean Kubernetes Cluster

หลังสร้าง DOKS Cluster แล้ว ดาวน์โหลด kubeconfig

doctl kubernetes cluster kubeconfig save my-cluster

ตรวจสอบ Node

kubectl get nodes

หาก Node อยู่ในสถานะ Ready แสดงว่า Cluster พร้อมใช้งาน


#3. สร้าง DigitalOcean Container Registry

สร้าง Registry

doctl registries create my-registry --region sgp1

Login

doctl registries login

Container Image จะมีรูปแบบ

registry.digitalocean.com/my-registry/my-web:TAG

เชื่อม Registry กับ DOKS

doctl kubernetes cluster registry add my-cluster

#4. เตรียม Web Application

ตัวอย่าง Node.js + Express

import express from "express";

const app = express();

app.get("/", (req, res) => {
  res.send("Hello GitOps on DigitalOcean");
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

ตัวอย่าง Dockerfile

FROM node:22-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

Build และ Run

docker build -t my-web .
docker run -p 3000:3000 my-web

#5. แยก Application Repository และ GitOps Repository

#Application Repository

my-web/
├── src/
├── package.json
├── Dockerfile
└── .github/
    └── workflows/
        └── build.yml

#GitOps Repository

my-web-gitops/
└── environments/
    └── production/
        ├── deployment.yaml
        ├── service.yaml
        ├── ingress.yaml
        └── kustomization.yaml

ข้อดี

  • แยก Source Code และ Deployment Configuration
  • Audit Production ได้ง่าย
  • Rollback ผ่าน Git ได้
  • แยก Permission ได้ชัดเจน
  • ใช้ Pull Request Review ก่อน Production ได้

#6. Kubernetes Deployment

สร้าง deployment.yaml

apiVersion: apps/v1
kind: Deployment

metadata:
  name: my-web

spec:
  replicas: 2

  selector:
    matchLabels:
      app: my-web

  template:
    metadata:
      labels:
        app: my-web

    spec:
      containers:
        - name: my-web
          image: registry.digitalocean.com/my-registry/my-web:INITIAL

          ports:
            - containerPort: 3000

          readinessProbe:
            httpGet:
              path: /
              port: 3000

          livenessProbe:
            httpGet:
              path: /
              port: 3000

          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"

            limits:
              cpu: "500m"
              memory: "512Mi"

#7. Kubernetes Service

สร้าง service.yaml

apiVersion: v1
kind: Service

metadata:
  name: my-web

spec:
  selector:
    app: my-web

  ports:
    - port: 80
      targetPort: 3000

  type: ClusterIP

#8. จัดการ Image Version ด้วย Kustomize

สร้าง kustomization.yaml

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml

images:
  - name: registry.digitalocean.com/my-registry/my-web
    newTag: INITIAL

เมื่อมี Release ใหม่ แก้เพียง

newTag: a12bc34

#9. ติดตั้ง Argo CD

kubectl create namespace argocd

ติดตั้ง

kubectl apply -n argocd \
  --server-side \
  --force-conflicts \
  -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

ตรวจสอบ

kubectl get pods -n argocd

สำหรับ Production ควร Pin Version ที่ผ่านการทดสอบแล้ว


#10. เข้า Argo CD

Port Forward

kubectl port-forward svc/argocd-server \
  -n argocd \
  8080:443

เปิด

https://localhost:8080

ดู Initial Password

argocd admin initial-password -n argocd

#11. สร้าง Argo CD Application

apiVersion: argoproj.io/v1alpha1
kind: Application

metadata:
  name: my-web
  namespace: argocd

spec:
  project: default

  source:
    repoURL: https://github.com/YOUR_USERNAME/my-web-gitops.git
    targetRevision: main
    path: environments/production

  destination:
    server: https://kubernetes.default.svc
    namespace: production

  syncPolicy:
    automated:
      enabled: true
      prune: true
      selfHeal: true

    syncOptions:
      - CreateNamespace=true

Apply

kubectl apply -f application.yaml

#12. GitHub Actions สำหรับ Build และ Push Image

สร้าง .github/workflows/build.yml

name: Build and Release

on:
  push:
    branches:
      - main

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install doctl
        uses: digitalocean/action-doctl@v2
        with:
          token: ${{ secrets.DIGITALOCEAN_ACCESS_TOKEN }}

      - name: Login to DOCR
        run: doctl registries login --expiry-seconds 1200

      - name: Build Docker Image
        run: |
          IMAGE_TAG=$(echo "$GITHUB_SHA" | cut -c1-7)

          docker build \
            -t registry.digitalocean.com/${{ secrets.REGISTRY_NAME }}/my-web:$IMAGE_TAG \
            .

      - name: Push Docker Image
        run: |
          IMAGE_TAG=$(echo "$GITHUB_SHA" | cut -c1-7)

          docker push \
            registry.digitalocean.com/${{ secrets.REGISTRY_NAME }}/my-web:$IMAGE_TAG

GitHub Secrets ที่ต้องใช้

DIGITALOCEAN_ACCESS_TOKEN
REGISTRY_NAME

#13. Update GitOps Repository

Checkout GitOps Repository

- name: Checkout GitOps Repository
  uses: actions/checkout@v4
  with:
    repository: YOUR_USERNAME/my-web-gitops
    token: ${{ secrets.GITOPS_TOKEN }}
    path: gitops

Update Image Tag

- name: Update Image Tag
  run: |
    IMAGE_TAG=$(echo "$GITHUB_SHA" | cut -c1-7)

    cd gitops/environments/production

    kustomize edit set image \
      registry.digitalocean.com/${{ secrets.REGISTRY_NAME }}/my-web=registry.digitalocean.com/${{ secrets.REGISTRY_NAME }}/my-web:$IMAGE_TAG

Commit

- name: Commit GitOps Change
  run: |
    cd gitops

    IMAGE_TAG=$(echo "$GITHUB_SHA" | cut -c1-7)

    git config user.name "gitops-bot"
    git config user.email "gitops-bot@users.noreply.github.com"

    git add .
    git commit -m "deploy: my-web $IMAGE_TAG" || exit 0
    git push

#14. GitOps Deployment Flow

Git Push
   |
   v
GitHub Actions
   |
   v
Automated Test
   |
   v
Docker Build
   |
   v
Push Image -> DOCR
   |
   v
Update GitOps Repository
   |
   v
Argo CD Detects Change
   |
   v
Sync -> DOKS
   |
   v
Kubernetes Rolling Update
   |
   v
Production

จุดสำคัญคือ GitHub Actions ไม่จำเป็นต้อง kubectl apply เข้า Production Cluster โดยตรง


#15. เพิ่ม Domain ด้วย Ingress

apiVersion: networking.k8s.io/v1
kind: Ingress

metadata:
  name: my-web

spec:
  ingressClassName: nginx

  rules:
    - host: app.example.com

      http:
        paths:
          - path: /
            pathType: Prefix

            backend:
              service:
                name: my-web

                port:
                  number: 80

เพิ่มใน Kustomize

resources:
  - deployment.yaml
  - service.yaml
  - ingress.yaml

#16. HTTPS

Production สามารถใช้

Ingress Controller
+
cert-manager
+
Let's Encrypt

Flow

Internet
   |
   v
DigitalOcean Load Balancer
   |
   v
Ingress Controller
   |
   v
HTTPS / TLS
   |
   v
Service
   |
   v
Pod

#17. Rollback ด้วย Git

หาก Release ใหม่มีปัญหา

git revert <commit-id>
git push

Argo CD จะตรวจพบ Desired State ใหม่และ Deploy Version ก่อนหน้ากลับมา

Rollback = Git Operation

#18. Self-Healing

ถ้า Git ระบุ

replicas: 2

แต่มีคนแก้ Cluster

kubectl scale deployment my-web --replicas=10

เมื่อเปิด

selfHeal: true

Argo CD จะนำระบบกลับไปเป็นจำนวน Replica ตาม Git


#19. Dev / Staging / Production

gitops/
└── environments/
    ├── development/
    ├── staging/
    └── production/

Promotion Flow

Development
     |
     v
Automated Test
     |
     v
Staging
     |
     v
Pull Request / Approval
     |
     v
Production

#20. Best Practices

#ใช้ Immutable Image Tag

ไม่แนะนำ

image: my-web:latest

แนะนำ

image: my-web:a12bc34

หรือ

image: my-web:v1.4.0

#ไม่เก็บ Secret แบบ Plain Text

ควรพิจารณา

  • External Secrets Operator
  • Sealed Secrets
  • SOPS
  • Secret Manager

#ใช้ Health Probe

ควรกำหนด

readinessProbe:
livenessProbe:

#กำหนด Resource Requests และ Limits

resources:
  requests:
    cpu: "100m"
    memory: "128Mi"

  limits:
    cpu: "500m"
    memory: "512Mi"

#21. GitOps กับ Security

Architecture ที่แนะนำ

GitHub Actions
      |
      +---- Push Image ----> DOCR
      |
      +---- Update Git ----> GitOps Repository
                                   |
                                   v
                                Argo CD
                                   |
                                   v
                                  DOKS

CI ไม่จำเป็นต้องมี cluster-admin credential

จึงช่วยลด Attack Surface ของ Deployment Pipeline


#22. GitOps ไม่ได้มาแทน CI

CI

GitHub Actions
=
Test + Build + Scan + Push Image

CD

Argo CD
=
Observe + Compare + Sync + Reconcile

ทั้งสองส่วนทำงานร่วมกัน


#23. Production Architecture

GitHub
   |
   v
GitHub Actions
   |
   +-- Unit Test
   +-- Integration Test
   +-- Docker Build
   +-- Security Scan
   +-- Push Image
            |
            v
           DOCR

GitHub Actions
   |
   v
GitOps Repository
   |
   v
Argo CD
   |
   v
DOKS
   |
   v
Ingress Controller
   |
   v
DigitalOcean Load Balancer
   |
   v
app.example.com

สามารถเพิ่ม Monitoring และ Observability ด้วย

  • Prometheus
  • Grafana
  • Loki
  • OpenTelemetry

#สรุป

Stack สำหรับ Deploy Web แบบ GitOps บน DigitalOcean

GitHub
+
GitHub Actions
+
Docker
+
DigitalOcean Container Registry
+
DigitalOcean Kubernetes
+
Argo CD
+
Kustomize

Flow หลัก

Git Commit
   |
   v
GitHub Actions
   |
   v
Test + Build
   |
   v
Docker Image
   |
   v
DOCR
   |
   v
GitOps Commit
   |
   v
Argo CD
   |
   v
DOKS
   |
   v
Rolling Update
   |
   v
Production

ข้อดีสำคัญของ GitOps

  • Git เป็น Source of Truth
  • Deployment ตรวจสอบย้อนหลังได้
  • Rollback ด้วย Git ได้
  • ตรวจจับ Configuration Drift ได้
  • รองรับ Self-Healing
  • ลดสิทธิ์ของ CI ต่อ Production Cluster
  • รองรับ Development / Staging / Production
  • เหมาะกับ Kubernetes และ Cloud Native Application

สำหรับระบบที่ Deploy บน DigitalOcean Kubernetes การใช้ GitHub Actions + DOCR + Argo CD + DOKS เป็นแนวทางที่ดีสำหรับสร้าง Deployment Pipeline ที่มี Automation, Auditability และ Governance มากขึ้น


#References