개발 기록

> [ CI/CD ] Docker & Jenkins & Spring boot CI/CD - 2. 호스트 도커에 젠킨스 구축하기 (DIND, DOOD) 본문

인프라

> [ CI/CD ] Docker & Jenkins & Spring boot CI/CD - 2. 호스트 도커에 젠킨스 구축하기 (DIND, DOOD)

1z 2024. 3. 8. 19:09

 

 

 

CI(빌드/테스트 자동화) 서버로 jenkins 이미지를 Docker 에서 컨테이너로 실행시키고 있을 때, CD(배포 자동) 를 위해 젠킨스 컨테이너에서 도커 데몬을 제어해야 하는데 Jenkins 이미지에는 Docker 가 설치되지 않기 때문에 불가하다. 이때 DOOD, DIND 방법으로 Jenkins 컨테이너에서 내부에서 Docker 데몬을 실행시키는 방법이 있다. 

 

 DIND (DOCKER IN DOCKER)

① Docker 가 설치된 Jenkins 이미지 빌드

② DIND 이미지를 실행하고 해당 TCP 소켓을 Jenkins 컨테이너에 노출 (젠킨스 공식문서에 나와있는 방법)

 

  DOOD (DOCKER OUT DOCKER)

① Host Docker Unix 소켓을 Jenkins 컨테이너에 마운트

 

 

 

1. DIND - DIND 컨테이너 TCP 소켓 연결  

 

(1) 구조 

도커 호스트에는 2개의 컨테이너가 올라간다. 두 컨테이너는 네트워크와 볼륨을 통해 상호작용한다. 

1. 도커-인-도커(docker:dind 이미지) 컨테이너  : 도커 자체에서 엑세스 하기 위함

2. 젠킨스(jenkins 이미지) 컨테이너 : 젠킨스가 수신 대기하는 포트 8080을 외부 트래픽에 노출한다. 

 

 

(2) Docker host 에서 네트워크 생성 

 

젠킨스 컨테이너와 도커-인-도커 컨테이너가 공유할 도커 네트워크 브리지를 만든다

 

도커 네트워크(Docker Network) 란 각각의 Docker 컨테이너 간의 통신을 관리하고 격리하기 위한 기능을 제공한다.

같은 네트워크 안에서는 컨테이너의 IP를 지정해주거나 할 필요 없이 name 만으로 손쉽게 네트워크를 연결할 수 있다는 장점이 있다. 또한 아웃바운드 포트를 오픈하지 않는 이상 내부적으로만 통신하게 된다.

# docker network create {name}
docker network create jenkins

 

(3) Pull the DinD Image  (dind 이미지)

docker pull docker:20.10-dind

 

 

 

(4) DIND 컨테이너 실행  

참고 (Docker Environment variables)

docker run \
# Docker 컨테이너 이름
  --name jenkins-docker \
  
# 컨테이너가 종료될 때 컨테이너와 관련된 리소스(파일 시스템, 볼륨)까지 제거(선택사항)
  --rm \
  
# 해당 컨테이너를 백그라운드에서 실행
  --detach \
  
# 권한 확장: Host 시스템 자원 접근 권한 부여 (default : Unprivileged)
  --privileged \
  
# jenkins 컨테이너와 통신하기 위한 network  
# 컨테이너를 네트워크에 연결
  --network jenkins \
  
# 컨테이너에 대한 네트워크 범위 별칭 
  --network-alias docker \
  
# TLS(HTTPS) 사용하여 Docker Daemon Socket 보호 
# CA가 서명한 인증서로 인증된 클라이언트만 연결 허용 
# 환경변수로 지정된 디렉터리에 인증서를 생성한다.
  --env DOCKER_TLS_CERTDIR=/certs \
  
# host 의 jenkins-docker-certs 디렉터리와 컨테이너의 /certs/client 디렉터리 간 마운트
  --volume jenkins-docker-certs:/certs/client \

