[2주차] GitHub Action 환경에서의 AWS ELB, EC2 배포 설정 및 배포 자동화 (Next SSR) - CLI

[월간-CS][24년 4월] React, Next 배포와 배포 자동화 A부터 Z
이민석's avatar
Apr 11, 2024
[2주차] GitHub Action 환경에서의 AWS ELB, EC2 배포 설정 및 배포 자동화 (Next SSR) - CLI

이 문서는 [월간-CS][24년 4월] React, Next 배포와 배포 자동화 A부터 Z를 위해서 작성된 문서입니다. 이 문서에는 그 어떤 저작권이 없으며, 편하게 참고 및 사용하셔도 됩니다.

!주의! !주의! !주의!

이번 실습은 시간당 과금되는 비용이 존재합니다. 따라서 몇일에 걸쳐서 실습을 진행할 경우, 아래와 같은 이유로 많은 비용이 과금될 수 있습니다.

  1. 리소스를 생성하고 삭제를 까먹은 경우

  2. 리소스를 생성하고 너무 오랜 시간 동안 실습을 진행한 경우

  3. 리소스를 생성해놓고 다른 PC에서 이어서 실습을 진행하는 경우

따라서 실습을 중단할 것이라면, 반드시 이 문서의 마지막에 있는 리소스 제거하기 파트를 진행해주세요.

사전 지식

지난 주차에는 아래의 2단계의 실습을 통해, AWS CloudFront, S3를 통해서 정적 파일을 제공하는 법을 배웠습니다. 따라서, 해당 내용을 반드시 숙지하고 진행해주세요.

  1. [1주차] CloudFront, S3 쉽게 배포하기

  2. [1주차] GitHub Action에서 React 지속적 전달 구현하기 (CF, S3)

사전 준비

  1. aws cli

  2. terraform

  3. aws iam

  4. aws cli configuration profile

  5. github cli(gh cli)

이번에는

이번 실습에서는 [2주차] AWS ELB, EC2, CodeDeploy에 대한 이해에서 설계한 아키텍처를 실제로 배포하고 배포 자동화를 구현해볼 것입니다.

필요 프로그램 설치

이번 실습에서는 코드 파일을 통해, AWS AMI를 생성하는데 사용 가능한 Packer를 설치할 것입니다.

Packer란?

Packer는 HashiCorp 사에서 만든 인프라 자동화 도구입니다.

다양한 환경에서 다양한 가상 머신 이미지(VMI, Virtual Machine Image)를 생성하는데 사용됩니다.

Packer 사용 예시

아래와 같은 요구사항에서 packer는 코드 파일(*.pkr.hcl)을 기반으로 탬플핏화된 가상 머신 이미지(VMI)를 생성할 수 있습니다.

  • 요구 사항

    서비스 기업 A에서 운영 중인 모든 서버는 Node 20CodeDeploy Agent라는 툴을 설치가 되어야 합니다.

  • 해결 방법

    서버가 실행될 때, 선택하는 이미지를 원하는 형태로 미리 만들어 놓으면 된다.

    AWS AMI란?
    AWS EC2(서버)가 시작되는데 필요한 가상 머신 이미지입니다.
    서버가 가동되기 위한 운영체제(Guest OS)를 포함한 일련의 툴(Bins/Libs)들이 설치가 되어 있습니다.

  • 코드 → 주요 코드 생략

    # VMI/AMI에서 사용할 블록 스토리지(EBS)
    source "amazon-ebs" "custom_ami_ebs" { }
    
    # VMI/AMI 빌드 선언
    build {
    
      provisioner "shell" {
        inline = [
          # [STEP 1] CodeDeploy에서 필요한 필수 프로그래 설치
          # [STEP 2] Node.js 20 버전 설치
          # [STEP 3] CodeDeploy Agent 설치
        ]
      }
    }

Packer 설치하기

인프라 소개하기

후술…

네트워크 부분

후술…

컴퓨팅 부분

후술…

배포 자동화 부분

후술…

작업 환경 준비

  1. 실습용 저장소 접속하기

    https://github.com/monthly-cs/2024-04-cicd-week-2-template

    • 실습용 저장소를 개인 저장소로 포크 뜨기

    • 해당 저장소를 클론 받기

      git clone https://github.com/<본인 GitHub UserName>/2024-04-cicd-week-2-template
      
      예)
      git clone https://github.com/unchaptered/2024-04-cicd-week-1-template.git 2024-04-cicd-week-2-template-practice

