article

ArgoCD GitOps ที่ทำให้ Kubernetes เป็นระบบจริงๆ

16 min read

วันที่ Kubernetes เป็น Chaos

ก่อนรู้จัก GitOps การจัดการ Kubernetes deployments เป็นฝันร้าย:

วิธีการ Deploy แบบเก่า (Push-based):

# Developer A deploy
kubectl apply -f deployment.yaml -n production
kubectl set image deployment/myapp myapp=myapp:v1.2.3 -n production

# Developer B deploy ทับ (ไม่รู้ว่า A เพิ่งเปลี่ยน)
helm upgrade myapp ./charts/myapp --set image.tag=v1.2.4 -n production

# Site Reliability Engineer พยายามแก้ hotfix
kubectl patch deployment myapp -p '{"spec":{"replicas":10}}' -n production

# DevOps Engineer rollback
kubectl rollout undo deployment/myapp -n production

ปัญหาที่เจอบ่อย:

  • Configuration Drift: ไม่รู้ว่า production เป็นแบบไหนจริง
  • Multiple Sources of Truth: Git, Helm, kubectl commands ไม่ sync กัน
  • No Audit Trail: ใครเปลี่ยนอะไรเมื่อไหร่ไม่รู้
  • Access Control ยาก: ต้องให้ kubectl access ทุกคน
  • Rollback ซับซ้อน: ไม่รู้ว่าจะ rollback ไปสถานะไหน
  • Environment Inconsistency: dev/staging/prod ไม่เหมือนกัน

ตัวอย่างความวุ่นวาย:

# สถานการณ์จริงที่เกิดขึ้น 😅

# 9:00 AM - Developer deploy
kubectl apply -f deployment.yaml

# 9:30 AM - SRE scale up (emergency)  
kubectl scale deployment myapp --replicas=5

# 10:00 AM - DevOps update config
kubectl create configmap app-config --from-file=config.json --dry-run=client -o yaml | kubectl apply -f -

# 11:00 AM - Another developer deploy (overwrite SRE's scaling!)
helm upgrade myapp ./charts --set replicas=2

# 11:30 AM - SRE confused: "Why is it 2 replicas? I set it to 5!"
# 12:00 PM - Nobody knows what the current state should be 😱

ผลลัพธ์: Configuration chaos, downtime, และความไม่แน่ใจตลอดเวลา! 😰

จนวันหนึ่งพบ ArgoCD และ GitOps แล้วชีวิตเปลี่ยนไป! 🚀

GitOps Fundamentals

1. GitOps Principles

4 หลักการพื้นฐาน:

  1. Declarative: ระบบทั้งหมดอธิบายด้วย declarative configuration
  2. Versioned: Configuration เก็บใน Git (version control)
  3. Immutable: ไม่แก้ไขโดยตรงใน cluster
  4. Pulled: System pull configuration จาก Git (ไม่ใช่ push)

Flow ใหม่:

Developer → Git Push → ArgoCD detects changes → Apply to Kubernetes
                  ↑                                      ↓
              Source of Truth                    Desired State

2. ArgoCD Installation และ Setup

# argocd-install.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: argocd
---
# Install ArgoCD using official manifests
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

# Or using Helm
helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd --namespace argocd --create-namespace
# argocd-values.yaml - Production Configuration
global:
  logging:
    level: info
    
server:
  service:
    type: LoadBalancer
  config:
    url: https://argocd.example.com
    application.instanceLabelKey: argocd.argoproj.io/instance
  
  ingress:
    enabled: true
    annotations:
      cert-manager.io/cluster-issuer: "letsencrypt-prod"
      nginx.ingress.kubernetes.io/ssl-passthrough: "true"
      nginx.ingress.kubernetes.io/backend-protocol: "GRPC"
    hosts:
      - host: argocd.example.com
        paths:
          - path: /
            pathType: Prefix
    tls:
      - secretName: argocd-server-tls
        hosts:
          - argocd.example.com

