Github, AWS EC2, Next.js CI/CD 자동화 + Slack 연동

직원들이 개발에 집중하는 동안 코드 배포 자동화 구성을 해주기 위해 아래 목적과 목표를 두고 작업하였다.

  • 목적 :
    - 기존 Next.js 로 개발되고 있는 프로젝트의 Github 저장소에서 특정 브랜치로 푸시할 경우 빌드 및 배포가 자동으로 이루어지게 한다.
    - 빌드 전과 배포 후에 슬랙으로 상태에 대한 알림이 오게 한다.
  • 목표 :
    Next.js, AWS EC2, Github Actions, Github Actions Runners, Slack으로만 연동 구성하는 것이며,
    AWS의 S3에 정적 파일을 올려서 CloudFront로 배포하는 형식이 아니다. 그렇기 때문에 AWS IAM 정책 또는 자격증명을 하는 aws cli 나 aws configure & credentials 설정은 사용하지 않는다.
    Github Actions Runners 를 사용하기 때문에 AWS의 CodeDeploy는 사용하지 않는다.


개요

1. AWS EC2 인스턴스 생성
2. 생성한 인스턴스에 IP 할당
3. EC2 인스턴스 접속하여 서버 설정
4. Github CI/CD 설정
5. Github Action과 슬랙 연동

(해당 글은 2023년 7월 기준으로 작성되어 시간이 지나면 AWS와 Github의 기능, UI, 명칭이 바뀔 수 있음을 주의)


1. AWS EC2 인스턴스 생성

1-1. 리전 확인하고 인스턴스 시작을 눌러 생성을 시작한다.

1-2. 더 많은 AMI 찾아보기를 누른다.

1-3. ubuntu 를 검색해서 서버 사양을 선택한다.

(서버 사양들은 캡쳐 화면과 다르게 구성될 수 있다.)

1-4. 인스턴스 유형 확인 후 이상 없으면 키페어의 새 키 페어 생성을 누른다.

이 키는 절대 잃어버리면 안된다. 잃어버리면 인스턴스에 접근할 수 없다.
그렇다고 다른 서버나 클라우드 서비스에 업로드하여 저장해 놓지 말아야 한다. (해킹에 노출 될 수 있다.)

1-5. 키 페어를 원하는 이름으로 입력하고 아래 체크 박스로 선택 후 키 페어 생성 버튼을 누른다.

1-6. 아래처럼 다운로드 받아둔다.

1-7. 인스턴스 생성하는 곳으로 돌아가 네트워크 설정을 아래와 같이 체크박스에 체크해 준다.

  • ✅ ~ SSH 트래픽 허용
  • ✅ 인터넷에서 HTTPS 트래픽 허용
  • ✅ 인터넷에서 HTTP 트래픽 허용

1-8. 보안 그룹 규칙 추가를 누른다.

1-9. 사용자 지정 TCP, 3000 번 포트, 소스는 Anywhere 로 추가하고 규칙 저장을 누른다.

(위 캡쳐는 추후에 지정한 모습이다. 진행 당시 빠뜨려서 캡쳐를 못했다. 캡쳐한 것처럼 추후에 보안 그룹을 추가하여 편집할 수 있다.)

1-10. 스토리지 구성에서 루트 볼륨을 20GiB 로 설정한다. 특별한 이유는 없다. 이 정도가 적당했다. 개개인별로 원하는 만큼 설정하면 된다.

1-11. 인스턴스 시작을 눌러 완료한다.


2. 생성한 인스턴스에 IP 할당

2-1. EC2 서비스 페이지 좌측 하단에 네트워크 및 보안 - 탄력적 IP 에서 탄력적 IP 주소 할당을 클릭한다.

2-2. 바로 할당 누르면 된다.

2-3. 탄력적 IP 주소가 할당되면 '이 탄력적 IP 주소 연결'을 눌러서 편집으로 들어가거나 목록에서 부여받은 아이피를 클릭하면 탄력적 IP 주소 연결로 진입할 수 있다.

2-4. 인스턴스 입력란에 진입하면 인스턴스 목록들이 나열되며 거기서 아까 생성했던 인스턴스를 선택하고 연결을 누른다.

