BuildKit으로 Docker 이미지 빌드
GitLab v19.2Offering: GitLab.com, GitLab Self-Managed, GitLab Dedicated
BuildKit은 Docker에서 사용하는 빌드 엔진으로 멀티 플랫폼 빌드와 빌드 캐싱을 제공합니다. BuildKit은 Docker 이미지를 빌드하기 위한 다음 방법을 제공합니다: 독립 실행형 모드의 BuildKit은 Docker 데몬 의존성 없이 rootless 이미지 빌드를 제공합니다.
BuildKit은 Docker에서 사용하는 빌드 엔진으로 멀티 플랫폼 빌드와 빌드 캐싱을 제공합니다.
BuildKit 방법#
BuildKit은 Docker 이미지를 빌드하기 위한 다음 방법을 제공합니다:
| 방법 | 보안 요구 사항 | 명령어 | 사용 시기 |
|---|---|---|---|
| BuildKit rootless | 권한 있는 컨테이너 불필요 | buildctl-daemonless.sh |
최대 보안 또는 Kaniko 대체 |
| Docker Buildx | docker:dind 필요 |
docker buildx |
익숙한 Docker 워크플로우 |
| Native BuildKit | docker:dind 필요 |
buildctl |
고급 BuildKit 제어 |
사전 요구 사항#
- Docker executor가 있는 GitLab Runner
- Docker Buildx를 사용하려면 Docker 19.03 이상
Dockerfile이 있는 프로젝트
BuildKit rootless#
독립 실행형 모드의 BuildKit은 Docker 데몬 의존성 없이 rootless 이미지 빌드를 제공합니다. 이 방법은 권한 있는 컨테이너를 완전히 없애고 Kaniko 빌드의 직접적인 대안을 제공합니다.
rootless 빌드에는 여전히 BuildKit이 사용자 네임스페이스와 마운트 지점을 생성하는 데 사용하는 시스템 호출을 허용하는 러너가 필요합니다. GitLab.com의 호스팅된 러너는 권한 있는 모드로 실행되므로 이러한 호출을 허용하며 추가 구성이 필요하지 않습니다. 권한 있는 모드 없이 Docker executor를 사용하는 자체 관리형 러너에서는 빌드가 권한 오류로 실패할 수 있습니다. 자세한 내용은 rootless 빌드가 권한 오류로 실패를 참조하세요. 러너 보안 설정을 변경할 수 없는 경우 대신 rootless Buildah를 사용하여 이미지를 빌드하세요.
다른 방법과의 주요 차이점:
moby/buildkit:rootless이미지 사용- rootless 작동을 위한
BUILDKITD_FLAGS: --oci-worker-no-process-sandbox포함 - BuildKit 데몬을 자동으로 관리하는
buildctl-daemonless.sh사용 - Docker 데몬 또는 권한 있는 컨테이너 의존성 없음
- 수동 레지스트리 인증 설정 필요
컨테이너 레지스트리 인증#
GitLab CI/CD는 사전 정의된 변수를 통해 GitLab 컨테이너 레지스트리에 대한 자동 인증을 제공합니다. BuildKit rootless의 경우 Docker 구성 파일을 수동으로 생성해야 합니다.
GitLab 컨테이너 레지스트리 인증#
GitLab은 다음과 같은 사전 정의된 변수를 자동으로 제공합니다:
CI_REGISTRY: 레지스트리 URLCI_REGISTRY_USER: 레지스트리 사용자 이름CI_REGISTRY_PASSWORD: 레지스트리 비밀번호
rootless 빌드에 대한 인증을 구성하려면 작업에 before_script 구성을 추가합니다. 예를 들어:
before_script:
- mkdir -p ~/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
여러 레지스트리 인증#
추가 컨테이너 레지스트리를 인증하려면 before_script 섹션에 인증 항목을 결합합니다. 예를 들어:
before_script:
- mkdir -p ~/.docker
- |
echo "{
\"auths\": {
\"${CI_REGISTRY}\": {
\"auth\": \"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"
},
\"docker.io\": {
\"auth\": \"$(printf "%s:%s" "${DOCKER_HUB_USER}" "${DOCKER_HUB_PASSWORD}" | base64 | tr -d '\n')\"
}
}
}" > ~/.docker/config.json
의존성 프록시 인증#
GitLab 의존성 프록시를 통해 이미지를 가져오려면 before_script 섹션에 인증을 구성합니다. 예를 들어:
before_script:
- mkdir -p ~/.docker
- |
echo "{
\"auths\": {
\"${CI_REGISTRY}\": {
\"auth\": \"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"
},
\"$(echo -n $CI_DEPENDENCY_PROXY_SERVER | awk -F[:] '{print $1}')\": {
\"auth\": \"$(printf "%s:%s" ${CI_DEPENDENCY_PROXY_USER} "${CI_DEPENDENCY_PROXY_PASSWORD}" | base64 | tr -d '\n')\"
}
}
}" > ~/.docker/config.json
자세한 내용은 CI/CD 내에서 인증을 참조하세요.
rootless 모드에서 이미지 빌드#
Docker 데몬 의존성 없이 이미지를 빌드하려면 이 예시와 유사한 작업을 추가합니다:
build-rootless:
image:
name: moby/buildkit:rootless
entrypoint: [""]
stage: build
variables:
BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
before_script:
- mkdir -p ~/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
script:
- |
buildctl-daemonless.sh build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true
entrypoint: [""] 재정의는 필수입니다.
기본적으로 moby/buildkit:rootless 이미지는 BuildKit 데몬을 장기 실행 서비스로 시작합니다.
이 재정의가 없으면 작업이 빌드 명령 대신 데몬을 실행하며, 작업이 시간 초과될 때까지 멈춰 있습니다.
rootless 모드에서 멀티 플랫폼 이미지 빌드#
rootless 모드에서 여러 아키텍처용 이미지를 빌드하려면 대상 플랫폼을 지정하도록 작업을 구성합니다. 예를 들어:
build-multiarch-rootless:
image:
name: moby/buildkit:rootless
entrypoint: [""]
stage: build
variables:
BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
before_script:
- mkdir -p ~/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
script:
- |
buildctl-daemonless.sh build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--opt platform=linux/amd64,linux/arm64 \
--output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true
rootless 모드에서 캐싱 사용#
후속 빌드를 더 빠르게 하기 위한 레지스트리 기반 캐싱을 활성화하려면 빌드 작업에서 캐시 가져오기 및 내보내기를 구성합니다. 예를 들어:
build-cached-rootless:
image:
name: moby/buildkit:rootless
entrypoint: [""]
stage: build
variables:
BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
CACHE_IMAGE: $CI_REGISTRY_IMAGE:cache
before_script:
- mkdir -p ~/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
script:
- |
buildctl-daemonless.sh build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--export-cache type=registry,ref=$CACHE_IMAGE \
--import-cache type=registry,ref=$CACHE_IMAGE \
--output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true
rootless 모드에서 레지스트리 미러 사용#
레지스트리 미러는 이미지 가져오기를 더 빠르게 하고 속도 제한이나 네트워크 제한을 해결하는 데 도움이 됩니다.
레지스트리 미러를 구성하려면 미러 엔드포인트를 지정하는 buildkit.toml 파일을 생성합니다. 예를 들어:
build-mirror-rootless:
image:
name: moby/buildkit:rootless
entrypoint: [""]
stage: build
variables:
BUILDKITD_FLAGS: --oci-worker-no-process-sandbox --config /tmp/buildkit.toml
before_script:
- mkdir -p ~/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
- cat <<'EOF' > /tmp/buildkit.toml
[registry."docker.io"]
mirrors = ["mirror.example.com"]
EOF
script:
- |
buildctl-daemonless.sh build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true
이 예시에서 mirror.example.com을 레지스트리 미러 URL로 교체합니다.
프록시 설정 구성#
GitLab Runner가 HTTP(S) 프록시 뒤에서 작동하는 경우 작업의 변수로 프록시 설정을 구성합니다. 예를 들어:
build-behind-proxy:
image:
name: moby/buildkit:rootless
entrypoint: [""]
stage: build
variables:
BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
http_proxy: <your-proxy>
https_proxy: <your-proxy>
no_proxy: <your-no-proxy>
before_script:
- mkdir -p ~/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
script:
- |
buildctl-daemonless.sh build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--build-arg http_proxy=$http_proxy \
--build-arg https_proxy=$https_proxy \
--build-arg no_proxy=$no_proxy \
--output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true
이 예시에서 <your-proxy>와 <your-no-proxy>를 프록시 구성으로 교체합니다.
커스텀 인증서 추가#
커스텀 CA 인증서를 사용하는 레지스트리에 푸시하려면 데몬이 시작되기 전에 BuildKit 구성 파일에 인증서를 구성합니다. 예를 들어:
build-with-custom-certs:
image:
name: moby/buildkit:rootless
entrypoint: [""]
stage: build
variables:
BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
before_script:
- mkdir -p "$HOME/.docker"
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > "$HOME/.docker/config.json"
- REG_HOST="${CI_REGISTRY%%/*}"
- mkdir -p "$HOME/.config/buildkit/certs/$REG_HOST"
- echo "$CA_CERT" > "$HOME/.config/buildkit/certs/$REG_HOST/ca.pem"
- |
cat > "$HOME/.config/buildkit/buildkitd.toml" << EOT
[registry."$REG_HOST"]
ca = ["$HOME/.config/buildkit/certs/$REG_HOST/ca.pem"]
EOT
- export SSL_CERT_FILE="$HOME/.config/buildkit/certs/$REG_HOST/ca.pem"
script:
- |
buildctl-daemonless.sh build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true
이 예시에서:
-
REG_HOST="${CI_REGISTRY%%/*}"는 레지스트리 URL에서 호스트 이름을 추출합니다. -
buildkitd.toml은 대상 레지스트리에 대해 CA 인증서를 신뢰하도록 BuildKit을 구성합니다. BuildKit은$HOME/.config/buildkit/에서 이 파일을 자동으로 검색합니다. -
SSL_CERT_FILE은 BuildKit 데몬이 완전히 초기화되기 전에 이루어지는 TLS 연결을 처리하기 위해buildkitd.toml과 함께 추가로 필요합니다.
루트 및 중간 인증서를 포함한 전체 인증서 체인을 담은 CA_CERT CI/CD 변수를 추가합니다.
PEM 인증서에는 개행 문자가 포함되므로 CA_CERT 값은 마스킹할 수 없습니다.
값을 마스킹하려면 대신 파일 유형 변수를 사용하고 before_script에서 echo "$CA_CERT"를 cat "$CA_CERT"로 교체합니다.
대상 레지스트리가 GitLab 인스턴스와 동일한 인증 기관을 사용하고 러너가 tls-ca-file로 구성되어 있는 경우, CA_CERT 변수 대신 사전 정의된 CI_SERVER_TLS_CA_FILE 변수를 참조할 수 있습니다.
Kaniko에서 BuildKit으로 마이그레이션#
BuildKit rootless는 Kaniko의 안전한 대안으로, 권한 있는 컨테이너 없이 향상된 성능, 더 나은 캐싱, 강화된 보안 기능을 제공합니다.
구성 업데이트#
BuildKit rootless 방법을 사용하도록 기존 Kaniko 구성을 업데이트합니다. 예를 들어:
Kaniko 이전:
build:
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- /kaniko/executor
--context $CI_PROJECT_DIR
--dockerfile $CI_PROJECT_DIR/Dockerfile
--destination $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
BuildKit rootless 이후:
build:
image:
name: moby/buildkit:rootless
entrypoint: [""]
variables:
BUILDKITD_FLAGS: --oci-worker-no-process-sandbox
before_script:
- mkdir -p ~/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
script:
- |
buildctl-daemonless.sh build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true
커스텀 CA 인증서#
Kaniko 작업에서 커스텀 CA 인증서를 사용했다면 BuildKit rootless에 해당 인증서를 명시적으로 구성해야 합니다.
Kaniko와 달리 moby/buildkit:rootless 이미지에는 시스템 인증서 저장소가 포함되어 있지 않습니다.
데몬이 시작되기 전에 BuildKit 구성 파일에 CA 인증서를 구성해야 합니다.
커스텀 CA 인증서 구성을 BuildKit rootless로 마이그레이션하려면:
-
루트 및 중간 인증서를 포함한 전체 인증서 체인을
CA_CERT라는 CI/CD 변수에 저장합니다. -
buildkitd.toml파일과SSL_CERT_FILE환경 변수를 사용하도록 작업 구성을 업데이트합니다. 전체 예시는 커스텀 인증서 추가를 참조하세요.
대안적인 BuildKit 방법#
rootless 빌드가 필요하지 않은 경우 BuildKit은 docker:dind 서비스가 필요하지만 익숙한 워크플로우나 고급 기능을 제공하는 추가 방법을 제공합니다.
Docker Buildx#
Docker Buildx는 익숙한 명령어 구문을 유지하면서 BuildKit 기능으로 Docker 빌드 기능을 확장합니다. 이 방법에는 docker:dind 서비스가 필요합니다.
기본 이미지 빌드#
Buildx로 Docker 이미지를 빌드하려면 docker:dind 서비스로 작업을 구성하고 buildx 빌더를 생성합니다. 예를 들어:
variables:
DOCKER_TLS_CERTDIR: "/certs"
build-image:
image: docker:cli
services:
- docker:dind
stage: build
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker buildx create --use --driver docker-container --name builder
- docker buildx inspect --bootstrap
script:
- docker buildx build --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA --push .
after_script:
- docker buildx rm builder
멀티 플랫폼 이미지 빌드#
멀티 플랫폼 빌드는 단일 빌드 명령으로 여러 아키텍처용 이미지를 생성합니다. 결과 매니페스트는 여러 아키텍처를 지원하며, Docker는 각 배포 대상에 적합한 이미지를 자동으로 선택합니다.
여러 아키텍처용 이미지를 빌드하려면 대상 아키텍처를 지정하는 --platform 플래그를 추가합니다. 예를 들어:
variables:
DOCKER_TLS_CERTDIR: "/certs"
build-multiplatform:
image: docker:cli
services:
- docker:dind
stage: build
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker buildx create --use --driver docker-container --name multibuilder
- docker buildx inspect --bootstrap
script:
- docker buildx build
--platform linux/amd64,linux/arm64
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
--push .
after_script:
- docker buildx rm multibuilder
빌드 캐싱 사용#
레지스트리 기반 캐싱은 빌드 레이어를 컨테이너 레지스트리에 저장하여 빌드 간 재사용합니다.
mode=max 옵션은 모든 레이어를 캐시로 내보내
후속 빌드에 대한 최대 재사용 가능성을 제공합니다.
빌드 캐싱을 사용하려면 빌드 명령에 캐시 옵션을 추가합니다. 예를 들어:
variables:
DOCKER_TLS_CERTDIR: "/certs"
CACHE_IMAGE: $CI_REGISTRY_IMAGE:cache
build-with-cache:
image: docker:cli
services:
- docker:dind
stage: build
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker buildx create --use --driver docker-container --name cached-builder
- docker buildx inspect --bootstrap
script:
- docker buildx build
--cache-from type=registry,ref=$CACHE_IMAGE
--cache-to type=registry,ref=$CACHE_IMAGE,mode=max
--tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
--push .
after_script:
- docker buildx rm cached-builder
Native BuildKit#
빌드 프로세스를 더 세밀하게 제어하기 위해 네이티브 BuildKit buildctl 명령을 사용합니다.
이 방법에는 docker:dind 서비스가 필요합니다.
BuildKit을 직접 사용하려면 BuildKit 이미지와 docker:dind 서비스로 작업을 구성합니다. 예를 들어:
variables:
DOCKER_TLS_CERTDIR: "/certs"
build-with-buildkit:
image: moby/buildkit:latest
services:
- docker:dind
stage: build
before_script:
- mkdir -p ~/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > ~/.docker/config.json
script:
- |
buildctl build \
--frontend dockerfile.v0 \
--local context=. \
--local dockerfile=. \
--output type=image,name=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA,push=true
문제 해결#
BuildKit으로 이미지를 빌드할 때 다음과 같은 문제가 발생할 수 있습니다.
인증 오류로 빌드 실패#
레지스트리 인증 오류가 발생하는 경우:
CI_REGISTRY_USER및CI_REGISTRY_PASSWORD변수를 사용할 수 있는지 확인합니다.- 대상 레지스트리에 푸시 권한이 있는지 확인합니다.
- 외부 레지스트리의 경우 프로젝트의 CI/CD 변수에 인증 자격 증명이 올바르게 구성되어 있는지 확인합니다.
rootless 빌드가 권한 오류로 실패#
rootless 빌드가 권한 오류로 실패하는 경우 다음을 확인합니다:
BUILDKITD_FLAGS: --oci-worker-no-process-sandbox가 설정되어 있는지 확인합니다.- GitLab Runner에 충분한 리소스가 할당되어 있는지 확인합니다.
Dockerfile에 권한 있는 작업이 시도되지 않는지 확인합니다.
Kubernetes 러너에서는 AppArmor와 관련된 마운트 권한 오류도 rootless 컨테이너를 차단할 수 있습니다. 자세한 내용은 Kubernetes executor의 AppArmor 마운트 권한 오류를 참조하세요.
실패가 다음 오류와 일치하는 경우, 러너 보안 정책이 rootless BuildKit에 필요한 시스템 호출을 차단하고 있는 것입니다.
오류: fork/exec /proc/self/exe: operation not permitted#
권한 있는 모드 없이 Docker executor를 사용하는 러너에서는 다음 오류 중 하나가 발생할 수 있습니다:
could not connect to unix:///run/user/1000/buildkit/buildkitd.sock after 10 trials
[rootlesskit:parent] error: failed to start the child: fork/exec /proc/self/exe: operation not permitted
이 문제는 러너 seccomp 프로필이 rootless BuildKit에 필요한 시스템 호출을 차단하기 때문에 발생합니다. GitLab.com의 호스팅된 러너는 권한 있는 모드로 실행되므로 영향을 받지 않습니다.
자체 관리형 러너에서 이 문제를 해결하려면 BuildKit에 필요한 시스템 호출만 허용하도록 Docker executor의 security_opt 설정을 구성합니다.
security_opt를 seccomp:unconfined로 설정하지 마세요. 이렇게 하면 오류는 해결되지만 컨테이너의 기본 seccomp 프로필이 비활성화되어 위험한 시스템 호출에 대한 보호가 제거되고 격리가 약화됩니다. 대신 필요한 호출만 허용하는 커스텀 seccomp 프로필을 사용하거나 rootless Buildah로 이미지를 빌드하세요.
오류: invalid local: stat path/to/image/Dockerfile: not a directory#
invalid local: stat path/to/image/Dockerfile: not a directory 오류가 발생할 수 있습니다.
이 문제는 --local dockerfile= 매개변수에 디렉토리 경로 대신 파일 경로를 지정할 때 발생합니다. BuildKit은 Dockerfile이라는 파일이 포함된 디렉토리 경로를 예상합니다.
이 문제를 해결하려면 전체 파일 경로 대신 디렉토리 경로를 사용합니다. 예를 들어:
- 사용:
--local dockerfile=path/to/image - 대신:
--local dockerfile=path/to/image/Dockerfile
멀티 플랫폼 빌드 실패#
멀티 플랫폼 빌드 문제:
Dockerfile의 기본 이미지가 대상 아키텍처를 지원하는지 확인합니다.- 아키텍처별 종속성이 모든 대상 플랫폼에서 사용 가능한지 확인합니다.
- 아키텍처별 로직을 위해
Dockerfile에서 조건부 구문 사용을 고려합니다.