From 8cadbf8ba9500e085d710ccd0ae567b18e3aadae Mon Sep 17 00:00:00 2001 From: dxin Date: Sat, 15 Nov 2025 18:18:21 +0800 Subject: [PATCH] + --- Dockerfile/java/admin_Dockerfile | 4 +- Dockerfile/java/agent_Dockerfile | 2 +- Dockerfile/java/email_Dockerfile | 70 +++++ Dockerfile/java/payment_Dockerfile | 4 +- SCM/构建镜像/build_image_flymoon_email.groovy | 231 ++++++++++++++ SCM/部署镜像/DM_s1_flymoon_admin.groovy | 287 ++++++++++++++++++ SCM/部署镜像/DM_s2_lessie_go_api.groovy | 232 ++++++++++++++ k8s_yaml/s1/s1-lessie-agents.yaml | 4 + k8s_yaml/s2/s2-lessie-go-api.yaml | 110 +++++++ 9 files changed, 939 insertions(+), 5 deletions(-) create mode 100644 Dockerfile/java/email_Dockerfile create mode 100644 SCM/构建镜像/build_image_flymoon_email.groovy create mode 100644 SCM/部署镜像/DM_s1_flymoon_admin.groovy create mode 100644 SCM/部署镜像/DM_s2_lessie_go_api.groovy create mode 100644 k8s_yaml/s2/s2-lessie-go-api.yaml diff --git a/Dockerfile/java/admin_Dockerfile b/Dockerfile/java/admin_Dockerfile index a41bcd0..f45c5ff 100644 --- a/Dockerfile/java/admin_Dockerfile +++ b/Dockerfile/java/admin_Dockerfile @@ -13,11 +13,11 @@ WORKDIR /app COPY target/flymoon-admin.jar /app/flymoon-admin.jar # 暴露应用端口(根据实际端口修改,如8070、9090等) -EXPOSE 8070 +EXPOSE 8080 # 默认启动环境sit,可被覆盖,外部使用-e or env SPRING_PROFILES_ACTIVE=xxx覆盖 ARG RUN_PROFILE=sit ENV SPRING_PROFILES_ACTIVE=${RUN_PROFILE} # 启动命令(支持外部传入JVM参数,如 -Xms512m -Xmx1024m) -ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/flymoon-admin.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE}"] \ No newline at end of file +ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/flymoon-admin.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE}"] diff --git a/Dockerfile/java/agent_Dockerfile b/Dockerfile/java/agent_Dockerfile index 8b7589e..560530f 100644 --- a/Dockerfile/java/agent_Dockerfile +++ b/Dockerfile/java/agent_Dockerfile @@ -22,4 +22,4 @@ ARG RUN_PROFILE=sit ENV SPRING_PROFILES_ACTIVE=${RUN_PROFILE} # 启动命令(支持外部传入JVM参数,如 -Xms512m -Xmx1024m) -ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/flymoon-agent.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE}"] \ No newline at end of file +ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/flymoon-agent.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE}"] diff --git a/Dockerfile/java/email_Dockerfile b/Dockerfile/java/email_Dockerfile new file mode 100644 index 0000000..e41b32e --- /dev/null +++ b/Dockerfile/java/email_Dockerfile @@ -0,0 +1,70 @@ +# # 使用官方 Java 8 精简镜像(含JDK,适合运行Spring Boot等Java应用) +# FROM mirror.ccs.tencentyun.com/library/openjdk:8-jdk-slim AS runtime + +# # 配置环境变量(解决中文乱码、时区问题) +# ENV LANG=C.UTF-8 \ +# TZ=Asia/Shanghai \ +# JAVA_OPTS="" + +# # 创建应用工作目录 +# WORKDIR /app + +# # 复制主应用JAR 和 API JAR +# COPY /start/target/start-0.0.1-SNAPSHOT.jar /app/flymoon-email.jar +# RUN chmod 755 /app/flymoon-email.jar + +# # 暴露应用端口(根据实际端口修改) +# EXPOSE 4997 4998 + +# # 默认启动环境sit,可被覆盖,外部使用-e or env SPRING_PROFILES_ACTIVE=xxx覆盖 +# ARG RUN_PROFILE=sit-us +# ENV SPRING_PROFILES_ACTIVE=${RUN_PROFILE} + +# # 启动命令(支持外部传入JVM参数,如 -Xms512m -Xmx1024m) +# ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/flymoon-email.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE}"] + + + + + + + + +# ---- Stage 1: Build with Maven ---- +FROM maven:3.9.6-eclipse-temurin-8 AS builder + +WORKDIR /build + +# 先仅复制 pom.xml 用于依赖缓存 +COPY pom.xml . + +# 预下载所有依赖,pom 不变就不会重新下载 +RUN mvn -B dependency:go-offline + +# 再复制源码 +COPY src ./src + +# Maven 构建配置变量可变,us 或 cn, 默认为 us +ARG MAVEN_BUILD_PROFILE=us +RUN mvn clean package -Dmaven.test.skip=true -P${MAVEN_BUILD_PROFILE} + + +# ---- Stage 2: Runtime ---- +FROM mirror.ccs.tencentyun.com/library/openjdk:8-jdk-slim AS runtime + +WORKDIR /app + +ENV LANG=C.UTF-8 \ + TZ=Asia/Shanghai \ + JAVA_OPTS="" + +# 从构建阶段复制 JAR +COPY --from=builder /start/target/start-0.0.1-SNAPSHOT.jar /app/flymoon-email.jar + +EXPOSE 4997 4998 + +# 运行时配置可变 +ARG RUN_PROFILE=sit-us +ENV SPRING_PROFILES_ACTIVE=${RUN_PROFILE} + +ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE}"] diff --git a/Dockerfile/java/payment_Dockerfile b/Dockerfile/java/payment_Dockerfile index 1eed53c..4c951a8 100644 --- a/Dockerfile/java/payment_Dockerfile +++ b/Dockerfile/java/payment_Dockerfile @@ -3,7 +3,7 @@ FROM mirror.ccs.tencentyun.com/library/openjdk:21-jdk-slim AS runtime # 配置环境变量(解决中文乱码、时区问题) ENV LANG=C.UTF-8 \ - TZ=Asia/Shanghai \ + TZ=Asia/Shanghai \ JAVA_OPTS="" # 创建应用工作目录 @@ -22,4 +22,4 @@ ARG RUN_PROFILE=sit ENV SPRING_PROFILES_ACTIVE=${RUN_PROFILE} # 启动命令(支持外部传入JVM参数,如 -Xms512m -Xmx1024m) -ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/flymoon-payment.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE}"] \ No newline at end of file +ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar /app/flymoon-payment.jar --spring.profiles.active=${SPRING_PROFILES_ACTIVE}"] diff --git a/SCM/构建镜像/build_image_flymoon_email.groovy b/SCM/构建镜像/build_image_flymoon_email.groovy new file mode 100644 index 0000000..62da23a --- /dev/null +++ b/SCM/构建镜像/build_image_flymoon_email.groovy @@ -0,0 +1,231 @@ +pipeline { + agent any + tools{ + maven 'mvn3.8.8' + jdk 'jdk21' + } + parameters { + gitParameter( + branchFilter: 'origin/(.*)', + defaultValue: 'dxin', + name: 'Code_branch', + type: 'PT_BRANCH_TAG', + selectedValue: 'DEFAULT', + sortMode: 'NONE', + description: '选择代码分支: ', + quickFilterEnabled: true, + tagFilter: '*', + listSize: "1" + ) + choice( + name: 'NAME_SPACES', + choices: ['sit', 'test', 'prod'], + description: '选择存放镜像的仓库命名空间:' + ) + string( + name: 'CUSTOM_TAG', + defaultValue: '', + description: '可选:自定义镜像 Tag (字母、数字、点、下划线、短横线), 留空则自动生成 “ v+构建次数_分支名_短哈希_构建时间 ”' + ) + } + environment { + REGISTRY = "uswccr.ccs.tencentyun.com" // 镜像仓库地址 + NAMESPACE = "lessie${params.NAME_SPACES}" // 命名空间根据choices的选择拼接 + IMAGE_NAME = "flymoon-admin" // 镜像名(固定前缀) + CREDENTIALS_ID = "dxin_img_hub_auth" // 容器仓库凭证ID + } + + stages { + stage('拉取代码') { + steps { + git branch: "${params.Code_branch}", + credentialsId: 'fly_gitlab_auth', + url: 'http://106.53.194.199/root/fly_moon_admin.git' + } + } + + stage('获取信息') { + steps { + script { + // 获取最近一次提交的哈希值(短格式,前8位) + env.GIT_COMMIT_SHORT = sh(script: 'git rev-parse --short HEAD',returnStdout: true).trim() + // 获取最近一次提交的作者 + env.GIT_AUTHOR = sh(script: 'git log -1 --pretty=format:%an',returnStdout: true).trim() + // 获取最近一次提交的时间(格式化) + env.GIT_COMMIT_TIME = sh( + script: 'git log -1 --pretty=format:%ct | xargs -I {} date -d @{} +%Y%m%d-%H%M%S', + returnStdout: true + ).trim() + // 获取最近一次提交的备注信息(转义特殊字符,避免构建失败) + env.GIT_COMMIT_MSG = sh(script: 'git log -1 --pretty=format:%s | sed -e \'s/"/\\"/g\'', returnStdout: true).trim() + + // Jenkins构建次数 + def buildNumber = env.BUILD_NUMBER // Jenkins内置变量,直接获取当前Job的构建序号 + // 当前分支名(处理/为-,如feature/docker_1015 → feature-docker_1015) + def branchName = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim() + def formattedBranch = branchName.replace('/', '-').replace('_', '-') // 替换分支名中的/和_为- + // 构建时间(格式:202510181215,年-月-日-时-分,无分隔符) + def buildTime = sh(script: 'date +%Y%m%d%H%M', returnStdout: true).trim() + def defaultTag = "v${buildNumber}_${formattedBranch}_${GIT_COMMIT_SHORT}_${buildTime}" + + def customTag = params.CUSTOM_TAG?.trim() + def tagPattern = ~/^[a-zA-Z0-9._-]+$/ + + // 判断最终Tag + if (customTag && customTag ==~ tagPattern) { + echo "✅ 使用自定义镜像 Tag: ${customTag}" + env.IMAGE_TAG = customTag + } else if (customTag) { + echo "⚠️ 自定义 Tag '${customTag}' 不符合规范,将使用默认生成的 Tag: ${defaultTag}" + + def confirmed = true + timeout(time: 1, unit: 'MINUTES') { + try { + input( + message: """⚠️ Tag 命名不规范: + ${customTag} + + 将使用自动生成的 Tag: + ${defaultTag} + + 是否继续构建?""", + ok: '确认' + ) + } catch (err) { + // 用户点击“取消”或中断 + echo "🚫 用户取消构建" + confirmed = false + } + } + + if (confirmed) { + echo "✅ 用户确认使用自动生成的 Tag:${defaultTag}" + env.IMAGE_TAG = defaultTag + } else { + error("流水线已终止。") + } + } else { + env.IMAGE_TAG = defaultTag + echo "未输入自定义 Tag, 使用自动生成规则: ${env.IMAGE_TAG}" + } + } + } + } + + stage('Maven 编译') { + steps { + sh "cd ${WORKSPACE}/ && mvn clean install -Dmaven.test.skip=true" + } + } + + stage('登录容器仓库') { + steps { + withCredentials([usernamePassword( + credentialsId: env.CREDENTIALS_ID, + usernameVariable: 'REGISTRY_USER', + passwordVariable: 'REGISTRY_PWD' + )]) { + sh ''' + echo "$REGISTRY_PWD" | docker login ${REGISTRY} -u ${REGISTRY_USER} --password-stdin + ''' + } + } + } + + stage('构建容器镜像') { + steps { + script { + // 构建镜像,添加标签信息 + sh """ + docker build -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG} \ + --label "git-branch='${params.Code_branch}'" \ + --label "git-commit='${GIT_COMMIT_SHORT}'" \ + --label "git-author='${GIT_AUTHOR}'" \ + --label "git-message='${GIT_COMMIT_MSG}'" \ + --label "build-time='${GIT_COMMIT_TIME}'" \ + . + """ + } + } + } + + stage('推送镜像到仓库') { + steps { + script { + sh "docker push ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}" + echo "推送镜像成功:${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}" + } + } + } + } + + post { + always { + script { + def keepCount = 2 + echo "开始清理本地旧镜像,仅保留最近 ${keepCount} 个构建版本" + def imagePrefix = "${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}" + + // 获取所有镜像(按创建时间排序,越新的越前) + // 格式:Repository:Tag ImageID CreatedAt + def allImagesRaw = sh(script: "docker images ${imagePrefix} --format '{{.Repository}}:{{.Tag}} {{.ID}} {{.CreatedAt}}' | sort -rk3", returnStdout: true).trim() + if (!allImagesRaw) { + echo "未找到任何镜像,无需清理" + return + } + + def allImages = allImagesRaw.split('\n') + if (allImages.size() <= keepCount) { + echo "当前镜像数未超过 ${keepCount} 个,无需清理" + return + } + + def oldImages = allImages.drop(keepCount) + echo "发现 ${oldImages.size()} 个旧镜像需要清理" + oldImages.each { line -> + echo " ${line}" + } + + oldImages.each { line -> + def parts = line.split(' ') + def imageTag = parts[0] + def imageId = parts.size() > 1 ? parts[1] : "" + + // 对于标签为的无效镜像,使用镜像ID删除 + if (imageTag.contains("") && imageId) { + echo "删除无效镜像: ${imageId}" + sh(returnStatus: true, script: "docker rmi -f ${imageId} || true") + } else if (imageId) { + // 对于有标签的有效镜像,优先使用镜像ID删除 + echo "删除旧镜像: ${imageTag} (${imageId})" + sh(returnStatus: true, script: "docker rmi -f ${imageId} || true") + } else { + // 兜底方案,使用标签删除 + echo "删除旧镜像: ${imageTag}" + sh(returnStatus: true, script: "docker rmi -f ${imageTag} || true") + } + } + + echo "清理完成,当前镜像状态:" + sh """ + docker images ${imagePrefix} --format 'table {{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}\\t{{.Size}}' + """ + + sh "docker logout ${REGISTRY}" + echo "容器仓库已登出,本地凭证已清理" + } + } + success { + // 输出构建结果 + echo "成功!" + echo "镜像地址:${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}" + echo "对应代码提交:${GIT_COMMIT_SHORT}(${GIT_COMMIT_MSG})" + } + failure { + // 输出构建结果 + echo "有步骤失败,请检查!" + } + } +} + + diff --git a/SCM/部署镜像/DM_s1_flymoon_admin.groovy b/SCM/部署镜像/DM_s1_flymoon_admin.groovy new file mode 100644 index 0000000..e74dee4 --- /dev/null +++ b/SCM/部署镜像/DM_s1_flymoon_admin.groovy @@ -0,0 +1,287 @@ +pipeline { + agent any + + parameters { + imageTag( + name: 'IMAGE_NAME', + description: '此处是 sit 镜像仓库命名空间内的镜像', + registry: 'https://uswccr.ccs.tencentyun.com', + image: 'lessiesit/lessie-sourcing-agents', + credentialId: 'dxin_img_hub_auth', + filter: '.*', + defaultTag: '', + verifySsl: true + ) + booleanParam( + name: 'DEPLOY_AFTER_BUILD', + defaultValue: false, + description: '是否快速回滚上一个版本?' + ) + } + environment { + KUBECONFIG = credentials('k8s-test-config-admin') // k8s 凭证 ID, Jenkins 中配置的凭证名称 + + Deployment_name = "s1-flymoon-admin-deployment" // 工作负载名 + Pod_container_name = "s1-flymoon-admin" // pod内运行的容器名 + K8s_namespace = "sit" // 这是k8s集群的命名空间 + } + + stages { + stage('拉取yaml') { + steps { + git branch: "opt", + credentialsId: 'fly_gitlab_auth', + url: 'http://106.53.194.199/opt/opt-config.git' + } + } + + stage('获取信息') { + steps { + script { + // 获取最近一次提交的哈希值(短格式,前8位) + env.GIT_COMMIT_SHORT = sh(script: 'git rev-parse --short HEAD',returnStdout: true).trim() + // 获取最近一次提交的作者 + env.GIT_AUTHOR = sh(script: 'git log -1 --pretty=format:%an',returnStdout: true).trim() + // 获取最近一次提交的时间(格式化) + env.GIT_COMMIT_TIME = sh( + script: 'git log -1 --pretty=format:%ct | xargs -I {} date -d @{} +%Y%m%d-%H%M%S', + returnStdout: true + ).trim() + // 获取最近一次提交的备注信息(转义特殊字符,避免构建失败) + env.GIT_COMMIT_MSG = sh(script: 'git log -1 --pretty=format:%s | sed -e \'s/"/\\"/g\'', returnStdout: true).trim() + + // Jenkins构建次数 + def buildNumber = env.BUILD_NUMBER // Jenkins内置变量,直接获取当前Job的构建序号 + // 当前分支名(处理/为-,如feature/docker_1015 → feature-docker_1015) + def branchName = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim() + def formattedBranch = branchName.replace('/', '-').replace('_', '-') // 替换分支名中的/和_为- + // 构建时间(格式:202510181215,年-月-日-时-分,无分隔符) + def buildTime = sh(script: 'date +%Y%m%d%H%M', returnStdout: true).trim() + def defaultTag = "v${buildNumber}_${formattedBranch}_${GIT_COMMIT_SHORT}_${buildTime}" + + def customTag = params.CUSTOM_TAG?.trim() + def tagPattern = ~/^[a-zA-Z0-9._-]+$/ + + // 判断最终Tag + if (customTag && customTag ==~ tagPattern) { + echo "✅ 使用自定义镜像 Tag: ${customTag}" + env.IMAGE_TAG = customTag + } else if (customTag) { + echo "⚠️ 自定义 Tag '${customTag}' 不符合规范,将使用默认生成的 Tag: ${defaultTag}" + + def confirmed = true + timeout(time: 1, unit: 'MINUTES') { + try { + input( + message: """⚠️ Tag 命名不规范: + ${customTag} + + 将使用自动生成的 Tag: + ${defaultTag} + + 是否继续构建?""", + ok: '确认' + ) + } catch (err) { + // 用户点击“取消”或中断 + echo "🚫 用户取消构建" + confirmed = false + } + } + + if (confirmed) { + echo "✅ 用户确认使用自动生成的 Tag:${defaultTag}" + env.IMAGE_TAG = defaultTag + } else { + error("流水线已终止。") + } + } else { + env.IMAGE_TAG = defaultTag + echo "未输入自定义 Tag, 使用自动生成规则: ${env.IMAGE_TAG}" + } + } + } + } + + stage('Maven 编译') { + steps { + sh "cd ${WORKSPACE}/ && mvn clean install -Dmaven.test.skip=true" + } + } + + stage('登录容器仓库') { + steps { + withCredentials([usernamePassword( + credentialsId: env.CREDENTIALS_ID, + usernameVariable: 'REGISTRY_USER', + passwordVariable: 'REGISTRY_PWD' + )]) { + sh ''' + echo "$REGISTRY_PWD" | docker login ${REGISTRY} -u ${REGISTRY_USER} --password-stdin + ''' + } + } + } + + stage('构建容器镜像') { + steps { + script { + // 构建镜像,添加标签信息 + sh """ + docker build -t ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG} \ + --label "git-branch='${params.Code_branch}'" \ + --label "git-commit='${GIT_COMMIT_SHORT}'" \ + --label "git-author='${GIT_AUTHOR}'" \ + --label "git-message='${GIT_COMMIT_MSG}'" \ + --label "build-time='${GIT_COMMIT_TIME}'" \ + . + """ + } + } + } + + stage('推送镜像到仓库') { + steps { + script { + // 推送主镜像(带唯一 Tag) + sh "docker push ${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}" + echo "推送镜像成功:${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}" + } + } + } + + stage('部署到K8S') { + when { + expression { return params.DEPLOY_AFTER_BUILD } + } + steps { + sh """ + echo "=== 更新 Deployment 镜像 ===" + kubectl set image deployment/${Deployment_name} ${Pod_container_name}=${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG} -n ${K8s_namespace} + echo "=== 添加注解 ===" + kubectl annotate deployment/${Deployment_name} kubernetes.io/change-cause="${GIT_COMMIT_MSG}" --overwrite -n ${K8s_namespace} + echo "=== 查看历史版本 ===" + kubectl rollout history deployment/${Deployment_name} -n ${K8s_namespace} + echo "=== 查看所使用的镜像 ===" + kubectl get deployment ${Deployment_name} -n ${K8s_namespace} -o=jsonpath='{.spec.template.spec.containers[*].image}' + """ + } + } + + stage('检查部署情况') { + when { + expression { return params.DEPLOY_AFTER_BUILD } + } + steps { + echo "检测部署状态并验证新版本运行情况" + sh """ + echo "=== 检查 Deployment 滚动更新状态 ===" + kubectl rollout status deployment/${Deployment_name} -n ${K8s_namespace} --timeout=180s + + if [ \$? -ne 0 ]; then + echo "❌ 部署超时或失败,开始收集诊断信息..." + echo "=== 查看当前 Pods 状态 ===" + kubectl get pods -l app=${Pod_container_name} -n ${K8s_namespace} -o wide + + echo "=== 查看最近的事件 ===" + kubectl get events -n ${K8s_namespace} --sort-by=.metadata.creationTimestamp | tail -n 20 + + echo "=== 查看最近一个失败 Pod 的详细描述 ===" + FAILED_POD=\$(kubectl get pods -l app=${Pod_container_name} -n ${K8s_namespace} --field-selector=status.phase!=Running -o jsonpath='{.items[0].metadata.name}') + if [ ! -z "\$FAILED_POD" ]; then + kubectl describe pod \$FAILED_POD -n ${K8s_namespace} || true + kubectl logs \$FAILED_POD -n ${K8s_namespace} --tail=50 || true + fi + + echo "=== 回滚到上一个版本 ===" + kubectl rollout undo deployment/${Deployment_name} -n ${K8s_namespace} + + exit 1 + fi + + echo "=== 检查 Pods 是否全部 Ready ===" + kubectl get pods -l app=${Pod_container_name} -n ${K8s_namespace} -o wide + + echo "=== 获取最新 Pod 名称 ===" + NEW_POD=\$(kubectl get pods -l app=${Pod_container_name} -n ${K8s_namespace} --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}') + + echo "=== 新 Pod 启动日志(最近20行) ===" + kubectl logs \$NEW_POD -n ${K8s_namespace} --tail=20 || true + + echo "✅ 部署成功:\$NEW_POD 已正常运行" + """ + } + } + + } + + post { + always { + script { + def keepCount = 2 + echo "开始清理本地旧镜像,仅保留最近 ${keepCount} 个构建版本" + def imagePrefix = "${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}" + + // 获取所有镜像(按创建时间排序,越新的越前) + // 格式:Repository:Tag ImageID CreatedAt + def allImagesRaw = sh(script: "docker images ${imagePrefix} --format '{{.Repository}}:{{.Tag}} {{.ID}} {{.CreatedAt}}' | sort -rk3", returnStdout: true).trim() + if (!allImagesRaw) { + echo "未找到任何镜像,无需清理" + return + } + + def allImages = allImagesRaw.split('\n') + if (allImages.size() <= keepCount) { + echo "当前镜像数未超过 ${keepCount} 个,无需清理" + return + } + + def oldImages = allImages.drop(keepCount) + echo "发现 ${oldImages.size()} 个旧镜像需要清理" + oldImages.each { line -> + echo " ${line}" + } + + oldImages.each { line -> + def parts = line.split(' ') + def imageTag = parts[0] + def imageId = parts.size() > 1 ? parts[1] : "" + + // 对于标签为的无效镜像,使用镜像ID删除 + if (imageTag.contains("") && imageId) { + echo "删除无效镜像: ${imageId}" + sh(returnStatus: true, script: "docker rmi -f ${imageId} || true") + } else if (imageId) { + // 对于有标签的有效镜像,优先使用镜像ID删除 + echo "删除旧镜像: ${imageTag} (${imageId})" + sh(returnStatus: true, script: "docker rmi -f ${imageId} || true") + } else { + // 兜底方案,使用标签删除 + echo "删除旧镜像: ${imageTag}" + sh(returnStatus: true, script: "docker rmi -f ${imageTag} || true") + } + } + + echo "清理完成,当前镜像状态:" + sh """ + docker images ${imagePrefix} --format 'table {{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}\\t{{.Size}}' + """ + + sh "docker logout ${REGISTRY}" + echo "容器仓库已登出,本地凭证已清理" + } + } + success { + // 输出构建结果 + echo "成功!" + echo "镜像地址:${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}" + echo "对应代码提交:${GIT_COMMIT_SHORT}(${GIT_COMMIT_MSG})" + } + failure { + // 输出构建结果 + echo "有步骤失败,请检查!" + } + } +} + + diff --git a/SCM/部署镜像/DM_s2_lessie_go_api.groovy b/SCM/部署镜像/DM_s2_lessie_go_api.groovy new file mode 100644 index 0000000..63e3f4c --- /dev/null +++ b/SCM/部署镜像/DM_s2_lessie_go_api.groovy @@ -0,0 +1,232 @@ +pipeline { + agent any + + parameters { + booleanParam( + name: 'DEPLOY_AFTER_BUILD', + defaultValue: false, + description: '是否快速回滚上一个版本?' + ) + imageTag( + name: 'IMAGE_NAME', + description: '此处是 sit 镜像仓库内的 go 服务镜像', + registry: 'https://uswccr.ccs.tencentyun.com', + image: 'lessiesit/go_lessie-sourcing-api', + credentialId: 'dxin_img_hub_auth', + filter: '.*', + defaultTag: '', + verifySsl: true + ) + } + environment { + KUBECONFIG = credentials('k8s-test-config-admin') // k8s 凭证 ID, Jenkins 中配置的凭证名称 + Deployment_yaml = "/opt-config/lessie-ai/s2/s2-lessie-go-api.yaml" + Deployment_name = "s2-lessie-go-api-deployment" + K8s_namespace = "sit" + Pod_container_name = "s2-lessie-go-api" + + } + + stages { + stage('快速回滚上一版本') { + when { + expression { return params.DEPLOY_AFTER_BUILD == 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=180s + echo "=== 查看所使用的镜像 ===" + kubectl get deployment ${Deployment_name} -n ${K8s_namespace} -o=jsonpath='{.spec.template.spec.containers[*].image}' + echo "=== 回滚完成 ===" + """ + } + } + } + + + stage('拉取yaml') { + when { + expression { return params.DEPLOY_AFTER_BUILD == false } + } + steps { + git branch: "opt", + credentialsId: 'fly_gitlab_auth', + url: 'http://106.53.194.199/opt/opt-config.git' + } + } + stage('修改YAML镜像') { + when { + expression { return params.DEPLOY_AFTER_BUILD == false } + } + steps { + script { + echo "=== 所选择镜像 tag: ${params.IMAGE_NAME} ===" + sh """ + echo "=== 修改 Deployment YAML 中的镜像版本 ===" + sed -i 's#image:.*#image: ${params.IMAGE_NAME}#' ${Deployment_yaml} + """ + } + } + } + + stage('部署到k8s') { + when { + expression { return params.DEPLOY_AFTER_BUILD == false } + } + steps { + script { + sh """ + kubectl apply -f ${Deployment_yaml} -n ${K8s_namespace} + kubectl rollout status deployment/${Deployment_name} -n ${K8s_namespace} --timeout=180s + """ + } + } + } + + stage('获取信息') { + steps { + script { + + } + } + } + + stage('部署到K8S') { + when { + expression { return params.DEPLOY_AFTER_BUILD } + } + steps { + sh """ + echo "=== 更新 Deployment 镜像 ===" + kubectl set image deployment/${Deployment_name} ${Pod_container_name}=${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG} -n ${K8s_namespace} + echo "=== 添加注解 ===" + kubectl annotate deployment/${Deployment_name} kubernetes.io/change-cause="${GIT_COMMIT_MSG}" --overwrite -n ${K8s_namespace} + echo "=== 查看历史版本 ===" + kubectl rollout history deployment/${Deployment_name} -n ${K8s_namespace} + echo "=== 查看所使用的镜像 ===" + kubectl get deployment ${Deployment_name} -n ${K8s_namespace} -o=jsonpath='{.spec.template.spec.containers[*].image}' + """ + } + } + + stage('检查部署情况') { + when { + expression { return params.DEPLOY_AFTER_BUILD } + } + steps { + echo "检测部署状态并验证新版本运行情况" + sh """ + echo "=== 检查 Deployment 滚动更新状态 ===" + kubectl rollout status deployment/${Deployment_name} -n ${K8s_namespace} --timeout=180s + + if [ \$? -ne 0 ]; then + echo "❌ 部署超时或失败,开始收集诊断信息..." + echo "=== 查看当前 Pods 状态 ===" + kubectl get pods -l app=${Pod_container_name} -n ${K8s_namespace} -o wide + + echo "=== 查看最近的事件 ===" + kubectl get events -n ${K8s_namespace} --sort-by=.metadata.creationTimestamp | tail -n 20 + + echo "=== 查看最近一个失败 Pod 的详细描述 ===" + FAILED_POD=\$(kubectl get pods -l app=${Pod_container_name} -n ${K8s_namespace} --field-selector=status.phase!=Running -o jsonpath='{.items[0].metadata.name}') + if [ ! -z "\$FAILED_POD" ]; then + kubectl describe pod \$FAILED_POD -n ${K8s_namespace} || true + kubectl logs \$FAILED_POD -n ${K8s_namespace} --tail=50 || true + fi + + echo "=== 回滚到上一个版本 ===" + kubectl rollout undo deployment/${Deployment_name} -n ${K8s_namespace} + + exit 1 + fi + + echo "=== 检查 Pods 是否全部 Ready ===" + kubectl get pods -l app=${Pod_container_name} -n ${K8s_namespace} -o wide + + echo "=== 获取最新 Pod 名称 ===" + NEW_POD=\$(kubectl get pods -l app=${Pod_container_name} -n ${K8s_namespace} --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}') + + echo "=== 新 Pod 启动日志(最近20行) ===" + kubectl logs \$NEW_POD -n ${K8s_namespace} --tail=20 || true + + echo "✅ 部署成功:\$NEW_POD 已正常运行" + """ + } + } + + } + + post { + always { + script { + def keepCount = 2 + echo "开始清理本地旧镜像,仅保留最近 ${keepCount} 个构建版本" + def imagePrefix = "${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}" + + // 获取所有镜像(按创建时间排序,越新的越前) + // 格式:Repository:Tag ImageID CreatedAt + def allImagesRaw = sh(script: "docker images ${imagePrefix} --format '{{.Repository}}:{{.Tag}} {{.ID}} {{.CreatedAt}}' | sort -rk3", returnStdout: true).trim() + if (!allImagesRaw) { + echo "未找到任何镜像,无需清理" + return + } + + def allImages = allImagesRaw.split('\n') + if (allImages.size() <= keepCount) { + echo "当前镜像数未超过 ${keepCount} 个,无需清理" + return + } + + def oldImages = allImages.drop(keepCount) + echo "发现 ${oldImages.size()} 个旧镜像需要清理" + oldImages.each { line -> + echo " ${line}" + } + + oldImages.each { line -> + def parts = line.split(' ') + def imageTag = parts[0] + def imageId = parts.size() > 1 ? parts[1] : "" + + // 对于标签为的无效镜像,使用镜像ID删除 + if (imageTag.contains("") && imageId) { + echo "删除无效镜像: ${imageId}" + sh(returnStatus: true, script: "docker rmi -f ${imageId} || true") + } else if (imageId) { + // 对于有标签的有效镜像,优先使用镜像ID删除 + echo "删除旧镜像: ${imageTag} (${imageId})" + sh(returnStatus: true, script: "docker rmi -f ${imageId} || true") + } else { + // 兜底方案,使用标签删除 + echo "删除旧镜像: ${imageTag}" + sh(returnStatus: true, script: "docker rmi -f ${imageTag} || true") + } + } + + echo "清理完成,当前镜像状态:" + sh """ + docker images ${imagePrefix} --format 'table {{.Repository}}\\t{{.Tag}}\\t{{.CreatedAt}}\\t{{.Size}}' + """ + + sh "docker logout ${REGISTRY}" + echo "容器仓库已登出,本地凭证已清理" + } + } + success { + // 输出构建结果 + echo "成功!" + echo "镜像地址:${REGISTRY}/${NAMESPACE}/${IMAGE_NAME}:${IMAGE_TAG}" + echo "对应代码提交:${GIT_COMMIT_SHORT}(${GIT_COMMIT_MSG})" + } + failure { + // 输出构建结果 + echo "有步骤失败,请检查!" + } + } +} + + diff --git a/k8s_yaml/s1/s1-lessie-agents.yaml b/k8s_yaml/s1/s1-lessie-agents.yaml index 3e8949f..91769f6 100644 --- a/k8s_yaml/s1/s1-lessie-agents.yaml +++ b/k8s_yaml/s1/s1-lessie-agents.yaml @@ -123,3 +123,7 @@ spec: port: 8000 # ClusterIP 内部端口 targetPort: 8000 # 容器端口 + + + + diff --git a/k8s_yaml/s2/s2-lessie-go-api.yaml b/k8s_yaml/s2/s2-lessie-go-api.yaml new file mode 100644 index 0000000..0420d79 --- /dev/null +++ b/k8s_yaml/s2/s2-lessie-go-api.yaml @@ -0,0 +1,110 @@ +# ---------------------------- +# Deployment +# ---------------------------- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: s2-lessie-go-api-deployment + namespace: sit + labels: + app: s2-lessie-go-api + environment: s2 + project: lessie +spec: + replicas: 1 + selector: + matchLabels: + app: s2-lessie-go-apis + environment: s2 + project: lessie + strategy: + type: RollingUpdate # 滚动更新策略 + rollingUpdate: + maxSurge: 1 # 最大新增副本数 + maxUnavailable: 0 # 最大不可用副本数 + template: + metadata: + labels: + app: s2-lessie-go-api + environment: s2 + project: lessie + spec: + imagePullSecrets: + - name: dxin-image-repository # 镜像仓库凭证Secret + volumes: + - name: go-logs-volume + hostPath: + path: /data/logs/s2-lessie-go-api/ + type: DirectoryOrCreate + containers: + - name: s2-lessie-go-api # 容器名称 + image: uswccr.ccs.tencentyun.com/lessiesit/go_lessie-sourcing-api:v13_dxin_d96a403_202511071407 # 容器镜像 + imagePullPolicy: Always # 镜像拉取策略 ,总是拉 + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: APP_ENV + value: "s2" + - name: APP_PORT + value: "8100" + ports: + - containerPort: 8100 # 容器暴露的端口 + resources: + requests: + cpu: "100m" # 容器请求分配0.1个CPU核心(这不是实际占用,但调度会以这里进行参考) + memory: "1Gi" # 容器请求分配1.5Gi内存(这会实际预留) + limits: + cpu: "1" # 最多可以使用1个CPU核心 + memory: "3Gi" # 容器最多可以使用3Gi内存 + volumeMounts: + - name: go-logs-volume + mountPath: /app/logs/ + subPathExpr: go-log-$(POD_NAME) + readinessProbe: # 就绪探针,用于判断容器是否已准备好接收流量 + httpGet: + path: /health + port: 8100 + initialDelaySeconds: 5 # 就绪探测在容器启动后等待多少秒才开始第一次探测(避免应用启动未完成即被判为不就绪) + periodSeconds: 10 # 就绪探测的间隔秒数,每隔多少秒执行一次探测 + timeoutSeconds: 5 # 单次就绪探测的超时时间(秒),超过则该次探测视为失败 + failureThreshold: 3 # 连续失败多少次后认为就绪探测失败(Pod 不再被视为就绪) + livenessProbe: # 存活探针,用于判断容器是否仍然健康,失败会触发重启 + httpGet: + path: /health + port: 8100 + initialDelaySeconds: 10 # 存活探测在容器启动后等待多少秒才开始第一次探测 + periodSeconds: 30 # 存活探测的间隔秒数 + timeoutSeconds: 5 # 单次存活探测的超时时间(秒) + failureThreshold: 3 # 连续失败多少次后认为容器不健康并触发重启 + +--- +# ---------------------------- +# Service +# 集群内部:http://s2-lessie-go-api-svc.sit.svc.cluster.local:8100 +# 集群外部:http://:30811 +# ---------------------------- + +apiVersion: v1 +kind: Service +metadata: + name: s2-lessie-go-api-svc + namespace: sit + labels: + app: s2-lessie-go-api + environment: s2 + project: lessie +spec: + type: NodePort # ClusterIP + selector: # 必须匹配 Deployment 的 labels 才能关联 Pod + app: s2-lessie-go-api + environment: s2 + project: lessie + ports: + - name: http + port: 8100 # ClusterIP 内部端口 + targetPort: 8100 # 容器端口 + nodePort: 30812 # 节点对外端口(30000-32767) + +