IaC/CI CD Tool

6. GitLab CI Build with Multi Container Registry (Harbor, GCP Artifact Registry, AWS ECR)

Somaz 2024. 6. 24. 13:27
728x90
반응형

Overview

이번 글에서는 GitLab CI를 활용하여 Docker 이미지를 빌드하고,

Harbor, GCP Artifact Registry, AWS ECR 등 세 가지 컨테이너 레지스트리에 환경별로 동적 업로드하는 방법을 알아본다.

 

빌드 도구로는 Kaniko를 사용하며, Namespace에 따라 GCP, AWS ECR, 또는 Harbor로 동적으로 업로드 대상이 전환되도록 구성한다.

 

또한, Kubernetes 환경에서 사용할 수 있도록 dockerconfigjson 기반의 Secret 생성 방식도 다루며,

CI Pipeline 안에서 GCP 인증, AWS ECR 인증, 비공개 레지스트리 인증,

그리고 이미지 캐시 전략까지 포함한 실전 수준의 GitLab CI 구성법을 설명한다.

 

 

 

 

 

📅 관련 글

2023.04.20 - [IaC/CI CD Tool] - 1. GitLab이란? / 개념 및 설치

2023.04.23 - [IaC/CI CD Tool] - 2. GitLab이란? / GitLab Runner 개념 및 설치

2023.04.24 - [IaC/CI CD Tool] - 3. GitLab이란? / GitLab CI/CD

2023.08.08 - [IaC/CI CD Tool] - 4. GitLab 버전 업그레이드

2023.08.08 - [IaC/CI CD Tool] - 5. GitLab ArgoCD 연동

2024.05.28 - [IaC/CI CD Tool] - 6. Gitlab CI Build(with GCP Artifact Registry, Harbor)

2024.06.18 - [IaC/CI CD Tool] - 7. Gitlab CI Template 활용

2025.01.09 - [IaC/CI CD Tool] - 8. Gitlab Repository Mirroring 방법

 

 

 

 

 


 

 

 

 

Gitlab CI Build and Push(with GCP Artifact Registry, Harbor)

 

 

 

dockerConfigJson 작성

 

Harbor

  1. 사용자 이름과 비밀번호 인코딩: `echo -n` 을 사용하여 사용자 이름과 비밀번호를 한 줄로 출력하고, `base64` 명령으로 인코딩한다. `n` 옵션은 라인 끝의 개행문자를 제거한다.
  2. JSON 파일 생성: `cat <<EOF > config.json` 를 사용하여 다중 라인의 텍스트를 `config.json` 파일로 리다이렉션한다. 변수 `$AUTH` 는 이전 단계에서 생성된 인코딩된 문자열을 사용한다.
  3. 전체 JSON 파일 인코딩: `cat config.json` 으로 파일을 읽고, ` base64` 로 인코딩한 다음, `tr -d '\\n'` 으로 인코딩된 문자열에서 개행 문자를 제거한다.
  4. Kubernetes Secret 생성용 YAML 파일 준비: 위와 비슷한 방식으로 `YAML` 파일을 생성하며, 이 파일은 Kubernetes에서 사용할 수 있다.

 

사용자 이름과 비밀번호를 인코딩한다.

AUTH=$(echo -n 'somaz:somaz@2024' | base64)

 

 

JSON 파일을 생성한다.

cat <<EOF > config.json
{
  "auths": {
    "your.harbor.domain": {
      "username": "somaz",
      "password": "somaz@2024",
      "email": "somaz.link",
      "auth": "$AUTH"
    }
  }
}
EOF

# 한줄작성
echo '{"auths":{"your.harbor.domain":{"username":"somaz","password":"somaz@2024","email":"somaz.link","auth":"'"$AUTH"'"}}}' > config.json

 

 

전체 JSON 파일을 BASE64로 한번더 인코딩한다.

# JSON 파일을 BASE64로 인코딩
ENCODED_JSON=$(cat config.json | base64 -w 0)

