Ssoon

Jenkins + ArgoCD : Argo CD + K8S(Kind) 본문

CICD Study [1기]

Jenkins + ArgoCD : Argo CD + K8S(Kind)

구구달스 2025. 10. 19. 15:12

1️⃣ Argo CD

🔧 1. Argo CD란 무엇인가?

  • Argo CD는 쿠버네티스(Kubernetes) 환경에서 GitOps 방식으로 배포(CD: Continuous Delivery)를 자동화하는 오픈소스 도구
  • 즉, 애플리케이션의 *“원하는 상태(desired state)”*를 Git 저장소에 선언하고, Argo CD가 실제 클러스터의 *“현재 상태(live state)”*와 비교해서 자동으로 맞춰주는 역할
  • 이런 방식은 수작업으로 kubectl apply … 하는 것보다 자동화 + 추적가능 + 안정성

🔧 2. 왜 사용해야 할까?

  • Git을 *단일 진실 저장소(single source of truth)*로 삼아 애플리케이션과 인프라 구성을 버전관리하고 깔끔히 관리
  • 실행 중인 클러스터 상태와 Git에 선언된 상태가 어긋나면(“drift”) 이를 자동으로 바로잡거나 경고
  • 여러 쿠버네티스 클러스터나 여러 팀 환경(멀티 테넌시)에서도 하나의 Argo CD 인스턴스로 관리 가능하고, UI/CLI 지원으로 사용 편의성

🔧 3. 주요 핵심 개념

  • 애플리케이션(Application): Argo CD에서 ‘배포하고자 하는 리소스 묶음’을 의미. Git 리포지토리 + 경로(path) + 타겟 클러스터 정보를 포함
  • 원하는 상태(Target state): Git 저장소 등에 선언된 애플리케이션 구성(예: YAML, Helm 차트, Kustomize 등)
  • 현재 상태(Live state): 쿠버네티스 클러스터에서 실제로 실행되고 있는 리소스 상태
  • 동기화(Sync): 현재 상태와 원하는 상태가 다를 때, Argo CD가 조치를 취해 두 상태를 일치시키는 과정
  • 드리프트(Drift): 현재 클러스터 상태가 Git에 선언된 상태와 달라지는 현상. Argo CD는 이를 탐지하고 동기화

🔧 4. 어떻게 동작하나? (간단 흐름)

  1. Git 저장소에 애플리케이션 구성(매니페스트, Helm 차트 등)을 커밋
  2. Argo CD는 이 저장소를 주기적으로 또는 이벤트(Webhook)로 감시
  3. 쿠버네티스 클러스터에서 실제 실행 중인 상태와 Git에 선언된 상태를 비교
  4. 상태가 다르면(Out-Of-Sync) → 자동 또는 수동으로 Sync 수행해서 실제 클러스터 상태를 원하는 상태로
  5. 이후에도 지속적으로 감시하여 새롭게 Git이 바뀌거나 클러스터에 직접 변경이 생기면 다시 동기화 유지

2️⃣ Argo CD 설치 및 기본 설정

root@DESKTOP-72C919S:~/cicd-labs# kubectl create ns argocd
namespace/argocd created
root@DESKTOP-72C919S:~/cicd-labs# cat <<EOF > argocd-values.yaml
dex:
  enabled: false

server:
  service:
    type: NodePort
    nodePortHttps: 30002
  extraArgs:
    - --insecure  # HTTPS 대신 HTTP 사용
EOF
  • 설치 : Argo CD v3.1.9
root@DESKTOP-72C919S:~/cicd-labs# helm repo add argo https://argoproj.github.io/argo-helm
"argo" has been added to your repositories
root@DESKTOP-72C919S:~/cicd-labs# helm install argocd argo/argo-cd --version 9.0.5 -f argocd-values.yaml --namespace argocd
NAME: argocd
LAST DEPLOYED: Fri Oct 31 22:04:04 2025
NAMESPACE: argocd
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
In order to access the server UI you have the following options:

