2025. 3. 22. 16:45ㆍAWS
지난 글에서는 간단한 프로젝트와 EC2 및 CodePipeline을 설정했습니다. 이번 글에서는 CodeBuild에서 실행할 명령어와 빌드 결과물을 정의하는 buildspec.yml 파일과 CodeDeploy가 배포 과정에서 실행할 명령어를 정의하는 appspec.yml 파일과 스크립트를 작성하고, 실제 배포까지 진행하는 과정을 살펴보겠습니다.
1. buildspec.yml 작성
buildspec.yml은 CodeBuild가 실행할 명령어와 빌드 단계를 정의하는 YAML 형식의 설정 파일입니다. 이 파일을 통해 의존성 설치, 코드 빌드, 빌드 아티팩트(결과물) 저장 등의 작업을 수행할 수 있습니다.
프로젝트 루트에 buildspec.yml 파일을 생성한 후, 아래와 같이 작성합니다.
[buildspec.yml]
version: 0.2
phases:
install:
runtime-versions:
nodejs: 22
commands:
- echo "Installing dependencies..."
- npm install -g corepack@latest
- corepack enable
- corepack use pnpm@latest-10
- pnpm install
build:
commands:
- echo 'Building project...'
- pnpm run build
artifacts:
files:
- 'dist/**/*'
- 'package.json'
- 'pnpm-lock.yaml'
- '.nvmrc'
buildspec.yml 파일에서 사용할 수 있는 구문은 아래와 같습니다. 자세한 내용은 공식 문서를 참고해 주세요.
- version : buildspec.yml의 버전을 지정합니다.
- phases : 실행할 명령어를 단계별로 정의합니다. 단계에는 install, pre_build, build, post_build가 있으며, 이 순서대로 실행됩니다.
- run-as : 해당 단계의 명령어를 특정 사용자 권한으로 실행합니다.
- runtime-versions : 사용할 런타임 버전을 지정합니다. 사용 가능한 런타임 버전은 공식 문서를 참고해 주세요. (install 단계에서만 사용합니다.)
- on-failure : 해당 단계가 실패했을 때 수행할 동작을 설정합니다. 기본 동작은 ABORT(실패 시 즉시 중지) 입니다.
- commands : 각 단계에서 실행할 명령어를 순서대로 정의합니다.
- finally : 해당 단계가 종료될 때 항상 실행되는 명령어입니다. commands 단계의 명령어가 실패해도 실행됩니다.
- artifacts : 빌드 결과물에 대해 정의합니다.
- files : 빌드 결과물 중에서 어떤 파일을 포함시킬지 지정합니다.
- name : 아티팩트의 이름을 지정합니다. 기본값은 랜덤하게 생성됩니다.
- discard-paths : 폴더 구조를 유지할지 여부를 결정합니다. 기본값은 no이며, 이 값을 yes로 지정하면 모든 파일들이 동일한 디렉토리에 배치됩니다. 예를 들어, files에 dist/main.js는 dist/main.js가 아닌 main.js로 아티팩트에 포함됩니다.
- base-directory : 기본 디렉토리를 설정합니다. 이 옵션을 설정하면 files의 경로는 base-directory를 기준으로 합니다.
2. CodeBuild 테스트
위와 같이 buildspec.yml을 작성한 후, 변경 사항을 커밋하고 GitHub에 푸시하면 CodePipeline이 실행됩니다. AWS 콘솔에서 생성한 CodePipeline에 접속하면 아래와 같이 실행 결과를 확인할 수 있습니다. 현재 CodeDeploy 설정 파일이 없기 때문에 배포 단계는 실패하지만, 이는 정상적인 동작이므로 신경 쓰지 않아도 됩니다.
CodeBuild의 자세한 로그를 확인하려면 Build 부분을 클릭합니다. 로그 부분을 확인해보면 buildspec.yml에서 작성한 명령어들이 순차적으로 실행된 것을 확인할 수 있습니다.
마지막으로, 빌드된 아티팩트는 S3 버킷에서 확인할 수 있습니다. AWS 콘솔에서 S3로 이동하면 codepipeline-.. 으로 시작하는 버킷을 확인할 수 있습니다. 해당 버킷 내부에 CodePipeline 프로젝트명과 동일한 이름의 폴더가 있으며, 그 안에 BuildArtif 폴더가 존재합니다. 이 폴더에 빌드 아티팩트가 저장됩니다.
저장된 빌드 아티팩트를 다운로드하여 확인해 보면, buildspec.yml 파일의 artifacts.files 항목에 지정한 파일들이 포함되어 있는 것을 확인할 수 있습니다.
3. appspec.yml과 스크립트 작성
appspec.yml은 CodeDeploy가 배포를 수행하는 방법을 정의한 YAML 형식의 설정 파일입니다. 이 파일을 통해 배포할 파일 및 디렉토리 지정, 배포 후 실행할 스크립트 등의 작업을 설정할 수 있습니다.
프로젝트 루트에 appspec.yml 파일을 생성한 후, 아래와 같이 작성합니다.
[appspec.yml]
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/app
overwrite: yes
permissions:
- object: /home/ubuntu
pattern: '**'
owner: ubuntu
group: ubuntu
hooks:
BeforeInstall:
- location: scripts/BeforeInstall.sh
runas: ubuntu
timeout: 300
AfterInstall:
- location: scripts/AfterInstall.sh
runas: ubuntu
timeout: 300
ApplicationStart:
- location: scripts/ApplicationStart.sh
runas: ubuntu
timeout: 300
appspec.yml 파일에서 사용할 수 있는 구문은 아래와 같습니다.. 자세한 내용은 공식 문서를 참고해 주세요.
- version : appspec.yml의 버전을 지정합니다.
- os : 배포 대상의 운영 체제를 지정합니다.
- files : 배포할 파일을 어디에서 가져와서 어디로 복사할지 지정합니다. 여러 개를 지정할 수 있습니다.
- source : 인스턴스에 복사할 파일이나 디렉토리를 지정합니다.
- destination : source가 복사될 디렉토리 경로를 지정합니다.
- overwrite : 기존 파일이 있으면 덮어쓸지 여부를 지정합니다.
- permissions : 배포된 파일 및 폴더의 소유자와 권한을 설정합니다. 여러 개를 지정할 수 있습니다.
- object : 권한을 설정할 디렉토리 또는 파일을 지정합니다.
- pattern : 대상 파일 또는 디렉토리 패턴을 지정합니다. '**'는 모든 파일을 의미합니다.
- owner : 소유자를 지정합니다.
- group : 그룹을 지정합니다.
- hooks : 배포 과정에서 특정 단계에 실행할 스크립트를 지정합니다. buildspec.yml의 phases와 비슷하지만, 명령어를 직접 나열하는 대신 스크립트를 파일을 지정합니다. 또한, hooks의 단계는 배포 대상(EC2, ECS, Lambda 등)에 따라 달라지므로, 공식 문서를 참고하는 것을 추천합니다. 여기서는 EC2를 대상으로 설명합니다.
- ApplicationStop : 새 버전의 애플리케이션(빌드 아티팩트)이 다운로드되기 전에 발생합니다. 이 이벤트에 스크립트를 지정하여 애플리케이션을 우아하게 중지하거나 배포를 준비하기 위해 현재 설치된 패키지를 제거할 수 있습니다.
- DownloadBundle : 새 버전의 애플리케이션을 임시 위치(/opt/codedeploy-agent/deployment-root/deployment-group-id/deployment-id/deployment-archive)에 복사합니다. 이 이벤트는 CodeDeploy 에이전트용으로 예약되어 있으므로 스크립트를 실행하는 데 사용할 수 없습니다.
- BeforeInstall : 새 버전의 애플리케이션이 최종 대상 폴더(destination)으로 복사하기 전에 발생합니다. 이 이벤트에 스크립트를 지정하여 파일 암호 해제나 현재 버전의 백업 생성과 같은 사전 설치 작업에 사용할 수 있습니다.
- Install : 임시 위치에 있는 새 버전의 애플리케이션을 최종 대상 폴더(destination)으로 복사합니다. 이 이벤트는 CodeDeploy 에이전트를 위해 예약되어 있으며 스크립트를 실행하는 데 사용할 수 없습니다.
- AfterInstall : 새 버전의 애플리케이션이 최종 대상 폴더에 복사한 후에 발생합니다. 이 이벤트에 스크립트를 지정하여 애플리케이션 구성이나 파일 권한 변경 등의 작업에 사용할 수 있습니다.
- ApplicationStart : ApplicationStop 중에 중지된 서비스를 다시 시작하는 데 사용됩니다.
- 이후에도 ValidateService, BeforeBlockTraffic, BlockTraffic, AfterBlockTraffic, BeforeAllowTraffic, AllowTraffic, AfterAllowTraffic 있습니다.
이제 hooks의 각 단계에서 사용할 스크립트를 작성해야 합니다. 여기서는 BeforeInstall, AfterInstall, ApplicationStart 이벤트에서 실행할 스크립트를 작성합니다.
BeforeInstall에서는 프로젝트에서 사용하는 Node.js를 설치하기 위해서 nvm을 설치합니다. 사실, EC2 인스턴스를 생성한 후 직접 접속하여 환경을 설정할 수도 있지만, 저는 가능한 한 스크립트에서 모든 환경을 관리하고 싶기 때문에 nvm 설치 과정도 스크립트에 포함하였습니다.
프로젝트 루트에 scripts 폴더를 생성하고, 그 안에 BeforeInstall.sh 파일을 작성합니다.
[scripts/BeforeInstall.sh]
#!/bin/bash
apt-get update
if [ -e ~/.nvm/nvm.sh ]
then
. ~/.nvm/nvm.sh
fi
# install nvm
if ! command -v nvm > /dev/null
then
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash
. ~/.nvm/nvm.sh
fi
AfterInstall 단계에서는 nvm을 사용하여 프로젝트의 Node.js 버전을 설치합니다. 이후, 의존성 관리를 위해 pnpm을 설치하고, 애플리케이션 실행을 위해 pm2를 설정합니다. 마지막으로, 프로젝트의 의존성을 설치합니다.
scripts 폴더 안에 AfterInstall.sh 파일을 작성합니다.
[scripts/AfterInstall.sh]
#!/bin/bash
# load nvm
if ! command -v nvm > /dev/null
then
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
fi
# install node
NODE_VERSION=$(cat /home/ubuntu/app/.nvmrc)
nvm install $NODE_VERSION
nvm use $NODE_VERSION
# load npm
if ! command -v npm > /dev/null
then
export PATH="$NVM_DIR/versions/node/$(nvm version)/bin:$PATH"
fi
# install pnpm
if ! command -v pnpm > /dev/null
then
npm install -g corepack@latest
corepack enable pnpm
corepack use pnpm@latest-10
fi
# install pm2
if ! command -v pm2 > /dev/null
then
npm install -g pm2
fi
cd /home/ubuntu/app
pnpm install --frozen-lockfile
마지막으로 ApplicationStart에서 PM2를 사용하여 애플리케이션을 실행합니다.
프로젝트 루트에 PM2 실행을 위한 ecosystem.config.js 파일을, 그리고 scripts 폴더 안에 ApplicationStart.sh 파일을 작성합니다.
[ecosystem.config.js]
module.exports = {
apps: [
{
name: 'app',
script: './dist/main.js',
},
],
};
[scripts/ApplicationStart.sh]
# load nvm
if ! command -v nvm > /dev/null
then
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
fi
# load npm
if ! command -v npm > /dev/null
then
export PATH="$NVM_DIR/versions/node/$(nvm version)/bin:$PATH"
fi
cd /home/ubuntu/app
pm2 startOrReload ecosystem.config.js
4. EC2 인스턴스에 CodeDeploy Agent 설치
CodeDeploy가 EC2 인스턴스에 애플리케이션 배포하려면, CodeDeploy Agent가 반드시 설치되어 있어야 합니다. 자세한 내용은 공식 문서를 참고해 주세요.
먼저, 앞서 생성한 EC2에 접속합니다.
$ ssh -i my-project.pem ubuntu@<인스턴스의 IP>
공식 문서를 참고하여 CodeDeploy Agent를 설치합니다.
$ sudo apt update
$ sudo apt install ruby-full
$ sudo apt install wget
$ wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
$ chmod +x ./install
$ sudo ./install auto
CodeDeploy Agent가 설치되었다면, 다음 명령어를 통해 실행 중인지 확인합니다.
# 실행 중인지 확인합니다.
$ systemctl status codedeploy-agent
# 실행 중이 아니라면 실행합니다.
$ systemctl start codedeploy-agent
5. EC2 인스턴스에 S3 접근 권한 부여
CodeDeploy Agent가 EC2 인스턴스에서 배포를 수행하려면, S3 버킷에서 빌드 아티팩트를 가져와야 합니다. 이를 위해 EC2 인스턴스가 S3 버킷에 접근하기 위한 권한이 필요합니다. 이는 IAM 역할을 통해 부여할 수 있습니다.
먼저, AWS 콘솔에서 IAM에 접속합니다. 역할 페이지로 이동하여 역할 생성을 클릭합니다.
"신뢰할 수 있는 엔터티 선택"에서 "AWS Service"을 선택하고, 사용 사례는 EC2를 선택합니다.
"권한 추가"에서 "AmazonS3FullAccess"를 검색하여 선택합니다.
마지막으로 역할 이름은 원하는 이름을 입력하고 역할을 생성합니다. 저는 "ec2-codedeploy-role"로 하겠습니다.
이제 생성한 역할을 EC2 인스턴스에 연결하기 위해서 EC2 인스턴스 페이지로 이동합니다. 앞서 생성한 인스턴스를 선택하고 작업 -> 보안 -> IAM 역할 수정을 클릭합니다. 이어서 나오는 화면에서 위에서 생성한 역할을 선택하여 연결합니다.
6. CodeDeploy 테스트
변경사항을 커밋하기 전에 builspec.yml을 수정해야 합니다. CodeDeploy는 빌드 아티팩트를 가져와서 실행하기 때문에 빌드 아티팩트에 appspec.yml과 scripts, ecosystem.config.js가 포함되어야 합니다. 아래와 같이 buildspec.yml 파일의 artifacts 부분을 수정합니다.
[buildspec.yml]
version: 0.2
phases:
install:
runtime-versions:
nodejs: 22
commands:
- echo "Installing dependencies..."
- npm install -g corepack@latest
- corepack enable
- corepack use pnpm@latest-10
- pnpm install
build:
commands:
- echo 'Building project...'
- pnpm run build
artifacts:
files:
- 'dist/**/*'
- 'package.json'
- 'pnpm-lock.yaml'
- '.nvmrc'
- 'appspec.yml'
- 'ecosystem.config.js'
- 'scripts/**/*'
이제 변경 사항을 커밋하고 GitHub에 푸시하면 CodePipeline이 실행됩니다. AWS 콘솔에서 생성한 CodePipeline에 접속하면 아래와 같이 실행 결과를 확인할 수 있습니다.
7. 배포 확인
배포 완료되었다면, 아래의 명령어를 사용해 "Hello World!"가 출력되는지 확인해 보세요. 먄약 호출이 되지 않는다면, 인스턴스의 보안 그룹 인바운드 규칙에 포트 3000번이 허용되어 있는지 확인해 보시기 바랍니다.
$ curl localhost:3000
Hello World!
인스턴스의 접속하여 앱이 실행되고 있는지도 한번 확인해 보세요.
$ ssh -i my-project.pem ubuntu@<인스턴스의 IP>
$ pm2 status
$ pm2 log app
8. 배포 실패 시
혹시 배포가 제대로 되지 않는다면, 아래의 사항들을 확인해 보세요.
- CodeDeploy Agent가 설치되었는지 확인해 보세요.
- CodeDeploy Agent가 실행되고 있는지 확인해 보세요.
- 빌드 아티팩트에 appspec.yml과 scripts, ecosystem.config.js 파일이 포함되어 있는지 확인해 보세요.
- EC2 인스턴스의 IAM 역할을 업데이트 했는지 확인해 보세요.
마치며
이번 글에서는 NestJS 프로젝트를 예시로, 이번 글에서는 NestJS 프로젝트를 예시로, GitHub에 푸시한 코드를 EC2에 자동으로 배포하는 CI/CD 파이프라인을 AWS CodePipeline을 활용해 구축하는 과정을 정리해보았습니다.
CodePipeline은 그동안 여러 프로젝트에서 꾸준히 사용해왔지만, 매번 만들 때마다 기억이 흐릿하거나 세세한 설정을 다시 찾아보는 일이 반복되더라구요. 그래서 이번 기회에 처음부터 끝까지 과정을 정리하고, 옵션 하나하나에 대한 설명도 함께 기록해두면 앞으로 비슷한 구성을 다시 만들 때 훨씬 수월하겠다는 생각이 들었습니다.
EC2에 간단하게 배포하고자 할 때, CodePipeline은 여전히 강력하면서도 유연한 선택지라고 생각합니다. 이 글이 저처럼 EC2 배포를 자주 하지만 매번 새로 구성하는 분들에게도 참고 자료처럼 도움이 되었으면 합니다.
2025.03.16 - [AWS] - CI/CD 파이프라인 구축 - AWS CodePipeline으로 EC2에 자동 배포하기 (1)
2025.03.16 - [AWS] - CI/CD 파이프라인 구축 - AWS CodePipeline으로 EC2에 자동 배포하기 (2)
2025.03.17 - [AWS] - AWS CodePipeline 주요 옵션 정리
2025.03.19 - [AWS] - CI/CD 파이프라인 구축 - AWS CodePipeline으로 EC2에 자동 배포하기 (3)
'AWS' 카테고리의 다른 글
CloudWatch EMF를 통해 지표 수집하기 (0) | 2025.05.11 |
---|---|
EC2에 CloudWatch 연동 (0) | 2025.05.06 |
AWS CodePipeline 주요 옵션 정리 (0) | 2025.03.18 |
CI/CD 파이프라인 구축 - AWS CodePipeline으로 EC2에 자동 배포하기 (2) (0) | 2025.03.18 |
CI/CD 파이프라인 구축 - AWS CodePipeline으로 EC2에 자동 배포하기 (1) (0) | 2025.03.16 |