들어가며...
최근 프로젝트를 하면서 Spring boot로 만든 프로젝트를 통합테스트 하기위해 TestContainers 를 사용하게 되었다. 로컬환경에서 테스트 컨테이너를 구성하고 통합 테스트를 성공하였지만, GitLab CI 단계에서 상당한 시행착오를 격게 되었다.
TestContainer를 활용한 통합 테스트 환경을 구성하고, 이를 Gitlab CI 파이프라인에서 활용할 수 있는 방법을 기록해본다.
프로젝트 TestContainers 환경 설정

TestContainers 란?
TestContainers란 Docker Container를 활용한 일회용 인스턴스를 제공하는 오픈소스 라이브러리이다.
TestContainers를 사용함으로써 다음과 같은 이점을 얻을 수 있다.
- 통합 테스트를 수행하기 위해 직접 연관된 서비스를 실행시켜주는 TestContainers를 사용해 테스트만을 위한 독립적인 환경을 마련할 수 있다.
- TestConatiners를 활용해 테스트가 필요한 환경의 서비스 컨테이너(Redis, Mysql..)를 실행하고 테스트가 종료되면 컨테이너도 자동적으로 종료되어 테스트에 사용했던 자원도 반환되게 된다.
- 컨테이너로 테스트를 진행하기 때문에 어떤 환경에서도 바로 테스트를 진행할 수있다.
gradle 작성
- 다음과 같이 testcontainers를 사용하기 위해 의존성을 추가해준다
//build.gradle
dependencies{
//생략...
// test Containers
testImplementation "org.testcontainers:junit-jupiter:1.17.6"
testImplementation "org.testcontainers:mysql:1.17.6"
//생략..
}
테스트 코드 작성
- 통합 테스트 추상 클래스 작성한다. 이번 프로젝트에서는 테스트를 위해 Mysql과 redis를 활용하였다. 앞으로 작성할 추상클래스에서 Redis에 대한 설정을 그리고 application.yml 파일에서는 Mysql에 대한 설정해주었다.
//./test/java/.../IntegrationTest.java
@SpringBootTest
@Testcontainers
@ActiveProfiles("test")
public abstract class IntegrationTest {
private static final String REDIS_VERSION = "redis:6.2.7";
private static final int REDIS_PORT = 6379;
@ClassRule
static GenericContainer<?> REDIS_CONTAINER;
static {
REDIS_CONTAINER = new GenericContainer<>(REDIS_VERSION)
.withExposedPorts(REDIS_PORT);
REDIS_CONTAINER.start();
}
@DynamicPropertySource
private static void properties(DynamicPropertyRegistry registry) {
registry.add("spring.data.redis.host", REDIS_CONTAINER::getHost);
registry.add("spring.data.redis.port", () -> "" + REDIS_CONTAINER.getMappedPort(REDIS_PORT));
}
}
- 이후 테스트를 위한 코드에서 추상클래스로 만든 IntegrationTest 를 상속받아 테스트 코드를 작성한다.
application.yml 작성
- application 설정을 통해 Mysql 테스트 컨테이너를 사용을 명시한다.
spring:
profiles:
active: test
datasource:
driver-class-name: org.testcontainers.jdbc.ContainerDatabaseDriver
url: jdbc:tc:mysql:8.0.31://testDB
// ...생략
테스트 실행결과 확인!

테스트를 위한 컨테이너가 다음과 같이 실행되는 것을 확인할 수 있다. 환경설정을 통해 만들어진 컨테이너는 2개(Mysql, Redis) 인데, 또 하나의 컨테이너가 더 생성되었다. testcontainers/ryuk 이미지로 생성된 컨테이너는 테스트 컨테이너를 관리한다. 테스트 컨테이너를 모니터링하고 테스트가 종료되면 컨테이너와 이미지를 종료하고 제거하는 컨테이너이다.