1. kubectl port-forward service/argocd-server -n argocd 8080:443

    and then open the browser on http://localhost:8080 and accept the certificate

2. enable ingress in the values file `server.ingress.enabled` and either
      - Add the annotation for ssl passthrough: https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/#option-1-ssl-passthrough
      - Set the `configs.params."server.insecure"` in the values file and terminate SSL at your ingress: https://argo-cd.readthedocs.io/en/stable/operator-manual/ingress/#option-2-multiple-ingress-objects-and-hosts


After reaching the UI the first time you can login with username: admin and the random password generated during the installation. You can find the password by running:

kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d

(You should delete the initial secret afterwards as suggested by the Getting Started Guide: https://argo-cd.readthedocs.io/en/stable/getting_started/#4-login-using-the-cli)
  • 최초 접속 암호 확인
root@DESKTOP-72C919S:~/cicd-labs# kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d ;echo
hHRyrjx2tJINiSHs
  • Argo CD 웹 접속

  • UPDATE PASSWORD

  • ops-deploy Repo 등록

  •  Argo CD가 Gogs 저장소에 연결은 했지만, 저장소가 비어 있어서 브랜치나 커밋을 찾을 수 없기 때문에 등록에 실패

3️⃣ Repo(ops-deploy) 에 nginx helm chart 를 Argo CD를 통한 배포 1

root@DESKTOP-72C919S:~/cicd-labs# MyIP=172.18.234.111
TOKEN=a85d3883ae34f3eb334c2cfd2aaa05419ef8493f
root@DESKTOP-72C919S:~/cicd-labs# git clone http://devops:$TOKEN@$MyIP:3000/devops/ops-deploy.git
Cloning into 'ops-deploy'...
warning: You appear to have cloned an empty repository.
root@DESKTOP-72C919S:~/cicd-labs# cd ops-deploy
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# git config --local user.name "devops"
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# git config --local user.email "devops@example.com"
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# git config --local init.defaultBranch main
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# git config --local credential.helper store
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# git --no-pager config --local --list
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
remote.origin.url=http://devops:a85d3883ae34f3eb334c2cfd2aaa05419ef8493f@172.18.234.111:3000/devops/ops-deploy.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
branch.master.remote=origin
branch.master.merge=refs/heads/master
user.name=devops
user.email=devops@example.com
init.defaultbranch=main
credential.helper=store
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# git --no-pager branch
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# git remote -v
origin  http://devops:a85d3883ae34f3eb334c2cfd2aaa05419ef8493f@172.18.234.111:3000/devops/ops-deploy.git (fetch)
origin  http://devops:a85d3883ae34f3eb334c2cfd2aaa05419ef8493f@172.18.234.111:3000/devops/ops-deploy.git (push)

 

root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# VERSION=1.26.1
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# mkdir nginx-chart
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# mkdir nginx-chart/templates
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# cat > nginx-chart/VERSION <<EOF
$VERSION
EOF
  • nginx-chart/templates/configmap.yaml
cat > nginx-chart/templates/configmap.yaml <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Release.Name }}
data:
  index.html: |
{{ .Values.indexHtml | indent 4 }}
EOF
  • nginx-chart/templates/deployment.yaml
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# cat > nginx-chart/templates/deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}
    spec:
      containers:
      - name: nginx
        image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
        ports:
        - containerPort: 80
        volumeMounts:
        - name: index-html
          mountPath: /usr/share/nginx/html/index.html
          subPath: index.html
      volumes:
      - name: index-html
        configMap:
          name: {{ .Release.Name }}
EOF
  • nginx-chart/templates/service.yaml
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# cat > nginx-chart/templates/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}
spec:
  selector:
    app: {{ .Release.Name }}
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
    nodePort: 30000
  type: NodePort
