ApplicationSet, Kustomize로 다중 환경/다중 클러스터 GitOps 운영하기 - ArgoCD 도입기 1편

누가 내 yaml에 똥 쌌어?

Do not index
Do not index

들어가며

안녕하세요? 오토피디아에서 여러 엔지니어링 문제를 해결하기 위해 노력하는 Kevin입니다. 2부로 구성될 이번 ArgoCD 도입기에서는 2023년 1~12월 사이 약 1년에 걸쳐 쿠버네티스 GitOps 프로젝트인 ArgoCD를 맛보고 오토피디아의 상황에 맞추어 일부 커스터마이징하여 사용한 후기를 담고 있습니다. 이번 1편에서는 다중 환경/다중 클러스터 상황에서 GitOps를 정갈하게 적용하기 위한 리소스 정리 방식(폴더 구조)에 대한 고민을 다루며 이어지는 2편에서는 간단한 커스텀 ArgoCD CMP(Config Management Plugin)를 설정하여 운영 효율과 보안을 향상시킨 실용적인 측면에 대해 다룹니다. ArgoCD를 조직 내에 본격적으로 도입하고자 하는 분들께 이번 시리즈가 도움이 되었으면 합니다.
 
본 글에서 사용되는 서비스Application 은 아래의 의미를 뜻합니다.
  • 서비스 : 쿠버네티스의 Service 리소스가 아닌 마이크로서비스에 해당하는 워크로드를 지칭하는 의미로 사용하였습니다.
  • Application : 일반적인 워크로드가 아닌 ArgoCD의 Application CRD를 지칭하는 의미로 사용하였습니다.
 

2023년, 점점 어려워지는 클러스터 관리

오토피디아의 엔지니어링 파트는 외부 사용자용 앱/웹 서비스를 제외한 나머지 워크로드(사내 서비스, 데이터 파이프라인)의 상당수를 비용 절감을 위해 온프레미스 쿠버네티스에 배포하여 운영해왔고 Confluent Kafka 파이프라인을 활용하는 일부 워크로드의 경우 EKS 클러스터도 도입하기 시작했습니다.
단일 쿠버네티스 클러스터 환경에서 벗어나 다중 클러스터로 확장되고 관리하는 워크로드가 Pod 기준 200~400개를 오가다 보니 점차 아래와 같은 문제들이 발생하기 시작했습니다.
 
  1. 아무도 알 수 없는 서비스 배포 이력
    1. 왜? - 로컬 터미널 환경에서 서비스 레포리토리 내에 포함되어 있는 리소스 정의 폴더로 이동한 뒤 Helm이나 kubectl로 리소스를 배포하다보니, 서비스 소스코드의 변경 이력은 Git에 남더라도 배포 이력은 추적되지 못했습니다. (지금 배포되어 있는 버전이 최신일까..? 아니면 지난주꺼..?)
  1. 환경 별 변경 사항 전파 시 잦은 실수 발생
    1. 왜? - 서비스를 구성하는 리소스 폴더를 배포 환경 개수(운영 환경, 개발 환경.. 최소 2개)만큼 복사하여 사용하다 보니 개발 환경 yaml에 작성한 수정 사항을 운영 환경 yaml에도 손수 적용해줬어야 했으며 이 과정에서 모든 변경 사항이 반영되지 않는 경우가 종종 발생했습니다. (배포 새로 됐는데 왜 수정이 안됐지?)
  1. 각 클러스터 별 운영 서비스 파악의 어려움
    1. 왜? - 수십 개의 서비스 소스코드 레포지토리에 리소스 폴더가 분산되어 있어서 서비스 담당자는 편하지만 인프라 담당자에게는 죽을 맛입니다. 다행히 Rancher 웹 대쉬보드를 활용하여 운영 중인 워크로드 및 리소스 파악이 가능했지만, 어떤 서비스에 의해 각 리소스가 정의된 것인지는 찾기가 힘든 구조였습니다. (이 Deployment는 누가 만든 걸까요?)
  1. 리소스 yaml 파일에 무분별하게 주입되는 크레덴셜
    1. 왜? - 환경 별로 매번 Secret 리소스를 생성하는 것도 번거롭고.. 값 수정이나 학인은 더 번거롭고.. 에잇! 난 몰라!! 일단 yaml에 묻고 더블로 가..!
      부록) 크레덴셜 관리 종합선물세트 - 절망편
      1. (바쁘다~) 소스코드에 크레덴셜 박고 깃에 올려버리기
      1. (소스코드에 하드코딩은 너무 양심 없지..! Private 레포는 안전하니까!!) yaml 파일에 환경 변수로 박고 깃에 올려버리기
      1. (깃에 올리는건 진짜 말도 안되는거지!!) 컨테이너 빌드할 때 환경 변수로 주입해서 이미지 말아버리기
      1. (XX님~ 이거 실행하니까 크레덴셜 필요한데요?) .gitignore 추가돼 있으니까 슬랙 DM으로 보내드린거 저장하시면 돼요!
그건 바로.. 한 달 전의 나!!
그건 바로.. 한 달 전의 나!!
 

ArgoCD, 왜 써야하죠?

