[Docker & Jenkins] 도커와 젠킨스를 활용한 Spring Boot CI/CD🥸

한재희·2021년 3월 8일
27
post-thumbnail

요약 : 도커에 Jenkins 띄운 후 Build Now를 클릭해 Github에서 소스코드를 가져와 Spring Boot 프로젝트 빌드, Docker이미지로 생성해 배포

작성 동기

이전 프로젝트에서는 빌드,배포를 쉘 스크립트로 진행했습니다.

현재 프로젝트에서는 브랜치 전략으로 Git-Flow를 선택하며 테스트를 위한 develop과 release에서는 자동 배포를, master에서는 수동 배포를 하기로 했습니다.

사용자에게 직접 영향이 가는 master가 WebHook으로 인해 자동으로 배포되며 혹시 모를 서비스 중단되는 것을 최대한 피하기 위해, 수동 배포를 선택하면서 적용한 것들을 공유하기 위해 테스트 글을 작성했습니다.

진행하면서 발생하는 모든 오류와 피드백 감사히 받겠습니다 !!

시작

모든 테스트는

Intel Mac

Intellj Ultimate

Java 8

Spring Boot 2.4.3

Gradle

Jenkins 2.282

로 했습니다.

본 테스트는 WebHook을 포함하지 않습니다.

테스트는 아래와 같은 순서로 진행됩니다.

  1. Spring Boot Project 생성

  2. Github에 프로젝트 푸쉬

  3. Github 토큰 새성

  4. Docker Jenkins Image 생성 및 실행

  5. Jenkins 초기 세팅

  6. CI/CD (빌드 및 배포) 세팅

1. Spring Boot Project 생성

( 생성 과정이 귀찮으신 분들은 https://github.com/HanJaehee/JenkinsTutorial.git clone 하셔도 좋습니다 )


스프링 부트 프로젝트를 생성해줍니다.

편의상 Gradle로 사용했습니다
본 테스트에서 Maven을 선택하신 분들은 후반부의 젠킨스 빌드 과정에서 커맨드가 달라 테스트가 정상적으로 진행되지 않을 수 있습니다!



프로젝트에는 controller 패키지에 HelloController를 생성합니다.

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String hello(){
        return "Hello #1";
    }
}

로컬에 테스트를 해보시고, Hello #1이 정상적으로 리턴된다면, 다음으로 Dockerfile을 추가해줍니다. 경로는 프로젝트 최상단에 추가합니다.

FROM java:8
EXPOSE 8081
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

java:8 이미지를 기반으로, 8081 포트로 매핑하며, build/libs에 위치한 jar파일을 실행시켜주는 도커 빌드파일입니다. 후반부의 도커 이미지 빌드과정에서 쓰일 예정입니다.

2. Github에 프로젝트 푸시

GitHub에 프로젝트를 생성하고 위에서 생성한 프로젝트를 푸시합니다.

푸시된것을 확인 했다면, 액세스 토큰을 생성하러 다음으로!👏

3. Github 액세스 토큰 생성

settings -> Develop settings -> personal access tokens 에서 토큰을 생성합니다.

노트는 되도록 띄어쓰기없는 이름으로, 그리고 퍼블릭 레포지토리의 접근을 허용하는 public_repo로 허용합니다.


만약 토이 프로젝트를 진행하실 서버에 테스트하실 예정이라면, Private 레포지토리에 프로젝트를 생성하시는 것을 추천합니다.

이것은 그저 연습용 테스트 시나리오니 양해해주세요!



### 4. Docker Jenkins Image 생성 및 실행 이제 본격적으로 젠킨스를 이용합니다.

저는 Docker Desktop for Mac에서 진행했습니다! 제가 사용한 jenkins 이미지는 jenkins/jenkins:jdk11 입니다

먼저, 도커파일을 작성해줍시다. ( 제 레포지토리의 ForJenkins 폴더내에 작성되어있습니다. )

FROM jenkins/jenkins:jdk11

#도커를 실행하기 위한 root 계정으로 전환
USER root

