이번 프로젝트에서 선택한 방식입니다. 하나의 ec2에 mysql, redis, mongodb를 생성을 하고 다른 하나의 ec2에 배포를 하는 방식을 사용을 하였습니다. 물론 이 방식이 최선의 방식은 아니라고 생각을 했습니다. DB 인스턴스에 문제가 생긴다면 DB의 데이터의 손실의 문제가 발생할 수 있고 확장성에서 떨어지지만 Git Action을 쉽게 학습하고 AWS의 다양한 기능을 사용하기에 적합한 방식이라고 생각하여 이 방식을 선택을 하였습니다.
small
로 설정을 하였습니다. 기존에 docker-compose로 배포를 하였을 때 자주 ec2가 다운이 되는 문제가 발생을 하여 small
로 변경하고 메모리 swap
을 수행을 하였습니다.메모리 Swap
sudo dd if=/dev/zero of=/swapfile bs=128M count=32
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo swapon -s
sudo vi /etc/fstab
--------------------
[ vi에 하단에 추가 ]
/swapfile swap swap defaults 0 0
----------------------
## 용량 확인
free
이후 EC2 대시보드를 살펴보면 정상적으로 생성이 된 것을 볼 수 있습니다.
이후 탄력적 IP 설정, 태그 관리를 하겠습니다.
사이드에 탄력적 IP를 선택하여 EC2에 할당을 하고 이후 태그를 선택을 합니다.
인스턴스 오른쪽 클릭 -> 인스턴스 설정 -> 태그 관리를 선택을 하여 태그를 생성을 합니다. -> 이때 태그는 나중에 DEPLOY에서 선택을 할 때 사용을 합니다.
mobaxTerm에서 session -> ssh를 선택을 하면 다음과 같은 화면이 나옵니다.
Remote Host : 탄력적 IP
Specify username : aws linux를 사용하면 ec2-user / ubuntu를 사용하면 ubuntu
use private key를 선택을 하여 ppm 키를 클릭을 하여 접속을 합니다.
ec2의 연결을 선택을 하면 다음과 같은 화면이 나옵니다. SSH 클라이언트를 선택하고 빨간색의 예를 복사하여 KEY가 있는 디렉토리로 이동을 하여 선택을 합니다.
# 스냅샷 update
sudo apt update && sudo apt upgrade
//서울 시간으로 세팅하기
timedatectl list-timezones | grep Seoul
sudo timedatectl set-timezone Asia/Seoul
# 자바 설치
sudo apt install openjdk-11-jdk
# 자바 버전 확인
java --version
# aws 가이드라인 문서
sudo apt install ruby-full
sudo apt install wget
cd /home/ubuntu
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto > /tmp/logfile
sudo service codedeploy-agent status
Ubuntu Server용 CodeDeploy 에이전트 설치 (공식 문서)
https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/codedeploy-agent-operations-install-ubuntu.html
AmazonS3FullAccess
AWSCodeDeployFullAccess
IAM을 눌러 이전의 권한이 아닌 사용자를 누르고 생성을 합니다. 이름을 입력하고 다음을 누르면 역할 설정이 나옵니다.
2개를AmazonS3FullAccess
, AWSCodeDeployFullAccess
를 추가를 합니다.
이후 완료를 누르고 생성된 사용자를 눌러 보안 자격 증명 -> 엑시스 키 만들기를 선택 -> AWS 컴퓨팅 서비스에서 실행되는 애플리케이션 를 선택을 합니다. 추가 만들어진 키 2개를 따로 저장을 합니다.
이후 CodeDeploy를 선택을 하여 애플리케이션 > 애플리케이션 생성을 선택을 합니다.
이후 애플리케이션 CSTUDY_CODE_DEPLOY > 배포 그룹 생성을 선택하여 서비스 역할에 서비스 역할 입력에 이전에 만들었던 IAM을 선택을 합니다.
여기까지 AWS 설정은 끝났습니다.
deploy.yaml
을 추가를 합니다.deploy.yaml
name: CI-CD
# Main 브랜치에 push를 하였을 때
on:
push:
branches:
- main
## 이전에 만들었던 S3, DEPLOY_NAME, GROUP_NAME을 ENV로 따로 변수로 설정을 합니다.
## RESOURCE_PATH는 현재 프로젝트가 멀티모듈로 구성이 되어서 모놀리직 구조이면 module-api를 삭제하고
## 적용을 하면 됩니다.
env:
S3_BUCKET_NAME: s3-cstudy
RESOURCE_PATH: ./module-api/src/main/resources/application.yml
CODE_DEPLOY_APPLICATION_NAME: CODE-DEPLOY-CSTUDY
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: CODE-DEPLOY-CSTUDY-GROUP
jobs:
build:
# 어떤 OS에 실행이 되는지
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
# JDK 설치
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
## Git Action이 실행이 되면서 동적으로 application.yml에 변수로 주입을 합니다.
## 해당 변수는 Github Setting에서 설정을 할 수 있습니다.
## 밑에 사진으로 설명을 하겠습니다.
- name: Set yaml file
uses: microsoft/variable-substitution@v1
with:
files: ${{ env.RESOURCE_PATH }}
env:
spring.data.mongodb.uri: ${{ secrets.MONGODB_URL }}
spring.redis.host: ${{ secrets.REDIS_HOST }}
spring.datasource.url: ${{ secrets.MYSQL_URL }}
spring.datasource.username: ${{ secrets.MYSQL_USERNAME }}
spring.datasource.password: ${{ secrets.MYSQL_PASSWD }}
spring.mail.username: ${{ secrets.MAIL_USERNAME }}
spring.mail.password: ${{ secrets.MAIL_PASSWORD }}
spring.security.oauth2.client.registration.google.client-id: ${{ secrets.GOOGLE_CLIENT_ID }}
spring.security.oauth2.client.registration.google.client-secret: ${{ secrets.GOOGLE_SECRET }}
spring.security.oauth2.client.registration.naver.client-id: ${{ secrets.NAVER_CLIENT_ID }}
spring.security.oauth2.client.registration.naver.client-secret: ${{ secrets.NAVER_SECRET }}
spring.security.oauth2.client.registration.kakao.client-id: ${{ secrets.KAKAO_CLIENT_ID }}
jwt.secretKey: ${{ secrets.JWT_SECRET_KEY }}
jwt.refreshKey: ${{ secrets.JWT_REFRESH_KEY }}
cloud.aws.credentials.accessKey: ${{ secrets.AWS_CREDENTIALS_ACCESS_KEY }}
cloud.aws.credentials.secretKey: ${{ secrets.AWS_CREDENTIALS_SECRET_KEY }}
cloud.aws.s3.bucket: ${{ secrets.S3_BUCKET }}
cloud.aws.region.static: ${{ secrets.AWS_REGION }}
## 권한을 주는 명령어
- name: Grant execute permission for gradlew
run: chmod +x ./gradlew
shell: bash
## build를 하는 명령어
## 모놀리직 구조는 gradle build -x test
- name: Build with Gradle
run: ./gradlew clean :module-api:buildNeeded --stacktrace --info --refresh-dependencies -x test
shell: bash
## Zip 파일 생성: 프로젝트를 압축하여 zip 파일 생성.
- name: Make zip file
run: zip -r ./$GITHUB_SHA .
shell: bash
## AWS 자격 증명 구성: AWS 자격 증명 설정.
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
## S3에 업로드: 생성된 zip 파일을 S3 버킷에 업로드.
- name: Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip
## Code Deploy: AWS CodeDeploy에 배포 생성.
- name: Code Deploy
run: |
aws deploy create-deployment \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$GITHUB_SHA.zip
## source : 인스턴스 복사 디렉토리
## destination : 인스턴스에서 파일이 복사되는 위치
## overwrite : 복사할 위치에 파일이 있는 경우 대체
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/CStudy
overwrite: yes
## object : 권한이 지정되는 파일 또는 디렉토리
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
## 파일 설치 뒤, AfterInstall에서 기존에 실행되던 애플리케이션을 종료시키고, ApplicationStart에서 새로운 애플리케이션을 실행시킨다.
hooks:
ApplicationStart:
- location: scripts/gh_deploy.sh
timeout: 60
runas: ubuntu
#!/bin/bash
PROJECT_NAME="CStudy"
JAR_PATH="/home/ubuntu/CStudy/module-api/build/libs/*.jar"
DEPLOY_PATH=/home/ubuntu/$PROJECT_NAME/
DEPLOY_LOG_PATH="/home/ubuntu/$PROJECT_NAME/deploy.log"
DEPLOY_ERR_LOG_PATH="/home/ubuntu/$PROJECT_NAME/deploy_err.log"
APPLICATION_LOG_PATH="/home/ubuntu/$PROJECT_NAME/application.log"
BUILD_JAR=$(ls $JAR_PATH)
JAR_NAME=$(basename $BUILD_JAR)
echo "===== 배포 시작 : $(date +%c) =====" >> $DEPLOY_LOG_PATH
echo "> build 파일명: $JAR_NAME" >> $DEPLOY_LOG_PATH
echo "> build 파일 복사" >> $DEPLOY_LOG_PATH
cp $BUILD_JAR $DEPLOY_PATH
echo "> 현재 동작중인 어플리케이션 pid 체크" >> $DEPLOY_LOG_PATH
CURRENT_PID=$(pgrep -f $JAR_NAME)
if [ -z $CURRENT_PID ]
then
echo "> 현재 동작중인 어플리케이션 존재 X" >> $DEPLOY_LOG_PATH
else
echo "> 현재 동작중인 어플리케이션 존재 O" >> $DEPLOY_LOG_PATH
echo "> 현재 동작중인 어플리케이션 강제 종료 진행" >> $DEPLOY_LOG_PATH
echo "> kill -9 $CURRENT_PID" >> $DEPLOY_LOG_PATH
kill -9 $CURRENT_PID
fi
DEPLOY_JAR=$DEPLOY_PATH$JAR_NAME
echo "> DEPLOY_JAR 배포" >> $DEPLOY_LOG_PATH
# 만약에 실제 서비스면
nohup java -jar $DEPLOY_JAR >> $APPLICATION_LOG_PATH 2> $DEPLOY_ERR_LOG_PATH &
sleep 3
echo "> 배포 종료 : $(date +%c)" >> $DEPLOY_LOG_PATH
빌드가 정상적으로 동작을 하였으면 AWS의 S3에 ZIP 파일이 정상적으로 로딩을 확인할 수 있습니다.
이후 Git Action에서 배포 요청을 보내고 S3에서 codeDeploy에게 zip 파일을 전달하여 EC2에 배포하는 방식으로 배포 자동화를 하였습니다.
실제 프로젝트에서 이걸 한번 확인하기 위해서 lsof -i:8080
을 통해서 PORT가 정상적으로 동작이 되었는지 확인하고
ubuntu@ip-172-XX-15-XXX:~/CStudy$ lsof -i:8080
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
java 32787 ubuntu 32u IPv6 100746 0t0 TCP *:http-alt (LISTEN)
cat deploy.log
를 통해서 배포를 확인할 수 있고 cat application.log
를 통하여 오류를 확인을 할 수 있습니다.이전에 Jenkins를 통해서 배포 자동화를 하였을 때 현업자와 함께 스터디를 통해서 학습을 하다보니 막히는 부분에서 도움을 많이 받았다. 하지만 이번에는 혼자서 학습하고 배포 자동화를 해보니 많은 실패가 있었지만 자료가 많아서 해결할 수 있었다.
현재는 배포 자동화만 하였지만 다음에는 VPC 설정, 오토 스케일링, Red-Green 무중단 배포를 학습하여 블로깅을 하겠다.