콘텐츠 대표 이미지 - Argo Workflows로 배포 자동화 파이프라인 구성
☁️ 클라우드 / DevOps

Argo Workflows로 배포 자동화 파이프라인 구성

쿠버네티스 위에서 돌아가는 워크플로우 엔진, 제대로 파헤쳐보자 🚀

Argo Workflows 배포 파이프라인 흐름 📦 코드 푸시 Git Repository ⚡ 트리거 Argo Events 🔄 워크플로우 Argo Workflows 실행 🧪 빌드/테스트 Docker Build + 자동 테스트 🚀 배포 Kubernetes 클러스터 반영 ✨ Argo Workflows의 핵심 특징 🐙 DAG 기반 워크플로우 ☸️ 쿠버네티스 네이티브 📋 YAML 선언형 정의 🔁 병렬 실행 지원 🔍 실시간 모니터링

🤔 Argo Workflows가 뭔데? 진짜로

야 솔직히 처음 들으면 "아르고? 그게 뭔 배 이름이야?" 싶잖아ㅋㅋㅋ
근데 이거 알고 나면 진짜 DevOps 세계에서 없으면 안 되는 존재임.

Argo Workflows는 쿠버네티스(Kubernetes) 위에서 동작하는 오픈소스 워크플로우 엔진이야.
2017년에 Applatix라는 회사에서 만들었고, 지금은 CNCF(Cloud Native Computing Foundation)의 인큐베이팅 프로젝트로 관리되고 있어.
쉽게 말하면 "이 작업 하고, 저 작업 하고, 그 다음에 이거 해" 같은 복잡한 작업 흐름을 자동화해주는 도구야.

💡 핵심 포인트!
Argo Workflows는 쿠버네티스 CRD(Custom Resource Definition)를 활용해서
워크플로우를 쿠버네티스 오브젝트로 관리해. 즉, kubectl로 워크플로우를 조회하고 관리할 수 있다는 뜻!
기존 CI/CD 도구들이 별도 서버가 필요한 것과 달리, 쿠버네티스 클러스터 자체가 실행 환경이 됨.

Jenkins 써봤어? Jenkins도 좋은데 솔직히 플러그인 지옥이고 유지보수가 좀 빡세잖아ㅋㅋ
GitHub Actions는 편한데 쿠버네티스 환경에 최적화된 건 아니고.
Argo Workflows는 쿠버네티스 네이티브라서 클러스터 리소스를 그대로 활용하고,
각 워크플로우 스텝이 독립적인 파드(Pod)로 실행돼. 이게 진짜 강점임.

🎯 Argo 생태계 전체 구성

Argo Workflows - 워크플로우 엔진 (오늘의 주인공!)
Argo CD - GitOps 기반 지속적 배포
Argo Events - 이벤트 기반 트리거
Argo Rollouts - 고급 배포 전략 (카나리, 블루/그린)

이 네 가지가 합쳐지면 진짜 완전체 DevOps 파이프라인이 완성됨 🔥

🏆 왜 Argo Workflows를 써야 하냐고?

"그냥 Jenkins 쓰면 되지 않냐"는 말 나올 거 알아ㅋㅋㅋ
근데 쿠버네티스 환경에서 작업하다 보면 Argo Workflows가 왜 게임체인저인지 바로 느껴짐.
비교해볼게.

비교 항목 Jenkins GitHub Actions Argo Workflows
실행 환경 별도 Jenkins 서버 GitHub 호스팅 러너 쿠버네티스 파드
쿠버네티스 통합 플러그인 필요 제한적 네이티브 통합 ✅
병렬 실행 복잡한 설정 matrix 전략 기본 지원 ✅
DAG 워크플로우 제한적 제한적 완전 지원 ✅
리소스 관리 수동 관리 GitHub 관리 K8s 스케줄러 활용
아티팩트 관리 플러그인 내장 S3/GCS 등 통합
🌟 Argo Workflows만의 킬러 기능들

1. DAG(Directed Acyclic Graph) 지원
단순한 순차 실행이 아니라 복잡한 의존성 그래프를 표현할 수 있어.
"A 끝나면 B, C 동시에 실행하고, B, C 둘 다 끝나면 D 실행" 이런 거 YAML로 깔끔하게 표현 가능!

2. 스텝 레벨 아티팩트 전달
각 스텝에서 생성된 파일이나 데이터를 다음 스텝으로 자연스럽게 전달할 수 있어.

3. 재시도 및 타임아웃 설정
스텝별로 재시도 횟수, 타임아웃을 세밀하게 설정 가능.

4. 워크플로우 템플릿
재사용 가능한 워크플로우 컴포넌트를 만들어서 여러 파이프라인에서 공유 가능!

⚙️ 설치하고 기본 설정해보자

자 이제 진짜 실전으로 들어가보자!
쿠버네티스 클러스터가 있다는 전제 하에 진행할게.
없으면 minikube나 kind로 로컬 클러스터 먼저 만들어놔.

1
네임스페이스 생성 및 Argo Workflows 설치

공식 릴리즈 YAML로 설치하는 게 제일 간단함.

# argo 네임스페이스 생성
kubectl create namespace argo

# Argo Workflows 설치 (최신 버전 확인 후 사용)
kubectl apply -n argo -f https://github.com/argoproj/argo-workflows/releases/download/v3.5.4/install.yaml

# 설치 확인
kubectl get pods -n argo
2
Argo CLI 설치

워크플로우를 커맨드라인에서 관리하려면 CLI가 필요해.

# macOS (Homebrew)
brew install argo

# Linux
curl -sLO https://github.com/argoproj/argo-workflows/releases/download/v3.5.4/argo-linux-amd64.gz
gunzip argo-linux-amd64.gz
chmod +x argo-linux-amd64
sudo mv argo-linux-amd64 /usr/local/bin/argo

