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 回滚自动撤销。'
|
||
}
|
||
}
|
||
}
|