or
# 둘중 하나 선택
ENCODED_JSON=$(cat config.json | base64 -w 0 | tr -d '\\n')

# 확인
echo $ENCODED_JSON
eyJhdXRocyI6eyJ5b3VyLmhhcmJvci5kb21haW4iOnsidXNlcm5hbWUiOiJzb21heiIsInBhc3N3b3JkIjoic29tYXpAMjAyNCIsImVtYWlsIjoic29tYXoubGluayIsImF1dGgiOiJjMjl0WVhvNmMyOXRZWHBBTWpBeU5BPT0ifX19Cg==

echo "eyJhdXRocyI6eyJ5b3VyLmhhcmJvci5kb21haW4iOnsidXNlcm5hbWUiOiJzb21heiIsInBhc3N3b3JkIjoic29tYXpAMjAyNCIsImVtYWlsIjoic29tYXoubGluayIsImF1dGgiOiJjMjl0WVhvNmMyOXRZWHBBTWpBeU5BPT0ifX19Cg==" |base64 -d -w 0
{"auths":{"your.harbor.domain":{"username":"somaz","password":"somaz@2024","email":"somaz.link","auth":"c29tYXo6c29tYXpAMjAyNA=="}}}

 

 

마지막으로 Kubernetes Secret 생성용 YAML 파일 준비한다.

cat <<EOF > k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: myregistrykey
  namespace: default
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: $ENCODED_JSON
EOF

 

 

 

 

 

 


 

 

 

 

 

Google Artifact Registry

  1. Docker 로그인 실행 : 로컬에서 `serviceaccount.json` 파일을 사용하여 Docker 로그인을 수행하고, 결과로 생성된 `config.json` 파일을 생성한다.
  2. dockerconfigjson 파일 준비 : 로그인 후 생성된 `~/.docker/config.json` 파일을 `base64` 로 인코딩한다. 인코딩된 결과는 Helm의 values 파일에 사용된다.
  3. Kubernetes Secret 생성 : 인코딩된 `dockerconfigjson`를 Helm 차트의 values 파일에 삽입하여 Kubernetes에서 사용할 수 있는 Secret을 생성한다.

 

`serviceaccount.json` 파일을 사용하여 로컬에서 `dockerconfigjson` 을 생성해본다.

export REGION=<YOUR-REGION>
cat serviceaccount.json | docker login -u _json_key --password-stdin https://$REGION.pkg.dev

 

 

생성된 `config.json` 파일의 내용을 복사하여 Helm values 파일의 `artifactregistry.dockerConfigJson` 필드에 `base64` 인코딩된 형태로 삽입한다.

cat ~/.docker/config.json | base64 -w 0

 

 

Kubernetes Secret을 생성한다. 이번에는 Helm Chart를 사용해본다. template을 먼저 생성한다.

 

`gcp-ar-secret.yaml`

{{- if .Values.artifactregistry.enabled }}
apiVersion: v1
kind: Secret
metadata:
  name: {{ include "somaz.fullname" . }}
  namespace: {{ .Release.Namespace }}
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: {{ .Values.artifactregistry.dockerConfigJson }}
{{- end }}

 

`values.yaml`

image:
  repository: <region>-docker.pkg.dev/mgmt-2023/<repo name>/<image name>
  pullPolicy: IfNotPresent
  # Overrides the image tag whose default is the chart appVersion.
  tag: "<image Tag>"

imagePullSecrets:
  - name: <image pull secret name>

artifactregistry:
  enabled: false
  dockerConfigJson: "BASE64_ENCODED_JSON_HERE"

 

 

 

 

 


 

 

 

AWS ECR

AWS ECR의 경우 Access Token을 기반으로 한 인증이 필요하다.

# AWS CLI를 통한 ECR 로그인 토큰 획득
ECR_TOKEN=$(aws ecr get-login-password --region $AWS_REGION)
ECR_AUTH=$(echo "AWS:$ECR_TOKEN" | base64 -w 0)

# dockerconfigjson 생성
echo "{\"auths\":{\"$AWS_ECR_REGISTRY\":{\"auth\":\"$ECR_AUTH\"}}}" > ~/.docker/config.json