# 버전 확인
argo version
3
UI 접근 설정

Argo Workflows는 웹 UI를 제공해. 포트 포워딩으로 접근 가능!

# 포트 포워딩
kubectl -n argo port-forward deployment/argo-server 2746:2746

# 브라우저에서 접근
# https://localhost:2746
4
인증 설정 (개발 환경용)

개발 환경에서 빠르게 테스트하려면 인증을 bypass할 수 있어.
(프로덕션에서는 절대 이렇게 하면 안 됨! 보안 설정 필수!)

# argo-server deployment 수정
kubectl patch deployment argo-server \
  -n argo \
  --type='json' \
  -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/args", "value": ["server", "--auth-mode=server"]}]'
💡 팁! 프로덕션 환경에서는 SSO(Single Sign-On)나 ServiceAccount 토큰 기반 인증을 사용해야 해.
Dex나 Keycloak 같은 OIDC 프로바이더와 연동하는 게 일반적인 방법이야.

Argo Workflows YAML 구조 해부 📄 YAML 구조 apiVersion: argoproj.io/v1alpha1 kind: Workflow metadata: generateName: my-pipeline- spec: entrypoint: main-dag templates: - name: main-dag dag: tasks: - name: build template: build-step - name: test dependencies: [build] template: test-step - name: deploy dependencies: [test] 🔍 각 요소 설명 apiVersion / kind Argo Workflows 리소스 타입 선언 metadata 워크플로우 이름, 레이블, 어노테이션 설정 entrypoint 워크플로우 시작점이 되는 템플릿 이름 templates 재사용 가능한 작업 단위 정의 목록 dag / dependencies 작업 간 의존성 정의 (DAG 구조) 💡 dependencies 없으면 병렬 실행!

✍️ 첫 번째 워크플로우 작성해보기

이론은 충분히 봤으니까 이제 진짜 코드 써보자!
간단한 "Hello World" 워크플로우부터 시작해서 점점 복잡하게 만들어볼게.

🌱 Step 1: 가장 기본적인 워크플로우
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: hello-world-
spec:
  entrypoint: hello-world
  templates:
  - name: hello-world
    container:
      image: alpine:3.18
      command: [echo]
      args: ["안녕하세요! Argo Workflows 첫 실행!"]
# 워크플로우 실행
kubectl apply -f hello-world.yaml -n argo

# 또는 argo CLI로 실행
argo submit hello-world.yaml -n argo --watch

# 워크플로우 목록 확인
argo list -n argo

# 로그 확인
argo logs @latest -n argo

실행하면 쿠버네티스가 파드를 하나 생성하고, echo 명령어 실행하고, 완료되면 파드가 사라져.
이게 Argo Workflows의 기본 동작 방식이야. 각 스텝 = 각 파드!

🔗 Step 2: 순차적 스텝 워크플로우
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: sequential-pipeline-
spec:
  entrypoint: main-steps
  templates:
  - name: main-steps
    steps:
    - - name: step-1-checkout
        template: git-checkout
    - - name: step-2-build
        template: docker-build
    - - name: step-3-test
        template: run-tests
    - - name: step-4-deploy
        template: k8s-deploy

  - name: git-checkout
    container:
      image: alpine/git:latest
      command: [sh, -c]
      args: ["echo '코드 체크아웃 완료!'"]

  - name: docker-build
    container:
      image: docker:24-dind
      command: [sh, -c]
      args: ["echo 'Docker 이미지 빌드 완료!'"]

  - name: run-tests
    container:
      image: node:20-alpine
      command: [sh, -c]
      args: ["echo '테스트 통과!'"]

  - name: k8s-deploy
    container:
      image: bitnami/kubectl:latest
      command: [sh, -c]
      args: ["echo '쿠버네티스 배포 완료!'"]
💡 steps vs dag 차이점!
steps: 순차적 실행. 각 단계(이중 대괄호 안)는 병렬, 단계 간은 순차.
dag: 의존성 기반 실행. 더 유연하고 복잡한 워크플로우 표현 가능.
복잡한 파이프라인은 dag를 쓰는 게 훨씬 직관적이야!

🕸️ DAG 워크플로우로 병렬 처리하기