#도커 설치
COPY docker_install.sh /docker_install.sh
RUN chmod +x /docker_install.sh
RUN /docker_install.sh

#설치 후 도커그룹의 jenkins 계정 생성 후 해당 계정으로 변경
RUN groupadd -f docker
RUN usermod -aG docker jenkins
USER jenkins

위 코드는 젠킨스 이미지를 빌드하기 위한 Dockerfile 입니다.

#!/bin/sh
apt-get update && \
apt-get -y install apt-transport-https \
  ca-certificates \
  curl \
  gnupg2 \
  zip \
  unzip \
  software-properties-common && \
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey && \
add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") \
$(lsb_release -cs) \
stable" && \
apt-get update && \
apt-get -y install docker-ce

위 코드는 도커파일 내에 있는 도커를 설치하기 위한 docker_install.sh 파일입니다.

이 두 파일을 동일 경로에 위치시켜 주시고, 도커 이미지를 빌드합니다.

docker build -t jenkins/myjenkins .

빌드가 완료되면, 빌드된 이미지를 확인해봅시다. jenkins/myjenkins 이미지가 생성되었다면 성공!

docker imgages

이제, 이미지를 젠킨스 이미지를 실행시킵니다. 아래의 명령어에 테스트 내용의 절반이 들어있습니다.
docker run --help를 통해 하나씩 뜯어봅시다.

docker run -d -p 9090:8080 --name=jenkinscicd \
-v /사용자경로/jenkinsDir:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
jenkins/myjenkins
docker run -d -p 9090:8080 --name=jenkinscicd

-d 는 백그라운드에서 실행을 의미하고

-p 는 매핑할 포트를 의미합니다. ( p가 port의 단축어가 아니었음 .. )

: 기준으로 왼쪽은 로컬포트, 오른쪽은 도커 이미지의 포트를 의미합니다. 도커 이미지에서의 8080 포트를 로컬 포트 9090으로 매핑한다는 뜻입니다.

젠킨스(Spring 기반) 디폴트 포트인 8080을 따라가지 않고 9090으로 매핑한 이유는 스프링 부트 디폴트 포트가 8080때문이기 때문에 혹시 모를 충돌을 피하기 위해서 9090으로 매핑했습니다.

--name은 컨테이너의 이름을 지정해줍니다.

-v /사용자경로/jenkinsDir:/var/jenkins_home

-v 옵션은 ":"를 기준으로 왼쪽의 로컬 경로를 오른쪽의 컨테이너 경로로 마운트 해줍니다.

즉, 제 컴퓨터의 사용자경로/jenkinsDir 을 컨테이너의 /var/jenkins_home과 바인드 시켜준다는 것입니다. 물론, 양방향으로 연결됩니다.

컨테이너가 종료되거나 알 수없는 오류로 정지되어도, jenkins_home에 남아있는 소중한 설정 파일들은 로컬 경로에 남아있게 됩니다.

단, 잘못된 설정으로 오류가 발생 했을 경우 로컬 경로를 꼭 비워 줍시다. 위와 같은 옵션으로 100번 젠킨스 컨테이너를 삭제하고 생성해도 동일한 설정파일로 실행되기 때문에 오류가 발생했을 때는 꼭! 비워 줍니다.. ~~제가 그래서 그런거 아님~~

-v /var/run/docker.sock:/var/run/docker.sock \
jenkins/myjenkins

이 옵션은 로컬의 도커와 젠킨스 내에서 사용할 도커 엔진을 동일한 것으로 사용하겠다는 의미입니다.

Docker in Docker(DinD)Docker out of Docker(DooD) 두 개의 개념이 존재하는데,
DinD는 도커에서도 권장하지 않습니다.

DinD와 DooD에 대한 자세한 개념은 https://aidanbae.github.io/code/docker/dinddood 에 아주 잘 소개되어있어서 링크를 걸어둡니다.

jenkins/myjenkins 는 위에서 빌드한 도커이미지의 이름으로, 해당 이미지로 컨테이너를 생성한다는 의미입니다.

