DigitalOcean에서 GitOps 구축하기 (4) - ArgoCD로 GitOps 완성

2025년 12월 04일

devops

# ArgoCD# Kubernetes# GitOps# CI/CD# ApplicationSet# Slack

시리즈 소개

시리즈 구성

  1. 왜 GitOps인가?
  2. Kubernetes 클러스터와 Traefik
  3. ExternalDNS - 자동 DNS 레코드 관리
  4. SOPS + age - 시크릿 암호화
  5. ArgoCD - GitOps 배포 자동화 (현재 글)

ArgoCD란?

ArgoCD는 Kubernetes를 위한 선언적 GitOps CD(Continuous Delivery) 도구입니다.

watch

sync

Git Repository

ArgoCD

Kubernetes Cluster

핵심 기능:

  • Git 저장소의 변경을 자동 감지
  • 클러스터 상태를 Git 상태와 자동 동기화
  • 웹 UI로 배포 상태 시각화
  • 롤백, 히스토리 관리

Pull vs Push 배포

방식 설명 보안
Push CI/CD가 kubectl apply 실행 클러스터 접근 권한 필요
Pull 클러스터 내부에서 Git 감시 외부 접근 불필요 ✅

ArgoCD는 Pull 방식입니다. CI 서버에 클러스터 접근 권한을 줄 필요가 없어 보안상 유리합니다.


ArgoCD 설치

Helmfile 설정

# helmfile.yaml
repositories:
  - name: argo
    url: https://argoproj.github.io/argo-helm

releases:
  - name: argocd
    namespace: argocd
    createNamespace: true
    chart: argo/argo-cd
    version: 8.0.0
    values:
      - ./values/argocd/common.yaml
      - ./values/argocd/{{ .Environment.Name }}.yaml

values 파일

# values/argocd/common.yaml
dex:
  enabled: true

crds:
  install: true
  keep: false

global:
  domain: argocd.example.com
  revisionHistoryLimit: 3

configs:
  params:
    # Traefik이 TLS 종료하므로 ArgoCD는 insecure
    server.insecure: true

  cm:
    url: https://argocd.example.com

    # GitHub OAuth 설정 (선택)
    dex.config: |
      connectors:
        - type: github
          id: github
          name: GitHub
          config:
            clientID: <GITHUB_CLIENT_ID>
            clientSecret: $dex.github.clientSecret
            orgs:
            - name: your-org

  rbac:
    create: true
    policy.default: readonly
    policy.csv: |
      p, role:admin, applications, *, */*, allow
      p, role:admin, projects, *, *, allow
      p, role:admin, applicationsets, *, */*, allow
      p, role:admin, clusters, *, */*, allow
      p, role:admin, repositories, *, *, allow

      g, your-org:admin-team, role:admin
      g, your-org:readonly-team, role:readonly

server:
  ingress:
    enabled: true
    ingressClassName: traefik
    annotations:
      traefik.ingress.kubernetes.io/router.tls: "true"
      traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
      external-dns.alpha.kubernetes.io/hostname: argocd.example.com
    hosts:
      - argocd.example.com
    paths:
      - /
    pathType: Prefix

설치 및 확인

# 설치
helmfile -e dev sync

# Pod 확인
kubectl get pods -n argocd

# 초기 admin 비밀번호 확인
kubectl -n argocd get secret argocd-initial-admin-secret \
  -o jsonpath="{.data.password}" | base64 -d

저장소 구조

GitOps에서는 애플리케이션 코드배포 설정을 분리하는 것이 권장됩니다.

Kubernetes

Git Repositories

CI: build & push

CI: update image tag

ArgoCD watch

helmfile sync

app-repo

애플리케이션 코드

manifest

배포 설정

infra

인프라 설정

Docker Hub

Applications

Infra Services

manifest 저장소 구조

manifest/
├── charts/
│   └── v1/
│       ├── app/           # 일반 앱용 차트
│       └── app-jvm/       # JVM 앱용 차트
├── appsets/
│   ├── dev-v1/
│   │   └── kohi/
│   │       ├── dev-kohi-appsets.yaml
│   │       ├── apps/
│   │       │   └── kohi-core-api/
│   │       │       └── config.json
│   │       └── values/
│   │           └── kohi-core-api/
│   │               └── values.yaml
│   └── prod-v1/
└── README.md

ApplicationSet 설정

ApplicationSet은 하나의 템플릿으로 여러 Application을 생성합니다.

ApplicationSet 정의

# appsets/dev-v1/kohi/dev-kohi-appsets.yaml
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: kohi-dev
spec:
  generators:
    # config.json 파일을 찾아 앱 목록 생성
    - git:
        repoURL: git@github.com:your-org/manifest.git
        revision: main
        files:
          - path: appsets/dev-v1/kohi/apps/**/config.json
  template:
    metadata:
      name: '{{release}}-dev'
      namespace: dev
    spec:
      project: default
      syncPolicy:
        automated:
          prune: true      # Git에서 삭제되면 클러스터에서도 삭제
          selfHeal: true   # 수동 변경 시 자동 복구
      source:
        repoURL: git@github.com:your-org/manifest.git
        targetRevision: main
        path: '{{chart}}'
        helm:
          releaseName: '{{release}}'
          ignoreMissingValueFiles: true
          valueFiles:
            - '../../../appsets/dev-v1/kohi/values/kohi-core-values.yaml'
            - '../../../appsets/dev-v1/kohi/values/{{release}}/values.yaml'
          version: v3
      destination:
        server: https://kubernetes.default.svc
        namespace: dev

