JEKINS
EC2
Docker
Nginx
Spring Boot
아래 블로그를 따라 실습해본 CI/CD와 무중단 배포에 대하여 제 삽질을 섞어 정리합니다. 무중단 배포 방식은 Blue/Green 방식을 이용합니다. 잘못된 내용 또는 오타가 있을 수도 있습니다. 혹시 그런게 있다면 코멘트 남겨 주세요!
필요한 것들
- 배포에 사용할 백엔드 어플리케이션
CI/CD와 무중단 배포에 사용할 백엔드 어플리케이션이 필요합니다. 백엔드 어플리케이션은
Spring Boot
를 이용하여 개발 되었으며 java 버전은 17
을 사용하고 빌드툴로는 Gradle 7.4.1
을 사용하였습니다.- EC2
jenkins용 EC2와 백엔드 어플리케이션이 배포될 EC2가 필요합니다. 즉, 총 2개의 EC2가 필요합니다.
백엔드 어플리케이션 작성
Spring Boot 2.6.9
Java 17
Gradle 7.4.1
actuator
무중단 배포를 위해 아래와 같이 application.yml과 ProfileController를 작성해줍니다. ProfileController는 현재 어떤 어플리케이션이 사용되고 있는지 알려주는 역할을 합니다.
application.yml
ProfileController.java
무중단 배포시 서버의 상태를 확인하기 위해 아래와 같이
actuator
를 추가 합니다.EC2 설정
Jenkins용 EC2 백엔드 어플리케이션용 EC2를 준비합니다.
- EC2 for Jenkins
ubuntu 22.04
- 인바운드 규칙
- 탄력적 IP 할당
유형 | 프로토콜 | 포트 범위 | 소스 |
SSH | TCP | 22 | 내 IP |
사용자 지정 TCP | TCP | 9000 | Anywhere-IPv4, Anywhere-IPv6 |
- EC2 for Backend Application
ubuntu 18.04
- 인바운드 규칙
- 탄력적 IP 할당
백엔드 어플리케이션용 EC2를
ubuntu 22.04
로 사용하였을 때 Jenkins에서 연동이 안되어 ubuntu 18.04
로 사용하게 되었다.유형 | 프로토콜 | 포트 범위 | 소스 |
SSH | TCP | 22 | 내 IP |
SSH | TCP | 22 | EC2 for Jenkins 의 탄력적 IP |
HTTP | TCP | 80 | Anywhere-IPv4, Anywhere-IPv6 |
HTTPS | TCP | 443 | Anywhere-IPv4, Anywhere-IPv6 |
EC2 인스턴스가 생성이 완료되면 ssh 접속을 통해 EC2 인스턴스에 초기 설정을 해준다. (해당 내용은 optional한 내용이니 하기 싫으면 안해도 된다.)
- SSH 접속
pem key는 ~/.ssh 디렉토리에 저장되어있다고 가정하겠다. 우선 발급 받은 pem key의 권한을 변경한다.
지금 상태에서는 굉장히 긴 명령어를 치고 EC2 인스턴스에 접근해야하 한다. 이는 귀찮으니 좀 더 편하게 접근할 수 있도록 설정하자. pem키가 있는 디렉토리에 config 파일을 생성하고 아래와 같은 내용을 채우고 저장한다 .
그후 config 파일에 소유자가 rwx 권한을 부여한다.
이제
ssh {원하는 이름}
을 입력하면 ec2 인스턴스에 접근할 수 있다.- 인스턴스 초기 설정
- Hostname 변경
- 서버 타임존을 UTC에서 KST로 변경
- EC2에 인스턴스에 docker 설치
- sudo 명령어 없이 docker 사용
- Jenkins용 EC2 메모리 스왑
현재 Hostname이 IP로 주어져 있어 이게 Jenkins용 EC2인지 어플리케이션용 EC2인지 구분이 안가는 경우가 있다. Hostname을 알기 쉽게 변경하도록 하자.
이후 EC2에
date
라고 입력해보면 변경된 타임존을 확인할 수 있다.만약 이렇게 하였는데 도커 명령어를 sudo 키워드 없이 사용하였을 때 아래와 같은 warning을 본다면
다음을 추가적으로 입력해주자.
현재 EC2는 모두 프리티어를 사용하고 있다. 직접 문제를 겪지는 않았지만 공부하면서 찾아보았을 때 프리티어는 기본 메모리가 1GB밖에 되지 않으므로 Jenkins가 빌드 도중 죽어버리는 문제가 있다고 한다. 이러한 문제를 사전에 방지하기 위해서는 swap 메모리를 할당해야 한다.
Jenkins
- Jenkins 설치
Jenkins용 EC2에 도커를 이용하여 Jenkins를 설치한다.
- Jenkins 접속
- 초기 비밀번호 확인
- 이후 admin(아이디, 비밀번호 등)을 등록하게 되는데 여기서 입력한 것들은 나중에 접속할 때 사용해야 하니 항상 따로 저장해두자.
Jenkins용 EC2의 탄력적 IP:9000
로 접속하여 초기 비밀번호를 입력한다.참고하고 있는 블로그에서는
docker exec jenkins cat /var/lib/jenkins/secrets/initialAdminPassword
라고 하는데 docker exec jekins cat /var/jenkins_home/secrets/initialAdminPassword
가 맞는거 같다.- Jenkins와 github 연동
github의 main 브랜치에 변화(push or merge)가 있으면 자동을 pull 받아와 build를 할 수 있도록 설정해야한다.
Github 프로필 클릭 → Settings 클릭 → Developer Settings 클릭 → Personal access tokens 클릭



