쿠버네티스를 처음 접하면 보통 kubectl apply -f명령어로 리소스를 배포 한다. 배포 자동화를 위해 ArgoCD를 만져보니 아래와 같은 고민이 생겼다.
ArgoCD만 사용해도 GitOps 방식으로 배포는 가능하지만, 여러 애플리케이션을 각각 Application 리소스로 관리하다 보면 변경 이력이 분산되고 관리 포인트가 많아진다.
ArgoCD에서 이런 문제를 해결하기 위해 사용하는 방법이 바로 App of Apps 패턴이며, 이 패턴은 Root Application을 통해 여러 Child Application을 Git 저장소에서 관리할 수 있도록 해준다.
이것은 애플리케이션 변경 사항이 모두 Git에 남고, ArgoCD는 이를 읽어 클러스터 상태를 동기화하기 때문에 GitOps 답게 변경 이력 관리와 배포를 할 수 있게 된다.
EKS 클러스터 위에 ArgoCD를 설치하고, 이 App of Apps 패턴을 적용해 애플리케이션을 깔끔하게 관리하는 방법을 정리했다.
위 문제를 해결하기 위해 사용한 방법이 바로 App of Apps 패턴이다. 이 패턴에서는 다음과 같은 구조로 애플리케이션을 관리한다.

