Skip to content

CD(지속적 통합)

김규성(LUKAS) edited this page Sep 11, 2023 · 1 revision

CD 설정 및 AWS 인프라 구축하기

[STEP 1] AWS 에 S3 버킷에 프로젝트 빌드 파일 올리기

1. IAM 생성

  • S3 접근 권한 부여
  • CodeDeploy 접근 권한 부여

2. S3 버킷 생성

  • public ip access 비활성화
  • secret-key 생성

3. GitHub Action Secret 등록

image

다음과 같은 과정으로 이루어진다.

  1. 다른 레포에 있는 서브모듈 가져오기
  2. JDK 11 설치
  3. Gradle 캐싱 (속도 증가를 위해)
  4. 권한 부여 및 프로젝트 빌드
  5. 결과물 압축
  6. AWS 인증
  7. S3 업로드

4. Github Action Flow 정의

name: deploy

on:
  push:
    branches:
      - main

env:
  S3_BUCKET_NAME: salmal-s3
  PROJECT_NAME: salmal

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: 체크아웃
        uses: actions/checkout@v3

      - name: 서브모듈 레파지토리 가져오기
        uses: actions/checkout@v3
        with:
          token: ${{ secrets.SUBMODULE_TOKEN }}
          submodules: recursive

      - name: JDK 11 설치
        uses: actions/setup-java@v3
        with:
          java-version: '11'
          distribution: 'temurin'

      - 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: Gradle 명령 실행을 위한 권한을 부여
        run: chmod +x gradlew

      - name: Gradle 빌드
        uses: gradle/gradle-build-action@bd5760595778326ba7f1441bcf7e88b49de61a25 # v2.6.0
        with:
          arguments: build

      - name: Make zip file (zip 으로 압축)
        run: zip -r ./$GITHUB_SHA.zip .
        shell: bash

      - name: Configure AWS credentials (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 }}

      - name: Upload to S3 (S3에 업로드)
        run: aws s3 cp --region ${{ secrets.AWS_REGION }} ./$GITHUB_SHA.zip s3://$S3_BUCKET_NAME/$PROJECT_NAME/$GITHUB_SHA.zip

#      - name: CodeDeploy (CodeDeploy를 통해 EC2에 전달)
#        run: aws deploy create-deployment --application-name trilo-system-deploy --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name develop --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$PROJECT_NAME/$GITHUB_SHA.zip

결과물을 보면 다음과 같이 S3 버킷 저장소에 프로젝트 결과물이 잘 올라간 것을 확인해볼 수 있다.

image

[STEP 2] VPC, 서브넷, 라우팅테이블, 인터넷 게이트웨이 설정

개념 설명

  • VPC(Virtual Private Cloud) 란, 같은 시스템 내에서 독립된 네트워크를 구성하기 위한 가상의 네트워크 공간이다.
  • 서브넷이란, IP 주소에서 네트워크 영역을 부분적으로 나눈 부분 네트워크를 뜻한다. VPC 를 좀 더 잘게 쪼개어 네트워크를 좀 더 세부적으로 분리할 수 있다.
  • 인터넷 게이트웨이란, 쉽게 말해 인터넷 간의 통신을 가능하게 해주는 AWS 컴포넌트이다. 이게 없으면 인터넷과의 연결이 불가능하다.
  • 라우팅 테이블이란, 네트워크 상의 특정 목적지까지의 거리와 가는 방법등을 명시하고 있는 테이블이다.

1. VPC 생성하기

[STEP 2] Code Deploy 를 통해 S3에 저장된 프로젝트 파일을 EC2에 전달하기

1. EC2 생성

  • Amazon Linux 2
  • pem 키 생성 후 SSH 연결

2. EC2 SSH 연결 후 JAVA 및 CodeDeploy 설치

JAVA 11 설치

sudo amazon-linux-extras install java-openjdk11

CodeDeploy 설치

# 패키지 매니저 업데이트, ruby 설치
sudo yum update
sudo yum install ruby
sudo yum install wget

# 서울 리전에 있는 CodeDeploy 리소스 키트 파일 다운로드
cd /home/ec2-user
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install

# 설치 파일에 실행 권한 부여
chmod +x ./install

# 설치 진행 및 Agent 상태 확인
sudo ./install auto
sudo service codedeploy-agent status

image

만약 실행중이 아니라면 다음의 커멘드로 codedeploy 실행시켜야한다.

sudo service codedeploy-agent start

3. EC2에 IAM 역할 부여하기

EC2 에서 S3, CodeDeploy 권한을 부여받아 사용할 수 있도록 IAM 권한 사용자를 생성한다.

아래의 권한을 가지는 CodeDeploy-S3-Role 이라는 이름의 역할을 생성한다.

  • 역할명 : CodeDeploy-S3-Role 생성
  • 연결 권한: AmazonS3FullAccess, AWSCodeDeployFullAccess
  • Name : S3-CodeDeploy-FullAccess

