배포 자동화 프로젝트(5) - github actions yml 작성

신혜린·2024년 6월 21일
0

참고자료


🤔 문제점 인식


기존 배포 방식은 로컬에서 build를 한 뒤, FileZilla라는 SFTP 툴을 이용하여 직접 파일을 내리고 올리는 수동적인 형태였다.

작업 내역을 반영할 때마다 이런 반복적인 작업을 하는 게 비효율적이라고 생각되어 자동화 시스템을 구축해야겠다고 다짐하게 됐고, 여러가지 자동화 툴들이 있지만 그 중에서도 aws S3, CloudFront, github actions을 사용한 CI/CD pipeline을 작성하기로 결정했다.

좀 더 자세한 계기는 배포 자동화 프로젝트(3)에서 확인 가능하다.



🪣 배포 자동화 계획 (Github Actions, CRA, aws S3, CloudFront)


  1. CRA 내 main 브랜치에서 push
  2. GitHub Actions 내에서 build and deploy to S3
  3. CloudFront 캐시 무효화

이런 흐름으로 배포 자동화를 위한 CI/CD 파이프라인을 구축할 예정이다.
그리고 S3 버킷에서 제공하는 정적 웹 호스팅 기능을 통해 파이프라인이 정상적으로 구동하는지 확인한다.

S3 버킷, CloudFront 생성 및 환경변수 관리를 위한 github repository 내 secrets 관리 방법은 배포 자동화 프로젝트(4)에서 확인 가능하다.


📂 yml 파일 작성


전체 코드

name: Build and Deploy To S3

on:
  push:
    branches:
      - main # main 브랜치에 push가 감지되었을 때 jobs 실행

