지난 글에서 Helm으로 ArgoCD를 설치하고, 그 과정을 Ansible로 자동화했습니다.
이 글은 "엔터프라이즈 JVM 애플리케이션의 Cloud-Native 전환기" 시리즈의 3편 후기입니다.
이번에는 설치한 ArgoCD를 이용하여 helm chart를 사용한 Jira를 배포하는 과정을 정리합니다.
helm으로 ArgoCD를 설치한 방법을, 이번에는 Github에 설정 파일을 올리는 방식으로 ArgoCD가 대신 실행하게 됩니다.
ArgoCD란 무엇인가
ArgoCD를 한 줄로 표현하면 "GitHub을 바라보는 K8s 전용 배포 자동화 도구”입니다.
그런데 기존에 쓰던 Jenkins 같은 CI/CD 도구와는 무엇이 다를까 궁금했는데, 차이는 배포 방향에 있었습니다.
- 기존 방식 (Push 모델)은 외부 파이프라인(Jenkins)이 K8s 클러스터의 관리자 권한을 가져와서 직접 밀어 넣습니다. 클러스터 인증서가 외부 시스템에 노출된다는 보안 문제가 있습니다.
- ArgoCD 방식 (Pull 모델)은 ArgoCD가 K8s 클러스터 내부에 설치되어, 주기적으로 GitHub 저장소를 감시하다가 변경이 생기면 스스로 당겨와서 반영합니다. 클러스터 인증서가 밖으로 나갈 필요가 없습니다.
여기에 GitOps의 핵심 철학이 더해집니다.
"GitHub에 적힌 상태가 유일한 진실(Single Source of Truth)이다."
- 누군가 터미널에서 파드 설정을 몰래 바꿔도, (kubectl edit)
- ArgoCD는 즉시 "Git과 현재 상태가 다르다"는 것을 감지하고 (Drift Detection)
- GitHub에 적힌 상태로 자동 복구합니다. (Self-Healing)
운영 중 발생하는 설정 변경 장애가 원천적으로 차단되는 것입니다.
ArgoCD의 주요 구성요소
ArgoCD는 세 가지 핵심 컴포넌트로 동작합니다. 각각의 역할을 이해해두면, 배포가 어디서 막혔는지 파악할 때 큰 도움이 됩니다.
argocd-server
사용자가 직접 마주하는 진입점입니다. 웹 UI, CLI(argocd 명령어), REST/gRPC API 요청을 모두 이 컴포넌트가 받습니다. "ArgoCD에 로그인한다", "애플리케이션 상태를 확인한다"는 행위는 전부 argocd-server를 통해 이루어집니다.
argocd-repo-server
GitHub 저장소에서 실제로 파일을 가져오는 역할을 합니다.
단순히 파일을 내려받는 것에서 그치지 않고, Helm 차트라면 values.yaml을 적용해 최종 K8s 매니페스트로 렌더링하고, Kustomize라면 오버레이를 합쳐서 최종 YAML을 만들어냅니다.
argocd-application-controller가 "지금 Git에는 어떤 상태여야 해?"라고 물으면, repo-server가 렌더링한 결과를 돌려주는 방식으로 실행됩니다.
argocd-application-controller
ArgoCD의 핵심 두뇌입니다. repo-server로부터 받은 Git의 목표 상태(Desired State)와 실제 K8s 클러스터의 현재 상태(Actual State)를 지속적으로 비교합니다.
차이가 생기면 이를 OutOfSync로 표시하고, selfHeal: true 설정이 되어 있다면 사람이 개입하기 전에 자동으로 동기화합니다.
앞서 설명한 Drift Detection과 Self-Healing이 바로 이 컴포넌트에서 일어납니다.
세 컴포넌트의 흐름을 순서로 정리하면 이렇습니다.
- repo-server가 GitHub을 주기적으로 확인해 최신 매니페스트를 렌더링합니다.
- application-controller가 렌더링된 결과와 현재 클러스터 상태를 비교합니다.
- 차이가 있으면 application-controller가 클러스터에 변경을 적용합니다.
- 그 과정과 결과를 argocd-server가 UI와 API로 노출합니다.
Ansible과 ArgoCD의 역할 분담
ArgoCD를 구성하기 전에 Ansible과의 차이점이 무엇일까 하는 의문이 생겨 두 도구의 역할을 정리해보겠습니다.
두 도구의 역할은 아래와 같습니다.
| Ansible | ArgoCD | |
| 역할 | 서버 OS 세팅, K3s 클러스터 구축, ArgoCD 설치 | Jira, DB 등 애플리케이션의 지속적 배포와 상태 감시 |
| 실행 시점 | 인프라를 처음 세울 때 (1회성) | 코드가 바뀔 때마다 (지속적) |
| 설정 위치 | Playbook 파일 | GitHub 저장소 |
Ansible이 하는 일
Ansible은 서버가 처음 세팅될 때 실행됩니다.
운영체제 기본 설정, 방화벽 규칙, K3s 설치, ArgoCD 설치와 같이 서버를 구성하기 위해 필요한 설정 파일을 작업하게 됩니다.
그렇기 때문에 ansible-playbook은 한 번 실행하고 나면 다시 건드릴 일이 거의 없습니다.
서버를 새로 프로비저닝하거나, K3s 버전을 올리거나, 클러스터 노드를 추가할 때처럼 인프라 자체가 바뀔 때만 다시 실행합니다.
ArgoCD도 Ansible로 설치했는데, ArgoCD 자체는 K8s 위에 올라가는 인프라성 도구이기 때문입니다.
"ArgoCD를 관리하는 도구는 ArgoCD가 아니다"라는 원칙을 지킨 것입니다.
ArgoCD가 하는 일
ArgoCD는 인프라가 준비된 이후부터 역할을 맡습니다.
Jira, PostgreSQL처럼 클러스터 위에서 실제로 돌아가는 애플리케이션들을 관리합니다.
Ansible과 가장 다른 점은 실행 시점입니다. Ansible은 사람이 명령어를 칠 때 실행되지만, ArgoCD는 GitHub에 변경이 생길 때마다 자동으로 클러스터에 반영합니다.
이 과정이 반복될수록 GitHub 저장소에는 애플리케이션의 변경 이력이 쌓이기 때문에, "언제, 누가, 무엇을 바꿨는지"가 자연스럽게 기록됩니다.
왜 Ansible로 Jira를 배포하지 않았을까
처음에는 "Ansible로 ArgoCD까지 설치했으니, Jira도 Ansible Playbook으로 배포하면 되지 않을까"하는 질문이 자연스럽게 떠올랐습니다.
기술적으로는 가능하지만, Playbook을 실행한 뒤에 누군가 Jira 설정을 손으로 바꾸면 Ansible은 그 변화를 모르기 때문에,
다음번 Playbook 실행 전까지 실제 상태와 코드가 어긋난 채로 운영됩니다.
ArgoCD는 GitHub과 클러스터 상태를 지속적으로 비교하기 때문에, 설정이 어긋나는 순간 감지하고 자동으로 복구합니다.
애플리케이션처럼 설정이 자주 바뀌고, 항상 일관된 상태를 유지해야 하는 대상에는 ArgoCD가 훨씬 적합한 이유입니다.
GitOps 저장소 구조 설계
ArgoCD가 감시할 GitHub 저장소를 먼저 만들었습니다.
atlassian-k8s-gitops/
├── gitops/
│ ├── bootstrap/
│ │ └── root-app.yaml # ArgoCD에 최초 1회 등록
│ ├── apps/
│ │ └── jira.yaml # root-app이 이 폴더를 감시
│ └── charts/
│ └── jira/
│ ├── Chart.yaml
│ └── values.yaml
구조를 세개의 폴더로 나눈 이유는 아래와 같습니다.
- bootstrap : ArgoCD에 딱 한 번만 등록하는 파일을 담아두는 곳
root-app.yaml 하나만 있으면 됩니다.
이 파일을 kubectl apply로 등록하는 순간, 이후의 모든 배포는 ArgoCD가 자동으로 처리합니다. - apps : 배포할 애플리케이션 목록을 관리하는 폴더
root-app이 이 폴더를 감시하고 있다가, 새 파일이 올라오면 자동으로 배포를 시작합니다.
Jira를 추가하고 싶으면 jira.yaml을, Confluence를 추가하고 싶으면 confluence.yaml을 이 폴더에 올리면 됩니다.
터미널에서 명령어를 칠 필요 없이, GitHub에 파일을 추가하는 것만으로 새 애플리케이션 배포가 시작됩니다. - charts : 실제 Helm 설정 파일이 위치하는 곳
apps/ 폴더의 파일이 "Jira를 배포해줘"라는 지시서라면,
charts/ 폴더는 "Jira를 이렇게 설정해서 배포해줘"라는 구체적인 설계도입니다.
App of Apps 패턴
이 구조의 핵심은 App of Apps 패턴입니다.
일반적으로 ArgoCD에 애플리케이션을 등록하려면 CLI나 UI에서 직접 하나씩 추가해야 하기 때문에, 애플리케이션이 늘어날수록 수동 등록 작업도 함께 늘어납니다.
App of Apps 패턴은 이 문제를 해결하기 위해 나온 방법입니다.
root-app 하나를 ArgoCD에 등록해두면, 이 앱이 apps/ 폴더 전체를 감시합니다. 그 폴더 안의 YAML 파일 하나하나를 ArgoCD Application으로 인식하고 자동 등록합니다.
흐름을 순서로 정리하면 이렇습니다.
1. kubectl apply -f bootstrap/root-app.yaml (최초 1회, 사람이 직접)
↓
2. root-app이 apps/ 폴더 감시 시작
↓
3. apps/jira.yaml 감지
↓
4. ArgoCD가 jira Application 자동 등록
↓
5. charts/jira/ 의 Helm 차트를 읽어서 K8s에 배포
이 구조의 장점은 확장성에 있습니다.
나중에 Confluence를 추가한다면 apps/confluence.yaml과 charts/confluence/를 만들어서 GitHub에 push하는 것만으로 끝입니다.
ArgoCD 설정을 건드리거나 터미널에 접속할 필요가 없습니다.
모든 변경 이력이 Git에 남기 때문에 "언제, 무엇을, 왜 추가했는지"도 자연스럽게 기록됩니다.
ArgoCD의 배포 방식
위에서 작성한 Apps of Apps 패턴으로 구성한 파일을 참고하여 ArgoCD에서 배포를 어떻게 진행할까? 하는 의문이 있었습니다.
ArgoCD는 배포할 설정을 반드시 공식 Helm 저장소에서만 가져오지 않습니다. Git 저장소 자체를 소스로 사용하는 것이 일반적입니다.
크게 세 가지 방식이 있습니다.
1. Git 저장소 내의 Helm 차트 (Helm in Git)
이번 프로젝트에서 사용한 방식입니다.
- 사내 Git 저장소의 특정 폴더에 Helm 차트 파일(Chart.yaml, values.yaml 등)을 업로드해 두는 방식입니다.
- ArgoCD가 해당 경로를 바라보도록 설정하면, 자체적으로 Helm 템플릿을 렌더링하여 배포합니다.
2. 순수 Kubernetes YAML 파일 (Raw Manifests)
- Helm을 사용하지 않고, 배포에 필요한 순수한 YAML 파일(Deployment, Service 등)을 Git에 올려두는 방식입니다.
- ArgoCD가 이 파일들을 그대로 클러스터에 적용(Apply)합니다.
- 간단한 애플리케이션이나 커스텀 리소스를 직접 관리할 때 주로 씁니다.
3. Kustomize 방식
- Git 저장소에 기본 YAML 파일들을 두고, kustomization.yaml을 통해 환경(Dev, Staging, Prod 등)에 맞게 설정값을 덮어쓰는 방식입니다. ArgoCD는 Kustomize도 기본으로 지원합니다.
ArgoCD 내부 동작 흐름
ArgoCD가 GitHub의 파일을 읽어서 실제로 K8s에 배포하기까지 내부에서는 크게 네 단계가 진행됩니다.
1단계: Git 저장소 읽기 (Clone & Fetch)
- ArgoCD는 주기적으로 설정된 Git 저장소를 확인합니다.
- 변경이 감지되면 해당 경로(gitops/charts/jira)의 파일들을 내부 캐시로 가져옵니다.
- 이 시점에서 가져오는 파일은 Chart.yaml과 values.yaml뿐입니다.
2단계: 의존성 다운로드 (Helm Dependency Build)
- ArgoCD는 Chart.yaml의 dependencies 항목을 확인합니다.
- 의존하는 차트가 있으면 해당 저장소 URL로 요청을 보내 차트 압축 파일(jira-1.21.1.tgz)을 내부 임시 공간으로 내려받고 압축을 풉니다.
- 터미널에서 helm dependency build를 직접 치는 것과 동일한 작업을 ArgoCD가 백그라운드에서 대신 처리하는 것입니다.
3단계: 매니페스트 렌더링
- 공식 차트 원본과 우리가 작성한 values.yaml이 모두 준비되면, ArgoCD는 내부적으로 helm template을 실행해 두 파일을 합칩니다.
- Helm 문법이 모두 사라지고, K8s가 이해할 수 있는 순수한 YAML 파일이 만들어집니다.
4단계: 클러스터와 비교 및 적용
렌더링된 YAML과 현재 클러스터의 실제 상태를 비교합니다.
- 다르면 (Out of Sync)→ Out of Sync로 표시하고, 자동 동기화 설정 시 즉시 kubectl apply를 수행합니다.
- 같으면 (Synced) → 현재의 동기화 상태를 그대로 유지합니다.
RBAC 권한 구성
ArgoCD에는 기본적으로 두 가지 역할이 있습니다.
role:admin은 모든 작업이 가능하고, role:readonly는 조회만 가능합니다.
이번 프로젝트는 혼자 진행한 로컬 환경이라 기본 admin 계정만 사용했지만, 실제 운영 환경에서는 역할별 권한 분리가 필수입니다.
ArgoCD는 argocd-rbac-cm이라는 ConfigMap으로 권한을 관리합니다.
apiVersion: v1
kind: ConfigMap
metadata:
name: argocd-rbac-cm
namespace: argocd
data:
policy.default: role:readonly # 기본은 읽기 전용
policy.csv: |
# 운영자: 배포 및 동기화 가능, 설정 변경 불가
p, role:operator, applications, sync, */*, allow
p, role:operator, applications, get, */*, allow
# 개발자: 조회와 수동 동기화만 허용
p, role:developer, applications, get, */*, allow
p, role:developer, applications, sync, */*, allow
# 역할 할당
g, ops-team, role:operator
g, dev-team, role:developer
이렇게 구성하면 개발자는 배포 상태를 확인하고 수동 동기화는 할 수 있지만, ArgoCD 자체 설정이나 저장소 연결은 건드릴 수 없습니다.
운영자는 배포와 동기화가 가능하지만, admin 권한이 필요한 시스템 설정은 제한됩니다.
실제로 Atlassian 제품을 운영하면서 느낀 것은, 배포 도구의 권한이 느슨하면 "누가 언제 뭘 바꿨는지" 추적이 어렵다는 점입니다. GitOps 방식에서는 모든 변경이 Git 커밋으로 남지만, ArgoCD 자체에서 수동으로 조작한 내용은 Git에 기록되지 않습니다. RBAC으로 수동 조작 권한 자체를 제한하는 것이 GitOps 원칙을 지키는 방법입니다.
ArgoCD CLI 설치 및 GitHub 연동
ArgoCD UI에서 대부분의 작업이 가능하지만, 저장소 연동과 초기 설정은 CLI로 하는 것이 빠릅니다.
# master-node에서 설치
curl -sSL -o /tmp/argocd <https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-arm64>
sudo install -m 555 /tmp/argocd /usr/local/bin/argocd
# ArgoCD 서버 로그인
argocd login argocd.orb.local --username admin --password <초기비밀번호> --insecure --grpc-web
# GitHub 저장소 연동
argocd repo add --grpc-web
# 연동 확인
argocd repo list --grpc-web
TYPE NAME REPO STATUS
git Successful
- -insecure : 로컬 환경에서 HTTPS 인증서 없이 HTTP로 접속하기 때문에 필요합니다.
- -grpc-web : Traefik Ingress 뒤에 ArgoCD가 있을 때 통신 방식을 맞춰주는 옵션입니다.
argocd CLI 설치 : https://argo-cd.readthedocs.io/en/stable/cli_installation/
ArgoCD로 Jira 배포하기
1. 데이터베이스 준비
Jira가 사용할 데이터베이스를 infra-node의 PostgreSQL에 미리 생성합니다.
orb -m infra-node
sudo -u postgres psql <<EOF
CREATE DATABASE jiradb
WITH ENCODING 'UNICODE'
LC_COLLATE 'C'
LC_CTYPE 'C'
TEMPLATE template0;
CREATE USER jira WITH PASSWORD 'jira';
GRANT ALL PRIVILEGES ON DATABASE jiradb TO jira;
EOF
2. Helm 차트 구성
ArgoCD가 Jira를 배포할 때 사용할 Helm 설정을 작성합니다.
Chart.yaml에는 사용할 공식 차트와 버전을 선언합니다.
# gitops/charts/jira/Chart.yaml
apiVersion: v2
name: jira
description: Atlassian Jira on K8s
type: application
version: 1.0.0
dependencies:
- name: jira
version: "1.21.1"
repository: "<https://atlassian.github.io/data-center-helm-charts>"
values.yaml에는 우리 환경에 맞게 덮어쓸 설정을 작성합니다. DB 연결 정보, NFS 공유 스토리지 경로, Ingress 설정이 핵심입니다.
# gitops/charts/jira/values.yaml
jira:
image:
repository: atlassian/jira-software
tag: "9.12.0"
jvmConfig:
jvmMinHeapSize: "384m"
jvmMaxHeapSize: "768m"
resources:
requests:
cpu: "500m"
memory: "1Gi"
limits:
cpu: "2000m"
memory: "1.5Gi"
datasource:
url: "jdbc:postgresql://infra-node.orb.local:5432/jiradb"
username: "jira"
password: "jira"
driver: "org.postgresql.Driver"
sharedHome:
nfsPermissionFixer:
enabled: true
customVolume:
nfs:
server: "infra-node.orb.local"
path: "/data/shared-home/jira-shared-home"
ingress:
create: true
className: "traefik"
host: "jira.orb.local"
https: false
readinessProbe:
initialDelaySeconds: 120
failureThreshold: 30
livenessProbe:
initialDelaySeconds: 180
failureThreshold: 10
helm chart 구성 : https://atlassian.github.io/data-center-helm-charts/
3. ArgoCD 매니페스트 작성
apps/jira.yaml은 ArgoCD에게 "이 GitHub 경로의 Helm 차트를 저 네임스페이스에 배포해줘"라고 지시하는 파일입니다.
# gitops/apps/jira.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: jira
namespace: argocd
spec:
project: default
source:
repoURL: ""
targetRevision: main
path: gitops/charts/jira
helm:
valueFiles:
- values.yaml
destination:
server: "<https://kubernetes.default.svc>"
namespace: atlassian
syncPolicy:
automated:
prune: true # Git에서 삭제된 리소스는 클러스터에서도 삭제
selfHeal: true # Git과 달라진 상태는 자동으로 복구
syncOptions:
- CreateNamespace=true
4. root-app 등록 (최초 1회)
모든 파일을 GitHub에 push한 뒤, root-app을 ArgoCD에 등록합니다. 이 작업만 직접 수행하고, 이후에는 ArgoCD가 알아서 처리합니다.
# Mac /etc/hosts 등록
sudo sh -c 'echo "192.168.139.219 jira.orb.local" >> /etc/hosts'
# root-app 등록
kubectl apply -f gitops/bootstrap/root-app.yaml
# root-app이 apps/ 폴더를 감지하고 Jira를 자동 등록하는지 확인
kubectl get applications -n argocd
NAME SYNC STATUS HEALTH STATUS
root-app Synced Healthy
jira Synced Healthy
정상적으로 배포되면 Ingress가 생성되고 http://jira.orb.local로 접속할 수 있습니다.
$ kubectl get ingress -n atlassian
NAME CLASS HOSTS ADDRESS
jira traefik jira.orb.local 192.168.139.139,192.168.139.160

