서버 (Linux, Windows, AWS)/자동배포(CI, CD)

[CI/CD] GitHub Action + AWS CodeDeploy를 이용한 CI/CD

Trillion Binary 2023. 2. 15. 14:11
SMALL

1. CI/CD란 ?

CI는 간단히 요약하자면 빌드/테스트 자동화 과정 과정입니다.
CI는 개발자를 위한 자동화 프로세스인 지속적인 통합(Continuous Integration)을의미합니다.
CI를 성공적으로 구현할 경우 애플리케이션에 대한 새로운 코드 변경 사항이 정기적으로 빌드 및 테스트되어 공유 리포지토리에 통합되므로 여러 명의 개발자가 동시에 애플리케이션 개발과 관련된 코드 작업을 할 경우 서로 충돌할 수 있는 문제를 해결할 수 있습니다.

CD는 간단히 말하면 배포 자동화 과정입니다.
CD는 지속적인 서비스 제공(Continuous Delivery) 또는 지속적인 배포(Continuous Deployment)를 의미하며 이 두 용어는 상호 교환적으로 사용됩니다. 두 가지 의미 모두 파이프라인의 추가 단계에 대한 자동화를 뜻하지만 때로는 얼마나 많은 자동화가 이루어지고 있는지를 설명하기 위해 별도로 사용되기도 합니다.

2. GitHub Action

Git 내의 특정 이벤트를 감지하여, 일련의 Workflow를 수행하는 Github에서 제공하는 서비스입니다.

3. AWS CodeDeploy

아마존에서 제공하는 배포 자동화 툴입니다.

4. 과정

간단하게, 개발 된 웹 페이지를 빌드, 테스트, 게시 후 서버에 배포하는 과정을 자동화 한다고 생각하시면 되겠습니다.
아래는 이를 GitHub Action, AWS CodeDeploy를 통해 게시되는 과정입니다.

1) 개발자가 Git Repository에 특정 이벤트를 수행합니다.
2) Github Action 을 통해 개발자가 수행한 이벤트를 감지하여 설정된 Workflow를 실행합니다.
3) AWS 인증, 및 IAM에 설정 된 정책에 따라 4,5번 과정이 수행됩니다.
4) Workflow를 통해 수행된 결과물들이 AWS S3 버킷에 올립니다.
5) AWS의 CodeDeploy 서비스를 실행합니다.
6, 7) S3 버킷에 올라가있는 결과물들이 실제 서비스에 배포됩니다.

5. 실행

아래의 과정은 AWS EC2서버에 Net Core + React 웹 페이지가 IIS로 호스팅 되고있는 상태에서 진행됩니다.
AWS내의 모든 과정은 AWS CLI를 이용해 커맨드로도 설정할 수 있습니다.

1) CodeDeploy 어플리케이션 생성

2) 배포 그룹 생성

 

서비스 역할은 아마존 웹 서비스에 대한 접근, 사용권한에 대한 정책입니다. CodeDeploy, EC2에 대한 권한이 필요합니다.

환경 구성에는 배포시킬 웹 페이지가 호스팅되고 있는 서버를 지정해줍니다.

 

배포 구성 설정은 3가지 방식이 있습니다. 아래의 설명은 EC2 인스턴스에 대해 적용되는 사항이고, 다른 인스턴스 환경이라면 약간씩 달라집니다.

- AllAtOnce
설정된 모든 인스턴스에 배포 과정이 동시에 실행되고, 한개의 서버에만 배포에 성공해도 경우 전체 배포 성공으로 처리됩니다.

- HalfAtATime
설정된 절반의 인스턴스씩 배포 과정이 실행되고, 전체 인스턴스의 절반 이상에 배포되면, 전체 배포 성공으로 처리됩니다.

- OneAtATime
한 번에 한 인스턴스씩 배포 과정을 실행합니다. 모든 인스턴스에 배포되면, 전체 배포 성공입니다.