controller:
  metrics:
    enabled: true
  resources:
    limits:
      cpu: 2000m
      memory: 2Gi
    requests:
      cpu: 1000m
      memory: 1Gi

repoServer:
  resources:
    limits:
      cpu: 1000m
      memory: 1Gi
    requests:
      cpu: 500m
      memory: 512Mi

redis:
  resources:
    limits:
      cpu: 200m
      memory: 256Mi
    requests:
      cpu: 100m
      memory: 128Mi

# RBAC Configuration
configs:
  rbac:
    policy.default: role:readonly
    policy.csv: |
      p, role:admin, applications, *, */*, allow
      p, role:admin, clusters, *, *, allow
      p, role:admin, repositories, *, *, allow
      
      p, role:developer, applications, get, */*, allow
      p, role:developer, applications, sync, */dev/*, allow
      p, role:developer, applications, sync, */staging/*, allow
      
      g, mycompany:devops, role:admin
      g, mycompany:developers, role:developer

3. Initial Setup และ Login

# Get initial admin password
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

# Port forward for local access
kubectl port-forward svc/argocd-server -n argocd 8080:443

# Login using CLI
argocd login localhost:8080

# Change admin password
argocd account update-password

# Add Git repository
argocd repo add https://github.com/mycompany/k8s-manifests --username myuser --password mytoken

# Add cluster (if external)
argocd cluster add my-cluster-context

Application Configuration

1. Basic Application

# applications/myapp-dev.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-dev
  namespace: argocd
  finalizers:
    - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  
  source:
    repoURL: https://github.com/mycompany/k8s-manifests
    targetRevision: HEAD
    path: environments/dev/myapp
    
  destination:
    server: https://kubernetes.default.svc
    namespace: myapp-dev
    
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
      allowEmpty: false
    syncOptions:
      - CreateNamespace=true
      - PrunePropagationPolicy=foreground
      - PruneLast=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

2. Helm-based Application

# applications/myapp-prod.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-prod
  namespace: argocd
  labels:
    environment: production
    team: backend
spec:
  project: production
  
  source:
    repoURL: https://github.com/mycompany/helm-charts
    targetRevision: main
    path: charts/myapp
    helm:
      values: |
        replicaCount: 5
        
        image:
          repository: myregistry/myapp
          tag: v1.2.3
          
        resources:
          limits:
            cpu: 1000m
            memory: 1Gi
          requests:
            cpu: 500m
            memory: 512Mi
            
        autoscaling:
          enabled: true
          minReplicas: 3
          maxReplicas: 20
          
        ingress:
          enabled: true
          hosts:
            - host: api.myapp.com
              paths:
                - path: /
                  pathType: Prefix
                  
      valueFiles:
        - values-production.yaml
        
  destination:
    server: https://kubernetes.default.svc
    namespace: myapp-prod
    
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ApplyOutOfSyncOnly=true
    retry:
      limit: 3
      backoff:
        duration: 5s
        maxDuration: 1m

3. Kustomize-based Application

# applications/monitoring-stack.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: monitoring-stack
  namespace: argocd
spec:
  project: infrastructure
  
  source:
    repoURL: https://github.com/mycompany/k8s-manifests
    targetRevision: HEAD
    path: infrastructure/monitoring
    kustomize:
      images:
        - prometheus/prometheus:v2.40.0
        - grafana/grafana:9.0.0
      patchesStrategicMerge:
        - patches/production.yaml
      namespace: monitoring
      
  destination:
    server: https://kubernetes.default.svc
    namespace: monitoring
    
  syncPolicy:
    automated:
      prune: false  # Careful with monitoring stack
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
      - ServerSideApply=true

Repository Structure และ Organization

1. GitOps Repository Structure

k8s-manifests/
├── applications/           # ArgoCD Application definitions
│   ├── dev/
│   ├── staging/
│   └── production/
├── environments/          # Environment-specific manifests
│   ├── dev/
│   │   ├── myapp/
│   │   │   ├── deployment.yaml
│   │   │   ├── service.yaml
│   │   │   └── kustomization.yaml
│   │   └── database/
│   ├── staging/
│   └── production/
├── infrastructure/        # Shared infrastructure
│   ├── monitoring/
│   ├── ingress/
│   └── security/
└── base/                 # Base configurations
    ├── myapp/
    │   ├── deployment.yaml
    │   ├── service.yaml
    │   └── configmap.yaml
    └── database/

2. Kustomization Example

# base/myapp/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml
  - configmap.yaml

commonLabels:
  app: myapp
  version: v1

images:
  - name: myapp
    newTag: latest

configMapGenerator:
  - name: app-config
    files:
      - config.properties
    options:
      disableNameSuffixHash: true
# environments/production/myapp/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: myapp-prod

bases:
  - ../../../base/myapp

images:
  - name: myapp
    newTag: v1.2.3

replicas:
  - name: myapp
    count: 5

patchesStrategicMerge:
  - patches/resources.yaml
  - patches/ingress.yaml

configMapGenerator:
  - name: app-config
    files:
      - config/production.properties
    behavior: replace
# environments/production/myapp/patches/resources.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
        - name: myapp
          resources:
            limits:
              cpu: 1000m
              memory: 1Gi
            requests:
              cpu: 500m
              memory: 512Mi
          env:
            - name: NODE_ENV
              value: "production"
            - name: LOG_LEVEL
              value: "info"

ArgoCD Projects และ Multi-tenancy

1. Project Configuration

# projects/production.yaml
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
  name: production
  namespace: argocd
spec:
  description: Production applications
  
  sourceRepos:
    - 'https://github.com/mycompany/k8s-manifests'
    - 'https://github.com/mycompany/helm-charts'
    
  destinations:
    - namespace: 'myapp-prod'
      server: https://kubernetes.default.svc
    - namespace: 'api-prod'
      server: https://kubernetes.default.svc
      
  clusterResourceWhitelist:
    - group: ""
      kind: Namespace
    - group: rbac.authorization.k8s.io
      kind: ClusterRole
    - group: rbac.authorization.k8s.io
      kind: ClusterRoleBinding
      
  namespaceResourceWhitelist:
    - group: ""
      kind: ConfigMap
    - group: ""
      kind: Service
    - group: apps
      kind: Deployment
    - group: networking.k8s.io
      kind: Ingress
      
  roles:
    - name: admin
      description: Full access to production project
      policies:
        - p, proj:production:admin, applications, *, production/*, allow
      groups:
        - mycompany:devops
        
    - name: viewer
      description: Read-only access to production project
      policies:
        - p, proj:production:viewer, applications, get, production/*, allow
      groups:
        - mycompany:developers
        
  syncWindows:
    - kind: deny
      schedule: "0 22 * * 1-5"  # No deployments Mon-Fri after 10 PM
      duration: 8h
      applications:
        - "*"
      manualSync: false

2. ApplicationSet for Multi-environment

# applicationsets/myapp.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: myapp
  namespace: argocd
spec:
  generators:
  - list:
      elements:
      - environment: dev
        namespace: myapp-dev
        project: default
        replicaCount: 1
        branch: develop
      - environment: staging
        namespace: myapp-staging
        project: staging
        replicaCount: 2
        branch: main
      - environment: production
        namespace: myapp-prod
        project: production
        replicaCount: 5
        branch: main
        
  template:
    metadata:
      name: 'myapp-{{environment}}'
      labels:
        environment: '{{environment}}'
        
    spec:
      project: '{{project}}'
      
      source:
        repoURL: https://github.com/mycompany/k8s-manifests
        targetRevision: '{{branch}}'
        path: 'environments/{{environment}}/myapp'
        
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{namespace}}'
        
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
          - CreateNamespace=true

Advanced GitOps Patterns

1. Blue-Green Deployment with ArgoCD

# applications/myapp-blue-green.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: myapp
spec:
  replicas: 5
  strategy:
    blueGreen:
      activeService: myapp-active
      previewService: myapp-preview
      autoPromotionEnabled: false
      scaleDownDelaySeconds: 30
      prePromotionAnalysis:
        templates:
        - templateName: success-rate
        args:
        - name: service-name
          value: myapp-preview
      postPromotionAnalysis:
        templates:
        - templateName: success-rate
        args:
        - name: service-name
          value: myapp-active
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myapp:v1.2.3
        ports:
        - containerPort: 3000

2. Canary Deployment

# rollouts/myapp-canary.yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
  name: myapp-canary
spec:
  replicas: 10
  strategy:
    canary:
      canaryService: myapp-canary
      stableService: myapp-stable
      trafficRouting:
        nginx:
          stableIngress: myapp-stable
      steps:
      - setWeight: 10
      - pause:
          duration: 5m
      - analysis:
          templates:
          - templateName: error-rate
          args:
          - name: service-name
            value: myapp-canary
      - setWeight: 25
      - pause:
          duration: 10m
      - setWeight: 50
      - pause:
          duration: 10m
      - setWeight: 75
      - pause:
          duration: 10m
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myapp:v1.2.3

3. Analysis Templates

# analysis/error-rate.yaml
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
  name: error-rate
spec:
  args:
  - name: service-name
  metrics:
  - name: error-rate
    interval: 1m
    count: 5
    successCondition: result[0] < 0.05
    provider:
      prometheus:
        address: http://prometheus:9090
        query: |
          sum(rate(http_requests_total{service="{{args.service-name}}",status=~"5.."}[5m])) /
          sum(rate(http_requests_total{service="{{args.service-name}}"}[5m]))

Security และ Secret Management

1. Sealed Secrets Integration

# Install Sealed Secrets Controller
kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.18.0/controller.yaml

# Install kubeseal CLI
wget https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.18.0/kubeseal-0.18.0-linux-amd64.tar.gz
tar xfz kubeseal-0.18.0-linux-amd64.tar.gz
sudo install -m 755 kubeseal /usr/local/bin/kubeseal
# Create regular secret (local only)
apiVersion: v1
kind: Secret
metadata:
  name: app-secrets
  namespace: myapp-prod
type: Opaque
data:
  database-password: cGFzc3dvcmQxMjM=
  api-key: YWJjZGVmZ2hpams=
# Seal the secret
kubeseal -f secret.yaml -w sealed-secret.yaml

# Commit sealed secret to Git
git add sealed-secret.yaml
git commit -m "Add sealed secrets for myapp-prod"
git push origin main
# sealed-secret.yaml (safe to commit)
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
  name: app-secrets
  namespace: myapp-prod
spec:
  encryptedData:
    database-password: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEQAx...
    api-key: AgAKAoiQm6u2/ULE5L1Rs3PfIx+f32h...
  template:
    type: Opaque

2. External Secrets Operator

# external-secrets/database-secret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: database-secret
  namespace: myapp-prod
spec:
  refreshInterval: 15s
  
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
    
  target:
    name: database-secret
    creationPolicy: Owner
    
  data:
  - secretKey: password
    remoteRef:
      key: database/production
      property: password
  - secretKey: username
    remoteRef:
      key: database/production
      property: username
# external-secrets/secret-store.yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
  namespace: myapp-prod
spec:
  provider:
    vault:
      server: "https://vault.example.com"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "myapp-role"
          serviceAccountRef:
            name: "external-secrets-sa"

Multi-cluster Management

1. Cluster Registration

# Add external cluster
kubectl config get-contexts
argocd cluster add staging-cluster --name staging

# List registered clusters
argocd cluster list

# Add cluster with custom config
argocd cluster add production-cluster \
  --name production \
  --server https://prod-k8s-api.example.com \
  --service-account argocd-manager \
  --namespace argocd

2. Cross-cluster Application

# applications/cross-cluster-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp-remote
  namespace: argocd
spec:
  project: default
  
  source:
    repoURL: https://github.com/mycompany/k8s-manifests
    targetRevision: HEAD
    path: environments/production/myapp
    
  destination:
    server: https://prod-cluster.example.com  # Remote cluster
    namespace: myapp-prod
    
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

3. ApplicationSet for Multi-cluster

# applicationsets/multi-cluster.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: multi-cluster-apps
  namespace: argocd
spec:
  generators:
  - clusters:
      selector:
        matchLabels:
          environment: production
  - list:
      elements:
      - app: myapp
        path: apps/myapp
      - app: database
        path: apps/database
        
  template:
    metadata:
      name: '{{app}}-{{name}}'
      
    spec:
      project: default
      
      source:
        repoURL: https://github.com/mycompany/k8s-manifests
        targetRevision: HEAD
        path: '{{path}}'
        
      destination:
        server: '{{server}}'
        namespace: '{{app}}-prod'
        
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Monitoring และ Observability

1. ArgoCD Metrics

# monitoring/servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: argocd-metrics
  namespace: argocd
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: argocd-metrics
  endpoints:
  - port: metrics
    interval: 30s
    path: /metrics

2. ArgoCD Alerts

# monitoring/alerts.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: argocd-alerts
  namespace: argocd
spec:
  groups:
  - name: argocd
    rules:
    - alert: ArgoCDAppNotSynced
      expr: argocd_app_info{sync_status!="Synced"} == 1
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "ArgoCD Application not synced"
        description: "Application {{ $labels.name }} has been out of sync for more than 5 minutes"
        
    - alert: ArgoCDAppUnhealthy
      expr: argocd_app_info{health_status!="Healthy"} == 1
      for: 2m
      labels:
        severity: critical
      annotations:
        summary: "ArgoCD Application unhealthy"
        description: "Application {{ $labels.name }} is in {{ $labels.health_status }} state"
        
    - alert: ArgoCDSyncFailed
      expr: increase(argocd_app_sync_total{phase="Failed"}[5m]) > 0
      for: 1m
      labels:
        severity: warning
      annotations:
        summary: "ArgoCD Sync failed"
        description: "Application {{ $labels.name }} sync failed"

3. Grafana Dashboard

{
  "dashboard": {
    "title": "ArgoCD Overview",
    "panels": [
      {
        "title": "Application Status",
        "type": "stat",
        "targets": [
          {
            "expr": "count by (sync_status) (argocd_app_info)",
            "legendFormat": "{{sync_status}}"
          }
        ]
      },
      {
        "title": "Sync Activity",
        "type": "graph",
        "targets": [
          {
            "expr": "sum(rate(argocd_app_sync_total[5m])) by (phase)",
            "legendFormat": "{{phase}}"
          }
        ]
      },
      {
        "title": "Repository Errors",
        "type": "graph", 
        "targets": [
          {
            "expr": "sum(rate(argocd_git_request_total{request_type=\"ls-remote\"}[5m])) by (repo)",
            "legendFormat": "{{repo}}"
          }
        ]
      }
    ]
  }
}

CI/CD Integration

1. GitHub Actions Integration

# .github/workflows/gitops-update.yml
name: Update GitOps

on:
  push:
    tags: ['v*']

jobs:
  update-gitops:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout app repo
      uses: actions/checkout@v4
      
    - name: Extract version
      id: version
      run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
      
    - name: Checkout GitOps repo
      uses: actions/checkout@v4
      with:
        repository: mycompany/k8s-manifests
        token: ${{ secrets.GITOPS_TOKEN }}
        path: gitops
        
    - name: Update image tag
      run: |
        cd gitops
        
        # Update staging
        yq eval '.images[0].newTag = "${{ steps.version.outputs.VERSION }}"' -i environments/staging/myapp/kustomization.yaml
        
        # Update production (manual approval required)
        yq eval '.images[0].newTag = "${{ steps.version.outputs.VERSION }}"' -i environments/production/myapp/kustomization.yaml
        
        # Commit changes
        git config --global user.name "GitHub Actions"
        git config --global user.email "actions@github.com"
        
        git add .
        git commit -m "Update myapp to ${{ steps.version.outputs.VERSION }}"
        git push origin main
        
    - name: Create ArgoCD sync
      run: |
        # Install ArgoCD CLI
        curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
        chmod +x /usr/local/bin/argocd
        
        # Login and sync
        argocd login argocd.example.com --username admin --password ${{ secrets.ARGOCD_PASSWORD }}
        argocd app sync myapp-staging --prune

2. Pull Request Preview Environments

# .github/workflows/preview-env.yml
name: Preview Environment

on:
  pull_request:
    branches: [main]

jobs:
  deploy-preview:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Create preview namespace
      run: |
        BRANCH_NAME=${GITHUB_HEAD_REF//[^a-zA-Z0-9]/-}
        PREVIEW_NAME="myapp-pr-${GITHUB_EVENT_NUMBER}"
        
        echo "PREVIEW_NAME=$PREVIEW_NAME" >> $GITHUB_ENV
        echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
        
    - name: Checkout GitOps repo
      uses: actions/checkout@v4
      with:
        repository: mycompany/k8s-manifests
        token: ${{ secrets.GITOPS_TOKEN }}
        path: gitops
        
    - name: Create preview application
      run: |
        cd gitops
        
        # Create preview directory
        mkdir -p environments/previews/${{ env.PREVIEW_NAME }}
        
        # Copy from dev environment
        cp -r environments/dev/myapp/* environments/previews/${{ env.PREVIEW_NAME }}/
        
        # Update namespace and image
        yq eval '.namespace = "${{ env.PREVIEW_NAME }}"' -i environments/previews/${{ env.PREVIEW_NAME }}/kustomization.yaml
        yq eval '.images[0].newTag = "${{ github.sha }}"' -i environments/previews/${{ env.PREVIEW_NAME }}/kustomization.yaml
        
        # Create ArgoCD application
        cat > applications/previews/${{ env.PREVIEW_NAME }}.yaml <<EOF
        apiVersion: argoproj.io/v1alpha1
        kind: Application
        metadata:
          name: ${{ env.PREVIEW_NAME }}
          namespace: argocd
          annotations:
            argocd.argoproj.io/cleanup: "true"
        spec:
          project: previews
          source:
            repoURL: https://github.com/mycompany/k8s-manifests
            targetRevision: HEAD
            path: environments/previews/${{ env.PREVIEW_NAME }}
          destination:
            server: https://kubernetes.default.svc
            namespace: ${{ env.PREVIEW_NAME }}
          syncPolicy:
            automated:
              prune: true
              selfHeal: true
            syncOptions:
              - CreateNamespace=true
        EOF
        
        # Commit changes
        git config --global user.name "GitHub Actions"
        git config --global user.email "actions@github.com"
        git add .
        git commit -m "Create preview environment for PR #${{ github.event.number }}"
        git push origin main
        
    - name: Comment on PR
      uses: actions/github-script@v6
      with:
        script: |
          github.rest.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: `🚀 Preview environment deployed!
            
            **URL:** https://${{ env.PREVIEW_NAME }}.preview.example.com
            **ArgoCD:** https://argocd.example.com/applications/${{ env.PREVIEW_NAME }}
            
            This environment will be automatically cleaned up when the PR is closed.`
          })

Disaster Recovery และ Backup

1. ArgoCD Backup Strategy

#!/bin/bash
# argocd-backup.sh

# Backup ArgoCD configuration
kubectl get applications -n argocd -o yaml > applications-backup.yaml
kubectl get appprojects -n argocd -o yaml > appprojects-backup.yaml
kubectl get secrets -n argocd -o yaml > secrets-backup.yaml

# Backup to S3
aws s3 cp applications-backup.yaml s3://mycompany-argocd-backups/$(date +%Y%m%d)/
aws s3 cp appprojects-backup.yaml s3://mycompany-argocd-backups/$(date +%Y%m%d)/

# Cleanup local files
rm *-backup.yaml

echo "ArgoCD backup completed at $(date)"

2. Disaster Recovery Playbook

# disaster-recovery/restore-argocd.yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: argocd-restore
spec:
  template:
    spec:
      containers:
      - name: restore
        image: bitnami/kubectl:latest
        command:
        - /bin/bash
        - -c
        - |
          # Download backups from S3
          aws s3 cp s3://mycompany-argocd-backups/latest/ /tmp/backup/ --recursive
          
          # Restore applications
          kubectl apply -f /tmp/backup/applications-backup.yaml
          kubectl apply -f /tmp/backup/appprojects-backup.yaml
          
          # Wait for ArgoCD to sync
          sleep 60
          
          # Force sync all applications
          for app in $(kubectl get applications -n argocd -o name); do
            argocd app sync $(basename $app) --prune
          done
          
        env:
        - name: AWS_ACCESS_KEY_ID
          valueFrom:
            secretKeyRef:
              name: aws-credentials
              key: access-key-id
        - name: AWS_SECRET_ACCESS_KEY
          valueFrom:
            secretKeyRef:
              name: aws-credentials
              key: secret-access-key
      restartPolicy: Never

เคสจริง: จาก Push Chaos สู่ Pull Harmony

ก่อนใช้ GitOps

ปัญหาที่เจอจริง:

# สถานการณ์วุ่นวายในการ deploy production

# 9:00 AM - Developer A
kubectl apply -f new-feature.yaml -n production

# 9:15 AM - SRE emergency scaling  
kubectl scale deployment myapp --replicas=10 -n production

# 9:30 AM - Developer B (ไม่รู้ว่า A deploy แล้ว)
helm upgrade myapp ./charts --set replicas=2 --set image.tag=v1.2.4

# 9:45 AM - SRE งง "Why is it back to 2 replicas?!"

# 10:00 AM - DevOps rollback (แต่ไม่รู้ว่าจะ rollback ไปสถานะไหน)
kubectl rollout undo deployment/myapp

# 10:30 AM - Production is down! 😱
# Nobody knows what the current desired state should be!

ปัญหาที่เจอบ่อย:

  • Configuration Drift: Production state ไม่ตรงกับ Git
  • Multiple Sources: ไม่รู้ว่าอะไรคือ source of truth
  • No Rollback Strategy: Rollback กลับไปสถานะไหนไม่รู้
  • Access Control: ต้องให้ kubectl access ทุกคน
  • Audit Trail: ไม่รู้ใครเปลี่ยนอะไรเมื่อไหร่

หลังใช้ ArgoCD GitOps

การ Deploy ใหม่:

# Developer wants to deploy
git add deployment.yaml
git commit -m "Update myapp to v1.2.4"
git push origin main

# ArgoCD automatically detects and syncs
# That's it! 🎉

สิ่งที่เปลี่ยนไป:

AspectBefore GitOpsAfter GitOps
Deployment Methodkubectl/helm pushGit push
Source of TruthMultiple sourcesGit only
Access Controlkubectl accessGit permissions
Audit TrailNoneGit history
RollbackManual kubectlGit revert
Drift DetectionManual checkAutomatic
Multi-environmentManual syncAutomated

ผลลัพธ์ที่วัดได้:

MetricBeforeAfterImprovement
Deployment Errors20%2%90% reduction
Config Drift Incidents5/month0/month100% elimination
Rollback Time30 min2 min93% faster
Environment Consistency60%99%65% improvement
Audit Compliance0%100%Complete

Team Benefits:

  • Developers: Focus on code, not deployment
  • SRE: No more emergency kubectl sessions
  • DevOps: Complete control and visibility
  • Security: Role-based access through Git
  • Compliance: Full audit trail

Real-world GitOps Workflow

Development Flow:

# 1. Developer develops feature
git checkout -b feature/new-api
# ... code changes ...
git push origin feature/new-api

# 2. CI/CD builds and tests
# GitHub Actions builds Docker image: myapp:sha-abc123

# 3. Auto-update dev environment
# Update environments/dev/myapp/kustomization.yaml
# ArgoCD syncs to dev cluster automatically

# 4. PR review and merge
# Manual approval required for staging/production updates

# 5. Production deployment
git checkout main
# Update environments/production/myapp/kustomization.yaml
# Set image.tag: v1.2.4
git commit -m "Deploy myapp v1.2.4 to production"
git push origin main

# 6. ArgoCD syncs to production
# Automatic rolling update with health checks

Emergency Rollback:

# Before: Panic mode with kubectl
kubectl rollout undo deployment/myapp  # Hope for the best!

# After: Simple git revert
git revert HEAD
git push origin main
# ArgoCD automatically rolls back to previous state

สรุป: GitOps ที่เปลี่ยนวิธีคิดเรื่อง Deployment

ก่อนรู้จัก GitOps:

  • Kubernetes = Chaos และ unpredictable 😰
  • Deployment = Manual commands และ fear
  • State management = “Hope it works”
  • Rollback = Panic และ guessing
  • Audit = “Who did what when?”

หลังใช้ ArgoCD GitOps:

  • Git เป็น Single Source of Truth 📚
  • Declarative Everything - อธิบายสิ่งที่ต้องการ ไม่ใช่วิธีการ
  • Automatic Drift Detection - รู้ทันทีเมื่อมีการเปลี่ยนแปลง
  • Git-based RBAC - ควบคุม access ผ่าน Git permissions
  • Predictable Deployments - ผลลัพธ์เป็นไปตาม expectation เสมอ

ข้อดีที่ได้จริง:

  • Reliability เพิ่ม 10x: ไม่มี manual errors
  • Security ดีขึ้น: Access control ผ่าน Git
  • Compliance: Audit trail ครบถ้วน
  • Developer Experience: Focus on code, not deployment
  • Operations: Predictable และ repeatable

GitOps Principles ที่ทำให้สำเร็จ:

  • Declarative: อธิบายสิ่งที่ต้องการ
  • Versioned: เก็บใน Git version control
  • Immutable: ไม่แก้ไขโดยตรง ใช้ Git เป็นตัวกลาง
  • Pulled: System pull configuration จาก Git

Best Practices ที่เรียนรู้:

  • Repository Structure: แยก environments ชัดเจน
  • Security: ใช้ Sealed Secrets หรือ External Secrets
  • Monitoring: ติดตาม ArgoCD metrics และ application health
  • Access Control: ใช้ Projects และ RBAC
  • Disaster Recovery: มี backup และ restore plan

Anti-patterns ที่หลีกเลี่ยง:

  • Direct kubectl commands ใน production
  • Mixing GitOps กับ imperative commands
  • ไม่มี proper secret management
  • ไม่ monitor ArgoCD health
  • ไม่ test GitOps workflows

ArgoCD GitOps เหมือน Autopilot สำหรับ Kubernetes

มันทำให้ Kubernetes deployment จาก “งานที่น่ากลัว” เป็น “ระบบที่เชื่อถือได้”

ตอนนี้ไม่สามารถคิดถึง K8s management โดยไม่มี GitOps ได้เลย!

เพราะมันทำให้ Kubernetes เป็นระบบจริงๆ แทนที่จะเป็นเครื่องมือที่ซับซ้อน! 🎯⚡