Skip to main content

Best Practices CI/CD

Pengenalan

Dokumen ini berisi kumpulan best practices untuk implementasi CI/CD dengan Gitea, Gitea Runner, dan Kubernetes. Mengikuti panduan ini akan membantu memastikan pipeline CI/CD yang reliable, secure, dan maintainable.

1. Repository Structure

Organized Directory Layout

project-root/
├── .gitea/
│ └── workflows/
│ ├── ci.yml # Continuous Integration
│ ├── cd.yml # Continuous Deployment
│ ├── release.yml # Release management
│ └── cleanup.yml # Maintenance tasks
├── k8s/
│ ├── base/ # Base configurations
│ │ ├── deployment.yaml
│ │ ├── service.yaml
│ │ └── kustomization.yaml
│ ├── overlays/
│ │ ├── dev/ # Development environment
│ │ ├── staging/ # Staging environment
│ │ └── production/ # Production environment
│ └── README.md
├── scripts/
│ ├── build.sh
│ ├── test.sh
│ └── deploy.sh
├── docs/ # Documentation
├── src/ # Source code
├── tests/ # Test files
├── .dockerignore
├── .gitignore
├── Dockerfile
├── Dockerfile.dev
└── README.md

Keuntungan Struktur Ini

  • Separation of concerns: Workflow, Kubernetes manifests, dan scripts terpisah
  • Environment management: Overlay untuk multiple environments
  • Maintainability: Mudah di-navigate dan di-update
  • Documentation: Dedicated folder untuk docs

2. Workflow Design Patterns

Pattern 1: Single Pipeline

Satu workflow untuk semua stages.

name: Complete Pipeline

on:
push:
branches: [main, develop]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci
- run: npm run build

test:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci
- run: npm test

deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: ./scripts/deploy.sh

Kapan digunakan:

  • Small to medium projects
  • Simple deployment process
  • Quick feedback needed

Pattern 2: Separate Workflows

Multiple workflows untuk different concerns.

# .gitea/workflows/ci.yml
name: CI
on:
push:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: npm test

---
# .gitea/workflows/cd.yml
name: CD
on:
workflow_run:
workflows: ["CI"]
types: [completed]
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: ./scripts/deploy.sh

Kapan digunakan:

  • Large projects
  • Complex deployment process
  • Different triggers untuk CI dan CD

Pattern 3: Reusable Workflows

# .gitea/workflows/reusable-build.yml
name: Reusable Build

on:
workflow_call:
inputs:
node-version:
required: true
type: string

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v3
with:
node-version: ${{ inputs.node-version }}
- run: npm ci
- run: npm run build

---
# .gitea/workflows/main.yml
name: Main Pipeline

on: [push]

jobs:
build-node-18:
uses: ./.gitea/workflows/reusable-build.yml
with:
node-version: '18'

build-node-20:
uses: ./.gitea/workflows/reusable-build.yml
with:
node-version: '20'

3. Secrets Management

Hierarchy of Secrets

Repository Secrets
├── Development
│ ├── DEV_API_KEY
│ └── DEV_DB_PASSWORD
├── Staging
│ ├── STAGING_API_KEY
│ └── STAGING_DB_PASSWORD
└── Production
├── PROD_API_KEY
└── PROD_DB_PASSWORD

Best Practices

1. Environment-specific Secrets

jobs:
deploy-staging:
environment: staging
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.STAGING_API_KEY }}

2. Never Log Secrets

- name: Use secret
run: |
# BAD - akan log secret
echo "API_KEY=${{ secrets.API_KEY }}"

# GOOD - tidak log secret
export API_KEY="${{ secrets.API_KEY }}"
./script.sh

3. Rotate Regularly

# Script untuk rotate secrets
#!/bin/bash
NEW_TOKEN=$(generate_token)
kubectl create secret generic app-secret \
--from-literal=token=$NEW_TOKEN \
--dry-run=client -o yaml | kubectl apply -f -

4. Use External Secret Managers

# Menggunakan external-secrets operator
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: gitea-docs-secrets
spec:
refreshInterval: 1h
secretStoreRef:
name: vault-backend
kind: SecretStore
target:
name: gitea-docs-secrets
data:
- secretKey: api-key
remoteRef:
key: secret/data/app
property: api-key

4. Docker Best Practices

Multi-stage Builds

# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production=false
COPY . .
RUN npm run build

# Production stage
FROM nginx:alpine AS production
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Image Tagging Strategy

tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
type=raw,value=latest,enable={{is_default_branch}}

Contoh hasil:

  • mainlatest, main-abc1234
  • developdevelop, develop-def5678
  • v1.2.31.2.3, 1.2, v1.2.3

Security Scanning

- name: Scan image
uses: aquasecurity/trivy-action@master
with:
image-ref: myimage:latest
format: 'table'
exit-code: '1'
ignore-unfixed: true
severity: 'CRITICAL,HIGH'