이제 진짜 Argo Workflows의 꽃인 DAG를 써보자!
실제 CI/CD 파이프라인에서는 "빌드 후 단위테스트, 통합테스트 동시에 돌리고, 둘 다 통과하면 배포" 같은 패턴이 많잖아.
이걸 DAG로 표현하면 이렇게 돼:

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: dag-pipeline-
spec:
  entrypoint: ci-cd-dag
  templates:
  - name: ci-cd-dag
    dag:
      tasks:
      # 1단계: 코드 체크아웃 (시작점)
      - name: checkout
        template: git-clone
        arguments:
          parameters:
          - name: repo-url
            value: "https://github.com/myorg/myapp.git"

      # 2단계: 빌드 (checkout 완료 후)
      - name: build
        dependencies: [checkout]
        template: docker-build

      # 3단계: 단위 테스트와 통합 테스트 병렬 실행
      - name: unit-test
        dependencies: [build]
        template: run-unit-tests

      - name: integration-test
        dependencies: [build]
        template: run-integration-tests

      # 4단계: 보안 스캔도 병렬로!
      - name: security-scan
        dependencies: [build]
        template: trivy-scan

      # 5단계: 모든 테스트 통과 후 스테이징 배포
      - name: deploy-staging
        dependencies: [unit-test, integration-test, security-scan]
        template: k8s-deploy
        arguments:
          parameters:
          - name: environment
            value: "staging"

      # 6단계: 스테이징 검증 후 프로덕션 배포
      - name: deploy-production
        dependencies: [deploy-staging]
        template: k8s-deploy
        arguments:
          parameters:
          - name: environment
            value: "production"

  # 템플릿 정의들
  - name: git-clone
    inputs:
      parameters:
      - name: repo-url
    container:
      image: alpine/git:latest
      command: [sh, -c]
      args: ["git clone {{inputs.parameters.repo-url}} /workspace && echo '클론 완료'"]
      volumeMounts:
      - name: workspace
        mountPath: /workspace
    volumes:
    - name: workspace
      emptyDir: {}

  - name: docker-build
    container:
      image: gcr.io/kaniko-project/executor:latest
      args:
      - --context=dir:///workspace
      - --destination=myregistry/myapp:{{workflow.name}}
      - --cache=true

  - name: run-unit-tests
    container:
      image: node:20-alpine
      command: [sh, -c]
      args: ["cd /workspace && npm test -- --testPathPattern=unit"]

  - name: run-integration-tests
    container:
      image: node:20-alpine
      command: [sh, -c]
      args: ["cd /workspace && npm test -- --testPathPattern=integration"]

  - name: trivy-scan
    container:
      image: aquasec/trivy:latest
      command: [trivy]
      args: ["image", "--exit-code", "1", "--severity", "HIGH,CRITICAL", "myregistry/myapp:{{workflow.name}}"]

  - name: k8s-deploy
    inputs:
      parameters:
      - name: environment
    container:
      image: bitnami/kubectl:latest
      command: [sh, -c]
      args: ["kubectl set image deployment/myapp myapp=myregistry/myapp:{{workflow.name}} -n {{inputs.parameters.environment}}"]

이 파이프라인 보면 unit-test, integration-test, security-scan이 동시에 실행되잖아!
Jenkins로 이거 구현하려면 진짜 복잡한데, Argo Workflows는 YAML 몇 줄로 끝남ㅋㅋㅋ
그리고 각 스텝이 독립적인 파드로 실행되니까 리소스 격리도 완벽해.

⚠️ 실제 프로덕션에서 주의할 점!

위 예시는 개념 설명용이야. 실제로는:
1. 볼륨 공유: 스텝 간 파일 공유는 PVC(PersistentVolumeClaim)나 아티팩트를 사용해야 해
2. Kaniko: Docker-in-Docker 대신 Kaniko 사용 권장 (보안상)
3. 시크릿 관리: 레지스트리 인증 정보는 K8s Secret으로 관리
4. 리소스 제한: 각 컨테이너에 resources.requests/limits 설정 필수

DAG 파이프라인 시각화 🕸️ 📥 checkout 코드 클론 🔨 build Docker 빌드 🧪 단위 테스트 unit-test 🔗 통합 테스트 integration-test 🔒 보안 스캔 security-scan 🚀 스테이징 배포 deploy-staging ⚡ 병렬 실행 구간

📦 아티팩트 관리 - 스텝 간 데이터 전달

실제 파이프라인에서 스텝 간에 파일을 주고받아야 하는 경우가 엄청 많잖아.
빌드 결과물을 테스트 스텝에 넘기거나, 테스트 리포트를 저장하거나.
Argo Workflows는 아티팩트(Artifact) 시스템으로 이걸 우아하게 처리해!

🗄️ 지원하는 아티팩트 저장소

S3 (AWS) GCS (Google Cloud) Azure Blob Storage MinIO (온프레미스) Artifactory HTTP Git
⚙️ 아티팩트 저장소 설정 (ConfigMap)
apiVersion: v1
kind: ConfigMap
metadata:
  name: workflow-controller-configmap
  namespace: argo
data:
  artifactRepository: |
    s3:
      bucket: my-argo-artifacts
      endpoint: s3.amazonaws.com
      accessKeySecret:
        name: my-s3-credentials
        key: accessKey
      secretKeySecret:
        name: my-s3-credentials
        key: secretKey
📤 아티팩트 입출력 예시
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: artifact-pipeline-
spec:
  entrypoint: artifact-dag
  templates:
  - name: artifact-dag
    dag:
      tasks:
      - name: build-step
        template: build-and-package
      - name: test-step
        dependencies: [build-step]
        template: test-with-artifact
        arguments:
          artifacts:
          - name: build-output
            from: "{{tasks.build-step.outputs.artifacts.build-result}}"

  # 빌드 스텝: 아티팩트 출력
  - name: build-and-package
    container:
      image: maven:3.9-eclipse-temurin-17
      command: [sh, -c]
      args: ["mvn package -DskipTests && echo '빌드 완료'"]
    outputs:
      artifacts:
      - name: build-result
        path: /workspace/target/myapp.jar
        s3:
          key: "{{workflow.name}}/build/myapp.jar"

  # 테스트 스텝: 아티팩트 입력
  - name: test-with-artifact
    inputs:
      artifacts:
      - name: build-output
        path: /workspace/myapp.jar
    container:
      image: eclipse-temurin:17-jre
      command: [sh, -c]
      args: ["java -jar /workspace/myapp.jar --test && echo '테스트 완료'"]
    outputs:
      artifacts:
      - name: test-report
        path: /workspace/test-results
        s3:
          key: "{{workflow.name}}/reports/test-results"

이렇게 하면 build-step에서 만든 JAR 파일이 S3에 올라가고,
test-step에서 그걸 다운받아서 테스트를 실행해.
스텝 간 파일 공유가 이렇게 깔끔하게 되는 거야!


🔧 WorkflowTemplate으로 재사용성 높이기

여러 프로젝트에서 같은 빌드/배포 로직을 쓴다면?
매번 YAML 복붙하는 건 너무 비효율적이잖아ㅋㅋㅋ
WorkflowTemplate을 쓰면 공통 로직을 한 번만 정의하고 여러 곳에서 참조할 수 있어!

