1. Poll SCM
기존의 item의 구성을 클릭합니다.
Poll SCM을 선택합니다.
여기서 schedule이 나올 텐데 * * * * *을 입력합니다.
크론 표현식을 뜻하며 자세한 건
https://zamezzz.tistory.com/197
위의 블로그 글을 읽는 걸 추천드립니다. 원래는 * * * * *가 이상적이지 않지만, 토이 프로젝트이고 즉각적인 결과를 얻고 싶기에 위의 크론 표현식을 사용했습니다.
Github master branch에 push 할 경우 자동으로 빌드가 되는지 확인하겠습니다.
※ 참고로 git branch에 default로 master라고 나왔을 텐데 언제든지 자기가 원하는 branch에 push 될 때 Jenkins가 자동으로 빌드할지 설정할 수 있습니다.
github에 push를 했더니
Jenkins에서 자동으로 빌드하는 걸 보실 수 있습니다.
일정 시간이 지났더니 정상적으로 빌드가 된 걸 확인하실 수 있습니다.
2. Vue 빌드
기존의 자바를 gradle로 빌드했다면 vue는 npm으로 빌드하겠습니다.
일반적으로 로컬 환경에서와 같이 똑같이 빌드하려면 npm install 명령어로 필요 모듈을 설치한 후, npm run build 해서 dist 폴더가 생성되는 과정을 거쳐야 합니다.
이때 환경은 node가 설치되어야 합니다. 이를 위해 Jenkins에 해당 tool을 등록해줘야 합니다.
플러그인에 들어가서 NodeJS를 설치해줍니다.
Installed에 NodeJS Plugin이 설치됐다면 정상적으로 된 것입니다.
Global Tool Configuration에 들어갑니다.
NodeJS 환경에서
기존 로컬 환경의 NodeJS 환경과 맞춰줍니다.
Item을 생성합시다.
vue 소스코드가 있는 github URL을 입력해줍니다.
Build Steps에서 Execute NodJS script를 선택.
이전 Configuration에 설정한 이름을 선택.
Add build step을 선택해서 Execute shell을 선택합니다.
아이템이 저장되는 workspace의 프로젝트에 cd를 하고, npm install과 npm run build 명령어를 입력합시다.
실행 버튼을 누르면
정상적으로 빌드된 것을 확인할 수 있습니다.
실제 폴더 위치에 들어가 보면
Node가 설치된 걸 볼 수 있고, dist라는 빌드된 폴더가 생성된 걸 확인할 수 있습니다.
3. publish over ssh 설정
Jenkins Server, Docker 서버, Ansible 서버, SonarQube 서버 등. 여러 단계의 EC2 서버를 만들어서 각각의 파이프라인을 구축하는 게 이상적입니다.
하지만 이렇게 하면 돈이 많이 들기 때문에 저는 2가지 EC2 서버밖에 준비를 안 했습니다.
Jenkins 서버에서 빌드한 뒤에 publish over ssh를 사용해서 서비스 서버에 빌드한 결과물들을 옮기도록 하겠습니다.
Publish Over SSH plugin 을 사용하면 원격 서버에 SSH를 통한 배포를 할 수 있습니다.
Jenkins 관리에서 플러그인 관리를 클릭합니다.
publish over ssh 플러그인을 설치해 줍니다.
Installed에서 publish over ssh를 검색해서 위의 사진처럼 나오면 정상적으로 설치가 된 것입니다.
1) SSH KEY 생성
ssh key를 생성하고, 생성된 ssh key를 서버에 등록하면 해당 서버에 접속하려는 계정의 비밀번호 입력 없이 ssh 접속이 가능합니다.
Jenkins 서버에서 Ssh-keygen -t -rsa 명령어로 key를 생성합니다.
패스워드 없게 지정. (모두 엔터)
Ssh-rsa 키들을 복사합니다.
배포 서버(서비스 서버)의 ssh에 접속하고 /.ssh 폴더에 들어가서 ls -al 명령어를 칩니다.
vim으로 authorized_keys를 수정하고,
기존 Jenkins 서버의 ssh-rsa 키 값을 붙여 넣어줍니다.
2) SSH IP 연결
서비스 서버에서 Jenkins 서버가 연결된 걸 ping 명령어로 확인해 보면
연결이 안 되는 것을 볼 수 있습니다.
ec2의 인바운드 규칙을 편집합시다.
Jenkins 서버에서 모든 ICMP - IPv4를 선택하고, 내가 적용한 보안 그룹(서비스 서버의 보안그룹)을 설정한다.
이 보안 그룹을 넣게되면 서로 같은 VPC에 있는 보안 그룹 상에서는 통신이 가능해집니다.
Private ip의 연결 시도를 하면 연결이 되는 걸 볼 수 있습니다.
배포 서버의 private ip를 확인해 봅시다.
ssh 계정명@privateip 명령어로 jenkins 서버에서 서비스 서버의 ssh 접속을 해보려고 했으나...
접속이 계속 안 됐습니다... 계속 구글링을 해보고 방화벽을 허용해 봤으나 안 됐습니다.
멘붕에 빠질 뻔하다가...
제가 예전에 ec2 인바운드 규칙을 수정할 때, 타인이 제 ssh에 들어가지 못하게끔 제 ip만 ssh에 접속하게 설정한 순간이 떠올랐고
서비스 서버의 인바운드 규칙에서 SSH에 접속하려고 하는 ip(Jenkins 서버의 private IP)를 추가해 줬더니. 정상적으로 접속이 됐습니다.
그러면 이전에 timeout 된 것이
정상적으로 접속된 것을 확인할 수 있습니다.
나가려면 exit 명령어를 입력해야 합니다.
3) Jenkins key 등록
Jenkins서버의 ssh에 접속해서. ssh 폴더에 들어간 다음 cat id_rsa로 private key들을 복사합시다.
시스템 설정에 들어가서
publish over ssh 항목에서 이전에 복사한
key값들을 붙여 줍니다.
Name에는 임의로지정해 주시고, Hostname에는 연결할 private ip, Username에는 계정 이름을 입력, Remote Directory에는 현재 디렉토리를 뜻하는 . 를 입력합시다. Apply하고 저장을 하면 기본적인 세팅은 끝났습니다.
3. Spring boot - publish over ssh
이제 이전에 만들어진 vue 프로젝트의 구성을 눌러서 수정을 하고
빌드 후 조치에서 send build artifacts over ssh를 선택합니다.
SSH-Server에는 이전에 publish over ssh에서 설정한 것을 선택.
Source files 경로는 workspace 상대 경로로 적어야 합니다. workspace를 확인했을 때 상위 폴더가 하나 더 있다면 Source files 경로를 '상위폴더/build/libs/*.jar' 로 해야 합니다.
저는 build 폴더의 libs 폴더의 .jar 확장자 파일들을 옮기고 싶었고, 이를 위해 Remove prefix에 build/libs/를 넣었습니다.
Remote directory에는 서비스 서버(배포 서버)의 remote 폴더에 빌드된 파일들을 옮기려고 합니다. Apply 하고 저장을 누른 다음에 실행 버튼을 누른다면
서비스 서버의 Remote 폴더에 정상적으로 이동한 것을 알 수 있습니다.
4. Vue - publish over ssh
Vue 빌드 파일들도 /** 표현으로 폴더 전체를 이동시킬 수 있습니다.
dist 폴더 내용들을 서비스 서버의 frontend 경로에 이동시켜 보겠습니다.
확인해 보면 정상적으로 옮겨진 것을 볼 수 있습니다.
이제 Exec command를 이용해서 cp -r /home/ubuntu/frontend/dist 폴더의 파일들을 /var/www/html에 이동시키겠습니다.
(/var/www/html에 왜 이동시키는지 이해가 안 가신다면 이전에 올린 https://kjw1313.tistory.com/81 포스팅 확인)
자 이제 정상적으로 배포가 될지 확인하겠습니다.
기존에 k-hospital - 메인 페이지였지만, 변경 후 push를 한다면?
이렇게 PUSH 성공이라고 성공적으로 적용된 걸 확인할 수 있습니다.
이렇게 프론트 엔드에서는 손쉽게 파일을 옮기는 것만으로 변경이 되지만, 문제는 백엔드입니다.
5. Spring boot - publish over ssh 배포하기
publish over ssh로 정상적으로 빌드된 파일이 이동된 것을 확인했으니 이제 서버 배포를 해보겠습니다.
이를 위해 서비스 서버에서 쉘 스크립트를 생성해 줍니다.
기존 경로에서 vim backend_start.sh 명령어로 쉘 스크립트를 생성해 줍니다.
참고로 frontend에는 vue가 빌드된 dist 폴더가 있고, backend에는 springboot가 빌드된 .jar 파일이 있습니다.
echo "현재 구동 중인 애플리케이션 PID CHECK"
CURRENT_PID=$(ps -ef | grep java | grep hospital | awk '{print $2}')
echo "현재 구동 중인 애플리케이션 PID: {$CURRENT_PID}"
if [ -n ${CURRENT_PID} ]
then
sudo kill -9 ${CURRENT_PID}
sleep 10
fi
BUILD_ID=dontKillMe nohup java -jar /home/ubuntu/backend/hospital-0.0.1-SNAPSHOT.jar > nohup.out 2>&1 &
echo "배포 SUCCESS"
sudo service nginx restart
이 과정에서도 정말 헤매었습니다... 기존 쉘 스크립트와 다르게 Jenkins에서 인식하는 게 다르더군요. 5-6시간을 여기서 허비했습니다. 어려운 이유는 아래와 같습니다.
1) 까다로운 쉘 스크립트 인식
if [ -n "${CURRENT_PID}" ]도 처음에 인식이 안 돼서 ""를 없애더니 동작이 됐습니다.
2) child process
Jenkins는 빌드 과정을 끝낸 이후 jenkins 사용자로 실행한 child process를 모두 kill 하기에 nohup 같은 명령어가 계속 종료되는 현상이 발생. 정상적으로 배포가 안 돼서 상당히 애를 먹었습니다.
이를 방지하기 위해 nohup 명령어 앞에 BUILD_ID=dontKillMe를 붙였습니다.
3) 무한 로딩
SSH로 원격 스크립트를 실행하면 stdoutput이 닫히거나 timeout이 발생할 때까지 스크립트가 닫히지 않습니다. 그래서 script로 백그라운드 작업을 하면 모든 output을 redirect 해줘야 스크립트가 종료됩니다.
기존에는 > /dev/null 2>&1 명령어를 쓰지만, 스크립트의 출력 결과와 에러 내용을 /dev/null에 리다이렉션 시켜서 삭제해 버립니다.
출력 결과와 에러 내용을 보존하기 위해서 > nohup.out 2>&1 명령어로 출력 결과와 에러 내용을 nohup.out에 리다이렉션 시켰습니다.
쉘 스크립트를 작성했다면 이제 Jenkins item의 구성에 들어가서
Exec command를 사용해 서비스 서버의 쉘 스크립트를 실행합니다.
그리고 github에 push를 해보면...
1) 변경 전
2) 변경 완료
정상적으로 변경된 걸 확인할 수 있습니다.
하지만 여기에 치명적인 문제가 있습니다. 프론트 엔드를 배포할 때는 상관이 없지만, 서버를 배포하는 동안 스프링 부트는 일시적으로 종료 상태가 되어 서비스를 이용할 수 없습니다.
서버를 배포하는 동안에 서비스를 계속 유지하는 방법이 없을까요? 다음 3편에서는 서비스 중단 없는 배포에 대해 소개하겠습니다.
'DevOps > CI&CD' 카테고리의 다른 글
EC2에 SonarQube 설치 & Jenkins로 코드 분석하기 (2) | 2023.04.27 |
---|---|
Jenkins를 활용한 CI/CD 구축(4/4) - Pipeline 구축 (0) | 2023.04.27 |
Jenkins를 활용한 CI/CD 구축(3/4) - Docker를 활용한 무중단 배포 (0) | 2023.04.25 |
Jenkins를 활용한 CI/CD 구축(1/4) - EC2 서버 Spring boot 빌드 (0) | 2023.02.15 |