# base64 인코딩
cat ~/.docker/config.json | base64 -w 0



 

 

 


 

 

 

 

 

실전 GitLab CI 구성

Build에는 Kaniko를 사용한다. 환경별 레지스트리 분기 처리가 포함된 완전한 GitLab CI 구성을 살펴보자.

 

 

 

Gitlab Runner 관련해서는 아래의 게시물 참고하면 된다.

2023.04.18 - [IaC/CI CD Tool] - 2. GitLab이란? / GitLab Runner 개념 및 설치

 

 

변수 설정

stages:
  - prepare_admin
  - aws-auth
  - gcloud  
  - build
  - build_aws
  - update

variables:
  NAMESPACE:
    value: 'dev'
    description: 'Select the namespace: dev1 or dev2 or staging or alpha'
    options:
      - 'dev'
      - 'dev2' 
      - 'staging'
      - 'alpha'
  SERVICE:
    value: 'game'
    description: 'Select game or admin'
    options:
      - 'game'
      - 'admin'
  
  # 공통 변수
  IMAGE_PROJECT: projectm
  CI_REGISTRY_IMAGE: $CI_REGISTRY/$IMAGE_PROJECT/$SERVICE
  BUILD_TAG: $CI_COMMIT_SHORT_SHA
  IMAGE_URL: '${CI_REGISTRY_IMAGE}:${BUILD_TAG}'
  BUILD_TAG_LATEST: latest
  IMAGE_URL_LATEST: '${CI_REGISTRY_IMAGE}:${BUILD_TAG_LATEST}'
  
  # GCP Artifact Registry 변수
  GCP_CI_REGISTRY_IMAGE: '$GCP_CI_REGISTRY/${SERVICE}-fgn/${SERVICE}-fgn'
  GCP_IMAGE_URL: '${GCP_CI_REGISTRY_IMAGE}:${BUILD_TAG}'
  GCP_IMAGE_URL_LATEST: '${GCP_CI_REGISTRY_IMAGE}:${BUILD_TAG_LATEST}'
  
  # AWS ECR 변수
  AWS_REGION: eu-central-1
  AWS_ACCOUNT_ID: 027438161209 # 예시
  AWS_ECR_REGISTRY: '$AWS_ACCOUNT_ID.dkr.ecr.eu-central-1.amazonaws.com'
  AWS_ECR_IMAGE_URL: '${AWS_ECR_REGISTRY}/luckyday/${SERVICE}:${BUILD_TAG}'
  AWS_ECR_IMAGE_URL_LATEST: '${AWS_ECR_REGISTRY}/luckyday/${SERVICE}:${BUILD_TAG_LATEST}'
  AWS_ECR_CACHE_REPO: '${AWS_ECR_REGISTRY}/luckyday/${SERVICE}-cache'

 

 

 

AWS ECR 인증 템플릿

 

방법 1: 기존 Access Key 방식 (Legacy)

.templates:
  .aws_auth_template_legacy: &aws_auth_template_legacy
    stage: aws-auth
    image: amazon/aws-cli:latest
    before_script:
      - echo "[INFO] Start AWS ECR auth config (Legacy method)"
      - export AWS_ACCESS_KEY_ID="${LUCKYDAY_AWS_ACCESS_KEY}"
      - export AWS_SECRET_ACCESS_KEY="${LUCKYDAY_AWS_SECRET_KEY}"
      - export AWS_DEFAULT_REGION="$AWS_REGION"
      # Role ARN이 있으면 assume role 수행
      - |
        if [ -n "${LUCKYDAY_AWS_ROLE_ARN}" ]; then
          CREDENTIALS=$(aws sts assume-role --role-arn "${LUCKYDAY_AWS_ROLE_ARN}" --role-session-name "gitlab-ci-$CI_JOB_ID")
          export AWS_ACCESS_KEY_ID=$(echo $CREDENTIALS | jq -r '.Credentials.AccessKeyId')
          export AWS_SECRET_ACCESS_KEY=$(echo $CREDENTIALS | jq -r '.Credentials.SecretAccessKey')  
          export AWS_SESSION_TOKEN=$(echo $CREDENTIALS | jq -r '.Credentials.SessionToken')
        fi
      - mkdir -p aws
    script:
      # Docker 없이 ECR 인증 토큰으로 config.json 직접 생성
      - ECR_TOKEN=$(aws ecr get-login-password --region $AWS_REGION)
      - ECR_AUTH=$(echo "AWS:$ECR_TOKEN" | base64 -w 0)
      - mkdir -p ~/.docker
      - echo "{\"auths\":{\"$AWS_ECR_REGISTRY\":{\"auth\":\"$ECR_AUTH\"}}}" > ~/.docker/config.json
      - cp ~/.docker/config.json aws/config.json
      - echo "[INFO] ECR auth config created successfully"
    artifacts:
      paths:
        - aws/config.json
      expire_in: 1 hour
    tags:
      - build-image

 

 

 

