13년차의 서버실

서버, 인프라, 홈랩 기술 블로그

[태그:] GitLab CI/CD

  • [Cloud] Vercel에서 GitLab CI/CD로 마이그레이션: 실제 경험과 고려사항

    [Cloud] Vercel에서 GitLab CI/CD로 마이그레이션: 실제 경험과 고려사항

    [DevOps] Vercel GitLab CI/CD 마이그레이션 실제 경험과 고려사항

    프론트엔드 배포를 빠르게 시작할 때는 Vercel이 정말 편합니다. 저도 처음엔 Git push만 하면 미리보기 배포(Preview Deployment, 변경사항 확인용 임시 배포)가 바로 올라오는 흐름이 너무 좋아서 한동안 만족하면서 썼거든요. 그런데 서비스가 조금씩 커지고, 백엔드와 인프라 정책까지 같이 맞춰야 하다 보니 Vercel GitLab CI/CD 마이그레이션을 진지하게 검토하게 되더라고요. 특히 팀에서 이미 GitLab을 중심으로 이슈, 머지 리퀘스트(Merge Request, 코드 리뷰 요청), 배포 이력을 관리하고 있다면 CI/CD 전환 자체가 단순한 툴 변경이 아니라 DevOps 워크플로우를 정리하는 작업이 됩니다. 이번 글에서는 제가 직접 정리하면서 겪었던 판단 포인트, 삽질했던 부분, 그리고 안정적으로 옮기는 방법을 차근차근 풀어보겠습니다.

    Vercel GitLab CI/CD 마이그레이션 전체 아키텍처 다이어그램

    Vercel 중심 배포에서 GitLab CI/CD 중심 배포로 흐름이 바뀌는 전체 구조를 보여주는 이미지입니다.

    1. 왜 Vercel에서 GitLab CI/CD로 옮기게 됐는가

    쉽게 말해, Vercel은 프론트엔드 배포 경험을 극도로 단순화해주는 플랫폼이고, GitLab CI/CD는 배포 과정을 내가 더 많이 통제할 수 있게 해주는 자동화 파이프라인입니다. 둘 중 뭐가 절대적으로 낫다기보다는, 팀 상황에 따라 기준이 달라집니다.

    제가 마이그레이션을 고민한 가장 큰 이유는 세 가지였습니다.

    • 배포 흐름 통합: 프론트엔드만 따로 Vercel에 있으면, 백엔드 배포와 인프라 변경 이력이 분리되기 쉽습니다.
    • 권한과 정책 일원화: GitLab 프로젝트 권한, 브랜치 보호(Protected Branch), 승인 규칙을 한 곳에서 다루는 게 편하더라고요.
    • 비용 절감: 서비스 규모와 팀 사용 방식에 따라 별도 플랫폼 비용보다 기존 GitLab Runner(러너, 작업 실행기)를 활용하는 쪽이 더 맞을 때가 있습니다.

    여기서 중요한 포인트! CI/CD 전환은 단순히 배포 속도만 보는 게 아닙니다. 로그를 어디서 볼지, 실패 시 누가 복구할지, 시크릿(Secret, 비밀값)을 어디서 관리할지까지 같이 봐야 합니다.

    2. Vercel과 GitLab CI/CD의 차이, 쉽게 설명해보면

    저도 처음엔 이게 뭔가 싶었는데, 아주 단순하게 비유하면 이렇습니다.

    • Vercel: 잘 차려진 배포 전문 주방입니다. 재료만 넣으면 빠르게 요리가 나옵니다.
    • GitLab CI/CD: 주방을 직접 설계할 수 있습니다. 대신 가스불, 조리 순서, 청소 방식까지 내가 정해야 합니다.

    그래서 소규모 프로젝트나 정적 사이트(Static Site, 서버 렌더링 없이 빌드 결과물만 배포하는 형태)는 Vercel이 아주 매력적입니다. 반대로 프론트엔드 빌드, 테스트, 컨테이너 이미지(Container Image), 쿠버네티스(Kubernetes, 컨테이너 오케스트레이션) 배포까지 한 줄로 이어야 한다면 GitLab CI/CD 쪽이 더 자연스러울 수 있습니다.

    항목 Vercel GitLab CI/CD
    초기 설정 매우 간단 직접 설계 필요
    프론트엔드 미리보기 강점 직접 구현 필요
    배포 통제력 플랫폼 기준 높음
    조직 내 표준화 분리 운영 가능성 GitLab 중심 통합 유리
    확장성 프론트엔드 친화적 전체 파이프라인 확장 유리

    3. 마이그레이션 전에 먼저 체크한 항목

    실제로 써보니까, 코드를 옮기는 것보다 현재 Vercel이 대신 해주던 것을 목록화하는 게 훨씬 중요했습니다. 이걸 빼먹으면 나중에 꼭 터집니다 ㅎㅎ

    1. 빌드 명령: 예를 들어 npm run build 또는 pnpm build가 정확히 무엇을 수행하는지 확인합니다.
    2. 출력 디렉터리: 정적 결과물이 dist, build, out 중 어디에 생성되는지 봅니다.
    3. 환경 변수(Environment Variable, 실행 환경별 설정값): API URL, 토큰, 공개 키 등을 개발/스테이징/운영으로 나눠 정리합니다.
    4. 리라이트/리다이렉트: Vercel 설정에 있던 경로 재작성(Rewrite) 규칙이 있다면 웹 서버나 인그레스에서 다시 구현해야 합니다.
    5. 프리뷰 배포 전략: 머지 리퀘스트마다 미리보기 URL이 꼭 필요한지 결정합니다.
    6. 도메인과 TLS: 커스텀 도메인(Custom Domain)과 인증서(TLS Certificate) 종료 지점이 어디인지 확인합니다.

    이 단계에서 제가 한 실수는, 빌드만 되면 끝이라고 생각한 거였습니다. 근데 실제 운영은 빌드보다 배포 후 라우팅과 환경 변수 관리에서 더 많이 흔들리더라고요.

    4. GitLab CI/CD로 기본 파이프라인 구성하기

    이제 실전입니다. 여기서는 가장 보편적인 방식으로, 프론트엔드 앱을 빌드한 뒤 정적 파일을 서버나 스토리지로 배포하는 흐름을 예시로 들겠습니다. 프레임워크는 Next.js, React, Vue 등 무엇이든 응용 가능하지만, 설정은 프로젝트 성격에 맞게 조정하셔야 합니다.

    4-1. 기본 디렉터리와 환경 준비

    먼저 프로젝트 루트에 .gitlab-ci.yml 파일을 둡니다. GitLab Runner가 Node.js 환경에서 의존성을 설치하고, 테스트 후 빌드를 수행하게 만들 겁니다.

    stages:
      - install
      - test
      - build
      - deploy
    
    variables:
      NODE_ENV: production
      npm_config_cache: .npm
    
    cache:
      paths:
        - .npm/
        - node_modules/
    
    install:
      stage: install
      image: node:20
      script:
        - npm ci
    
    unit_test:
      stage: test
      image: node:20
      script:
        - npm ci
        - npm run test -- --runInBand
      rules:
        - if: '$CI_COMMIT_BRANCH'
    
    build_app:
      stage: build
      image: node:20
      script:
        - npm ci
        - npm run build
      artifacts:
        paths:
          - dist/
        expire_in: 1 day
    
    deploy_production:
      stage: deploy
      image: alpine:latest
      script:
        - echo "Deploy step runs here"
      rules:
        - if: '$CI_COMMIT_BRANCH == "main"'

    위 예시는 아주 기본 골격입니다. 핵심은 install – test – build – deploy 단계를 분리해서 실패 지점을 명확하게 보는 겁니다. 나중에 장애가 나도 어디서 깨졌는지 바로 보이거든요.

    Vercel GitLab CI/CD 마이그레이션 파이프라인 구성 이미지

    설치, 테스트, 빌드, 배포 단계가 GitLab Runner에서 어떻게 순차 실행되는지 보여주는 이미지입니다.

    4-2. 정적 파일 서버로 배포하는 예시

    만약 Nginx(엔진엑스, 웹 서버)로 정적 파일을 서빙한다면 이런 식으로 배포 스크립트를 둘 수 있습니다.

    #!/usr/bin/env bash
    set -euo pipefail
    
    TARGET_DIR="/var/www/my-frontend"
    
    rm -rf "${TARGET_DIR:?}"/*
    cp -r dist/* "$TARGET_DIR"/
    
    echo "deploy completed"

    그리고 GitLab CI에서는 SSH(Secure Shell, 원격 접속 프로토콜)로 원격 서버에 접속해 배포할 수 있습니다.

    deploy_production:
      stage: deploy
      image: alpine:latest
      before_script:
        - apk add --no-cache openssh-client rsync
        - eval $(ssh-agent -s)
        - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
        - mkdir -p ~/.ssh
        - chmod 700 ~/.ssh
      script:
        - rsync -avz --delete dist/ deploy@your-server:/var/www/my-frontend/
        - ssh deploy@your-server "sudo systemctl reload nginx"
      rules:
        - if: '$CI_COMMIT_BRANCH == "main"'

    여기서 중요한 포인트는 시크릿을 코드에 넣지 않는 겁니다. SSH 키, API 토큰, 배포 대상 주소는 GitLab CI/CD Variables에 넣어야 합니다.

    5. 프리뷰 배포와 브랜치 전략은 어떻게 바꿨는가

    Vercel을 쓰다가 GitLab으로 넘어오면 가장 아쉬운 부분 중 하나가 프리뷰 배포입니다. 저도 이 부분이 꽤 컸습니다. Vercel은 이 경험이 워낙 매끄럽거든요. 근데 GitLab에서도 포기할 필요는 없습니다.

    • 옵션 1: 스테이징 환경 하나를 두고, 머지 전 검증은 공용 URL에서 진행
    • 옵션 2: 브랜치별 또는 머지 리퀘스트별 임시 환경을 생성
    • 옵션 3: 정적 아티팩트만 확인하고 실제 프리뷰 URL은 운영하지 않음

    제가 해보니 팀 규모가 크지 않다면, 처음부터 브랜치별 임시 환경을 만들기보다 스테이징 하나를 안정적으로 운영하는 쪽이 훨씬 덜 피곤했습니다. 특히 프론트엔드 배포만 있는 게 아니라 API, 인증, CORS(Cross-Origin Resource Sharing, 교차 출처 요청 정책)까지 얽혀 있으면 프리뷰 환경 증식이 오히려 운영 복잡도를 키우더라고요.

    6. ⚠️ 실제로 겪었던 문제와 트러블슈팅

    이 섹션은 꼭 넣고 싶었습니다. 마이그레이션 자체보다 여기서 시간을 더 썼거든요.

    6-1. SPA 라우팅이 깨지는 문제

    React Router 같은 클라이언트 라우팅(Client-side Routing, 브라우저에서 경로 처리)을 쓰는 앱은 새로고침 시 404가 날 수 있습니다. Vercel에서는 비교적 자연스럽게 처리되던 부분이, Nginx에서는 별도 설정이 필요합니다.

    location / {
      try_files $uri $uri/ /index.html;
    }

    처음엔 정적 파일만 복사하면 끝인 줄 알았는데, 이 설정 빠져서 새로고침할 때마다 페이지가 죽더라고요. 여기서 좀 삽질했습니다 ㅎㅎ

    6-2. 환경 변수 이름이 달라서 빌드가 실패한 문제

    Vercel에 등록해 둔 환경 변수와 GitLab Variables 이름이 다르면 빌드는 되는데 런타임에서 깨지기도 하고, 아예 빌드 시점에 실패하기도 합니다. 그래서 저는 아래처럼 체크리스트를 따로 뒀습니다.

    • NODE_ENV
    • PUBLIC_* 또는 프레임워크별 공개 변수 prefix
    • API 엔드포인트 URL
    • 서드파티 인증 키

    6-3. 캐시 때문에 이전 빌드 결과가 섞이는 문제

    CI 캐시는 속도에는 도움이 되지만, 설정이 애매하면 오히려 독이 됩니다. 의존성 캐시와 빌드 산출물 캐시는 분리해서 보시는 걸 추천드립니다. 저는 한 번은 캐시를 과하게 잡아놔서, 수정했는데도 예전 결과물이 살아남는 바람에 한참 헷갈렸습니다.

    6-4. 배포는 성공인데 서비스는 실패한 문제

    이거 많이 놓칩니다. 파이프라인이 초록불이라고 서비스가 정상이라는 뜻은 아니거든요. 배포 후 헬스 체크(Health Check, 상태 확인)나 간단한 smoke test(스모크 테스트, 핵심 기능 점검)를 꼭 넣어야 합니다.

    Vercel GitLab CI/CD 마이그레이션 트러블슈팅 로그 이미지

    배포 로그에서 자주 만나는 오류와 원인 분석 포인트를 시각적으로 정리한 이미지입니다.

    7. 검증은 이렇게 했습니다

    마이그레이션 후에는 감으로 보면 안 됩니다. 저는 최소한 아래 순서로 확인했습니다.

    1. 빌드 재현성: 같은 커밋에서 동일한 결과가 나오는지 확인
    2. 정적 자산 로딩: JS, CSS, 이미지 경로가 깨지지 않는지 확인
    3. 라우팅: 직접 URL 접근과 새로고침 테스트
    4. 환경별 설정: 개발/스테이징/운영에서 API 호출 대상이 올바른지 확인
    5. 롤백 가능성: 이전 결과물로 빠르게 되돌릴 수 있는지 점검

    여기서 제가 특히 중요하게 본 건 롤백 전략이었습니다. Vercel은 비교적 되돌리기 경험이 좋았는데, GitLab 기반으로 직접 운영할 때는 내가 롤백 절차를 설계해야 하거든요. 배포 디렉터리를 버전별로 보관하거나, 아티팩트를 일정 기간 유지하는 방식이 실무적으로 꽤 유용했습니다.

    curl -I https://your-service.example.com/
    curl -s https://your-service.example.com/health
    

    아주 단순한 명령이지만, 배포 직후 이 두 개만 자동으로 돌려도 문제를 빨리 찾는 데 도움이 됩니다.

    Vercel GitLab CI/CD 마이그레이션 결과 검증 대시보드 이미지

    파이프라인 성공 상태와 서비스 헬스 체크 결과를 함께 보여주는 검증 이미지입니다.

    8. 그래서 누구에게 적합한가: 선택 기준 정리

    결론적으로 Vercel GitLab CI/CD 마이그레이션은 모든 팀에 정답은 아닙니다. 다만 아래에 가깝다면 꽤 의미가 있습니다.

    • 프론트엔드, 백엔드, 인프라 배포를 한 플랫폼에서 관리하고 싶은 팀
    • 머지 리퀘스트 승인과 배포 이력을 강하게 연결하고 싶은 팀
    • 러너 운영이 가능하고, YAML 기반 파이프라인 관리에 거부감이 없는 팀
    • 배포 자동화와 권한 모델을 조직 표준에 맞추고 싶은 팀

    반대로, 빠른 배포 경험과 프리뷰 환경이 최우선이고 운영 인력이 많지 않다면 Vercel 유지가 더 좋은 선택일 수도 있습니다. 사실 이건 기술 우열보다 운영 철학 차이에 가깝습니다.

    상황 추천 방향
    작은 팀, 프론트엔드 중심, 빠른 배포 우선 Vercel 유지 검토
    조직 표준 CI/CD 필요, 배포 통합 필요 GitLab CI/CD 전환 검토
    스테이징/운영 분리와 승인 규칙 중요 GitLab CI/CD 유리
    프리뷰 경험이 가장 중요 Vercel 강점 큼
    Vercel GitLab CI/CD 마이그레이션 선택 기준 비교 인포그래픽

    도입 난이도, 통제력, 프리뷰 배포, 운영 표준화 관점에서 두 방식을 비교한 요약 이미지입니다.

    9. 마무리: 배포 도구보다 중요한 건 운영 기준입니다

    이번에 정리하면서 다시 느낀 건, 도구를 바꾸는 것보다 배포 기준을 문서화하는 일이 더 중요하다는 점이었습니다. 제가 직접 해보니 GitLab CI/CD로 옮긴 뒤 얻은 가장 큰 장점은 화려한 기능보다도, 누가 봐도 같은 절차로 배포할 수 있는 상태가 됐다는 거였습니다. 이거 진짜 편하더라고요.

    물론 처음엔 귀찮습니다. YAML도 손봐야 하고, 시크릿도 다시 넣어야 하고, 웹 서버 설정도 만져야 하거든요. 근데 한 번 기준을 잡아두면 이후에는 프론트엔드 배포뿐 아니라 배치 작업, 백엔드, 운영 스크립트까지 같은 방식으로 확장하기가 좋습니다. 이게 결국 장기적으로는 비용 절감과 운영 안정성으로 이어집니다.

    혹시 지금 CI/CD 전환을 고민하고 계신다면, 먼저 현재 Vercel이 대신 해주고 있는 기능부터 목록으로 적어보세요. 그다음 GitLab CI/CD에서 반드시 재현해야 할 항목과, 과감히 포기해도 되는 항목을 나누는 게 좋습니다. 다음 글에서는 GitLab Runner를 Docker Executor(도커 실행기)로 운영할 때 주의할 점도 다뤄볼 예정입니다. 이전 글에서 다뤘던 Nginx 리버스 프록시 구성과 같이 보시면 흐름 잡는 데 더 도움이 되실 겁니다.

    자주 묻는 질문

    Q1. Vercel에서 GitLab CI/CD로 옮기면 무조건 비용 절감이 되나요?

    꼭 그렇지는 않습니다. Runner 운영 비용, 저장소, 로그 관리, 엔지니어링 시간까지 같이 봐야 합니다. 다만 기존 GitLab 중심 운영 체계가 이미 있다면 중복 도구 비용을 줄이는 효과는 기대할 수 있습니다.

    Q2. 프리뷰 배포가 꼭 필요하면 GitLab CI/CD는 불리한가요?

    기본 경험은 Vercel이 더 좋다고 느끼는 경우가 많습니다. 대신 GitLab에서도 머지 리퀘스트 기반 임시 환경을 설계할 수 있으니, 팀이 감당할 운영 복잡도와 맞는지 보는 게 중요합니다.

    Q3. 가장 먼저 검증해야 할 한 가지는 뭔가요?

    저는 라우팅과 환경 변수라고 봅니다. 빌드는 됐는데 실제 서비스가 안 뜨는 경우가 대부분 여기서 나왔습니다. 특히 SPA 라우팅과 공개 환경 변수 prefix는 꼭 확인해보세요.

  • [Cloud] GitLab CI/CD, 월 300달러 린트 비용 낭비 막는 법

    [Cloud] GitLab CI/CD, 월 300달러 린트 비용 낭비 막는 법

    안녕하세요, 13년차 인프라 엔지니어 ’13년차의 서버실’입니다. 오늘은 많은 분들이 공감하실 만한, 하지만 간과하기 쉬운 CI/CD 비용 문제에 대해 이야기해보려고 해요. 특히 GitLab CI/CD 비용 최적화와 관련해서 제가 직접 겪었던 뼈아픈 경험과 그 해결 과정을 공유합니다. 사실 저도 처음엔 CI/CD를 구축하고 나면 다 끝난 줄 알았거든요. 그런데 시간이 지나면서 예상치 못한 곳에서 비용이 새고 있더라고요. 바로 불필요하게 돌아가는 린트(Lint) 비용이었죠. 한 달에 무려 300달러나 되는 돈이 린트 때문에 나가고 있었다니, 처음엔 믿을 수가 없었어요. 오늘은 이 낭비를 어떻게 막았는지, 그 노하우를 풀어볼게요!

    GitLab CI/CD 린트 비용 낭비 및 최적화 전후 비교 다이어그램

    GitLab CI/CD 파이프라인에서 불필요한 린트(Lint) 작업으로 인해 비용이 낭비되고, 이를 최적화하여 절감하는 과정을 시각적으로 보여주는 다이어그램.

    CI/CD 린트(Lint)가 왜 비용 낭비의 주범이 될까요?

    CI/CD(Continuous Integration/Continuous Deployment, 지속적 통합/지속적 배포)는 정말 신기한 도구거든요. 코드를 푸시할 때마다 자동으로 테스트하고 배포하니까요. 이 과정에서 린트(Lint, 코드 스타일 및 잠재적 오류 검사)는 코드 품질을 유지하는 데 정말 중요한 역할을 합니다. 문제가 크기 전에 미리 잡아주니 정말 고맙죠.

    그런데 여기서 문제가 발생합니다. 대부분의 CI/CD 설정은 코드가 변경될 때마다, 즉 <code>git push 할 때마다 모든 파이프라인 작업을 실행하도록 되어 있더라고요. 작은 오타 수정이나 README 파일 업데이트 같은 사소한 변경에도 린트 작업을 포함한 모든 CI 작업이 돌아가는 거죠. 린트 작업 자체는 비교적 가볍다고 생각하기 쉽지만, 이게 수십, 수백 번 반복되면 이야기가 달라집니다. 특히 GitLab.com 같은 SaaS(Software as a Service) 환경에서는 사용량에 따라 CI/CD Runner minutes(러너 사용 시간) 비용이 발생하거든요. 제가 운영하는 홈랩에서도 처음엔 이런 비용을 크게 신경 쓰지 않았는데, 프로젝트가 많아지고 커밋이 잦아지면서 슬금슬금 비용이 올라가는 걸 보고 깜짝 놀랐습니다.

    비용 낭비의 핵심 원인:

    • 잦은 커밋: 개발 과정에서 수많은 중간 커밋이 발생합니다.
    • 불필요한 실행: 코드를 변경하지 않는 파일(예: 주석, 문서)의 변경에도 린트가 실행됩니다.
    • 모든 브랜치 실행: 개발 브랜치, 피처 브랜치 등 모든 브랜치에 푸시될 때마다 린트가 실행됩니다.

    이런 상황을 제가 직접 겪어보니, “아, 이건 뭔가 바꿔야겠다!” 싶더라고요. 그래서 CI/CD 최적화 방안을 진지하게 고민하기 시작했습니다.

    GitLab CI/CD 비용 최적화를 위한 핵심 전략: 조건부 실행 (Conditional Execution)

    GitLab CI/CD에서 비용을 절감하는 가장 효과적인 방법 중 하나는 조건부 실행(Conditional Execution)입니다. 말 그대로 특정 조건이 충족될 때만 CI/CD 작업을 실행하도록 하는 거죠. 린트 작업의 경우, 모든 커밋에 대해 실행하기보다는 특정 상황에서만 실행하도록 제한할 수 있습니다.

    제가 주로 활용한 전략은 다음과 같습니다:

    1. 머지 리퀘스트(Merge Request) 시에만 실행: 피처 브랜치에서 개발할 때는 린트 작업을 건너뛰고, 메인 브랜치로 머지하기 위한 MR이 생성될 때만 린트 검사를 실행합니다. 이렇게 하면 불필요한 중간 커밋에 대한 린트 실행을 대폭 줄일 수 있거든요.
    2. 코드 변경이 있는 파일에 대해서만 실행: 정말 필요한 경우에만 린트 작업을 실행하도록 조건을 더 세분화할 수 있습니다. 예를 들어, 특정 코드 파일이 변경되었을 때만 린트 작업을 실행하는 방식이죠.

    GitLab CI/CD는 rules 키워드를 통해 이런 조건부 실행을 강력하게 지원합니다. 처음엔 only/except 문법을 사용하기도 했는데, rules가 훨씬 유연하고 강력하더라고요. rules를 사용하면 여러 조건을 조합해서 원하는 시나리오를 만들 수 있습니다.

    실전 구현: `.gitlab-ci.yml`에 조건부 린트 잡(Job) 추가하기

    자, 그럼 이제 제가 실제로 어떻게 GitLab CI 비용 절감을 이뤄냈는지 `.gitlab-ci.yml` 설정과 함께 설명해 드릴게요. 핵심은 린트 작업을 위한 잡(Job)에 rules를 적용하는 겁니다.

    1. 머지 리퀘스트(MR) 시에만 린트 실행하기

    가장 기본적이고 효과적인 방법입니다. 개발 브랜치에 푸시할 때는 린트를 건너뛰고, MR이 생성되거나 업데이트될 때만 린트를 실행합니다. 이렇게 하면 개발 중 발생하는 수많은 중간 커밋의 CI 비용을 아낄 수 있거든요.

    
    lint_job:
      stage: lint
      image: python:3.9-slim # 린트 도구에 맞는 이미지 사용
      script:
        - pip install flake8 # 예시: Python flake8 린터 설치
        - flake8 .
      rules:
        - if: '$CI_PIPELINE_SOURCE == "merge_request_event"' # MR이 생성되거나 업데이트될 때만 실행
          when: on_success
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' # 기본 브랜치(master/main)에 푸시될 때 실행 (최종 검증)
          when: on_success
    

    위 코드에서 $CI_PIPELINE_SOURCE == "merge_request_event"는 머지 리퀘스트 파이프라인에서만 이 잡(Job)을 실행하라는 의미입니다. 그리고 $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH는 메인 브랜치(보통 main 또는 master)에 직접 푸시될 때도 린트가 실행되도록 해서 최종적인 코드 품질을 보장합니다. 이렇게 설정하니 월 300달러나 나가던 린트 비용이 확 줄어들더라고요! 🎉

    GitLab CI/CD 린트 조건부 실행을 위한 .gitlab-ci.yml rules 설정

    GitLab CI/CD 설정 파일(.gitlab-ci.yml)에서 ‘rules’ 키워드를 사용하여 린트(Lint) 작업을 조건부로 실행하도록 구성된 YAML 코드 스니펫.

    2. 특정 파일 변경 시에만 린트 실행하기 (고급)

    좀 더 세밀한 제어가 필요하다면 rules:changes를 활용할 수 있습니다. 예를 들어, Python 코드 파일(.py)이 변경되었을 때만 Python 린트를 실행하고, JavaScript 파일(.js)이 변경되었을 때만 JavaScript 린트를 실행하는 식이죠.

    
    python_lint_job:
      stage: lint
      image: python:3.9-slim
      script:
        - pip install flake8
        - flake8 .
      rules:
        - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
          changes:
            - "**/*.py" # .py 파일 변경 시에만 실행
          when: on_success
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
          changes:
            - "**/*.py"
          when: on_success
    
    javascript_lint_job:
      stage: lint
      image: node:16-slim # Node.js 린터에 맞는 이미지 사용
      script:
        - npm install eslint
        - npx eslint .
      rules:
        - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
          changes:
            - "**/*.js" # .js 파일 변경 시에만 실행
          when: on_success
        - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
          changes:
            - "**/*.js"
          when: on_success
    

    이 방식은 파이프라인을 더욱 효율적으로 만들지만, rules:changes는 GitLab Runner가 변경된 파일을 확인하는 과정에서 약간의 오버헤드가 발생할 수 있거든요. 하지만 특정 언어의 린트가 매우 무겁거나, 모노레포(Monorepo)처럼 여러 프로젝트가 한 레포지토리에 있을 때는 정말 유용하게 활용할 수 있습니다. 제가 홈랩에서 여러 마이크로서비스를 한 레포에 넣어두고 관리할 때 이 방법을 써봤는데, 확실히 비용 절감 효과가 좋았어요.

    ⚠️ 주의사항 및 트러블슈팅: 꼼꼼함이 핵심!

    GitLab CI/CD 최적화는 비용 절감이라는 큰 장점이 있지만, 몇 가지 주의할 점도 있습니다. 제가 삽질 좀 하면서 겪었던 시행착오들을 공유해 드릴게요.

    1. 조건 설정의 오작동: rules 문법이 생각보다 까다로울 수 있거든요. 조건을 너무 복잡하게 설정하면 예상치 못하게 잡(Job)이 실행되지 않거나, 반대로 불필요하게 실행될 수 있어요. 항상 테스트를 통해 의도한 대로 동작하는지 확인해야 합니다. rules:if와 rules:changes를 함께 사용할 때는 특히 주의해야 합니다.
    2. 개발자의 실수: 머지 리퀘스트 파이프라인에서만 린트를 실행하도록 했는데, 개발자가 실수로 로컬에서 린트를 돌리지 않고 MR을 올리는 경우가 생길 수 있거든요. 이렇게 되면 MR 파이프라인에서 린트 에러가 발생하고, 다시 수정해서 커밋해야 하는 번거로움이 생기죠. 이를 방지하기 위해 프리-커밋 훅(Pre-commit Hook) 같은 도구를 도입하여 로컬에서도 린트 검사를 강제하는 것을 고려해볼 수 있습니다. 저도 이 문제 때문에 초기에는 좀 곤란했는데, husky나 lint-staged 같은 도구를 도입해서 해결했어요.
    3. 초기 러너 비용: rules:changes를 사용할 때, GitLab Runner는 변경된 파일 목록을 가져오기 위해 Git 히스토리를 확인해야 합니다. 이 과정에서 필요한 최소한의 Git 클론(clone) 작업이 발생하므로, 아주 미미하지만 초기 러너 사용 시간이 발생할 수 있거든요. 대부분의 경우 무시할 만한 수준이지만, 극단적인 최적화를 목표로 한다면 고려할 만한 요소입니다.

    가장 중요한 건, 변경 사항을 적용한 후에 GitLab CI/CD 파이프라인을 여러 시나리오(새 브랜치 푸시, MR 생성, 메인 브랜치 푸시 등)로 테스트해보고, CI/CD Analytics(분석) 탭에서 러너 사용 시간을 주기적으로 확인하는 겁니다. 저도 처음엔 설정이 제대로 됐는지 확신이 없어서 파이프라인 로그를 꼼꼼히 뜯어봤거든요. 💡

    결과 검증: 실제로 비용이 줄었는지 확인하기

    그럼 이제 가장 중요한 부분이죠. 이렇게 설정하고 나면 정말 비용 절감이 되는지 어떻게 확인할까요? GitLab은 친절하게도 CI/CD 사용량에 대한 통계를 제공하더라고요. 저는 주로 Settings → CI/CD → Usage Quotas 섹션과 Analytics → CI/CD Analytics를 활용했습니다.

    제 경험으로는, 조건부 린트 잡을 적용하기 전과 후의 Runner minutes(러너 사용 시간) 그래프가 확연히 달라지는 것을 확인할 수 있었습니다. 특히 월말에 청구되는 금액을 비교해보니, 월 300달러 가까이 나가던 CI/CD 비용이 100달러 미만으로 줄어드는 것을 보고 정말 뿌듯했습니다. 🎉 이 정도면 꽤 괜찮은 성과 아닌가요?

    GitLab CI/CD 러너 사용 시간 및 비용 최적화 효과 차트

    GitLab CI/CD Usage Quotas 또는 CI/CD Analytics 대시보드에서 러너 사용 시간(Runner minutes)이 최적화 전후로 극적으로 감소한 것을 보여주는 차트 또는 그래프.

    ✅ 핵심 검증 포인트:

    • Runner minutes 감소: 월별, 주별 러너 사용 시간이 줄어들었는지 확인.
    • 파이프라인 실행 횟수 감소: 불필요한 린트 잡의 실행 횟수가 줄었는지 확인.
    • 청구서 확인: 실제 청구되는 금액이 줄었는지 최종적으로 확인.

    마무리: 작은 변화가 큰 절약을 만듭니다

    오늘은 GitLab CI/CD 비용 최적화, 특히 불필요한 린트 비용을 절감하는 방법에 대해 제 경험을 바탕으로 이야기해 봤습니다. 사실 린트 작업 하나에 월 300달러라는 비용이 나가는 건 좀 과하다 싶었거든요. 하지만 조건부 실행(Conditional Execution)이라는 작은 변화를 통해 예상보다 훨씬 큰 비용 절감 효과를 볼 수 있었습니다.

    이러한 비용 최적화는 린트 작업뿐만 아니라, 빌드(Build), 테스트(Test) 등 다른 CI/CD 잡에도 확장해서 적용할 수 있습니다. 예를 들어, 프론트엔드 코드만 변경되었을 때는 백엔드 테스트를 건너뛰는 식으로요. 항상 “이 잡이 지금 꼭 실행되어야 하는가?”라는 질문을 던져보면 최적화 포인트를 찾기 쉬울 겁니다.

    결국, CI/CD 최적화는 단순히 비용을 줄이는 것을 넘어, 파이프라인의 효율성을 높이고 개발자들이 더 빠르고 정확하게 피드백을 받을 수 있도록 돕는 중요한 과정입니다. 저도 이 과정을 통해 CI/CD에 대한 이해를 한 단계 더 높일 수 있었어요. 여러분도 이 글을 통해 비용 절감과 효율적인 CI/CD 운영에 도움이 되셨기를 바랍니다!

    GitLab CI/CD 비용 절감 전략 요약 인포그래픽

    GitLab CI/CD 비용 절감 전략을 요약하고, 지속적인 최적화의 중요성을 강조하는 인포그래픽 또는 비교표.

    다음 글에서는 GitHub Actions에서도 유사한 비용 최적화 전략을 어떻게 적용할 수 있는지 알아보는 시간을 가져볼게요. 궁금한 점이 있다면 언제든지 댓글로 남겨주세요!