CI/CD 스터디 03: Pipeline YAML, Stage, Job, Step 설계
Pipeline YAML에서 workflow, stage, job, step, needs, rules를 구분하고 GitHub Actions, GitLab CI/CD, Jenkins 예제로 설계합니다.
Pipeline YAML은 자동화 순서를 적는 파일이지만, 실제로는 변경 정책을 코드로 표현한 운영 문서입니다. stage, job, step, needs, rules를 구분해야 빠르고 안전한 파이프라인을 만들 수 있습니다.
구성 단위
| 단위 | 의미 | 설계 기준 |
|---|---|---|
| workflow / pipeline | 하나의 자동화 실행 전체 | 언제 실행할지, 어떤 이벤트를 받을지 결정합니다. |
| stage | 큰 단계 | build, test, scan, deploy처럼 사람이 이해하는 흐름입니다. |
| job | runner에 예약되는 실행 단위 | 병렬화, 실패 격리, artifact 전달의 기준입니다. |
| step / script | job 안의 명령 단위 | 같은 workspace와 환경을 공유합니다. |
| needs / dependencies | job 간 의존성 | 불필요한 stage 대기를 줄이고 필요한 산출물만 받게 합니다. |
나쁜 YAML의 전형
처음 파이프라인을 만들 때 가장 흔한 실수는 모든 명령을 하나의 job에 밀어 넣는 것입니다. 이러면 실패 지점이 흐려지고, 캐시와 artifact도 제대로 나누기 어렵습니다.
# 피해야 할 형태: 모든 일을 한 job에서 처리
all-in-one:
script:
- npm ci
- npm run lint
- npm test
- npm run build
- docker build -t app .
- ./deploy.shGitHub Actions 예시
GitHub Actions에서는 workflow event, jobs, steps, needs가 기본 뼈대입니다. 배포 job은 build/test가 모두 통과한 뒤 main 브랜치에서만 실행되도록 분리합니다.
name: app-ci
on:
pull_request:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
build:
runs-on: ubuntu-latest
needs: [ test ]
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
deploy-staging:
runs-on: ubuntu-latest
needs: [ build ]
if: github.ref == 'refs/heads/main'
environment: staging
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy-staging.shGitLab CI/CD 예시
GitLab은 stages와 job keyword를 조합합니다. stage 순서만 쓰면 전체 stage가 끝날 때까지 기다리므로, 빠른 feedback이 필요하면 needs를 명시합니다.
stages:
- test
- build
- deploy
lint:
stage: test
image: node:22-alpine
script:
- npm ci
- npm run lint
unit-test:
stage: test
image: node:22-alpine
script:
- npm ci
- npm test
build-app:
stage: build
image: node:22-alpine
needs: [ lint, unit-test ]
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 7 days
deploy-staging:
stage: deploy
needs: [ build-app ]
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
script:
- ./scripts/deploy-staging.shJenkins Declarative Pipeline 예시
Jenkinsfile은 Groovy 기반이라 YAML과 다르지만, agent, stages, stage, steps라는 구조를 분명히 나누면 같은 방식으로 읽을 수 있습니다.
pipeline {
agent any
stages {
stage('Test') {
steps {
sh 'npm ci'
sh 'npm run lint'
sh 'npm test'
}
}
stage('Build') {
steps {
sh 'npm run build'
archiveArtifacts artifacts: 'dist/**', fingerprint: true
}
}
stage('Deploy Staging') {
when { branch 'main' }
steps {
sh './scripts/deploy-staging.sh'
}
}
}
}설계 원칙
- 빠른 실패를 앞에 둡니다. format, lint, unit test가 image build보다 먼저 와야 합니다.
- 비싼 작업은 입력 변경이 있을 때만 실행합니다.
- 배포 job은 테스트 job과 runner 권한을 분리합니다.
- job 이름은 실패 메시지만 봐도 원인을 알 수 있게 씁니다.
- YAML이 길어지면 재사용 템플릿을 쓰되, 핵심 deploy 흐름은 숨기지 않습니다.