CI-CD

DevSecOps 파이프라인

자바바라 2025. 4. 20. 13:03

DevSecOps 파이프라인을 Kubernetes(Kind) 기반 구축 가이드. AWS/GCP/Azure 또는 온프레미스 환경에 맞게 변형 가능.

1. 인프라 아키텍처 개요


graph TD
    A[Developer] -->|Git Push| B(GitHub/GitLab)
    B -->|Webhook| C[Jenkins CI]
    C -->|Build & Scan| D[Docker Registry]
    C -->|Security Scan| E[SonarQube/Trivy]
    D -->|Pull Image| F[ArgoCD]
    F -->|Deploy| G[Kubernetes]
    G -->|Monitoring| H[Prometheus+Grafana]
    H -->|Alert| I[Slack/Email]

2. Kind 클러스터 구성 (Production-Grade)

# kind-ha.yaml (3노드 클러스터)
cat <<EOF > kind-ha.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP
- role: worker
- role: worker
EOF

kind create cluster --name prod-cluster --config kind-ha.yaml
---

3. 필수 툴 설치 (Helm 3)
1) Jenkins (CI 서버)

# jenkins-values-prod.yaml (실무용 설정)
cat <<EOF > jenkins-values-prod.yaml
controller:
  serviceType: NodePort
  servicePort: 8080
  nodePort: 32000
  installPlugins:
    - workflow-aggregator
    - git:4.11.4
    - blueocean:1.25.3
    - docker-workflow:1.28
    - sonar:2.14
    - owasp-dependency-check:2.1.1
    - pipeline-aws:1.43
  resources:
    requests:
      cpu: "1"
      memory: "2Gi"
    limits:
      cpu: "2"
      memory: "4Gi"
  jenkinsAdminPassword: "admin123!"
  adminUser: "admin"
  adminPassword: "admin123!"
agent:
  podTemplate:
    containers:
    - name: jnlp
      resources:
        limits:
          cpu: "500m"
          memory: "1Gi"
EOF

helm upgrade --install jenkins jenkins/jenkins \
  -n jenkins --create-namespace \
  -f jenkins-values-prod.yaml
---

2) SonarQube (정적 분석)

# sonarqube-values-prod.yaml
cat <<EOF > sonarqube-values-prod.yaml
service:
  type: NodePort
  nodePort: 32001
postgresql:
  enabled: true
  persistence:
    size: 20Gi
resources:
  requests:
    memory: 2Gi
    cpu: 1
  limits:
    memory: 4Gi
    cpu: 2
EOF

helm upgrade --install sonarqube sonarqube/sonarqube \
  -n sonarqube --create-namespace \
  -f sonarqube-values-prod.yaml

---


3) Trivy (보안 스캐닝)

# trivy-operator 설치 (지속적 스캐닝)
helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm upgrade --install trivy-operator aqua/trivy-operator \
  -n trivy-system --create-namespace \
  --set="trivy.ignoreUnfixed=true"
---

4. CI/CD 파이프라인 (Jenkinsfile - 실무용)

pipeline {
    agent {
        kubernetes {
            label 'jenkins-agent-pod'
            yaml """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: jnlp
    image: jenkins/inbound-agent:4.11.2-jdk11
    resources:
      limits:
        cpu: 500m
        memory: 1Gi
  - name: docker
    image: docker:20.10-dind
    securityContext:
      privileged: true
    env:
    - name: DOCKER_TLS_CERTDIR
      value: ""
"""
        }
    }
    environment {
        REGISTRY = "registry.example.com"
        DOCKER_CRED = credentials('docker-registry')
        KUBECONFIG = credentials('kubeconfig')
    }
    stages {
        stage('Checkout') {
            steps {
                git branch: 'main', url: 'https://github.com/your-org/your-app.git'
            }
        }
        stage('Unit Test') {
            steps {
                sh 'mvn test'  # or pytest/npm test 등
            }
        }
        stage('Build & Push') {
            steps {
                script {
                    docker.withRegistry("https://${REGISTRY}", 'docker-registry') {
                        def customImage = docker.build("${REGISTRY}/your-app:${env.BUILD_ID}")
                        customImage.push()
                    }
                }
            }
        }
        stage('Security Scan') {
            parallel {
                stage('SonarQube') {
                    steps {
                        withSonarQubeEnv('sonarqube') {
                            sh 'sonar-scanner -Dsonar.projectKey=your-app -Dsonar.java.binaries=target/classes'
                        }
                    }
                }
                stage('Trivy Scan') {
                    steps {
                        sh '''
                        trivy image --exit-code 1 \
                          --severity CRITICAL,HIGH \
                          --ignore-unfixed \
                          ${REGISTRY}/your-app:${BUILD_ID}
                        '''
                    }
                }
            }
        }
        stage('Deploy to Staging') {
            steps {
                sh '''
                kubectl apply -f k8s/staging/ -n staging
                kubectl rollout status deploy/your-app -n staging --timeout=300s
                '''
            }
        }
        stage('Integration Test') {
            steps {
                sh 'curl -sSf http://staging.your-domain.com/health'
            }
        }
        stage('Promote to Prod') {
            when {
                branch 'main'
            }
            steps {
                sh '''
                kubectl apply -f k8s/prod/ -n prod
                kubectl rollout status deploy/your-app -n prod --timeout=300s
                '''
            }
        }
    }
    post {
        always {
            junit '**/target/surefire-reports/*.xml'
            archiveArtifacts artifacts: '**/target/*.jar', fingerprint: true
        }
        failure {
            slackSend channel: '#alerts', message: "Build Failed: ${env.JOB_NAME} ${env.BUILD_NUMBER}"
        }
    }
}
---