GitOps의 등장

위의 문제들을 해결하기 위해 제안되는 방법론 중 하나가 GitOps이며, GitOps는 1) 쿠버네티스 리소스의 모든 수정을 Config Repository(예시. Github)에 기록하고 2) Config Repository의 내용으로 운영 인프라를 변경하는 자동화된 시스템(예시. ArgoCD)을 활용하는 운영 방법론입니다[1]. 쿠버네티스는 선언적(Declarative)으로 리소스를 관리하기 때문에 쿠버네티스의 컨트롤 플레인이 현재 배포 상태와 원하는 배포 상태 간의 차이를 지속적으로 모니터링하고 이 차이를 메꾸기 위해 일련의 과정을 자동으로 수행하는 비범한 효능이 있습니다. 이러한 쿠버네티스의 특성은, 사용자가 희망하는 리소스 정의를 Git에 커밋하여 푸쉬하면 컨트롤 플레인이 마지막 커밋을 원하는 상태로 바라보며 현재 클러스터와의 차이를 인식하고 이를 스스로 줄여나가주는 방식으로 운영할 때 많은 시너지가 납니다.
따라서 GitOps를 채택한다는 것은 더 이상 사람이 CLI로 직접 리소스를 배포하지 않는다는 것을 뜻합니다. 대신 희망하는 리소스의 최종 상태(yaml 파일들)를 깃에 커밋/푸쉬 후 기다리면 클러스터가 알아서 기존 상태 → 최종 상태로의 전환을 수행합니다. 덕분에 리소스 yaml 파일들의 모든 변경 이력은 Config Repository에 기록되며 Git 기반의 Config Repostitory(Github)를 사용할 경우 이러한 변경 사항들이 개별 커밋으로 남기 때문에 필요한 경우 과거 시점의 배포 상태를 추적할 수 있습니다.
 

ArgoCD가 희망

여기까지 쿠버네티스 리소스를 Git을 이용하여 관리할 때의 장점을 알아 보았습니다만, 엔지니어링 파트에 ArgoCD 도입을 제안했을 때 동료들로부터 받았던 주된 질문 중 “하나는 단순히 yaml을 깃허브에 푸쉬했을 때 kubectl apply .를 실행하는 Github Action을 설정하면 ArgoCD 없이도 GitOps를 달성할 수 있지 않는가?” 였습니다.
반은 맞고 반은 틀리다고 생각하는데요. 서비스의 개발 과정에서 리소스들이 변경/삭제 없이 신규 추가만 되는 상황이라면 점진적인 kubectl apply . 명령어를 매번 실행하는 Continuous Deployment 전략이 가능하다고 볼 수 있습니다. 혹은 매 변경사항마다 기존의 서비스 리소스를 전체 삭제 후 다시 생성하는 방식도 이론적으로는 가능하지만 불필요한 다운타임을 야기합니다.
알다시피, 실제 개발과정에서는 여러 리소스들에 걸쳐서 변경과 삭제가 발생될 수 있기에 단순히 Creat/Delete 명령어 외에도 리소스의 부분 수정도 필요하며 신규 버전(Revision)과 이전 버전을 비교하여 각 리소스 별로 어떤 오퍼레이션이 발동되어야 하는지 계산하는 과정이 필요합니다.
ArgoCD는 이러한 버전 간 전환을 위해 필요한 각 리소스 별 수정 명령어를 자동으로 계산하고 실행함으로써 사용자로 하여금 깃에 올려둔 상태로 클러스터가 자동 배포되는 듯한 경험을 느끼게 합니다.
이 같은 경험을 위해 ArgoCD가 제공하는 CRD인 Application 리소스에 ArgoCD가 관리할 레포지토리와 레포지토리 내의 상세 경로, 희망하는 타겟 클러스터를 정의하면 매 3분 마다 깃허브 레포를 Pull하여 Sync를 자동으로 수행합니다. Sync 단계에서는 현재 클러스터 상태와 다른 점이 있는지 체크한 뒤, 다른 점이 있다면 Out-of-sync 상태로 전환하고 차이를 줄이기 위한 명령어들을 클러스터에 실행시켜 줍니다. 최종적으로 원하는 상태에 도달했을 때 (심신의 안정을 주는) In-Sync 상태로 표시됩니다.
 
