Github Actions & Nginx를 이용한 CI/CD 무중단 배포 자동화 구축 - 나머지 설정과 Github Actions 셋팅

DevSeoRex·2023년 5월 13일
6
post-thumbnail

🐒 AWS CodeDeploy & AWS EC2 연동 설정

지난 게시글에서 IAM 설정까지 모두 완료 했습니다. 이제  CodeDeploy 설정부터 다시 시작해보겠습니다!

  • CodeDeploy 서비스에서 EC2 연동을 위한 IAM 역할을 생성합니다.
    • 아래 사진과 같이 셋팅하고 다음을 클릭합니다.

  • 권한 정책에서 권한을 추가해줍니다(AmazonEC2RoleforAWSCodeDeploy 선택)

  • 사용자 이름을 만들어줍니다.

  • EC2에서 이 역할을 사용하도록 변경해줍니다.

  • 방금 작성한 역할로 변경해줍니다.

  • 역할 변경 후에는 인스턴스를 재시작해줍니다.

다시 EC2로 접속합니다.

$ ssh {서비스 명(호스트 명) }
  • CodeDeploy 설치 및 상태 확인을 위해 아래 명령어를 순서대로 입력해줍니다.
// 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
  • CodeDeploy 서비스에서 사용하기 위한 iam 역할을 생성해줍니다.
  • 역할의 이름을 정해줍니다.
  • AWS CodeDeploy 서비스로 이동해서 애플리케이션을 생성합니다.
  • 생성된 애플리케이션을 선택하고 배포 그룹을 생성해줍니다.



    사진에 입력해야 할 셋팅 정보를 모두 담아 두었으니 설명은 생략하도록 하겠습니다.

🐕 프로젝트 생성과 appspec.yml 작성

  • 빈 프로젝트 생성은 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 설치 & 설정

배포에서 사용할 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
  • docker-compose.blue.yml과 docker-compose.green.yml 파일을 작성합니다.
#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 폴더를 만들고, Dockerfile을 작성해줍니다.
### 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를 셋팅해보겠습니다.

🐯 드디어 Github Actions 셋팅!

프로젝트를 업로드한 Repository에 들어가서 Actions 탭으로 진입합니다.

Java With Gradle을 클릭하면, 기본 템플릿이 나오는데 저는 이 템플릿을 수정해서 사용하도록 하겠습니다.

이벤트에 따라서 yml 파일을 여러개를 둘 수 있는데, 저는 배포용 deploy.yml과 bulid.yml 파일 두개를 작성하겠습니다.

  • deploy.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()
  • build.yml
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 셋팅
다음 시리즈 게시글로 이동 ->

4개의 댓글

comment-user-thumbnail
2023년 6월 29일

이 분은 토스 갑시다...

1개의 답글
comment-user-thumbnail
2024년 4월 13일

안녕하세요, 글 잘 읽었습니다! 혹시 application-app.yml과 application.yml는 어떤 차이인지 알 수 있을까요?

1개의 답글