5. GitOps 배포 ( ArgoCD 설치, HA 모드 - Production Grade)

# argocd-values-prod.yaml
cat <<EOF > argocd-values-prod.yaml
server:
  service:
    type: NodePort
    nodePort: 32002
  autoscaling:
    enabled: true
    minReplicas: 2
  resources:
    limits:
      cpu: 1
      memory: 1Gi
repoServer:
  replicas: 2
controller:
  replicas: 2
redis:
  ha:
    enabled: true
    resources:
      limits:
        cpu: 500m
        memory: 512Mi
EOF

helm upgrade --install argocd argo/argo-cd \
  -n argocd --create-namespace \
  -f argocd-values-prod.yaml
---

ApplicationSet 예시 (멀티 환경 배포)

# applicationset.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: your-app
  namespace: argocd
spec:
  generators:
  - git:
      repoURL: https://github.com/your-org/gitops-repo.git
      revision: HEAD
      directories:
      - path: "apps/your-app/overlays/*"
  template:
    metadata:
      name: '{{path.basename}}'
    spec:
      project: default
      source:
        repoURL: https://github.com/your-org/gitops-repo.git
        targetRevision: HEAD
        path: '{{path}}'
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{path.basename}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true
        syncOptions:
        - CreateNamespace=true
---

6. 모니터링 스택 (Prometheus + Grafana)

# monitoring-values-prod.yaml
cat <<EOF > monitoring-values-prod.yaml
grafana:
  service:
    type: NodePort
    nodePort: 32003
  adminPassword: "admin123!"
prometheus:
  service:
    type: NodePort
    nodePort: 32004
alertmanager:
  config:
    global:
      slack_api_url: 'https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK'
    route:
      receiver: 'slack-notifications'
      routes:
      - match:
          severity: critical
        receiver: 'slack-critical'
EOF

helm upgrade --install monitoring prometheus-community/kube-prometheus-stack \
  -n monitoring --create-namespace \
  -f monitoring-values-prod.yaml
---

커스텀 대시보드 예시

# grafana-dashboard.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-dashboard
  namespace: monitoring
  labels:
    grafana_dashboard: "1"
data:
  app-metrics.json: |-
    {
      "title": "App Metrics",
      "panels": [
        {
          "title": "HTTP Requests",
          "type": "graph",
          "datasource": "Prometheus",
          "targets": [{
            "expr": "sum(rate(http_requests_total[1m])) by (status_code)",
            "legendFormat": "{{status_code}}"
          }]
        }
      ]
    }
---

7. 인그레스 구성 (Traefik/Nginx)

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: devsecops-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
  - host: "jenkins.example.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: jenkins
            port:
              number: 8080
  - host: "argocd.example.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: argocd-server
            port:
              number: 80
---

8. 고려사항
1. Persistent Storage

   # jenkins-pvc.yaml
   kind: PersistentVolumeClaim
   apiVersion: v1
   metadata:
     name: jenkins-data
   spec:
     storageClassName: standard
     accessModes:
       - ReadWriteOnce
     resources:
       requests:
         storage: 50Gi

2. 백업 전략

   # Velero를 이용한 백업
   velero install \
     --provider aws \
     --bucket your-backup-bucket \
     --secret-file ./credentials-velero \
     --use-restic

3. Zero-Downtime 배포

   # deployment-strategy.yaml
   strategy:
     rollingUpdate:
       maxSurge: 25%
       maxUnavailable: 0
     type: RollingUpdate

4. HPA (Auto Scaling)

   # hpa.yaml
   apiVersion: autoscaling/v2
   kind: HorizontalPodAutoscaler
   metadata:
     name: your-app
   spec:
     scaleTargetRef:
       apiVersion: apps/v1
       kind: Deployment
       name: your-app
     minReplicas: 2
     maxReplicas: 10
     metrics:
     - type: Resource
       resource:
         name: cpu
         target:
           type: Utilization
           averageUtilization: 70
---