이번 글은 Kubernetes 클러스터에 ArgoCD를 올리는 과정을 정리한 글입니다.
처음에는 직접 손으로 명령어를 치면서 동작 원리를 파악하고, 그 다음에 같은 과정을 Ansible Playbook으로 옮기는 순서로 진행했습니다. 직접 터미널에서 환경을 구성하고 → 자동화하는 흐름이 개념을 이해하는 데 훨씬 도움이 됐습니다.
환경은 Mac 위에서 OrbStack으로 K3s 클러스터를 구성한 로컬 환경입니다.
Helm이란 무엇인가
Kubernetes에 애플리케이션을 배포하려면 Deployment, Service, ConfigMap, Ingress 같은 리소스를 하나하나 YAML로 작성해야 합니다. 애플리케이션 하나를 올리는 데 수십 개의 파일이 필요하기도 합니다.
Helm은 이 과정을 단순하게 만들어주는 Kubernetes 전용 패키지 매니저입니다.
npm이 Node.js 패키지를 한 줄로 설치하듯, Helm은 복잡한 K8s 리소스 묶음을 명령어 하나로 설치할 수 있게 해줍니다.
Chart와 values.yaml
Helm에서 핵심 개념은 Chart와 values.yaml입니다.
Chart는 애플리케이션 설치에 필요한 K8s 리소스 템플릿의 묶음입니다.
ArgoCD 공식 Helm 차트 안에는 수천 줄짜리 기본 values.yaml이 들어있고, "Redis를 고가용성 모드로 3대 띄워라", "Ingress는 꺼둬라" 같은 기본값들이 미리 정의되어 있습니다.
그런데 우리 환경은 로컬 개발용이라 리소스를 아껴야 했고, K3s에 내장된 Traefik Ingress를 통해 도메인으로 접속해야 했습니다.
이렇게 프로젝트 환경에 맞게 구성하기 위해 사용할 수 있는 것이 values.yaml입니다.
바꾸고 싶은 부분만 골라서 파일로 작성하고, -f 옵션으로 Helm에 넘겨주면 기본값을 덮어씁니다.
helm install argocd argo/argo-cd \\\\
-n argocd \\\\
--version 7.7.0 \\\\
-f argocd-values.yaml # 이 파일의 값으로 기본값을 덮어써라
이 구조가 처음에는 낯설었는데, 나중에 ArgoCD로 다른 애플리케이션을 배포할 때도 똑같은 방식을 사용하기 때문에 확실히 이해하고 넘어가는 게 중요한것 같습니다.
Helm 설치
Helm 공식 설치 스크립트를 사용하면 OS에 맞는 최신 버전을 자동으로 설치합니다.
curl -fsSL <https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3> | bash
Helm은 기본적으로 ~/.kube/config에서 클러스터 인증서를 읽는데, K3s는 인증서를 /etc/rancher/k3s/k3s.yaml이라는 경로에 따로 보관합니다. Helm이 K3s 클러스터와 통신하려면 인증서를 복사해줘야 합니다.
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config
chmod 600 ~/.kube/config
이제 설치와 연동이 잘 됐는지 확인합니다.
$ helm version
version.BuildInfo{Version:"v3.20.1", ...}
$ helm ls -A
NAME NAMESPACE STATUS CHART
traefik kube-system deployed traefik-39.0.201
traefik-crd kube-system deployed traefik-crd-39.0.201
에러 없이 클러스터의 목록이 나오면 정상입니다.
Helm으로 ArgoCD 배포하기
Traefik(Ingress Controller) 확인
ArgoCD를 도메인으로 접속하려면 외부 요청을 내부 서비스로 연결해주는 Ingress Controller가 필요합니다. K3s는 Traefik을 기본으로 내장하고 있어서 따로 설치할 필요가 없습니다. 먼저 잘 떠있는지 확인합니다.
kubectl get pods -n kube-system -l app.kubernetes.io/name=traefik
NAME READY STATUS RESTARTS
traefik-788bc4688c-z4fk6 1/1 Running 4
values.yaml 작성
앞서 이야기한 대로, 로컬 환경에 맞게 덮어쓸 설정을 파일로 작성합니다.
# argocd-values.yaml
global:
domain: "argocd.orb.local"
configs:
params:
# ArgoCD는 HTTP로 서비스하고, SSL 처리는 Traefik에 맡깁니다.
# 이 옵션이 없으면 ArgoCD가 HTTPS로 리다이렉트해서 무한 루프가 발생합니다.
server.insecure: "true"
server:
ingress:
enabled: true
ingressClassName: "traefik"
annotations:
traefik.ingress.kubernetes.io/service.serversscheme: "http"
values.yml 파일을 명령어로 가져와서 구성할 수 있습니다. (postgresql 예시)
# 1. Bitnami 저장소 추가 (안 되어 있다면)
helm repo add bitnami <https://charts.bitnami.com/bitnami>
# 2. values.yml 원본 파일 저장
helm show values bitnami/postgresql > original-postgres-values.yaml
argocd 배포
# 네임스페이스 생성
kubectl create namespace argocd
# Argo 공식 저장소 추가
helm repo add argo <https://argoproj.github.io/argo-helm>
helm repo update
# 작성한 values.yaml을 적용하여 배포!
helm install argocd argo/argo-cd -n argocd --version 7.7.0 -f argocd-values.yaml
NAME: argocd
LAST DEPLOYED: Mon Mar 23 14:39:03 2026
NAMESPACE: argocd
STATUS: deploye
파드가 모두 Running 상태인지 확인합니다.
$ kubectl get pods -n argocd -w
NAME READY STATUS
argocd-application-controller-0 1/1 Running
argocd-applicationset-controller-669cbf7ff5-r6mnm 1/1 Running
argocd-dex-server-86b76cc5d5-zcf2c 1/1 Running
argocd-notifications-controller-6476d694c7-p94hw 1/1 Running
argocd-redis-f9d86dcc5-hdznt 1/1 Running
argocd-repo-server-66c4fc5467-9klr4 1/1 Running
argocd-server-8645744799-g9xk4 1/1 Running
Ingress도 정상 등록되어 있는지 봅니다.
$ kubectl get ingress -n argocd
NAME CLASS HOSTS ADDRESS
argocd-server traefik argocd.orb.local 192.168.139.219
초기 관리자 비밀번호를 추출하고, Mac의 /etc/hosts에 도메인을 등록하면 접속할 수 있습니다.
# 초기 비밀번호 추출
kubectl -n argocd get secret argocd-initial-admin-secret \\\\
-o jsonpath="{.data.password}" | base64 -d && echo
# Mac /etc/hosts 등록
sudo sh -c 'echo "192.168.139.219 argocd.orb.local" >> /etc/hosts'
브라우저에서 http://argocd.orb.local로 접속하면 ArgoCD UI가 나옵니다.
(Trouble Shooting) 권한 충돌 문제
터미널에는 에러가 없는데, kubectl로 확인하면 아무것도 배포되지 않은 문제가 있었습니다.
원인은 sudo와 일반 계정이 서로 다른 인증서를 바라보고 있었던 것입니다.
sudo kubectl은 K3s 기본 경로(/etc/rancher/k3s/k3s.yaml)의 인증서를 사용하고, 일반 계정의 helm은 ~/.kube/config를 찾습니다. ~/.kube/config가 없거나 권한이 꼬여 있으면, Helm은 클러스터에 접속 자체를 못합니다.
K3s 설치 시 시스템 전체에 KUBECONFIG=/etc/rancher/k3s/k3s.yaml 환경변수가 자동으로 설정되는 것도 원인 중 하나였습니다. ~/.kube/config에 인증서를 복사해뒀어도 환경변수가 원본 경로를 가리키고 있으면 kubectl과 helm이 둘 다 원본을 읽으려다 권한 오류로 튕겨나갑니다.
해결은 환경변수를 명시적으로 바꿔주는 것이었습니다.
# 현재 세션에 적용
export KUBECONFIG=~/.kube/config
# 영구 적용
echo 'export KUBECONFIG=~/.kube/config' >> ~/.bashrc
이후에는 sudo 없이 kubectl과 helm이 같은 클러스터를 바라보게 됩니다.
Ansible로 자동화하기
터미널에서 helm을 설치하고 argocd를 배포하는 과정을 확인했으니, 이 내용을 ansible-playbook으로 옮기려고 합니다.
추후에 같은 작업을 다시 할 때 실수 없이 재현할 수 있고, 운영 환경으로 확장할 때도 변수만 바꾸면 되기 때문입니다.
디렉토리 구조
ansible/
├── site.yml
├── inventories/
│ └── dev/
│ └── group_vars/
│ └── all.yml # 환경별 변수 선언
└── roles/
├── helm/
│ └── tasks/
│ └── main.yml
└── argocd/
├── tasks/
│ ├── main.yml
│ ├── kubeconfig.yml
│ ├── deploy.yml
│ └── verify.yml
└── templates/
└── argocd-values.yaml.j2
환경 변수 분리
group_vars/all.yml에 환경마다 달라질 수 있는 값을 변수로 선언합니다.
argocd_namespace: "argocd"
argocd_chart_version: "7.7.0"
argocd_domain: "argocd.orb.local"
argocd_ingress_class: "traefik"
운영 환경으로 전환할 때는 inventories/prod/group_vars/all.yml에서 도메인이나 버전만 바꾸면 됩니다.
site.yml — 실행 순서와 권한 설계
- name: 7. Helm 설치
hosts: k3s_master
become: yes # 바이너리 설치는 sudo 필요
roles: [ helm ]
- name: 8. ArgoCD 배포
hosts: k3s_master
become: no # Helm 실행은 반드시 일반 유저로
environment:
KUBECONFIG: "/home/{{ ansible_user }}/.kube/config"
roles: [ argocd ]
become: no 옵션은 root 권한이 아닌 실행 유저 권한으로 실행하는 옵션입니다.
become: yes로 실행하면 Helm이 root의 kubeconfig를 찾다가 아무것도 반영되지 않는 현상이 그대로 재현됩니다.
environment를 Play 레벨에 선언하면 해당 Play의 모든 Task가 동일한 환경변수를 공유하기 때문에 Task마다 반복해서 쓸 필요가 없습니다.
Helm 설치 Role
# roles/helm/tasks/main.yml
- name: Helm 설치 여부 확인
command: which helm
register: helm_check
failed_when: false
changed_when: false
- name: Helm 3 설치
shell: curl -fsSL <https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3> | bash
when: helm_check.rc != 0
- name: Argo Helm 저장소 추가
command: helm repo add argo <https://argoproj.github.io/argo-helm>
register: repo_add
failed_when:
- repo_add.rc != 0
- "'already exists' not in repo_add.stderr"
changed_when: repo_add.rc == 0
- name: Helm 저장소 업데이트
command: helm repo update
changed_when: false
which helm으로 설치 여부를 먼저 확인하고 없을 때만 설치합니다.
Ansible은 기본적으로 멱등성을 지향하기 때문에, 같은 Playbook을 여러 번 실행해도 결과가 동일하게 유지되어야 합니다.
changed_when을 명시적으로 설정한 것도 같은 이유입니다.
which나 repo update처럼 상태를 바꾸지 않는 명령이 매번 changed로 표시되는 것을 막기 위해서입니다.
ArgoCD Role — kubeconfig 설정
# roles/argocd/tasks/kubeconfig.yml
- name: .kube 디렉토리 생성
file:
path: "/home/{{ ansible_user }}/.kube"
state: directory
mode: '0755'
- name: k3s.yaml을 사용자 kubeconfig로 복사
become: yes # 이 Task만 잠깐 sudo 권한을 빌립니다
copy:
src: /etc/rancher/k3s/k3s.yaml
dest: "/home/{{ ansible_user }}/.kube/config"
remote_src: yes
owner: "{{ ansible_user }}"
group: "{{ ansible_user }}"
mode: '0600'
- name: KUBECONFIG 환경변수 영구 설정
blockinfile:
path: "/home/{{ ansible_user }}/.bashrc"
marker: "# {mark} ANSIBLE MANAGED BLOCK: kubeconfig"
block: |
export KUBECONFIG=~/.kube/config
become: yes를 Play 전체가 아닌 Task 단위로 좁혀서 적용했습니다.
인증서 복사할 때만 잠깐 sudo 권한을 빌리고, 나머지는 일반 유저로 처리합니다.
ArgoCD Role — 배포
# roles/argocd/tasks/deploy.yml
- name: argocd 네임스페이스 생성
command: kubectl create namespace {{ argocd_namespace }}
register: ns_create
failed_when:
- ns_create.rc != 0
- "'already exists' not in ns_create.stderr"
changed_when: ns_create.rc == 0
- name: argocd-values.yaml 생성
template:
src: argocd-values.yaml.j2
dest: "/home/{{ ansible_user }}/argocd-values.yaml"
- name: ArgoCD 배포 (설치 또는 업그레이드)
command: >
helm upgrade --install argocd argo/argo-cd
-n {{ argocd_namespace }}
--version {{ argocd_chart_version }}
-f /home/{{ ansible_user }}/argocd-values.yaml
--create-namespace
helm upgrade --install을 사용하면, install/upgrade를 분기하지 않더라도 두 경우를 모두 처리할 수 있어 코드가 훨씬 단순해집니다.
배포에 사용하는 argocd-values.yaml.j2는 Jinja2 템플릿입니다.
Jinja2는 group_vars에 선언한 변수를 그대로 주입받기 때문에, 환경이 달라져도 템플릿 파일 자체는 수정하지 않아도 됩니다.
# roles/argocd/templates/argocd-values.yaml.j2
global:
domain: "{{ argocd_domain }}"
configs:
params:
server.insecure: "true"
server:
ingress:
enabled: true
ingressClassName: "{{ argocd_ingress_class }}"
annotations:
traefik.ingress.kubernetes.io/service.serversscheme: "http"
ArgoCD Role — 검증
배포가 끝난 뒤 파드 상태와 접속 정보를 자동으로 출력하도록 했습니다.
# roles/argocd/tasks/verify.yml
- name: ArgoCD 파드 Ready 대기 (최대 3분)
command: >
kubectl wait pod
-n {{ argocd_namespace }}
--for=condition=Ready
--all
--timeout=180s
- name: 초기 관리자 비밀번호 추출
shell: >
kubectl -n {{ argocd_namespace }}
get secret argocd-initial-admin-secret
-o jsonpath="{.data.password}" | base64 -d
register: argocd_password
changed_when: false
- name: 접속 정보 출력
debug:
msg:
- "URL : http://{{ argocd_domain }}"
- "ID : admin"
- "Password : {{ argocd_password.stdout }}"
실행 방법
# Helm만 설치
ansible-playbook site.yml --tags "helm"
# ArgoCD만 배포 (Helm이 이미 설치된 경우)
ansible-playbook site.yml --tags "argocd"
# Helm 설치 + ArgoCD 배포 한 번에
ansible-playbook site.yml --limit k3s_master
Playbook이 정상적으로 완료되면 다음과 같이 접속 정보가 출력됩니다.
TASK [argocd : 접속 정보 출력]
ok: [master-node.orb.local] => {
"msg": [
"URL : <http://argocd.orb.local>",
"ID : admin",
"Password : ZyYW4h0PB1M4Nh8j"
]
}
위의 정보로 접속을 하게 되면, 아래와 같은 argocd 화면을 확인하실 수 있습니다.

