Notification
새로운 알림이 없습니다.

[CI/CD] GCP VM Deploy Guide with Jenkins CI/CD(3)

GCP VM Deploy Guide with Jenkins CI/CD

  • Agenda
    • Teams Webhook with Jenkins Configuration
    • Jenkins Pipeline Script ( VM CI/CD )
  • Next Actions
  • Reference Link

Teams Webhook with Jenkins Configuration

Teams에서 Jenkins에서 발생한 배포 로그등을 보내기 위해 Webhook을 Teams에서 구성하였습니다.
Jenkins에서 Teams Webhook 사용을 위해선 Office 365 Plugin 설치가 사전에 필요하니 참고 부탁드립니다.
(*해당 링크는 Reference Link에 공유해두었습니다. )

  • Teams Incoming Webhook Configuration

    • Connector 구성

      • Incoming Webhook 구성을 선택합니다.

    • Connector Settings
      • Naming 및 Icon Image 지정후 만들기를 진행합니다.

    • Incoming Webhook URL

Webhook 구성이 완료되었으며, 해당 URL은 다음 Agenda에 있는 Pipeline Script에서 사용할 예정입니다.
Clip Board에 URL을 복사해두는것을 추천드립니다.

Jenkins Pipeline Script( VM CI/CD )



CI/CD Architecture는 이전 (2)의 글에서 설명을 드린것 같습니다.
위 이미지는 Pipeline Step별 설명을 위해 첨부 하였으니 참고 부탁드립니다 :) 

단계별로 Jenkinsfile의 Pipeline에 대해서 아래와 같이 알아보겠습니다.
(*Pipeline에서 Commit Message에 "Rollback"이라는 문구가 없다면 순차적으로 진행될 것이고, 반대의 경우, Rollback Version Step으로 바로 이동됩니다.)



1.Checkout SCM
stage('Checkout Code') {
steps {
checkout scm
}
}
  • checkout scm은 Source code를 pull해서 Checkout 하는 과정입니다.
    • SCM에 저장된 소스코드를 Project 작업 공간으로 Copy
    • Jenkinsfile에서 Project에서 사용중인 SCM 시스템에 따라 해당 SCM을 사용 가능
      • Git, Subversion, Mercurial등 다양한 SCM 시스템과 통합 가능


2. Packer Build(Golden Image)
stage('Packer Build') {
when {
// 커밋 메시지에 "rollback"이 포함되어 있지 않을 때만 실행
        expression {
def commitMessage = sh(
script: 'git log --format=%B -n 1',
returnStdout: true
).trim().toLowerCase()
return !commitMessage.contains('rollback')
         }
     }
steps {
script {
try {
echo 'Building the Application...'
// Simulate a successful build
sh '''pwd;
packer build template.json
'''
echo 'Build the Application with Packer Successs....'
} catch (Exception e) {
currentBuild.result = 'FAILURE'
error("Build failed: ${e.getMessage()}")
}
}
}
}
  • SCM Check이후 Packer Golden Image를 구성합니다.
    • Packer에서 template.json 파일은 이미지 생성 프로세스를 정의하는 설정 파일입니다.
    • template.json은 일반적으로 아래 3가지 항목에 대해서 수행하게 됩니다.
      • Image 설정
        • 어떤 종류의 이미지를 빌드할 것인지, 기본 이미지가 어떤 것인지에 대한 정의
      • Builder 정의
        • Packer는 다양한 Builder를 지원하며, 각 빌더는 특정 클라우드(AWS,Azure,GCP) 또는 가상화 플랫폼과 통합되어 해당 환경에서 이미지를 빌드
      • Provisioner 설정
        • Image Build 후에도 Configuration Management 또는 Provisioning 단계가 필요한 경우, Provisioner 설정을 포함시킬수 있습니다.
          • Shell Script, Ansible등이 포함될수도 있습니다.
      • 변수 및 사용자 정의
        • 동적으로 값을 설정하거나 조작 할 수 있으며, Image Build Process를 다양한 환경에 맞게 사용자 정의 할수도 있습니다.
  • Golden Image 생성은 GCP에서 Instance Template 배포를 위해 사용할 예정입니다.


3. Instance Template(Launch Template) 생성  
stage('Create Instance Template') {
when {
// 커밋 메시지에 "rollback"이 포함되어 있지 않을 때만 실행
expression {
def commitMessage = sh(
script: 'git log --format=%B -n 1',
returnStdout: true
).trim().toLowerCase()
return !commitMessage.contains('rollback')
}
}
steps {
script {
// ImageId(Packer), timestamp(생성 시각)
def imageId = sh(script: """
gcloud compute images list \\
--format 'value(name)' \\
--filter='family=${params.IMAGE_FAMILY}'
""", returnStdout: true).trim()
def timestamp = new Date().format("yyyyMMddHHmmss")
def prodtemplateName = "${params.PROD_TEMPLATE_BASE_NAME}-${timestamp}"
def devtemplateName = "${params.DEV_TEMPLATE_BASE_NAME}-${timestamp}"
def projectId = "$PROJECT_ID"
// Python App Build를 위한 Startup Script
def metadataStartupScript = '''
#!/bin/bash
sudo pip3 install -r requirements.txt
nohup /usr/bin/gunicorn --bind 0.0.0.0:80 app:app --daemon &
'''
def setProjectId= """
gcloud config set project ${env.PROJECT_ID}
"""
// Cloud SDK를 활용하여 Instance Template 생성하는 Command
def createTemplateCmd = """
gcloud compute instance-templates create ${devtemplateName} \\
--project=${env.PROJECT_ID} \\
--machine-type=${params.MACHINE_TYPE} \\
--network-interface=nic-type=GVNIC,subnet=${params.VPC_SUBNET} \\
--metadata=enable-oslogin=true,startup-script='${metadataStartupScript}' \\
--maintenance-policy=MIGRATE \\
--provisioning-model=STANDARD \\
--service-account=${params.SERVICE_TP_ACCOUNT} \\
--scopes=https://www.googleapis.com/auth/cloud-platform \\
--region=${env.REGION} \\
--tags=${params.VPC_FIRWALL_TAG} \\
--create-disk=auto-delete=yes,boot=yes,device-name=dev-device-demo-${timestamp}, \
image-family=projects/${env.PROJECT_ID}/global/images/family/${params.IMAGE_FAMILY}, \
mode=rw,size=50,type=pd-balanced \
--no-address \\
--no-shielded-secure-boot \\
--shielded-vtpm \\
--shielded-integrity-monitoring \\
--labels="${params.SERVICE_LABEL}" \\
--reservation-affinity=any
"""
try {
echo 'Create Instance Template......'
// Google Coud Config Set Project ID
gcloudConfigSetProject(env.PROJECT_ID)
// Creating a GCP Instance Template based on the baked image
sh createTemplateCmd
echo 'Template Create Success....'
} catch (Exception e) {
currentBuild.result = 'FAILURE'
error("Build failed: ${e.getMessage()}")
}
}
}
}
  • Packer Image가 구성이 완료되었다면, 만들어진 Image로 GCP MIG(Managed Instance Group)에 배포를 위한 Instance Template 생성을 진행합니다.
  • 해당 Step에서는 Instance Template을 운영/개발계 각각 지정한 환경에 따라 배포를 합니다.
    • 현재는 개발계 배포를 위한 Template 생성만 cmd에 정의되어있으나, 각 branch별로 {{env}templateName}에서 env만 변경해서 배포해주면 됩니다.


4. Approval Request & Deployment Approval
stage('Send Approval Request') {
when {
// 커밋 메시지에 "rollback"이 포함되어 있지 않을 때만 실행
expression {
def commitMessage = sh(
script: 'git log --format=%B -n 1',
returnStdout: true
).trim().toLowerCase()
return !commitMessage.contains('rollback')
}
}
steps {
script {
currentBuild.description = "Please review the deployment plan and provide your approval."
sendTeamsApprovalAndProcess(currentBuild.description)
}
}
}
stage('Deployment Approval') {
when {
// 커밋 메시지에 "rollback"이 포함되어 있지 않을 때만 실행
expression {
return !commitMessage.contains('rollback')
}
}
steps {
script {
def envType = env.BRANCH_NAME == 'main' ? '/^prod-.*/' :
env.BRANCH_NAME == 'stg' ? '/^stg-.*/' :
env.BRANCH_NAME == 'dev' ? '/^dev-.*/' :
env.BRANCH_NAME == 'feature' ? '/^feature-.*/' :
'Unknown'
           def approverId = input(
            message: "Deployment approved to ${envType}. Enter your name:",
            parameters: [
                string(
                    defaultValue: '',
                    description: 'Your Name',
                    name: 'name'
                    )
                ]
            )
echo "Deployment approved to ${envType} by ${approverId}."
}
}
}
  • Approval Request
    • 배포 과정이 진행되어가고 있으며, 승인 절차 들어가기전에 Teams로 Notification을 보내게 됩니다. 

      • Author(배포를 진행한사람) 과 Deployment URL을 알려주며, Jenkins에서 승인하도록 MessageCard가 생성됩니다.
  • Deployment Approval
    • 아래 이미지처럼 어떤 Branch에 Deploy를 하는 주체가 어떤 사람인지에 대해 확인합니다.



