K8s Argo Workflows CI/CD 심화

Argo Workflows란?

Argo Workflows는 Kubernetes 네이티브 워크플로 엔진으로, 복잡한 CI/CD 파이프라인, 데이터 처리, ML 학습 등 다단계 작업을 DAG(Directed Acyclic Graph) 형태로 오케스트레이션합니다. CRD 기반으로 동작하며, 각 스텝이 독립 Pod로 실행되어 자원 격리와 확장성을 보장합니다.

ArgoCD가 배포 동기화에 집중한다면, Argo Workflows는 작업 오케스트레이션에 특화되어 있습니다. CI 빌드, 테스트, 이미지 빌드, 통합 테스트를 하나의 워크플로로 연결할 수 있습니다.

핵심 개념: Workflow CRD 구조

Argo Workflows의 기본 단위는 Workflow CRD입니다. 템플릿 기반으로 스텝을 정의하고, 의존성 그래프를 선언합니다.

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: ci-pipeline-
spec:
  entrypoint: main
  arguments:
    parameters:
      - name: repo-url
        value: "https://github.com/org/app.git"
      - name: branch
        value: "main"
  templates:
    - name: main
      dag:
        tasks:
          - name: checkout
            template: git-clone
            arguments:
              parameters:
                - name: repo
                  value: "{{workflow.parameters.repo-url}}"
          - name: test
            template: run-tests
            dependencies: [checkout]
          - name: build-image
            template: docker-build
            dependencies: [checkout]
          - name: deploy
            template: deploy-staging
            dependencies: [test, build-image]

dag 블록에서 dependencies로 실행 순서를 제어합니다. testbuild-imagecheckout 완료 후 병렬 실행되고, deploy는 둘 다 끝난 뒤 실행됩니다.

템플릿 유형별 활용

Argo Workflows는 4가지 템플릿 유형을 제공합니다.

  • Container: 단일 컨테이너 실행 (가장 기본)
  • Script: 인라인 스크립트 실행 (bash, python 등)
  • DAG: 의존성 기반 병렬 실행 그래프
  • Steps: 순차·병렬 스텝 조합
# Container 템플릿
- name: git-clone
  inputs:
    parameters:
      - name: repo
  container:
    image: alpine/git:latest
    command: [git, clone, "{{inputs.parameters.repo}}", /work]
    volumeMounts:
      - name: workdir
        mountPath: /work

# Script 템플릿
- name: run-tests
  script:
    image: node:20-alpine
    command: [sh]
    source: |
      cd /work
      npm ci
      npm test -- --coverage
    volumeMounts:
      - name: workdir
        mountPath: /work

Artifact Passing: 스텝 간 데이터 전달

워크플로에서 가장 중요한 패턴 중 하나는 스텝 간 아티팩트 전달입니다. Argo Workflows는 S3, GCS, MinIO 등을 아티팩트 저장소로 사용합니다.

templates:
  - name: docker-build
    inputs:
      artifacts:
        - name: source
          path: /src
    container:
      image: gcr.io/kaniko-project/executor:latest
      args:
        - --dockerfile=/src/Dockerfile
        - --context=/src
        - --destination=registry.example.com/app:{{workflow.uid}}
        - --digest-file=/tmp/digest
    outputs:
      artifacts:
        - name: build-digest
          path: /tmp/digest
      parameters:
        - name: image-tag
          valueFrom:
            path: /tmp/digest

  - name: deploy-staging
    inputs:
      parameters:
        - name: digest
    container:
      image: bitnami/kubectl:latest
      command: [sh, -c]
      args:
        - |
          kubectl set image deployment/app 
            app=registry.example.com/app@{{inputs.parameters.digest}}

outputs.artifacts로 파일을 내보내고, 다음 스텝의 inputs.artifacts에서 받습니다. 파라미터도 valueFrom.path로 파일 내용을 변수화할 수 있어, 이미지 다이제스트 같은 동적 값 전달에 유용합니다.

WorkflowTemplate: 재사용 가능한 템플릿

반복되는 워크플로 로직을 WorkflowTemplate으로 분리하면 DRY 원칙을 지킬 수 있습니다.

apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
  name: docker-build-template
spec:
  templates:
    - name: kaniko-build
      inputs:
        parameters:
          - name: dockerfile
            default: Dockerfile
          - name: context
          - name: destination
      container:
        image: gcr.io/kaniko-project/executor:latest
        args:
          - --dockerfile={{inputs.parameters.dockerfile}}
          - --context={{inputs.parameters.context}}
          - --destination={{inputs.parameters.destination}}
          - --cache=true
          - --cache-repo={{inputs.parameters.destination}}-cache
---
# Workflow에서 참조
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: app-build-
spec:
  entrypoint: build
  templates:
    - name: build
      steps:
        - - name: build-image
            templateRef:
              name: docker-build-template
              template: kaniko-build
            arguments:
              parameters:
                - name: context: s3://artifacts/source
                - name: destination
                  value: registry.example.com/app:v1.2.0

templateRef로 외부 WorkflowTemplate를 참조하면, 빌드 로직 변경 시 템플릿만 수정하면 모든 워크플로에 반영됩니다.

Retry·Timeout 전략

CI/CD 파이프라인에서 일시적 실패(네트워크 오류, 이미지 풀 실패 등)는 흔합니다. Argo Workflows의 재시도 전략으로 안정성을 높일 수 있습니다.

templates:
  - name: flaky-integration-test
    retryStrategy:
      limit: 3
      retryPolicy: "OnError"      # OnError | OnFailure | Always
      backoff:
        duration: "30s"
        factor: 2
        maxDuration: "5m"
    activeDeadlineSeconds: 600     # 10분 타임아웃
    container:
      image: app-test:latest
      command: [npm, run, test:e2e]
      resources:
        requests:
          memory: "512Mi"
          cpu: "500m"
        limits:
          memory: "1Gi"
          cpu: "1"
  • retryPolicy: OnError는 시스템 오류(OOM 등)만, OnFailure는 exit code ≠ 0, Always는 모든 실패
  • backoff: 지수 백오프로 재시도 간격 증가 (30s → 60s → 120s)
  • activeDeadlineSeconds: 전체 실행 시간 제한

Sensor + EventSource: 이벤트 기반 트리거

Argo Events와 결합하면 Git push, webhook, cron 등 이벤트로 워크플로를 자동 트리거할 수 있습니다.

# EventSource: GitHub webhook 수신
apiVersion: argoproj.io/v1alpha1
kind: EventSource
metadata:
  name: github-webhook
spec:
  github:
    app-repo:
      repositories:
        - owner: org
          names: [app]
      webhook:
        endpoint: /push
        port: "12000"
      events: [push, pull_request]
      apiToken:
        name: github-token
        key: token
---
# Sensor: push 이벤트 → 워크플로 트리거
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
  name: ci-trigger
spec:
  dependencies:
    - name: github-push
      eventSourceName: github-webhook
      eventName: app-repo
      filters:
        data:
          - path: body.ref
            type: string
            value: ["refs/heads/main"]
  triggers:
    - template:
        name: run-ci
        argoWorkflow:
          operation: submit
          source:
            resource:
              apiVersion: argoproj.io/v1alpha1
              kind: Workflow
              metadata:
                generateName: ci-
              spec:
                workflowTemplateRef:
                  name: ci-pipeline-template
          parameters:
            - src:
                dependencyName: github-push
                dataKey: body.after
              dest: spec.arguments.parameters.0.value

이 구성으로 main 브랜치 push 시 자동으로 CI 파이프라인이 실행됩니다. filters로 특정 브랜치만 필터링하고, 커밋 SHA를 워크플로 파라미터로 전달합니다.

Volume·캐시 전략

스텝 간 대용량 데이터 공유나 빌드 캐시에는 PVC 볼륨이 효과적입니다.

spec:
  volumeClaimTemplates:
    - metadata:
        name: work
      spec:
        accessModes: [ReadWriteOnce]
        resources:
          requests:
            storage: 10Gi
        storageClassName: fast-ssd
  templates:
    - name: checkout
      container:
        image: alpine/git
        command: [git, clone, --depth=1, "{{workflow.parameters.repo}}", /work/src]
        volumeMounts:
          - name: work
            mountPath: /work
    - name: build
      container:
        image: node:20
        command: [sh, -c, "cd /work/src && npm ci --cache /work/.npm && npm run build"]
        volumeMounts:
          - name: work
            mountPath: /work

