github action 을 활용하여 CI/CD 적용하기

최지환·2023년 5월 23일
1

졸업작품-동네줍깅

목록 보기
4/11
post-thumbnail

개요

졸업 작품을 하면서 서버를 배포를 하려고 했다.

이전에 했던 프로젝트에서는 젠킨스를 활용해 CI/CD 를 하여 ec2에 서버를 띄우도록 구축을 해두었다.

이번에는 CI 를 깃헙 액션으로 했였고, 기존에 젠킨스로 했던 것들을 깃헙액션으로 하고 싶어 깃헙 액션으로 CI/CD 작업을 해주었다.

또한 이전에는 git pull 을 활용해 ec2에서 깃허브를 통해 코드를 내려받고, 서버를 띄우도록 하였는데, 이번에는 AWS 의 CodeDeploy 와 AWS S3 를 활용하여 CD 를 동작시켰다.

하면서 조금 꼬인 부분도 많았고, 과정이 쉽지가 않았기 때문에 간단하게 한 과정을 기록해두었다.

github actions 에서 공식적으로 권장하는 배포프로세스는 2가지가 있다.

  1. AWS S3 빌드파일 압축해서 업로드 → CodeDeploy 를 활용하여 AWS EC2 에 배포
  2. AWS ECR 에 도커 이미지를 업로드 → Task Definition 을 활용해서 AWS ECS 배

나는 1번 방법으로 서버를 띄웠다. 기존에 이미 배포용으로 EC2를 만들었고, RDS 설정 까지 해두었기 때문에 이 방법을 활용해야 미리 만들어둔 EC2를 사용할 수 있을 것이라 판단했다.

2번 방법은 간단하게 말해 AWS ECR 이라는 도커 이미지 저장 레지스트리에 도커 이미지를 정장 한 후, Task Definition 을 기반으로 클러스터에 인스턴스를 생성한 후, AWS ECR에 도커 이미지 자체를 배포하는 개념이다. → 개념적으로 모르는 내용이 많아서 공부하고 적용하는데 시간이 오래걸린다 판단했다.

이 방법은 여러 서버 인스턴스를 관리하는데 편하다는 장점이 있다고 한다.


전체적인 배포 과정

큰 그림을 위와 같다.

  1. main 브랜치에 코드가 push 되는 경우가 트리거가 되어 해당 내역에 대한 ci 동작 후, 프로젝트를 S3에 압축하여 올린다.
  2. 깃헙액션을 통해 CodeDeploy 를 실행한다.
  3. 실행 된 CodeDeploy 에서 EC2 에게 배포 명령을 보낸다. 이 명령은 AppSpec.yml 파일을 따른다.
  4. EC2는 S3에서 프로젝트를 가져온 후, start.sh, stop.sh 스크립트를 실행한다.
  5. 배포 완료

용어 정리

  • CodeDeploy : CodeDeploy는 Amazon EC2 인스턴스, 온프레미스 인스턴스, 서버리스 Lambda 함수 또는 Amazon ECS 서비스로 애플리케이션 배포를 자동화하는 배포 서비스이다. 쉽게 말해 배포를 자동화할 수 있도록 해주는 서비스
  • S3 : 온라인 스토리지 웹 서비스. 저장소라 이해하면 될 것 같다.

EC2 준비

나는 기존에 미리 띄워둔 EC2에 해당 내용을 적용했다.

ec2 사양

  • 프리티어
  • 리눅수 우분투 20.04 버전 확인 필요!! → CodeDeploy Agent 설치 시 버전에 따라 설치 방법이 다르다. 자세한 내용은 뒤에 명시할 예정
  • RDS 도 프리티어를 사용하여 Mysql 로 적용
    →RDS 내용은 다음 참조 : 프로젝트 CD 적용기 1 - RDS 생성 및 ec2 연결

태그 설정

CodeDeploy 를 생성하기 전에 어떤 인스턴스에서 수행할지 구분하는 값으로 태그를 사용해야한다.

태그 만들기

사용하려는 ec2 인스턴스에서 태그를 직접 만들어 주면된다.

나는 mokindang-ec2-tag 라는 이름으로 태그를 만들어 주었다.


IAM 역할 설정

IAM역할 이란?

계정에 생성할 수 있는, 특정 권한을 지닌 IAM 자격 증명입니다.

역할을 만들어주는 이유

  • EC2 인스턴스에서 S3에 올려 놓은 파일에 접근을 할때, 권한이 필요함. 따라서 권한을 위한 자격 증명인 IAM 역할을 만듬

