들어가기 앞서...
pknu-wap/WABI-BE: 와비 : 부경대학교 소속 및 학생 회비 납부자 확인 서비스 BE (github.com)
"WABI" 라는 학생회비 납부 서비스에 백엔드로 참가하면서 전적으로 CI/CD를 맡게 되었다.
CI는 Git을 통해 어느 정도 경험이 있었지만 CD는 진짜 문외한이었다.
이 글은 WABI 를 참가하면서 오랫동안 삽질한 결과물이자 Jenkins를 이용해서 스프링 프로젝트를 배포할 때 놓치기 쉬운 점을 작성해 보았다.
즉, Jenkins 및 Docker에 대한 이야기이다.
WABI 에서의 나의 첫 번째 목표
1. 스프링 프로젝트 지속적인 통합과 배포까지의 자동화(CI/CD)
2. 웹 서버와 DB 서버 분리
3. https과 DNS 연결
크게 3가지였는데 모두 한 번도 안 해본 큰 벽이었다!
2번과 3번도 차후 회고록을 써보고자 한다.
프로젝트 구조
먼저, Google Cloud Platform(GCP)를 사용하였다.
AWS를 써도 되지만, 무료일 경우 성능 제한이 어느 정도 있는 반면에, GCP는 3개월 동안 한화 약 40만 크레딧을 주기 때문에 성능 좋은 서버를 짧지만 경험해 볼 수 있다는 메리트를 고려하여 GCP를 사용하기로 결정하였다.
(사실 AWS나 GCP나 거기서 거기다..)
그리고 원래라면 배포 서버를 분리해야 하는데.. 이건 40만 원을 최대한 3개월 동안 뽑아야 하는 현실 때문에 웹 서버 인스턴스에 배포 서버도 같이 두었다.
이런 아찔한 상황을 타개하기 위해 여러 가지를 찾다가 Docker(Docker-Compose)라는 가상화를 이용하여 독립적으로 어플리케이션을 운용할 수 있는 유명한 게 있길래 이걸 사용해 보기로 했었다.
그렇게 만들어진 GCP 인스턴스 구조이다.
전체적인 CI/CD 흐름은 Git Repository develop 브랜치에 push -> webhook으로 Jenkins 서버에 알림 -> Jenkins 파이프라인으로 빌드 -> Docker Image 빌드 -> 빌드된 Docker Image를 DockerHub에 push -> DockerHub로부터 Docker Image pull -> Docker Container 재생성으로 배포 완료
여기서 웹 서버랑 Jenkins가 같은 서버 인스턴스에 존재하는데 왜 DockerHub에 빌드된 이미지를 Push 하기로 했냐면
이 글을 쓸 당시에, 차후 WABI 기획이 구체화 및 확장을 준비하고 있었기 때문에 배포 서버를 분리할 경우의 수를 대비하여
미리 DockerHub에 Push & Pull 하는 방식으로 구현하였다.
아무튼 처음 이렇게 구조를 다 짰으나.. 막상 해보니 정말 문제가 많았다
1. Jenkins가 계속 엉뚱한 곳에 찾아가서 Gradle Build를 하던 문제
+ ./gradlew clean build --exclude-task test
/var/jenkins_home/workspace/wabi-spring@2/wabi@tmp/durable-0c9c70fd/script.sh.copy: 2: ./gradlew: not found
gradle
stage('Bulid Gradle') {
agent any
steps {
echo 'Bulid Gradle'
dir ('./wabi'){
sh """
./gradlew clean build --exclude-task test
"""
}
}
post {
failure {
error 'This pipeline stops here...'
}
}
}
분명 Jenkins 파이프라인에서 Git Clone을 제대로 하고 진행을 했었는데 계속 wabi-spring@2와 같이 새로운 곳에서
(wabi-spring에서 해야 정상)
Build를 진행하는 것이었다.
진짜 이 문제 때문에 몇 시간을 썼는지도 모를 정도로 Jenkins 컨테이너를 지웠다가 다시 설치했다가 무한 반복을 했었다..
사실 아직도 뭐 때문에 이런 현상이 발생했는지 의문이다.
다만, 추정하기론 Jenkins Docker 컨테이너를 실행할 때, 볼륨을 명확히 하지 않았던 걸로 추측하고 있다.
(차후 두 번째 문제에서 자세히 설명 예정)
당시엔 dir() 파이프라인 함수로 강제로 경로를 고정시켜 build를 넘겼으나 진짜 문제는 바로 여기에 있었다...
2. Jenkins가 Docker 를 찾지 못하는 문제
stage('Docker Build') {
steps {
dir("./wabi"){
sh """docker build -t ${DOCKER_IMAGE} ."""
sh 'docker ps -a'
}
}
}
+ docker build -t seongwonyoon/wabi_public .
/var/jenkins_home/workspace/wabi-spring@tmp/durable-eccbc7b2/script.sh.copy: 1: docker: not found
1번도 시간이 오래 걸렸지만, 2번은 진짜였다.
Jenkins는 다양한 플러그인이 존재한다.
이 중에선 Jenkins의 파이프라인에서 Docker를 사용할 수 있도록 플러그인들이 존재한다.
이 당시에 "이것만 깔면 자동으로 docker를 Jenkins 컨테이너 안에서도 쓸 수 있겠지"라는 생각으로 진행했으나 웬걸? docker가 없다고 나를 반겨준다
당시엔 몰랐다.. 이게 이렇게 쓰는 게 아니었다는 걸..
어쨌든 이걸 몰랐던 나는 Docker Build를 sh 명령으로 실행하고 싶었다.
그래서 여러 가지 찾아본 결과로
Docker in Jenkins in Docker | Blog (tiuweehan.com)
위 글을 발견했다.
결국 Jenkins의 파이프라인에서 커맨드로 Docker Image를 Build 하겠다는 말은 Jenkins 컨테이너 안에도 Docker가 설치가 되어 있어야 함을 의미한다.
이를 위해선 Docker가 어떻게 작동되는지 알아야 한다.
Docker는 기본적으로 3가지의 구성 요소로 만들어져 있다.
사용자의 명령을 입력받는 Client, 실제로 컨테이너를 실행하고 있는 Daemon, 이 둘 사이를 중재하는 Socket이다.
Docker가 정상적으로 작동하려면 이 세 가지 구성요소가 모두 필요하다는 것이다.
크게 위 글에선 해결 방법을 3가지로 언급하고 있는데
1. 걍 새로 다시 깔기
2. 서버 컴퓨터에 docker 설치한 것을 Jenkins 컨테이너에 마운트
3. Docker는 기본적으로 Unix Socket를 사용하므로 TCP Socket을 사용하는 Socker과 Daemon을 새롭게 Docker 컨테이너를 통해 만들어 분리
1번은 이미 엄청 재설치를 많이 한 입장에서 싫었고 3번은 굳이 컨테이너를 하나 더 만들어야 한다는 점에서 싫었기 때문에 2번을 선택하였다.
2번은 비교적 하는 방법도 쉬운데 Docker 컨테이너를 만들 때 볼륨 경로만 잘 설정해 주면 된다.
version: "3.7"
services:
jenkins:
image: jenkins/jenkins:lts
user: root
container_name: jenkins
ports:
- 8080:8080
- 50000:50000
volumes:
- ./jenkins:/var/jenkins_home
- /usr/bin/docker:/usr/bin/docker
- /var/run/docker.sock:/var/run/docker.sock
위는 docker-compose 방식이다.
위와 같은 과정을 통해서 결국 Docker Image를 빌드하고 DockerHub에 Push 하는 건 성공했다.
하지만 여기서 끝이 아니었는데..
3. Jenkins가 Docker-Compose 를 찾지 못하는 문제
stage('Server Container Setting'){
steps {
script{
sh """
docker-compose --project-name wabi pull spring
docker-compose --project-name wabi stop spring
docker-compose --project-name wabi rm -f spring
docker-compose --project-name wabi up -d spring
"""
}
}
}
Spring 컨테이너를 Docker-Compose를 이용해서 관리하고 있었다.
컨테이너를 관리하는데도 편했지만 가장 큰 이유는 따로 있었다.
바로 application.properties 값 주입을 docker-compose.yml에서 진행하고 있었기 때문이다.
하지만 2번을 겪었던 나는 금방 해결방법을 떠올렸다.
바로 Jenkins 컨테이너에 Docker-Compose를 설치하면 되는 것이다.
그리고 Jenknins 컨테이너 내부 루트 폴더에 Spring 컨테이너를 관리할 Docker-Compose.yml을 만들어 놓는다.
이로써 계획했던 CI/CD를 완성하게 되었다.
'DevOps > CI|CD' 카테고리의 다른 글
[CI/CD]Jenkins에서 Github Action으로.. (With GCP, Docker) - WABI회고록 (0) | 2024.12.09 |
---|---|
[CI/CD] CI/CD에 관하여 (0) | 2024.04.14 |