image

이후 해당 역할을 다음과 같은 과정을 통해 EC2에 부여한다

  • EC2 → 인스턴스 선택 → 우클릭 → 보안 → IAM 역할 수정
  • CodeDeploy-S3-Role 연결

image

4. CodeDeploy 어플리케이션 생성 및 IAM 역할 생성 후 부여

우선 CodeDeploy 를 생성한다.

  • CodeDeploy - 애플리케이션 생성
  • 애플리케이션 이름 : salmal-deploy
  • 컴퓨팅 플랫폼 : EC2/온프레미스

image

이후 IAM 역할을 생성하고

  • 역할 이름 : CodeDeploy-Role
  • 사용 사례 : CodeDeploy
  • 연결 정책 : AWSCodeDeployRole
  • Name: CodeDeploy

CodeDeploy 에 역할을 부여한다.

  • salmal-system-deploy → 배포그룹 생성
  • 배포 그룹 이름 : develop
  • 서비스 역할 : 위에서 생성한 역할 연결
  • 배포 유형 : 현재 위치
  • 환경 구성 : Amazon EC2 인스턴스
  • AWS CodeDeploy 에이전트 설치 → 안 함
  • 배포 설정 : CodeDeployDefault.AllAtOnce
  • 로드밸런싱 활성화 끄기
  • 배포그룹 생성

5. 외부에서 접근할 수 있도록 VPC, Subnet, GateWay 생성 및 설정

우선 VPC 를 생성한다.

서비스 -> 네트워킹 및 콘텐츠 전송 -> VPC -> Virtual Private Cloud -> VPC -> VPC 생성

image

생성 후 확인해보면 위와 같이 가상 라우터가 생성되어 기본 라우팅 테이블을 보유하게 된다.

Public 서브넷을 생성한다.

서비스 -> 네트워킹 및 콘텐츠 전송 -> VPC -> Virtual Private Cloud -> 서브넷 -> 서브넷 생성

게이트웨이를 생성하고 생성한 VPC 와 연결한다.

서비스 -> 네트워킹 및 콘텐츠 전송 -> VPC -> Virtual Private Cloud -> 인터넷 게이트웨이 -> 인터넷 게이트웨이 생성


작업 -> VPC 연결 -> TriLo-VPC 에 연결

Public 라우팅 테이블을 생성하고, Public 서브넷을 연결한다.

서비스 -> 네트워킹 및 콘텐츠 전송 -> VPC -> Virtual Private Cloud -> 라우팅 테이블 -> 라우팅 테이블 생성


작업 -> VPC 연결 -> TriLo-VPC 에 연결

6. Codeploy 를 통해 EC2 에 전달하기

이전에 작성했던 Github Actions 의 배포 Flow 에 CodeDeploy 관련 Flow 를 추가한다.

      - name: CodeDeploy (CodeDeploy를 통해 EC2에 전달)
        run: aws deploy create-deployment --application-name salmal-system-deploy --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name develop --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=$PROJECT_NAME/$GITHUB_SHA.zip

이제 Github Actions 에서 배포를 수행하면 다음과 같이 S3 에 올린 파일이 CodeDeploy 를 통해 EC2로 전달되는 것을 확인할 수 있다. image image

[STEP 3] Nginx 설정 및 무중단 배포 설정하기 (Blue/Green)

본래는 로드밸런서의 역할을 하기에 별도의 EC2로 분리하는 것이 맞다. 하지만 지금 당장은 프리티어 계정의 여유분이 없고 그렇다고 AWS 로드밸런서를 도입하기엔 (당장 서버도 한개여서 분산환경 구축할 이유도 없고..) 시간이 너무 짧으며 빠르게 배포환경을 구축하여 IOS 분들과의 원활한 협업을 진행하기 위해서 ONLY 한 개의 서버에 몽땅 때려박는다. 시간이 남으면 서버 다중화와 DB 다중화, 그리고 제대로된 로드밸런서 및 스케줄링을 설정하여 더 많은 트래픽을 수용할 수 있는 서비스 환경을 구축하기로 한다.

앞서 EC2에 프로젝트 파일을 전달했으니, 이제 Nginx 를 설치 및 설정하여 외부 API 요청에 대한 프록시를 설정한다.

먼저 설치 스크립트는 다음과 같다.

# 설치
sudo amazon-linux-extras install nginx1

# 버전 확인
sudo nginx -v

이후 Nginx 설정으로 들어가서 지정 URL 로 요청을 보낼 수 있도록 하는 프록시를 설정한다.

EC2에 먼저 외부에서 참조할 수 있는 include 파일을 생성하고, 서비스 url 주소를 정의한다.

