GitLab CI 잡 문제 해결
GitLab CI 잡 문제 해결
잡(job)을 운영하다 보면 예상치 못한 동작이나 오류를 마주칠 때가 있어요. 이 문서에서는 잡을 다룰 때 자주 부딪히는 문제들을 정리하고, 각각의 해결 방법을 차근차근 안내해 드릴게요. 다루고 있는 문제를 그대로 따라 하다 보면 대부분 해결할 수 있을 거예요.
출처: 문서
본문
changes 사용 시 잡이나 파이프라인이 예상치 않게 실행될 때
merge request 파이프라인 없이 rules: changes나 only: changes를 사용하면, 잡이나 파이프라인이 예상치 않게 실행될 수 있어요.
merge request과 명시적으로 연결되지 않은 브랜치나 태그의 파이프라인은 diff를 계산할 때 이전 SHA를 사용해요. 이 계산은 git diff HEAD~와 동일해서 다음과 같은 예상치 못한 동작이 발생할 수 있죠.
- GitLab에 새 브랜치나 새 태그를 푸시하면
changes규칙이 항상 참(true)으로 평가돼요. - 새 커밋을 푸시할 때는 이전 커밋을 기준 SHA로 삼아 변경된 파일을 계산해요.
또한 예약 파이프라인에서는 changes가 붙은 규칙이 항상 참으로 평가돼요. 예약 파이프라인이 실행될 때 모든 파일이 변경된 것으로 간주되기 때문에, changes를 사용하는 잡이 예약 파이프라인에 항상 추가될 수 있답니다.
CI/CD 변수 안의 파일 경로
CI/CD 변수에 파일 경로를 넣을 땐 주의해야 해요. 변수 정의에서는 마지막 슬래시(/)가 올바르게 보여도, script:, changes: 등에서 확장되면 유효하지 않게 될 수 있어요. 예를 들면:
docker_build:
variables:
DOCKERFILES_DIR: 'path/to/files/' # 이 변수는 마지막에 '/'가 붙으면 안 됩니다
script: echo "A docker job"
rules:
- changes:
- $DOCKERFILES_DIR/*
DOCKERFILES_DIR 변수가 changes: 구간에서 확장되면 전체 경로가 path/to/files//*가 돼요. 이중 슬래시는 사용하는 키워드나 러너의 셸·OS에 따라 예기치 않은 동작을 일으킬 수 있으니 유의하세요.
You are not allowed to download code from this project. 오류 메시지
GitLab 관리자가 비공개(private) 프로젝트에서 보호된(protected) 수동 잡을 실행할 때 파이프라인이 실패하는 경우가 있어요.
CI/CD 잡은 보통 잡이 시작될 때 프로젝트를 클론하는데, 이때 잡을 실행한 사용자의 권한을 사용해요. 관리자를 포함한 모든 사용자는 비공개 프로젝트의 소스를 클론하려면 그 프로젝트의 직접 멤버가 되어야 해요. 이 동작을 바꾸려는 이슈가 진행 중이에요.
보호된 수동 잡을 실행하려면 다음 중 하나를 하세요.
- 관리자를 비공개 프로젝트의 직접 멤버(어떤 역할이든)로 추가한다.
- 프로젝트의 직접 멤버인 사용자를 가장(impersonate)한다.
CI/CD 잡을 다시 실행해도 새 설정을 사용하지 않을 때
파이프라인의 설정은 파이프라인이 생성될 때만 가져와요. 잡을 다시 실행하면 매번 동일한 설정을 사용하게 돼요. include로 추가한 별도 파일을 포함해 설정 파일을 수정했다면, 새 설정을 사용하려면 새 파이프라인을 시작해야 해요.
Job may allow multiple pipelines to run for a single action 경고
if 절 없이 when 절만 있는 rules를 사용하면 여러 파이프라인이 실행될 수 있어요. 보통 열려 있는 merge request과 연결된 브랜치에 커밋을 푸시할 때 이런 일이 발생하죠.
중복 파이프라인을 방지하려면 workflow: rules를 쓰거나, 어떤 파이프라인이 실행될지 제어하도록 규칙을 다시 작성하세요.
This GitLab CI configuration is invalid for variable expressions 오류
CI/CD 변수 표현식을 사용할 때 This GitLab CI configuration is invalid 오류를 몇 가지 형태로 만날 수 있어요. 이 구문 오류는 대개 따옴표 문자를 잘못 사용해서 생겨요.
변수 표현식에서는 문자열은 따옴표로 감싸야 하고, 변수는 따옴표로 감싸면 안 돼요. 예를 들면:
variables:
ENVIRONMENT: production
job:
script: echo
rules:
- if: $ENVIRONMENT == "production"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
이 예시에서는 production 문자열은 따옴표로 감싸고 CI/CD 변수는 따옴표를 빼서 두 if: 절 모두 올바르게 동작해요.
반대로 이 if: 절들은 모두 잘못된 예시예요.
variables:
ENVIRONMENT: production
job:
script: echo
rules: # 이 규칙들은 모두 YAML 구문 오류를 일으킵니다:
- if: ${ENVIRONMENT} == "production"
- if: "$ENVIRONMENT" == "production"
- if: $ENVIRONMENT == production
- if: "production" == "production"
이 예시를 하나씩 살펴보면:
if: ${ENVIRONMENT} == "production"—if:절에서 CI/CD 변수는${ENVIRONMENT}형식으로 쓰면 안 되기 때문에 잘못됐어요.if: "$ENVIRONMENT" == "production"— 변수를 따옴표로 감쌌기 때문에 잘못됐어요.if: $ENVIRONMENT == production— 문자열을 따옴표로 감싸지 않아서 잘못됐어요.if: "production" == "production"— 비교할 CI/CD 변수가 없어서 잘못됐어요.
get_sources 잡 섹션이 HTTP/2 문제로 실패할 때
가끔 잡이 다음과 같은 cURL 오류로 실패할 수 있어요.
++ git -c 'http.userAgent=gitlab-runner <version>' fetch origin +refs/pipelines/<id>:refs/pipelines/<id> ...
error: RPC failed; curl 16 HTTP/2 send again with decreased length
fatal: ...
이 문제는 Git과 libcurl이 HTTP/1.1을 사용하도록 설정해서 해결할 수 있어요. 설정을 추가할 수 있는 곳은 두 군데예요.
job_name:
hooks:
pre_get_sources_script:
- git config --global http.version "HTTP/1.1"
- 러너의 config.toml에 Git 설정 환경 변수를 추가:
[[runners]]
...
environment = [
"GIT_CONFIG_COUNT=1",
"GIT_CONFIG_KEY_0=http.version",
"GIT_CONFIG_VALUE_0=HTTP/1.1"
]
resource_group을 사용하는 잡이 멈춰버릴 때
- Tier: Free, Premium, Ultimate
- Offering: GitLab Self-Managed, GitLab Dedicated
resource_group을 사용하는 잡이 멈춘다면, GitLab 관리자가 rails console에서 다음 명령을 실행해서 해결해 볼 수 있어요.
# 이름으로 리소스 그룹 찾기
resource_group = Project.find_by_full_path('...').resource_groups.find_by(key: 'the-group-name')
busy_resources = resource_group.resources.where('build_id IS NOT NULL')
# 어떤 빌드가 리소스를 점유하고 있는지 확인
# (현재 기준으로는 1이어야 합니다)
busy_resources.pluck(:build_id)
# 이 빌드가 왜 리소스를 잡고 있는지 확인하면 좋습니다.
# 멈춰 있나요? 시스템이 강제로 떨궈버렸나요?
# 바쁜 리소스 해제하기
busy_resources.update_all(build_id: nil)
Error: data integrity failure 오류
잡 처리 중에 data integrity failure 오류를 볼 수 있어요. 하위 파이프라인용 트리거 잡, 러너 배정을 기다리는 잡, 정리(cleanup) 중 멈춘 잡 등 어떤 잡 유형에서든 발생할 수 있어요.
근본 원인을 찾으려면 PostgreSQL과 Sidekiq 로그를 확인하세요. GitLab Self-Managed 인스턴스에서 흔한 원인은 다음과 같아요.
업그레이드 후 데이터베이스 시퀀스 손상 — PostgreSQL 로그에 PG::UniqueViolation 오류가 남아 있어요. 관련 데이터베이스 트리거 함수가 올바른 시퀀스를 참조하는지 확인하세요.
업그레이드 후 남아 있는 오래된 Sidekiq 프로세스 — 실패가 간헐적으로 발생하고 잡을 다시 실행하면 성공하는 경우예요. 모든 Sidekiq 노드가 예상한 GitLab 버전을 실행 중인지 확인하고, 그렇지 않은 노드는 재시작하세요.
스키마 변경으로 생긴 모호하거나 잘못된 SQL — PostgreSQL 로그에 잡 처리 중 실행된 쿼리의 SQL 오류가 남아 있어요. 최근 스키마 변경이 이 잡 유형에 대해 실행되는 쿼리에 영향을 줬는지 확인하세요.
오류가 계속된다면 Rails console에서 잡을 조사해서 failure_reason과 하위 파이프라인이 생성됐는지 여부를 확인해 보세요.
You are not authorized to run this manual job 메시지
수동 잡을 실행하려는데 Run이 비활성화 되어 있고 이 메시지가 나온다면, 원인은 다음 중 하나일 수 있어요.
- 대상 환경이 보호된 환경이고, 내 계정이 Allowed to deploy 목록에 포함되어 있지 않은 경우.
- 오래된 배포 잡 방지 설정이 켜져 있고, 잡을 실행하면 최신 배포를 덮어쓸 위험이 있는 경우.
멈추거나 타임아웃을 넘긴 잡이 떨어질 때
잡이 오랜 시간 진행되지 않으면 GitLab이 자동으로 잡을 중단시키고 특정 failure_reason을 기록해요. 기준은 잡의 상태에 따라 달라져요.
| 잡 상태 | 기준 | 실패 사유(failure_reason) | | Pending | 24시간 | stuck_pending_with_matching_runners | | Pending | 1시간 | stuck_pending_no_matching_runners | | Running | 업데이트 없이 30분 | no_updates_running | | Canceling | 업데이트 없이 30분 | no_updates_canceling | | Running | 설정된 타임아웃 + 15분 | server_timeout_running | | Canceling | 설정된 타임아웃 + 15분 | server_timeout_canceling |
잡이 기다리지 않고 즉시 실패했다면 원인이 이 때문은 아니에요. 잡을 자동으로 재시도하거나 전체 실패 사유 목록을 보려면 retry:when을 확인하세요.
더 알아보기
잡과 관련해 더 자세한 내용이 궁금하다면, 잡 규칙 문서를 함께 읽어 보세요. rules를 정교하게 쓰면 원치 않는 파이프라인 실행을 줄일 수 있답니다. 오류 메시지가 특정 키워드에서 나온다면 해당 키워드의 공식 문서를 참고하는 것도 좋아요.