ArgoCD의 작동 다이어그램 (https://argo-cd.readthedocs.io/en/stable/)
ArgoCD의 작동 다이어그램 (https://argo-cd.readthedocs.io/en/stable/)
 

써보면서 좋았던 기능들

ArgoCD가 GitOps에서 어떤 역할을 갖는지에 대해 설득 되셨으리라 생각됩니다. 이제 ArgoCD 를 써보며 특히 기억에 남았던 기능들을 소개하며 리소스 정리 방식으로 넘어가도록 하겠습니다.
  • 맥락이 있는 가시성 : ArgoCD에 의해 관리되는 Application들은 GUI를 통해서 배포된 각각의 리소스의 연결관계(특정 Pod는 어떤 Deployment에 의해 생성된 것인지)와 리소스 별 Health 상태를 한눈에 보여줌으로써 인프라에 대한 폭 넓은 가시성을 제공해 줍니다. 이러한 GUI는 단순히 클러스터에 띄워져 있는 리소스를 나열만 하는 Rancher나 Lens의 GUI에 비해 훨씬 직관적으로 느껴졌습니다.
    • notion image
  • 노드 별 리소스 요약 기능 : 여러 보기 옵션 중, 아래 스크린샷과 같이 격자 모양으로된 아이콘을 클릭하면 실제 노드의 자원을 소모하는 Pod들이 어떤 노드들에 할당되어 있는지 확인하기에 용이합니다. 해당 뷰는 이중 배포가 구성되어 있는 서비스가 한 쪽 노드에 치우쳐 할당되지 않고, 의도한대로 두 개 이상의 노드에 골고루 배분되어 있는지 빠르게 확인하는데 편리했습니다.
    • notion image
  • 빠른 리소스 상태 체크 : GUI를 통해서 Application 내의 리소스를 클릭하면 최신 yaml 상태값, 제한적이지만 로그 등과 같은 세부 정보를 바로 체크할 수 있어서 장애 발생 시에 어디서부터 손봐야 할지 빠르게 감을 잡을 수 있었습니다.
  • 타임머신 기능 : History AND Rollback 기능을 이용하면 가장 마지막 커밋 대신 특정 시점의 커밋으로 클러스터에 재배포하는 것이 가능합니다. 덕분에 신규 버전 배포 과정에서 장애 발생 시 일단 빠르게 전 버전으로 되돌려 임시 복구(매우 중요)하고, 오류가 수정된 커밋을 푸쉬 후 다시 Sync하면 장애에도 손쉽게 대응이 가능합니다.
 

수 많은 Yaml 파일들, 어떻게 정리하면 좋을까?

1) 단일 환경, 단일 서비스 - 리소스 정리 방식

ArgoCD를 쓰고 싶은 마음이 드셨다면 곧 어떤 식으로 리소스들을 정리할지 고민하게 됩니다.
이 섹션에서는 아주 간단한 단일 Application 예시에서 시작하여 점진적으로 확장시켜나가며 오토피디아에서는 어떻게 다중 Application, 다중 환경, 다중 클러스터를 관리하는지 보여드리고자 합니다.
 

서비스 소스코드와 인프라 설정 : 같은 레포지토리 vs 분리된 레포지토리

엔지니어링 파트 내에서 처음 고민을 했던 주제는 서비스를 구성하는 소스코드와 인프라 설정을 담고 있는 yaml 파일을 같은 레포지토리에서 관리할 것인가 혹은 별개의 레포지토리에서 관리할 것인가였습니다.
ArgoCD의 Best Practices 도큐먼트[2]에서 바로 이 고민에 대한 답으로 분리된 레포지토리를 권장하고 있습니다. 특히 가장 설득력이 있게 다가왔던 예시는, 종종 하나 이상의 레포지토리로 구성되는 서비스의 경우 각 레포지토리에 걸쳐서 관련된 인프라 설정을 분산시켜두는 것보다는 인프라용 레포지토리를 별도로 마련하고 모아두는 것이 좋다는 의견이었습니다.
이에 공감하여 모든 소스코드와 인프라 설정은 분리하는 방향으로 결정하였습니다.
 

ArgoCD CLI로 단일 서비스 등록하기

인프라 설정을 관리할 신규 레포지토리 내에 아래와 같이 Deployment, Ingress, Service 리소스로 구성된 단일 서비스가 있다고 가정해보겠습니다.
.
└── first-application
    └── base
        ├── deployment.yaml
        ├── ingress.yaml
        └── service.yaml
 
로컬 환경에서 아래 ArgoCD CLI 명령어를 입력하면 /first-application/base 하위에 있는 리소스(yaml 파일)를 찾아서 ArgoCD가 관리하도록 연동할 수 있습니다. 이 시점 이후로 서비스를 이루는 리소스의 수정 사항을 레포지토리에 푸쉬하면 ArgoCD가 정기적으로 Pull하며 최신 상태로 관리해줍니다.
argocd app create guestbook \
--repo https://github.com/argoproj/argocd-example-apps.git \
--path first-application/base \
--dest-server https://kubernetes.default.svc \
--dest-namespace first-namespace
 

Application CRD로 ArgoCD CLI 탈피하기

그러나 가만히 생각해보면, 애초에 인프라 변경 이력을 Git으로 관리하기 위해 GitOps를 도입하였는데 ArgoCD에 신규 서비스 추가 과정이 CLI 명령어를 수반한다면, 관련 이력이 남지 않고 추후 다른 클러스터로 서비스들을 이전해야 하는 경우에는 각 서비스별 CLI 명령어를 운영자가 하나씩 직접 실행해야 되는 불상사가 발생할 수 있습니다. 😠😠
 
이러한 고민은 Application CRD를 ArgoCD CLI를 통해 생성하지 않고 직접 작성하여 Git에 등록함으로써 해결할 수 있습니다. 앞서 소개드린 명령어는 내부적으로 ArgoCD의 CRD인 Application 리소스를 자동 생성하고 클러스터에 등록하는 역할에 불과합니다.
 
따라서 위의 ArgoCD CLI와 동일한 설정값을 지니는 Application CRD yaml 파일을 직접 작성한 뒤 kubectl apply -f application.yaml 명령어를 통해 ArgoCD에 등록해줍시다. 이제 application.yaml 을 Git에 같이 올려두면 추후 클러스터를 이전하는 일이 발생하더라도 application.yaml 경로에서 apply 명령어를 실행하는 것으로 모든 배포 설정을 복원할 수 있습니다.
 
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: first-application
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/argoproj/argocd-example-apps.git
    targetRevision: HEAD
    path: first-application/base
  destination:
    server: https://kubernetes.default.svc
    namespace: first-namespace
  syncPolicy:
      automated:
          prune: true
          selfHeal: true
      syncOptions:
          - CreateNamespace=true
 

Application CRD와 서비스 리소스는 어떻게 모을까?

이어서 application.yaml 파일을 어디에 배치하는 것이 좋을지 고민되었습니다. 두가지 후보가 논의되었는데요. 1안은 왼편과 같이 별도 폴더(applications)를 생성하고 모든 Application CRD를 모아두는 것이었으며 두번째 방식은 서비스 폴더 내에 해당하는 Application CRD를 배치하는 것이었습니다.
ArgoCD 도입 초기에는 서비스 리소스를 수정하는 담당자와 Application CRD를 수정하는 담당자가 주로 다를 것이라는 가설 하에 1안을 택하였지만 실제로 수개월간 운영해본 결과 같은 서비스의 두 리소스는 주로 한 사람이 담당하여 수정하는 경우가 잦았고 서비스 별로 담당자가 달라지는 경향성이 두드러져 최종적으로 2안으로 정착하게 되었습니다.
.
├── applications
│   └── first-application.yaml
└── first-application
    └── base
        ├── deployment.yaml
        ├── ingress.yaml
        └── service.yaml
.
└── first-application
    ├── application.yaml
    └── base
        ├── deployment.yaml
        ├── ingress.yaml
        └── service.yaml
 
이로써 본 섹션에서는 인프라용 레포지토리를 별도로 분리하고, 하나의 어플리케이션을 위한 리소스 배치를 어떻게 하는 것이 좋을까에 대해 살펴보았습니다. 이어지는 섹션에서는 다중 환경/다중 클러스터 환경에서 어떻게 리소스를 정리할 수 있을지에 대해 다루겠습니다.
 

2) 다중 배포 환경 - Kustomize, ApplicationSet으로 관리하기