Root App 하나만 관리하면 하위 App들이 자동으로 동기화되므로, 쿠버네티스 클러스터 전체 상태를 Git에 그대로 남기고, 재현 가능한 형태로 관리할 수 있게 된다.
Child Application을 무조건 Git에 올려야만 사용 가능하기 때문에 GitOps가 강제된다.
당연한 이야기일 수도 있지만, GitOps를 지향하는 조직이라면 Git에 안 올리고 운영하는 예외 케이스가 원천 차단된다는 점이 꽤 큰 장점이 된다.
Application의 추가/삭제, 설정 변경 등 모든 이력을 Git 커밋으로 남길 수 있다.
새로운 클러스터를 만들고 ArgoCD를 설치한 다음, Root Application 하나만 추가하면 된다.
그럼 기존 클러스터에 있던 모든 Application이 자동으로 배포된다.
클러스터마다 ArgoCD를 따로 설치해야 하는 불편이 있는데, 이는 멀티 클러스터 ArgoCD 운영 또는 ApplicationSet 기능을 활용하면 해결할 수 있다.
ArgoCD App of Apps 를 구성하기 전 아래의 제약 사항을 정리했다.
eksctl사용하지 않음Helm을 설치하려면 운영 체제(Linux, macOS, Windows 등)에 따라 공식 바이너리 릴리스 페이지에서 다운로드하거나, Homebrew, Chocolatey 같은 패키지 관리 도구를 사용하는 방법이 있다.
더 자세한 방법은 Helm 설치공식 문서를 통해 확인하면 좋다.
Mac (Homebrew 사용)
brew install helm
Windows (Chocolatey 사용)
choco install kubernetes-helm
Linux (Fedora 계열)
sudo yum install helm
sudo dnf install helm
Mac, Linux
# Linux-amd64 버전 다운로드
wget https://get.helm.sh/helm-v3.12.3-linux-amd64.tar.gz
tar -zxvf helm-v3.12.3-linux-amd64.tar.gz
# /usr/local/bin 디렉토리로 이동
sudo mv linux-amd64/helm /usr/local/bin/helm
과정은 아래와 같으며, AWS 환경에서 진행할 수 있는 것은 tofu로 진행하였다.
로드밸런서 컨트롤러도 Helm 및 Manifest 방식으로 설치할 수 있으며, 작성자는 Helm 방식으로 진행한다.
OIDC Provider 생성 → IAM Policy 생성 → IAM Role 생성 및 Trust Policy 설정 → ServiceAccount에 Role 연결 (kubectl을 통해서 진행)
ServiceAccount의 경우 eksctl로 수행할 수 있지만, 작성자는 수동으로 입력
# ServiceAccount 생성
$ kubectl create serviceaccount <SA Name> -n kube-system
# ServiceAccount에 Role 연결
$ kubectl annotate serviceaccount <SA Name> -n kube-system eks.amazonaws.com/role-arn=arn:aws:iam::<Account>:role/<Role Name>
# ServiceAccount 확인 (IRSA)
$ kubectl get serviceaccount <SA Name> -n kube-system -o yaml
# annotation이 제대로 설정되었는지 확인
$ kubectl describe serviceaccount <SA Name> -n kube-system
ArgoCD 접속을 위한 Ingress 설정을 위해 AWS LoadBalancer Controller도 함께 설치를 한다.
# Helm 레포지토리를 추가
$ helm repo add eks https://aws.github.io/eks-charts
# 로컬 레포지토리를 업데이트
$ helm repo update eks
# Helm AWS 로드 밸런서 컨트롤러 버전 확인
$ helm search repo eks/aws-load-balancer-controller --versions
NAME CHART VERSION APP VERSION DESCRIPTION
eks/aws-load-balancer-controller 1.13.4 v2.13.4 AWS Load Balancer Controller Helm chart for Kub...
eks/aws-load-balancer-controller 1.13.3 v2.13.3 AWS Load Balancer Controller Helm chart for Kub...
eks/aws-load-balancer-controller 1.13.2 v2.13.2 AWS Load Balancer Controller Helm chart for Kub...
...중략...
# AWS 로드 밸런서 컨트롤러를 설치
# --version은 제거해도 무방하며, 없이 한다면 최신 버전이 설치된다.
$ helm install <Helm Release Name> eks/aws-load-balancer-controller -n kube-system --set clusterName=<EKS Name> --set serviceAccount.create=false --set serviceAccount.name=<SA Name> --set region=<Region> --set vpcId=<VPC ID> --version <Version>
# 만약 에러가 뜬다면 아래와 같이 진행
$ helm upgrade <Helm Release Name> eks/aws-load-balancer-controller -n kube-system --set clusterName=<EKS Name> --set serviceAccount.create=false --set serviceAccount.name=<SA Name> --set region=<Region> --set vpcId=<VPC ID> --version <Chart Version>
or
# 삭제 후 위 install 재진행
$ helm uninstall <Helm Release Name> -n kube-system
# deployment 확인
$ kubectl get deployment -n kube-system aws-load-balancer-controller
NAME READY UP-TO-DATE AVAILABLE AGE
aws-load-balancer-controller 2/2 2 2 5s
# crd 확인 (TargetGroupBinding, IngressClassParams 같은 리소스)
$ kubectl get crd | findstr elbv2
ingressclassparams.elbv2.k8s.aws 2025-09-08T02:29:56Z
targetgroupbindings.elbv2.k8s.aws 2025-09-08T02:29:57Z
# 확인이 안될 시 아래 추가 명령어 수행
$ wget https://raw.githubusercontent.com/aws/eks-charts/master/stable/aws-load-balancer-controller/crds/crds.yaml
$ kubectl apply -f crds.yaml
# Ingress가 생성이 되지 않는다면 꼭 로그 확인!!
$ kubectl logs -n kube-system -l app.kubernetes.io/name=aws-load-balancer-controller
ArgoCD는 GitOps를 구현하기 위한 도구 중 하나로 애플리케이션의 자동 배포를 위한 오픈소스 도구다. 클러스터에 배포된 애플리케이션의 CI/CD 파이프라인에서 CD부분을 담당하며, Git 저장소에서 변경 사항을 감지하여 자동으로 Kubernetes 클러스터에 애플리케이션을 배포할 수 있다.
# Helm repo 추가
$ helm repo add argo https://argoproj.github.io/argo-helm
$ helm repo update
# Helm ArgoCD 버전 확인
$ helm search repo argo/argo-cd --versions
NAME CHART VERSION APP VERSION DESCRIPTION
argo/argo-cd 8.3.5 v3.1.4 A Helm chart for Argo CD
argo/argo-cd 8.3.4 v3.1.3 A Helm chart for Argo CD
argo/argo-cd 8.3.3 v3.1.1 A Helm chart for Argo CD
argo/argo-cd 8.3.2 v3.1.1 A Helm chart for Argo CD
# Namespace 생성
$ kubectl create namespace argocd
# yaml 다운
$ wget https://github.com/argoproj/argo-cd/blob/master/manifests/ha/install.yaml
# ArgoCD 설치 (아래 yaml 참고, values 파일에서 커스텀하게 만들고 싶을 시)
$ helm upgrade --install argocd argo/argo-cd -n argocd --create-namespace -f values-ha.yaml --version <Chart Version>
# Ingress 배포 (아래 yaml 참고)
$ kubectl apply -f ingress.yaml
# ArgoCD 패스워드 확인 (User는 admin)
$ kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}"
Incode : TnpXTGlLZG1vSmJKZ3ZJbA==
Decode : NzWLiKdmoJbJgvIl
ArgoCD 구축 시 외부 접근을 위한 설정은 아래의 yaml을 통해 진행하며, ArgoCD는 위 helm 명령어를 통해 배포하며, Ingress는 ingress.yaml 생성하여 배포한다.
# yaml 다운
$ wget https://github.com/argoproj/argo-cd/blob/master/manifests/ha/install.yaml
# 특정 버전 다운
$ wget https://raw.githubusercontent.com/argoproj/argo-cd/v2.4.7/manifests/ha/install.yaml
# Manifest 배포
$ kubectl apply -f install.yaml
# Ingress 배포 (아래 yaml 참고)
$ kubectl apply -f ingress.yaml
# ArgoCD 패스워드 확인 (User는 admin)
$ kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}"
Incode : TnpXTGlLZG1vSmJKZ3ZJbA==
Decode : NzWLiKdmoJbJgvIl
작성자는 Manifest로 진행하지 않았지만, 위와 같은 방법을 통해서 동일하게 ArgoCD를 생성할 수 있다.
# values-ha.yaml
controller:
name: controller
replicas: 1
redis:
enabled: false
redis-ha:
name: redis-ha
enabled: true
replicas: 2
haproxy:
enabled: true
replicas: 2
server:
name: server
replicas: 2
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 2
extraArgs:
- --insecure
service:
type: ClusterIP
ingress:
enabled: false
config:
url: https://<URL>
application.instanceLabelKey: argocd.argoproj.io/instance
resources:
limits:
cpu: 1024m
memory: 1Gi
requests:
cpu: 250m
memory: 64Mi
repoServer:
name: repo
replicas: 2
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 2
resources:
limits:
cpu: 500m
memory: 1Gi
requests:
cpu: 250m
memory: 64Mi
위 yaml을 통해 helm으로 배포 시 아래와 같이 argocd 네임스페이스에서 관련한 리소스를 확인할 수 있다.
# argocd 네임스페이스의 리소스 확인
kubectl get all -n argocd
NAME READY STATUS RESTARTS AGE
pod/argocd-applicationset-controller-6597554f78-gjd47 1/1 Running 0 13m
pod/argocd-controller-0 1/1 Running 0 13m
pod/argocd-dex-server-74cb795495-bq6kj 1/1 Running 0 13m
pod/argocd-notifications-controller-5546c494d6-p8ml8 1/1 Running 0 13m
pod/argocd-redis-ha-haproxy-56bbcd78b5-jfcbc 1/1 Running 0 13m
pod/argocd-redis-ha-haproxy-56bbcd78b5-vrpwx 1/1 Running 0 13m
pod/argocd-redis-ha-server-0 3/3 Running 0 13m
pod/argocd-redis-ha-server-1 3/3 Running 0 12m
pod/argocd-repo-58c788b985-rsj2t 1/1 Running 0 13m
pod/argocd-repo-58c788b985-sbqmt 1/1 Running 0 13m
pod/argocd-server-cf7797685-vrvpn 1/1 Running 0 13m
pod/argocd-server-cf7797685-w9v49 1/1 Running 0 13m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/argocd-applicationset-controller ClusterIP 10.90.79.203 <none> 7000/TCP 13m
service/argocd-dex-server ClusterIP 10.90.137.70 <none> 5556/TCP,5557/TCP 13m
service/argocd-redis-ha ClusterIP None <none> 6379/TCP,26379/TCP 13m
service/argocd-redis-ha-announce-0 ClusterIP 10.90.24.194 <none> 6379/TCP,26379/TCP 13m
service/argocd-redis-ha-announce-1 ClusterIP 10.90.230.74 <none> 6379/TCP,26379/TCP 13m
service/argocd-redis-ha-haproxy ClusterIP 10.90.49.242 <none> 6379/TCP,9101/TCP 13m
service/argocd-repo ClusterIP 10.90.22.6 <none> 8081/TCP 13m
service/argocd-server ClusterIP 10.90.133.215 <none> 80/TCP,443/TCP 13m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/argocd-applicationset-controller 1/1 1 1 13m
deployment.apps/argocd-dex-server 1/1 1 1 13m
deployment.apps/argocd-notifications-controller 1/1 1 1 13m
deployment.apps/argocd-redis-ha-haproxy 2/2 2 2 13m
deployment.apps/argocd-repo 2/2 2 2 13m
deployment.apps/argocd-server 2/2 2 2 13m
NAME DESIRED CURRENT READY AGE
replicaset.apps/argocd-applicationset-controller-6597554f78 1 1 1 13m
replicaset.apps/argocd-dex-server-74cb795495 1 1 1 13m
replicaset.apps/argocd-notifications-controller-5546c494d6 1 1 1 13m
replicaset.apps/argocd-redis-ha-haproxy-56bbcd78b5 2 2 2 13m
replicaset.apps/argocd-repo-58c788b985 2 2 2 13m
replicaset.apps/argocd-server-cf7797685 2 2 2 13m
NAME READY AGE
statefulset.apps/argocd-controller 1/1 13m
statefulset.apps/argocd-redis-ha-server 2/2 13m
Architectural Overview 에서 ArgoCD에서 제공하는 각 컴포넌트의 역할을 정리하였다. (Controller, Server, Repository Server)
High Availability 페이지는 HA 구성 시 사용되는 컴포넌트의 설정이나 역할 등 자세히 확인할 수 있다.
controller
Git에 선언된 애플리케이션 Manifest를 감시하고 클러스터 상태와 선언된 상태를 동기화하는 핵심 컴포넌트. 라이프사이클 이벤트(PreSync, Sync, PostSync)에 대한 사용자 정의 후크를 호출
redis
단일 인스턴스 Redis를 내장(또는 의존 차트로) 띄우는 설정. ArgoCD는 세션/캐시용으로 Redis를 사용
redis-ha
고가용성(HA) Redis 구성. 보통 Redis master/replica + Sentinel(또는 클러스터) + (선택적) 프록시(Haproxy)를 포함하는 형태
haproxy
HAProxy는 Redis 노드(특히 master/replica 전환 시)를 앞단에서 추상화해주는 프록시/로드밸런서 역할. 클라이언트(예: argocd-server)가 한 곳으로 접속하면 HAProxy가 현재 마스터로 라우팅
server
argocd-server (API + UI). 사용자가 로그인하고 UI/CLI가 통신하는 엔드포인트. gRPC/HTTP API를 제공하고, 로그인/세션을 관리
repoServer
argocd-repo-server는 Git/Helm/Kustomize 등 레포지토리를 클론하고 Manifest를 렌더링하는 구성요소. 레포지토리의 내용을 캐시하고, 템플릿 렌더링(helm template, kustomize build, plugins 실행 등)을 담당
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: argocd-ingress
namespace: argocd
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/scheme: internet-facing # 내부 사용 시 internal
alb.ingress.kubernetes.io/target-type: ip
# ALB Subnet
alb.ingress.kubernetes.io/subnets: <Subnet ID1>,<Subnet ID2> ...
# ALB Security Group
alb.ingress.kubernetes.io/security-groups: <ALB Security Group ID>
# ALB Listener 설정
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP":80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/certificate-arn: <ACM ARN>
# HTTP → HTTPS 리다이렉션
alb.ingress.kubernetes.io/ssl-redirect: '443'
spec:
rules:
- host: <Use Domain>
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: argocd-server
port:
number: 443