config.json

각 앱의 메타데이터를 정의합니다:

// appsets/dev-v1/kohi/apps/kohi-core-api/config.json
{
  "release": "kohi-core-api",
  "chart": "charts/v1/app-jvm"
}

values.yaml

앱별 설정:

# appsets/dev-v1/kohi/values/kohi-core-api/values.yaml
nameOverride: kohi-core-api
fullnameOverride: kohi-core-api

replicaCount: 2

image:
  repository: your-dockerhub/kohi-core-api
  tag: "1.0.0"  # CI에서 업데이트

ingress:
  annotations:
    traefik.ingress.kubernetes.io/router.tls: "true"
    traefik.ingress.kubernetes.io/router.tls.certresolver: letsencrypt
    external-dns.alpha.kubernetes.io/hostname: kohi-api.dev.example.com
  hosts:
    - host: kohi-api.dev.example.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - hosts:
        - kohi-api.dev.example.com

app:
  resources:
    requests:
      cpu: 300m
      memory: 768Mi

service:
  type: ClusterIP
  ports:
    - name: http
      port: 8080
      targetPort: 8080

spring:
  enabled: true
  profiles:
    active: "dev"

secrets:
  name: "kohi-core-api-secrets"

imagePullSecrets:
  - name: dockerhub-secret

Git Repository 연결

SSH 키 Secret 생성

ArgoCD가 private repo에 접근하려면 SSH 키가 필요합니다.

# values/argocd-repo/secrets/secrets.dev.yaml (SOPS 암호화)
sshPrivateKey: ENC[AES256_GCM,data:-----BEGIN OPENSSH PRIVATE KEY-----...,tag:...]
# charts/argocd-repo/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: manifest-repo
  namespace: argocd
  labels:
    argocd.argoproj.io/secret-type: repository
type: Opaque
stringData:
  type: git
  url: git@github.com:your-org/manifest.git
  sshPrivateKey: |
    {{ .Values.sshPrivateKey | nindent 4 }}

전체 배포 흐름

KubernetesArgoCDManifest RepoDocker HubGitHub ActionsApp Repo개발자KubernetesArgoCDManifest RepoDocker HubGitHub ActionsApp Repo개발자git push (코드 변경)trigger workflowdocker build & pushUpdate image tag (PR)Merge PR변경 감지 (3분 주기)Sync (helm upgrade)배포 완료

GitHub Actions 예시 (App Repo)

# .github/workflows/deploy.yml
name: Build and Deploy

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Build and Push
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: your-dockerhub/kohi-core-api:${{ github.sha }}

      - name: Update manifest repo
        uses: peter-evans/create-pull-request@v5
        with:
          repository: your-org/manifest
          token: ${{ secrets.MANIFEST_REPO_TOKEN }}
          branch: update-${{ github.sha }}
          title: "chore: update kohi-core-api to ${{ github.sha }}"
          body: "Auto-generated by GitHub Actions"
          commit-message: "chore: update image tag"
          # values.yaml의 image.tag 업데이트

ArgoCD UI 활용

배포 상태 확인