이번 과정에서는 .Net Core + IIS 호스팅의 특성 상, 배포 시 사이트 호스팅을 중지하고, 프로세스의 점유가 풀린 상태에서 진행되야되는데, 이 때 해당 인스턴스 서버는 요청이 들어오면 해당 트래픽을 처리해 줄 수 없습니다. 그래서 로드밸런서를 통해 한 서버에 배포가 실행되는동안 다른 서버로 트래픽을 할당 할 수 있도록 구성하기 위해 OneAtATime 배포 구성을 사용합니다.(HalfAtATime 을 이용해도 가능)'

로드 밸런서 설정입니다. 배포 과정이 실행 중인 서버에는 트래픽을 할당하지 않도록 해줍니다.
이렇게 해서 CodeDeploy를 사용하기위한 어플리케이션 생성과 배포 그룹 설정이 완료되었습니다.

3) Github Action 설정

Github Action은 Github 페이지의 Action > new workflow를 이용해도 되고,

레포지토리 디렉토리의 루트에 .github 폴더에 workflow.yml파일을 만들어서도 설정 할 수 있습니다.
여기서는 아래 방법을 이용하겠습니다.

- workflow.yml

workflow.yml 파일의 기본 형태는 아래와 같습니다.

name: GitHub Actions Demo
on: [push]
jobs:
 Explore-GitHub-Actions:
   runs-on: ubuntu-latest
   steps:
     - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
     - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
     - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
     - name: Check out repository code
       uses: actions/checkout@v3
     - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
     - run: echo "🖥️ The workflow is now ready to test your code on the runner."
     - name: List files in the repository
       run: |
         ls ${{ github.workspace }}
     - run: echo "🍏 This job's status is ${{ job.status }}."

Github Action은 하나의 가상 환경에서 workflow에 정의 된 내용을 수행합니다. 여러 환경변수를 제공하며, 여러개의 job 단위로 분할이 가능하며, 특정 job 부터 재 실행하거나, 특정 job이 실패하는 경우, 해당 작업부터 재 실행이 가능합니다.

아래는 workflow.yml 내의 각 항목에 대한 간단한 설명입니다.

- 최상단 name : workflow의 이름입니다.
- on : 감지할 Github 이벤트 트리거 입니다. push, merge, pull-request 이벤트가 일어났을때 아래 과정을 실행합니다.
- jobs : 수행할 작업들을 가집니다.
- runs-on : 작업을 수행할 가상환경 os 입니다. linux, window, mac 설정이 가능합니다. 각 os별로 청구되는 요금이 다릅니다.
- steps : job이 수행되는 수행 과정입니다. 순서대로 실행됩니다.
- steps 내의 name : 각 과정에 대한 이름입니다. 임의로 설정 가능합니다.
- uses : run 에 사용되는 특정 패키지/브랜치 등에 대한 설명입니다.
- run : 수행될 command 입니다.

아래는 이번에 사용할 workflow 설정입니다.

name: admin Test Server Deploy

# feature-CodeDeploy 브랜치에 푸쉬 되었을 때만 git action이 실행 될 수 있도록 함 
on:
 push:
   branches:    
     - 'feature-CodeDeploy'

jobs:
 build:

   runs-on: ubuntu-latest

   steps:  
   # feature-CodeDeploy 브랜치 체크아웃
   - name: Checkout
     uses: actions/checkout@v2
     with:
       ref: 'feature-CodeDeploy'

   - name: Setup .NET Core SDK ${{ matrix.dotnet-version }}
     uses: actions/setup-dotnet@v1.7.2
     with:
       dotnet-version: 5.0.x

   - name: Install dependencies
     run: dotnet restore Admin

   - name: Build
     run: dotnet build Admin --configuration Release --no-restore

   - name: Test
     run: dotnet test Admin --no-restore --verbosity normal

   # 게시 될 서버 대상, 자체포함모드로 게시
   #- name: Publish
     #run: dotnet publish githubActionExample -c Release -r win-x64 --self-contained true --output ./Release

     # 게시 될 서버 대상, 자체포함하지 않고 게시
   - name: Publish
     run: dotnet publish Admin -c Release -r win10-x64 --output ./Release

   - name: Zip Folder
     run: zip -r ${{ github.event.repository.name }}.zip . -x ".git/*" ".github/*" "phpcs.xml" "composer.json" "composer.lock" ".gitignore" "Admin/*" "Admin.sln" "README.md"

   # Deploy
   - name: Deploy
     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: ap-northeast-2

   # S3로 업로드
   - name: Upload to S3
     run: aws s3 cp --region ap-northeast-2 --acl private ./${{ github.event.repository.name }}.zip s3://core-with-githubaction/
           
   # CodeDeploy 실행
   - name: CodeDeploy
     run: aws deploy create-deployment --application-name test-admin  --deployment-config-name CodeDeployDefault.OneAtATime --deployment-group-name test-admin --s3-location bucket=core-with-githubaction,key=${{ github.event.repository.name }}.zip,bundleType=zip

