CI 구축
1. 환경 변수 설정
Settings -> Security ->Secretes and variables -> Actions 탭에 들어가 New repository secret 클릭
Application.yml(Properties) 등 gitignore 한 파일들을 작성해 줍니다.
우리 프로그램은 env.properties 에 환경변수 정보를 적은 후 gitignore 해주었기 때문에
ENV라는 이름으로 env.properties 내용을 복붙 해주었습니다.
Name은 컨벤션에 맞게 대문자, 숫자와 언더스코어만 쓰자
2. workflow 구축
Github에 올라와있는 자바 프로젝트를 jar파일로 빌드하는 작업을 자동화할 것입니다.
프로젝트에서 /. github/workflows 폴더 안에 Deploy.yml을 생성한 후 아래 코드를 작성하였습니다.
# github repository actions 페이지에 나타날 이름
name: Deploy
# event trigger
# develp 브랜치에 push가 되었을 때 실행
on:
push:
branches: [ "develop" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
# 기본 체크아웃 - 레포지토리의 최신 코드를 가져와 현재 워크플로우에서 사용할 수 있도록 로컬에 복제
- name: Checkout
uses: actions/checkout@v3
# Gradlew 실행 허용
- name: Run chmod to make gradlew executable
run: chmod +x ./gradlew
# JDK 17 세팅 - github actions에서 사용할 JDK 설정 (프로젝트나 AWS의 java 버전과 달라도 무방)
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# 환경 변수 파일 생성
- name: Set environment values
run: |
cd ./src/main/resources # resources 폴더로 이동
touch ./env.properties # env.properties 파일 생성
echo "${{ secrets.ENV }}" > ./env.properties # github actions secret에서 가져온 값을 application.yml 파일에 씁니다
shell: bash
# Gradle build (Test 제외)
- name: Build with Gradle
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: clean build -x test
3. CI 테스트
Deploy.yml에 pull_request를 이벤트 트리거로 추가하여
develop 브랜치에 pr을 날렸을 때 CI가 동작하도록 만든 후 테스트를 진행합니다.
저는 cicd 용 branch를 만들어 cicd branch -> develp branch로 pr을 생성하여 테스트를 진행하였습니다.
on:
push:
branches: [ "develop" ]
pull_request:
branches: [ "develop" ]
actions 탭으로 들어가 빌드가 잘 되는 것을 확인할 수 있다.
Docker & Docker-compose 설정
1. MAC에 도커 설치 ( Homebrew 사용 )
brew cask는 Docker Desktop on Mac 도커를 설치해 주며,
docker-compose, docker-machine 등을 같이 설치해 줍니다.
맥 OS에서 띄우기 때문에 가상 머신에서 포트 포워딩을 할 필요가 없습니다.
터미널에서 아래 명령어를 입력합니다.
brew install --cask docker
다음과 같이 잘 설치된 것을 확인할 수 있습니다.
2. Dockerfile 생성
프로젝트 최상단 경로에 Dockerfile을 생성하고 아래 코드를 작성합니다.
FROM openjdk:17
COPY build/libs/sumnail-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
3. Docker Hub 리포지토리 생성
Docker Hub에 회원가입 후, 리포지토리를 생성해 줍니다.
저는 저희 프로젝트의 이름인 sumnail으로 리포지토리를 생성했습니다
도커 허브 리포지토리는 꼭 Private로 생성해 주세요!
public으로 설정할 시 github secret이 외부에 노출될 수 있습니다.
4. 인스턴스에 Docker 설치
https://docs.docker.com/engine/install/ubuntu/
위의 공식문서에 나온 대로 Docker를 설치해 주면 됩니다.
마지막에 버전명이 나오면 설치 성공입니다.
$ sudo apt-get update
$ sudo apt-get install \
ca-certificates \
curl \
gnupg \
lsb-release
$ sudo mkdir -m 0755 -p /etc/apt/keyrings
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
$ sudo docker --version
5. 인스턴스에 docker-compose 설치
아래 명령어로 docker-compose를 설치해주면 됩니다.
마지막에 버전명이 나오면 설치 성공입니다.
$ sudo curl -L "https://github.com/docker/compose/releases/download/1.27.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ sudo ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose
$ docker-compose -v
6. Docker-compose.yml 생성
인스턴스 최상단 경로(/)에 docker-compose.yml 파일을 생성해 줍니다.
/home/ubuntu 경로가 아니라 최상단 경로에 생성해야 합니다.
sudo vim ~ 로 sudo 권한을 주어야 파일이 저장됩니다.
ubuntu@ip-172-31-35-223:~$ cd ../../
ubuntu@ip-172-31-35-223:/$ ls
bin docker-compose.yml lib libx32 mnt root snap tmp
boot etc lib32 lost+found opt run srv usr
dev home lib64 media proc sbin sys var
ubuntu@ip-172-31-35-223:/$ sudo vim docker-compose.yml
docker-compose.yml에 아래 내용을 작생해준 뒤 저장합니다.
version : "3"
services:
application:
image: sumnail # 사용할 Docker 이미지를 지정
environment:
SPRING_DATASOURCE_URL: <RDS url>
SPRING_DATASOURCE_USERNAME: <RDS username>
SPRING_DATASOURCE_PASSWORD: <RDS password>
restart: always # 서비스가 종료되면 항상 자동으로 다시 시작하도록 설정
container_name: sumnail # 컨테이너의 이름 지정
ports:
- "80:8080" # 호스트 머신과 컨테이너 간의 포트 매핑
호스트에서 웹 브라우저인 80번 포트로 접속하면,
해당 요청은 Docker 컨테이너 내부의 8080 포트로 전달되어 Spring boot 서비스에 도달하게 됩니다.
이렇게 설정함으로써 호스트 머신의 80번 포트를 통해 도커 컨테이너 내의 Spring boot 서비스에 접근할 수 있습니다.
CD 구축
빌드한 jar 파일을 바탕으로 docker image를 생성하고, docker hub에 푸시했다가
ec2에서 해당 이미지를 pull 받아와 배포하는 작업을 자동화할 것입니다.
1. docker image를 생성하고 docker hub에 푸시하는 작업 자동화
(1) docker username, password 환경변수에 추가
위에서 ENV 파일을 만들어 준 것처럼
DOCKER_USERNAME, DOCKE_PASSWORD을 작성해 줍니다.
(2) Deploy.yml 에 Docker build & push 추가
우리 프로젝트의 경우 docker hub repository 이름이 sumnail 이였다.
# Docker build & push
- name: Docker build
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t sumnail .
docker tag sumnail ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7}
docker push ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7}
위의 코드를 한 줄씩 설명하겠습니다.
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
Docker Hub에 로그인합니다. Github secrets에 저장된 Docker Hub 사용자 이름과 비밀번호를 사용합니다.
# docker build -t <이미지 이름>:<태그> <Dockerfile 경로>
docker build -t sumnail .
'.'는 현재 디렉터리에 있는 Dockerfile을 사용하여 Docker 이미지를 빌드합니다.
저희의 경우 빌드한 이미지에 sumnail이라는 이름을 부여하였습니다.
# docker tag <이전 이미지 이름>:<이전 태그> <새 이미지 이름>:<새 태그>
docker tag sumnail ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7}
이전에 빌드한 이미지에 태그를 지정합니다. Github 커밋의 일부인 SHA 값의 앞 7자리를 사용하여 이미지를 고유하게 식별합니다.
새 이미지 이름은 유저네임/리포지토리이름으로 만들었습니다.
docker push ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7}
Docker이미지를 Docker Hub에 push 합니다
2. EC2에서 해당 이미지를 pull 받아와 배포하는 작업 자동화
(1) github에 환경변수 등록
PEM_KEY라는 이름으로 EC2에 접속하는데 필요한 pem 키의 내용을 복붙 해 저장해 줍니다.
SSH_USERNAME과 ELASTIC_IP라는 이름으로 각각 현재 인스턴스의 username과 eip를 저장해 줍니다.
(2) Deploy.yml에 Docker pull & deploy 코드 추가
이미지 이름은 Docker-compose.yml 파일의 application.image에 지정해 둔 것입니다
# Deploy (docker compose 사용)
- name: Deploy
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.ELASTIC_IP }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.PEM_KEY }}
envs: GITHUB_SHA
script: |
sudo docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7}
sudo docker tag ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7} sumnail
sudo docker-compose -p sumnail up -d
위의 코드를 한 줄씩 설명하겠습니다
uses: appleboy/ssh-action@master
ssh를 통해 원격 서버에 접속하는 데 사용되는 ssh-action 액션을 사용합니다
with:
host: ${{ secrets.ELASTIC_IP }}
username: ${{ secrets.SSH_USERNAME }}
key: ${{ secrets.PEM_KEY }}
envs: GITHUB_SHA
- host : 원격 서버의 ip 주소를 GitHub Secrets에서 가져옵니다
- username: 원격 서버에 접속할 사용자 이름을 지정합니다 ( 기본 설정은 ubuntu )
- key: 원격 서버 접속에 사용될 개인 키를 Github Secrets에서 가져옵니다.
- envs : GITHUB_SHA 환경 변수로 현재 Github 커밋의 SHA 값을 전달합니다.
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7}
Docker hub에서 해당 이미지를 pull 합니다.
# docker tag <이전 이미지 이름>:<이전 태그> <새 이미지 이름>:<새 태그>
sudo docker tag ${{ secrets.DOCKER_USERNAME }}/sumnail:${GITHUB_SHA::7} sumnail
새로 pull 한 이미지에 다시 태그를 지정합니다
# docker-compose -p <docker compose 프로젝트 이름> up -d
sudo docker-compose -p sumnail up -d
- -p 옵션 : Docker Compose 프로젝트의 이름을 설정
- -d 옵션 : 컨테이너를 백그라운드에서 실행
Docker compose를 사용하여 컨테이너를 배포합니다.
(3) CD 테스트
CI 테스트와 같은 방식으로 cicd branch -> develop branch로 PR을 날려 테스트합니다.
CICD 가 될 되는 것을 확인한 후 추가했던 코드를 제거해 줍니다.
필요에 따라 pull_request가 생성될 때도 CICD가 적용되길 바라시는 분들은 그대로 나 두시면 됩니다.