다음은 공식문서에서 쓰여진 ryuk 이미지에 대한 설명이다.
This project helps you to remove containers/networks/volumes/images by given filter after specified delay.
Gitlab runner 환경 구축
gitlab ci/cd를 위해서 runner를 세팅해줘야한다. gitlab-runner를 설치하는 방법은 application을 통해서 직접 설치하는 방법과 runner이미지를 내려받아 컨테이너 형태로 실행시켜주는 방법이 있다. 이번 포스팅에서는 두번째 방식을 활용해 CI/CD 환경을 만들고자 한다.
gitlab-runner 띄우기
docker run -d \
--name gitlab-runner \
--restart always \
--network gitlab-runner-net \
--volume /srv/gitlab-runner/config:/etc/gitlab-runner \
--volume /var/run/docker.sock:/var/run/docker.sock \
gitlab/gitlab-runner:latest
runner를 띄운 다음 해당 runner를 프로젝트가 올라가있는 gitlab repository에 등록해줘야한다.
gitlab-runner 를 프로젝트에 등록하기 위한 값 확인
gitlab repository에서 URL과 token 값 확인(Settings → CI/CD → Runners)

gitlab-runner 컨테이너 안으로 접근
docker container exec -it gitlab-runner bash
비대화식 모드로 프로젝트에 runner 등록
gitlab-runner register -n \
--url <Your-register-url> \
--registration-token <Your-registration-token> \
--description gitlab-runner \
--tag-list "gitlab-runner" \
--executor docker \
--docker-privileged \
--docker-image docker:latest \
--docker-volumes "/certs/client"
등록된 runner 확인

Docker in docker
Testcontainer를 Gitlab CI로 띄우기 위해서 공식 문서에서는 2가지 옵션을 제공한다(참고)
- Dood(Docker out of Docker)
- Dind(Docker-in-docker)
첫번째 방법으로 CI 환경을 구축해보려했지만, 도커 호스트 머신과 runner가 상호작용하는 과정에서 testcontainer가 올라가지 않아 두번째 방법인 Dind 방식으로 통합테스트를 구성할 수 있었다.
Dind방식으로 testcontainer를 띄우기 위해서는 dind 컨테이너를 실행해줘야한다.
- Docker in docker(dind) 컨테이너 생성
docker run –d --privileged \\
--name gitlab-dind \\
--restart always -p 2375:2375 \\
--network gitlab-runner-net \\
--volume /var/lib/docker \\
-e DOCKER_TLS_CERTDIR="" docker:dind
dind 컨테이너를 실행하기 위해 privileged 옵션을 적용해주고 DOCKER_TLS_CERTDIR 공백으로 넣어준다.
또한 dind 컨테이너의 도커 데몬을 사용할 수 있도록 2375 포트를 열어준다.
테스트 파이프라인 스크립트 작성
테스트 단계의 파이프라인에 다음과 같은 환경변수 설정을 해준다.
- GRADLE_OPTS: "-Dorg.gradle.daemon=false" : 데몬 빌드 옵션 금지
- DOCKER_HOST: "tcp://docker:2375" : docker dind와 연결을 위한 Docker Host 세팅
- DOCKER_TLS_CERTDIR: “” : tls 인증을 사용하지 않음
- DOCKER_DRIVER : overlay2 : 레이어 파일 시스템 사용
- services : docker:dind : dind 도커 이미지 사용
.gitlab-ci.yml
# ... 생략
test:
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
# Instruct Testcontainers to use the daemon of DinD
# Use port 2735 for non-tls connections.
DOCKER_HOST: "tcp://docker:2375"
# Improve performance with overlayfs.
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: ""
image: openjdk:17-alpine
stage: test
tags:
- gitlab-runner
services:
- name: docker:dind
script:
- ./gradlew test
# ... 생략
파이프라인 확인
CI/CD → Pipelines 를 확인해보면 작성한 테스트가 잘 동작하는것을 확인할 수 있다.

마치며...
프로젝트를 수행하며 gitlab ci 파이프라인을 위한 runner 세팅, dind 컨테이너 설정, 그리고 ci 스크립트까지 작성하는 경험을 해볼 수 있었다. docker에 대해 잘 알고 있다고 생각했었는데 이번에 실습을 진행하며 Dind, Dood의 컨셉을 이해하지 못해 상당한 시행착오를 격게 되었다. 도커의 아키텍처에 대한 이해와 컨테이너 환경을 구성하는 컨셉을 다시한번 정리해봐야겠다는 생각이 들었다.
다음 포스팅에서는 Docker in Docker, Docker out of Docker에 대한 개념 정리와 도커의 아키텍쳐에 대한 포스팅을 진행해보고자한다.
'자동화 > Docker' 카테고리의 다른 글
[Docker] Docker 아키택처와 Dind, Dood (0) | 2023.02.26 |
---|