Files
aiot-platform-cloud/Jenkinsfile
lzh 279510dd66
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: 将并行部署改为串行部署
- 避免多个 SSH 连接同时建立导致的冲突
- 保持构建阶段的并行,只有部署改为串行
2026-01-13 16:36:14 +08:00

266 lines
8.4 KiB
Groovy

// ============================================
// AIOT Platform - Jenkins Pipeline
// 并行构建 + Maven 依赖缓存优化
// ============================================
pipeline {
agent any
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timeout(time: 60, unit: 'MINUTES')
timestamps()
}
environment {
REGISTRY = 'localhost:5000'
IMAGE_TAG = "${env.BRANCH_NAME}-${env.BUILD_NUMBER}-${env.GIT_COMMIT?.take(8)}"
DEPS_IMAGE = "${REGISTRY}/aiot-deps:latest"
CORE_SERVICES = 'viewsh-gateway,viewsh-module-system-server,viewsh-module-infra-server,viewsh-module-iot-server,viewsh-module-iot-gateway'
DEPLOY_HOST = '172.19.0.1'
DEPLOY_PATH = '/opt/aiot-platform-cloud'
MAX_PARALLEL = 2 // 最大并行构建数
}
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 "⏭️ 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 services = env.SERVICES_TO_BUILD.split(',') as List
def batchSize = MAX_PARALLEL.toInteger()
echo "🔨 Building ${services.size()} services with parallelism=${batchSize}"
// 分批并行构建
services.collate(batchSize).each { batch ->
echo "📦 Building batch: ${batch.join(', ')}"
def parallelBuilds = [:]
batch.each { service ->
parallelBuilds[service] = {
buildAndPush(service)
}
}
parallel parallelBuilds
}
// 清理
sh "docker image prune -f || true"
}
}
}
stage('Deploy') {
when {
allOf {
expression { env.SERVICES_TO_BUILD != '' }
branch 'master'
}
}
steps {
script {
def services = env.SERVICES_TO_BUILD.split(',') as List
// 串行部署(避免 SSH 连接冲突)
services.each { service ->
deployService(service)
}
echo "🚀 Deployment completed!"
}
}
}
}
post {
success {
echo """
✅ 构建成功
📦 Services: ${env.SERVICES_TO_BUILD}
🏷️ Tag: ${IMAGE_TAG}
"""
}
failure {
echo "❌ 构建失败,请检查日志"
}
always {
sh 'df -h | grep -E "/$|/var" || true'
sh 'docker system df || true'
}
}
}
// ============================================
// 辅助函数
// ============================================
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
}
def triggerAll = ['pom.xml', 'viewsh-framework', 'viewsh-dependencies', 'Jenkinsfile', 'docker/']
if (triggerAll.any { changedFiles.contains(it) }) {
return env.CORE_SERVICES
}
def services = []
env.CORE_SERVICES.split(',').each { service ->
def path = getModulePath(service).split('/')[0]
if (changedFiles.contains(path)) {
services.add(service)
}
}
return services.isEmpty() ? env.CORE_SERVICES : services.join(',')
}
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()
// 如果 pom.xml 或基础模块变化,需要重建依赖镜像
def depsFiles = ['pom.xml', 'viewsh-dependencies', 'viewsh-framework']
if (changedFiles == 'all') {
return 'true'
}
return depsFiles.any { changedFiles.contains(it) } ? 'true' : 'false'
}
def depsImageExists() {
def result = sh(
script: "docker manifest inspect ${env.DEPS_IMAGE} > /dev/null 2>&1",
returnStatus: true
)
return result == 0
}
def buildAndPush(String service) {
def modulePath = getModulePath(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"
}
def deployService(String service) {
echo "🚀 Deploying ${service}..."
sh """
ssh -o StrictHostKeyChecking=no root@${DEPLOY_HOST} '
cd ${DEPLOY_PATH}
docker compose -f docker-compose.core.yml pull ${service}
docker compose -f docker-compose.core.yml up -d ${service}
'
"""
// 健康检查
sh """
ssh -o StrictHostKeyChecking=no root@${DEPLOY_HOST} '
for i in 1 2 3 4 5 6 7 8 9 10 11 12; do
STATUS=\$(docker inspect --format="{{.State.Health.Status}}" aiot-${service} 2>/dev/null || echo "starting")
[ "\$STATUS" = "healthy" ] && echo "${service} is healthy" && exit 0
sleep 10
done
echo "${service} health check timeout"
'
"""
echo "✅ ${service} deployed"
}
def getModulePath(String service) {
def map = [
'viewsh-gateway': 'viewsh-gateway',
'viewsh-module-system-server': 'viewsh-module-system/viewsh-module-system-server',
'viewsh-module-infra-server': 'viewsh-module-infra/viewsh-module-infra-server',
'viewsh-module-iot-server': 'viewsh-module-iot/viewsh-module-iot-server',
'viewsh-module-iot-gateway': 'viewsh-module-iot/viewsh-module-iot-gateway'
]
return map[service] ?: service
}