서버가 어떤 이유로 죽어버리면 EC2도 같이 죽어버려서 CICD를 하지 않으면 아래의 과정을 직접 손으로 해야한다.
EC2 재부팅 → git pull → ./gradlew build → java -jar *.jar
이 행위를 자동화 하고자 CICD를 도입해야 한다.
CICD가 없는 상태에서의 배포 흐름은 위와 같다. 깃허브에 push 한 코드를 ec2에서 pull하고, 빌드 후 실행시킨다.
이 방식의 안좋은 점은 2가지이다.
Github에 코드를 푸시하면 자동으로 ec2에 코드 내용이 반영되게 하는 방식은 어떨까?
이 단계에서는 aws의 CodeDeploy, workflow 자동화 도구를 사용했다.
가장 많이 사용되는 것은 Jenkins이지만 간단히 GitHub Actions을 선택했다.
스프링 서버가 죽으면 EC2도 같이 죽어버리는 문제를 아직 해결하지 못했다.
서버 실행 중 갑자기 무한 루프가 발생한다면 서버를 백그라운드에서 실행한다고 해도 host os도 멈춰버리기 때문에 간단한 작업조차 실행할 수 없다.
Docker는 프로세스 간 격리를 제공하기 때문에 이런 문제 해결에 아주 적합하다. 바로 이 장점 때문에 Docker를 도입하게 되었다.
# Dockerfile
# jdk 17 alpine 버전을 jdk로 사용한다.
# 런타임 환경만 제공하면 되기 때문에 가벼운 alpine을 사용했다.
FROM openjdk:17-jdk-alpine
# Spring Boot 프로젝트를 gradlew을 사용해 빌드하면
# /build/libs/ 위치에 .jar(자바 아카이브)파일이 생성된다.
# 이를 위한 인자를 정의한다.
ARG JAR_FILE=build/libs/*.jar
# 빌드 결과인 아카이브 파일을 app.jar 파일로 복사한다.
COPY ${JAR_FILE} app.jar
# 나는 도커 이미지를 사용해 스프링 서버를 위한 컨테이너를 만들고 싶은 것이다.
# 그렇다면 컨테이너를 띄울 때 항상 실행해야 하는 명령어가 필요하다.
# 이제 자바 아카이브 실행 명령어를 추가하자.
ENTRYPOINT ["java", "-jar", "app.jar"]
CLI로 이미지를 만들 수도 있지만 이렇게 Dockerfile을 활용하는게 가장 편리하다. 이렇게 만들어진 이미지는 현재 로컬에 존재하기 때문에 EC2에서 이 이미지를 사용해 컨테이너를 띄우기 위해선 도커 허브에 이미지를 올려야 한다.
#터미널에서
# 윈도우 환경
# 아래의 명령어를 입력하고 도커 허브의 ID와 PW를 입력한다.
# 이때 이메일을 입력해도 로그인이 가능하니 조심할 것. 이메일이 아니라 아이디를 입력해야한다.
> docker login
# 도커 파일이 있는 위치에서 아래의 명령어를 실행한다.
# ex) docker build -t myid/server:test .
# 앞에서 이메일로 로그인했다면 "docker : invalid reference format" 에러를 만날 것이다.
> docker build -t [도커 허브 ID]/[원하는 이미지 이름]:[태그] [경로]
# 도커 허브에 푸시한다.
> docker push myid/server:test
# 잘 올라갔는지 확인해 보자.
# 명령어가 아니라 직접 도커 허브에서 확인해도 된다. https://hub.docker.com/
> docker pull rookie97/server:test
이런식으로 보이면 성공한 것이다.
Dockerfile에 빌드 결과물이 .jar를 포함했으니 Gradle 빌드 후 Docker 빌드를 수행하는 과정이 필요하다.
name: 🚀 Build & Deploy workflow environment
on:
pull_request:
branches: [main]
types: [closed]
jobs:
build:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
env:
ENV_PATH: ./src/main/resources
ENV_DB: application-db.yml
ENV_AUTH: application-auth.yml
DOCKER_IMAGE_NAME: us-server
DOCKER_IMAGE_TAG: deploy
steps:
- name: ✅ Checkout branch
uses: actions/checkout@v3
- name: 📀 Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
- name: 💽 Make application-db.yml
run: |
cd ${{ env.ENV_PATH }}
touch ${{ env.ENV_DB }}
echo "${{ secrets.SPRING_DB_CONFIG }}" >> ${{ env.ENV_DB }}
shell: bash
- name: 🔑 Make application-auth.yml
run: |
cd ${{ env.ENV_PATH }}
touch ${{ env.ENV_AUTH }}
echo "${{ secrets.SPRING_AUTH_CONFIG }}" > ${{ env.ENV_AUTH }}
shell: bash
- name: ✨ Grant execute permission for gradlew
run: chmod +x gradlew
- name: 🔨 Build with Gradle
uses: gradle/gradle-build-action@v2
with:
arguments: clean build
gradle-version: 8.3
- name: 🐳 Build Docker Image
run: |
docker build -t ${{ secrets.DOCKERHUB_USERNAME}}/${{ env.DOCKER_IMAGE_NAME }}:${{ env.DOCKER_IMAGE_TAG }} .
- name: 🌎 Login DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: 🐋 Push a Docker Image to DockerHub
run: |
docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:${{ env.DOCKER_IMAGE_TAG }}
deploy:
needs: build
runs-on: self-hosted
env:
DOCKER_IMAGE_NAME: server
DOCKER_IMAGE_TAG: deploy
steps:
- name: Pull Latest Image
run: |
sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:${{ env.DOCKER_IMAGE_TAG }}
- name: Stop Current Running Container
run: |
sudo docker stop $(sudo docker ps -q) 2>/dev/null || true
- name: Run Latest Image
run: |
sudo docker run --name api-server -d -p 8080:8080 ${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKER_IMAGE_NAME }}:${{ env.DOCKER_IMAGE_TAG }}
- name: Delete Old Image
run: |
sudo docker system prune -f
낯선 스크립트인 deploys.runs_on: self-hosted가 있다 뭘까?
GitHub Actions에서 Runner는 깃허브 액션 워크플로우 내에서 job을 실행하는 일종의 서버다.
깃허브 액션에서 호스트되는 가상 머신(VM)이라고 볼 수 있다.
위에서 작성한 스크립트를 실행하기 위해서 github에서 제공하는 러너를 사용해도 되지만 ec2에서 runner를 실행하기 위해서는 self-hosted runner가 필요하다.
self-hosted Runner란 GitHub Actions에서 사용자가 지정하는 로컬 컴퓨팅 자원으로 빌드를 수행하도록 설정하는 기능인데, 우리의 로컬 컴퓨팅 자원이 EC2 인스턴스가 되겠다. 결국 핵심은 EC2에서 cicd의 job이 수행된다는 것이다.
위의 화면이 보이면 ec2의 인스턴스 콘솔로 이동해서 화면에 표시되는 명령어들을 차례로 입력해주자.
sha관련 에러를 만나면, 아래의 명령어를 설치
sudo yum install perl-Digest-SHA -y
./config.sh —url .. 명령어를 입력하다가 libicu 관련 에러를 만난다면, 아래의 명령어를 입력해 설치한다.
sudo yum install libicu -y
./config 명령어까지만 입력하고 아래의 명령어를 입력하면 끝이다.
sudo ./svc.sh install
sudo ./svc.sh start
콘솔창을 꺼도 계속 서버를 켜두게 하는 툴
러너가 생성된 것을 확인할 수 있다.
이제 action이 실행되고 로그를 확인할 수 있다.
여기서 에러가 발생할 수 있다. 대부분은 runner가 띄워주는 log를 확인하고 해결할 수 있다.
하지만, docker에서 발생하는 오류는 찾기가 어렵다. 그럴 때는 직접 EC2에 들어가서 docker의 로그를 확인해야 한다.
docker logs [컨테이너ID]
현재 실행중인 컨테이너 확인
docker ps -a