Files
aiot-platform-cloud/Jenkinsfile
lzh 4e4f0eea5c
Some checks failed
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled
fix: 移除会导致问题的@NonCPS注解
@NonCPS函数不能调用Pipeline步骤(sh, echo等)
移除以下函数的@NonCPS:
- detectChangedServices (调用sh)
- checkDepsChanged (调用sh)
- depsImageExists (调用sh)
- sortServicesByDependency (避免序列化问题)

保留纯函数的@NonCPS:
- getContainerNameForService
- getModulePathForService
2026-01-14 00:38:22 +08:00

410 lines
13 KiB
Groovy
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

// ============================================
// AIOT Platform - Jenkins Pipeline (Optimized)
// ä¿®å¤<C3A5>åº<C3A5>列åŒé—®é¢?+ 并行构建 + 优化部署
// ============================================
pipeline {
agent any
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timeout(time: 60, unit: 'MINUTES')
timestamps()
}
environment {
// 镜åƒ<C3A5>仓库é…<C3A9>ç½®
REGISTRY = 'localhost:5000'
IMAGE_TAG = "${env.BRANCH_NAME}-${env.BUILD_NUMBER}-${env.GIT_COMMIT?.take(8)}"
DEPS_IMAGE = "${REGISTRY}/aiot-deps:latest"
// æœ<C3A6>务é…<C3A9>ç½®
CORE_SERVICES = 'viewsh-gateway,viewsh-module-system-server,viewsh-module-infra-server,viewsh-module-iot-server,viewsh-module-iot-gateway'
// 部署é…<C3A9>ç½®
DEPLOY_HOST = '172.19.0.1'
DEPLOY_PATH = '/opt/aiot-platform-cloud'
SSH_KEY = '/var/jenkins_home/.ssh/id_rsa'
// 性能é…<C3A9>ç½®
MAX_PARALLEL_BUILDS = 2
HEALTH_CHECK_TIMEOUT = 120 // ç§?
HEALTH_CHECK_INTERVAL = 5 // ç§?
}
stages {
stage('Checkout') {
steps {
checkout scm
script {
env.GIT_COMMIT_MSG = sh(script: 'git log -1 --pretty=%B', returnStdout: true).trim()
echo "📦 Commit: ${env.GIT_COMMIT?.take(8)} - ${env.GIT_COMMIT_MSG}"
}
}
}
stage('Detect Changes') {
steps {
script {
env.SERVICES_TO_BUILD = detectChangedServices()
env.DEPS_CHANGED = checkDepsChanged()
if (env.SERVICES_TO_BUILD.isEmpty()) {
echo <>­ï¸<C3AF> No changes detected, skipping build"
currentBuild.result = 'SUCCESS'
error("No changes")
}
echo "🔄 Services to build: ${env.SERVICES_TO_BUILD}"
echo "📦 Deps changed: ${env.DEPS_CHANGED}"
}
}
}
stage('Build Dependencies Image') {
when {
expression {
env.DEPS_CHANGED == 'true' || !depsImageExists()
}
}
steps {
script {
echo "📦 Building dependencies base image..."
sh """
docker build \\
-f docker/Dockerfile.deps \\
-t ${DEPS_IMAGE} \\
.
docker push ${DEPS_IMAGE}
"""
echo "�Dependencies image built and pushed"
}
}
}
stage('Build Services') {
when { expression { env.SERVICES_TO_BUILD != '' } }
steps {
script {
def servicesToBuild = env.SERVICES_TO_BUILD.split(',')
echo "🔨 Building ${servicesToBuild.size()} services in parallel (max ${MAX_PARALLEL_BUILDS})"
// 并行构建æœ<C3A6>务
def buildTasks = [:]
servicesToBuild.each { service ->
buildTasks[service] = {
buildService(service)
}
}
// é™<C3A9>制并å<C2B6>æ•?
parallel buildTasks
echo "�All services built successfully"
// 清ç<E280A6>†æ—§é•œåƒ?
sh "docker image prune -f || true"
}
}
}
stage('Deploy') {
when {
allOf {
expression { env.SERVICES_TO_BUILD != '' }
branch 'master'
}
}
steps {
script {
def servicesToDeploy = env.SERVICES_TO_BUILD.split(',')
// 按ä¾<C3A4>èµé¡ºåº<C3A5>æŽåº?
def sortedServices = sortServicesByDependency(servicesToDeploy)
echo "🚀 Deploying ${sortedServices.size()} services in order"
// 串行部署(ä¿<C3A4>è¯<C3A8>ä¾<C3A4>èµå…³ç³»ï¼‰
sortedServices.each { service ->
deployService(service)
}
echo "🚀 All services deployed successfully!"
}
}
}
stage('Final Health Check') {
when {
allOf {
expression { env.SERVICES_TO_BUILD != '' }
branch 'master'
}
}
steps {
script {
echo "ðŸ<C3B0>¥ Running final health check..."
def servicesToCheck = env.SERVICES_TO_BUILD.split(',')
def healthCheckTasks = [:]
servicesToCheck.each { service ->
def containerName = getContainerNameForService(service)
healthCheckTasks[service] = {
checkServiceHealth(containerName, service)
}
}
// 并行å<C592>¥åº·æ£€æŸ?
parallel healthCheckTasks
echo "�All services are healthy"
}
}
}
}
post {
success {
echo """
âœ?构建æˆ<C3A6>功
📦 Services: ${env.SERVICES_TO_BUILD}
ðŸ<C3B0>·ï¸? Tag: ${IMAGE_TAG}
"""
}
failure {
echo <>?构建失败,请检查日�
}
always {
sh 'df -h | grep -E "/$|/var" || true'
sh 'docker system df || true'
}
}
}
// ============================================
// 辅助函数(使ç”?@NonCPS é<>¿å…<C3A5>åº<C3A5>列åŒé—®é¢˜ï¼‰
// ============================================
@NonCPS
def detectChangedServices() {
def changedFiles = sh(
script: '''
PREV=$(git rev-parse HEAD~1 2>/dev/null || echo "")
[ -z "$PREV" ] && echo "all" || git diff --name-only $PREV HEAD
''',
returnStdout: true
).trim()
if (changedFiles == 'all' || changedFiles.isEmpty()) {
return env.CORE_SERVICES
}
// 触å<C2A6>å…¨é‡<C3A9>构建的æ‡ä»?
def triggerAll = ['pom.xml', 'viewsh-framework', 'viewsh-dependencies', 'Jenkinsfile', 'docker/']
if (triggerAll.any { changedFiles.contains(it) }) {
return env.CORE_SERVICES
}
// 检æµå<E280B9>˜æ´çš„æœ<C3A6>务
def changedServices = []
def allServices = env.CORE_SERVICES.split(',')
allServices.each { service ->
def modulePath = getModulePathForService(service)
def moduleDir = modulePath.split('/')[0]
if (changedFiles.contains(moduleDir)) {
changedServices.add(service)
}
}
return changedServices.isEmpty() ? env.CORE_SERVICES : changedServices.join(',')
}
@NonCPS
def checkDepsChanged() {
def changedFiles = sh(
script: '''
PREV=$(git rev-parse HEAD~1 2>/dev/null || echo "")
[ -z "$PREV" ] && echo "all" || git diff --name-only $PREV HEAD
''',
returnStdout: true
).trim()
if (changedFiles == 'all') {
return 'true'
}
def depsFiles = ['pom.xml', 'viewsh-dependencies', 'viewsh-framework']
return depsFiles.any { changedFiles.contains(it) } ? 'true' : 'false'
}
@NonCPS
def depsImageExists() {
def result = sh(
script: "docker manifest inspect ${env.DEPS_IMAGE} > /dev/null 2>&1",
returnStatus: true
)
return result == 0
}
// 构建å<C2BA>•个æœ<C3A6>务
def buildService(String service) {
def modulePath = getModulePathForService(service)
echo "🨠Building ${service}..."
sh """
docker build \\
-f docker/Dockerfile.service \\
--build-arg DEPS_IMAGE=${DEPS_IMAGE} \\
--build-arg MODULE_NAME=${modulePath} \\
--build-arg JAR_NAME=${service} \\
--build-arg SKIP_TESTS=true \\
-t ${REGISTRY}/${service}:${IMAGE_TAG} \\
-t ${REGISTRY}/${service}:latest \\
.
docker push ${REGISTRY}/${service}:${IMAGE_TAG}
docker push ${REGISTRY}/${service}:latest
"""
echo "�${service} built and pushed"
}
// 部署å<C2B2>•个æœ<C3A6>务
def deployService(String service) {
echo "🚀 Deploying ${service}..."
def containerName = getContainerNameForService(service)
def sshOpts = "-o StrictHostKeyChecking=no -o ConnectTimeout=10 -i ${SSH_KEY}"
// 部署æœ<C3A6>务
sh """
ssh ${sshOpts} root@${DEPLOY_HOST} '
cd ${DEPLOY_PATH}
echo "Pulling ${service}..."
docker compose -f docker-compose.core.yml pull ${service}
echo "Starting ${service}..."
docker compose -f docker-compose.core.yml up -d ${service}
'
"""
// 等待æœ<C3A6>务å<C2A1>¥åº·
waitForServiceHealthy(containerName, service, sshOpts)
echo "�${service} deployed successfully"
}
// 等待æœ<C3A6>务å<C2A1>¥åº·
def waitForServiceHealthy(String containerName, String serviceName, String sshOpts) {
def maxAttempts = env.HEALTH_CHECK_TIMEOUT.toInteger() / env.HEALTH_CHECK_INTERVAL.toInteger()
sh """
ssh ${sshOpts} root@${DEPLOY_HOST} '
echo "Waiting for ${serviceName} to be healthy..."
for i in \$(seq 1 ${maxAttempts}); do
STATUS=\$(docker inspect --format="{{.State.Health.Status}}" ${containerName} 2>/dev/null || echo "starting")
if [ "\$STATUS" = "healthy" ]; then
echo "�${serviceName} is healthy"
exit 0
elif [ "\$STATUS" = "unhealthy" ]; then
echo "â<>?${serviceName} is unhealthy"
echo "=== Last 100 lines of logs ==="
docker logs --tail 100 ${containerName}
exit 1
fi
ELAPSED=\$((i * ${HEALTH_CHECK_INTERVAL}))
echo "â<>?${serviceName} status: \$STATUS (\${ELAPSED}s/${HEALTH_CHECK_TIMEOUT}s)"
sleep ${HEALTH_CHECK_INTERVAL}
done
echo "â<>?${serviceName} health check timeout after ${HEALTH_CHECK_TIMEOUT}s"
echo "=== Full logs ==="
docker logs ${containerName}
exit 1
'
"""
}
// 检查æœ<C3A6>务å<C2A1>¥åº·çжæ€?
def checkServiceHealth(String containerName, String serviceName) {
def sshOpts = "-o StrictHostKeyChecking=no -o ConnectTimeout=10 -i ${SSH_KEY}"
sh """
ssh ${sshOpts} root@${DEPLOY_HOST} '
echo "Checking ${serviceName}..."
STATUS=\$(docker inspect --format="{{.State.Health.Status}}" ${containerName} 2>/dev/null || echo "not_found")
if [ "\$STATUS" = "healthy" ]; then
echo "�${serviceName} is healthy"
elif [ "\$STATUS" = "not_found" ]; then
echo "âš ï¸<C3AF> ${serviceName} not found (may not be deployed)"
else
echo "â<>?${serviceName} is \$STATUS"
docker logs --tail 50 ${containerName}
exit 1
fi
'
"""
}
// 按ä¾<C3A4>èµé¡ºåº<C3A5>æŽåº<C3A5>æœ<C3A6>åŠ?
@NonCPS
def sortServicesByDependency(def services) {
def deployOrder = [
'viewsh-gateway',
'viewsh-module-system-server',
'viewsh-module-infra-server',
'viewsh-module-iot-server',
'viewsh-module-iot-gateway'
]
return services.sort { a, b ->
deployOrder.indexOf(a) <=> deployOrder.indexOf(b)
}
}
// 获å<C2B7>æœ<C3A6>务对应的容器å<C2A8><C3A5>ç§?
@NonCPS
def getContainerNameForService(String service) {
switch(service) {
case 'viewsh-gateway':
return 'aiot-gateway'
case 'viewsh-module-system-server':
return 'aiot-system-server'
case 'viewsh-module-infra-server':
return 'aiot-infra-server'
case 'viewsh-module-iot-server':
return 'aiot-iot-server'
case 'viewsh-module-iot-gateway':
return 'aiot-iot-gateway'
default:
return "aiot-${service}"
}
}
// 获å<C2B7>æœ<C3A6>务对应的模å<C2A1>—è·¯å¾?
@NonCPS
def getModulePathForService(String service) {
switch(service) {
case 'viewsh-gateway':
return 'viewsh-gateway'
case 'viewsh-module-system-server':
return 'viewsh-module-system/viewsh-module-system-server'
case 'viewsh-module-infra-server':
return 'viewsh-module-infra/viewsh-module-infra-server'
case 'viewsh-module-iot-server':
return 'viewsh-module-iot/viewsh-module-iot-server'
case 'viewsh-module-iot-gateway':
return 'viewsh-module-iot/viewsh-module-iot-gateway'
default:
return service
}
}