Docker 컨테이너에서 CI/CD 잡 실행하기

Docker 컨테이너에서 CI/CD 잡 실행하기

전용 CI/CD 빌드 서버나 로컬 머신에 호스팅된 Docker 컨테이너에서 CI/CD 잡을 실행할 수 있어요. Docker 컨테이너에서 CI/CD 잡을 실행하려면 다음이 필요해요.

  1. 러너를 등록하고 Docker executor를 사용하도록 구성해요.
  2. .gitlab-ci.yml 파일에서 CI/CD 잡을 실행할 컨테이너 이미지를 지정해요.
  3. (선택) .gitlab-ci.yml 파일에서 services를 지정해서 MySQL 같은 다른 서비스를 컨테이너에서 실행해요.

출처: 문서

본문

Docker executor를 사용하는 러너 등록하기

Docker와 함께 GitLab Runner를 사용하려면 Docker executor를 사용하는 러너를 등록해야 해요.

이 예제는 서비스를 제공하기 위한 임시 템플릿을 설정하는 방법을 보여줘요.

cat > /tmp/test-config.template.toml << EOF
[[runners]]
[runners.docker]
[[runners.docker.services]]
name = "postgres:latest"
[[runners.docker.services]]
name = "mysql:latest"
EOF

그런 다음 이 템플릿을 사용해 러너를 등록해요.

sudo gitlab-runner register \
  --url "https://gitlab.example.com/" \
  --token "$RUNNER_TOKEN" \
  --description "docker-ruby:2.6" \
  --executor "docker" \
  --template-config /tmp/test-config.template.toml \
  --docker-image ruby:3.3

등록된 러너는 ruby:2.6 Docker 이미지를 사용하고, postgres:latestmysql:latest 두 개의 서비스를 실행해요. 두 서비스 모두 빌드 과정 중에 접근할 수 있어요.

이미지란 무엇인가요

image 키워드는 Docker executor가 CI/CD 잡을 실행할 때 사용하는 Docker 이미지의 이름이에요.

기본적으로 executor는 Docker Hub에서 이미지를 가져와요. 하지만 gitlab-runner/config.toml 파일에서 레지스트리 위치를 구성할 수 있어요. 예를 들어 Docker pull 정책을 설정해 로컬 이미지를 사용할 수도 있어요.

이미지와 Docker Hub에 대한 자세한 내용은 Docker 개요를 참고해요.

이미지 요구 사항

CI/CD 잡을 실행하는 데 사용하는 모든 이미지에는 다음 애플리케이션이 설치되어 있어야 해요.

  • sh 또는 bash
  • grep

.gitlab-ci.yml 파일에서 image 정의하기

모든 잡에 사용할 이미지와 런타임 중에 사용할 서비스 목록을 정의할 수 있어요.

default:
  image: ruby:2.6
  services:
    - postgres:16.10
  before_script:
    - bundle install

test:
  script:
    - bundle exec rake spec

이미지 이름은 다음 형식 중 하나여야 해요.

  • image: <image-name> (<image-name>latest 태그로 사용하는 것과 동일)
  • image: <image-name>:<tag>
  • image: <image-name>@<digest>

확장된 Docker 구성 옵션

imageservices 항목에는 문자열이나 맵을 사용할 수 있어요.

  • 문자열은 전체 이미지 이름을 포함해야 해요. (Docker Hub가 아닌 다른 레지스트리에서 이미지를 받아오려면 레지스트리도 포함)
  • 맵에는 최소한 name 옵션이 있어야 해요. 이는 문자열 설정에서 사용한 것과 동일한 이미지 이름이에요.

예를 들어 다음 두 정의는 동일해요.

  • imageservices에 문자열 사용:
image: "registry.example.com/my/image:latest"

services:
  - postgresql:16.10
  - redis:latest
  • imageservices에 맵 사용. image:name이 필수예요.
image:
  name: "registry.example.com/my/image:latest"

services:
  - name: postgresql:16.10
  - name: redis:latest

