안녕하세요, 짧은머리 개발자에요.

오늘은 초기 스타트업인 회사에서 CI/CD 파이프라인을 도입하고 이에 대한 정책 결정에 대한 고민과 솔루션을 공유하려 해요.


우선 제가 입사하기 전까지 회사는 데브옵스 엔지니어가 없었기에 CI/CD 파이프라인 마찬가지로 없었고, 클라우드 내에 딱히 정해진 정책 없이 애플리케이션을 운영중하고 있었어요. 그렇기 때문에 현재 운영중인 애플리케이션의 버전을 알 수 없었고, 언제 어떻게 배포되었는지 추적하기 어려운 환경이었어요.

이러한 환경에서 제가 CI/CD 파이프라인을 도입하기 위해 회사의 현재 상황을 분석하고 마련한 예비 요구사항은 다음과 같아요.

  1. Continuous Integration, Continuous Delivery, Continuous Deploy 각 단계에서 현재 상태와 결과를 알 수 있어야 한다.
  2. 현재 운영중인 애플리케이션의 버전과 상태를 알 수 있어야 한다.
  3. 자동화된 시스템을 통해 개발자는 CI/CD를 수행할 수 있어야 한다.
    • 기존에는 CI ~ Deploy까지 한 번에 무조건 실행되어서 애플리케이션의 테스트 없이 곧바로 Production에 올라가는 문제가 있었어요. 이를 해결하기 위해 “적절한 자동화”를 통해 각자가 담당한 애플리케이션을 관리, 배포할 수 있게 하고자 했어요.
  4. DevOps Engineer를 제외한 개발자는 CI/CD 각 단계가 어떻게 진행되는지 몰라도 된다.
    • DevOps Engineer가 생긴 만큼 개발자는 각자의 영역에 충실할 수 있게끔 하고자 했어요.
  5. Git Ops와 융합되어야 한다.
    • 회사에서 GitHub Enterprise를 이용중이었고, 따라서 GitOps와 융합하고자 했어요.

1, 2번을 묶어서 기존 시스템의 가장 큰 문제점인 현재 운영중인 애플리케이션의 버전을 알 수 없는 점각 단계에서의 결과와 상태를 앎으로써 배포 후보군이 되는 애플리케이션이 어떤 내용을 갖는지 추적할 수 있게 하고자 했어요.


GCP Artifact Registry에 대한 Git Actions 접근 허가하기

GCP의 경우 워크로드 아이덴티티 제휴(Workload Identity Federation)를 통해 외부 서비스와의 융합을 지원하는데요, Git Actions에서 GCP에 접근 또한 이를 활용하여 해결할 수 있어요.

https://cloud.google.com/blog/products/identity-security/enabling-keyless-authentication-from-github-actions?hl=en

워크로드 아이덴티티 제휴는 쉽게 말해 GCP의 Iam Roles for Service Accounts(IRSA)라 볼 수 있어요. 즉, GCP의 리소스를 이용하는 작업(Workload)에 대해 신원(Identity)을 제공하는 기능이에요.

Git Actions에서 GCP의 서비스인 Artifact Registry에 접근할 필요가 있었고, 이를 위한 워크로드 아이덴티티 제휴를 구성했어요.

생성할 서비스 계정은 워크로드 아이덴티티 풀과 Artifact Registry에 접근하기 위해서 다음의 Role들을 가지고 있어요.

  • roles/iam.workloadIdentityUser
    • 서비스 계정을 통해 GCP의 워크로드 아이덴티티를 사용하기 위함이에요.
  • roles/iam.serviceAccountUser
    • 서비스 계정을 통해 GCP 리소스에 접근하기 위함이에요.
  • roles/artifactregistry.writer
    • 서비스 계정을 통해 Artifact Registry에 Git Actions로 생성된 컨테이너 이미지를 업로드하기 위함이에요.
  • roles/iam.serviceAccountTokenCreator
    • 서비스 계정에 대한 단기 인증정보를 만들 수 있도록 해요. 이를 통해 Git Actions 워크 플로우에 정의된 서비스 계정의 OAuth 2.0 토큰을 획득하고 명시된 권한을 사용할 수 있어요.

또한 아무 Git 리포지터리에서 실행되는 Git Actions의 워크플로우를 통해 우리의 GCP 인프라에 접근하면 안되기 때문에 다음과 같이 GitHub 계정 혹은 리포지터리에 대한 제한 및 격리를 시켜야 합니다.

module "oidc_gh" {
  ...
  sa_mapping = {
    "github-sa" = {
      sa_name   = google_service_account.github_sa.id
      attribute = "attribute.repository_owner/{{ Organization }}"
    }
  }
  ...
}

# module.oidc_gh 중
resource "google_service_account_iam_member" "wif-sa" {
  for_each           = var.sa_mapping
  service_account_id = each.value.sa_name
  role               = "roles/iam.workloadIdentityUser"
  member             = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.this.name}/${each.value.attribute}"
}

이렇게 GCP와 Git Actions에 대한 WIP를 설정했다면, Git Actions에서 실제로 접근 가능한지 테스트해야 해요.

GCP의 경우 Git Actions에서 사용 가능한 Git Action을 제공하고 있는데요, google-github-actions/auth@v2를 통해서 GCP 서비스 계정의 권한을 획득할거에요.

