🚀 이번 포스팅에서 알아갈 키워드
- Continuous Deploy
- GitHub Self Hosted Runner
- Workflow
- Docker Hub
- Docker Image
- Docker Run
💬 개요
먼저 지속적 배포(CD, Continuous Deploy)를 구축하기 위해서 다음과 같은 기술을 선택했습니다.
- GitHub Self Hosted Runner : 현재 클라우드 상황에 적합하다.
- Docker Hub : EC2 내부에서 스프링 프로젝트가 빌드(build)된 이미지(Image)를 가져와 실행할 수 있다.
✅ GitHub Self Hosted Runner를 선택한 이유
현재 진행하고 있는 프로젝트의 클라우드 환경에 가장 적합하다고 느꼈다.
사용하고 있는 EC2의 vpc 환경은 외부에서 내부로 들어올 수 있는 ip와 port가 제한된 상태였다.
때문에 GitHub Actions의 워크플로우(workflow)를 이용하여 scp 명령어로 빌드된 jar를 전송하거나 AWS에 접근해서 스크립트를 직접 실행하는 것이 불가능했다.
즉, EC2 내부에서 지속적 배포를 진행하기 위해서는 EC2 내부에서 외부로 접근하는 방식이 필요했다.
이 때 사용해본 경험이 있는 도구는 두 가지였는데
바로 Jenkins와 GitHub Self Hosted Runner 였고 나는 후자인 GitHub를 선택했다.
Jenkins에 비해 러닝 커브가 적고 GitHub와 연동이 잘되어 있어 문제 해결이 쉽기 때문이다.
Jenkins가 필요한 자원보다 GitHub Self Hosted Runner가 더 적다는 장점을 가지고 있다.
또한 같은 프로젝트를 진행하고 있는 팀원들에게 GitHub를 설명하여 기술을 전파하는 것이 Jenkins 보다 더 쉽다는 것도 선택한 이유 중 하나다.
✅ 🐳 Docker Hub를 선택한 이유
AWS에서 EC2를 띄어 사용하고 있는 상태에서 스프링 프로젝트를 클론 받아 빌드하는 경우 EC2 메모리가 부족해질 수도 있다.
이러한 위험성을 줄이기 위해서 미리 빌드된 jar 파일을 가져와서 실행시키도록 했다.
하지만 현재 클라우드 환경은 빌드된 jar를 바로 가져올 수 없다.
AWS에서 제공하는 S3와 같은 기술을 사용하지 않고 jar 파일을 다운받아 실행시켜야 했다.
또한 저희는 하나의 EC2에서 React, Nginx, Spring을 같이 띄워 놓을거기 때문에 이후에 꼬이는 것을 방지하기 위해 환경을 독립적으로 가져갈 수 있었으면 했다.
이러한 상황을 봤을 때 컨테이너로 환경을 제공해주면서 빌드된 이미지(image)를 풀(pull)받아 서버를 실행시킬 수 있는 도커(🐳 Docker)를 선택하기로 했다.
기술이 정해졌고 대략적인 플로우도 나왔다.
🤔 그렇다면 이제 무슨 일을 해야 할까?
이론적으로 현재 플로우에 대해 이해는 할 수 있었지만 실제로 기술을 사용해본적은 없기 때문에 먼저 기존 플로우를 다시 한 번 정리하면서 해야할 일을 체크리스트로 만들었다.
💬 먼저 플로우를 정리해보자.
- GitHub Repository의 브랜치에 push 되었을 때 GitHub가 인지하고 스프링 프로젝트를 빌드(build)한다.
- 빌드된 jar파일을 도커 이미지(Docker Image)로 만들어 도커 허브(Docker Hub)에 push한다.
- EC2에 띄운 GitHub Self Hosted Runner가 도커 허브가 업데이트 되었다는 것을 확인하고 도커 이미지를 자동으로 pull한다.
- EC2에 있는 도커 이미지를 컨테이너로 띄워 스프링 서버를 실행시킨다.
플로우를 작성하고 흐름은 이해 되었다. 이제 체크리스트를 작성하면서 구체화 시켜보자.
✔️ 체크리스트
- 깃허브가 제공하는 클라우드 환경에서 할 일
- GitHub Repository 클론
- JDK 17 환경 설정
- Gradle Build
- Docker Buildx
- Docker Login
- Docker Image Build
- Docker Hub Push
- EC2 내부에 있는 GitHub Self Hosted Runner가 할 일
- Docker Login
- Docker Pull
- Docker Run
이제 이 흐름을 따라가보자.
🟩 깃허브가 제공하는 클라우드 환경에서 할 일
먼저 '깃허브가 제공하는 클라우드 환경에서 할 일'을 끝내보자.
01. on: push : branches: ["dev/BE"]
workflow를 트리거(trigger)하기 위해서는 이벤트(event)를 발생시켜야 한다.
현재 'dev/BE' 브랜치에 push 이벤트가 발생했을 때 workflow가 동작해야 한다.
깃허브에서는 이러한 push 이벤트가 발생했을 때 workflow가 트리거할 수 있도록 설정하는 명령어를 문서에 정리해두었다.
사용자 입장에서는 workflow에 다음과 같이 작성하면 된다.
on:
push:
branches: [ "dev/BE" ]
워크플로를 트리거하는 이벤트 - GitHub Docs
GitHub에 대한 특정 작업이 예약된 시간에 발생하거나 GitHub 외부의 이벤트가 발생할 때 실행되도록 워크플로를 구성할 수 있습니다.
docs.github.com
02. jobs: build: runs-on: ubuntu-latest
workflow를 실행할 수 있는 환경을 러너(runner)라고 한다.
그리고 workflow 내부에 작업(job)들을 진행하기 위해서는 운영체제와 같은 환경을 만들어줘야 한다.
MacOS, Window 환경에서 작업을 진행할 수 있지만 EC2가 Linux로 띄어져있기 때문에 Ubuntu를 이용하기로 한다.
workflow에는 다음과 같이 작성하면 된다.
jobs:
build:
runs-on: ubuntu-latest
GitHub 호스팅 실행기 정보 - GitHub Docs
GitHub는 워크플로를 실행하는 호스팅 가상 머신을 제공합니다. 가상 머신에는 GitHub Actions에서 사용할 수 있는 도구, 패키지 및 설정 환경이 포함되어 있습니다.
docs.github.com
03. uses: actions/checkout
GitHub Actions를 사용해서 스프링 프로젝트를 빌드하려면 먼저 기본적인 파일을 클론받아야 한다.
이 스텝을 하나의 액션(Action)으로 GitHub Marketplace에서 제공하고 있다.
workflow에는 다음과 같이 작성하면 된다.
on:
push:
branches: [ "dev/BE" ]
jobs:
build:
runs-on: ubuntu-latest
# 위에 적힌 내용은 depth를 보여주기 위해 따로 분리하지 않았다.
steps:
- name: Checkout repository
uses: actions/checkout@v3
Checkout - GitHub Marketplace
Checkout a Git repository at a particular version
github.com
04. uses: actions/setup-java
JDK 17를 사용하고 있는 스프링 프로젝트를 빌드하기 위해서 JDK를 설치하는 액션을 사용한다.
workflow에는 다음과 같이 작성하면 된다.
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
Setup Java JDK - GitHub Marketplace
Set up a specific version of the Java JDK and add the command-line tools to the PATH
github.com
05. uses: gradle/gradle-build-action
Gradle로 스프링 프로젝트를 관리하고 있다. Gradle을 통해서 프로젝트를 빌드하려고 한다.
workflow에는 다음과 같이 작성하면 된다.
- name: Build with Gradle
uses: gradle/gradle-build-action@v2.6.0
- name: Execute Gradle build
run: |
cd backend/baton
./gradlew build
Gradle Build Action - GitHub Marketplace
Configures Gradle for use in GitHub actions, caching useful state in the GitHub actions cache
github.com
06. uses: docker/login-action
Docker Hub에 만든 이미지를 푸쉬하기 위해서 계정으로 로그인한다.
Docker에 로그인하고 계정에서 토큰을 만들어야 접근이 가능하다.
아래는 아직 토큰을 만들지 않은 상태의 페이지다.
토큰을 만들어서 GitHub Secret에 등록해서 workflow에서 사용하도록 설정했다.
참고로 workflow에서 secret을 사용하기 위해서는 `${{ secrets.이름 }}`와 같은 형식으로 작성하면 된다.
workflow에는 다음과 같이 작성하면 된다.
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKERHUB_DEV_USERNAME }}
password: ${{ secrets.DOCKERHUB_DEV_TOKEN }}
Docker Hub Container Image Library | App Containerization
Deliver your business through Docker Hub Package and publish apps and plugins as containers in Docker Hub for easy download and deployment by millions of Docker users worldwide.
hub.docker.com
Docker Login - GitHub Marketplace
GitHub Action to login against a Docker registry
github.com
07. run: docker build
Docker Image를 만들기 위해서 docker build 명령어를 작성한다.
프로젝트 최상단 패키지에 만들어 놓은 Dockerfile을 실행할 수 있도록 명시했다.
Dockerfile 내용은 다음과 같다.
EC2 환경은 arm64v8이다.
JDK는 Corretto 17을 이용했다.
jar를 복사고 스프링 프로필 환경 변수를 dev로 두고 스프링 서버를 실행시킨다.
FROM arm64v8/amazoncorretto:17
WORKDIR /app
COPY ./build/libs/baton-0.0.1-SNAPSHOT.jar /app/baton.jar
CMD ["java", "-jar", "-Dspring.profiles.active=dev", "baton.jar"]
workflow에는 다음과 같이 작성하면 된다.
- name: Docker Image Build
run: |
cd backend/baton
docker build --platform linux/arm64/v8 -t 2023baton/2023baton -f Dockerfile-dev .
08. run: docker push
이제 이미지로 만든 스프링 프로젝트를 Docker Hub에 push하자.
먼저 Docker에서 Repository를 만들자.
workflow에 `docker push 2023baton/2023baton` 이라는 명령어가 명시되어 있는데
`docker push {docker_username}/{docker_repository_name}` 형태로 명시하면 된다.
workflow에는 다음과 같이 작성하면 된다.
- name: Docker Hub Push
run: docker push 2023baton/2023baton
09. `깃허브가 제공하는 클라우드 환경에서 할 일` workflow 종합
01 ~ 08 까지 소개한 스텝들을 합쳐 하나의 워크플로우를 작성하면 아래와 같다.
on:
push:
branches: [ "dev/BE" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
- name: Build with Gradle
uses: gradle/gradle-build-action@v2.6.0
- name: Execute Gradle build
run: |
cd backend/baton
./gradlew build
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKERHUB_DEV_USERNAME }}
password: ${{ secrets.DOCKERHUB_DEV_TOKEN }}
- name: Docker Image Build
run: |
cd backend/baton
docker build --platform linux/arm64/v8 -t 2023baton/2023baton -f Dockerfile-dev .
- name: Docker Hub Push
run: docker push 2023baton/2023baton
🟩 EC2 내부에 있는 GitHub Self Hosted Runner가 할 일
이제 Docker Hub에 스프링 이미지까지 올라가 있다.
EC2에 있는 GitHub Self Hosted Runner가 Docker Hub에 이미지를 pull해서 컨테이너를 실행하도록 workflow를 작성하자.
01. GitHub Self Hosted Runner 설치
Repository의 Settinggs에 들어가 Actions 탭에 Runners를 들어가서 `New self-hosted runner` 버튼을 누르면 명령어를 보여준다.
다음과 같이 Runner Image를 고를 수 있고 다운로드, 설정, 실행 등 의 정보를 전부 제공해주기 때문에 EC2에 명령어를 실행하면 끝이다.
02. runs-on: [self-hosted]
EC2에 GitHub Self Hosted Runner를 설치했다면 workflow를 작성해주자.
`self-hosted`라고 작성하면 우리가 방금 만든 runner를 선택하게 된다.
만약 다음과 같이 Self-Hosted Runner가 두 개 이상이라면 라벨(label)을 넣어서 구분할 수 있다.
다음과 같은 상황에서 나는 deploy 환경에서는 deploy라는 라벨을 붙였다.
runner가 1개 일 때는 workflow는 다음과 같이 작성할 수 있다.
jobs:
dev-deploy:
runs-on: self-hosted
runner가 2개 이상일 때는 구분지을 수 있어야 되기 때문에 아래와 같이 라벨까지 명시해주면 된다.
# Self-Hosted Runner : dev
jobs:
dev-deploy:
runs-on: [self-hosted, Linux, ARM64]
# Self-Hosted Runner : deploy
jobs:
dev-deploy:
runs-on: [self-hosted, Linux, ARM64, deploy]
현재 워크플로우에서 사용하는 Self-Hosted Runner는 dev라는 것을 생각해줬으면 좋겠다.
03. needs: build
이전에 Docker Hub에 이미지를 push하는 작업이 있었다.
그리고 지금 작성하고 있는 작업은 EC2에서 Docker Hub 이미지를 pull 받아 컨테이너를 실행시키는 것이다.
이 때 순서를 정해주지 않으면 업데이트 되지 않은 이미지를 받아 컨테이너를 실행시킬 수 도 있다.
그러한 문제를 해결하기 위해서 needs라는 키워드를 달아 build라는 작업이 끝나면 다음 작업이 진행되도록 만들자.
workflow에는 다음과 같이 작성하면 된다.
on:
push:
branches: [ "dev/BE" ]
jobs:
build:
runs-on: ubuntu-latest
deploy:
runs-on: [self-hosted, Linux, ARM64]
needs: build
04. docker pull
이제 Docker Hub에 로그인하여 이미지를 pull하도록 하자.
Docker Hub에 2023baton 레포지터리에서 2023baton 이미지를 pull한다.
workflow는 다음과 같이 작성하면 된다.
steps:
- name: Pull Latest Docker Image
run: |
sudo docker login --username ${{ secrets.DOCKERHUB_DEV_USERNAME }} --password ${{ secrets.DOCKERHUB_DEV_TOKEN }}
sudo docker pull 2023baton/2023baton:latest
05. docker run
Docker Image가 pull 받아 최신 상태가 된 경우 스프링 Container를 멈추고 삭제한다.
그리고 다시 새로운 Container를 띄우도록 한다.
workflow는 다음과 같이 작성하면 된다.
steps:
- name: Pull Latest Docker Image
run: |
sudo docker login --username ${{ secrets.DOCKERHUB_DEV_USERNAME }} --password ${{ secrets.DOCKERHUB_DEV_TOKEN }}
if sudo docker inspect spring-baton &>/dev/null; then
sudo docker stop spring-baton
sudo docker rm -f spring-baton
fi
sudo docker pull 2023baton/2023baton:latest
- name: Docker Run
run: |
sudo docker run --name spring-baton -p 8080:8080 2023baton/2023baton:latest 1>> build.log 2>> error.log &name: dev/BE CD on Push
06. `EC2 내부에 있는 GitHub Self Hosted Runner가 할 일` worflow 종합
deploy:
runs-on: [self-hosted, Linux, ARM64]
needs: build
steps:
- name: Pull Latest Docker Image
run: |
sudo docker login --username ${{ secrets.DOCKERHUB_DEV_USERNAME }} --password ${{ secrets.DOCKERHUB_DEV_TOKEN }}
if sudo docker inspect spring-baton &>/dev/null; then
sudo docker stop spring-baton
sudo docker rm -f spring-baton
fi
sudo docker pull 2023baton/2023baton:latest
- name: Docker Run
run: |
sudo docker run --name spring-baton -p 8080:8080 2023baton/2023baton:latest 1>> build.log 2>> error.log &
🟩 최종 워크플로우
name: dev/BE CD on Push
on:
push:
branches: [ "dev/BE" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
token: ${{ secrets.SUBMODULE_BE_TOKEN }}
submodules: recursive
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'corretto'
- name: Build with Gradle
uses: gradle/gradle-build-action@v2.6.0
- name: Execute Gradle build
run: |
cd backend/baton
./gradlew build
- name: Login to Docker Hub
uses: docker/login-action@v2.2.0
with:
username: ${{ secrets.DOCKERHUB_DEV_USERNAME }}
password: ${{ secrets.DOCKERHUB_DEV_TOKEN }}
- name: Docker Image Build
run: |
cd backend/baton
docker build --platform linux/arm64/v8 -t 2023baton/2023baton -f Dockerfile-dev .
- name: Docker Hub Push
run: docker push 2023baton/2023baton
deploy:
runs-on: [self-hosted, Linux, ARM64]
needs: build
steps:
- name: Pull Latest Docker Image
run: |
sudo docker login --username ${{ secrets.DOCKERHUB_DEV_USERNAME }} --password ${{ secrets.DOCKERHUB_DEV_TOKEN }}
if sudo docker inspect spring-baton &>/dev/null; then
sudo docker stop spring-baton
sudo docker rm -f spring-baton
fi
sudo docker pull 2023baton/2023baton:latest
- name: Docker Compose
run: |
sudo docker run --name spring-baton -p 8080:8080 2023baton/2023baton:latest 1>> build.log 2>> error.log &
이제 브랜치에 push 할 때 마다 새로운 이미지가 Docker Hub에 push 되고 Self-hosted는 pull 받아 새로운 컨테이너를 띄운다.
저번에 GitHub Actions를 이용해서 지속적 통합(Continuous Integration) 환경을 구축했다.
이번에는 Docker Hub를 이용하여 CD를 구축한 경험을 작성했다.
혹시 GitHub Actions의 동작이 헷갈리면 아래 블로그를 잠시 읽어보는 것을 추천한다.
깃허브 액션(GitHub Actions)을 이용해서 지속적 통합(CI)할 때의 흐름 알아보기
🚀 이번 포스팅 목표GitHub Actions를 사용하는 포스팅보다 어떤 식으로 동작되는지 흐름을 보도록 한다.GitHub Actions의 Runner에 대해서 이해한다.GitHub Actions의 Event에 대해서 이해한다.GitHub Actions의 Jo
programming-hyena.tistory.com
'🐳 인프라 > 😸 Github Actions' 카테고리의 다른 글
깃허브 액션(GitHub Actions)을 이용해서 지속적 통합(CI)할 때의 흐름 알아보기 (7) | 2023.07.16 |
---|