목차
1. CI/CD 개념
2. OverView
3. VPC 생성 및 연결
4. Github Actions 설정
5. AWS IAM 생성
6. AWS Elastic Beanstalk 설정
7. 프로젝트 파일 생성 및 수정
8. HTTPS 적용
1. CI/CD 개념
CI
- 빌드/테스트 자동화 과정
- CI는 개발자를 위한 자동화 프로세스인 지속적인 통합 의미
- 커밋할때마다 빌드와 일련의 자동화 테스트가 이루어짐
→ 동작을 확인하고 변경으로 인해 문제가 생기는 부분이 없도록 보장 - ex) main 브랜치로 commit or pull request가 발생할 때마다 항상 검증
CD
- 지속적인 서비스 제공/배포 의미
- 코드 변경이 main에 커밋되면, 자동화된 빌드 및 테스트 프로세스를 거쳐 문제가 발견되지 않으면 최종적으로 배포
- ex) main 브랜치에 커밋 후 CI를 통과 > gradle.yml workflow를 따라 Docker Image를 생성 > Docker Image를 EC2에 배포
2. Overview
아키텍처 구조는 다음과 같다.
1. vscode에서 코드 작성 후 github에 커밋 → main 브랜치에 이벤트 트리거
2. github actions workflow가 동작하면서 EB에 배포
(이때 EB가 생성한 S3에 zip 파일 업로드 + EB는 S3에 접근해 zip 파일을 가져옴)
3. AutoScaling으로 최대 2개의 인스턴스를 사용하는 EB가 새로운 인스턴스를 생성해서 배포
4. 배포 성공 시 기존 인스턴스를 죽임 (기존 인스턴스를 죽이면서 다시 1개의 인스턴스만 동작)
5. nginx를 프록시 서버로 사용하여 80 포트 요청이 들어오면 3000 포트로 연결!
3. VPC 생성 및 연결
첫 단계는 VPC 생성이다.
프리티어인 경우 VPC는 최대 5개까지 생성할 수 있다. 172.31.0.0/16이 default VPC 이다.
먼저 VPC 생성 버튼을 클릭 후 VPC 설정 옵션을 다음과 같이 선택한다.
이렇게 생성된 VPC는 연결된 서브넷이 없기 때문에 껍데기만 존재하는 것과 다름이 없다.
따라서 서브넷을 생성해주어야 하며, 이때 서브넷은 총 4개가 필요하다.
- ELB가 사용할 퍼블릭 서브넷 2개
- RDS에서 서브넷 그룹 생성 시 사용할 프라이빗 서브넷 2개
다음은 서브넷 생성이다.
왼쪽 메뉴 탭에서 서브넷 선택 후 생성을 클릭한다. 앞서 생성한 VPC(bean-stalk)를 선택한다.
서브넷 설정을 통해 총 4개를 생성해야 한다.
- 이때 가용영역은 b를 피하는게 좋다!
- 프리티어 t2.micro인 경우 EC2에 올라가지 않는 경우가 있다!
- 따라서 그냥 가용영역 a나 c를 선택하는 것이 안전한다~
IPv4 VPC CIDR block은 10.0.0.0/16으로 동일하고
IPv4 subnet CIDR block은 각각 10.0.1.0/24 10.0.2.0/24 10.0.3.0/24 10.0.4.0/24 로 작성해준다.
방금 생성한 서브넷은 아직 public이 아닌 private 상태이다. 즉 인터넷 게이트웨이를 생성해야 private이 아닌 public 서브넷으로 만들어줄 수 있다는 의미이다. 따라서 현재 서브넷은 라우팅 테이블과 연결되어 있으며 인터넷 게이트웨이와 연결되지 않았기 때문에 private 서브넷이다.
다음으로 인터넷 게이트웨이를 생성해준다.
왼쪽 메뉴 탭에서 인터넷 게이트웨이를 선택 후 생성해주면 된다.
생성한 인터넷게이트웨이를 VPC에 연결하기 위해 초록색 팝업창에 뜬 VPC에 연결 버튼을 클릭해 할당해준다.
다음은 라우팅 테이블 생성이다.
기본적으로 VPC를 생성하면 기본 라우팅 테이블이 자동으로 생성된다. 서브넷이 라우팅 테이블과 명시적으로 연결되지 않은 경우엔 서브넷은 기본 라우팅 테이블이 사용된다.
그런데 AWS 공식 문서에서 라우팅 테이블을 설명하기를
라우팅 테이블 에는 서브넷 또는 게이트웨이의 네트워크 트래픽이 전송되는 위치를 결정하는 라우팅 이라는 규칙 세트가 포함되어 있습니다.
즉 라우팅 테이블은 네트워크 트래픽을 통과시키기 위해 서브넷 or 게이트웨이를 거친다는 것을 알 수 있다.
따라서 나는 위에서 생성한 서브넷과 인터넷 게이트웨이를 명시적으로 연결한 라우팅 테이블을 생성하려고 한다!
Q. 근데 기본 라우팅 테이블에 연결해주면 되는거 아닌가???
위와 같은 의문이 들 수 있다~
그러나 게이트웨이 라우팅 테이블은 기본 라우팅 테이블로 설정할 수 없기 때문에
인터넷 게이트웨이를 사용하기 위해 새로 라우팅 테이블을 만들어주었다.
라우팅 테이블이 사용할 VPC를 위에서 생성한 VPC로 선택하고 라우팅 테이블을 생성한다.
생성 직후엔 안으로 들어오는 인바운드 패킷에 대한 정책만 있기 때문에 아웃바운드 패킷 정책도 추가해주어야 한다.
라우팅 > 라우팅 편집을 클릭해서 모든 IP 주소(0.0.0.0/0)에 대해 인터넷 게이트웨이를 통해 나가도록 설정해준다.
이때 대상은 반드시 인터넷 게이트웨이 + 위에서 생성한 인터넷 게이트웨이 를 선택해주어야 한다. (NAT 게이트웨이 써보고 싶었는데 왜 그렇게 비싼가요;)
라우팅 테이블을 생성했다면 이제 서브넷을 연결해주어야 한다.
서브넷을 연결해야 private이 아닌 public 서브넷이 되기 때문이다.
라우팅 테이블 > 서브넷 연결 > 서브넷 연결 편집을 선택해서 public 서브넷을 만들고 싶은 것만 선택해준다.
다음은 마지막 단계 보안 그룹 생성 및 연결이다.
먼저 보안 그룹을 생성하고 인바운드 규칙을 다음과 같이 편집해준다. 나는 데이터베이스로 mongodb를 사용했기 때문에 27017 포트를 열어주었지만 다른 데이터베이스를 사용한다면 그에 맞게 포트를 열어주어야 한다.
생성한 보안 그룹을 VPC에 연결해준다.
https://way-be-developer.tistory.com/269
AWS 의 VPC, 서브넷, 인터넷게이트웨이 개념잡기!
참고 https://medium.com/harrythegreat/aws-%EA%B0%80%EC%9E%A5%EC%89%BD%EA%B2%8C-vpc-%EA%B0%9C%EB%85%90%EC%9E%A1%EA%B8%B0-71eef95a7098 [AWS] 가장쉽게 VPC 개념잡기 가장쉽게 VPC 알아보기 medium.com VPC 에 대해서 알기전에 VPN(Virtual P
way-be-developer.tistory.com
VPC, 인터넷 게이트웨이, 서브넷의 관계에 대해 참고한 블로그로 아래 사진에서 그 관계를 잘 보여주는 것 같아 가져왔다.
짧게 요약하자면 다음과 같다.
Resource | Description |
VPC | 같은 물리적 네트워크에 존재하지만 서로 다른 네트워크처럼 사용할 수 있게 해주는 가상 네트워크망 |
서브넷 | 더 많은 네트워크망을 만들기 위해 VPC를 더 잘게 쪼개주는 과정 (인터넷과 연결 → public 서브넷 | 그렇지 않은 서브넷 → private 서브넷) |
인터넷 게이트웨이 | VPC와 인터넷을 연결해주는 관문 (인바운드 아웃바운드 모두 포함) |
4. Github Actions 설정
다음은 빌드와 beanstalk으로 배포하는 단계를 담당하는 Github Actions를 다룰 차례이다.
보통 개발 인프라와 릴리즈 인프라를 분리해두지만 지금은 릴리즈 인프라를 구축한다고 가정하고 작성할 예정이다.
(즉, main 브랜치에 이벤트가 발생했을 때 CI/CD가 동작한다.)
먼저 나는 로컬에서는 babel을 사용했고 배포 환경에서는 webpack을 사용했기 때문에 package.json은 다음과 같다.
(dependencies는 배포랑 관련된 부분만 남겨두었다.)
{
"name": "bloodtrail-be",
"version": "1.0.0",
"description": "bloodtrail-be",
"main": "index.js",
"scripts": {
"test": "jest",
"start:dev": "nodemon --exec babel-node index.js",
"start": "node main.js",
"build": "webpack --mode production"
},
"author": "",
"license": "ISC",
"dependencies": {
"webpack": "^5.89.0",
"webpack-cli": "^5.1.4",
"webpack-node-externals": "^3.0.0",
},
"devDependencies": {
"@babel/cli": "^7.23.4",
"@babel/core": "^7.23.7",
"@babel/node": "^7.22.19",
"@babel/preset-env": "^7.23.7",
"jest": "^29.7.0",
"nodemon": "^3.0.2",
"supertest": "^6.3.3"
}
}
이때 스크립트에 유의해야 한다. 배포 시 사용되는 스크립트는 아래 두 가지이다.
"scripts": {
"start": "node main.js",
"build": "webpack --mode production"
}
다음으로 프로젝트에서 package.json과 같은 레벨에 .github/workflows 폴더를 만들고 어쩌고.yml 이라는 이름으로 배포 스크립트를 만들어주어야 한다. 나는 prod_deploy.yml 이라는 이름으로 만들어 주었고 파일 내용은 다음과 같다.
name: Node.js Prod CI/CD
on:
pull_request: # pull request -> merge 가 되었을 때 Github Action 실행!
types: [closed]
workflow_dispatch: # 수동 실행도 가능하도록 함
jobs:
build:
# pull 요청이 main에 merge 되었을 때 아래 steps를 실행
if: github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'main'
runs-on: ubuntu-latest # 우분투 최신 버전으로 실행
strategy:
matrix:
node-version: ["18.x"] # 노드 버전 지정! 여러 개도 가능! ['18.x', '14.x'] 요렇게
steps:
# build 할 코드를 가져옴 (코드 checkout - github에서 제공해주는 checkout@v3 사용)
- name: Checkout
uses: actions/checkout@v3
# Node.js 세팅
- name: Set up Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- name: Install Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
# dependencies 설치, test and build
- name: Install dependencies
run: npm install # dependencies 설치 npm ci, npm install 모두 다 됨!
- name: Run Build
run: npm run build # 빌드
# 배포 패키지 생성
- name: Generate deployment package
run: |
cp -R .platform dist
cp package.json dist/package.json
cp package-lock.json dist/package-lock.json
cp Procfile dist/Procfile
cd dist
zip -r deploy.zip .
# 생성한 deploy.zip 파일 내부 확인용!
- name: Get Zip Inside
run: zipinfo -1 dist/deploy.zip
# 현재 시간 얻기 (Build 시점의 시간 얻기)
- name: Get current time
uses: 1466587594/get-current-time@v2
id: current-time
with:
format: YYYY-MM-DDTHH-mm-ss
utcOffset: "+09:00" # 한국 시간 고려
# 현재 시간 출력 (위에서 얻은 build 시점의 시간 보여주기)
- name: Show Current Time
run: echo "CurrentTime=${{steps.current-time.outputs.formattedTime}}"
shell: bash
# Beanstalk 배포
- name: Beanstalk Deploy
uses: einaregilsson/beanstalk-deploy@v21
with:
aws_access_key: ${{secrets.AWS_ACTION_ACCESS_KEY_ID}}
aws_secret_key: ${{secrets.AWS_ACTION_SECRET_ACCESS_KEY}}
application_name: bloodtrail-prod
environment_name: Bloodtrail-prod-env
version_label: github-action-${{ steps.current-time.outputs.formattedTime }} # version_label은 이전에 배포한 label과 중복되면 안됨!
use_existing_version_if_available: true
region: ap-northeast-2
deployment_package: dist/deploy.zip
wait_for_deployment: false # 바로 Beanstalk으로 넘어갈 수 있도록 함
# 그냥 다 했다고 출력하기
- name: Deployed!
run: echo App deployed to ELB
Q. .gitignore에 등록된 .env 파일을 스크립트로 만들어주지 않으면 환경 변수 주입 어떻게 받지???
요런 의문이 들 수 있지만 .env 파일은 스크립트로 생성하지 않았고 beanstalk 애플리케이션 구성에서 생성해주었으니 걱정하지 않아도 된다!
Q. Install Dependencies 단계에서 npm install과 npm ci 둘 중 어떤걸 사용해도 상관 없는지???
참나.
이것 때문에!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
탈모 온 것 같다!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
일단 결론 먼저 말하자면 나는 npm install을 추천한다. (제발 끝까지 읽어주세요)
먼저 npm install과 npm ci의 차이점에 대해 알고 있어야 한다.
노드 프로젝트는 package.json과 package-lock.json이 있다.
npm install
- package.json을 참고하여 모든 종속성을 설치
- package-lock.json을 수정
npm ci
- node_modules를 삭제하여 항상 깨끗한 상태를 유지
- package-lock.json을 수정하지 않음
??? : 원래 CI/CD 구축할 땐 npm ci 많이 쓰지 않나? 똘추 아냐 쟤?
보통 npm ci를 많이 사용하는 것이 맞다. 수많은 빌드 과정에서 package-lock.json을 수정하지 않기 위해서이다.
나 또한 초반에 dependencies에 모듈이 10개 정도로 손에 꼽힐 때엔 npm ci가 정상적으로 동작했다.
문제는 모듈이 40개 정도가 넘어가니까..
로컬에선 문제가 없었는데 배포 환경에서는 npm ci가 성공했다는 로그가 찍혔음에도 불구하고
Can't find module 'express'를 포함하여 모듈을 찾을 수 없다는 에러가 무지막지하게 발생하고, express를 못찾으니 배포하자마자 서버가 죽어버렸다.. 에러 메시지는 아래와 비슷했다.
https://stackoverflow.com/questions/63389779/aws-eb-error-cannot-find-module-express
AWS-EB Error: Cannot find module 'express'
I am first time trying to deploy my code using elastic bean stalk . I have created a small node webservice which is working fine on local but when I deploy that code in Elastic beanstalk i am getting
stackoverflow.com
그래서 정말 무한 삽질의 시간을 보내다가 npm ci를 npm install로 수정했더니 거짓말처럼!!!! 성공했다; 아직 정확한 원인은 찾지 못했지만 EC2 인스턴스 환경에서 모듈 간 버전 충돌로 npm ci가 제대로 동작하지 않은게 아닐까 의심 중이다.
아무튼 모듈이 쬠 많다 싶으면 npm install을 사용하고 적당하다 싶으면 npm ci를 사용하는 것을 추천한다~
'Server > CI&CD' 카테고리의 다른 글
[CI/CD] Github Actions + Elastic Beanstalk를 활용한 Node.js CI/CD 구축 (3) - 完 (3) | 2024.03.14 |
---|---|
[CI/CD] Github Actions + Elastic Beanstalk를 활용한 Node.js CI/CD 구축 (2) (0) | 2024.03.11 |
[CI/CD] GitLab + Jenkins를 활용한 SpringBoot CI/CD 구축 (3) - 完 (2) | 2024.02.13 |
[CI/CD] GitLab + Jenkins를 활용한 SpringBoot CI/CD 구축 (2) (0) | 2023.10.20 |
[CI/CD] GitLab + Jenkins를 활용한 SpringBoot CI/CD 구축 (1) (0) | 2023.08.18 |