카카오클라우드 Container Registry

김유경·2025년 6월 8일

들어가며

이번 프로젝트는 MSA 아키텍처로 구성되어 있으며, 각 서비스는 도커 이미지로 빌드되어 쿠버네티스 클러스터에 배포됩니다. 기존에는 DockerHub를 통해 이미지를 관리했지만, 보안 강화를 위해 Kakao Cloud Container Registry로 전환했습니다.

😚 이번 글에서는 DockerHub에서 KakaoCloud Container Registry로 마이그레이션한 과정을 공유합니다.


KakaoCloud Container Registry로 이전한 이유

  • 비공개 저장소 지원
  • 도커 표준 호환
  • IAM 기반 접근 제어
  • Trivy 기반 보안 스캔

1. 카카오클라우드 Container Registry 설정

먼저 카카오클라우드 콘솔에 접속해 Container Registry를 생성합니다.

1) 카카오클라우드 콘솔 접속
2) Container Pack → Container Registry 메뉴 이동
3) 리포지토리 생성

주요 설정

태그 덮어쓰기

  • 가능: 같은 태그로 이미지 푸시 시 기존 이미지가 덮어쓰기됨
  • 불가: 동일 태그로는 한 번만 푸시 가능

이미지 스캔 설정

  • 자동: 이미지 푸시 시 보안 취약점 자동 스캔
  • 수동: 필요 시 수동으로 스캔 실행

2. IAM 액세스 키 발급

Kakao Cloud Container Registry는 Private Registry이기 때문에, 이미지를 Push하거나 Pull하려면 인증이 필요합니다. 이를 위해 IAM 액세스 키를 발급해야 합니다.

1) 카카오클라우드 콘솔에서 자격 증명 → 액세스 키 메뉴로 이동

2) IAM 액세스 키 생성


3. 도커 로그인

Container Registry는 Docker CLI를 통해 인증한 사용자만 접근할 수 있습니다. 발급받은 IAM 액세스 키를 사용해 도커에 로그인합니다.

⚠️ 도커가 로컬에 설치되어 있어야 합니다. 설치가 필요한 경우 도커 공식 문서를 참고하세요.

로그인 명령어

docker login <레지스트리 URL> \
  --username <IAM 액세스 키 ID> \
  --password <보안 액세스 키>

참고로 사용 가능한 명령어와 옵션은 Container Registry 커맨드 보기에서 확인할 수 있습니다.


4. GitHub Actions 워크플로우 수정

기존 DockurHub 워크플로우

- name: Log in to DockerHub
  uses: docker/login-action@v2
  with:
    username: ${{ secrets.DOCKER_USERNAME }}
    password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build Docker image
  run: |
    docker build -t ${{ secrets.DOCKER_USERNAME }}/playus-match-service:${{ env.BASE_TAG }} \
                 -t ${{ secrets.DOCKER_USERNAME }}/playus-match-service:${{ env.BUILD_VERSION }} .

- name: Push Docker images
  run: |
    docker push ${{ secrets.DOCKER_USERNAME }}/playus-match-service:${{ env.BASE_TAG }}
    docker push ${{ secrets.DOCKER_USERNAME }}/playus-match-service:${{ env.BUILD_VERSION }}

카카오클라우드 Container Registry로 수정

# 카카오 클라우드 Container Registry 로그인
- name: Log in to Kakao Cloud Container Registry
  uses: docker/login-action@v2
  with:
    registry: ${{ secrets.KCR_REGISTRY_URL }}
    username: ${{ secrets.KCR_ACCESS_KEY_ID }}
    password: ${{ secrets.KCR_SECRET_ACCESS_KEY }}

- name: Build Docker image
  run: |
    docker build -t ${{ secrets.KCR_REGISTRY_URL }}/playus-match-service/match-service:${{ env.BASE_TAG }} \
                 -t ${{ secrets.KCR_REGISTRY_URL }}/playus-match-service/match-service:${{ env.BUILD_VERSION }} .

- name: Push Docker images
  run: |
    docker push ${{ secrets.KCR_REGISTRY_URL }}/playus-match-service/match-service:${{ env.BASE_TAG }}
    docker push ${{ secrets.KCR_REGISTRY_URL }}/playus-match-service/match-service:${{ env.BUILD_VERSION }}

GitHub Secrets 변경

KCR_REGISTRY_URL       = <레지스트리 URL> 
KCR_ACCESS_KEY_ID      = <IAM 액세스 키 ID>
KCR_SECRET_ACCESS_KEY  = <보안 액세스 키>

✅ 빌드 및 푸시 성공



5. 쿠버네티스 배포 설정

카카오클라우드 Container Registry는 Private Registry이기 때문에, 쿠버네티스에서도 인증 정보를 통해 이미지를 Pull해야 합니다.

1) ImagePullSecret 생성

아래 명령어를 통해 K8s 네임스페이스에 도커 인증 정보를 등록합니다.

kubectl create secret docker-registry kcr-secret \
  --docker-server=<레지스트리 URI> \
  --docker-username=<IAM 액세스 키 ID> \
  --docker-password=<보안 액세스 키> \
  -n <네임스페이스>

