본문 바로가기

IT

gitlab runner docker in docker 구조

728x90

대표적인 CICD(Continuous Integration/Continuous Delivery) 도구들 (gitlab runner, github action, jenkins 등) 이 있지만 이 글에서는 gitlab runner를  사용해 구축하는 과정에서 겪었던 이슈와 해결, 궁금증 대한 정리를 담아보려고 한다.  

 

개발관련된 툴들을 docker compose로 관리하기 위해 호스트는 하나의 EC2에서 진행하였고 따라서 gitlab runner를 컨테이너 환경에서 실행하였다. 기본적인 구축 셋은 다른 사람들의 예시가 많으니 참고해도 좋고 문서에 내용을 그대로 진행해도 좋다.

https://docs.gitlab.com/runner/install/docker.html 

 

Run GitLab Runner in a container | GitLab

GitLab product documentation.

docs.gitlab.com

 

docker-compose.yml

services:
	gitlab-runner:  
    	image: gitlab/gitlab-runner:latest
        container_name: gitlab-runner  
        restart: always  
        volumes:  
        - ./config/gitlab-runner:/etc/gitlab-runner
        - /var/run/docker.sock:/var/run/docker.sock

docker.sock에 대해서는 도커를 사용한다면 꼭 알아야 하는 포인트 중 하나인 것 같다. 간단히 설명하자면 Docker 데몬과 컨테이너 간 통신을 위한 Unix 소켓 파일이다. 이 파일은 Docker 데몬이 컨테이너와 상호 작용할 때 사용되며 Docker CLI가 명령을 실행하면, 해당 명령은 docker.sock 파일을 통해 Docker 데몬에 전달되어 실행된다. 이 파일을 bind mount를 통해 호스트의 Docker 데몬을 gitlab-runner 컨테이너에 공유한다. gitlab-runner 이미지에 대한 소스도 문서에서 제공하고 있으니 참고하면 좋을 듯 하다.  

 

러너를 실행시키고 gitlab-ruuner register 이후에는 설정 정보를 추가할 것이다.

다음은 gitlab-runner의 config.toml 파일을 작성하면서 러너 설정에 name, url, excutor등을 설정할텐데 초기 구축 시 중요한건 runners.docker 설정인 듯하다.

concurrent = 1
check_interval = 0
shutdown_timeout = 0
[session_server]
session_timeout = 1799
[[runners]]
name = "runner_name"
url = "url"
id = 50
token = "token"
token_obtained_at = 2024-01-22T03:58:08Z
token_expires_at = 0001-01-01T00:00:00Z
executor = "docker"
[runners.cache]
MaxUploadedArchiveSize = 0
[runners.docker]
tls_verify = false
image = "docker"
privileged = true
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache"]
shm_size = 0
network_mtu = 0

 

excutor를 docker로 실행하였고 privileged값은 true로 주었다. 러너는 결국 푸시 또는 머지 시 excutor로 새로운 컨테이너를 생성하고 privileged 값을 true로 주어 호스트의 도커 데몬을 사용할 수 있는 권한(그 이상)을 준 것이다. 이 값에 대한 설정은 gitlab-runner register 시 default 값은 false이니 수정후 restart가 필요하다. 

 

https://docs.gitlab.com/ee/ci/docker/using_docker_build.html

gitlab-ci.yml 

default:
  image: docker:20
  services:
    - docker:20-dind
  before_script:
    - docker info

variables:

  DOCKER_TLS_CERTDIR: ""

build:
  stage: build
  script:
    - docker build -t my-docker-image .
    - docker run my-docker-image /script/to/run/tests

공식 문서의 가이드대로 gitlab-ci.yml 파일을 작성하였고 다음과 같은 이슈를 맞았다. 

Docker in Docker is failing with error - Cannot connect to the Docker daemon at tcp://docker:2375/. Is the docker daemon running?

구글링 결과 https://gitlab.com/gitlab-org/gitlab-runner/-/issues/27300  해당 이슈에 다양한 개발자가 참여하여 손쉽게 해결할 수는 있었다. 

 

수정된 gitlab-ci.yml

build:  
stage: build
image: docker:20
services:      
  - name: docker:20-dind
    command: [ "--tls=false" ]  
variables:    
    DOCKER_HOST: tcp://docker:2375
    DOCKER_DRIVER: overlay2
    DOCKER_TLS_CERTDIR: ""

여기서 의문점이 들었다. dind가 무엇인지, 눈에 보이지 않는 이 흐름은 대체 어떤 구조로 돌아가고 있는 것인지에 대해서 말이다.

그래서 실제로 머지가 되었을때 어떻게 흘러가는지 체크해봤다.

docker ps

 

실제로 러너가 실행될때 위 스크립트대로면 두개의 추가 컨테이너가 실행됐다. 도커 데몬을 호스트의 도커 데몬을 docker.sock을 통해 사용했기에 gitlab-runner와 동일한 레이어에 컨테이너가 실행됨을 볼 수 있었다. 하나는 build job을 처리하기 위한 컨테이너인 것은 알겠는데 다른 하나는 무엇이지? 

현재 구조-발로그린다이어그램

요즘 밀고 있는 발로그린 다이어그램이다. 현재 아마 이런 구조를 가지고 있지 않을까 싶다.  gitlab-runner는 현재 host의 Docker 데몬에 대한 권한이 있고 docker.sock을 통해 컨테이너를 실행시켰고 동일 레이어에 container를 만들어냈다. 

 

