· 47분 읽기
GitLab CI/CD 완벽 가이드 (하): 파이프라인 아키텍처와 고급 Job 제어
상편에서 .gitlab-ci.yml의 기본 구조와 변수, Runner를 다뤘습니다. 하편에서는 파이프라인이 커질 때 필요한 것들을 다룹니다. 수백 줄로 불어난 설정을 구조화하는 아키텍처 패턴, Job 실행을 정밀하게 제어하는 rules·needs·extends, 그리고 Triggers·Webhooks·API를 통한 외부 시스템 통합까지, 실무에서 가장 자주 부딪히는 순서대로 정리합니다. 특히 모노레포에서 변경된 서비스만 골라 빌드하는 동적 파이프라인 패턴은 그대로 가져다 쓸 수 있도록 예제를 갖췄습니다.
왜 파이프라인 아키텍처가 필요한가
단일 .gitlab-ci.yml이 수백 줄로 커지면 관리가 어려워집니다.
# 거대한 단일 파일의 문제점
stages:
- build
- test
- deploy
# 50개 이상의 Jobs...
build-frontend:
# ...
build-backend:
# ...
build-mobile-ios:
# ...
build-mobile-android:
# ...
# ... 수백 줄
GitLab은 이 문제를 풀기 위한 네 가지 패턴을 제공합니다.
| 패턴 | 용도 |
|---|---|
| include | 설정 파일 분리 (동일 프로젝트 내) |
| Parent-Child | 동적 파이프라인, 조건부 실행 |
| Multi-Project | 프로젝트 간 트리거 |
| DAG | Stage를 무시하고 Job 간 의존성 직접 정의 |
include: 설정 파일 분리
가장 기본적인 모듈화 방법입니다. 네 가지 소스에서 설정을 가져올 수 있습니다.
include:
# 1. 로컬 파일
- local: '/templates/docker.yml'
# 2. 다른 프로젝트의 파일
- project: 'my-group/ci-templates'
ref: main
file: '/templates/node.yml'
# 3. 원격 URL
- remote: 'https://example.com/ci/template.yml'
# 4. GitLab 제공 템플릿
- template: 'Auto-DevOps.gitlab-ci.yml'
실전에서는 역할별로 파일을 나누고 메인 파일에서 조립하는 구조를 권장합니다.
project/
├── .gitlab-ci.yml # 메인 파일
├── .gitlab/
│ ├── ci/
│ │ ├── build.yml # 빌드 Jobs
│ │ ├── test.yml # 테스트 Jobs
│ │ └── deploy.yml # 배포 Jobs
│ └── templates/
│ └── docker.yml # 공통 템플릿
# .gitlab-ci.yml
stages:
- build
- test
- deploy
include:
- local: '.gitlab/ci/build.yml'
- local: '.gitlab/ci/test.yml'
- local: '.gitlab/ci/deploy.yml'
# .gitlab/ci/build.yml
build-app:
stage: build
script:
- npm run build
Parent-Child Pipeline
include가 파일을 나누는 수준이라면, Parent-Child는 파이프라인 자체를 계층화합니다. Parent Pipeline이 별도의 Child Pipeline을 트리거하는 구조입니다.
flowchart TB
subgraph Parent [Parent Pipeline]
P1[build]
P2[trigger-child]
end
subgraph Child [Child Pipeline]
C1[test-unit]
C2[test-e2e]
C3[deploy]
end
P1 --> P2
P2 -->|trigger| C1 & C2
C1 & C2 --> C3
정적 Child Pipeline
# .gitlab-ci.yml (Parent)
stages:
- build
- trigger
build:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
trigger-child:
stage: trigger
trigger:
include: .gitlab/child-pipeline.yml
strategy: depend # Child 완료까지 대기
# .gitlab/child-pipeline.yml (Child)
stages:
- test
- deploy
unit-test:
stage: test
script:
- npm run test:unit
deploy:
stage: deploy
script:
- ./deploy.sh
strategy 옵션에 따라 Parent가 Child를 기다릴지 결정됩니다.
| 옵션 | 동작 |
|---|---|
depend |
Child 완료까지 Parent Job 대기 |
| (없음) | Parent Job 즉시 완료, Child 비동기 실행 |
동적 Child Pipeline과 모노레포 패턴
Parent-Child의 진짜 위력은 런타임에 Child Pipeline YAML을 생성할 때 나옵니다. 변경된 코드에 따라 파이프라인 모양 자체가 달라지므로, 모노레포에서 특히 효과적입니다.
변경된 디렉토리만 테스트하기
# .gitlab-ci.yml
stages:
- generate
- trigger
generate-pipeline:
stage: generate
script:
- |
# 변경된 디렉토리에 따라 동적으로 파이프라인 생성
cat > child-pipeline.yml <<EOF
stages:
- test
$(for dir in $(git diff --name-only HEAD~1 | cut -d/ -f1 | sort -u); do
echo "${dir}-test:"
echo " stage: test"
echo " script:"
echo " - echo 'Testing $dir'"
echo ""
done)
EOF
artifacts:
paths:
- child-pipeline.yml
trigger-tests:
stage: trigger
trigger:
include:
- artifact: child-pipeline.yml
job: generate-pipeline
strategy: depend
변경된 서비스만 빌드하기
# .gitlab-ci.yml
stages:
- detect
- build
detect-changes:
stage: detect
script:
- |
# 변경된 서비스 감지
for service in frontend backend api; do
if git diff --name-only HEAD~1 | grep -q "^$service/"; then
echo "$service" >> changed_services.txt
fi
done
# 동적 파이프라인 생성
echo "stages:" > child.yml
echo " - build" >> child.yml
echo "" >> child.yml
while read service; do
cat >> child.yml <<EOF
build-${service}:
stage: build
script:
- cd ${service} && make build
EOF
done < changed_services.txt
artifacts:
paths:
- child.yml
trigger-builds:
stage: build
trigger:
include:
- artifact: child.yml
job: detect-changes
rules:
- exists:
- changed_services.txt
rules:changes로 서비스별 파이프라인 분기
YAML을 동적으로 생성할 필요까지 없다면, rules:changes로 특정 경로 변경 시에만 해당 Child를 트리거하는 방식이 더 단순합니다.
# .gitlab-ci.yml
stages:
- triggers
trigger-frontend:
stage: triggers
trigger:
include: frontend/.gitlab-ci.yml
rules:
- changes:
- frontend/**/*
trigger-backend:
stage: triggers
trigger:
include: backend/.gitlab-ci.yml
rules:
- changes:
- backend/**/*
trigger-shared:
stage: triggers
trigger:
include: shared/.gitlab-ci.yml
rules:
- changes:
- shared/**/*
flowchart TB
subgraph Parent [Parent Pipeline]
Check[변경 감지]
end
subgraph Children [Child Pipelines]
F[Frontend Pipeline]
B[Backend Pipeline]
S[Shared Pipeline]
end
Check -->|frontend/* 변경| F
Check -->|backend/* 변경| B
Check -->|shared/* 변경| S
Multi-Project Pipeline
Parent-Child가 한 프로젝트 안의 계층화라면, Multi-Project Pipeline은 다른 프로젝트의 파이프라인을 트리거합니다. 라이브러리 배포 후 이를 사용하는 앱을 빌드하는 식의 프로젝트 간 연쇄에 씁니다.
# Project A의 .gitlab-ci.yml
stages:
- build
- trigger
build:
stage: build
script:
- npm run build
trigger-project-b:
stage: trigger
trigger:
project: my-group/project-b
branch: main
strategy: depend
variables로 다운스트림에 값을 넘길 수 있습니다.
trigger-deploy:
stage: trigger
trigger:
project: my-group/deployment
branch: main
variables:
DEPLOY_ENV: production
IMAGE_TAG: $CI_COMMIT_SHA
flowchart LR
subgraph Upstream [Project A - 라이브러리]
U1[build]
U2[publish]
U3[trigger]
end
subgraph Downstream [Project B - 앱]
D1[install]
D2[build]
D3[deploy]
end
U1 --> U2 --> U3
U3 -->|trigger| D1
D1 --> D2 --> D3
실전 예제: 마이크로서비스 배포
베이스 이미지를 빌드한 뒤 각 서비스 프로젝트를 트리거하고, 모두 완료되면 인프라 배포를 트리거하는 구성입니다.
# infrastructure/.gitlab-ci.yml (Parent)
stages:
- build
- trigger-services
- deploy-infra
build-base-images:
stage: build
script:
- docker build -t base-node:$CI_COMMIT_SHA ./base-images/node
- docker push base-node:$CI_COMMIT_SHA
trigger-user-service:
stage: trigger-services
trigger:
project: my-org/user-service
branch: main
variables:
BASE_IMAGE_TAG: $CI_COMMIT_SHA
trigger-order-service:
stage: trigger-services
trigger:
project: my-org/order-service
branch: main
variables:
BASE_IMAGE_TAG: $CI_COMMIT_SHA
trigger-payment-service:
stage: trigger-services
trigger:
project: my-org/payment-service
branch: main
variables:
BASE_IMAGE_TAG: $CI_COMMIT_SHA
deploy-kubernetes:
stage: deploy-infra
trigger:
project: my-org/k8s-manifests
branch: main
needs:
- trigger-user-service
- trigger-order-service
- trigger-payment-service
variables:
SERVICES_VERSION: $CI_COMMIT_SHA
파이프라인 간 아티팩트 공유
Parent에서 만든 아티팩트는 needs로 Child에 전달할 수 있습니다.
# Parent
parent-job:
script:
- echo "data" > file.txt
artifacts:
paths:
- file.txt
trigger-child:
trigger:
include: child.yml
needs:
- parent-job
# Child (child.yml)
child-job:
script:
- cat file.txt # Parent의 아티팩트 사용 가능
프로젝트가 다른 경우에는 API로 직접 내려받습니다.
# Project B
downstream-job:
script:
- |
# Project A의 아티팩트 다운로드
curl --header "PRIVATE-TOKEN: $API_TOKEN" \
"$CI_API_V4_URL/projects/123/jobs/$UPSTREAM_JOB_ID/artifacts" \
--output artifacts.zip
unzip artifacts.zip
rules: 조건부 Job 실행
아키텍처를 갖췄다면 다음은 개별 Job의 실행 조건입니다. rules는 only/except를 대체하는 조건부 실행 키워드입니다.
job:
script:
- echo "Hello"
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: always
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
when: manual
- when: never # 기본값
중요
rules는 위에서 아래로 순차 평가하며, 첫 번째로 매칭된 rule이 적용되고 평가가 끝납니다. 매칭되는 rule이 없으면 Job이 실행되지 않습니다.
세 가지 조건 유형
if: 표현식 평가
rules:
# 브랜치 조건
- if: $CI_COMMIT_BRANCH == "main"
# Pipeline Source
- if: $CI_PIPELINE_SOURCE == "push"
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# 변수 존재 여부
- if: $DEPLOY_TOKEN
# 정규식
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
# 복합 조건
- if: $CI_COMMIT_BRANCH == "main" && $CI_PIPELINE_SOURCE == "push"
changes: 파일 변경 감지
build-frontend:
rules:
- changes:
- frontend/**/*
- shared/**/*
build-backend:
rules:
- changes:
paths:
- backend/**/*
compare_to: main # main 브랜치와 비교
exists: 파일 존재 확인
docker-build:
rules:
- exists:
- Dockerfile
- docker-compose.yml
npm-build:
rules:
- exists:
- package.json
when 옵션
| 값 | 동작 |
|---|---|
on_success |
이전 Stage 성공 시 (기본값) |
always |
항상 실행 |
never |
실행 안 함 |
manual |
수동 승인 필요 |
delayed |
지연 후 실행 |
deploy-prod:
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: manual
allow_failure: false # 블로킹 수동 Job
only/except에서 마이그레이션
# 이전 (only/except) - 더 이상 권장하지 않음
job:
only:
- main
except:
- tags
# 현재 (rules) - 권장
job:
rules:
- if: $CI_COMMIT_BRANCH == "main" && $CI_COMMIT_TAG == null
workflow:rules: 파이프라인 전역 제어
rules가 Job 단위라면, workflow:rules는 파이프라인 자체의 생성 여부를 제어합니다.
workflow:
rules:
# MR 파이프라인: MR 이벤트에서만
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# 브랜치 파이프라인: main, develop만
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_BRANCH == "develop"
# 태그 파이프라인
- if: $CI_COMMIT_TAG
# 그 외: 파이프라인 생성 안 함
stages:
- build
- test
같은 커밋에 브랜치 파이프라인과 MR 파이프라인이 중복 생성되는 흔한 문제도 여기서 해결합니다.
workflow:
rules:
# MR 이벤트 시 브랜치 파이프라인 방지 (MR 파이프라인만 실행)
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
when: never # MR이 열려있으면 브랜치 파이프라인 스킵
- if: $CI_COMMIT_BRANCH
needs: Stage를 넘어서는 DAG
기본 파이프라인은 Stage 단위로 순차 실행됩니다. needs를 쓰면 Stage를 무시하고 Job 간 직접 의존성(DAG, Directed Acyclic Graph)을 정의할 수 있습니다.
flowchart LR
subgraph Stage-based [Stage 기반]
direction TB
S1[Stage 1] --> S2[Stage 2] --> S3[Stage 3]
end
subgraph DAG [DAG 기반]
direction LR
A[Job A] --> C[Job C]
B[Job B] --> D[Job D]
C --> E[Job E]
D --> E
end
stages:
- build
- test
- deploy
build-frontend:
stage: build
script: make build-frontend
build-backend:
stage: build
script: make build-backend
test-frontend:
stage: test
needs: [build-frontend] # build-frontend 완료 즉시 시작
script: make test-frontend
test-backend:
stage: test
needs: [build-backend]
script: make test-backend
deploy:
stage: deploy
needs: [test-frontend, test-backend]
script: make deploy
build-frontend가 완료되면 build-backend를 기다리지 않고 즉시 test-frontend가 시작됩니다. 빌드 시간이 서로 다른 모노레포에서 전체 파이프라인 시간을 크게 줄여 줍니다.
flowchart LR
BF[build-frontend] --> TF[test-frontend]
BB[build-backend] --> TB[test-backend]
TF --> D[deploy]
TB --> D
needs 옵션
test:
needs:
- job: build
artifacts: true # 아티팩트 다운로드 (기본값)
optional: false # 필수 의존성 (기본값)
deploy:
needs:
- job: test
artifacts: false # 아티팩트 불필요
- job: security-scan
optional: true # 실패해도 진행
parallel로 분할된 Job에 의존할 때는 매트릭스를 명시합니다.
test:
parallel: 3
script: run-tests.sh
report:
needs:
- job: test
parallel:
matrix:
- RUNNER: [1, 2, 3] # parallel 모든 인스턴스 대기
dependencies: 아티팩트만 제어하기
dependencies는 실행 순서에는 관여하지 않고 아티팩트 다운로드만 제어합니다.
build:
stage: build
script: make build
artifacts:
paths:
- dist/
test:
stage: test
dependencies:
- build # build의 아티팩트만 다운로드
script: make test
deploy:
stage: deploy
dependencies: [] # 아티팩트 다운로드 안 함
script: make deploy
| 특성 | needs | dependencies |
|---|---|---|
| 실행 순서 | 제어함 (DAG) | 제어 안 함 |
| 아티팩트 | 기본 포함 | 전용 제어 |
| Stage 무시 | 가능 | 불가능 |
팁
needs를 사용하면 dependencies가 필요 없는 경우가 많습니다. needs: [job]은 해당 Job의 아티팩트를 자동으로 가져옵니다.
extends와 !reference: Job 템플릿
반복되는 Job 설정은 extends로 상속합니다. 점(.)으로 시작하는 Job은 실행되지 않는 템플릿입니다.
.test-template:
stage: test
image: node:20
before_script:
- npm ci
cache:
paths:
- node_modules/
unit-test:
extends: .test-template
script:
- npm run test:unit
integration-test:
extends: .test-template
script:
- npm run test:integration
services:
- postgres:15
다중 상속도 가능하며, 나중에 정의된 값이 이전 값을 덮어씁니다.
.base:
tags:
- docker
.node:
image: node:20
.cache:
cache:
paths:
- node_modules/
build:
extends:
- .base
- .node
- .cache
script:
- npm run build
!reference는 전체 상속이 아니라 특정 키만 선택적으로 재사용합니다.
.setup:
before_script:
- echo "Setting up..."
after_script:
- echo "Cleaning up..."
script:
- echo "Default script"
.test-vars:
variables:
TEST_ENV: "test"
build:
# .setup의 before_script만 가져옴
before_script:
- !reference [.setup, before_script]
- echo "Additional setup"
script:
- npm run build
variables:
# .test-vars의 variables 병합
!reference [.test-vars, variables]
# extends: 전체 병합
job1:
extends: .template # 모든 키 상속
# !reference: 선택적 재사용
job2:
script:
- !reference [.template, script] # script만 가져옴
실전 예제: 고급 제어를 모두 적용한 파이프라인
지금까지의 키워드를 한 파일에 조합한 예제입니다.
# 전역 설정
default:
image: node:20-alpine
interruptible: true
variables:
npm_config_cache: "$CI_PROJECT_DIR/.npm"
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_TAG
stages:
- prepare
- build
- test
- deploy
# 템플릿
.node-cache:
cache:
key:
files:
- package-lock.json
paths:
- .npm/
- node_modules/
.deploy-template:
image: bitnami/kubectl:latest
before_script:
- kubectl config use-context $KUBE_CONTEXT
# Jobs
install:
stage: prepare
extends: .node-cache
script:
- npm ci
artifacts:
paths:
- node_modules/
expire_in: 1 hour
lint:
stage: build
needs: [install]
script:
- npm run lint
allow_failure: true
build:
stage: build
needs: [install]
script:
- npm run build
artifacts:
paths:
- dist/
unit-test:
stage: test
needs:
- job: build
artifacts: true
script:
- npm run test:unit
coverage: '/Coverage: (\d+\.?\d*)%/'
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
e2e-test:
stage: test
needs: [build]
image: mcr.microsoft.com/playwright:v1.40.0
script:
- npm run test:e2e
rules:
- if: $CI_COMMIT_BRANCH == "main"
- when: manual
allow_failure: true
deploy-staging:
stage: deploy
extends: .deploy-template
needs: [unit-test]
environment:
name: staging
url: https://staging.example.com
script:
- kubectl apply -f k8s/staging/
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy-production:
stage: deploy
extends: .deploy-template
needs: [unit-test, e2e-test]
environment:
name: production
url: https://example.com
script:
- kubectl apply -f k8s/production/
rules:
- if: $CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/
when: manual
Pipeline Triggers: 토큰 기반 외부 트리거
여기서부터는 파이프라인을 GitLab 바깥과 연결하는 방법입니다. Trigger Token을 쓰면 외부 시스템에서 파이프라인을 실행할 수 있습니다.
설정 위치는 Settings > CI/CD > Pipeline trigger tokens이며, Add trigger로 토큰을 생성합니다.
# 기본 트리거
curl --request POST \
--form "token=YOUR_TRIGGER_TOKEN" \
--form "ref=main" \
"https://gitlab.com/api/v4/projects/PROJECT_ID/trigger/pipeline"
# 변수와 함께
curl --request POST \
--form "token=YOUR_TRIGGER_TOKEN" \
--form "ref=main" \
--form "variables[DEPLOY_ENV]=production" \
--form "variables[VERSION]=1.2.3" \
"https://gitlab.com/api/v4/projects/PROJECT_ID/trigger/pipeline"
파이프라인 쪽에서는 $CI_PIPELINE_SOURCE == "trigger"로 트리거 여부를 감지합니다.
triggered-deploy:
script:
- echo "Deploying version $VERSION to $DEPLOY_ENV"
rules:
- if: $CI_PIPELINE_SOURCE == "trigger"
needs: [] # 다른 Job 대기 없이 즉시 실행
Webhooks: 외부 이벤트로 파이프라인 실행
Trigger Token을 URL에 포함하면 Webhook 엔드포인트로 쓸 수 있습니다.
https://gitlab.com/api/v4/projects/PROJECT_ID/ref/REF_NAME/trigger/pipeline?token=TOKEN
# GitHub → GitLab 트리거
# GitHub 저장소의 Webhooks에 등록
https://gitlab.com/api/v4/projects/12345/ref/main/trigger/pipeline?token=abc123
# AWS SNS → GitLab 트리거
# Lambda를 통해 변환 후 트리거
Webhook으로 트리거된 파이프라인은 $TRIGGER_PAYLOAD로 페이로드에 접근할 수 있습니다.
process-webhook:
script:
- echo "$TRIGGER_PAYLOAD" | jq .
- export EVENT_TYPE=$(echo "$TRIGGER_PAYLOAD" | jq -r '.event_type')
- |
if [ "$EVENT_TYPE" = "release" ]; then
./deploy-release.sh
fi
rules:
- if: $CI_PIPELINE_SOURCE == "trigger"
GitLab API로 파이프라인 제어
Personal Access Token을 쓰면 파이프라인 생성부터 취소까지 API로 다룰 수 있습니다.
# 파이프라인 생성
curl --request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"ref": "main",
"variables": [
{"key": "DEPLOY_ENV", "value": "staging"},
{"key": "DEBUG", "value": "true"}
]
}' \
"https://gitlab.com/api/v4/projects/PROJECT_ID/pipeline"
# 특정 파이프라인 상태 조회
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.com/api/v4/projects/PROJECT_ID/pipelines/PIPELINE_ID"
# 최근 파이프라인 목록
curl --header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.com/api/v4/projects/PROJECT_ID/pipelines?per_page=5"
# Job 재시도
curl --request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.com/api/v4/projects/PROJECT_ID/jobs/JOB_ID/retry"
# 파이프라인 취소
curl --request POST \
--header "PRIVATE-TOKEN: $GITLAB_TOKEN" \
"https://gitlab.com/api/v4/projects/PROJECT_ID/pipelines/PIPELINE_ID/cancel"
CI Job Token: 파이프라인 안에서의 인증
CI_JOB_TOKEN은 파이프라인 실행 중에만 유효한 임시 토큰입니다. 별도 토큰 발급 없이 다른 프로젝트를 트리거하거나 아티팩트를 받을 때 씁니다.
notify-other-project:
script:
# 다른 프로젝트 파이프라인 트리거
- |
curl --request POST \
--form "token=$CI_JOB_TOKEN" \
--form "ref=main" \
"https://gitlab.com/api/v4/projects/OTHER_PROJECT_ID/trigger/pipeline"
download-artifacts:
script:
- |
curl --header "JOB-TOKEN: $CI_JOB_TOKEN" \
--output artifacts.zip \
"https://gitlab.com/api/v4/projects/PROJECT_ID/jobs/JOB_ID/artifacts"
- unzip artifacts.zip
접근을 허용할 프로젝트는 Settings > CI/CD > Token Access에서 관리합니다.
flowchart LR
subgraph ProjectA [Project A]
PA_Job[Job]
end
subgraph ProjectB [Project B]
PB_API[GitLab API]
PB_Token[Token Access 설정]
end
PA_Job -->|CI_JOB_TOKEN| PB_API
PB_Token -->|허용| PA_Job
ChatOps: Slack 알림과 배포 승인
배포 결과를 Slack으로 알리는 것은 외부 통합의 가장 흔한 형태입니다.
notify-slack:
stage: .post
script:
- |
curl -X POST -H 'Content-type: application/json' \
--data '{
"channel": "#deployments",
"username": "GitLab CI",
"text": "✅ Pipeline succeeded for $CI_PROJECT_NAME",
"attachments": [{
"color": "good",
"fields": [
{"title": "Branch", "value": "'$CI_COMMIT_BRANCH'", "short": true},
{"title": "Commit", "value": "'$CI_COMMIT_SHORT_SHA'", "short": true}
]
}]
}' \
$SLACK_WEBHOOK_URL
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
반대로 Slack에서 승인 버튼을 눌러 배포를 진행시키는 흐름도 만들 수 있습니다. Slack 버튼 클릭이 Lambda를 거쳐 GitLab API를 호출하는 구조입니다.
# Slack 버튼 클릭 → Lambda → GitLab API
request-approval:
stage: deploy
script:
- |
curl -X POST -H 'Content-type: application/json' \
--data '{
"text": "🚀 Production deployment pending",
"attachments": [{
"text": "Approve deployment?",
"callback_id": "deploy_'$CI_PIPELINE_ID'",
"actions": [
{"name": "approve", "text": "Approve", "type": "button", "style": "primary"},
{"name": "reject", "text": "Reject", "type": "button", "style": "danger"}
]
}]
}' \
$SLACK_WEBHOOK_URL
environment:
name: production
action: prepare
외부 CI/CD와 GitOps 연동
Jenkins, GitHub Actions에서 GitLab 트리거
기존 CI 시스템과 GitLab을 병행하는 조직이라면 Trigger Token으로 연결합니다.
// Jenkinsfile
pipeline {
agent any
stages {
stage('Build') {
steps {
sh 'make build'
}
}
stage('Trigger GitLab') {
steps {
sh '''
curl --request POST \
--form "token=${GITLAB_TRIGGER_TOKEN}" \
--form "ref=main" \
--form "variables[JENKINS_BUILD]=${BUILD_NUMBER}" \
"https://gitlab.com/api/v4/projects/${GITLAB_PROJECT_ID}/trigger/pipeline"
'''
}
}
}
}
# .github/workflows/trigger-gitlab.yml
name: Trigger GitLab Pipeline
on:
push:
branches: [main]
jobs:
trigger:
runs-on: ubuntu-latest
steps:
- name: Trigger GitLab
run: |
curl --request POST \
--form "token=${{ secrets.GITLAB_TRIGGER_TOKEN }}" \
--form "ref=main" \
--form "variables[GITHUB_SHA]=${{ github.sha }}" \
"https://gitlab.com/api/v4/projects/${{ secrets.GITLAB_PROJECT_ID }}/trigger/pipeline"
GitLab CI → ArgoCD
CI에서 ArgoCD를 직접 조작하는 방식입니다.
deploy-argocd:
stage: deploy
image: argoproj/argocd:latest
script:
# ArgoCD 로그인
- argocd login $ARGOCD_SERVER --username admin --password $ARGOCD_PASSWORD --insecure
# 이미지 태그 업데이트
- argocd app set $APP_NAME --helm-set image.tag=$CI_COMMIT_SHA
# Sync 트리거
- argocd app sync $APP_NAME --prune
# 배포 완료 대기
- argocd app wait $APP_NAME --timeout 300
rules:
- if: $CI_COMMIT_BRANCH == "main"
Image Updater 패턴
더 GitOps다운 방식은 CI가 이미지만 빌드해 푸시하고, ArgoCD Image Updater가 레지스트리를 감시해 자동 배포하는 것입니다.
# CI는 이미지만 빌드 & 푸시
# ArgoCD Image Updater가 자동으로 감지하여 배포
build-and-push:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
# SemVer 태그도 푸시 (Image Updater가 감지)
- docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:v$VERSION
- docker push $CI_REGISTRY_IMAGE:v$VERSION
flowchart LR
subgraph CI [GitLab CI]
Build[Build & Push Image]
end
subgraph Registry [Container Registry]
Images[(Images)]
end
subgraph GitOps [GitOps]
Updater[ArgoCD Image Updater]
ArgoCD[ArgoCD]
K8s[Kubernetes]
end
Build --> Images
Updater -->|Scan| Images
Updater -->|Update| ArgoCD
ArgoCD -->|Sync| K8s
팁
CI/CD 분리 원칙: CI는 아티팩트 생성, CD는 GitOps Agent가 담당합니다.
Scheduled Pipelines: 정기 실행
Build > Pipeline schedules > New schedule에서 cron 형식으로 등록하고, rules로 스케줄 전용 Job을 분리합니다.
nightly-test:
script:
- npm run test:full
rules:
- if: $CI_PIPELINE_SOURCE == "schedule"
variables:
FULL_TEST: "true"
daily-backup:
script:
- ./backup.sh
rules:
- if: $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULE_TYPE == "backup"
Schedule 설정에 변수를 지정하면 하나의 파이프라인으로 여러 스케줄을 구분할 수 있습니다.
# Schedule 설정에서 SCHEDULE_TYPE=security 지정
security-scan:
script:
- trivy image $CI_REGISTRY_IMAGE:latest
rules:
- if: $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULE_TYPE == "security"
실전 예제: 빌드부터 GitOps 배포, 알림까지
외부 통합 요소를 모두 조합한 마무리 예제입니다. 이미지를 빌드하고, GitOps 레포에 태그 업데이트 커밋을 만들고, 결과를 Slack으로 알립니다.
stages:
- build
- deploy
- notify
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
build:
stage: build
script:
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
rules:
- if: $CI_COMMIT_BRANCH == "main"
# ArgoCD 배포 트리거
trigger-argocd:
stage: deploy
image: curlimages/curl:latest
script:
- |
# GitOps 레포에 이미지 태그 업데이트 PR 생성
curl --request POST \
--header "PRIVATE-TOKEN: $GITOPS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"branch": "update-'$CI_COMMIT_SHORT_SHA'",
"commit_message": "Update image to '$DOCKER_IMAGE'",
"actions": [{
"action": "update",
"file_path": "apps/myapp/values.yaml",
"content": "image:\n tag: '$CI_COMMIT_SHA'"
}]
}' \
"https://gitlab.com/api/v4/projects/GITOPS_PROJECT_ID/repository/commits"
rules:
- if: $CI_COMMIT_BRANCH == "main"
# Slack 알림
notify-success:
stage: notify
script:
- |
curl -X POST -H 'Content-type: application/json' \
--data '{
"channel": "#deployments",
"text": "✅ '$CI_PROJECT_NAME' deployed successfully",
"attachments": [{
"color": "good",
"fields": [
{"title": "Version", "value": "'$CI_COMMIT_SHORT_SHA'"},
{"title": "Pipeline", "value": "'$CI_PIPELINE_URL'"}
]
}]
}' \
$SLACK_WEBHOOK_URL
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
notify-failure:
stage: notify
script:
- |
curl -X POST -H 'Content-type: application/json' \
--data '{
"channel": "#deployments",
"text": "❌ '$CI_PROJECT_NAME' deployment failed!",
"attachments": [{
"color": "danger",
"fields": [
{"title": "Pipeline", "value": "'$CI_PIPELINE_URL'"}
]
}]
}' \
$SLACK_WEBHOOK_URL
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_failure
정리
하편에서 다룬 내용을 한 표로 요약합니다.
| 영역 | 키워드 | 용도 |
|---|---|---|
| 아키텍처 | include |
설정 파일 분리 |
| 아키텍처 | trigger: include: |
Parent-Child, 동적 파이프라인 |
| 아키텍처 | trigger: project: |
Multi-Project 트리거 |
| Job 제어 | rules |
조건부 실행 (if, changes, exists) |
| Job 제어 | workflow:rules |
파이프라인 생성 자체를 제어 |
| Job 제어 | needs |
DAG 의존성, Stage 무시 |
| Job 제어 | dependencies |
아티팩트 다운로드 제어 |
| Job 제어 | extends, !reference |
템플릿 상속과 선택적 재사용 |
| 외부 통합 | Trigger Token, Webhook | 외부 시스템에서 파이프라인 실행 |
| 외부 통합 | API, CI_JOB_TOKEN |
파이프라인 생성·조회·재시도·취소 |
| 외부 통합 | ArgoCD, Schedule | GitOps 배포와 정기 실행 |
설정이 커지면 include와 Parent-Child로 나누고, 실행 조건은 rules와 workflow:rules로 좁히고, 속도는 needs 기반 DAG로 끌어올리고, 바깥 세계와는 Trigger와 API로 연결한다. 이 네 문장이 하편의 전부입니다. 기본기가 필요하다면 상편: 파이프라인 구조, 변수, Runner를 먼저 읽어 보시기 바랍니다.