summaryrefslogtreecommitdiff
path: root/doc/user/infrastructure/iac/terraform_state.md
blob: fc86210ed56109eb04811589dde66d7341331a63 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
---
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/product/ux/technical-writing/#assignments
---

# GitLab-managed Terraform state **(FREE)**

> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2673) in GitLab 13.0.
> - Support for state names that contain periods introduced in GitLab 15.7 [with a flag](../../../administration/feature_flags.md) named `allow_dots_on_tf_state_names`. Disabled by default. [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106861) in GitLab 15.7.

FLAG:
On self-managed GitLab, by default support for state names that contain periods is not available. To make it available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `allow_dots_on_tf_state_names`. On GitLab.com, support for state names that contain periods is available. Requests for state files might generate HTTP 404 errors after enabling this feature. For more information, see [Troubleshooting the Terraform integration with GitLab](troubleshooting.md#state-not-found-if-the-state-name-contains-a-period).

Terraform uses state files to store details about your infrastructure configuration.
With Terraform remote [backends](https://www.terraform.io/language/settings/backends/configuration),
you can store the state file in a remote and shared store.

GitLab provides a [Terraform HTTP backend](https://www.terraform.io/language/settings/backends/http)
to securely store your state files with minimal configuration.

In GitLab, you can:

- Version your Terraform state files.
- Encrypt the state file both in transit and at rest.
- Lock and unlock states.
- Remotely execute `terraform plan` and `terraform apply` commands.

WARNING:
**Disaster recovery planning**
Terraform state files are encrypted with the lockbox Ruby gem when they are at rest on disk and in object storage.
[To decrypt a state file, GitLab must be available](https://gitlab.com/gitlab-org/gitlab/-/issues/335739).
If it is offline, and you use GitLab to deploy infrastructure that GitLab requires (like virtual machines,
Kubernetes clusters, or network components), you cannot access the state file easily or decrypt it.
Additionally, if GitLab serves up Terraform modules or other dependencies that are required to bootstrap GitLab,
these will be inaccessible. To work around this issue, make other arrangements to host or back up these dependencies,
or consider using a separate GitLab instance with no shared points of failure.

## Prerequisites

For self-managed GitLab, before you can use GitLab for your Terraform state files:

- An administrator must [set up Terraform state storage](../../../administration/terraform_state.md).
- You must enable the **Infrastructure** menu for your project. Go to **Settings > General**,
  expand **Visibility, project features, permissions**, and under **Operations**, turn on the toggle.

## Initialize a Terraform state as a backend by using GitLab CI/CD

After you execute the `terraform init` command, you can use GitLab CI/CD
to run `terraform` commands.

Prerequisites:

- To lock, unlock, and write to the state by using `terraform apply`, you must have at least the Maintainer role.
- To read the state by using `terraform plan -lock=false`, you must have at least the Developer role.

WARNING:
Like any other job artifact, Terraform plan data is viewable by anyone with the Guest role on the repository.
Neither Terraform nor GitLab encrypts the plan file by default. If your Terraform plan
includes sensitive data, like passwords, access tokens, or certificates, you should
encrypt plan output or modify the project visibility settings.

To configure GitLab CI/CD as a backend:

1. In your Terraform project, in a `.tf` file like `backend.tf`,
   define the [HTTP backend](https://developer.hashicorp.com/terraform/language/settings/backends/http):

   ```hcl
   terraform {
     backend "http" {
     }
   }
   ```

1. In the root directory of your project repository, create a `.gitlab-ci.yml` file. Use the
   [`Terraform.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Terraform.gitlab-ci.yml)
   template to populate it.
1. Push your project to GitLab. This action triggers a pipeline, which
   runs the `gitlab-terraform init`, `gitlab-terraform validate`, and
   `gitlab-terraform plan` commands.
1. Trigger the manual `deploy` job from the previous pipeline, which runs `gitlab-terraform apply` command, to provision the defined infrastructure.

The output from the above `terraform` commands should be viewable in the job logs.

The `gitlab-terraform` CLI is a wrapper around the `terraform` CLI. You can [view the source code of `gitlab-terraform`](https://gitlab.com/gitlab-org/terraform-images/-/blob/master/src/bin/gitlab-terraform.sh) if you're interested.

If you prefer to call the `terraform` commands explicitly, you can override
the template, and instead, use it as reference for what you can achieve.

### Customizing your Terraform environment variables

When you use the `Terraform.gitlab-ci.yml` template, you can use [Terraform HTTP configuration variables](https://www.terraform.io/language/settings/backends/http#configuration-variables) when you define your CI/CD jobs.

To customize your `terraform init` and override the Terraform configuration,
use environment variables instead of the `terraform init -backend-config=...` approach.
When you use `-backend-config`, the configuration is:

- Cached in the output of the `terraform plan` command.
- Usually passed forward to the `terraform apply` command.

This configuration can lead to problems like [being unable to lock Terraform state files in CI jobs](troubleshooting.md#unable-to-lock-terraform-state-files-in-ci-jobs-for-terraform-apply-using-a-plan-created-in-a-previous-job).

## Access the state from your local machine

You can access the GitLab-managed Terraform state from your local machine.

WARNING:
On clustered deployments of GitLab, you should not use local storage.
A split state can occur across nodes, making subsequent Terraform executions
inconsistent. Instead, use a remote storage resource.

1. Ensure the Terraform state has been
   [initialized for CI/CD](#initialize-a-terraform-state-as-a-backend-by-using-gitlab-cicd).
1. Copy a pre-populated Terraform `init` command:

   1. On the top bar, select **Main menu > Projects** and find your project.
   1. On the left sidebar, select **Infrastructure > Terraform**.
   1. Next to the environment you want to use, select **Actions**
      (**{ellipsis_v}**) and select **Copy Terraform init command**.

1. Open a terminal and run this command on your local machine.

## Migrate to a GitLab-managed Terraform state

Terraform supports copying the state when the backend changes or is
reconfigured. Use these actions to migrate from another backend to
GitLab-managed Terraform state.

You should use a local terminal 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.

### Set 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,
re-run this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
```

### Change 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 \
  -migrate-state \
  -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.

## Use your GitLab backend as a remote data source

You can use a GitLab-managed Terraform state backend as a
[Terraform data source](https://www.terraform.io/language/state/remote-state-data).

1. In your `main.tf` or other relevant file, declare these variables. Leave the values empty.

   ```hcl
   variable "example_remote_state_address" {
     type = string
     description = "Gitlab remote state file address"
   }

   variable "example_username" {
     type = string
     description = "Gitlab username to query remote state"
   }

   variable "example_access_token" {
     type = string
     description = "GitLab access token to query remote state"
   }
   ```

1. To override the values from the previous step, create a file named `example.auto.tfvars`. This file should **not** be versioned in your project repository.

   ```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. In a `.tf` file, define the data source by using [Terraform input variables](https://www.terraform.io/language/values/variables):

   ```hcl
   data "terraform_remote_state" "example" {
     backend = "http"

     config = {
       address = var.example_remote_state_address
       username = var.example_username
       password = var.example_access_token
     }
   }
   ```

   - **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 value is your GitLab username. If you are using GitLab CI/CD, this value is `'gitlab-ci-token'`.
   - **password**: The password to authenticate with the data source. If you are using a Personal Access Token for
     authentication, this value is the token value (the token must have the **API** scope).
     If you are using GitLab CI/CD, this value is the contents of the `${CI_JOB_TOKEN}` CI/CD variable.

Outputs from the data source can now be referenced in your Terraform resources
using `data.terraform_remote_state.example.outputs.<OUTPUT-NAME>`.

To read the Terraform state in the target project, you need at least the Developer role.

## Manage Terraform state files

> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/273592) in GitLab 13.8.

To view Terraform state files:

1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Infrastructure > Terraform**.

[An epic exists](https://gitlab.com/groups/gitlab-org/-/epics/4563) to track improvements to this UI.

### Manage individual Terraform state versions

> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207347) in GitLab 13.4.

Individual state versions can be managed using the GitLab REST API.

If you have at least the Developer role, you can retrieve state versions by using their serial number::

```shell
curl --header "Private-Token: <your_access_token>" "https://gitlab.example.com/api/v4/projects/<your_project_id>/terraform/state/<your_state_name>/versions/<version-serial>"
```

If you have at least the Maintainer role, you can remove state versions by using their serial number:

```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>/versions/<version-serial>"
```

### Remove a state file

If you have at least the Maintainer role, you can remove a state file.

1. On the left sidebar, select **Infrastructure > Terraform**.
1. In the **Actions** column, select **Actions** (**{ellipsis_v}**) and then **Remove state file and versions**.

### Remove a state file by using the 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>"
```

You can also use [the GraphQL API](../../../api/graphql/reference/index.md#mutationterraformstatedelete).

## Related topics

- [Troubleshooting GitLab-managed Terraform state](troubleshooting.md).
- To use GitLab and Terraform to deploy an AWS EC2 instance in a custom VPC,
  see [this sample project](https://gitlab.com/gitlab-org/configure/examples/gitlab-terraform-aws).