많은 서비스들은 정식 배포 전 테스트와 QA를 위한 별도의 환경 상에서 배포됩니다. 우리의 first-application을 dev , prod 환경에서 운영하고자 할 때, 앞서 보았던 정리 방식을 따른다면 아래와 같은 구조로 정리해볼 수 있습니다.
이러한 방식은 가장 직관적인 가시성을 제공해주지만 dev 환경에 발생한 인프라 수정사항을 prod 환경에 반영하기 위해서는 동일한 수정을 리소스 파일에 직접 수행해야 되는 번거로움이 존재합니다. 간단한 변경사항이라면 부담 없지만 대대적인 인프라 변경 과정에서는 사람의 실수에 취약해질 수 밖에 없습니다.
.
└── first-application
    ├── dev
    │   ├── deployment.yaml
    │   ├── ingress.yaml
    │   └── service.yaml
    ├── dev-first-application.yaml
    ├── prod
    │   ├── deployment.yaml
    │   ├── ingress.yaml
    │   └── service.yaml
    └── prod-first-application.yaml
 

브랜치를 활용하면 어떨까요?

이와 비슷한 상황에서 많은 개발자 분들이 인프라 설정도 Git-Flow 혹은 Github-Flow와 같은 브랜치 전략을 바탕으로 관리하면 devprod 환경으로의 변경 사항 업데이트를 아주 손쉽게 해결할 수 있지 않을까? 라는 야심찬 생각을 하게 됩니다.
그러나 Stop Using Branches for Deploying to Different GitOps Environments[3]에서는 브랜치를 이용한 멀티 환경의 GitOps가 왜 부적절한지에 대해서 자세히 설명하고 있는데요. 실제 운영하면서 가장 공감되었던 이유는, 서로 다른 환경에서도 동일한 소스코드로 운영되길 바라는 서비스와 다르게 인프라는 대체로 환경에 따라 희망하는 설정값이 달라진다는 특성이 강합니다. 각 환경 별로 동일한 변경사항에 대해서는 Git Merge를 통해 손쉽게 반영이 가능하지만, 개발/QA 환경에는 오토 스케일링을 비활성화하거나 레플리카 수를 줄여놓는 등의 차이는 일시적이지 않고 지속됩니다. 이처럼 환경에 따라 항상 차이가 나는 부분을 브랜치 전략을 통해 극복하기란 매우 쉽지 않습니다.
 

Kustomize의 도입