두개의 이미지 모두 동일한 것도 확인할 수 있었다. image 섹션에서 정의한 image를 기반으로 runner의 excutor가 실행한 것으로 확인되고 나머지 하나는 services 섹션에서 정의한 이미지가 실행된 것으로 생각했다. 

https://docs.gitlab.com/ee/ci/services/

services 섹션에서의 예로는 해당 job에서 db를 컨테이너로 띄워 해당 job에 필요한 설정 등을 맞춰주는데 사용하는 듯하다. 아마 쿠버네티스의 pod나 ECS의 task에서 여러 컨테이너들이 런타임 시 소통이 필요한 경우를 테스트 하기 위한 섹션이 아닐까 생각이 들었다. 

 

아무튼 본론으로 들어와 services 섹션을 지우고 다시 파이프라인을 돌려서 확인해보았다.

 

services섹션을 지운 gitlab-ci.yml

build:  
stage: build
image: docker:20-dind
variables:    
    DOCKER_HOST: tcp://docker:2375
    DOCKER_DRIVER: overlay2
    DOCKER_TLS_CERTDIR: ""

docker 이미지 기반이라 docker-cli에 대한 명령은 가능하나 이를 실행시킬 Docker 데몬을 찾을 수 없는 것이다.

이 당시 실행된 컨테이너를 확인해본 결과 다음과 같았다.

docker ps

빌드에 대한 컨테이너는 있지만 이전에 확인한 docker-0에 대한 컨테이너는 없었다. 결국 그 컨테이너는 services 섹션에서 정의한 이미지 기반의 컨테이너가 실행되고 있었다는 것이다. 추가적으로 알아낸 것은 그 컨테이너가 없는 경우 Docker 데몬을 찾지 못한다는 것은 해당 컨테이너의 Docker 데몬을 사용하고 있다고 추측할 수 있었다. 해당 컨테이너는 호스트의  Docker 데몬 사용하도록 DinD를 제공하는 Service 역할, SideCar 느낌이들었다. 이 컨테이너는 도커 명령을 실행할 수 있겠네? 라고 생각했고 각 컨테이너 마다 들어가서 확인해보았다. 

컨테이너 내부에서 docker ps

실제로 build 컨테이너안에는 docker-cli에 대한 명령은 가능하지만 docker 데몬을 찾지 못했고 docker-0 컨테이너는 docker 명령이 가능했으며 이 안에는 busy_jackson이라는 또 다른 컨테이너가 dind(docker in docker)로 실행 중이였다.

docker in docker-발로그린다이어그램

그림을 그려보면 이런 느낌이지 않을까 싶다.  

컨테이너 내부에서 컨테이너를 실행시킬때 host의 Docker 데몬을 활용하면 host에 생성이 되지만 docker 컨테이너안에 격리된 또 다른 Docker 데몬을 사용하여 컨테이너를 생성하면 다음과 같이 컨테이너 안에 또 다른 컨테이너가 실행됨을 확인했다. 결국 컨테이너 안에 격리된 또 다른 Docker 데몬을 활용하여 도커 명령어를 수행하고 있는 것이다. 눈으로 보기에는 다음 그림 처럼 보일지라도 Docker socket을 통해 이 또한 호스트의 Docker 데몬을 사용한다.

docker demon 사용 관계-발로그린다이어그램

그림만 보고 오해하지 않도록 한다. 어차피 이 또한 내부적으로 호스트의 Docker 데몬을 사용할 것이기 때문이라는 것을 놓치지 말자.

gitlab-runner는 host의 Docker 데몬을 사용하였고 build 컨테이너는 docker-0에서 실행되는 격리된 Docker 데몬을 이용하는 것이다. 이게 어떻게 가능할까는 DOCKER_HOST: tcp://docker:2375 설정에 달려있다. tcp://docker:2375는 docker 컨테이너 간 통신을 위한 주소이다. docker라는 호스트명과 2375라는 도커 데몬의 포트를 통해 내부 도커 데몬과 연결하게 된다. 

 

services 섹션은 .gitlab-ci.yml 파일에서 지정할 수 있지만 config.toml 파일에서 미리 지정할 수 있는 듯 하다.

[[runners]]
[runners.docker]
[[runners.docker.services]]
name = "mysql:latest"
[[runners.docker.services]]
name = "redis:latest"

여기부터는 상황에 맞게 설정하도록 하자. 또한 자연스럽게 설명하고 넘어갔던 bind mount라는 개념도 volume 과는 근소하지만 다른 개념이니 따로 찾아보고 실습해보면 좋을 듯 하다. 

 

도커의 개념은 알다가도 모르겠지만 재밌으면서 신기한 경험을 계속 제공하는 것 같다. 오픈 소스는 아니지만 여러 개발자들에 대한 인터페이스는 열려있으며 계속 버전업하는 느낌이 든다. 아직까지 얕은 개념이지만 이러한 좋은 경험을 계속 맞이하다보면 나만의 길이 생길 듯하다.. 

 

이어서 CICD 구축하는 과정에서 겪었던 다른 이슈는 메이븐 환경의 .m2를 어떻게 캐싱하고 해결하였는지에 대해 이어서 포스팅하려고 한다. 

https://sh970901.tistory.com/138

 

Gitlab-Runner .m2 Caching

https://sh970901.tistory.com/136 gitlab runner docker in docker 구조 대표적인 CICD(Continuous Integration/Continuous Delivery) 도구들 (gitlab runner, github action, jenkins 등) 이 있지만 이 글에서는 gitlab runner를 사용해 구축하

sh970901.tistory.com

 

 

혹 글을 읽다 잘못된 해석이나 부분이 있다면 댓글 남겨주시면 참고하여 반영하도록 하겠습니다.