이제 github actions를 이용하여 CI/CD 파이프라인을 구축합니다.
![]() | ![]() |
---|
github settings에서 Actions->General 페이지를 들어간 후 Workflow permissions에서 Read and write permissions를 체크해주어야 합니다.
organization의 경우 레파지토리의 settings가 아닌 organization의 settings에 들어가야 저 설정을 할 수 있습니다.
.github/workflows/ 아래에 yml 파일이 있어야 github actions에서 해당 파일을 바탕으로 배포를 진행합니다.
저는 라즈베리파이에서 도커 컨테이너를 띄워 홈서버로 하였기 때문에 이에 맞춰 yml 파일을 작성했습니다.
제 경우 github actions 러너에서 백엔드(스프링부트) 애플리케이션을 빌드하여 도커 이미지를 만든 후
이를 ghcr(github container registry. 도커 허브.)에 push한 다음
라즈베리파이에서 해당 이미지를 pull 하여 컨테이너를 띄우도록 했습니다.
라즈베리파이에선 성능 문제로 빌드 시간이 오래 걸려 github actions runner에서 빌드를 하도록 한 것입니다.
yml 파일은 각자의 개발 환경에 맞게 수정하면 됩니다.
name: Deploy to Development Server
on:
push:
branches: [develop]
pull_request:
branches: [develop]
jobs:
build-and-deploy:
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 pull
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_HOST=${{ secrets.DB_HOST }}
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 }}
SEOUL_API_KEY=${{ secrets.SEOUL_API_KEY }}
EOL
# 백엔드만 재배포
docker-compose pull backend
docker-compose up -d --no-deps backend
# 상태 확인
docker-compose ps
# 미사용 이미지 정리
docker image prune -f --filter "until=168h"
deploy.yml을 보면 알 수 있듯이 github secrets를 사용하여 민감한 변수들을 관리해 주었습니다.
원래는 github settings 페이지에서 secrets를 일일이 등록해주어야 합니다.
하지만 변수가 많아지면 일일이 등록하기가 힘들어 변수를 한 번에 등록할 수 있게 스크립트를 만들어 주었습니다.
참고로 GH_PAT는 github personal access token입니다. 깃허브 settings에서 발급 받으면 됩니다.
먼저 ssh key를 생성해주는 스크립트입니다. 깃허브 actions에서 배포 서버에 원격으로 접속할 때 ssh 연결을 하게 됩니다.
따라서 ssh key가 필요합니다.
#!/bin/bash
# ANSI 색상 코드
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m'
# SSH 디렉토리 및 파일 확인
echo "SSH 설정을 확인합니다..."
# .ssh 디렉토리가 없으면 생성
if [ ! -d ~/.ssh ]; then
echo ".ssh 디렉토리를 생성합니다..."
mkdir -p ~/.ssh
chmod 700 ~/.ssh
fi
# id_ed25519 파일이 이미 존재하는지 확인
if [ -f ~/.ssh/id_ed25519 ]; then
echo "${YELLOW}SSH 키가 이미 존재합니다.${NC}"
echo "기존 키를 사용하시겠습니까? (y/n)"
read -r response
if [ "$response" = "y" ]; then
echo "${GREEN}기존 SSH 키를 사용합니다.${NC}"
else
echo "새로운 키로 대체하시겠습니까? 이 작업은 되돌릴 수 없습니다. (y/n)"
read -r replace
if [ "$replace" = "y" ]; then
echo "${RED}Warning: 필요한 경우 기존 키를 먼저 백업하는 것을 추천합니다.${NC}"
echo "계속하시겠습니까? (y/n)"
read -r confirm
if [ "$confirm" = "y" ]; then
echo "새로운 SSH 키를 생성합니다..."
echo "이메일 주소를 입력해주세요:"
read -r email
rm ~/.ssh/id_ed25519*
ssh-keygen -t ed25519 -b 4096 -C "$email"
else
echo "${RED}스크립트를 종료합니다.${NC}"
exit 1
fi
else
echo "${RED}스크립트를 종료합니다.${NC}"
exit 1
fi
fi
else
# SSH 키 생성
echo "새로운 SSH 키를 생성합니다..."
echo "이메일 주소를 입력해주세요:"
read -r email
ssh-keygen -t ed25519 -b 4096 -C "$email"
fi
# authorized_keys 설정
echo "authorized_keys 파일을 설정합니다..."
cat ~/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
echo "${GREEN}SSH 키 설정이 완료되었습니다!${NC}"
.env 파일에 있는 환경변수들을 github secrets에 등록해주는 스크립트입니다. 이때 .env 파일은 스크립트를 실행시키는 위치에 있어야 하며
DB_HOST=host
DB_NAME=db
DB_USER=user
DB_PASSWORD=1234
DOMAIN_NAME=domain
BACKEND_PORT=1234
NGINX_PORT=5678
DB_PORT=9012
BACKEND_IMAGE=ghcr.io/user/project:tag
이렇게 환경변수명=값
형식으로 입력 되어 있어야 합니다.
#!/bin/bash
# ANSI 색상 코드
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NC='\033[0m' # No Color
# 사용법 체크
if [ -z "$1" ]; then
echo "${RED}Usage: ./set-github-env-secrets.sh <environment_name>${NC}"
echo "${YELLOW}Example: ./set-github-env-secrets.sh development${NC}"
exit 1
fi
ENVIRONMENT=$1
# Environment secrets 설정
echo "⚙️ ${GREEN}GitHub ${ENVIRONMENT} environment secrets 설정을 시작합니다...${NC}"
# .env 파일이 존재하는지 확인
if [ ! -f .env ]; then
echo "${RED}Error: .env 파일을 찾을 수 없습니다.${NC}"
exit 1
fi
# .env 파일을 읽어서 secrets 설정
while IFS='=' read -r key value
do
if [ ! -z "$key" ] && [ ! -z "$value" ]; then
gh secret set "$key" -b"$value" --env $ENVIRONMENT || exit 1
fi
done < .env
# SSH key 설정 (선택적)
if [ -f ~/.ssh/id_ed25519 ]; then
gh secret set SSH_PRIVATE_KEY -b"$(cat ~/.ssh/id_ed25519)" --env $ENVIRONMENT || exit 1
fi
echo "✅ ${GREEN}GitHub ${ENVIRONMENT} environment secrets 설정이 완료되었습니다!${NC}"
services:
nginx:
image: nginx:alpine
container_name: mb-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
data-init:
build:
context: ./docker-files/data-init
dockerfile: Dockerfile
container_name: mb-data-init
depends_on:
mariadb:
condition: service_healthy
environment:
DB_HOST: ${DB_HOST}
DB_USER: ${DB_USER}
DB_PASSWORD: ${DB_PASSWORD}
DB_NAME: ${DB_NAME}
SEOUL_API_KEY: ${SEOUL_API_KEY}
networks:
- app-network
restart: unless-stopped
mariadb:
image: mariadb:latest
container_name: mb-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: mb-backend
depends_on:
mariadb:
condition: service_healthy
environment:
- DB_HOST=${DB_HOST}
- 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:
server {
listen 80;
server_name ${DOMAIN_NAME};
location / {
proxy_pass http://backend:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# 빌드 스테이지
FROM amazoncorretto:17-alpine as build
WORKDIR /workspace/app
COPY gradle gradle
COPY build.gradle settings.gradle gradlew ./
COPY src src
RUN chmod +x ./gradlew
RUN ./gradlew bootJar
RUN mkdir -p build/libs
# 실행 스테이지
FROM amazoncorretto:17-alpine
VOLUME /tmp
COPY --from=build /workspace/app/build/libs/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
FROM python:3.11-slim
WORKDIR /app
RUN apt-get update && apt-get install -y bash
COPY requirements.txt .
COPY get_cultural_events.py .
RUN pip install -r requirements.txt
COPY . .
CMD ["tail", "-f", "/dev/null"]
이렇게 되면 nginx가 들어오는 모든 요청을 backend 컨테이너로 프록시하는 구조가 됩니다. 전체 시스템은 다음과 같이 동작합니다:
CMD ["tail", "-f", "/dev/null"]
를 사용해 컨테이너가 종료되지 않게 하였습니다.이 구조의 장점은 다음과 같습니다:
이 CI/CD 파이프라인은 GitHub의 develop 브랜치에 코드가 푸시되거나 PR이 생성될 때마다 자동으로 실행됩니다. 빌드된 이미지는 GitHub Container Registry(ghcr.io)에 저장되고, 라즈베리파이 서버에서 이 이미지를 가져와 컨테이너를 실행합니다.
특히 라즈베리파이의 성능 한계로 인해 빌드 과정은 GitHub Actions에서 처리하고, 배포만 라즈베리파이에서 진행하는 방식을 채택했습니다.
전체 시스템이 제대로 동작하려면 GitHub Secrets에 필요한 환경 변수를 모두 설정해야 하며, 이를 위한 스크립트도 함께 제공하여 설정 과정을 간소화했습니다.