이제 정상적으로 실행되었다면, 다음과 같이 컨테이너 ID가 출력됩니다. 가장 하단의 컨테이너 해시값 를 잘 확인해주세요.

5. Jenkins 초기 세팅

젠킨스에 접속하기 전에 /var/run/docker.sock 에 대한 권한을 설정해주어야 합니다.

일반적인 도커를 이용한 젠킨스 튜토리얼에서는 도커파일에 작성했던

RUN groupadd -f docker
RUN usermod -aG docker jenkins

이 두 명령으로 jenkins 계정을 docker group에 추가하며 설정되는 것 같지만.. 저는 여전히 Permission denied가 떴습니다.

확인해보니, 초기 /var/run/docker.sock의 권한이 소유자와 그룹 모두 root였기 때문에 이제 그룹을 root에서 docker로 변경해줄겁니다.

먼저, jenkins로 실행됐던 컨테이너의 bash를 root 계정으로 로그인 하기전에, 현재 실행되고 있는 컨테이너의 정보들을 확인할 수 있는 명령어를 입력해 아이디를 확인하겠습니다.

docker ps -a

우리가 방금 생성한 컨테이너의 ID는 80bcdb8~ 입니다. 도커는 다른 컨테이너 ID와 겹치지 않는 부분까지 입력하면 해당 컨테이너로 알아서 매핑해줍니다.

docker exec -it -u root 컨테이너ID /bin/bash

exec는 컨테이너에 명령어를 실행시키는 명령어인데, /bin/bash와 옵션 -it를 줌으로써 컨테이너의 쉘에 접속할 수 있습니다.

이제 정말로 root 계정으로 컨테이너에 접속하기 위해 컨테이너ID에 0bc를 입력해 실행합니다.

root 계정으로 로그인이 잘 되었습니다. 이제 그룹을 바꾸기 위해 다음 명령어를 실행해줍니다.

chown root:docker /var/run/docker.sock

그리고 이제 쉘을 exit 명령어로 빠져나온 후 다음 명령어를 실행해 컨테이너를 재실행해줍니다.

docker restart 0bc

드디어 젠킨스 재부팅이 끝나면,

관리자 패스워드를 입력하라는 창이 뜹니다. 로컬 쉘로 돌아가 다음 명령어를 입력해줍니다.

docker logs bd2

docker logs 컨테이너 id를 입력해 로그를 출력하면 initialAdminPassword가 출력됩니다. 이 패스워드를 입력해주면 됩니다.

정상적으로 입력했다면 플러그인 설치가 나오는데, 우리는 Install suggested plugins를 선택합니다.

자주 사용되는 플러그인들을 많이 설치해줍니다. 우리가 사용할 gradle, git 등등 도 보이네요.

여기서 설치실패가 절반이상 나시는 분들은 젠킨스 버전을 확인해주세요. 저도 구버전으로 할땐 젠킨스와 플러그인의 버전이 안맞아서 설치 실패하는 경우가 많았습니다..

설치가 완료되면, 어드민 계정 생성창이 나오고, 본인이 사용하실 정보들을 입력해줍시다. 앞으로 이 url로 젠킨스에 접속하시면 됩니다. 여기까지 오셨다면, 젠킨스 설치 및 초기 세팅 완료!

6. CI/CD (빌드 및 배포) 세팅

먼저, 대쉬보드의 새로운 아이템을 클릭합니다.

아이템이름을 자유롭게 입력해주시고, Freestyle project를 선택하고, OK로 생성합니다.

이제 빌드 설정창이 뜰텐데, 소스 코드 관리쪽에서 Git을 선택하고, Repository URL에 다음과 같이 입력해줍니다.

https://토큰이름:토큰값@레포지토리경로

이제 Build에서 Execute shell을 선택해줍니다.

./gradlew clean build

커맨드에 위와 같이 입력해주세요! 동일한 방법으로 하단의 커맨드도 모두 각각 입력해주세요.

docker build -t jenkins/testapp .

현재 경로에 있는 Dockerfile을 이용해 빌드하고 jenkins/testapp이라는 이름의 도커 이미지를 생성합니다.