2-5. 인스턴스가 연결되었다고 나오면 이제 해당 아이피로 ssh 접속할 수 있게 된다.


3. EC2 인스턴스 접속하여 서버 설정

3-1. 맥의 경우 터미널을 이용하여 위 1-6에서 다운받은 .pem 파일 위치로 이동하여 아래 커맨드를 입력해준다.

chmod 400 키파일.pem
ssh -i 키파일.pem ubuntu@서버 아이피

예제로는 아래와 같다.

chmod 400 nextjs-aws-deploy.pem
ssh -i nextjs-aws-deploy.pem ubuntu@15.165.00.00

서버에 처음 접속하게 되면 아래 이미지처럼 계속 연결을 원하냐고 묻기도 한다.

3-2. 접속이 완료되면 아래처럼 비슷하게 해당 인스턴스의 프라이빗 IP 스트링을 포함한 프롬프트가 뜬다.

ubuntu@ip-172-31-00-000:~$

3-3. 우선 apt 업데이트부터 해준다.

sudo apt update

3-4. nginx 설치

sudo apt install nginx

3-5. nvm 설치 (Node 버전 관리자 NVM을 사용하여 Node.js 설치)

wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash
source ~/.bashrc
source ~/.bashrc
nvm install --lts

노드 설치하고 node -v 로 버전 체크 하면 아래처럼 나오면 된다.

3-6. 백그라운드에서 Next.js 를 구동시키기 위한 PM2 설치

npm i -g pm2

3-7. 인스턴스 사양 중 RAM이 1GB라 빌드 도중 메모리 오류 발생할 수 있으므로 스왑 공간 구성한다.

free -h 를 통해 코드 적용 전후 Swap 용량을 비교해서 잘 적용되었는 지 확인한다.

sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo cp /etc/fstab /etc/fstab.bak
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

3-8. nano 편집기로 next.js의 3000번 포트의 구동 서버를 사용자들이 일반 포트인 80포트로 접속해도 접근될 수 있도록 .conf 파일을 생성한다.

sudo nano /etc/nginx/conf.d/설정이름.conf

.conf 파일의 파일명은 자유롭게 지어도 된다. 파일명 때문에 지금 설정하는 것들에 영향 받는건 없다. /conf.d 폴더 안의 존재하는 모든 .conf를 불러와서 실행되게끔 nginx 에 설정되어 있어서 무조건 생성하면 작동된다.

예제로는 아래와 같다.

sudo nano /etc/nginx/conf.d/nananananananaminsik.conf

그런데 파일명을 하이픈 넣어서 길게 넣어봤는데 작동이 안될 때도 있으니 너무 길게 파일명을 설정하지 말자.

