실전 프로젝트 4주차. 오늘은 테스트코드를 작성했다. 오늘의 TIL에는 분산 인스턴스 환경에서 도커로 CI/CD하는 방법에대해 적어보려한다.
기존에는 EC2 인스턴스 한개에다 CI/CD를 구축해둬서 크게 신경쓸 게 없었는데 이번에 Scale Out을 하면서 EC2 인스턴스가 늘어났고, 오토스케일링을 적용하면서 인스턴스가 유동적으로 변하는 환경이 되었다.
그래서 일단 jar 빌드파일로 바로 구동하던 방식을 Docker를 활용해 이미지로 컨테이너 환경을 만들어 구동하는 방식으로 바꿨다.
aws 설정은 참고 사이트를 보자
Github Actions + Autoscaling Group + S3 + CodeDeploy + Docker + EC2
전체적인 순서
- main브랜치에 push
- Github Actions에서 Gradle로 Jar 파일로 빌드
- Docker 로그인 후 Jar 파일을 Docker 이미지로 빌드하고 DockerHub로 Push
- 스크립트 파일을 압축해 S3로 이동
- 4번의 스크립트 파일을 오토스케일링 그룹에서 CodeDeploy로 실행
- 오토스케일링 그룹에 속한 각각의 EC2인스턴스가 현재 컨테이너를 중지하고 이미지를 도커허브에서 pull 받아 새로 컨테이너 실행
on:
push:
branches: [ "main" ]
env:
AWS_REGION: 지역이름
S3_BUCKET_NAME: 버킷이름
CODE_DEPLOY_APPLICATION_NAME: 코드디플로이 이름
CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: 코드디플로이 그룹이름
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
IMAGE_TAG: ${{ github.sha }}
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
# 체크아웃 및 jdk17 설정
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
# main 설정 파일 생성 및 write
- name: Set .yml for main
run: |
cd ./src/main/resources
touch ./application.yaml
echo "${{ secrets.APPLICATION }}" >> ./application.yaml
shell: bash
# Gradle build
- name: Build with Gradle
run: ./gradlew bootJar
# Spring 어플리케이션 Docker Image 빌드
- name: Build Docker Image For Spring
run: |
docker login -u ${{ env.DOCKER_USERNAME }} -p ${{ env.DOCKER_PASSWORD }}
docker build -t ${{ env.DOCKER_USERNAME }}/이미지이름 .
docker push ${{ env.DOCKER_USERNAME }}/이미지이름
# 디렉토리 생성
- name: Make Directory
run: mkdir -p deploy
# appspec.yml 파일 복사
- name: Copy appspec.yml
run: cp appspec.yml ./deploy
# script files 복사
- name: Copy script
run: cp ./scripts/*.sh ./deploy
- name: Make zip file
run: zip -r ./$IMAGE_TAG.zip ./deploy
shell: bash
# AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Upload to S3
run: aws s3 cp --region ap-northeast-2 ./$IMAGE_TAG.zip s3://버킷이름/
# Deploy
- name: Deploy
run: |
aws deploy create-deployment \
--application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
--deployment-config-name CodeDeployDefault.AllAtOnce \
--deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \
--s3-location bucket=$S3_BUCKET_NAME,key=$IMAGE_TAG.zip,bundleType=zip
jar {
enabled = false
}
bootJar {
archiveFileName = 'app.jar'
}
# 도커 허브에서 이미지를 가져와서 이미지를 작업한다
# FROM (이미지 이름:버전)
FROM openjdk:17
ENV TZ=Asia/Seoul
# 컨테이너 실행 전 작동할 명령
# RUN (명령)
# 타임존 설정 (설정을 하지 않으면 시간 저장시 다른 시간대로 저장됨)
RUN ln -snf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
RUN echo Asia/Seoul > /etc/timezone
# 작업 파일을 변수화 하기
# ARG (변수명)=(파일명)
ARG JAR_FILE=build/libs/app.jar
# 작업 파일을 컨테이너로 복사
# COPY (파일명 또는 ${변수명}) (복사할 파일명)
COPY ${JAR_FILE} ./app.jar
EXPOSE 8080
ENV SPRING_PROFILES_ACTIVE=prod
# 컨테이너 시작 시 내릴 명령 (CMD와 ENTRYPOINT 차이 확인)
# ENTRYPOINT [(명령),(매개변수),(매개변수),(...)]
ENTRYPOINT ["java", "-jar", "./app.jar"]
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/app
overwrite: yes
permissions:
- object: /
pattern: "**"
owner: ubuntu
group: ubuntu
hooks:
AfterInstall:
- location: stop.sh
timeout: 60
runas: ubuntu
ApplicationStart:
- location: start.sh
timeout: 60
runas: ubuntu
#!/usr/bin/env bash
# 실행 중인 모든 Docker 컨테이너 ID를 가져옵니다.
container_ids=$(sudo docker ps -q)
# 컨테이너 ID가 있는 경우에만 실행합니다.
if [ ! -z "$container_ids" ]; then
echo "실행 중인 Docker 컨테이너를 종료하고 삭제합니다."
# 각 컨테이너를 중지하고 삭제합니다.
sudo docker stop $container_ids
sudo docker rm $container_ids
else
echo "실행 중인 Docker 컨테이너가 없습니다."
fi
# 모든 Docker 이미지 ID를 가져옵니다.
image_ids=$(sudo docker images -q)
# 이미지 ID가 있는 경우에만 실행합니다.
if [ ! -z "$image_ids" ]; then
echo "모든 Docker 이미지를 삭제합니다."
# 각 이미지를 삭제합니다.
sudo docker rmi -f $image_ids
else
echo "삭제할 Docker 이미지가 없습니다."
fi
#!/usr/bin/env bash
# 새로운 이미지 이름과 태그를 설정합니다. (tag는 가장 최신)
user_name="도커유저이름"
image_name="도커이미지이름"
image_tag="latest"
# 이미지를 다운로드합니다.
echo "새로운 Docker 이미지를 다운로드합니다."
sudo docker pull $user_name/$image_name:$image_tag
# 이미지를 실행합니다.
echo "새로운 Docker 컨테이너를 실행합니다."
port_mapping="8080:8080"
sudo docker run -d -p $port_mapping $user_name/$image_name:$image_tag