멀티 환경 GitOps 문제에 대해 많은 자료들은 Kustomize[4]를 권장합니다. Kustomize는 Configuration Management Tool로 소개하고 있으며 Helm과 비슷하게 느껴지는 부분도 있습니다. 하지만 Helm의 경우 정성스레 정의된 Chart를 수 많은 사용자들이 하나의 어플리케이션을 매우 유연하게 입맛에 맞추어 배포할 수 있도록 지원하기 위해 많이 사용되다 보니, 2~3개 남짓한 환경에 배포하기 위해서 Chart를 만드는 것은 다소 효율이 떨어진다고 볼 수 있습니다. 이에 비해 Kustomize는 훨씬 가볍고 직관적인 리소스 템플릿팅을 위해 사용될 수 있습니다. Kustomize에 대한 자세한 설명은 한글 소개자료인 Kustomize로 K8S 리소스 관리하기[5]를 추천드리며 본 글에서는 멀티 환경을 위해 어떤 식으로 활용될 수 있는지만 가볍게 짚어 보겠습니다.
 
 
Bases와 Overlays
Kustomize에서는 Bases, Overlays 개념을 제공함 Bases는 default 값에 해당하는 리소스들이 정의되어 있는 폴더의 역할을 띄며 Overlays는 default 값으로부터 변경사항에 대한 내역 정의되어 있는 폴더의 역할을 띕니다. 아래 왼편에 묘사된 것과 같이 devprod 환경에 따라서 ingress URL 주소만 달라지는 상황이라면 대부분의 yaml 파일들이 중복에 해당합니다. overlays 폴더 하에 dev / prod 환경을 생성하고 각 환경 별로 변경할 리소스(ingress.yaml)에 대해서만 새로 정의하고 kustomization.yaml 을 함께 배치해줍니다.
.
└── first-application
    ├── dev
    │   ├── deployment.yaml
    │   ├── ingress.yaml
    │   └── service.yaml
    ├── dev-first-application.yaml
    ├── prod
    │   ├── deployment.yaml
    │   ├── ingress.yaml
    │   └── service.yaml
    └── prod-first-application.yaml
.
└── first-application
    ├── base
    │   ├── deployment.yaml
    │   ├── ingress.yaml
    │   ├── kustomization.yaml
    │   └── service.yaml
    ├── overlays
    │   ├── dev
    │   │   ├── ingress.yaml
    │   │   └── kustomization.yaml
    │   └── prod
    │       ├── ingress.yaml
    │       └── kustomization.yaml
    ├── dev-first-application.yaml
    └── prod-first-application.yaml
base/kustomization.yaml 에서는 아래와 같이 kustomize가 최종 yaml을 렌더링하는 과정에서 포함시킬 리소스들을 정의합니다. resources 필드에서 누락되면 최종 렌더링되는 yaml에 포함되지 않기 때문에 빠짐 없이 나열해줍니다.
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- ingress.yaml
- service.yaml
이제 dev 환경의 변경 사항이 적혀 있는 ingress.yaml 파일을 base 환경의 ingress.yaml에 merge하기 위해서 아래와 같이 overlays/dev/kustomization.yaml 을 생성합니다. bases 필드를 통해서 기본 값으로 활용할 리소스가 있는 상대 경로를 지정해줍니다. patchesStrategicMerge 필드를 통하면 dev 경로의 어떤 리소스를 오버라이드하는데 사용할지 알려줍니다. 본 예시에서는 dev 환경에서 ingress URL이 변경되는 시나리오이니 ingress.yaml 을 추가해줍니다. 아래 예시와 같이 namePrefix 필드를 설정하면 최종 렌더링 과정에서 모든 리소스명에 dev- 를 추가해주기 때문에 배포 후 환경별 리소스를 식별하는데 도움이 됩니다.
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namePrefix: dev-
bases:
    - ../../base
patchesStrategicMerge:
    - ingress.yaml
 
최종적으로 kustomize를 ArgoCD에서 사용하는 방법은 매우 간단합니다. ArgoCD의 경우 path 경로 내에 kustomization.yaml 파일이 감지되면 자체적으로 kustomize 렌더링을 수행하는 로직이 내장되어 있기 때문에 아래와 같이 path 필드 값을 first-application/overlays/dev 으로 지정하면 끝납니다.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: first-application
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/argoproj/argocd-example-apps.git
    targetRevision: HEAD
    path: first-application/overlays/dev
  destination:
    server: https://kubernetes.default.svc
    namespace: dev-first-namespace
  syncPolicy:
      automated:
          prune: false
          selfHeal: true
      syncOptions:
          - CreateNamespace=true
 
소규모 어플리케이션의 경우에는 kustomize를 적용할 때 오히려 상대적인 복잡도가 늘어날 수 있지만, 수십개의 리소스로 이루어지는 중대형 서비스의 경우 상당한 리소스 중복을 절약할 수 있습니다. 때문에 오토피디아에서도 kustomize의 활용을 권장하지만 규모가 매우 작은 어플리케이션에서는 이를 생략하기도 합니다.
 
