// 에드센스


1. 무슨일이야

Canary branch를 생성하며 기존의 [빈스톡+S3+RDS+Firebase] 세트의 인프라를 똑같이 하나 더 생성하려고했다.

순조롭게 하나씩 뚝딱뚝딱 만들어갔다..

아예 새로운 환경이기에 firebase나 kas api등 모든 키값들을 새로 넣어주어야했다.

따라서 NodeJS의 .env파일과 firebase 설정파일인 firebase.json을 불러오는 script까지 canary버전으로 새로 작성해주었다.

 

그런데 이게 무슨일인가

 

빈스톡에 배포를 하고나니 여전히 Dev DB, Dev Firebase, Dev KAS api를 가리키고있는게 아닌가?

데이터는 데브 디비로 저장되고 Firebase JWT는 프로젝트가 다르다며 풀리지 않았다.

왜! 분명 나는 잘 한거같은데!!!(물론 역시 내 실수였다)

 

빈스톡 로그파일을 까보면 데브환경으로 구성돼있음을 확인할 수 있었다.

 

 

 


2. 어디서 꼬였을까

의심1. 내가 S3에 env를 잘못올려놨겠지

  • 아니었다. 다시 다운받아서 열어보니까 잘 올라가 있더라.
  • Github Actions에서 env, firebase.json을 가져오는 스크립트에도 문제가 없었다.
  • 그 외에 모든 S3와 관련된 부분에서도 문제는 없었다.

깃헙액션에서 로그를 찍어가며 canary env파일임을 확인했다.

 

 

의심2. POSTMAN의 환경변수 문제인줄 알았다.

테스트를 포스트맨으로 하는데 엔드포인트를 데브 빈스톡으로 설정한줄 알았다.

  • 역시 아니었다. 잘 해놨다.

 

 

의심3. 빈스톡 deploy.zip이 생성될 때 뭔가 env가 누락이 되는가 싶었다.

  • 로컬에선 잘 포함되어 만들어지는데.. 이것도 아닌것 같았다.

 

 

 

 


3. 등잔밑이 어두웠다

깃헙액션 스크립트 곳곳에 로그를 찍어가며 어디서부터 잘못된 env로 바뀌는지 찾아갔고..

결국 Generate deployment packageDeploy to EB 사이 어딘가에서 문제가 생김을 알 수 있었다.

 

 

근데 조금 자세히 보면 

[deploy.zip 생성 -> 배포시작 -> 배포완료 -> 애플리케이션 구동] 의 흐름인건데

이 흐름에서 deploy.zip 생성에는 문제가 없었다면,

그 이후의 과정에는 내가 관여할 수 없는것 아닌가? 싶었다.

즉, 배포가 시작되고부터는 내 손을 떠난 상태라고 생각했다.

 

 

결국 선임님께 헬프를 쳤는데 30초만에 "혹시 이거 아닌가요?" 라고 답이 왔다.

 

정답은 빈스톡을 처음 설정할 때 멋모르고 만들어 놓은 .ebextension파일이었다.

 

 

 

 


4. ebextension이란?

한마디로

"빈스톡이 배포될때 실행되는 커맨드"

라고 할 수 있다.

 

프로젝트 최상위 경로에 .ebextension 폴더를 생성하고 폴더 안에 .config 파일을 생성하여 커맨드를 지정한다.

 

commands와 container_commands 이렇게 두가지 종류의 명령이 있다.

이 둘은 실행시점이 다르다.

 

  • commands는 웹서버가 설정되고 애플리케이션 버전이 추출되기 전에 실행되고,
  • container_commands는 웹서버가 설정되고 애플리케이션 버전 아카이브의 압축이 풀린 후에 실행된다.

 

위 이미지에는 없지만 나는 container_commands에 엉뚱한 env를 불러오는 S3 스크립트를 넣어놨었다.

그래서 깃헙액션에서 canary env를 불러와서 deploy.zip에 넣어줘도 빈스톡 배포 직후 엉뚱한 env로 덮어씌워졌던 것이다!!!!

바로 해당 커맨드를 빼주니 정상 동작하더라.

 

 

 

