CI/CD 변수 문제 해결

CI/CD 변수 문제 해결

CI/CD 변수를 다루다 보면 예상치 못한 오류나 의아한 동작을 만나게 돼요. 이 문서는 변수 관련해서 자주 겪는 문제들을 정리하고 해결 방법을 안내해 드릴게요. 각 항목의 예시 코드를 그대로 따라 해 보면 왜 그런 일이 일어났는지 바로 이해할 수 있을 거예요.

출처: 문서

본문

모든 변수 나열하기

스크립트에서 사용할 수 있는 모든 변수는 Bash의 export 명령이나 PowerShell의 dir env:로 나열할 수 있어요. 이 명령은 사용 가능한 모든 변수의 값을 노출하므로, 보안 위험이 될 수 있어요. 마스킹된 변수[MASKED]로 표시돼요.

예를 들어 Bash에서는:

job_name:
  script:
    - export

예시 잡 로그 출력(일부 생략):

export CI_JOB_ID="50"
export CI_COMMIT_SHA="1ecfd275763eff1d6b4844ea3168962458c9f27a"
export CI_COMMIT_SHORT_SHA="1ecfd275"
export CI_COMMIT_REF_NAME="main"
export CI_REPOSITORY_URL="https://gitlab-ci-token:[MASKED]@example.com/gitlab-org/gitlab.git"
export CI_COMMIT_TAG="1.0.0"
export CI_JOB_NAME="spec:other"
export CI_JOB_STAGE="test"
export CI_JOB_MANUAL="true"
export CI_JOB_TRIGGERED="true"
export CI_JOB_TOKEN="[MASKED]"
export CI_PIPELINE_ID="1000"
export CI_PIPELINE_IID="10"
export CI_PAGES_DOMAIN="gitlab.io"
export CI_PAGES_URL="https://gitlab-org.gitlab.io/gitlab"
export CI_PROJECT_ID="34"
export CI_PROJECT_DIR="/builds/gitlab-org/gitlab"
export CI_PROJECT_NAME="gitlab"
export CI_PROJECT_TITLE="GitLab"
...

디버그 로깅 활성화하기

디버그 로깅은 심각한 보안 위험이 될 수 있어요. 그 출력에는 잡이 사용할 수 있는 모든 변수의 내용이 들어 있거든요. 그 출력은 GitLab 서버에 업로드되고 잡 로그에서 볼 수 있어요.

디버그 로깅을 사용하면 파이프라인 설정이나 잡 스크립트의 문제를 해결하는 데 도움이 돼요. 디버그 로깅은 보통 러너가 숨기는 잡 실행 세부 정보를 노출하고 잡 로그를 더 상세하게 만들어요. 또한 잡이 사용할 수 있는 모든 변수와 비밀 정보를 노출해요.

디버그 로깅을 활성화하기 전에 팀 구성원만 잡 로그를 볼 수 있는지 확인하세요. 또 로그를 다시 공개로 만들기 전에 디버그 출력이 들어 있는 잡 로그를 삭제해야 해요.

디버그 로깅을 활성화하려면 CI_DEBUG_TRACE 변수를 true로 설정하세요.

job_name:
  variables:
    CI_DEBUG_TRACE: "true"

예시 출력(일부 생략):

