🎯 배포 방법
그동안 프로젝트를 만들고 배포할때 직접 .jar 파일을 만들어 FileZilla 에 올리면서 Ec2 에 배포했었습니다. 정말 원초적인 방법이죠.. 그래서 이번 개인 프로젝트를 배포할때 Docker 와 Git Action 을 활용해서 CI/CD 배포 환경을 구축해볼까 합니다.
현재 진행중인 프로젝트는 멀티 모듈 구조 입니다. 즉, 하나의 서버만 배포하는 것이 아닌 여러개의 서버를 배포해야 하는 상황입니다.
총 7개의 모듈이 있고 그중 fl-api, fl-chatting 모듈을 배포할 예정입니다. 두개의 모듈에 각각 Docker 파일을 만들고 프로젝트 최상단에는 Docker Compose 파일을 만들 예정입니다. (Docker Compose 는 여러개의 Docker 이미지를 하나의 컨테이너로 만들어주는 역할을 합니다.)
🐳 Docker 설정
✅ fl-api 의 DockerFile
# base image
FROM --platform=linux/amd64 openjdk:21-jdk-slim
# 작업 디렉토리 설정
WORKDIR /app
# JAR 파일 복사 (빌드된 JAR 파일을 docker 이미지로 복사)
COPY build/libs/fl-api.jar fl-api.jar
# 포트 열기
EXPOSE 8080
# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "fl-api.jar"]
✅ fl-chatting 의 DockerFile
# base image
FROM --platform=linux/amd64 openjdk:21-jdk-slim
# 작업 디렉토리 설정
WORKDIR /app
# JAR 파일 복사 (빌드된 JAR 파일을 docker 이미지로 복사)
COPY build/libs/fl-chatting.jar fl-chatting.jar
# 포트 열기
EXPOSE 8082
# 애플리케이션 실행
ENTRYPOINT ["java", "-jar", "fl-chatting.jar"]
각각의 모듈에 위와 같은 DockerFile 을 만들었습니다. 여기서 FROM --platform=linux/amd64 openjdk:21-jdk-slim 은 Ec2 운영체제에 맞춰 설정한 값입니다.
✅ docker-compose.yml
version: '3.8'
services:
fl-api:
image: minseok2000/cozy-api
container_name: fl-api
expose:
- "8080"
ports:
- "8080:8080"
env_file:
- /home/ubuntu/.env
depends_on:
- redis
networks:
- app-network
fl-chatting:
image: minseok2000/cozy-chatting
container_name: fl-chatting
expose:
- "8082"
ports:
- "8082:8082"
env_file:
- /home/ubuntu/.env
depends_on:
- redis
networks:
- app-network
redis:
image: redis:7.0
container_name: redis
hostname: redis
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
networks:
- app-network
networks:
app-network:
프로젝트 상단에 위치한 docker-compose 파일입니다. 해당 설정에서는 DB (mysql, redis) 이미지와 fl-api, fl-chatting 에 대한 이미지 설정입니다.
image: minseok2000/cozy-api
이처럼 이미지 설정은 추후 Docker 이미지를 만들어 Docker Hub 로 전송할 예정입니다. 그러면 Ec2 에서는 Docker Hub 에서 이미지를 pull 받아 실행 시키는 원리입니다.
env_file:
- /home/ubuntu/.env
fl-api 와 fl-chatting 에서는 환경변수가 사용됩니다. 환경변수와 같이 민감한 값들을 하드코딩할순 없으니, .env 파일을 만들어 Ec2 에 올릴 예정입니다. 자세한 설명은 아래에서 하겠습니다.
이렇게 만들어진 Docker 파일을 DockerHub 에다 Image 로 만들어 올려야 합니다.
먼저 Docker Hub 로 들어가 Docker Image 를 올릴 Repository 를 만들어야 합니다. 저는 api 모듈 이미지와 chatting 모듈 이미지를 만들어야 하기 때문에 minseok2000/cozy-chatting, minseok2000/cozy-api Repository 를 만들었습니다.
이후 프로젝트로 돌아와 각각의 모듈에 .jar 파일을 만들어야 합니다.
.jar 파일을 만드는 명령어
./gradlew :fl-api:build
./gradlew :fl-chatting:build
위 모듈은 각각의 모듈에 .jar 파일을 만듭니다.
.jar 파일을 만들었다면 이제 Docker 이미지를 만들고 Docker Hub 에 푸쉬 해야 합니다.
Docker Image 만드는 명령어
docker build -t [Dokcer 이름]/[Docker Image 이름]:latest .
저같은 경우 minseok2000/cozy-chatting 과 minseok2000/cozy-api 로 만들었으니 다음과 같이 실행시켰습니다.
docker build -t minseok2000/cozy-chatting:latest .
docker build -t minseok2000/cozy-api:latest .
이후 Docker Hub 에 로그인해야 합니다.
docker login
로그인이 완료됐다면 만든 이미지를 Docker Hub 에 올려야 합니다.
docker push myusername/myapp:latest
여기까지 완료됐으면 Docker 의 기본적인 설정은 완료됐습니다.
🎯 Github Actions 개념
우리는 Spring 애플리케이션을 Github Actions 을 이용해 EC2 에 배포해야 합니다. 이를 사용하기전 기본적인 개념부터 이해하고 가면 좋을거 같아 설명드리도록 하겠습니다.
GitHub Actions 은 코드가 Github 에 푸쉬되거나 풀 리퀘스트가 생성될 때 자동으로 빌드, 테스트, 배포등을 처리하는 워크 플로우를 정의할 수 있게 도와주는 도구입니다. 이를 이용한다면 개발자는 수동으로 처리해야 할 작업을 자동화할 수 있어 코드의 품질을 높이고 빠르게 배포할 수 있다는 장점이 있습니다.
GitHub Actions 구성요소
1. Workflow : Github Actions 에서 정의하는 작업 흐름입니다. '.github/workflows/' 디렉토리 안에 YAML 파일 형식으로 정의합니다.
2.Jobs : 워크플로우 내에서 실행되는 개별 작업입니다. 여러개의 Job 을 설계해 작업 실행 순서를 지정합니다.
3. Steps : 각 Job 에서 실행되는 명령어들입니다.
🎯 Github Actions 를이용한 CI/CD 배포 자동화 진행
가장 먼저 프로젝트 최 상단에 .github/workflows/deploy.yml 파일을 만듭니다.
이제 워크플로우를 설계 합니다.
✅ deploy.yml
name: Deploy to EC2
on:
push:
branches:
- fl-master
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# 1. 코드 체크아웃
- name: Checkout code
uses: actions/checkout@v3
# 2. Docker 빌드 설정
- name: Set up Docker
uses: docker/setup-buildx-action@v2
# 3. Docker Hub 로그인
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
# 4. .env 파일 생성
- name: Create .env file from GitHub secret
run: |
echo "FL_DB_URL=${{ secrets.FL_RDS_DB_URL }}" > .env
echo "FL_DB_URL=${{ secrets.FL_RDS_DB_URL }}" > .env
echo "FL_DB_USER_NAME=${{ secrets.FL_DB_USER_NAME }}" >> .env
echo "FL_DB_PASSWROD=${{ secrets.FL_DB_PASSWROD }}" >> .env
echo "FL_CHAT_DB_USER_NAME=${{ secrets.FL_CHAT_DB_USER_NAME }}" >> .env
echo "FL_CHAT_DOMAIN_BASE_URL=${{ secrets.FL_CHAT_DOMAIN_BASE_URL }}" >> .env
echo "FL_CHAT_DB_URL=${{ secrets.FL_CHAT_DB_URL }}" >> .env
echo "FL_MAP_API_KEY=${{ secrets.FL_MAP_API_KEY }}" >> .env
echo "AWS_SECRET_KEY=${{ secrets.AWS_SECRET_KEY }}" >> .env
echo "ANDROID_OAUTH_CLIENT_KEY=${{ secrets.ANDROID_OAUTH_CLIENT_KEY }}" >> .env
echo "IOS_OAUTH_CLIENT_KEY=${{ secrets.IOS_OAUTH_CLIENT_KEY }}" >> .env
echo "PAYPAL_CLIENT_ID=${{ secrets.PAYPAL_CLIENT_ID }}" >> .env
echo "PAYPAL_CLIENT_SECRET=${{ secrets.PAYPAL_CLIENT_SECRET }}" >> .env
echo "JWT_KEY=${{ secrets.JWT_KEY }}" >> .env
echo "AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}" >> .env
- name: Upload .env to EC2
uses: appleboy/scp-action@master
with:
host: ${{ secrets.EC2_PUBLIC_IP }}
username: ubuntu
port: 22
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
source: ./.env
target: /home/ubuntu/
debug: true
protocol: tcp
timeout: 30s
command_timeout: 10m
tar_exec: tar
proxy_port: 22
proxy_timeout: 30s
- name: Upload docker-compose.yml to EC2
uses: appleboy/scp-action@master
with:
host: ${{ secrets.EC2_PUBLIC_IP }}
username: ubuntu
port: 22
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
source: ./docker-compose.yml
target: /home/ubuntu/
debug: true
protocol: tcp
timeout: 30s
command_timeout: 10m
tar_exec: tar
proxy_port: 22
proxy_timeout: 30s
- name: SSH to EC2 and deploy using docker-compose
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_PUBLIC_IP }}
username: ubuntu
port: 22
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
script: |
cd /home/ubuntu/
# 기존 실행 중인 컨테이너 중지 및 제거
sudo docker-compose down
# 최신 이미지 가져오기
sudo docker-compose pull
# docker-compose로 모든 컨테이너 실행
sudo docker-compose up -d
deploy.yml 을 보면 환경변수로 설정된 값들이 보일겁니다. 환경변수는 민감하고 노출되면 안되는 값이기 때문에 깃허브에 직접 .env 파일을 올려선 안됩니다. 그렇기에 GitHub 에서는 이러한 변수들을 안전하게 저장하고 관리하는 기능이 있습니다.
Setting -> 좌측 Bar Security -> Secrets and variables -> Actions 로 이동하면 다음과 같은 화면이 나옵니다. "New repository secret" 버튼을 눌러 사용할 환경 변수들을 설정할 수 있습니다.
Key-Value 형식으로 저장되며 Name 은 Key, Secret 는 Value 로 저장됩니다.
자, 이제 다시 돌아와 deploy.yml 코드에 대해 설명드리겠습니다.
name: Deploy to EC2
on:
push:
branches:
- fl-master
먼저 GitActions 을 실행할 브랜치 이름을 지정해야 합니다. 저같은 경우 fl-master 가 메인 브랜치입니다. 이제 해당 브랜치로 새로운 코드를 푸쉬하면 푸쉬한 코드를 이용해 Ec2 에 자동으로 배포될 예정입니다.
steps:
# 1. 코드 체크아웃
- name: Checkout code
uses: actions/checkout@v3
# 2. Docker 빌드 설정
- name: Set up Docker
uses: docker/setup-buildx-action@v2
# 3. Docker Hub 로그인
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
우리는 Docker Hub 에 올라간 Image 를 받아와 실행시켜야 합니다. 때문에 Docker 로 로그인하는 과정이 필요합니다. Actions 에 저장한 도커 username 과 accessToken 을 다음과 같이 가져와 도커로 로그인하는 로직을 추가합니다.
참고로 DOCKER_HUB_ACCESS_TOKEN 은 Docker Hub 로 들어가 Account Settings -> Security -> Personal access tokens 에서 만들 수 있습니다.
# 4. .env 파일 생성
- name: Create .env file from GitHub secret
run: |
echo "FL_DB_URL=${{ secrets.FL_RDS_DB_URL }}" > .env
echo "FL_DB_URL=${{ secrets.FL_RDS_DB_URL }}" > .env
echo "FL_DB_USER_NAME=${{ secrets.FL_DB_USER_NAME }}" >> .env
echo "FL_DB_PASSWROD=${{ secrets.FL_DB_PASSWROD }}" >> .env
echo "FL_CHAT_DB_USER_NAME=${{ secrets.FL_CHAT_DB_USER_NAME }}" >> .env
echo "FL_CHAT_DOMAIN_BASE_URL=${{ secrets.FL_CHAT_DOMAIN_BASE_URL }}" >> .env
echo "FL_CHAT_DB_URL=${{ secrets.FL_CHAT_DB_URL }}" >> .env
echo "FL_MAP_API_KEY=${{ secrets.FL_MAP_API_KEY }}" >> .env
echo "AWS_SECRET_KEY=${{ secrets.AWS_SECRET_KEY }}" >> .env
echo "ANDROID_OAUTH_CLIENT_KEY=${{ secrets.ANDROID_OAUTH_CLIENT_KEY }}" >> .env
echo "IOS_OAUTH_CLIENT_KEY=${{ secrets.IOS_OAUTH_CLIENT_KEY }}" >> .env
echo "PAYPAL_CLIENT_ID=${{ secrets.PAYPAL_CLIENT_ID }}" >> .env
echo "PAYPAL_CLIENT_SECRET=${{ secrets.PAYPAL_CLIENT_SECRET }}" >> .env
echo "JWT_KEY=${{ secrets.JWT_KEY }}" >> .env
echo "AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}" >> .env
.jar 로 만든 Java 파일에서는 다양한 환경변수들이 사용됩니다. DB 연결, OAuth 설정, Google Map 연동 등등.. 이러한 값들을 관리할 .env 파일을 만들어 EC2 에 올려놓으면 EC2 에 올라간 Docker Image 들이 환경변수들을 읽어 사용할 수 있습니다. (docker-compose 에서 설정해놨기 때문에 가능)
아래 설정은 docker-compose.yml 에 넣은 코드 입니다. 즉, 우리는 Git Actions 에 저장한 환경 변수들을 이용해 워크 플로우에서 .env 파일을 만들어 EC2 에 배포한다면 EC2 에 같이 올라간 Docker Image 에서는 .env 파일에 접근해 환경변수들을 외부에 노출하지 않고 안전하게 사용할 수 있습니다.
env_file:
- /home/ubuntu/.env
그렇다면 여기까지 .env 파일을 만들었고 이제 만든 .env 파일을 EC2 에 올려놔야 합니다.
- name: Upload .env to EC2
uses: appleboy/scp-action@master
with:
host: ${{ secrets.EC2_PUBLIC_IP }}
username: ubuntu
port: 22
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
source: ./.env
target: /home/ubuntu/
debug: true
protocol: tcp
timeout: 30s
command_timeout: 10m
tar_exec: tar
proxy_port: 22
proxy_timeout: 30s
EC2_PUBLIC_IP 는 Ec2 퍼블릭 IP 주소이고 EC2_SSH_PRIVATE_KEY 는 .pem 키 입니다. pem 키를 Actions 에 저장할때 다음 명령어를 써서 내부를 확인해서 넣어야 합니다.
cat your-key.pem
scp-actions 은 GitHub Actions 워크 플로우에서 파일 전송을 자동화 하기 위해 사용되는 액션입니다. 즉 우리는 이전 과정에서 만든 .env 파일을 EC2 의 /home/ububtu 경로로 전달합니다.
- name: Upload docker-compose.yml to EC2
uses: appleboy/scp-action@master
with:
host: ${{ secrets.EC2_PUBLIC_IP }}
username: ubuntu
port: 22
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
source: ./docker-compose.yml
target: /home/ubuntu/
debug: true
protocol: tcp
timeout: 30s
command_timeout: 10m
tar_exec: tar
proxy_port: 22
proxy_timeout: 30s
docker-compose.yml 도 EC2 에 전달해야 합니다. 이렇게 전달된 docker-compose.yml 은 아래 로직에서 실행될 예정입니다.
- name: SSH to EC2 and deploy using docker-compose
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_PUBLIC_IP }}
username: ubuntu
port: 22
key: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
script: |
cd /home/ubuntu/
# 기존 실행 중인 컨테이너 중지 및 제거
sudo docker-compose down
# 최신 이미지 가져오기
sudo docker-compose pull
# docker-compose로 모든 컨테이너 실행
sudo docker-compose up -d
Ec2 에 올린 docker-compose 을 실행하는 로직입니다. 만약 실행중인 도커 컨테이너가 있으면 중지시키고 이미지를 다시 pull 받아와 실행시킵니다.
이렇게 설정된 deploy.yml 과 docker-compose 를 deploy 에 설정한 브랜치로 푸쉬하면 자동으로 Ec2 에 서버가 배포된 것을 확인할 수 있습니다.
그리고 EC2 인스턴스 Public Ip 를 이용해 API 요청을 보내면 정상적으로 잘 실행되는 것을 확인할 수 있습니다.
'Devops' 카테고리의 다른 글
ElasticSearch 를 EC2 에서 실행하기 (0) | 2025.04.27 |
---|---|
[Devops] GitHub Container Registry 를 사용한 협업 방법 (2) | 2025.03.16 |
[Devops] Docker Compose 를 활용한 Redis Ec2 배포 (0) | 2025.01.18 |