지난 게시글에서 IAM 설정까지 모두 완료 했습니다. 이제 CodeDeploy 설정부터 다시 시작해보겠습니다!
권한 정책에서 권한을 추가해줍니다(AmazonEC2RoleforAWSCodeDeploy 선택)
사용자 이름을 만들어줍니다.
EC2에서 이 역할을 사용하도록 변경해줍니다.
방금 작성한 역할로 변경해줍니다.
역할 변경 후에는 인스턴스를 재시작해줍니다.
다시 EC2로 접속합니다.
$ ssh {서비스 명(호스트 명) }
// EC2 접속 후 입력 - CodeDeploy 설치
$ aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2
// ruby 설치
$ sudo yum install ruby
// CodeDeploy 설치
$ chmod +x ./install
$ sudo ./install auto
// CodeDeploy 상태 확인
$ sudo service codedeploy-agent status
빈 프로젝트 생성은 http://start.spring.io 에서 자유롭게 생성합니다.
프로젝트 루트에 appspec.yml 파일을 작성합니다. 전부 주석을 작성했으니 읽어보시면 이해가 되실 거 같습니다.
version: 0.0
# Deploy 대상 서버의 운영체제를 표시
os: linux
# 코드 파일 전송과 관련된 설정
files:
# 코드 파일의 소스 경로
- source: /
# 코드 파일의 대상 경로 -> /home/ec2-user/app 디렉토리로 파일을 복사한다.
destination: /home/ec2-user/app
# 대상 경로에 이미 파일이 존재하는 경우, 덮어쓰기를 허용할지 여부
overwrite: yes
# 파일 및 디렉토리 권한에 관련된 설정
permissions:
# 권한을 설정할 대상 경로
- object: /
# 모든 파일 및 디렉토리를 의미
pattern: "**"
# 파일 및 디렉토리의 소유자를 ec2-user로 설정
owner: ec2-user
# 파일 및 디렉토리의 그룹을 ec2-user로 설정
group: ec2-user
# Deploy 전후에 실행할 스크립트 또는 명령에 관련된 설정
hooks:
# 애플리케이션 시작시 실행할 스크립트 또는 명령에 관련된 설정
ApplicationStart:
# 실행할 스크립트 또는 명령의 위치
- location: deploy.sh
# 스크립트 또는 명령 실행의 제한 시간을 설정
timeout: 60
# CodeDeploy 중 실행되는 스크립트 또는 명령을 실행할 사용자를 지정
runas: ec2-user
배포에서 사용할 Docker와 Docker-Compose를 EC2 서버에 설치하겠습니다.
아래 작성된 명령어를 순서대로 실행하면 설치가 완료됩니다.
// 도커를 설치
sudo yum install docker
// 도커를 실행
sudo systemctl start docker
// 도커에서 실행되는 컨테이너를 확인
sudo docker ps
// 도커 컴포즈 설치
sudo curl -L https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose
// 도커 컴포즈 권한 부여
sudo chmod +x /usr/local/bin/docker-compose
// 도커 컴포즈 버전 확인
docker-compose version
#blue
version: '3'
services:
# 서비스의 이름
backend:
# 현재 디렉토리에서의 Dockerfile을 사용하여 Docker 이미지를 빌드
build: .
# 호스트의 8081 포트와 컨테이너의 80 포트를 매핑
ports:
- "8081:80"
# 컨테이너의 이름
container_name: spring-blue
#green
version: '3'
services:
backend:
build: .
ports:
- "8082:80"
container_name: spring-green
green과 blue는 서비스의 이름과, 매핑된 포트번호만 다르기때문에 green은 따로 주석을 작성하지 않았습니다.
### Docker 이미지를 생성할 때 기반이 되는 베이스 이미지를 설정한다.
FROM openjdk:11-jre-slim
### Dockerfile 내에서 사용할 변수 JAR_FILE을 정의한다.
ARG JAR_FILE=build/libs/*.jar
### JAR_FILE 경로에 해당하는 파일을 Docker 이미지 내부로 복사한다.
COPY ${JAR_FILE} {원하는 파일 이름.jar}
### Docker 컨테이너가 시작될 때 실행할 명령을 지정한다.
ENTRYPOINT ["java","-jar","/{원하는 파일 이름.jar}"]
주석을 전부 달아 두었습니다. 주석을 읽어보시면 내용을 이해하시는데 어려움이 없으실거라 생각합니다.
이제 Github Actions를 셋팅해보겠습니다.
프로젝트를 업로드한 Repository에 들어가서 Actions 탭으로 진입합니다.
Java With Gradle을 클릭하면, 기본 템플릿이 나오는데 저는 이 템플릿을 수정해서 사용하도록 하겠습니다.
이벤트에 따라서 yml 파일을 여러개를 둘 수 있는데, 저는 배포용 deploy.yml과 bulid.yml 파일 두개를 작성하겠습니다.
# Workflow의 이름을 지정합니다.
name: BackEnd - CI/CD
on:
push:
# main branch에 push(merge)될 경우 실행됩니다.
branches: ["main"]
permissions:
contents: read
jobs:
build:
# build를 진행할 운영체제를 선택합니다.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# JDK를 11 버전으로 셋팅합니다.
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
# Gradle을 캐싱해둡니다 -> 빌드 속도가 증가하는 효과가 있습니다.
- name: Gradle 캐싱
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: Make application.yml
run: |
# src/main 으로 경로를 이동합니다.
cd ./src/main
# src/main 경로에 resources 폴더를 만들어줍니다.
mkdir resources
cd ./resources
# 필요한 yml 파일들을 만들어줍니다.
touch ./application.yml
touch ./application-aws.yml
touch ./application-oauth.yml
touch ./application-app.yml
# 등록해둔 Github Secrets의 내용을 이용해서 yml 파일의 내용을 써줍니다.
echo "$APPLICATION_OAUTH" > ./application-oauth.yml
echo "$APPLICATION_APP" > ./application-app.yml
echo "$APPLICATION" > ./application.yml
echo "$APPLICATION_AWS" > ./application-aws.yml
env:
APPLICATION_OAUTH: ${{ secrets.APPLICATION_OAUTH }}
APPLICATION_APP: ${{ secrets.APPLICATION_APP }}
APPLICATION: ${{ secrets.APPLICATION }}
APPLICATION_AWS: ${{ secrets.APPLICATION_AWS }}
shell: bash
- name: Gradle 권한 부여
run: chmod +x gradlew
- name: Gradle로 빌드 실행
run: ./gradlew bootjar
# 배포에 필요한 여러 설정 파일과 프로젝트 빌드파일을 zip 파일로 모아줍니다.
- name: zip file 생성
run: |
mkdir deploy
cp ./docker/docker-compose.blue.yml ./deploy/
cp ./docker/docker-compose.green.yml ./deploy/
cp ./appspec.yml ./deploy/
cp ./docker/Dockerfile ./deploy/
cp ./scripts/*.sh ./deploy/
cp ./build/libs/*.jar ./deploy/
zip -r -qq -j ./spring-build.zip ./deploy
# AWS에 연결해줍니다.
- name: AWS 연결
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에 프로젝트를 업로드 합니다.
- name: S3에 프로젝트 업로드
run: |
aws s3 cp \
--region ap-northeast-2 \
./spring-build.zip s3://backend-rex-bucket
# CodeDelploy에 배포를 요청합니다.
- name: Code Deploy 배포 요청
run: aws deploy create-deployment --application-name backend-deploy-group
--deployment-config-name CodeDeployDefault.OneAtATime
--deployment-group-name backend-deploy-group
--s3-location bucket=backend-rex-bucket,bundleType=zip,key=spring-build.zip
# 빌드 성공 & 실패 여부를 Slack 알람으로 발송합니다.
- name: Slack 알람 발송
uses: rtCamp/action-slack-notify@v2
env:
SLACK_CHANNEL: general
SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff'
SLACK_ICON: https://github.com/rtCamp.png?size=48
SLACK_MESSAGE: 배포 결과 => ${{ job.status }}
SLACK_TITLE: 배포 결과 알람
SLACK_USERNAME: Notification-Bot
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
# 이 구문을 추가해야 빌드 성공 실패와 상관없이 동작합니다.
if: always()
name: BackEnd - CI/CD
on:
pull_request:
// main branch, dev branch에 pr 할경우 동작합니다.
branches: ["main", "dev"]
push:
// dev branch에 push 하면 동작합니다.
branches: ["dev"]
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 11
uses: actions/setup-java@v3
with:
java-version: '11'
distribution: 'temurin'
## gradle caching
- name: Gradle 캐싱
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: Make application.yml
run: |
cd ./src/main
mkdir resources
cd ./resources
touch ./application.yml
touch ./application-aws.yml
touch ./application-oauth.yml
touch ./application-app.yml
echo "$APPLICATION_OAUTH" > ./application-oauth.yml
echo "$APPLICATION_APP" > ./application-app.yml
echo "$APPLICATION" > ./application.yml
echo "$APPLICATION_AWS" > ./application-aws.yml
env:
APPLICATION_OAUTH: ${{ secrets.APPLICATION_OAUTH }}
APPLICATION_APP: ${{ secrets.APPLICATION_APP }}
APPLICATION: ${{ secrets.APPLICATION }}
APPLICATION_AWS: ${{ secrets.APPLICATION_AWS }}
shell: bash
- name: Gradle 권한 부여
run: chmod +x gradlew
- name: Gradle로 빌드 실행
run: ./gradlew bootjar
- name: Slack 알람 발송
uses: rtCamp/action-slack-notify@v2
env:
SLACK_CHANNEL: general
SLACK_COLOR: ${{ job.status }} # or a specific color like 'good' or '#ff00ff'
SLACK_ICON: https://github.com/rtCamp.png?size=48
SLACK_MESSAGE: 빌드 결과 => ${{ job.status }}
SLACK_TITLE: 빌드 결과 알람
SLACK_USERNAME: Notification-Bot
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
if: always()
deploy.yml과 build.yml은 내용은 비슷하지만 build.yml 파일에는 aws에 접근하고 배포요청을 하는 부분이 빠졌습니다.
deploy.yml은 개발이 완료되서 테스트가 된 main branch에 합쳐질경우 배포까지 진행해야 하기 때문에 aws에 배포하는 코드가 있습니다.
반면에 build.yml은 main branch를 통해 배포된 버전과 다른 dev branch가 push 되거나 합쳐질때 작동하기 때문에 배포를 위한 코드를 제외하였습니다.
🍒 Slack 연동 설정까지 포스팅을 작성하면 너무 길어질 거 같아서, 이 부분은 연동이 간단하니까 한번 찾아 보시면 바로 적용이 가능합니다!
이제 마지막으로 Github Actions에서 사용할 secrets를 설정하겠습니다.
Github Actions의 Sercret에는 외부에 공개되면 안되는 값들을 저장하고 사용할 수 있습니다.
대표적으로 AWS의 액세스키와 접근 정보들, 각종 yml 파일의 내용이 있습니다.
New repository secret을 클릭하면 등록이 가능합니다.
이제 배포를 하기전에 마지막으로 프로젝트 루트에 deploy.sh 파일을 작성하겠습니다.
deploy.sh 파일은 배포를 위해 실행되는 파일입니다.
#!/bin/bash
# 작업 디렉토리를 /home/ec2-user/app으로 변경
cd /home/ec2-user/app
# 환경변수 DOCKER_APP_NAME을 spring으로 설정
DOCKER_APP_NAME=spring
# 실행중인 blue가 있는지 확인
# 프로젝트의 실행 중인 컨테이너를 확인하고, 해당 컨테이너가 실행 중인지 여부를 EXIST_BLUE 변수에 저장
EXIST_BLUE=$(sudo docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml ps | grep Up)
# 배포 시작한 날짜와 시간을 기록
echo "배포 시작일자 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
# green이 실행중이면 blue up
# EXIST_BLUE 변수가 비어있는지 확인
if [ -z "$EXIST_BLUE" ]; then
# 로그 파일(/home/ec2-user/deploy.log)에 "blue up - blue 배포 : port:8081"이라는 내용을 추가
echo "blue 배포 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
# docker-compose.blue.yml 파일을 사용하여 spring-blue 프로젝트의 컨테이너를 빌드하고 실행
sudo docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml up -d --build
# 30초 동안 대기
sleep 30
# /home/ec2-user/deploy.log: 로그 파일에 "green 중단 시작"이라는 내용을 추가
echo "green 중단 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
# docker-compose.green.yml 파일을 사용하여 spring-green 프로젝트의 컨테이너를 중지
sudo docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml down
# 사용하지 않는 이미지 삭제
sudo docker image prune -af
echo "green 중단 완료 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
# blue가 실행중이면 green up
else
echo "green 배포 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
sudo docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml up -d --build
sleep 30
echo "blue 중단 시작 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
sudo docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml down
sudo docker image prune -af
echo "blue 중단 완료 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
fi
echo "배포 종료 : $(date +%Y)-$(date +%m)-$(date +%d) $(date +%H):$(date +%M):$(date +%S)" >> /home/ec2-user/deploy.log
echo "===================== 배포 완료 =====================" >> /home/ec2-user/deploy.log
echo >> home/ec2-user/deploy.log
주석을 전부 달아두어서, 읽어 보시면 이해가 되실거라 생각합니다.
드디어 길고 길었던 설정이 마무리 되어가고 있습니다!
다음 게시글에는 Nginx 설정을 마무리하고 배포가 잘되는지 실행해보도록 하겠습니다.
읽어주셔서 감사합니다.
다음 시리즈 게시글
Github Actions & Nginx를 이용한 CI/CD 무중단 배포 자동화 구축 - 나머지 설정과 Github Actions 셋팅
다음 시리즈 게시글로 이동 ->
이 분은 토스 갑시다...