Jenkinsfile 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. pipeline {
  2. agent any
  3. options {
  4. buildDiscarder(logRotator(numToKeepStr: '10'))
  5. timestamps()
  6. ansiColor('xterm')
  7. disableConcurrentBuilds()
  8. }
  9. // 设计原则:
  10. // - 无 parameters 块,所以"Build Now"直接跑、不弹窗
  11. // - 环境(APP_ENV) 与 分支(BRANCH) 都从 Jenkins 自动注入的 GIT_BRANCH 推断
  12. // GIT_BRANCH 来自每个 Job 的 SCM 配置(Branch Specifier *​/dev、*​/sit、*​/uat 之一)
  13. // - 因此每台机器上 Job 唯一要配的就是 Branch Specifier,三台机器的 Jenkinsfile 内容完全一致
  14. stages {
  15. stage('Resolve Environment') {
  16. // 从 Jenkins 注入的 GIT_BRANCH 推断 APP_ENV
  17. // GIT_BRANCH 形如 "origin/uat" / "uat" / "refs/heads/uat",统一去前缀
  18. steps {
  19. script {
  20. def raw = env.GIT_BRANCH ?: env.BRANCH_NAME ?: ''
  21. def branch = raw.replaceFirst('^origin/', '').replaceFirst('^refs/heads/', '').trim()
  22. if (!(branch in ['dev', 'sit', 'uat'])) {
  23. error """
  24. ============ 无法识别当前部署环境 ============
  25. Jenkins 注入的 GIT_BRANCH = '${env.GIT_BRANCH}'
  26. 解析后的分支名 = '${branch}'
  27. 期望分支必须是 dev / sit / uat 之一。
  28. 请检查 Job 配置中的 Branch Specifier 是否正确:
  29. DEV 服务器 → */dev
  30. SIT 服务器 → */sit
  31. UAT 服务器 → */uat
  32. =============================================
  33. """.stripIndent()
  34. }
  35. // 把推断结果写回 env,后续 stage 用 env.APP_ENV 而不是 params.APP_ENV
  36. env.APP_ENV = branch
  37. env.BRANCH = branch
  38. // 派生命名规则(容器名/网络/日志/镜像 TAG 全部以 APP_ENV 区分)
  39. env.IMAGE_TAG = "${env.APP_ENV}-${env.BUILD_NUMBER}"
  40. env.IMAGE_STORE = "alien_store:${env.IMAGE_TAG}"
  41. env.IMAGE_GATEWAY = "alien_gateway:${env.IMAGE_TAG}"
  42. env.IMAGE_CONTRACT = "alien_contract:${env.IMAGE_TAG}"
  43. env.CONTAINER_NAME_STORE = "alien_store_py-${env.APP_ENV}"
  44. env.CONTAINER_NAME_GATEWAY = "alien_gateway_py-${env.APP_ENV}"
  45. env.CONTAINER_NAME_CONTRACT = "alien_contract_py-${env.APP_ENV}"
  46. env.DOCKER_NET = "alien_net_${env.APP_ENV}"
  47. env.LOG_ROOT = "/docker/python-${env.APP_ENV}/logs"
  48. }
  49. }
  50. }
  51. stage('Show Build Info') {
  52. steps {
  53. echo "============================================================"
  54. echo " 部署环境 : ${env.APP_ENV} (来自 GIT_BRANCH=${env.GIT_BRANCH})"
  55. echo " 部署分支 : ${env.BRANCH}"
  56. echo " 镜像 TAG : ${env.IMAGE_TAG}"
  57. echo " 容器网络 : ${env.DOCKER_NET}"
  58. echo " 日志根目录: ${env.LOG_ROOT}"
  59. echo "============================================================"
  60. }
  61. }
  62. stage('Load Env Port Mapping') {
  63. // 从 .env.${APP_ENV} 解析 GATEWAY_PORT,作为 docker -p 宿主端口映射用
  64. steps {
  65. script {
  66. def envFile = ".env.${env.APP_ENV}"
  67. if (!fileExists(envFile)) {
  68. error "缺少环境配置文件: ${envFile}(仓库里应该有这份;请检查 .gitignore 是否误屏蔽)"
  69. }
  70. def gatewayPort = sh(
  71. script: "grep -E '^GATEWAY_PORT=' ${envFile} | head -n1 | cut -d= -f2 | tr -d '\"' | tr -d \"'\"",
  72. returnStdout: true
  73. ).trim()
  74. if (!gatewayPort) {
  75. gatewayPort = "33333"
  76. }
  77. env.GATEWAY_PORT = gatewayPort
  78. echo "从 ${envFile} 解析到 GATEWAY_PORT=${env.GATEWAY_PORT}"
  79. }
  80. }
  81. }
  82. stage('Build Images') {
  83. steps {
  84. script {
  85. def buildArgs = "--build-arg APP_ENV=${env.APP_ENV}"
  86. sh "docker build ${buildArgs} -f alien_store/Dockerfile -t ${env.IMAGE_STORE} ."
  87. sh "docker build ${buildArgs} -f alien_gateway/Dockerfile -t ${env.IMAGE_GATEWAY} ."
  88. sh "docker build ${buildArgs} -f alien_contract/Dockerfile -t ${env.IMAGE_CONTRACT} ."
  89. }
  90. }
  91. }
  92. stage('Deploy') {
  93. steps {
  94. script {
  95. echo ">>> [${env.APP_ENV}] 部署镜像: ${env.IMAGE_STORE} / ${env.IMAGE_GATEWAY} / ${env.IMAGE_CONTRACT}"
  96. sh """
  97. set -e
  98. docker network create ${env.DOCKER_NET} 2>/dev/null || true
  99. mkdir -p ${env.LOG_ROOT}/store ${env.LOG_ROOT}/gateway ${env.LOG_ROOT}/contract
  100. docker rm -f ${env.CONTAINER_NAME_STORE} ${env.CONTAINER_NAME_GATEWAY} ${env.CONTAINER_NAME_CONTRACT} 2>/dev/null || true
  101. # 1) 下游:store
  102. # APP_ENV=${env.APP_ENV} 让 config.py 加载镜像内的 .env.${env.APP_ENV}
  103. docker run -d --name ${env.CONTAINER_NAME_STORE} \\
  104. --network ${env.DOCKER_NET} \\
  105. -e APP_ENV=${env.APP_ENV} \\
  106. -v ${env.LOG_ROOT}/store:/app/common/logs/alien_store \\
  107. --restart unless-stopped \\
  108. ${env.IMAGE_STORE}
  109. # 2) 下游:contract
  110. docker run -d --name ${env.CONTAINER_NAME_CONTRACT} \\
  111. --network ${env.DOCKER_NET} \\
  112. -e APP_ENV=${env.APP_ENV} \\
  113. -v ${env.LOG_ROOT}/contract:/app/common/logs/alien_contract \\
  114. --restart unless-stopped \\
  115. ${env.IMAGE_CONTRACT}
  116. # 3) 网关:gateway(唯一对外端口,并用 -e 把下游地址覆盖为容器名)
  117. docker run -d --name ${env.CONTAINER_NAME_GATEWAY} \\
  118. --network ${env.DOCKER_NET} \\
  119. -p ${env.GATEWAY_PORT}:${env.GATEWAY_PORT} \\
  120. -e APP_ENV=${env.APP_ENV} \\
  121. -e STORE_BASE_URL=http://${env.CONTAINER_NAME_STORE}:8001 \\
  122. -e CONTRACT_BASE_URL=http://${env.CONTAINER_NAME_CONTRACT}:8002 \\
  123. -v ${env.LOG_ROOT}/gateway:/app/common/logs/alien_gateway \\
  124. --restart unless-stopped \\
  125. ${env.IMAGE_GATEWAY}
  126. """
  127. }
  128. }
  129. }
  130. stage('Smoke Test') {
  131. steps {
  132. script {
  133. sh """
  134. sleep 3
  135. echo '--- /health ---'
  136. curl -sf http://127.0.0.1:${env.GATEWAY_PORT}/health || (echo 'gateway /health 失败' && exit 1)
  137. echo
  138. echo '--- /health/redis ---'
  139. curl -sf http://127.0.0.1:${env.GATEWAY_PORT}/health/redis || echo 'redis 健康检查未通过(非阻塞,部署继续)'
  140. """
  141. }
  142. }
  143. }
  144. }
  145. post {
  146. success {
  147. echo "[${env.APP_ENV}] 环境部署成功!gateway: http://<host>:${env.GATEWAY_PORT}"
  148. }
  149. failure {
  150. echo "[${env.APP_ENV}] 环境部署失败,请检查上面日志。"
  151. }
  152. always {
  153. cleanWs()
  154. }
  155. }
  156. }