➡️ 배포 자동화 도입 이유
프로젝트를 진행하면서 프론트엔드 팀원이 작업한 웹 클라이언트를 디자인 팀원이 체크해야 했다. 하지만 배포과정은
- 새로운 기능 추가 또는 오류 수정 → 로컬에서 테스트 → 문제가 없다면 서버에 직접 배포 → 배포가 완료된 것을 확인하고 결과를 다른 팀원 확인 요청
와 같이 번거로운 작업이었고 이는 백엔드와 프론트엔드 간의 개발 과정에도 꼭 필요하다고 생각되어,
이전 프로젝트에서 따라하기만 해봤던 배포 자동화를 제대로 정리하며 도입하기로 결정했다!
⤳ 배포 과정
배포는 크게 두 가지의 과정으로 나눠서 진행했다
- Jenkins로 CI(Continuous integration) 구축
- 개발자가 새로운 기능을 개발하고 변경된 내용을 Github에 push 및 merge
- Github Webhook이 특정 branch에 merge될 때, Jenkins에서 해당 branch의 코드를 pull 해서 빌드 및 테스트
- CD(Continuous Deployment) 및 쉘 스크립트 실행 구축
- 이상이 없다면 기존 컨테이너를 종료하고 이미지와 함께 삭제하고, Dockerfile을 사용해 새로운 컨테이너를 생성 및 실행

해당 과정을 백엔드 레포지토리와 프론트 레포지토리 둘 다 적용!
Jenkins로 CI (continues integration) 구축
- 구축 환경
- Ubuntu 22.04.2 LTS
- SpringBoot 3.0.4
- Docker 23.0.2
- docker-compose 1.29.2
Docker를 사용해 서버를 구축했기 때문에 먼저 Jenkins 이미지를 pull 해준다
→ Docker를 사용한 이유에 대한 정리(Docker 의 개념과 장점 / 단점)
docker image pull jenkins/jenkins:jdk17
이 때 스프링부트 프로젝트의 버전이 3.0.4이고 jdk17 버전 이상부터 지원이 된다. 버전을 명시하지 않고 이미지를 pull하게 되면 latest 버전의 이미지가 설치되는데 latest 버전의 jenkins는 jdk 11버전까지만 지원하므로 이미지를 pull 할 때에 jdk17 버전을 명시해서 pull 해준다.

pull한 Jenkins 이미지를 가지고 배포에 사용할 수 있도록 커스텀 이미지를 생성하는 Dockerfile(이미지 생성을 위함)을 작성한다
파일의 위치는 임의로 작성가능하며, 나는 현재 디렉토리에서 jenkins-dockerfile 디렉토리를 생성해 작성했다
# /groot/jenkins-dockerfile/Dockerfile
FROM jenkins/jenkins:jdk17
USER root
RUN apt-get update &&\
apt-get upgrade -y &&\
apt-get install -y openssh-client
위의 openssh-client 가 Jenkins에 필요한 이유는 빌드가 완료된 프로젝트를 Jenkins(컨테이너) 외부의 서버에서 실행시켜야 하기 때문에 ssh 를 통해 서버에 접속해 빌드된 프로젝트를 실행시키는 방법을 사용하기 위함
해당 Dockerfile을 바탕으로 Docker 컨테이너를 생성해주는 docker-compose.yml(컨테이너 생성을 위함) 을 작성한다
# /groot/docker-compose.yml
version: "3.1"
services:
jenkins:
container_name: jenkins
build:
context: jenkins-dockerfile
dockerfile: Dockerfile
restart: unless-stopped
user: root
ports:
- 8888:8080
- 50000:50000
volumes:
- /home/groot/jenkins:/var/jenkins_home
- /home/groot/.ssh:/root/.ssh
- 위의 volumes 옵션이 필요한 이유는 독립된 Jenkins 컨테이너 외부에 저장소를 둠으로써 데이터를 지속적으로 저장할 수 있기 때문이다.
- "서버의 저장 경로 : 도커 컨테이너의 저장 경로"와 같은 식으로 저장소를 공유 가능
- restart: unless-stopped는 서버가 재시작할 때 해당 컨테이너도 자동으로 재실행됨을 의미
docker-compose 파일을 up 해준다
docker-compose [-f docker-compose.yml][→ 생략가능] up --build -d
이후 Jenkins 가 정상적으로 실행되고 8888포트로 접속해 기본 설정을 마쳐주면 다음과 같은 화면이 나오게 된다