아래와 같이 설정하고 토큰을 생성한다. 토큰은 반드시 기억해두자.

이후 깃허브 레파지토리의 Settings의 Webhook에서
Add Wehook
을 클릭한다. 그리고 Payload URL에 Jekins용 EC2의 IP/github-webhook/
또는 Jekins용 EC2의 도메인이름/github-webhook/
을 입력하고 Webhook을 추가한다.
다시 Jenkins로 돌아와 새로운 item 클릭하고 Freestyle project를 생성한다.

아이템이 만들어지면 구성을 클릭하고 소스 코드 관리 섹션에가서 깃허브 레파지토리의 URL, 아까 받아놓은 Personal access token 그리고 대상 브랜치를 입력한다.


Personal access token은 Credentials에 Add를 클릭하여 Kind를 Secret text로 설정하고 Secret에 입력한다.

마지막으로 빌드 유발 섹션에서
GitHub hook trigger for GITScm polling
(Github에서 push에 의한 hook 이벤트가 발생할 경우 저장소를 polling해서 빌드를 유발)을 선택하고 저장한다. 
- Jenkins JDK 설정
현재 프로젝트는 Java 17로 작성되었고 이에 맞는 JDK를 Jenkins에 설정 해주어야 한다.
Jekins 관리 클릭하고
Global Tool Configuration
클릭한다.
아래와 같이 JDK 17 설치를 설정하고 저장해주자.

마지막으로 Jekins 관리에서 시스템 설정에 들어가 환경변수를 설정해주어야 한다. JAVA_HOME이라는 변수에 Jenkins내에 앞에서 설정한 JDK가 설치된 경로를 입력해주어야 한다.