실습 진행

이 실습은 [1주차] CloudFront, S3 쉽게 배포하기 - DNS 리소스 배포하기 + 가비아의 도메인 NS 변경하기를 진행한 후에 진행됩니다.

수동으로 배포하기

  1. AMI 배포하기

    해당 리소스는 N분 내로 배포가 완료되었습니다.

    # 프로젝트 루트 경로에서
    cd ./infra/ami
    
    packer init .
    packer build .
  2. Server 배포하기

    해당 리소스는 NN분 내로 배포가 완료되었습니다.

    # 프로젝트 루트 경로에서
    cd ./infra/server
    
    terraform init
    terraform apply -var="profile=monthly-cs" -var="domain_name=<가비아에서 구매한 도메인>" -var="ami_id=<AMI ID>"
    
    예)
    terraform apply -var="profile=monthly-cs" -var="domain_name=unchaptered.shop" -var=ami_id=ami-0e22b048b09df457f"
    1. AWS Console 로그인하기

    2. AWS Console에서 CodeDeploy 검색하기

    3. 배포된 CodeDeploy Application 클릭하기

      리전을 us-east-1으로 선택하셔야 합니다!

    4. CodeDeploy Deployment 클릭하기

    5. CodeDeploy Deployment에서 아래 빨간 체크박스에 맞게 수정하기

  1. 로컬에서 배포 준비하기

    1. 프로젝트 루트 디렉토리로 이동하기

      cd ../../
    2. npm install를 이용해서 모듈 다운받기

    3. npm run build를 이용해서 빌드하기

      Next 빌드 파일은 .next 폴더 안에 들어가있습니다.
      MacOS에서는 [커맨드] + [쉬프트] + [온점(.)]으로 볼 수 있습니다.

    4. npm start를 이용해서 서버 시작해보기

      Next 서버를 가동한 이후, 별도의 터미널(cmd)에서 curl 요청을 보내서 이를 확인해볼 수 있습니다

    5. 빌드 파일을 zip 형태로 압축하기

      1. Windows

        zip -r next-server.zip          ^
                ./.next                 ^
                ./component             ^
                ./public                ^
                ./scripts               ^
                ./src                   ^
                ./appspec.yml           ^
                ./ecosystem.config.json ^
                ./next-env.d.ts         ^
                ./next.config.mjs       ^
                ./package-lock.json     ^
                ./package.json          ^
                ./README.md             ^
                ./tsconfig.json         ^
                ./yarn.lock
      2. MacOS, Linux

        zip -r next-server.zip          \
                ./.next                 \
                ./component             \
                ./public                \
                ./scripts               \
                ./src                   \
                ./appspec.yml           \
                ./ecosystem.config.json \
                ./next-env.d.ts         \
                ./next.config.mjs       \
                ./package-lock.json     \
                ./package.json          \
                ./README.md             \
                ./tsconfig.json         \
                ./yarn.lock
    6. 빌드 파일을 s3에 업로드하기

      cd ./infra/server
      
      # 아래 명령어로 빌드 파일을 업로드할 s3 이름을 확인
      terraform output s3_name

      <terraform output s3_name의 결과>에는 쌍따옴표(“)를 제거하고 적어주세요.
      즉, 출력값이 “*-monthly-cs-s3-bucket“ 이라면 *-monthly-cs-s3-bucket 라고만 적어주세요.

      1. Windows

        aws s3 cp                           ^
            --region us-east-1              ^
            ../../next-server.zip           ^
            s3://<terraform output s3_name의 결과>/next-server.zip ^
            --profile monthly-cs
        
        예)
        aws s3 cp                           ^
            --region us-east-1              ^
            ../../next-server.zip           ^
            s3://*-monthly-cs-s3-bucket/next-server.zip ^
            --profile monthly-cs
      2. MacOS, Linux

        aws s3 cp                           \
            --region us-east-1              \
            ../../next-server.zip           \
            s3://<terraform output s3_name의 결과>/next-server.zip \
            --profile monthly-cs
    7. 빌드 파일로 CodeDeploy 배포 실행하기

      cd ./infra/server # 만약 이미 server 폴더라면, 제외
      
      terraform output s3_name
      terraform output codedeploy_app_name
      terraform output codedeploy_deployment_name
      1. Windows

        aws deploy create-deployment                                                    ^
            --application-name <출력값 codedeploy_app_name>	                            ^
            --deployment-group-name <출력값 codedeploy_deployment_name>	                ^
            --s3-location bucket=<출력값 s3_name>,bundleType=zip,key=next-server.zip    ^
            --profile monthly-cs
      2. MacOS,Linux

        aws deploy create-deployment                                                    \
            --application-name <출력값 codedeploy_app_name>	                            \
            --deployment-group-name <출력값 codedeploy_deployment_name>	                \
            --s3-location bucket=<출력값 s3_name>,bundleType=zip,key=next-server.zip    ^
            --profile monthly-cs