IAM 역할 만들기

IAM → 역할 → 역할 생성

EC2 에서 사용할 것이니 위와 같이 만들어 준다.

S3에 있는 파일을 접근해야하니 AmazonS3FullAccess 권한을 추가한다.

권한 추가 후 생성 완료. 나는 mokindang-ec2-iam-role 라는 이름으로 IAM 역할을 만들어 주었다.

이제 만들어 둔 IAM 역할을 EC2에 적용 해주자.

ec2 → 인스턴스 → 인스턴스 상세 → 작업 → 보안 → IAM 역할 설정

위와 같이 바꿔주면된다.

이제 해당 EC2 인스턴스는 S3에 접근할 권한이 주어진다.


CodeDeploy Agent 설치

CodeDeploy Agnet 를 설치해햐한다. 설치는 CodeDeploy 를 통해 배포할 EC2의 인스턴스에 설치 해야한다.

이 과정은 공식문서를 보고 따라 하면 된다. 사용하는 ec2에 설치된 os 의 종류와 버전에 따라서 설치방법이 다르기 때문에 꼭 공식문서를 보고 설치를 하는 것을 추천한다.

Ubuntu Server용 CodeDeploy 에이전트 설치 - AWS CodeDeploy

추가적으로 Agent 설치 시 버킷과 리전에 대한 명령어를 적는 부분이 있는데.

wget https://[mokindang-github-actions-s3-bucket](https://s3.console.aws.amazon.com/s3/buckets/mokindang-github-actions-s3-bucket?region=ap-northeast-2).s3.ap-northeast-2.amazonaws.com/latest/install

나는 다음과 같이 입력해주었다.

또한 리눅스 우분투 20.04 사용시 공식문서에서 언급한대로 설치를 한번에 제대로 하지 않으면 ruby 버전을 체크하라는 에러가 발생한다. AWS CodeDeploy 는 Ruby 2.xx 버전으로 설치를 해주어야한다.

일반적으로 리눅스 우분투 20.04 에서 ruby를 설치한 경우 ruby 3.xx 버전이 설치된다. 공식문서에서 리눅스 우분투 16 버전 이상 방법대로 설치하지 않고, 다른 우분투 버전에서 사용하는 방법으로 AWS CodeDeploy 를 설치하면 ruby 3.xx 버전이 설치된다.

이 경우 다시 제대로 16 버전 이상 방법으로 설치를 해도 기존에 설치된 ruby 3.xx 버전 때문에 AWS CodeDeploy 가 제대로 설치가 되지 않는다.

이 경우 해결책은 2가지가 있다.

  1. ruby 버전을 2.xx 로 낮춘다.
  2. ruby 3.xx 에 맞추어 CodeDeploy 를 설치해준다.

나는 2번째 방법으로 해결함

# Ubuntu 22.04에서의 CodeDeploy 설치

mkdir codedeploy-agent_1.3.2-1902_ubuntu22 #폴더명은 자율
dpkg-deb -R codedeploy-agent_1.3.2-1902_all.deb codedeploy-agent_1.3.2-1902_ubuntu22
sed 's/Depends:.*/Depends:ruby3.0/' -i ./codedeploy-agent_1.3.2-1902_ubuntu22/DEBIAN/control
dpkg-deb -b codedeploy-agent_1.3.2-1902_ubuntu22/
sudo dpkg -i codedeploy-agent_1.3.2-1902_ubuntu22.deb
systemctl list-units --type=service | grep codedeploy

sudo service codedeploy-agent start

설치가 완료 되면 아래 명령어로 CodeDeploy 를 실행한 후

sudo service codedeploy-agent start

아래 명령어로 제대로 동작하는지 확인해주면 된다.

sudo service codedeploy-agent status


AWS S3 버킷 생성

AWS S3 버킷을 만들어주자.

빌드한 프로젝트를 압축해서 이 버킷에 저장을 해둔다. 이후 ec2 에서 버킷 내부에있는 파일을 가져와 사용할 수 있다.

버킷 만들기

Amazon S3 → 버킷 → 버킷 만들기

버킷 이름을 설정하고 리전을 선택한다.

ACL 은 비활성화 하였다.

그외 설정은 모두 default 상태로 두었다.

생성된 버킷 확인


CodeDeploy 생성

Code Deploy 전용 IAM 만들기

IAM → 역할 → 역할 생성

AWS 서비스 선택 후, CodeDeploy를 적용한다.

역할 이름을 mokindang-codedeploy-iam 으로 만들어 주었다.

CodeDeploy 애플리케이션 생성

