저번 프로젝트에서는 깃랩을 사용하여 도커로 받은 이미지를 크론탭으로 배포 스크립트를 실행시키는 식으로 ci/cd를 구현했었다.
이번에는 Github action을 통해 코드를 빌드하고, 이를 도커 레포지토리에 push, 그리고 ssh로 원격 서버에 접속하여 도커의 이미지를 pull 하여 Docker-Compose를 통해 Spring Boot와 MYSQL, REDIS를 같이 실행시키는 방식을 사용하고 정리해 보고자 한다.
이 방법을 선택한 이유는 docker-compose를 통해 mysql과 redis도 같이 관리할 수 있다는 점 편리해 보였다.
도커가 설치된 AWS EC2와 Github Repository가 필요합니다.
아래 주소를 참고해 주세요.
1. AWS EC2 서버 띄우기
2. AWS EC2에 도커 설치
✔ dockerhub 사이트에 접속하여 회원가입 및 Repoistory 생성
✨ Repository Name은 꼭 기억해 둘것
파일 이름 Dockerfile에서 변경 ❌ -> 파일이름을 임의로 변경하면 오류 납니다.
FROM gradle:7.6-jdk17-alpine as builder
WORKDIR /build
# 그래들 파일이 변경되었을 때만 새롭게 의존패키지 다운로드 받게함.
COPY build.gradle settings.gradle /build/
RUN gradle build -x test --parallel --continue > /dev/null 2>&1 || true
# 빌더 이미지에서 애플리케이션 빌드
COPY . /build
RUN gradle build -x test --parallel
# APP
FROM openjdk:17.0-slim
WORKDIR /app
# 빌더 이미지에서 jar 파일만 복사
COPY --from=builder /build/build/libs/*-SNAPSHOT.jar ./app.jar
EXPOSE 8080
# root 대신 nobody 권한으로 실행
USER nobody
ENTRYPOINT [ \
"java", \
"-jar", \
"-Djava.security.egd=file:/dev/./urandom", \
"-Dsun.net.inetaddr.ttl=0", \
"app.jar" \
]
이제 github action의 workflow를 작성해보겠습니다.
Github Actions가 무엇을 수행할지를 정의할 수 있습니다.
깃헙 레파지토리에서 Actios에 들어가 새로운 workflow를 생성할 수 있습니다.
아래와 같이 작성 후 Commit
name: CI/CD
on:
push:
branches: [ "main" ]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: checkout
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
## create application-database.yml
- name: make application-database.yml
run: |
cd ./src/main/resources
# application-database.yml 파일 생성
touch ./application-database.yml
# GitHub-Actions 에서 설정한 값을 application-database.yml 파일에 쓰기
echo "${{ secrets.DATABASE }}" >> ./application-database.yml
shell: bash
- name: make application-redis.yml
run: |
cd ./src/main/resources
# application-redis.yml 파일 생성
touch ./application-redis.yml
# GitHub-Actions 에서 설정한 값을 application-redis.yml 파일에 쓰기
echo "${{ secrets.REDIS }}" >> ./application-redis.yml
shell: bash
- name: Build with Gradle
run: |
chmod +x gradlew
./gradlew build
# ./gradlew bootJar
## 웹 이미지 빌드 및 도커허브에 push
- name: web docker build and push
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
docker build -t ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }} .
docker push ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}
## docker compose up
- name: executing remote ssh commands using password
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST }}
username: ubuntu
key: ${{ secrets.KEY }}
script: |
sudo docker rm $(sudo docker ps -a)
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPO }}
cd ~
docker-compose up -d
sudo docker image prune -f
👀 참고) ./gradlew build과 ./gradlew bootJar 차이
- gradle build
gradle build는 bootJar를 포함하고 있어 내부 동작이 더 길다.
build는 test 코드가 있다면 테스트도 수행을 한다.
build와 bootJar가 다른 것 중 대표적인 것은 Life Cycle과 관련된 Task가 존재한다.
예를 들어 check, assemble가 있다.
- gradle bootJar
단순히 프로젝트의 jar 파일만을 만드는데 목적을 가지고 있다.
그만큼 빌드하는데 속도도 빠를 것.
깃헙 레파지토리 -> Seetings -> Secrets and variables -> Actions -> new repository secret 에서 변수 설정이 가능합니다.
깃헙에 올리지 못하는 db정보들을 저장할 변수입니다.
예시로 database는 아래와 같습니다.
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql:/<AWS EC2 public IP>/<DB 스키마>
username: root
password: 1q2w3e4r
jpa:
database: mysql
database-platform: org.hibernate.dialect.MySQLDialect
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
참고로 application.yml은 아래와 같이 올라가있습니다. 프로젝트를 돌리면 application-database.yml와 application-redis.yml를 찾아서 설정정보를 참고하게됩니다.
spring:
profiles:
include:
- mysql
- redis
- DOCKER_USERNAME = docker hub 아이디
- DOCKER_PASSWORD = docker hub 비밀번호
- DOCKER_REPO = 1.에서 만든 docker repository 이름
- HOST = AWS EC2 public Ip
- KEY = AWS EC2 생성시 사용한 pem 키
✨ -----BEGIN RSA PRIVATE KEY----- 부터 -----END RSA PRIVATE KEY----- 부분을 포함
❓ pem key 확인 방법
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# 선택 사항 : PermissionError: [Errno 13] Permission denied 발생하면 적어주세요.
sudo chmod 666 /var/run/docker.sock
docker-compose --version 통해 설치가 잘 되었는지 확인합니다.
# 홈 디렉토리로 이동
cd ~
# docker-compose.yml 파일을 생성
sudo vi docker-compose.yml
i = 입력 모드
esc = 명령어 모드
:wq = 저장 후 종료
version: '3.8'
services:
mysql:
container_name: mysql
image: mysql
ports:
- 3306:3306
environment:
MYSQL_ROOT_PASSWORD: 1q2w3e4r
MYSQL_DATABASE: shoppingmall
restart: always
networks:
- testnet
redis:
container_name: redis
image: redis
ports:
- 6379:6379
restart: always
networks:
- testnet
backend:
container_name: backend
image: zvyg1023/shopping-mall
expose:
- 8080
ports:
- 8080:8080
restart: always
depends_on:
- mysql
- redis
networks:
- testnet
networks:
mynet:
driver: bridge
우선 워크플로우는 성공적으로 마쳤습니다.
docker ps를 통해 컨테이너가 돌아가는지 확인해보겠습니다.
마지막으로 배포된 주소로 스웨거 접속 해보겠습니다.
docker-compose를 사용하여 Spring과 MySQL을 배포했으나 Spring에서 MySQL과 연결이 되지 않아서 exception이 발생하며 Spring application이 종료되는 문제 발생하였다.
Spring이 MYSQL에 종속적이라 depends_on 옵션을 사용하여 작업 순서를 보장해주었지만 여진히 작동하지 않았다.
docker-compose up의 로그를 확인 해 보니 MySQL에서 ready for connections 상태가 되기 전에 Spring이 먼저 실행되며 DB connection을 시도하였다는 것을 알 수 있었다.
최종적으로 restart 옵션을 주어서 해결하였지만 맞는 방법인지는 모르겠다.