Overview
오늘은 GitLab CI/CD에서 Template을 활용해 효율적이고 일관된 파이프라인을 구성하는 방법에 대해 알아본다.
GitLab에서는 반복되는 CI/CD 작업을 간결하게 관리하고 유지보수를 단순화하기 위해 템플릿 구조를 지원하며,
이를 통해 공통된 스크립트나 설정을 여러 Job에서 재사용할 수 있다.
이번 글에서는 GitLab에서 제공하는 공식 템플릿 참조 방식부터,
조직 내부에서 정의한 `.yml` 템플릿을 외부 프로젝트에서 참조하거나,
`.gitlab-ci.yml` 내부에 `.templates` 구조를 활용해 직접 정의하는 방식까지 다양한 활용법을 살펴보았다.
특히 이미지 빌드, 배포, Slack 알림까지 전체 워크플로우를 템플릿으로 캡슐화함으로써,
코드 중복 제거, 관리 편의성 향상, 자동화 일관성 확보라는 효과를 얻을 수 있다.
📅 관련 글
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 Template 활용
Gitlab에서 제공해주는 다양한 template 들도 있다.
template 파일을 참조하는 방법은 아래와 같다.
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml
include:
- template: Terraform/Base.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Terraform/Base.gitlab-ci.yml
- template: Jobs/SAST-IaC.gitlab-ci.yml # https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/SAST-IaC.gitlab-ci.yml
만약 자신의 gitlab의 project에 template이 정의되어 있다면 아래와 같이 작성하면 된다.
include:
- project: 'somaz94/server'
ref: master
file: '/template/my_template_file.yml'
참조하지 않고 `.gitlab-ci.yml` 에 정의해도 무관하다.
Template 작성
아래와 같이 build 관련한 template을 작성한다.
.templates:
.common_build_before_script: &common_build_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] 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"
- mkdir -p /kaniko/.docker
- >
if [[ "$NAMESPACE" =~ ^sarena- ]]; then
echo "Configuring registry for special namespace $NAMESPACE"
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
.common_build_script: &common_build_script
- >
if [[ "$NAMESPACE" =~ ^sarena- ]]; then
/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
/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
fi
- KANIKO_RESULT=$?
- echo "$SERVICE" > service_status.txt
- >
if [ $KANIKO_RESULT -eq 0 ]; then
echo "✅ 성공" > build_status.txt
else
echo "❌ 실패" > build_status.txt
fi
그리고 아래와 같이 활용할 수 있다.
build_manual_image:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.22.0-debug
interruptible: true
retry:
max: 2 # Maximum of 2 retries
when:
- runner_system_failure
- unknown_failure
before_script: *common_build_before_script
script: *common_build_script
artifacts:
paths:
- build_status.txt
- service_status.txt
rules:
- if: '($CI_PIPELINE_SOURCE == "web")'
tags:
- build-image
그리고 다른 job에도 활용이 가능하다.
.change_files_game: &change_files_game
changes:
- apps/game/src/**/*
build_auto_image_game:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.22.0-debug
interruptible: true
retry:
max: 2 # Maximum of 2 retries
when:
- runner_system_failure
- unknown_failure
before_script: *common_build_before_script
script: *common_build_script
variables:
SERVICE: $GAME_SERVICE
IMAGE_URL: $IMAGE_URL_GAME
IMAGE_URL_LATEST: $IMAGE_URL_LATEST_GAME
artifacts:
paths:
- build_status.txt
- service_status.txt
rules:
- if: '$CI_PIPELINE_SOURCE == "push"'
<<: *change_files_game
tags:
- build-image
SLACK에 메세지 보내는 템플릿도 아래와 같이 작성할 수 있다. 각각의 job마다 artifact로 결과를 받아서 메세지를 보내면 된다.
.common_update_after_script: &common_update_after_script
- >
if [ "$CI_JOB_STATUS" = "success" ]; then
echo "✅ 성공" > /builds/somaz94/server/update_status.txt
elif [ "$CI_JOB_STATUS" = "failed" ]; then
echo "❌ 실패" > /builds/somaz94/server/update_status.txt
elif [ "$CI_JOB_STATUS" = "canceled" ]; then
echo "⚠️ 취소" > /builds/somaz94/server/update_status.txt
else
echo "🔍 상태 불명" > /builds/somaz94/server/update_status.txt
...
update_manual_image:
stage: update
image: alpine:latest
interruptible: true
retry:
max: 2 # Maximum of 2 retries
when:
- runner_system_failure
- unknown_failure
before_script: *common_update_before_script
script: *common_update_script
after_script: *common_update_after_script
artifacts:
paths:
- update_status.txt
rules:
- if: $CI_PIPELINE_SOURCE == "web"
tags:
- deploy-image
dependencies:
- build_manual_image
나는 SLACK 웹훅을 활용했고, 워크플로우 빌더를 활용해 결과 값만 받으면 되게 작성하였다.
Build Result
[작업 상태]
- 프로젝트 :
- 대상 환경 :
- 배포 서비스:
- 기준 브랜치 :
- generator 결과 :
- 빌드 결과 :
- 배포 결과 :
- Gitlab CI URL :
- Commit Message :
- 트리거 유저 :
Slack notify Template을 작성해준다.
.common_notify_slack_script: &common_notify_slack_script
- export GENERATOR_STATUS=$(cat generator_status.txt || echo "⏭️ 스킵 or ✅ Commit Massage 확인")
- export BUILD_STATUS=$(cat build_status.txt || echo "⏭️ 스킵")
- export UPDATE_STATUS=$(cat update_status.txt || echo "⏭️ 스킵")
- export DEPLOY_SERVICE=$(cat service_status.txt || echo "⏭️ 스킵")
- export CLEAN_COMMIT_MESSAGE=$(echo "$CI_COMMIT_MESSAGE" | tr -d '\n' | tr -d '\r')
- >
export JSON_DATA="{\"source_branch\": \"$CI_COMMIT_REF_NAME\", \"generator_result\": \"$GENERATOR_STATUS\", \"deploy_result\": \"$UPDATE_STATUS\", \"build_result\": \"$BUILD_STATUS\", \"commit_message\": \"$CLEAN_COMMIT_MESSAGE\", \"trigger_user\": \"$GITLAB_USER_LOGIN\", \"gitlab_ci_run_url\": \"$CI_PIPELINE_URL\", \"repository_name\": \"$CI_PROJECT_PATH\", \"environment\": \"$NAMESPACE\", \"deploy_service\": \"$DEPLOY_SERVICE\"}"
- >
echo "Sending the following data to Slack: $JSON_DATA"
- >
export RESPONSE=$(curl -sS -X POST -H 'Content-type: application/json' --data "$JSON_DATA" $SLACK_WEBHOOK_URL)
- >
echo "Slack response: $RESPONSE"
그리고 아래와 같이 yml 파일을 작성해준다.
notify_slack:
stage: notify
image: curlimages/curl:latest
script: *common_notify_slack_script
rules:
- if: $CI_PIPELINE_SOURCE == "web"
- if: $CI_PIPELINE_SOURCE == "trigger"
tags:
- deploy-image
when: always
...
notify_slack_auto_game:
stage: notify
image: curlimages/curl:latest
script: *common_notify_slack_script
rules:
- if: '$CI_PIPELINE_SOURCE == "push"'
<<: *change_files_game
tags:
- deploy-image
when: always
dependencies:
- build_auto_image_game
- update_auto_image_game
이런식으로 활용하면, template을 작성해 아래와 같이 다양한 job에 적용이 가능하다.
Template 매개변수화 (Template Parameterization)
템플릿을 더욱 유연하게 만들기 위해 변수(변수화)를 사용하는 것도 중요하다.
`.gitlab-ci.yml` 에서는 `anchor(&)` 와 `alias(*)` 외에도 !reference 기능을 통해 정의된 템플릿 블록에 변수 값을 전달할 수 있다.
예를 들어 아래와 같이 `before_script` 내부에서 공통 스크립트를 작성하고, `job` 마다 환경변수를 오버라이드 할 수 있다.
.templates:
.base_env_template: &base_env_template
variables:
NODE_ENV: "default"
REGION: "kr"
job_example_1:
<<: *base_env_template
variables:
NODE_ENV: "dev"
REGION: "us"
Condition 분기 및 다양한 include 전략
조건 분기(`rules, only, except`)와 함께 `include:` 를 환경 별로 나눌 수 있어, 특정 브랜치 또는 환경에서만 특정 템플릿을 포함시킬 수 있다.
Template 분기 로딩 예시
`include:` 문법은 다양한 조건 분기에 따라 동적으로 템플릿을 구성할 수 있다. 예를 들어 브랜치에 따라 서로 다른 템플릿을 불러오는 방식도 가능하다.
include:
- local: '/ci-templates/common.yml'
- when: '$CI_COMMIT_BRANCH == "develop"'
local: '/ci-templates/dev.yml'
- when: '$CI_COMMIT_BRANCH == "main"'
local: '/ci-templates/prod.yml'
- 이를 활용하면 개발, 스테이징, 프로덕션 별 파이프라인 차별화가 가능하며,
- 공통 로직은 `common.yml` 로 관리하면서 환경에 따라 세부 구성이 달라진다.
Template Lint 및 시각화 도구
GitLab은 `.gitlab-ci.yml` 파일이 유효한지 확인할 수 있는 CI Lint 기능을 제공하며,
이를 통해 템플릿 포함 여부, 병합 결과 등을 미리 시뮬레이션할 수 있다.
CI 템플릿 디버깅 & Lint 활용법
`.gitlab-ci.yml` 이 복잡해질수록, 특히 여러 템플릿이 포함되는 구조에서는
Lint 도구를 사용하여 구문 오류나 병합 충돌 여부를 미리 검증하는 것이 좋다.
- GitLab Web UI → CI Lint 기능 사용
프로젝트의 CI/CD > Editor > 오른쪽 상단의 "CI Lint" 버튼을 클릭하면 현재 `.gitlab-ci.yml` 을 테스트할 수 있다. - API 기반으로 Lint 자동화
curl --header "PRIVATE-TOKEN: <your_access_token>" \
--data "content=$(cat .gitlab-ci.yml)" \
"https://gitlab.example.com/api/v4/ci/lint"
마무리
CI/CD 환경이 점점 복잡해지고 팀과 서비스가 많아질수록,
코드의 재사용성과 유지보수성은 더욱 중요해진다.
GitLab CI Template은 이러한 문제를 해결하는 훌륭한 도구로,
하나의 정의된 템플릿이 수십, 수백 개의 파이프라인에 적용될 수 있다.
또한 공통된 빌드 스크립트, 조건부 작업, Slack 알림 메시지 구성 등
다양한 작업을 모듈화함으로써 팀 간 표준화를 실현하고,
새로운 Job을 추가할 때도 빠르게 확장할 수 있는 기반을 마련할 수 있다.
앞으로 GitLab CI/CD를 설계하거나 개선할 때, 단순히 `.gitlab-ci.yml` 을 작성하는 수준을 넘어서
템플릿 기반 구조화 설계를 도입해보는 것을 추천한다.
작은 반복을 줄이고, 큰 효율을 만드는 시작이 될 것이다.
Reference
https://icinga.com/blog/2022/10/05/gitlab-ci-cd-job-templates/
'IaC > CI CD Tool' 카테고리의 다른 글
9. Github Action Steps Context 활용법 (0) | 2024.11.10 |
---|---|
8. Github Action Template 생성후 MarketPlace 등록하기 (2) | 2024.07.01 |
6. Gitlab CI Build(with GCP Artifact Registry, Harbor) (0) | 2024.06.24 |
7. Github Action Build and Push(with GCP Artifact Registry) (0) | 2024.06.19 |
ArgoCD SSO 구성 가이드(GCP Oauth) (0) | 2024.04.14 |