이제 CodeDeploy 애플리케이션을 생성 해주자.

개발자 도구 → CodeDeploy → 애플리케이션 → 애플리케이션 생성

하단과 같이 애플리케이션 이름과 컴퓨팅 폴랫폼을 지정해주자.

나는 mokindang-codedeploy-app 라는 이름으로 만들었고, 컴퓨팅 플랫폼은 EC2/온프레이스를 선택 했다.

CodeDeploy 배포 그룹 생성

CodeDeploy 애플리케이션에서 사용하는 배포그룹을 생성해주자.

만들어두었던 CodeDeploy 애플리케이션에서 배포 그룹 생성을 누루면

배포 그룹 생성창이 나온다.

배포 그룹 이름을 입력하고, 해당 서비스의 역할을 설정 해주자. 이전에 만들어 두었던 mokindang-codeploy-iam 을 역할로 지정해주었다.

이후 어떤 인스턴스에서 동작할 지 선택한다.

사전에 EC2 인스턴스에서 태그를 추가해야 선택이 가능하다.

나는 기존에 태그를 추가해주었기 때문에 바로 적용하였다.

배포 설정 을 해준다. 로드밸런서는 사용하지 않았다.

이후 배포 그룹 생성을 눌러 배포 그룹을 생성해주었다.


GitHub Actions 에서 사용할 IAM 사용자 추가

이제 Github Actions 에서 사용할 IAM 사용자를 추가해주어야 한다.

GitHub Acions 에서 AWS 에 접근을 할때는 당연하게 권한이 필요하다.

이런 권한을 IAM 사용자로 추가하여 생성해줄 것 이다.

IAM → 사용자 → 사용자 생성

다음과 같이 입력해주었다.

위와 같이 AmazonS3FullAccess AWSCodeDeployFullAccess 에 대한 권한을 주었다.

이제 생성을 해주자

생성 후 암호가 생성되는데 이를 기억해두자!!
나중에는 확인이 불가하다.

github 액션에서 CD 스크립트 실행 시, 키값을 기반으로 aws 에 접근을 하기 위해서 적용해주기 위해 액세스 키 를 만들어야 한다.

iam → 사용자 →사용자 선택 → 보안 자격 증명 → 액세스 키 만들기

액세스키를 만들었다.

저장해두자!

GitHub - action 에 키를 등록하자.

이전에 만들어둔 access key id 와 secret access key를 등록하자


AppSpec 파일작성

CodeDeploy 에서 배포를 위해 참조할 AppSpec 파일을 작성하자.

AppSpec 는 로컬에서 작성한다.

AppSpec 파일을 이용해 프로젝트에 어떤 파일들을 EC2의 어떤 경로에 복사할지 설정이 가능하고, 배포 프로세스 이후에 수행할 스크립트를 지정하여 서버를 띄울 수 있다.

AppSpec 파일은 기본적으로 루트 디렉터리(프로젝트 최상단)에 위치해야한다.

AWS CodeDeploy는 appspec.yml에 명시된 어떤 파일들을, 어느 위치배포하고, 이후 어떤 스크립트를 실행시킬 것인지를 알수 있다. Appspec.yml 파일은 CodeDeploy 를 사용하기 위해서는 필수적이다.

appspec.yml

#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: scripts/stop.sh
      timeout: 60
      runas: ubuntu
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
      runas: ubuntu

files 부분

files:
  - source:  /
    destination: /home/ubuntu/app
    overwrite: yes
  • source : 인스턴스에 복사할 디렉터리 경로
  • destination : 인스턴스에서 파일이 복사되는 위치
  • overwrite : 복사할 위치에 파일이 있는 경우 대체

permission 부분

permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu
  • object : 권한이 지정되는 파일 또는 디렉터리
  • pattern (optional) : 매칭되는 패턴에만 권한 부여
  • owner (optional) : object 의 소유자
  • group ( optional) : object 의 그룹 이름

hooks 부분

hooks:
  AfterInstall:
    - location: scripts/stop.sh
      timeout: 60
      runas: ubuntu
  ApplicationStart:
    - location: scripts/start.sh
      timeout: 60
      runas: ubuntu

배포 이후 수행할 스크립트를 지정할 수 있음.

AfterInstall 에서 기존에 실행중이던 애플리케이션을 종료시키고 ApplicationStart 에서 새로운 애플리케이션을 실행하도록 할 것임.

  • location : hooks 에서 실행할 스크립트의 위치
  • timeout (optional) : 스크립트 실행에 허용되는 최대 시간이며, 넘으면 배포 실패로 간주됨
  • runa s (optional) : 스크립트를 실행하는 사용자

