목표 : 깃허브 커밋을 하면 자동으로 EC2에 반영되도록 하는 CD/CD를 구축한다.
지난번에는 Docker를 이용하여 Spring boot 애플리케이션을 EC2에 배포를 했습니다.
이번에는 해당 과정을 로컬환경에서 깃허브 커밋&푸시가 이루어지면 Github Actions가 대신해 주도록 하여, CI/CD를 구축해 봅시다.
(자신이 Spring Boot 애플리케이션을 빌드하고 도커허브에 푸시하고 EC2에서 풀 받아서 실행시키도록 했던 과정을 Github Actions에게 모두 시킨다고 생각하시면 됩니다.)
깃허브 참조
환경
- Java 17
- Spring Boot 3.1.0
- gradle
- 로컬 PC : M2 Mac os
- EC2 : Ubuntu 22.04 프리티어
사전 작업
- 깃허브 계정 & 사용할 레포지토리
- 지난 글 과정까지 완료하기 (아래 링크 참조)
1. CI 과정 해보기
이전글에서 했던 것들 중, Spring Boot 애플리케이션을 app.jar로 빌드하고 app.jar를 도커허브에 푸시하는 과정까지의 단계를 다루겠습니다.
1-1. Github Actions 환경변수 설정
깃허브 액션의 secrets에 도커허브의 username과 password를 등록해준다.
1-2. Actions의 gradle.yml 생성
이전글에서 했던 과정을 Guthub actions가 대신하도록 gradle.yml에 작성해 줍시다.
1-3. grdle.yml 수정
name: Java CI with Gradle
# 동작 조건 설정 : main 브랜치에 push 혹은 pull request가 발생할 경우 동작한다.
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
# Spring Boot 애플리케이션을 빌드하여 도커허브에 푸시하는 과정
build-docker-image:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# 1. Java 17 세팅
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# 2. Spring Boot 애플리케이션 빌드
- name: Build with Gradle
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
with:
arguments: clean bootJar
# 3. Docker 이미지 빌드
- name: docker image build
run: docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/github-actions-demo .
# 4. DockerHub 로그인
- name: docker login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
# 5. Docker Hub 이미지 푸시
- name: docker Hub push
run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/github-actions-demo
steps
- Java17을 사용하였으므로 Java17을 이용하라는 명령입니다.
- Spring boot를 Gradle을 이용하여 빌드하라는 명령입니다.
- Docker 이미지를 빌드합니다.
- 1-1에서 설정했던 secrets들을 불러와서 사용합니다.
- 이미지 이름은 적절하게 원하는 데로 수정하여 설정하도록 합니다.
- DockerHub에 로그인합니다.
- DockerHub에 이미지를 푸시합니다.
위와 같이 수정 후 저장한 뒤, Actions 탭에 들어가 보면 해당 과정이 진행되고 있음을 볼 수 있습니다.
각각의 과정이 진행되는 모습을 볼 수 있으며, 실패할 경우 어디서 실패했는지도 볼 수 있습니다.
1-4. Docker 허브 확인 및 EC2 배포
gradle.yml에 github-actions-demo 라는 이름으로 도커허브에 푸시하도록 명시해 놓았습니다.
정상적으로 업로드된 모습을 확인할 수 있습니다.
이제 EC2에서 실행시켜 보자
$ sudo docker run -p 8080:8080 philip2767/github-actions-demo
접속해 보면 정상적으로 응답되는 모습을 확인할 수 있습니다.
2. CD 과정 구축하기 (배포 자동화하기)
이제 app.jar를 도커허브에 푸시하는 데까지 자동화가 완료되었습니다.
이후의 과정인 (현 글에서는 1-4) 도커허브에 푸시(업로드)된 이미지를 EC2에서풀(다운로드) 받아서 서버를 실행시키는 부분을 자동화해보겠습니다.
해당 과정에서는 여러 방법이 존재하지만 self-hosted라는 방법을 이용해서 진행해 보도록 하겠습니다.
이유는 가장 쉽고 간단하다고 느꼈기 때문입니다.
2-1. Github Actions의 self-hosted 수신 준비하기
자세한 내용은 아래과정에서 Java를 설치하는 과정은 스킵하시고 github Actions에서 new self-hosted runner 연결을 하는 과정까지만 진행하시면 됩니다.
2-2. gradle.yml 수정하기
도커허브에 올라가 있는 이미지를 pull 받아서 기존의 컨테이너를 중지시키고 새롭게 실행시키도록 하는 부분을 진행하겠습니다.
name: Java CI with Gradle
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
# Spring Boot 애플리케이션을 빌드하여 도커허브에 푸시하는 과정
build-docker-image:
runs-on: ubuntu-latest
... 생략
# 위 과정에서 푸시한 이미지를 ec2에서 풀받아서 실행시키는 과정
run-docker-image-on-ec2:
# build-docker-image (위)과정이 완료되어야 실행됩니다.
needs: build-docker-image
runs-on: self-hosted
steps:
# 1. 최신 이미지를 풀받습니다
- name: docker pull
run: sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/github-actions-demo
# 2. 기존의 컨테이너를 중지시킵니다
- name: docker stop container
run: sudo docker stop $(sudo docker ps -q) 2>/dev/null || true
# 3. 최신 이미지를 컨테이너화하여 실행시킵니다
- name: docker run new container
run: sudo docker run --name github-actions-demo --rm -d -p 8080:8080 ${{ secrets.DOCKERHUB_USERNAME }}/github-actions-demo
# 4. 미사용 이미지를 정리합니다
- name: delete old docker image
run: sudo docker system prune -f
살펴보자면
needs: build-docker-image
build-docker-image 과정의 완료가 필요한 실행조건입니다.
해당 부분이 없다면 동시에 실행이 되므로 정상적으로 최신 도커이미지가 실행되지 않습니다.
runs-on: self-hosted 를 통해 ec2에서 이후 명령이 동작하도록 합니다.
steps
- 최신 이미지를 풀 받도록 합니다.
- 기존의 컨테이너를 중지합니다.
- $(sudo docker ps -q) 를 통해 현재 실행 중인 모든 컨테이너의 id를 인자로 받습니다.
- sudo docker stop을 통해 인자로 받은 컨테이너를 종료시킵니다.
- 실행 중인 컨이너가 없을 경우 sudo docker stop시 에러가 발생합니다.
해당 에러를 방지하기 위해 2>/dev/null || true 를 사용했습니다.
- 최신 이미지를 컨테이너화하여 실행시킵니다.
- --name 은 컨테이너의 이름을 설정해 줍니다. (ci/cd과정에서 컨테이너의 이름을 알아야 하는 경우는 없어서 굳이 안 하셔도 무방합니다. 다만, 추후에 여러 컨테이너가 사용될 경우 식별 편의를 위해 사용해 주었습니다.)
- --rm 컨테이너 종료 시 해당 컨테이너가 자동으로 삭제되도록 합니다.
- -d 백그라운드에서 실행하도록 합니다.
- -p 포트를 설정해 줍니다.
- 미사용 이미지를 삭제합니다. (구버전이미지 = 기존에 실행 중이던 컨테이너 이미지)
- 컨테이너 중지 후 삭제해 주어도 되지만 해당 부분을 뒤로 뺀 이유는
기존 이미지에서 공유하여 사용하던 레이어 부분을 다시 다운로드하도록 하지 않기 위해서입니다.
ex) demo 이미지가 a, b, c 로 구성되어 있을 경우 최신버전에서는 a만 변경되었을 경우 a만 새로 다운로드하게 됩니다.
- 컨테이너 중지 후 삭제해 주어도 되지만 해당 부분을 뒤로 뺀 이유는
2-3. 실행 결과 확인
Controller를 살짝 수정한 뒤 정상적으로 실행되는지 확인해 봅시다.
@GetMapping("/")
public ResponseEntity hello() {
return ResponseEntity.ok("eroom 배포 자동화 테스트");
}
커밋&푸시해주면
gradle.yml에 명시한 데로 순차적으로 실행됩니다.
모든 Jobs가 완료된 뒤 실제로 서버에 GET요청을 해봅시다.
정상적으로 배포가 완료되었습니다.
이제 main 브랜치에 push 혹은 pull request 가 발생할 경우 자동적으로 서버가 최신화되어 배포됩니다.
감사합니다.
참고
self-hosted 대신 AWS IAM 사용한 방식
'개발일지 > 돌픽' 카테고리의 다른 글
Docker로 spring-boot EC2에 배포하기 (0) | 2023.06.07 |
---|---|
Spring boot 쿼리파라미터 Dto 사용하기 (@ModelAttribute) (2) | 2023.05.23 |
spring-boot Cache-Control 설정으로 부하 줄이기 (0) | 2023.05.19 |
ImgBB API 이용해보기 (0) | 2023.05.17 |
Spring-boot 파일 업로드 - 공식문서 따라하기 ! (0) | 2023.05.12 |