‼️ 이 시크릿은 이후 Deployment에 명시적으로 연결해야 이미지 Pull이 가능합니다.

2) Deployment 설정 수정

기존 이미지 설정

spec:
  template:
    spec:
      containers:
        - name: match-service
          image: yukyung0/playus-match-service:latest

카카오클라우드 Registry로 변경

spec:
  template:
    spec:
      imagePullSecrets:
        - name: kcr-secret
      containers:
        - name: match-service
          image: chunsam.kr-central-2.kcr.dev/playus-match-service/match-service:latest

트러블슈팅: ImagePullSecret 누락

이미지를 Pull하지 못하고 ImagePullBackOff 오류가 발생하였습니다.

처음에는 시크릿을 생성했지만, Deployment에 imagePullSecrets 설정을 누락해 이미지 Pull에 실패했습니다. Private Registry를 사용할 때는 시크릿 생성 + Deployment에 연결 이 두 단계를 모두 꼭 확인해야 합니다!

# 현재 Deployment 설정 확인
kubectl describe deployment match-service -n dev-match-service

# imagePullSecrets 누락 시 패치 적용
kubectl patch deployment match-service -n dev-match-service \
  -p '{"spec":{"template":{"spec":{"imagePullSecrets":[{"name":"kcr-secret"}]}}}}'

🥳 성공

참고: 전체 GitHub Actions 워크플로우

name: Build and Push Docker Image

on:
  push:
    branches:
      - main
      - develop
  workflow_dispatch:  # 수동 실행 허용

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up JDK
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Cache Gradle packages
        uses: actions/cache@v4
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Build with Gradle
        uses: gradle/gradle-build-action@v2
        with:
          arguments: clean bootJar

      # 카카오 클라우드 Container Registry 로그인
      - name: Log in to Kakao Cloud Container Registry
        uses: docker/login-action@v2
        with:
          registry: ${{ secrets.KCR_REGISTRY_URL }}
          username: ${{ secrets.KCR_ACCESS_KEY_ID }}
          password: ${{ secrets.KCR_SECRET_ACCESS_KEY }}

      - name: Set Docker image tags
        run: |
          BRANCH_NAME=${GITHUB_REF##*/}
          SHORT_SHA=$(echo $GITHUB_SHA | cut -c1-7)
          BUILD_VERSION=${{ github.run_number }}-${SHORT_SHA}
          if [[ "$BRANCH_NAME" == "main" ]]; then
            echo "BASE_TAG=latest" >> $GITHUB_ENV
          elif [[ "$BRANCH_NAME" == "develop" ]]; then
            echo "BASE_TAG=dev" >> $GITHUB_ENV
          fi
          echo "BUILD_VERSION=$BUILD_VERSION" >> $GITHUB_ENV

      - name: Build Docker image
        run: |
          docker build -t ${{ secrets.KCR_REGISTRY_URL }}/playus-match-service/match-service:${{ env.BASE_TAG }} \
                       -t ${{ secrets.KCR_REGISTRY_URL }}/playus-match-service/match-service:${{ env.BUILD_VERSION }} .

      - name: Push Docker images
        run: |
          docker push ${{ secrets.KCR_REGISTRY_URL }}/playus-match-service/match-service:${{ env.BASE_TAG }}
          docker push ${{ secrets.KCR_REGISTRY_URL }}/playus-match-service/match-service:${{ env.BUILD_VERSION }}

      - name: Update patch-image.yaml
        if: success()
        run: |
          BRANCH_NAME=${GITHUB_REF##*/}
          if [[ "$BRANCH_NAME" == "main" ]]; then
            K8S_BRANCH="main"
           elif [[ "$BRANCH_NAME" == "develop" ]]; then
            K8S_BRANCH="develop"
          fi
          
          git clone --single-branch --branch "$K8S_BRANCH" https://x-access-token:${{ secrets.K8S_REPO_TOKEN }}@github.com/KEA-ChunSam/PlayUs-k8s.git
          
          if [[ "$BRANCH_NAME" == "main" ]]; then
             echo "Skipping main branch update (prod) for now"
          elif [[ "$BRANCH_NAME" == "develop" ]]; then
            sed -i "/name: match-service/{n;s|image: .*|image: ${{ secrets.KCR_REGISTRY_URL }}/playus-match-service/match-service:${{ env.BUILD_VERSION }}|}" PlayUs-k8s/overlays/develop/match-service/patch-image.yaml
          fi
          
          cd PlayUs-k8s
          git config user.name "github-actions"
          git config user.email "github-actions@github.com"
          
          # 변경사항 있으면 커밋, 없으면 빈 커밋 생성
          if [[ -n "$(git status --porcelain)" ]]; then
            git add .
            git commit -m "Update match-service image to ${{ env.BUILD_VERSION }} on $BRANCH_NAME"
          else
            echo "No changes detected, creating empty commit to trigger deploy"
            git commit --allow-empty -m "Update match-service image to ${{ env.BUILD_VERSION }} on $BRANCH_NAME"
          fi
          
          git push https://x-access-token:${{ secrets.K8S_REPO_TOKEN }}@github.com/KEA-ChunSam/PlayUs-k8s.git

0개의 댓글