이후 서버와 클라이언트의 배포에 사용할 아이템을 2개 만들어 준다

둘 중 하나의 아이템에 들어간 다음 좌측에 구성으로 들어간다

Github 저장소의 주소를 입력하고 Credentials 에서 Kind는 Username with password로 설정하고, Username에 github id, Password에 github password를 입력한다. 원하는 브랜치 명을 입력하고, 아래의 빌드 유발에서 Github hook trigger를 체크해준다.

이후 Github의 저장소로 이동해 Settings → Webhooks → Add webhook 으로 들어간다

Payload URL에 젠킨스가 구동되는 서버의 ip 또는 DNS와 포트번호를 적고 뒤에 github-webhook/ 를 붙여주자. 그리고 다음과 같이 설정한 후 Update webhook 버튼을 누르자
!!꼭 github-webhook 뒤에 /를 붙여줘야한다!!

그러면 다음과 같은 화면이 나오고 시간이 조금 지난뒤 웹페이지를 리로드하면 초록색 체크를 확인할 수 있다

똑같은 과정을 서버 Repository에도 적용하고 README 수정을 통해 제대로 작동하는지 테스트한다


또한 Jenkins 컨테이너와 공유했던 볼륨으로 들어가 /workspace/groot-server 에 들어가면 저장소의 프로젝트 소스 코드가 pull 되어있는 것을 확인할 수 있다

CD(Continuous Deployment) 및 쉘 스크립트 실행 구축
배포의 과정 또한 서버와 클라이언트가 비슷한 방식으로 진행한다
- 서버
- Jenkins에서 프로젝트 소스를 바탕으로 build 및 test
- 빌드 후 생성된 jar파일을 컨테이너에서 외부 서버로 ssh 접속해서 Docker 이미지로 만들고 컨테이너를 실행
- 클라이언트
- Jenkins에서 외부 서버로 ssh 접속해 빌드 및 실행하는 Docker이미지로 만들고 컨테이너를 실행
먼저 배포를 진행하기 전에 Jenkins 컨테이너와 서버간의 연결을 위한 ssh 설정을 해야한다. ssh 명령을 password 없이 수행하려면 Client(Jenkins 컨테이너)쪽 공개키를 Server(컨테이너 외부 Ubuntu 서버) 쪽 허용키에 등록해야 한다. 따라서 공개키 생성을 위해 Jenkins 컨테이너에 exec 명령어로 접속한다

이제 키를 생성하는 명령어를 입력하고 생성된 키를 확인하여 복사한다. 중간에 나오는 질문들은 Enter를 눌러 스킵한다
ssh-keygen -t rsa
cat /root/.ssh/id_rsa.pub
exit


이제 생성한Jenkins 컨테이너의 키를 Ubuntu 서버 허용키 목록에 등록한다
파일에 아무 내용이 없다면 그냥 붙여넣기 하면 되고, 내용이 있다면 맨 마지막에 복사한 내용을 추가한다
다시 Jenkins 컨테이너에 들어가서 ssh 접속을 테스트한다