Trouble Shooting
1. app path does not exist
ComparisonError: apps: app path does not exist
root-app.yaml의 path를 apps로 설정했는데, 실제 저장소 구조가 gitops/apps였습니다.
ArgoCD의 경로는 저장소 루트 기준이므로 디렉토리 구조와 정확히 일치해야 하기 때문에 path: gitops/apps로 수정한 뒤 해결됐습니다.
2. Chart.yaml: no such file or directory
ComparisonError: error reading helm chart from .../Chart.yaml: no such file or directory
파일명이 chart.yaml(소문자)로 저장되어 있어 발생한 문제입니다.
Helm은 반드시 Chart.yaml(대문자 C)을 요구합니다. 파일명 수정 후 push하니 ArgoCD가 바로 감지해서 배포를 재시도했습니다.
두 오류 모두 ArgoCD가 에러 메시지를 UI와 CLI 양쪽에서 명확하게 보여줬기 때문에 원인을 빠르게 파악할 수 있었습니다.
터미널에서 kubectl describe를 사용하여 헤메던 것과 비교하면 디버깅 경험 자체가 달랐습니다.
마치며
지금까지는 Jira 설정을 바꿔야 할 때마다, 서버에서 파일을 수정 후에 서비스를 재시작하고 반영 여부를 확인하는 과정을 반복했습니다.
작은 설정 하나를 바꾸는 데도 서비스 중단 공지 후에 서비스 재기동을 진행해야 했고, 손으로 파일을 직접 건드리다 보니 오타와 같은 사소한 문제가 서비스 장애로 이어지는 경우도 적지 않았습니다.
그 경험이 있었기 때문에 GitOps 방식으로 k3s에 Atlassian 제품을 올려보는 작업을 하면서 체감되는 차이가 매우 컸습니다.
이번 작업을 마치고 나서 GitOps가 왜 주목받는지 체감할 수 있었습니다.
Jira 설정을 바꾸고 싶을 때, 서버에 접속하거나 명령어를 칠 필요가 없습니다.
GitHub에서 values.yaml의 숫자 하나를 바꾸고 commit하면, ArgoCD가 감지하고 알아서 클러스터에 반영합니다.
인프라와 애플리케이션의 모든 상태가 GitHub에 기록되고, 그 기록이 곧 현재 운영 중인 상태와 동일하게 유지됩니다.
이를 통해, 애플리케이션을 구성하는 요소들의 변경사항을 코드로 추적하고, 관리할 수 있다는 이점을 알게 되었습니다.
지금까지 세 편에 걸쳐 인프라 자동화의 큰 그림을 따라왔습니다.
- Ansible : k3s 클러스터 구축 자동화
- helm + Ansible : ArgoCD 설치 및 배포 자동화
- ArgoCD : Jira, Confluence 등의 애플리케이션 배포 자동화
세 도구가 각자의 역할을 맡아 하나의 파이프라인을 이루는 구조를 구성하면서, 개념으로만 알고 있던 GitOps를 실제 운영 경험과 연결해서 이해하게 된 좋은 경험이 되었습니다.
'프로젝트 > 엔터프라이즈 JVM 애플리케이션의 Cloud-Native 전환기' 카테고리의 다른 글
| 3-1. Helm으로 ArgoCD 설치하고, Ansible로 자동화하기 (1) | 2026.04.29 |
|---|---|
| 2-2. ansible-playbook 역할 기반으로 재구성하기 (0) | 2026.04.22 |
| 2-1. OrbStack + Ansible로 k3s 클러스터 구축하기 (0) | 2026.04.08 |
| 1. 엔터프라이즈 JVM 애플리케이션의 Cloud-Native 전환기 (0) | 2026.03.31 |