237 lines
8.8 KiB
Groovy
237 lines
8.8 KiB
Groovy
pipeline {
|
|
agent any
|
|
|
|
options {
|
|
disableConcurrentBuilds()
|
|
buildDiscarder(logRotator(numToKeepStr: '20'))
|
|
skipDefaultCheckout(true)
|
|
}
|
|
|
|
parameters {
|
|
string(
|
|
name: 'ACR_REGISTRY',
|
|
defaultValue: '',
|
|
description: 'ACR 登录域名,不包含 https://,优先使用与 ACK 同 VPC 的内网域名'
|
|
)
|
|
string(name: 'ACR_NAMESPACE', defaultValue: 'wonderq', description: 'ACR 命名空间')
|
|
string(name: 'API_REPOSITORY', defaultValue: 'wonderq-api', description: 'API 镜像仓库名')
|
|
string(name: 'ADMIN_REPOSITORY', defaultValue: 'wonderq-admin-ui', description: 'Admin UI 镜像仓库名')
|
|
string(name: 'ACK_NAMESPACE', defaultValue: 'wonderq-prod', description: 'ACK Kubernetes 命名空间')
|
|
string(name: 'API_DEPLOYMENT', defaultValue: 'wonderq-api', description: 'API Deployment 名称')
|
|
string(name: 'API_CONTAINER', defaultValue: 'api', description: 'API Deployment 中的容器名称')
|
|
string(name: 'ADMIN_DEPLOYMENT', defaultValue: 'wonderq-admin-ui', description: 'Admin UI Deployment 名称')
|
|
string(name: 'ADMIN_CONTAINER', defaultValue: 'admin-ui', description: 'Admin UI Deployment 中的容器名称')
|
|
string(
|
|
name: 'ADMIN_API_BASE_URL',
|
|
defaultValue: '',
|
|
description: 'Admin UI 构建时的 API 地址;ALB 使用同域名 /api 分流时保持为空'
|
|
)
|
|
string(
|
|
name: 'ACR_CREDENTIALS_ID',
|
|
defaultValue: 'acr-push-credential',
|
|
description: 'Jenkins 中 ACR 用户名密码 Credential ID'
|
|
)
|
|
string(
|
|
name: 'ACK_KUBECONFIG_CREDENTIALS_ID',
|
|
defaultValue: 'ack-prod-kubeconfig',
|
|
description: 'Jenkins 中 ACK kubeconfig Secret File Credential ID'
|
|
)
|
|
booleanParam(name: 'DEPLOY_TO_ACK', defaultValue: true, description: '推送镜像后是否更新 ACK Deployment')
|
|
}
|
|
|
|
environment {
|
|
DOCKER_BUILDKIT = '1'
|
|
}
|
|
|
|
stages {
|
|
stage('Checkout') {
|
|
steps {
|
|
checkout scm
|
|
script {
|
|
def commit = sh(script: 'git rev-parse --short=12 HEAD', returnStdout: true).trim()
|
|
env.IMAGE_TAG = "${commit}-${env.BUILD_NUMBER}"
|
|
env.API_IMAGE = "${params.ACR_REGISTRY}/${params.ACR_NAMESPACE}/${params.API_REPOSITORY}"
|
|
env.ADMIN_IMAGE = "${params.ACR_REGISTRY}/${params.ACR_NAMESPACE}/${params.ADMIN_REPOSITORY}"
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Validate') {
|
|
steps {
|
|
sh '''
|
|
set -eu
|
|
|
|
if [ -z "$ACR_REGISTRY" ]; then
|
|
echo "ACR_REGISTRY 不能为空" >&2
|
|
exit 1
|
|
fi
|
|
|
|
case "$ACR_REGISTRY" in
|
|
*://*)
|
|
echo "ACR_REGISTRY 只填写域名,不要包含协议" >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
command -v docker >/dev/null
|
|
docker version >/dev/null
|
|
|
|
if [ "$DEPLOY_TO_ACK" = "true" ]; then
|
|
command -v kubectl >/dev/null
|
|
fi
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Build API') {
|
|
steps {
|
|
sh '''
|
|
set -eu
|
|
docker build \
|
|
--tag "$API_IMAGE:$IMAGE_TAG" \
|
|
WonderQ-Admin
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Test API') {
|
|
steps {
|
|
sh '''
|
|
set -eu
|
|
|
|
test_container=''
|
|
cleanup_test_container() {
|
|
if [ -n "$test_container" ]; then
|
|
docker rm --force "$test_container" >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
trap cleanup_test_container EXIT
|
|
|
|
test_container="$(docker create \
|
|
--entrypoint python \
|
|
"$API_IMAGE:$IMAGE_TAG" \
|
|
-m pytest)"
|
|
docker cp WonderQ-Admin/tests/. "$test_container:/app/tests"
|
|
docker start --attach "$test_container"
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Build Admin UI') {
|
|
steps {
|
|
sh '''
|
|
set -eu
|
|
docker build \
|
|
--build-arg "VITE_API_BASE_URL=$ADMIN_API_BASE_URL" \
|
|
--tag "$ADMIN_IMAGE:$IMAGE_TAG" \
|
|
WonderQ-Admin-UI
|
|
|
|
docker run --rm \
|
|
--entrypoint nginx \
|
|
"$ADMIN_IMAGE:$IMAGE_TAG" \
|
|
-t
|
|
'''
|
|
}
|
|
}
|
|
|
|
stage('Push Images') {
|
|
steps {
|
|
withCredentials([
|
|
usernamePassword(
|
|
credentialsId: params.ACR_CREDENTIALS_ID,
|
|
usernameVariable: 'ACR_USERNAME',
|
|
passwordVariable: 'ACR_PASSWORD'
|
|
)
|
|
]) {
|
|
sh '''
|
|
set -eu
|
|
set +x
|
|
|
|
docker_config="$(mktemp -d)"
|
|
export DOCKER_CONFIG="$docker_config"
|
|
cleanup_docker_config() {
|
|
docker logout "$ACR_REGISTRY" >/dev/null 2>&1 || true
|
|
rm -rf -- "$docker_config"
|
|
}
|
|
trap cleanup_docker_config EXIT
|
|
|
|
printf '%s' "$ACR_PASSWORD" | docker login \
|
|
--username "$ACR_USERNAME" \
|
|
--password-stdin \
|
|
"$ACR_REGISTRY"
|
|
|
|
docker push "$API_IMAGE:$IMAGE_TAG"
|
|
docker push "$ADMIN_IMAGE:$IMAGE_TAG"
|
|
'''
|
|
}
|
|
}
|
|
}
|
|
|
|
stage('Deploy ACK') {
|
|
when {
|
|
expression { params.DEPLOY_TO_ACK }
|
|
}
|
|
steps {
|
|
withCredentials([
|
|
file(
|
|
credentialsId: params.ACK_KUBECONFIG_CREDENTIALS_ID,
|
|
variable: 'ACK_KUBECONFIG'
|
|
)
|
|
]) {
|
|
sh '''
|
|
set -eu
|
|
|
|
kubectl --kubeconfig "$ACK_KUBECONFIG" \
|
|
--namespace "$ACK_NAMESPACE" \
|
|
get deployment "$API_DEPLOYMENT" >/dev/null
|
|
|
|
kubectl --kubeconfig "$ACK_KUBECONFIG" \
|
|
--namespace "$ACK_NAMESPACE" \
|
|
get deployment "$ADMIN_DEPLOYMENT" >/dev/null
|
|
|
|
kubectl --kubeconfig "$ACK_KUBECONFIG" \
|
|
--namespace "$ACK_NAMESPACE" \
|
|
set image "deployment/$API_DEPLOYMENT" \
|
|
"$API_CONTAINER=$API_IMAGE:$IMAGE_TAG"
|
|
|
|
if ! kubectl --kubeconfig "$ACK_KUBECONFIG" \
|
|
--namespace "$ACK_NAMESPACE" \
|
|
rollout status "deployment/$API_DEPLOYMENT" --timeout=300s; then
|
|
kubectl --kubeconfig "$ACK_KUBECONFIG" \
|
|
--namespace "$ACK_NAMESPACE" \
|
|
rollout undo "deployment/$API_DEPLOYMENT" || true
|
|
exit 1
|
|
fi
|
|
|
|
kubectl --kubeconfig "$ACK_KUBECONFIG" \
|
|
--namespace "$ACK_NAMESPACE" \
|
|
set image "deployment/$ADMIN_DEPLOYMENT" \
|
|
"$ADMIN_CONTAINER=$ADMIN_IMAGE:$IMAGE_TAG"
|
|
|
|
if ! kubectl --kubeconfig "$ACK_KUBECONFIG" \
|
|
--namespace "$ACK_NAMESPACE" \
|
|
rollout status "deployment/$ADMIN_DEPLOYMENT" --timeout=300s; then
|
|
kubectl --kubeconfig "$ACK_KUBECONFIG" \
|
|
--namespace "$ACK_NAMESPACE" \
|
|
rollout undo "deployment/$ADMIN_DEPLOYMENT" || true
|
|
kubectl --kubeconfig "$ACK_KUBECONFIG" \
|
|
--namespace "$ACK_NAMESPACE" \
|
|
rollout undo "deployment/$API_DEPLOYMENT" || true
|
|
exit 1
|
|
fi
|
|
'''
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
post {
|
|
success {
|
|
echo "部署完成,镜像标签:${env.IMAGE_TAG}"
|
|
}
|
|
failure {
|
|
echo '流水线失败,请查看对应阶段日志;数据库迁移不会随 Deployment 回滚自动撤销。'
|
|
}
|
|
}
|
|
}
|