...
export CI_SERVER_TLS_CA_FILE="/builds/gitlab-examples/ci-debug-trace.tmp/CI_SERVER_TLS_CA_FILE"
if [[ -d "/builds/gitlab-examples/ci-debug-trace/.git" ]]; then
  echo $'\''\x1b[32;1mFetching changes...\x1b[0;m'\''
  $'cd' "/builds/gitlab-examples/ci-debug-trace"
  $'git' "config" "fetch.recurseSubmodules" "false"
  $'rm' "-f" ".git/index.lock"
  $'git' "clean" "-ffdx"
  $'git' "reset" "--hard"
  $'git' "remote" "set-url" "origin" "https://gitlab-ci-token:[email protected]/gitlab-examples/ci-debug-trace.git"
  $'git' "fetch" "origin" "--prune" "+refs/heads/*:refs/remotes/origin/*" "+refs/tags/*:refs/tags/lds"
++ CI_BUILDS_DIR=/builds
++ export CI_PROJECT_DIR=/builds/gitlab-examples/ci-debug-trace
++ CI_PROJECT_DIR=/builds/gitlab-examples/ci-debug-trace
++ export CI_CONCURRENT_ID=87
++ CI_CONCURRENT_ID=87
++ export CI_CONCURRENT_PROJECT_ID=0
++ CI_CONCURRENT_PROJECT_ID=0
++ export CI_SERVER=yes
++ CI_SERVER=yes
++ mkdir -p /builds/gitlab-examples/ci-debug-trace.tmp
++ echo -n '-----BEGIN CERTIFICATE-----
-----END CERTIFICATE-----'
++ export CI_SERVER_TLS_CA_FILE=/builds/gitlab-examples/ci-debug-trace.tmp/CI_SERVER_TLS_CA_FILE
++ CI_SERVER_TLS_CA_FILE=/builds/gitlab-examples/ci-debug-trace.tmp/CI_SERVER_TLS_CA_FILE
++ export CI_PIPELINE_ID=52666
++ CI_PIPELINE_ID=52666
++ export CI_PIPELINE_URL=https://gitlab.com/gitlab-examples/ci-debug-trace/pipelines/52666
++ CI_PIPELINE_URL=https://gitlab.com/gitlab-examples/ci-debug-trace/pipelines/52666
++ export CI_JOB_ID=7046507
++ CI_JOB_ID=7046507
++ export CI_JOB_URL=https://gitlab.com/gitlab-examples/ci-debug-trace/-/jobs/379424655
++ CI_JOB_URL=https://gitlab.com/gitlab-examples/ci-debug-trace/-/jobs/379424655
++ export CI_JOB_TOKEN=[MASKED]
++ CI_JOB_TOKEN=[MASKED]
++ export CI_REGISTRY_USER=gitlab-ci-token
++ CI_REGISTRY_USER=gitlab-ci-token
++ export CI_REGISTRY_PASSWORD=[MASKED]
++ CI_REGISTRY_PASSWORD=[MASKED]
++ export CI_REPOSITORY_URL=https://gitlab-ci-token:[MASKED]@gitlab.com/gitlab-examples/ci-debug-trace.git
++ CI_REPOSITORY_URL=https://gitlab-ci-token:[MASKED]@gitlab.com/gitlab-examples/ci-debug-trace.git
++ export CI_JOB_NAME=debug_trace
++ CI_JOB_NAME=debug_trace
++ export CI_JOB_STAGE=test
++ CI_JOB_STAGE=test
++ export CI_NODE_TOTAL=1
++ CI_NODE_TOTAL=1
++ export CI=true
++ CI=true
++ export GITLAB_CI=true
++ GITLAB_CI=true
++ export CI_SERVER_URL=https://gitlab.com:3000
++ CI_SERVER_URL=https://gitlab.com:3000
++ export CI_SERVER_HOST=gitlab.com
++ CI_SERVER_HOST=gitlab.com
++ export CI_SERVER_PORT=3000
++ CI_SERVER_PORT=3000
++ export CI_SERVER_SHELL_SSH_HOST=gitlab.com
++ CI_SERVER_SHELL_SSH_HOST=gitlab.com
++ export CI_SERVER_SHELL_SSH_PORT=22
++ CI_SERVER_SHELL_SSH_PORT=22
++ export CI_SERVER_PROTOCOL=https
++ CI_SERVER_PROTOCOL=https
++ export CI_SERVER_NAME=GitLab
++ CI_SERVER_NAME=GitLab
++ export GITLAB_FEATURES=audit_events,burndown_charts,code_owners,...
++ GITLAB_FEATURES=audit_events,burndown_charts,code_owners,...
++ export CI_PROJECT_ID=17893
++ CI_PROJECT_ID=17893
++ export CI_PROJECT_NAME=ci-debug-trace
++ CI_PROJECT_NAME=ci-debug-trace
...

디버그 로깅 접근 권한

디버그 로깅에 대한 접근은 Developer, Maintainer 또는 Owner 역할의 사용자로 제한돼요. 더 낮은 역할의 사용자는 다음 위치에 변수로 디버그 로깅이 활성화될 때 로그를 볼 수 없어요.

러너에 로컬 변수로 CI_DEBUG_TRACE를 추가하면 디버그 로그가 생성되고 잡 로그에 접근할 수 있는 모든 사용자에게 보여요. 권한 수준은 러너가 확인하지 않으므로, 이 변수는 GitLab 안에서만 사용해야 해요.

argument list too long 오류

이 문제는 잡에 정의된 모든 CI/CD 변수의 길이 합이 잡이 실행되는 셸이 허용하는 한도를 초과할 때 발생해요. 여기에는 사전 정의 변수와 사용자 정의 변수의 이름·값이 모두 포함돼요. 이 한도는 보통 ARG_MAX라고 부르며 셸과 운영체제에 따라 달라져요. File-type(파일 형식) 변수 하나의 내용이 ARG_MAX를 초과할 때도 이 문제가 발생해요.

자세한 내용은 이슈 392406을 참고하세요.

해결 방법으로는 다음 중 하나를 쓸 수 있어요.

  • 큰 환경 변수는 가능하면 File-type CI/CD 변수로 사용한다.
  • 하나의 큰 변수가 ARG_MAX보다 크다면 Secure Files를 사용하거나 다른 방법으로 파일을 잡으로 가져와 본다.

하위 파이프라인에 파이프라인 변수를 설정할 권한이 없다는 오류

하위 파이프라인을 트리거할 때 예상치 못하게 다음 오류가 나올 수 있어요.

Failed - (downstream pipeline can not be created, Insufficient permissions to set pipeline variables)

이 오류는 하위 프로젝트에 제한된 파이프라인 변수가 있고 트리거 잡이 다음 중 하나인 경우에 발생해요.

  • 변수를 정의한 경우. 예를 들면:
trigger-job:
  variables:
    VAR_FOR_DOWNSTREAM: "test"
  trigger: my-group/my-project
variables:
  DEFAULT_VAR: "test"

trigger-job:
  trigger: my-group/my-project

트리거 잡에서 하위 파이프라인으로 전달되는 변수는 파이프라인 변수라서, 해결 방법은 다음 중 하나예요.

같은 이름의 잡 변수에서 기본 변수가 확장되지 않을 때

같은 이름의 잡 변수 안에서는 기본 변수의 값을 사용할 수 없어요. 기본 변수는 잡에 같은 이름의 변수가 정의되어 있지 않을 때만 그 잡에서 사용할 수 있어요. 잡에 같은 이름의 변수가 있으면 잡의 변수가 우선하고 기본 변수는 잡에서 사용할 수 없게 돼요.

예를 들어 다음 두 예시는 동등해요.

  • 이 예시에서 $MY_VAR는 어디에도 정의되지 않았으므로 값이 없어요.
Job-with-variable:
  variables:
    MY_VAR: $MY_VAR
  script: echo "Value is '$MY_VAR'"
  • 이 예시에서 같은 이름의 기본 변수가 잡에서 사용할 수 없으므로 $MY_VAR는 값이 없어요.
variables:
  MY_VAR: "Default value"

Job-with-same-name-variable:
  variables:
    MY_VAR: $MY_VAR
  script: echo "Value is '$MY_VAR'"

두 경우 모두 echo 명령은 Value is '$MY_VAR'를 출력해요.

일반적으로 기본 변수를 잡에서 직접 사용하는 게 낫고, 그 값을 새 변수에 다시 할당하는 건 피하는 게 좋아요. 꼭 그렇게 해야 한다면 서로 다른 이름의 변수를 사용하세요. 예를 들면:

variables:
  MY_VAR1: "Default value1"
  MY_VAR2: "Default value2"

overwrite-same-name:
  variables:
    MY_VAR2_FROM_DEFAULTS: $MY_VAR2
  script: echo "Values are '$MY_VAR1' and '$MY_VAR2_FROM_DEFAULTS'"

더 알아보기

변수 전체를 이해하고 싶다면 CI/CD 변수 개요 문서를, 파이프라인 변수·파일 변수 같은 세부 개념은 그 안의 각 섹션을 참고해 보세요. 디버그 로깅처럼 보안과 관련된 설정은 반드시 팀의 권한 체계가 제대로 갖춰졌는지 먼저 확인하는 습관을 들이면 좋답니다.