# 공통 템플릿 정의 (한 번만!)
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
  name: common-ci-templates
  namespace: argo
spec:
  templates:
  # Docker 빌드 공통 템플릿
  - name: docker-build
    inputs:
      parameters:
      - name: image-name
      - name: dockerfile-path
        value: "Dockerfile"
      - name: context-path
        value: "."
    container:
      image: gcr.io/kaniko-project/executor:latest
      args:
      - --dockerfile={{inputs.parameters.dockerfile-path}}
      - --context={{inputs.parameters.context-path}}
      - --destination={{inputs.parameters.image-name}}:{{workflow.name}}
      - --cache=true
      - --cache-ttl=24h
      volumeMounts:
      - name: docker-config
        mountPath: /kaniko/.docker
    volumes:
    - name: docker-config
      secret:
        secretName: docker-registry-credentials

  # Kubernetes 배포 공통 템플릿
  - name: k8s-rolling-deploy
    inputs:
      parameters:
      - name: deployment-name
      - name: image-name
      - name: namespace
        value: "default"
    resource:
      action: patch
      manifest: |
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: {{inputs.parameters.deployment-name}}
          namespace: {{inputs.parameters.namespace}}
        spec:
          template:
            spec:
              containers:
              - name: app
                image: {{inputs.parameters.image-name}}:{{workflow.name}}

  # Slack 알림 공통 템플릿
  - name: slack-notify
    inputs:
      parameters:
      - name: message
      - name: channel
        value: "#deployments"
    container:
      image: curlimages/curl:latest
      command: [sh, -c]
      args:
      - |
        curl -X POST -H 'Content-type: application/json' \
        --data '{"channel":"{{inputs.parameters.channel}}","text":"{{inputs.parameters.message}}"}' \
        $SLACK_WEBHOOK_URL
      env:
      - name: SLACK_WEBHOOK_URL
        valueFrom:
          secretKeyRef:
            name: slack-credentials
            key: webhook-url
# 실제 프로젝트에서 공통 템플릿 참조
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: myapp-deploy-
spec:
  entrypoint: deploy-pipeline
  templates:
  - name: deploy-pipeline
    dag:
      tasks:
      # 공통 템플릿 참조! (templateRef 사용)
      - name: build
        templateRef:
          name: common-ci-templates
          template: docker-build
        arguments:
          parameters:
          - name: image-name
            value: "myregistry/myapp"

      - name: deploy-staging
        dependencies: [build]
        templateRef:
          name: common-ci-templates
          template: k8s-rolling-deploy
        arguments:
          parameters:
          - name: deployment-name
            value: "myapp"
          - name: image-name
            value: "myregistry/myapp"
          - name: namespace
            value: "staging"

      - name: notify-success
        dependencies: [deploy-staging]
        templateRef:
          name: common-ci-templates
          template: slack-notify
        arguments:
          parameters:
          - name: message
            value: "✅ myapp 스테이징 배포 완료! 버전: {{workflow.name}}"
💡 ClusterWorkflowTemplate도 있어!
WorkflowTemplate은 특정 네임스페이스에만 적용되는데,
ClusterWorkflowTemplate은 클러스터 전체에서 사용 가능해.
조직 공통 CI/CD 템플릿은 ClusterWorkflowTemplate으로 관리하는 게 베스트 프랙티스야!

⚡ Argo Events로 자동 트리거 설정하기

워크플로우 만들었으면 이제 자동으로 실행되게 해야지!
Argo Events는 다양한 이벤트 소스를 감지해서 Argo Workflows를 자동으로 트리거해주는 도구야.
GitHub 푸시, 웹훅, 메시지 큐, 스케줄 등 다양한 트리거를 지원해.

🎯 Argo Events 핵심 개념

EventSource - 이벤트를 수신하는 소스 정의 (GitHub, Webhook, Kafka 등)
Sensor - 이벤트를 감지하고 트리거를 실행하는 컴포넌트
Trigger - 이벤트 발생 시 실행할 액션 (워크플로우 실행 등)
# Argo Events 설치
kubectl create namespace argo-events
kubectl apply -f https://github.com/argoproj/argo-events/releases/download/v1.9.0/install.yaml
kubectl apply -n argo-events -f https://github.com/argoproj/argo-events/releases/download/v1.9.0/install-validating-webhook.yaml
📡 GitHub 웹훅 EventSource 설정
apiVersion: argoproj.io/v1alpha1
kind: EventSource
metadata:
  name: github-eventsource
  namespace: argo-events
spec:
  service:
    ports:
    - port: 12000
      targetPort: 12000
  github:
    myapp-repo:
      repositories:
      - owner: myorg
        names:
        - myapp
      webhook:
        endpoint: /push
        port: "12000"
        method: POST
        url: https://my-argo-events.example.com
      events:
      - push
      apiToken:
        name: github-access
        key: token
      webhookSecret:
        name: github-webhook-secret
        key: secret
      insecure: false
      active: true
      contentType: json
apiVersion: argoproj.io/v1alpha1
kind: Sensor
metadata:
  name: github-sensor
  namespace: argo-events
spec:
  template:
    serviceAccountName: argo-events-sa
  dependencies:
  - name: github-dep
    eventSourceName: github-eventsource
    eventName: myapp-repo
    filters:
      data:
      # main 브랜치 푸시만 트리거
      - path: body.ref
        type: string
        value:
        - refs/heads/main

  triggers:
  - template:
      name: argo-workflow-trigger
      argoWorkflow:
        group: argoproj.io
        version: v1alpha1
        resource: workflows
        operation: submit
        source:
          resource:
            apiVersion: argoproj.io/v1alpha1
            kind: Workflow
            metadata:
              generateName: github-triggered-
              namespace: argo
            spec:
              workflowTemplateRef:
                name: myapp-ci-pipeline
        parameters:
        # GitHub 이벤트 데이터를 워크플로우 파라미터로 전달
        - src:
            dependencyName: github-dep
            dataKey: body.after
          dest: spec.arguments.parameters.0.value
        - src:
            dependencyName: github-dep
            dataKey: body.repository.clone_url
          dest: spec.arguments.parameters.1.value