Kustomize 기타 팁
  • kustomize CLI를 로컬환경에 설치하면 kustomize build overlays/dev 명령어를 통해 yaml 렌더링을 미리볼 수 있습니다. (의도한대로 Override되었는지 확인하는 용도로 활용합니다)
  • 종종 특정 컨테이너의 이미지 이름 혹은 태그만 변경하고 싶은 경우가 있습니다. 이럴 때는 아래의 예시처럼 kustomization.yaml의 images 필드를 이용하여 nginx 라는 이름을 갖는 컨테이너를 my.image.registry/nginx:1.4.0 으로 손쉽게 변경할 수 있습니다.
    • apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: my-nginx
      spec:
        selector:
          matchLabels:
            run: my-nginx
        replicas: 2
        template:
          metadata:
            labels:
              run: my-nginx
          spec:
            containers:
            - name: my-nginx
              image: nginx
              ports:
              - containerPort: 80
      kind: Application
      resources:
      - deployment.yaml
      images:
      - name: nginx
        newName: my.image.registry/nginx
        newTag: 1.4.0
      $ kubectl kustomize ./
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: my-nginx
      spec:
        replicas: 2
        selector:
          matchLabels:
            run: my-nginx
        template:
          metadata:
            labels:
              run: my-nginx
          spec:
            containers:
            - name: my-nginx
              image: my.image.registry/nginx:1.4.0
              ports:
              - containerPort: 80
 

ApplicationSet으로 배포 환경 템플릿팅하기

위 섹션에서는 Kustomize를 활용하여 각 환경별 리소스를 템플릿팅하는 방법에 대해 알아보았습니다. 그런데 환경 별로 Application 리소스를 매번 정의하는 것도 번거로울 수 있습니다. ArgoCD는 ApplicationSet이라는 CRD를 통해 Application 리소스에 대한 템플릿팅도 지원하고 있습니다.
가장 기본적인 사용 방법으로는 아래 예시와 같이 list 타입 generator를 활용하여 필요한 변수들을 리스트로 정의하면 ApplicationSet Controller는 이 값들을 순회하며 템플릿 내의 변수 플레이스홀더를 할당된 값으로 치환하여 Application 리소스를 동적으로 렌더링합니다. 즉, {{env}} 로 되어 있는 부분이 순차적으로 devprod 로 치환되며 두 개의 Application 리소스가 생성됩니다. 더불어 ArgoCD에서는 list 외에도 다양한 유형의 generator[6]를 지원하며 열려 있는 PR 마다 테스트 환경이 자동 배포되게 한다던지, 디렉토리 내에 특정 조건을 만족하는 Application.yaml 들을 찾아서 모두 배포하는 등의 활용이 가능하니 참고해보시면 좋겠습니다.
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
    name: first-application
spec:
    generators:
        - list:
              elements:
                  - env: prod
                  - env: dev
    template:
        metadata:
            name: '{{env}}-first-application'
        spec:
            project: default
            source:
                repoURL: https://github.com/argoproj/argocd-example-apps.git
                targetRevision: HEAD
                path: first-application/overlays/{{env}}
            destination:
                server: https://kubernetes.default.svc
                namespace: '{{env}}-first-application'
            syncPolicy:
                automated:
                    prune: true
                    selfHeal: true
                syncOptions:
                    - CreateNamespace=true
 
아래 파일 구조를 보게 되면, ApplicationSet을 활용함으로써 기존에는 devprod 환경 별로 직접 선언했던 application.yaml 이 하나의 applicationset.yaml 로 합쳐졌습니다. 운영하는 환경 수가 적을 때는 효용이 작지만 다양한 환경을 동시에 운영하는 조직일수록 ApplicationSet을 도입했을 때 사람의 실수를 줄이는데 많은 도움이 된다고 생각합니다.
.
└── first-application
    ├── base
    │   ├── deployment.yaml
    │   ├── ingress.yaml
    │   ├── kustomization.yaml
    │   └── service.yaml
    ├── overlays
    │   ├── dev
    │   │   ├── ingress.yaml
    │   │   └── kustomization.yaml
    │   └── prod
    │       ├── ingress.yaml
    │       └── kustomization.yaml
    ├── dev-first-application.yaml
    └── prod-first-application.yaml
.
└── first-application
    ├── base
    │   ├── deployment.yaml
    │   ├── ingress.yaml
    │   ├── kustomization.yaml
    │   └── service.yaml
    ├── overlays
    │   ├── dev
    │   │   ├── ingress.yaml
    │   │   └── kustomization.yaml
    │   └── prod
    │       ├── ingress.yaml
    │       └── kustomization.yaml
    └── first-applicationset.yaml
 
 

3) 다중 클러스터 - 중앙 레포지토리로 모두 모으기

다중 ArgoCD vs 중앙 ArgoCD

지금까지 Kustomize와 ApplicationSet을 사용하여 다중 환경을 편리하게 템플릿팅 하는 방법에 대해 알아보았습니다. 만약 환경에 따라 배포해야 되는 쿠버네티스 클러스터가 달라지거나, 오토피디아와 같이 온프레미스 쿠버네티스 클러스터와 CSP가 제공하는 클라우드 쿠버네티스 클러스터를 같이 관리하고 싶다면 어떤 접근이 좋을까요?
 