server {
    listen 80;
    server_name 도메인 또는 서버 아이피;
location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server_name 에 naminsik.com 과 같은 해당 도메인이나 아까 생성한 탄력적 IP 를 넣어준다.

작성이 끝나면 nano 편집기는 control + x 를 누르고 y 를 눌른다.
여기서 한번 더 엔터를 눌러야 해당 파일로 저장하고 나오게 된다.

3-8. 서버 재가동

sudo systemctl restart nginx


4. Github CI/CD 설정

4-1. Github 에서 CI/CD를 진행하려는 깃 저장소로 이동하여 Actions 탭을 클릭 - 검색창에 node.js 입력하여 Node.js 워크플로의 configure 버튼을 누른다.

4-2. Code 탭을 클릭하여 CI/CD를 원하는 브랜치를 선택 후 Add file 셀렉트 박스에서 Create new file을 눌러 새 파일을 작성한다.

꼭 develop 브랜치에 할 필요는 없다. 나는 이번에 develop 브랜치에 대해서만 우선 작업을 할 뿐이기 때문이다.

4-3. 새 파일은 .yml 파일로 파일명은 자유롭게 쓰면 된다. 해당 파일의 파일명은 다른 곳에서 참조하는 곳이 없어 진행하는 CI/CD 구성 중에 아무곳에도 영향 받지 않는다.

.yml 파일 작성 후 Commit-Changes... 버튼을 누른다.

.yml 파일 내용은 아래와 같다. (슬랙 연동 내용까지 미리 넣어두었다.)

name: 빌드/배포

on:
  push:
    branches:
      - develop

jobs:
  #첫번째 작업 : 슬랙에 이벤트 발생했다는 알림 보내기
  event-notification:
    runs-on: ubuntu-latest
    steps:
      - name: Send Slack notification
        uses: 8398a7/action-slack@v3
        with:
          status: custom
          fields: workflow,job,commit,repo,ref,author,took
          custom_payload: |
            {
              attachments: [{
                color: '#e9f018',
                text: `깃 : ${process.env.AS_REPO}\n브랜치 : ${process.env.AS_REF === 'refs/heads/develop' ? 'develop' : ''}\n커밋 번호 : ${process.env.AS_COMMIT}\n작성자 : ${process.env.AS_AUTHOR}\n발생 이벤트 : ${process.env.GITHUB_EVENT_NAME}`,
              }]
            }
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required
        if: always() # Pick up events even if the job fails or is canceled.

  build:
    if: ${{ github.ref == 'refs/heads/develop' }}
    runs-on: [self-hosted,Linux,X64,런너이름]

    strategy:
      matrix:
        node-version: [18.x]
        # See supported Node.js release schedule at https://nodejs.org/en/about/releases/

    steps:
    - uses: actions/checkout@v3
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    - run: npm ci --legacy-peer-deps
    - run: npm run build --if-present
    - run: pm2 restart "naminsikkkkkkkkkkk"

    - name: action-slack
      uses: 8398a7/action-slack@v3
      with:
        status: custom
        fields: workflow,job,commit,repo,ref,author,took
        custom_payload: |
          {
            attachments: [{
              color: '${{ job.status }}' === 'success' ? '#03ff03' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning',
              text: `깃 : ${process.env.AS_REPO}\n브랜치 : ${process.env.AS_REF === 'refs/heads/develop' ? 'develop' : ''}\n커밋 번호 : ${process.env.AS_COMMIT}\n작성자 : ${process.env.AS_AUTHOR}\n결과 : ${{ job.status }}`,
            }]
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # required
      if: always() # Pick up events even if the job fails or is canceled.

위 내용 중에 runs-on: [self-hosted,Linux,X64,런너이름] 부분이 있는데 4-7 에서 runner 를 만들면서 입력한 'name of runner'를 배열안의 런너이름에 그 runner 이름이 추가 될 예정이다.

4-4. 작성한 .yml을 깃에 적용시키기 위해 Commit Changes 를 누른다.

4-5. Settings 탭을 눌러서 좌측 ActionsRunners를 진입하고 New self-hosted runner 버튼을 클릭한다.

4-6. Linux 를 선택하면 ssh 터미널에 입력해야할 커맨드들이 나온다.
클릭하면 카피가 된다.

Download 영역에 있던 내용들을 터미널에 입력하면 아래와 같은 화면처럼 처리된다.

4-7. 4-6 에서 Configure 영역에 있던 내용들을 터미널에 입력하면 아래와 같은 화면처럼 시작된다.

  • Enter the name of the runner group to add this runner to: [press Enter for Default] - 엔터 누름
  • Enter the name of runner: [press Enter for ip-172-31-00-000] - 원하는 이름 입력(4-3 글 마지막쯤에 쓴 것처럼 runner 이름이다.)
  • This runner will have the following labels: 'self-hosted', 'Linux', 'X64' Enter any additional labels (ex. label-1,label-2): [press Enter to skip] - 여기도 runner 이름을 넣어준다.
  • Enter name of work folder: [press Enter for _work] press enter - 엔터 누름

./run.sh

4-8. 같은 폴더내의 svc 쉘 스크립트를 작동시킨다.

sudo ./svc.sh install
sudo ./svc.sh start

4-9. _work 폴더안에서 ls 명령어로 보면 연결하고 있는 Github의 깃 저장소 이름이 폴더로 있고 그 안에 또 똑같은 이름의 폴더가 존재할 것이다. 거기로 이동하여 3-6 에서 설치했던 pm2를 이용해 백그라운드 동작을 설정한다.
(_work 와 그안의 폴더들은 4-3 에 작성한 .yml 을 포함하여 한번 푸시하면 만들어져 있다.)

_work 폴더가 만들어졌으면 아래 커맨드를 실행한다.

cd _work
cd 깃 저장소 이름
cd 깃 저장소 이름
pm2 start npm --name "package.json에 있는 name 키의 값 " -- start

예제로는 아래와 같다.

cd _work
cd naminsik-github
cd naminsik-github
pm2 start npm --name "naminsikkkkkkkkkkk" -- start

설정이 완료되면 아래처럼 프로세스가 떠있는 모습을 보여준다. (pm2 list 프롬프트를 이용해도 상태를 확인할 수 있다.)

(서버 재가동시에도 pm2 start로 서버를 구동시켜 줘야하기 때문에 설정이 필요하다. 설정 방법은 https://lab.naminsik.com/4586 참고하면 된다.)

4-10. 여기까지 정상 처리가 되었다면 2-5 에서 부여 받은 탄력적 IP로 웹브라우저에서 접속하면 Next.js 프로젝트가 구동되어 있게 된다.

Next.js 구성할 당시 접근 포트가 기본 3000로 되어 있다면 역시 http://탄력적ip:3000 과 같이 포트로도 접근할 수 있다.

4-11. 이제 깃 저장소의 해당 브랜치에 커밋을 푸시하게 되면 Github에서 해당 저장소의 Actions 탭에서 빌드되는 모습을 볼 수 있다.

빌드 중에 Run pm2 restart /home/runner/work/_temp/c04a9950-215d-4e33-bc1f-adcbbd5ab0b6.sh: line 1: pm2: command not found Error: Process completed with exit code 127. 이런 오류가 난 적이 있었는데 해결 방법이 여러개가 있었다.
이 부분은 따로 작성하였다. (https://lab.naminsik.com/4570)


5. Github Action과 슬랙 연동

5-1. 두가지 연동 방식이 있는데 슬랙 에서 도구 - 워크플로빌더 통해 진행하는 방법이거나 슬랙 api에서 내 앱을 만들기 해서 슬랙용 앱을 만들어서 연동하는 방식이다.
나는 후자의 방식을 선호한다.

5-2. https://api.slack.com/apps 을 접속하여 Create New App 을 누른다.

5-3. From scratch 를 선택한다.

5-4. 앱 이름 입력 후 알림 보낼 워크스페이스를 선택하고 Create App 을 누른다.

5-5. Incoming Webhooks 를 선택한다.

5-6. Activate Incoming Webhooks 를 활성화하여 on 한다.

5-7. 스크롤 하여 아래의 Add New Webhooks to Workspace 를 누른다.

5-8. 연동 시킬 채널에 대해 선택하고 허용을 누른다.

5-9. Webhook URL 을 복사해 둔다.

5-10. 연동할 Github 의 Settings - Security 메뉴 영역의 Secrets and variables - Actions 를 클릭하여 Secrets 탭에서 New repository secret 을 누른다.

5-11. Name 에 SLACK_WEBHOOK_URL 이라 넣고 Secret 에 아까 5-9 에서 복사한 Webhook URL 을 넣은 뒤 Add secret 을 누른다.

5-12. 좌측 메뉴에서 Code and automation 영역의 Actions - General 를 클릭하여 본문에서 Workflow permissions 영역의 Read and write permissions 을 선택 후 Save를 눌러준다.

5-13. .yml 파일 설정은 4-3 에서 완성했기 때문에 모든 작업은 끝났다. 4-3의 .yml 설정에서 uses: 8398a7/action-slack@v3 이 부분이 있을텐데 이 것은 https://github.com/marketplace/actions/action-slack 해당 마켓플레이스의 액션을 사용하겠다는 의미이다. 거기에 나와있는 포맷을 바탕으로 본인의 취향에 맞게 수정하면 된다.

사전에 4-3 에서 설정한 결과값은 아래처럼 노출된다.


Subscribe
Notify of
guest

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.

0 댓글
Inline Feedbacks
View all comments
TOP