Github actions 는 기본적으로 CI/CD(Continuous Integration/Continuous Delivery)를 가능하게 해주는 깃허브의 기능이다.
CI/CD란 영어 말그대로 지속적인 통합/배포의 뜻이다.
그전까지 CI/CD의 필요성에 대해서 깨닫지 못하고 있었는데, 이번에 배포를 하는 과정에서 그 중요성에 대해 깨닫게 되었다.
만약 우리가 완전 초기의 MVP모델을 도커로 이미지화해서 배포를 했다고 가정해보자.
초기 배포를 한후에 기능을 추가하려고 한다면,
의 단계를 거쳐야하는데, 기능이 추가 될때마다 이 과정을 계속 수동으로 반복해서 해주어야만한다.
Github actions를 이용한다면 이런 과정들을 자동화 할수 있다.
전체적인 흐름은 다음과 같다
- 소스 코드 변경
- 해당 github actions의 트리거 작동
- 바뀐 소스를 바탕으로 도커 재빌드 그리고 push
- ec2에서 해당 이미지를 다시 pull해와서 재실행
토큰 같은 경우 무조건 필수적인 부분은 아니지만, 만약 본인이 docker hub와 같은 외부 사이트 연결해야하고, github api 같이 제한된 기능을 사용하기 위해서는 토큰이 필요하다.
깃허브의 본인 프로필에서 Settings를 들어간다.
왼쪽 하단에 Developer Settings라는 메뉴에서 Personal access tokens를 들어간다.
2가지의 종류의 토큰을 발급받을수 있는데, Fine-grained는 베타버전이기도하고 검색해보니 issue들이 아직 있는것 같아서 일단 연습삼아 classic 토큰을 발급받는다.
Generate a personal access token
를 클릭해주면 된다.
토큰 이름을 작성해주고, 만료기한, 그리고 범위를 설정해준다.
workflow, write, delete는 꼭 해주어야한다.
이러면 토큰 발급이 완료되는데, 토큰 값 같은 경우 다시 볼수는 없으므로 따로 기록해두어야한다.
해당 레포의 settings 창의 하단에서 Actions를 들어가서 발급받은 토큰을 입력해준다.
위에서 Actions를 눌러준다.
이미 깃허브 내에서 많은 단계들에 맞춰서 템플릿을 만들어놓았다.
아예 차근차근 공부하기 위해서 처음부터 작성해보자.
name: workflow의 이름
name
은 해당 yml파일의 워크플로우 명을 작성해주면 된다.
on:
push:
branches:
- main
on
은 트리거인데, 즉 main 브랜치에서 push가 일어나면 하위의 동작들을 수행하겠다. 라는 뜻이다.
jobs:
build_and_push_frontend:
jobs
는 실행할 작업들의 그룹을 정의한다.
각 작업은 독립적으로 실행될 수 있으며, 다른 작업들과 병렬 또는 순차적으로 실행될 수 있다.
하나의 jobs안에 여러개의 작업들이 수행될수 있는것이다.
runs-on: ubuntu-latest
해당 작업이 실행될 환경을 의미한다. 깃허브 자체에서 이 작업이 수행될 호스트머신을 대여해준다고 생각하면 되는데, 그 호스트머신의 환경을 의미한다.
if:
if
는 해당 작업이 수행될 조건이다.
만약에 한 레포 안에 백엔드/ 프론트엔드가 전부 존재할때, 프론트엔드 코드가 수정되면 프론트엔드의 이미지만 빌드되면 된다.
하지만 if가 없이 모든 작업들을 합쳐주면, 다시 빌드될 필요가 없는 백엔드측 이미지도 재빌드 되기 때문에 이를 방지해줄수 있다.
steps:
- name: Checkout Repository
uses: actions/checkout@v2
steps
는 해당 작업 내에서 수행되어야할 단계들이다. 여기서 각각의 수행할 단계를 지정할수 있고, name과 uses가 보통 함께 쓰인다.
name
은 스텝의 이름이다.
uses
는 다른 액션을 사용할때 적용된다. 예를들어서 checkout이라는 메서드는 기본적으로 깃허브 액션에 내장되어있는 액션인데, 이를 가져와서 사용함으로써 재사용성을 높인다.
다른 곳에서 커스텀 액션을 만들어서 그것을 또 재사용할수도 있다.
run
은 쉘커맨드를 사용할수 있도록 해준다.
예를 들어서 docker build나 npm run 같은 커맨드 명령어를 수행해야하는경우, run을 통해서 이 작업을 자동화해줄수 있다.
만약 도커와 관련한 워크플로우를 작성해주어야하는 경우, 도커 허브내에서 액세스 토큰을 발급 받아서 해당 토큰값을 변수로 저장해놓아야한다.
Docker hub 사이트 내에서 계정 => My Account => Security 창으로 가면 액세스 토큰을 발급 받을 수 있다.
이 또한 한번 창이 닫히면 다신 볼수 없으니 따로 메모해두자.
name: Docker build and push
on:
push:
branches:
- main
paths:
- "front/**"
- "backend/**"
jobs:
path_changes:
runs-on: ubuntu-latest
outputs:
front: ${{ steps.filter.outputs.front }}
backend: ${{ steps.filter.outputs.backend }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
front:
- 'front/**'
backend:
- 'backend/**'
build_and_push_frontend:
needs: path_changes
if: needs.path_changes.outputs.front == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Meta for Frontend
id: docker_meta_frontend
uses: docker/metadata-action@v5
with:
images: brgndy/brgndy_dev_blog_front
- name: Build and Push Frontend Docker Image
uses: docker/build-push-action@v5
with:
context: ./front
file: ./front/Dockerfile
platforms: linux/amd64
push: true
tags: ${{ steps.docker_meta_frontend.outputs.tags }}
labels: ${{ steps.docker_meta_frontend.outputs.labels }}
build_and_push_backend:
needs: path_changes
if: needs.path_changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Set up Docker Meta for Backend
id: docker_meta_backend
uses: docker/metadata-action@v5
with:
images: brgndy/brgndy_dev_blog_backend
- name: Build and Push Backend Docker Image
uses: docker/build-push-action@v5
with:
context: ./backend // 빌드할 컨텍스트
file: ./backend/Dockerfile // 실제 Dockerfile이 있는 경로
platforms: linux/amd64
push: true
tags: ${{ steps.docker_meta_backend.outputs.tags }}
labels: ${{ steps.docker_meta_backend.outputs.labels }}
본인은 한 레포에 프론트와 백엔드 폴더 두개가 존재하므로, path-filter를 사용해서 소스코드 업데이트가 일어난 폴더에 따라서 트리거를 분리해주었다.
정상적으로 Actions도 작동되었고, 도커허브에 해당 이미지가 정상적으로 업데이트 되었다.
- name: EC2 Front Docker Run
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_FRONT_HOST }}
username: ec2-user
key: ${{ secrets.EC2_FRONT_KEY }}
script: |
docker stop 이미지 || true
docker rm 이미지 || true
docker pull push 한이미지
docker run --name 컨테이너이름 -d -p 3000:3000 이미지
기본적으로 깃허브 환경변수에 담아놔야하는 부분이 HOST와 KEY 부분이다.
KEY는 인스턴스 생성시 다운받은 .pem 파일의 값이고, HOST는 해당 인스턴스의 주소이다.
프론트와 백엔드 둘다 배포 자동화까지 제대로 이루어진것을 확인할수 있다.
궁금증은 현재 그럴일은 거의 없겠지만, 만약 사용자가 사용중에 재배포가 이루어진다면 ?
그 사이에 사용자는 사이트에 접속하지 못하게 될텐데 이런 경우에 무중단배포가 이용되는것 같다.
무중단 배포에는 또 많은 전략들이 존재하는것 같은데, 이는 추후에 알아봐야겠다.