volumeClaimTemplates로 워크플로 전용 PVC를 생성하면, 모든 스텝이 같은 볼륨을 마운트하여 git clone 결과를 재사용합니다. npm 캐시도 볼륨에 저장해 반복 빌드 속도를 높입니다.

실전 CI/CD 파이프라인 완성본

지금까지의 개념을 조합한 실전 파이프라인입니다.

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: full-ci-
  labels:
    app: myapp
spec:
  entrypoint: ci-pipeline
  serviceAccountName: argo-workflow
  ttlStrategy:
    secondsAfterCompletion: 86400  # 24시간 후 정리
  arguments:
    parameters:
      - name: repo
      - name: commit-sha
      - name: branch
        value: main
  volumeClaimTemplates:
    - metadata:
        name: workspace
      spec:
        accessModes: [ReadWriteOnce]
        resources:
          requests:
            storage: 5Gi

  templates:
    - name: ci-pipeline
      dag:
        tasks:
          - name: clone
            template: git-checkout
          - name: lint
            template: eslint
            dependencies: [clone]
          - name: unit-test
            template: jest-test
            dependencies: [clone]
          - name: build
            template: kaniko
            dependencies: [lint, unit-test]
          - name: scan
            template: trivy-scan
            dependencies: [build]
          - name: deploy
            template: kubectl-apply
            dependencies: [scan]
            when: "{{workflow.parameters.branch}} == main"

    - name: git-checkout
      container:
        image: alpine/git
        command: [sh, -c]
        args: ["git clone --depth=1 -b {{workflow.parameters.branch}} {{workflow.parameters.repo}} /workspace/src"]
        volumeMounts:
          - { name: workspace, mountPath: /workspace }

    - name: eslint
      container:
        image: node:20-alpine
        command: [sh, -c, "cd /workspace/src && npm ci && npx eslint src/"]
        volumeMounts:
          - { name: workspace, mountPath: /workspace }

    - name: jest-test
      retryStrategy:
        limit: 2
        retryPolicy: OnFailure
      container:
        image: node:20-alpine
        command: [sh, -c, "cd /workspace/src && npm ci && npm test -- --ci --coverage"]
        volumeMounts:
          - { name: workspace, mountPath: /workspace }

    - name: kaniko
      container:
        image: gcr.io/kaniko-project/executor:latest
        args:
          - --context=/workspace/src
          - --destination=registry.example.com/app:{{workflow.parameters.commit-sha}}
          - --cache=true
        volumeMounts:
          - { name: workspace, mountPath: /workspace }

    - name: trivy-scan
      container:
        image: aquasec/trivy:latest
        command: [trivy, image, --severity, HIGH,CRITICAL, --exit-code, "1",
                  "registry.example.com/app:{{workflow.parameters.commit-sha}}"]

    - name: kubectl-apply
      container:
        image: bitnami/kubectl:latest
        command: [sh, -c]
        args:
          - |
            kubectl set image deployment/app 
              app=registry.example.com/app:{{workflow.parameters.commit-sha}} 
              -n production
            kubectl rollout status deployment/app -n production --timeout=300s

이 파이프라인은 clone → lint/test 병렬 → 이미지 빌드 → 보안 스캔 → 배포 순서로 진행됩니다. when 조건으로 main 브랜치만 배포하고, ttlStrategy로 완료된 워크플로를 자동 정리합니다.

운영 팁: 모니터링·디버깅

  • argo CLI: argo list, argo get, argo logs로 워크플로 상태와 로그 확인
  • Argo UI: DAG 시각화로 병목 스텝 식별, 재실행 버튼으로 실패 스텝만 재시도
  • Prometheus 메트릭: argo_workflows_count, argo_workflows_error_count 등으로 성공률 모니터링
  • 리소스 제한: parallelism 필드로 동시 실행 Pod 수 제한, 클러스터 과부하 방지

관련 글

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux