[GitOps] ArgoCD 구축 및 Application 기반 배포

2023. 1. 19. 20:11[ DevOps ]/[ CI-CD ]

이번 포스팅에서는 ArgoCD 기반의 GitOps 환경 구축 과정을 정리한다.

 

GitOps 구축 순서

1. ArgoCD 환경 구축

0) 사전준비: k8s 클러스터 외부에서 ArgoCD 접속을 위해 사용할 NodePort 포트포워딩(또는 LoadBalancer 사용)

1) ArgoCD Manifest 파일 작성 (kustomize 기반)

2) ArgoCD 설치 쉘 스크립트 파일 작성 

3) ArgoCD Web UI접속 및 로그인

 

2. Git 연동

1) SSH Key 발급

2) GitHub에 등록

3) ArgoCD에 등록

 

3. k8s 클러스터에 애플리케이션 Manifest 배포

1) k8s 클러스터에 애플리케이션 배포

2) GitOps 및 Desired State 검증

 

 


 

ArgoCD 구성 환경

 

 

k8s 클러스터 외부에서 ArgoCD에 접속하기 위해 ArgoCD Manifest의 Service을 LoadBalancer 또는 NodePort로 변경해야 한다. 현재 CSP를 사용하고 있지 않고 private 망에 k8s 클러스터를 구축했기 때문에 NodePort를 사용하기로 했고 해당 포트를 포트포워딩해 두어 외부에서 접속할 수 있도록 변경한다.

 

cf. 플랫폼 버전

- kubeadm, kubelet, kubectl version: 1.26

- Linux kernel(VM): 5.16.2

 

 

ArgoCD 환경 구축

1. ArgoCD Manifest 파일 작성 (kustomize로 다운로드)

- manifests/kustomization.yaml 파일

resources:
  - https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

- 원격서버에 있는 ArgoCD의 Manifest 파일을 가져오기 위해 kustomize를 사용했다.

- 현시점 ArgoCD 버전은 v2.5.4이다.

cf. v2.5.4 버전으로 고정: https://raw.githubusercontent.com/argoproj/argo-cd/v2.5.4/manifests/install.yaml

 

 

2. ArgoCD 설치 쉘 스크립트 파일 작성 (setup-argocd.sh)

# 1. argocd 네임스페이스 생성
kubectl create namespace argocd

# 2. ArgoCD 설치 및 배포
kubectl kustomize ../manifests/| kubectl apply -n argocd -f -

# 3. argocd-server의 type을 NodePort(32666)으로 변경
kubectl patch svc argocd-server -n argocd -p 'apiVersion: v1
kind: Service
metadata:
  labels:
    app.kubernetes.io/component: server
    app.kubernetes.io/name: argocd-server
    app.kubernetes.io/part-of: argocd
  name: argocd-server
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 8080
    nodePort: 32666
  - name: https
    port: 443
    protocol: TCP
    targetPort: 8080
  selector:
    app.kubernetes.io/name: argocd-server'

# 4. ArgoCD Web UI 접속시 admin 패스워드 확인 방법
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo

스크립트 파일 설명

[2. ArgoCD 설치 및 배포]

kustomize를 통해 원격 레지스트리에 있는 Manifest 파일을 가져온다. manifests 디렉토리는 현재 GitOps 방식을 사용하기 위해 관리 중인 GitHub 레포지토리 상에 위치한다.

cf. GitOps Management Repositroy: https://github.com/price-offer/gitops-management

cf. kustomize에 대한 포스팅: https://jh-labs.tistory.com/614

 

 

[3. argocd-server의 type을 NodePort(32666)으로 변경]

ArgoCD의 Manifest 파일을 보면 ArgoCD로 접속하는 Service(argocd-server)의 type이 명시되어 있지 않다. 즉, 기본으로 ClusterIP이다. 따라서 이를 k8s 클러스터 외부에서 접속할 수 있도록 하기 위해 NodePort 또는 LoadBalancer로 변경해줘야 한다.

 

현재 k8s 클러스터가 private 망에 (윈도우 위에 VM을 올려서 실행 중) 구동 중이기 때문에 LoadBalancer type은 사용할 수 없다.(MetalLB + 도메인 연결하면 가능할수도?) 따라서 NodePort로 Service type을 변경해 주었다. 또한 ArgoCD를 윈도우 시스템 외부에서 접속하기 위해 해당 NodePort를 윈도우 시스템에 NAT 포트포워딩을 추가했다. 윈도우 시스템으로 들어오는 포트와 worker 노드로 들어갈 NodePort모두 32666으로 설정했다. 포트포워딩은 worker1 노드와 연결했다. 

 

cf. 참고로 kubectl patch 명령으로 수정할 대상 파일은 YAML와 JSON 모두 가능하다. 결국 YAML도 JSON 기반이기 때문이다. YAML은 줄바꿈과 띄어쓰기에 유의해야 하기 때문에 JSON 형식으로 사용하는 게 더 편하다고 한다. 위 patch 명령은 실제 파일을 수정하는 것이 아니라 실행중인 오브젝트의 설정을 변경한다.

 

 

 

3. 스크립트 실행

아래와 같이 ArgoCD 설치에 대한 위 쉘 스크립트 파일에 실행 권한을 주고 설치한다.

# git으로부터 가져오는 과정 생략

sudo chmod +x setup-argocd.sh && ./setup-argocd.sh

 

설치 확인

kubectl get po -n argocd

kubectl get svc -n argocd

# argocd-server NodePort 10.99.184.78 <none> 80:32666/TCP,443:32556/TCP

- argocd-server 이름의 NodePort Service가 32666 포트로 정상 실행 중임을 확인한다.

- AWS 사용중일 경우 인바운드 규칙에 해당 포트를 추가한다.

 

 

4. ArgoCD Web UI접속 및 로그인

- 위에서 윈도우 시스템과 Worker 노드로 NAT 포트포워딩을 설정해 두었기 때문에 윈도우 시스템 외부에서도 윈도우 시스템의 IP로 접속이 가능하다.(접속하는 클라이언트도 윈도우 시스템과 같은 망에 위치해야 함)

- 웹 브라우저에서 '윈도우 호스트 시스템IP:포워딩한 포트'(필자의 경우, 192.168.9.37:32666)로 접속하고 (크롬의 경우) '고급 -> 안전하지 않음'을 클릭하여 접속한다. (HTTP로 접속했기 때문)

 

- 초기 유저 admin 및 password를 입력하고 로그인한다. (초기 비밀번호 확인 방법: 위 스크립트 파일 참고)

 

 


 

Git 연동

SSH의 키 교환 인증 방식을 통해 ArgoCD와 GitHub을 연동하는 과정이다.

 

1) SSH Key 발급

아래와 같이 SSH Key를 one-line으로 발급한다. (필자는 개인 PC인 Mac환경에서 진행했다.)

ssh-keygen -q -t rsa -N '' -m PEM -b 4096 -C argo -f ~/.ssh/argo_rsa <<<y >/dev/null 2>&1

# SSH Agent에 등록 (Mac)
ssh-add ~/.ssh/argo_rsa

 

 

-q ssh-keygen 명령에 대한 결과를 프롬프트에 출력하지 않는다.
-t rsa 키의 형식을 rsa로 암호화한다.
-N '' 키에 대한 암호를 ''로 사용한다.
-m PEM 키를 pem형식으로 저장한다.
-b 4096 키를 4096 비트로 생성한다.
-C argo 키에 대해 argo라는 주석을 설정한다. SSH에 사용되진 않지만 추후 키를 식별하는데 유용하다.
-f  ~/.ssh/ 디렉토리에 argo_rsa 이름으로 키 파일을 생성한다.
<<<y 문자열 y를 입력으로 리다이렉션하여 입력을 요구하지 않게 한다. (?)
>/dev/null 앞선 명령의 출력을 null 장치로 리다이렉션한다. 따라서 출력 내용이 화면으로 출력되지 않는다.
2>&1 표준 오류 출력을 표준 출력과 동일한 대상으로 리디렉션한다. 즉, 오류메시지도 출력되지 않는다.

 

 

SSH 키 교환 인증 방식을 사용하기 위해 아래와 같이 SSH키를 발급하고 SSH클라이언트(ArgoCD)에 private key를 등록한다. public key는 GitHub에 등록한다.

 

 

2) GitHub에 등록

- GitHub은 SSH 접속 요청을 받을 서버이므로 public key를 등록한다.

- GitHub -> 유저 프로필 Settings -> SSH and GPG Keys

 

 

3) ArgoCD에 등록

 

- ArgoCD Web UI에서 Settings -> Repositories -> CONNECT REPO로 이동한다.

- ArgoCD는 SSH 접속을 시도할 클라이언트이므로 private key를 등록한다.

- Repository URL에는 애플리케이션 소스코드가 아니라 애플리케이션의 Manifest 파일이 관리되는 레포지토리의 SSH URL을 등록해야 한다. 

- CONNECT를 누르고 연결 테스트를 진행한다.

 

 

 


 

k8s 클러스터에 애플리케이션 Manifest 배포

1. k8s 클러스터에 애플리케이션 배포

1) ArgoCD가 관리하는 오브젝트(Application과 Project)

1-1) ArgoCD의 Application

ArgoCD는 애플리케이션을 k8s 클러스터에 배포하기 위해 애플리케이션 Pod를 그대로 사용하지 않고 'Application'이라는 타입의 kind를 정의하여 사용한다. 이는 ArgoCD가 CRD(Custom Resource Definition) 형태로 정의한 오브젝트이며 ArgoCD가 관리하는 배포 단위이고 ArgoCD Web UI 상에서 보이는 Application 탭이 의미하는 것과 같다. ArgoCD의 API를 통해 구현되는 이 Application이라는 오브젝트가 kubectl을 통해 배포되면 ArgoCD의 Web UI 상에 나타나지고 관리자가 확인할 수 있게 된다. Application은 아래와 같이 ArgoCD 설치 Manifest 파일에 CRD 형태로 명시되어 있다.

 

. . .

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  labels:
    app.kubernetes.io/name: applications.argoproj.io
    app.kubernetes.io/part-of: argocd
  name: applications.argoproj.io
spec:
  group: argoproj.io
  names:
    kind: Application
    
. . .

 

 

1-2) ArgoCD의 Project

https://malwareanalysis.tistory.com/407

Project는 ArgoCD가 관리하는 논리적인 단위로 Project 하나는 Application을 그룹화하여 관리한다. 따라서 특정 Application은 특정 Project에 속해야 한다. Project도 Application과 마찬가지로 ArgoCD가 설치될 때 CRD로 생성된다.

 

. . .

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  labels:
    app.kubernetes.io/name: appprojects.argoproj.io
    app.kubernetes.io/part-of: argocd
  name: appprojects.argoproj.io
spec:
  group: argoproj.io
  names:
    kind: AppProject
    
. . .

 

 

cf. CLI 환경에서 Application, Project 관련 명령어

kubectl get AppProject -n argocd     # Project 조회
kubectl get application -n argocd    # Application 조회
kubectl get crd | grep application   # Application CRD 조회
kubectl describe applicatipn <name> -n argocd # Application 상세 조회(Web UI에서 보는 정보와 동일)

 

 

2) Manifest 작성

2-1) ArgoCD Application Manifest

- argo-application/offer-be/application.yaml

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: offer-be
  namespace: argocd
spec:
  destination:
    namespace: offer-be
    server: https://kubernetes.default.svc
  project: default
  source:
    path: services/offer-be
    repoURL: git@github.com:price-offer/application-manifests.git
    targetRevision: HEAD
  syncPolicy:
    automated:
      selfHeal: true
    syncOptions:
    - CreateNamespace=true

 

- ArgoCD v2.5.4 기준으로 Application의 apiVersion은 v1alpha1이다.

- kind 필드로 Application을 사용한다.

- metadata: namespace 필드를 확인하면 Application은 argocd 네임스페이스에 배포되는 것을 확인할 수 있다. 하지만 소스코드를 가져오는 source 부분과 destination은 다른 네임스페이스이다. 혼동해서는 안된다. 즉, Application이 배포될 네임스페이스와 Application을 통해 배포될 오브젝트의 네임스페이스를 구분한 것이다.

 

[Application Manifest의 주요 정보]

Application이 포함하는 주요 정보는 source, destination, project, synchPolicy이다. 즉, "어디로부터 Manifest를 가져와서 어디에 배포할지, sync 전략은 어떻게 할 지에 대한 정보"이다. 이는 spec하위에 작성된다.

 

1) source

- 배포될 Manifest 파일이 위치한 경로

- 실제 서비스(예를 들어 웹 서비스)로 구동될 Manifest들(예를 들어 deployment, service 등)이 저장된 Git 레포지토리 경로이다.

- targetRevision을 HEAD로 지정하여 가장 최신의 서비스 Manifest들을 가져와 sync 한다.

 

2) destination

- 어느 k8s 클러스터와 namespace에 source로부터 가져온 Manifest를 실행할 지에 대한 정보를 작성한다.

 

3) project

- 해당 Application이 관리될 Project를 반드시 1개 입력해야 한다. 

 

4) syncPolicy

- source로부터 서비스의 Manifest를 어떠한 방식으로 가져올지(sync 할지)를 지정한다.

- sync 방식에는 수동, 자동 등의 방식이 있다.

- 자동화된 GitOps를 위해 selfHeal을 true로 설정한다. 이 경우 ArgoCD가 기본값 3분 간격으로 polling 한다.

- syncOptions, CreateNamespace: 네임스페이스가 없으면 자동으로 생성한다.

 

즉, 위와 같이 작성된 Application Manifest는 "source에 정의된 서비스의 Manifest들을 가져와 destination에 배포하는데, sync 전략은 3분 간격으로 자동 polling 하는 것"을 의미한다. 

 

 

2-2) ArgoCD Service Manifest (Application의 source에 해당)

- services/offer-be/offer-be-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: offer-be
spec:
  replicas: 2
  revisionHistoryLimit: 3
  selector:
    matchLabels:
      app: offer-be
  template:
    metadata:
      labels:
        app: offer-be
    spec:
      containers:
      - image: goharrm/offer-dev
        name: offer-be
        ports:
        - containerPort: 7070

실제 서비스될 애플리케이션(현재 Spring 기반 백엔드 애플리케이션)의 Manifest이다. Deployment로 정의했으며 k8s의 기본 컨테이너 이미지 레지스트리인 Docker Hub에서 컨테이너 이미지를 가져온다. 

cf. Deployment Controller 포스팅: https://jh-labs.tistory.com/506

 

 

- services/offer-be/offer-be-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: offer-be
spec:
  type: NodePort
  ports:
  - name: http
    port: 7070
    targetPort: 7070
    protocol: TCP
    nodePort: 32667
  selector:
    app: offer-be
  externalTrafficPolicy: Cluster
  sessionAffinity: None

 

 

 

 

3) ArgoCD Application 배포

# from github https://github.com/price-offer/application-manifests

kubectl apply -f <application yaml file>

 

 

- Application 토폴로지 확인

 

 

 

 

- 오브젝트 생성 확인

 

 

- NodePort 접속 확인

아래와 같이 32667로 접속 시 보일 API 문서가 정상적으로 실행되었음을 확인할 수 있다. (192.168.9.37은 k8s 클러스터가 구동 중인 윈도우 호스트PC 주소이며 NodePort를 포트포워딩해 둔 상태)

 

- Service, Deployment 확인

 

DESIRED MANIFEST는 GitHub에서 관리되는 Manifest이며 LIVE MANIFEST는 k8s 환경에서 배포된 상태의 Manifest이다. 둘의 차이는 DIFF 탭에서 확인할 수 있다. 현재는 둘 간의 차이가 없기 때문에 DIFF에 보는 정보가 없다.

 

 

 

 

2. GitOps 및 Desired State 검증

기본적으로 ArgoCD를 통해 Pod를 관리할 때, 직접 kubectl 커맨드를 통해 Deployment의 replicas를 scale-out해도 변경이 발생하지 않는다. 즉, ArgoCD를 통해 Desired State를 변경하려면 GitOps 기반으로 수행해야 한다.

 

아래는 GitHub에서 Deployment의 replicas를 2에서 3으로 변경하고 커밋한 뒤 결과이다. 

 

자동 sync 주기의 기본값은 3분이며 3분 뒤 Scale-out이 정상 실행되었음을 확인할 수 있다. 즉, Desired State가 변경되면 Live(Observable, Real) State와 sync가 진행됨으로써 GitOps가 정상적으로 동작되었다.

 

 

 

결론

GitOps를 통해 k8s 클러스터에 직접 접속하지 않고도 Git 상에서 형상관리를 통해 실제 운영서버의 상태를 변경할 수 있게 된다. 이러한 GitOps를 통해 요구사항을 seamless 하게 반영하여 Time to market을 만족시킬 수 있다는 점에서 중요하다.

 

다음 포스팅에서는 Argo Rollout을 통해 기본적인 배포 외에 다양한 배포 방식을 적용시키고 CI툴인 Jenkins와 연동시켜 manifest 변경사항을 자동으로 반영될 수 있도록 한다.

 

- Argo Rollout 컨트롤러 기반 Canary 배포 환경 구성 경험: https://jh-labs.tistory.com/672

- Jenkins 설치 및 pipeline 작성(앱의 manifest 파일 변경 감지 및 온전한 GitOps 구축): https://jh-labs.tistory.com/673

 

 

 

 

 

 

Reference

- ArgoCD Manifest yaml file, https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

- NodePort 타입으로 ArgoCD 설치, https://cwal.tistory.com/19

- kubectl patch 명령어 사용법, https://bcho.tistory.com/1266

- ArgoCD GitHub, https://github.com/argoproj/argo-cd

- ArgoCD Docs, https://argo-cd.readthedocs.io/en/stable/

- ArgoCD Project Application, https://malwareanalysis.tistory.com/407