서버 배포
SpringBoot 서버 Docker 이미지를 생성하기 위해 Dockerfile을 작성한다
# /groot/spring-dockerfile/Dockerfile
FROM amazoncorretto:17
ENTRYPOINT java -jar /deploy/mindmap-0.0.1-SNAPSHOT.jar
EXPOSE 8080
corretto jdk17 이미지를 사용해 ENTRYPOINT에 작성된 명령어를 수행하고 8080 포트를 사용한다
위에서 만든 이미지를 컨테이너로 실행할 docker-compose.yml 파일도 작성한다
# /groot/docker-compose.spring.yml
version: "3.1"
services:
spring:
container_name: spring
build:
context: spring-dockerfile
dockerfile: Dockerfile
ports:
- 8090:8080
volumes:
- /home/groot/jenkins/workspace/groot-server/build/libs:/deploy
Jenkins의 서버 프로젝트의 "구성"으로 이동해 Build Steps → Execute shell 에다가 아래와 같은 내용을 작성한다
mkdir /var/jenkins_home/workspace/groot-server/src/main/resources
cp /var/jenkins_home/application/application.properties /var/jenkins_home/workspace/groot-server/src/main/resources/application.properties
./gradlew clean build
rm /var/jenkins_home/workspace/groot-server/src/main/resources/application.properties
rmdir /var/jenkins_home/workspace/groot-server/src/main/resources
ssh -t -t 사용자명@IP 주소 <<EOF
cd /home/groot
docker-compose -f docker-compose.spring.yml up --build -d
exit
EOF
Github 저장소에는 보안상의 이유로 중요한 정보가 담긴 application.properties 파일을 올리지 않는다. 그러므로 Jenkins가 필요로하는 application.properties 파일을 빌드하기 전에 프로젝트 소스의 src/main/resources/application.properties에 추가해주고 빌드 이후에 삭제해준다.
빌드가 완료된 후 Jenkins에서 Ubuntu 서버로 ssh 접속해서 docker-compose up 해서 이미지 생성 및 컨테이너 실행을 한다.
위의 shell 명령들이 잘 실행되는지 확인하기 위해 Jenkins의 서버 프로젝트에서 "지금 빌드"를 실행하고, 잘 실행 되었다면 아래와 같이 Docker Image, container 가 잘 생성 및 실행되고, 8090포트로 접속했을 때 스프링 프로젝트의 기본 실행화면을 볼 수 있다



웹 클라이언트 배포
React 웹 클라이언트 Docker 이미지를 생성하기 위해 Dockerfile을 작성한다
# /groot/jenkins/dockerfile
FROM node:latest
COPY . .
RUN yarn install
EXPOSE 3000
RUN yarn build
RUN yarn global add serve
CMD ["serve", "-s", "dist"]
Docker 파일을 레포지토리의 최상위 폴더에 위치시켜 빌드 및 배포하기 때문에 Jenkins 저장 환경에서 Docker 파일을 Repository 소스 프로젝트에 복사하고 docker-compose를 실행하는 방법을 사용
위에서 만든 이미지를 컨테이너로 실행할 docker-compose.yml 파일도 작성한다
# /groot/docker-compose.client.yml
version: "3.1"
services:
client:
container_name: client
build:
context: /home/groot/jenkins/workspace/groot-client
dockerfile: Dockerfile
ports:
- 3000:3000
이후 Jenkins로 이동해 서버 프로젝트와 같은 방식으로 "구성"으로 이동해 Build Steps → Execute shell 에다가 아래와 같은 내용을 작성한다
cp /var/jenkins_home/dockerfile/Dockerfile /var/jenkins_home/workspace/groot-client/Dockerfile
ssh -t -t 사용자명@IP 주소 <<EOF
cd /home/groot
docker-compose -f docker-compose.client.yml up --build -d
exit
EOF
위의 shell 명령들이 잘 실행되는지 확인하기 위해 서버 프로젝트와 같은 방법으로 Jenkins의 서버 프로젝트에서 "지금 빌드"를 실행하고, 잘 실행 되었다면 아래와 같이 Docker Image, container 가 잘 생성 및 실행되고, 3000포트로 접속했을 때 기본 웹 페이지를 볼 수 있다



