그동안 프로젝트를 배포할 때 Github Action을 이용해서, Build, Test 후 배포를 해왔는데, Jenkins를 써서 CI/CD 파이프라인을 구성해 보면서 공부한 내용을 정리하고자 합니다.
프로젝트는 NestJS 기본 템플릿을 사용하고, 빌드를 성공하고 테스트를 통과하면 AWS CodeDeploy를 이용해서 EC2에 배포하는 파이프라인을 만들고, 나아가 단일 EC2가 아닌 target group 내에 여러 EC2에 동시에 배포되도록 구성해보려 합니다.
환경
로컬 환경
OS : Win10
Docker Desktop : 4.30.0
Nest : 10.3.2
Node : 20.11.0
Jenkins : 2.479.1
AWS EC2 환경
OS : Amazon Linux 2023 AMI
Node : 22.11.0
production에서는 Nest 프로젝트 자체를 docker로 감싸서 EC2(ECS)에 배포함으로써 버전에 일관성을 챙기면 좋겠지만, 이 글에서는 편의상 EC2에서 pm2로 바로 실행하겠습니다.
Jenkins 세팅
Jenkins 설치에 관해서는 많은 블로그에서 다루고 있기에 생략하겠습니다. 본인은 윈도우 PC 로컬 환경에서 Docker를 이용해 Jenkins를 세팅했습니다.
docker pull jenkins/jenkins:lts
docker run -d -p 18080:8080 -v C:\docker\jenkins:/var/jenkins_home jenkins/jenkins --name jenkins-container -u root jenkins/jenkins:lts
이때 docker run 명령어 실행 전에 로컬 파일 탐색기에서 C:\docker\jenkins 폴더를 만들고 `-v` 옵션으로, docker가 꺼졌다 켜져도 jenkins 설정이나 플러그인이 저장될 수 있도록 합니다.
Jenkins Plugin 설치
총 3개의 플러그인을 설치해 준다.
Generic Webhook Trigger
GitHub Integration
AWS CodeDeploy
Nest.js 프로젝트 생성 + github 설정
Nest.js 프로젝트 및 Github Repo 생성
nest new jenkins-test
새로운 NestJS 프로젝트를 만들어주고, github private repository도 만들어줬습니다.
Github Token 생성 및 Jenkins에 등록
Github에서 토큰을 발급받아줍니다. 이때 권한은 아래와 같이 넣어주면 됩니다.
생성하면 ghp_************************************* 이런 식으로 token을 주는데 잘 복사해 둡시다.
Jenkins로 돌아와서 관리 > Credentials로 들어갑니다.
여기서 글로벌을 클릭하고, Add Credentials를 눌러 방금 발급받은 token을 등록합니다.
Username에는 깃헙 계정명
password에 발급받은 토큰
ID는 원하는 식별자를 넣어주면 됩니다.
Jenkins 관리 > System에서 스크롤을 내려보면 Github Server 부분이 있는데 여기에 Credentials 부분에 방금 만든 식별자를 선택하고, 하단에 Test connetion을 눌렀을 때, 'Credentials verified for user americano212, rate limit: 4999' 이렇게 나오면 성공입니다.
Github Webhook 생성
Github repository > Settings > Webhooks에서 새로운 webhook을 생성합니다.
Payload URL에 Jenkins가 돌아가고 있는 서버 URL을 입력합니다.
형식은 http://{서버 주소}:{Jenkins 포트}/github-webhook/ 입니다.
성공하면 녹색 체크 표시가 들어옵니다.
필자는 Jenkins를 local에서 실행했고, docker에서 기본 8080 포트를 18080 포트로 변환해서 listen 하고 있습니다. 추가로 집 안에 있는 공유기와 모뎀에서 18080을 포트포워딩하고 있고, 집으로 들어오는 공인 IP에 연결된 DNS 주소도 있는 상태입니다.
이런 설정이 어려운 환경이라면 또 다른 EC2에서 Jenkins를 띄워서 연습해 보는 게 편할 것 같습니다.
AWS EC2, S3 생성 및 CodeDeploy 설정 + IAM
IAM 역할 + 사용자 생성
1. EC2용 IAM 역할을 생성합니다. 부여 정책은 아래 참조.
2. CodeDeploy용 IAM 역할을 생성합니다. 부여 정책은 아래 참조.
3. IAM 사용자를 생성합니다. 부여 정책은 아래 참조.
자격증명으로 액세스 키를 만들고, Access/Secret Key를 잘 기록해 둡니다.
EC2 생성
1. AWS에서 EC2를 생성합니다. Amazon Linux AMI를 t2.micro 인스턴스 유형으로 시작했습니다.
2. CodeDeploy에서 식별할 태그를 지정해 줍니다. 편의상 Name 태그를 사용하겠습니다. 이렇게 설정하면 jenkins-demo라는 Name을 가진 모든 EC2에 배포하게 됩니다. 단, 로드밸런싱이나 ASG는 따로 설정해야합니다.
3. 위에서 만든 EC2용 IAM역할을 연결합니다.
4. 보안 그룹은 nest.js 기본 포트인 3000 포트와 ssh 접속을 위한 22 포트를 개방합니다.
EC2에 CodeDeploy Agent + node 설치
1. ssh로 EC2에 접속하고 CodeDeploy Agent를 설치합니다.
sudo yum install -y ruby
sudo yum install -y aws-cli
cd /home/ec2-user/
wget https://aws-codedeploy-ap-northeast-2.s3.amazonaws.com/latest/install
chmod +x ./install
sudo ./install auto
sudo service codedeploy-agent status
'sudo service codedeploy-agent status'를 입력했을 때 아래와 같이 뜨면 성공입니다.
2. node를 설치해 줍니다.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
source ~/.bashrc
nvm install --lts
node -e "console.log('Running Node.js ' + process.version)"
3. pm2를 설치해 줍니다.
npm install -g pm2
S3 생성
다른 설정 안 건들고 생성만 하면 됩니다.
CodeDeploy 설정
1. 애플리케이션 생성
2. 배포 그룹 생성
3. 환경 구성에서 EC2인스턴스를 선택하고 Name 키로 아까 만든 EC2의 value를 선택합니다.
4. 일단은 단일 EC2에 배포할 거면 로드밸런싱은 꺼두면 됩니다.
새로운 Jenkins Item 생성
메인화면에서 New Item을 누르고 원하는 Item 이름을 넣은 뒤 Freestyle project를 생성합니다.
1. General > GitHub project 선택 후 url 입력
2. 소스 코드 관리 > Git 선택 후 URL, Credentials, Branch 입력
3. 빌드 유발 'GitHub hook trigger for GITScm polling' 선택
4. Build Steps > Execute shell 선택 후 npm install, npm run build 입력
5. 빌드 후 조치 > AWS CodeDeploy 선택 후 아까 만든 CodeDeploy 애플리케이션 이름, 배포 그룹, 배포 리전, S3 버킷 명을 입력해 줍니다. (원한다면 include 할 폴더를 지정할 수 있습니다)
6. 하단에 Access Key, Secret Key에 아까 만든 IAM 사용자 자격 증명 입력
로컬 코드에 appspec.yml + script 파일 생성
CodeDeploy에서 실행될 스크립트를 작성합니다. appspec.yml에 명세하게 되는데, 정해진 format과 lifecycle이 있으니 자세한 사항은 AWS 공식 문서를 참고하면 좋을 것 같습니다.
https://docs.aws.amazon.com/ko_kr/codedeploy/latest/userguide/reference-appspec-file.html
appspec.yml 파일 생성
version: 0.0
os: linux
files:
- source: /
destination: /home/ec2-user/jenkins-test
overwrite: yes
hooks:
BeforeInstall:
- location: scripts/clean.sh
timeout: 300
runas: ec2-user
ApplicationStart:
- location: scripts/start.sh
timeout: 300
runas: ec2-user
script 생성
clean.sh
#!/bin/bash
# 기존의 node_modules와 package-lock.json을 삭제하여 충돌 방지
echo "Cleaning up old dependencies..."
# 기존 디렉토리와 파일 삭제
sudo rm -rf /home/ec2-user/jenkins-test/node_modules
sudo rm -f /home/ec2-user/jenkins-test/package-lock.json
start.sh
#!/bin/bash
cd /home/ec2-user/jenkins-test
pm2 stop all
pm2 start dist/main.js
* 만약 pm2를 찾지 못한다는 에러가 뜬다면 start.sh를 아래와 같이 수정
#!/bin/bash
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This loads nvm bash_completion
sudo npm install -g pm2
cd /home/ec2-user/jenkins-test
pm2 stop all
pm2 start dist/main.js
https://stackoverflow.com/questions/46048453/aws-codedeploy-command-not-found
동작 확인
1. 정상적으로 동작하는지 확인을 위해 로컬에서 Github로 코드를 push 해봅니다.
2. Jenkins에서 Github webhook을 트리거로 반응하여 동작합니다. Jenkins에서 Test와 Build 가능 여부, lint 등을 검사하고 나서, 빌드된 코드를 zip 형태로 S3로 보냅니다.
3. S3에서 객체를 확인할 수 있고, CodeDeploy 콘솔에서도 성공 유무를 확인할 수 있습니다.
4. EC2의 공인 IP 주소로 접속해 보면 정상적으로 배포된 것 확인
그동안 Beanstalk을 써서 배포할 때 잊고 있었던 서버 설정을 오랜만에 만지다 보니, 권한이나 shell script 설정 등 손이 많이 가는 작업이었습니다. 비슷하게 설정 중이신 많은 분들께 도움이 되었으면 합니다 :)