Docker Builds in Jenkins using Kaniko for CI/CD Pipelines
Introduction
Jenkins를 통해 GitOps 기반으로 Docker image가 빌드되고 Docker Hub에 push 되게 하려면 아래와 같은 여러 방식이 존재한다.
| Docker-in-Docker (DinD) | Docker Outside of Docker (DooD) | Kaniko | |
|---|---|---|---|
| Definition | Container 내부에 별도의 Docker daemon 실행 | Host의 Docker daemon을 사용 | Docker daemon 없이 container image 빌드 |
| Security | 더 높은 격리 제공, 하지만 보안상의 우려도 존재 | Host Docker와 직접적인 상호작용으로 보안상 취약할 수 있음 | Docker daemon 없이 작동하여 보안 강화 |
| Performance | 성능 overhead 가능성 | 일반적으로 더 나은 성능 | Docker daemon을 사용하지 않기 때문에 성능이 최적화될 수 있음 |
| Complexity | 설정과 관리가 더 복잡 | 상대적으로 간단한 설정 | 환경 설정에 따라 다르나, 일반적으로 설정이 간단함 |
| Used Tools | Jib, Buildah | Docker CLI, Docker Compose | Kaniko CLI, Kubernetes와의 통합 |
| Suitable Use Cases | 격리된 환경에서의 독립적인 container 관리 | 간단한 CI/CD pipeline 구성 | Cloud 환경 및 Kubernetes에서의 image build |
Kaniko Setup
1 | echo -n '${DOCKER_HUB_USER}:${DOCKER_HUB_TOKEN}' | base64 |
여기서 출력된 결과 (${DOCKER_HUB_BASE64})를 아래와 같이 JSON file로 작성한다.
1 | { |
아래와 같이 해당 JSON file을 다시 base64로 encoding 한다.
1 | cat config.json | base64 |
여기서 출력된 결과 (${CONFIG_JSON_BASE64})를 아래와 같이 Kubernetes secret으로 적용한다.
1 | apiVersion: v1 |
Test를 위해 아래와 같은 Jenkinsfile을 작성했고 Docker image build 및 Docker Hub로 잘 push 되는 것을 확인했다.
1 | pipeline { |
From GitHub Actions to Jenkins
기존에는 아래와 같이 GitHub Actions를 통해 GitOps 기반의 Docker CI/CD를 수행했다.
GitHub Actions
1 | name: CI |
특정 이름으로 시작하는 directory (airflow-*)에 변경이 존재하면 build 후 Docker Hub에 push하는 workflow다.
이를 Jenkinsfile로 구현하면 아래와 같다.
Jenkinsfile
1 | void setBuildStatus(String message, String state, String context) { |

- Detect Changes
git diff --name-only HEAD^ HEAD | grep -E '${regex}' | xargs -r -n 1 dirname | uniq"으로 변경 사항이 존재하는 목표 directory를 불러온다.
- Kaniko
curl -s "https://hub.docker.com/v2/repositories/${DOCKERHUB_USERNAME}/${imageName}/tags/?page_size=100"를 통해 현재 Docker Hub에 존재하는 version의 이름을 불러온다.- Tag가
v${major}.${minor}.${patch}의 format을 따르지 않거나 Docker Hub에 존재하지 않는다면 push할 tag를v1.0.0으로 설정한다. - Tag가
v${major}.${minor}.${patch}의 format을 따르면v${major}.${minor}.${patch + 1}으로 push한다.
- Tag가
sh "/kaniko/executor --context ${dir} --dockerfile ${dir}/Dockerfile --destination ${DOCKERHUB_USERNAME}/${imageName}:${newTag} --cleanup && mkdir -p /workspace"으로 다음 version의 image를 push한다.sh "/kaniko/executor --context ${dir} --dockerfile ${dir}/Dockerfile --destination ${DOCKERHUB_USERNAME}/${imageName}:latest --cleanup && mkdir -p /workspace"으로latesttag를 사용할 수 있게 동일한 image를 push한다.
Errors
Error: unknown command “/kaniko/executor –context . –dockerfile Dockerfile –no-push” for “executor”
Jenkinsfile
1 | pipeline { |
위와 같이 Jenkinsfile을 설정하면 항상 오류가 발생한다.
그 이유는 agent pod를 생성할 때 kaniko container의 args에 --dockerfile로 경로를 지정해야하는데 multibranch pipeline은 agent 생성 후 ~ stage 시작 전에 원격 저장소에서 Dockerfile을 포함한 code들을 불러오기 때문에 아래와 같은 오류가 발생한다.
1 | Error: unknown command "/kaniko/executor --context . --dockerfile Dockerfile --no-push" for "executor" |
Cleanup
아래 Jenkinsfile과 같이 Kaniko를 이용해 한 개의 Dockerfile을 build 하고 사용하면 잘 작동하지만, 여러 Dockerfile들을 build 하고 사용해보면 오류가 발생한다.
Jenkinsfile
1 | void setBuildStatus(String message, String state, String context) { |
Build에 실패하면 아래와 같은 log들이 출력되며 성공하더라도 두 번째 이후로 build된 image는 RUN pip install -r requirements.txt와 같은 명령어가 제대로 적용되지 않아 정상적으로 사용할 수 없다.
Jenkins error logs
1 | Traceback (most recent call last): |
여러 image를 build하는 과정에서 layer 단위의 정보를 다시 가져와서 생기는 오류로 파악된다.
따라서 이와 같이 --cleanup && mkdir -p /workspace를 추가해야한다.