123 lines
5.3 KiB
Groovy
123 lines
5.3 KiB
Groovy
// BuildFlow buildflow-api CI/CD
|
||
// 链路:①拉取代码 → ②编译打包(docker build) → 推送 Harbor → ③部署(cd-target docker compose) → HealthCheck
|
||
// 自动触发:Gitea push → Generic Webhook Trigger(token=buildflow-api)
|
||
// Gitea webhook 指向:http://jenkins:8080/generic-webhook-trigger/invoke?token=buildflow-api
|
||
// 部署:docker compose;api 生产密钥通过 volume 挂载 appsettings.Secrets.json 注入(不进镜像)
|
||
// api 运行依赖:PostgreSQL(5库) / Redis / MongoDB(im_system) / RabbitMQ / S3(RustFS) / 本地LLM(1234) / KKFileView(8012)
|
||
|
||
pipeline {
|
||
agent any
|
||
triggers {
|
||
// Gitea push 触发:Generic Webhook Trigger 按 token 路由到本 job
|
||
// token 通过 URL ?token=xxx 传入,与 Gitea webhook URL 的 token 参数匹配
|
||
GenericTrigger(
|
||
token: 'buildflow-api',
|
||
causeString: 'Gitea push 触发',
|
||
printContributedVariables: false,
|
||
printPostContent: false,
|
||
regexpFilterText: '',
|
||
regexpFilterExpression: ''
|
||
)
|
||
}
|
||
environment {
|
||
HARBOR = "43.134.235.58:58080"
|
||
IMAGE = "${HARBOR}/buildflow/buildflow-api"
|
||
TAG = "${env.BUILD_NUMBER}"
|
||
// Jenkins 容器内无 ssh 别名,直接用 IP;ubuntu 用户 sudo 免密(NOPASSWD)
|
||
DEPLOY_HOST = "ubuntu@110.42.240.10"
|
||
APP_PORT = "5667"
|
||
// cd-target 上 compose 编排目录 + 本服务名
|
||
DEPLOY_DIR = "/home/ubuntu/deploy"
|
||
SERVICE = "buildflow-api"
|
||
}
|
||
options {
|
||
timeout(time: 30, unit: "MINUTES")
|
||
buildDiscarder(logRotator(numToKeepStr: "10"))
|
||
}
|
||
stages {
|
||
stage("① 拉取代码") {
|
||
steps {
|
||
checkout scm
|
||
sh "git log --oneline -3"
|
||
}
|
||
}
|
||
stage("② 编译打包") {
|
||
steps {
|
||
sh "docker build -t ${IMAGE}:${TAG} -t ${IMAGE}:latest ."
|
||
sh "docker images | grep ${IMAGE} | head -2"
|
||
}
|
||
}
|
||
stage("推送 Harbor") {
|
||
steps {
|
||
withCredentials([usernamePassword(credentialsId: "harbor-credentials", usernameVariable: "HUSER", passwordVariable: "HPASS")]) {
|
||
sh '''
|
||
# 跨地域 push 大 blob 易断流 + Harbor auth session 会过期
|
||
# 每次 push 前重新 login,组合成原子重试单元(断点续传,已传层跳过)
|
||
push_retry() {
|
||
local img=$1
|
||
for i in 1 2 3 4 5 6 7 8 9 10; do
|
||
echo $HPASS | docker login $HARBOR -u $HUSER --password-stdin >/dev/null 2>&1
|
||
docker push "$img" && return 0
|
||
echo "push $img 第 $i 次中断,5秒后重试(重新登录+断点续传)..."
|
||
sleep 5
|
||
done
|
||
return 1
|
||
}
|
||
push_retry $IMAGE:$TAG
|
||
push_retry $IMAGE:latest
|
||
docker logout $HARBOR
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
stage("③ 部署") {
|
||
steps {
|
||
withCredentials([usernamePassword(credentialsId: "harbor-credentials", usernameVariable: "HUSER", passwordVariable: "HPASS")]) {
|
||
sh """
|
||
ssh -o StrictHostKeyChecking=accept-new ${DEPLOY_HOST} "
|
||
cd ${DEPLOY_DIR}
|
||
echo '${HPASS}' | sudo docker login ${HARBOR} -u ${HUSER} --password-stdin
|
||
sudo docker compose pull ${SERVICE}
|
||
sudo docker compose up -d --no-deps ${SERVICE}
|
||
"
|
||
"""
|
||
}
|
||
echo "部署完成"
|
||
}
|
||
}
|
||
stage("HealthCheck") {
|
||
steps {
|
||
script {
|
||
// .NET 启动较慢(连 PG/Redis/Mongo),轮询最多 60s
|
||
def healthy = false
|
||
for (int i = 1; i <= 12; i++) {
|
||
sleep 5
|
||
try {
|
||
sh """
|
||
ssh -o StrictHostKeyChecking=accept-new ${DEPLOY_HOST} 'wget -q --spider http://127.0.0.1:${APP_PORT}/health && echo HEALTHCHECK_OK'
|
||
"""
|
||
healthy = true
|
||
echo "=== HealthCheck OK: api http://110.42.240.10:${APP_PORT} ==="
|
||
break
|
||
} catch (e) {
|
||
echo "HealthCheck 第 ${i}/12 次探测未就绪,5s 后重试..."
|
||
}
|
||
}
|
||
if (!healthy) {
|
||
// 不再静默吞失败:标记 unstable,流水线变黄便于发现
|
||
unstable("HealthCheck 60s 内未通过,查日志:ssh cd-target 'sudo docker logs ${SERVICE} --tail 50'")
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
post {
|
||
success { echo '=== buildflow-api CI/CD SUCCESS ===' }
|
||
failure { echo '=== buildflow-api CI/CD FAILED ===' }
|
||
always {
|
||
sh "docker rmi ${IMAGE}:${TAG} ${IMAGE}:latest 2>/dev/null || true"
|
||
deleteDir()
|
||
}
|
||
}
|
||
}
|