# host 의 jenkins-data 디렉터리와 컨테이너의 /var/jenkins_home 디렉터리 간 마운트 
  --volume jenkins-data:/var/jenkins_home \
  
#  ( 선택 사항 ) 호스트에 Docker Daemon 포트를 노출 (Host 에서 해당 컨테이너의 Docker Daemon 을 제어할경우)
# 2375(암호화되지않은 트래픽), 2376(암호화된 트래픽)
#  --publish 2376:2376 \
  
# docker:dind 이미지
  docker:dind \
  
# Docker 볼륨용 스토리지 드라이버
  --storage-driver overlay2

 

※ 참고 : 주석 제거 명령문 

 

(4) Jenkins 컨테이너 실행
 
 

젠킨스 이미지 빌드

: DIND 컨테이너 내부 Docker Daemon 에 명령어를 보내기 위해 Docker Client 설치

 

- Dockerfile

FROM jenkins/jenkins:2.440.1-jdk17
USER root
RUN apt-get update && apt-get install -y lsb-release
RUN curl -fsSLo /usr/share/keyrings/docker-archive-keyring.asc \
  https://download.docker.com/linux/debian/gpg
RUN echo "deb [arch=$(dpkg --print-architecture) \
  signed-by=/usr/share/keyrings/docker-archive-keyring.asc] \
  https://download.docker.com/linux/debian \
  $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list

# Docker client 만 설치
RUN apt-get update && apt-get install -y docker-ce-cli
USER jenkins
RUN jenkins-plugin-cli --plugins "blueocean docker-workflow"

 

- image build

docker build -t myjenkins-blueocean:2.440.1-1 .

 

 

 젠킨스 컨테이너 실행 

참고 (Docker Environment variables)

docker run \
  --name jenkins-blueocean \
  # container가 정상적으로 종료되지 않은 경우(exit code가 0이 아님)에만 재시작 (with max-retries)
  # default = no
  --restart=on-failure \
  
  # 현재 컨테이너를 백그라운드에서 실행 
  --detach \
  
  # DIND 컨테이너와 통신하기 위한 network  
  # 컨테이너를 네트워크에 연결
  --network jenkins \
  
  # 연결할 Daemon Socket
  # TCP : Docker Daemonn 에 원격으로 access 하는 경우
  # 2376(TLS), 2375(일반)
  
  --env DOCKER_HOST=tcp://docker:2376 \
  # 인증키 위치 (CLI와 Daemon 에서 사용)
  --env DOCKER_CERT_PATH=/certs/client \
  # TLS 사용 (CLI와 Daemon 에서 사용)
  --env DOCKER_TLS_VERIFY=1 \
  
  #	현재 컨테이너의 포트 8080을 호스트의 포트 8080에 매핑. (host port:container port)
  --publish 8080:8080 \
  
  # Jenkins agents 포트는 50000 로 Jenkins "controller" 매핑한다. (복수의 jenkins 시 설정 필요)
  --publish 50000:50000 \
  
  # 컨테이너의 /var/jenkins_home 디렉터리를 host 시스템의 디렉터리 jenkins_home 에 매핑
  --volume jenkins-data:/var/jenkins_home \
  
  # Docker 데몬에 연결하는 데 필요한 클라이언트 TLS 인증서는 DOCKER_CERT_PATH 에서 사용할 수 있다 
  --volume jenkins-docker-certs:/certs/client:ro \
  
  myjenkins-blueocean:2.440.1-1

 

 

※ 참고 : 주석 제거 명령문 

 

ⓛ 장점

 기본 Jenkins 이미지로 직접 작업 가능

  호스트와 Jenkins 컨테이너 간의 격리

 

단점

☞  Jenkins 컨테이너의 도커 명령어를 받는 컨테이너를 추가해야 한다.

