이전에 포스팅 했던 Coin Game을 한번 백엔드까지 붙여서 개발을 하려고 합니다.
https://codekunst.tistory.com/167
사과 게임 | 코인 게임 운영 후기
https://coingame0.netlify.app/ Coin Game coingame0.netlify.app 게임을 만들게 된 계기부트캠프를 다니고 있는데 지금은 Vue를 배우고 있다. Vue와 React를 동시에 알게 되니까 조금 더 인사이트가 넓어지는 기
codekunst.tistory.com
이전에는 netlify를 통해 간단하게 배포를 진행해봤는데요
추가적으로 모드를 여러 개 구현하기 위해서는 다양한 기술 스택이 필요하게 됐고 그로 인해 백엔드를 추가해야 했습니다.
프로젝트와 목적이 맞는 팀원을 한명 구했고, 저는 인프라와 프론트엔드를 담당해서 개발을 진행했습니다.
이 글을 읽으면 좋은 분들은 아마 프론트엔드 이면서 배포에 대한 지식을 쌓으려고 하시는 분이 될 것 같습니다.
프로젝트를 어떻게 배포하지?
Vercel + AWS | 프론트 중심 개발, 빠른 배포 |
EC2 + Docker | 팀 내 관리 편의성 우선 |
ECS + ECR | 고도화된 인프라 구성 필요 시 |
S3 + Lambda | 정적 프론트 + 서버리스 백엔드 |
Firebase | 소규모 프로젝트, 빠른 시작 |
배포를 할 때는 보통 위와 같은 조합으로 하게 됩니다.
각각의 조합에는 장단점이 존재합니다. 저는 주로 개발이 빠른 편이라 1번 조합인 Vercel + AWS 혹은 5번 Firebase + Baas를 사용해 왔습니다.
이번에는 같은 컨테이너 위에서 동작하는 것을 해보고 싶었고 + 소켓 통신이 들어가야 했기에
- 로컬 네트워크 상에서의 빠르고 안정적인 통신
- 포트/도메인 관리가 간단
- Cross-Origin 문제 없이 통신 가능
- 배포 및 재시작 시 프론트/백 동시에 관리 가능
이러한 이점이 있어서 싶어서 2번 조합을 선택했습니다.
Docker? GIthub Actions? EC2?
처음 들어보시는 분들을 위해 간단히 설명을 적겠습니다.
Docker
검색해보면 귀여운 고래가 많이 뜨죠.
우리의 Docker는 다른 컴퓨터에 있는 것을 내 컴퓨터에서 그대로 실행할 수 있게 해주는 녀석입니다.
제 컴퓨터에서는 되던데요?
이런 명언을 해결해주는 아주 훌륭한 기술이죠
프론트엔드 개발을 한다고 가정해보면
1. nodeJS 설치
2. npm 설치
3. 프로젝트 설치
와 같은 과정을 거쳐야 프로젝트를 실행할 수 있습니다.
여기서 1, 2번 같은 경우 컴퓨터마다 버전이 달라지면 실행에 문제가 생길 수도 있죠
-> 이때 하나의 프로그램 (Docker) 을 설치하고
-> 1,2 번에 맞는 설치 명령어(Image)를 넣으면
-> Docker 이미지 빌드 후 빌드된 이미지를 기반으로 Docker 컨테이너 생성 & 실행을 합니다.
그래서 Docker를 쓰는 것입니다. 이제 살짝 이해가 온 틈을 타서 EC2에 대해 설명드리겠습니다.
EC2
AWS라는 회사의 컴퓨터 한대라고 생각하면 됩니다. ( 정확히는 VM )
이걸 대여해서 우리는 Docker를 설치하고 Image 파일을 넣어서 실행할 수 있는 환경을 만들 것입니다.
그렇게 되면 이런 구조가 됩니다.
이제 EC2라는 남의 컴퓨터에도 내 웹 프로그램을 실행시킬 수 있게 됩니다.
Github Actions
그러면 제 컴퓨터에서 열심히 개발한 코드를 이제 올려야 하는데
저야 혼자 개발을 하니까 문제가 없지만 만약 협업을 한다면 어떻게 될까요?
( 혼자 하면 그냥 EC2에 올려도 됩니다. 수동 배포라고 하죠 허허 )
이렇게 되면 도커 이미지를 빌드하는 과정이 번거로워집니다.
이런 짓은 하지말고 우리는 협업 툴인 깃허브를 사용합시다.
그림이 애매한 부분이 있습니다. 깃허브에는 도커가 아닌 이미지 파일만 올라가면 됩니다.
뭐 암튼 이런식으로 한다면 도커를 굳이 깔지 않아도 자동으로 협업을 하면서 EC2에 코드를 전달할 수 있게 됩니다.
이러한 장점때문에 우리는 이러한 아키텍쳐를 가지고 CI/CD를 구축하겠습니다.
Step1. Docker Image를 만들어라, 그리고 Docker compose해라
저의 프로젝트에는
/backend
/frontend
/docker-compose.yml
이렇게 되어 있습니다.
각각의 프로젝트 안에 Docker 이미지로 만들기 위한 설정들을 넣어줍니다.
backend Dockerfile, frontend Dockerfile과 같이 파일을 넣어줍니다.
// backend/DockeFile
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY build/libs/app.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
// frontend/Dockerfile
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
아 물론 Docker를 설치하고 로그인도 해주셔야 합니다 :)
이따가 Personal Access Token을 발급해야 하기 때문에 ~
명령어에 대한 설명은 생략하겠습니다. 안되면 다른 블로그나 챗지피티한테 달라고 하심됩니다 허헣
그리고 Docker-Compose.yml 파일을 만들고
version: "3.8"
services:
spring-app:
build:
context: ./backend
container_name: spring-app
image: {도커 사용자 이름}/spring-app:latest
ports:
- "8080:8080"
restart: always
networks:
- app-network
vue-app:
build:
context: ./frontend
container_name: vue-app
image: {도커 사용자 이름}/vue-app:latest
ports:
- "3000:3000"
depends_on:
- spring-app
restart: always
networks:
- app-network
networks:
app-network:
driver: bridge
이런식으로 작성해두면 됩니다. 저기 안에는 자기 이름 넣으면 됩니다.
그렇게 빌드 파일이 잘 만들어지나 테스트를 해보고 깃허브에 올리면 step1 끝!
+ 추가) 도커 컴포즈란?
각각의 폴더 안에 맞는 도커 파일들이 들어갔는데 이것들을 통합해서 한번에 정의하고 실행하도록 하는 도구입니다.
-> 여러개의 컨테이너를 하나의 서비스로 묶어서 관리 가능하게 해준다고 보시면 됩니다. ( 명령어 docker compose up )
이렇게 같이 묶이면 자동으로 같은 네트워크에서 속하게 되어서 통신이 가능해집니다 .
Step2. Github Actions 설정하기
.github/workflows/ci-cd.yml 파일을 만들고 여기에 깃헙 액션이 수행할 내용을 적습니다.
그러면 우리의 깃헙이 커밋이 변경될 때마다 자동으로 이 스크립트를 실행합니다.
name: CI/CD Pipeline
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Spring Boot 빌드
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
distribution: "temurin"
java-version: "17"
- name: Build Spring Boot app
run: |
cd backend
./gradlew clean build -x test
# Docker Hub 로그인
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# Spring Docker 이미지 빌드 및 푸시
- name: Build and Push Spring Docker image
uses: docker/build-push-action@v5
with:
context: ./backend
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/spring-app:latest
# Vue Docker 이미지 빌드 및 푸시
- name: Build and Push Vue Docker image
uses: docker/build-push-action@v5
with:
context: ./frontend
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/vue-app:latest
# EC2에 SSH로 접속하여 배포
- name: Deploy to EC2 instance
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USERNAME }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
export DOCKER_USERNAME=${{ secrets.DOCKER_USERNAME }}
cd ~/app
docker pull $DOCKER_USERNAME/spring-app:latest
docker pull $DOCKER_USERNAME/vue-app:latest
docker-compose down
docker-compose up -d
그리고 중간 중간에 있는 secrets같은 시크릿 키는
Settings > Security > Secrets and variables에 있는 Actions를 눌러서 등록해주면 됩니다.
DOCKER_PASSWORD : Personal Access Token
DOCKER_USERNAME : 도커 사용자 닉네임
EC2_HOST : EC2 사용자 닉네임
EC2_SSH_KEY : .pem을 base64로 인코딩 ( cat 으로 열어서 --- 앞뒤 포함해서 붙여야합니다. )
Step3. 무한 디버깅
상단에 Actions에서 계속 배포를 시도하면서 에러가 뜨면 잡으면 됩니다
사진처럼 초록 체크가 뜨면 CI/CD 구축 성공입니다 :)
회고
인프라를 구축한다는 것이 어려운 일이 아니라는 것을 알게 되었다.
무엇이든 해낼 수 있다는 자신감을 얻었고 다음엔 블루 그린 아키텍쳐를 적용해 봐야겠다.