github actions를 실행하면 코드 변경이 있을 때 백엔드 이미지를 github actions 러너에서 새로 빌드해서 ghcr에 푸시하고
ssh로 라즈베리파이에 접속한 다음 새로운 이미지를 pull 해와서 백엔드 컨테이너만 재시작 시킴.
(초기에 라즈베리파이에서 $ docker-compose up -d --build
로 nginx, mariadb, backend 컨테이너를 미리 띄워놔야 함)
name: Deploy to Development Server
on:
push:
branches: [develop]
pull_request:
branches: [develop]
jobs:
build-and-deploy:
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
environment: development
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: true
token: ${{ secrets.GH_PAT }}
# Gradle 캐시 설정
- name: Setup Gradle cache
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-gradle-
# 서브모듈 설정
- name: Setup submodule
run: |
cd src/main/resources/config
git checkout develop
cp data-dev.sql ../data-dev.sql
cd ../../../../
# GitHub Container Registry 로그인
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GH_PAT }}
# 도커 빌더 설정
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
# Backend 이미지 빌드 및 푸시
- name: Build and push Backend image
uses: docker/build-push-action@v5
with:
context: .
file: docker-files/backend/Dockerfile
push: true
tags: ${{ secrets.BACKEND_IMAGE }}
platforms: linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
# 필요한 파일만 서버로 전송
- name: Copy deployment files
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.HOST }}
port: ${{ secrets.PORT }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
source: "docker-compose.yml,docker-files/nginx/"
target: "${{ secrets.DEPLOY_PATH }}"
strip_components: 0
# 백엔드 배포
- name: Deploy backend
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
port: ${{ secrets.PORT }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd ${{ secrets.DEPLOY_PATH }}
# GitHub Container Registry 로그인 추가
echo ${{ secrets.GH_PAT }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
# 환경 변수 파일 생성
cat > .env << EOL
DB_ROOT_PASSWORD=${{ secrets.DB_ROOT_PASSWORD }}
DB_NAME=${{ secrets.DB_NAME }}
DB_USER=${{ secrets.DB_USER }}
DB_PASSWORD=${{ secrets.DB_PASSWORD }}
DOMAIN_NAME=${{ secrets.DOMAIN_NAME }}
BACKEND_PORT=${{ secrets.BACKEND_PORT }}
NGINX_PORT=${{ secrets.NGINX_PORT }}
DB_PORT=${{ secrets.DB_PORT }}
BACKEND_IMAGE=${{ secrets.BACKEND_IMAGE }}
EOL
# 백엔드만 재배포
docker-compose pull backend
docker-compose up -d --no-deps backend
# 상태 확인
docker-compose ps
# 미사용 이미지 정리
docker image prune -f --filter "until=168h"
Github Actions 설정을 위해선 .github 디렉토리에 yml 파일을 생성해줘야 한다.
+) 파일을 설명하기 전에 헤맸던 내용을 적어보면..
처음엔 GitHub Actions 워크플로우에서 라즈베리파이에 ssh 접속을 하고 레파지토리의 파일들을 SCP(Secure Copy Protocol)를 사용해 전송해준 다음, 라즈베리파이에서 백엔드 도커 이미지를 빌드하고 컨테이너를 띄우는 방식을 사용했었다.
그런데 배포 시간이 너무 오래 걸렸다. 라즈베리파이의 성능이 좋지 않아 이미지 빌드에 시간이 오래 걸리는 것이 문제였다.
그래서 백엔드 이미지 빌드는 GitHub Actions에서 수행하여 GHCR(GitHub Container Registry)에 올리고, 라즈베리파이에서는 필요한 설정 파일(docker-compose.yml, nginx 설정)만 SCP로 전송받은 후 GHCR에서 이미지를 pull 받아 실행하는 방식으로 변경했다.
이외에도 Github Actions 러너에서 Gradle 빌드 캐시를 활용하여 이전 빌드의 의존성 다운로드와 컴파일 결과물을 재사용함으로써 빌드 속도를 개선시켰다.
on:
push:
branches: [develop]
pull_request:
branches: [develop]
jobs:
build-and-deploy:
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
environment: development
권한 설정을 해준 이유는 백엔드 도커 이미지를 GHCR에 push 해야하기 때문이다.
이때 github settings(Actions->General)에서 workflow permissions를 수정해줘야 한다. repository의 settings에서 저 설정이 막혀 있다면 organization의 settings에서 설정을 해줘야 한다!
배포에 필요한 변수값들을 깃허브의 development environment에 github secrets로 등록해두었기 때문에 development 환경으로 설정해준다.
환경별로 변수를 나눠서 관리할 수 있어 편하다!
a. 코드 체크아웃
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: true
token: ${{ secrets.GH_PAT }}
백엔드 스프링의 application.properties와 같은 파일들을 보안상 이유로 private repository에 올리고 서브모듈로 관리하고 있기 때문에 서브모듈을 포함한 코드를 가져오도록 설정해줬다.
이때 private 레파지토리에 접근하기 위해 토큰이 필요하기 때문에 깃허브에서 토큰을 생성해주고 github secrets에 등록해줬다.
b. Gradle 캐시 설정
- name: Setup Gradle cache
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: ${{ runner.os }}-gradle-
~/.gradle/caches
: Gradle이 다운로드한 의존성 파일들이 저장되는 위치~/.gradle/wrapper
: Gradle Wrapper 파일이 저장되는 위치runner.os
: 운영체제 정보hashFiles()
: gradle 설정 파일들의 해시값속도 향상을 위해 추가했던 설정이다.
GitHub Actions에서 Gradle 빌드 시 매번 모든 의존성을 다운로드하면 시간이 오래 걸린다. 그래서 actions/cache 액션을 사용하여 Gradle의 의존성 파일들을 캐시하고 재사용하도록 했다.
c. 서브모듈 설정
- name: Setup submodule
run: |
cd src/main/resources/config
git checkout develop
cp data-dev.sql ../data-dev.sql
submodule도 develop 브랜치로 설정해준다. 그리고 resources/config에 두었던 테스트용 sql을 resoures/ 아래로 복사해준다.
d. Docker 관련 설정
# Backend 이미지 빌드 및 푸시
- name: Build and push Backend image
uses: docker/build-push-action@v5
with:
context: .
file: docker-files/backend/Dockerfile
push: true
tags: ${{ secrets.BACKEND_IMAGE }}
platforms: linux/arm64
cache-from: type=gha
cache-to: type=gha,mode=max
GitHub Container Registry는 github에서 제공하는 컨테이너 레지스트리 서비스다. 도커 이미지를 Github Actions 러너에서 빌드하고 GHCR에 push하고 라즈베리파이에선 pull하는 방식으로 사용했다.
그런데 Github Actions의 러너는 linux/amd64 기반이다. 라즈베리파이3B는 64-bit ARM 프로세서를 탑재하고 있고 내가 설치한 OS가 64bit라 linux/arm64 플랫폼으로 타겟팅을 해줘야했다. 크로스 플랫폼 빌드라 시간이 훨씬 더 걸리게 되는데ㅠ.. 이건 어떻게 시간을 더 줄일 방법이 없는 것 같다.
e. 배포 단계
# 필요한 파일만 서버로 전송
- name: Copy deployment files
uses: appleboy/scp-action@v0.1.7
with:
source: "docker-compose.yml,docker-files/nginx/"
target: "${{ secrets.DEPLOY_PATH }}"
strip_components: 0
# 백엔드 배포
- name: Deploy backend
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.HOST }}
port: ${{ secrets.PORT }}
username: ${{ secrets.USERNAME }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd ${{ secrets.DEPLOY_PATH }}
# GitHub Container Registry 로그인 추가
echo ${{ secrets.GH_PAT }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
# 환경 변수 파일 생성
cat > .env << EOL
DB_ROOT_PASSWORD=${{ secrets.DB_ROOT_PASSWORD }}
DB_NAME=${{ secrets.DB_NAME }}
...
EOL
# 백엔드만 재배포
docker-compose pull backend
docker-compose up -d --no-deps backend
# 상태 확인
docker-compose ps
# 미사용 이미지 정리
docker image prune -f --filter "until=168h"
배포 과정은 크게 두 단계로 나눠진다.
먼저 SCP(Secure Copy Protocol)를 통해 배포에 필요한 파일들을 라즈베리파이로 전송한다. 백엔드 도커 이미지는 GHCR에서 pull해서 사용하기 때문에 Docker Compose 설정 파일과 Nginx 설정 파일만 전송하면 된다.
그 다음 라즈베리파이에 SSH 접속을 해 실제 배포를 수행한다:
1. GHCR에서 이미지를 받아오기 위해 GitHub Personal Access Token으로 로그인
2. GitHub Secrets의 변수들을 .env 파일로 생성
3. 백엔드 컨테이너만 새로운 이미지로 업데이트. --no-deps
옵션으로 의존성 컨테이너(예: 데이터베이스)는 재시작하지 않는다.
4. docker-compose ps
명령어로 컨테이너들이 정상적으로 실행되고 있는지 확인한다.
5. docker image prune
명령어로 7일 이상 된 사용하지 않는 이미지들을 정리하여 디스크 공간을 관리한다.
이제 이렇게 github actions 설정은 끝!
6분 정도 걸린다
services:
nginx:
image: nginx:alpine@arm64
container_name: cr-nginx
depends_on:
backend:
condition: service_started
ports:
- "${NGINX_PORT}:80"
volumes:
- ./docker-files/nginx/nginx.conf:/etc/nginx/conf.d/default.conf
networks:
- app-network
restart: unless-stopped
mariadb:
image: mariadb:latest@arm64
container_name: cr-mariadb
healthcheck:
test: ["CMD", "mariadb", "-h", "localhost", "-u${DB_USER}", "-p${DB_PASSWORD}", "-e", "SELECT 1"]
start_period: 30s
interval: 10s
timeout: 5s
retries: 5
environment:
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MARIADB_DATABASE: ${DB_NAME}
MARIADB_USER: ${DB_USER}
MARIADB_PASSWORD: ${DB_PASSWORD}
ports:
- "${DB_PORT}:3306"
volumes:
- mariadb-data:/var/lib/mysql
networks:
- app-network
restart: unless-stopped
backend:
image: ${BACKEND_IMAGE}
platform: linux/arm64
container_name: cr-backend
depends_on:
mariadb:
condition: service_healthy
environment:
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
ports:
- "${BACKEND_PORT}:8080"
networks:
- app-network
restart: unless-stopped
networks:
app-network:
driver: bridge
volumes:
mariadb-data:
백엔드 서비스 설정 변경
build
설정을 제거하고 image: ${BACKEND_IMAGE}
로 변경하여 GHCR에서 이미지를 가져오도록 수정platform: linux/arm64
를 명시하여 라즈베리파이에서 실행할 ARM64 아키텍처 지정depends_on
조건 추가MariaDB 설정 개선
image: mariadb:latest@arm64
로 변경하여 ARM64 아키텍처 지정Nginx 설정 수정
image: nginx:alpine@arm64
로 변경하여 ARM64 아키텍처 지정depends_on
조건을 더 명확하게 수정 (condition: service_started
추가)빌드 전략의 변경(로컬 빌드 → GHCR 활용)에 따라 Docker Compose 설정도 함께 수정했다.
일단 라즈베리파이에서 실행하기 위해 모든 이미지를 ARM64 아키텍처용으로 지정했다.
주요 변경사항은 mariadb에 healthcheck를 추가하고 backend가 의존하도록(depends_on) 하게 한 것과 nginx가 backend에 의존하도록 하게 한 것, 그리고 backend에서 db 관련 설정값들을 환경변수를 사용할 수 있도록 지정해준 것이다.
healthcheck는 컨테이너가 특정 조건을 만족하는지 확인하여 정상적으로 실행 중인지 판단할 때 사용된다. mariadb에 접속이 가능할 때 backend가 실행돼야 db 접속 오류로 종료되지 않기 때문에 해당 내용을 추가해줬다.
또 nginx.conf에서 proxy_pass http://backend:8080;
와 같이 백엔드 컨테이너의 도메인명을 사용하기 때문에, backend 컨테이너가 시작된 후에 nginx 컨테이너가 시작되도록 의존성을 추가해줬다. 그렇지 않으면 해당하는 도메인을 찾을 수 없어 nginx에서 오류가 나게 됐었다.