본문 바로가기
Cloud/Docker | K8s + CI, CD

[CI/CD] GitHub Actions + AWS Elastic Beanstalk를 활용한 자동화 파이프라인 구축 (정성가득♥️)

by HelloJudy 2022. 10. 2.

 

웹 애플리케이션 및 서비스를 간편하게 배포하고 조정할 수 있는 서비스인

AWS Elastic Beanstalk를 만드는 법을 포스팅으로 따로 정리한 적 있다.

 

👉 [AWS] Elastic Beanstalk(EB) 사용해서 웹 애플리케이션 배포하기

 

 

또한 이전에 CI,CD 중 JenkinsCircle CI를 공부한 적이 있다.

젠킨스는 아래와 같은 이유로 공부했는데 따로 호스팅을 해야하는 등 서비스 운영을 위한

리소스 비용을 발생하여 부담이 되었다. 

  • 무료 사용
  • 방대한 양의 플로그인
  • 다양한 적용사례 및 풍부한 레퍼런스

🍀 그래서 이번에는 최근 현업에서 많이 사용하는 GitHub Actions를 이용하여 CI/CD 파이프라인을 구축하고

AWS Elastic Beanstalk배포 및 관리해보겠다.

 


1. AWS Elastic Beanstalk 환경 생성

내가 따로 작성한 포스팅을 확인하자!

 

https://hello-judy-world.tistory.com/162

 

[AWS] Elastic Beanstalk(EB) 사용해서 웹 애플리케이션 배포하기

0. 개요 AWS Elastic Beanstalk는 Java, .NET, PHP, Node.js, Python, Ruby, Go, Docker를 사용하여 Apache, Nginx, Passenger, IIS와 같은 친숙한 서버에서 개발된 웹 애플리케이션 및 서비스를 간편하게 배포하..

hello-judy-world.tistory.com

 

+ 추가로

 

beanstalk 환경을 설정할 때 인바운드 규칙을 설정하지 않았다면

EC2 인스턴스에 가서 설정해주자!

beanstalk를 구축하면 EC2는 자동으로 설정되어 있다.

 

 


2. GitHub Actions 환경 세팅

1) AWS IAM 계정 생성

 

Actions 연결을 위해 Beanstalk에 접근 권한을 가진 유저를 만들어주자.

 

IAM > 사용자 추가를 클릭한다.

 

사용자 이름을 적고 엑세스 유형을 선택한다.

 

elastic beanstalk의 admin 권한을 주는 정책을 적용해준다.

 

태그는 선택사항이다.

 

 

최종적으로 사용자를 만들었다.

이때 제공하는 엑세스 키 ID비밀 엑세스 키를 .csv로 저장해둘 수 있다.

이것을 잘 보관하도록 하자!

이것을 이용해서 Actions에서 AWS console에 접근할 수 있다.

 

 

 

2) Actions secrets 등록

 

깃허브에서 AWS CLI로 접근을 할 것이다. 이때 아무나 문을 두드린다고 열어주면 안된다!!!

그래서 Secrets를 등록해주자!

 

레포의 Settings > Secrets에 들어가면 Actions가 있다.

 

여기서 New repository secret을 등록하자.

 

아까 위에서 저장한 값을 넣어준다.

  • AWS_EB_ACCESS_KEY_ID
  • AWS_EB_SECRET_ACCESS_KEY
  • AWS_REGION

 

* 참고로 EB를 만든 리전은 AWS console에서 확인 가능하다.

 

 

모두 완료하였다.

 

 

3) .github/workflows 폴더 생성

현재 CI/CD를 연결할 Repository에 .github/workflows 폴더를 생성한다.

 

 

이제 여기에 내가 원하는 작업의 yaml 파일을 작성하면 된다.

 

yaml파일을 간단하게 설명하면 build 파일을 만들어서 zip파일로 만들고 그것을 s3에 올려 배포한다.

 

  • deploy.yml
name: deploy main to EB

# main branch에 push 되면 실행 (action 트리거 브랜치)
on:
  push:
    branches:
      - main