배포는 두번째로 경험하는 것인데 이전에 했던 부분을 다시 직접 정리해서 따라해보며 Docker와 Jenkins에 대해 더 잘 공부할 수 있었고, 자동 배포를 통해 우리 팀원들에게 좀 더 도움이 되었으면 좋겠다!
아직 무중단 배포를 구성한 것이 아니라 추후에 Slack과 Jenkins를 연동하고 무중단 배포를 구현할 부분도 정리할 예정이다!
참고
➡️ 배포 자동화 도입 이유
프로젝트를 진행하면서 프론트엔드 팀원이 작업한 웹 클라이언트를 디자인 팀원이 체크해야 했다. 하지만 배포과정은
- 새로운 기능 추가 또는 오류 수정 → 로컬에서 테스트 → 문제가 없다면 서버에 직접 배포 → 배포가 완료된 것을 확인하고 결과를 다른 팀원 확인 요청
와 같이 번거로운 작업이었고 이는 백엔드와 프론트엔드 간의 개발 과정에도 꼭 필요하다고 생각되어,
이전 프로젝트에서 따라하기만 해봤던 배포 자동화를 제대로 정리하며 도입하기로 결정했다!
⤳ 배포 과정
배포는 크게 두 가지의 과정으로 나눠서 진행했다
- Jenkins로 CI(Continuous integration) 구축
- 개발자가 새로운 기능을 개발하고 변경된 내용을 Github에 push 및 merge
- Github Webhook이 특정 branch에 merge될 때, Jenkins에서 해당 branch의 코드를 pull 해서 빌드 및 테스트
- CD(Continuous Deployment) 및 쉘 스크립트 실행 구축
- 이상이 없다면 기존 컨테이너를 종료하고 이미지와 함께 삭제하고, Dockerfile을 사용해 새로운 컨테이너를 생성 및 실행

해당 과정을 백엔드 레포지토리와 프론트 레포지토리 둘 다 적용!
Jenkins로 CI (continues integration) 구축
- 구축 환경
- Ubuntu 22.04.2 LTS
- SpringBoot 3.0.4
- Docker 23.0.2
- docker-compose 1.29.2
Docker를 사용해 서버를 구축했기 때문에 먼저 Jenkins 이미지를 pull 해준다
→ Docker를 사용한 이유에 대한 정리(Docker 의 개념과 장점 / 단점)
docker image pull jenkins/jenkins:jdk17
이 때 스프링부트 프로젝트의 버전이 3.0.4이고 jdk17 버전 이상부터 지원이 된다. 버전을 명시하지 않고 이미지를 pull하게 되면 latest 버전의 이미지가 설치되는데 latest 버전의 jenkins는 jdk 11버전까지만 지원하므로 이미지를 pull 할 때에 jdk17 버전을 명시해서 pull 해준다.

pull한 Jenkins 이미지를 가지고 배포에 사용할 수 있도록 커스텀 이미지를 생성하는 Dockerfile(이미지 생성을 위함)을 작성한다
파일의 위치는 임의로 작성가능하며, 나는 현재 디렉토리에서 jenkins-dockerfile 디렉토리를 생성해 작성했다
# /groot/jenkins-dockerfile/Dockerfile
FROM jenkins/jenkins:jdk17
USER root
RUN apt-get update &&\
apt-get upgrade -y &&\
apt-get install -y openssh-client
위의 openssh-client 가 Jenkins에 필요한 이유는 빌드가 완료된 프로젝트를 Jenkins(컨테이너) 외부의 서버에서 실행시켜야 하기 때문에 ssh 를 통해 서버에 접속해 빌드된 프로젝트를 실행시키는 방법을 사용하기 위함
해당 Dockerfile을 바탕으로 Docker 컨테이너를 생성해주는 docker-compose.yml(컨테이너 생성을 위함) 을 작성한다
# /groot/docker-compose.yml
version: "3.1"
services:
jenkins:
container_name: jenkins
build:
context: jenkins-dockerfile
dockerfile: Dockerfile
restart: unless-stopped
user: root
ports:
- 8888:8080
- 50000:50000
volumes:
- /home/groot/jenkins:/var/jenkins_home
- /home/groot/.ssh:/root/.ssh
- 위의 volumes 옵션이 필요한 이유는 독립된 Jenkins 컨테이너 외부에 저장소를 둠으로써 데이터를 지속적으로 저장할 수 있기 때문이다.
- "서버의 저장 경로 : 도커 컨테이너의 저장 경로"와 같은 식으로 저장소를 공유 가능
- restart: unless-stopped는 서버가 재시작할 때 해당 컨테이너도 자동으로 재실행됨을 의미
docker-compose 파일을 up 해준다
docker-compose [-f docker-compose.yml][→ 생략가능] up --build -d
이후 Jenkins 가 정상적으로 실행되고 8888포트로 접속해 기본 설정을 마쳐주면 다음과 같은 화면이 나오게 된다

