Locking Terraform states using Yandex Managed Service for YDB
Yandex Cloud supports infrastructure management through Terraform. To allow multiple users to manage the infrastructure, you can automatically upload the Terraform states and store them in Yandex Object Storage.
When multiple users try to access the same state from Object Storage at the same time, conflicts may occur. To prevent such conflicts, you can deploy a database in Yandex Managed Service for YDB and use it to implement Terraform's native state locking mechanism. Every time you use Terraform to update the infrastructure, the state will be automatically locked until the update is applied.
To set up storing Terraform states in Object Storage and locking them by Managed Service for YDB:
- Prepare your cloud.
- Create a service account and static access key.
- Create a bucket.
- Create a Managed Service for YDB database.
- Install and configure Terraform.
- Configure the backend.
- Deploy the configuration.
- Check the saved state.
- Check whether the state is locked.
If you no longer need the resources you created, delete them.
Terraform and its providers are distributed under the Business Source License
Prepare your cloud
Sign up for Yandex Cloud and create a billing account:
- Go to the management console
and log in to Yandex Cloud or create an account if you do not have one yet. - On the Yandex Cloud Billing
page, make sure you have a billing account linked and it has theACTIVE
orTRIAL_ACTIVE
status. If you do not have a billing account, create one.
If you have an active billing account, you can go to the cloud page
Learn more about clouds and folders.
Required paid resources
The infrastructure support cost for Terraform states includes:
- Data storage fees (see Object Storage pricing).
- Fee for running queries to the database (see Managed Service for YDB pricing).
The cost of support for the example infrastructure deployed through Terraform in this tutorial includes:
- Fee for a continuously running VM (see Yandex Compute Cloud pricing).
- Fee for using a dynamic public IP address (see Yandex Virtual Private Cloud pricing).
If you deploy resources of other Yandex Cloud services, the cost will change according to the relevant service plans.
Create a service account and static access key
- Create a service account with the storage.editor and ydb.admin roles for the folder specified in the provider settings.
- Get a static access key. Save the key ID and secret key: you will need them at the next steps.
Create a bucket
Create a bucket with restricted access. This bucket will store the Terraform state file.
Create a Managed Service for YDB database
Create a Serverless DB named state-lock-db
:
Create a table
-
In the management console
, select the folder where the database is located. -
In the list of services, select Managed Service for YDB.
-
In the DB list, select
state-lock-db
. -
Go to the Navigation tab.
-
In the top-right corner, click Create and choose Table.
-
Enter the table name:
state-lock-table
. -
In the table parameters, specify document table as Table type.
-
Under Columns, specify:
- Name:
LockID
. - Type:
String
. - Partitioning key: Enable this option by ticking the checkbox.
Delete the other columns.
- Name:
-
Click Create table.
To create a table through the AWS CLI:
-
Run this command:
aws dynamodb create-table \ --table-name <table_name> \ --attribute-definitions \ AttributeName=LockID,AttributeType=S \ --key-schema \ AttributeName=LockID,KeyType=HASH \ --endpoint <document-api-endpoint_of_the_database>
Where:
--table-name
: Table name.--attribute-definitions
: Column parameters:AttributeName
: Column name.AttributeType
: Data type. In our example, we use string data (S
).
--key-schema
: Key schema for the column:AttributeName
: Column name.KeyType
: Key type. In our example, we use a partitioning key (HASH
).
--endpoint
: Document API DB endpoint. You can find it on the database main page under Document API endpoint.
Install and configure Terraform
Install Terraform
Use one of the following methods:
-
Download the Terraform distribution
and follow this guide to install it. -
Install Terraform using the Chocolatey
package manager and the command below:choco install terraform
Download the Terraform distribution
Use one of the following methods:
-
Download the Terraform distribution
and follow this guide to install it. -
Install Terraform using the Homebrew
package manager and the command below:brew install terraform
Get the authentication credentials
Use a service account to manage the Yandex Cloud infrastructure using Terraform. It will help you flexibly configure access permissions to resources.
You can also access Terraform from your Yandex account,, or a federated account, but this method is less secure. For more information, see the end of this section.
-
If you do not have the Yandex Cloud command line interface, install it.
-
Set up the CLI profile to run operations on behalf of the service account:
CLI-
Create an authorized key for your service account and save the file:
yc iam key create \ --service-account-id <service_account_ID> \ --folder-name <service_account_folder_name> \ --output key.json
Where:
service-account-id
: Service account ID.folder-name
: Name of the folder in which the service account was created.output
: Name of the file with the authorized key.
Result:
id: aje8nn871qo4******** service_account_id: ajehr0to1g8b******** created_at: "2022-09-14T09:11:43.479156798Z" key_algorithm: RSA_2048
-
Create a CLI profile to run operations on behalf of the service account. Name the profile:
yc config profile create <profile_name>
Result:
Profile 'sa-terraform' created and activated
-
Set the profile configuration:
yc config set service-account-key key.json yc config set cloud-id <cloud_ID> yc config set folder-id <folder_ID>
Where:
-
-
Add the credentials to the environment variables:
BashPowerShellexport YC_TOKEN=$(yc iam create-token) export YC_CLOUD_ID=$(yc config get cloud-id) export YC_FOLDER_ID=$(yc config get folder-id)
Where:
YC_TOKEN
: IAM token.YC_CLOUD_ID
: Cloud ID.YC_FOLDER_ID
: Folder ID.
$Env:YC_TOKEN=$(yc iam create-token) $Env:YC_CLOUD_ID=$(yc config get cloud-id) $Env:YC_FOLDER_ID=$(yc config get folder-id)
Where:
YC_TOKEN
: IAM token.YC_CLOUD_ID
: Cloud ID.YC_FOLDER_ID
: Folder ID.
Note
The IAM token lifetime does not exceed 12 hours; however, we recommend requesting it more often, such as once per hour.
Managing resources on behalf of a Yandex account or a federated account
Warning
Managing resources under a user's Yandex account or federated account is less secure than under a service account.
If you do not have the Yandex Cloud command line interface yet, install and initialize it.
The folder specified in the CLI profile is used by default. You can specify a different folder using the --folder-name
or --folder-id
parameter.
If you use a federated account, authenticate with the CLI on behalf of the federated user.
Add the credentials to the environment variables:
export YC_TOKEN=$(yc iam create-token)
export YC_CLOUD_ID=$(yc config get cloud-id)
export YC_FOLDER_ID=$(yc config get folder-id)
Where:
YC_TOKEN
: IAM token.YC_CLOUD_ID
: Cloud ID.YC_FOLDER_ID
: Folder ID.
$Env:YC_TOKEN=$(yc iam create-token)
$Env:YC_CLOUD_ID=$(yc config get cloud-id)
$Env:YC_FOLDER_ID=$(yc config get folder-id)
Where:
YC_TOKEN
: IAM token.YC_CLOUD_ID
: Cloud ID.YC_FOLDER_ID
: Folder ID.
Note
The IAM token lifetime does not exceed 12 hours; however, we recommend requesting it more often, such as once per hour.
Create a Terraform configuration file
- Create a directory with any name, for example,
cloud-terraform
. It will store the Terraform configuration files. - Create a configuration file with the
.tf
extension in this directory, for example,example.tf
.
Configure a provider
Note
These settings apply to Terraform 0.13
and higher. We recommend using the latest stable version of Terraform.
-
If you previously configured a provider from the HashiCorp registry, save its settings:
Linux/macOSWindowsmv ~/.terraformrc ~/.terraformrc.old
mv $env:APPDATA/terraform.rc $env:APPDATA/terraform.rc.old
-
Specify the source the provider will be installed from.
Linux/macOSWindowsOpen the Terraform CLI configuration file:
nano ~/.terraformrc
Note
The
.terraformrc
file must be in the user's home root folder, e.g.,/home/user/
or/User/user/
.Open the Terraform CLI configuration file named
terraform.rc
in your user's%APPDATA%
folder.To find out the absolute path to the
%APPDATA%
folder, run theecho %APPDATA%
command for cmd or the$env:APPDATA
command for PowerShell.Add the following section to the file:
provider_installation { network_mirror { url = "https://terraform-mirror.yandexcloud.net/" include = ["registry.terraform.io/*/*"] } direct { exclude = ["registry.terraform.io/*/*"] } }
For more information about setting up mirrors, see the documentation
. -
Add the following sections at the top of the
.tf
configuration file:terraform { required_providers { yandex = { source = "yandex-cloud/yandex" } } required_version = ">= 0.13" } provider "yandex" { zone = "<default_availability_zone>" }
Where:
source
: Provider's global source address .required_version
: Minimum Terraform version the provider is compatible with.provider
: Provider name.zone
: Availability zone where all cloud resources will be created by default.
-
Run the
terraform init
command in the folder containing the.tf
configuration file. This command initializes the providers specified in the configuration files and allows you to work with the provider resources and data sources.
If the provider installation failed, create a support request
If you used the .terraform.lock.hcl
file, prior to the initialization, run the terraform providers lock
command specifying the URL of the mirror to upload the provider from and the platforms the configuration will run on:
terraform providers lock -net-mirror=https://terraform-mirror.yandexcloud.net -platform=<platform_1_name> -platform=<platform_2_name> yandex-cloud/yandex
Where:
-net-mirror
: Address of the mirror to upload the provider from.-platform
: Platforms to use the configuration on. The possible values include:windows_amd64
: 64-bit Windowslinux_amd64
: 64-bit Linuxdarwin_arm64
: 64-bit macOS
If you used the Terraform modules, first run terraform init
, then delete the lock file. After that, run the terraform providers lock
command.
For more information about the terraform providers lock
command, see the Terraform documentation
Configure the backend
Note
The backend settings apply to Terraform 1.6.3
and higher.
To save the Terraform state in Object Storage and activate state locking:
-
Add the previously obtained key ID and secret key to environment variables:
BashPowerShellexport ACCESS_KEY="<key_ID>" export SECRET_KEY="<secret_key>"
$Env:AWS_ACCESS_KEY_ID="<key_ID>" $Env:AWS_SECRET_ACCESS_KEY="<secret_key>"
-
Add provider and backend settings to the configuration file:
terraform { required_providers { yandex = { source = "yandex-cloud/yandex" } } required_version = ">= 0.13" backend "s3" { endpoints = { s3 = "https://storage.yandexcloud.net" dynamodb = "<Document_API_DB_endpoint>" } bucket = "<bucket_name>" region = "ru-central1" key = "<path_to_state_file_in_bucket>/<state_file_name>.tfstate" dynamodb_table = "<table_name>" skip_region_validation = true skip_credentials_validation = true skip_requesting_account_id = true # This option is required for Terraform 1.6.1 or higher. skip_s3_checksum = true # This option is required to describe backend for Terraform version 1.6.3 or higher. } } provider "yandex" { zone = "<default_availability_zone>" }
Where:
bucket
: Bucket name.dynamodb
: Document API DB inhttps://docapi.serverless.yandexcloud.net/ru-central1/b1gia87mbaom********
format.key
: Object key in the bucket (name and path to the Terraform state file in the bucket).dynamodb_table
: Table name.
To read more about the state storage backend, see the Terraform website
. -
Run the following command in the folder with the configuration file:
terraform init
Deploy the configuration
In this example, you will create a VM named terraform-vm
that will be connected to the subnet-1
subnet in the ru-central1-a
availability zone. This subnet will be in the network-1
cloud network.
The VM will have 2 cores and 4 GB RAM. It will be automatically assigned a public and a private IP address from the 192.168.10.0/24
range in subnet-1
. The VM will run Ubuntu and host the public part of the key to enable SSH access.
-
Save the following configuration as a separate
example-vm.tf
file in the folder with the backend configuration file:resource "yandex_compute_image" "ubuntu_2004" { source_family = "ubuntu-2004-lts" } resource "yandex_compute_disk" "boot-disk" { name = "boot-disk" type = "network-hdd" zone = "ru-central1-a" size = "20" image_id = yandex_compute_image.ubuntu_2004.id } resource "yandex_compute_instance" "vm-1" { name = "terraform-vm" resources { cores = 2 memory = 4 } boot_disk { disk_id = yandex_compute_disk.boot-disk.id } network_interface { subnet_id = yandex_vpc_subnet.subnet-1.id nat = true } metadata = { user-data = "#cloud-config\nusers:\n - name: <username>\n groups: sudo\n shell: /bin/bash\n sudo: 'ALL=(ALL) NOPASSWD:ALL'\n ssh-authorized-keys:\n - ${file("<path_to_public_SSH_key>")}" } } resource "yandex_vpc_network" "network-1" { name = "network1" } resource "yandex_vpc_subnet" "subnet-1" { name = "subnet1" zone = "ru-central1-a" network_id = yandex_vpc_network.network-1.id v4_cidr_blocks = ["192.168.10.0/24"] } output "internal_ip_address_vm_1" { value = yandex_compute_instance.vm-1.network_interface.0.ip_address } output "external_ip_address_vm_1" { value = yandex_compute_instance.vm-1.network_interface.0.nat_ip_address } output "subnet-1" { value = yandex_vpc_subnet.subnet-1.id }
-
Check the configuration using the
terraform plan
command. -
Deploy the configuration using the
terraform apply
command.
Check the saved state
Make sure the state file is uploaded to Yandex Object Storage:
- Go to the management console
and select the folder containing the bucket you created. - Select Object Storage.
- In the bucket list, select the bucket you saved the Terraform state to.
- Make sure the state file is in the bucket.
Check the state lock
Try to update the infrastructure concurrently with another user. If the lock mechanism works, Terraform will return the following message after running terraform apply
:
member Error: Error acquiring the state lock
member Error message: ConditionalCheckFailedException: Condition not satisfied
member Lock Info:
member ID: <...>
member Path: terraform-object-storage-tutorial/TF/cloud-storage.tfstate
member Operation: OperationTypeApply
member Who: LD\user@i7293
member Version: 1.4.2
member Created: <...>
member Info:
member Terraform acquires a state lock to protect the state from being written
member by multiple users at the same time. Please resolve the issue above and try
member again. For most commands, you can disable locking with the "-lock=false"
member flag, but this is not recommended.
How to delete the resources you created
If you no longer need the resources you created, delete them: