
1편에 이어서 2편을 작성하겠습니다.
Jenkins 서버 생성.#

NCP의 Server로 들어가서 Jenkins용으로 사용할 새로운 서버를 생성합니다.

새롭게 생성한 Jenkins서버에서는 Github의 webhook요청을 받을텐데 이를 허용하기 위해서 Github webhook의 요청 IP를 ACG에 설정 해야합니다.

위의 링크로 들어가서 hooks을 검색하고 4개의 ipv4주소를 inbound 규칙에 추가해줍니다.

그리고 생성한 Jenkins용 서버에 접속을 합니다.
https://www.jenkins.io/doc/book/installing/linux/#debianubuntu
위의 링크를 참고해서 Jenkins을 설치해줍니다.

sudo systemctl status jenkins
서버를 설치 후 위의 명령어 실행으로 초록색의 active(running)이 나타났다면 Jenkins 설치는 성공입니다.
그리고 나중에 Jenkins가 프로젝트를 빌드하기 위해서 본인이 프로젝트에 사용하는 JDK 버전에 맞게 JDK도 설치해 줍니다.
저는 Jenkins설치할 때와 동일하게 JDK17을 사용해서 별도로 설치하지는 않겠습니다.
Object Storage Bucket 생성#
위의 링크를 들어가서 버킷을 생성해줍니다.
Jenkins 서버에 AWS CLI 설치#
NCP의 Object Storage는 AWS S3위에서 만들어진 플랫폼이라 AWS S3의 CLI와 호완이 가능합니다.
위의 링크를 참고해서 설치까지만 해줍니다. 인증을 함께 해두어도 되지만, 나중에 SourceDeploy을 사용할 때도 NCP 인증정보가 필요해서 뒤로 미루겠습니다.
참고로 Ubuntu22.04는 기본으로 설치된 Python이 3.10 버전이라 AWS CLI을 사용하기 위해서는 python 3.8이하를 설치해주어야 합니다.
SourceDeploy 생성#
SourceDeploy을 이용하기 위해서는 SourceDeploy agent가 배포할려는 서버에 설치되어 있어야 합니다. 이 부분은 1편에서 알맞게 설치했을 것이라 생각하겠습니다.

SourceDeploy에 들어가서 배포 프로젝트 생성을 클릭합니다.

원하시는 이름으로 지정하고

배포 환경설정에서 배포 타겟을 Auto Scaling으로 선택합니다.
그리고 1편에서 생성한 Auto Scaling Group을 선택합니다. 저의 경우 이전에 만들었던 프로젝트라 Auto Scaling Group 이름이 1편과는 다른 점 양해바랍니다:)

배포 프로젝트가 생성되었으면 클릭 후 배포 시나리오를 생성합니다.


무중단 배포 전략을 선택합니다. Auto Scaling에 적용하는 무중단 배포 전략은 기본(Rolling) 또는 블루/그린 배포전략만 가능합니다. Canary배포 전략은 아마 ncloud kubernetes 에서 가능한 것으로 알고있습니다.
그리고 배포 후 기존의 Auto Scaling Group을 처리할 방법을 선택할 수 있는데 편의상 유지하도록 하겠습니다.

배포 파일을 가져올 위치를 선택합니다. 여기서 저는 Jenkins가 빌드 후 Object Storage에 빌드된 파일을 업로드할 것이기에 Object Storage을 선택합니다.

그리고 배포 파일을 선택하도록 하는데
이때 설정을 하다보니 배포파일을 미리 업로드 해놓을 필요가 있어서 zip으로 압축한 jar파일을 미리 올려두었습니다.
zip -r nutridiary.zip build/libs/nutridiary-0.0.1-SNAPSHOT.jar압축 명령어는 위의 명령어를 참고해 주세요!

각 단계 별로 명령어에 대한 설명을 하면
“배포전 실행”은 Blue Green 배포 방식으로인해 새롭게 생성된 Auto Scaling Group 서버에 적용됩니다. 서버가 새롭게 생성되었을 때 앞서 만들었던 init script가 실행되어 기존의 배포 전 jar파일이 실행되고 있을 것입니다. 따라서 이것을 중지시킬 필요가 있으며 새로운 jar 파일과의 중복 방지를 위해 기존의 jar파일을 삭제합니다.
kill -15 $(pgrep -f nutridiary-0.0.1-SNAPSHOT.jar) && rm /root/deploy/nutridiary-0.0.1-SNAPSHOT.jar두번째 “파일 배포”는 Object Storage에서 서버로 파일을 전송하는 단계입니다. object storage에서 zip으로 압축된 폴더 기준으로 본인의 jar 파일 위치를 정확히 적어주시고 서버에 저장할 jar파일의 위치를 지정해주세요.
세번째 “배포 후 실행"에서 이제 새로운 jar파일을 백그라운드에서 실행합니다.
nohup java -jar /root/deploy/nutridiary-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > /root/deploy/output.log 2>&1 &따라서 위의 명령어를 입력해 줍니다.

그리고 마지막으로 배포 시나리오를 생성합니다.
자 이제 다시 Jenkins로 가봅시다!
Github Access Token 발급#
Jenkins 서버에서 github repository에 접근할려면 access token을 발급받아야 합니다. 또는 젠킨스 서버에서 ssh키를 생성해서 인증하는 방법도 있는데, 여기서는 access token 형식으로 하겠습니다.


본인 깃허브 계정의 Settings에서 Developer Settings을 클릭해 줍니다.

우측의 Generate new token 버튼을 클릭해 줍니다.

access token명을 지정하고 repo와 admin:repo_hook 을 선택해줍니다.

그리고 하단에 Generate token을 선택해주면, 위의 토큰값이 나타납니다.
토큰 값은 한번만 나타나기 때문에 안전한 곳에 복사해두고, 이제 젠킨스에서 Github Repository에 접근할 수 있도록 Credential을 등록해야 합니다
Jenkins에 Github 토큰 등록#

이제 웹브라우저에서 “젠킨스서버의 공인ip주소:8080” 으로 접속해 봅니다.
그리고 위에 빨간 부분으로 나타난 url을 참고하여 Jenkins의 초기 비밀번호를 복사 붙여넣기를 합니다.

저는 첫번째 버튼을 선택했고, 필요한 플러그인을 모두 설치해 줍니다.

이후 회원가입을 진행하고
Jenkins 화면에 접속한다음 Jenkins 관리를 클릭하고 Credential 을 클릭합니다.

빨간 박스의 global을 클릭해 줍니다.

오른쪽 상단의 Add Credential을 클릭해 줍니다.

kind는 Username with password을 선택해주고 Username에 github 아이디, Password에 발급받은 토큰값을 붙여넣습니다. ID 값은 임의로 지정합니다.
추가로 NCP 정보도 Credential에 미리 등록합니다.#

링크로 들어가서 Access key와 Secret key을 복사합니다.

그리고 kind는 Secret text로 선택하고 Secret에 각각 복사한 NCP의 값을 입력하고 ID에는 본인이 임의로 정합니다.
나중에 Jenkins pipeline script에서 해당 ID로 환경변수를 넣어줄것을 미리 참고해 주세요!

모두 등록하면 위의 이미지와 같습니다.
사실 이렇게 NCP의 정보를 Jenkins 서버에 저장하는 것은 위험할 수 있습니다. 만약 Jenkins 서버가 해킹당한다면 Access값들이 모두 털리는 것이죠.
그래서 AWS 같은 경우 AMI 기반의 Role을 가지고 Jenkins서버에서 AWS 서비스에 접속하는 것을 허용할 수 있습니다. 하지만 NCP는 문의 결과 그런게 지원이 안되는 군요ㅠ

Github webhook 설정#

젠킨스 관리에 들어가서 Plugins 을 클릭해 봅니다.

github integration 플러그인을 검색해서 선택하고 설치해줍니다.

이제 Github 프로젝트의 repository에서 Settings에 들어갑니다. 그리고 Webhooks을 클릭하고 Add webhook을 클릭합니다.

Payload URL에 본인의 Jenkins 서버 주소와 뒤에 “/github-webhook/ “을 추가해줍니다.
이제 본인의 깃허브 레포지토리에서 커밋이 발생하면 Jenkins 서버로 webhook이 발생합니다.
Jenkins 파이프라인 구성#

Jenkins 서버에 접속해서 새로운 Item을 클릭해줍니다.

이름을 지정하고 Pipeline을 선택해줍니다.


본인의 Github repository 주소를 적어줍니다. 안 적어줘도 되지만 입력해 놓으면, 오른쪽 이미지 처럼 Jenkins 프로젝트 탭에서 프로젝트 Github repository로 이동하는 버튼이 생성됩니다.


각 항목들을 위와 같이 구성하고 Credential은 앞에서 등록했던 것을 사용합니다.
빌드할 브랜치는 main으로 작성해 줍니다.

그리고 마지막으로 Script Path는 Jenkinsfile로 적어줍니다.
Jenkins script 작성#
pipeline {
agent any
environment {
API_ACCESS_KEY = credentials('ncp-api-access-key')
API_SECRET_KEY = credentials('ncp-api-secret-key')
}
stages {
stage('Prepare') {
steps {
echo 'Preparing...'
git branch: 'main', url: 'https://github.com/f-lab-edu/nutri-diary.git'
}
post {
success {
echo 'Preparation completed successfully!'
}
failure {
echo 'Preparation failed!'
}
}
}
stage('Build') {
steps {
sh './gradlew clean build'
}
post {
success {
echo 'Build completed successfully!'
}
failure {
echo 'Build failed!'
}
}
}
stage('Upload') {
steps {
sh 'chmod +x ./script/upload.sh'
sh './script/upload.sh'
}
post {
success {
echo 'Upload completed successfully!'
}
failure {
echo 'Upload failed!'
}
}
}
stage('Deploy') {
steps {
sh 'chmod +x ./script/deploy.sh'
sh './script/deploy.sh'
}
post {
success {
echo 'Deploy completed successfully!'
}
failure {
echo 'Deploy failed!'
}
}
}
}
post {
success {
echo 'Build, Test, and Deploy completed successfully!'
}
failure {
echo 'Build or Deploy failed!'
}
}
}첫 라인에서는 Jenkins에 등록했던 NCP의 Credential 정보를 환경변수로 넣어줍니다.
그리고 각 단계별로 빌드 후 upload.sh, deploy.sh을 실행합니다. (upload.sh, deploy.sh 스크립트는 아래에 있습니다.)

Jenkins pipeline script는 위와 같이 작성하고 파일명은 “Jenkinsfile” (확장자 없습니다) 로 지정합니다. 그리고 Jenkinsfile은 프로젝트 최상단에 두고 각 upload.sh, deploy.sh 은 script 폴더를 만들고 그 안에 두었습니다.
upload.sh#

#!/bin/bash
zip -r nutridiary.zip build/libs/nutridiary-0.0.1-SNAPSHOT.jar
aws configure set aws_access_key_id ${API_ACCESS_KEY}
aws configure set aws_secret_access_key ${API_SECRET_KEY}
aws --endpoint-url=https://kr.object.ncloudstorage.com s3 rm s3://${your_bucket_name}/nutridiary.zip
aws --endpoint-url=https://kr.object.ncloudstorage.com s3 cp nutridiary.zip s3://${your_bucket_name}/nutridiary.zip먼저 빌드된 파일을 압축해줍니다.
이때 파일 압축 경로와 SourceDeploy명령어 경로를 유의해 주세요!
2~3 명령어는 환경변수로 등록한 accesskey와 secretkey로 인증을 수행합니다.
4~5 명령어는 각각 기존의 ObjectStorage에 있는 zip파일을 제거하고 새로운 zip 파일을 업로드합니다.
deploy.sh#
#!/bin/bash
# Naver Cloud Platform API 기본 설정
SOURCEDEPLOY_API_URL="https://vpcsourcedeploy.apigw.ntruss.com"
PROJECT_NAME="nutridiary"
# 헤더 설정 (API 인증)
get_auth_headers() {
local method="$1"
local uri="$2"
local api_timestamp=$(perl -MTime::HiRes -e 'printf("%d\n", Time::HiRes::time()*1000)')
local signature=$(generate_signature "${method}" "${uri}" "${api_timestamp}")
# 헤더를 명시적으로 배열로 관리하여 curl에 전달
headers=(
-H "x-ncp-apigw-timestamp: ${api_timestamp}"
-H "x-ncp-iam-access-key: ${API_ACCESS_KEY}"
-H "x-ncp-apigw-signature-v2: ${signature}"
)
}
# Signature 생성 함수 (API 호출에 필요)
generate_signature() {
local method="$1"
local uri="$2"
local time_stamp="$3"
local nl=$'\\n'
SIG="${method}"' '"${uri}"${nl}
SIG+="${time_stamp}"${nl}
SIG+="${API_ACCESS_KEY}"
SIGNATURE=$(echo -n -e "${SIG}"|iconv -t utf8 |openssl dgst -sha256 -hmac ${API_SECRET_KEY} -binary|openssl enc -base64)
echo "${SIGNATURE}"
}
# 1. 프로젝트 ID 가져오기
get_project_id() {
local project_name="$1"
local uri="/api/v1/project?projectName=${project_name}"
# 헤더 준비
get_auth_headers "GET" "${uri}"
# 프로젝트 목록 가져오기
response=$(curl -s -X GET "${SOURCEDEPLOY_API_URL}${uri}" "${headers[@]}")
# 프로젝트 ID 파싱
project_id=$(echo "${response}" | jq -r '.result.projectList[0].id')
# 에러 처리: project_id가 없을 경우 에러 메시지 출력
if [[ -z "${project_id}" || "${project_id}" == "null" ]]; then
echo "Error: 프로젝트 ID를 찾을 수 없습니다."
exit 1
fi
# project_id 리턴
echo "${project_id}"
}
# 2. 스테이지 아이디 가져오기 (프로젝트 ID 필요)
get_stage_id() {
local project_id="$1"
local uri="/api/v1/project/${project_id}/stage"
# 헤더 준비
get_auth_headers "GET" "${uri}"
# 스테이지 목록 가져오기
response=$(curl -s -X GET "${SOURCEDEPLOY_API_URL}${uri}" "${headers[@]}")
# 스테이지 ID 파싱
stage_id=$(echo "${response}" | jq -r '.result.stageList[0].id')
# 에러 처리: stage_id가 없을 경우 에러 메시지 출력
if [[ -z "${stage_id}" || "${stage_id}" == "null" ]]; then
echo "Error: 스테이지 ID를 찾을 수 없습니다."
exit 1
fi
# stage_id 리턴
echo "${stage_id}"
}
# 3. 시나리오 아이디 가져오기
get_scenario_id() {
local project_id="$1"
local stage_id="$2"
local uri="/api/v1/project/${project_id}/stage/${stage_id}/scenario"
# 헤더 준비
get_auth_headers "GET" "${uri}"
# 시나리오 목록 가져오기
response=$(curl -s -X GET "${SOURCEDEPLOY_API_URL}${uri}" "${headers[@]}")
# 시나리오 ID 파싱
scenario_id=$(echo "${response}" | jq -r '.result.scenarioList[0].id')
# 에러 처리: scenario_id가 없을 경우 에러 메시지 출력
if [[ -z "${scenario_id}" || "${scenario_id}" == "null" ]]; then
echo "Error: 시나리오 ID를 찾을 수 없습니다."
exit 1
fi
# scenario_id 리턴
echo "${scenario_id}"
}
# 4. 배포 시작 요청
start_deploy() {
local project_id="$1"
local stage_id="$2"
local scenario_id="$3"
local uri="/api/v1/project/${project_id}/stage/${stage_id}/scenario/${scenario_id}/deploy"
# 헤더 준비
get_auth_headers "POST" "${uri}"
response=$(curl -s -X POST "${SOURCEDEPLOY_API_URL}${uri}" "${headers[@]}")
# 응답에서 historyId 추출
history_id=$(echo "${response}" | jq -r '.result.historyId')
# 에러 처리: history_id가 없을 경우 에러 메시지 출력
if [[ -z "${history_id}" || "${history_id}" == "null" ]]; then
echo "Error: 배포 요청에 실패했습니다."
exit 1
fi
# 배포 성공
echo "배포가 시작되었습니다. History ID: ${history_id}"
}
# 실행 흐름
PROJECT_ID=$(get_project_id "${PROJECT_NAME}")
STAGE_ID=$(get_stage_id "${PROJECT_ID}")
SCENARIO_ID=$(get_scenario_id "${PROJECT_ID}" "${STAGE_ID}")
## 배포 시작
start_deploy "${PROJECT_ID}" "${STAGE_ID}" "${SCENARIO_ID}"deploy.sh에서 실행하는 SourceDeploy API의 자세한 정보는 위의 링크를 참고해 주세요!
추가로 위의 script에서 json값을 파싱하기 위해서 “jq”라이브러리를 사용하는데 젠킨스 서버에 접속하셔서 apt-get install jq 로 설치해줍니다!
마무리#
이제 모든 것이 끝났습니다. 테스트해보기 위해서 github 프로젝트에 임의의 커밋을 적용하면 NCP의 SourceDeploy을 활용한 무중단 배포까지 적용될 것입니다.
혹시 부족한 부분이 있으면 댓글로 남겨주세요!