방법 2: OIDC Provider 방식 (권장)

OIDC를 사용하면 장기 자격증명 없이도 AWS 리소스에 안전하게 접근할 수 있다.

 

 

 

AWS 설정

# 1. OIDC Provider 생성
aws iam create-open-id-connect-provider \
  --url https://gitlab.example.com \
  --client-id-list project_path:group/project:ref_type:branch:ref:main \
  --thumbprint-list a031c46782e6e6c662c2c87c76da9aa62ccabd8e

# 2. IAM Role 생성
cat > trust-policy.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::ACCOUNT-ID:oidc-provider/gitlab.example.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "gitlab.example.com:sub": "project_path:group/project:ref_type:branch:ref:main"
        }
      }
    }
  ]
}
EOF

aws iam create-role \
  --role-name GitLabCIRole \
  --assume-role-policy-document file://trust-policy.json

# 3. ECR 권한 정책 연결
aws iam attach-role-policy \
  --role-name GitLabCIRole \
  --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser

 

 

 

GitLab CI 구성

.aws_auth_oidc_template: &aws_auth_oidc_template
    stage: aws-auth
    image: amazon/aws-cli:latest
    id_tokens:
      GITLAB_OIDC_TOKEN:
        aud: https://gitlab.example.com
    before_script:
      - echo "[INFO] Start AWS ECR OIDC auth config"
      - echo "[INFO] Using OIDC token for authentication"
      - mkdir -p aws
    script:
      # OIDC 토큰으로 AWS 역할 assume
      - |
        export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" \
        $(aws sts assume-role-with-web-identity \
        --role-arn ${AWS_ROLE_ARN} \
        --role-session-name "GitLabRunner-${CI_PROJECT_ID}-${CI_PIPELINE_ID}" \
        --web-identity-token ${GITLAB_OIDC_TOKEN} \
        --duration-seconds 3600 \
        --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]' \
        --output text))
      
      # ECR 로그인 토큰 생성
      - ECR_TOKEN=$(aws ecr get-login-password --region $AWS_REGION)
      - ECR_AUTH=$(echo "AWS:$ECR_TOKEN" | base64 -w 0)
      - mkdir -p ~/.docker
      - echo "{\"auths\":{\"$AWS_ECR_REGISTRY\":{\"auth\":\"$ECR_AUTH\"}}}" > ~/.docker/config.json
      - cp ~/.docker/config.json aws/config.json
      - echo "[INFO] OIDC ECR auth config created successfully"
    artifacts:
      paths:
        - aws/config.json
      expire_in: 1 hour
    tags:
      - build-image

 

 

 

 

GCP 인증 템플릿

 

방법 1: Service Account Key 방식 (Legacy)

