K8s 배포 자동화와 시크릿 관리

CI/CD 구조와 자동화 — GitHub Actions와 GitOps

2026. 06. 22.
KubernetesCI/CDGitHub ActionsArgoCDGitOps

이 글의 목표: 수동 배포 흐름을 GitHub Actions + ArgoCD 패턴으로 자동화하는 그림과 단계를 본다.


두 가지 큰 패턴: Push vs Pull

Push 모델 (전통적)

GitHub → Actions가 빌드 + push + helm upgrade → EKS
                                ▲
                                └ kubeconfig가 필요
  • CI 도구가 클러스터에 직접 명령
  • 빠르고 단순
  • 단점: CI에 클러스터 접근 권한 필요 (보안 위협면)

Pull 모델 (GitOps)

GitHub → Actions가 빌드 + push
                          ↓
                  manifest 저장소 업데이트 (PR/commit)
                          ↑
                          └ ArgoCD/Flux가 폴링/감지

EKS ← ArgoCD가 클러스터 안에서 manifest를 apply
  • 클러스터가 스스로 git을 보고 동기화
  • Git이 Single Source of Truth
  • 클러스터 외부 권한 노출 X (보안 ↑)
  • 단점: 약간 복잡함, 도구 추가 학습 필요

요즘은 Pull(GitOps) 모델이 표준.


권장 저장소 구조

조직/
├── meeny-backend/               # 앱 코드
│   ├── src/
│   ├── Dockerfile
│   └── .github/workflows/
│       └── build.yml            # 이미지 빌드 + push
│
└── meeny-deploy/                # 배포 manifest (분리!)
    └── helm/
        ├── backend/
        │   ├── values.yaml
        │   ├── values-dev.yaml
        │   └── values-prod.yaml
        ├── frontend/
        └── ingress/

왜 분리?

  • 앱 저장소: 개발자 PR 활발
  • 배포 저장소: 운영팀/리뷰 강한 컨트롤
  • 권한·보안 정책 분리 가능
  • ArgoCD가 보는 게 배포 저장소

💡 작은 팀이면 단일 저장소 안에 apps/deploy/ 디렉터리 분리도 흔하다.


표준 빌드 워크플로우 (GitHub Actions)

meeny-backend/.github/workflows/build.yml 예시:

name: Build & Push to ECR
 
on:
  push:
    branches: [main]
    tags: ['v*']
 
env:
  AWS_REGION: ap-northeast-2
  ECR_REGISTRY: 505947591451.dkr.ecr.ap-northeast-2.amazonaws.com
  ECR_REPOSITORY: meeny/backend
 
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      id-token: write   # OIDC로 AWS 인증
      contents: read
 
    steps:
      - uses: actions/checkout@v4
 
      - name: AWS 자격증명 (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::505947591451:role/GitHubActionsECRPushRole
          aws-region: ${{ env.AWS_REGION }}
 
      - name: ECR 로그인
        uses: aws-actions/amazon-ecr-login@v2
 
      - name: 태그 결정
        id: tag
        run: |
          SHORT_SHA=${GITHUB_SHA::7}
          echo "image_tag=git-${SHORT_SHA}" >> $GITHUB_OUTPUT
 
      - name: buildx 셋업
        uses: docker/setup-buildx-action@v3
 
      - name: 빌드 + Push
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64
          push: true
          tags: |
            ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ steps.tag.outputs.image_tag }}
            ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:latest
          cache-from: type=gha
          cache-to: type=gha,mode=max
 
      - name: 배포 저장소 업데이트 (GitOps)
        uses: actions/checkout@v4
        with:
          repository: meeny-org/meeny-deploy
          token: ${{ secrets.DEPLOY_REPO_PAT }}
          path: deploy
 
      - name: values.yaml 이미지 태그 패치
        run: |
          cd deploy/helm/backend
          yq -i '.image.tag = "${{ steps.tag.outputs.image_tag }}"' values-dev.yaml
          git config user.name "github-actions"
          git config user.email "actions@github.com"
          git commit -am "chore(backend): bump image to ${{ steps.tag.outputs.image_tag }}"
          git push

이 워크플로우의 핵심

요소의미
OIDC 인증AWS Access Key 저장 X. 임시 자격증명 발급
이미지 태그 = git SHA어떤 커밋이 어떤 이미지인지 1:1
buildx + cache-from두 번째 빌드부터 빠름
deploy 저장소에 commitArgoCD가 이 변화를 감지해서 배포

ArgoCD 도입 (Pull 모델)

설치 (요약)

helm repo add argo https://argoproj.github.io/argo-helm
helm install argocd argo/argo-cd \
  -n argocd --create-namespace \
  --set server.service.type=LoadBalancer

Application 리소스 등록

backend-app.yaml:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: backend
  namespace: argocd
spec:
  project: default
 
  source:
    repoURL: https://github.com/meeny-org/meeny-deploy.git
    targetRevision: main
    path: helm/backend
    helm:
      valueFiles:
        - values.yaml
        - values-prod.yaml
 
  destination:
    server: https://kubernetes.default.svc
    namespace: backend
 
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true