.net core 프로젝트 특성 상 어떠한 플랫폼 환경에서도 실행이 가능하기때문에 가장 비용이 적게들고 속도가 빠른 linux(runs-on: ubuntu-latest) 환경에서 실행할 것이고, 특정 브랜치(feature-CodeDeploy)에 특정 이벤트(push:)가 일어났을 때, 일련의 job들을 수행 합니다.

Github Action 비용

job 목록은 다음과 같습니다.

Checkout
특정 브랜치에 푸쉬가 일어날 때, 수행할 것이기 때문에 변경사항은 특정 브랜치에 있습니다. 그래서 해당 브랜치로 체크아웃합니다.

Setup .NET Core SDK ${{ matrix.dotnet-version }}
가상 환경에서 테스트, 빌드, 게시 작업을 수행하기 위한 Net Core SDK 세팅 작업입니다.

Install dependencies
프로젝트의 의존성 파일들을 설치합니다. (Nuget 패키지, npm 패키지 등)

Build
프로젝트를 빌드합니다.

Test
프로젝트 내에 단위테스트 코드가 있다면, 이를 실행합니다.

Publish
이제 웹 서버에 올릴 수 있도록 게시합니다. 서버의 런타임 환경(-r win-x64), Realse 구성(-c Release), 그리고 Core 종속성을 자체 포함할지 (--self-contained true)등을 포함합니다.

Zip Folder
이제 AWS S3 버킷에 올리기 위해, 배포 시 필요한 파일들만 압축합니다.(-x 태그 활용)

Deploy
S3에 업로드를 실행하기 전에, AWS 인증에 필요한 세팅을 진행합니다. (엑세스키[secrets.AWS_ACCESS_KEY_ID] , 시크릿키[secrets.AWS_SECRET_ACCESS_KEY], 지역[ap-northeast-2])

secrets 환경 변수는 git 레포지토리 settings > action secrets에서 설정이 가능합니다. AWS 인증을 위한 엑세스키(AWS_ACCESS_KEY_ID ), 시크릿 키(AWS_SECRET_ACCESS_KEY)를 등록해두었습니다.

Upload to S3
AWS S3 버킷에 Zip Folder단계에서 압축한 배포 할 압축파일을(${{ github.event.repository.name }}.zip) 특정 S3(core-with-githubaction) 업로드합니다.

CodeDeploy
이제 AWS CodeDeploy를 실행합니다. 이때 앞서 생성했던 어플리케이션 명(test-admin), 배포 구성 방식 (CodeDeployDefault.OneAtATime * 저희는 배포 그룹에서 미리 설정해놨기때문에 안써도 됩니다.), 배포 그룹 명(test-admin),
, S3 버킷 명(core-with-githubaction), S3에 올린 파일명, 파일 형식이(key=${{ github.event.repository.name }}.zip,bundleType=zip) 포함됩니다.

위 설정파일을 만들었다면, 이제 feature-CodeDeploy브랜치에 push만 하면 레포지토리의 action 탭에서 진행상황을 아래처럼 확인하실 수 있습니다.

<Workflow 진행상황 확인 및, 진행 내용 확인>
<각 push 이벤트 당 일어났던 workflow 목록>

이제 Github Action을 통해 AWS CodeDeploy까지 실행 했으니, CodeDeploy 진행 상황을 체크하면됩니다.

AWS CLI을 이용해서도 확인 가능하고, 웹페이지에서 아래처럼 확인 가능합니다.

<CodeDeploy 내역들>
<한 인스턴스 CodeDeploy 진행 상황>