EOF
  • nginx-chart/values-dev.yaml
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# cat > nginx-chart/values-dev.yaml <<EOF
indexHtml: |
  <!DOCTYPE html>
  <html>
  <head>
    <title>Welcome to Nginx!</title>
  </head>
  <body>
    <h1>Hello, Kubernetes!</h1>
    <p>DEV : Nginx version $VERSION</p>
  </body>
  </html>

image:
  repository: nginx
  tag: $VERSION

replicaCount: 1
EOF
  • nginx-chart/values-prd.yaml

 

root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# cat > nginx-chart/values-prd.yaml <<EOF
indexHtml: |
  <!DOCTYPE html>
  <html>
  <head>
    <title>Welcome to Nginx!</title>
  </head>
  <body>
    <h1>Hello, Kubernetes!</h1>
    <p>PRD : Nginx version $VERSION</p>
  </body>
  </html>

image:
  repository: nginx
  tag: $VERSION

replicaCount: 2
EOF
  • nginx-chart/Chart.yaml
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# cat > nginx-chart/Chart.yaml <<EOF
apiVersion: v2
name: nginx-chart
description: A Helm chart for deploying Nginx with custom index.html
type: application
version: 1.0.0
appVersion: "$VERSION"
EOF
  • 폴더 구조
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# tree
.
└── nginx-chart
    ├── Chart.yaml
    ├── VERSION
    ├── templates
    │   ├── configmap.yaml
    │   ├── deployment.yaml
    │   └── service.yaml
    ├── values-dev.yaml
    └── values-prd.yaml

3 directories, 7 files
  • git push
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# git branch -m main
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# git add .
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# git commit -m "Add nginx helm chart"
On branch main
Your branch is based on 'origin/master', but the upstream is gone.
  (use "git branch --unset-upstream" to fixup)

nothing to commit, working tree clean
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# git push -u origin main
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 12 threads
Compressing objects: 100% (9/9), done.
Writing objects: 100% (11/11), 1.38 KiB | 704.00 KiB/s, done.
Total 11 (delta 1), reused 0 (delta 0), pack-reused 0
To http://172.18.234.111:3000/devops/ops-deploy.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

  • ArgoCD 연결확인

  • Argo CD에 App 등록
    • SYNC POLICY : Manual
      • AUTO-CREATE NAMESPACE : 클러스터에 네임스페이스가 없을 시 argocd에 입력한 이름으로 자동 생성
      • APPLY OUT OF SYNC ONLY : 현재 동기화 상태가 아닌 리소스만 배포
    • PRUNE PROPAGATION POLICY
      • foreground : 부모(소유자, ex. deployment) 자원을 먼저 삭제함
      • background : 자식(종속자, ex. pod) 자원을 먼저 삭제함
      • orphan : 고아(소유자는 삭제됐지만, 종속자가 삭제되지 않은 경우) 자원을 삭제함

root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# kubectl get applications -n argocd
NAME        SYNC STATUS   HEALTH STATUS
dev-nginx   OutOfSync     Missing
  • SYNC 클릭 으로 K8S(Live) 반영
    • PRUNE : GIt에서 자원 삭제 후 배포시 K8S에서는 삭제되지 않으나, 해당 옵션을 선택하면 삭제시킴
    • FORCE : --force 옵션으로 리소스 삭제
    • APPLY ONLY : ArgoCD의 Pre/Post Hook은 사용 안함 (리소스만 배포)
    • DRY RUN : 테스트 배포 (배포에 에러가 있는지 한번 확인해 볼때 사용)

4️⃣ Repo(ops-deploy) 에 nginx helm chart 를 Argo CD를 통한 배포 2 : ArgoCD Declarative Setup

 

🧷 정의 — 파이널라이저(Finalizer)

  • 파이널라이저는 리소스의 metadata.finalizers 필드에 들어가는 문자열 목록이며, 해당 리소스가 실제로 삭제되기 전에 실행되어야 하는 정리 작업을 표현합니다. 