이러한 상황에서는 크게 2가지 방향이 제안됩니다.
  1. one to one (1 ArgoCD - 1 Cluster) : 모든 쿠버네티스 클러스터마다 개별적으로 ArgoCD를 설치하는 방법입니다. 이는 ArgoCD가 설치되어 있는 로컬 클러스터만 관리하기 때문에 타 클러스터에 장애가 생겨도 확산되는 것을 방지할 수 있습니다. 그러나 여러 개의 ArgoCD를 설치하고 유지보수하기 위한 관리 비용이 증가한다는 단점이 있습니다.
  1. one to many (1 ArgoCD - Many Clusters) : 중앙화된 하나의 ArgoCD로 여러 개의 클러스터 배포를 관장하는 방법입니다. ArgoCD가 설치되어 있는 로컬 클러스터에 장애가 발생했을 때 전체 워크로드의 관리가 깨질 수 있다는 리스크가 있지만 관리 비용이 적다는 장점이 있습니다.
 
오토피디아에서는 현재 1번과 2번을 혼용하는 형태로 사용하고 있습니다. 일부 장애가 발생하더라도 수용 가능한 서비스들은 2번 방식으로 중앙화된 ArgoCD로 묶어서 관리 중이며, 장애에 민감한 서비스들은 1번 방식으로 워크로드가 실행되는 클러스터 내에 ArgoCD를 별도로 설치하여 관리하고 있습니다.
 

다중 클러스터 리소스 정리하기

ArgoCD는 multi-cluster의 관리를 지원[7]하기 때문에 2번 방식을 쉽게 구현할 수 있습니다. 따라서 로컬 클러스터 외에 타 클러스터의 kubeConfig를 등록해놓으면 ArgoCD가 외부 클러스터에 대한 리소스 배포를 관리할 수 있도록 확장할 수 있습니다.
위에서 살펴보았던 다중 환경의 서비스 구성을 다중 클러스터 구성으로 확장하기 위해서 아래와 같이 간단히 클러스터 별 폴더를 만들어주었습니다. 이러한 레포지토리 구조를 통해 클러트터-서비스-배포환경에 대한 가시성을 한눈에 파악할 수 있었습니다.
.
└── clusters
    ├── eks-kafka
    │   └── first-application
    │       ├── base
    │       │   ├── deployment.yaml
    │       │   ├── ingress.yaml
    │       │   ├── kustomization.yaml
    │       │   └── service.yaml
    │       ├── overlays
    │       │   ├── dev
    │       │   │   ├── ingress.yaml
    │       │   │   └── kustomization.yaml
    │       │   └── prod
    │       │       ├── ingress.yaml
    │       │       └── kustomization.yaml
    │       └── first-applicationset.yaml
    └── rke2-on-premise
        └── second-application
            ├── base
            │   ├── deployment.yaml
            │   ├── ingress.yaml
            │   ├── kustomization.yaml
            │   └── service.yaml
            ├── overlays
            │   ├── dev
            │   │   ├── ingress.yaml
            │   │   └── kustomization.yaml
            │   └── prod
            │       ├── ingress.yaml
            │       └── kustomization.yaml
            └── second-apllicationset.yaml

ApplicationSet으로 배포 클러스터도 템플릿팅하기

위 리소스 정리 구조는 실은 한가지 한계가 있습니다. 예를 들면 배포 환경에 따라 배포 클러스터도 함께 서비스의 경우 dev-eks-kafkaprod-eks-kafka 폴더를 각각 만들고 각 클러스터 별로 Application을 분산시켜 정의하는 것은 비효율적입니다. 이런 상황을 대응하기 위해 클러스터 폴더명은 배포 환경을 포함하지 않되 클러스터 종류만 명시하고 ApplicationSet을 이용해서 배포 클러스터를 템플릿팅하는 방향을 채택하였습니다.
아래 두번째 yaml의 경우 list generator 상에 env 외에도 url 파라미터를 추가하여 각 배포 환경 별 클러스터의 URL을 함께 정의해주었습니다. 이를 template 필드 하위에 있는 server 필드에 적용하면 각 배포 환경에 따라 로컬 클러스터 외에 지정한 클러스터에 동적으로 배포되도록 설정할 수 있습니다.
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
    name: first-application
spec:
    generators:
        - list:
              elements:
                  - env: prod
                  - env: dev
    template:
        metadata:
            name: '{{env}}-first-application'
        spec:
            project: default
            source:
                repoURL: https://github.com/argoproj/argocd-example-apps.git
                targetRevision: HEAD
                path: first-application/overlays/{{env}}
            destination:
                server: https://kubernetes.default.svc
                namespace: '{{env}}-first-application'
            syncPolicy:
                automated:
                    prune: true
                    selfHeal: true
                syncOptions:
                    - CreateNamespace=true
---
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
    name: first-application
spec:
    generators:
        - list:
              elements:
                  - env: dev
                    url: https://<dev-cluster-url>
                  - env: prod
                    url: https://<prod-cluster-url>
    template:
        metadata:
            name: '{{env}}-first-application'
        spec:
            project: default
            source:
                repoURL: https://github.com/argoproj/argocd-example-apps.git
                targetRevision: HEAD
                path: clusters/eks-kafka/first-application/overlays/{{env}}
                directory:
                    recurse: true
            destination:
                server: '{{url}}'
                namespace: '{{env}}-first-application'
            syncPolicy:
                automated:
                    prune: true
                    selfHeal: true
                syncOptions:
                    - CreateNamespace=true
 