.gcp_auth_template_legacy: &gcp_auth_template_legacy
    stage: gcloud
    image: gcr.io/google.com/cloudsdktool/google-cloud-cli:latest
    variables:
      CLOUDSDK_CONFIG: $CI_PROJECT_DIR/gcloud
    before_script:
      - echo "[INFO] Start gcloud config setting (Legacy method)"
      - echo "[INFO] GCP_CI_REGISTRY is $GCP_CI_REGISTRY"
      - mkdir -p ${CLOUDSDK_CONFIG}
    script:
      - echo $GCP_SERVICE_ACCOUNT_KEY_BASE64 | base64 --decode > ${CLOUDSDK_CONFIG}/gcloud-service-key.json
      - gcloud auth activate-service-account --key-file=${CLOUDSDK_CONFIG}/gcloud-service-key.json
      - token=$(gcloud auth print-access-token)
      - docker_token=$(echo -n "gclouddockertoken:$token" | base64 | tr -d "\\n")
      - echo "{\"auths\":{\"$GCP_CI_REGISTRY\":{\"auth\":\"$docker_token\",\"email\":\"admin@company.com\"}}}" > gcloud/config.json
    artifacts:
      paths:
        - gcloud/config.json
      expire_in: 1 hour
    tags:
      - build-image

 

 

 

방법 2: Workload Identity Federation 방식 (권장)

Workload Identity Federation을 사용하면 Service Account JSON 키 없이도 안전하게 GCP에 인증할 수 있다.

 

 

GCP 설정

# 1. Workload Identity Pool 생성
gcloud iam workload-identity-pools create "gitlab-pool" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --display-name="GitLab CI Pool"

# 2. Workload Identity Provider 생성 (GitLab용)
gcloud iam workload-identity-pools providers create-oidc "gitlab-provider" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --workload-identity-pool="gitlab-pool" \
  --display-name="GitLab Provider" \
  --attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.project_path" \
  --issuer-uri="https://gitlab.example.com" \
  --attribute-condition="assertion.project_path == 'group/project'"

# 3. Service Account 생성 및 권한 부여
gcloud iam service-accounts create gitlab-ci-sa \
  --display-name="GitLab CI Service Account"

# 4. Artifact Registry 권한 부여
gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:gitlab-ci-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role="roles/artifactregistry.writer"

# 5. Workload Identity 바인딩
gcloud iam service-accounts add-iam-policy-binding \
  --role roles/iam.workloadIdentityUser \
  --member "principalSet://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/gitlab-pool/attribute.repository/group/project" \
  gitlab-ci-sa@${PROJECT_ID}.iam.gserviceaccount.com

# 6. Provider 정보 확인
gcloud iam workload-identity-pools providers describe "gitlab-provider" \
  --project="${PROJECT_ID}" \
  --location="global" \
  --workload-identity-pool="gitlab-pool" \
  --format="value(name)"

 

 

 

GitLab CI 구성

