k8s-produ-lib.groovy 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /**
  2. * Harbor image promote + optional ACK deploy (loaded from Jenkinsfile, not a job entry).
  3. */
  4. def DEFAULT_SOURCE_TAG = 'uat-latest'
  5. def resolveTargetTag(def script, String targetTagParam) {
  6. def t = (targetTagParam ?: '').trim()
  7. return t ?: "produ-${script.env.BUILD_NUMBER}"
  8. }
  9. /** Empty SOURCE_TAG (incl. old Job cached params) falls back to uat-latest. */
  10. def requireSourceTag(def script, String sourceTag) {
  11. def srcTag = (sourceTag ?: '').trim()
  12. if (!srcTag) {
  13. script.echo ">>> SOURCE_TAG empty, using default: ${DEFAULT_SOURCE_TAG}"
  14. return DEFAULT_SOURCE_TAG
  15. }
  16. return srcTag
  17. }
  18. def promoteHarborImages(def script, List services, Map cfg) {
  19. def regHost = cfg.harborRegistry.trim()
  20. def proj = cfg.harborProject.trim()
  21. def srcTag = requireSourceTag(script, cfg.sourceTag)
  22. def tgtTag = cfg.targetTag
  23. def dryRun = cfg.dryRun == true
  24. def method = (cfg.promoteMethod ?: 'harbor-api').trim()
  25. def repos = services*.prodDir.join(' ')
  26. services.each { s ->
  27. def src = "${regHost}/${proj}/${s.prodDir}:${srcTag}"
  28. def tgt = "${regHost}/${proj}/${s.prodDir}:${tgtTag}"
  29. echo ">>> promote ${s.prodDir}: ${src} -> ${tgt} (method=${method})"
  30. }
  31. if (dryRun) {
  32. return
  33. }
  34. script.withCredentials([script.usernamePassword(
  35. credentialsId: cfg.harborCredentialsId,
  36. usernameVariable: 'HARBOR_USER',
  37. passwordVariable: 'HARBOR_PASS',
  38. )]) {
  39. script.sh """
  40. set -e
  41. REG='${regHost}'
  42. PROJ='${proj}'
  43. SRC='${srcTag}'
  44. TGT='${tgtTag}'
  45. METHOD='${method}'
  46. if [ "\${METHOD}" != "docker-pull" ] && ! command -v jq >/dev/null 2>&1; then
  47. echo '>>> jq missing, fallback to docker-pull'
  48. METHOD=docker-pull
  49. fi
  50. if [ "\${METHOD}" = "docker-pull" ]; then
  51. echo "\${HARBOR_PASS}" | docker login \${REG} -u "\${HARBOR_USER}" --password-stdin
  52. fi
  53. promote_repo() {
  54. local repo="\$1"
  55. local src_img="\${REG}/\${PROJ}/\${repo}:\${SRC}"
  56. local tgt_img="\${REG}/\${PROJ}/\${repo}:\${TGT}"
  57. if [ "\${METHOD}" = "docker-pull" ]; then
  58. echo ">>> docker promote \${repo}"
  59. docker pull "\${src_img}"
  60. docker tag "\${src_img}" "\${tgt_img}"
  61. docker push "\${tgt_img}"
  62. docker rmi "\${tgt_img}" "\${src_img}" 2>/dev/null || true
  63. return 0
  64. fi
  65. local enc_repo
  66. enc_repo=\$(printf '%s' "\${repo}" | jq -sRr @uri)
  67. echo ">>> Harbor API promote \${repo}: \${SRC} -> \${TGT}"
  68. if curl -fsS -X POST -u "\${HARBOR_USER}:\${HARBOR_PASS}" \\
  69. "http://\${REG}/api/v2.0/projects/\${PROJ}/repositories/\${enc_repo}/artifacts/\${SRC}/tags" \\
  70. -H 'Content-Type: application/json' \\
  71. -d "{\\"name\\":\\"\${TGT}\\"}"; then
  72. return 0
  73. fi
  74. echo ">>> WARN: Harbor API failed for \${repo}, fallback docker"
  75. docker pull "\${src_img}"
  76. docker tag "\${src_img}" "\${tgt_img}"
  77. docker push "\${tgt_img}"
  78. docker rmi "\${tgt_img}" "\${src_img}" 2>/dev/null || true
  79. }
  80. if [ "\${METHOD}" != "docker-pull" ]; then
  81. echo "\${HARBOR_PASS}" | docker login \${REG} -u "\${HARBOR_USER}" --password-stdin 2>/dev/null || true
  82. fi
  83. for repo in ${repos}; do
  84. promote_repo "\${repo}"
  85. done
  86. """
  87. }
  88. }
  89. def deployServicesToAck(def script, List services, Map cfg) {
  90. def parallelDeploy = cfg.parallelDeploy != false
  91. def regHost = cfg.harborRegistry.trim()
  92. def proj = cfg.harborProject.trim()
  93. def tgtTag = cfg.targetTag
  94. def strategy = cfg.deployStrategy ?: 'rolling'
  95. def ns = cfg.k8sNamespace
  96. def canaryWeight = (cfg.canaryWeight ?: '10').trim()
  97. def kubeCreds = cfg.kubeCredentialsId
  98. def runDeploy = { s ->
  99. def imageRef = "${regHost}/${proj}/${s.prodDir}:${tgtTag}"
  100. deployToAck(script, [
  101. k8sNamespace: ns,
  102. imageRef: imageRef,
  103. deployStrategy: strategy == 'canary' ? 'canary' : 'rolling',
  104. deploymentStable: s.deployName,
  105. deploymentCanary: "${s.deployName}-canary",
  106. ingressCanary: "${s.deployName}-canary",
  107. canaryWeight: canaryWeight,
  108. kubeCredentialsId: kubeCreds,
  109. ])
  110. }
  111. if (!parallelDeploy || services.size() <= 1) {
  112. services.each { runDeploy(it) }
  113. return
  114. }
  115. def branches = [:]
  116. services.each { s ->
  117. branches[s.prodDir] = { runDeploy(s) }
  118. }
  119. script.parallel branches
  120. }
  121. def deployToAck(def script, Map cfg) {
  122. def ns = cfg.k8sNamespace
  123. def imageRef = cfg.imageRef
  124. def strategy = cfg.deployStrategy
  125. def targetDeploy = strategy == 'canary' ? cfg.deploymentCanary : cfg.deploymentStable
  126. def canaryWeight = cfg.canaryWeight
  127. def ingressCanary = cfg.ingressCanary
  128. def kubeCreds = cfg.kubeCredentialsId
  129. script.withCredentials([script.file(credentialsId: kubeCreds, variable: 'KUBECONFIG')]) {
  130. script.sh """
  131. set -e
  132. kubectl config current-context
  133. kubectl -n ${ns} set image deployment/${targetDeploy} app=${imageRef} --record
  134. kubectl -n ${ns} rollout status deployment/${targetDeploy} --timeout=300s
  135. """
  136. if (strategy == 'canary') {
  137. script.sh """
  138. set -e
  139. kubectl -n ${ns} annotate ingress ${ingressCanary} \\
  140. nginx.ingress.kubernetes.io/canary=true \\
  141. nginx.ingress.kubernetes.io/canary-weight=${canaryWeight} \\
  142. --overwrite
  143. """
  144. }
  145. }
  146. }
  147. /** Single-service promote + ACK. Use script.params / script.env (do not pass params/env as args: CPS breaks method dispatch). */
  148. def promoteOneServiceToAck(def script, Map svc) {
  149. def regHost = script.params.HARBOR_REGISTRY.trim()
  150. def proj = script.params.HARBOR_PROJECT.trim()
  151. def srcTag = requireSourceTag(script, script.params.SOURCE_TAG)
  152. def tgtTag = resolveTargetTag(script, script.params.TARGET_TAG)
  153. def dryRun = script.params.DRY_RUN == true
  154. def strategy = script.params.DEPLOY_STRATEGY ?: 'rolling'
  155. script.env.TARGET_TAG_RESOLVED = tgtTag
  156. script.env.IMAGE_REF = "${regHost}/${proj}/${svc.prodDir}:${tgtTag}"
  157. promoteHarborImages(script, [svc], [
  158. harborRegistry: regHost,
  159. harborProject: proj,
  160. sourceTag: srcTag,
  161. targetTag: tgtTag,
  162. harborCredentialsId: script.env.HARBOR_CREDENTIALS,
  163. promoteMethod: (script.params.PROMOTE_METHOD ?: 'harbor-api').trim(),
  164. dryRun: dryRun,
  165. ])
  166. if (dryRun || strategy == 'skip') {
  167. return
  168. }
  169. deployToAck(script, [
  170. k8sNamespace: script.params.K8S_NAMESPACE,
  171. imageRef: script.env.IMAGE_REF,
  172. deployStrategy: strategy == 'canary' ? 'canary' : 'rolling',
  173. deploymentStable: svc.deployName,
  174. deploymentCanary: "${svc.deployName}-canary",
  175. ingressCanary: "${svc.deployName}-canary",
  176. canaryWeight: (script.params.CANARY_WEIGHT ?: '10').trim(),
  177. kubeCredentialsId: script.env.KUBECONFIG_CREDENTIALS,
  178. ])
  179. }
  180. return this