신규 인프라 변경 사항은 어떻게 전파할까?

지금까지 다중 환경/다중 클러스터의 서비스를 하나의 ArgoCD로 관리하는데 오토피디아가 선택한 리소스 정리 방식에 대해 소개해드렸습니다만, 실제 서비스를 운영하는 과정에서는 소스코드가 끊임 없이 수정되는 것과 같이 인프라 설정 또한 지속적으로 변경됩니다. 템플릿팅을 중점적으로 적용한 위 정리 방식에서는 검증되지 않은 변경 사항이 의도치 않게 여러 개의 배포 환경에 동시 적용되어 장애를 유발할 수 있는 위험이 존재합니다. 템플릿팅의 이점을 잃지 않으면서도 인프라 설정을 실험할 수 있는 전파 방식에 대한 레퍼런스를 소개드리고 1편을 마무리 하고자 합니다.
 
https://codefresh.io/blog/how-to-model-your-gitops-environments-and-promote-releases-between-them/
CodeFresh의 How to Model Your GitOps Environments and Promote Releases between Them[8] 글에서는 위 그림과 같은 프로모션 방식을 제안하고 있는데요. 보라색 박스(EU Variant)는 위에서 살펴보았던 base 폴더에 해당하는 인프라 설정이며 하늘색 박스(Staging EU , Production EU)는 각각 overlays 하위에 있는 dev , prod 인프라 설정에 해당됩니다. 마지막으로 초록색 박스(Extra Setting)는 신규 인프라 변경사항이라고 가정해보겠습니다.
이때 제안하고 있는 방식은 아래와 같습니다.
  1. 우선 dev 환경 폴더 내에 Extra Setting 을 먼저 적용하여 배포합니다.
  1. dev 환경 내에서 정상 반영된 것을 확인하면 prod 환경 폴더 내에 중복으로 Extra Setting 을 선언하여 배포합니다.
  1. 두 환경 내에서 모두 정상 반영된 것이 확인된 후에 devprod 에서 Extra Setting 을 걷어내고 base 폴더 내로 이전합니다.
 
즉, 신규 변경 사항을 base 폴더에 바로 적용하지 않고 각 환경에서 독립적으로 테스트를 먼저 진행하는 것을 의미합니다. 이러한 프로모션 방식은 다소 번거로워 보일 수도 있지만 각 환경에서의 작동을 확실히 검증된 뒤에 템플릿 코드에 반영되기 때문에 예기치 못한 실패가 발생했을 때 영향을 최소화하고 복구가 용이하다고 느꼈습니다.

마치며

시간 내어 긴 글 읽어주신 분들께 감사의 말씀을 드립니다. 이번 1편에서는 다중 환경/다중 클러스터 워크로드 시나리오에서 ArgoCD, Application CRD, ApplicationSet CRD, Multi-Cluster Support Feature, Kustomize를 활용하여 템플릿팅을 극대화하는 오토피디아의 GitOps 스타일을 소개드렸습니다. 이어지는 2편에서는 커스텀 CMP를 설치하여 리소스 파일에 배포 환경 정보와 Vault의 시크릿을 동적으로 주입하여 운영 효율성과 안전한 크레덴셜 관리에 대한 글이 이어집니다.
 
오토피디아는 2024년도에도 운전자의 차량 문제 해결을 돕고 정비 업계를 지금보다 더 빛내기 위해 정진합니다.
다음 글에서 만나요!

Reference

  1. 데브옵스의 확장 모델 – 깃옵스(GitOps) 이해하기, 삼성SDS (https://www.samsungsds.com/kr/insights/gitops.html)
  1. Best Practices, ArgoCD (https://argo-cd.readthedocs.io/en/stable/user-guide/best_practices/#separating-config-vs-source-code-repositories)
  1. Stop Using Branches for Deploying to Different GitOps Environments, codefresh (https://codefresh.io/blog/stop-using-branches-deploying-different-gitops-environments/)
  1. Kustomize (https://kustomize.io/)
  1. Kustomize로 K8S 리소스 관리하기, pullee (https://velog.io/@pullee/Kustomize로-K8S-리소스-관리하기)
  1. Generators, ArgoCD (https://argocd-applicationset.readthedocs.io/en/stable/Generators/)
  1. ArgoCD Cluster, ArgoCD (https://argo-cd.readthedocs.io/en/stable/user-guide/commands/argocd_cluster/)
  1. How to Model Your GitOps Environments and Promote Releases between Them, CodeFresh (https://codefresh.io/blog/how-to-model-your-gitops-environments-and-promote-releases-between-them/)
 

오토피디아 채용에 관한 모든 것을 준비했어요

첨단기술을 통한 모빌리티 혁신, 함께 하고 싶다면?

채용 둘러보기

글쓴이

Kevin Jo
Kevin Jo

Software Engineer (Data/ML) | 오토피디아 공동창업자 지금 여기서, 인공지능 기술로 세상을 바꿀 수 있다고 믿습니다.

0 comments