Jenkins AWS Github Docker 사용한 CI/CD 구축
안녕하세요. 이번 시간엔 요즘 학습하고 있는 Docker, Jenkins와 Github, AWS를 사용하여
SpringBoot 서버 CI/CD 구축을 진행하는 과정을 보여드리겠습니다.
현재 완벽하게 숙달된 상태가 아닌,
여러 자료들을 찾아보며 처음 성공했던 과정을 기록하는 것입니다.
혹시라도 잘못된 부분이 있다면 댓글로 지적 부탁드리겠습니다!!
바로 시작해보겠습니다.
사용하는 도구 및 환경
- Mac
- Spring boot
- gradle 7.2
- AWS 프리티어 EC2 2대 (젠킨스용,운영용)
- AWS RDS
- AWS Secret Manager
- Mysql
- Jenkins
- Java 11
- Docker
- IntelliJ
- DockerHub
- Github
- IntelliJ에서 Github로 코드를 Push
- Github의 webhook 기능을 통해 Jenkins에 코드 변경 사항을 알린다.
- Jenkins 동작
- 지정한 Repository의 코드를 받아 빌드, 테스트 진행한다.
- 테스트 성공 시, Dockerfile을 이용하여 이미지를 빌드하고 DockerHub에 빌드한 이미지 Push
- 미리 만들어둔 deploy.sh 파일을 운영 EC2로 전송하고 운영 EC2에 deploy.sh 실행 명령을 보낸다.
- deploy.sh 안에는 DockerHub에서 이미지를 받아오고 실행시키는 코드가 담겨있다.
- 운영용 EC2에서 deploy.sh 파일을 실행하여 docker로 spring boot 프로젝트를 띄운다.
- SpringBoot서버는 RDS와 연결되며 주요 설정값은 Secret Manager에 저장한다.
1. 프로젝트 세팅
프로젝트 구조
SpringBoot 서버가 정상적으로 작동하는지 확인을 위해 간단한 프로젝트를 생성한다.
프로젝트에 필요한 파일은 다음과 같다.
HealthController
package com.sj.dockerpractice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HealthController {
@GetMapping("")
public String healthCheck() {
return "ping-pong!";
}
}
서버가 정상적으로 작동하고 있는지 확인하기 위한 RestController이다.
deploy.sh
# 가동중인 app 도커 중단 및 삭제
sudo docker ps -a -q --filter "name=app" | grep -q . && docker stop app && docker rm app | true
# 기존 이미지 삭제
sudo docker rmi pgmjun/cicdstudy:latest
# 도커허브 이미지 pull
sudo docker pull pgmjun/cicdstudy:latest
# 도커 run
docker run -d -p 8080:8080 --env-file=env_list.txt --name app pgmjun/cicdstudy:latest
# 사용하지 않는 불필요한 이미지 삭제 -> 현재 컨테이너가 물고 있는 이미지는 삭제되지 않습니다.
docker rmi -f $(docker images -f "dangling=true" -q) || true
deploy.sh 는 운영용 EC2에서 수행할 작업을 명시해놓은 파일이다.
이미지는 도커허브ID/도커허브REPOSITORY:TAG 형태로 작성한다.
--env-file : 환경변수를 저장할 파일 명인데 이따 운영용EC2에 Secrets Manager 사용을 위해 만들 예정이다.
도커허브 관련 내용은 아래에서 설명하니 우선 사용할 값들을 미리 적어둔다.
Dockerfile
#스프링 부트 프로젝트 빌드 및 jar 파일 생성
FROM openjdk:11-jdk-slim as builder
COPY gradlew .
# 실제 파일을 이미지에 복사한다
COPY gradle gradle
COPY build.gradle .
COPY settings.gradle .
COPY src src
RUN chmod +x ./gradlew
# 프로젝트 루트 디렉토리에서 아래 명령어를 실행하면 ./build/libs 디렉토리에 jar 실행파일이 생성된다.
RUN ./gradlew bootjar
#builder에서 jar파일 복사 및 실행
FROM openjdk:11-jre-slim
# heurit-refactoring-0.0.1-SNAPSHOT.jar 이라는 이름으로 현재 이미지에 저장한다.
COPY --from=builder build/libs/*.jar app.jar
VOLUME /tmp
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
VOLUME을 /tmp에 마운트하는 이유는
spring boot의 내장 WAS인 Tomcat의 default 저장소가 /tmp인데
위와 같이 볼륨 마운트를 해주면 호스트의 /var/lib/docker에 임시파일을 만들고 컨테이너 안의 /tmp 와 연결할 수 있다고 한다.
application-dev.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: ${MYSQL_URL}
username: ${MYSQL_USERNAME}
password: ${MYSQL_PASSWORD}
devtools:
restart:
enabled: true
jpa:
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
show-sql: true
properties:
hibernate:
format_sql: true
hibernate:
ddl-auto: update
naming:
implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
generate-ddl: true
open-in-view: false
server:
port: 8080
보통 프로퍼티 관리를 위해 application.yml을 사용한다.
하지만 Local용과 Deploy용의 프로퍼티는 차이가 존재할 수 있기 때문에
application-dev.yml을 사용할 것이다.
2. EC2 생성
:: 운영용 EC2 만들기 ::
AWS에 접속하여 EC2를 검색하여 페이지에 접속한 뒤 인스턴스 시작 버튼을 클릭한다.
이름은 알아보기 편한 이름으로 짓는다.
EC2 이미지는프리 티어로 사용중이면 무료로 이용할 수 있는
Amazon Linux 2 AMI 을 선택한다.
인스턴스 유형 또한 무료 사용이 가능한
t2.micro 를 선택
추후 EC2에 접속할 때 사용해야하기 때문에 키페이를 생성한다.
우측에 있는 새 키 페어 생성 버튼을 클릭한다.
키 페어의 이름 또한 자신이 알아보기 쉽도록 설정하고 나머지 설정은 위 사진과 같이 해준 뒤 생성 버튼을 클릭한다.
보안 그룹은 새로 생성하는 것을 선택하고 세부 설정은 아래에서 세팅하겠다.
스토리지는 프리 티어에서 최대 30GB까지 지원하기 때문에 30을 입력한 뒤
다른 설정을 건드리지 않고 인스턴스를 생성한다.
인스턴스가 생성되면 인스턴스의 보안 그룹에 들어가 인바운드 규칙을 변경해야한다.
- HTTPS : Anywhere IPv4
- TCP 8080 : Anywhere IPv4
- SSH : 내 IP
:: Jenkins용 EC2 만들기 ::
Jenkins용 EC2는 위와 설정을 같게 해주되 아래 설명하는 부분만 다르게 세팅한다.
인스턴스 이름을 운영용 서버와 구분할 수 있도록 생성한다.
키 페어는 아까 생성해둔 키 페어를 그대로 사용한다.
보안 그룹을 새로 생성하고 인바운드 규칙을 다음과 같이 변경한다.
- SSH : 내 IP
- TCP 8080 : 내 IP
:: 탄력적 IP 할당 ::
EC2는 인스턴스가 중지되었다가 다시 실행될 때마다 IP가 새롭게 할당된다.
이걸 그대로 사용한다면 인스턴스를 재시작할 때마다 AWS에 접속하여 IP를 확인해야하는 불상사가 벌어진다.
이때 사용하는 것이 바로 탄력적 IP이다.
탄력적 IP를 할당하면 인스턴스를 중지했다 재시작을 시켜도 IP변경이 발생하지 않아 편리하다.
바로 할당해보자
우선 EC2-네트워크 및 보안-탄력적 IP 에 접속하여 우측 상단의 탄력적 IP 할당 버튼을 클릭한다.
다른 설정은 딱히 건드릴게 없으니 알아보기 편하게 Name 태그를 통해 구분만 해서 2개 생성한다.
최종적으로 이렇게 2개가 생성되면 된다.
이제 이 IP를 인스턴스에 연결해야한다.
할당할 IP의 체크 박스를 선택해준 뒤
우측 상단 작업 드롭다운 박스에서 탄력적 IP 주소 연결 선택
연결 페이지에선 인스턴스 선택에서 아까 생성해둔 인스턴스를 이름에 맞게 연결해주면 된다.
다시 인스턴스 탭으로 이동하면 인스턴스 정보에서 탄력적 IP가 제대로 연결된 것을 확인할 수 있다.
3. RDS 생성 및 연결
RDS는 과거 포스팅이 있어 참고하여 IAM과 DB를 생성한다.
IAM 권한은 해당 포스팅대로 넣지 않고 잡다한 것들을 빼고 현재 필요한 권한만 넣는다.
:: IAM / RDS 생성 ::
IAM은 노출되었을 시에 누군가가 악용한다면 AWS로 부터 어마어마한 금액의 영수증이 날아올 수 있으므로 정말 필요한 권한만 넣어서 사용해야한다.
위 포스팅을 통해 IAM과 RDS를 만들었다면 이제 SpringBoot 프로젝트에 연결해야한다.
:: RDS 설정 ::
우선 사용할 Database를 생성해야하기 때문에 RDS로 접속해야한다.
맥북 기준으로 설명하기 때문에 Window는 본인 블로그 AWS 카테고리에서 MySQL RDS 접속 포스팅을 보며 따라와주길 바란다.
homebrew가 깔려있단 기준하에 Terminal을 실행하여 아래 명령어를 입력하여 Mysql을 설치한다.
Install MySQL
brew update
brew install mysql
설치가 완료되면 아래 명령어로 RDS에 접속한다.
mysql -u 계정이름 -p -h 엔드포인트
계정 이름은 RDS 생성 시 입력했던 값이다. 본인은 기본 값인 admin으로 했었다.
접속이 완료되면 아래 명령어를 통해 스프링 프로젝트와 연결할 db를 생성하고 연결을 끊는다.
# database 확인
show databases;
# database 생성
create database 사용할DB명;
# 연결 종료
quit
:: Secrets Manager 생성 ::
해당 내용 또한 이미 포스팅을 해놓은 것이 있기 때문에 참고하여 프로젝트에 Secrets Manager를 적용해보자
application-dev.yml에 적용할 DB URL, DB Username, DB Password 를 담은 Secrets를 생성한다.
DB URL : url은 jdbc:mysql://RDS엔드포인트:3306/사용할DB명
bootstrap.yml 작성과 build.grade에 의존성 추가까지 마쳤다면
위와 같이 정상적으로 실행되는지 테스트 한 후에 다음 단계로 넘어가자
4. EC2 기초 세팅
:: EC2 접속 세팅 ::
ssh -i pem키위치 탄력적IP주소
Local에서 EC2에 접속하기 위해선 Terminal에 다음과 같은 명령어를 입력해야한다.
하지만 너무 귀찮지 않은가?
때문에 이 명령어를 ssh ec2이름 으로 줄여보자
mkdir ~/.ssh
cp pem키파일명.pem ~/.ssh/
우선 ec2 생성 과정에서 만들었던 pem키를 ~/.ssh/ 로 옮긴다.
chmod 600 ~/.ssh/pem키파일명.pem
그 다음 pem키의 권한를 변경한다.
vim ~/.ssh/config
그리고 pem키 사용 관련 설정을 위한 config 파일을 ~/.ssh 디렉토리 안에 만들어준다.
Host : 접속할 때 사용할 단축명
HostName : 탄력적 IP
User : ec2 사용자
IdentityFile : pem키 파일 위치
config파일은 다음과 같이 설정한 뒤 :wq 로 저장/종료 한다.
chmod 700 ~/.ssh/config
config 파일은 실행 권한이 필요하기 때문에 rwx 권한을 부여한다.
이제 접속을 위한 세팅이 끝났기 때문에 복잡한 명령어가 아닌
`ssh 단축명` 으로 접속할 수 있다.
ex) ssh ec2-deploy, ssh ec2-jenkins
최초로 접속할 때 yes를 한번 입력해주면 다음부터 원만하게 접속이 가능하다.
:: EC2 Hostname 변경 ::
ec2에 접속을 간단히 할 수 있도록 변경하였지만 접속된 ec2가 운영용인지 jenkins용인지 구분하기 어렵다.
때문에 Hostname을 변경하여 알아보기 쉽게 변경하고자 한다.
방법은 간단하다.
sudo hostnamectl set-hostname 원하는이름 # hostname 변경
sudo reboot # 재시작
첫 번째 줄 명령어로 Hostname을 변경하고 적용을 위해
두 번째 줄 명령어로 재시작하면 이름이 변경된 것을 확인할 수 있다.
:: EC2 시간 변경 ::
EC2의 기본 서버 타임존은 UTC이기 때문에 한국 시간으로 변경해주자.
sudo rm /etc/localtime
sudo ln -s /usr/share/zoneinfo/Asia/Seoul /etc/localtime
위 명령어를 통해 시간을 한국 시간으로 변경이 가능하다.
변경된 시간은 `date` 명령어로 확인할 수 있다.
5. 운영용 EC2 세팅
운영용 EC2는 Jenkins가 DockerHub에 Push한 이미지를 가져와 Docker를 통해 컨테이너로 띄울 것이기 때문에 우선 도커 설치 작업을 수행하겠다.
# 운영 EC2 접속
ssh 단축명
# 패키지 업데이트
sudo yum -y upgrade
# 도커 설치
sudo yum -y install docker
# 도커 설치 작업이 잘 되었는지 버전 확인
docker -v
# 도커 시작
sudo service docker start
# 도커 그룹에 사용자 추가 -> docker가 그룹명, ec2-user가 사용자명
sudo usermod -aG docker ec2-user
그 다음 초반에 docker run시 --env-file 옵션에 담아주었던 env_list.txt를 생성한다. 이곳엔 Secrets Manager 사용을 위한 IAM키가 담겨있다. Docker의 컨테이너 내부에서 aws cli를 통해 설정하는 방법을 찾지 못해 ENV로 값을 삽입하여 사용하게 되었다.
조금 더 보안적이고 효율적인 방법이 있다면 댓글 부탁드립니다.
# 환경 변수를 담을 파일 생성
vi env_list.txt
# 파일 내용 =====================
# AWS KEY
AWS_ACCESS_KEY=IAM생성할때받은키값
AWS_SECRET_ACCESS_KEY=IAM생설할때받은비밀키값
#저장 후 종료
:wq
6. Jenkins용 EC2 세팅
EC2로 Jenkins를 사용하게 되면 기본 할당된 RAM 크기인 1GB를 초과하게 된다.
Docker를 통해 Jenkins를 띄우는 것 까지는 문제가 되지 않으나
Github webhook을 이용하여 SpringBoot 프로젝트를 Build하는 과정에서 문제가 발생한다.
이 문제를 해결하기 위해 Swap 메모리를 사용하면 된다.
Swap 메모리란?
RAM 용량이 부족할 경우 HDD의 일부를 RAM 처럼 사용하는 것이다.
이걸 사용해서 RAM 용량이 부족하여 시스템에 문제가 생기는 일을 방지할 수 있다.
RAM이 2GB 이하인 경우 AWS에서 권하는 스왑 공간은 2배이다.
즉, 1GB인 RAM을 사용하는 현제 ec2 t2.micro환경은
스왑 메모리 방식을 통해 2GB를 추가하여
총 3GB의 메모리로 사용이 가능하다.
sudo dd if=/dev/zero of=/swapfile bs=128M count=16
dd 명령어를 통해 swap 메모리를 생성할 수 있다.
of : swapfile 경로
bs : Block 사이즈
count : Block 갯수
지정한 블록 사이즈는 인스턴스에서 사용 가능한 메모리보다 작아야한다. 그렇지 않으면 memory exhauted 오류가 발생한다.
현재 설정대로면 128MB * 16 = 2048MB 로 스왑 메모리는 총 2GB가 된다.
# swapfile 읽기 쓰기 권한 업데이트
sudo chmod 600 /swapfile
# 리눅스 swap 영역 설정
sudo mkswap /swapfile
# 스왑 메모리를 활성화
sudo swapon /swapfile
# 절차가 성공했는지 확인
sudo swapon -s
swap 메모리를 생성했다면 위의 명령어들을 통해 swap 메모리를 활성화 시켜주자.
그럼 이렇게 메모리가 활성화 된다.
# 파일 열기
sudo vi /etc/fstab
# 파일 가장 마지막에 다음을 추가하고 :wq로 저장하고 종료
/swapfile swap swap defaults 0 0
fstab을 설정하여 부팅 시에 swap 파일을 활성화 되도록 설정해주면 EC2 세팅은 끝이다.
`free` 명령어를 통해 2GB 메모리가 추가 할당된 것을 확인할 수 있다.
:: 도커 설치 ::
Jenkins용 EC2 또한 Jenkins를 Docker 환경에서 컨테이너로 실행시키는 것이기 때문에 Docker 설치가 필요하다.
# jenkins용 EC2 접속
ssh 단축명
# 패키지 업데이트
sudo yum -y upgrade
# 도커 설치
sudo yum -y install docker
# 도커 설치 작업이 잘 되었는지 버전 확인
docker -v
# 도커 시작
sudo service docker start
# 도커 그룹에 사용자 추가 -> docker가 그룹명, ec2-user가 사용자명
sudo usermod -aG docker ec2-user
위와 똑같이 Docker를 설치하도록 하자
:: Dockerfile 생성 ::
현재 시나리오는 EC2에서 Docker를 통해 Jenkins를 띄우고 Github Webhook을 통해 특정 Branch에 Push가 들어오면, Jenkins가 새로운 Docker Image를 생성하여 DockerHub에 Push하는 시나리오다.
따라서 Docker로 띄운 Jenkins 컨테이너 내부에서 Docker 명령어 사용이 가능해야한다.
다시말하면 Docker 위에서 Docker를 띄워야한다는 말인데 이러한 방식은 Docker in Docker(DinD) & Docker out of Docker(DooD)로 2가지가 있다.
Docker 측에선 도커 안에 도커를 띄우는 것을 권장하지 않지만 그나마 DooD 방식은 권장하고 있기 때문에 DooD 방식을 통해 구현할 예정이다.
DooD 방식으로 도커 위에 도커를 띄우기 위해서는 v 옵션으로 호스트의 docker.sock을 빌려서 사용하면 된다.
docker.sock이란?
docker.sock은 Docker 서버 측 데몬인 dockerd가 REST API를 통해 명령을 줄 인터페이스와 통신할 수 있도록 하는 Unix 소켓이다.
컨테이너 내부에서 데몬과 상호작용 할 수 있게 도와주는 것이라고 생각하면 된다.
소켓은 소프트웨어 사이에 데이터를 전달하는 네트워크 엔드 포인트이다.
그럼 이제 Dockerfile과 docker_install.sh 파일을 작성해보자.
Dockerfile
# Dockerfile 생성
sudo vim Dockerfile
# =======================================
# Dockerfile 작성 시작
FROM jenkins/jenkins:jdk11
# 도커를 실행하기 위한 root 계정으로 전환
USER root
#도커 설치
COPY docker_install.sh /docker_install.sh
RUN chmod +x /docker_install.sh
RUN /docker_install.sh
# 도커 그룹에 사용자 추가
RUN usermod -aG docker jenkins
USER jenkins
EC2 내의 Docker에서 Jenkins를 띄우기 위한 Dockerfile이다.
Docker 내부에 Docker를 설치하기 위한 명령들도 포함되어 있는 것을 확인할 수 있다.
Docker는 docker_install.sh 를 통해 설치할 예정이다.
docker_install.sh
# docker_install.sh 파일 생성
sudo vim docker_install.sh
# ===============================
#docker_install.sh 파일 작성 시작
#!/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를 설치하기 위한 코드라고 생각하면 편하다.
sudo chmod 666 /var/run/docker.sock
두 파일을 같은 위치에 두고 이미지를 빌드하기 전에 우선적으로 해야할 작업이 있다.
앞서 도커안에 있는 도커는 host의 docker.sock을 빌려서 사용한다고 설명했다. 따라서 docker.sock 파일의 권한을 변경하여 그룹 내 다른 사용자도 접근 가능하도록 변경해줘야 한다.
위의 명령어로 접근 가능하도록 세팅해주자.
docker build -t jenkins .
이제 Dockerfile을 build하여 이미지를 생성한다.
# jenkins Volume 설정을 위한 폴더 만들기
mkdir jenkins
# 해당 폴더에 대해 권한 부여하기
sudo chown -R 1000 ./jenkins
만들어진 이미지를 사용해서 Jenkins 컨테이너를 띄울 때에 -v 옵션으로 볼륨 마운트할 폴더를 만들어야 한다.
폴더를 생성하여 볼륨 마운트를 하는 이유는?
도커는 이미지를 run 시키면 이미지 Layer 위에 Read/Write Layer를 씌워 컨테이너를 실행시킨다.
이미지는 불변을 유지해야하기 때문에 Read/Write Layer에 컨테이너에서 수행되는 내용이 저장되는데 컨테이너가 종료되면
Read/Write Layer가 삭제되어 수행했던 내용들이 전부 삭제된다.
Volume Mount를 하게 되면 Read/Wrtie Layer의 내용이 볼륨 마운트한 폴더에 저장되어 컨테이너가 종료되어도 Host에 있는 마운트된 볼륨에 의해 내용이 사라지지않고 남아 데이터의 유실을 방지할 수 있다.
sudo docker run -d --name jenkins \
-v /home/ec2-user/jenkins:/var/jenkins_home \
-v /var/run/docker.sock:/var/run/docker.sock \
-p 8080:8080 \
-e TZ=Asia/Seoul \
jenkins
마운트할 폴더 생성 및 권한 설정을 완료했다면 위의 코드를 통해 생성된 jenkins 이미지를 run 한다.
-d : 백그라운드 실행
--name : 컨테이너명 설정
-v : 볼륨 마운트
-p : 포트 설정 ( 호스트port:컨테이너port )
-> 호스트의 8080번 포트로 접속하면 컨테이너의 8080번 포트로 연결되게 하는 설정
-e : 환경변수 설정
`docker ps -a`를 통해 컨테이너가 제대로 동작하는지 확인한다.
만약 jenkins 이미지가 실행되지 않고 죽어버린다면 jenkins 디렉터리를 삭제하고 다시 생성한다.
삭제 명령어 : `rm -r jenkins`
7. Jenkins 세팅
Jenkins 컨테이너가 돌아가고 있으니 이제 Jenkins에 접속하여 세팅을 이어가야한다.
바로 시작해보자
:: 접속 및 계정 세팅 ::
브라우저 주소창에 Jenkins EC2 탄력적 IP를 8080번 포트로 접속해보자
ex) 52.79.203.78:8080
접속하게되면 패스워드 입력창이 반겨주는데 패스워드를 확인하기 위해선 Jenkins용 EC2에서 Jenkins 도커 컨테이너로 접속해서 확인해야한다.
# Jenkins용 EC2에서 jenkins 도커 컨테이너에 입력 가능 모드로 진입
sudo docker exec -it jenkins bash
# Jenkins password 출력
cat /var/jenkins_home/secrets/initialAdminPassword
위 명령어로 패스워드를 확인할 수 있다.
다음 나오는 화면에선 좌측의 Install suggested plugins를 선택한 후 계정을 생성한다.
:: 플러그인 설치 ::
Jenkins에 접속했으니 우선 우리가 사용할 플러그인을 다운받아야 한다.
좌측 탭에서 Jenkins 관리 버튼을 클릭
그 다음 Available plugins에 접속하여 아래 플러그인들을 다운받는다.
- Gradle : spring boot의 gradle을 사용하기 위한 플러그인
- GitHub Integration : github의 webhook을 사용하기 위한 플러그인
- Post build task : 빌드 로그를 판단하여 script 혹은 shell을 실행할 수 있게 하는 플러그인
- Publish Over SSH : 다른 EC2에 접속하여 작업을 가능하게 해주는 플러그인
검색해도 나오지 않는 플러그인은 초반에 install suggested plugins 과정에서 다운받아졌을 수 있으니
좌측 탭에 있는 Installed plugins에서 찾아보자.
Gradle이 초반에 자동으로 설치되었기 때문에 나머지를 선택하고 Install with restart 를 클릭한다.
:: Credentials 세팅 ::
다음으로 해줘야하는 것으 credentials 세팅이다.
이것은 Jenkins에서 사용할 계정 정보를 등록하는 공간이다.
webhook 사용을 위한 Github 계정 / DockerHub에 Image 업로드를 위한 DockerHub 계정 을 등록할 것이다.
다시 Jenkins 관리에 들어가 Security 부분에서 Manage Credentials를 클릭한다.
들어가서 global쪽에 있는 Add credentials를 선택한다.
credential을 2개 생성해야하는데 둘 다 위의 선택창은 그대로 두고 Username과 Password만 지정해준다. 그리고 아래로 내리다보면 Description 옵션이 나오는데
Github용 Credential은 Github / DockerHub용 Credential은 DockerHub로 적어준다.
:: Github Webhook 연결 ::
Credential을 생성하였으니 각 서비스와 Jenkins를 연결해야한다. 우선 Github를 연결해보자.
깃허브 프로필에서 Settings 클릭
좌측 탭에서 Developer settings 클릭
좌측 탭에서 Personal access tokens의 Tokens 클릭
우측의 Generate new token - Generate new token (classic) 클릭
본인이 알아보기 편한 토큰이름 입력
repo, admin:org, admin:repo_hook 권한 부여
발급받은 키는 한 번만 볼 수 있으므로 따로 저장
webhook 적용할 레포지토리에 들어가서 Settings 클릭
좌측 탭에서 Webhooks 클릭
Payload URL에 Jenkins EC2 탄력적IP를 아래 형식으로 입력
http://JenkinsEC2탄력적IP:8080/github-webhook/
다시 Jenkins 관리로 돌아와 시스템 설정 클릭
아래로 내리다보면 Github탭이 있다.
Add Github Server 를 해준다.
Name은 알아보기 편한대로 설정하고
API URL은 그대로 둔다.
Credentials를 설정해야하는데 그러기 위해 Add 버튼을 클릭하여 Jenkins Credential을 생성한다.
Credential의 Kind는 Secret text로 변경한다.
Secret은 아까 발급한 git token을 입력한다.
더 건드리지 않고 Add를 클릭한다.
Credentials 를 방금 만든 것으로 변경해주고 Test connection 버튼을 사용하여 정상적으로 연결되었는지 확인한다.
Jenkins와 GitHub 연결을 해주었지만 아직은 정상작동하는 단계는 아니다.
이유는 Jenkins용 EC2에서 GitHUb에서 webhook으로 ec2에 보내는 요청 IP를 허용해주지 않았기 때문이다.
이 부분은 SSH 설정 과정에서 AWS에 접속하여 설정해줄 것이다.
:: SSH Servers 세팅 ::
github 설정에서 조금만 더 내리면 SSH Servers 설정이 있다. 추가를 눌러서 작성해주자
Name : Job에 표시될 이름
Hostname : IP Address (운영 EC2의 탄력적 IP)
Username : ssh 접근 계정
Remote Directory : 업로드될 디렉토리(안적어도 됨)
하단에 있는 고급을 선택 후 User password authentication, or a use a different key 선택
그리고 Key값을 입력하는데 운영용 EC2를 생성할 때 얻은 pem키의 값이다. 이는 아래 명령을 통해 얻을 수 있다.
cat ~/.ssh/pem키명.pem
이 접근 과정은 Jenkins EC2가 ssh키(pem키)를 가지고 운영용 EC2에 접근하는 과정이다.
때문에 운영용 EC2의 보안 그룹에서는 Jenkins의 ssh 접근을 허용해줘야 한다.
이제 AWS에 접속하여 Jenkins EC2와 운영용 EC2의 보안 그룹 설정을 변경해보자.
:: AWS EC2 보안 그룹 규칙 변경 ::
우선 운영용 EC2의 보안 그룹을 선택한다.
인바운드 규칙 편집을 선택한다.
규칙 추가 버튼을 눌러 SSH 프로토콜 / Jenkins EC2 탄력적IP 를 입력한 규칙을 생성하고 저장한다.
다음으로 Jenkins용 EC2 보안그룹 설정이다. 똑같은 방법으로 Jenkins용 EC2의 보안그룹-인바운드 규척 편집에 접속한다.
3가지 IP를 8080포트로 추가하는데 IP는 아래에 있는 것을 추가한다.
192.30.252.0/22
185.199.108.0/22
140.82.112.0/20
webhook을 위한 3개의 IP이다.
모든 연결 설정은 끝났으니 다시 Jenkins로 돌아가서 Test Connection을 수행한다.
제대로 따라왔다면 Success가 출력될 것이다.
:: Gradle 세팅 ::
이제는 빌드에서 사용할 Gradle 버전을 세팅해주어야한다.
Jenkins 관리 - Global Tool Configuration 을 클릭한다.
아래로 내리다보면 Gradle 메뉴가 있는데 Add Gradle버튼을 클릭하여 설정해준다.
name은 식별하기 쉬운 이름으로 정해주고 Version은 사용할 프로젝트의 gradle버전을 선택하고 Save한다..
스프링부트 프로젝트는 gradle/wrapper/gradle-wrapper.properties 에서 버전을 확인할 수 있다.
:: 프로젝트 세팅 ::
우선 도커허브에 Repository를 생성해야한다.
중요한 업무용 프로젝트라면 private Repository로 생성한다.
하지만 개인 계정에서 private 저장소는 단 1개만 생성이 가능하므로 연습용이라면 Public으로 생성하도록 하자.
저장소를 생성했다면 이제 코드를 빌드, 테스트하고 이미지를 빌드하여 도커허브에 푸시하는 작업까지 수행해보도록 하겠다.
Jenkins 관리의 좌측 탭에서 `새로운 Item` 클릭
자신이 원하는 이름으로 프로젝트명 입력후 Freestyle project 생성
GitHub project 체크박스 선택 후 Project url에 깃허브 프로젝트 주소 입력
소스 코드 관리에서 Git 선택 후 위와 같이 Repositoy URL에 프로젝트주소.git을 입력한다.
Credentials는 아까 생성한 Github Credential 선택
아래쪽에서 Branch Specifier는 Build할 Branch를 입력
빌드 유발에서 GitHub hook triiger for GITScm polling 선택
빌드 환경에서 Use secret text(s) or file(s) 선택 후 Bindings는 Username and password(separated) 선택
각각 Variable에 Username으로 사용할 변수명, password로 사용할 변수명을 지정해주고
Credentials에 specific credentials을 선택하고 아까 만들어놓았던 DockerHub Credential 선택
이렇게 설정하면 아래 Command 부분에서 DockerHub Credential에 입력해놓았던 Username과 password를 USERNAME, PASSWORD변수로 사용이 가능하다.
Build Steps에서 Invoke Gradle script 선택
Invoke Gradle 선택 후 아까 생성한 Gradle Version 선택
Tasks에는 clean build 입력
clean build는 이전에 있던 build파일을 지우고 새로 build하는 명령어이다.
하단에서 Add build step 버튼을 클릭하여 Execute shell 추가
Command에 아래 명령어를 자신의 환경에 맞춰 입력한다.
# Dockerfile 빌드하여 Image 생성
docker build -t 도커허브ID/REPOSITORY명:TAG .
# DockerHub Credential에 설정한 값으로 dockerHub에 로그인 시도
echo $PASSWORD | docker login -u $USERNAME --password-stdin
# 이미지를 dockerhub에 push
docker push 도커허브ID/REPOSITORY명:TAG
# 이전에 있던 이미지 삭제
docker rmi 도커허브ID/REPOSITORY명:TAG
주석은 제거하여 사용하며 `도커허브ID/REPOSITORY명` 이 무엇인지 모르겠다면 아래 사진을 보고 참고하자.
다음 작업은 Jenkins EC2에서 운영용 EC2로 접속하여 수행하는 작업이다.
빌드 후 조치 추가
Send build artifacts over SSH 선택
- Name : 전에 세팅했던 ssh 서버를 선택합니다.
- Source files : 운영용 EC2로 보낼 파일의 위치를 작성합니다.
- Remove prefix : 파일 앞부분의 경로를 적어줍니다.
- scripts/deploy.sh 파일의 scripts/를 제거하고 deploy.sh만 복사시켜 줍니다
- Remote directory : 운영용 EC2에 업로드될 경로를 작성합니다
- Exec command : 실행할 명령어를 작성합니다.
# 운영용 EC2에서 도커에 로그인
echo $PASSWORD | docker login -u $USERNAME --password-stdin
# deploy.sh 파일을 실행
sh deploy.sh
추가적으로 Name 바로 아래 고급을 클릭하고 Verbos output in console을 체크해두면 빌드 후 조치 과정에서 발생하는 output 도 콘솔에 찍힌다.
최종적으로 작업이 끝난 후 workspace를 비우도록 Delete workspace when build is done 빌드 후 조치를 추가한다.
설정이 끝났다면 하단에 저장을 클릭하면 모든 세팅이 끝난다.
이제 github에 코드를 푸시하시면 자동으로 빌드되고 배포까지 성공하게 된다.
8. 작동 테스트
:: 수동 테스트 ::
지금 빌드 버튼을 통해 CI/CD가 제대로 작동하는지 테스트 해볼 수 있다.
지금 빌드 클릭시 하단에 이렇게 표시된다.
#번호 를 클릭하면 세부 내역을 볼 수 있다.
좌측 탭에 Console Output 버튼 클릭시 Console 메시지를 볼 수 있다.
본인은 DockerHub PASSWORD를 잘못입력해서 4번이나 수정을 거쳐 성공시켰다.
실패해도 Console Output에서 로그확인이 가능하기 때문에 비교적 쉽게 해결이 가능하다.
성공을 하니 실제로 DockerHub 레포지토리에 이미지가 올라왔다.
:: 자동 테스트 ::
GitHub에 Push가 되면 CI/CD가 작동해야하니 조금의 수정을 하여 푸시해보았다.
그럼 곧바로 빌드 대기 목록에 추가가 되고
조금 지나면 빌드가 진행된다.
최종적으로 cicdStudy 프로젝트의 빌드가 완료되고 서버에는 변경된 사항이 반영된다.
꼭 해보고 싶었던 CI/CD인데 성공하게 되어 너무 기뻤다.
추후엔 GitActions 를 이용한 CI/CD도 학습하여 포스팅하겠다.
우선 다음 시간엔 github에 Push 후 빌드에 Jenkins에서 실패했을 때 이메일로 알림을 받는 방법에 대해 포스팅하는 것을 우선순위로 두었다.
참고로 도커의 기본적인 내용들은 현재 학습을 하며 github에 올리고 있으니 필요한 사람들은 참고하길 바란다.
reference
https://backtony.github.io/spring/aws/2021-08-08-spring-cicd-1/