- Jenkins Gradle 설정
현재 프로젝트의 Build Tool은 Gradle이므로 Jenkins에서도 이에 따른 설정을 해주어야 한다. 우선 Jenkins에 Gradle 플러그인이 설치 되었는지 확인하고 그렇지 않다면 설치해준다. 이는
Jenkins 관리
에서 플러그인 관리
를 통해 할 수 있다.
이후
Global Tool Configuration
의 Gradle 섹션에서 자신의 프로젝트와 맞는 Gradle을 추가하고 이를 저장한다.
마지막으로 아이템의 구성에 들어가
Add Build step
을 통해 Invoke Gradle Script를 선택하고 Gradle의 버전을 선택하고 Tasks에 Gradle 작업을 명시하고 저장한다.(당연한 이야기이지만 tasks를 명시하지 않는다면 아무일도 일어나지 않는다.)
- Jekins와 백엔드 어플리케이션 서버 연동
- Publish Over SSH
Name
: 서버 이름을 입력한다.Key
: EC2에 접속하기 위해 사용하는 pem 파일의 내용을 입력하면된다.Hostname
: 연결할 서버의 IP를 입력한다.Username
: 서버의 Username을 입력한다. EC2에 직접 접속하면 확인할 수 있다.Remote Directory
: 배포할 서버의 기본 workspace(EC2에 접속했을 때의 기본 데렉토리)를 입력한다.Name
: 앞에서 설정한 서버 이름Source files
: 전송할 파일Remove prefix
: Source files에서 지정한 경로의 하위 폴더를 지우는 기능이다. 위의 예시같이 입력한다면 폴더를 제외하고 jar 파일만 전송하게 된다.Remote directory
: 백엔드 어플리케이션 서버에서 파일이 전송될 디렉토리Exec command
: 파일 전송이 끝난 후에 백엔드 어플리케이션 서버에서 실행될 명령어
Jekins에서 빌드가 성공적으로 완료된다면 그 결과물(jar 파일)을 백엔드 어플리케이션 서버에 전달해주어야한다.
위에서 말한 빌드 결과물을 다른 서버로 보내기 위해서는
Publish Over SSH
라는 플러그인을 Jenkins에 설치해주어야 한다. 해당 플러그인을 설치한 후에는 Jekins 관리
의 시스템 설정
에서 Publish Over SSH 항목을 작성해주어야 한다.
Publish Over SSH 항목의 작성을 완료하였다면 Test Configuration을 클릭하여 성공하는지 확인한다. (실패했다면 무언가 잘못 입력한 것이다.)
Publish Over SSH 항목의 작성을 완료하고 Test Configuration까지 성공하였다면 아이템의 구성으로 돌아가
빌드 후 조치
에서 Send build artifacts over SSH를 누르고 아래 사진과 같이 입력을 해줍니다.
위의 입력 내용들은 자신이 사용하고 있는 환경에 따라 달라질 수 있다. 예시와 동일하게 작성하려 하지 말고 자신이 사용하고 있는 환경을 고려하여 입력하자.
- .gitignore된 파일들은 어떻게 해야하나?
현재 Jenkins는 main에 push 또는 merge가 되는 경우 github 레파지토리에서 코드를 가져와 빌드를 한다. 당연히 .gitignore된 파일에 대해서는 알지 못하기 때문에 빌드를 실패하게 된다. 이 문제에 대한 해결방법은 생각보다 간단하다. jekins 내부의 workspace에 .gitignore된 파일을 직접 추가해주면 된다. 현재 Jenkins는 EC2에서 Docker로 실행되고 있다. Docker 내부에 vi를 설치하기 귀찮고 힘드니 로컬의 파일을 도커 내부 지정된 위치로 복사하자.
- 테스트할 때 Redis가 필요하면 어떻게 해야하는가?
테스트할 때 Redis가 필요하다면 Jenkins 내부에서 Redis를 따로 실행시킬 수 없기 때문에 build가 실패하게 될 것으로 생각된다. 하지만 찾아보니 embedded h2와 같이 embedded Redis도 존재하는거 같다. 이걸 사용하면 해당 문제를 해결할 수 있지 않을까?
- Slack alarm 설정
아래 글을 참고하여 설정하자
무중단 배포 설정 및 스크립트
- Nginx
- Nginx 설치
- Nginx 설정
- 주의
sudo vim /etc/nginx/nginx.conf
를 입력하여 설정 파일을 열고 http 블록내에 아래 server 블록을 추가시켜 주자.sudo vim /etc/nginx/conf.d/service-url.inc
를 입력하고 아래 내용을 작성한 후 저장한다.마지막으로
sudo service nginx restart
를 입력하여 Nginx를 재시작 해준다.http 블록내 아래 부분을 지워주도록 하자. 안지워주면 계속 Nginx에서 기본적으로 제공하는 페이지만 보여준다.
- Dockerfile 및 쉘 스크립트 작성
- Dockerfile
- deploy.sh
- switch.sh
Dockerfile과 쉘 스크립트들은 블로그에 있는 것들을 카피하고 필요한 부분만 변경하였다.