In this article I will demonstrate how to take a Terraform configuration that is using a local state file and migrate its persistent state to a remote Google Cloud Storage bucket (GCS). We will then perform the migration again, but this time to bring the remote state back to a local file.
We will illustrate this concept with a Terraform configuration that creates a single Google GCP VM instance.
Overview
- Create GCP service account that will perform Terraform actions
- Create Google Cloud Storage bucket where remote state can be persisted
- Create GCP VM instance with state saved locally
- Migrate Terraform state from local to remote Google Cloud Storage bucket
- Migrate Terraform state from remote Google Cloud Storage bucket to local
Prerequisites
You will need a GCP account, and gcloud and Terraform installed in order to run the steps in this article.
Create GCP service account
Our first step will be to login to GCP with our personal privileges (typically ‘Editor‘ role), that has the ability to create a GCP service account. This new GCP service account “tf-creator1” will be the one that performs all Terraform actions.
# login with personal user privileges gcloud config set pass_credentials_to_gsutil true gcloud auth login # explicitly set project id based on available list gcloud config projects list project_id=<fromList> gcloud config set project $project_id newServiceAccount="tf-creator1" # create service account gcloud iam service-accounts create $newServiceAccount --display-name "terraform" --project=$project_id # get email identifier for service account accountEmail=$(gcloud iam service-accounts list --project=$project_id --filter=$newServiceAccount --format="value(email)") # download json private key gcloud iam service-accounts keys create serviceaccount.json --iam-account $accountEmail # assign IAM roles for role in roles/compute.admin; do gcloud projects add-iam-policy-binding $project_id --member=serviceAccount:$accountEmail --role=$role > /dev/null done # show all IAM roles for service account gcloud projects get-iam-policy $project_id --flatten='bindings[].members' --filter="bindings.members:serviceaccount:${accountEmail}" --format='value(bindings.role)'
Create GCS bucket
Then we create a remote GCS bucket where terraform state can be persisted. We must add both the ObjectCreator and ObjectAdmin privileges for the service account.
# create GCS bucket random_string=$(head /dev/urandom | tr -dc a-z0-9 | head -c 20) bucket_name="$project_id-$random_string" gsutil mb -p $project_id gs://$bucket_name # add self as admin my_user=$(gcloud config get account) gsutil iam ch user:${my_user}:admin gs://$bucket_name # add service account in creator,admin role for creation and deletion sa_name="$newServiceAccount@${project_id}.iam.gserviceaccount.com" gsutil iam ch serviceAccount:${sa_name}:objectCreator,objectAdmin gs://$bucket_name
Use Terraform to create GCP VM instance with local state
Clone my github project that has Terraform code to create a GCP VM instance using the earlier downloaded “serviceaccount.json” as credentials, and Terraform state saved locally.
# check git project repo sudo apt update && sudo apt install -y git git clone https://github.com/fabianlee/tf-gcp-migrate-state.git # copy GCP service account key into project directory cp serviceaccount.json tf-gcp-migrate-state/. cd tf-gcp-migrate-state # init Terraform for local state tf init tf plan # export projectId with special 'TF_VAR_' prefix, so variable is always passed to 'terraform apply' export TF_VAR_project_id=$(gcloud config get project) # show backend in use is local cat backend.tf # create GCP VM instance and save state locally tf apply -auto-approve # confirm local state file is populated ls -l terraform.tfstate # show initial tags saved in local state: foo,bar cat terraform.tfstate | jq ".resources[].instances[].attributes.tags" # show initial tags on VM instance gcloud compute instances describe test123 --zone=us-central1-a --format='value(tags)'
Local state is the default if left empty, but note that the state is explicitly local because of our backend definition.
$ cat backend.tf terraform { backend "local" {} }
Migrate local state to remote GCS bucket
First, modify the backend file to use GCS instead of local storage.
# change backend to use gcs bucket cp backend.gcs.hcl backend.tf $ cat backend.tf terraform { backend "gcs" { prefix = "tfstate" credentials = "serviceaccount.json" } }
Then use terraform init to migrate the state to a remote GCS bucket.
# migrate state to gcp bucket terraform init -backend-config="bucket=$bucket_name" -migrate-state # validate tags in gcs remote state are still: foo,bar terraform state pull | jq ".resources[].instances[].attributes.tags" # changes tags TF_VAR_additional_tags='["state-remotegcs"]' terraform apply -auto-approve # verify remote state now has 'state-remotegcs' terraform state pull | jq ".resources[].instances[].attributes.tags"
Migrate remote GCS bucket to local state
Modify the backend file to use local state then use the “terraform init” command to migrate the state back to local.
# change backend to use local provider cp backend.local.hcl backend.tf cat backend.tf # migrate state locally terraform init -migrate-state # validate local state still shows tag 'state-remotegcs' cat terraform.tfstate | jq ".resources[].instances[].attributes.tags" # changes tags TF_VAR_additional_tags='["state-local"]' terraform apply -auto-approve # verify local state now has 'state-local' (and not 'state-remotegcs') cat terraform.tfstate | jq ".resources[].instances[].attributes.tags"
Teardown GCP infrastructure
# destroy GCP VM terraform destroy -auto-approve # remove GCS bucket gsutil rm -fr gs://$bucket_name
REFERENCES
fabianlee github, project code
devcoops.com, migrate tf state from remote to local using ‘tf state pull’
medium.com martin.hrasek, migrating tf state between various backends
hashicorp, backend configurations
Brendan Thompson, tf backend dynamically
google, impersonate service account in terraform code
terraform, special ‘TF_VAR_’ variables that get passed to terraform as variables
registry.terraform.io, google provider reference
terraform, resource google compute_instance
google, store terraform state in google cloud bucket
google, IAM roles for GCS bucket storage
terraform google_compute_instance
NOTES
If gcloud throws ‘InsecureRequestWarning: Unverified HTTPS request is being made to host’
# try to mute like this export PYTHONWARNINGS="ignore:Unverfied" # but may need to use broad scope like this to mute warnings export PYTHONWARNINGS="ignore::Warning"