해결완료-

 

 

참고:


1. Github Actions를 구성하는 요소

너무 잘 정리하신분의 글을 퍼왔다.

 

  • 1) Workflow
    • 여러 Job으로 구성되고, Event에 의해 트리거될 수 있는 자동화된 프로세스
    • 최상위 개념
    • Workflow 파일은 YAML으로 작성되고, Github Repository의 .github/workflows 폴더 아래에 저장됨
  • 2) Event
    • Workflow를 Trigger(실행)하는 특정 활동이나 규칙
    • 예를 들어 다음과 같은 상황을 사용할 수 있음
      • 특정 브랜치로 Push하거나
      • 특정 브랜치로 Pull Request하거나
      • 특정 시간대에 반복(Cron)
      • Webhook을 사용해 외부 이벤트를 통해 실행
    • 자세한 내용은 Events that trigger workflows 참고
  • 3) Job
    • Job은 여러 Step으로 구성되고, 가상 환경의 인스턴스에서 실행됨
    • 다른 Job에 의존 관계를 가질 수 있고, 독립적으로 병렬 실행도 가능함
  • 4) Step
    • Task들의 집합으로, 커맨드를 날리거나 action을 실행할 수 있음
  • 5) Action
    • Workflow의 가장 작은 블럭(smallest portable building block)
    • Job을 만들기 위해 Step들을 연결할 수 있음
    • 재사용이 가능한 컴포넌트
    • 개인적으로 만든 Action을 사용할 수도 있고, Marketplace에 있는 공용 Action을 사용할 수도 있음
  • 6) Runner
    • Gitbub Action Runner 어플리케이션이 설치된 머신으로, Workflow가 실행될 인스턴스
    • Github에서 호스팅해주는 Github-hosted runner와 직접 호스팅하는 Self-hosted runner로 나뉨
    • Github-hosted runner는 Azure의 Standard_DS2_v2로 vCPU 2, 메모리 7GB, 임시 스토리지 14GB

 

참고:

 

 

 

 


2. Beanstalk으로 배포하기

프로젝트 최상위에 .github/worfklows 폴더를 만들고 그 안에 .yml 스크립트로 깃헙 액션의 동작을 지정할 수 있다.

yml 파일 대부분은 처음 보더라도 대충 어떤 의미인지 알 수 있다.

 

name: Backend CI/CD

on:
  push:
    branches: [ develop ]

jobs:
  build:
    name: Build and Test
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [14.x]

    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v2
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      # Download 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_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      # npm install for ci
      - run: npm ci

      # Build
      - run: npm run build

      # Unit test
      - run: npm run test:unit

  deploy:
    name: BeanStalk Deploy
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: ['14.x']
    needs: build
    steps:
      - uses: actions/checkout@v2

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

      # Install project dependencies
      - name: Install dependencies
        run: npm install

      # Download 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_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      # Build
      - name: Run build
        run: npm run build

      # Make upload zip file
      - name: Generate deployment package
        run: zip -r deploy.zip . -x '*.git*' './aws/*' './node_modules/*' './dist/*' awscliv2.zip

      # Deploy to Elastic Beanstalk
      - name: Deploy to EB
        uses: einaregilsson/beanstalk-deploy@v18
        with:
          aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          application_name: ${{ secrets.APPLICATION_NAME }}
          environment_name: ${{ secrets.ENVIRONMENT_NAME }}
          region: ${{ secrets.AWS_REGION }}
          version_label: ${{github.SHA}}

위에서부터 살피며 내려가자

 

 

 

 

on

jobs를 실행시킬 트리거를 지정한다. 위의 코드처럼하면 develop 브랜치에 push가 발생했을때 github actions가 실행된다는 의미다.

다음처럼 여러개의 트리거를 지정할 수 있다.

  on:
    push:
      branches: [ master ]
    pull_request:
      branches: [ master ]

 

 

 

 

jobs

jobs는 Build와 Deploy로 구성된다. 그리고 각각 name을 지정한 것을 볼 수 있다.

 

 

 

 