sudo vi /home/ec2-user/service_url.inc
# service_url.inc
set $service_url http://127.0.0.1:8081;

그 다음, Nginx 설정 파일을 수정하여 외부 요청에 대한 매핑을 수행한다.

include /home/ec2-user/service_url.inc;

location / {
    proxy_set_header    X-Forwarded-For $remote_addr;
    proxy_set_header    Host $http_Host;
    proxy_pass          $service_url;
}

변경한 설정으로 Nginx 가 동작하려면, 재시작을 해야한다.

# 시작
sudo service nginx start
# 재시작 (서버의 shutdown 없이 설정만 로드한 뒤 재시작)
sudo service nginx reload 
# 재시작 (서버의 shutdown 후 재시작)
sudo service nginx restart
# 상태확인
sudo service nginx status

[STEP 4] CodeDeploy 무중단 배포 스크립트 추가하기

먼저 세 가지 스크립트 파일을 추가한다.

  1. 새로운 WAS 를 띄우는 스크립트
#!/bin/bash

CURRENT_PORT=$(cat /home/ec2-user/service_url.inc | grep -Po '[0-9]+' | tail -1)
TARGET_PORT=0

echo "> Current port of running WAS is ${CURRENT_PORT}."

if [ ${CURRENT_PORT} -eq 8081 ]; then
  TARGET_PORT=8082
elif [ ${CURRENT_PORT} -eq 8082 ]; then
  TARGET_PORT=8081
else
  echo "> No WAS is connected to nginx"
fi

TARGET_PID=$(lsof -Fp -i TCP:${TARGET_PORT} | grep -Po 'p[0-9]+' | grep -Po '[0-9]+')

if [ ! -z ${TARGET_PID} ]; then
  echo "> Kill WAS running at ${TARGET_PORT}."
  sudo kill ${TARGET_PID}
fi

nohup java -jar -Dserver.port=${TARGET_PORT} /home/ec2-user/trilo/build/libs/* > /home/ec2-user/nohup.out 2>&1 &
echo "> Now new WAS runs at ${TARGET_PORT}."
exit 0
  1. Health Check 스크립트
#!/bin/bash

# Crawl current connected port of WAS
CURRENT_PORT=$(cat /home/ec2-user/service_url.inc | grep -Po '[0-9]+' | tail -1)
TARGET_PORT=0

# Toggle port Number
if [ ${CURRENT_PORT} -eq 8081 ]; then
    TARGET_PORT=8082
elif [ ${CURRENT_PORT} -eq 8082 ]; then
    TARGET_PORT=8081
else
    echo "> No WAS is connected to nginx"
    exit 1
fi


echo "> Start health check of WAS at 'http://127.0.0.1:${TARGET_PORT}' ..."

for RETRY_COUNT in 1 2 3 4 5 6 7 8 9 10
do
    echo "> #${RETRY_COUNT} trying..."
    RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}"  http://127.0.0.1:${TARGET_PORT}/deploy/health)

    if [ ${RESPONSE_CODE} -eq 200 ]; then
        echo "> New WAS successfully running"
        exit 0
    elif [ ${RETRY_COUNT} -eq 10 ]; then
        echo "> Health check failed."
        exit 1
    fi
    sleep 10
done
  1. Switching 스크립트
#!/bin/bash

# Crawl current connected port of WAS
CURRENT_PORT=$(cat /home/ec2-user/service_url.inc  | grep -Po '[0-9]+' | tail -1)
TARGET_PORT=0

echo "> Nginx currently proxies to ${CURRENT_PORT}."

# Toggle port number
if [ ${CURRENT_PORT} -eq 8081 ]; then
    TARGET_PORT=8082
elif [ ${CURRENT_PORT} -eq 8082 ]; then
    TARGET_PORT=8081
else
    echo "> No WAS is connected to nginx"
    exit 1
fi

# Change proxying port into target port
echo "set \$service_url http://127.0.0.1:${TARGET_PORT};" | tee /home/ec2-user/service_url.inc

echo "> Now Nginx proxies to ${TARGET_PORT}."

# Reload nginx
sudo service nginx reload

echo "> Nginx reloaded."

appspec.yml 에 앞서 추가했던 세가지 스크립트를 실행하는 명령문을 추가한다.

# 새로 추가되는 부분
hooks:
  ApplicationStart:
    - location: scripts/run_new_was.sh
      timeout: 180
      runas: ec2-user
    - location: scripts/health_check.sh
      timeout: 180
      runas: ec2-user
    - location: scripts/switch.sh
      timeout: 180
      runas: ec2-user

현재 아무 WAS 가 떠있지 않은 상태이므로 8081 포트에 새로운 WAS 를 수동으로 띄워준다.

nohup java -jar -Dserver.port=8081 /home/ec2-user/salmal/build/libs/* &

참고자료