.gcp_auth_wif_template: &gcp_auth_wif_template
    stage: gcloud
    image: gcr.io/google.com/cloudsdktool/google-cloud-cli:latest
    id_tokens:
      GITLAB_OIDC_TOKEN:
        aud: https://gitlab.example.com
    variables:
      CLOUDSDK_CONFIG: $CI_PROJECT_DIR/gcloud
    before_script:
      - echo "[INFO] Start gcloud Workload Identity Federation config"
      - echo "[INFO] GCP_CI_REGISTRY is $GCP_CI_REGISTRY"
      - echo "[INFO] Using Workload Identity Federation"
      - mkdir -p ${CLOUDSDK_CONFIG}
    script:
      # Workload Identity Federation을 통한 인증
      - |
        gcloud iam workload-identity-pools create-cred-config \
          projects/${GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${GCP_WIF_POOL_ID}/providers/${GCP_WIF_PROVIDER_ID} \
          --service-account=${GCP_SERVICE_ACCOUNT}@${GCP_PROJECT_ID}.iam.gserviceaccount.com \
          --output-file=${CLOUDSDK_CONFIG}/wif-config.json \
          --credential-source-file=${CI_PROJECT_DIR}/gitlab-oidc-token
      
      # OIDC 토큰을 파일로 저장
      - echo ${GITLAB_OIDC_TOKEN} > ${CI_PROJECT_DIR}/gitlab-oidc-token
      
      # WIF 자격증명으로 gcloud 인증
      - gcloud auth login --cred-file=${CLOUDSDK_CONFIG}/wif-config.json
      - gcloud config set project ${GCP_PROJECT_ID}
      
      # Docker 인증을 위한 토큰 생성
      - token=$(gcloud auth print-access-token)
      - docker_token=$(echo -n "_json_key:$(gcloud auth print-access-token)" | base64 | tr -d "\\n")
      - echo "{\"auths\":{\"$GCP_CI_REGISTRY\":{\"auth\":\"$docker_token\",\"email\":\"gitlab-ci@${GCP_PROJECT_ID}.iam.gserviceaccount.com\"}}}" > gcloud/config.json
      - echo "[INFO] Workload Identity Federation auth completed"
    artifacts:
      paths:
        - gcloud/config.json
      expire_in: 1 hour
    tags:
      - build-image

  # 간소화된 WIF 템플릿 (gcloud auth configure-docker 사용)
  .gcp_auth_wif_simple_template: &gcp_auth_wif_simple_template
    stage: gcloud
    image: gcr.io/google.com/cloudsdktool/google-cloud-cli:latest
    id_tokens:
      GITLAB_OIDC_TOKEN:
        aud: https://gitlab.example.com
    script:
      # 환경변수 설정으로 간소화
      - export GOOGLE_APPLICATION_CREDENTIALS="/tmp/wif-config.json"
      - |
        cat > /tmp/wif-config.json << EOF
        {
          "type": "external_account",
          "audience": "//iam.googleapis.com/projects/${GCP_PROJECT_NUMBER}/locations/global/workloadIdentityPools/${GCP_WIF_POOL_ID}/providers/${GCP_WIF_PROVIDER_ID}",
          "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
          "token_url": "https://sts.googleapis.com/v1/token",
          "credential_source": {
            "file": "/tmp/gitlab-oidc-token"
          },
          "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${GCP_SERVICE_ACCOUNT}@${GCP_PROJECT_ID}.iam.gserviceaccount.com:generateAccessToken"
        }
        EOF
      - echo ${GITLAB_OIDC_TOKEN} > /tmp/gitlab-oidc-token
      - gcloud auth login --cred-file=/tmp/wif-config.json
      - gcloud auth configure-docker ${GCP_CI_REGISTRY} --quiet
      - cp ~/.docker/config.json gcloud/config.json
    artifacts:
      paths:
        - gcloud/config.json
      expire_in: 1 hour
    tags:
      - build-image

 

 

 

 

빌드 템플릿 (멀티 레지스트리 지원)

.build_template: &build_template
    stage: build
    image:
      name: harbor.concrit.us/library/kaniko-project/executor:v1.23.0-debug
      entrypoint: ['']
    before_script:
      - echo "[INFO] Start build image"
      - echo "[INFO] SERVICE is $SERVICE"
      - echo "[INFO] NAMESPACE is $NAMESPACE"
      - echo "[INFO] BUILD_TAG is $BUILD_TAG"
      - mkdir -p /kaniko/.docker
      # 환경별 레지스트리 설정
      - |
        if [[ "$NAMESPACE" == "alpha" ]]; then
          echo "Using AWS ECR for alpha environment"
          cp aws/config.json /kaniko/.docker/config.json
        elif [[ "$NAMESPACE" =~ ^somaz- ]]; then
          echo "Using GCP Artifact Registry for somaz environments"
          cp gcloud/config.json /kaniko/.docker/config.json
        else
          echo "Using Harbor for standard environments"
          echo "{\"auths\":{\"$CI_REGISTRY\":{\"auth\":\"$(echo -n ${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD} | base64)\"}}}" > /kaniko/.docker/config.json
        fi
    script:
      - |
        if [[ "$NAMESPACE" == "alpha" ]]; then
          # AWS ECR 빌드
          /kaniko/executor \
            --cache=true \
            --cache-ttl=24h \
            --cache-repo $AWS_ECR_CACHE_REPO \
            --snapshot-mode=redo \
            --context $CI_PROJECT_DIR \
            --dockerfile $CI_PROJECT_DIR/$SERVICE.Dockerfile \
            --destination $AWS_ECR_IMAGE_URL \
            --destination $AWS_ECR_IMAGE_URL_LATEST \
            --build-arg NODE_ENV=$NAMESPACE \
            --skip-tls-verify
        elif [[ "$NAMESPACE" =~ ^somaz- ]]; then
          # GCP Artifact Registry 빌드
          /kaniko/executor \
            --cache=true \
            --cache-ttl=24h \
            --snapshot-mode=redo \
            --context $CI_PROJECT_DIR \
            --dockerfile $CI_PROJECT_DIR/$SERVICE.Dockerfile \
            --destination $GCP_IMAGE_URL \
            --destination $GCP_IMAGE_URL_LATEST \
            --build-arg NODE_ENV=$NAMESPACE \
            --skip-tls-verify
        else
          # Harbor 빌드
          /kaniko/executor \
            --cache=true \
            --cache-ttl=24h \
            --snapshot-mode=redo \
            --context $CI_PROJECT_DIR \
            --dockerfile $CI_PROJECT_DIR/$SERVICE.Dockerfile \
            --destination $IMAGE_URL \
            --destination $IMAGE_URL_LATEST \
            --build-arg NODE_ENV=$NAMESPACE \
            --skip-tls-verify \
            --insecure-pull
        fi
    tags:
      - build-image

 

 

 

 

실제 Job 정의

# AWS ECR 인증 (alpha 환경용)
aws_auth_manual:
  <<: *aws_auth_template
  rules:
    - if: '($CI_PIPELINE_SOURCE == "web" && $NAMESPACE == "alpha")'

aws_auth_auto:
  <<: *aws_auth_template
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_REF_NAME == "alpha"'

# GCP 인증 (somaz- 환경용)
gcp_auth_manual:
  <<: *gcp_auth_template
  rules:
    - if: '($CI_PIPELINE_SOURCE == "web" && $NAMESPACE =~ /^somaz-/)'

gcp_auth_auto:
  <<: *gcp_auth_template
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_REF_NAME =~ /^somaz-/'

# 빌드 Job들
build_manual_image:
  <<: *build_template
  needs:
    - job: aws_auth_manual
      artifacts: true
      optional: true
    - job: gcp_auth_manual
      artifacts: true
      optional: true
  rules:
    - if: '($CI_PIPELINE_SOURCE == "web" && $SERVICE != "admin")'

build_auto_image_game:
  <<: *build_template
  needs:
    - job: aws_auth_auto
      artifacts: true
      optional: true
    - job: gcp_auth_auto
      artifacts: true
      optional: true
  variables:
    SERVICE: game
    NAMESPACE: $CI_COMMIT_REF_NAME
  rules:
    - if: '$CI_PIPELINE_SOURCE == "push"'
      changes:
        - apps/game/**/*
        - game.Dockerfile
        - libs/**/*

 

 

환경별 분기 처리 로직

1. Alpha 환경 → AWS ECR

  • NAMESPACE == "alpha"인 경우
  • AWS ECR 캐시 레포지토리 활용
  • IAM Role 기반 인증 지원

2. Somaz 환경 → GCP Artifact Registry

  • NAMESPACE =~ /^somaz-/인 경우
  • Service Account 키 기반 인증
  • GCP 토큰 방식 사용

3. 일반 환경 → Harbor

  • 기타 모든 환경 (dev, dev2, staging 등)
  • 기본 username/password 인증
  • --insecure-pull 옵션 활용

 

 

 

 


 

 

 

 

 

 

이미지 정리 정책(Retention Policy) 고려하기

장기 운영 시 Docker 이미지가 계속 누적되므로,
Artifact RegistryHarbor에서 제공하는 Retention Policy 설정도 병행해야 한다.

  • GCP Artifact Registry:
    Retention policy 설정 문서를 참고하여 일정 기간 지난 태그 없는 이미지를 자동 삭제하도록 구성 가능하다.
  • Harbor:
    Harbor 관리 페이지에서 Tag Retention Rule을 설정하여,
    "Latest 5개 태그만 보관" 등 규칙 기반으로 이미지 정리를 자동화할 수 있다.

 

 

불필요한 이미지가 지속적으로 쌓이면 저장소 용량 부족이나 비용 문제가 발생할 수 있으므로,
CI/CD 파이프라인과 함께 이미지 정리 전략도 함께 고려하자.

 

 

 

 

 

 

`CI_JOB_TOKEN` 을 활용한 인증 (옵션)

GitLab 자체 레지스트리(`registry.gitlab.com` 또는 `self-hosted GitLab Registry`)를 사용하는 경우
별도 `docker login` 없이 `CI_JOB_TOKEN` 을 활용한 인증도 가능하다.

docker login -u gitlab-ci-token -p $CI_JOB_TOKEN registry.gitlab.com

 

 

해당 방식은 GitLab CI의 Job 안에서 보안적으로 인증 정보를 따로 노출하지 않고도
이미지 Push/Pull이 가능하여 내부 프로젝트에는 유용하다.
단, 외부 레지스트리(GCP, Harbor 등)에는 해당 토큰을 사용할 수 없다.

 

 

 

 

 

Kaniko의 --build-arg, --cache 전략 고도화

`--build-arg` 를 통해 동적으로 환경 변수를 주입하거나 `--cache` 세부 설정을 조절해 최적화할 수 있습니다.

 

 

Kaniko 캐시 및 빌드 인자 최적화 팁

  • `--cache-repo`: 특정 이미지 저장소에 캐시를 유지할 수 있어 멀티 파이프라인에서 공용 캐시가 가능함
  • `--build-arg`: Dockerfile에 정의된 ARG를 외부에서 주입 가능
/kaniko/executor \
  --cache=true \
  --cache-ttl=24h \
  --cache-repo=gcr.io/my-project/kaniko-cache \
  --build-arg ENV=production \
  ...
  • 빌드 환경에 따라 다양한 ARG를 주입해보자.
  • 예: `NODE_ENV, VERSION, BASE_URL`

 

 

 

 


 

 

 

 

마무리

GitLab CI를 기반으로 Harbor, GCP Artifact Registry, AWS ECR 등 세 가지 레지스트리를 환경별로 활용하는 파이프라인을 구성하면 다음과 같은 이점을 얻을 수 있다.

  1. 유연한 배포 전략: 환경별 최적화된 레지스트리 선택
  2. 하이브리드 인프라 지원: 온프레미스와 멀티 클라우드 동시 운영
  3. 보안 강화: 환경별 격리된 인증 체계
  4. 비용 최적화: 각 클라우드별 최적 요금 정책 활용
  5. 장애 대응: 레지스트리 장애시 대안 경로 확보

 

실제 프로덕션 환경에서는 각 레지스트리의 특성을 고려하여

  • 개발/테스트: Harbor (온프레미스 비용 절약)
  • 스테이징: GCP Artifact Registry (클라우드 네이티브)
  • 프로덕션: AWS ECR (AWS 인프라와 통합)

 

과 같은 전략적 배치도 고려할 수 있다.

네임스페이스 기반의 동적 분기 처리 방식은 향후 추가 클라우드 플랫폼(Azure Container Registry 등) 확장시에도 쉽게 적용할 수 있는 확장 가능한 아키텍처를 제공한다.

 

 

이제 단순히 GitLab CI로 이미지를 빌드하는 것을 넘어,
환경에 따라 전략적으로 이미지를 저장하고 배포하는 파이프라인 설계가 중요해진 시점이다.

실제 프로젝트에서 이번 구성을 어떻게 응용할 수 있을지 고민해보는 것도 좋은 연습이 될 것이다.

 

 

 

 

 

 

 


Reference

https://docs.gitlab.com/ee/ci/docker/using_kaniko.html

728x90
반응형