웹 UI(https://argocd.example.com)에서:

  • Applications: 모든 앱 목록과 동기화 상태
  • Sync Status: OutOfSync, Synced, Unknown
  • Health Status: Healthy, Progressing, Degraded

주요 작업

작업 설명
Sync Git 상태로 강제 동기화
Refresh Git 변경 즉시 감지
Rollback 이전 버전으로 되돌리기
Delete Application 삭제

새 서비스 추가하기

  1. config.json 추가
mkdir -p appsets/dev-v1/kohi/apps/new-service
echo '{"release": "new-service", "chart": "charts/v1/app-jvm"}' > appsets/dev-v1/kohi/apps/new-service/config.json
  1. values.yaml 추가
mkdir -p appsets/dev-v1/kohi/values/new-service
# values.yaml 작성
  1. Git Push
git add .
git commit -m "feat: add new-service"
git push
  1. ArgoCD 자동 감지 & 배포

ApplicationSet이 새 config.json을 감지하고 자동으로 Application을 생성합니다.


Slack 알림 설정

배포 성공/실패 시 Slack으로 알림을 받으면 팀 전체가 배포 상태를 파악할 수 있습니다.

ArgoCD Notifications 설치

ArgoCD Notifications는 ArgoCD Helm Chart에 포함되어 있습니다. values 파일에 설정을 추가합니다.

# values/argocd/common.yaml에 추가
notifications:
  enabled: true

  secret:
    create: false  # SOPS로 별도 관리

  cm:
    create: true

  # Slack 설정
  notifiers:
    service.slack: |
      token: $slack-token

  # 알림 템플릿
  templates:
    template.app-deployed: |
      message: |
        :white_check_mark: *{{.app.metadata.name}}* 배포 완료
        - 환경: {{.app.spec.destination.namespace}}
        - 버전: {{.app.status.sync.revision | substr 0 7}}
        - <{{.context.argocdUrl}}/applications/{{.app.metadata.name}}|ArgoCD에서 보기>

    template.app-sync-failed: |
      message: |
        :x: *{{.app.metadata.name}}* 동기화 실패
        - 환경: {{.app.spec.destination.namespace}}
        - 에러: {{.app.status.operationState.message}}
        - <{{.context.argocdUrl}}/applications/{{.app.metadata.name}}|ArgoCD에서 확인>

    template.app-health-degraded: |
      message: |
        :warning: *{{.app.metadata.name}}* 상태 이상
        - Health: {{.app.status.health.status}}
        - <{{.context.argocdUrl}}/applications/{{.app.metadata.name}}|ArgoCD에서 확인>

  # 알림 트리거
  triggers:
    trigger.on-deployed: |
      - when: app.status.operationState.phase == 'Succeeded' and app.status.health.status == 'Healthy'
        send: [app-deployed]

    trigger.on-sync-failed: |
      - when: app.status.operationState.phase == 'Failed'
        send: [app-sync-failed]

    trigger.on-health-degraded: |
      - when: app.status.health.status == 'Degraded'
        send: [app-health-degraded]

  # 기본 구독 설정
  subscriptions:
    - recipients:
        - slack:deploy-notifications  # Slack 채널명
      triggers:
        - on-deployed
        - on-sync-failed
        - on-health-degraded

Slack Token Secret 생성

  1. Slack App 생성: https://api.slack.com/apps
  2. Bot Token Scopes 추가: chat:write, chat:write.public
  3. Workspace에 앱 설치 후 Bot Token 복사
# values/argocd/secrets/secrets.dev.yaml (SOPS 암호화)
slack-token: ENC[AES256_GCM,data:xoxb-...,tag:...]
# charts/argocd-notifications-secret/templates/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: argocd-notifications-secret
  namespace: argocd
type: Opaque
stringData:
  slack-token: {{ .Values.slackToken | quote }}

특정 앱에만 알림 설정

Application이나 ApplicationSet에 annotation을 추가하면 앱별로 알림 채널을 다르게 설정할 수 있습니다:

# ApplicationSet의 template.metadata에 추가
metadata:
  name: '{{release}}-dev'
  annotations:
    notifications.argoproj.io/subscribe.on-deployed.slack: kohi-deploy
    notifications.argoproj.io/subscribe.on-sync-failed.slack: kohi-alerts

알림 테스트

# ArgoCD Notifications Controller 로그 확인
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-notifications-controller

# 수동으로 Sync 트리거하여 알림 테스트
argocd app sync <app-name>

알림이 정상 작동하면 Slack 채널에 다음과 같은 메시지가 표시됩니다:

✅ kohi-core-api 배포 완료
- 환경: dev
- 버전: a1b2c3d
- ArgoCD에서 보기

트러블슈팅

Sync 실패

# 상세 로그 확인
kubectl logs -n argocd -l app.kubernetes.io/name=argocd-application-controller

# Application 상태 확인
kubectl get application -n argocd <app-name> -o yaml

Repository 연결 실패

# Repository Secret 확인
kubectl get secret -n argocd -l argocd.argoproj.io/secret-type=repository

# SSH 키 테스트
ssh -T git@github.com

마무리

이 시리즈를 통해 다음을 구축했습니다:

DigitalOcean Kubernetes

Git Repositories

helmfile

helmfile

helmfile

App Code

manifest

infra

Traefik

Let's Encrypt

ExternalDNS

DO DNS

ArgoCD

Applications

이제 가능한 것들:

  • git push → 자동 배포
  • 모든 인프라/앱 설정이 Git에 버전 관리
  • 문제 시 git revert로 롤백
  • 새 팀원은 README만 읽으면 온보딩 완료

GitOps로 인프라 관리에 쓰는 시간을 줄이고, 비즈니스 로직 개발에 집중할 수 있게 되었습니다.


참고 자료

© 2025, 미나리와 함께 만들었음