DevKim

[Webtooniverse] CI/CD 구조 변경 - GitHub Action 본문

Spring Project/Webtooniverse

[Webtooniverse] CI/CD 구조 변경 - GitHub Action

on_doing 2021. 8. 11. 23:59
728x90

더 나은 배포 프로세스를 적용하기 위해 docker + jenkins로 배포해보고,

jenkins + webhook으로 배포 자동화까지 간단한 실습에 적용해보면서

내부적으로 어떻게 동작하는지에 대해 생각할 수 있는 시간이었다.

 

과금 문제로 인해, 지금 당장은 Jenkins 대신 Github Action을 프로젝트에 적용해보려고한다.

 

현재 프로젝트 구조는 이렇다.

아직 캐싱 서버와, nginx는 도입하지 않았다. 필요하다고 느껴질 때 그 이유와 함께 도입할 예정이다.


[ GitThub Action ]

Github Action은 Github에서 바로 소프트웨어 Workflow를 자동화할 수 있는 도구이다.

간단하게 말하면 Github에서 직접 제공하는 CI/CD 도구이다.

장점

사용하면서 느꼈던 가장 장점은,

  • seperate된 CI/CD 서비스를 사용하지 않아도된다는 점
  • webhook을 set up하지 않아도된다는 점
  • 다른 CI툴보다 단순하다는 점

용어

  • Workflows
    • : Github 저장소에서 발생하는 build, test, package, release, deploy 등 다양한 이벤트를 기반으로 직접 원하는 Workflow를 만들 수 있다. 즉, 작업 뭉치이다.
    • 기본적인 방법은 저장소에 .github/workflows 폴더를 만들어서 .yml 형식 파일 만든 뒤 Workflow를 정의하는 것이지만, Github Action 툴에서 다양한 환경에 맞는 기본적인 workflows를 제공하고있다.
  • Events: 작업 뭉치 동작하는 이벤트, push, release, pull request, 스케줄, 등
  • Jobs: 동시 수행 가능한 작업 단위
  • Runners: Job 실행되는 곳. GitHub이 제공하는 가상 머신(Linux, macOS, Windows)
  • Steps: 순서대로 실행되는 쉘 커맨드 또는 action
  • Actions: 재사용할 수 있는 job의 step. 

[ 환경 ]

- ubuntu

- worker instance : aws ec2 t2.micro

- Java 8, Gradle 7.1.1

 

[ Workflow ]

직접 파일을 생성해도되지만, 제공해주는 Java with Gradle로 set up 해주었다.

생성된 gradle.yml 파일을 보자.

name: workflow이름

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up JDK 8
      uses: actions/setup-java@v2
      with:
        java-version: '8'
        distribution: 'adopt'
    - name: Grant execute permission for gradlew
      run: chmod +x gradlew
    - name: Build with Gradle
      run: ./gradlew build
  • name
    • 원하는 workflow의 이름을 적어주면된다.
  • on
    • Event that trigger workflows이다. 즉 특정 이벤트를 할 때 작동한다.
    • 현재 test 환경에서는 main 브랜치에 push하거나 pr을 보냈을 때 작동하도록 설정했다.
  • jobs
    • Event가 발생하는 경우 jobs가 작동한다.
    • runs-on : machine의 type을 정한다. (Ubuntu,macOs,Windows..)
    • steps : 일련의 단계별 작업 (JDK 권한 설정 후 빌드)

https://github.com/actions/setup-java


[ github Action & S3, CodeDeploy 접근 키 생성 ]

S3는 AWS에서 제공하는 일종의 파일 서버이다. 현재 구조에서 Github Action애서 S3로 Jar파일을 전달해야기 때문에 외부 서비스가 AWS 서비스에 접근할 수 있는 권한 생성 해줘야한다. 이때 이러한 인증과 관련된 기능을 제공하는 서비스로 AWS에 IAM이 있다.

 

IAM을 통해 Github Action이 S3와 배포를 담당하는 CodeDeploy에 접근할 수 있도록하자.

 

[ 사용자 추가 ]

우선 사용자 추가를 해준다. 엑세스 유형은 프로그래밍 방식 엑세스이며, 권한 설정은 기존 정책 직접 연결을 눌러주고 S3full - & CodeDeployfull- 을 선택하여 각각의 권한을 추가해준다.

실제 서비스라면 '이 두개의 권한을 분리해서 관리'하기도 한다고한다.

 

권한 최종 확인을 마치면, 엑세스 키 ID와 비밀 엑세스 키가 생성되는데 이 두개가 Github Action에서 사용될 키이다.

 

