Files
jenkins-pipeline/SCM/部署镜像/s5/DM_s5_lessie_agent
2026-02-11 14:55:11 +08:00

294 lines
13 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@NonCPS
def getHiddenParams() {
def results = [:]
def action = currentBuild.rawBuild.getAction(hudson.model.ParametersAction.class)
if (action) {
results.user = action.getParameter("ACTUAL_USER")?.getValue()
}
return results
}
// 飞书通知函数 - 带重试机制,防止限频导致 Job 失败
def sendLarkNotification(String robot, String type, String title, List text) {
def retryCount = env.LARK_RETRY_COUNT.toInteger()
def retryDelay = env.LARK_RETRY_DELAY.toInteger()
for (int i = 0; i < retryCount; i++) {
try {
lark(
robot: robot,
type: type,
title: title,
text: text
)
echo "飞书通知发送成功"
return true
} catch (Exception e) {
echo "飞书通知发送失败 (第 ${i+1}/${retryCount} 次): ${e.message}"
if (i < retryCount - 1) {
echo "等待 ${retryDelay} 秒后重试..."
sleep(retryDelay)
}
}
}
echo "⚠️ 飞书通知发送失败,已重试 ${retryCount} 次,但不影响部署结果"
return false
}
pipeline {
agent any
parameters {
imageTag(
name: 'IMAGE_NAME',
description: 'sit 仓库镜像 (除非输入 CUSTOM_IMAGE, 否则使用这里的)',
registry: 'https://uswccr.ccs.tencentyun.com',
image: 'lessiesit/lessie-sourcing-agents',
credentialId: 'dxin_img_hub_auth',
filter: '.*',
defaultTag: '',
tagOrder: 'DSC_VERSION',
verifySsl: true
)
string(
name: 'CUSTOM_IMAGE',
defaultValue: '',
description: '手输完整镜像<registry>/<namespace>/<repo>:<tag>,例如: uswccr.ccs.tencentyun.com/lessiesit/lessie-sourcing-agents:v0.0.1 (填充后,则忽略镜像仓库选择)'
)
booleanParam(
name: 'ROLLBACK_VERSION',
defaultValue: false,
description: '是否快速回滚上一个版本?'
)
}
environment {
// ========== 超时配置 ==========
ROLLBACK_TIMEOUT = "120s" // 回滚超时时间
DEPLOYMENT_TIMEOUT = "300s" // 部署超时时间
// ========== 飞书通知配置 ==========
LARK_ROBOT = "4b8d66d0-c0f0-4587-b0e5-cff772cb3046" // 飞书机器人ID
LARK_RETRY_COUNT = "3" // 飞书通知重试次数
LARK_RETRY_DELAY = "2" // 飞书通知重试间隔(秒)
// ========== K8s 配置 ==========
KUBECONFIG = credentials('k8s-test-config-admin') // k8s 凭证 ID, Jenkins 中配置的凭证名称
Deployment_name = "s5-lessie-agents"
K8s_namespace = "sit"
Pod_container_name = "lessie-agents"
Pod_environment = "s5"
}
stages {
stage('回滚上版') {
when { expression { return params.ROLLBACK_VERSION == true } }
steps {
script {
sh """
echo "开始回滚到上一个版本"
kubectl rollout undo deployment/${Deployment_name} -n ${K8s_namespace}
echo "回滚中"
kubectl rollout status deployment/${Deployment_name} -n ${K8s_namespace} --timeout=${ROLLBACK_TIMEOUT}
echo "查看所使用的镜像"
kubectl get deployment ${Deployment_name} -n ${K8s_namespace} -o=jsonpath='{.spec.template.spec.containers[*].image}'
echo "回滚完成"
"""
}
}
}
stage('决定镜像') {
when { expression { return params.ROLLBACK_VERSION == false } }
steps {
script {
// 调用上面的非 CPS 方法,安全拿值
def hidden = getHiddenParams()
env.ACTUAL_USER = hidden.user ?: (currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause')[0]?.userName ?: "系统触发")
env.ACTUAL_BRANCH = hidden.branch ?: "未知"
echo "触发用户: ${env.ACTUAL_USER}"
// 当前时间格式YYYY-MM-DD HH:MM:SS
env.currentTime = sh(script: 'date "+%Y-%m-%d %H:%M:%S"', returnStdout: true).trim()
def imageRegex = /^([a-zA-Z0-9.-]+)(:[0-9]+)?\/([a-zA-Z0-9._-]+)\/([a-zA-Z0-9._-]+):([a-zA-Z0-9._-]+)$/
if (params.CUSTOM_IMAGE?.trim()) {
echo "检测手输镜像格式: ${params.CUSTOM_IMAGE}"
def customImage = params.CUSTOM_IMAGE.trim()
// 验证镜像格式
def matcher = (customImage =~ imageRegex)
if (!matcher.matches()) {
error "CUSTOM_IMAGE 格式不正确,必须是 registry/namespace/repository:tag 格式\n" +
"示例: uswccr.ccs.tencentyun.com/lessiesit/apex:v1.0.0\n" +
"当前输入: ${customImage}"
}
echo "使用手输镜像: ${customImage}"
env.IMAGE_FULL_NAME = customImage
} else {
def img = "uswccr.ccs.tencentyun.com/${params.IMAGE_NAME}"
echo "使用 imageTag 插件镜像: ${img}"
env.IMAGE_FULL_NAME = img
}
}
}
}
stage('查询镜像') {
when { expression { return params.ROLLBACK_VERSION == false } }
steps {
script {
echo "查询镜像: ${IMAGE_FULL_NAME}"
def result = sh(returnStdout: true, script: """
/data/sh/get-image-labels-fast.sh \
"${IMAGE_FULL_NAME}" \
"100038894437" \
'h8H1o6Fd!HLXn' | awk '/^{/,/^}\$/'
""").trim()
if (result == "" || result == null) {
error("查询镜像 label 失败,请检查镜像是否存在或凭证问题")
}
echo "镜像 Label:\n${result}"
env.IMAGE_LABEL = result
}
}
}
stage('更新镜像') {
when { expression { return params.ROLLBACK_VERSION == false } }
steps {
script {
echo "保存当前镜像信息"
env.OLD_IMAGE_NAME = sh(
script: "kubectl get deployment ${Deployment_name} -n ${K8s_namespace} -o=jsonpath='{.spec.template.spec.containers[0].image}'",
returnStdout: true
).trim()
echo "更新前镜像: ${env.OLD_IMAGE_NAME}"
sh """
echo "更新Deployment: ${Deployment_name} 镜像为: ${IMAGE_FULL_NAME}"
kubectl set image deployment/${Deployment_name} ${Pod_container_name}=${IMAGE_FULL_NAME} -n ${K8s_namespace}
"""
}
}
}
stage('部署情况') {
when { expression { return params.ROLLBACK_VERSION == false } }
steps {
script {
echo "检测部署状态并验证新版本运行情况"
def rolloutStatus = sh(
script: "kubectl rollout status deployment/${Deployment_name} -n ${K8s_namespace} --timeout=${DEPLOYMENT_TIMEOUT}",
returnStatus: true
)
if (rolloutStatus != 0){
echo "Deployment ${Deployment_name} 发布 **超时或失败**,开始排查..."
sh """
echo "--- Deployment 状态 ---"
kubectl describe deployment ${Deployment_name} -n ${K8s_namespace} || true
echo '--- Pod 列表 ---'
kubectl get pods -n ${K8s_namespace} -l "app=${Pod_container_name},environment=${Pod_environment}" -o wide || true
echo "--- Pod 描述 (describe) ---"
for pod in \$(kubectl get pods -n ${K8s_namespace} -l "app=${Pod_container_name},environment=${Pod_environment}" -o jsonpath='{.items[*].metadata.name}'); do
echo "-- Pod 描述 \$pod --"
kubectl describe pod \$pod -n ${K8s_namespace} || true
done
echo "--- 最近 200 行 Pod 日志 ---"
for pod in \$(kubectl get pods -n ${K8s_namespace} -l "app=${Pod_container_name},environment=${Pod_environment}" -o jsonpath='{.items[*].metadata.name}'); do
echo "-- logs \$pod --"
kubectl logs \$pod -n ${K8s_namespace} --tail=200 || true
done
"""
error("=== Deployment 发布失败,请检查以上输出定位问题 ===")
}
echo "=== 检查 Pods 是否全部 Ready ==="
sh "kubectl get pods -l 'app=${Pod_container_name},environment=${Pod_environment}' -n ${K8s_namespace} -o wide"
echo "=== 获取最新 Pod 名称 ==="
NEW_POD = sh (
script: "kubectl get pods -l 'app=${Pod_container_name},environment=${Pod_environment}' -n ${K8s_namespace} --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}'",
returnStdout: true
).trim()
echo "=== 新 Pod 启动日志(最近20行) ==="
sh "kubectl logs ${NEW_POD} -n ${K8s_namespace} --tail=20 || true"
echo "部署成功:${NEW_POD} 已正常运行"
}
}
}
}
post {
success {
script {
echo "部署成功,所选择镜像: ${IMAGE_FULL_NAME}"
sendLarkNotification(
"${LARK_ROBOT}",
"CARD",
"${Deployment_name}部署成功",
[
"📅 **部署时间**: ${currentTime}",
"📋 **任务名称**: [${JOB_NAME}](${JOB_URL})",
"🔢 **任务编号**: [${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"🌟 **部署状态**: <font color='green'>部署成功</font>",
"⏱️ **部署用时**: ${currentBuild.duration.intdiv(1000)} 秒",
"📦 **部署镜像**: ${env.IMAGE_FULL_NAME}",
"📝 **镜像摘要**: ${env.IMAGE_LABEL ?: '无'}",
"📦 **上次镜像**: ${env.OLD_IMAGE_NAME ?: '无'}",
"👤 **执行账号**: ${env.ACTUAL_USER}",
]
)
}
}
failure {
script {
echo "有步骤失败,请检查!"
sendLarkNotification(
"${LARK_ROBOT}",
"CARD",
"${Deployment_name}部署失败",
[
"📅 **部署时间**: ${currentTime}",
"📋 **任务名称**: [${JOB_NAME}](${JOB_URL})",
"🔢 **任务编号**: [${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"🌟 **部署状态**: <font color='red'>部署失败</font>",
"⏱️ **部署用时**: ${currentBuild.duration.intdiv(1000)} 秒",
"📦 **部署镜像**: ${env.IMAGE_FULL_NAME ?: '无'}",
"📝 **镜像摘要**: ${env.IMAGE_LABEL ?: '无'}",
"📦 **上次镜像**: ${env.OLD_IMAGE_NAME ?: '无'}",
"👤 **执行账号**: ${env.ACTUAL_USER}",
]
)
}
}
aborted {
script {
echo "部署被中止!"
sendLarkNotification(
"${LARK_ROBOT}",
"CARD",
"${Deployment_name}部署取消",
[
"📅 **部署时间**: ${currentTime}",
"📋 **任务名称**: [${JOB_NAME}](${JOB_URL})",
"🔢 **任务编号**: [${BUILD_DISPLAY_NAME}](${BUILD_URL})",
"🌟 **部署状态**: <font color='red'>部署取消</font>",
"⏱️ **部署用时**: ${currentBuild.duration.intdiv(1000)} 秒",
"📦 **部署镜像**: ${env.IMAGE_FULL_NAME ?: '无'}",
"📝 **镜像摘要**: ${env.IMAGE_LABEL ?: '无'}",
"📦 **上次镜像**: ${env.OLD_IMAGE_NAME ?: '无'}",
"👤 **执行账号**: ${env.ACTUAL_USER}",
]
)
}
}
}
}