build-and-delivery:
  runs-on: ubuntu-latest
  permissions:
    id-token: "write"
  steps:
	- name: "GCP Auth"
	  id: "gcp-auth"
	  uses: "google-github-actions/auth@v2"
	  with:
	    token_format: "access_token"
	    workload_identity_provider: "{{ 워크로드 아이덴티티 제공자 }}"
	    service_account: "{{ 워크로드 아이덴티티 서비스 계정 }}"
	    access_token_lifetime: "300s"

해당 Git Actions를 실행하면 GCP의 OAuth 2.0 토큰을 해당 잡에서 사용할 수 있게 해요.

  • 이를 위해 Git Actions에서 토큰에 대한 권한을 설정해야 해요. permissions의 id-token 속성을 write로 구성해야 합니다.
  • Github OIDC Provider는 클라우드 제공자로 부터 토큰을 전달받아 사용하는데, 여기서는 GCP의 OAuth 2.0 토큰을 받아 사용합니다.

이렇게 획득한 OAuth 2.0 토큰을 바탕으로 Artifact Registry 로그인 하여 이미지를 업로드 할 수 있어요.

- name: "Login to GAR"
  uses: docker/login-action@v3
  with:
    registry: asia-northeast3-docker.pkg.dev
    username: oauth2accesstoken
    password: ${{ steps.auth.outputs.auth_token }}

- name: "Docker auth with GCP Auth"
  run: |
    gcloud auth configure-docker asia-northeast3-docker.pkg.dev --quiet

Continuous Integration ~ Continuous Delivery 스텝 정의하기

Git Actions를 통해 Google Artifact Registry에 대한 권한을 획득했으니 실질적으로 CI ~ Delivery 까지의 Step을 정의해야 해요.

Continuous Integration은 한 제품을 개발하는 많은 개발자들이 각자의 코드를 융합하고 문제없이 돌아가는지 확인하며 제품으로 빌드하는 행위를 하도록 다음과 같이 정의했어요.

  1. 코드 테스트 단계
    • Git Actions에서 GCP Artifact Registry로 잘 접근이 되는지 확인해요.
    • 코드에 대한 테스트를 수행해요.
  2. 코드 빌드 단계
    • 코드 테스트 단계를 통과했다면 합쳐진 코드를 바탕으로 제품을 빌드해요.
    • 우리의 경우 Container 환경에서 애플리케이션을 운영하기 때문에 Container Image를 생성하는 행위를 의미해요.
  3. 제품 배달 단계
    • 생성된 Container Image를 GCP Artifact Registry로 업로드해요.

특히 우리는 예비 요구사항에서 각 스텝에 대한 결과와 상태를 알 수 있어야 한다.는 요구사항이 있었어요. 이를 지원하기 위해 CI ~ Delivery에 Slack Notification을 전송하는 Job을 추가했어요.

Slack Notification은 총 두 단계로 구성되어 있어요.

  1. Git Actions 시작 알림 메시지
    • 해당 메시지를 통해서 Git Actions의 결과물로 나올 컨테이너 이미지에 대한 정보를 함축적으로 전달해요.
    • 변경에 대한 Commits List를 추가하여 Slack의 쓰레드 메시지로 전달해요.
  2. Git Actions 종료 알림 메시지
    • Git Actions의 성공 여부와 Git Actions 링크 정보를 포함한 메시지를 전달해요
  • 단순히 알림 메시지를 단순히 보내는 것에 끝내지 않고 데이터베이스에 저장하여서 해당 커밋 혹은 릴리즈를 통해 배달된 버전이 어떤 변경점을 갖는지 추후에 확인할 수 있게 했어요.

슬랙에 메시지를 보냄으로써 생성된 이미지에 대해 변경사항을 확인할 수 있고 추후 애플리케이션 배포 시에도 활용하고자 했어요.

CI/CD 파이프라인의 성공 여부 메시지. 부모 슬랙 메시지의 쓰레드로 전달 되어요

독립된 Continuous Deploy

이제 배달 된 제품 빌드에 대해 배포할 차례에요. 기존의 문제점에서 현재 운영중인 애플리케이션의 버전을 알 수 없는 점을 해결하기 위해 배포에 대한 파이프라인을 앞 단계와 분리했어요.

웹 대시보드를 통해 배포하고자 하는 버전과 그 변경사항을 알 수 있게 했고, 현재 배포된 애플리케이션 차트가 어떻게 구성되어 있는지 확인할 수 있도록 배포 서버를 개발 했어요. 해당 서버의 경우 코드를 정리한 다음에 오픈소스로 공개할 예정이에요.

우리 회사는 GKE를 이용해서 애플리케이션을 운영하고 있는데, 실제 쿠버네티스에 배포에 대한 책임은 Argo CD에 위임했어요.

즉, 직접적으로 Continuous Deploy가 해결해야 하는 것은 Argo CD가 알 수 있도록 애플리케이션 매니페스트를 수정해주면 돼요.


여기까지 CI/CD 파이프라인을 설계하며 고민했던 내용을 정리했어요.

읽어주셔서 고맙습니다 🙂

+ Recent posts