[CI&CD] EC2, Docker, Jenkins, React 프로젝트 CI/CD 구축
현재 진행 중인 구름톤 풀스택 과정에서 팀 프로젝트의 CI/CD를 구축해 보았습니다.
CI/CD 구축은 처음 해봐서 구글링을 통해 다양한 구축법을 조사한 결과
오성원님의 블로그를 중심으로 추가/변경하여 설계한 CI/CD 파이프라인을 기준으로 구축해 보았습니다!
OS는 Mac 환경입니다!
✦ CI/CD 파이프라인

✦ 프로젝트 배경
우리 홍삼팀에서는 로그인/회원가입 기능 + 게시물 CRUD 기능을 넣은 간단한 SNS를 만드는 프로젝트를 진행했다.
백엔드 개발자 3명은 Spring을 사용하고 프런트엔드 개발자 2명은 React를 사용했다.
각자 맡은 기능을 개인 branch에서 개발하고 develop branch에 merge를 했다.
develop branch에서 모든 코드를 합치고 회의를 통해 develop branch에서 main branch로
merge가 되면 webhook을 통해 main branch로 연결해 둔 CI/CD 파이프라인이 실행되도록 했다.
✦ Frontend CI 구축
📌 준비 사항
1. Jenkins용 EC2에 Docker와 Jenkins컨테이너 생성 (추후 포스팅 예정)
2. Frontend 배포용 EC2 생성
1. Frontend 배포 서버용 인스턴스에 Nginx 설치
1.1 Nginx 설치
sudo apt-get update
sudo apt install nginx
설치가 잘 되었는지 확인하려면 구글 등 검색창에 public ip를 입력해 보면 된다. (인바운드 규칙 http, https 열어두기)

// nginx 서버 상태 확인
sudo systemctl status nginx
// nginx 재시작
sudo systemctl restart nginx
1.2 기본 설정 파일 제거
sudo rm /etc/nginx/sites-available/default
sudo rm /etc/nginx/sites-enabled/default
nginx를 설치하면 /var/www/html 안에 있는 기본 html 파일로 연결해 주는 default 파일이 있는데
나의 설정 파일과의 충돌에 대비해서 미리 삭제해준다.
1.3 새로운 nginx 설정 파일 생성
nginx가 내가 만든 새로운 html 파일을 로드하도록 하기 위해 1.2에서 지워준 /etc/nginx/sites-available 이곳에
새로운 파일을 만들어준다. (여러개의 설정 파일 생성 가능)
sudo vi /etc/nginx/sites-available/project.conf
project.conf 파일에 밑의 내용을 넣어준다.