🔁 동작 방식 — 삭제 흐름

  • 사용자가 kubectl delete 등으로 리소스를 삭제하면 API는 바로 리소스를 즉시 지우지 않고 deletionTimestamp를 설정해 삭제 대기(terminating) 상태로 만듭니다.
  • 그 리소스에 finalizer 항목이 있으면, finalizer를 담당하는 컨트롤러(또는 코드)가 정리 작업을 완료하고 finalizer 항목을 metadata.finalizers에서 제거해야 리소스가 실제로 삭제됩니다. 

⚠️ 문제 상황 — 삭제가 멈출 때(스테이킹)

  • 정리 작업을 처리하는 컨트롤러가 동작하지 않거나 finalizer를 제거하지 못하면 리소스는 Terminating 상태에 영구적으로 머무를 수 있습니다(삭제가 끝나지 않음). 이 상태를 흔히 “stuck terminating”이라고 부릅니다. 

🛠️ 해결(임시/수동) 방법

  • 컨트롤러가 복구되도록 조치하는 것이 우선입니다.
  • 급하면 kubectl edit으로 해당 리소스의 metadata.finalizers를 수동으로 삭제하거나(편집 후 저장), kubectl patch로 finalizer 배열을 비워서(또는 제거해서) 강제 삭제할 수 있습니다. (수동 제거는 정리 작업이 실제로 수행되지 않으므로 데이터 무결성에 영향을 줄 수 있으니 주의) 

 권장 사항 / 모범 사례 (짧게)

  • finalizer는 꼭 필요한 경우에만 사용하세요(예: 외부 시스템 정리, 영구 스토리지 정리 등).
  • finalizer를 만든 컨트롤러는 실패 시 재시도 로직과 상태 관리를 잘 갖춰야 합니다.
  • “stuck” 상황을 모니터링하고, 수동 제거 절차(누가 언제 제거할지)를 운영 정책으로 정의해 두세요
  • ArgoCD Finalizers의 목적
    1. 리소스 정리 보장: 애플리케이션 삭제 시 관련 리소스가 남지 않도록 보장합니다. 이는 GitOps 워크플로우에서 선언적 상태를 유지하는 데 중요합니다.
    2. 의도치 않은 삭제 방지: finalizer가 없으면 실수로 Argo App을 삭제해도 K8S 리소스가 남아 혼란이 생길 수 있습니다. finalizer는 이를 방지합니다.
    3. App of Apps 패턴 지원: 여러 애플리케이션을 계층적으로 관리할 때, 상위 애플리케이션 삭제 시 하위 리소스까지 정리되도록 합니다.

  • dev-nginx App 생성 및 Auto SYNC
    • ⚠️ 경고 메시지 설명
      • finalizers 경고:
        • resources-finalizer.argocd.argoproj.io는 일반적으로 사용되며, 경고는 권장사항일 뿐입니다.
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: dev-nginx
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    helm:
      valueFiles:
      - values-dev.yaml
    path: nginx-chart
    repoURL: http://$MyIP:3000/devops/ops-deploy
    targetRevision: HEAD
  syncPolicy:
    automated:
      prune: true
    syncOptions:
    - CreateNamespace=true
  destination:
    namespace: dev-nginx
    server: https://kubernetes.default.svc
EOF
Warning: metadata.finalizers: "resources-finalizer.argocd.argoproj.io": prefer a domain-qualified finalizer name including a path (/) to avoid accidental conflicts with other finalizer writers
application.argoproj.io/dev-nginx created

root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# curl http://127.0.0.1:30000
<!DOCTYPE html>
<html>
<head>
  <title>Welcome to Nginx!</title>
</head>
<body>
  <h1>Hello, Kubernetes!</h1>
  <p>DEV : Nginx version 1.26.1</p>
