| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197 |
- pipeline {
- agent any
- options {
- buildDiscarder(logRotator(numToKeepStr: '10'))
- timestamps()
- ansiColor('xterm')
- }
- // 构建时手选环境与分支:一份 Jenkinsfile 服务 dev / sit / uat 三个环境
- parameters {
- choice(
- name: 'APP_ENV',
- choices: ['dev', 'sit', 'uat'],
- description: '部署到哪个环境'
- )
- string(
- name: 'BRANCH',
- defaultValue: 'dev',
- description: '要部署的 Git 分支(一般 dev 环境用 dev 分支,sit 用 sit,uat 用 uat;只有一条主干时统一填 main)'
- )
- }
- environment {
- // ---------- Dockerfile 路径 ----------
- DOCKERFILE_STORE = "alien_store/Dockerfile"
- DOCKERFILE_GATEWAY = "alien_gateway/Dockerfile"
- DOCKERFILE_CONTRACT = "alien_contract/Dockerfile"
- // ---------- 镜像仓库(如不推远端 Registry 就留空) ----------
- REGISTRY = ""
- REGISTRY_CRED = "registry-cred-id"
- // ---------- 三环境共用的命名规则:全部以 ${APP_ENV} 区分 ----------
- IMAGE_TAG = "${params.APP_ENV}-${env.BUILD_NUMBER}"
- IMAGE_STORE = "${REGISTRY ? REGISTRY + '/' : ''}alien_store:${IMAGE_TAG}"
- IMAGE_GATEWAY = "${REGISTRY ? REGISTRY + '/' : ''}alien_gateway:${IMAGE_TAG}"
- IMAGE_CONTRACT = "${REGISTRY ? REGISTRY + '/' : ''}alien_contract:${IMAGE_TAG}"
- CONTAINER_NAME_STORE = "alien_store_py-${params.APP_ENV}"
- CONTAINER_NAME_GATEWAY = "alien_gateway_py-${params.APP_ENV}"
- CONTAINER_NAME_CONTRACT = "alien_contract_py-${params.APP_ENV}"
- DOCKER_NET = "alien_net_${params.APP_ENV}"
- LOG_ROOT = "/docker/python-${params.APP_ENV}/logs"
- }
- stages {
- stage('Show Build Info') {
- steps {
- echo "============================================================"
- echo " 部署环境 : ${params.APP_ENV}"
- echo " 部署分支 : ${params.BRANCH}"
- echo " 镜像 TAG : ${IMAGE_TAG}"
- echo " 网络名 : ${DOCKER_NET}"
- echo " 日志根目录: ${LOG_ROOT}"
- echo "============================================================"
- }
- }
- // 注意:使用 "Pipeline script from SCM" 模式时 Jenkins 已自动 checkout
- // 但因为我们参数化了 BRANCH,这里显式 checkout 指定分支
- stage('Checkout') {
- steps {
- checkout([
- $class: 'GitSCM',
- branches: [[name: "*/${params.BRANCH}"]],
- userRemoteConfigs: scm.userRemoteConfigs
- ])
- }
- }
- stage('Load Env Port Mapping') {
- // 从 .env.${APP_ENV} 解析 GATEWAY_PORT,作为 docker -p 宿主端口映射用
- // (容器内监听端口同样靠 Dockerfile 默认 ARG/ENV 与 .env 配合)
- steps {
- script {
- def envFile = ".env.${params.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}"
- }
- }
- }
- stage('Build Images') {
- steps {
- script {
- def buildArgs = "--build-arg APP_ENV=${params.APP_ENV}"
- if (env.REGISTRY?.trim()) {
- withDockerRegistry(credentialsId: env.REGISTRY_CRED, url: "") {
- sh "docker build ${buildArgs} -f ${DOCKERFILE_STORE} -t ${IMAGE_STORE} ."
- sh "docker build ${buildArgs} -f ${DOCKERFILE_GATEWAY} -t ${IMAGE_GATEWAY} ."
- sh "docker build ${buildArgs} -f ${DOCKERFILE_CONTRACT} -t ${IMAGE_CONTRACT} ."
- }
- } else {
- sh "docker build ${buildArgs} -f ${DOCKERFILE_STORE} -t ${IMAGE_STORE} ."
- sh "docker build ${buildArgs} -f ${DOCKERFILE_GATEWAY} -t ${IMAGE_GATEWAY} ."
- sh "docker build ${buildArgs} -f ${DOCKERFILE_CONTRACT} -t ${IMAGE_CONTRACT} ."
- }
- }
- }
- }
- stage('Push Images') {
- when { expression { return env.REGISTRY?.trim() as boolean } }
- steps {
- withDockerRegistry(credentialsId: env.REGISTRY_CRED, url: "") {
- sh "docker push ${IMAGE_STORE}"
- sh "docker push ${IMAGE_GATEWAY}"
- sh "docker push ${IMAGE_CONTRACT}"
- }
- }
- }
- stage('Deploy') {
- steps {
- script {
- echo ">>> [${params.APP_ENV}] 部署镜像: ${IMAGE_STORE} / ${IMAGE_GATEWAY} / ${IMAGE_CONTRACT}"
- sh """
- set -e
- docker network create ${DOCKER_NET} 2>/dev/null || true
- mkdir -p ${LOG_ROOT}/store ${LOG_ROOT}/gateway ${LOG_ROOT}/contract
- docker rm -f ${CONTAINER_NAME_STORE} ${CONTAINER_NAME_GATEWAY} ${CONTAINER_NAME_CONTRACT} 2>/dev/null || true
- # 1) 下游:store
- # APP_ENV=${params.APP_ENV} 让 config.py 加载镜像内的 .env.${params.APP_ENV}
- docker run -d --name ${CONTAINER_NAME_STORE} \\
- --network ${DOCKER_NET} \\
- -e APP_ENV=${params.APP_ENV} \\
- -v ${LOG_ROOT}/store:/app/common/logs/alien_store \\
- --restart unless-stopped \\
- ${IMAGE_STORE}
- # 2) 下游:contract
- docker run -d --name ${CONTAINER_NAME_CONTRACT} \\
- --network ${DOCKER_NET} \\
- -e APP_ENV=${params.APP_ENV} \\
- -v ${LOG_ROOT}/contract:/app/common/logs/alien_contract \\
- --restart unless-stopped \\
- ${IMAGE_CONTRACT}
- # 3) 网关:gateway(唯一对外端口,并用 -e 把下游地址覆盖为容器名)
- docker run -d --name ${CONTAINER_NAME_GATEWAY} \\
- --network ${DOCKER_NET} \\
- -p ${env.GATEWAY_PORT}:${env.GATEWAY_PORT} \\
- -e APP_ENV=${params.APP_ENV} \\
- -e STORE_BASE_URL=http://${CONTAINER_NAME_STORE}:8001 \\
- -e CONTRACT_BASE_URL=http://${CONTAINER_NAME_CONTRACT}:8002 \\
- -v ${LOG_ROOT}/gateway:/app/common/logs/alien_gateway \\
- --restart unless-stopped \\
- ${IMAGE_GATEWAY}
- """
- }
- }
- }
- stage('Smoke Test') {
- steps {
- script {
- sh """
- sleep 3
- echo '--- /health ---'
- curl -sf http://127.0.0.1:${env.GATEWAY_PORT}/health || (echo 'gateway /health 失败' && exit 1)
- echo
- echo '--- /health/redis ---'
- curl -sf http://127.0.0.1:${env.GATEWAY_PORT}/health/redis || echo 'redis 健康检查未通过(非阻塞,部署继续)'
- """
- }
- }
- }
- }
- post {
- success {
- echo "[${params.APP_ENV}] 环境部署成功!gateway: http://<host>:${env.GATEWAY_PORT}"
- }
- failure {
- echo "[${params.APP_ENV}] 环境部署失败,请检查上面日志。"
- }
- always {
- cleanWs()
- }
- }
- }
|