วันที่ 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 หลักการพื้นฐาน:
- Declarative: ระบบทั้งหมดอธิบายด้วย declarative configuration
- Versioned: Configuration เก็บใน Git (version control)
- Immutable: ไม่แก้ไขโดยตรงใน cluster
- 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! 🎉
สิ่งที่เปลี่ยนไป:
| Aspect | Before GitOps | After GitOps |
|---|---|---|
| Deployment Method | kubectl/helm push | Git push |
| Source of Truth | Multiple sources | Git only |
| Access Control | kubectl access | Git permissions |
| Audit Trail | None | Git history |
| Rollback | Manual kubectl | Git revert |
| Drift Detection | Manual check | Automatic |
| Multi-environment | Manual sync | Automated |
ผลลัพธ์ที่วัดได้:
| Metric | Before | After | Improvement |
|---|---|---|---|
| Deployment Errors | 20% | 2% | 90% reduction |
| Config Drift Incidents | 5/month | 0/month | 100% elimination |
| Rollback Time | 30 min | 2 min | 93% faster |
| Environment Consistency | 60% | 99% | 65% improvement |
| Audit Compliance | 0% | 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 เป็นระบบจริงๆ แทนที่จะเป็นเครื่องมือที่ซับซ้อน! 🎯⚡