BeforeBlockTraffic, BlockTraffic, AfterBlockTraffic, BeforeAllowTraffic, AllowTraffic, AfterAllowTraffic은 로드밸런서에서 배포 진행중인 인스턴스에 트래픽을 할당하지 못하게하고, 배포가 완료 된 후 다시 이 인스턴스에 트래픽을 할당하도록 하는 과정입니다.

그 외의 항목들은 아래 CodeDeploy에 대해 설정한 내용에 의해 각각 이벤트 훅을 적용할 수 있습니다.
CodeDeploy 실행 설정파일은 아까 Github Action을 통해 업로드한 압축파일의 최 상단에 appspec.yml 파일에 설정합니다.
아래는 사용된 설정파일 내용입니다.

version: 0.0
os: windows
files:
 - source: /Release/ClientApp
   destination: C:\WEBDIR\a.test.com\ClientApp
 - source: /Release/Admin.exe
   destination: C:\WEBDIR\a.test.com.com
 - source: /Release/Admin.dll
   destination: C:\WEBDIR\a.test.com
file_exists_behavior: OVERWRITE
hooks:
 ApplicationStop:
   - location: /deploy_hooks/serverstop.bat
     timeout: 60
     runas: Administrator
 BeforeInstall:
   - location: /deploy_hooks/beforeInstall.bat
     timeout: 180
     runas: Administrator
 AfterInstall:
   - location: /deploy_hooks/afterInstall.bat
     timeout: 180
     runas: Administrator
 ApplicationStart:
   - location: /deploy_hooks/serverstart.bat
     timeout: 60
     runas: Administrator

- os: windows 실행될 서버의 os
- files: 배포될 파일들(배포가 필요한 파일만 설정가능, 디렉토리 단위도 가능)
- file_exists_behavior: 그 파일들이 배포될 방법(DISALLOW|OVERWRITE|RETAIN)
- hooks <한 인스턴스 CodeDeploy 진행 상황>에서 확인할수 있는 진행 상황에서 실행될 hook들입니다.
- timeout 각 훅 당 최대로 대기할 수 있는 시간입니다. (단위 : 초 ) 해당 시간이 지나도 훅이 종료되지않으면 배포 중단 후 실패처리됩니다.
(ApplicationStop, BundleDownload, BeforeInstall, Install, AfterInstall, ApplicationStart, ValidateService)

이번에 배포할 프로젝트는 프론트단인 리액트 프로젝트의 폴더(ClientApp), 프로젝트의 DLL(Admin.dll), Core 프로그램의 실행파일(Admin.exe)입니다. 호스팅 되고 있는 디렉토리에 파일이 존재할 경우 덮어쓰기(OVERWRITE) 옵션으로 배포를 합니다. 만약 추가되는 파일만 배포하고싶다거나 (RETAIN), 배포가 실패해도 신경쓰지않음(DISALLOW) 옵션을 사용할 수 있습니다.

필요하다면 프로젝트를 게시한 폴더 전체를 덮어씌우는것도 가능합니다. (이 경우 게시 시 appsettings, web config 파일들에 철저한 관리가 요구됩니다.)

그리고 배포 시에 각 상황별 이벤트에 대한 hook 설정이 가능한데,
앞서 말씀드린 것처럼 .Net Core 를 IIS 호스팅 하는 프로젝트의 경우, IIS 사이트를 중지하고, 해당 프로젝트의 dll과 exe 파일의 점유가 끝난 상태에서 배포가 이루어져야합니다.

이를 여기서는 ApplicationStop 훅을 이용해서 IIS 사이트 중지, 점유 프로세스의 중단을 합니다.
serverstop.bat배치 파일을 이용합니다.

:: /deploy_hooks/serverstop.bat
​ 
%systemroot%\system32\inetsrv\appcmd stop site "a.test.com"
taskkill /F /FI "USERNAME eq ta.test.com" && echo taskkill eq domain name

%systemroot%\system32\inetsrv\appcmd stop site "a.test.com"
호스팅 중인 사이트를 정지하는 명령어입니다.
taskkill /F /FI "USERNAME eq a.test.com" && echo taskkill eq domain name
그 후, 호스팅 도메인을 이용해서 EXE 파일이나 DLL파일을 점유하고 있는 프로세스를 종료하고 문구를 출력합니다.

