230427 TIL #70 오토스케일링 + CI/CD

김춘복·2023년 4월 27일
0

TIL : Today I Learned

목록 보기
70/543
post-custom-banner

230427 Today I Learned

실전 프로젝트 4주차. 오늘은 테스트코드를 작성했다. 오늘의 TIL에는 분산 인스턴스 환경에서 도커로 CI/CD하는 방법에대해 적어보려한다.


오토스케일링 CI/CD

  • 기존에는 EC2 인스턴스 한개에다 CI/CD를 구축해둬서 크게 신경쓸 게 없었는데 이번에 Scale Out을 하면서 EC2 인스턴스가 늘어났고, 오토스케일링을 적용하면서 인스턴스가 유동적으로 변하는 환경이 되었다.

  • 그래서 일단 jar 빌드파일로 바로 구동하던 방식을 Docker를 활용해 이미지로 컨테이너 환경을 만들어 구동하는 방식으로 바꿨다.

  • aws 설정은 참고 사이트를 보자

  • Github Actions + Autoscaling Group + S3 + CodeDeploy + Docker + EC2

  • 전체적인 순서

    1. main브랜치에 push
    2. Github Actions에서 Gradle로 Jar 파일로 빌드
    3. Docker 로그인 후 Jar 파일을 Docker 이미지로 빌드하고 DockerHub로 Push
    4. 스크립트 파일을 압축해 S3로 이동
    5. 4번의 스크립트 파일을 오토스케일링 그룹에서 CodeDeploy로 실행
    6. 오토스케일링 그룹에 속한 각각의 EC2인스턴스가 현재 컨테이너를 중지하고 이미지를 도커허브에서 pull 받아 새로 컨테이너 실행

Github Actions에 쓸 yaml 파일

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

build.gradle (jar 파일 app.jar 하나로 통일)

jar {
    enabled = false
}

bootJar {
    archiveFileName = 'app.jar'
}

Dockerfile

# 도커 허브에서 이미지를 가져와서 이미지를 작업한다
# 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"]

appspec.yml

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

scripts / stop.sh

#!/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

scripts / start.sh

#!/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

profile
Backend Dev / Data Engineer
post-custom-banner

0개의 댓글