Docker를 사용해 Docker 이미지 빌드하기
Docker를 사용해 Docker 이미지 빌드하기
GitLab CI/CD와 Docker를 함께 사용해 컨테이너 이미지를 빌드·테스트·푸시하는 방법을 설명하는 문서예요. CI/CD 작업에서 docker 명령을 실행하려면 GitLab Runner가 docker 명령을 지원하도록 구성해야 해요.
인프라, 실행기 유형, 보안 요구 사항에 따라 선택하는 여러 접근 방식(shell 실행기, Docker-in-Docker, 소켓 바인딩, 파이프 바인딩)을 코드 예시와 함께 옆에서 설명해 주는 방식으로 정리했어요.
출처: 문서
본문
GitLab CI/CD와 Docker를 함께 사용해 컨테이너 이미지를 빌드, 테스트, 푸시할 수 있어요. CI/CD 작업에서 docker 명령을 실행하려면 GitLab Runner가 docker 명령을 지원하도록 구성해야 해요.
선택하는 접근 방식은 인프라, 실행기 유형, 보안 요구 사항에 따라 달라져요. 일부 접근 방식은 러너에서 privileged(권한 상승) 모드를 요구해요. privileged 모드를 활성화할 수 없다면 Docker 대체 방법을 사용하세요.
| 접근 방식 | 실행기 | 권한 상승 모드 | OS |
|---|---|---|---|
| Shell 실행기 | Shell | 아니요 | Linux |
| Docker-in-Docker | Docker, Kubernetes | 예 | Linux |
| Docker 소켓 바인딩 | Docker, Kubernetes | 아니요 | Linux |
| Docker 파이프 바인딩 | Docker, Kubernetes | 아니요 | Windows |
Shell 실행기 사용하기
CI/CD 작업에 Docker 명령을 포함하려면 러너가 shell 실행기를 사용하도록 구성할 수 있어요. 이 구성에서 gitlab-runner 사용자가 Docker 명령을 실행하지만, 그러려면 권한이 필요해요.
- GitLab Runner 설치.
- 러너 등록.
shell실행기를 선택하세요. 예를 들어:sudo gitlab-runner register -n \ --url "https://gitlab.com/" \ --registration-token REGISTRATION_TOKEN \ --executor shell \ --description "My Runner" - GitLab Runner가 설치된 서버에 Docker Engine을 설치하세요. 지원 플랫폼 목록을 확인하세요.
gitlab-runner사용자를docker그룹에 추가하세요.sudo usermod -aG docker gitlab-runnergitlab-runner가 Docker에 접근할 수 있는지 확인하세요.sudo -u gitlab-runner -H docker info- GitLab에서 Docker가 동작하는지 확인하려면
.gitlab-ci.yml에docker info를 추가하세요.default: before_script: - docker info build_image: script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
이제 docker 명령(필요하면 Docker Compose 설치 포함)을 사용할 수 있어요.
gitlab-runner를 docker 그룹에 추가하면 사실상 gitlab-runner에 전체 루트 권한을 부여하게 돼요. 자세한 내용은 docker 그룹의 보안을 참고하세요.
Docker-in-Docker 사용하기
Docker-in-Docker(dind)는 등록된 러너가 Docker 실행기나 Kubernetes 실행기를 사용하고, 실행기가 Docker의 컨테이너 이미지를 사용해 CI/CD 작업을 실행하는 것을 의미해요.
각 작업은 자신만의 격리된 Docker 데몬을 얻으므로 동시 작업이 충돌하지 않아요. 러너가 privileged 모드를 지원할 때 이 접근 방식을 사용하세요.
설정 지침은 Docker-in-Docker 사용하기를 참고하세요.
Docker 소켓 바인딩 사용하기
CI/CD 작업에서 Docker 명령을 사용하려면 /var/run/docker.sock을 빌드 컨테이너에 바인드 마운트할 수 있어요. 그러면 이미지 컨텍스트에서 Docker를 사용할 수 있어요.
Docker 소켓을 바인딩하면 docker:24.0.5-dind를 서비스로 사용할 수 없어요. 볼륨 바인딩은 서비스에도 영향을 주어 서로 호환되지 않게 만들어요.
Docker 실행기와 Docker 소켓 바인딩 사용하기
Docker 실행기로 Docker 소켓을 마운트하려면 [runners.docker] 섹션의 Volumes에 "/var/run/docker.sock:/var/run/docker.sock"을 추가하세요.
- 러너를 등록하는 동안
/var/run/docker.sock을 마운트하려면 다음 옵션을 포함하세요.sudo gitlab-runner register \ --non-interactive \ --url "https://gitlab.com/" \ --registration-token REGISTRATION_TOKEN \ --executor "docker" \ --description "docker-runner" \ --tag-list "socket-binding-docker-runner" \ --docker-image "docker:24.0.5-cli" \ --docker-volumes "/var/run/docker.sock:/var/run/docker.sock" - 이전 명령은 다음 예시와 유사한
config.toml항목을 만들어요.[[runners]] url = "https://gitlab.com/" token = RUNNER_TOKEN executor = "docker" [runners.docker] tls_verify = false image = "docker:24.0.5-cli" privileged = false disable_cache = false volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"] [runners.cache] Insecure = false - 작업 스크립트에서 Docker를 사용하세요.
default: image: docker:24.0.5-cli before_script: - docker info build: stage: build tags: - socket-binding-docker-runner script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
Kubernetes 실행기와 Docker 소켓 바인딩 사용하기
Kubernetes 실행기로 Docker 소켓을 마운트하려면 [[runners.kubernetes.volumes.host_path]] 섹션의 Volumes에 "/var/run/docker.sock"을 추가하세요.
- 볼륨 마운트를 지정하려면 Helm 차트를 사용해
values.yml파일을 업데이트하세요.runners: tags: "socket-binding-kubernetes-runner" config: | [[runners]] [runners.kubernetes] image = "ubuntu:20.04" privileged = false [runners.kubernetes] [[runners.kubernetes.volumes.host_path]] host_path = '/var/run/docker.sock' mount_path = '/var/run/docker.sock' name = 'docker-sock' read_only = true - 작업 스크립트에서 Docker를 사용하세요.
default: image: docker:24.0.5-cli before_script: - docker info build: stage: build tags: - socket-binding-kubernetes-runner script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
Docker 소켓 바인딩의 알려진 이슈
Docker 소켓 바인딩을 사용하면 privileged 모드에서 Docker를 실행하지 않게 돼요. 하지만 이 방법의 영향은 다음과 같아요.
- Docker 데몬을 공유하면 컨테이너의 보안 메커니즘을 사실상 비활성화하고 호스트를 권한 상승에 노출시켜요. 이는 컨테이너 탈출(container breakout)을 유발할 수 있어요. 예를 들어 프로젝트에서
docker rm -f $(docker ps -a -q)를 실행하면 GitLab Runner 컨테이너를 제거해요. - 동시 작업이 동작하지 않을 수 있어요. 테스트가 특정 이름으로 컨테이너를 만들면 서로 충돌할 수 있어요.
- Docker 명령으로 생성된 컨테이너는 러너의 자식이 아니라 형제(sibling)예요. 이로 인해 워크플로에 복잡함이 생길 수 있어요.
- 소스 저장소의 파일과 디렉터리를 컨테이너로 공유하는 것이 예상대로 동작하지 않을 수 있어요. 볼륨 마운팅은 빌드 컨테이너가 아니라 호스트 머신의 컨텍스트에서 수행돼요. 예를 들어:
docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests - Docker-in-Docker 실행기를 사용할 때처럼
docker:24.0.5-dind서비스를 포함할 필요는 없어요.default: image: docker:24.0.5-cli before_script: - docker info build: stage: build script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
CodeClimate을 사용한 Code Quality 검사 같은 복잡한 Docker-in-Docker 설정은 올바른 실행을 위해 호스트와 컨테이너 경로를 일치시켜야 해요. 자세한 내용은 CodeClimate 기반 검사를 위한 프라이빗 러너 사용을 참고하세요.
Docker 파이프 바인딩 사용하기
Windows 컨테이너는 Windows Server 커널과 유저랜드(Windows Server Core 또는 Nano Server)용으로 컴파일된 Windows 실행 파일을 실행해요. Windows 컨테이너를 빌드하고 실행하려면 컨테이너 지원이 있는 Windows 시스템이 필요해요. 자세한 내용은 Windows Containers를 참고하세요.
Windows 컨테이너는 Docker-in-Docker 방식을 지원하지 않기 때문에 컨테이너 안에서 중첩된 Docker Engine을 실행할 수 없어요. Windows 컨테이너 내부에서 Docker 이미지를 빌드하거나 관리하려면 Docker 파이프 바인딩(Docker-outside-of-Docker 또는 DooD라고도 함)을 사용하세요.
Docker 파이프 바인딩에는 보안 영향이 있어요. \\\\.\\pipe\\docker_engine을 바인드 마운트하면 컨테이너가 호스트의 Docker 데몬에 대한 전체 관리 권한을 갖게 돼요. 컨테이너 내부의 프로세스는 다른 컨테이너를 시작·중지하고, 이미지를 관리하며, 호스트 시스템에서 권한 상승된 권한을 얻을 수 있어요.
Docker 파이프 바인딩을 사용하려면 호스트 Windows Server 운영 체제에 Docker Engine을 설치하고 실행해야 해요. 자세한 내용은 Windows Server에 Docker Community Edition(CE) 설치를 참고하세요.
Windows 기반 컨테이너 CI/CD 작업에서 Docker 명령을 사용하려면 \\\\.\\pipe\\docker_engine을 실행기 컨테이너에 바인드 마운트할 수 있어요. 그러면 이미지 컨텍스트에서 Docker를 사용할 수 있어요.
Windows의 Docker 파이프 바인딩은 Linux의 Docker 소켓 바인딩과 유사해요. Docker 파이프 바인딩의 알려진 이슈는 Docker 소켓 바인딩의 알려진 이슈와 유사해요.
Docker 파이프 바인딩 사용의 필수 전제 조건은 호스트 Windows Server 운영 체제에 Docker Engine이 설치되고 실행되고 있어야 한다는 것이에요. Windows Server에 Docker Community Edition(CE) 설치를 참고하세요.
Docker 실행기와 Docker 파이프 바인딩 사용하기
Docker 실행기를 사용해 Windows 기반 컨테이너에서 작업을 실행할 수 있어요.
Docker 실행기로 Docker 파이프를 마운트하려면 [runners.docker] 섹션의 Volumes에 "\\\\.\\pipe\\docker_engine:\\\\.\\pipe\\docker_engine"을 추가하세요.
- 러너를 등록하는 동안
\\\\.\\pipe\\docker_engine을 마운트하려면 다음 옵션을 포함하세요..\gitlab-runner.exe register \ --non-interactive \ --url "https://gitlab.com/" \ --registration-token REGISTRATION_TOKEN \ --executor "docker-windows" \ --description "docker-windows-runner" --tag-list "docker-windows-runner" \ --docker-image "docker:25-windowsservercore-ltsc2022" \ --docker-volumes "\\\\.\\pipe\\docker_engine:\\\\.\\pipe\\docker_engine" - 이전 명령은 다음 예시와 유사한
config.toml항목을 만들어요.[[runners]] url = "https://gitlab.com/" token = RUNNER_TOKEN executor = "docker-windows" [runners.docker] tls_verify = false image = "docker:25-windowsservercore-ltsc2022" privileged = false disable_cache = false volumes = ["\\\\.\\pipe\\docker_engine:\\\\.\\pipe\\docker_engine"] - 작업 스크립트에서 Docker를 사용하세요.
default: image: docker:25-windowsservercore-ltsc2022 before_script: - docker version - docker info build: stage: build tags: - docker-windows-runner script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
Kubernetes 실행기와 Docker 파이프 바인딩 사용하기
Kubernetes 실행기를 사용해 Windows 기반 컨테이너에서 작업을 실행할 수 있어요.
Kubernetes 실행기를 Windows 기반 컨테이너에 사용하려면 Kubernetes 클러스터에 Windows 노드를 포함해야 해요. 자세한 내용은 Kubernetes의 Windows 컨테이너를 참고하세요.
Linux 환경에서 운영되지만 Windows 노드를 대상으로 하는 러너를 사용할 수 있어요.
Kubernetes 실행기로 Docker 파이프를 마운트하려면 [[runners.kubernetes.volumes.host_path]] 섹션의 Volumes에 "\\.\\pipe\\docker_engine"을 추가하세요.
- 볼륨 마운트를 지정하려면 Helm 차트를 사용해
values.yml파일을 업데이트하세요.runners: tags: "kubernetes-windows-runner" config: | [[runners]] executor = "kubernetes" # The FF_USE_POWERSHELL_PATH_RESOLVER feature flag has to be enabled for PowerShell # to resolve paths for Windows correctly when Runner is operating in a Linux environment # but targeting Windows nodes. [runners.feature_flags] FF_USE_POWERSHELL_PATH_RESOLVER = true [runners.kubernetes] [[runners.kubernetes.volumes.host_path]] host_path = '\\\\.\\pipe\\docker_engine' mount_path = '\\\\.\\pipe\\docker_engine' name = 'docker-pipe' read_only = true [runners.kubernetes.node_selector] "kubernetes.io/arch" = "amd64" "kubernetes.io/os" = "windows" "node.kubernetes.io/windows-build" = "10.0.20348" - 작업 스크립트에서 Docker를 사용하세요.
default: image: docker:25-windowsservercore-ltsc2022 before_script: - docker version - docker info build: stage: build tags: - kubernetes-windows-runner script: - docker build -t my-docker-image . - docker run my-docker-image /script/to/run/tests
AWS EKS Kubernetes 클러스터의 알려진 이슈
dockerd에서 containerd로 마이그레이션할 때 AWS EKS 부트스트랩 스크립트 Start-EKSBootstrap.ps1은 Docker 서비스를 중지하고 비활성화해요. 이 문제를 해결하려면 Windows Server에 Docker Community Edition(CE) 설치 후 다음 스크립트로 Docker 서비스를 이름을 바꾸세요.
Write-Output "Rename the just installed Docker Engine Service from docker to dockerd"
Write-Output "because the Start-EKSBootstrap.ps1 stops and disables the docker Service as part of migration from dockerd to containerd"
Stop-Service -Name docker
dockerd --register-service --service-name dockerd
Start-Service -Name dockerd
Write-Output "Ready to do Docker pipe binding on Windows EKS Node! :-)"
Docker 파이프 바인딩의 알려진 이슈
Docker 파이프 바인딩은 Docker 소켓 바인딩의 알려진 이슈와 동일한 보안 및 격리 이슈를 가져요.
docker:dind 서비스에 레지스트리 미러 활성화
Docker 데몬이 서비스 컨테이너 안에서 시작될 때 기본 구성을 사용해요. 성능 향상과 Docker Hub 속도 제한을 초과하지 않도록 레지스트리 미러를 구성하고 싶을 수 있어요.
.gitlab-ci.yml 파일의 서비스
dind 서비스에 추가 CLI 플래그를 추가해 레지스트리 미러를 설정할 수 있어요.
services:
- name: docker:24.0.5-dind
command: ["--registry-mirror", "https://registry-mirror.example.com"] # Specify the registry mirror to use
GitLab Runner 구성 파일의 서비스
GitLab Runner 관리자라면 command를 지정해 Docker 데몬의 레지스트리 미러를 구성할 수 있어요. dind 서비스는 Docker 또는 Kubernetes 실행기에 정의되어야 해요.
Docker:
[[runners]]
...
executor = "docker"
[runners.docker]
...
privileged = true
[[runners.docker.services]]
name = "docker:24.0.5-dind"
command = ["--registry-mirror", "https://registry-mirror.example.com"]
Kubernetes:
[[runners]]
...
name = "kubernetes"
[runners.kubernetes]
...
privileged = true
[[runners.kubernetes.services]]
name = "docker:24.0.5-dind"
command = ["--registry-mirror", "https://registry-mirror.example.com"]
GitLab Runner 구성 파일의 Docker 실행기
GitLab Runner 관리자라면 모든 dind 서비스에 미러를 사용할 수 있어요. 구성을 업데이트해 볼륨 마운트를 지정하세요.
예를 들어 다음 내용의 /opt/docker/daemon.json 파일이 있다면:
{
"registry-mirrors": [
"https://registry-mirror.example.com"
]
}
config.toml 파일을 업데이트해 파일을 /etc/docker/daemon.json에 마운트하세요. 이렇게 하면 GitLab Runner가 만드는 모든 컨테이너에 파일이 마운트돼요. 구성은 dind 서비스가 감지해요.
[[runners]]
...
executor = "docker"
[runners.docker]
image = "alpine:3.12"
privileged = true
volumes = ["/opt/docker/daemon.json:/etc/docker/daemon.json:ro"]
GitLab Runner 구성 파일의 Kubernetes 실행기
GitLab Runner 관리자라면 모든 dind 서비스에 미러를 사용할 수 있어요. 구성을 업데이트해 ConfigMap 볼륨 마운트를 지정하세요.
예를 들어 다음 내용의 /tmp/daemon.json 파일이 있다면:
{
"registry-mirrors": [
"https://registry-mirror.example.com"
]
}
이 파일의 내용으로 ConfigMap을 만드세요. 다음과 같은 명령으로 할 수 있어요.
kubectl create configmap docker-daemon --namespace gitlab-runner --from-file /tmp/daemon.json
GitLab Runner의 Kubernetes 실행기가 작업 파드를 만들 때 사용하는 네임스페이스를 사용해야 해요.
ConfigMap이 생성된 후 config.toml 파일을 업데이트해 파일을 /etc/docker/daemon.json에 마운트할 수 있어요. 이 업데이트는 GitLab Runner가 만드는 모든 컨테이너에 파일을 마운트해요. dind 서비스가 이 구성을 감지해요.
[[runners]]
...
executor = "kubernetes"
[runners.kubernetes]
image = "alpine:3.12"
privileged = true
[[runners.kubernetes.volumes.config_map]]
name = "docker-daemon"
mount_path = "/etc/docker/daemon.json"
sub_path = "daemon.json"
Docker-in-Docker에서 레지스트리 인증
Docker-in-Docker를 사용할 때는 서비스로 새 Docker 데몬이 시작되므로 표준 인증 방법이 동작하지 않아요. 레지스트리로 인증해야 해요.
Docker 레이어 캐싱
Docker 레이어를 캐시해 빌드를 빠르게 할 수 있어요. 자세한 내용은 Docker-in-Docker 빌드에서 Docker 레이어 캐시하기를 참고하세요.
OverlayFS 드라이버 사용하기
GitLab.com의 인스턴스 러너는 기본적으로 overlay2 드라이버를 사용해요.
기본적으로 docker:dind를 사용할 때 Docker는 vfs 스토리지 드라이버를 사용하는데, 이는 매 실행마다 파일 시스템을 복사해요. 이 디스크 집약적인 작업을 피하려면 overlay2 같은 다른 드라이버를 사용할 수 있어요.
요구 사항
- 최신 커널(가급적
>= 4.2)을 사용하는지 확인하세요. overlay모듈이 로드되었는지 확인하세요.sudo lsmod | grep overlay- 결과가 없으면 모듈이 로드되지 않은 거예요. 모듈을 로드하려면 다음을 사용하세요.
sudo modprobe overlay - 모듈이 로드되었다면 재부팅 후에도 로드되도록 해야 해요. Ubuntu 시스템에서는
/etc/modules에 다음 줄을 추가해 이를 수행할 수 있어요.overlay
프로젝트별 OverlayFS 드라이버 사용
.gitlab-ci.yml에서 DOCKER_DRIVER CI/CD 변수를 사용해 각 프로젝트에 대해 개별적으로 드라이버를 활성화할 수 있어요.
variables:
DOCKER_DRIVER: overlay2
모든 프로젝트에 OverlayFS 드라이버 사용
자체 러너를 사용한다면 config.toml 파일의 [[runners]] 섹션에서 DOCKER_DRIVER 환경 변수를 설정해 모든 프로젝트에 대해 드라이버를 활성화할 수 있어요.
environment = ["DOCKER_DRIVER=overlay2"]
여러 러너를 실행 중이라면 모든 구성 파일을 수정해야 해요.
러너 구성과 OverlayFS 스토리지 드라이버 사용에 대해 더 읽어보세요.
Docker 대체 방법
러너에서 privileged 모드를 활성화하지 않고도 컨테이너 이미지를 빌드할 수 있어요.
Buildah 예시
GitLab CI/CD에서 Buildah를 사용하려면 다음 실행기 중 하나가 있는 러너가 필요해요.
이 예시에서 Buildah를 사용해:
- Docker 이미지를 빌드합니다.
- GitLab 컨테이너 레지스트리로 푸시합니다.
마지막 단계에서 Buildah는 프로젝트 루트 디렉터리의 Dockerfile을 사용해 Docker 이미지를 빌드해요. 마지막으로 프로젝트의 컨테이너 레지스트리로 이미지를 푸시해요.
build:
stage: build
image: quay.io/buildah/stable
variables:
# Use vfs with buildah. Docker offers overlayfs as a default, but Buildah
# cannot stack overlayfs on top of another overlayfs filesystem.
STORAGE_DRIVER: vfs
# Write all image metadata in the docker format, not the standard OCI format.
# Newer versions of docker can handle the OCI format, but older versions, like
# the one shipped with Fedora 30, cannot handle the format.
BUILDAH_FORMAT: docker
FQ_IMAGE_NAME: "$CI_REGISTRY_IMAGE/test"
before_script:
# GitLab container registry credentials taken from the
# [predefined CI/CD variables](../variables/_index.md#predefined-cicd-variables)
# to authenticate to the registry.
- echo "$CI_REGISTRY_PASSWORD" | buildah login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
script:
- buildah images
- buildah build -t $FQ_IMAGE_NAME
- buildah images
- buildah push $FQ_IMAGE_NAME
OpenShift 클러스터에 배포된 GitLab Runner Operator를 사용 중이라면 rootless 컨테이너에서 Buildah로 이미지 빌드 튜토리얼을 시도해 보세요.
여러 CPU 아키텍처용 이미지를 빌드하려면 Buildah로 멀티플랫폼 빌드를 참고하세요.
GitLab 컨테이너 레지스트리 사용하기
Docker 이미지를 빌드한 후 GitLab 컨테이너 레지스트리로 푸시할 수 있어요.
문제 해결
open //./pipe/docker_engine: The system cannot find the file specified
마운트된 Docker 파이프에 접근하는 PowerShell 스크립트에서 docker 명령을 실행할 때 다음 오류가 나타날 수 있어요.
PS C:\> docker version
Client:
Version: 25.0.5
API version: 1.44
Go version: go1.21.8
Git commit: 5dc9bcc
Built: Tue Mar 19 15:06:12 2024
OS/Arch: windows/amd64
Context: default
error during connect: this error may indicate that the docker daemon is not running: Get "http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.44/version": open //./pipe/docker_engine: The system cannot find the file specified.
이 오류는 Windows EKS 노드에서 Docker Engine이 실행되고 있지 않아 Windows 기반 실행기 컨테이너에서 Docker 파이프 바인딩을 사용할 수 없음을 나타내요.
문제를 해결하려면 Kubernetes 실행기와 Docker 파이프 바인딩 사용하기에 설명된 해결 방법을 사용하세요.
더 알아보기
Docker-in-Docker의 상세 구성은 Docker-in-Docker 사용하기 문서를, Docker 데몬 없이 이미지를 빌드하는 대체 방법은 Docker 대체 방법에서 더 자세히 볼 수 있어요. 보안 측면에서 privileged 모드 사용을 검토할 때는 Docker 공식 보안 문서도 함께 참고하세요.