|
|
@@ -1,3 +1,24 @@
|
|
|
+/**
|
|
|
+ * alien_py_cloud 统一部署流水线(dev / sit / uat / produ 共用)
|
|
|
+ *
|
|
|
+ * 设计原则:
|
|
|
+ * - 无 parameters,Build Now 直接跑
|
|
|
+ * - APP_ENV 由 Jenkins SCM 的 GIT_BRANCH 自动推断(*/dev、*/sit、*/uat、*/produ)
|
|
|
+ * - 端口、SSH 目标等环境差异全部来自 .env.${APP_ENV},代码与 Jenkinsfile 四分支一致
|
|
|
+ * - dev/sit/uat:Jenkins 本机 docker build + run
|
|
|
+ * - produ:SSH 到 DEPLOY_SSH_TARGET 远程 build + run(目标见 .env.produ)
|
|
|
+ */
|
|
|
+
|
|
|
+def PROD_SSH_CREDENTIALS_ID = 'e611a045-2fdc-4613-babd-a72d69bf9814'
|
|
|
+
|
|
|
+def readEnvVar(String envFile, String key, String defaultValue = '') {
|
|
|
+ def val = sh(
|
|
|
+ script: "grep -E '^${key}=' ${envFile} | head -n1 | cut -d= -f2- | tr -d '\"' | tr -d \"'\" | xargs || true",
|
|
|
+ returnStdout: true
|
|
|
+ ).trim()
|
|
|
+ return val ?: defaultValue
|
|
|
+}
|
|
|
+
|
|
|
pipeline {
|
|
|
agent any
|
|
|
|
|
|
@@ -7,42 +28,46 @@ pipeline {
|
|
|
disableConcurrentBuilds()
|
|
|
}
|
|
|
|
|
|
- // 设计原则:
|
|
|
- // - 无 parameters 块,所以"Build Now"直接跑、不弹窗
|
|
|
- // - 环境(APP_ENV) 与 分支(BRANCH) 都从 Jenkins 自动注入的 GIT_BRANCH 推断
|
|
|
- // GIT_BRANCH 来自每个 Job 的 SCM 配置(Branch Specifier */dev、*/sit、*/uat 之一)
|
|
|
- // - 因此每台机器上 Job 唯一要配的就是 Branch Specifier,三台机器的 Jenkinsfile 内容完全一致
|
|
|
-
|
|
|
stages {
|
|
|
|
|
|
stage('Resolve Environment') {
|
|
|
- // 从 Jenkins 注入的 GIT_BRANCH 推断 APP_ENV
|
|
|
- // GIT_BRANCH 形如 "origin/uat" / "uat" / "refs/heads/uat",统一去前缀
|
|
|
steps {
|
|
|
script {
|
|
|
def raw = env.GIT_BRANCH ?: env.BRANCH_NAME ?: ''
|
|
|
def branch = raw.replaceFirst('^origin/', '').replaceFirst('^refs/heads/', '').trim()
|
|
|
|
|
|
- if (!(branch in ['dev', 'sit', 'uat'])) {
|
|
|
+ if (!(branch in ['dev', 'sit', 'uat', 'produ'])) {
|
|
|
error """
|
|
|
============ 无法识别当前部署环境 ============
|
|
|
Jenkins 注入的 GIT_BRANCH = '${env.GIT_BRANCH}'
|
|
|
解析后的分支名 = '${branch}'
|
|
|
- 期望分支必须是 dev / sit / uat 之一。
|
|
|
+ 期望分支必须是 dev / sit / uat / produ 之一。
|
|
|
|
|
|
- 请检查 Job 配置中的 Branch Specifier 是否正确:
|
|
|
- DEV 服务器 → */dev
|
|
|
- SIT 服务器 → */sit
|
|
|
- UAT 服务器 → */uat
|
|
|
+ 请检查 Job 配置中的 Branch Specifier:
|
|
|
+ DEV → */dev
|
|
|
+ SIT → */sit
|
|
|
+ UAT → */uat
|
|
|
+ PRODU → */produ
|
|
|
=============================================
|
|
|
""".stripIndent()
|
|
|
}
|
|
|
|
|
|
- // 把推断结果写回 env,后续 stage 用 env.APP_ENV 而不是 params.APP_ENV
|
|
|
env.APP_ENV = branch
|
|
|
env.BRANCH = branch
|
|
|
+ def envFile = ".env.${env.APP_ENV}"
|
|
|
+
|
|
|
+ if (!fileExists(envFile)) {
|
|
|
+ error "缺少环境配置文件: ${envFile}"
|
|
|
+ }
|
|
|
+
|
|
|
+ env.STORE_PORT = readEnvVar(envFile, 'STORE_PORT', '8001')
|
|
|
+ env.GATEWAY_PORT = readEnvVar(envFile, 'GATEWAY_PORT', '33333')
|
|
|
+ env.CONTRACT_PORT = readEnvVar(envFile, 'CONTRACT_PORT', '8002')
|
|
|
+ env.GATEWAY_HOST_PORT = readEnvVar(envFile, 'GATEWAY_HOST_PORT', env.GATEWAY_PORT)
|
|
|
+ env.CONTRACT_HOST_PORT = readEnvVar(envFile, 'CONTRACT_HOST_PORT', '')
|
|
|
+
|
|
|
+ env.DEPLOY_MODE = (env.APP_ENV == 'produ') ? 'ssh' : 'local'
|
|
|
|
|
|
- // 派生命名规则(容器名/网络/日志/镜像 TAG 全部以 APP_ENV 区分)
|
|
|
env.IMAGE_TAG = "${env.APP_ENV}-${env.BUILD_NUMBER}"
|
|
|
env.IMAGE_STORE = "alien_store:${env.IMAGE_TAG}"
|
|
|
env.IMAGE_GATEWAY = "alien_gateway:${env.IMAGE_TAG}"
|
|
|
@@ -51,7 +76,24 @@ pipeline {
|
|
|
env.CONTAINER_NAME_GATEWAY = "alien_gateway_py-${env.APP_ENV}"
|
|
|
env.CONTAINER_NAME_CONTRACT = "alien_contract_py-${env.APP_ENV}"
|
|
|
env.DOCKER_NET = "alien_net_${env.APP_ENV}"
|
|
|
- env.LOG_ROOT = "/docker/python-${env.APP_ENV}/logs"
|
|
|
+
|
|
|
+ if (env.DEPLOY_MODE == 'ssh') {
|
|
|
+ env.SSH_TARGET = readEnvVar(envFile, 'DEPLOY_SSH_TARGET')
|
|
|
+ env.CODE_DIR_REMOTE = readEnvVar(envFile, 'DEPLOY_CODE_DIR')
|
|
|
+ env.LOG_ROOT = readEnvVar(envFile, 'DEPLOY_LOG_ROOT')
|
|
|
+ env.ENV_FILE_REMOTE = "${env.CODE_DIR_REMOTE}/.env.${env.APP_ENV}"
|
|
|
+ if (!env.SSH_TARGET || !env.CODE_DIR_REMOTE) {
|
|
|
+ error ".env.produ 缺少 DEPLOY_SSH_TARGET 或 DEPLOY_CODE_DIR"
|
|
|
+ }
|
|
|
+ if (!env.LOG_ROOT) {
|
|
|
+ env.LOG_ROOT = "${env.CODE_DIR_REMOTE}/logs"
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ env.LOG_ROOT = "/docker/python-${env.APP_ENV}/logs"
|
|
|
+ }
|
|
|
+
|
|
|
+ echo "APP_ENV=${env.APP_ENV} DEPLOY_MODE=${env.DEPLOY_MODE}"
|
|
|
+ echo "端口 store=${env.STORE_PORT} gateway=${env.GATEWAY_PORT} host=${env.GATEWAY_HOST_PORT} contract=${env.CONTRACT_PORT}"
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -59,32 +101,56 @@ pipeline {
|
|
|
stage('Show Build Info') {
|
|
|
steps {
|
|
|
echo "============================================================"
|
|
|
- echo " 部署环境 : ${env.APP_ENV} (来自 GIT_BRANCH=${env.GIT_BRANCH})"
|
|
|
- echo " 部署分支 : ${env.BRANCH}"
|
|
|
+ echo " 部署环境 : ${env.APP_ENV} (GIT_BRANCH=${env.GIT_BRANCH})"
|
|
|
+ echo " 部署模式 : ${env.DEPLOY_MODE}"
|
|
|
echo " 镜像 TAG : ${env.IMAGE_TAG}"
|
|
|
echo " 容器网络 : ${env.DOCKER_NET}"
|
|
|
- echo " 日志根目录: ${env.LOG_ROOT}"
|
|
|
+ echo " 日志目录 : ${env.LOG_ROOT}"
|
|
|
+ if (env.DEPLOY_MODE == 'ssh') {
|
|
|
+ echo " SSH 目标 : ${env.SSH_TARGET}"
|
|
|
+ echo " 远程目录 : ${env.CODE_DIR_REMOTE}"
|
|
|
+ }
|
|
|
echo "============================================================"
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- stage('Load Env Port Mapping') {
|
|
|
- // 从 .env.${APP_ENV} 解析 GATEWAY_PORT,作为 docker -p 宿主端口映射用
|
|
|
+ stage('Verify SSH') {
|
|
|
+ when {
|
|
|
+ expression { env.DEPLOY_MODE == 'ssh' }
|
|
|
+ }
|
|
|
steps {
|
|
|
- script {
|
|
|
- def envFile = ".env.${env.APP_ENV}"
|
|
|
- if (!fileExists(envFile)) {
|
|
|
- error "缺少环境配置文件: ${envFile}(仓库里应该有这份;请检查 .gitignore 是否误屏蔽)"
|
|
|
- }
|
|
|
- def gatewayPort = sh(
|
|
|
- script: "grep -E '^GATEWAY_PORT=' ${envFile} | head -n1 | cut -d= -f2 | tr -d '\"' | tr -d \"'\"",
|
|
|
- returnStdout: true
|
|
|
- ).trim()
|
|
|
- if (!gatewayPort) {
|
|
|
- gatewayPort = "33333"
|
|
|
- }
|
|
|
- env.GATEWAY_PORT = gatewayPort
|
|
|
- echo "从 ${envFile} 解析到 GATEWAY_PORT=${env.GATEWAY_PORT}"
|
|
|
+ sshagent(credentials: [PROD_SSH_CREDENTIALS_ID]) {
|
|
|
+ sh """
|
|
|
+ set -e
|
|
|
+ ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new '${env.SSH_TARGET}' \\
|
|
|
+ 'test -f ${env.ENV_FILE_REMOTE} && sudo docker info >/dev/null'
|
|
|
+ echo ">>> SSH OK: ${env.SSH_TARGET}"
|
|
|
+ """
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ stage('Sync Code') {
|
|
|
+ when {
|
|
|
+ expression { env.DEPLOY_MODE == 'ssh' }
|
|
|
+ }
|
|
|
+ steps {
|
|
|
+ sshagent(credentials: [PROD_SSH_CREDENTIALS_ID]) {
|
|
|
+ sh """
|
|
|
+ set -e
|
|
|
+ ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new '${env.SSH_TARGET}' bash -s <<'REMOTE_EOF'
|
|
|
+set -e
|
|
|
+cd '${env.CODE_DIR_REMOTE}'
|
|
|
+if [ ! -d .git ]; then
|
|
|
+ echo "ERROR: ${env.CODE_DIR_REMOTE} is not a git repo"
|
|
|
+ exit 1
|
|
|
+fi
|
|
|
+git fetch origin
|
|
|
+git checkout '${env.BRANCH}'
|
|
|
+git pull origin '${env.BRANCH}'
|
|
|
+echo ">>> git at \$(git rev-parse --short HEAD) on \$(hostname)"
|
|
|
+REMOTE_EOF
|
|
|
+ """
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -92,10 +158,38 @@ pipeline {
|
|
|
stage('Build Images') {
|
|
|
steps {
|
|
|
script {
|
|
|
- def buildArgs = "--build-arg APP_ENV=${env.APP_ENV}"
|
|
|
- sh "docker build ${buildArgs} -f alien_store/Dockerfile -t ${env.IMAGE_STORE} ."
|
|
|
- sh "docker build ${buildArgs} -f alien_gateway/Dockerfile -t ${env.IMAGE_GATEWAY} ."
|
|
|
- sh "docker build ${buildArgs} -f alien_contract/Dockerfile -t ${env.IMAGE_CONTRACT} ."
|
|
|
+ def buildCmds = """
|
|
|
+sudo docker build --build-arg APP_ENV=${env.APP_ENV} --build-arg STORE_PORT=${env.STORE_PORT} \\
|
|
|
+ -f alien_store/Dockerfile -t ${env.IMAGE_STORE} .
|
|
|
+sudo docker build --build-arg APP_ENV=${env.APP_ENV} --build-arg GATEWAY_PORT=${env.GATEWAY_PORT} \\
|
|
|
+ -f alien_gateway/Dockerfile -t ${env.IMAGE_GATEWAY} .
|
|
|
+sudo docker build --build-arg APP_ENV=${env.APP_ENV} --build-arg CONTRACT_PORT=${env.CONTRACT_PORT} \\
|
|
|
+ -f alien_contract/Dockerfile -t ${env.IMAGE_CONTRACT} .
|
|
|
+""".stripIndent()
|
|
|
+
|
|
|
+ if (env.DEPLOY_MODE == 'ssh') {
|
|
|
+ sshagent(credentials: [PROD_SSH_CREDENTIALS_ID]) {
|
|
|
+ sh """
|
|
|
+ set -e
|
|
|
+ ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new '${env.SSH_TARGET}' bash -s <<'REMOTE_EOF'
|
|
|
+set -e
|
|
|
+cd '${env.CODE_DIR_REMOTE}'
|
|
|
+${buildCmds}
|
|
|
+echo ">>> images built on \$(hostname)"
|
|
|
+REMOTE_EOF
|
|
|
+ """
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ sh """
|
|
|
+ set -e
|
|
|
+ docker build --build-arg APP_ENV=${env.APP_ENV} --build-arg STORE_PORT=${env.STORE_PORT} \\
|
|
|
+ -f alien_store/Dockerfile -t ${env.IMAGE_STORE} .
|
|
|
+ docker build --build-arg APP_ENV=${env.APP_ENV} --build-arg GATEWAY_PORT=${env.GATEWAY_PORT} \\
|
|
|
+ -f alien_gateway/Dockerfile -t ${env.IMAGE_GATEWAY} .
|
|
|
+ docker build --build-arg APP_ENV=${env.APP_ENV} --build-arg CONTRACT_PORT=${env.CONTRACT_PORT} \\
|
|
|
+ -f alien_contract/Dockerfile -t ${env.IMAGE_CONTRACT} .
|
|
|
+ """
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -103,58 +197,75 @@ pipeline {
|
|
|
stage('Deploy') {
|
|
|
steps {
|
|
|
script {
|
|
|
- echo ">>> [${env.APP_ENV}] 部署镜像: ${env.IMAGE_STORE} / ${env.IMAGE_GATEWAY} / ${env.IMAGE_CONTRACT}"
|
|
|
- sh """
|
|
|
- set -e
|
|
|
+ def dockerBin = (env.DEPLOY_MODE == 'ssh') ? 'sudo docker' : 'docker'
|
|
|
+ def envFileArgs = (env.DEPLOY_MODE == 'ssh') ? """
|
|
|
+ --env-file ${env.ENV_FILE_REMOTE} \\
|
|
|
+ -v ${env.ENV_FILE_REMOTE}:/app/.env.${env.APP_ENV}:ro \\""" : ''
|
|
|
|
|
|
- docker network create ${env.DOCKER_NET} 2>/dev/null || true
|
|
|
- mkdir -p ${env.LOG_ROOT}/store ${env.LOG_ROOT}/gateway ${env.LOG_ROOT}/contract
|
|
|
-
|
|
|
- docker rm -f ${env.CONTAINER_NAME_STORE} ${env.CONTAINER_NAME_GATEWAY} ${env.CONTAINER_NAME_CONTRACT} 2>/dev/null || true
|
|
|
-
|
|
|
- # 1) 下游:store
|
|
|
- # APP_ENV=${env.APP_ENV} 让 config.py 加载镜像内的 .env.${env.APP_ENV}
|
|
|
- docker run -d --name ${env.CONTAINER_NAME_STORE} \\
|
|
|
- --network ${env.DOCKER_NET} \\
|
|
|
- -e APP_ENV=${env.APP_ENV} \\
|
|
|
- -v ${env.LOG_ROOT}/store:/app/common/logs/alien_store \\
|
|
|
- --restart unless-stopped \\
|
|
|
- ${env.IMAGE_STORE}
|
|
|
-
|
|
|
- # 2) 下游:contract
|
|
|
- docker run -d --name ${env.CONTAINER_NAME_CONTRACT} \\
|
|
|
- --network ${env.DOCKER_NET} \\
|
|
|
- -e APP_ENV=${env.APP_ENV} \\
|
|
|
- -v ${env.LOG_ROOT}/contract:/app/common/logs/alien_contract \\
|
|
|
- --restart unless-stopped \\
|
|
|
- ${env.IMAGE_CONTRACT}
|
|
|
-
|
|
|
- # 3) 网关:gateway(唯一对外端口)
|
|
|
- # -e GATEWAY_PORT=... 是关键:必须覆盖 Dockerfile 的默认 33333,
|
|
|
- # 否则容器内 uvicorn 会监听默认端口,导致与宿主机映射端口对不上
|
|
|
- # pydantic-settings 也是环境变量优先于 .env 文件
|
|
|
- docker run -d --name ${env.CONTAINER_NAME_GATEWAY} \\
|
|
|
- --network ${env.DOCKER_NET} \\
|
|
|
- -p ${env.GATEWAY_PORT}:${env.GATEWAY_PORT} \\
|
|
|
- -e APP_ENV=${env.APP_ENV} \\
|
|
|
- -e GATEWAY_PORT=${env.GATEWAY_PORT} \\
|
|
|
- -e STORE_BASE_URL=http://${env.CONTAINER_NAME_STORE}:8001 \\
|
|
|
- -e CONTRACT_BASE_URL=http://${env.CONTAINER_NAME_CONTRACT}:8002 \\
|
|
|
- -v ${env.LOG_ROOT}/gateway:/app/common/logs/alien_gateway \\
|
|
|
- --restart unless-stopped \\
|
|
|
- ${env.IMAGE_GATEWAY}
|
|
|
- """
|
|
|
+ def contractPortArgs = env.CONTRACT_HOST_PORT?.trim() ?
|
|
|
+ "-p ${env.CONTRACT_HOST_PORT}:${env.CONTRACT_PORT} \\" : ''
|
|
|
+
|
|
|
+ def legacyCleanup = (env.APP_ENV == 'produ') ? """
|
|
|
+${dockerBin} rm -f py_esign_produ py_gateway_produ py_contract_produ esign alien_gateway_py alien_contract_py \\
|
|
|
+ alien_store_produ alien_gateway_produ alien_contract_produ 2>/dev/null || true
|
|
|
+""" : ''
|
|
|
+
|
|
|
+ def deployScript = """
|
|
|
+set -e
|
|
|
+${dockerBin} network create ${env.DOCKER_NET} 2>/dev/null || true
|
|
|
+mkdir -p ${env.LOG_ROOT}/store ${env.LOG_ROOT}/gateway ${env.LOG_ROOT}/contract
|
|
|
+
|
|
|
+${dockerBin} rm -f ${env.CONTAINER_NAME_STORE} ${env.CONTAINER_NAME_GATEWAY} ${env.CONTAINER_NAME_CONTRACT} 2>/dev/null || true
|
|
|
+${legacyCleanup}
|
|
|
+${dockerBin} run -d --name ${env.CONTAINER_NAME_STORE} \\
|
|
|
+ --network ${env.DOCKER_NET} \\
|
|
|
+${envFileArgs}
|
|
|
+ -e APP_ENV=${env.APP_ENV} \\
|
|
|
+ -e STORE_PORT=${env.STORE_PORT} \\
|
|
|
+ -v ${env.LOG_ROOT}/store:/app/common/logs/alien_store \\
|
|
|
+ --restart unless-stopped \\
|
|
|
+ ${env.IMAGE_STORE}
|
|
|
+
|
|
|
+${dockerBin} run -d --name ${env.CONTAINER_NAME_CONTRACT} \\
|
|
|
+ --network ${env.DOCKER_NET} \\
|
|
|
+ ${contractPortArgs}
|
|
|
+${envFileArgs}
|
|
|
+ -e APP_ENV=${env.APP_ENV} \\
|
|
|
+ -e CONTRACT_PORT=${env.CONTRACT_PORT} \\
|
|
|
+ -v ${env.LOG_ROOT}/contract:/app/common/logs/alien_contract \\
|
|
|
+ --restart unless-stopped \\
|
|
|
+ ${env.IMAGE_CONTRACT}
|
|
|
+
|
|
|
+${dockerBin} run -d --name ${env.CONTAINER_NAME_GATEWAY} \\
|
|
|
+ --network ${env.DOCKER_NET} \\
|
|
|
+ -p ${env.GATEWAY_HOST_PORT}:${env.GATEWAY_PORT} \\
|
|
|
+${envFileArgs}
|
|
|
+ -e APP_ENV=${env.APP_ENV} \\
|
|
|
+ -e GATEWAY_PORT=${env.GATEWAY_PORT} \\
|
|
|
+ -e STORE_BASE_URL=http://${env.CONTAINER_NAME_STORE}:${env.STORE_PORT} \\
|
|
|
+ -e CONTRACT_BASE_URL=http://${env.CONTAINER_NAME_CONTRACT}:${env.CONTRACT_PORT} \\
|
|
|
+ -v ${env.LOG_ROOT}/gateway:/app/common/logs/alien_gateway \\
|
|
|
+ --restart unless-stopped \\
|
|
|
+ ${env.IMAGE_GATEWAY}
|
|
|
+"""
|
|
|
+
|
|
|
+ if (env.DEPLOY_MODE == 'ssh') {
|
|
|
+ sshagent(credentials: [PROD_SSH_CREDENTIALS_ID]) {
|
|
|
+ sh """
|
|
|
+ set -e
|
|
|
+ ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new '${env.SSH_TARGET}' bash -s <<'REMOTE_EOF'
|
|
|
+${deployScript}
|
|
|
+REMOTE_EOF
|
|
|
+ """
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ sh deployScript
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
stage('Smoke Test') {
|
|
|
- // 注意:Jenkins 本身可能跑在 Docker 容器里,无法直接 curl 宿主机端口。
|
|
|
- // 这里采取两层验证:
|
|
|
- // 1) 硬验证:3 个业务容器必须都在 running 状态(不通过则 fail)
|
|
|
- // 2) 软验证:通过 docker exec 进入 gateway 容器内部跑 /health 自检
|
|
|
- // (HTTP 失败只 warn,不让 Pipeline 失败,因为这只代表 Jenkins 的网络位置看不到,
|
|
|
- // 不代表服务对外不可用,需要人工从其他位置验证)
|
|
|
steps {
|
|
|
script {
|
|
|
sleep(time: 5, unit: 'SECONDS')
|
|
|
@@ -164,29 +275,56 @@ pipeline {
|
|
|
env.CONTAINER_NAME_CONTRACT,
|
|
|
env.CONTAINER_NAME_GATEWAY
|
|
|
]
|
|
|
- def allRunning = true
|
|
|
- containers.each { c ->
|
|
|
- def status = sh(
|
|
|
- returnStdout: true,
|
|
|
- script: "docker inspect -f '{{.State.Status}}' ${c} 2>/dev/null || echo missing"
|
|
|
- ).trim()
|
|
|
- echo " ${c}: ${status}"
|
|
|
- if (status != 'running') {
|
|
|
- allRunning = false
|
|
|
- sh "docker logs --tail 50 ${c} || true"
|
|
|
- }
|
|
|
- }
|
|
|
- if (!allRunning) {
|
|
|
- error "存在容器未处于 running 状态,部署失败"
|
|
|
- }
|
|
|
- echo "✓ 3 个业务容器均在 running 状态"
|
|
|
|
|
|
- def healthCmd = """docker exec ${env.CONTAINER_NAME_GATEWAY} python -c 'import urllib.request as u; r=u.urlopen("http://localhost:${env.GATEWAY_PORT}/health", timeout=3); print("HTTP", r.status, r.read(200).decode())'"""
|
|
|
- def rc = sh(returnStatus: true, script: healthCmd)
|
|
|
- if (rc == 0) {
|
|
|
- echo "✓ gateway /health 通过"
|
|
|
+ if (env.DEPLOY_MODE == 'ssh') {
|
|
|
+ sshagent(credentials: [PROD_SSH_CREDENTIALS_ID]) {
|
|
|
+ def allRunning = true
|
|
|
+ containers.each { c ->
|
|
|
+ def status = sh(
|
|
|
+ returnStdout: true,
|
|
|
+ script: """
|
|
|
+ ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new '${env.SSH_TARGET}' \\
|
|
|
+ "sudo docker inspect -f '{{.State.Status}}' ${c} 2>/dev/null || echo missing"
|
|
|
+ """
|
|
|
+ ).trim()
|
|
|
+ echo " ${c}: ${status}"
|
|
|
+ if (status != 'running') { allRunning = false }
|
|
|
+ }
|
|
|
+ if (!allRunning) {
|
|
|
+ error "存在容器未处于 running 状态,部署失败"
|
|
|
+ }
|
|
|
+ def rc = sh(returnStatus: true, script: """
|
|
|
+ ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new '${env.SSH_TARGET}' \\
|
|
|
+ "sudo docker exec ${env.CONTAINER_NAME_GATEWAY} python -c \\"import urllib.request as u; r=u.urlopen('http://localhost:${env.GATEWAY_PORT}/health', timeout=3); print('HTTP', r.status)\\""
|
|
|
+ """)
|
|
|
+ if (rc == 0) {
|
|
|
+ echo "✓ gateway /health 通过"
|
|
|
+ } else {
|
|
|
+ echo "⚠️ gateway /health 未通过,请手动验证:curl http://<host>:${env.GATEWAY_HOST_PORT}/health"
|
|
|
+ }
|
|
|
+ }
|
|
|
} else {
|
|
|
- echo "⚠️ gateway /health 未通过(容器在跑但 HTTP 自检失败,请从外部手动验证:curl http://<host>:${env.GATEWAY_PORT}/health)"
|
|
|
+ def allRunning = true
|
|
|
+ containers.each { c ->
|
|
|
+ def status = sh(
|
|
|
+ returnStdout: true,
|
|
|
+ script: "docker inspect -f '{{.State.Status}}' ${c} 2>/dev/null || echo missing"
|
|
|
+ ).trim()
|
|
|
+ echo " ${c}: ${status}"
|
|
|
+ if (status != 'running') { allRunning = false }
|
|
|
+ }
|
|
|
+ if (!allRunning) {
|
|
|
+ error "存在容器未处于 running 状态,部署失败"
|
|
|
+ }
|
|
|
+ echo "✓ 3 个业务容器均在 running 状态"
|
|
|
+ def rc = sh(returnStatus: true, script: """
|
|
|
+ docker exec ${env.CONTAINER_NAME_GATEWAY} python -c 'import urllib.request as u; r=u.urlopen("http://localhost:${env.GATEWAY_PORT}/health", timeout=3); print("HTTP", r.status, r.read(200).decode())'
|
|
|
+ """)
|
|
|
+ if (rc == 0) {
|
|
|
+ echo "✓ gateway /health 通过"
|
|
|
+ } else {
|
|
|
+ echo "⚠️ gateway /health 未通过,请手动验证:curl http://<host>:${env.GATEWAY_HOST_PORT}/health"
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -195,10 +333,10 @@ pipeline {
|
|
|
|
|
|
post {
|
|
|
success {
|
|
|
- echo "[${env.APP_ENV}] 环境部署成功!请从外部访问 http://<host>:${env.GATEWAY_PORT}/health 验证"
|
|
|
+ echo "[${env.APP_ENV}] 部署成功!访问 http://<host>:${env.GATEWAY_HOST_PORT}/health"
|
|
|
}
|
|
|
failure {
|
|
|
- echo "[${env.APP_ENV}] 环境部署失败,请检查上面日志。"
|
|
|
+ echo "[${env.APP_ENV}] 部署失败,请检查日志。"
|
|
|
}
|
|
|
always {
|
|
|
cleanWs()
|