docker ps -q --filter "name=jenkins-testapp" | grep -q . && docker stop jenkins-testapp && docker rm jenkins-testapp | true

실행되고있는 컨테이너의 이름이 jenkins-testapp 을 필터링하고, jenkins-testapp 이름의 실행되고 있는 컨테이너를 stop, 그리고 삭제합니다. 초기 빌드에선 실행되고 있는 컨테이너가 없겠지만, 빌드를 두번 이상 할 때는 기존에 실행되고 컨테이너와의 충돌을 막아줍니다.

docker run -p 8081:8080 -d --name=jenkins-testapp jenkins/testapp

위에서 빌드한 이미지 jenkins/testappjenkins-testapp 이름의 컨테이너로 실행합니다. 여기서 jenkins-testapp을 바꾼다면, 위의 이름들도 모두 변경해주어야합니다.

docker rmi -f $(docker images -f "dangling=true" -q) || true

도커 이미지중 dangling=true옵션을 이용해 사용되지 않는 불필요한 이미지들을 지워줍니다.

결과적으로 다음과 같이 세팅하면 됩니다.

7. 빌드 확인

이제 드디어 세팅한 값들을 확인해볼 차례입니다. 위의 내용들을 저장하고, Build Now를 눌러봅니다.

그럼 첫 빌드가 실행됩니다. #1을 누르고, console output로 진행사항을 확인해봅시다.

빌드를 기다리고 Finished: SUCCESS가 보이면, 로컬에서 docker ps -a를 확인해봅시다.

로컬의 docker.sock과 젠킨스의 docker.sock이 공유되어 로컬에서도 jenkins/testapp이 실행중인것을 볼 수 있습니다!

이제 http://localhost:8081/hello에 접속해보면,

Hello #1을 확인하실 수 있습니다.

이제, Hello #1을 Hello #2로 변경해 푸시 후 젠킨스에서 다시 빌드를 해봅시다.

두번째 빌드의 Console Output 중 커밋메세지에 마지막으로 푸시한 커밋 메세지가 보이면서, 빌드 완료 후 다시 접속해 Hello #2를 보셨다면, 드디어 끝 🔥!

다음 글에서는 develop 브랜치를 위한 Web Hook 자동 배포를 공유해볼 예정입니다.
현재 프로젝트에서는 Jenkins를 활용하지 않아, 당분간은 Jenkins Webhook 관련 글 작성이 어려울 것 같습니다.
https://pooney.tistory.com/86
대신 위의 글을 소개 드리면서, 마치겠습니다! ( 구글 검색을 통해 다양한 케이스들을 살펴보시는 것 추천드립니다! )

모두 화이팅!

참고

coding-start.tistory.com/329

도커관련 글을 써주신 모든 분들.. 감사합니다

profile
IT관련 된 것들은 가리지 않고 먹어요.

7개의 댓글

comment-user-thumbnail
2021년 7월 30일

사랑해요 너무 유익합니다.

1개의 답글
comment-user-thumbnail
2021년 10월 27일

"develop 브랜치를 위한 Web Hook 자동 배포"에 관한건 어딜참고하면될까요~? ㅋ

1개의 답글
comment-user-thumbnail
2022년 5월 18일

안녕하세요

좋은 내용 잘 보고 갑니다
감사합니다.

그리고 혹시

테스트는 아래와 같은 순서로 진행됩니다.
...

GitLab에 프로젝트 푸쉬

GitLab 토큰 새성

...

에 GitLab이 아니라 Github이 아닌가요 ?? 쓰고 계신게 Github 같아서요!

그리고 2번째로

aidanbae.github.io/code/docker/dinddood/ 사이트 링크 주신거에서 마지막 / 때문에 사이트가 없는 사이트라고 뜨네요. aidanbae.github.io/code/docker/dinddood 로 수정해주시면 정상동작 하네요!

1개의 답글
comment-user-thumbnail
2022년 6월 29일

혹시 액세스 토큰 이후 작성하는 도커파일은 위에서 작성한 도커파일과는별게인가요?

답글 달기