Table of Contents
Terraform
Links:-
https://blog.scottlowe.org/2015/11/25/intro-to-terraform/
https://stephenmann.io/post/setting-up-monitoring-and-alerting-on-amazon-aws-with-terraform/
Starting
Terraform reads all files in a directory to build a project. By convention they end .tf
Terrraform used plug in modules to communicate with a cloud provider, eg. AWS, the init phase loads these in a .terraform directory.
Phases of build:-
$ cd to target directory $ terraform init $ terraform plan $ terraform apply $ terraform destroy
init phase
The init phase sets up the terraform environment prior to being able to run the apply phase. If you need to run this under a different AWS profile, you need to set this as an env variable:-
$ AWS_PROFILE=myprofile terraform init
I needed to do this to initialise an s3 backend for a statefile, but my defaut AWS crededtials were for a different account, so it was trying to access a bucket I did not have permission for, setting the env var solved this.
The AWS profile info can be found here:- Amazon Web Services CLI
Another option is to include a profile in your provider
statement:-
provider "aws" { region = "${var.AWS_REGION}" profile = "myprofile" default_tags { tags = { BuiltBy = "Terraform" } }
validate phase
validate -json { "format_version": "1.0", "valid": true, "error_count": 0, "warning_count": 0, "diagnostics": [] }
plan phase
The plan phase will show the resources to be built or changed, you can also see resources to be destroyed.
As with init
, you can run this as a different AWS profile:-
$ AWS_PROFILE=rainsbrook terraform plan $ terraform plan -destroy
apply phase
apply phase will do a plan and then attempt to apply this plan, you will be prompted to respond yes
to approve. You anc avoid this if you are sure by specifying -auto-approve
.
$ terraform apply -auto-approve
destroy phase
destroy phase removes resources Terraform has built, you can use the plan option to see what is to be destroyed.
$ terraform plan -destroy $ terraform destroy $ terraform destroy -auto-approve
Modules
State files and Lock files
See Cloudformation for Terraform State Files and Lock Table, this is some Cloudformation code which runs to bootstrap Terraform by creating an S3 bucket, DynamoDB Lock Table and stores the resulting Bucket and Table name in parameter Store where Terraform can retreive it.
Use S3 and Dynamo db to store this info.
AWS Provisioner code
aws_provider.tf
# Export key and secret credentials first. # run as -> $ terraform apply # might need to do this if init times out:- # "export https_proxy=http://proxy.company.com:8080" data "aws_ssm_parameter" "TerraformLockTable-SG" { name = "TerraformLockTable-SG" } data "aws_ssm_parameter" "TerraformStateBucket" { name = "TerraformStateBucket-SG" } terraform { required_providers { aws = { source = "hashicorp/aws" version = "3.74.1" } } backend "s3" { region = "eu-west-2" bucket = "vpc-ec2-statefiles-sg" key = "training_vpc_ec2-statefiles-SG/prod/vpc-ec2.tfstate" dynamodb_table = "vpc-ec2-lockfiles-SG" } } provider "aws" { region = "eu-west-2" }
Snippets
resource "aws_s3_bucket" "aws-vpn-vpc-prod-statefiles" { bucket = "aws-vpn-vpc-prod-statefiles" versioning { enabled = true } } resource "aws_dynamodb_table" "aws-vpn-vpc-prod-locks" { name = "aws-vpn-vpd-prod-locks" billing_mode = "PAY_PER_REQUEST" hash_key = "LockID" attribute { name = "LockID" type = "S" } } terraform { backend "s3" { profile = "rainsbrook-terraform" bucket = "aws-vpn-vpc-prod-statefiles" key = "aws-vpn-vpc-prod/terraform.tfstate" region = "eu-west-1" dynamodb_table = "aws-vpn-vpc-prod-locks" }
Visualising what is going to be built
terraform graph
will produce a block of text you can upload to http://webgraphviz.com to draw a diagram of dependencies.
digraph { compound = "true" newrank = "true" subgraph "root" { "[root] aws_iam_role.iam-key-age-role (expand)" [label = "aws_iam_role.iam-key-age-role", shape = "box"] "[root] provider[\"registry.terraform.io/hashicorp/aws\"]" [label = "provider[\"registry.terraform.io/hashicorp/aws\"]", shape = "diamond"] "[root] var.AWS_REGION" [label = "var.AWS_REGION", shape = "note"] "[root] aws_iam_role.iam-key-age-role (expand)" -> "[root] provider[\"registry.terraform.io/hashicorp/aws\"]" "[root] meta.count-boundary (EachMode fixup)" -> "[root] aws_iam_role.iam-key-age-role (expand)" "[root] provider[\"registry.terraform.io/hashicorp/aws\"] (close)" -> "[root] aws_iam_role.iam-key-age-role (expand)" "[root] provider[\"registry.terraform.io/hashicorp/aws\"]" -> "[root] var.AWS_REGION" "[root] root" -> "[root] meta.count-boundary (EachMode fixup)" "[root] root" -> "[root] provider[\"registry.terraform.io/hashicorp/aws\"] (close)" } }
What's already built
terraform state
list & show
will show what Terraform knows about from the state files.
$ terraform state list aws_iam_policy.iam-key-age-policy aws_iam_policy_attachment.iam-key-age-attachment aws_iam_role.iam-key-age-role
$ terraform state show aws_iam_role.iam-key-age-role # aws_iam_role.iam-key-age-role: resource "aws_iam_role" "iam-key-age-role" { arn = "arn:aws:iam::581230658448:role/iam-key-age-role" assume_role_policy = jsonencode( ... edited... ) create_date = "2021-12-03T14:48:44Z" force_detach_policies = false id = "iam-key-age-role" managed_policy_arns = [] max_session_duration = 3600 name = "iam-key-age-role" path = "/" tags = { "name" = "iam-key-age-role" } tags_all = { "BuiltBy" = "Terraform" "name" = "iam-key-age-role" } unique_id = "AROAYOVAMAOIMSEBWOIHN" inline_policy {} }
Errors & fixes
No valid credential sources found for AWS Provider.
andrew@mac:prod$ terraform12 init Initializing modules... Initializing the backend... Error: No valid credential sources found for AWS Provider. Please see https://terraform.io/docs/providers/aws/index.html for more information on providing credentials for the AWS Provider
Fix:-
AWS_PROFILE needed exporting.
andrew@mac:prod$ export AWS_PROFILE=MyProfileName andrew@mac:prod$ terraform init Initializing modules... Initializing the backend... Successfully configured the backend "s3"! Terraform will automatically use this backend unless the backend configuration changes.
See also:- init phase to include the profile in the Provider section.
Testing something to show in a plan
resource "null_resource" "temporary" { triggers = { path = "${path.module}/../files" } }
Removing a state lock
If you have interrupted terraform with a Ctrl-C, there may be a state lock left behind. This can be removed thus:-
$ terraform plan 2019/10/16 13:11:19 [INFO] Terraform version: 0.11.11 ac4fff416318bf0915a0ab80e062a99ef3724334 ...edited... Error: Error locking state: Error acquiring the state lock: ConditionalCheckFailedException: The conditional request failed status code: 400, request id: VKFDCAN5H28CSJ3NQKO1CUHT3BVV4KQNSO5AEMVJF66Q9ASUAAJG Lock Info: ID: cece51d7-ee71-0f70-d6cc-0830631ed672 ...edited.... Terraform acquires a state lock to protect the state from being written by multiple users at the same time. Please resolve the issue above and try again. For most commands, you can disable locking with the "-lock=false" flag, but this is not recommended. $ $ terraform force-unlock cece51d7-ee71-0f70-d6cc-0830631ed672 2019/10/16 13:13:04 [INFO] Terraform version: 0.11.11 ac4fff416318bf0915a0ab80e062a99ef3724334 2019/10/16 13:13:04 [INFO] Go runtime version: go1.11.1 2019/10/16 13:13:04 [INFO] CLI args: []string{"/usr/local/bin/terraform", "force-unlock", "cece51d7-ee71-0f70-d6cc-0830631ed672"} ...edited... Do you really want to force-unlock? Terraform will remove the lock on the remote state. This will allow local Terraform commands to modify this state, even though it may be still be in use. Only 'yes' will be accepted to confirm. Enter a value: yes Terraform state has been successfully unlocked! The state has been unlocked, and Terraform commands should now be able to obtain a new lock on the remote state. $