Image Optimization

# Use specific versions
FROM node:20.10.0-alpine3.18

# Combine RUN commands
RUN apk add --no-cache git curl && \
npm ci && \
npm cache clean --force

# Use .dockerignore
# .dockerignore file:
node_modules
.git
.gitignore
*.md
tests/

5. Testing Strategy

Test Pyramid

        /\
/ \
/ E2E \ E2E Tests (Few)
/------\
/ \
/Integration\ Integration Tests (Some)
/------------\
/ \
/ Unit Tests \ Unit Tests (Many)
------------------

Implement in CI/CD

jobs:
unit-tests:
runs-on: ubuntu-latest
steps:
- run: npm run test:unit
- uses: codecov/codecov-action@v3

integration-tests:
needs: unit-tests
runs-on: ubuntu-latest
steps:
- run: npm run test:integration

e2e-tests:
needs: integration-tests
runs-on: ubuntu-latest
steps:
- run: npm run test:e2e

Parallel Testing

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
test-group: [unit, integration, e2e]
node-version: [18, 20, 22]
steps:
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm run test:${{ matrix.test-group }}

6. Deployment Strategies

Blue-Green Deployment

# Deployment blue
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-blue
spec:
replicas: 3
template:
metadata:
labels:
app: myapp
version: blue

---
# Deployment green
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-green
spec:
replicas: 3
template:
metadata:
labels:
app: myapp
version: green

---
# Service switch
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
version: green # Switch to green

Workflow:

jobs:
deploy:
steps:
- name: Deploy to green
run: kubectl apply -f k8s/deployment-green.yaml

- name: Wait for green to be ready
run: kubectl rollout status deployment/app-green

- name: Run smoke tests
run: ./scripts/smoke-test.sh green

- name: Switch traffic to green
run: kubectl patch service myapp -p '{"spec":{"selector":{"version":"green"}}}'

- name: Verify production
run: ./scripts/verify.sh

- name: Scale down blue
run: kubectl scale deployment/app-blue --replicas=0

Canary Deployment

# Main deployment (90%)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-stable
spec:
replicas: 9
template:
metadata:
labels:
app: myapp
track: stable

---
# Canary deployment (10%)
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-canary
spec:
replicas: 1
template:
metadata:
labels:
app: myapp
track: canary
spec:
containers:
- name: app
image: myapp:new-version
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
replicas: 3

Workflow:

jobs:
deploy:
steps:
- name: Update image
run: |
kubectl set image deployment/myapp \
myapp=myapp:${{ github.sha }}

- name: Monitor rollout
run: |
kubectl rollout status deployment/myapp --timeout=5m

- name: Verify deployment
run: ./scripts/verify.sh

- name: Rollback on failure
if: failure()
run: kubectl rollout undo deployment/myapp

7. Monitoring dan Observability

Logging Best Practices

# Structured logging
- name: Log structured data
run: |
echo "::group::Build Information"
echo "Commit: ${{ github.sha }}"
echo "Branch: ${{ github.ref }}"
echo "Author: ${{ github.actor }}"
echo "::endgroup::"

Metrics Collection

# Prometheus metrics
apiVersion: v1
kind: Service
metadata:
name: myapp
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"

Alerting

# AlertManager configuration
groups:
- name: deployment_alerts
rules:
- alert: DeploymentFailed
expr: kube_deployment_status_replicas_unavailable > 0
for: 5m
labels:
severity: critical
annotations:
summary: "Deployment {{ $labels.deployment }} has unavailable replicas"

Dashboard

# Grafana dashboard JSON
{
"dashboard": {
"title": "CI/CD Pipeline",
"panels": [
{
"title": "Build Duration",
"targets": [{
"expr": "workflow_run_duration_seconds"
}]
},
{
"title": "Deployment Frequency",
"targets": [{
"expr": "rate(deployments_total[1h])"
}]
}
]
}
}

8. Performance Optimization

Caching Strategy

- name: Cache dependencies
uses: actions/cache@v3
with:
path: |
~/.npm
~/.cache
node_modules
key: ${{ runner.os }}-deps-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-deps-

- name: Cache build output
uses: actions/cache@v3
with:
path: build/
key: build-${{ github.sha }}

Docker Layer Caching

- name: Build with cache
uses: docker/build-push-action@v4
with:
context: .
cache-from: type=registry,ref=myimage:buildcache
cache-to: type=registry,ref=myimage:buildcache,mode=max

Artifact Management

- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: build-artifacts
path: build/
retention-days: 7 # Auto-cleanup after 7 days

- name: Download artifacts
uses: actions/download-artifact@v3
with:
name: build-artifacts

9. Security Best Practices

1. Scan Dependencies

- name: Audit dependencies
run: npm audit --audit-level=moderate

- name: Check for vulnerabilities
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

2. Image Scanning