이후 서버와 클라이언트의 배포에 사용할 아이템을 2개 만들어 준다

둘 중 하나의 아이템에 들어간 다음 좌측에 구성으로 들어간다

Github 저장소의 주소를 입력하고 Credentials 에서 Kind는 Username with password로 설정하고, Username에 github id, Password에 github password를 입력한다. 원하는 브랜치 명을 입력하고, 아래의 빌드 유발에서 Github hook trigger를 체크해준다.

이후 Github의 저장소로 이동해 Settings → Webhooks → Add webhook 으로 들어간다

Payload URL에 젠킨스가 구동되는 서버의 ip 또는 DNS와 포트번호를 적고 뒤에 github-webhook/ 를 붙여주자. 그리고 다음과 같이 설정한 후 Update webhook 버튼을 누르자
!!꼭 github-webhook 뒤에 /를 붙여줘야한다!!

그러면 다음과 같은 화면이 나오고 시간이 조금 지난뒤 웹페이지를 리로드하면 초록색 체크를 확인할 수 있다

똑같은 과정을 서버 Repository에도 적용하고 README 수정을 통해 제대로 작동하는지 테스트한다


또한 Jenkins 컨테이너와 공유했던 볼륨으로 들어가 /workspace/groot-server 에 들어가면 저장소의 프로젝트 소스 코드가 pull 되어있는 것을 확인할 수 있다

CD(Continuous Deployment) 및 쉘 스크립트 실행 구축
배포의 과정 또한 서버와 클라이언트가 비슷한 방식으로 진행한다
- 서버
- Jenkins에서 프로젝트 소스를 바탕으로 build 및 test
- 빌드 후 생성된 jar파일을 컨테이너에서 외부 서버로 ssh 접속해서 Docker 이미지로 만들고 컨테이너를 실행
- 클라이언트
- Jenkins에서 외부 서버로 ssh 접속해 빌드 및 실행하는 Docker이미지로 만들고 컨테이너를 실행
먼저 배포를 진행하기 전에 Jenkins 컨테이너와 서버간의 연결을 위한 ssh 설정을 해야한다. ssh 명령을 password 없이 수행하려면 Client(Jenkins 컨테이너)쪽 공개키를 Server(컨테이너 외부 Ubuntu 서버) 쪽 허용키에 등록해야 한다. 따라서 공개키 생성을 위해 Jenkins 컨테이너에 exec 명령어로 접속한다

이제 키를 생성하는 명령어를 입력하고 생성된 키를 확인하여 복사한다. 중간에 나오는 질문들은 Enter를 눌러 스킵한다
ssh-keygen -t rsa
cat /root/.ssh/id_rsa.pub
exit


이제 생성한Jenkins 컨테이너의 키를 Ubuntu 서버 허용키 목록에 등록한다
파일에 아무 내용이 없다면 그냥 붙여넣기 하면 되고, 내용이 있다면 맨 마지막에 복사한 내용을 추가한다
다시 Jenkins 컨테이너에 들어가서 ssh 접속을 테스트한다