배포 스크립트 작성

stop.sh

#!/usr/bin/env bash

PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/spring-webapp.jar"

DEPLOY_LOG="$PROJECT_ROOT/deploy.log"

TIME_NOW=$(date +%c)

# 현재 구동 중인 애플리케이션 pid 확인
CURRENT_PID=$(pgrep -f $JAR_FILE)

# 프로세스가 켜져 있으면 종료
if [ -z $CURRENT_PID ]; then
  echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG
else
  echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG
  kill -15 $CURRENT_PID
fi

start.sh

#!/usr/bin/env bash

PROJECT_ROOT="/home/ubuntu/app"
JAR_FILE="$PROJECT_ROOT/spring-webapp.jar"

APP_LOG="$PROJECT_ROOT/application.log"
ERROR_LOG="$PROJECT_ROOT/error.log"
DEPLOY_LOG="$PROJECT_ROOT/deploy.log"

TIME_NOW=$(date +%c)

# build 파일 복사
echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG
cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE

# jar 파일 실행
echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG
nohup java -jar -Dspring.profiles.active=deploy $JAR_FILE > $APP_LOG 2> $ERROR_LOG &
#프로파일 명시!!
CURRENT_PID=$(pgrep -f $JAR_FILE)
echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG

기존에 CI 작업을 해두었으니, github Actions 워크플로우에서 이미 빌드가 마쳤기 때문에 JAR 파일만 복사 후 실행한다.

이때 사용하는 프로파일을 꼭 명시할 것!!


build.gradle 파일 수정

위 스크립트를 보면 Jar 파일 복사 시, /build/libs/*.jar
파일을 $JAR_FILE 파일로 복사를 한다.

Spring Boot 2.5 부터는 빌드 시 일반 jar 하나와 -plin.jar 파일 하나가 만들어진다. 따라서 하나의 jar 파일만 만들어지도록 하기 위해 build.gradle 파일에 다음의 내용을 추가해야한다.

jar {
    enabled = false
}

Github Actions Workflow 작성

이제 Gitbub Actions Workflow 를 작성해보자

main 브랜치에 push 되는경우 ci/cd 를 모두 하도록 작성하였다.

name: Deploy to Amazon EC2

on:
  push:
    branches:
      - main

  pull_request:
    branches:
      - main

# 리전, 버킷 이름, CodeDeploy 앱 이름, CodeDeploy 배포 그룹 이름
env:
  AWS_REGION: ap-northeast-2
  S3_BUCKET_NAME: mokindang-github-actions-s3-bucket
  CODE_DEPLOY_APPLICATION_NAME: mokindang-codedeploy-app
  CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: mokindang-codedeploy-deployment-group

permissions:
  contents: read

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production

    steps:
      # (1) 기본 체크아웃
      - name: Checkout
        uses: actions/checkout@v3

      # (2) JDK 11 세팅
      - name: Set up JDK 11
        uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '11'

      # (3) Gradle build(빌드, 테스트는 ci 에서 하기 때문에 x)
      - name: Build with Gradle
        uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee
        with:
          arguments: clean build -x test

      # (4) AWS 인증 (IAM 사용자 Access Key, Secret Key 활용)
      - name: Configure AWS credentials
        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: ${{ env.AWS_REGION }}

      # (5) 빌드 결과물을 S3 버킷에 업로드
      - name: Upload to AWS S3
        run: |
          aws deploy push \
            --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \
            --ignore-hidden-files \
            --s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \
            --source .

      # (6) S3 버킷에 있는 파일을 대상으로 CodeDeploy 실행
      - name: Deploy to AWS EC2 from S3
        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=$GITHUB_SHA.zip,bundleType=zip

main 브랜치에 있는 코드를 배포할 것이니 트리거를 main에 push 되는 경우로 설정해주었다.

코드를 push 하면 다음과 같이 배포가 실행 된다.

배포 완료!


중간 중간 작은 이슈들

프로파일 설정을 해두는 것을 깜빡하고,,, 배포시 문제가 발생했음. 다음부턴 배포시 프로파일 적용 상태를 제대로 확인하기

참고한 내용

https://bcp0109.tistory.com/363

https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/codedeploy-agent-operations-install-ubuntu.html

https://velog.io/@tjddnths0223/Github-Actions로-AWS-EC2에-Spring-Boot-배포하기-정리

https://coding-nyan.tistory.com/62

https://sic-dev.tistory.com/63

0개의 댓글