server {
listen 80;
location / {
root /home/ubuntu/build;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
}
root 에는 내가 보여줄 파일이 담긴 경로를 적어준다.
nginx가 실제로 페이지를 로드할 때 참고하는 설정 파일은 /etc/nginx/sites-enabled 안에 있는 파일이므로
방금 만든 sites-available의 project.conf 파일을 sites-enabled의 심볼릭 링크로 연결해 준다.
ln -s {대상 원본 파일} {새로 만들 파일 이름}
sudo ln -s /etc/nginx/sites-available/project.conf /etc/nginx/sites-enabled/project.conf
설정을 마쳤으면 nginx를 재시작 해준다.
❗️nginx 설정 파일 설정 방법은 default를 그대로 이용하거나 새로 만들거나 html 파일은 /var/www/html 에 두는 등등
여러 가지 방법이 있으므로 헷갈리지 말고 경로, 이름 등을 잘 봐야 한다..!
2. Jenkins Item 생성 + git clone (backend 과정과 동일)
1.1 Jenkins Item 생성

jenkins 메인 화면에서 Item 생성을 클릭한 후 frontend용 파이프라인을 생성한다.
Item이름을 적고 Pipeline을 선택 후 OK 버튼을 누른다.
1.2 git clone 스크립트 작성

우선 체크하는 부분은 다 넘어가고 Definition은 Pipeline script로 선택한 후 git clone단계까지의 스크립트를 작성한다.
저장을 누르고 지금 빌드를 누르면 Jenkins 컨테이너에 git clone이 진행된다.
혹시 clone이 잘 됐나 확인하고 싶으면 Jenkins용 인스턴스에 접근해서
sudo docker exec -it {컨테이너 이름} /bin/bash
위의 명령어를 입력하면 Jenkins 컨테이너에 접속하게 되고
cd /var/jenkins_home/workspace
위의 파일로 이동해서 ls를 진행하면 Jenkins의 Item 이름으로 파일로 git clone 된 파일을 확인할 수 있다.
3. 빌드
3.1 Jenkins에 NodeJs Plugin 설치
gradle과는 다르게 빌드 과정에서 사용할 NodeJs Plugin을 미리 설치하고 설정해줘야 한다.

Jenkins 관리의 Plugins에서 위의 플러그인을 설치해 준다.
Jenkins 관리의 Tools에서 NodeJs 설정을 해줘야 한다.

본인의 프로젝트 NodeJs 버전 확인 후 설정하면 된다.
Name은 파이프라인 스크립트에서 사용할 예정이다.
3.2 빌드 스크립트 작성

git clone으로 다운로드한 우리 팀의 파일 구조는 hongsam_front 파일에서 react 파일인 frontend 파일을 build 해야 하기 때문에
frontend 디렉터리로 이동한 후 빌드를 진행하는 스크립트를 추가로 작성해 주었다.
git clone과 build 과정을 진행하는 과정까지 스크립트가 작성되었다.
pipeline {
agent any
stages {
stage('git clone') {
steps {
sh 'rm -rf hongsam_front hongsam_front@tmp'
git branch: 'main', url: 'https://github.com/seoyeoning/2023-hongsamSNS.git'
}
}
stage('F-build') {
steps {
dir("./frontend") {
nodejs(nodeJSInstallationName: 'NodeJS 18.16.0') {
sh 'npm install && npm run build'
}
}
}
}
}
}
sh 'rm -rf hongsam_front hongsam_front@tmp'
이 명령어로 CI를 진행할 때 전에 clone 했던 파일을 모두 지워주었다.
전에 생긴 파일을 지워주지 않으면 CI를 할 때마다 변경된 내용이 업데이트되는데
가끔 코드가 꼬일 때가 있어서 깔끔하게 삭제하고 다시 clone 하는 방법을 선택했다.
위에서 설치한 NodeJs 플러그인 버전 설정 관련 코드를 추가해야 빌드가 진행된다.
여기까지 작성한 후 지금 빌드를 누르면 빌드가 잘 되었는지 확인할 수 있다.

✦ Frontend CD 구축
CD 배포 과정에서는 Jenkins용 EC2 인스턴스의 jenkins 컨테이너에 빌드된 build 폴더를
프런트 배포용 EC2 인스턴스에 보내주는 과정이다.
따라서 우선 Jenkins 서버에서 백엔드 운영 서버로 통신을 해야 하므로 SSH 통신 설정을 하고 스크립트를 작성했다.
1. SSH 통신 설정 (백엔드와 동일)
1.1 SSH Agent Plugin 설치

1.2 1.2 credentials 생성

Jenkins 관리의 Credentials에서 새로운 credential을 생성하고 위의 양식처럼 작성한다.
ID : 스크립트에서 식별할 이름
Username : 기본으로 ubuntu
Private Key : 프런트 운영 서버에 접속할 때 사용하는 프런트 EC2의 pem 키를 모두 복사해서 넣어준다.
-----BEGIN RSA PRIVATE KEY-----(포함) 부터 -----END RSA PRIVATE KEY-----(포함) 까지 전부 복사한다.

2. CD (배포) 스크립트 작성
위에서 작성한 빌드까지의 스크립트에 CD 스크립트를 추가한 최종 CI/CD 스크립트이다.
pipeline {
agent any
stages {
stage('git clone') {
steps {
sh 'rm -rf hongsam_front hongsam_front@tmp'
git branch: 'main', url: 'https://github.com/seoyeoning/2023-hongsamSNS.git'
withCredentials([GitUsernamePassword(credentialsId: 'submodule_key', gitToolName: 'Default')]) {
sh 'git submodule update --init --recursive'
}
}
}
stage('F-build') {
steps {
dir("./frontend") {
nodejs(nodeJSInstallationName: 'NodeJS 18.16.0') {
sh 'npm install && npm run build'
}
}
}
}
stage('Compression') {
steps {
sh '''
rm -rf /frontend/node_modules
cd frontend
tar -cvf build.tar build
'''
}
}
stage('Deploy') {
steps {
sshagent(credentials: ['aws_key_front']) {
sh '''
ssh -o StrictHostKeyChecking=no ubuntu@{frontend ec2 public ip} uptime
scp /var/jenkins_home/workspace/hongsam_front/frontend/build.tar ubuntu@{frontend ec2 public ip}:/home/ubuntu
ssh -t ubuntu@{frontend ec2 public ip} ./deploy.sh
'''
}
}
}
}
}
Compression 단계
node-modules 파일을 삭제하고 프런트 서버로 전송할 build 파일은 tar로 압축한다.
Deploy 단계
우선 프런트 서버에 접속한 후
scp를 사용해서 압축해 둔 build.tar 파일을 프런트 서버의 /home/ubuntu로 복사해 준다.
마지막으로 프런트 서버에 원격 접속해서 deploy.sh 파일을 실행시켜 준다.
경로 등은 본인의 프로젝트에 맞게 수정해 주면 된다.
❗️git clone 단계에서 추가된 명령어
withCredentials([GitUsernamePassword(credentialsId: 'submodule_key', gitToolName: 'Default')]) {
sh 'git submodule update --init --recursive'
}
이 부분은 보안 관련 github의 서브 모듈을 추가한 부분으로 만약 서브 모듈이 없으면 삭제해 주면 된다.
서브 모듈 관련 포스팅은 추후에 따로 포스팅할 예정이다.
deploy.sh 파일은 프런트 인스턴스에 미리 생성해 놨다.

deploy.sh의 내용은 다음과 같다.

전송받은 압축파일 build.tar 파일을 압축 해제하고 nginx를 재시작하는 과정이다.
#!/bin/bash
tar -xvf build.tar
rm -rf build.tar
sudo service nginx restart
혹시 오류 나면 권한문제 확인해 보기.
chmod +x deploy.sh
여기까지 완성한 후 지금 빌드를 누르면 CI/CD 구축 성공이다!!

❗️현재는 Jenkins 내에서 지금 빌드를 눌러야 작동이 되는데
github main branch를 트리거로 작동하게 해주는 webhook은 추후 따로 포스팅할 예정이다.
❗️혹시 검색창에 프런트 ip 입력했는데 500 Internal Server Error 오류가 난다면
우선 nginx 로그를 확인해서 오류를 찾아본다.
sudo cat /var/log/nginx/error.log
내 경우에는 배포 파일 등의 실행 권한 문제였다.
ls -la /home/ubuntu/build
이 명령어로 권한이 잘 있나 확인해봐야 한다..
✦ 배운점
백엔드 배포에 이어서 프러트 배포 파이프 라인도 작성해 보았다.
nginx 설정 파일에 대해 그동안 대충 알고 있었는데
자세히 찾아보고 화면 로드에 대해 알게 되었다.