5. Deploy OR Rollback
stage('Deploy or Rollback') {
when {
// "Deploy or Rollback" 스테이지는 항상 실행
expression { true }
}
steps {
script {
def userInput = input(
message: 'Select Action: Deploy or Rollback?',
parameters: [choice(
name: 'ACTION',
choices: ['Deploy', 'Rollback'],
description: 'Select Deployment Action'
)]
)
def action = userInput

echo "action ${action}"

if (action == 'Deploy') {
// Deploy 작업을 위한 처리
def targetInstanceGroup
def updateVersion

if (env.BRANCH_NAME == 'main' || env.GIT_TAG ==~ /^prod-.*/) {
echo "Deploying to production..."
targetInstanceGroup = PROD_INSTANCE_GROUP_NAME
} else if (env.BRANCH_NAME == 'stg' || env.GIT_TAG ==~ /^stg-.*/) {
echo "Deploying to staging..."
targetInstanceGroup = STG_INSTANCE_GROUP_NAME
} else if (env.BRANCH_NAME == 'dev' || env.GIT_TAG ==~ /^dev-.*/) {
echo "Deploying to Development...."
targetInstanceGroup = DEV_INSTANCE_GROUP_NAME
} else {
error("Unsupported branch or tag: ${env.BRANCH_NAME} / ${env.GIT_TAG}")
currentBuild.result = 'FAILURE'
return
}

try {
updateVersion = getLatestTemplateVersion()
echo "Version Rolling Update.... version : $updateVersion"
gcloudConfigSetProject(env.PROJECT_ID)
startManagedInstanceGroupUpdate(targetInstanceGroup, updateVersion)
} catch (Exception e) {
currentBuild.result = 'FAILURE'
error("Build failed: ${e.getMessage()}")
}
}
  • Stage에 대한 Code는 위 내용과 같습니다. 
    • Deploy or Rollback을 해야하는 Branch가 없다면 Pipeline이 자동으로 예외처리 되며 종료됩니다.
  • Deploy OR Rollback? 
    • 해당 과정은 Jenkins에서 진행되며 아래 이미지에서 더 상세하게 확인하실수 있습니다.



      • Deploy OR Rollback을 선택하는 단계이며, Deploy를 선택한다면 개발(DEV) MIG에 바로 배포되며, Rollback을 선택하게되면, Rollback Version을 아래와 이미지에서 보시는것과 같이 선택하실수 있게됩니다.


        • 총 3개의 버전을 선택할수 있도록 만들어두었으며, Rollback Version을 늘리기 위해선 getRollbackVersion() 함수에서 tail 명령어를 -3f로 되어있는것을 -[RANDOM_NUMBER] 형태로 변경해주면 됩니다.
          • 해당 내용은 Reference GitHub을 참고해주시면 됩니다.



6. Deploy OR Rollback Result



  • Pipeline이 정상적으로 Deploy되거나 Rollback 됐을 경우에 위와 같은 성공 메시지를 받을수 있습니다.
  • Demo로 올려둔 App의 Button이 Deploy 하면서 이전과 달라진것을 확인할 수 있습니다.


    • Before



    • After

이로써 간단하게 Jenkins를 통해 Python App의 Version을 Update하는 배포 과정까지 확인해봤습니다.

Next Actions

해당 글을 통해 Jenkins Pipeline Script 동작 과정 관련해서 학습하였습니다.
다음 글에서는 해당 Pipeline을 고도화 시키거나, 현재 VM으로 배포된 App을 Container형태로 Containerization하여 배포해보는 과정을 해보려고 합니다.

끝으로 이 포스팅을 보시는 분들이 관련 정보를 얻으시는데 도움이 되었으면 합니다. 

Reference Link

CI/CD
Stan Cloud
Stan Cloud
An avid cloud engineering Fan
대화 참여하기
댓글 쓰기