→ ArgoCD가 3분마다 git 폴링 + manifest 변경 감지 즉시 클러스터 동기화.

selfHeal: true : 누가 kubectl edit으로 손대도 git 상태로 되돌린다.

prune: true : 삭제된 리소스도 자동 제거.


환경별 분리 패턴 (dev/staging/prod)

values 파일 분리

helm/backend/
├── values.yaml         # 공통 기본값
├── values-dev.yaml     # 개발 오버라이드
├── values-staging.yaml
└── values-prod.yaml

values-prod.yaml 예시:

replicaCount: 4
springProfilesActive: prod
resources:
  requests: { cpu: 1000m, memory: 1Gi }
  limits:   { cpu: 2000m, memory: 2Gi }

ArgoCD Application도 환경별 분리

  • backend-dev.yaml → dev 클러스터의 backend 네임스페이스
  • backend-prod.yaml → prod 클러스터의 backend 네임스페이스

또는 ApplicationSet 으로 한 번에 여러 환경 관리.


PR 기반 흐름 예시

1. 개발자가 backend 코드 수정 → PR 생성
       ↓
2. CI가 PR에서 테스트 실행 (push X)
       ↓
3. PR 머지 → main 브랜치 push
       ↓
4. CI: 이미지 빌드 + ECR push (tag = git SHA)
       ↓
5. CI: meeny-deploy/helm/backend/values-dev.yaml 의 image.tag 자동 업데이트 + commit
       ↓
6. ArgoCD가 deploy 저장소 변경 감지 → dev 클러스터 자동 배포
       ↓
7. dev 검증 후 PR로 values-prod.yaml 업데이트 → 사람 승인
       ↓
8. ArgoCD가 prod 배포

운영 배포만 사람 승인, 그 전까진 자동화.


단계별 자동화 도입 추천

전부 한 번에 도입하면 부담. 점진적으로:

Phase 1: CI 자동 빌드만 (당장)

  • GitHub Actions로 build.yml 추가
  • ECR에 자동 push
  • 배포는 여전히 수동 helm upgrade

Phase 2: 배포 manifest 자동 패치

  • CI가 deploy 저장소의 values.yaml을 자동으로 수정
  • 사람은 PR로 승인 후 머지 → manifest 변경 후 수동 helm upgrade

Phase 3: ArgoCD 도입

  • ArgoCD 설치
  • Application 등록
  • 자동 동기화

Phase 4: 환경 분리, 멀티 클러스터

  • ApplicationSet, App-of-Apps 패턴
  • 자동 카나리/블루-그린 (Argo Rollouts)

주요 도구 비교

도구위치역할
GitHub ActionsCI빌드, 테스트, ECR push, manifest 패치
GitLab CI / JenkinsCI위와 같음
ArgoCDCD (GitOps)manifest 보고 클러스터 동기화
FluxCDCD (GitOps)ArgoCD 대안. CRD 중심
SpinnakerCD멀티 클라우드, 카나리
Helm패키징차트 단위 배포
Kustomize패키징패치 기반 (오버레이)
Argo Rollouts배포 전략Canary, BlueGreen

→ 일단 GitHub Actions + ArgoCD + Helm 조합이면 충분하다.


ECR/EKS 측에서 준비할 것

① GitHub Actions OIDC 자격증명

EKS에 노드를 IRSA로 묶었던 것과 같은 원리. GitHub OIDC Provider를 IAM에 등록 후, role을 만들어 push 권한 부여:

# Terraform 예시
resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
 
resource "aws_iam_role" "github_ecr_push" {
  name = "GitHubActionsECRPushRole"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{
      Effect = "Allow"
      Principal = { Federated = aws_iam_openid_connect_provider.github.arn }
      Action = "sts:AssumeRoleWithWebIdentity"
      Condition = {
        StringLike = {
          "token.actions.githubusercontent.com:sub" = "repo:meeny-org/*:*"
        }
      }
    }]
  })
}

② ECR Lifecycle Policy

latest만 두고 나머지 오래된 이미지는 자동 삭제:

aws ecr put-lifecycle-policy --repository-name meeny/backend \
  --lifecycle-policy-text '{
    "rules": [{
      "rulePriority": 1,
      "description": "Keep last 30 images",
      "selection": {
        "tagStatus": "any",
        "countType": "imageCountMoreThan",
        "countNumber": 30
      },
      "action": { "type": "expire" }
    }]
  }'

핵심 요약

  • CI는 빌드/푸시까지, CD는 GitOps(ArgoCD) 가 표준
  • 이미지 태그 = git SHA (mutable latest 의존 X)
  • 앱 저장소와 배포 저장소 분리 (또는 디렉터리 분리)
  • 환경별 values 파일 로 dev/staging/prod 분리
  • OIDC로 AWS 인증 (정적 키 X)
  • ECR Lifecycle Policy 로 오래된 이미지 정리

다음 글에서는 시크릿(DB 비밀번호, JWT secret 등)을 어떻게 안전하게 다룰지, AWS Secrets Manager + External Secrets Operator를 적용하는 패턴을 본다.