자세한 코드 내용은 아래 깃허브에서 확인하실 수 있습니다.
https://github.com/hweyoung/atlassian-k8s-gitops/tree/main/ansible
마치며
이번 작업에서 가장 크게 느낀 것은, 직접 손으로 쳐보지 않으면 자동화 코드를 제대로 쓸 수 없다는 점입니다.
권한 충돌 문제도 터미널에서 직접 부딪혀봤기 때문에 Ansible에서 become: no로 설정해야 하는 이유를 이해할 수 있었고, server.insecure: "true"도 무한 리다이렉트를 실제로 경험한 뒤에야 왜 필요한지 납득이 됐습니다.
이 구조를 이해하고 나면 한 가지가 눈에 들어옵니다. 우리가 이번에 한 작업을 다시 보면 이렇습니다.
helm upgrade --install argocd argo/argo-cd -f argocd-values.yaml
# "공식 차트를 가져오되, 이 파일의 내용으로 기본값을 덮어써서 배포해 줘"
f 옵션 하나로 공식 차트의 기본값 위에 우리 환경에 맞는 설정을 얹는 것이 Helm 배포의 핵심입니다.
그리고 이 구조는 argocd 배포 과정에서도 유사하게 적용됩니다.
다음 글에서는 이렇게 설치한 ArgoCD를 실제로 사용해서 GitHub 저장소와 연동하고, GitOps 방식으로 배포하는 과정을 정리하겠습니다.
'프로젝트 > 엔터프라이즈 JVM 애플리케이션의 Cloud-Native 전환기' 카테고리의 다른 글
| 3-2. ArgoCD로 GitOps 배포 파이프라인 구축하기 (0) | 2026.05.06 |
|---|---|
| 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 |