시리즈 소개
시리즈 구성
- 왜 GitOps인가?
- Kubernetes 클러스터와 Traefik
- ExternalDNS - 자동 DNS 레코드 관리
- SOPS + age - 시크릿 암호화
- ArgoCD - GitOps 배포 자동화 (현재 글)
ArgoCD란?
ArgoCD는 Kubernetes를 위한 선언적 GitOps CD(Continuous Delivery) 도구입니다.
핵심 기능:
- 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 }}.yamlvalues 파일
# 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에서는 애플리케이션 코드와 배포 설정을 분리하는 것이 권장됩니다.
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.mdApplicationSet 설정
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: devconfig.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-secretGit 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 }}전체 배포 흐름
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 삭제 |
새 서비스 추가하기
- 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- values.yaml 추가
mkdir -p appsets/dev-v1/kohi/values/new-service
# values.yaml 작성- Git Push
git add .
git commit -m "feat: add new-service"
git push- 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-degradedSlack Token Secret 생성
- Slack App 생성: https://api.slack.com/apps
- Bot Token Scopes 추가:
chat:write,chat:write.public - 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 yamlRepository 연결 실패
# Repository Secret 확인
kubectl get secret -n argocd -l argocd.argoproj.io/secret-type=repository
# SSH 키 테스트
ssh -T git@github.com마무리
이 시리즈를 통해 다음을 구축했습니다:
이제 가능한 것들:
git push→ 자동 배포- 모든 인프라/앱 설정이 Git에 버전 관리
- 문제 시
git revert로 롤백 - 새 팀원은 README만 읽으면 온보딩 완료
GitOps로 인프라 관리에 쓰는 시간을 줄이고, 비즈니스 로직 개발에 집중할 수 있게 되었습니다.