결론

위 과정을 모두 완료하고 week2.<가비아에서 구매한 도메인>에 접속하면, 사이트가 나옵니다.

배포 자동화 구축

appspec.yml 파일

프로젝트 루트 경로에 아래 파일을 생성해주세요

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ubuntu/project
    overwrite: yes

file_exists_behavior: OVERWRITE

permissions:
  - object: /home/ubuntu
    pattern: '**'
    owner: ubuntu
    group: ubuntu

hooks:
  AfterInstall:
    - location: scripts/after-install.sh
      timeout: 300
      runas: ubuntu

scripts/after-install

프로젝트 루트 경로에 scripts 폴더를 만들고 after-install.sh 파일을 만들어주세요.

DIRECTORY=/home/ubuntu/project

cd $DIRECTORY

sudo npm i pm2 -g
sudo npm i
sudo pm2 start npm --name "next-server" -- start

로그 확인하기

모든 방법이 실패했을 때

ssh "PEM 키 주소" ubuntu@IP 주소

  1. PEM 키 권한 400으로 변경하기

    1. Windows 현장설명

    2. MacOS

      # 프로젝트 루트 경로에서
      
      chmod 400 <프로젝트 루트 경로>/infra/server/monthly-cs.pem
      
      혹은
      
      chmod 400 ./infra/server/monthly-cs.pem
  1. SSH 설정하기

    MacOS는 ssh의 위치가 다릅니다.
    이 경우 where ssh 혹은 which ssh로 위치를 찾고 아래에 C:/ 경로를 바꿉시다.

    Host Monthly-Bastion-Host
      HostName <Public IP>
      IdentityFile "<프로젝트 루트 경로>\infra\server\monthly-cs.pem"
      User ubuntu
    
    Host Monthly-Next-Server
      HostName <Private IP>
      IdentityFile "<프로젝트 루트 경로>\infra\server\monthly-cs.pem"
      User ubuntu
    
      # Windows
      ProxyCommand C:/Windows/System32/OpenSSH/ssh.exe -q -W %h:%p Monthly-Bastion-Host
    
      # MacOS
      ProxyCommand ssh -q -W %h:%p Monthly-Bastion-HostSSH
  2. 로그 모니터링하기

    sudo su -
    pm2 log
  3. 포켓몬 잡기를 시도해서 로그가 찍히는 것을 확인하기

리소스 정리

아래의 순서에 맞춰서 리소스를 정리해주세요.

모든 리소스를 제거해야 비용이 나오지 않습니다.
반드시 마지막 리소스까지 제거해주세요!

AWS S3 버킷 비우기

  1. S3 버킷 이름 조회하기

cd ./infra/server

terraform output s3_name
  1. S3 버킷 이름 지우기

aws s3 rm s3://<출력값 s3_name> --recursive --profile monthly-cs

Next Server 배포 지우기

# 프로젝트 루트 경로에서
cd ./infra/server

terraform init
terraform destroy -var="profile=monthly-cs" -var="domain_name=<가비아에서 구매한 도메인>" -var "ami_id=<AMI_ID>"

예)
terraform destroy -var="profile=monthly-cs" -var="domain_name=unchaptered.shop" -var=ami_id=ami-0e22b048b09df457f"

AWS AMI 이미지 조회 및 삭제하기

  1. 이미지 조회하기

  2. 이미지 삭제하기

  3. 한 줄 구문(Windows)

  4. 한 줄 구문(MacOS/Linux)

