CD는 지속적인 서비스 제공(Continuous Delivery) 또는 지속적인 배포(Continuous Deployment)를 의미한다. 간단하게 자동 배포라고 이해하면 된다.
프로젝트 빌드, 도커 빌드, 배포 등 배포의 전 과정을 직접하지 않고
GitHub가 업데이트 될 때마다 자동으로 수행하도록 하기 위해 구축했습니다.
자동 배포를 통해 개발자는 개발에만 집중을 할 수 있게 됩니다 !
배포 & 운영 서버에 도커를 설치합니다
# Ubuntu의 패키지 목록을 업데이트
sudo apt update
# 도커 설치
sudo apt install -y docker.io
# 도커 컴포즈 설치
sudo apt install docker-compose
편의를 위해 sudo 명령 생략 작업 수행
# docker에 sudo 권한 주기
sudo usermod -aG docker ubuntu
# 서버 로그아웃
logout
# 재 접속 후 확인
id -nG
스프링 jar 파일을 Docker Image로 빌드할 때 빌드 설정을 지정하는 Dockerfile이 필요하다
파일명: Dockerfile
위치: 프로젝트 root 디렉토리
# 프로젝트 Java 버전에 맞는 openjdk 이미지 설정
FROM openjdk:17-jdk-slim
# db Dependencies 설정
RUN apt-get update && apt-get install -y mariadb-client
# gradle 빌드를 하면 build/libs 하위에 *.jar 생성됨. 해당 '*.jar'를 'app.jar'로 cpoy
ARG JAR_FILE_PATH=build/libs/*.jar
COPY ${JAR_FILE_PATH} app.jar
# .yaml local/prod 프로필 분리 구조일때 실행할 프로필 지정 prod(=운영)
ENV USE_PROFILE prod
# 이미지 빌드 명령
ENTRYPOINT ["java", "-Dspring.profiles.active=${USE_PROFILE}", "-jar", "app.jar"]
'스프링 프로젝트 컨테이너'와 'redis 컨테이너'를 함께 실행시키고 연결하기 위해
Docker Compose를 이용할 것이기 때문에 docker-compose.yaml 도 작성해둔다
파일명: docker-compose.yaml
위치: 프로젝트 root 디렉토리
기존 docker run 명령어를 하나의 파일로 관리한다고 이해하면 쉽다
'수행할 서비스 이름', 'image', 'container_name', 'ports' 는 상황에 맞게 지정하면 된다.
services:
redis: # 수행할 서비스 이름
image: redis:latest # run 할 image
container_name: test_redis # container 이름
ports: # container port 바인딩
- 6379:6379
volumes: # redis volume 설정
- ./redis/data:/data
- ./redis/conf/redis.conf:/usr/local/conf/redis.conf
restart: always # 컨테이너가 종료됐을 때 재시작 여부
command: redis-server /usr/local/conf/redis.conf # 실행할 명령
spring_application:
image: test_spring
container_name: application
ports:
- 8080:8080 # 서버 port
- 443:443 # https 인증 port
depends_on: # 의존관계 설정 <- redis 서비스를 먼저 시작하도록 지정
- redis
Docker image 빌드 후 Docker Hub에 Push & Pull 하기 위해
회원가입 및 Private Repository를 생성한다.
Namespace에는 자신의 계정이름이 자동 입력되어 있고
Repository Name을 설정한 후 Private 로 선택하면 된다.
나중에 아래와 같이 접근하게 된다.
docker push Namespace/Repository:tagname
EC2 프리티어는 메모리가 작아 젠킨스 빌드 발동 시 서버가 다운된다.
따라서 메모리 swap 설정을 통해 늘려준다.
보통 기존의 2배로 설정. EC2 프리티어 메모리 사이즈: 1G
# 2GB 크기의 스왑 파일을 생성
sudo fallocate -l 2G /swapfile
# /swapfile의 권한을 600으로 설정
sudo chmod 600 /swapfile
# /swapfile을 스왑 파일로 설정
sudo mkswap /swapfile
# /swapfile을 스왑 파일로 활성화
sudo swapon /swapfile
jenkins 컨테이너 내부에서 docker 이미지를 빌드하려면 docker를 사용하여 빌드를 해야한다.
기본적으로 컨테이너 내부에는 docker 가 없기 때문에 이를 해결하는 다양한 방법이 존재한다.
하나는 jenkins 컨테이너 내에 다른 docker 데몬을 실행하는 것 인데 이 방법은 docker에서 권장하지 않는다.
채택한 방법으로는, host의 docker 소켓을 컨테이너와 공유하는것이다.
이렇게 하면 컨테이너가 host의 docker 데몬을 사용하여 이미지 빌드를 수행을 할 수 있다.
# host port:docker container port 연결
# docker 소켓 연결
# container name
# docker image
docker run -p 8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
--name jenkins \
jenkins/jenkins:lts
# 해당 jenkins 컨테이너의 shell에 접속
docker exec -it -u root jenkins bash
# docker apt repository 구성 및 docker ce 바이너리 설치
apt-get update && \
apt-get -y install apt-transport-https \
ca-certificates \
curl \
gnupg2 \
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"라는 이름의 그룹을 생성
groupadd -f docker
# "jenkins" 사용자를 "docker" 그룹에 추가
usermod -aG docker jenkins
# /var/run/docker.sock 파일의 소유권을 root 사용자와 docker 그룹으로 변경
chown root:docker /var/run/docker.sock
[ http://서버 ip 주소:port ] 로 접속하면 Jenkins 초기 페이지가 뜬다.
아래 명령어로 초기 비밀번호를 확인 하여 입력해준다.
docker exec -it jenkins bash -c "cat /var/jenkins_home/secrets/initialAdminPassword"
다음 화면에서 [Install Suggested plugins]를 선택하고 plugins 설치가 끝나면 계속 화면을 따라 계정을 생성해준다
마지막으로 Jenkins URL 설정이 나오는데 위에서 접속했던 [ http://서버 ip 주소:port ] 로 설정해주면 된다.
[Dashboard]-[Jenkins 관리]-[plugins]-[Available plugins]로 가서 파이프라인 구축에 필요한 플러그인들을 설치해준다.
설치할 plugins
- gradle # Gradle build scripts 플러그인
- github integration # github의 webhook 사용 플러그인
- post build task # 빌드 로그를 판단하여 script 혹은 shell을 실행할 수 있게 하는 플러그인
- publish over ssh # 다른 EC2에 접속하여 작업을 가능하게 해주는 플러그인
[Settings]-[Developer Settings]-[Personal access tokens]-[Tokens (classic)]-[new] 선택 후
아래 이미지와 동일하게 설정해준다. 발급받은 토큰은 !반드시! 따로 저장해두어야 한다.
발급 시에만 확인이 가능하고 토큰을 잃어버리면 새로 발급 받아야 한다.
총 3개의 Credentials 등록이 필요하다.
[Dashboard]-[Jenkins 관리]-[Credentials]에 들어와서 global 도메인의 [Add credentials] 선택
- kind: Secret text 선택
- Scpoe: Global 선택
- Secret: GitHub Personal access tokens 입력
- ID: 식별 가능한 이름 입력
- Description: 설명 입력
- kind: Username with password 선택
- Scpoe: Global 선택
- username: GitHub 계정 아이디
- password: GitHub Personal access tokens 입력
- ID: 식별 가능한 이름 입력
- Description: 설명 입력
- kind: Username with password 선택
- Scpoe: Global 선택
- username: Docker Hub 계정 아이디
- password: Docker Hub 계정 비밀번호
- ID: 식별 가능한 이름 입력
- Description: 설명 입력
[DashBoard]-[Jenkins 관리]-[System] 에 들어와서 쭉 내리다 보면 GitHub 파트가 나온다.
Name: 식별 가능한 이름
API URL: 자동 입력된 그래도 두기. 수정 x
Credentials: 위에서 등록한 1번 Credentials
그리고 GitHub Repository 에서도 설정을 해줘야 하는데
[Repository]-[Settings]-[Webhooks]-[Add webhook]로 가서
Payload URL: Jenkins URL
Content type: application/json
Just the push event ✅
Active ✅
[DashBoard]-[Jenkins 관리]-[System] 맨 아래로 내려오면 SSH Servers 탭이 있다.
여기에 배포시킬 운영 EC2를 연결해줘야 한다.
Name: 식별 가능한 이름
Hostname: 운영 서버 ip
Username: EC2 OS 차이가 있다. 우분투의 경우 ubuntu
Remote Directory : 파일이 업로드될 디렉토리. 기본은 루트 디렉토리
그 다음 고급을 눌러 [Use password authentication, or user a different key]를 클릭하고 Key를 작성한다. Key는 운영 EC2의 Pem키를 넣어주면 된다.
# Key 확인 방법 {pemname}에 pem 이름
cat ~/.ssh/pemname.pem
[DashBoard]-[Jenkins 관리]-[Tools] 로 들어가서 프로젝트 Gradle 버전과 동일하게 설정해준다.
이제 기본적인 setting 은 끝났다. 본격적으로 Jenkins가 할 일을 정해주자
DashBoard 로 나와서 좌측 [새로운 Item] -> 이름을 작성 한 후 유형은 FreeStyle Project 로 생성
(Item 이름으로 디렉토리가 생성되기 때문에 '공백'은 피하는 것이 편함)
아이템 생성 후 아이템 구성 페이지로 이동하면 바로 GitHub 설정을 할 수 있다.
GitHub project ✅ 후, url에 Repository 주소 입력
조금 내리다보면 나오는 [소스 코드 관리]에서 Git ✅
Repository URL: .git으로 끝나는 clone HTTPS 주소
Credentials: 위에서 등록한 2번 Credentials
Secret key, yaml 등의 파일을 Submodule 로 관리하고 있는 경우
그 Repository의 파일들도 가져와야 하기 때문에 추가 설정이 필요하다.
[소스 코드 관리] 탭 하단에 [add] 드롭 박스를 열어 [Advanced sub-modules behaviours]를 추가해준다.
Recursively update submodules, Use credentials from default remote of parent repository ✅ 해주고
마찬가지로 서브모듈의 .git으로 끝나는 clone HTTPS 주소를 입력해준다.
마지막으로 조금 내려서 [빌드 유발]에서 GitHub hook trigger for GITScm polling ✅
도커 허브에 로그인 할 때 쓰일 환경 변수를 추가해준다.
[Add Build step]으로 [Invoke Gradle script], [Execute shell] 순서로 추가해준다
아까 위에서 설정한 Gradle 을 선택 해준 후, Task에 clean build
작성
이 구간에서 jar 파일로 빌드가 된다.
이제 빌드한 jar 파일을 Docker image 로 빌드하고 Docker Hub에 Push 하는 Shell script를 작성해줄 것이다.
이제부터 Docker image 이름은 Namespace/Repository:tagname 라고 생각하면 된다.
ex) 도커 허브 계정 이름이 meong이고 repository 이름이 myjenkinsrepo 라면
-> meong/myjenkinsrepo:1.0
# docker 이미지로 빌드
docker build -t meong/myjenkinsrepo:1.0 .
# 도커 허브 로그인
echo $PASSWORD | docker login -u $USERNAME --password-stdin
# 도커 허브 repository에 push
docker push meong/myjenkinsrepo:1.0
# 이미지 제거
docker rmi meong/myjenkinsrepo:1.0
제대로 push 가 되었다면 Docker Hub에 이렇게 뜨게 된다
맨 마지막 [빌드 후 조치] 탭에서 [추가]-[Send build artifacts over SSH] 선택
Name: 초기 설정했던 SSH Server(운영 서버) 선택
Source files: SSH Server로 보낼 파일
Remove prefix: 파일 경로에서 지울 경로
Remote directory: SSH Server에 파일을 저장할 위치
docker-compose.yaml 파일은 서브모듈에 있고 git pull 받은 후
운영 서버에 전달하여 운영 서버에서 compose 명령을 수행하는 구조이다.
Exec command 에는
도커 허브에 로그인 - pull - run 과정의 스크립트를 작성해준다
#도커 허브 로그인
echo $PASSWORD | docker login -u $USERNAME --password-stdin
# 도커허브 이미지 pull
docker pull meong/myjenkinsrepo:1.0
# 도커 compose run
docker-compose -f ./scripts/docker-compose.yaml up -d
(*) 고급 탭을 열어 Verbose output in console을 체크해주면
빌드 후 조치 과정에서 발생하는 output 도 콘솔에 찍힌다.
이용을 하다보니 배포가 제대로 되고 있는지 매번 Jenkins에 접속하는 것도 일이라는 생각이 들었다.
너무 편리하게도 디스코드, 슬랙, 이메일, github 등 다양한 채널로 빌드 결과를 받을 수 있다.
기존에 GitHub hook도 디스코드로 받고 잇었기 때문에 디스코드를 연결하기로 했다.
디스코드 웹후크 URL 발급
디스코드에서 [서버 설정]-[앱]-[연동]-[웹후크]로 가서 새 웹후크 추가
이름과 알림을 받을 채널을 설정 한 후 [웹후크 URL 복사]
Plugins 로 가서 디스코드 플러그인 설치
Item의 빌드후 조치에서 Discord Notifier 추가
Webhook URL에 디스코드 웹후크 URL 입력
알림 설정
[고급]을 눌러서 Title 을 지정하고 받고 싶은 내용 선택
결과