Configure AWS Credentials

      # Configure AWS credentials
      - 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: ${{ secrets.AWS_REGION }}

코드 내부에 aws를 사용한 부분이 다수 존재하기에 runner에 aws cli를 설치해 주어야한다.

${{ secrets.블라블라 }}

이 부분은 깃허브 - Settings - Secrets 탭에서 Name-Value를 지정할 수 있다.

 

 

${{ secrets.블라블라 }} 에서 블라블라에 Name을 입력해주면 대응되는 Value를 사용할 수 있다.

 

 

 

 

npm ci

이거는 npm install과 같은 역할이다.

다만,

ci 과정에서 쓰는 install이다.

거의 두배 이상 빠른 속도를 보여준다.

 

npm install은 package.json 내부의 dependencies와 devDependencies를 기준으로 패키지 파일을 설치하는 반면

npm ci는 package-lock.json의 lockfile을 기준으로 패키지를 설치한다.

 

이렇게 되면 package.json내의 파일과 package-lock.json 내의 버전 등이 다를 경우,

package-lock.json을 기준으로 package.json 파일을 수정하며, 명시되지 않은 부분에서는 오류를 발생시키므로 Application 관리에 있어서 안정성을 확보할 수 있다.

 

 

 

 

npm run build

# Build
- run: npm run build

package.json에 build 스크립트를 실행한다.

이때 한가지 사용한것이 

 

 

npm install concurrently --save

이거다.

 

 

 

빌드시 .env파일을 비롯한 여러 설정파일들이 존재해야하는데,

이런 중요파일들은 S3에서 다운로드해야만 한다.

따라서 build 스크립트 이전에 수행되는 prebuild스크립트에서 중요 파일들을 미리 다운 받는 행동을 먼저 해야한다.

 

그런데 중요 파일들이 2개 이상일 경우 prebuild 안에서 동시에 스크립트가 수행될 수 있도록 해주는 concurrently를 사용했다.

 

"scripts": {
	...
    "prebuild": "concurrently \"aws s3 cp s3://blahblah .env\" \"aws s3 cp s3://blahblah somethingimportant.json \"",
    "build": "tsc",
    ...
  },

이렇게 concurrently 키워드를 쓰고 ""로 묶어서 스크립트를 넣어주면 된다.

 

 

 

 

deploy - Make upload zip file

# Make upload zip file
- name: Generate deployment package
  run: zip -r deploy.zip . -x '*.git*' './aws/*' './node_modules/*' './dist/*' awscliv2.zip

Beanstalk에 올리기위한 zip 파일을 생성한다.

zip 명령어로 압축파일을 생성하고 다음 step에서 S3로 업로드하고, Beanstalk에 배포된다.

 

 

이때, 압축파일에 포함시키지 않을 파일들을 -x 옵션으로 지정하는데,

node_modules를 지정하지 않았더니 문제가 생겼었다. (node_modules를 zip에 포함시켰더니)

 

 

어자피 .ebextensions에 npm install 키워드가 있기에 node_modules를 zip에 안넣어도 되지만,

어자피 덮어쓰기가 될 줄 알고 딱히 신경쓰지 않았었다.

그랬더니 안됏따아아아ㅏㅏ

정확히는 

 ./lib/cli

를 찾을 수 없다고 에러가 발생했다. 저게 뭔지 모르겠다.

* .ebextensions란 Beanstalk 구성파일로, Beanstalk에 배포될 때 최초에 실행되는 커맨드라고 보면된다.

 

 

우연히 node_modules를 제외시키면서 문제가 해결되긴 했는데 왜 내부에서 충돌이 난것인지는 모르겠다.

혹시 명쾌한 답을 아신다면 댓글 부탁드립니다.

 

 

 

 


삽질한 점

  • ci때와 cd때 둘다 aws cli를 설치해야한다.
  • zip할때 node_modules를 빼줘야한다.

 

 

 

참고:

더보기

'DevOps' 카테고리의 다른 글

[DevOps] CI/CD 파이프라인이란  (0) 2021.11.17
[DevOps] 도커(docker)란?  (0) 2021.10.09

+ Recent posts