이렇게 설정하면 GitHub main 브랜치에 푸시할 때마다 자동으로 CI/CD 파이프라인이 실행돼!
커밋 해시나 레포 URL 같은 이벤트 데이터도 워크플로우 파라미터로 넘길 수 있어서 진짜 유용함.


🚀 실전! 완전한 배포 자동화 파이프라인

자 이제 진짜 실전 파이프라인을 만들어보자!
Node.js 앱을 기준으로 코드 체크아웃부터 프로덕션 배포까지 전 과정을 담아볼게.
재능넷 같은 플랫폼에서도 이런 파이프라인으로 안정적인 배포를 할 수 있어!

apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
  name: nodejs-full-pipeline
  namespace: argo
spec:
  arguments:
    parameters:
    - name: git-repo
      value: "https://github.com/myorg/myapp.git"
    - name: git-branch
      value: "main"
    - name: image-registry
      value: "registry.example.com"
    - name: image-name
      value: "myapp"
    - name: app-version
      value: "{{workflow.name}}"

  entrypoint: full-pipeline
  
  # 볼륨 설정 (스텝 간 공유)
  volumeClaimTemplates:
  - metadata:
      name: workspace
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 2Gi

  templates:
  # ===== 메인 DAG =====
  - name: full-pipeline
    dag:
      tasks:
      # 1. 코드 체크아웃
      - name: checkout
        template: git-checkout

      # 2. 의존성 설치 및 빌드
      - name: install-and-build
        dependencies: [checkout]
        template: npm-build

      # 3. 병렬 실행: 단위테스트, 린트, 타입체크
      - name: unit-tests
        dependencies: [install-and-build]
        template: npm-test
        arguments:
          parameters:
          - name: test-script
            value: "test:unit"

      - name: lint-check
        dependencies: [install-and-build]
        template: npm-test
        arguments:
          parameters:
          - name: test-script
            value: "lint"

      - name: type-check
        dependencies: [install-and-build]
        template: npm-test
        arguments:
          parameters:
          - name: test-script
            value: "type-check"

      # 4. Docker 이미지 빌드 (테스트 통과 후)
      - name: docker-build
        dependencies: [unit-tests, lint-check, type-check]
        template: kaniko-build

      # 5. 이미지 보안 스캔
      - name: image-scan
        dependencies: [docker-build]
        template: trivy-image-scan

      # 6. 스테이징 배포
      - name: staging-deploy
        dependencies: [image-scan]
        template: helm-deploy
        arguments:
          parameters:
          - name: environment
            value: "staging"
          - name: replicas
            value: "2"

      # 7. 스테이징 스모크 테스트
      - name: staging-smoke-test
        dependencies: [staging-deploy]
        template: smoke-test
        arguments:
          parameters:
          - name: target-url
            value: "https://staging.myapp.example.com"

      # 8. 프로덕션 배포 (승인 필요)
      - name: production-deploy
        dependencies: [staging-smoke-test]
        template: helm-deploy
        arguments:
          parameters:
          - name: environment
            value: "production"
          - name: replicas
            value: "5"

      # 9. 완료 알림
      - name: notify-complete
        dependencies: [production-deploy]
        template: send-notification
        arguments:
          parameters:
          - name: status
            value: "SUCCESS"

  # ===== 개별 템플릿들 =====
  
  - name: git-checkout
    container:
      image: alpine/git:2.40.1
      command: [sh, -c]
      args:
      - |
        git clone --depth=1 --branch {{workflow.parameters.git-branch}} \
        {{workflow.parameters.git-repo}} /workspace/app
        echo "체크아웃 완료: $(git -C /workspace/app rev-parse --short HEAD)"
      volumeMounts:
      - name: workspace
        mountPath: /workspace

  - name: npm-build
    container:
      image: node:20-alpine
      command: [sh, -c]
      args:
      - |
        cd /workspace/app
        npm ci --prefer-offline
        npm run build
        echo "빌드 완료!"
      volumeMounts:
      - name: workspace
        mountPath: /workspace
      resources:
        requests:
          memory: "512Mi"
          cpu: "500m"
        limits:
          memory: "1Gi"
          cpu: "1000m"

  - name: npm-test
    inputs:
      parameters:
      - name: test-script
    container:
      image: node:20-alpine
      command: [sh, -c]
      args:
      - |
        cd /workspace/app
        npm run {{inputs.parameters.test-script}}
      volumeMounts:
      - name: workspace
        mountPath: /workspace
    outputs:
      artifacts:
      - name: test-results
        path: /workspace/app/coverage
        optional: true

  - name: kaniko-build
    container:
      image: gcr.io/kaniko-project/executor:v1.19.0
      args:
      - --context=/workspace/app
      - --dockerfile=/workspace/app/Dockerfile
      - --destination={{workflow.parameters.image-registry}}/{{workflow.parameters.image-name}}:{{workflow.parameters.app-version}}
      - --destination={{workflow.parameters.image-registry}}/{{workflow.parameters.image-name}}:latest
      - --cache=true
      - --cache-repo={{workflow.parameters.image-registry}}/{{workflow.parameters.image-name}}-cache
      - --compressed-caching=false
      volumeMounts:
      - name: workspace
        mountPath: /workspace
      - name: docker-config
        mountPath: /kaniko/.docker
    volumes:
    - name: docker-config
      secret:
        secretName: registry-credentials
        items:
        - key: .dockerconfigjson
          path: config.json

  - name: trivy-image-scan
    container:
      image: aquasec/trivy:0.48.0
      command: [trivy]
      args:
      - image
      - --exit-code=1
      - --severity=HIGH,CRITICAL
      - --ignore-unfixed
      - --format=table
      - "{{workflow.parameters.image-registry}}/{{workflow.parameters.image-name}}:{{workflow.parameters.app-version}}"
    retryStrategy:
      limit: "2"
      retryPolicy: "OnFailure"

  - name: helm-deploy
    inputs:
      parameters:
      - name: environment
      - name: replicas
    container:
      image: alpine/helm:3.13.0
      command: [sh, -c]
      args:
      - |
        helm upgrade --install myapp ./charts/myapp \
          --namespace {{inputs.parameters.environment}} \
          --create-namespace \
          --set image.repository={{workflow.parameters.image-registry}}/{{workflow.parameters.image-name}} \
          --set image.tag={{workflow.parameters.app-version}} \
          --set replicaCount={{inputs.parameters.replicas}} \
          --set environment={{inputs.parameters.environment}} \
          --wait \
          --timeout=5m
        echo "{{inputs.parameters.environment}} 배포 완료!"
      volumeMounts:
      - name: workspace
        mountPath: /workspace
      env:
      - name: KUBECONFIG
        value: /root/.kube/config

  - name: smoke-test
    inputs:
      parameters:
      - name: target-url
    container:
      image: curlimages/curl:8.5.0
      command: [sh, -c]
      args:
      - |
        echo "스모크 테스트 시작: {{inputs.parameters.target-url}}"
        for i in 1 2 3 4 5; do
          STATUS=$(curl -s -o /dev/null -w "%{http_code}" {{inputs.parameters.target-url}}/health)
          if [ "$STATUS" = "200" ]; then
            echo "✅ 헬스체크 통과! (HTTP $STATUS)"
            exit 0
          fi
          echo "재시도 $i/5... (HTTP $STATUS)"
          sleep 10
        done
        echo "❌ 스모크 테스트 실패!"
        exit 1

  - name: send-notification
    inputs:
      parameters:
      - name: status
    container:
      image: curlimages/curl:8.5.0
      command: [sh, -c]
      args:
      - |
        curl -X POST $SLACK_WEBHOOK \
          -H 'Content-type: application/json' \
          -d "{\"text\": \"🚀 배포 {{inputs.parameters.status}}! 버전: {{workflow.parameters.app-version}}\"}"
      env:
      - name: SLACK_WEBHOOK
        valueFrom:
          secretKeyRef:
            name: slack-config
            key: webhook-url

