bluegreen.yaml 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. # This workflow performs a "blue-green" deployment, while preserving the original deployment object
  2. # and name. It accomplishes this by temporarily redirecting traffic to a *clone* of the original
  3. # deployment. Then after upgrading the original deployment to a later version, redirects traffic
  4. # back to the original (now upgraded) deployment.
  5. apiVersion: argoproj.io/v1alpha1
  6. kind: Workflow
  7. metadata:
  8. generateName: k8s-bluegreen-
  9. annotations:
  10. argocd.argoproj.io/hook: Sync
  11. spec:
  12. entrypoint: k8s-bluegreen
  13. arguments:
  14. parameters:
  15. - name: deployment-name
  16. - name: service-name
  17. - name: new-deployment-manifest
  18. - name: new-service-manifest
  19. templates:
  20. - name: k8s-bluegreen
  21. steps:
  22. # 1. Create a parallel Kubernetes deployment with tweaks to name and selectors
  23. - - name: create-blue-deployment
  24. template: clone-deployment
  25. # 2. Wait for parallel deployment to become ready
  26. - - name: wait-for-blue-deployment
  27. template: wait-deployment-ready
  28. arguments:
  29. parameters:
  30. - name: deployment-name
  31. value: '{{workflow.parameters.deployment-name}}-blue'
  32. # 3. Patch the named service to point to the parallel deployment app
  33. - - name: switch-service-to-blue-deployment
  34. template: patch-service
  35. # 4. Update the original deployment (receiving no traffic) with a new version
  36. - - name: apply-green-deployment
  37. template: apply-manifest
  38. arguments:
  39. parameters:
  40. - name: manifest
  41. value: '{{workflow.parameters.new-deployment-manifest}}'
  42. # 5. Wait for the original deployment, now updated, to become ready
  43. - - name: wait-for-green-deployment
  44. template: wait-deployment-ready
  45. arguments:
  46. parameters:
  47. - name: deployment-name
  48. value: '{{workflow.parameters.deployment-name}}'
  49. # dummy approval step for demo purposes. Sleeps for 30 seconds
  50. - - name: approve
  51. template: approve
  52. # 6. Patch the named service to point to the original, now updated app
  53. - - name: switch-service-to-green-deployment
  54. template: apply-manifest
  55. arguments:
  56. parameters:
  57. - name: manifest
  58. value: '{{workflow.parameters.new-service-manifest}}'
  59. # 7. Remove the cloned deployment (no longer receiving traffic)
  60. - - name: delete-cloned-deployment
  61. template: delete-deployment
  62. # end of steps
  63. # clone-deployment creates a "blue" clone of an existing deployment. The string -blue is appended to:
  64. # - metadata.name
  65. # - spec.selector.matchLabels
  66. # - spec.template.metadata.labels
  67. - name: clone-deployment
  68. container:
  69. image: argoproj/argoexec:v2.1.1
  70. command: [sh, -c, -x]
  71. args:
  72. - kubectl get --export -o json deployment.apps/{{workflow.parameters.deployment-name}} > /original-deploy &&
  73. jq -r '.metadata.name+="-blue" |
  74. .spec.template.metadata.labels += (.spec.template.metadata.labels | to_entries | map(select(.key != "app.kubernetes.io/instance")) | map(.value+="-blue") | from_entries) |
  75. .spec.selector.matchLabels += (.spec.selector.matchLabels | to_entries | map(select(.key != "app.kubernetes.io/instance")) | map(.value+="-blue") | from_entries)'
  76. /original-deploy > /cloned-deploy &&
  77. cat /cloned-deploy &&
  78. kubectl apply -o yaml -f /cloned-deploy
  79. # apply-manifest takes a kubernetes manifest and carrys over the app-name label (if present)
  80. # before running `kubectl apply`. The label is used by ArgoCD for monitoring.
  81. - name: apply-manifest
  82. inputs:
  83. parameters:
  84. - name: manifest
  85. artifacts:
  86. - name: manifest-file
  87. path: /manifest
  88. raw:
  89. data: '{{inputs.parameters.manifest}}'
  90. container:
  91. image: argoproj/argoexec:v2.1.1
  92. command: [sh, -c, -x]
  93. args:
  94. - cp /manifest /manifest-new &&
  95. APP_NAME=$(kubectl get -f /manifest-new -o json | jq -r '.metadata.labels."app.kubernetes.io/instance"') &&
  96. if [ "$APP_NAME" != "null" ]; then
  97. jq -r --arg APP_NAME "$APP_NAME" '.metadata.labels."app.kubernetes.io/instance" = $APP_NAME' /manifest-new > /manifest-new.tmp &&
  98. mv /manifest-new.tmp /manifest-new &&
  99. if [ "$(jq -r .spec.template.metadata.labels /manifest-new)" != "null" ]; then
  100. jq -r --arg APP_NAME "$APP_NAME" '.spec.template.metadata.labels."app.kubernetes.io/instance" = $APP_NAME' /manifest-new > /manifest-new.tmp &&
  101. mv /manifest-new.tmp /manifest-new ;
  102. fi ;
  103. fi &&
  104. cat /manifest-new &&
  105. kubectl apply -f /manifest-new
  106. # wait-deployment-ready waits for a deployment to become fully deployed and ready, using the
  107. # `kubectl rollout` command
  108. - name: wait-deployment-ready
  109. inputs:
  110. parameters:
  111. - name: deployment-name
  112. container:
  113. image: argoproj/argoexec:v2.1.1
  114. command: [kubectl, rollout, status, --watch=true, 'deployments/{{inputs.parameters.deployment-name}}']
  115. # patch-service updates the service selector labels to point to the "blue" deployment
  116. - name: patch-service
  117. container:
  118. image: argoproj/argoexec:v2.1.1
  119. command: [sh, -c, -x]
  120. args:
  121. - kubectl get service {{workflow.parameters.service-name}} --export -o json > /original-svc &&
  122. jq '.spec.selector = (.spec.selector | with_entries(.value+="-blue"))' /original-svc > /blue-svc &&
  123. kubectl apply -o yaml -f /blue-svc
  124. - name: delete-deployment
  125. resource:
  126. action: delete
  127. manifest: |
  128. apiVersion: apps/v1
  129. kind: Deployment
  130. metadata:
  131. name: {{workflow.parameters.deployment-name}}-blue
  132. - name: approve
  133. container:
  134. image: argoproj/argoexec:v2.1.1
  135. command: [sleep, "30"]