이미지 로딩 중...
AI Generated
2025. 11. 5. · 5 Views
GitLab CI 핵심 개념 완벽 정리
GitLab CI/CD의 핵심 개념부터 실무 활용까지 상세하게 다룹니다. Pipeline, Job, Stage, Runner 등 필수 개념과 실전 팁을 통해 자동화 배포를 마스터하세요.
목차
- Pipeline 기본 구조 - CI/CD 자동화의 시작점
- Job과 Script - 실제 작업을 수행하는 핵심 단위
- Stage와 실행 순서 - Pipeline의 흐름 제어하기
- GitLab Runner - Pipeline을 실행하는 실행 엔진
- Variables와 Secrets - 환경별 설정 관리하기
- Artifacts와 Cache - 빌드 산출물과 의존성 관리
- Docker Integration - 컨테이너 기반 빌드 환경
- Rules와 조건부 실행 - 똑똑한 Pipeline 만들기
- Environments와 Deployments - 배포 이력 관리하기
- Merge Request Pipelines - 코드 리뷰와 CI 통합
1. Pipeline 기본 구조 - CI/CD 자동화의 시작점
시작하며
여러분이 코드를 커밋할 때마다 수동으로 테스트를 돌리고, 빌드하고, 배포하느라 시간을 낭비한 적 있나요? 팀원들과 협업할 때 "내 컴퓨터에서는 잘 됐는데"라는 말을 자주 듣는다면, 바로 CI/CD 파이프라인이 필요한 순간입니다.
이런 문제는 개발 현장에서 매우 흔합니다. 수동 배포는 실수를 유발하고, 배포 시간이 오래 걸리며, 팀원 간 환경 차이로 인한 버그가 발생합니다.
결과적으로 개발 속도가 느려지고 품질도 떨어집니다. 바로 이럴 때 필요한 것이 GitLab CI의 Pipeline입니다.
Pipeline을 설정하면 코드를 푸시하는 순간 자동으로 테스트, 빌드, 배포가 진행되어 개발자는 코드 작성에만 집중할 수 있습니다.
개요
간단히 말해서, Pipeline은 여러분의 코드가 커밋부터 배포까지 거치는 자동화된 여정 전체를 의미합니다. GitLab에서 코드를 푸시하면 자동으로 실행되는 일련의 작업들이 있는데, 이것들을 하나로 묶은 것이 바로 Pipeline입니다.
예를 들어, 프론트엔드 프로젝트에서 코드를 푸시하면 자동으로 ESLint 검사, Jest 테스트, 빌드, 그리고 AWS S3 배포까지 진행되는 경우가 이에 해당합니다. 기존에는 개발자가 수동으로 각 단계를 실행했다면, 이제는 .gitlab-ci.yml 파일 하나로 모든 과정을 자동화할 수 있습니다.
이를 통해 배포 시간을 90% 이상 단축하고, 인적 오류를 거의 제거할 수 있습니다. Pipeline의 핵심 특징은 세 가지입니다: 첫째, 모든 과정이 자동으로 실행되어 개발자 개입이 최소화됩니다.
둘째, 각 단계가 성공해야 다음 단계로 진행되어 품질을 보장합니다. 셋째, 실패 시 즉시 알림을 받아 빠르게 대응할 수 있습니다.
코드 예제
# .gitlab-ci.yml 파일의 기본 구조
# stages: 파이프라인의 단계들을 순서대로 정의
stages:
- test # 1단계: 테스트 실행
- build # 2단계: 빌드 수행
- deploy # 3단계: 배포 진행
# 테스트 작업 정의
test_job:
stage: test
script:
- npm install # 의존성 설치
- npm test # 테스트 실행
only:
- main # main 브랜치에만 적용
설명
이것이 하는 일: .gitlab-ci.yml 파일을 프로젝트 루트에 생성하면, GitLab이 이 파일을 읽어서 자동으로 Pipeline을 실행합니다. 위 예제는 테스트, 빌드, 배포라는 세 단계로 구성된 기본적인 Pipeline 구조입니다.
첫 번째로, stages 섹션에서 파이프라인의 전체 흐름을 정의합니다. test → build → deploy 순서로 실행되며, 앞 단계가 실패하면 다음 단계는 실행되지 않습니다.
이렇게 하는 이유는 테스트가 실패한 코드를 빌드하거나 배포하는 것을 방지하기 위함입니다. 실무에서는 보통 3-5개의 stage를 사용하며, lint, security-scan 같은 단계를 추가하기도 합니다.
그 다음으로, test_job이라는 구체적인 작업을 정의합니다. stage: test로 이 작업이 test 단계에 속한다고 명시하고, script에는 실제로 실행할 명령어들을 나열합니다.
npm install로 의존성을 먼저 설치하고, npm test로 Jest나 Mocha 같은 테스트를 실행합니다. 내부적으로 GitLab Runner라는 프로그램이 이 명령어들을 순차적으로 실행하며, 하나라도 실패하면 전체 작업이 실패로 표시됩니다.
마지막으로, only 키워드를 통해 이 Pipeline이 실행될 조건을 지정합니다. only: - main은 main 브랜치에 푸시될 때만 실행된다는 의미입니다.
이렇게 하면 개발 브랜치에서는 Pipeline을 실행하지 않아 Runner 자원을 절약할 수 있습니다. 반대로 except를 사용하면 특정 브랜치를 제외할 수도 있습니다.
여러분이 이 설정을 사용하면 코드를 main 브랜치에 푸시하는 순간 자동으로 테스트가 돌아가고, 성공하면 자연스럽게 빌드와 배포로 이어집니다. 실무에서 이점은 명확합니다: 배포 시간이 30분에서 3분으로 단축되고, 수동 배포로 인한 실수가 사라지며, 모든 배포 이력이 GitLab에 자동으로 기록되어 추적이 쉬워집니다.
실전 팁
💡 .gitlab-ci.yml 파일은 반드시 프로젝트 루트에 위치해야 합니다. 다른 위치에 있으면 GitLab이 인식하지 못합니다. 💡 처음에는 단순하게 시작하세요. test와 build 두 단계만으로도 충분히 효과를 볼 수 있습니다. 복잡한 Pipeline은 나중에 점진적으로 추가하는 것이 좋습니다. 💡 YAML 문법은 들여쓰기에 매우 민감합니다. 스페이스 2칸으로 통일하고, 탭 문자는 절대 사용하지 마세요. VSCode의 YAML 확장을 설치하면 문법 오류를 미리 잡을 수 있습니다. 💡 GitLab의 CI Lint 기능을 활용하세요. 프로젝트 > CI/CD > Pipelines > CI Lint에서 .gitlab-ci.yml 파일의 문법을 검증할 수 있습니다. 커밋하기 전에 꼭 확인하세요. 💡 실패한 Pipeline은 재실행할 수 있습니다. 일시적인 네트워크 오류로 실패한 경우, Pipeline 페이지에서 'Retry' 버튼을 클릭하면 처음부터 다시 실행됩니다.
2. Job과 Script - 실제 작업을 수행하는 핵심 단위
시작하며
여러분의 Pipeline이 여러 단계로 나뉘어 있다면, 각 단계에서 구체적으로 무슨 일을 해야 할까요? "테스트 단계"라고만 정의하면 GitLab은 무엇을 테스트해야 할지 알 수 없습니다.
이런 문제는 CI/CD를 처음 설정할 때 누구나 겪습니다. Stage는 큰 틀을 제공하지만, 실제로 실행될 명령어와 환경 설정이 없으면 아무 일도 일어나지 않습니다.
결과적으로 "Pipeline이 제대로 작동하지 않는다"는 오해가 생기기도 합니다. 바로 이럴 때 필요한 것이 Job입니다.
Job은 Stage 안에서 실제로 실행되는 작업 단위로, script를 통해 구체적인 명령어를 지정합니다. 하나의 Stage에 여러 Job을 병렬로 실행할 수도 있어 시간을 크게 절약할 수 있습니다.
개요
간단히 말해서, Job은 Pipeline에서 실제로 명령어를 실행하는 최소 작업 단위이며, Script는 그 Job이 수행할 구체적인 명령어 목록입니다. Stage가 "테스트"라는 추상적인 개념이라면, Job은 "Jest로 유닛 테스트 실행하기" 같은 구체적인 작업입니다.
예를 들어, 테스트 단계에서 프론트엔드 테스트와 백엔드 테스트를 동시에 실행하고 싶다면, 같은 Stage에 두 개의 Job을 만들어 병렬로 처리할 수 있습니다. 이렇게 하면 순차 실행 대비 절반의 시간만 소요됩니다.
기존에는 하나의 Job에 모든 명령어를 몰아넣었다면, 이제는 역할별로 Job을 분리하여 관리하고 재사용할 수 있습니다. Job이 실패하면 해당 Job만 재실행하면 되므로 디버깅도 훨씬 효율적입니다.
Job의 핵심 특징은 다음과 같습니다: 첫째, 각 Job은 독립적인 환경에서 실행되어 서로 영향을 주지 않습니다. 둘째, 같은 Stage의 Job들은 기본적으로 병렬 실행되어 시간을 절약합니다.
셋째, artifacts와 cache를 통해 Job 간 데이터를 공유할 수 있습니다. 이러한 특징들이 복잡한 빌드 프로세스를 효율적으로 관리하는 데 필수적입니다.
코드 예제
# 여러 Job을 정의한 예제
# 프론트엔드와 백엔드 테스트를 병렬로 실행
stages:
- test
# Job 1: 프론트엔드 테스트
frontend_test:
stage: test
script:
- cd frontend # 프론트엔드 디렉토리로 이동
- npm install # 의존성 설치
- npm run test:unit # 유닛 테스트 실행
- npm run test:e2e # E2E 테스트 실행
coverage: '/Coverage: \d+\.\d+%/' # 커버리지 추출 정규식
# Job 2: 백엔드 테스트 (frontend_test와 동시 실행)
backend_test:
stage: test
script:
- cd backend # 백엔드 디렉토리로 이동
- pip install -r requirements.txt # Python 의존성 설치
- pytest --cov=app # pytest로 테스트 및 커버리지 측정
allow_failure: true # 실패해도 Pipeline 계속 진행
설명
이것이 하는 일: 위 코드는 test라는 하나의 Stage에 두 개의 Job(frontend_test, backend_test)을 정의하여 병렬로 테스트를 실행합니다. 각 Job은 독립적인 환경에서 돌아가므로 서로 간섭하지 않습니다.
첫 번째로, frontend_test Job을 살펴보겠습니다. stage: test로 이 Job이 test 단계에 속한다고 명시하고, script 섹션에 실행할 명령어들을 순서대로 나열합니다.
cd frontend로 작업 디렉토리를 변경하고, npm install로 package.json에 정의된 의존성을 설치한 후, 두 가지 테스트(유닛, E2E)를 순차적으로 실행합니다. coverage 속성은 테스트 출력에서 커버리지 정보를 추출하는 정규식으로, GitLab이 이를 파싱하여 UI에 표시합니다.
이렇게 하면 각 커밋마다 테스트 커버리지를 추적할 수 있습니다. 그 다음으로, backend_test Job이 frontend_test와 동시에 실행됩니다.
같은 test Stage에 속해 있기 때문에 GitLab Runner는 가능하면 두 Job을 병렬로 처리합니다. 이 Job은 Python 프로젝트를 위한 것으로, pip로 의존성을 설치하고 pytest로 테스트를 실행합니다.
allow_failure: true는 매우 중요한 설정으로, 이 Job이 실패해도 전체 Pipeline을 중단하지 않고 계속 진행하도록 합니다. 실무에서는 선택적 테스트나 불안정한 E2E 테스트에 이 옵션을 사용합니다.
마지막으로, 각 Job의 script는 순차적으로 실행되며, 하나의 명령어가 실패하면(exit code 0이 아니면) 그 즉시 Job이 실패로 표시되고 다음 명령어는 실행되지 않습니다. 예를 들어 npm install이 실패하면 npm run test:unit은 실행되지 않습니다.
Job이 완료되면 exit code, 실행 시간, 로그가 모두 GitLab에 기록되어 나중에 확인할 수 있습니다. 여러분이 이 구조를 사용하면 프론트엔드와 백엔드 테스트를 동시에 실행하여 시간을 절반으로 줄일 수 있습니다.
실무에서의 이점은 분명합니다: 빠른 피드백으로 개발 속도가 향상되고, Job별로 독립적으로 관리되어 유지보수가 쉬우며, 실패한 Job만 재실행하여 비용을 절감할 수 있습니다.
실전 팁
💡 Job 이름은 의미 있게 지으세요. "job1", "job2" 대신 "unit_test", "e2e_test"처럼 명확한 이름을 사용하면 Pipeline UI에서 한눈에 파악할 수 있습니다. 💡 script의 각 명령어는 별도의 라인으로 작성하는 것이 좋습니다. 한 줄에 여러 명령어를 &&로 연결하면 어느 부분에서 실패했는지 파악하기 어렵습니다. 💡 긴 스크립트는 별도의 셸 스크립트 파일로 분리하세요. script: - bash scripts/run-tests.sh처럼 사용하면 .gitlab-ci.yml이 깔끔해지고 스크립트를 재사용하기도 쉽습니다. 💡 allow_failure는 신중하게 사용하세요. 중요한 테스트에 이 옵션을 켜면 실패를 놓칠 수 있습니다. 주로 실험적 기능이나 불안정한 외부 서비스 테스트에만 적용하세요. 💡 Job의 실행 시간을 모니터링하세요. GitLab UI에서 각 Job의 평균 실행 시간을 확인할 수 있습니다. 시간이 오래 걸리는 Job은 병렬화하거나 캐싱을 추가하여 최적화하세요.
3. Stage와 실행 순서 - Pipeline의 흐름 제어하기
시작하며
여러분의 프로젝트에서 테스트가 실패했는데도 빌드와 배포가 진행된다면 어떨까요? 또는 빌드가 완료되기 전에 배포가 시작되어 에러가 발생한다면?
이런 문제들은 실행 순서를 제대로 제어하지 못해서 발생합니다. CI/CD Pipeline에서 작업 순서는 매우 중요합니다.
테스트 없이 배포하면 버그가 프로덕션에 올라갈 수 있고, 빌드 전에 배포를 시도하면 배포할 파일 자체가 없어 실패합니다. 이런 논리적 순서를 지키지 않으면 Pipeline이 무용지물이 됩니다.
바로 이럴 때 필요한 것이 Stage입니다. Stage는 Job들을 논리적 그룹으로 묶고 실행 순서를 보장합니다.
앞 Stage가 모두 성공해야 다음 Stage로 넘어가므로, 품질 게이트 역할을 합니다.
개요
간단히 말해서, Stage는 Pipeline에서 Job들의 실행 순서를 제어하는 논리적 단계입니다. Stage를 사용하면 "테스트가 성공한 후에만 빌드하고, 빌드가 성공한 후에만 배포한다"는 규칙을 자동으로 적용할 수 있습니다.
예를 들어, lint → test → build → deploy 순서로 Stage를 정의하면, 코드 스타일 검사가 실패한 순간 나머지 단계는 모두 스킵됩니다. 이는 불필요한 리소스 낭비를 막고 빠른 피드백을 제공합니다.
기존에는 if문으로 조건을 체크하며 복잡하게 관리했다면, 이제는 Stage 순서만으로 자연스럽게 흐름이 제어됩니다. 또한 각 Stage별로 승인(manual action)을 요구할 수도 있어, 프로덕션 배포처럼 신중한 단계에 사람의 확인을 추가할 수 있습니다.
Stage의 핵심 특징은 세 가지입니다: 첫째, Stage는 순차적으로 실행되어 앞 Stage의 모든 Job이 성공해야 다음 Stage가 시작됩니다. 둘째, 같은 Stage 내의 Job들은 병렬로 실행되어 효율성을 높입니다.
셋째, 특정 Stage를 수동으로 실행하도록 설정하여 배포 타이밍을 제어할 수 있습니다.
코드 예제
# Stage를 활용한 완전한 CI/CD Pipeline
# 4단계 파이프라인: 린트 → 테스트 → 빌드 → 배포
stages:
- lint # 1단계: 코드 품질 검사
- test # 2단계: 테스트 실행
- build # 3단계: 애플리케이션 빌드
- deploy # 4단계: 서버에 배포
# Lint Job: 코드 스타일 검사 (가장 빠르게 실패 감지)
code_quality:
stage: lint
script:
- npm run lint # ESLint 실행
- npm run format:check # Prettier 검사
# Test Job: lint 성공 시에만 실행
run_tests:
stage: test
script:
- npm test
needs: [] # 의존성 없음 (lint Stage 완료 후 자동 시작)
# Build Job: test 성공 시에만 실행
build_app:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/ # 빌드 결과물을 다음 Stage로 전달
# Deploy Job: 수동 실행으로 설정 (배포 타이밍 제어)
deploy_production:
stage: deploy
script:
- scp -r dist/* server:/var/www/
when: manual # 버튼 클릭으로만 실행
only:
- main # main 브랜치에만 적용
설명
이것이 하는 일: 위 Pipeline은 4개의 Stage를 통해 코드 품질 검사부터 배포까지 안전하게 진행합니다. 각 단계가 게이트 역할을 하여, 문제가 있으면 즉시 중단됩니다.
첫 번째로, lint Stage가 가장 먼저 실행됩니다. 이 단계에서는 코드 스타일과 기본 문법을 검사합니다.
ESLint와 Prettier를 실행하여 코딩 컨벤션을 확인하는데, 이 검사는 매우 빠르게 완료됩니다(보통 10-30초). 만약 여기서 실패하면 뒤의 모든 Stage는 실행되지 않습니다.
왜 이렇게 하냐면, 코드 스타일 문제를 먼저 잡아야 테스트나 빌드에 시간을 낭비하지 않기 때문입니다. 실무에서는 "빠른 실패(fail fast)" 원칙이 매우 중요합니다.
그 다음으로, lint가 성공하면 test Stage가 자동으로 시작됩니다. run_tests Job이 npm test를 실행하여 유닛 테스트, 통합 테스트 등을 수행합니다.
needs: []는 이 Job이 다른 Job의 결과물(artifacts)에 의존하지 않는다는 의미입니다. 만약 needs: ["code_quality"]로 설정하면 lint Stage가 완료되는 즉시 test Stage를 시작하여 시간을 약간 절약할 수 있지만, 일반적으로는 Stage 순서만으로도 충분합니다.
테스트가 실패하면 당연히 빌드와 배포는 건너뛰어집니다. 세 번째로, build Stage에서는 실제 배포 가능한 파일을 생성합니다.
npm run build가 성공하면 artifacts를 통해 dist/ 디렉토리를 저장합니다. 이 artifacts는 다음 Stage(deploy)에서 자동으로 다운로드되어 사용됩니다.
GitLab은 artifacts를 기본 30일간 보관하며, 나중에 다운로드할 수도 있습니다. 이렇게 Stage 간에 파일을 전달하는 것은 CI/CD에서 매우 중요한 패턴입니다.
마지막으로, deploy Stage는 when: manual로 설정되어 있어 자동 실행되지 않습니다. 앞의 모든 Stage가 성공하면 GitLab UI에 "Deploy" 버튼이 나타나고, 담당자가 버튼을 클릭해야만 배포가 시작됩니다.
이는 프로덕션 배포처럼 신중해야 하는 작업에 적합합니다. only: - main 조건으로 main 브랜치에만 배포 옵션이 표시되도록 제한했습니다.
여러분이 이 구조를 사용하면 코드 품질과 안정성이 크게 향상됩니다. 실무에서의 이점: 문제가 있는 코드는 절대 배포되지 않고, 각 단계별로 명확한 피드백을 받으며, 수동 배포로 배포 타이밍을 완벽히 제어할 수 있습니다.
또한 Pipeline UI에서 어느 단계에서 실패했는지 한눈에 파악할 수 있습니다.
실전 팁
💡 Stage는 3-5개가 적당합니다. 너무 많으면 복잡해지고, 너무 적으면 유연성이 떨어집니다. 일반적으로 lint, test, build, deploy면 충분합니다. 💡 빠른 검사를 앞 Stage에 배치하세요. 린트 검사(30초)를 테스트(5분)보다 먼저 실행하면 실패를 빠르게 감지하여 시간과 비용을 절약할 수 있습니다. 💡 when: manual은 프로덕션 배포에만 사용하세요. 개발/스테이징 환경은 자동 배포로 설정하여 개발 속도를 높이고, 프로덕션만 수동으로 제어하는 것이 좋은 균형입니다. 💡 only/except 대신 rules를 사용하는 것이 권장됩니다. rules는 더 유연하고 강력한 조건 설정을 제공합니다. 예: rules: - if: '$CI_COMMIT_BRANCH == "main"' 💡 Stage별 타임아웃을 설정하세요. timeout: 10m처럼 Job별로 제한을 두면 무한 대기를 방지할 수 있습니다. 기본값은 1시간이지만, 대부분의 Job은 10-15분이면 충분합니다.
4. GitLab Runner - Pipeline을 실행하는 실행 엔진
시작하며
여러분이 완벽한 .gitlab-ci.yml 파일을 작성했는데 Pipeline이 "pending" 상태에서 멈춰 있다면? 또는 "No runners available" 에러를 본 적 있나요?
이는 Pipeline을 실행할 Runner가 없기 때문입니다. 많은 개발자들이 GitLab CI를 설정할 때 YAML 파일 작성에만 집중하고, 실제로 그 파일을 실행하는 주체에 대해서는 간과합니다.
Pipeline은 그저 명령어 목록일 뿐이고, 이를 실제로 수행하는 것은 GitLab Runner입니다. Runner가 없거나 제대로 설정되지 않으면 아무것도 실행되지 않습니다.
바로 이럴 때 필요한 것이 GitLab Runner에 대한 이해입니다. Runner는 실제로 코드를 체크아웃하고, 명령어를 실행하고, 결과를 GitLab 서버로 전송하는 에이전트입니다.
Shared Runner를 사용하거나 직접 설치할 수 있으며, 적절한 Runner 설정이 CI/CD 성능을 좌우합니다.
개요
간단히 말해서, GitLab Runner는 .gitlab-ci.yml에 정의된 Job을 실제로 실행하는 별도의 프로그램입니다. GitLab 서버는 코드 저장소와 Pipeline 관리만 담당하고, 실제 빌드/테스트/배포 작업은 Runner가 수행합니다.
예를 들어, 여러분이 코드를 푸시하면 GitLab 서버가 Runner에게 "이 Job을 실행해줘"라고 요청하고, Runner가 작업을 완료한 후 결과를 다시 서버로 보냅니다. 이런 아키텍처 덕분에 GitLab 서버의 부하가 분산되고, Runner를 원하는 환경(클라우드, 온프레미스 등)에 설치할 수 있습니다.
기존에는 CI 서버 하나에서 모든 빌드를 처리했다면, 이제는 여러 Runner를 등록하여 작업을 분산 처리할 수 있습니다. 팀의 규모가 커지면 Runner를 추가하기만 하면 되므로 확장성이 뛰어납니다.
Runner의 핵심 특징은 다음과 같습니다: 첫째, Shared Runner(GitLab.com이 제공)와 Specific Runner(직접 설치)를 선택할 수 있습니다. 둘째, Docker, Shell, Kubernetes 등 다양한 Executor를 지원하여 격리된 환경을 제공합니다.
셋째, Tags를 통해 특정 Runner에게만 Job을 할당할 수 있습니다. 이러한 유연성이 다양한 프로젝트 요구사항을 충족시킵니다.
코드 예제
# Runner와 태그를 활용한 Job 할당
# .gitlab-ci.yml에서 특정 Runner 지정하기
stages:
- build
- deploy
# Docker Runner를 사용하는 빌드 Job
build_docker:
stage: build
tags:
- docker # 'docker' 태그가 있는 Runner에서만 실행
- linux # 'linux' 태그도 있어야 함 (AND 조건)
image: node:18 # Docker 이미지 지정
script:
- npm ci # package-lock.json 기반 설치 (더 빠름)
- npm run build
artifacts:
paths:
- dist/
# Shell Executor를 사용하는 배포 Job
deploy_server:
stage: deploy
tags:
- shell # 'shell' 태그가 있는 Runner 사용
- production # 프로덕션 서버에 설치된 Runner
script:
- rsync -avz dist/ /var/www/html/
environment:
name: production # 배포 환경 이름
url: https://example.com
only:
- main
설명
이것이 하는 일: 위 설정은 두 가지 다른 유형의 Runner를 사용하여 빌드와 배포를 수행합니다. 각 Job은 tags를 통해 적절한 Runner에 할당됩니다.
첫 번째로, build_docker Job은 Docker Executor를 사용하는 Runner에서 실행됩니다. tags: - docker로 지정하면, GitLab은 'docker' 태그가 등록된 Runner 중 하나를 선택하여 이 Job을 할당합니다.
여러 태그를 나열하면 모든 태그를 가진 Runner만 선택되므로(AND 조건), docker와 linux 태그를 모두 가진 Runner가 필요합니다. image: node:18은 Docker Executor에서만 작동하는 설정으로, 이 이미지를 기반으로 격리된 컨테이너를 생성하여 작업을 수행합니다.
매번 깨끗한 환경에서 시작하므로 "내 컴퓨터에서는 되는데" 문제가 발생하지 않습니다. 그 다음으로, npm ci 명령어를 사용하는 점에 주목하세요.
npm install 대신 npm ci를 사용하면 package-lock.json을 엄격히 따르고, node_modules를 삭제한 후 설치하므로 더 안정적이고 빠릅니다. CI 환경에서는 항상 npm ci를 사용하는 것이 권장됩니다.
빌드가 완료되면 artifacts로 dist/ 디렉토리를 저장하여 다음 Stage에서 사용할 수 있게 합니다. 세 번째로, deploy_server Job은 완전히 다른 Runner를 사용합니다.
tags: - shell로 지정하면 Shell Executor로 설정된 Runner가 선택됩니다. Shell Executor는 Runner가 설치된 서버의 쉘에서 직접 명령어를 실행하므로, 해당 서버에 배포 권한과 도구(rsync 등)가 설치되어 있어야 합니다.
production 태그를 추가하여 프로덕션 서버에 설치된 특정 Runner만 사용하도록 제한했습니다. 이렇게 하면 실수로 잘못된 서버에 배포하는 것을 방지할 수 있습니다.
마지막으로, environment 설정은 GitLab의 Environments 기능을 활성화합니다. 이를 통해 배포 이력을 추적하고, 각 환경의 현재 상태를 확인하며, 필요시 롤백할 수 있습니다.
url을 지정하면 GitLab UI에서 "View deployment" 버튼이 나타나 바로 사이트에 접속할 수 있습니다. 여러분이 이 구조를 사용하면 빌드는 격리된 Docker 환경에서, 배포는 실제 서버에서 수행되어 보안과 효율성을 모두 확보할 수 있습니다.
실무에서의 이점: Docker Runner로 환경 일관성을 보장하고, Shell Runner로 직접 서버 접근이 필요한 작업을 수행하며, 태그로 정확한 Runner 선택을 보장합니다.
실전 팁
💡 GitLab.com의 Shared Runner는 무료 티어에서 월 400분(2024년 기준)을 제공합니다. 이를 초과하면 자체 Runner를 설치하거나 유료 플랜을 고려하세요. 💡 Docker Executor가 가장 권장됩니다. 매번 깨끗한 환경에서 시작하고, 설정이 간단하며, 보안도 우수합니다. Shell Executor는 특별한 이유가 없다면 피하세요. 💡 Specific Runner 설치는 간단합니다. GitLab의 Settings > CI/CD > Runners에서 토큰을 복사하고, Runner를 설치한 후 gitlab-runner register 명령으로 등록하면 끝입니다. 💡 민감한 작업(프로덕션 배포, 보안 스캔)은 반드시 전용 Runner를 사용하세요. Shared Runner는 다른 프로젝트와 공유되므로 보안 위험이 있을 수 있습니다. 💡 Runner의 동시 실행 수를 조정하세요. config.toml 파일의 concurrent 설정으로 동시에 실행할 Job 수를 제한할 수 있습니다. 서버 리소스에 맞게 설정하지 않으면 과부하가 발생합니다.
5. Variables와 Secrets - 환경별 설정 관리하기
시작하며
여러분이 개발, 스테이징, 프로덕션 환경마다 다른 데이터베이스 URL이나 API 키를 사용한다면, 이를 어떻게 관리하시나요? .gitlab-ci.yml 파일에 하드코딩하면 보안 문제가 발생하고, Git 히스토리에 남아 위험합니다.
이런 문제는 CI/CD에서 가장 흔하면서도 심각한 보안 이슈입니다. API 키나 비밀번호를 코드에 포함시키면 누구나 볼 수 있고, 실수로 public 저장소에 푸시하면 몇 분 내에 악용될 수 있습니다.
또한 환경마다 다른 값을 사용하려면 코드를 매번 수정해야 하는 번거로움도 있습니다. 바로 이럴 때 필요한 것이 GitLab의 Variables 기능입니다.
Variables를 사용하면 민감한 정보를 안전하게 저장하고, 환경별로 다른 값을 자동으로 주입하며, 코드 변경 없이 설정을 업데이트할 수 있습니다.
개요
간단히 말해서, Variables는 Pipeline에서 사용할 수 있는 환경 변수로, 민감한 정보나 환경별 설정을 안전하게 관리하는 수단입니다. GitLab은 프로젝트, 그룹, 인스턴스 레벨에서 Variables를 정의할 수 있으며, 이들은 Job 실행 시 자동으로 환경 변수로 주입됩니다.
예를 들어, DATABASE_URL이라는 Variable을 정의하면, script에서 $DATABASE_URL로 바로 사용할 수 있습니다. GitLab은 Masked와 Protected 옵션을 제공하여 보안을 강화합니다.
기존에는 .env 파일을 Git에 커밋하거나 서버에 수동으로 복사했다면, 이제는 GitLab UI에서 중앙 집중식으로 관리할 수 있습니다. 값을 변경해도 Git 히스토리에 남지 않고, 접근 권한이 있는 사람만 볼 수 있어 안전합니다.
Variables의 핵심 특징은 다음과 같습니다: 첫째, Masked Variables는 로그에 ***로 표시되어 유출을 방지합니다. 둘째, Protected Variables는 protected 브랜치(main, master 등)에서만 사용되어 중요한 정보 보호를 강화합니다.
셋째, Environment Scope로 특정 환경에만 적용되는 값을 설정할 수 있습니다. 이러한 기능들이 엔터프라이즈 수준의 보안을 제공합니다.
코드 예제
# Variables를 활용한 안전한 배포 스크립트
# .gitlab-ci.yml에서 Variables 사용하기
stages:
- deploy
# 환경별로 다른 Variables 사용
deploy_staging:
stage: deploy
script:
# GitLab에서 정의한 Variables를 환경 변수로 사용
- echo "Deploying to $DEPLOY_SERVER"
- echo "Database URL: $DATABASE_URL" # 로그에는 ***로 표시됨
# AWS CLI에 자격 증명 전달
- aws s3 sync dist/ s3://$S3_BUCKET/
--region $AWS_REGION
environment:
name: staging
url: https://staging.example.com
only:
- develop
deploy_production:
stage: deploy
script:
# 프로덕션 환경의 Variables 사용 (다른 값)
- echo "Deploying to $DEPLOY_SERVER"
- docker login -u $DOCKER_USER -p $DOCKER_PASSWORD
- docker push myapp:$CI_COMMIT_SHA # GitLab 내장 변수
environment:
name: production
url: https://example.com
only:
- main
when: manual
설명
이것이 하는 일: 위 Pipeline은 스테이징과 프로덕션 배포에서 각각 다른 Variables를 사용합니다. GitLab의 Settings > CI/CD > Variables에서 정의한 값들이 자동으로 주입됩니다.
첫 번째로, Variables 설정 방법을 살펴보겠습니다. GitLab UI에서 프로젝트 > Settings > CI/CD > Variables로 이동하여 "Add variable" 버튼을 클릭합니다.
예를 들어, DATABASE_URL이라는 키에 postgres://user:pass@host/db 같은 값을 입력하고, "Mask variable" 체크박스를 활성화하면 됩니다. Mask를 켜면 이 값이 로그에 출력되더라도 ***로 대체되어 보안이 유지됩니다.
단, Masked Variables는 최소 8자 이상이고 특정 패턴을 따라야 한다는 제약이 있습니다. 그 다음으로, Environment Scope를 활용하여 환경별로 다른 값을 설정합니다.
같은 DEPLOY_SERVER라는 키로 두 개의 Variables를 만들되, 하나는 Scope를 "staging"으로, 다른 하나는 "production"으로 설정합니다. 그러면 deploy_staging Job에서는 staging의 값이, deploy_production Job에서는 production의 값이 자동으로 사용됩니다.
이렇게 하면 코드 한 줄 변경 없이 환경별 배포가 가능합니다. 세 번째로, Protected Variables를 사용하여 보안을 더욱 강화합니다.
DOCKER_PASSWORD 같은 매우 민감한 정보는 "Protected" 옵션을 켜세요. 그러면 이 Variable은 protected 브랜치(main, master)에서만 사용할 수 있고, 개발 브랜치에서는 접근할 수 없습니다.
이는 공격자가 악의적인 코드를 feature 브랜치에 푸시하여 비밀번호를 탈취하는 것을 방지합니다. 마지막으로, GitLab은 많은 내장 변수(Predefined Variables)를 제공합니다.
CI_COMMIT_SHA(현재 커밋 해시), CI_COMMIT_BRANCH(브랜치 이름), CI_PIPELINE_ID(Pipeline 번호) 등을 사용하면 유용합니다. 예를 들어 Docker 이미지 태그를 $CI_COMMIT_SHA로 지정하면 각 커밋마다 고유한 이미지가 생성되어 추적이 쉽습니다.
전체 목록은 GitLab 문서의 "Predefined variables reference"에서 확인하세요. 여러분이 이 방식을 사용하면 절대 코드에 비밀번호를 커밋하지 않게 됩니다.
실무에서의 이점: Git 히스토리에 민감 정보가 남지 않아 보안 감사를 통과하고, 값 변경 시 재배포 없이 Variable만 수정하면 되며, 팀원별로 접근 권한을 세밀하게 제어할 수 있습니다.
실전 팁
💡 절대 API 키나 비밀번호를 .gitlab-ci.yml에 직접 작성하지 마세요. 한 번 커밋되면 Git 히스토리에 영구히 남아 삭제가 매우 어렵습니다. 💡 모든 민감한 Variables는 Masked로 설정하세요. 실수로 echo $API_KEY 같은 명령을 실행해도 로그에 **로 표시되어 안전합니다. 💡 프로덕션 자격 증명은 반드시 Protected로 설정하세요. 이렇게 하면 main 브랜치에서만 사용되어 보안이 크게 강화됩니다. 💡 Environment Scope를 적극 활용하세요. /, production, staging/ 같은 패턴으로 유연하게 설정할 수 있습니다. 우선순위는 가장 구체적인 매치가 이깁니다. 💡 Variables를 너무 많이 만들지 마세요. 관리가 어려워지므로, 비슷한 것들은 JSON 형태로 하나의 Variable에 저장하고 jq로 파싱하는 것도 좋은 방법입니다.
6. Artifacts와 Cache - 빌드 산출물과 의존성 관리
시작하며
여러분이 빌드 단계에서 생성한 dist/ 디렉토리를 배포 단계에서 사용하려는데 "파일이 없다"는 에러가 발생한다면? 또는 매번 npm install로 몇 분씩 기다리는 게 답답하다면?
이런 문제는 GitLab CI에서 각 Job이 독립적인 환경에서 실행되기 때문에 발생합니다. 기본적으로 한 Job에서 생성한 파일은 다른 Job에서 접근할 수 없고, 매번 깨끗한 상태에서 시작하므로 node_modules 같은 의존성을 반복적으로 다운로드해야 합니다.
이는 시간과 네트워크 대역폭을 낭비합니다. 바로 이럴 때 필요한 것이 Artifacts와 Cache입니다.
Artifacts는 Job 간에 빌드 산출물을 전달하는 메커니즘이고, Cache는 의존성 같은 파일을 재사용하여 실행 시간을 단축시킵니다. 이 둘을 제대로 설정하면 Pipeline 실행 시간을 절반 이하로 줄일 수 있습니다.
개요
간단히 말해서, Artifacts는 Job의 결과물을 저장하고 다른 Job에 전달하는 기능이고, Cache는 빌드 간에 파일을 재사용하여 속도를 높이는 기능입니다. 두 개념은 비슷해 보이지만 용도가 완전히 다릅니다.
Artifacts는 필수 결과물(빌드된 앱, 테스트 리포트 등)을 저장하고 GitLab에 업로드되어 나중에 다운로드할 수 있습니다. 예를 들어, 빌드 Job에서 생성한 dist/를 artifacts로 저장하면, 배포 Job에서 자동으로 다운로드되어 사용할 수 있습니다.
반면 Cache는 재생성 가능한 파일(node_modules, pip cache 등)을 저장하여 다음 실행 시 다운로드 시간을 절약합니다. 기존에는 빌드 결과를 외부 스토리지에 수동으로 업로드했다면, 이제는 artifacts로 자동 관리됩니다.
또한 매번 의존성을 처음부터 설치했다면, 이제는 cache로 재사용하여 시간을 대폭 단축할 수 있습니다. 핵심 특징은 다음과 같습니다: 첫째, Artifacts는 기본 30일간 보관되며 GitLab UI에서 다운로드할 수 있습니다.
둘째, Cache는 best-effort로 작동하여 없어도 빌드는 성공해야 합니다. 셋째, 두 가지 모두 paths로 대상 파일/디렉토리를 지정합니다.
이러한 메커니즘이 효율적인 CI/CD를 가능하게 합니다.
코드 예제
# Artifacts와 Cache를 활용한 최적화된 Pipeline
stages:
- build
- test
- deploy
# 의존성 캐싱 정의 (모든 Job에서 재사용 가능)
.cache_template: &cache_config
cache:
key: ${CI_COMMIT_REF_SLUG} # 브랜치별로 다른 캐시
paths:
- node_modules/ # npm 의존성 캐시
- .npm/ # npm 캐시 디렉토리
policy: pull-push # 캐시 읽고 쓰기
# 빌드 Job: 의존성 설치 및 빌드
build_app:
<<: *cache_config # 위에서 정의한 캐시 설정 재사용
stage: build
script:
- npm ci --cache .npm --prefer-offline # 캐시 활용
- npm run build
artifacts:
paths:
- dist/ # 빌드 결과물
expire_in: 1 week # 1주일 후 자동 삭제
reports:
dotenv: build.env # 환경 변수 파일도 전달 가능
# 테스트 Job: artifacts 자동 다운로드됨
test_app:
<<: *cache_config
cache:
policy: pull # 읽기만 (테스트는 캐시 수정 안 함)
stage: test
script:
- npm ci --cache .npm --prefer-offline
- npm test
artifacts:
reports:
junit: junit.xml # 테스트 결과를 GitLab UI에 표시
when: always # 실패해도 결과 업로드
# 배포 Job: build_app의 artifacts만 필요
deploy_app:
stage: deploy
dependencies:
- build_app # build_app의 artifacts만 다운로드
script:
- ls -la dist/ # dist/가 자동으로 존재함
- rsync -avz dist/ server:/var/www/
설명
이것이 하는 일: 위 Pipeline은 Cache로 node_modules를 재사용하여 설치 시간을 단축하고, Artifacts로 빌드 결과물을 배포 Job에 전달합니다. 이를 통해 Pipeline 실행 시간을 크게 줄입니다.
첫 번째로, Cache 설정을 YAML anchor(&cache_config)로 정의하여 여러 Job에서 재사용합니다. key: ${CI_COMMIT_REF_SLUG}는 브랜치 이름을 기반으로 캐시 키를 생성하여, main 브랜치와 feature 브랜치가 각각 독립적인 캐시를 갖게 합니다.
이렇게 하는 이유는 브랜치마다 의존성 버전이 다를 수 있기 때문입니다. paths에는 캐싱할 디렉토리를 나열하는데, node_modules/는 npm 패키지들이고, .npm/은 npm의 내부 캐시 디렉토리입니다.
policy: pull-push는 캐시를 읽고(pull) 작업 후 업데이트(push)한다는 의미입니다. 그 다음으로, build_app Job에서 npm ci --cache .npm --prefer-offline을 사용합니다.
--cache .npm은 npm이 .npm/ 디렉토리를 캐시로 사용하도록 지정하고, --prefer-offline은 가능하면 네트워크 없이 캐시에서 설치하도록 합니다. 이 조합으로 npm install이 5분에서 30초로 단축될 수 있습니다.
빌드가 완료되면 artifacts로 dist/를 저장하고, expire_in: 1 week로 1주일 후 자동 삭제되도록 설정합니다. 기본값은 30일이지만, 저장 공간을 절약하려면 짧게 설정하세요.
세 번째로, test_app Job에서는 cache policy를 pull로 변경했습니다. 테스트는 node_modules를 수정하지 않으므로 읽기만 하면 되고, 푸시하지 않으면 시간을 절약할 수 있습니다.
artifacts의 reports: junit은 특별한 artifacts 유형으로, 테스트 결과를 GitLab UI에서 시각적으로 표시합니다. when: always는 테스트가 실패해도 결과를 업로드하여, 어떤 테스트가 실패했는지 확인할 수 있게 합니다.
마지막으로, deploy_app Job은 dependencies: - build_app으로 build_app의 artifacts만 다운로드합니다. 기본적으로는 이전 Stage의 모든 artifacts가 다운로드되는데, 이렇게 명시하면 필요한 것만 받아 시간을 절약합니다.
ls -la dist/를 실행하면 build_app에서 생성한 파일들이 자동으로 존재하는 것을 확인할 수 있습니다. 이게 바로 artifacts의 핵심 기능입니다.
여러분이 이 구조를 사용하면 Pipeline이 월등히 빨라집니다. 실무에서의 이점: npm install이 5분에서 30초로 단축되고, 빌드 결과물을 확실하게 다음 단계로 전달하며, GitLab UI에서 테스트 결과와 빌드 산출물을 쉽게 확인할 수 있습니다.
또한 artifacts를 통해 특정 커밋의 빌드 결과를 언제든지 다운로드할 수 있어 롤백이나 디버깅에 유용합니다.
실전 팁
💡 Cache는 보장되지 않습니다. 캐시가 없어도 빌드가 성공하도록 스크립트를 작성하세요. 캐시는 단지 최적화일 뿐입니다. 💡 artifacts는 꼭 필요한 것만 저장하세요. node_modules 전체를 artifacts로 저장하면 업로드/다운로드 시간이 오래 걸리고 저장 공간을 낭비합니다. node_modules는 cache로, 빌드 결과만 artifacts로 저장하세요. 💡 GitLab의 무료 티어는 artifacts 저장 용량 제한이 있습니다. expire_in을 적절히 설정하여 오래된 artifacts를 자동 삭제하세요. 1 week가 보통 적당합니다. 💡 cache key를 신중하게 선택하세요. ${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHA}처럼 커밋 해시를 포함하면 매번 새 캐시가 생성되어 의미가 없습니다. 브랜치 이름만으로도 충분합니다. 💡 캐시 성능을 모니터링하세요. Job 로그에서 "Creating cache" / "Extracting cache" 시간을 확인하고, 캐시 크기가 너무 크면(1GB 이상) 오히려 느려질 수 있으니 조정하세요.
7. Docker Integration - 컨테이너 기반 빌드 환경
시작하며
여러분이 Node.js 프로젝트를 빌드하는데 Runner에 Node.js가 설치되어 있지 않다면? 또는 팀원마다 다른 Python 버전을 사용해서 빌드 결과가 달라진다면?
이런 환경 불일치 문제는 개발 현장에서 끊임없이 발생합니다. 이런 문제는 "내 컴퓨터에서는 되는데" 증후군의 주요 원인입니다.
Runner 서버에 필요한 모든 도구를 설치하고 관리하는 것도 부담이며, 프로젝트마다 요구사항이 다르면 충돌이 발생하기도 합니다. 결과적으로 빌드 환경을 설정하고 유지보수하는 데 많은 시간이 소비됩니다.
바로 이럴 때 필요한 것이 GitLab CI의 Docker Integration입니다. Docker 이미지를 사용하면 프로젝트마다 격리된 환경에서 빌드할 수 있고, 필요한 도구가 모두 포함된 이미지를 선택하기만 하면 됩니다.
환경 설정이 코드화되어 재현 가능하고 일관성이 보장됩니다.
개요
간단히 말해서, GitLab CI의 Docker Integration은 각 Job을 Docker 컨테이너 안에서 실행하여 격리되고 일관된 빌드 환경을 제공하는 기능입니다. image 키워드로 Docker 이미지를 지정하면, Runner가 해당 이미지로 컨테이너를 생성하고 그 안에서 script를 실행합니다.
예를 들어, image: node:18을 사용하면 Node.js 18이 설치된 환경에서 빌드되고, image: python:3.11을 사용하면 Python 3.11 환경에서 실행됩니다. Docker Hub의 수백만 개 이미지를 활용하거나, 직접 만든 커스텀 이미지를 사용할 수도 있습니다.
기존에는 Runner 서버에 직접 도구를 설치하고 버전을 맞춰야 했다면, 이제는 이미지 태그만 변경하면 됩니다. Node.js를 16에서 18로 업그레이드하려면 image: node:16을 image: node:18로 바꾸기만 하면 되고, 서버에는 아무것도 설치할 필요가 없습니다.
Docker Integration의 핵심 특징은 다음과 같습니다: 첫째, 매 실행마다 깨끗한 컨테이너를 생성하여 이전 빌드의 영향을 받지 않습니다. 둘째, services 키워드로 데이터베이스나 Redis 같은 부가 서비스를 쉽게 추가할 수 있습니다.
셋째, Docker-in-Docker를 지원하여 CI 안에서 Docker 이미지를 빌드하고 푸시할 수 있습니다. 이러한 기능들이 현대적인 CI/CD의 기반이 됩니다.
코드 예제
# Docker 이미지를 활용한 다양한 환경 구성
stages:
- test
- build
- docker_build
# 전역 기본 이미지 설정 (모든 Job에 적용)
default:
image: node:18-alpine # 경량 Alpine 기반 Node.js
before_script:
- npm --version # 이미지 검증
# 특정 Job만 다른 이미지 사용
python_test:
stage: test
image: python:3.11-slim # Python 이미지로 오버라이드
script:
- python --version
- pip install -r requirements.txt
- pytest
# 데이터베이스 서비스와 함께 실행
integration_test:
stage: test
image: node:18
services:
- postgres:15 # PostgreSQL 컨테이너 자동 시작
- redis:7-alpine # Redis 컨테이너도 추가
variables:
POSTGRES_DB: testdb # 서비스 환경 변수
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
script:
# postgres와 redis가 자동으로 hostname으로 접근 가능
- npm run test:integration
# Docker 이미지 빌드 (Docker-in-Docker 사용)
build_docker_image:
stage: docker_build
image: docker:24 # Docker CLI 포함 이미지
services:
- docker:24-dind # Docker-in-Docker 서비스
variables:
DOCKER_TLS_CERTDIR: "/certs" # DinD 보안 설정
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
설명
이것이 하는 일: 위 Pipeline은 여러 Docker 이미지를 활용하여 Node.js, Python, Docker 빌드 등 다양한 환경을 구성합니다. 각 Job은 독립된 컨테이너에서 실행되어 서로 간섭하지 않습니다.
첫 번째로, default 섹션에서 전역 설정을 정의합니다. image: node:18-alpine은 모든 Job에서 기본적으로 사용될 이미지입니다.
-alpine 태그는 Alpine Linux 기반의 매우 작은 이미지(100MB 미만)를 의미하며, 다운로드 시간을 크게 단축시킵니다. before_script는 모든 Job의 script 실행 전에 자동으로 실행되는 명령어로, npm --version으로 이미지가 제대로 로드되었는지 확인합니다.
이렇게 전역 설정을 해두면 각 Job에서 반복할 필요가 없습니다. 그 다음으로, python_test Job은 image: python:3.11-slim으로 전역 설정을 오버라이드합니다.
이 Job만 Python 환경에서 실행되며, 다른 Node.js Job들과 완전히 격리됩니다. -slim 태그는 Debian 기반의 경량 이미지로, 필요한 최소한의 도구만 포함하여 크기를 줄였습니다.
이렇게 Job별로 다른 이미지를 사용하면 monorepo처럼 여러 언어가 섞인 프로젝트도 하나의 Pipeline으로 처리할 수 있습니다. 세 번째로, integration_test Job에서 services를 사용합니다.
services: - postgres:15를 추가하면 GitLab Runner가 자동으로 PostgreSQL 15 컨테이너를 시작하고, postgres라는 hostname으로 접근할 수 있게 네트워크를 구성합니다. variables로 POSTGRES_DB 같은 환경 변수를 전달하여 데이터베이스를 초기화합니다.
script에서 npm run test:integration을 실행하면 테스트 코드가 postgres://testuser:testpass@postgres/testdb 같은 URL로 데이터베이스에 연결할 수 있습니다. Redis도 동일하게 redis hostname으로 접근됩니다.
Job이 끝나면 모든 서비스 컨테이너가 자동으로 삭제됩니다. 마지막으로, build_docker_image Job은 Docker-in-Docker(DinD)를 사용하여 CI 안에서 Docker 이미지를 빌드합니다.
image: docker:24는 Docker CLI가 포함된 이미지이고, services: - docker:24-dind는 실제 Docker 데몬을 실행하는 서비스입니다. DOCKER_TLS_CERTDIR 변수는 Docker 데몬과 CLI 간 TLS 인증을 활성화합니다.
docker login으로 GitLab Container Registry에 로그인하고, docker build로 이미지를 빌드한 후 docker push로 레지스트리에 푸시합니다. $CI_REGISTRY_IMAGE와 $CI_COMMIT_SHA는 GitLab이 제공하는 내장 변수로, 각각 프로젝트의 레지스트리 URL과 현재 커밋 해시입니다.
여러분이 이 구조를 사용하면 환경 설정의 고민에서 벗어날 수 있습니다. 실무에서의 이점: Runner 서버에 아무것도 설치할 필요 없고, 이미지 버전만 바꿔서 쉽게 업그레이드하며, 로컬 개발 환경과 CI 환경을 동일하게 유지할 수 있습니다.
또한 services로 복잡한 통합 테스트 환경을 코드 몇 줄로 구성할 수 있습니다.
실전 팁
💡 가능하면 alpine이나 slim 태그를 사용하세요. node:18은 900MB지만 node:18-alpine은 170MB에 불과합니다. 다운로드 시간이 크게 줄어듭니다. 💡 이미지 버전을 명시하세요. latest 태그는 언제든 변할 수 있어 빌드가 갑자기 깨질 수 있습니다. node:18처럼 메이저 버전을 명시하거나, node:18.17.0처럼 정확한 버전을 사용하세요. 💡 자주 사용하는 도구를 포함한 커스텀 이미지를 만드세요. 매 빌드마다 apt-get install을 반복하는 대신, 필요한 도구가 모두 설치된 이미지를 만들어 Docker Hub나 GitLab Registry에 올려두면 시간을 절약할 수 있습니다. 💡 services는 테스트에만 사용하세요. 프로덕션 데이터베이스는 별도로 관리해야 하며, services는 임시 테스트용입니다. Job이 끝나면 데이터가 모두 사라집니다. 💡 Docker-in-Docker는 권한이 필요합니다. Runner가 privileged 모드로 실행되어야 DinD가 작동하므로, 보안이 중요한 환경에서는 신중히 사용하세요. 대안으로 Kaniko를 사용하면 privileged 없이 이미지를 빌드할 수 있습니다.
8. Rules와 조건부 실행 - 똑똑한 Pipeline 만들기
시작하며
여러분의 Pipeline이 feature 브랜치에 푸시할 때마다 프로덕션 배포까지 실행된다면? 또는 README.md만 수정했는데 전체 테스트와 빌드가 돌아간다면?
이런 불필요한 실행은 시간과 비용을 낭비합니다. 모든 커밋에 동일한 Pipeline을 실행하는 것은 비효율적입니다.
문서만 수정했을 때는 빌드가 필요 없고, develop 브랜치에서는 배포를 스킵해야 하며, 태그가 푸시되었을 때만 릴리스를 만들어야 합니다. 이런 로직을 구현하지 않으면 Runner 자원이 낭비되고 Pipeline 큐가 길어져 전체 팀의 생산성이 떨어집니다.
바로 이럴 때 필요한 것이 Rules입니다. Rules를 사용하면 브랜치, 파일 변경, 환경 변수 등 다양한 조건에 따라 Job의 실행 여부를 동적으로 결정할 수 있습니다.
only/except보다 훨씬 강력하고 유연한 제어가 가능합니다.
개요
간단히 말해서, Rules는 Job을 실행할지 말지를 조건에 따라 결정하는 고급 제어 메커니즘입니다. Rules를 사용하면 "main 브랜치에 푸시되고, .js 파일이 변경되었을 때만 이 Job을 실행해"처럼 복잡한 조건을 표현할 수 있습니다.
예를 들어, 문서 파일(*.md)만 변경된 경우 빌드를 스킵하거나, 특정 라벨이 있는 Merge Request에서만 성능 테스트를 실행할 수 있습니다. GitLab의 내장 변수와 결합하면 거의 모든 시나리오를 처리할 수 있습니다.
기존의 only/except는 단순한 브랜치 필터링만 가능했다면, rules는 if, changes, exists 등 다양한 조건 타입을 제공합니다. 또한 when과 variables를 함께 사용하여 조건에 따라 다른 변수를 주입할 수도 있습니다.
Rules의 핵심 특징은 다음과 같습니다: 첫째, 여러 규칙을 순서대로 평가하여 첫 번째로 매치되는 규칙을 적용합니다. 둘째, changes로 특정 파일이 변경되었을 때만 실행하여 불필요한 빌드를 방지합니다.
셋째, if로 복잡한 조건 표현식을 작성할 수 있습니다. 이러한 유연성이 대규모 프로젝트의 효율적인 Pipeline 관리를 가능하게 합니다.
코드 예제
# Rules를 활용한 스마트한 Job 제어
stages:
- test
- build
- deploy
# 코드 변경 시에만 테스트 실행
test_code:
stage: test
script:
- npm test
rules:
# 규칙 1: *.md 파일만 변경되면 스킵
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- "**/*.md"
when: never # 절대 실행 안 함
# 규칙 2: src/ 디렉토리가 변경되면 실행
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
changes:
- "src/**/*"
- "package.json"
when: on_success # 이전 Stage 성공 시 실행
# 규칙 3: main 브랜치는 항상 실행
- if: '$CI_COMMIT_BRANCH == "main"'
when: always
# 조건부 배포: main 브랜치 + 수동 실행
deploy_production:
stage: deploy
script:
- echo "Deploying to production"
rules:
# main 브랜치이고 커밋 메시지에 [skip deploy]가 없을 때
- if: '$CI_COMMIT_BRANCH == "main" && $CI_COMMIT_MESSAGE !~ /\[skip deploy\]/'
when: manual # 수동 실행
- when: never # 그 외는 실행 안 함
# 태그 푸시 시에만 릴리스 생성
create_release:
stage: deploy
script:
- echo "Creating release for $CI_COMMIT_TAG"
rules:
# 버전 태그 (v1.0.0 형식)가 푸시되었을 때만
- if: '$CI_COMMIT_TAG =~ /^v\d+\.\d+\.\d+$/'
when: on_success
release:
tag_name: $CI_COMMIT_TAG
description: "Release $CI_COMMIT_TAG"
# Merge Request에 특정 라벨이 있을 때만 실행
performance_test:
stage: test
script:
- npm run test:performance
rules:
- if: '$CI_MERGE_REQUEST_LABELS =~ /performance/'
when: on_success
설명
이것이 하는 일: 위 Pipeline은 rules를 활용하여 불필요한 Job 실행을 최소화하고, 상황에 맞는 작업만 수행합니다. 이를 통해 비용을 절감하고 Pipeline을 빠르게 완료합니다.
첫 번째로, test_code Job의 규칙들을 살펴보겠습니다. rules는 위에서 아래로 순서대로 평가되며, 첫 번째로 매치되는 규칙이 적용됩니다.
규칙 1은 Merge Request 이벤트이고 마크다운 파일만 변경되었다면 when: never로 Job을 스킵합니다. changes: - "**/*.md"는 모든 디렉토리의 .md 파일을 의미합니다.
이렇게 하면 README.md를 수정했을 때 불필요하게 테스트가 돌아가지 않습니다. 규칙 2는 src/ 디렉토리나 package.json이 변경되었을 때만 실행하여, 실제 코드 변경이 있을 때만 테스트하도록 합니다.
그 다음으로, 규칙 3은 main 브랜치에서는 어떤 파일이 변경되든 항상 테스트를 실행합니다. when: always는 이전 Stage가 실패해도 실행한다는 의미입니다.
이렇게 하는 이유는 main 브랜치는 프로덕션 코드이므로 반드시 검증해야 하기 때문입니다. if 조건에서 사용된 $CI_PIPELINE_SOURCE와 $CI_COMMIT_BRANCH는 GitLab이 제공하는 내장 변수로, 각각 Pipeline이 트리거된 방법과 현재 브랜치 이름을 나타냅니다.
세 번째로, deploy_production Job은 두 가지 조건을 AND로 결합합니다. $CI_COMMIT_BRANCH == "main"으로 main 브랜치를 확인하고, $CI_COMMIT_MESSAGE !~ /[skip deploy]/로 커밋 메시지에 [skip deploy]가 없는지 검사합니다.
!~는 정규식 불일치 연산자입니다. 이렇게 하면 개발자가 긴급 핫픽스를 커밋할 때 "[skip deploy] Fix typo"처럼 작성하여 배포를 방지할 수 있습니다.
when: manual로 조건이 맞아도 자동 실행되지 않고 수동 확인을 요구합니다. 네 번째로, create_release Job은 태그 기반으로 실행됩니다.
$CI_COMMIT_TAG =~ /^v\d+.\d+.\d+$/는 "v"로 시작하고 숫자.숫자.숫자 형식(예: v1.2.3)의 태그만 매치합니다. 이렇게 하면 git tag v1.0.0 && git push origin v1.0.0처럼 버전 태그를 푸시할 때만 자동으로 릴리스가 생성됩니다.
release 블록은 GitLab Releases 기능을 활용하여 GitHub Releases와 유사한 릴리스 노트를 생성합니다. 마지막으로, performance_test Job은 Merge Request의 라벨을 검사합니다.
$CI_MERGE_REQUEST_LABELS =~ /performance/는 MR에 "performance"라는 단어가 포함된 라벨이 있는지 확인합니다. 성능 테스트는 시간이 오래 걸리므로, 필요한 경우에만 개발자가 라벨을 붙여서 실행하도록 합니다.
이런 선택적 테스트 패턴은 대규모 프로젝트에서 매우 유용합니다. 여러분이 rules를 적극 활용하면 Pipeline 효율성이 극대화됩니다.
실무에서의 이점: 문서 변경 시 빌드를 스킵하여 시간을 절약하고, 조건부 배포로 실수를 방지하며, Runner 사용량을 줄여 비용을 절감할 수 있습니다. 또한 복잡한 워크플로우를 코드로 명확하게 표현하여 팀원 모두가 이해할 수 있습니다.
실전 팁
💡 rules의 순서가 중요합니다. 첫 번째로 매치되는 규칙이 적용되므로, 구체적인 규칙을 위에, 일반적인 규칙을 아래에 배치하세요. 💡 when: never를 마지막 규칙으로 추가하여 기본 동작을 명확히 하세요. 규칙이 하나도 매치되지 않으면 Job은 실행되지 않습니다. 💡 changes는 Merge Request와 푸시에서만 작동합니다. 스케줄 Pipeline이나 수동 Pipeline에서는 changes가 무시되므로 주의하세요. 💡 복잡한 조건은 YAML anchor로 재사용하세요. 여러 Job에서 동일한 rules를 사용한다면 .rules_template: &common_rules로 정의하고 재사용하면 유지보수가 쉽습니다. 💡 GitLab CI Lint로 rules를 테스트하세요. Project > CI/CD > Pipelines > CI Lint에서 시뮬레이션할 수 있어, 규칙이 의도대로 작동하는지 미리 확인할 수 있습니다.
9. Environments와 Deployments - 배포 이력 관리하기
시작하며
여러분이 여러 번 배포를 했는데 "지금 스테이징에 배포된 버전이 뭐였지?"라는 질문에 답하지 못한다면? 또는 프로덕션에 문제가 생겼을 때 이전 버전으로 빠르게 롤백하고 싶다면?
많은 팀이 배포 이력을 별도로 관리하지 않아 어떤 커밋이 어느 환경에 배포되었는지 추적하기 어렵습니다. Slack이나 엑셀로 수동 기록하는 것은 오류가 발생하기 쉽고, 배포 실패나 성공 여부를 확인하기 어렵습니다.
문제가 생겼을 때 빠르게 롤백할 방법도 없어 다운타임이 길어집니다. 바로 이럴 때 필요한 것이 GitLab의 Environments 기능입니다.
environment를 정의하면 GitLab이 자동으로 배포 이력을 추적하고, 각 환경의 현재 상태를 UI에 표시하며, 원클릭 롤백 기능을 제공합니다. 배포가 투명하고 추적 가능해집니다.
개요
간단히 말해서, Environments는 GitLab이 배포 대상(개발, 스테이징, 프로덕션 등)을 추적하고 관리할 수 있게 해주는 기능입니다. Job에 environment 블록을 추가하면, GitLab은 해당 Job을 "배포"로 인식하고 Deployments 메뉴에 기록합니다.
예를 들어, environment: name: production으로 설정하면 GitLab UI에서 "Production" 환경이 생성되고, 배포될 때마다 커밋 정보, 배포 시간, 배포한 사람이 기록됩니다. url을 추가하면 "View deployment" 버튼으로 바로 사이트에 접속할 수도 있습니다.
기존에는 배포 로그를 별도로 관리했다면, 이제는 GitLab이 자동으로 모든 것을 기록합니다. 또한 수동 롤백이 필요했다면, 이제는 이전 배포를 클릭하고 "Rollback" 버튼만 누르면 됩니다.
Environments의 핵심 특징은 다음과 같습니다: 첫째, 각 환경의 현재 배포 상태를 한눈에 볼 수 있습니다. 둘째, 배포 이력이 자동으로 추적되어 감사(audit)에 유용합니다.
셋째, on_stop으로 환경 정리(cleanup) Job을 연결하여 임시 환경을 자동 삭제할 수 있습니다. 이러한 기능들이 프로페셔널한 배포 관리를 가능하게 합니다.
코드 예제
# Environments를 활용한 다단계 배포 관리
stages:
- deploy
- cleanup
# 개발 환경: 자동 배포
deploy_dev:
stage: deploy
script:
- echo "Deploying to development"
- rsync -avz dist/ dev-server:/var/www/
environment:
name: development # 환경 이름
url: https://dev.example.com # 환경 URL
on_stop: stop_dev # 정리 Job 연결
only:
- develop
# 스테이징 환경: PR 병합 시 자동 배포
deploy_staging:
stage: deploy
script:
- echo "Deploying to staging"
- kubectl apply -f k8s/staging/
environment:
name: staging
url: https://staging.example.com
deployment_tier: staging # 환경 티어 지정
only:
- main
# 프로덕션 환경: 수동 배포 + 자동 롤백 지원
deploy_production:
stage: deploy
script:
- echo "Deploying version $CI_COMMIT_SHORT_SHA"
- kubectl set image deployment/myapp myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
environment:
name: production
url: https://example.com
deployment_tier: production
action: start # 배포 시작
only:
- main
when: manual # 수동 실행
retry:
max: 2 # 실패 시 최대 2회 재시도
when: script_failure
# 동적 환경: Feature 브랜치마다 임시 환경 생성
deploy_review:
stage: deploy
script:
- echo "Deploying review app for $CI_COMMIT_REF_SLUG"
- helm upgrade --install review-$CI_COMMIT_REF_SLUG ./chart
--set image.tag=$CI_COMMIT_SHA
environment:
name: review/$CI_COMMIT_REF_SLUG # 동적 이름
url: https://$CI_COMMIT_REF_SLUG.review.example.com
on_stop: stop_review # 정리 Job
auto_stop_in: 1 week # 1주일 후 자동 정리
only:
- branches
except:
- main
- develop
# Review 환경 정리 Job
stop_review:
stage: cleanup
script:
- helm uninstall review-$CI_COMMIT_REF_SLUG
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop # 환경 정리
when: manual # 수동 또는 auto_stop_in으로 실행
only:
- branches
except:
- main
설명
이것이 하는 일: 위 Pipeline은 개발, 스테이징, 프로덕션, 그리고 동적 Review 환경을 관리합니다. GitLab UI에서 모든 환경의 상태를 확인하고, 필요시 롤백할 수 있습니다.
첫 번째로, deploy_dev Job은 가장 기본적인 환경 설정을 보여줍니다. environment: name: development로 "development"라는 환경을 생성하고, url로 배포된 사이트 주소를 지정합니다.
이 Job이 성공하면 GitLab의 Deployments > Environments 메뉴에 "development" 항목이 나타나고, "View deployment" 버튼으로 https://dev.example.com에 바로 접속할 수 있습니다. on_stop: stop_dev는 이 환경을 정리할 때 실행할 Job을 지정하는데, 동적 환경에서 특히 유용합니다.
그 다음으로, deploy_staging Job은 deployment_tier: staging을 추가했습니다. Deployment tier는 환경의 중요도를 나타내며(development, testing, staging, production), GitLab UI에서 환경을 구분하는 데 사용됩니다.
kubectl apply를 사용하여 Kubernetes에 배포하는 예제로, 실제 프로젝트에서는 Helm, Terraform, AWS CLI 등 다양한 도구를 사용할 수 있습니다. only: - main으로 main 브랜치에서만 스테이징 배포가 실행되도록 제한했습니다.
세 번째로, deploy_production Job은 프로덕션 배포를 위한 고급 설정을 포함합니다. when: manual로 수동 실행을 요구하여 실수로 배포되는 것을 방지합니다.
retry: max: 2로 네트워크 오류 같은 일시적 실패 시 자동으로 2회까지 재시도합니다. when: script_failure는 script 실행 실패 시에만 재시도하고, 타임아웃이나 Runner 오류는 재시도하지 않는다는 의미입니다.
$CI_COMMIT_SHORT_SHA는 커밋 해시의 짧은 버전(앞 8자)으로, 배포 버전을 명확히 표시하는 데 유용합니다. 네 번째로, deploy_review Job은 동적 환경(Dynamic Environments)을 구현합니다.
name: review/$CI_COMMIT_REF_SLUG처럼 환경 이름에 변수를 사용하면, 브랜치마다 독립적인 환경이 생성됩니다. 예를 들어 feature/new-ui 브랜치라면 "review/feature-new-ui" 환경이 자동으로 만들어집니다.
url도 동적으로 생성되어 https://feature-new-ui.review.example.com 같은 주소로 접속할 수 있습니다. auto_stop_in: 1 week는 1주일 후 자동으로 stop_review Job을 실행하여 환경을 정리합니다.
이는 임시 Review App이 계속 남아 리소스를 낭비하는 것을 방지합니다. 마지막으로, stop_review Job은 environment: action: stop으로 정의되어 환경 정리를 담당합니다.
Helm으로 배포했다면 helm uninstall, Kubernetes라면 kubectl delete, AWS라면 terraform destroy 같은 명령을 실행합니다. 이 Job은 MR이 머지되거나 auto_stop_in 기간이 지나면 자동으로 실행되며, GitLab UI에서 수동으로도 트리거할 수 있습니다.
여러분이 Environments를 활용하면 배포 관리가 완전히 달라집니다. 실무에서의 이점: 모든 배포 이력이 자동으로 기록되어 규정 준수(compliance)를 충족하고, 각 환경의 현재 상태를 한눈에 파악하며, 문제 발생 시 이전 버전으로 원클릭 롤백할 수 있습니다.
또한 동적 환경으로 각 feature 브랜치마다 실제 동작하는 프리뷰 환경을 제공하여 코드 리뷰 품질을 크게 향상시킬 수 있습니다.
실전 팁
💡 url은 반드시 설정하세요. GitLab UI에서 "View deployment" 버튼이 생겨 배포된 결과를 즉시 확인할 수 있어 매우 편리합니다. 💡 deployment_tier를 명시하여 환경을 구조화하세요. GitLab이 중요도에 따라 UI를 구성하고, 특정 티어에만 적용되는 정책을 설정할 수 있습니다. 💡 Review Apps는 팀 협업에 혁신적입니다. 개발자가 "이 기능 확인해주세요"라고 할 때 코드 대신 실제 동작하는 URL을 공유할 수 있어 피드백 속도가 10배 빨라집니다. 💡 auto_stop_in을 설정하여 비용을 절약하세요. 임시 환경이 계속 남아 있으면 클라우드 비용이 급증합니다. 1 day, 1 week 같은 값으로 자동 정리를 설정하세요. 💡 Rollback 기능을 테스트하세요. Deployments > Environments에서 이전 배포를 클릭하고 "Rollback" 버튼을 누르면 해당 커밋으로 재배포됩니다. 비상 시 매우 유용하므로 미리 연습해두세요.
10. Merge Request Pipelines - 코드 리뷰와 CI 통합
시작하며
여러분이 Merge Request를 생성했는데 CI가 돌아가지 않아 테스트 없이 머지된 적 있나요? 또는 feature 브랜치에서는 Pipeline이 실행되지만, 실제로 머지되었을 때 동작이 다르다면?
일반 브랜치 Pipeline과 Merge Request의 컨텍스트는 다릅니다. MR Pipeline은 소스 브랜치와 타겟 브랜치가 머지된 결과를 기준으로 실행되어야 "머지 후에도 정말 잘 동작하는지" 확인할 수 있습니다.
이를 제대로 설정하지 않으면 통합 버그를 놓칠 수 있고, 코드 리뷰 중에 CI 결과를 확인하기 어렵습니다. 바로 이럴 때 필요한 것이 Merge Request Pipelines입니다.
workflow와 rules를 조합하여 MR 이벤트에 최적화된 Pipeline을 구성하면, 코드 리뷰와 CI가 완벽하게 통합되어 품질을 보장할 수 있습니다.
개요
간단히 말해서, Merge Request Pipelines는 MR이 생성되거나 업데이트될 때 자동으로 실행되는 특별한 Pipeline으로, 머지 결과를 기준으로 테스트합니다. 일반 Pipeline은 커밋된 코드 그대로를 테스트하지만, MR Pipeline은 소스 브랜치와 타겟 브랜치(보통 main)를 자동으로 머지한 상태에서 테스트합니다.
예를 들어, feature 브랜치를 main으로 머지하는 MR을 만들면, GitLab이 두 브랜치를 임시로 머지하고 그 결과에 대해 Pipeline을 실행합니다. 이렇게 하면 "main 브랜치의 최신 변경사항과 충돌하지 않는지" 미리 확인할 수 있습니다.
기존에는 브랜치 Pipeline만 실행되어 머지 후 문제를 발견하지 못했다면, 이제는 MR Pipeline으로 머지 전에 미리 검증할 수 있습니다. 또한 GitLab UI가 MR과 CI 결과를 연결하여 리뷰어가 테스트 통과 여부를 바로 확인할 수 있습니다.
MR Pipelines의 핵심 특징은 다음과 같습니다: 첫째, workflow로 MR 이벤트에만 반응하도록 Pipeline을 제한할 수 있습니다. 둘째, Merge Train과 결합하여 여러 MR을 안전하게 순차 머지할 수 있습니다.
셋째, MR에 Draft, WIP 같은 상태가 있으면 특정 Job을 스킵할 수 있습니다. 이러한 기능들이 GitFlow나 Trunk-based Development 같은 현대적인 워크플로우를 지원합니다.
코드 예제
# Merge Request에 최적화된 Pipeline 구성
# workflow로 MR과 브랜치 Pipeline 분리
workflow:
rules:
# 규칙 1: Merge Request 이벤트는 항상 실행
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
# 규칙 2: main/develop 브랜치 푸시도 실행
- if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == "develop"'
# 규칙 3: 태그 푸시도 실행
- if: '$CI_COMMIT_TAG'
# 그 외에는 Pipeline 생성 안 함 (중복 방지)
stages:
- lint
- test
- build
- deploy
# 모든 MR에서 실행되는 린트 검사
code_quality:
stage: lint
script:
- npm run lint
- npm run format:check
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
# MR과 main 브랜치에서 실행되는 테스트
unit_tests:
stage: test
script:
- npm test -- --coverage
coverage: '/Coverage: \d+\.\d+%/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
- if: '$CI_COMMIT_BRANCH == "main"'
# Draft MR에서는 스킵되는 E2E 테스트 (시간 절약)
e2e_tests:
stage: test
script:
- npm run test:e2e
rules:
# Draft MR이 아닐 때만 실행
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TITLE !~ /^Draft:/ && $CI_MERGE_REQUEST_TITLE !~ /^WIP:/'
- if: '$CI_COMMIT_BRANCH == "main"'
# MR이 특정 조건을 만족할 때만 빌드
build_preview:
stage: build
script:
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 day
rules:
# MR이고, approval을 받았고, 타겟이 main일 때
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
when: manual
# MR 머지 후 자동 배포
deploy_staging:
stage: deploy
script:
- echo "Deploying to staging"
environment:
name: staging
url: https://staging.example.com
rules:
# main 브랜치에 머지되었을 때만 (MR 머지 완료 후)
- if: '$CI_COMMIT_BRANCH == "main"'
when: on_success
설명
이것이 하는 일: 위 설정은 Merge Request와 브랜치 푸시를 명확히 구분하고, MR 상태에 따라 다른 Job을 실행하여 효율적인 워크플로우를 만듭니다. 첫 번째로, workflow 섹션이 가장 중요합니다.
이는 Pipeline 레벨의 규칙으로, "어떤 경우에 Pipeline을 생성할지" 결정합니다. 규칙 1은 merge_request_event일 때 Pipeline을 생성하는데, 이는 MR이 생성되거나 새 커밋이 푸시될 때를 의미합니다.
규칙 2와 3은 main/develop 브랜치나 태그 푸시 시에도 Pipeline을 만듭니다. 중요한 점은 workflow가 없으면 같은 커밋에 대해 브랜치 Pipeline과 MR Pipeline이 둘 다 생성되어 중복 실행된다는 것입니다.
workflow로 이를 명확히 제어하여 Runner 자원을 절약합니다. 그 다음으로, code_quality Job은 MR에서만 실행됩니다.
rules: - if: '$CI_PIPEL