스크립트가 실행되는 위치

CI 잡이 Docker 컨테이너에서 실행되면 before_script, script, after_script 명령이 /builds/<project-path>/ 디렉터리에서 실행돼요. 이미지에 다른 기본 WORKDIR가 정의되어 있을 수도 있어요. WORKDIR로 이동하려면 WORKDIR를 환경 변수로 저장해 두면 잡 실행 중에 컨테이너 안에서 참조할 수 있어요.

이미지의 entrypoint 재정의하기

사용 가능한 entrypoint 재정의 방법을 설명하기 전에, 러너가 어떻게 시작하는지부터 살펴볼게요. 러너는 CI/CD 잡에 사용되는 컨테이너에 Docker 이미지를 사용해요.

  1. 러너가 정의된 entrypoint로 Docker 컨테이너를 시작해요. 기본값은 .gitlab-ci.yml 파일에서 재정의할 수 있는 Dockerfile의 값이에요.
  2. 러너가 실행 중인 컨테이너에 자신을 연결해요.
  3. 러너가 스크립트를 준비해요. 이는 [before_script](/ci/yaml/#before_script), [script](/ci/yaml/#script), [after_script](/ci/yaml/#after_script)를 합친 것이에요.
  4. 러너가 스크립트를 컨테이너의 셸 stdin으로 보내고 출력을 받아요.

Docker 이미지의 entrypoint를 재정의하려면 .gitlab-ci.yml 파일에서:

  • Docker 17.06 이상에서는 entrypoint를 빈 값으로 설정해요.
  • Docker 17.03 이하에서는 entrypoint/bin/sh -c, /bin/bash -c 또는 이미지에 있는 동등한 셸로 설정해요.

image:entrypoint의 문법은 [Dockerfile ENTRYPOINT](https://docs.docker.com/reference/dockerfile/#entrypoint)와 비슷해요.

SQL 데이터베이스가 들어 있는 super/sql:experimental 이미지가 있다고 가정해 볼게요. 이 데이터베이스 바이너리로 몇 가지 테스트를 실행하려고 이 이미지를 잡의 기본 이미지로 쓰고 싶어요. 그리고 이 이미지가 /usr/bin/super-sql run을 entrypoint로 구성되어 있다고 가정해요. 추가 옵션 없이 컨테이너가 시작되면 데이터베이스 프로세스가 실행돼요. 러너는 이미지에 entrypoint가 없거나, 셸 명령을 시작하도록 준비된 entrypoint가 있을 것으로 기대해요.

확장 Docker 구성 옵션을 사용하면 다음을 하지 않아도 돼요.

  • super/sql:experimental을 기반으로 자체 이미지 만들기.
  • ENTRYPOINT를 셸로 설정하기.
  • CI 잡에서 새 이미지 사용하기.

이제 .gitlab-ci.yml 파일에서 entrypoint를 정의할 수 있어요.

Docker 17.06 이상:

image:
  name: super/sql:experimental
  entrypoint: [""]

Docker 17.03 이하:

image:
  name: super/sql:experimental
  entrypoint: ["/bin/sh", "-c"]

config.toml에서 이미지와 서비스 정의하기

config.toml 파일에서 다음을 정의할 수 있어요.

  • [runners.docker] 섹션에서 CI/CD 잡을 실행하는 데 사용할 컨테이너 이미지
  • [[runners.docker.services]] 섹션에서 services 컨테이너
[runners.docker]
  image = "ruby:latest"
  services = ["mysql:latest", "postgres:latest"]

이렇게 정의한 이미지와 서비스는 해당 러너가 실행하는 모든 잡에 추가돼요.

프라이빗 컨테이너 레지스트리에서 이미지 접근하기

프라이빗 컨테이너 레지스트리에 접근하기 위해 GitLab Runner 프로세스는 다음 중 하나를 사용할 수 있어요.

같은 GitLab 인스턴스에서 GitLab Container Registry를 사용하면, GitLab이 이 레지스트리에 대한 기본 자격 증명을 제공해요. 이 자격 증명으로 CI_JOB_TOKEN이 인증에 사용돼요. 잡 토큰을 사용하려면 잡을 시작하는 사용자가 프라이빗 이미지가 호스팅된 프로젝트에 대해 최소한 Reporter 역할을 가져야 해요. 프라이빗 이미지를 호스팅하는 프로젝트도 다른 프로젝트가 잡 토큰으로 인증하는 것을 허용해야 해요. 이 접근은 기본적으로 비활성화되어 있어요. 자세한 내용은 CI/CD 잡 토큰을 참고해요.

어떤 옵션을 사용할지 정의하려면 러너 프로세스가 다음 순서로 구성을 읽어요.

  • /root/.docker 디렉터리의 config.json 파일.
  • DOCKER_AUTH_CONFIG CI/CD 변수.
  • 러너의 config.toml 파일에 설정된 DOCKER_AUTH_CONFIG 환경 변수.
  • 프로세스를 실행하는 사용자의 $HOME/.docker 디렉터리의 config.json 파일. 하위 프로세스를 비특권 사용자로 실행하기 위해 --user 플래그를 제공하면, 메인 러너 프로세스 사용자의 홈 디렉터리가 사용돼요.

정적으로 정의된 자격 증명 사용하기

두 가지 방법으로 프라이빗 레지스트리에 접근할 수 있어요. 두 방법 모두 적절한 인증 정보를 담은 CI/CD 변수 DOCKER_AUTH_CONFIG를 설정해야 해요.

  1. 잡별(Per-job): 하나의 잡이 프라이빗 레지스트리에 접근하도록 구성하려면 DOCKER_AUTH_CONFIGCI/CD 변수로 추가해요.
  2. 러너별(Per-runner): 러너의 모든 잡이 프라이빗 레지스트리에 접근하도록 구성하려면 DOCKER_AUTH_CONFIG를 러너 구성에 환경 변수로 추가해요.

각각의 예시는 아래 섹션을 참고해요.

DOCKER_AUTH_CONFIG 데이터 확인하기

예시로 registry.example.com:5000/private/image:latest 이미지를 사용한다고 가정해 볼게요. 이 이미지는 프라이빗이라 프라이빗 컨테이너 레지스트리에 로그인해야 해요.

로그인 자격 증명이 다음과 같다고 가정해요.

registry registry.example.com:5000
username my_username
password my_password

DOCKER_AUTH_CONFIG 값을 확인하려면 다음 방법 중 하나를 사용해요.

  • 로컬 머신에서 docker login을 실행해요.
docker login registry.example.com:5000 --username my_username --password my_password

그런 다음 ~/.docker/config.json의 내용을 복사해요.

컴퓨터에서 레지스트리에 접근할 필요가 없다면 docker logout을 해도 돼요.

docker logout registry.example.com:5000
  • 어떤 구성에서는 Docker 클라이언트가 시스템 키 저장소를 사용해 docker login의 결과를 저장할 수도 있어요. 그 경우 ~/.docker/config.json을 읽을 수 없으므로, ${username}:${password}의 base64 인코딩 버전을 직접 준비해서 Docker 구성 JSON을 수동으로 만들어야 해요. 터미널을 열고 다음 명령을 실행해요.
# The use of printf (as opposed to echo) prevents encoding a newline in the password.
printf "my_username:my_password" | openssl base64 -A

# Example output to copy
bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=

사용자 이름에 @ 같은 특수 문자가 포함되어 있으면, 인증 문제를 피하기 위해 백슬래시(\)로 이스케이프해야 해요.

Docker JSON 구성 내용을 다음과 같이 만들어요.

{
    "auths": {
        "registry.example.com:5000": {
            "auth": "(Base64 content from above)"
        }
    }
}
잡 구성하기

registry.example.com:5000에 대한 접근 권한이 있는 단일 잡을 구성하려면 다음 단계를 따라요.

  1. Docker 구성 파일의 내용을 값으로 하는 CI/CD 변수 DOCKER_AUTH_CONFIG를 만들어요.
{
    "auths": {
        "registry.example.com:5000": {
            "auth": "bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ="
        }
    }
}
  1. 이제 .gitlab-ci.yml 파일에서 imageservices에 정의된 registry.example.com:5000의 모든 프라이빗 이미지를 사용할 수 있어요.
image: registry.example.com:5000/namespace/image:tag

위 예제에서 GitLab Runner는 namespace/image:tag 이미지를 registry.example.com:5000에서 찾아요.

원하는 만큼 많은 레지스트리에 대한 구성을 추가할 수 있어요. 앞서 설명한 대로 "auths" 해시에 레지스트리를 더 추가하면 돼요.

러너가 DOCKER_AUTH_CONFIG와 매칭하려면 어디에서나 전체 hostname:port 조합이 필요해요. 예를 들어 .gitlab-ci.yml 파일에 registry.example.com:5000/namespace/image:tag이 지정되어 있으면 DOCKER_AUTH_CONFIG에도 registry.example.com:5000을 지정해야 해요. registry.example.com만 지정하면 동작하지 않아요.

러너 구성하기

같은 레지스트리에 접근하는 파이프라인이 많다면 러너 수준에서 레지스트리 접근을 설정하는 게 좋아요. 그러면 파이프라인 작성자는 적절한 러너에서 잡만 실행해도 프라이빗 레지스트리에 접근할 수 있어요. 또한 레지스트리 변경과 자격 증명 교체(rotation)를 단순화하는 데도 도움이 돼요.

즉, 해당 러너의 어떤 잡이든 그 레지스트리에 같은 권한으로 접근할 수 있다는 뜻이에요. 프로젝트를 넘어서까지요. 레지스트리 접근을 제어해야 한다면 러너 접근을 제어해야 해요.

러너에 DOCKER_AUTH_CONFIG를 추가하려면:

  1. 러너의 config.toml 파일을 다음과 같이 수정해요.
[[runners]]
  environment = ["DOCKER_AUTH_CONFIG={\"auths\":{\"registry.example.com:5000\":{\"auth\":\"bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=\"}}}"]
  • DOCKER_AUTH_CONFIG 데이터에 포함된 큰따옴표는 백슬래시로 이스케이프해야 해요. 그래야 TOML로 해석되지 않아요.
  • environment 옵션은 목록이에요. 러너에 기존 항목이 있을 수 있으므로 목록을 교체하지 말고 여기에 추가해야 해요.
  1. 러너 서비스를 다시 시작해요.

자격 증명 저장소(Credentials Store) 사용하기

인스턴스 러너나 러너가 설치된 환경에 접근할 수 없는 다른 러너에서는 자격 증명 저장소를 사용할 수 없어요.

전제 조건:

  • 특정 키체인이나 외부 저장소와 상호작용할 외부 헬퍼 프로그램이 GitLab Runner $PATH에 있어야 해요.

자격 증명 저장소를 구성하려면:

  1. GitLab Runner가 헬퍼 프로그램을 사용하게 만들어요. 다음 옵션 중 하나로 할 수 있어요.

    • Docker 구성 파일의 내용을 값으로 하는 CI/CD 변수 DOCKER_AUTH_CONFIG를 만들어요.
  {
    "credsStore": "osxkeychain"
  }
  • 또는 셀프 매니지드 러너를 실행 중이라면 JSON을 ${GITLAB_RUNNER_HOME}/.docker/config.json에 추가해요. GitLab Runner가 이 구성 파일을 읽고 특정 저장소에 필요한 헬퍼를 사용해요.

credsStore는 모든 레지스트리에 접근할 때 사용돼요. 프라이빗 레지스트리의 이미지와 Docker Hub의 공개 이미지를 함께 사용하면 Docker Hub에서 가져오는 데 실패해요. Docker 데몬이 모든 레지스트리에 같은 자격 증명을 사용하려 하기 때문이에요.

자격 증명 헬퍼(Credential Helpers) 사용하기

예시로 <aws_account_id>.dkr.ecr.<region>.amazonaws.com/private/image:latest 이미지를 사용한다고 가정해 볼게요. 이 이미지는 프라이빗이라 프라이빗 컨테이너 레지스트리에 로그인해야 해요.

인스턴스 러너나 러너가 설치된 환경에 접근할 수 없는 다른 러너에서는 자격 증명 헬퍼를 사용할 수 없어요.

전제 조건:

  • GitLab Runner $PATH에 있는 [docker-credential-ecr-login](https://github.com/awslabs/amazon-ecr-credential-helper).
  • 다음 AWS 자격 증명 설정 중 하나. GitLab Runner Manager가 자격 증명을 획득해 러너에게 전달해요. GitLab Runner가 자격 증명에 접근할 수 있는지 확인해요.

<aws_account_id>.dkr.ecr.<region>.amazonaws.com에 대한 접근을 구성하려면:

  1. GitLab Runner가 자격 증명 헬퍼를 사용하게 만들어요. 다음 옵션 중 하나로 할 수 있어요.

    • Docker 구성 파일의 내용을 값으로 하는 CI/CD 변수 DOCKER_AUTH_CONFIG를 만들어요.
{
  "credHelpers": {
    "<aws_account_id>.dkr.ecr.<region>.amazonaws.com": "ecr-login"
  }
}

이 구성은 Docker가 특정 레지스트리에 자격 증명 헬퍼를 사용하도록 설정해요.

대신, 모든 Amazon Elastic Container Registry(ECR) 레지스트리에 자격 증명 헬퍼를 사용하도록 Docker를 구성할 수도 있어요.

{
  "credsStore": "ecr-login"
}

{"credsStore": "ecr-login"}을 사용한다면 AWS 공유 구성 파일(~/.aws/config)에서 지역(region)을 명시적으로 설정해요. ECR 자격 증명 헬퍼가 인증 토큰을 가져올 때 지역이 지정되어야 해요.

  • 또는 셀프 매니지드 러너를 실행 중이라면 위의 JSON을 ${GITLAB_RUNNER_HOME}/.docker/config.json에 추가해요. GitLab Runner가 이 구성 파일을 읽고 특정 저장소에 필요한 헬퍼를 사용해요.
  1. 이제 .gitlab-ci.yml 파일에서 image, services 또는 둘 다에 정의된 <aws_account_id>.dkr.ecr.<region>.amazonaws.com의 모든 프라이빗 이미지를 사용할 수 있어요.
image: <aws_account_id>.dkr.ecr.<region>.amazonaws.com/private/image:latest

예시에서 GitLab Runner는 private/image:latest 이미지를 <aws_account_id>.dkr.ecr.<region>.amazonaws.com에서 찾아요.

원하는 만큼 많은 레지스트리에 대한 구성을 추가할 수 있어요. "credHelpers" 해시에 레지스트리를 더 추가하면 돼요.

체크섬으로 이미지를 안전하게 유지하기

.gitlab-ci.yml 파일의 잡 정의에서 이미지 체크섬을 사용해 이미지의 무결성을 검증해요. 이미지 무결성 검증이 실패하면 수정된 컨테이너를 사용할 수 없어요.

이미지 체크섬을 사용하려면 끝에 체크섬을 추가해요.

image: ruby:2.6.8@sha256:d1dbaf9665fe8b2175198e49438092fdbcf4d8934200942b94425301b17853c7

이미지 체크섬을 얻으려면 이미지의 TAG 탭에서 DIGEST 열을 보세요. 예를 들어 Ruby 이미지를 확인해요. 체크섬은 6155f0235e95 같은 무작위 문자열이에요.

docker images --digests 명령으로 시스템의 어떤 이미지든 체크섬을 얻을 수도 있어요.

❯ docker images --digests
REPOSITORY                                                        TAG       DIGEST                                                                    (...)
gitlab/gitlab-ee                                                  latest    sha256:723aa6edd8f122d50cae490b1743a616d54d4a910db892314d68470cc39dfb24   (...)
gitlab/gitlab-runner                                              latest    sha256:4a18a80f5be5df44cb7575f6b89d1fdda343297c6fd666c015c0e778b276e726   (...)

커스텀 GitLab Runner Docker 이미지 만들기

AWS CLI와 Amazon ECR Credential Helper를 패키징한 커스텀 GitLab Runner Docker 이미지를 만들 수 있어요. 이 설정은 컨테이너화된 애플리케이션, 특히 AWS 서비스와의 안전하고 간소화된 상호작용을 돕고, 시간이 오래 걸리고 오류가 나기 쉬운 구성과 수동 자격 증명 관리를 피할 수 있게 해줘요.

  1. GitLab을 AWS에 인증해요.

  2. 다음 내용으로 Dockerfile을 만들어요.

# Control package versions
ARG GITLAB_RUNNER_VERSION=v17.3.0
ARG AWS_CLI_VERSION=2.17.36

# AWS CLI and Amazon ECR Credential Helper
FROM amazonlinux as aws-tools
RUN set -e \
    && yum update -y \
    && yum install -y --allowerasing git make gcc curl unzip \
    && curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" --output "awscliv2.zip" \
    && unzip awscliv2.zip && ./aws/install -i /usr/local/bin \
    && yum clean all

# Download and install ECR Credential Helper
RUN curl --location --output  /usr/local/bin/docker-credential-ecr-login "https://github.com/awslabs/amazon-ecr-credential-helper/releases/latest/download/docker-credential-ecr-login-linux-amd64"
RUN chmod +x /usr/local/bin/docker-credential-ecr-login

# Configure the ECR Credential Helper
RUN mkdir -p /root/.docker
RUN echo '{ "credsStore": "ecr-login" }' > /root/.docker/config.json

# Final image based on GitLab Runner
FROM gitlab/gitlab-runner:${GITLAB_RUNNER_VERSION}

# Install necessary packages
RUN apt-get update \
    && apt-get install -y --no-install-recommends jq procps curl unzip groff libgcrypt20 tar gzip less openssh-client \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

# Copy AWS CLI and Amazon ECR Credential Helper binaries
COPY --from=aws-tools /usr/local/bin/ /usr/local/bin/

# Copy ECR Credential Helper Configuration
COPY --from=aws-tools /root/.docker/config.json /root/.docker/config.json
  1. .gitlab-ci.yml에서 커스텀 GitLab Runner Docker 이미지를 빌드하려면 다음 예시를 포함해요.
variables:
  DOCKER_DRIVER: overlay2
  IMAGE_NAME: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
  GITLAB_RUNNER_VERSION: v17.3.0
  AWS_CLI_VERSION: 2.17.36

stages:
  - build

build-image:
  stage: build
  script:
    - echo "Logging into GitLab container registry..."
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - echo "Building Docker image..."
    - docker build --build-arg GITLAB_RUNNER_VERSION=${GITLAB_RUNNER_VERSION} --build-arg AWS_CLI_VERSION=${AWS_CLI_VERSION} -t ${IMAGE_NAME} .
    - echo "Pushing Docker image to GitLab container registry..."
    - docker push ${IMAGE_NAME}
  rules:
    - changes:
        - Dockerfile
  1. 러너를 등록해요.

더 알아보기

Docker executor의 동작 방식과 서비스 컨테이너 실행에 대해 더 알고 싶다면, 먼저 GitLab Runner Docker executor 문서GitLab CI/CD services 문서를 함께 읽어보는 걸 추천해요. 프라이빗 레지스트리 인증이 자주 필요하다면 DOCKER_AUTH_CONFIG를 러너 수준에서 구성해 두면 여러 잡에서 재사용하기 편해요.