이미지 조회하기

  1. AWS AMI 리스트 조회하기

    aws ec2 describe-images --owners self --profile monthly-cs
  2. Monthly-CS에서 Packer로 생성한 이미지 조회하기

    aws ec2 describe-images --owners self --filters "Name=tag:monthly-cs,Values=monthly-cs" --profile monthly-cs
  3. Monthly-CS에서 Packer로 생성한 이미지의 ID(ami_id) 조회하기

    1. 배열

      aws ec2 describe-images --owners self --filters "Name=tag:monthly-cs,Values=monthly-cs" --query "Images[*].ImageId" --profile monthly-cs
    2. 배열의 첫번째 인덱스만

      aws ec2 describe-images --owners self --filters "Name=tag:monthly-cs,Values=monthly-cs" --query "Images[0].ImageId" --profile monthly-cs

이미지 삭제하기

  1. 특정한 이미지 삭제하기

    aws ec2 deregister-image --image-id <조회한 AWS AMI ID>

한 줄 구문(Windows)

for /f %i in ('aws ec2 describe-images                              ^
                --owners self                                       ^
                --filters "Name=tag:monthly-cs,Values=monthly-cs"   ^
                --query "Images[0].ImageId"                         ^
                --profile monthly-cs') do aws ec2 deregister-image --image-id %i --profile monthly-cs

한 줄 구문(MacOS/Linux)

aws ec2 deregister-image $(aws ec2 describe-images                  \
                --owners self                                       \
                --filters "Name=tag:monthly-cs,Values=monthly-cs"   \
                --query "Images[0].ImageId"                         \
                --profile monthly-cs)                               \
                --profile monthly-cs

AWS Snapshot 조회 및 삭제하기

  1. 이미지 조회하기

  2. 이미지 삭제하기

  3. 한 줄 구문(Windows)

  4. 한 줄 구문(MacOS/Linux)

이미지 조회하기

  1. AWS Snapshot 리스트 조회하기

    aws ec2 describe-snapshots                              ^
        --profile monthly-cs
    
    aws ec2 describe-snapshots                              \
        --profile monthly-cs
  2. 내 계정의 AWS Snapshot 리스트 조회하기

    aws ec2 describe-snapshots                              ^
        --owner-ids self                                    ^
        --profile monthly-cs
    
    aws ec2 describe-snapshots                              \
        --owner-ids self                                    \
        --profile monthly-cs
  3. 내 계정의 Monthly-CS에서 AWS Snapshot 리스트 조회하기

    aws ec2 describe-snapshots                              ^
        --filters "Name=tag:monthly-cs,Values=monthly-cs"   ^
        --profile monthly-cs
  4. 내 계정의 Monthly-CS에서 AWS Snapshot 리스트 ID(ami_id) 조회하기

    1. 배열

      aws ec2 describe-snapshots                              ^
          --filters "Name=tag:monthly-cs,Values=monthly-cs"   ^
          --query "Snapshots[*].SnapshotId"                     ^
          --profile monthly-cs
    2. 배열의 첫번째 인덱스만

      aws ec2 describe-snapshots                             ^
         --filters "Name=tag:monthly-cs,Values=monthly-cs"   ^
         --query "Snapshots[0].SnapshotId"                     ^
         --profile monthly-cs

이미지 삭제하기

  1. 특정한 이미지 삭제하기

    aws ec2 delete-snapshot                                 ^
        --snapshot-id <스냅샷 ID>                           ^
        --profile monthly-cs

한 줄 구문(Windows)

for /f %i in ('aws ec2 describe-snapshots              ^
      --filters "Name=tag:monthly-cs,Values=monthly-cs"   ^
      --query "Snapshots[0].SnapshotId"                     ^
      --profile monthly-cs') do aws ec2 delete-snapshot --snapshot-id %i --profile monthly-cs

한 줄 구문(MacOS/Linux)

aws ec2 deregister-image $(aws ec2 describe-images                  \
                --owners self                                       \
                --filters "Name=tag:monthly-cs,Values=monthly-cs"   \
                --query "Images[0].ImageId"                         \
                --profile monthly-cs)                               \
                --profile monthly-cs

Share article

Unchaptered