</body>
</html>
  • Argo CD App 삭제
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# kubectl delete applications -n argocd dev-nginx
application.argoproj.io "dev-nginx" deleted from argocd namespace
  • prd-nginx App 생성 및 Auto SYNC
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: prd-nginx
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  destination:
    namespace: prd-nginx
    server: https://kubernetes.default.svc
  project: default
  source:
    helm:
      valueFiles:
      - values-prd.yaml
    path: nginx-chart
    repoURL: http://$MyIP:3000/devops/ops-deploy
    targetRevision: HEAD
  syncPolicy:
    automated:
      prune: true
    syncOptions:
    - CreateNamespace=true
EOF
Warning: metadata.finalizers: "resources-finalizer.argocd.argoproj.io": prefer a domain-qualified finalizer name including a path (/) to avoid accidental conflicts with other finalizer writers
application.argoproj.io/prd-nginx created

root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# curl http://127.0.0.1:30000
<!DOCTYPE html>
<html>
<head>
  <title>Welcome to Nginx!</title>
</head>
<body>
  <h1>Hello, Kubernetes!</h1>
  <p>PRD : Nginx version 1.26.1</p>
</body>
</html>
  • Argo CD App 삭제
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# kubectl delete applications -n argocd prd-nginx
application.argoproj.io "prd-nginx" deleted from argocd namespace

 

5️⃣ Repo(ops-deploy) 에 Webhook 를 통해 Argo CD 에 즉시 반영 trigger하여 k8s 배포 할 수 있게 설정

  • Repo(ops-deploy) 에 webhooks 설정 : 이후 생성된 webhook 클릭 후 Test Delivery 클릭 후 정상 응답 확인

 

root@DESKTOP-72C919S:~/cicd-labs/ops-deploy# cat <<EOF | kubectl apply -f -
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: dev-nginx
  namespace: argocd
  finalizers:
  - resources-finalizer.argocd.argoproj.io
spec:
  project: default
  source:
    helm:
      valueFiles:
      - values-dev.yaml
    path: nginx-chart
    repoURL: http://$MyIP:3000/devops/ops-deploy
    targetRevision: HEAD
  syncPolicy:
    automated:
      prune: true
    syncOptions:
    - CreateNamespace=true
  destination:
    namespace: dev-nginx
    server: https://kubernetes.default.svc
EOF
Warning: metadata.finalizers: "resources-finalizer.argocd.argoproj.io": prefer a domain-qualified finalizer name including a path (/) to avoid accidental conflicts with other finalizer writers
application.argoproj.io/dev-nginx created

  • Git(Gogs) 수정 후 ArgoCD 즉시 반영 확인
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy/nginx-chart# sed -i "s|replicaCount: 1|replicaCount: 2|g" va
lues-dev.yaml

root@DESKTOP-72C919S:~/cicd-labs/ops-deploy/nginx-chart# git status
On branch main
Your branch is up to date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   values-dev.yaml

no changes added to commit (use "git add" and/or "git commit -a")

root@DESKTOP-72C919S:~/cicd-labs/ops-deploy/nginx-chart# git add values-dev.yaml
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy/nginx-chart# git commit -m "Modify nginx-chart : values-dev.yaml"
[main 55fafde] Modify nginx-chart : values-dev.yaml
 1 file changed, 1 insertion(+), 1 deletion(-)
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy/nginx-chart# git push -u origin main
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 12 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 348 bytes | 348.00 KiB/s, done.
Total 4 (delta 2), reused 0 (delta 0), pack-reused 0
To http://172.18.234.111:3000/devops/ops-deploy.git
   a7a6884..55fafde  main -> main
branch 'main' set up to track 'origin/main'.

  • Argo CD Application 삭제
root@DESKTOP-72C919S:~/cicd-labs/ops-deploy/nginx-chart# kubectl delete applications -n argocd dev-nginx
application.argoproj.io "dev-nginx" deleted from argocd namespace

 

 

 

Comments