diff options
Diffstat (limited to 'doc/user/infrastructure/iac/terraform_state.md')
-rw-r--r-- | doc/user/infrastructure/iac/terraform_state.md | 446 |
1 files changed, 446 insertions, 0 deletions
diff --git a/doc/user/infrastructure/iac/terraform_state.md b/doc/user/infrastructure/iac/terraform_state.md new file mode 100644 index 00000000000..fb051c7fa14 --- /dev/null +++ b/doc/user/infrastructure/iac/terraform_state.md @@ -0,0 +1,446 @@ +--- +stage: Configure +group: Configure +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments +--- + +# GitLab managed Terraform State **(FREE)** + +> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2673) in GitLab 13.0. + +[Terraform remote backends](https://www.terraform.io/docs/language/settings/backends/index.html) +enable you to store the state file in a remote, shared store. GitLab uses the +[Terraform HTTP backend](https://www.terraform.io/docs/language/settings/backends/http.html) +to securely store the state files in local storage (the default) or +[the remote store of your choice](../../../administration/terraform_state.md). + +WARNING: +Using local storage (the default) on clustered deployments of GitLab will result in +a split state across nodes, making subsequent executions of Terraform inconsistent. +You are highly advised to use a remote storage in that case. + +The GitLab managed Terraform state backend can store your Terraform state easily and +securely, and spares you from setting up additional remote resources like +Amazon S3 or Google Cloud Storage. Its features include: + +- Versioning of Terraform state files. +- Supporting encryption of the state file both in transit and at rest. +- Locking and unlocking state. +- Remote Terraform plan and apply execution. + +A GitLab **administrator** must [setup the Terraform state storage configuration](../../../administration/terraform_state.md) +before using this feature. + +## Permissions for using Terraform + +In GitLab version 13.1, the [Maintainer role](../../permissions.md) was required to use a +GitLab managed Terraform state backend. In GitLab versions 13.2 and greater, the +[Maintainer role](../../permissions.md) is required to lock, unlock, and write to the state +(using `terraform apply`), while the [Developer role](../../permissions.md) is required to read +the state (using `terraform plan -lock=false`). + +## Set up GitLab-managed Terraform state + +To get started with a GitLab-managed Terraform state, there are two different options: + +- [Use a local machine](#get-started-using-local-development). +- [Use GitLab CI](#get-started-using-gitlab-ci). + +Terraform States can be found by navigating to a Project's +**{cloud-gear}** **Infrastructure > Terraform** page. + +### Get started using local development + +If you plan to only run `terraform plan` and `terraform apply` commands from your +local machine, this is a simple way to get started: + +1. Create your project on your GitLab instance. +1. Navigate to **Settings > General** and note your **Project name** + and **Project ID**. +1. Define the Terraform backend in your Terraform project to be: + + ```hcl + terraform { + backend "http" { + } + } + ``` + +1. Create a [Personal Access Token](../../profile/personal_access_tokens.md) with + the `api` scope. + +1. On your local machine, run `terraform init`, passing in the following options, + replacing `<YOUR-STATE-NAME>`, `<YOUR-PROJECT-ID>`, `<YOUR-USERNAME>` and + `<YOUR-ACCESS-TOKEN>` with the relevant values. This command initializes your + Terraform state, and stores that state in your GitLab project. The name of + your state can contain only uppercase and lowercase letters, decimal digits, + hyphens, and underscores. This example uses `gitlab.com`: + + ```shell + terraform init \ + -backend-config="address=https://gitlab.com/api/v4/projects/<YOUR-PROJECT-ID>/terraform/state/<YOUR-STATE-NAME>" \ + -backend-config="lock_address=https://gitlab.com/api/v4/projects/<YOUR-PROJECT-ID>/terraform/state/<YOUR-STATE-NAME>/lock" \ + -backend-config="unlock_address=https://gitlab.com/api/v4/projects/<YOUR-PROJECT-ID>/terraform/state/<YOUR-STATE-NAME>/lock" \ + -backend-config="username=<YOUR-USERNAME>" \ + -backend-config="password=<YOUR-ACCESS-TOKEN>" \ + -backend-config="lock_method=POST" \ + -backend-config="unlock_method=DELETE" \ + -backend-config="retry_wait_min=5" + ``` + +If you already have a GitLab-managed Terraform state, you can use the `terraform init` command +with the prepopulated parameters values: + +1. On the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Infrastructure > Terraform**. +1. Next to the environment you want to use, select the [Actions menu](#managing-state-files) + **{ellipsis_v}** and select **Copy Terraform init command**. + +You can now run `terraform plan` and `terraform apply` as you normally would. + +### Get started using GitLab CI + +If you don't want to start with local development, you can also use GitLab CI to +run your `terraform plan` and `terraform apply` commands. + +Next, [configure the backend](#configure-the-backend). + +#### Configure the backend + +After executing the `terraform init` command, you must configure the Terraform backend +and the CI YAML file: + +1. In your Terraform project, define the [HTTP backend](https://www.terraform.io/docs/language/settings/backends/http.html) + by adding the following code block in a `.tf` file (such as `backend.tf`) to + define the remote backend: + + ```hcl + terraform { + backend "http" { + } + } + ``` + +1. In the root directory of your project repository, configure a + `.gitlab-ci.yml` file. This example uses a pre-built image which includes a + `gitlab-terraform` helper. For supported Terraform versions, see the [GitLab + Terraform Images project](https://gitlab.com/gitlab-org/terraform-images). + + ```yaml + image: registry.gitlab.com/gitlab-org/terraform-images/stable:latest + ``` + +1. In the `.gitlab-ci.yml` file, define some CI/CD variables to ease + development. In this example, `TF_ROOT` is the directory where the Terraform + commands must be executed, `TF_ADDRESS` is the URL to the state on the GitLab + instance where this pipeline runs, and the final path segment in `TF_ADDRESS` + is the name of the Terraform state. Projects may have multiple states, and + this name is arbitrary, so in this example we set it to `example-production` + which corresponds with the directory we're using as our `TF_ROOT`, and we + ensure that the `.terraform` directory is cached between jobs in the pipeline + using a cache key based on the state name (`example-production`): + + ```yaml + variables: + TF_ROOT: ${CI_PROJECT_DIR}/environments/example/production + TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/example-production + + cache: + key: example-production + paths: + - ${TF_ROOT}/.terraform + ``` + +1. In a `before_script`, change to your `TF_ROOT`: + + ```yaml + before_script: + - cd ${TF_ROOT} + + stages: + - prepare + - validate + - build + - deploy + + init: + stage: prepare + script: + - gitlab-terraform init + + validate: + stage: validate + script: + - gitlab-terraform validate + + plan: + stage: build + script: + - gitlab-terraform plan + - gitlab-terraform plan-json + artifacts: + name: plan + paths: + - ${TF_ROOT}/plan.cache + reports: + terraform: ${TF_ROOT}/plan.json + + apply: + stage: deploy + environment: + name: production + script: + - gitlab-terraform apply + dependencies: + - plan + when: manual + only: + - master + ``` + +1. Push your project to GitLab, which triggers a CI job pipeline. This pipeline + runs the `gitlab-terraform init`, `gitlab-terraform validate`, and + `gitlab-terraform plan` commands. + +The output from the above `terraform` commands should be viewable in the job logs. + +WARNING: +Like any other job artifact, Terraform plan data is [viewable by anyone with Guest access](../../permissions.md) to the repository. +Neither Terraform nor GitLab encrypts the plan file by default. If your Terraform plan +includes sensitive data such as passwords, access tokens, or certificates, GitLab strongly +recommends encrypting plan output or modifying the project visibility settings. + +### Example project + +See [this reference project](https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-aws) using GitLab and Terraform to deploy a basic AWS EC2 in a custom VPC. + +## Using a GitLab managed Terraform state backend as a remote data source + +You can use a GitLab-managed Terraform state as a +[Terraform data source](https://www.terraform.io/docs/language/state/remote-state-data.html). +To use your existing Terraform state backend as a data source, provide the following details +as [Terraform input variables](https://www.terraform.io/docs/language/values/variables.html): + +- **address**: The URL of the remote state backend you want to use as a data source. + For example, `https://gitlab.com/api/v4/projects/<TARGET-PROJECT-ID>/terraform/state/<TARGET-STATE-NAME>`. +- **username**: The username to authenticate with the data source. If you are using a [Personal Access Token](../../profile/personal_access_tokens.md) for + authentication, this is your GitLab username. If you are using GitLab CI, this is `'gitlab-ci-token'`. +- **password**: The password to authenticate with the data source. If you are using a Personal Access Token for + authentication, this is the token value. If you are using GitLab CI, it is the contents of the `${CI_JOB_TOKEN}` CI/CD variable. + +An example setup is shown below: + +1. Create a file named `example.auto.tfvars` with the following contents: + + ```plaintext + example_remote_state_address=https://gitlab.com/api/v4/projects/<TARGET-PROJECT-ID>/terraform/state/<TARGET-STATE-NAME> + example_username=<GitLab username> + example_access_token=<GitLab Personal Access Token> + ``` + +1. Define the data source by adding the following code block in a `.tf` file (such as `data.tf`): + + ```hcl + data "terraform_remote_state" "example" { + backend = "http" + + config = { + address = var.example_remote_state_address + username = var.example_username + password = var.example_access_token + } + } + ``` + +Outputs from the data source can now be referenced in your Terraform resources +using `data.terraform_remote_state.example.outputs.<OUTPUT-NAME>`. + +You need at least the [Developer role](../../permissions.md) in the target project +to read the Terraform state. + +## Migrating to GitLab Managed Terraform state + +Terraform supports copying the state when the backend is changed or +reconfigured. This can be useful if you need to migrate from another backend to +GitLab managed Terraform state. Using a local terminal is recommended to run the commands needed for migrating to GitLab Managed Terraform state. + +The following example demonstrates how to change the state name, the same workflow is needed to migrate to GitLab Managed Terraform state from a different state storage backend. + +### Setting up the initial backend + +```shell +PROJECT_ID="<gitlab-project-id>" +TF_USERNAME="<gitlab-username>" +TF_PASSWORD="<gitlab-personal-access-token>" +TF_ADDRESS="https://gitlab.com/api/v4/projects/${PROJECT_ID}/terraform/state/old-state-name" + +terraform init \ + -backend-config=address=${TF_ADDRESS} \ + -backend-config=lock_address=${TF_ADDRESS}/lock \ + -backend-config=unlock_address=${TF_ADDRESS}/lock \ + -backend-config=username=${TF_USERNAME} \ + -backend-config=password=${TF_PASSWORD} \ + -backend-config=lock_method=POST \ + -backend-config=unlock_method=DELETE \ + -backend-config=retry_wait_min=5 +``` + +```plaintext +Initializing the backend... + +Successfully configured the backend "http"! Terraform will automatically +use this backend unless the backend configuration changes. + +Initializing provider plugins... + +Terraform has been successfully initialized! + +You may now begin working with Terraform. Try running "terraform plan" to see +any changes that are required for your infrastructure. All Terraform commands +should now work. + +If you ever set or change modules or backend configuration for Terraform, +rerun this command to reinitialize your working directory. If you forget, other +commands will detect it and remind you to do so if necessary. +``` + +### Changing the backend + +Now that `terraform init` has created a `.terraform/` directory that knows where +the old state is, you can tell it about the new location: + +```shell +TF_ADDRESS="https://gitlab.com/api/v4/projects/${PROJECT_ID}/terraform/state/new-state-name" + +terraform init \ + -backend-config=address=${TF_ADDRESS} \ + -backend-config=lock_address=${TF_ADDRESS}/lock \ + -backend-config=unlock_address=${TF_ADDRESS}/lock \ + -backend-config=username=${TF_USERNAME} \ + -backend-config=password=${TF_PASSWORD} \ + -backend-config=lock_method=POST \ + -backend-config=unlock_method=DELETE \ + -backend-config=retry_wait_min=5 +``` + +```plaintext +Initializing the backend... +Backend configuration changed! + +Terraform has detected that the configuration specified for the backend +has changed. Terraform will now check for existing state in the backends. + + +Acquiring state lock. This may take a few moments... +Do you want to copy existing state to the new backend? + Pre-existing state was found while migrating the previous "http" backend to the + newly configured "http" backend. No existing state was found in the newly + configured "http" backend. Do you want to copy this state to the new "http" + backend? Enter "yes" to copy and "no" to start with an empty state. + + Enter a value: yes + + +Successfully configured the backend "http"! Terraform will automatically +use this backend unless the backend configuration changes. + +Initializing provider plugins... + +Terraform has been successfully initialized! + +You may now begin working with Terraform. Try running "terraform plan" to see +any changes that are required for your infrastructure. All Terraform commands +should now work. + +If you ever set or change modules or backend configuration for Terraform, +rerun this command to reinitialize your working directory. If you forget, other +commands will detect it and remind you to do so if necessary. +``` + +If you type `yes`, it copies your state from the old location to the new +location. You can then go back to running it in GitLab CI/CD. + +## Managing state files + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/273592) in GitLab 13.8. + +Users with Developer and greater [permissions](../../permissions.md) can view the +state files attached to a project at **Infrastructure > Terraform**. Users with the +Maintainer role can perform commands on the state files. The user interface +contains these fields: + +![Terraform state list](img/terraform_list_view_v13_8.png) + +- **Name**: The name of the environment, with a locked (**{lock}**) icon if the + state file is locked. +- **Pipeline**: A link to the most recent pipeline and its status. +- **Details**: Information about when the state file was created or changed. +- **Actions**: Actions you can take on the state file, including copying the `terraform init` command, + downloading, locking, unlocking, or [removing](#remove-a-state-file) the state file and versions. + +NOTE: +Additional improvements to the +[graphical interface for managing state files](https://gitlab.com/groups/gitlab-org/-/epics/4563) +are planned. + +## Remove a state file + +Users with Maintainer and greater [permissions](../../permissions.md) can use the +following options to remove a state file: + +- **GitLab UI**: Go to **Infrastructure > Terraform**. In the **Actions** column, + click the vertical ellipsis (**{ellipsis_v}**) button and select + **Remove state file and versions**. +- **GitLab REST API**: You can remove a state file by making a request to the + REST API. For example: + + ```shell + curl --header "Private-Token: <your_access_token>" --request DELETE "https://gitlab.example.com/api/v4/projects/<your_project_id>/terraform/state/<your_state_name>" + ``` + +- [GitLab GraphQL API](#remove-a-state-file-with-the-gitlab-graphql-api). + +### Remove a state file with the GitLab GraphQL API + +You can remove a state file by making a GraphQL API request. For example: + +```shell +mutation deleteState { + terraformStateDelete(input: { id: "<global_id_for_the_state>" }) { + errors + } +} +``` + +You can obtain the `<global_id_for_the_state>` by querying the list of states: + +```shell +query ProjectTerraformStates { + project(fullPath: "<your_project_path>") { + terraformStates { + nodes { + id + name + } + } + } +} +``` + +For those new to the GitLab GraphQL API, read +[Getting started with GitLab GraphQL API](../../../api/graphql/getting_started.md). + +## Troubleshooting + +### Unable to lock Terraform state files in CI jobs for `terraform apply` using a plan created in a previous job + +When passing `-backend-config=` to `terraform init`, Terraform persists these values inside the plan +cache file. This includes the `password` value. + +As a result, to create a plan and later use the same plan in another CI job, you might get the error +`Error: Error acquiring the state lock` errors when using `-backend-config=password=$CI_JOB_TOKEN`. +This happens because the value of `$CI_JOB_TOKEN` is only valid for the duration of the current job. + +As a workaround, use [http backend configuration variables](https://www.terraform.io/docs/language/settings/backends/http.html#configuration-variables) in your CI job, +which is what happens behind the scenes when following the +[Get started using GitLab CI](#get-started-using-gitlab-ci) instructions. |