jobs:
  build:
    name: CI Pipeline
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: ['16.x']

    steps:
      - uses: actions/checkout@v3

      # Initialize Node.js
      - name: Install Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}

      # cache
      - name: Cache node modules
        id: node-cache
        uses: actions/cache@v2
        env:
          cache-name: cache-node-modules
        with:
          path: node_modules
          key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-modules-

      # Install project dependencies, test and build
      - name: Install dependencies
        if: steps.node-cache.outputs.cache-hit != 'true'
        run: npm install
      - name: Run build
        run: npm run build

  deploy:
    name: CD Pipeline
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: ['16.x']
    # 위에서 정의한 build 실행
    needs: build
    steps:
      - uses: actions/checkout@v3
      # Github Secrets를 이용하여 env 파일을 만들고 추가한다.
      # 참고로 ElasticBeanstalk에 Node 관련을 배포할 때는,
      # 8081 포트를 열어줘야 한다!!
      # (정윤memo: 프로젝트할 때 .env.dev랑 .env.prod로 나누고 실행하는 환경에 따라 .env로 copy되도록)
      - name: Create env file
        run: |
          touch .env
          echo PORT=8081 >> .env
          echo NODE_ENV=${{ secrets.NODE_ENV }} >> .env
          cat .env
      # Initialize Node.js
      - name: Install Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}

      # Install project dependencies and build
      - name: Install dependencies
        run: npm install
      # 필자는 TS 파일들을 빌드해서 dist 폴더에 js 파일들을 만들기 때문에
      # 아래에서 zip 파일 구성 시 src 폴더를 제외한 것을 확인할 수 있다.
      - name: Run build
        run: npm run build

      # Install AWS CLI 2
      - name: Install AWS CLI 2
        run: |
          curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
          unzip awscliv2.zip
          which aws
          sudo ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli --update
      # Configure AWS credentials
      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_EB_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_EB_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      # Make ZIP file with source code
      # -x는 zip파일 생성 시에 해당 부분들을 제외한다.
      - name: Generate deployment package
        run: zip -r modumarket-server.zip . -x '*.git*' './src/*' './aws/*' './node_modules/*' awscliv2.zip

      # Deploy to Elastic Beanstalk
      # application_name과 environment_name을 꼭 확인하자!
      # 해당 부분은 꼭 같아야 한다!!
      - name: deploy main to EB
        uses: einaregilsson/beanstalk-deploy@v16
        with:
          aws_access_key: ${{ secrets.AWS_EB_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_EB_SECRET_ACCESS_KEY }}
          application_name: practice_elastic_beanstalk
          environment_name: Practiceelasticbeanstalk-env
          region: ${{ secrets.AWS_REGION }}
          version_label: ${{github.SHA}}
          deployment_package: modumarket-server.zip
          use_existing_version_if_available: true

 

  • 여기서 job은 build와 deploy다. 위의 설정을 확인하면 job은 ubuntu os에서 build를 한 뒤 deploy하는 코드이다. 
jobs:
  build:
    name: CI Pipeline
    runs-on: ubuntu-latest

 

  • 여기서 환경 변수는 위에서 actions secrets에 등록한 이름으로 넣어야 된다.
  • 그리고 application_nameenvironment_name은 꼭 AWS elastic beanstalk에 이름과 동일해야한다.
      - name: deploy main to EB
        uses: einaregilsson/beanstalk-deploy@v16
        with:
          aws_access_key: ${{ secrets.AWS_EB_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_EB_SECRET_ACCESS_KEY }}
          application_name: practice_elastic_beanstalk
          environment_name: Practiceelasticbeanstalk-env
          region: ${{ secrets.AWS_REGION }}
          version_label: ${{github.SHA}}
          deployment_package: modumarket-server.zip
          use_existing_version_if_available: true

 

 

  • 여기서 사용하는 이것은 깃허브 공식 Action이다.

이것이 actions를 쓰는 가장 큰 장점이다. Github Actions Marcket Place에서 가져올 수 있다.

이번에 yaml을 작성하면서 Jenkins보다 훨씬 더 쉽게 사용할 수 있는걸 알게되었다.

( GitHub Actions에 대해서는 따로 포스팅해야지!)

uses: actions/checkout@v3

 

 

이제 main 브랜치에서 push하면 이렇게 Actions 탭에서 로그와 진행사항을 확인할 수 있다.

 

이제 끝인가~~~?? 😃

 

[ Error 지옥 ]

어림도 없지 ㅎ 😑

 

1번. 빨간 불 지옥

 

하하 이때 발생한 에러지옥.. 그래 이렇게 간단하게 되는건 기대도 안했지.. (정말?)

