Commit 7d36af4f authored by Clement Bois's avatar Clement Bois
Browse files

Merge branch 'feat/rolling-strategy' into 'master'

feat: rolling strategy

Closes #23

See merge request to-be-continuous/cloud-foundry!74
parents 9cf138de 178b688a
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
@@ -93,7 +93,9 @@ The Cloud Foundry template uses some global configuration used throughout all jo
| `base-app-name` / `CF_BASE_APP_NAME`           | Base application name                  | `$CI_PROJECT_NAME` ([see GitLab doc](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)) |
| `default-domain` / `CF_DEFAULT_DOMAIN`         | Default CF domain _(only define if you want to use a different domain from CF default)_ | _none_ |
| `default-route-path` / `CF_DEFAULT_ROUTE_PATH` | Default CF route path                  | _none_ |
| `default-push-args` / `CF_DEFAULT_PUSH_ARGS`   | Additional arguments for cf push command _(only define if you want has a specific need not med by the template)_ | _none_ |
| `scripts-dir` / `CF_SCRIPTS_DIR`               | Directory where CF scripts (manifest, hook scripts) are located| `.` _(root project dir)_ |
| `rolling-strategy` / `CF_ROLLING_STRATEGY`     | Use Cloud Foundry native zero-downtime deployment [strategy](https://docs.cloudfoundry.org/devguide/deploy-apps/rolling-deploy.html). See [Zero-downtime deployment](#zero-downtime-deployment) | `false` |

### Secrets management

@@ -145,6 +147,7 @@ Here are variables supported to configure review environments:
| `review-environment-scheme` / `CF_REVIEW_ENVIRONMENT_SCHEME` | The review environment protocol scheme | `https` |
| `review-environment-domain` / `CF_REVIEW_ENVIRONMENT_DOMAIN` | The review environment domain          | _none_ |
| `review-route-path` / `CF_REVIEW_ROUTE_PATH`                 | CF route path for `review` env         | `$CF_DEFAULT_ROUTE_PATH` |
| `review-push-args` / `CF_REVIEW_PUSH_ARGS`                   | Additional arguments for push command  | `$CF_DEFAULT_PUSH_ARGS` |
| `review-retired-app-suffix` / `CF_REVIEW_RETIRED_APP_SUFFIX` | If set, the app old version is not deleted/overriden but renamed with this suffix | _none_ |

Note: By default review `environment.url` will be built as `${CF_REVIEW_ENVIRONMENT_SCHEME}://${$CI_PROJECT_NAME}-${CI_ENVIRONMENT_SLUG}.${CF_REVIEW_ENVIRONMENT_DOMAIN}/${CF_REVIEW_ROUTE_PATH}`
@@ -167,6 +170,7 @@ Here are variables supported to configure the integration environment:
| `integ-app-name` / `CF_INTEG_APP_NAME`                     | Application name for `integration` env | `"${CF_BASE_APP_NAME}-integration"` |
| `integ-domain` / `CF_INTEG_DOMAIN`                         | CF domain for `integration` env        | `$CF_DEFAULT_DOMAIN` |
| `integ-route-path` / `CF_INTEG_ROUTE_PATH`                 | CF route path for `integration` env    | `$CF_DEFAULT_ROUTE_PATH` |
| `integ-push-args` / `CF_INTEG_PUSH_ARGS`                   | Additional arguments for push command  | `$CF_DEFAULT_PUSH_ARGS` |
| `integ-host-name` / `CF_INTEG_HOST_NAME`                   | Application host name for `integration` env | `"${CF_BASE_APP_NAME}-integration"` |
| `integ-zerodowntime` / `CF_INTEG_ZERODOWNTIME`             | Enables zero-downtime deployment on `integration` env | `false` |
| `integ-environment-url` / `CF_INTEG_ENVIRONMENT_URL`       | The integration environment url **including scheme** (ex: `https://my-application-integration.nonpublic.domain.com`). Do not use variable inside variable definition as it will result in a two level cascade variable and gitlab does not allow that. | _none_ |
@@ -191,6 +195,7 @@ Here are variables supported to configure the staging environment:
| `staging-app-name` / `CF_STAGING_APP_NAME`                     | Application name for `staging` env     | `"${CF_BASE_APP_NAME}-staging"` |
| `staging-domain` / `CF_STAGING_DOMAIN`                         | CF domain for `staging` env            | `$CF_DEFAULT_DOMAIN` |
| `staging-route-path` / `CF_STAGING_ROUTE_PATH`                 | CF route path for `integration` env    | `$CF_DEFAULT_ROUTE_PATH` |
| `staging-push-args` / `CF_STAGING_PUSH_ARGS`                   | Additional arguments for push command  | `$CF_DEFAULT_PUSH_ARGS` |
| `staging-host-name` / `CF_STAGING_HOST_NAME`                   | Application host name for `staging` env | `"${CF_BASE_APP_NAME}-staging"` |
| `staging-zerodowntime` / `CF_STAGING_ZERODOWNTIME`             | Enables zero-downtime deployment on `staging` env | `false` |
| `staging-environment-url` / `CF_STAGING_ENVIRONMENT_URL`       | The staging environment url **including scheme** (ex: `https://my-application-staging.nonpublic.domain.com`). Do not use variable inside variable definition as it will result in a two level cascade variable and gitlab does not allow that. | _none_ |
@@ -214,6 +219,7 @@ Here are variables supported to configure the production environment:
| `prod-app-name` / `CF_PROD_APP_NAME`                     | Application name for `production` env  | `$CF_BASE_APP_NAME` |
| `prod-domain` / `CF_PROD_DOMAIN`                         | CF domain for `production` env         | `$CF_DEFAULT_DOMAIN` |
| `prod-route-path` / `CF_PROD_ROUTE_PATH`                 | CF domain for `production` env | `$CF_DEFAULT_DOMAIN` |
| `prod-push-args` / `CF_PROD_PUSH_ARGS`                   | Additional arguments for push command  | `$CF_DEFAULT_PUSH_ARGS` |
| `prod-host-name` / `CF_PROD_HOST_NAME`                   | Application host name for `production` env | `$CF_DEFAULT_ROUTE_PATH` |
| `prod-deploy-strategy` / `CF_PROD_DEPLOY_STRATEGY`       | Defines the deployment to production strategy. One of `manual` (i.e. _one-click_) or `auto`. | `manual` |
| `prod-zerodowntime` / `CF_PROD_ZERODOWNTIME`             | Enables zero-downtime deployment on `production` env | `true` |
@@ -287,6 +293,24 @@ So, what can be done about that?
    * remind to delete your review env **manually before deleting the branch**
    * otherwise you'll have to do it afterwards from your computer (using `cf` CLI) or from the Cloud Foundry console.

### Zero-downtime deployment

Historically Cloud Foundry did not provided any feature for deploying a new version without service interruption. The documentation proposed a [blue-green method](https://docs.cloudfoundry.org/devguide/deploy-apps/blue-green.html) with was implemented in this template. It is enabled with the `CF_XXX_ZERODOWNTIME`variable and is enabled by default in production.

- This solution is complex, runs a full copy of the application and so requires to double the allocated resources _(cpu, ram and disk)_

Starting with CF CLI v7, a new builtin feature called [rolling startegy](https://docs.cloudfoundry.org/devguide/deploy-apps/rolling-deploy.html) was added. It allow to rotate instances one by one instead of switching all instances at once.

This feature can be enabled with `rolling-strategy` input or `CF_ROLLING_STRATEGY` variable. Enabling it as a few implications:

- All environments will use this strategy. So `$CF_XXX_ZERODOWNTIME` is ignored
- The `cf-pre-start.sh` hook is only called during the first deployment as `--no-start` is incompatible with the strategy
- During the next deployments, two versions of your application will accept requests concurrently. It may have a impact on:
  - Single instance application which will temporarily become concurrent
  - Atomic actions like database migrations which might break the previous version
- Deployment won't stop if `cf-readiness-check.sh` hook fails. So the `health-check-http-endpoint` should check all services availability.
- The `cf-post-bluegreen.sh` hook is not called as it is not a blue-green deployment

### Manifest processing

Deployment jobs support a versatile way to evaluate the **deployment manifest**.
+32 −0
Original line number Diff line number Diff line
@@ -38,6 +38,11 @@
      "description": "Global Cloud Foundry default CF route path _(only define if you want to add a route path to your application route)_",
      "advanced": true
    },
    {
      "name": "CF_DEFAULT_PUSH_ARGS",
      "description": "Global additional arguments for cf push command _(only define if you want has a specific need not med by the template)_",
      "advanced": true
    },
    {
      "name": "CF_USER",
      "description": "Global Cloud Foundry username",
@@ -61,6 +66,13 @@
      "description": "Directory where Cloud Foundry scripts (manifest, hook scripts) are located",
      "default": ".",
      "advanced": true
    },
    {
      "name": "CF_ROLLING_STRATEGY",
      "description": "Use Cloud Foundry native zero-downtime deployment strategy instead of the historical blue-green method _(ignores $CF_XXX_ZERODOWNTIME)_",
      "type": "boolean",
      "default": "false",
      "advanced": true
    }
  ],
  "features": [
@@ -109,6 +121,11 @@
          "description": "The review environment route path",
          "advanced": true
        },
        {
          "name": "CF_REVIEW_PUSH_ARGS",
          "description": "The review environment additional cf push arguments",
          "advanced": true
        },
        {
          "name": "CF_REVIEW_ZERODOWNTIME",
          "type": "boolean",
@@ -178,6 +195,11 @@
          "description": "The integration environment route path",
          "advanced": true
        },
        {
          "name": "CF_INTEG_PUSH_ARGS",
          "description": "The integration environment additional cf push arguments",
          "advanced": true
        },
        {
          "name": "CF_INTEG_ENVIRONMENT_URL",
          "type": "url",
@@ -247,6 +269,11 @@
          "description": "The staging environment route path",
          "advanced": true
        },
        {
          "name": "CF_STAGING_PUSH_ARGS",
          "description": "The staging environment additional cf push arguments",
          "advanced": true
        },
        {
          "name": "CF_STAGING_ENVIRONMENT_URL",
          "type": "url",
@@ -316,6 +343,11 @@
          "description": "The production environment route path",
          "advanced": true
        },
        {
          "name": "CF_PROD_PUSH_ARGS",
          "description": "The production environment additional cf push arguments",
          "advanced": true
        },
        {
          "name": "CF_PROD_ENVIRONMENT_URL",
          "type": "url",
+51 −9
Original line number Diff line number Diff line
@@ -24,6 +24,10 @@ spec:
    scripts-dir:
      description: Directory where Cloud Foundry scripts (manifest, hook scripts) are located
      default: "."
    rolling-strategy:
      description: Use Cloud Foundry native zero-downtime deployment strategy instead of the historical blue-green method _(ignores $CF_XXX_ZERODOWNTIME)_
      type: boolean
      default: false
    url:
      description: Global Cloud Foundry API url
      default: ''
@@ -39,6 +43,9 @@ spec:
    default-route-path:
      description: Global Cloud Foundry default CF route path _(only define if you want to add a route path to your application route)_
      default: ''
    default-push-args:
      description: Global additional arguments for cf push command _(only define if you want has a specific need not med by the template)_
      default: ''
    review-url:
      description: Cloud Foundry API url for review env _(only define if different from global)_
      default: ''
@@ -60,6 +67,9 @@ spec:
    review-route-path:
      description: The review environment route path
      default: ''
    review-push-args:
      description: The review environment additional cf push arguments
      default: ''
    review-zerodowntime:
      description: Enables zero-downtime deployment on review env
      type: boolean
@@ -101,6 +111,9 @@ spec:
    integ-route-path:
      description: The integration environment route path
      default: ''
    integ-push-args:
      description: The integration environment additional cf push arguments
      default: ''
    integ-zerodowntime:
      description: Enables zero-downtime deployment on integration env
      type: boolean
@@ -132,6 +145,9 @@ spec:
    staging-route-path:
      description: The staging environment route path
      default: ''
    staging-push-args:
      description: The staging environment additional cf push arguments
      default: ''
    staging-zerodowntime:
      description: Enables zero-downtime deployment on staging env
      type: boolean
@@ -163,6 +179,9 @@ spec:
    prod-route-path:
      description: The production environment route path
      default: ''
    prod-push-args:
      description: The production environment additional cf push arguments
      default: ''
    prod-zerodowntime:
      description: Enables zero-downtime deployment on production env
      type: boolean
@@ -214,12 +233,14 @@ variables:

  CF_MANIFEST_BASENAME: $[[ inputs.manifest-basename ]]
  CF_SCRIPTS_DIR: $[[ inputs.scripts-dir ]]
  CF_ROLLING_STRATEGY: $[[ inputs.rolling-strategy ]]

  CF_URL: $[[ inputs.url ]]
  CF_ORG: $[[ inputs.org ]]
  CF_DEFAULT_DOMAIN: $[[ inputs.default-domain ]]
  CF_BASE_APP_NAME: $[[ inputs.base-app-name ]]
  CF_DEFAULT_ROUTE_PATH: $[[ inputs.default-route-path ]]
  CF_DEFAULT_PUSH_ARGS: $[[ inputs.default-push-args ]]
  # [mandatory] CF_USER               : the Cloud Foundry username
  # [mandatory] CF_PASSWORD           : the Cloud Foundry password

@@ -230,6 +251,7 @@ variables:
  CF_REVIEW_APP_NAME: $[[ inputs.review-app-name ]]
  CF_REVIEW_HOST_NAME: $[[ inputs.review-host-name ]]
  CF_REVIEW_ROUTE_PATH: $[[ inputs.review-route-path ]]
  CF_REVIEW_PUSH_ARGS: $[[ inputs.review-push-args ]]
  CF_REVIEW_ZERODOWNTIME: $[[ inputs.review-zerodowntime ]]
  CF_REVIEW_RETIRED_APP_SUFFIX: $[[ inputs.review-retired-app-suffix ]]
  # [optional]  CF_REVIEW_USER        : specific Cloud Foundry username in review env (defaults to $CF_USER)
@@ -245,6 +267,7 @@ variables:
  CF_INTEG_APP_NAME: $[[ inputs.integ-app-name ]]
  CF_INTEG_HOST_NAME: $[[ inputs.integ-host-name ]]
  CF_INTEG_ROUTE_PATH: $[[ inputs.integ-route-path ]]
  CF_INTEG_PUSH_ARGS: $[[ inputs.integ-push-args ]]
  CF_INTEG_ZERODOWNTIME: $[[ inputs.integ-zerodowntime ]]
  CF_INTEG_RETIRED_APP_SUFFIX: $[[ inputs.integ-retired-app-suffix ]]
  CF_INTEG_ENVIRONMENT_URL: $[[ inputs.integ-environment-url ]]
@@ -258,6 +281,7 @@ variables:
  CF_STAGING_APP_NAME: $[[ inputs.staging-app-name ]]
  CF_STAGING_HOST_NAME: $[[ inputs.staging-host-name ]]
  CF_STAGING_ROUTE_PATH: $[[ inputs.staging-route-path ]]
  CF_STAGING_PUSH_ARGS: $[[ inputs.staging-push-args ]]
  CF_STAGING_ZERODOWNTIME: $[[ inputs.staging-zerodowntime ]]
  CF_STAGING_RETIRED_APP_SUFFIX: $[[ inputs.staging-retired-app-suffix ]]
  CF_STAGING_ENVIRONMENT_URL: $[[ inputs.staging-environment-url ]]
@@ -271,6 +295,7 @@ variables:
  CF_PROD_APP_NAME: $[[ inputs.prod-app-name ]]
  CF_PROD_HOST_NAME: $[[ inputs.prod-host-name ]]
  CF_PROD_ROUTE_PATH: $[[ inputs.prod-route-path ]]
  CF_PROD_PUSH_ARGS: $[[ inputs.prod-push-args ]]
  CF_PROD_ZERODOWNTIME: $[[ inputs.prod-zerodowntime ]]
  CF_PROD_RETIRED_APP_SUFFIX: $[[ inputs.prod-retired-app-suffix ]]
  CF_PROD_ENVIRONMENT_URL: $[[ inputs.prod-environment-url ]]
@@ -543,6 +568,8 @@ stages:
    log_info "--- routepath: \\e[33;1m${routepath:-(n/a)}\\e[0m"
    log_info "--- manifest: \\e[33;1m${manifestfile}\\e[0m"
    cf_push_args=("$tmpappname" "--vars-file" "$targetvarfile")
    # shellcheck disable=SC2206
    cf_push_args+=($pushargs)

    # extract routes from manifest and replace vars
    routes_from_manifest=$(get_routes "$manifestfile")
@@ -593,6 +620,13 @@ stages:
      cf start "$tmpappname"
    else
      log_info "--- auto-start: \\e[94;1myes\\e[0m"

      if [[ "$CF_ROLLING_STRATEGY" == "true" ]]
      then
        log_info "--- using rolling strategy for zero-downtime\\e[0m"
        cf_push_args+=("--strategy" "rolling")
      fi

      cf push "${cf_push_args[@]}"
    fi

@@ -660,10 +694,11 @@ stages:
    else
      export routepath="/$5"
    fi
    # 6th argument is explicit target appname (for blue/green)
    if [[ "$6" ]]
    export pushargs=$6
    # 7th argument is explicit target appname (for blue/green)
    if [[ "$7" ]]
    then
      appname="$6"
      appname="$7"
    fi

    # find manifest
@@ -710,6 +745,7 @@ stages:
    hostname_target=$3
    domain_target=$4
    vroute_path=$5
    push_args=$6

    log_info "--- \\e[32mblue_green_deploy\\e[0m (env: \\e[33;1m${env}\\e[0m)"
    app_tmp=${app_target}-tmp
@@ -726,7 +762,7 @@ stages:
      else
        # simple deployment
        log_info "app \\e[33;1m${app_target}\\e[0m not found: proceed with simple deployment"
        simple_deploy "$env" "$app_target" "$hostname_target" "$domain_target" "$vroute_path"
        simple_deploy "$env" "$app_target" "$hostname_target" "$domain_target" "$vroute_path" "$push_args"
        return 0
      fi
    fi
@@ -737,7 +773,7 @@ stages:
    # try/catch-like pattern
    set -E
    trap 'blue_green_rollback "$app_tmp"' ERR
    simple_deploy "$env" "$app_tmp" "$hostname_tmp" "$domain_tmp" "$vroute_path" "$app_target"
    simple_deploy "$env" "$app_tmp" "$hostname_tmp" "$domain_tmp" "$vroute_path" "$push_args" "$app_target"
    set +E

    # map target route(s) to "green"
@@ -805,14 +841,15 @@ stages:
    hostname=$4
    domain=${5:-$(default_domain)}
    routepath=${6}
    pushargs=${7}

    if [[ "$zdt" = "true" ]]
    if [[ "$zdt" = "true" ]] && [[ "$CF_ROLLING_STRATEGY" != "true" ]]
    then
      # blue/green deploy
      blue_green_deploy "$env" "$appname" "$hostname" "$domain" "$routepath"
      blue_green_deploy "$env" "$appname" "$hostname" "$domain" "$routepath" "$pushargs"
    else
      # simple deploy
      simple_deploy "$env" "$appname" "$hostname" "$domain" "$routepath"
      simple_deploy "$env" "$appname" "$hostname" "$domain" "$routepath" "$pushargs"
    fi

    # finally persist environment url
@@ -1138,6 +1175,7 @@ stages:
# @arg ENV_DOMAIN    : env-specific domain
# @arg ENV_ROUTE_PATH: env-specific route path
# @arg ENV_HOST_NAME : env-specific application hostname to use
# @arg ENV_PUSH_ARGS : env-specific additional push command arguments
# @arg ENV_RETIRED_APP_SUFFIX : If set, the app old version is not deleted/overriden but renamed with this suffix
.cf-deploy:
  extends: .cf-base
@@ -1157,7 +1195,7 @@ stages:
    # use $CI_ENVIRONMENT_SLUG for appname to avoid service name constraints (<=50 chars)
    # use $CI_ENVIRONMENT_SLUG for hostname to avoid constraints (<=63 chars)
    - manage_services "create"
    - deploy "$ENV_TYPE" "${ENV_ZERODOWNTIME:-false}" "${ENV_APP_NAME:-${CF_BASE_APP_NAME}${ENV_APP_SUFFIX}}" "${ENV_HOST_NAME:-${CF_BASE_APP_NAME}${ENV_APP_SUFFIX}}" "${ENV_DOMAIN:-${CF_DEFAULT_DOMAIN}}" "${ENV_ROUTE_PATH:-${CF_DEFAULT_ROUTE_PATH}}"
    - deploy "$ENV_TYPE" "${ENV_ZERODOWNTIME:-false}" "${ENV_APP_NAME:-${CF_BASE_APP_NAME}${ENV_APP_SUFFIX}}" "${ENV_HOST_NAME:-${CF_BASE_APP_NAME}${ENV_APP_SUFFIX}}" "${ENV_DOMAIN:-${CF_DEFAULT_DOMAIN}}" "${ENV_ROUTE_PATH:-${CF_DEFAULT_ROUTE_PATH}}" "${ENV_PUSH_ARGS:-${CF_DEFAULT_PUSH_ARGS}}"
  artifacts:
    name: "$ENV_TYPE env url or cf logs for $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
    when: always
@@ -1217,6 +1255,7 @@ cf-review:
    ENV_DOMAIN: "$CF_REVIEW_DOMAIN"
    ENV_HOST_NAME: "$CF_REVIEW_HOST_NAME"
    ENV_ROUTE_PATH: "$CF_REVIEW_ROUTE_PATH"
    ENV_PUSH_ARGS: "$CF_REVIEW_PUSH_ARGS"
    ENV_RETIRED_APP_SUFFIX: "$CF_REVIEW_RETIRED_APP_SUFFIX"
  environment:
    name: review/$CI_COMMIT_REF_NAME
@@ -1305,6 +1344,7 @@ cf-integration:
    ENV_DOMAIN: "$CF_INTEG_DOMAIN"
    ENV_HOST_NAME: "$CF_INTEG_HOST_NAME"
    ENV_ROUTE_PATH: "$CF_INTEG_ROUTE_PATH"
    ENV_PUSH_ARGS: "$CF_INTEG_PUSH_ARGS"
    ENV_RETIRED_APP_SUFFIX: "$CF_INTEG_RETIRED_APP_SUFFIX"
  environment:
    name: integration
@@ -1333,6 +1373,7 @@ cf-staging:
    ENV_DOMAIN: "$CF_STAGING_DOMAIN"
    ENV_HOST_NAME: "$CF_STAGING_HOST_NAME"
    ENV_ROUTE_PATH: "$CF_STAGING_ROUTE_PATH"
    ENV_PUSH_ARGS: "$CF_STAGING_PUSH_ARGS"
    ENV_RETIRED_APP_SUFFIX: "$CF_STAGING_RETIRED_APP_SUFFIX"
  environment:
    name: staging
@@ -1361,6 +1402,7 @@ cf-production:
    ENV_DOMAIN: "$CF_PROD_DOMAIN"
    ENV_HOST_NAME: "$CF_PROD_HOST_NAME"
    ENV_ROUTE_PATH: "$CF_PROD_ROUTE_PATH"
    ENV_PUSH_ARGS: "$CF_PROD_PUSH_ARGS"
    ENV_RETIRED_APP_SUFFIX: "$CF_PROD_RETIRED_APP_SUFFIX"
  environment:
    name: production