jobs:
  echo:
    runs-on: ubuntu-latest
    steps:
      - run: echo "🚀 This workflow is automatically triggered by ${{ github.actor }} "

      - name: 배포 및 빌드 시작 슬랙 알림
        if: ${{ always() }}
        id: slack-notification
        uses: slackapi/slack-github-action@v1.24.0
        with:
          payload: |
            {
              "channel": "채널 ID",
              "attachments": [
                {
                  "color": "#FFD55A", 
                  "title": "${{ github.repository }}", 
                  "title_link": "https://github.com/${{github.repository}}",
                  "text": "테스트 서버 빌드 및 배포 시작하겠습니다. :rocket:",
                  "fields": [
                    {
                      "title": "브랜치",
                      "value": "${{ github.ref_name }}",
                      "short": true
                    },
                    {
                      "title": "작업자",
                      "value": "${{ github.actor }}",
                      "short": true
                    }
                  ]      
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: '{SLACK API에서 제공하는 URL}' # 실제 값으로 넣어줘야 함.
          SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v2

      - name: Set up Node.js
        uses: actions/setup-node@v1
        with:
          node-version: '12.x' # 프로젝트에서 사용 중인 노드 버전에 맞게 설정

      - name: .env.dev 파일 생성 및 환경변수 설정 # 테스트 서버용 env 파일명
        run: | # 환경변수는 github actions secret variable 로 관리
          echo "NODE_ENV=${{ secrets.NODE_ENV }}" >> .env.dev
          echo "REACT_APP_API_URL=${{ secrets.REACT_APP_API_URL }}" >> .env.dev
          echo "REACT_APP_ADMIN_API_URL=${{ secrets.REACT_APP_ADMIN_API_URL }}" >> .env.dev
          echo "REACT_APP_KAKAO_AUTH_URI=${{ secrets.REACT_APP_KAKAO_AUTH_URI }}" >> .env.dev
          echo "REACT_APP_NAVER_AUTH_URI=${{ secrets.REACT_APP_NAVER_AUTH_URI }}" >> .env.dev
          echo "REACT_APP_KAKAO_CLIENT_ID=${{ secrets.REACT_APP_KAKAO_CLIENT_ID }}" >> .env.dev
          echo "REACT_APP_NAVER_CLIENT_ID=${{ secrets.REACT_APP_NAVER_CLIENT_ID }}" >> .env.dev
          echo "REACT_APP_REDIRECT_URI=${{ secrets.REACT_APP_REDIRECT_URI }}" >> .env.dev
          echo "REACT_APP_CHANNEL_IO_KEY=${{ secrets.REACT_APP_CHANNEL_IO_KEY }}" >> .env.dev

      - name: dotenv, dotenv-expand 설치 # env 접근을 위한 툴 설치
        run: npm install dotenv dotenv-expand

      - name: 테스트 서버 빌드 # package.json scripts 내 build-dev 명령어 실행
        run: npm run build-dev
        env:
          CI: false # default는 true

      - name: AWS Resource에 접근할 수 있게 AWS credentials 설정
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-region: ap-northeast-2
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # 환경변수는 github actions secret variable 로 관리

      - name: S3 버킷에 빌드한 파일들 새로 업로드
        run: aws s3 cp build/ s3://{S3 버킷 이름}/ --recursive # 실제 값으로 넣어줘야 함.

      - name: CloudFront 캐시 무효화
        run: aws cloudfront create-invalidation --distribution-id {CloudFront ID값} --paths "/*" # 실제 값으로 넣어줘야 함.

      - name: 빌드 및 배포 성공 슬랙 알림
        if: success()
        id: slack-success
        uses: slackapi/slack-github-action@v1.24.0
        with:
          payload: |
            {
              "channel": "채널 ID",
              "attachments": [
                {
                  "color": "#36a64f", 
                  "title": "${{ github.repository }}", 
                  "title_link": "https://github.com/${{github.repository}}/actions",
                  "text": "빌드 및 배포를 성공적으로 완료했습니다. :tada:",
                  "fields": [
                    {
                      "title": "레포",
                      "value": "${{ github.repository }}",
                      "short": true
                    }
                  ]      
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: 'https://hooks.slack.com/services/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'
          SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK

      - name: 빌드 및 배포 실패 슬랙 알림
        if: failure()
        id: slack-failure
        uses: slackapi/slack-github-action@v1.24.0
        with:
          payload: |
            {
              "channel": "채널 ID",
              "attachments": [
                {
                  "color": "#ff0000", 
                  "title": "${{ github.repository }}", 
                  "title_link": "https://github.com/${{github.repository}}/actions", 
                  "text": "빌드 및 배포에 실패하였습니다. 에러 메세지를 확인해주세요. :x:",
                  "fields": [
                    {
                      "title": "레포",
                      "value": "${{ github.repository }}",
                      "short": true
                    }
                  ]      
                }
              ]
            }
        env:
          SLACK_WEBHOOK_URL: '{SLACK API에서 제공하는 URL}' # 실제 값으로 넣어줘야 함.
          SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
  1. name: Checkout code: checkout 을 통해 github actions가 해당 레포지토리에 접근 가능하도록 허용
  2. name: Set up Node.js: 프로젝트가 사용 중인 노드 버전에 맞게 설정
  3. name: .env.dev 파일 생성 및 환경변수 설정: 빌드에 필요한 환경변수 값들을 env 파일을 통해 관리, 접근한다
  4. name: dotenv, dotenv-expand 설치: .env.dev 파일에 접근하기 위한 툴 설치
  5. name: 테스트 서버 빌드: 빌드 명령어 실행
  6. name: AWS Resource에 접근할 수 있게 AWS credentials 설정: aws에서 발급 받아 github action 내 secrets로 저장해둔 환경변수 값을 통해 actions와 aws를 연결
  7. name: S3 버킷에 빌드한 파일들 새로 업로드: build/ (빌드 폴더 내 모든 파일들, 즉 빌드 후 생성된 정적 파일들)을 S3 버킷으로 업로드
  8. name: CloudFront 캐시 무효화: S3로 성공적으로 업로드한 후 CloudFront 내 캐시 무효화 작업 실행

💡 코드 중간 중간에 끼워져 있는 슬랙 알림 기능들은 [Github Actions] GitHub Actions을 이용한 Slack 알림 설정하기 를 보면 쉽게 따라할 수 있다.

  • Github actions 가 실행되면 "테스트 서버 빌드 및 배포 시작하겠습니다." 라는 알림을 슬랙으로 발송
  • 빌드 및 배포가 성공적으로 마쳤을 시 (if: success()) 성공 알림 슬랙으로 발송
  • 빌드 및 배포가 실패했을 시 (if: failure()) 실패 알림 슬랙으로 발송


⚡️ 트러블 슈팅

🔥 에러1: CI 환경에서의 env 환경변수 처리


      - name: 프로젝트 빌드
        run: npm run build-dev
"scripts": {
	"build-dev": "env-cmd .env.dev node scripts/build.js",
	}
  • 로컬에서 수동으로 배포할 때엔 npm run build-dev 실행 시 env-cmd 를 이용하여 .env.dev 파일 내 환경변수 값에 접근하는 게 가능
  • 스크립트 명령어를 actions CI 환경에서 그대로 실행시키려고 했더니 CI에는 .env.dev라는 파일 자체를 찾을 수가 없다는 에러가 났었다.
    (gitignore.env.dev 파일이 추가되어 있기 때문에 CI 환경에서는 당연히 찾을 수가 없음)

    이는 .env.dev 파일이 로컬에 존재하고, gitignore에 추가되어 있기 때문에 Git 저장소에는 포함되지 않지만 로컬에서는 문제없이 동작하는 것.


1. 해결 방안 - github repository 내 secret variable을 통한 환경변수 관리

2. CI 환경 내 .env.dev 파일 생성 후 환경 변수 추가

- name: .env.dev 파일 생성 및 환경변수 설정 # 테스트 서버용 env 파일명
        run: | # 환경변수는 github actions secret variable 로 관리
          echo "NODE_ENV=${{ secrets.NODE_ENV }}" >> .env.dev
          echo "REACT_APP_API_URL=${{ secrets.REACT_APP_API_URL }}" >> .env.dev
          echo "REACT_APP_ADMIN_API_URL=${{ secrets.REACT_APP_ADMIN_API_URL }}" >> .env.dev
          echo "REACT_APP_KAKAO_AUTH_URI=${{ secrets.REACT_APP_KAKAO_AUTH_URI }}" >> .env.dev
          echo "REACT_APP_NAVER_AUTH_URI=${{ secrets.REACT_APP_NAVER_AUTH_URI }}" >> .env.dev
          echo "REACT_APP_KAKAO_CLIENT_ID=${{ secrets.REACT_APP_KAKAO_CLIENT_ID }}" >> .env.dev
          echo "REACT_APP_NAVER_CLIENT_ID=${{ secrets.REACT_APP_NAVER_CLIENT_ID }}" >> .env.dev
          echo "REACT_APP_REDIRECT_URI=${{ secrets.REACT_APP_REDIRECT_URI }}" >> .env.dev
          echo "REACT_APP_CHANNEL_IO_KEY=${{ secrets.REACT_APP_CHANNEL_IO_KEY }}" >> .env.dev

- name: dotenv, dotenv-expand 설치 # env 접근을 위한 툴 설치
       run: npm install dotenv dotenv-expand

dotenv, dotenv-expand를 로컬에서 설치를 해도 CI에서는 라이브러리를 못 찾는 오류가 발생해서 CI 내에서도 확실하게 설치되도록 보장하기 위해 workflow에 install 명령어를 추가해주었다.


3. scripts/build.js - dotenv, dotenv-expand require문 추가

// 수정 전

// Ensure environment variables are read.
require('../config/env');
// 수정 후

// Ensure environment variables are read.
const dotenv = require('dotenv');
const dotenvExpand = require('dotenv-expand');
const myEnv = dotenv.config({ path: '../config/.env.dev' }); // Adjust the path if needed
dotenvExpand(myEnv);

require('../config/env');

dotenv를 이용하여 .env 파일 내 환경 변수를 읽고, dotenv-expand를 사용해 확장하는 역할을 한다.

build.js 내에 위처럼 실행 코드를 추가해줘야 설치해준 dotenvdotenv-expand가 제대로 작동한다.



🔥 에러2: npm ERR! Failed at the build-dev script.


env 환경 변수 문제를 해결했음에도 불구하고 자꾸 build가 fail되는 문제가 발생했다.
이유를 찾아보니, 로컬에서는 warning과 같은 경고문이 발생해도 빌드해주는 반면, CI 환경 내에서는 이러한 warning을 error로 인식해서 빌드를 실패하게 하는 것이다.

해결 방법 - CI: false로 설정

 - name: 테스트 서버 빌드 # package.json scripts 내 build-dev 명령어 실행
        run: npm run build-dev
        env:
          CI: false # default는 true

빌드를 실행하는 명령어에 CI: false 조건을 추가해주면 warning이 있더라도 무시하고 빌드를 해준다.




이제 커밋 메세지 작성 후 지정해준 브랜치 내에서 push 만 하면 빌드부터 배포까지 자동으로 실행해주는 CI/CD 파이프라인이 구축되었다! 👏🏻
다음에는 빌드 시간을 단축할 수 있는 방법을 고안 해봐야겠다.

profile
개 발자국 🐾

0개의 댓글