Warning: Environment update finished, but health is Red and health status is Degraded. Giving it 30 seconds to recover...
Warning: Environment still has health: Red and health status Degraded. Waiting 19 more seconds before failing...
Warning: Environment still has health: Red and health status Degraded. Waiting 8 more seconds before failing...
Error: Deployment failed: Error: Environment still has health Red 30 seconds after update finished!

 

외않되????

 

 

때에에엥!!!

 

S3에 올라갈 zip파일을 구성하는데 여기에 -x는 빼고 싶은 파일을 선택하는 옵션인데

이때 node_modules도 추가해주니 cd가 완성됐다.

 

(내 추측으로는 아마도 프리티어 인스턴스 용량 부족...?)

 

👉 [공식 문서] How do I resolve a yellow or red health status warning in my Elastic Beanstalk environment?

 

드디어 초록불이다 ㅠㅠㅠ

 

하지만 여전히 안됩니다!

 

 

가지가지한다... 빨간불에 502 오류가 여전히 난다!  AWS Elastic Beanstalk은 웹서버로 Nginx를 사용하고 있는데 여기서 뭔가 고장난듯 하다... 뭘 놓쳤지..? 뭘까...

 

 

2번. Beanstalk 상태

CD는 성공했는데 Beanstalk 실패했다..

 

이 문제는 다음과 같이 해결했다.

 

✔️ 실행 스크립트 변경

 

beanstalk에서 따로 설정이 하지 않으면 package.json scripts 내에 start를 찾아서 실행한다고 한다.

 

  • package.json
"scripts": {
    "prebuild": "rimraf dist",
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json",
  },

 

여기서 나는 새로 eb에서 실행시킬 scripts를 추가했다.

 

"eb:prod": "npm install && npm run start:prod"

 

근데 이제 이걸 실행시키도록 하려면 어떻게 해야할까

 

 

✔️ procfile

 

👉 공식문서

 

원래 따로 정의 안하면 scripts에서 npm start를 찾아서 실행하는데 prod환경에서 실행할 스크립트를 사용하기 위해서 Procfile을 사용했다. 이 파일을 루트 폴더에 넣으면 실행할 때 해당 파일 내에서 실행한다.

 

  • Procfile

web: npm run eb:prod

 

오케이 이건 해결

 

 

 

3번. nginx 에러

하놔.. 이 부분에서 정말 헷갈렸다. 처음에 80포트로 들어오면 nginx서버가 프록시하는데 기본 포트는 5000번이라고 한다.

하지만 나는 3000포트를 쓰고 싶었고 변경하기 위해서 시도했다.

 

(사실 ㅋ..그냥 얌전히 5000번 쓰면 되는데 괜히 오기생기고 못하면 자존심 상하고... 고집으로 결국 3000번으로 바꿨다.)

 

 

(시도1) 환경 속성에 포트 변경 (기본으로 설정된 것을)

 

-> 근데 이게 아닌가벼?

 

'구성'에서 '소프트웨어' 옆에 편집

편집에서 스크롤을 내리면 하단에 환경 속성이 있다. 

 

 

(시도2) 안될 땐 공식문서^^

 

👉 공식문서 1

👉 공식문서 2

 

이 말이 포인트였다. 좀 읽어!!!!

 

기본적으로 Elastic Beanstalk는 포트 80에서 수신되는 요청을 포트 5000의 기본 웹 애플리케이션에 전달하도록 프록시를 구성합니다. 다음 예제와 같이 구성 파일에서  aws:elasticbeanstalk:application:environment 네임스페이스를 사용하는 PORT 환경 속성을 설정하여 이 포트 번호를 구성할 수 있습니다.

 

 

다음과 같이 파일을 추가하니!!!

 

option_settings:
  - namespace:  aws:elasticbeanstalk:application:environment
    option_name:  PORT
    value:  3000

 

성 공

 

 

 

엉엉 ㅠㅠㅠ 🐙문어🐙지지 않았어 ㅠㅠ

 

 

처음 세팅하는거라 조금 오래걸리긴 했지만 확실히 복잡한 nginx 설정 등을 대신해주어 간편하게 배포할 수 있었다.

끝내고 새벽 5시쯤 잤는데 자소서 쓰면서 밤새는 것보다 훨씬 행복하다.

인프라가 재밌는데 제대로 깊이 들어가면 머리아프겠지? ㅎㅎ

 

그럼 연습했으니 이제 다음 해커톤에서 제대로 세팅 가보자구~~

 

안~ 녕~


📌 Reference

 

반응형

댓글