이게 진짜 실전에서 쓸 수 있는 완전한 파이프라인이야!
코드 푸시 → 빌드 → 병렬 테스트 → 이미지 빌드 → 보안 스캔 → 스테이징 배포 → 스모크 테스트 → 프로덕션 배포 → 알림
이 모든 게 자동으로 돌아가는 거임ㅋㅋㅋ 진짜 개쩔지 않냐?


🎓 고급 기능들 - 이것까지 알면 진짜 고수

🔄 재시도 전략 (Retry Strategy)
templates:
- name: flaky-step
  retryStrategy:
    limit: "3"              # 최대 3번 재시도
    retryPolicy: "Always"   # Always, OnFailure, OnError, OnTransientError
    backoff:
      duration: "10s"       # 첫 재시도 대기 시간
      factor: "2"           # 지수 백오프 (10s, 20s, 40s)
      maxDuration: "1m"     # 최대 대기 시간
  container:
    image: alpine
    command: [sh, -c]
    args: ["curl https://flaky-api.example.com/data"]
⏱️ 타임아웃 설정
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: timeout-example-
spec:
  # 전체 워크플로우 타임아웃
  activeDeadlineSeconds: 3600  # 1시간
  entrypoint: main
  templates:
  - name: main
    # 특정 스텝 타임아웃
    activeDeadlineSeconds: 300  # 5분
    container:
      image: alpine
      command: [sh, -c]
      args: ["long-running-task.sh"]
🔀 조건부 실행 (When)
templates:
- name: conditional-pipeline
  dag:
    tasks:
    - name: check-environment
      template: get-env

    # 프로덕션 환경일 때만 실행
    - name: production-only-step
      dependencies: [check-environment]
      template: prod-task
      when: "{{tasks.check-environment.outputs.result}} == production"

    # 스테이징이 아닐 때 실행
    - name: non-staging-step
      dependencies: [check-environment]
      template: other-task
      when: "{{tasks.check-environment.outputs.result}} != staging"
🔁 반복 실행 (withItems / withParam)
templates:
- name: parallel-deploy
  dag:
    tasks:
    # 여러 리전에 동시 배포!
    - name: multi-region-deploy
      template: deploy-to-region
      arguments:
        parameters:
        - name: region
          value: "{{item}}"
      withItems:
      - ap-northeast-2    # 서울
      - us-east-1         # 버지니아
      - eu-west-1         # 아일랜드

- name: deploy-to-region
  inputs:
    parameters:
    - name: region
  container:
    image: bitnami/kubectl:latest
    command: [sh, -c]
    args: ["kubectl --context={{inputs.parameters.region}} apply -f deployment.yaml"]

withItems 쓰면 여러 리전에 동시 배포가 이렇게 간단해져!
Jenkins로 이거 구현하려면 진짜 복잡한 Groovy 코드 써야 하는데ㅋㅋㅋ