☞  DIND 컨테이너에 --privileged 옵션을 설정하여 호스트 시스템의 모든 장치에 액세스 권한을 부여해야한다. 이 부분은 보안상 좋지 않다.  그래도 Jenkins 컨테이너에 직접 부여하는 것보다 낫다.

 

 


 

2. DIND - Jenkins 컨테이너  내 Docker 설치 

 

 

 

1. Jenkins  컨테이너 내부에 접속해서 Docker 를 수동 설치해도 되고, Dockerfile 을 사용하여 jenkins 기반 이미지를 만들면서  RUN 명령어을 사용하여 Docker 를 설치해도 된다.

 

- Dockerfile

from jenkins/jenkins:lts

USER root

RUN apt-get update -qq && apt-get install -qqy apt-transport-https ca-certificates curl gnupg2 software-properties-common

RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -

RUN add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \
   stable"

RUN apt-get update -qq && apt-get install -qqy docker-ce docker-ce-cli containerd.io

RUN usermod -aG docker jenkins
# Build image
docker build --no-cache /path/to/Dockerfile --tag jenkins-docker

# Run container
docker run --rm -d -v ./jenkins_home:/var/jenkins_home -p 8080:8080 -p 50000:50000 --privileged jenkins-docker

 

 

* 중요한점

[ - -v ./jenkins_home:/var/jenkins_home]

:Jenkins 컨테이너가 재시작 및 삭제 될 때 데이터가 날라가지 않다록, 호스트의 디렉토리와 마운트하여 데이터를 유지한다.

 

ⓛ 장점

  호스트와 Jenkins 컨테이너의 프로세스 격리

 

② 단점

기존 jenkins 이미지를 이용하는 것이 아니라, docker 설치가 포함된 jenkins 이미지를 빌드해야한다.

--privileged 권한 부여 옵션 설정

☞ docker에 docker 를 설치하는 것은 권장하지 않는다. (참고)

 

 


3. DOOD  (Docker out of  Docker)

 

: 컨테이너 내부에서 도커를 설치하지 않고 외부의 도커를 사용하는 방식이다. 

 

호스트의 Docker Unix 소켓을 Jenkins 컨테이너에 마운트 함으로써 Jenkins 컨테이너는 호스트의 docker 데몬에 액세스 할 수 있다. 반대로 Jenkins 컨테이너에서 생성된 모든 컨테이너/이미지 또한 호스트에서 액세스할 수 있다.

 

 

(1) Pull Jenkins Image 

$ docker pull jenkins/jenkins:lts

 

☞ 문제 발생 : 권한 error

 

해결 방법:  Docker 그룹에 현재 계정 추가

 

 

 

(2) 볼륨 생성  

docker volume create jenkins-volume

 

 

(3) 젠킨스 컨테이너 실행 

$ docker run -d \

# port 연결
-p 8080:8080 \
-p 50000:50000 \

# 젠킨스 컨테이너의 설정을 호스트 서버와 공유함으로써, 컨테이너가 삭제되는 경우에도 설정을 유지할수 있게 해준다.
-v /jenkins:/var/jenkins_home \

# 젠킨스 컨테이너에서도 호스트 서버의 도커를 사용하기 위한 바인딩
-v /usr/bin/docker:/usr/bin/docker \
# docker.sock 마운트
-v /var/run/docker.sock:/var/run/docker.sock \

--name jenkins \

-u root \

jenkins/jenkins:lts

 

 

ⓛ 장점

기본 Jenkins 이미지로 직접 작업 가능

 --privileged 권한 부여 옵션 설정 필요 없음

 많이 사용하는 방식

 

② 단점

 호스트와 Jenkins 컨테이너가 격리되지 않아 결합도가 높다. 

 Jenkins 컨테이너가 호스트 도커 소켓에 관한 권한이 없을 수 있다.

 

 

 

 

참고

https://www.jenkins.io/doc/book/installing/docker/

https://www.tiuweehan.com/blog/2020-09-10-docker-in-jenkins-in-docker/