- name: Scan Docker image
uses: aquasecurity/trivy-action@master
with:
image-ref: myimage:latest
severity: CRITICAL,HIGH
exit-code: 1

3. SBOM Generation

- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: myimage:latest
format: spdx-json
output-file: sbom.spdx.json

4. Sign Images

- name: Install Cosign
uses: sigstore/cosign-installer@main

- name: Sign image
run: |
cosign sign --key cosign.key myimage:latest

5. Policy Enforcement

# OPA Policy
package kubernetes.admission

deny[msg] {
input.request.kind.kind == "Pod"
not input.request.object.spec.securityContext.runAsNonRoot
msg = "Containers must not run as root"
}

10. Error Handling

Retry Logic

- name: Deploy with retry
uses: nick-invision/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
retry_wait_seconds: 30
command: kubectl apply -f k8s/

Graceful Degradation

- name: Deploy to primary
id: primary
run: ./deploy.sh primary
continue-on-error: true

- name: Deploy to backup
if: steps.primary.outcome == 'failure'
run: ./deploy.sh backup

Notification on Failure

- name: Notify on failure
if: failure()
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Deployment failed! 🔴'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}

11. Documentation

Workflow Documentation

name: Build and Deploy

# Purpose: Build Docker image and deploy to Kubernetes
# Triggers: Push to main branch
# Requirements:
# - DOCKER_USERNAME secret
# - DOCKER_PASSWORD secret
# - KUBE_CONFIG secret

on:
push:
branches: [main]

README Template

# Project CI/CD

## Pipeline Overview

- **CI**: Runs on every push and PR
- **CD**: Deploys to staging on merge to develop
- **Production**: Manual approval required

## Required Secrets

| Secret | Description | Where to get |
|--------|-------------|--------------|
| DOCKER_USERNAME | Docker Hub username | Docker Hub account |
| DOCKER_PASSWORD | Docker Hub token | Docker Hub → Security |
| KUBE_CONFIG | Kubernetes config | Base64 encoded kubeconfig |

## Deployment Process

1. Code merged to main
2. CI pipeline runs tests
3. Docker image built and pushed
4. Deployed to staging automatically
5. Manual approval for production
6. Deployed to production

12. Cost Optimization

Efficient Runner Usage

# Use smaller runners for simple tasks
jobs:
lint:
runs-on: ubuntu-latest # Sufficient for linting

build:
runs-on: ubuntu-latest-4core # Heavy build tasks

Conditional Execution

jobs:
deploy:
# Only deploy on main branch
if: github.ref == 'refs/heads/main'

expensive-tests:
# Only on tags
if: startsWith(github.ref, 'refs/tags/')

Resource Limits

spec:
containers:
- name: app
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"

13. Compliance dan Audit

Audit Logging

- name: Log deployment
run: |
echo "Deployment initiated" >> audit.log
echo "User: ${{ github.actor }}" >> audit.log
echo "Time: $(date -u)" >> audit.log
echo "Version: ${{ github.sha }}" >> audit.log

Change Approval

jobs:
deploy-production:
environment:
name: production
# Requires manual approval from protected reviewers
steps:
- run: ./deploy.sh

Compliance Checks

- name: Check compliance
run: |
# Verify all required labels exist
kubectl get pods -n production -o json | \
jq '.items[] | select(.metadata.labels.owner == null)'

14. Troubleshooting Guide

Common Issues

Issue 1: Pipeline Timeout

# Increase timeout
jobs:
build:
timeout-minutes: 30 # Default is 360

Issue 2: Intermittent Failures

# Add retry logic
- name: Flaky test
uses: nick-invision/retry@v2
with:
max_attempts: 3
command: npm test

Issue 3: Resource Constraints

# Optimize resource usage
- name: Build
run: |
export NODE_OPTIONS="--max-old-space-size=4096"
npm run build

Debug Mode

- name: Debug information
run: |
echo "::debug::Current directory: $(pwd)"
echo "::debug::Environment: $(env | sort)"
echo "::debug::Disk usage: $(df -h)"

15. Checklist untuk Production

Pre-deployment Checklist

  • All tests passing
  • Security scans completed
  • Documentation updated
  • Secrets configured
  • Monitoring setup
  • Backup strategy in place
  • Rollback plan tested
  • Team notified

Post-deployment Checklist

  • Verify application health
  • Check logs for errors
  • Monitor metrics
  • Verify database migrations
  • Test critical user paths
  • Update status page
  • Document changes

Kesimpulan

Mengikuti best practices ini akan membantu:

  • Reliability: Pipeline yang stable dan predictable
  • Security: Protection terhadap vulnerabilities
  • Performance: Faster build dan deployment
  • Maintainability: Easy to understand dan update
  • Scalability: Handle increased workload
  • Cost efficiency: Optimal resource utilization
  • Compliance: Meet regulatory requirements

Terus iterate dan improve pipeline sesuai kebutuhan project dan team.