📊 메트릭 수집 (Prometheus 연동)
templates:
- name: tracked-step
  metrics:
    prometheus:
    # 워크플로우 완료 카운터
    - name: workflow_complete_total
      help: "완료된 워크플로우 수"
      labels:
      - key: status
        value: "{{status}}"
      counter:
        value: "1"
    # 실행 시간 히스토그램
    - name: workflow_duration_seconds
      help: "워크플로우 실행 시간"
      when: "{{status}} == Succeeded"
      gauge:
        value: "{{duration}}"
  container:
    image: alpine
    command: [sh, -c]
    args: ["deploy.sh"]
💡 Grafana 대시보드 연동!
Argo Workflows는 Prometheus 메트릭을 기본으로 제공해.
Grafana에 Argo Workflows 공식 대시보드(ID: 13927)를 임포트하면
워크플로우 성공률, 실행 시간, 실패 패턴 등을 한눈에 볼 수 있어!

🔒 보안 설정 - 프로덕션에서 꼭 해야 하는 것들

보안 설정 안 하면 나중에 진짜 큰일 남ㅋㅋㅋ
프로덕션 환경에서 반드시 챙겨야 할 보안 설정들 정리해줄게.

🛡️ ServiceAccount 및 RBAC 설정
# 워크플로우 전용 ServiceAccount
apiVersion: v1
kind: ServiceAccount
metadata:
  name: workflow-runner
  namespace: argo
---
# 필요한 권한만 부여 (최소 권한 원칙)
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: workflow-role
  namespace: argo
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "patch"]
- apiGroups: [""]
  resources: ["pods/log"]
  verbs: ["get", "watch"]
- apiGroups: ["argoproj.io"]
  resources: ["workflows"]
  verbs: ["get", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: workflow-role-binding
  namespace: argo
subjects:
- kind: ServiceAccount
  name: workflow-runner
roleRef:
  kind: Role
  name: workflow-role
  apiGroup: rbac.authorization.k8s.io
# 워크플로우에서 ServiceAccount 지정
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: secure-workflow-
spec:
  serviceAccountName: workflow-runner  # 전용 SA 사용
  securityContext:
    runAsNonRoot: true    # root 실행 금지
    runAsUser: 1000
  entrypoint: main
  templates:
  - name: main
    container:
      image: alpine
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]
      command: [sh, -c]
      args: ["echo '보안 강화된 워크플로우 실행!'"]
🔑 시크릿 관리 베스트 프랙티스

External Secrets Operator - AWS Secrets Manager, Vault 등과 연동
Sealed Secrets - 암호화된 시크릿을 Git에 저장
Vault Agent Injector - HashiCorp Vault와 직접 연동

절대로 YAML 파일에 시크릿 값을 하드코딩하지 마!
항상 K8s Secret 참조나 외부 시크릿 관리 도구를 사용해야 해.

🔍 모니터링 & 트러블슈팅

파이프라인 만들었으면 잘 돌아가는지 봐야지!
그리고 뭔가 잘못됐을 때 빠르게 원인 찾는 법도 알아야 해.

# 워크플로우 상태 확인
argo list -n argo
argo list -n argo --status Failed  # 실패한 것만

# 특정 워크플로우 상세 정보
argo get my-workflow-xyz -n argo

# 실시간 로그 확인
argo logs my-workflow-xyz -n argo --follow

# 특정 스텝 로그
argo logs my-workflow-xyz -n argo --node-id=my-workflow-xyz-build-123

# 워크플로우 재실행
argo resubmit my-workflow-xyz -n argo

# 실패한 스텝부터 재시작
argo retry my-workflow-xyz -n argo

# 워크플로우 중단
argo terminate my-workflow-xyz -n argo

# 완료된 워크플로우 삭제 (정리)
argo delete --completed -n argo
⚠️ 자주 만나는 에러들과 해결법

1. "Pending" 상태에서 멈춤
→ 리소스 부족이나 노드 셀렉터 문제. kubectl describe pod 로 이벤트 확인

2. "Error" 상태 - ImagePullBackOff
→ 이미지 이름/태그 오류 또는 레지스트리 인증 문제. imagePullSecrets 설정 확인

3. 아티팩트 업로드 실패
→ S3/GCS 권한 문제. IAM 역할이나 서비스 계정 권한 확인

4. 워크플로우 컨트롤러 메모리 부족
→ 동시 실행 워크플로우가 많을 때. controller의 resources.limits 증가

5. 파드 간 볼륨 공유 안 됨
→ ReadWriteOnce PVC는 같은 노드에서만 공유 가능. ReadWriteMany 스토리지 사용 고려
# 워크플로우 컨트롤러 로그 확인
kubectl logs -n argo deployment/workflow-controller -f

# 아르고 서버 로그
kubectl logs -n argo deployment/argo-server -f

# 특정 워크플로우 파드 직접 확인
kubectl get pods -n argo -l workflows.argoproj.io/workflow=my-workflow-xyz
kubectl describe pod -n argo my-workflow-xyz-build-123
kubectl logs -n argo my-workflow-xyz-build-123 -c main

🔗 Argo CD와 연동해서 GitOps 완성하기

Argo Workflows로 CI(빌드/테스트)를 처리하고,
Argo CD로 CD(배포)를 처리하면 진짜 완전한 GitOps 파이프라인이 완성돼!
이 조합이 요즘 쿠버네티스 환경에서 가장 많이 쓰이는 패턴이야.

🔄 GitOps 플로우