최초 로그인 후 접속을 하게 된다면 위와 같은 화면을 확인할 수 있다.
리소스(Manifest)가 올라갈 곳은 Github이며, Root Application과 Child Application의 소스는 다르게 가져갔다. 동일한 레포지토리로 구성 할 수 있지만, 작성자는 어플리케이션 구분을 위해 분리 했다.
ArgoCD Root Application : https://github.com/Shin9184/argocd-apps
# Root Application
argocd-apps
├─ Chart.yaml
├─ values.yaml
└─ templates
├─ nginx1.yaml
└─ nginx2.yaml
ArgoCD Child Application : https://github.com/Shin9184/helm-charts
# Child Application
helm-charts
├─ nginx1
│ ├─ Chart.yaml
│ ├─ values.yaml
│ └─ templates
│ ├─ nginx1-deployment.yaml
│ ├─ nginx1-ingress.yaml
│ ├─ nginx1-namespace.yaml
│ └─ nginx1-service.yaml
└─ nginx2
├─ Chart.yaml
├─ values.yaml
└─ templates
├─ nginx2-deployment.yaml
├─ nginx2-ingress.yaml
├─ nginx2-namespace.yaml
└─ nginx2-service.yaml
Applications 에서 NEW APP 클릭하여 콘솔에서 생성도 가능하다. 또는 아래 yaml과 같이 상단의 Application 생성이 필요하다.
# Root Application manifest 파일 예시
project: default
source:
repoURL: https://github.com/Shin9184/argocd-apps
path: .
targetRevision: HEAD
destination:
server: https://kubernetes.default.svc
설정이 되었다면 Root Application 의 SYNC를 클릭하면 아래와 같이 나온다.

application 클릭 시 아래와 같이 확인이 되며, App아래에 App들이 있는 것을 확인할 수 있다.

nginx1 App

nginx2 App

각 애플리케이션에서 Ingress를 따로 생성하여 접속 시 각 파드로 접속된 것을 확인할 수 있다.
아래 화면에 보이는 접속 시 보이는 내용은 각 Pod Nginx에 html 파일을 수정하였다.
nginx1

nginx2

App of Apps 패턴은 단순히 애플리케이션을 배포하는 방법이 아니라, 쿠버네티스 애플리케이션 전체 수명 주기를 GitOps스럽게 관리할 수 있게 해주는 구조적 접근이다.
GitOps를 강제함으로써 운영 안정성을 확보하고, 커밋만으로 변경 내역을 추적할 수 있으며, Root Application 하나로 클러스터 전체를 재현할 수 있다는 장점이 있다.
ArgoCD를 사용하고 있다면 단일 Application 배포에서 멈추지 말고 App of Apps 패턴을 한 번 실습한다면 좋을 것 같다.
참고 :
https://malwareanalysis.tistory.com/478
https://heumsi.github.io/blog/posts/argocd-apps-deployment/
https://beer1.tistory.com/68