[ github Action & S3 연동 ]

S3 버킷을 하나 만들어주자!

이때 jar 파일이 public일 경우 누구나 내려받게되면 코드나 설정값, 주요 키값들이 다 탈취될 수 있으므로 권한 설정에서 모든 퍼블릭 엑세스 차단을 선택해주어야한다.

 

[ Gradle.yml 파일 설정 ]

- name: Make Directory
  run: mkdir -p deploy

- name: Copy Jar
  run: cp ./build/libs/*.jar ./deploy

- name: Make zip file
  run: zip -r ./webtooniverse.zip ./deploy

#Deploy
- name: Deploy Webtooniverse
  uses: aws-actions/configure-aws-credentials@v1
  with:
	aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY }}
	aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
	aws-region: ap-northeast-2

- name: Upload to S3
  run: aws s3 cp --region ap-northeast-2 --acl private ./webtooniverse.zip s3://webtooniverse/
  • build 후 생성된 jar파일을 넣을 폴더 생성
  • Jar파일 deploy 폴더로 복사
  • CodeDeploy는 Jar 파일을 인식하지 못하기 때문에 압축한다.
  • 외부 서비스와 연동될 행동들을 선언한다. (S3로 해당 zip 파일을 업로드)

Push하자마자 Github Action이 동작한다. 만약 빌드에 성공했다면 S3버킷에 업로드가 된 것을 확인할 수 있다.

 

[ EC2에 IAM 역할 추가 ]

EC2가 CodeDeploy를 연동 받을 수 있게 IAM 역할을 하나 생성해주자

 

* 앞에서 만든 사용자와의 차이점은 무엇일까? *

  • 역할
    • AWS 서비스에만 할당할 수 있는 권한
    • Ec2.CodeDeploy 등
  • 사용자
    • AWS 서비스 외에 사용할 수 있는 권한
    • 로컬 PC 등

 

CodeDeploy의 연동을 할 것이기 때문에, 정책에서 'AmazonEC2RoleforAWSCodeDeploy'를 선택해준다.

 

이렇게 만든 역할을 EC2 서비스에 등록하기 위해선, worker 인스턴스에서 해당 IAM 역할을 선택해준 뒤, 재부팅해준다.

 

[ EC2 서버에 CodeDeploy 에이전트 설치 ]

  • Java 8 설치
sudo apt install openjdk-8-jdk
  • 타임존 변경
sudo rm /etc/localtime
sudo ln -s /usr/share/zoneinfo/Asia/Seoul /etc/localtime
  • 기본 설치
sudo apt update
sudo apt install ruby-full
sudo apt install wget
  • home/ubuntu 경로에 CodeDeploy 리소스 키트
cd /home/ubuntu
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
  • install 파일에 권한 추가 & codeDeploy 에이전트 설치
chmod +x ./install
sudo ./install auto

#실행확인
sudo service codedeploy-agent status

[ CodeDeploy -> EC2 접근 권한 생성 ]

AWS 서비스 이므로 IAM 역할을 생성해준다.

 

[ CodeDeploy 생성 ]

- CodeDeploy는 AWS 의 배포 서비스로, 대체재가 없다. 현재 Code commit의 역할은 깃허브가, Code Build는 Github Action이 해주고있다.

 

애플리케이션 생성을 눌러서 컴퓨팅 프랫폼은 EC2/온프래미스를 선택해주자. 

생성 후엔 배포 그룹을 생성해야하는데 이때 서비스 역할은 위에서 생성한 CodeDeploy용 IAM 역할을 선택하면된다.

현재는 배포 할 서비스가 1대의 Ec2이므로 배포 유형은 현재 위치로 선택해주었다.

배포 설정은 다음과 같이 선택해주자

설정하는 과정에서 태그를 선택 사항으로 입력할 수 있는데, 이때 태그 Name을 지정해줬다면 사용하고자하는 EC2서버에도 태그 Name을 지정해줘야 인식하는 이슈가 있었다. (EC2에 Name 태그를 지정해주지 않았을 땐, CodeDeploy가 EC2를 인식하지 못해 빌드에 실패했다. )

 

[ appspec.yml 생성 ]

Github Action의 설정은 gradle.yml에 넣어주었고, CodeDeploy 설정은 appspec.yml에 넣어주자

 

먼저, S3에서 넘겨줄 zip 파일을 저장할 디렉토리를 하나 생성해주자

/home/ubuntu/app

appspec.yml 파일은 build.gradle 파일과 동일선상의 위치에 하나 만들어주면된다.

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ubuntu/app/
    overwrite: yes
  • source : /
    • CodeDeploy에 전달해 준 파일 중 전체 파일을 이동
  • destination: /home/ubuntu/app/
    • source에서 보낸 파일을 저장 할 위치
  • overwrite: yes
    • 기존 파일이 있다면 덮어쓰기

[ gradle.yml 추가 ]

# appspec.yml 파일 복사
- name: Copy appspec.yml
  run: cp appspec.yml ./deploy

...

# s3에 있는 zip 파일을 Deploy
- name: CodeDeploy
  run: aws deploy create-deployment --application-name webtooniverse --deployment-group-name webtooniverse-group --s3-location bucket=webtooniverse,key=webtooniverse.zip,bundleType=zip

해당 브랜치로 push하면 AWS에서 배포 성공 여부를 알 수 있다. 성공했으면 Jar 파일을 배포해보자

 

[ deploy.sh 생성 ]

src파일과 동일선상에 scripts 파일을 하나 만들어서 그 안에 deploy.sh 파일을 만들어주자

#!/usr/bin/env bash

REPOSITORY=/home/ubuntu/app

echo "> 현재 구동 중인 애플리케이션 pid 확인"

CURRENT_PID=$(pgrep -fl webtooniverse | grep jar | awk '{print $1}')

echo "현재 구동 중인 애플리케이션 pid: $CURRENT_PID"

if [ -z "$CURRENT_PID" ]; then
  echo "현재 구동 중인 애플리케이션이 없으므로 종료하지 않습니다."
else
  echo "> kill -15 $CURRENT_PID"
  kill -15 $CURRENT_PID
  sleep 5
fi

echo "> 새 애플리케이션 배포"

JAR_NAME=$(ls -tr $REPOSITORY/webtooniverse-0.0.1-SNAPSHOT.jar | tail -n 1)

echo "> JAR NAME: $JAR_NAME"

echo "> $JAR_NAME 에 실행권한 추가"

chmod +x $JAR_NAME

echo "> $JAR_NAME 실행"

nohup java -jar $JAR_NAME > $REPOSITORY/nohup.out 2>&1 &

 

[ gradle.yml 추가 ]

script 파일도 S3에 보내준다.

# script files 복사
- name: Copy script
  run: cp ./scripts/*.sh ./deploy

[ appspec.yml 추가 ]

permissions:
  - object: /
    pattern: "**"
    owner: ubuntu
    group: ubuntu

hooks:
  ApplicationStart:
    - location: deploy.sh
      timeout: 60
      runas: ubuntu
  • permissions : CodeDeploy에서 EC2 서버로 넘겨준 파일들이 모두 ubuntu 권한을 갖게한다.
  • hooks 
    • CodeDeploy 배포 단계에서 실행할 명령어를 지정
    • 스크립트 실행시간이 60초가 넘어가면 실패
    • deploy.sg를 ubuntu 권한으로 실행

 

 

Main 브랜치에 push시, 잘 배포되어진다.


[ properties 파일 은닉 ]

RDS 정보나, OAuth 같은 정보는 보호해야 할 정보이니 Git ignore에 포함되어있는 정보들이다.

따라서 RDS 접속 정보, OAuth 정보들을 EC2 서버에 직접 설정 파일 생성해야한다.

 

먼저, 기존의 properties와 같은 경로에 application-real.properties를 추가해준다.

 

real로 파일을 만들면 profile=real인 환경이 구성된다고 보면된다고한다. 보안상 이슈가 될 만한 설정들은 모두 제거하며 RDS 환경 profile 설정이 추가된다.

spring.profiles.include=real-db
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.session.store-type=jdbc

vim을 이용하여 특정 경로에 properties 파일을 만들어서 원래 properties에 있던 RDS 설정 코드를 넣어주자

vim /home/ubuntu/app/application-real-db.properties

deploy.sh가 real profile을 쓸 수 있도록 다음과 같이 sh파일을 변경한다.

nohup java -jar \
 -Dspring.config.location=classpath:/application.properties,classpath:/application-real.properties,/home/ubuntu/app/application-real-db.properties \
 -Dspring.profiles.active=real \
 $JAR_NAME > $REPOSITORY/nohup.out 2>&1 &

[ CodeDeploy 로그확인 ]

vim /opt/codedeploy-agent/deployment-root/deployment-logs/codedeploy-agent-deployments.log
728x90
Comments