IaC/CI CD Tool

6. Gitlab CI Build(with GCP Artifact Registry, Harbor)

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

Overview

이번 글에서는 GitLab CI를 활용하여 Docker 이미지를 빌드하고, GCP Artifact Registry 또는 Harbor에 업로드하는 방법을 알아본다.

 

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

 

또한, Kubernetes 환경에서 사용할 수 있도록 dockerconfigjson 기반의 Secret 생성 방식도 다루며,
CI Pipeline 안에서 GCP 인증, 비공개 레지스트리 인증, 그리고 이미지 캐시 전략까지 포함한 실전 수준의 GitLab CI 구성법을 설명한다.

 

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"

 

 

 

 

 

 


 

 

 

 

 

 

 

Gitlab CI 작성

Build에는 Kaniko를 사용한다. GCP Artifact Registry와 Harbor를 상황에 맞게 사용할 수 있는 Gitlab Ci를 작성해본다.

 

 

Harbor & GCP ArtifactRegistry

 

Namespace에 `somaz-` 가 들어가면 GCP로 Upload 하고, 그 이외의 값이 들어가면 Harbor로 업로드 하게 된다. Predefined 변수를 제외하곤 Gitlab Variables 값에 넣어줘야 한다.

stages:
  - gcloud
  - build # buld images & push to private image registry at once

# 스크립트에서 사용할 변수 설정
# CI_COMMIT_REF_NAME 파이프라인 실행하는 현재 브랜치명
variables:
  NAMESPACE:
    value: "dev1"
    description: "Select the namespace: dev1 or dev2 or staging(use deploy)"
    options:
      - "dev1"
      - "dev2"
      - "staging"
      - "qa1"
      - "somaz-dev"
  SERVICE:
    value: "game"
    description: "Select admin or game or batch or etc."
    options:
      - "game"
      - "admin"
  CI_REGISTRY_IMAGE: $CI_REGISTRY/$SERVICE
  BUILD_TAG: $CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA
  IMAGE_URL: '${CI_REGISTRY_IMAGE}:${BUILD_TAG}'
  BUILD_TAG_LATEST: $CI_COMMIT_REF_NAME-latest
  IMAGE_URL_LATEST: '${CI_REGISTRY_IMAGE}:${BUILD_TAG_LATEST}'
  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}'  

gcloud:
  stage: gcloud
  image:
    name: gcr.io/google.com/cloudsdktool/google-cloud-cli:latest
  variables:
    CLOUDSDK_CONFIG: $CI_PROJECT_DIR/gcloud
  before_script:
    - echo [INFO] Start gcloud config setting.
    - echo [INFO] GCP_SERVICE_ACCOUNT is $GCP_SERVICE_ACCOUNT
    - echo [INFO] CLOUDSDK_CONFIG is $CLOUDSDK_CONFIG
    - 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@nerdystar.io\\"}}}" > gcloud/config.json
  artifacts:
    paths:
      - gcloud/config.json
  rules:
    - if: '$NAMESPACE =~ /^somaz-/'
      when: on_success
    - when: never
  tags:
    - build-image

# web에서 수동 빌드 시 사용
build_manual_image:
  stage: build
  image:
    name: gcr.io/kaniko-project/executor:v1.22.0-debug
    # entrypoint 는 명시적으로  "" 빈 스트링 지정해야함.
    entrypoint: [""]
  before_script:
    - echo [INFO] Start build image.
    - echo [INFO] CI_REGISTRY is $CI_REGISTRY
    - echo [INFO] CI_REGISTRY_IMAGE is $CI_REGISTRY_IMAGE
    - echo [INFO] CI_COMMIT_REF_NAME is $CI_COMMIT_REF_NAME
    - echo [INFO] BUILD_TAG is $BUILD_TAG
    - echo [INFO] IMAGE_URL is $IMAGE_URL
    - echo [INFO] BUILD_TAG_LATEST is $BUILD_TAG_LATEST
    - echo [INFO] IMAGE_URL_LATEST is $IMAGE_URL_LATEST
    - echo [INFO] CI_PROJECT_DIR is $CI_PROJECT_DIR
    - echo [INFO] SERVICE is $SERVICE
    - echo [INFO] GCP Config
    - echo [INFO] GCP_CI_REGISTRY is $GCP_CI_REGISTRY
    - echo [INFO] GCP_CI_REGISTRY_IMAGE is $GCP_CI_REGISTRY_IMAGE
    - echo [INFO] GCP_IMAGE_URL is $GCP_IMAGE_URL
    - echo [INFO] GCP_IMAGE_URL_LATEST is $GCP_IMAGE_URL_LATEST
    - echo [INFO] NAMESPACE is $NAMESPACE
    # registry 접속 정보를 저장하기 위한 디렉토리 생성
    - mkdir -p /kaniko/.docker
    # private 레지스트리에 대한 접속 정보 지정
    - >
      if [[ "$NAMESPACE" =~ ^somaz- ]]; then
        echo "Configuring registry for special namespace $NAMESPACE"
        # echo $GCP_SERVICE_ACCOUNT_KEY_BASE64 | base64 -d > /kaniko/.docker/config.json
        cp gcloud/config.json /kaniko/.docker/config.json
      else
        echo "{\\"auths\\":{\\"$CI_REGISTRY\\":{\\"auth\\":\\"$(echo -n ${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD} | base64)\\"}}}" > /kaniko/.docker/config.json
      fi
  script:
    - >
      if [[ "$NAMESPACE" =~ ^somaz- ]]; then
        # somaz-가 포함된 NAMESPACE용 설정
        echo "Using special build settings for $NAMESPACE"
        /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=$CI_COMMIT_REF_NAME --skip-tls-verify
      else        
        /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=$CI_COMMIT_REF_NAME --skip-tls-verify
      fi
  rules:
    - if: '($CI_PIPELINE_SOURCE == "web")'
  tags:
    - build-image

 

 

 

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

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

 

 

 

 

 

 

 


 

 

 

 

 

이미지 정리 정책(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를 기반으로 GCP Artifact Registry와 Harbor 양쪽 모두에 이미지를 업로드하는 파이프라인을 구성하면
유연한 배포 전략과 클라우드·온프레미스 간 하이브리드 인프라 운영이 가능해진다.

 

특히 네임스페이스 기반의 레지스트리 분기 처리 방식은 프로젝트 규모가 커지거나
멀티 클러스터 환경을 운영할 때 유용하게 활용할 수 있다.

 

Kaniko를 활용한 Docker Build와 GitLab Runner의 결합은
보안과 속도 면에서도 뛰어난 CI/CD 환경을 제공하며,
앞으로도 다양한 클라우드 플랫폼 또는 자체 레지스트리로의 확장을 고려할 수 있다.

 

 

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

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

 

 

 

 

 

 

 


Reference

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

728x90
반응형