서버 배포
SpringBoot 서버 Docker 이미지를 생성하기 위해 Dockerfile을 작성한다
# /groot/spring-dockerfile/Dockerfile
FROM amazoncorretto:17
ENTRYPOINT java -jar /deploy/mindmap-0.0.1-SNAPSHOT.jar
EXPOSE 8080
corretto jdk17 이미지를 사용해 ENTRYPOINT에 작성된 명령어를 수행하고 8080 포트를 사용한다
위에서 만든 이미지를 컨테이너로 실행할 docker-compose.yml 파일도 작성한다
# /groot/docker-compose.spring.yml
version: "3.1"
services:
spring:
container_name: spring
build:
context: spring-dockerfile
dockerfile: Dockerfile
ports:
- 8090:8080
volumes:
- /home/groot/jenkins/workspace/groot-server/build/libs:/deploy
Jenkins의 서버 프로젝트의 "구성"으로 이동해 Build Steps → Execute shell 에다가 아래와 같은 내용을 작성한다
mkdir /var/jenkins_home/workspace/groot-server/src/main/resources
cp /var/jenkins_home/application/application.properties /var/jenkins_home/workspace/groot-server/src/main/resources/application.properties
./gradlew clean build
rm /var/jenkins_home/workspace/groot-server/src/main/resources/application.properties
rmdir /var/jenkins_home/workspace/groot-server/src/main/resources
ssh -t -t 사용자명@IP 주소 <<EOF
cd /home/groot
docker-compose -f docker-compose.spring.yml up --build -d
exit
EOF
Github 저장소에는 보안상의 이유로 중요한 정보가 담긴 application.properties 파일을 올리지 않는다. 그러므로 Jenkins가 필요로하는 application.properties 파일을 빌드하기 전에 프로젝트 소스의 src/main/resources/application.properties에 추가해주고 빌드 이후에 삭제해준다.
빌드가 완료된 후 Jenkins에서 Ubuntu 서버로 ssh 접속해서 docker-compose up 해서 이미지 생성 및 컨테이너 실행을 한다.
위의 shell 명령들이 잘 실행되는지 확인하기 위해 Jenkins의 서버 프로젝트에서 "지금 빌드"를 실행하고, 잘 실행 되었다면 아래와 같이 Docker Image, container 가 잘 생성 및 실행되고, 8090포트로 접속했을 때 스프링 프로젝트의 기본 실행화면을 볼 수 있다



웹 클라이언트 배포
React 웹 클라이언트 Docker 이미지를 생성하기 위해 Dockerfile을 작성한다
# /groot/jenkins/dockerfile
FROM node:latest
COPY . .
RUN yarn install
EXPOSE 3000
RUN yarn build
RUN yarn global add serve
CMD ["serve", "-s", "dist"]
Docker 파일을 레포지토리의 최상위 폴더에 위치시켜 빌드 및 배포하기 때문에 Jenkins 저장 환경에서 Docker 파일을 Repository 소스 프로젝트에 복사하고 docker-compose를 실행하는 방법을 사용
위에서 만든 이미지를 컨테이너로 실행할 docker-compose.yml 파일도 작성한다
# /groot/docker-compose.client.yml
version: "3.1"
services:
client:
container_name: client
build:
context: /home/groot/jenkins/workspace/groot-client
dockerfile: Dockerfile
ports:
- 3000:3000
이후 Jenkins로 이동해 서버 프로젝트와 같은 방식으로 "구성"으로 이동해 Build Steps → Execute shell 에다가 아래와 같은 내용을 작성한다
cp /var/jenkins_home/dockerfile/Dockerfile /var/jenkins_home/workspace/groot-client/Dockerfile
ssh -t -t 사용자명@IP 주소 <<EOF
cd /home/groot
docker-compose -f docker-compose.client.yml up --build -d
exit
EOF
위의 shell 명령들이 잘 실행되는지 확인하기 위해 서버 프로젝트와 같은 방법으로 Jenkins의 서버 프로젝트에서 "지금 빌드"를 실행하고, 잘 실행 되었다면 아래와 같이 Docker Image, container 가 잘 생성 및 실행되고, 3000포트로 접속했을 때 기본 웹 페이지를 볼 수 있다



배포는 두번째로 경험하는 것인데 이전에 했던 부분을 다시 직접 정리해서 따라해보며 Docker와 Jenkins에 대해 더 잘 공부할 수 있었고, 자동 배포를 통해 우리 팀원들에게 좀 더 도움이 되었으면 좋겠다!
아직 무중단 배포를 구성한 것이 아니라 추후에 Slack과 Jenkins를 연동하고 무중단 배포를 구현할 부분도 정리할 예정이다!
참고