window의 cmd 커맨드를 이용해서 실행되는 방법입니다. 만약 .net core 프로젝트가 아닌 그냥 .net 프로젝트라면 위 과정은 필요하지 않습니다. ( IIS 무중단 )

ApplicationStop 이후, DownloadBundle이 일어나는데, 이건 S3에 올려 둔 저희의 게시된 프로젝트 압축파일입니다. 위의 Github Action에서 실행했었죠. 해당 압축파일을 다운로드하고, 압축 해제합니다. (때문에 ApplicationStop 이벤트는, 이전 배포 내역의 bat파일을 이용하게 됩니다. 만약 이게 싫다면 ApllicationStop 훅 대신 BeforeInstall 훅을 이용하면 됩니다.)

그 후 BeforeInstall 이벤트가 일어나는데, 저는 혹시 모를 상황(앞서 프로세스 종료가 오래걸린다던지)에 대비해 10초간 딜레이를 주었습니다. 딜레이는 ping 커맨드를 이용하였습니다.


:: afterInstall, beforeInstall
:: 10초간 딜레이 주기 위함 timeout 명령어 대신 사용
ping -n 10 127.0.0.1 >NUL

그리고 Install은 DownloadBundle한 파일을 appspec.yml에 적힌 목록들을(files:) 게시된 파일들을(source) 웹 사이트 디렉토리에(destination) 설치하는(덮어씌우는) 과정입니다.

install 이후에도 10초간 딜레이를 주었는데, 혹시모를 상황을 대비하기 위해서니 없어도 무방합니다.

이후 중지 된 사이트를 시작합니다.( ApplicationStart:)

: /deploy_hooks/serverstart.bat
 %systemroot%\system32\inetsrv\appcmd start site "a.test.com"

이제 모든 배포 과정이 끝났습니다. 제대로 서비스 되고 있는지 검증하는 훅(ValidateService) 도 있으니 꼭 실행 후 검증되어야 하는 과정이 있다면 추가하면 됩니다.

CodeDeploy과정도 도중에 실패한 이벤트가 있으면 재 배포 할 수 있고 중단할수도, 아예 처음부터(Github Action) 부터 시작할 수 있습니다.

그리고 어떤 과정에서 오류가 생겼는지, 무슨일로 실패했는지도 확인이 가능합니다.

<강제로 CodeDeploy 오류를 시킨 예>

위는 기존 IIS 사이트 중지, 점유 프로세스 종료 대신 140초 정도의 딜레이를 준 뒤, 배포가 되게 했을 때의 codedeploy 인스턴스 화면입니다. 프로세스가 종료되지않아 test.dll이 프로세스에서 점유하고있어, 덮어쓰기가 거부된 에러 메세지를 확인할 수 있습니다.

6. 마치며

CI/CD 자동화를 최근 진행한 Admin 테스트 관리자에 적용 한 과정입니다. git push 한 번으로 배포까지 할 수 있어 편리합니다.

linux 서버 환경이라면 조금 더 편리하게 진행 할 수도 있지만 window 서버라도 크게 상관 없이 적용 할 수 있습니다.

CI/CD 자동화를 위한 많은 툴들이 있으니 각 프로젝트 유형에 맞추어 이용하는게 좋습니다.

이 외에도 Github Action에서 더 많은 검증, 테스트를 진행해도되고, CodeDeploy 각 훅마다 정밀한 검증 로직을 추가할 수도 있습니다.

더 많은 내용은 아래 참고 자료에서 참고해주세요.

감사합니다.

[ 참고 자료 ]

[ GitHub Action 공식문서 ] : https://docs.github.com/en/actions

 

GitHub Actions Documentation - GitHub Docs

Automate, customize, and execute your software development workflows right in your repository with GitHub Actions. You can discover, create, and share actions to perform any job you'd like, including CI/CD, and combine actions in a completely customized wo

docs.github.com

 

[ AWS CodeDeploy 공식 문서 ] : https://docs.aws.amazon.com/codedeploy/index.html

 

https://docs.aws.amazon.com/codedeploy/index.html

 

docs.aws.amazon.com

 

BIG