cloudwithbass

[Jenkins] 트러블 슈팅: 젠킨스 도커인도커(DinD) 사용기 본문

Docker and Jenkins

[Jenkins] 트러블 슈팅: 젠킨스 도커인도커(DinD) 사용기

여영클 2024. 7. 23. 15:00

목차


     

    • 젠킨스 파이프라인을 빌드하던 도중 파이프라인 내에서 Docker 명령이 수행되지 않는 에러가 발생했습니다.
    • 이번 포스팅에선 이 에러를 해결하려는 3일 동안의 과정을 담았습니다.
    • 도커로 젠킨스 에이전트를 구성하는 방법은 두 가지가 있습니다. 
      • 영구 도커 에이전트 방법 (Node)
        젠킨스 에이전트가 젠킨스 마스터 컨테이너에 영구적으로 연결된 상태로 유지됩니다. 에이전트가 한 번 설정되면 에이전트는 수동으로 조작하지 않는 한 계속해서 활성 상태로 유지됩니다.

      • 동적 프로비저닝 도커 에이전트 (Cloud)
        파이프라인 빌드를 시작할 때 젠킨스 에이전트를 생성하고, 빌드가 끝나면 자동으로 에이전트를 삭제합니다. 

    저는 첫 번째 구성 방법에선 이 에러를 해결했으나, 두 번째 방법에선 해결에 실패했습니다. 각각 3번, 4번 목차에서 확인하실 수 있습니다.


    1. 에러 상황

    Jenkins 파이프라인에서 docker 명령 실행 시 not found, Is the docker daemon running? 에러 발생

     

    정확히는 아래 Jenkinsfile의 Docker build stage에서 발생합니다.

    pipeline{
        agent any
        stages{
            stage("Complie"){
    			//생략
            }
            stage("Unit test"){
    			//생략
            }
    		stage("Code coverage"){
    			//생략
        	}
    		stage("Static code analysis"){
    			//생략
    		}
    		stage("Docker build"){
    	    	steps{
    				sh "docker build -t dminus251/calculator ."
    		    }
    		}
        }	
    }

     


    2. 원인 탐색과 도커 인 도커(DinD, Docker in Docker)

     

    • 이는 젠킨스 에이전트가 도커 명령어를 찾을 수 없어서 생기는 에러입니다.
    • 젠킨스 에이전트 안에는 docker가 설치되어 있지 않기 때문에 docker 명령을 수행할 수 없습니다.

    따라서 젠킨스 에이전트에서 도커를 이용해야 하고, 이를 도커 인 도커라고 합니다.

    도커 인 도커를 구현하는 방법은 두 가지입니다.

    1. 에이전트에 도커를 설치
      Docker 데몬이 컨테이너 내부에서 실행되므로 호스트 시스템과의 격리가 뛰어나지만, 시스템 성능 저하 문제가 발생합니다.
    2. docker.sock을 마운트해서 호스트의 도커 명령 이용하기
      별도의 Docker daemon을 실행하지 않으므로 컨테이너가 가볍지만, privileged 옵션이 필요하므로 보안 취약점이 생깁니다.

    참고: Docker agent 사용을 위해 젠킨스에 Docker pipeline, Docker plugin 플러그인을 설치해야 합니다.


    3. 영구 도커 에이전트에 DinD 환경 구성

    3-1. 에이전트에 도커 설치

    젠킨스의 Nodes에서 도커 노드를 생성합니다.

    이때 이 에이전트를 사용하기 위해 label을 docker-node-agent로 지정합니다.

    Jenkins 컨테이너에 들어가 위 사진의 Run from agent commind line을 복사 후 붙여넣기해서 에이전트 노드를 연결합니다.

     

    다음으로 도커가 설치된 gradle 이미지를 빌드하고, 레지스트리에 푸시합니다.

    젠킨스 에이전트가 이 도커 이미지를 에이전트로 사용하게 할 것입니다.

    # dminus251/jenkins-docker-agent:latest
    FROM gradle:jdk17
    
    # Docker 클라이언트 설치
    RUN apt-get update && \
        apt-get install -y docker.io
    
    # Jenkins가 Docker를 사용할 수 있도록 권한 설정
    USER root

     

    docker build -t your_registry_name/jenkins-docker-agent:latest .
    docker push your_registry_name/jenkins-docker-agent

     

     

    이제 Dockerfile 내용을 다음처럼 애플리케이션을 실행하도록 변경합니다. 

    이는 젠킨스 에이전트가 애플리케이션 Dockerfile을 빌드하기 위함입니다.

    #Dockerfile for mdinus251/calculator:latest
    FROM openjdk:17-slim
    
    # JAR 파일을 이미지에 복사
    COPY build/libs/calculator-0.0.1-SNAPSHOT.jar app.jar
    
    # 컨테이너가 시작될 때 JAR 파일을 실행하도록 설정
    ENTRYPOINT ["java", "-jar", "app.jar"]

     

     

    이제 Jenkinsfile을 올바르게 구성해보겠습니다.

    pipeline {
        agent {
            docker {
                    image 'dminus251/jenkins-docker-agent:latest'
                    label 'docker-node-agent'
            }
        }
        stages {
            stage('Check Docker Installation') {
                steps {
                    script {
                        sh 'which docker'
                        sh 'docker --version'
                    }
                }
            }
            stage('Compile') {
                steps {
                    sh './gradlew compileJava'
                }
            }
            stage('Unit Test') {
                steps {
                    sh './gradlew test'
                }
            }
            stage('Code Coverage') {
                steps {
                    sh './gradlew jacocoTestReport'
                    sh './gradlew jacocoTestCoverageVerification'
                }
            }
            stage('Static Code Analysis') {
                steps {
                    sh './gradlew checkstyleMain'
                }
            }
            stage('Build Jar') {
                steps {
                    sh './gradlew build'
                }
            }
            stage('Docker Build') {
                steps {
                    script {
                        // Build Docker image
                        sh 'docker build -t dminus251/calculator:2 .'
                    }
                }
            }
        }
    }

     

    성공적으로 파이프라인 빌드에 성공한 모습입니다.

     

    3-2. DinD: docker.sock 마운트

    dminus251/jenkins-docker-agent:latest 컨테이너에 들어가 확인해보니 파일의 그룹명이 1001로 나와있습니다.

    따라서 해당 컨테이너의 Dockerfile을 다음과 같이 변경 후 다시 빌드, 푸시합니다.

     

    그룹 1001을 newdocker 그룹으로 추가하고, newdocker 그룹에 root 사용자를 추가하는 명령을 추가했습니다.

    이 작업을 하지 않으면 'Is the docker deamon running?' 에러가 발생합니다.

    # Dockerfile for dminus251/jenkins-docker-agent:latest
    FROM gradle:jdk17
    
    # Docker 클라이언트 설치
    RUN apt-get update && \
        apt-get install -y docker.io
    
    RUN groupadd -g 1001 newdocker && usermod -aG newdocker root
    
    USER root

     

    docker.sock을 마운트하더라도 docker cli를 사용해야 하기 때문에 여전히 docker.io 패키지를 설치해야 합니다.

     

    새로 만든 이미지 dminus251/jenkins-docker-agent:using_socket를 통해 파이프라인 빌드에 성공한 모습입니다.

     


    4. 의문점: 왜 동적 프로비저닝 도커 에이전트에선 빌드에 실패할까?

    3번 과정에서 영구 도커 에이전트 유형(Node)에서 Docker 명령어를 잘 수행할 수 있는 것을 확인했습니다.

     

    하지만 왜 동적 프로비저닝 도커 에이전트 유형(Cloud)에선 Docker가 설치된 이미지를 사용해도 docker not found 에러가 발생할까요?

     

    동적 프로비저닝 도커 유형에선 젠킨스 에이전트가 dminus251/jenkins-docker-agent:latest 이미지가 아닌 jenkins/agent:latest를 pulling하는 것으로 확인했습니다.

     

    Jenkinsfile에서 agent의 도커 이미지를 지정해도, 처음에는 에이전트 템플릿의 이미지를 사용하는 것 같습니다.

     

    따라서 아래 사진처럼 템플릿의 이미지를 변경합니다.

     

    (좌) 수정 전, (우) 수정 후

    템플릿 이미지에 도커가 설치되어 있으므로, 이제 도커 명령을 실행할 수 있을 것입니다.

     

     

    템플릿 이미지를 수정 후 다시 파이프라인을 빌드했습니다.

    이제 Docker not found 에러는 발생하지 않지만, Is the docker daemon running? 에러가 발생합니다.

    도커 데몬과 통신이 원활하지 않아 생기는 에러입니다.

    분명 Jenkinsfile에서 agent에 /var/run/docker.sock을 마운트 했고, privileged 권한까지 줬는데, 왜 이런 에러가 발생할까요?

     

     

    아래는 제가 에러를 해결하려고 시도한 방법들입니다.

    1.  3-2와 마찬가지로 newdocker 그룹을 추가해서 roote 유저를 그룹에 추가
    2. Jenkinsfile의 agent에 args '--privileged -v /var/run/docker.sock:/var/run/docker.sock' 추가
      agent 부분을 다음과 같이 변경했습니다.
      docker-cloud-agent 레이블은 동적 프로비저닝 도커 에이전트의 레이블입니다.
      agent {
      	docker {
      		image 'dminus251/gardle-agent:latest'
                      args '--privileged -v /var/run/docker.sock:/var/run/docker.sock' #추가
                      label 'docker-cloud-agent'
              }
          }


    3. dminus251/jenkins-docker-agent:latest 컨테이너에 들어가 docker --version, docker ps 명령 수행
      → 수행 결과 도커 데몬과 통신할 수 있는 것을 확인함

    4. dminus251/jenkins-docker-agent:latest 이미지의 Dockerfile에서 docker.io 외에도 도커 관련 패키지들인 docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin을 모두 설치

     

    혹시 이외에 더 시도해 볼만한 게 있을 경우 댓글로 알려주신다면 정말 감사드리겠습니다.


    5. 후기

    비록 의문점을 해결하진 못 했지만 도커, 젠키스 명령과 친해질 수 있는 기회가 되었습니다.

    나중에 더 성장해서 이 의문점을 해결하게 된다면 꼭 포스팅을 올리겠습니다.

    3일 동안 총 99번이나 빌드해준 젠킨스 에이전트에게 아주... 고맙습니다.