1️⃣ 개발자가 앱 코드 푸시 → Argo Events가 감지
2️⃣ Argo Workflows가 CI 실행 (빌드 → 테스트 → 이미지 빌드 → 보안 스캔)
3️⃣ CI 성공 시 Argo Workflows가 Helm 차트 레포의 이미지 태그 업데이트
4️⃣ Argo CD가 Helm 차트 레포 변경 감지
5️⃣ Argo CD가 자동으로 쿠버네티스 클러스터에 배포
6️⃣ 배포 상태 Slack 알림!
# Argo Workflows에서 Helm 차트 레포 업데이트하는 스텝
- name: update-helm-chart
  inputs:
    parameters:
    - name: new-image-tag
  container:
    image: alpine/git:latest
    command: [sh, -c]
    args:
    - |
      # Helm 차트 레포 클론
      git clone https://github.com/myorg/helm-charts.git /tmp/helm-charts
      cd /tmp/helm-charts
      
      # 이미지 태그 업데이트 (yq 사용)
      yq e '.image.tag = "{{inputs.parameters.new-image-tag}}"' \
        -i charts/myapp/values.yaml
      
      # 변경사항 커밋 및 푸시
      git config user.email "argo-bot@myorg.com"
      git config user.name "Argo Bot"
      git add charts/myapp/values.yaml
      git commit -m "chore: update myapp image to {{inputs.parameters.new-image-tag}}"
      git push origin main
      
      echo "Helm 차트 업데이트 완료! Argo CD가 자동 배포할 거야 🚀"
    env:
    - name: GIT_CREDENTIALS
      valueFrom:
        secretKeyRef:
          name: git-credentials
          key: token

이렇게 하면 Argo Workflows가 CI를 완료하고 Helm 차트를 업데이트하면,
Argo CD가 그 변경을 감지해서 자동으로 배포해줘.
완전 자동화된 GitOps 파이프라인 완성! 🎉

이런 자동화 파이프라인 구성 능력은 DevOps 엔지니어로서 진짜 핵심 스킬이야.
재능넷에서도 이런 DevOps 자동화 관련 재능을 찾는 수요가 꾸준히 있으니까 참고해봐!


✨ 베스트 프랙티스 총정리

1
워크플로우 아카이빙 설정
완료된 워크플로우를 DB에 저장해서 히스토리 관리. PostgreSQL 연동 권장.
2
리소스 제한 항상 설정
모든 컨테이너에 resources.requests/limits 설정. 클러스터 안정성 필수!
3
WorkflowTemplate 적극 활용
공통 로직은 WorkflowTemplate으로 분리. DRY(Don't Repeat Yourself) 원칙 준수.
4
TTL 설정으로 자동 정리
완료된 워크플로우가 쌓이면 etcd 부하가 생겨. ttlStrategy로 자동 삭제 설정.
5
PodGC 설정
완료된 파드를 자동으로 삭제해서 클러스터 리소스 절약.
6
병렬 실행 제한
parallelism 설정으로 동시 실행 워크플로우/스텝 수 제한. 클러스터 과부하 방지.
7
Kaniko 사용 (Docker-in-Docker 대신)
보안상 DinD보다 Kaniko 사용 권장. 루트 권한 없이 이미지 빌드 가능.
8
이미지 태그 고정
latest 태그 사용 금지! 항상 특정 버전 태그 사용. 재현성 보장.
# TTL 및 PodGC 설정 예시
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: best-practice-
spec:
  # 완료 후 24시간 뒤 자동 삭제
  ttlStrategy:
    secondsAfterCompletion: 86400
    secondsAfterSuccess: 86400
    secondsAfterFailure: 259200  # 실패는 3일 보관

  # 완료된 파드 자동 삭제
  podGC:
    strategy: OnPodSuccess  # OnPodCompletion, OnWorkflowCompletion, OnWorkflowSuccess

  # 동시 실행 제한
  parallelism: 5

  entrypoint: main
  templates:
  - name: main
    container:
      image: alpine:3.18.5  # 정확한 버전 태그!
      resources:
        requests:
          memory: "128Mi"
          cpu: "100m"
        limits:
          memory: "256Mi"
          cpu: "200m"
      command: [echo]
      args: ["베스트 프랙티스 적용 완료!"]

🎯 마무리 - Argo Workflows 도입 로드맵

자 여기까지 Argo Workflows로 배포 자동화 파이프라인 구성하는 법을 쭉 살펴봤어!
처음엔 좀 복잡해 보여도 하나씩 따라하다 보면 진짜 강력한 도구라는 걸 느끼게 될 거야.

도입 순서를 추천하자면 이렇게 해봐:

📅 단계별 도입 로드맵

1주차: 로컬 환경(minikube/kind)에 Argo Workflows 설치, 기본 워크플로우 실습
2주차: DAG 워크플로우 작성, 아티팩트 관리 실습
3주차: WorkflowTemplate 작성, Argo Events 연동
4주차: 실제 프로젝트 파이프라인 구성, 보안 설정
5주차~: Argo CD 연동, GitOps 완성, 모니터링 구축

Argo Workflows는 단순한 CI/CD 도구를 넘어서 ML 파이프라인, 데이터 처리, 배치 작업
다양한 분야에서도 활용되고 있어. 한 번 익혀두면 정말 다양하게 써먹을 수 있는 도구야!

DevOps 자동화에 관심 있다면 재능넷에서 관련 전문가들의 도움을 받거나,
직접 재능을 공유하는 것도 좋은 방법이야. 이 분야 수요가 진짜 많거든!

쿠버네티스 환경에서 일한다면 Argo Workflows는 이제 선택이 아니라 필수야.
오늘 배운 내용으로 여러분의 배포 파이프라인을 한 단계 업그레이드해봐! 🚀

🎉 Argo Workflows 마스터 체크리스트 ✅ 기본 워크플로우 작성 (steps/dag) ✅ 아티팩트 관리 및 파라미터 전달 ✅ WorkflowTemplate 재사용 ✅ Argo Events 트리거 연동 ✅ 보안 설정 (RBAC, 시크릿) ✅ 재시도/타임아웃/조건부 실행 ✅ Argo CD GitOps 연동 ✅ 모니터링 및 트러블슈팅
댓글 작성

이 글에 대한 여러분의 생각을 들려주세요

댓글 0