696 lines
15 KiB
Markdown
696 lines
15 KiB
Markdown
# CI/CD Pipelines & GitOps
|
|
|
|
## CI/CD Pipeline Stages
|
|
|
|
### 1. Source → 2. Build → 3. Test → 4. Deploy → 5. Monitor
|
|
|
|
```
|
|
┌──────────┐ ┌───────┐ ┌──────┐ ┌────────┐ ┌─────────┐
|
|
│ Source │──▶│ Build │──▶│ Test │──▶│ Deploy │──▶│ Monitor │
|
|
│ (Git) │ │ │ │ │ │ │ │ │
|
|
└──────────┘ └───────┘ └──────┘ └────────┘ └─────────┘
|
|
```
|
|
|
|
## GitOps Principles
|
|
|
|
### Core Concepts
|
|
1. **Git as Single Source of Truth**: All configuration in Git
|
|
2. **Declarative**: Desired state defined, not imperative steps
|
|
3. **Automated**: Continuous reconciliation of desired vs actual state
|
|
4. **Auditable**: All changes tracked in Git history
|
|
|
|
### GitOps Workflow
|
|
```
|
|
Developer ──▶ Git Push ──▶ GitOps Controller ──▶ Kubernetes Cluster
|
|
(ArgoCD/Flux)
|
|
│
|
|
▼
|
|
Continuous Sync
|
|
```
|
|
|
|
## ArgoCD Setup
|
|
|
|
### Installation
|
|
```bash
|
|
# Install ArgoCD
|
|
kubectl create namespace argocd
|
|
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
|
|
|
|
# Access ArgoCD UI
|
|
kubectl port-forward svc/argocd-server -n argocd 8080:443
|
|
|
|
# Get initial password
|
|
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
|
|
```
|
|
|
|
### ArgoCD Application
|
|
```yaml
|
|
apiVersion: argoproj.io/v1alpha1
|
|
kind: Application
|
|
metadata:
|
|
name: myapp-production
|
|
namespace: argocd
|
|
finalizers:
|
|
- resources-finalizer.argocd.argoproj.io
|
|
spec:
|
|
project: default
|
|
|
|
source:
|
|
repoURL: https://github.com/myorg/myapp-gitops.git
|
|
targetRevision: main
|
|
path: kubernetes/overlays/production
|
|
|
|
destination:
|
|
server: https://kubernetes.default.svc
|
|
namespace: production
|
|
|
|
syncPolicy:
|
|
automated:
|
|
prune: true # Delete resources not in Git
|
|
selfHeal: true # Sync if cluster state drifts
|
|
allowEmpty: false
|
|
|
|
syncOptions:
|
|
- CreateNamespace=true
|
|
- PrunePropagationPolicy=foreground
|
|
- PruneLast=true
|
|
|
|
retry:
|
|
limit: 5
|
|
backoff:
|
|
duration: 5s
|
|
factor: 2
|
|
maxDuration: 3m
|
|
|
|
ignoreDifferences:
|
|
- group: apps
|
|
kind: Deployment
|
|
jsonPointers:
|
|
- /spec/replicas # Ignore HPA-managed replicas
|
|
```
|
|
|
|
### ArgoCD ApplicationSet (Multi-Environment)
|
|
```yaml
|
|
apiVersion: argoproj.io/v1alpha1
|
|
kind: ApplicationSet
|
|
metadata:
|
|
name: myapp
|
|
namespace: argocd
|
|
spec:
|
|
generators:
|
|
- list:
|
|
elements:
|
|
- env: dev
|
|
cluster: https://kubernetes.default.svc
|
|
- env: staging
|
|
cluster: https://kubernetes.default.svc
|
|
- env: production
|
|
cluster: https://prod-cluster-api.example.com
|
|
|
|
template:
|
|
metadata:
|
|
name: 'myapp-{{env}}'
|
|
spec:
|
|
project: default
|
|
source:
|
|
repoURL: https://github.com/myorg/myapp-gitops.git
|
|
targetRevision: main
|
|
path: 'kubernetes/overlays/{{env}}'
|
|
destination:
|
|
server: '{{cluster}}'
|
|
namespace: '{{env}}'
|
|
syncPolicy:
|
|
automated:
|
|
prune: true
|
|
selfHeal: true
|
|
```
|
|
|
|
## Flux CD Setup
|
|
|
|
### Installation
|
|
```bash
|
|
# Install Flux CLI
|
|
brew install fluxcd/tap/flux
|
|
|
|
# Check prerequisites
|
|
flux check --pre
|
|
|
|
# Bootstrap Flux
|
|
flux bootstrap github \
|
|
--owner=myorg \
|
|
--repository=fleet-infra \
|
|
--branch=main \
|
|
--path=clusters/production \
|
|
--personal
|
|
```
|
|
|
|
### Flux GitRepository
|
|
```yaml
|
|
apiVersion: source.toolkit.fluxcd.io/v1
|
|
kind: GitRepository
|
|
metadata:
|
|
name: myapp
|
|
namespace: flux-system
|
|
spec:
|
|
interval: 1m
|
|
url: https://github.com/myorg/myapp-gitops
|
|
ref:
|
|
branch: main
|
|
secretRef:
|
|
name: git-credentials
|
|
```
|
|
|
|
### Flux Kustomization
|
|
```yaml
|
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
|
kind: Kustomization
|
|
metadata:
|
|
name: myapp-production
|
|
namespace: flux-system
|
|
spec:
|
|
interval: 5m
|
|
path: ./kubernetes/overlays/production
|
|
prune: true
|
|
sourceRef:
|
|
kind: GitRepository
|
|
name: myapp
|
|
healthChecks:
|
|
- apiVersion: apps/v1
|
|
kind: Deployment
|
|
name: myapp
|
|
namespace: production
|
|
timeout: 2m
|
|
```
|
|
|
|
## Progressive Delivery
|
|
|
|
### Canary Deployment with Flagger
|
|
```yaml
|
|
apiVersion: flagger.app/v1beta1
|
|
kind: Canary
|
|
metadata:
|
|
name: myapp
|
|
namespace: production
|
|
spec:
|
|
targetRef:
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
name: myapp
|
|
|
|
service:
|
|
port: 80
|
|
targetPort: 8080
|
|
|
|
analysis:
|
|
interval: 1m
|
|
threshold: 5
|
|
maxWeight: 50
|
|
stepWeight: 10
|
|
|
|
metrics:
|
|
- name: request-success-rate
|
|
thresholdRange:
|
|
min: 99
|
|
interval: 1m
|
|
|
|
- name: request-duration
|
|
thresholdRange:
|
|
max: 500
|
|
interval: 1m
|
|
|
|
webhooks:
|
|
- name: load-test
|
|
url: http://flagger-loadtester/
|
|
timeout: 5s
|
|
metadata:
|
|
cmd: "hey -z 1m -q 10 -c 2 http://myapp-canary.production/"
|
|
|
|
- name: smoke-test
|
|
url: http://flagger-loadtester/
|
|
timeout: 5s
|
|
metadata:
|
|
type: smoke
|
|
cmd: "curl -s http://myapp-canary.production/healthz | grep ok"
|
|
```
|
|
|
|
### Blue/Green Deployment
|
|
```yaml
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: myapp
|
|
namespace: production
|
|
spec:
|
|
selector:
|
|
app: myapp
|
|
version: blue # Switch to 'green' for deployment
|
|
ports:
|
|
- port: 80
|
|
targetPort: 8080
|
|
|
|
---
|
|
# Blue Deployment
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: myapp-blue
|
|
namespace: production
|
|
spec:
|
|
replicas: 3
|
|
selector:
|
|
matchLabels:
|
|
app: myapp
|
|
version: blue
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: myapp
|
|
version: blue
|
|
spec:
|
|
containers:
|
|
- name: myapp
|
|
image: myapp:v1.0.0
|
|
|
|
---
|
|
# Green Deployment
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: myapp-green
|
|
namespace: production
|
|
spec:
|
|
replicas: 3
|
|
selector:
|
|
matchLabels:
|
|
app: myapp
|
|
version: green
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: myapp
|
|
version: green
|
|
spec:
|
|
containers:
|
|
- name: myapp
|
|
image: myapp:v2.0.0
|
|
```
|
|
|
|
## CI/CD Pipeline Examples
|
|
|
|
### GitHub Actions - Complete Pipeline
|
|
See [templates.md](templates.md) for full GitHub Actions pipeline
|
|
|
|
### GitLab CI Pipeline
|
|
```yaml
|
|
variables:
|
|
DOCKER_DRIVER: overlay2
|
|
DOCKER_TLS_CERTDIR: "/certs"
|
|
IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
|
|
|
|
stages:
|
|
- lint
|
|
- build
|
|
- test
|
|
- security
|
|
- deploy
|
|
|
|
# Lint stage
|
|
lint:code:
|
|
stage: lint
|
|
image: node:20-alpine
|
|
script:
|
|
- npm ci
|
|
- npm run lint
|
|
cache:
|
|
key: ${CI_COMMIT_REF_SLUG}
|
|
paths:
|
|
- node_modules/
|
|
|
|
lint:dockerfile:
|
|
stage: lint
|
|
image: hadolint/hadolint:latest-alpine
|
|
script:
|
|
- hadolint Dockerfile
|
|
|
|
# Build stage
|
|
build:image:
|
|
stage: build
|
|
image: docker:24
|
|
services:
|
|
- docker:24-dind
|
|
before_script:
|
|
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
|
script:
|
|
- docker build --cache-from $CI_REGISTRY_IMAGE:latest -t $IMAGE_TAG .
|
|
- docker tag $IMAGE_TAG $CI_REGISTRY_IMAGE:latest
|
|
- docker push $IMAGE_TAG
|
|
- docker push $CI_REGISTRY_IMAGE:latest
|
|
only:
|
|
- main
|
|
- develop
|
|
|
|
# Test stage
|
|
test:unit:
|
|
stage: test
|
|
image: node:20-alpine
|
|
script:
|
|
- npm ci
|
|
- npm run test:unit
|
|
coverage: '/Statements\s*:\s*(\d+\.\d+)%/'
|
|
artifacts:
|
|
reports:
|
|
coverage_report:
|
|
coverage_format: cobertura
|
|
path: coverage/cobertura-coverage.xml
|
|
|
|
test:integration:
|
|
stage: test
|
|
image: $IMAGE_TAG
|
|
services:
|
|
- postgres:15-alpine
|
|
variables:
|
|
POSTGRES_DB: testdb
|
|
POSTGRES_USER: testuser
|
|
POSTGRES_PASSWORD: testpass
|
|
script:
|
|
- npm run test:integration
|
|
|
|
# Security stage
|
|
security:trivy:
|
|
stage: security
|
|
image: aquasec/trivy:latest
|
|
script:
|
|
- trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE_TAG
|
|
allow_failure: true
|
|
|
|
security:secrets:
|
|
stage: security
|
|
image: trufflesecurity/trufflehog:latest
|
|
script:
|
|
- trufflehog filesystem . --fail
|
|
|
|
# Deploy stage
|
|
deploy:dev:
|
|
stage: deploy
|
|
image: bitnami/kubectl:latest
|
|
script:
|
|
- kubectl config use-context dev-cluster
|
|
- kubectl set image deployment/myapp myapp=$IMAGE_TAG -n development
|
|
- kubectl rollout status deployment/myapp -n development
|
|
environment:
|
|
name: development
|
|
only:
|
|
- develop
|
|
|
|
deploy:staging:
|
|
stage: deploy
|
|
image: bitnami/kubectl:latest
|
|
script:
|
|
- kubectl config use-context staging-cluster
|
|
- kubectl set image deployment/myapp myapp=$IMAGE_TAG -n staging
|
|
- kubectl rollout status deployment/myapp -n staging
|
|
environment:
|
|
name: staging
|
|
only:
|
|
- main
|
|
|
|
deploy:production:
|
|
stage: deploy
|
|
image: bitnami/kubectl:latest
|
|
script:
|
|
- kubectl config use-context prod-cluster
|
|
- kubectl set image deployment/myapp myapp=$IMAGE_TAG -n production
|
|
- kubectl rollout status deployment/myapp -n production
|
|
environment:
|
|
name: production
|
|
when: manual
|
|
only:
|
|
- main
|
|
```
|
|
|
|
### Jenkins Pipeline (Jenkinsfile)
|
|
```groovy
|
|
pipeline {
|
|
agent any
|
|
|
|
environment {
|
|
DOCKER_REGISTRY = 'ghcr.io'
|
|
IMAGE_NAME = 'myorg/myapp'
|
|
IMAGE_TAG = "${env.GIT_COMMIT.take(7)}"
|
|
}
|
|
|
|
stages {
|
|
stage('Checkout') {
|
|
steps {
|
|
checkout scm
|
|
}
|
|
}
|
|
|
|
stage('Lint') {
|
|
parallel {
|
|
stage('Lint Code') {
|
|
steps {
|
|
sh 'npm ci'
|
|
sh 'npm run lint'
|
|
}
|
|
}
|
|
stage('Lint Dockerfile') {
|
|
steps {
|
|
sh 'hadolint Dockerfile'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Build') {
|
|
steps {
|
|
script {
|
|
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-credentials') {
|
|
def app = docker.build("${IMAGE_NAME}:${IMAGE_TAG}")
|
|
app.push()
|
|
app.push('latest')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Test') {
|
|
parallel {
|
|
stage('Unit Tests') {
|
|
steps {
|
|
sh 'npm run test:unit'
|
|
}
|
|
}
|
|
stage('Integration Tests') {
|
|
steps {
|
|
sh 'npm run test:integration'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Security Scan') {
|
|
steps {
|
|
sh "trivy image --exit-code 1 --severity HIGH,CRITICAL ${IMAGE_NAME}:${IMAGE_TAG}"
|
|
}
|
|
}
|
|
|
|
stage('Deploy to Dev') {
|
|
when {
|
|
branch 'develop'
|
|
}
|
|
steps {
|
|
kubernetesDeploy(
|
|
configs: 'kubernetes/overlays/dev',
|
|
kubeconfigId: 'dev-kubeconfig'
|
|
)
|
|
}
|
|
}
|
|
|
|
stage('Deploy to Production') {
|
|
when {
|
|
branch 'main'
|
|
}
|
|
steps {
|
|
input message: 'Deploy to production?', ok: 'Deploy'
|
|
kubernetesDeploy(
|
|
configs: 'kubernetes/overlays/prod',
|
|
kubeconfigId: 'prod-kubeconfig'
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
post {
|
|
success {
|
|
slackSend(
|
|
color: 'good',
|
|
message: "✅ Build ${env.BUILD_NUMBER} succeeded: ${env.JOB_NAME}"
|
|
)
|
|
}
|
|
failure {
|
|
slackSend(
|
|
color: 'danger',
|
|
message: "❌ Build ${env.BUILD_NUMBER} failed: ${env.JOB_NAME}"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Infrastructure Testing
|
|
|
|
### Terratest (Go)
|
|
```go
|
|
package test
|
|
|
|
import (
|
|
"testing"
|
|
"github.com/gruntwork-io/terratest/modules/terraform"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestTerraformVPCModule(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
|
|
TerraformDir: "../modules/vpc",
|
|
Vars: map[string]interface{}{
|
|
"name_prefix": "test",
|
|
"vpc_cidr": "10.0.0.0/16",
|
|
},
|
|
})
|
|
|
|
defer terraform.Destroy(t, terraformOptions)
|
|
|
|
terraform.InitAndApply(t, terraformOptions)
|
|
|
|
vpcID := terraform.Output(t, terraformOptions, "vpc_id")
|
|
assert.NotEmpty(t, vpcID)
|
|
}
|
|
```
|
|
|
|
### Checkov (Infrastructure Security)
|
|
```bash
|
|
# Scan Terraform
|
|
checkov -d terraform/ --framework terraform
|
|
|
|
# Scan Kubernetes
|
|
checkov -d kubernetes/ --framework kubernetes
|
|
|
|
# Output JSON
|
|
checkov -d terraform/ --framework terraform -o json > results.json
|
|
|
|
# Skip specific checks
|
|
checkov -d terraform/ --skip-check CKV_AWS_23
|
|
```
|
|
|
|
### Kitchen-Terraform
|
|
```ruby
|
|
# kitchen.yml
|
|
driver:
|
|
name: terraform
|
|
|
|
provisioner:
|
|
name: terraform
|
|
|
|
verifier:
|
|
name: terraform
|
|
systems:
|
|
- name: default
|
|
backend: aws
|
|
controls:
|
|
- vpc_test
|
|
|
|
platforms:
|
|
- name: aws
|
|
|
|
suites:
|
|
- name: default
|
|
driver:
|
|
variables:
|
|
region: us-east-1
|
|
```
|
|
|
|
## Deployment Strategies
|
|
|
|
### Rolling Update (Default in Kubernetes)
|
|
```yaml
|
|
spec:
|
|
strategy:
|
|
type: RollingUpdate
|
|
rollingUpdate:
|
|
maxSurge: 1 # Max pods above desired during update
|
|
maxUnavailable: 0 # Zero-downtime deployment
|
|
```
|
|
|
|
### Recreate (Downtime Acceptable)
|
|
```yaml
|
|
spec:
|
|
strategy:
|
|
type: Recreate # Kill all pods, then create new ones
|
|
```
|
|
|
|
### Canary (Gradual Rollout)
|
|
- Deploy new version alongside old
|
|
- Route small % of traffic to new version
|
|
- Monitor metrics
|
|
- Gradually increase traffic
|
|
- Rollback if issues detected
|
|
|
|
### Blue/Green (Instant Switch)
|
|
- Deploy new version (green) alongside old (blue)
|
|
- Test green environment
|
|
- Switch traffic from blue to green
|
|
- Keep blue for quick rollback
|
|
|
|
## Rollback Procedures
|
|
|
|
### Kubernetes Rollback
|
|
```bash
|
|
# View rollout history
|
|
kubectl rollout history deployment/myapp -n production
|
|
|
|
# Rollback to previous version
|
|
kubectl rollout undo deployment/myapp -n production
|
|
|
|
# Rollback to specific revision
|
|
kubectl rollout undo deployment/myapp -n production --to-revision=3
|
|
|
|
# Check rollout status
|
|
kubectl rollout status deployment/myapp -n production
|
|
```
|
|
|
|
### ArgoCD Rollback
|
|
```bash
|
|
# Rollback application
|
|
argocd app rollback myapp-production
|
|
|
|
# Rollback to specific revision
|
|
argocd app rollback myapp-production 123
|
|
```
|
|
|
|
## CI/CD Best Practices
|
|
|
|
1. **Automate Everything**: From code commit to production
|
|
2. **Fast Feedback**: Fail fast, fix fast
|
|
3. **Test in Production-like**: Staging mirrors production
|
|
4. **Gradual Rollout**: Canary or blue/green for production
|
|
5. **Easy Rollback**: One-click rollback capability
|
|
6. **Security Scanning**: Every stage, every pipeline
|
|
7. **Infrastructure as Code**: No manual changes
|
|
8. **GitOps**: Git as single source of truth
|
|
9. **Observability**: Monitor every deployment
|
|
10. **Post-Deployment Tests**: Smoke tests after deploy
|
|
|
|
---
|
|
|
|
## Pipeline Performance Optimization
|
|
|
|
- **Caching**: Cache dependencies between runs
|
|
- **Parallel Execution**: Run independent stages in parallel
|
|
- **Docker Layer Caching**: Use BuildKit and layer caching
|
|
- **Artifacts**: Share build artifacts between stages
|
|
- **Resource Limits**: Optimize CI runner resources
|
|
- **Skip Unnecessary**: Only run tests for changed code
|