Skip to content
Snippets Groups Projects
README.md 20.2 KiB
Newer Older
Pierre Smeyers's avatar
Pierre Smeyers committed
# GitLab CI template for Helm

This project implements a generic GitLab CI template for [Helm](https://helm.sh/).

## Overview

This template implements continuous delivery/continuous deployment based on [Helm](https://helm.sh/) for projects hosted
on [Kubernetes](https://kubernetes.io) platforms.

It provides several features, usable in different modes (by configuration).

### Review environments

The template supports **review** environments: those are dynamic and ephemeral environments to deploy your
_ongoing developments_ (a.k.a. _feature_ or _topic_ branches).

When enabled, it deploys the result from upstream build stages to a dedicated and temporary environment.
It is only active for non-production, non-integration branches.

It is a strict equivalent of GitLab's [Review Apps](https://docs.gitlab.com/ee/ci/review_apps/) feature.

It also comes with a _cleanup_ job (accessible either from the _environments_ page, or from the pipeline view).

### Integration environment

If you're using a Git Workflow with an integration branch (such as [Gitflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow)),
the template supports an **integration** environment.

When enabled, it deploys the result from upstream build stages to a dedicated environment.
It is only active for your integration branch (`develop` by default).

### Production environments

Lastly, the template supports 2 environments associated to your production branch (`master` by default):

* a **staging** environment (an iso-prod environment meant for testing and validation purpose),
* the **production** environment.

You're free to enable whichever or both, and you can also choose your deployment-to-production policy:

* **continuous deployment**: automatic deployment to production (when the upstream pipeline is successful),
* **continuous delivery**: deployment to production can be triggered manually (when the upstream pipeline is successful).

## Usage

### Include

In order to include this template in your project, add the following to your `gitlab-ci.yml`:

```yaml
include:
  - project: 'Orange-OpenSource/tbc/helm'
    ref: '1.2.1'
Pierre Smeyers's avatar
Pierre Smeyers committed
    file: '/templates/gitlab-ci-helm.yml'
```

### Global configuration

The Helm template uses some global configuration used throughout all jobs.

| Name                  | description                            | default value     |
| --------------------- | -------------------------------------- | ----------------- |
| `HELM_CLI_IMAGE`      | The Docker image used to run Helm      | `alpine/helm:latest` - **it is highly recommended to set the CLI version compatible with your Kubernetes server** |
| `HELM_CHART_DIR`      | The folder in which is stored the Helm chart | `.` |

### Secrets management

Here are some advices about your **secrets** (variables marked with a :lock:):

1. Manage them as [project or group CI/CD variables](https://docs.gitlab.com/ee/ci/variables/#create-a-custom-variable-in-the-ui):
    * [**masked**](https://docs.gitlab.com/ee/ci/variables/#mask-a-custom-variable) to prevent them from being inadvertently
      displayed in your job logs,
    * [**protected**](https://docs.gitlab.com/ee/ci/variables/#protect-a-custom-variable) if you want to secure some secrets
      you don't want everyone in the project to have access to (for instance production secrets).
2. In case a secret contains [characters that prevent it from being masked](https://docs.gitlab.com/ee/ci/variables/#masked-variable-requirements),
  simply define its value as the [Base64](https://en.wikipedia.org/wiki/Base64) encoded value prefixed with `@b64@`:
  it will then be possible to mask it and the template will automatically decode it prior to using it.
3. Don't forget to escape special characters (ex: `$` -> `$$`).

:warning: your [Values files](https://helm.sh/docs/chart_template_guide/values_files/) **may** contain variable patterns such as `${MY_SECRET}`.
If so, those patterns will be evaluated (replaced) with actual environment values. This is a safe way of managing your application secrets.

### Deploy & cleanup jobs

The Helm template declares deployment & cleanup jobs for each supported environment.

It supports 2 deployment cases:

* using an **external** Helm chart (retrieved from a repository),
* using an **internal** Helm chart (located in the project).

Here are global configuration variables for deploy jobs.

| Name                  | description                            | default value     |
| --------------------- | -------------------------------------- | ----------------- |
| `KUBE_NAMESPACE`      | The default Kubernetes namespace to use | _none_ but this variable is automatically set by [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html) when enabled |
| :lock: `HELM_DEFAULT_KUBE_CONFIG` | The default kubeconfig content to use | `$KUBECONFIG` (thus supports the [GitLab Kubernetes integration](https://docs.gitlab.com/ee/user/project/clusters/index.html) when enabled) |
| `HELM_DEPLOY_ARGS`    | The Helm [command with options](https://helm.sh/docs/helm/helm_upgrade/) to deploy the application (_without dynamic arguments such as release name and chart_) | `upgrade --install --atomic --timeout 120s` |
| `HELM_DELETE_ARGS`    | The Helm [command with options](https://helm.sh/docs/helm/helm_uninstall/) to cleanup the application (_without dynamic arguments such as release name_) | `uninstall` |
| `HELM_DEPLOY_CHART`   | The Helm [chart](https://helm.sh/docs/topics/charts/) to deploy. _Only required if you want to deploy an **external** chart._  | _none_ |
| `HELM_REPOS`          | The Helm [chart repositories](https://helm.sh/docs/topics/chart_repository/) to use (formatted as `repo_name_1@:repo_url_1 repo_name_2@:repo_url_2 ...`) | `stable@https://charts.helm.sh/stable bitnami@https://charts.bitnami.com/bitnami` |
| `HELM_BASE_APP_NAME`  | Base application name                  | `$CI_PROJECT_NAME` ([see GitLab doc](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html)) |

Each deployment job produces _output variables_ that are propagated to downstream jobs (using [dotenv artifacts](https://docs.gitlab.com/ee/ci/pipelines/job_artifacts.html#artifactsreportsdotenv)):

* `environment_type`: set to the type of environment (`review`, `integration`, `staging` or `production`),
* `environment_name`: the application name (see below),
* `environment_url`: set to `$CI_ENVIRONMENT_URL`.

They may be freely used in downstream jobs (for instance to run acceptance tests against the latest deployed environment).

Here are configuration details for each environment.

#### Review environments

Review environments are dynamic and ephemeral environments to deploy your _ongoing developments_ (a.k.a. _feature_ or _topic_ branches).

They are **enabled by default** and can be disabled by setting the `HELM_REVIEW_DISABLED` variable (see below).

Here are variables supported to configure review environments:

| Name                     | description                            | default value     |
| ------------------------ | -------------------------------------- | ----------------- |
| `HELM_REVIEW_DISABLED`   | Set to disable `review` env            | _none_ (enabled) |
| `HELM_REVIEW_APP_NAME`   | Application name for `review` env      | `"${HELM_BASE_APP_NAME}-${CI_ENVIRONMENT_SLUG}"` (ex: `myproject-review-fix-bug-12`) |
| `HELM_REVIEW_NAMESPACE`  | The Kubernetes namespace to use for `review` env _(only define to override default)_ | `$KUBE_NAMESPACE` |
| :lock: `HELM_REVIEW_KUBE_CONFIG` | kubeconfig content used for `review` env _(only define to override default)_ | `$HELM_DEFAULT_KUBE_CONFIG` |
| `HELM_REVIEW_VALUES`     | The [Values file](https://helm.sh/docs/chart_template_guide/values_files/) to use with `review` environments | _none_ |
| `HELM_REVIEW_ENVIRONMENT_SCHEME` | The review environment protocol scheme | `https` |
| `HELM_REVIEW_ENVIRONMENT_DOMAIN` | The review environment domain | _none_ |

Note: By default review `environment.url` will be built as `${HELM_REVIEW_ENVIRONMENT_SCHEME}://${$CI_PROJECT_NAME}-${CI_ENVIRONMENT_SLUG}.${HELM_REVIEW_ENVIRONMENT_DOMAIN}`

#### Integration environment

The integration environment is the environment associated to your integration branch (`develop` by default).

It is **enabled by default** and can be disabled by setting the `HELM_INTEG_DISABLED` variable (see below).

Here are variables supported to configure the integration environment:

| Name                     | description                            | default value     |
| ------------------------ | -------------------------------------- | ----------------- |
| `HELM_INTEG_DISABLED`    | Set to disable `integration` env       | _none_ (enabled) |
| `HELM_INTEG_APP_NAME`    | Application name for `integration` env | `$HELM_BASE_APP_NAME-integration` |
| `HELM_INTEG_NAMESPACE`   | The Kubernetes namespace to use for `integration` env _(only define to override default)_ | `$KUBE_NAMESPACE` |
| :lock: `HELM_INTEG_KUBE_CONFIG` | kubeconfig content used for `integration` env _(only define to override default)_ | `$HELM_DEFAULT_KUBE_CONFIG` |
| `HELM_INTEG_VALUES`      | The [Values file](https://helm.sh/docs/chart_template_guide/values_files/) to use with the `integration` environment | _none_ |
| `HELM_INTEG_ENVIRONMENT_URL` | The integration environment url **including scheme** (ex: `https://my-application-integration.nonpublic.k8s.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_ |

#### Staging environment

The staging environment is an iso-prod environment meant for testing and validation purpose associated to your production branch (`master` by default).

It is **enabled by default** and can be disabled by setting the `HELM_STAGING_DISABLED` variable (see below).

Here are variables supported to configure the staging environment:

| Name                     | description                            | default value     |
| ------------------------ | -------------------------------------- | ----------------- |
| `HELM_STAGING_DISABLED`  | Set to disable `staging` env           | _none_ (enabled) |
| `HELM_STAGING_APP_NAME`  | Application name for `staging` env     | `$HELM_BASE_APP_NAME-staging` |
| `HELM_STAGING_NAMESPACE` | The Kubernetes namespace to use for `staging` env _(only define to override default)_ | `$KUBE_NAMESPACE` |
| :lock: `HELM_STAGING_KUBE_CONFIG` | kubeconfig content used for `staging` env _(only define to override default)_ | `$HELM_DEFAULT_KUBE_CONFIG` |
| `HELM_STAGING_VALUES`    | The [Values file](https://helm.sh/docs/chart_template_guide/values_files/) to use with the staging environment | _none_ |
| `HELM_STAGING_ENVIRONMENT_URL` | The staging environment url **including scheme** (ex: `https://my-application-staging.nonpublic.k8s.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_ |

#### Production environment

The production environment is the final deployment environment associated with your production branch (`master` by default).

It is **enabled by default** and can be disabled by setting the `HELM_PROD_DISABLED` variable (see below).

Here are variables supported to configure the production environment:

| Name                     | description                            | default value     |
| ------------------------ | -------------------------------------- | ----------------- |
| `HELM_PROD_DISABLED`     | Set to disable `production` env        | _none_ (enabled)  |
| `HELM_PROD_APP_NAME`     | Application name for `production` env  | `$HELM_BASE_APP_NAME` |
| `HELM_PROD_NAMESPACE`    | The Kubernetes namespace to use for `production` env _(only define to override default)_ | `$KUBE_NAMESPACE` |
| :lock: `HELM_PROD_KUBE_CONFIG` | kubeconfig content used for `production` env _(only define to override default)_ | `$HELM_DEFAULT_KUBE_CONFIG` |
| `AUTODEPLOY_TO_PROD`     | Set this variable to auto-deploy to production. If not set deployment to production will be `manual` (default behaviour). | _none_ (disabled) |
| `HELM_PROD_VALUES`       | The [Values file](https://helm.sh/docs/chart_template_guide/values_files/) to use with the production environment | _none_ |
| `HELM_PROD_ENVIRONMENT_URL` | The production environment url **including scheme** (ex: `https://my-application.public.k8s.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_ |

#### Dynamic Values

You have to be aware that your deployment (and cleanup) scripts have to be able to cope with various environments
(`review`, `integration`, `staging` and `production`), each with different application names, exposed routes, settings, ...

Part of this complexity can be handled by the lookup policies described above (ex: one resource per env).

In order to be able to implement some **genericity** in your scripts and templates, you should use generic [values](https://helm.sh/docs/chart_best_practices/values/)
dynamically set and passed by the template:

* `env`: the environment type (`review`, `integration`, `staging` or `production`)
* `hostname`: the environment hostname, extracted from `${CI_ENVIRONMENT_URL}` (got from [`environment:url`](https://docs.gitlab.com/ee/ci/yaml/#environmenturl) - see `OS_REVIEW_ENVIRONMENT_SCHEME`, `OS_REVIEW_ENVIRONMENT_DOMAIN`, `OS_STAGING_ENVIRONMENT_URL` and `OS_PROD_ENVIRONMENT_URL`)

### `helm-lint` job

This job [examines your chart for possible issues](https://helm.sh/docs/helm/helm_lint/) and uses the following variables:

| Name                  | description                              | default value     |
| --------------------- | ---------------------------------------- | ----------------- |
| `HELM_LINT_DISABLED`  | Set to disable Helm lint                 | _none_ (enabled) |
| `HELM_LINT_ARGS`      | The Helm [command with options](https://helm.sh/docs/helm/helm_lint/) to trigger the analysis (_without dynamic arguments such as the chart path_) | `lint --strict` |
| `HELM_DEPENDENCY_ARGS` | The Helm [command with options](https://helm.sh/docs/helm/helm_dependency_update/) to update on-disk the chart dependencies (_without dynamic arguments such as the chart path_) | `dependency update` |

### `helm-values-*-lint` job

These jobs perform a [Yaml Lint](https://github.com/adrienverge/yamllint) of your Helm [values file](https://helm.sh/docs/chart_template_guide/values_files/) and uses the following variables:

| Name                     | description                           | default value     |
| ------------------------ | ------------------------------------- | ----------------- |
| `HELM_YAMLLINT_IMAGE`    | The Docker image used to run YamlLint test | `cytopia/yamllint` |
| `HELM_YAMLLINT_DISABLED` | Set to disable Yaml lint              | _none_ (enabled) |
| `HELM_YAMLLINT_CONFIG`   | Config used with the yamllint tool    | `{extends: relaxed, rules: {line-length: {max: 160}}}` |
| `HELM_YAMLLINT_ARGS`     | Arguments used by the lint job        | `-f colored --strict` |

### `helm-*-score` job

This job runs [Kube-Score](https://kube-score.com/) on the resources to be created by Helm and uses the following variables:

| Name                  | description                              | default value     |
| --------------------- | ---------------------------------------- | ----------------- |
| `HELM_KUBE_SCORE_DISABLED`   | Set to disable [Kube-Score](https://kube-score.com/)   | _none_ (enabled) |
| `HELM_KUBE_SCORE_IMAGE`   | The Docker image used to run [Kube-Score](https://kube-score.com/)   | `zegl/kube-score:latest-helm3` |
| `HELM_KUBE_SCORE_ARGS`   | Arguments used by the helm-score job   | _none_ |

### `helm-package` job

This job [packages your chart into an archive](https://helm.sh/docs/helm/helm_package/) and uses the following variables:

| Name                  | description                              | default value     |
| --------------------- | ---------------------------------------- | ----------------- |
| `HELM_PACKAGE_ARGS`   | The Helm [command with options](https://helm.sh/docs/helm/helm_package/) to perform the packaging (_without dynamic arguments such as the chart path_)   | `package --dependency-update` |

| `HELM_SEMREL_RELEASE_DISABLED`   | Set to disable usage of semrel release info for helm package  | _none_ (enabled) |

#### `semantic-release` integration

If you activate the [`semantic-release-info` job from the `semantic-release` template](https://gitlab.com/Orange-OpenSource/tbc/semantic-release/#semantic-release-info-job), the `mvn-release` job will automatically use the generated next version info for both application version (`--app-version`) and chart version (`--version`).

If no next version info is generated by semantic-release, the package will be created either, but without versionning info.

Note: You can disable the `semantic-release` integration (as it's the `helm-package`job that will perform the release) with the `HELM_SEMREL_RELEASE_DISABLED` variable.

Pierre Smeyers's avatar
Pierre Smeyers committed
### `helm-publish` job

This job uses the following variables:

| Name                  | description                              | default value     |
| --------------------- | ---------------------------------------- | ----------------- |
| `HELM_PUBLISH_ARGS`   | Arguments used by the Helm publish job   | _none_ |
| `HELM_PUBLISH_DIR`    | The folder in which the job will publish the chart  | `.` |
| `HELM_PUBLISH_URL`    | The URL of the the chart to publish      | _none_ |

### `helm-test` job

This job runs [Helm tests](https://helm.sh/docs/topics/chart_tests/). The job definition must contain the helm test hook annotation: `helm.sh/hook: test`
You are welcome to nest your test suite under a `tests/` directory like `$HELM_CHART_DIR/templates/tests/` for more isolation.

It is **disabled by default** and can be enabled by setting the ``HELM_TEST_ENABLED`` variable (see below).

It  uses the following variables:

| Name                  | description                              | default value     |
| --------------------- | ---------------------------------------- | ----------------- |
| `HELM_TEST_ENABLED`  | Set to enable Helm test                 | _none_ (disabled) |
| `HELM_TEST_ARGS`      | The Helm [command with options](https://helm.sh/docs/helm/helm_test/) to perform acceptance test (_without dynamic arguments such as the chart path_) | `test`        |

## Variants

### Vault variant

This variant allows delegating your secrets management to a [Vault](https://www.vaultproject.io/) server.

#### Configuration

In order to be able to communicate with the Vault server, the variant requires the additional configuration parameters:

| Name              | description                            | default value     |
| ----------------- | -------------------------------------- | ----------------- |
| `VAULT_BASE_URL`  | The Vault server base API url          | _none_ |
| :lock: `VAULT_ROLE_ID`   | The [AppRole](https://www.vaultproject.io/docs/auth/approle) RoleID | **must be defined** |
| :lock: `VAULT_SECRET_ID` | The [AppRole](https://www.vaultproject.io/docs/auth/approle) SecretID | **must be defined** |

#### Usage

Then you may retrieve any of your secret(s) from Vault using the following syntax:

```text
@url@http://vault-secrets-provider/api/secrets/{secret_path}?field={field}
```

With:

| Name                             | description                            |
| -------------------------------- | -------------------------------------- |
| `secret_path` (_path parameter_) | this is your secret location in the Vault server |
| `field` (_query parameter_)      | parameter to access a single basic field from the secret JSON payload |

#### Example

```yaml
include:
  # main template
  - project: 'Orange-OpenSource/tbc/helm'
Pierre Smeyers's avatar
Pierre Smeyers committed
    file: '/templates/gitlab-ci-helm.yml'
  # Vault variant
  - project: 'Orange-OpenSource/tbc/helm'
Pierre Smeyers's avatar
Pierre Smeyers committed
    file: '/templates/gitlab-ci-helm-vault.yml'

variables:
    # Secrets managed by Vault
    HELM_DEFAULT_KUBE_CONFIG: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/my-app/helm/noprod?field=kube_config"
    HELM_PROD_KUBE_CONFIG: "@url@http://vault-secrets-provider/api/secrets/b7ecb6ebabc231/my-app/helm/prod?field=kube_config"
    VAULT_BASE_URL: "https://vault.acme.host/v1"
    # $VAULT_ROLE_ID and $VAULT_SECRET_ID defined as a secret CI/CD variable
```