왜 로컬 캐시로는 부족한가

GitLab Runner의 기본 캐시는 러너 호스트의 로컬 디스크에 저장된다. 러너가 한 대뿐이라면 문제없지만, 오토스케일링이나 Kubernetes executor 환경에서는 잡마다 다른 노드에서 실행되므로 이전 잡이 만든 캐시를 다음 잡이 재사용하지 못한다. 결과적으로 node_modules나 .m2 같은 의존성을 매번 새로 내려받아 빌드 시간이 늘고 네트워크 비용도 커진다. 이를 해결하는 방법이 S3(또는 GCS, MinIO) 같은 오브젝트 스토리지를 공유 백엔드로 쓰는 분산 캐시다.

분산 캐시 기본 구성

러너 설정 파일 config.toml의 [runners.cache] 섹션에 S3 백엔드를 지정한다. 인증 키는 파일에 직접 넣기보다 IAM Role이나 환경변수로 주입하는 편이 안전하다.

[[runners]]
  name = "k8s-runner"
  executor = "kubernetes"
  [runners.cache]
    Type = "s3"
    Shared = true
    [runners.cache.s3]
      ServerAddress = "s3.ap-northeast-2.amazonaws.com"
      BucketName = "ci-cache-prod"
      BucketLocation = "ap-northeast-2"
      # AccessKey/SecretKey는 IAM Role 사용 시 생략

Shared = true는 여러 프로젝트가 캐시 키를 공유할 수 있게 한다. 격리가 필요하면 false로 두고 캐시 키에 프로젝트 식별자를 넣는다.

캐시 키와 정책 설계

.gitlab-ci.yml에서 캐시 키를 락파일 해시에 묶으면 의존성이 바뀔 때만 캐시가 갱신된다. policy로 잡이 캐시를 읽기만 할지, 쓰기만 할지 제어할 수 있다.

build:
  stage: build
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
    policy: pull-push   # 캐시를 받아 쓰고, 끝나면 갱신

test:
  stage: test
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
    policy: pull        # 읽기 전용: 업로드 생략으로 속도 향상

policy 값 비교

policy다운로드업로드적합한 상황
pull-pushOO의존성을 생성/갱신하는 build 잡
pullOX캐시만 소비하는 test·lint 잡
pushXO캐시를 처음 채우는 warmup 잡

흔한 실수는 모든 잡을 기본값 pull-push로 두는 것이다. 병렬 test 잡이 각자 캐시를 업로드하면 마지막 잡이 앞선 결과를 덮어써 불필요한 쓰기 트래픽만 발생한다. 소비 전용 잡은 반드시 pull로 지정한다.

수명주기 관리와 비용 통제

분산 캐시는 방치하면 무한히 쌓인다. 캐시 객체에는 별도 만료 개념이 없으므로 버킷 수준의 수명주기 정책으로 정리한다.

aws s3api put-bucket-lifecycle-configuration \
  --bucket ci-cache-prod \
  --lifecycle-configuration '{
    "Rules": [{
      "ID": "expire-ci-cache",
      "Filter": {"Prefix": "runner/"},
      "Status": "Enabled",
      "Expiration": {"Days": 14}
    }]
  }'

14일 정도가 무난하다. 너무 짧으면 캐시 적중률이 떨어지고, 너무 길면 스토리지 비용과 낡은 캐시로 인한 디버깅 혼란이 늘어난다.

주의점

  • 캐시는 신뢰 대상이 아니다. 캐시가 없거나 손상돼도 빌드가 처음부터 성공해야 한다. 캐시에만 있는 산출물을 다음 스테이지로 넘기려면 캐시가 아니라 artifacts를 써야 한다.
  • 키에 브랜치를 섞을지 판단한다. ${CI_COMMIT_REF_SLUG}를 키에 넣으면 브랜치 격리는 되지만 신규 브랜치마다 캐시 미스가 난다. 락파일 해시 기반이 대개 더 효율적이다.
  • 동시 쓰기 경합을 인지한다. S3는 잠금을 제공하지 않으므로 같은 키에 병렬 push가 몰리면 마지막 쓰기가 이긴다. 캐시 생성은 단일 warmup 잡으로 모으는 것이 안전하다.
  • MinIO 등 자체 호스팅 시 Insecure·엔드포인트 설정과 시계 동기화(서명 검증 실패 방지)를 확인한다.