Learn Terraform from Scratch#

Note

🎓🎓🎓 本文档的目的是帮助大家快速入门Terraform,顺便可以通过 HashiCorp Certified: Terraform Associate 的认证考试

Warning

⚠️⚠️⚠️ 本文档部分代码在cloud中部署的时候,因为使用了一些资源,可能会产生一些费用💰💰💰,请周知。最好及时的去进行清理和删除, 以免产生过多的费用💰💰💰。

Before Start You Need Know#

  • 一定的Linux命令行基础

  • AWS的基础知识

目录#

What is Infrastructure as Code?#

What is Infrastructure?#

Infrastructure通常指的是application运行所依赖的底层的 基础设施 和它们的 配置. 这里的基础设施通常是指物理层之上的部分,并不是一个物理设备,一块硬盘,而是一个虚拟机,一个操作系统,一个软件防火墙,网络配置,负载均衡等等。

Infrastructure as Code (IaC)#

简单来说,IaC就是通过代码的方式去管理Infrastructure,它的创建,配置等等。

IaC可以解决软件部署时,运行环境的一致性问题。通过同一套代码就可以创建出一套完全相同的环境,而这样一套环境在IaC产生之前,需要通过人工在Web GUI上去创建和管理。

Infrastructure as code (IaC) tools allow you to manage infrastructure with configuration files rather than through a graphical user interface. IaC allows you to build, change, and manage your infrastructure in a safe, consistent, and repeatable way by defining resource configurations that you can version, reuse, and share.

The idea is to treat your Infrastructure like software. You need to write the code, test it, and execute it to set up, deploy, update, or delete the required Infrastructure.

IaC是实现DevOps的关键基础之一。

Which are the Best IaC Tools in 2022?#

https://medium.com/cloudnativeinfra/when-to-use-which-infrastructure-as-code-tool-665af289fbde

  • Terraform

  • Ansible

  • Cloud init

  • Chef

  • Puppet

  • SaltStack

  • Vagrant

iac-tools
Declarative vs. Imperative#

可以参考 Declarative and Imperative programming

  • Declarative - Tell what “not” how

  • Imperative - Tell what “and” how

Quick Start#

What is Terraform#

https://www.terraform.io/

  • Infrastructure as code tool

  • Open-source and vendor agnostic

  • Single binary compiled from Go

  • Declarative syntax

  • in HCL(HashCorp Configuration Language) or JSON format

  • Agentless (Push mode)

Terraform Core Components#
  • Terraform executable file

  • Configuration files

  • Provider plugins

  • State data

Terraform Install#
  • 下载可执行文件

  • 添加PATH

https://www.terraform.io/downloads

以Windows为例:

PS C:\> terraform
Usage: terraform [global options] <subcommand> [args]

The available commands for execution are listed below.
The primary workflow commands are given first, followed by
less common or more advanced commands.

Main commands:
init          Prepare your working directory for other commands
validate      Check whether the configuration is valid
plan          Show changes required by the current configuration
apply         Create or update infrastructure
destroy       Destroy previously-created infrastructure

All other commands:
console       Try Terraform expressions at an interactive command prompt
fmt           Reformat your configuration in the standard style
force-unlock  Release a stuck lock on the current workspace
get           Install or upgrade remote Terraform modules
graph         Generate a Graphviz graph of the steps in an operation
import        Associate existing infrastructure with a Terraform resource
login         Obtain and save credentials for a remote host
logout        Remove locally-stored credentials for a remote host
output        Show output values from your root module
providers     Show the providers required for this configuration
refresh       Update the state to match remote systems
show          Show the current state or a saved plan
state         Advanced state management
taint         Mark a resource instance as not fully functional
test          Experimental support for module integration testing
untaint       Remove the 'tainted' state from a resource instance
version       Show the current Terraform version
workspace     Workspace management

Global options (use these before the subcommand, if any):
-chdir=DIR    Switch to a different working directory before executing the
                given subcommand.
-help         Show this help output, or the help for a specified subcommand.
-version      An alias for the "version" subcommand.
PS C:\> terraform version
Terraform v1.2.6
on windows_amd64
PS C:\>
Terraform Object Types#
  • Providers

  • Resources

  • Data sources

Terraform workflow#
  • terraform init

  • terraform plan

  • terraform apply

  • terraform destroy

环境准备#

准备AWS 账户以及access key和secret access key

创建第一个tf文件#

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.16"
    }
  }

  required_version = ">= 1.2.0"
}

provider "aws" {
  access_key = "********************"
  secret_key = "********************"
  region     = "eu-central-1"
}

resource "aws_vpc" "vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = "true"
  tags = {
    Name = "my-vpc"
  }
}
terraform init#
> terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 4.16"...
- Installing hashicorp/aws v4.24.0...
- Installed hashicorp/aws v4.24.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

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.
terraform fmt & validate#
> terraform fmt
> terraform validate
Success! The configuration is valid.
terraform plan#
> terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_vpc.vpc will be created
  + resource "aws_vpc" "vpc" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.0.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_classiclink                   = (known after apply)
      + enable_classiclink_dns_support       = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Name" = "my-vpc"
        }
      + tags_all                             = {
          + "Name" = "my-vpc"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
terraform apply#

Note

如果不想每次在apply或者destroy的时候提示输入yes,而是直接apply或则destroy,那么可以加参数 -auto-approve , 例如 terraform apply -auto-approve

> terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_vpc.vpc will be created
  + resource "aws_vpc" "vpc" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.0.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_classiclink                   = (known after apply)
      + enable_classiclink_dns_support       = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Name" = "my-vpc"
        }
      + tags_all                             = {
          + "Name" = "my-vpc"
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_vpc.vpc: Creating...
aws_vpc.vpc: Still creating... [10s elapsed]
aws_vpc.vpc: Creation complete after 11s [id=vpc-0226b147ad3c83404]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Inspect state#
> terraform show
# aws_vpc.vpc:
resource "aws_vpc" "vpc" {
    arn                              = "arn:aws:ec2:eu-central-1:879589088447:vpc/vpc-0226b147ad3c83404"
    assign_generated_ipv6_cidr_block = false
    cidr_block                       = "10.0.0.0/16"
    default_network_acl_id           = "acl-08f8e3c4aca141247"
    default_route_table_id           = "rtb-0490c915e0bebf54d"
    default_security_group_id        = "sg-04aa1dce6a47ed020"
    dhcp_options_id                  = "dopt-f207cf9a"
    enable_classiclink               = false
    enable_classiclink_dns_support   = false
    enable_dns_hostnames             = true
    enable_dns_support               = true
    id                               = "vpc-0226b147ad3c83404"
    instance_tenancy                 = "default"
    ipv6_netmask_length              = 0
    main_route_table_id              = "rtb-0490c915e0bebf54d"
    owner_id                         = "879589088447"
    tags                             = {
        "Name" = "my-vpc"
    }
    tags_all                         = {
        "Name" = "my-vpc"
    }
}
> terraform state list
aws_vpc.vpc
Update#

update main.ft file, change the VPC tag name from my-vpc to my-vpc-demo

then try to do a plan and apply

> terraform plan
aws_vpc.vpc: Refreshing state... [id=vpc-0226b147ad3c83404]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_vpc.vpc will be updated in-place
  ~ resource "aws_vpc" "vpc" {
        id                               = "vpc-0226b147ad3c83404"
      ~ tags                             = {
          ~ "Name" = "my-vpc" -> "my-vpc-demo"
        }
      ~ tags_all                         = {
          ~ "Name" = "my-vpc" -> "my-vpc-demo"
        }
        # (15 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
> terraform apply
aws_vpc.vpc: Refreshing state... [id=vpc-0226b147ad3c83404]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_vpc.vpc will be updated in-place
  ~ resource "aws_vpc" "vpc" {
        id                               = "vpc-0226b147ad3c83404"
      ~ tags                             = {
          ~ "Name" = "my-vpc" -> "my-vpc-demo"
        }
      ~ tags_all                         = {
          ~ "Name" = "my-vpc" -> "my-vpc-demo"
        }
        # (15 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_vpc.vpc: Modifying... [id=vpc-0226b147ad3c83404]
aws_vpc.vpc: Modifications complete after 1s [id=vpc-0226b147ad3c83404]

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
terraform destroy#
> terraform destroy
aws_vpc.vpc: Refreshing state... [id=vpc-0226b147ad3c83404]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_vpc.vpc will be destroyed
  - resource "aws_vpc" "vpc" {
      - arn                              = "arn:aws:ec2:eu-central-1:879589088447:vpc/vpc-0226b147ad3c83404" -> null
      - assign_generated_ipv6_cidr_block = false -> null
      - cidr_block                       = "10.0.0.0/16" -> null
      - default_network_acl_id           = "acl-08f8e3c4aca141247" -> null
      - default_route_table_id           = "rtb-0490c915e0bebf54d" -> null
      - default_security_group_id        = "sg-04aa1dce6a47ed020" -> null
      - dhcp_options_id                  = "dopt-f207cf9a" -> null
      - enable_classiclink               = false -> null
      - enable_classiclink_dns_support   = false -> null
      - enable_dns_hostnames             = true -> null
      - enable_dns_support               = true -> null
      - id                               = "vpc-0226b147ad3c83404" -> null
      - instance_tenancy                 = "default" -> null
      - ipv6_netmask_length              = 0 -> null
      - main_route_table_id              = "rtb-0490c915e0bebf54d" -> null
      - owner_id                         = "879589088447" -> null
      - tags                             = {
          - "Name" = "my-vpc-demo"
        } -> null
      - tags_all                         = {
          - "Name" = "my-vpc-demo"
        } -> null
    }

Plan: 0 to add, 0 to change, 1 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_vpc.vpc: Destroying... [id=vpc-0226b147ad3c83404]
aws_vpc.vpc: Destruction complete after 1s

Destroy complete! Resources: 1 destroyed.

Variables and Outputs#

  • input variables

  • local values

  • output values

If you’re familiar with traditional programming languages, it can be useful to compare Terraform modules to function definitions:

Input variables are like function arguments. Output values are like function return values. Local values are like a function’s temporary local variables.

Variables#

https://www.terraform.io/language/values/variables

Variable Syntax#
variable "name_label" {

  type = string

  description = "value"

  default = "value"

  sensitive = true

}

to reference the variable var.<name_label>

Note

我们可以使用 terraform console 去测试variable. https://www.terraform.io/cli/commands/console

Data Types#

https://www.terraform.io/language/expressions/types

Primitive Types#
  • String

  • number

  • boolean

variable "aws_secret_key" {
  type        = string
  description = "aws secret key"
  sensitive   = true
}

variable "enable_dns_hostnames" {
  type        = bool
  description = "enable dns hostname"
  default     = true
}

variable "volume_size" {
  type        = number
  description = "volume size in gibibytes"
  default     = 10
}

to reference the values in terraform code, just var.<name_label> , like var.aws_secret_key

> var.aws_secret_key
(sensitive)
> var.enable_dns_hostnames
true
> var.volume_size
10
Collections Types#
  • List (list里的所有数据的数据类型必须是一样的,比如 list(string), list(number) )

variable "aws_regions" {

  type = list(string)

  description = "Region to use for AWS"

  default = ["us-east-1", "us-east-2", "us-west-1", "us-west-2"]

}

to reference collection values:

var.<name_labe>[<index>] index will start 0.

> var.aws_regions
tolist([
  "eu-central-1",
  "us-east-1",
  "us-east-2",
])
> var.aws_regions[1]
"us-east-1"
> var.aws_regions[0]
"eu-central-1"
  • map , a group of values identified by named labels, 数据类型需要一致.

variable "aws_instance_sizes" {

  type        = map(string)
  description = "instance sizes"
  default = {

    small  = "t2.micro"
    medium = "t2.small"
    large  = "t2.large"
  }
}

to reference var.<name_label>.<key_name> or var.<name_label>["key_name"]

> var.aws_instance_sizes
tomap({
  "large" = "t2.large"
  "medium" = "t2.small"
  "small" = "t2.micro"
})
>

> var.aws_instance_sizes.large
"t2.large"
> var.aws_instance_sizes["large"]
"t2.large"
>

类型不一致会进行类型转换

variable "student1" {
  type        = map(string)
  description = "student information"
  default = {
    name = "xxxx"
    age  = 20
  }
}
> var.student1
tomap({
  "age" = "20"
  "name" = "xxxx"
})
> var.student1.age
"20"
Structural Types#
  • Tuple,对应List,不同之处是Tuple里的数据元素可以是不同的数据类型

variable "tuple_test" {

  type = tuple

  description = "tuple test"

  default = ["a", 15, true]

}
$ terraform console
>

> var.tuple_test
[
  "a",
  15,
  true,
]
> var.tuple_test[0]
"a"
> var.tuple_test[1]
15
> var.tuple_test[2]
true
>
  • object,对应Map,但是数值的类型可以不同

variable "db_port" {
  type = object({
    external = number
    internal = number
    protocol = string
  })

  default = {
    external = 5432
    internal = 5433
    protocol = "tcp"
  }

}
$ terraform console
>

> var.db_port
{
  "external" = 5432
  "internal" = 5433
  "protocol" = "tcp"
}
> var.db_port.external
5432
> var.db_port["internal"]
5433
>
Supply variable values#
  • default value

  • -var flag

  • -var-file flag

  • tf var files

    • terraform.tfvars

    • terraform.tfvars.json

    • .auto.tfvars

    • .auto.tfvars.json

  • Environment variable name starts with TF_VAR_

tf-vars
Demo#
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.16"
    }
  }

  required_version = ">= 1.2.0"
}

variable "aws_region" {
  type    = string
  default = "eu-central-1"
}

variable "aws_access_key" {

  type        = string
  description = "aws access key"
  sensitive   = true
}

variable "aws_secret_key" {

  type        = string
  description = "aws secret key"
  sensitive   = true
}

variable "enable_dns_hostnames" {

  type        = bool
  description = "enable dns hostname"
  default     = true
}


provider "aws" {
  access_key = var.aws_access_key
  secret_key = var.aws_secret_key
  region     = var.aws_region
}

resource "aws_vpc" "vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = var.enable_dns_hostnames
  tags = {
    Name = "my-vpc-demo"
  }
}
use -var#
terraform plan -var=aws_access_key="xxxxxxxxx" -var=aws_secret_key="xxxxxxxx"
use Environment vars#

for Linux and Mac

export TF_VAR_aws_access_key=xxxxxxxxxxxxxxxx
export TF_VAR_aws_secret_key=xxxxxxxxxxxxxxxx

for windows powershell

$env:TF_VAR_aws_access_key="xxxxxxxxxxxxxxxx"
$env:TF_VAR_aws_secret_key="xxxxxxxxxxxxxxxx"
use tfvars files#

create a file terraform.tfvars

aws_access_key="xxxxxxxxxxxxxxxx"
aws_secret_key="xxxxxxxxxxxxxxxx"
Locals#

https://www.terraform.io/language/values/locals

Syntax#
locals {
  # Common tags to be assigned to all resources
  common_tags = {
    Company = "example.com"
    Owner   = "test"
  }
}
How to Use locals#
resource "aws_vpc" "vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = var.enable_dns_hostnames
  tags = local.common_tags
}
Output#
Syntax#
output "name_label" {

  value = output_value

}
output "vpc_id" {

  value = aws_vpc.vpc.id

}

resource "aws_vpc" "vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = var.enable_dns_hostnames
  tags                 = local.common_tags
}
Demo#

output like

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Outputs:

vpc_id = "vpc-0e77125c45cdf65c4"
sensitive#

Note

如果不想讓output的值被顯示出來,可以使用sensitive = true,这样就不会在CLI中显示了,但是可以在Terraform state文件中中查看。

output "secret" {
  sensitive = true
  value     = "secret"
}

https://www.terraform.io/language/values/outputs#sensitive-suppressing-values-in-cli-output

AWS Command Line Interface#

Configure#
$ aws configure
AWS Access Key ID [None]: ****************
AWS Secret Access Key [None]: ************6**********lz91IDAm+2k
Default region name [None]: eu-central-1
Default output format [None]: json
$
$
$ ls ~/.aws/
config  credentials
$ more ~/.aws/config
[default]
region = eu-central-1
output = json
$ more ~/.aws/credentials
[default]
aws_access_key_id = ****************
aws_secret_access_key = ************6**********lz91IDAm+2k
$
aws_cli for terraform#

实际上如果已经通过aws cli配置了,那么可以直接使用aws cli,不需要配置terraform里的aws provider。

$ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************B4XB shared-credentials-file
secret_key     ****************m+2k shared-credentials-file
    region             eu-central-1      config-file    ~/.aws/config

也就是下面的 provider "aws" 可以删掉了。

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.16"
    }
  }

  required_version = ">= 1.2.0"
}

provider "aws" {
  access_key = "xxxxxxxxxxxxxxx"
  secret_key = "xxxxxxxxxxxxxx"
  region     = "eu-central-1"
}

resource "aws_vpc" "vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags = {
    Name = "my-vpc"
  }
}

Deploy Web Server#

  • Create a vpc

  • Create subnets for different parts of the infrastructure

  • Attach an internet gateway to the VPC

  • Create a route table for a public subnet

  • Create security groups to allow specific traffic

  • Create ec2 instances on the subnets

Functions and Looping#

  • Looping

    • count (integer)

    • for each (map or set)

  • Functions

  • Expressions

Count#
Syntax#
resource "aws_vpc" "vpc" {

  count                = 2
  cidr_block           = "10.${count.index}.0.0/16"
  enable_dns_hostnames = var.enable_dns_hostnames
  tags                 = {
    Name = "terraform-vpc-${count.index}"
  }
}

terraform plan

> terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_vpc.vpc[0] will be created
  + resource "aws_vpc" "vpc" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.0.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_classiclink                   = (known after apply)
      + enable_classiclink_dns_support       = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Company" = "example.com"
          + "Owner"   = "test"
        }
      + tags_all                             = {
          + "Company" = "example.com"
          + "Owner"   = "test"
        }
    }

  # aws_vpc.vpc[1] will be created
  + resource "aws_vpc" "vpc" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.1.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_classiclink                   = (known after apply)
      + enable_classiclink_dns_support       = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Company" = "example.com"
          + "Owner"   = "test"
        }
      + tags_all                             = {
          + "Company" = "example.com"
          + "Owner"   = "test"
        }
    }

Plan: 2 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
Count References#

<resource_type>.<name_label>[index].<attribute>

aws_vpc.vpc[0].id

aws_vpc.vpc[*].id # all instance

for example

output "vpc_id" {

  value = aws_vpc.vpc[*].id

}

output:

Changes to Outputs:
  + vpc_id = [
      + (known after apply),
      + (known after apply),
    ]
for_each#

https://www.terraform.io/language/meta-arguments/for_each

The for_each meta-argument accepts a map or a set of strings, and creates an instance for each item in that map or set.

Each instance has a distinct infrastructure object associated with it, and each is separately created, updated, or destroyed when the configuration is applied.

In blocks where for_each is set, an additional each object is available in expressions, so you can modify the configuration of each instance. This object has two attributes:

  • each.key — The map key (or set member) corresponding to this instance.

  • each.value — The map value corresponding to this instance. (If a set was provided, this is the same as each.key.)

resource "aws_vpc" "vpc2" {

  for_each = {
    private = "10.1.0.0/16"
    public  = "192.168.0.0/16"
  }
  cidr_block           = each.value
  enable_dns_hostnames = var.enable_dns_hostnames
  tags = {
    Name = "terraform-vpc-${each.key}"
  }

}

Terraform plan

$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the
following symbols:
  + create

Terraform will perform the following actions:

  # aws_vpc.vpc2["private"] will be created
  + resource "aws_vpc" "vpc2" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.1.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_classiclink                   = (known after apply)
      + enable_classiclink_dns_support       = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Name" = "terraform-vpc-private"
        }
      + tags_all                             = {
          + "Name" = "terraform-vpc-private"
        }
    }

  # aws_vpc.vpc2["public"] will be created
  + resource "aws_vpc" "vpc2" {
      + arn                                  = (known after apply)
      + cidr_block                           = "192.168.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_classiclink                   = (known after apply)
      + enable_classiclink_dns_support       = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "Name" = "terraform-vpc-public"
        }
      + tags_all                             = {
          + "Name" = "terraform-vpc-public"
        }
    }

Plan: 2 to add, 0 to change, 0 to destroy.

───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run
"terraform apply" now.
Functions#

https://www.terraform.io/language/functions

func_name(arg1, arg2, ...)

Common Functions#
  • Numeric: min(42, 13, 7)

  • String: lower(“TEST”)

  • Collection: merge(map1, map2)

  • IP Network: cidrsubnet()

  • File system: file(path)

  • Type Conversion: toset()

Examples#

try terraform functions with terraform console

$ terraform console
> min(12, 23, 1, 30)
1
> lower("TEST")
"test"
>
> var.vpc_cidr_block
tomap({
"private" = "10.1.0.0/16"
"public" = "192.168.0.0/16"
})
> lookup(var.vpc_cidr_block, "public")
"192.168.0.0/16"
> lookup(var.vpc_cidr_block, "public1")
╷
│ Error: Error in function call
│
│   on <console-input> line 1:
│   (source code not available)
│
│ Call to function "lookup" failed: lookup failed to find key "public1".
╵


> lookup(var.vpc_cidr_block, "public1", "unknow")
"unknow"
>
> merge(var.vpc_cidr_block, {"public1": "1.1.1.1/32"})
{
"private" = "10.1.0.0/16"
"public" = "192.168.0.0/16"
"public1" = "1.1.1.1/32"
}

file and templatefile

$ terraform console
> file("${path.module}/main.tf")
<<EOT
# # use count
# resource "aws_vpc" "vpc" {

#   count                = 2
#   cidr_block           = "10.${count.index}.0.0/16"
#   enable_dns_hostnames = var.enable_dns_hostnames
#   tags                 = {
#     Name = "terraform-vpc-${count.index}"
#   }
# }

# # Create a Subnet
# output "vpc_id" {
#   value = aws_vpc.vpc[0].id
# }
resource "aws_vpc" "vpc2" {

for_each             = var.vpc_cidr_block
cidr_block           = each.value
enable_dns_hostnames = var.enable_dns_hostnames
tags = {
    Name = "terraform-vpc-${each.key}"
}

}

EOT
>
> file("${path.module}/test.tpl")
"hello world ${name}"
>

> templatefile("test.tpl", {"name": "terraform"})
"hello world terraform"
>
Expressions#
  • for

  • condition

for Expressions#
> var.aws_regions
tolist([
"eu-central-1",
"us-east-1",
"us-east-2",
])
> [for v in var.aws_regions: upper(v)]
[
"EU-CENTRAL-1",
"US-EAST-1",
"US-EAST-2",
]
> {for s in var.aws_regions : s => upper(s)}
{
"eu-central-1" = "EU-CENTRAL-1"
"us-east-1" = "US-EAST-1"
"us-east-2" = "US-EAST-2"
}
>
>
> [for i, v in var.aws_regions : "${i} is ${v}"]
[
"0 is eu-central-1",
"1 is us-east-1",
"2 is us-east-2",
]
>
Condition#

定义一个variable

variable "users" {
  type = map(object({
    is_admin = bool
    name     = string
  }))
  default = {
    "admin" = {
      is_admin = true
      name     = "admin"
    }
    "user" = {
      is_admin = false
      name     = "user"
    }
  }
}

locals {
  admin_users = {
    for name, user in var.users : name => user
    if user.is_admin
  }
  regular_users = {
    for name, user in var.users : name => user
    if !user.is_admin
  }
}
> var.users
tomap({
  "admin" = {
    "is_admin" = true
    "name" = "admin"
  }
  "user" = {
    "is_admin" = false
    "name" = "user"
  }
})
> local.admin_users
{
  "admin" = {
    "is_admin" = true
    "name" = "admin"
  }
}
> local.regular_users
{
  "user" = {
    "is_admin" = false
    "name" = "user"
  }
}
>
alias: Multiple Provider Configurations#

https://www.terraform.io/language/providers/configuration#alias-multiple-provider-configurations

在provider里我们可以指定provider的相关配置,但是如果有多个provider怎么办,比如我们要在不同的region里创建资源,这时候就需要用到alias了,

alias 可以让我们在一个provider里配置多个不同的provider,比如下面的例子:

provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  alias  = "us-west-2"
  region = "us-west-2"
}

resource "aws_vpc" "vpc1" {
  provider = aws.us-west-2
  cidr_block           = "10.1.0.0/16"
  enable_dns_hostnames = true
  tags                 = {
    Name = "terraform-vpc1"
  }
}

resource "aws_vpc" "vpc2" {

  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags                 = {
    Name = "terraform-vpc2"
  }
}

比如以上,我们只想在us-west-2里创建资源vpc1,我们就可以在resource里指定provider为 aws.us-west-2,这样就可以在us-west-2里创建资源了。

vpc2并没有指定provider,所以默认使用的是第一个provider,也就是us-east-1。

Providers#

https://registry.terraform.io/browse/providers

Public and private registries

  • Official

  • Verified

  • Community

版本控制(重要考点之一)#

https://www.terraform.io/language/expressions/version-constraints#version-constraint-syntax

The following operators are valid:

  • = (or no operator) : Allows only one exact version number. Cannot be combined with other conditions.

  • != : Excludes an exact version number.

  • >, >=, <, <= : Comparisons against a specified version, allowing versions for which the comparison is true. “Greater-than” requests newer versions, and “less-than” requests older versions.

  • ~> : Allows only the rightmost version component to increment. For example, to allow new patch releases within a specific minor release, use the full version number: ~> 1.0.4 will allow installation of 1.0.5 and 1.0.10 but not 1.1.0. This is usually called the pessimistic constraint operator.

Module#

What is a module?#

Module可以理解成Python里的library或者模块,主要是为了代码重用和防止重新造轮子。

Module可以是本地的,也可以是远程的。

  • 远程module可以在https://registry.terraform.io/browse/modules 查看下载

  • 远程module会在引用时,通过terraform init下载到本地。

  • 远程module需要指定版本

Structure#

和“函数”类似,输入->函数体->输出。

  • input

  • resources

  • output

module
发布一个Module#

https://www.terraform.io/registry/modules/publish

module的文件结构如下:

参考 https://www.terraform.io/language/modules/develop/structure

  • 在github上创建一个repo

  • 在repo里创建一个main.tf

  • 在repo里创建一个variables.tf

  • 在repo里创建一个outputs.tf

  • 在repo里创建一个README.md

  • 在repo里创建一个LICENSE

  • 在repo里创建一个.gitignore

Managing State#

https://www.terraform.io/language/state

state is a file that contains a serialized representation of your infrastructure and configuration.

Backends determine where state is stored. For example, the local (default) backend stores state in a local JSON file on disk.

  1. local state

默认情况下,Terraform会将状态保存在一个文件中,这个文件的名称是 terraform.tfstate

local state的文件和存储位置是可以修改的

terraform {
   backend "local" {
      path = "relative/path/to/terraform.tfstate"
   }
}
  1. remote state

Terraform writes the state data to a remote data store, which can then be shared between all members of a team. Terraform supports storing state in:

  • Terraform Cloud

  • HashiCorp Consul

  • Amazon S3

  • Azure Blob Storage

  • Google Cloud Storage

  • Alibaba Cloud OSS

  • and more

  1. lock

state_lock
State Command#
$ terraform state
Usage: terraform [global options] state <subcommand> [options] [args]

This command has subcommands for advanced state management.

These subcommands can be used to slice and dice the Terraform state.
This is sometimes necessary in advanced cases. For your safety, all
state management commands that modify the state create a timestamped
backup of the state prior to making modifications.

The structure and output of the commands is specifically tailored to work
well with the common Unix utilities such as grep, awk, etc. We recommend
using those tools to perform more advanced state tasks.

Subcommands:
    list                List resources in the state
    mv                  Move an item in the state
    pull                Pull current state and output to stdout
    push                Update remote state from a local state file
    replace-provider    Replace provider in the state
    rm                  Remove instances from the state
    show                Show a resource in the state
$ terraform state list
aws_vpc.vpc
$ terraform state show aws_vpc.vpc
# aws_vpc.vpc:
resource "aws_vpc" "vpc" {
    arn                              = "arn:aws:ec2:eu-central-1:879589088447:vpc/vpc-0c872fe63a2a3a244"
    assign_generated_ipv6_cidr_block = false
    cidr_block                       = "10.0.0.0/16"
    default_network_acl_id           = "acl-06189d002ed790bcc"
    default_route_table_id           = "rtb-0b01d11a6ae442915"
    default_security_group_id        = "sg-04179c7d9dae848f1"
    dhcp_options_id                  = "dopt-f207cf9a"
    enable_classiclink               = false
    enable_classiclink_dns_support   = false
    enable_dns_hostnames             = true
    enable_dns_support               = true
    id                               = "vpc-0c872fe63a2a3a244"
    instance_tenancy                 = "default"
    ipv6_netmask_length              = 0
    main_route_table_id              = "rtb-0b01d11a6ae442915"
    owner_id                         = "879589088447"
    tags                             = {
        "Name" = "terraform-vpc"
    }
    tags_all                         = {
        "Name" = "terraform-vpc"
    }
}
Remote State S3#

This is a remote state that can be used to store and retrieve data from an S3 bucket.

Example Usage

Consul#

download and install consul https://www.consul.io/downloads

Macos/Linux#
vagrant@ubuntu-focal:~$ curl -OL https://releases.hashicorp.com/consul/1.13.1/consul_1.13.1_linux_amd64.zip
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                Dload  Upload   Total   Spent    Left  Speed
100 43.5M  100 43.5M    0     0  18.9M      0  0:00:02  0:00:02 --:--:-- 18.9M
vagrant@ubuntu-focal:~$ unzip consul_1.13.1_linux_amd64.zip
Archive:  consul_1.13.1_linux_amd64.zip
inflating: consul
vagrant@ubuntu-focal:~$ sudo mv consul /usr/local/bin/
vagrant@ubuntu-focal:~$ consul version
Consul v1.13.1
Revision c6d0f9ec
Build Date 2022-08-11T19:07:00Z
Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

vagrant@ubuntu-focal:~$
vagrant@ubuntu-focal:~$ rm -rf consul_1.13.1_linux_amd64.zip
Prepare config#
$ mkdir data
vagrant@ubuntu-focal:~/github/learn-terraform-from-scratch/code/manage-state/consul$ ls
config  data
vagrant@ubuntu-focal:~/github/learn-terraform-from-scratch/code/manage-state/consul$ more config/consul-config.hcl
## server.hcl

ui = true
server = true
bootstrap_expect = 1
datacenter = "dc1"
data_dir = "./data"

acl = {
    enabled = true
    default_policy = "deny"
    enable_token_persistence = true

}
Start consul instance locally#
vagrant@ubuntu-focal:~/github/learn-terraform-from-scratch/code/manage-state/consul$ ls
config  data
vagrant@ubuntu-focal:~/github/learn-terraform-from-scratch/code/manage-state/consul$
vagrant@ubuntu-focal:~/github/learn-terraform-from-scratch/code/manage-state/consul$ consul agent -bootstrap -config-file="config/consul-config.hcl" -bind="127.0.0.1"
==> Starting Consul agent...
        Version: '1.13.1'
        Build Date: '2022-08-11 19:07:00 +0000 UTC'
        Node ID: '7305b65b-098d-2998-e887-4652a4d016b9'
        Node name: 'ubuntu-focal'
        Datacenter: 'dc1' (Segment: '<all>')
            Server: true (Bootstrap: true)
    Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: -1, DNS: 8600)
    Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
        Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false, Auto-Encrypt-TLS: false

==> Log data will now stream in as it occurs:
......

consul将会在前台运行

Get Token#
$ consul acl bootstrap
AccessorID:       0e9b5506-6855-d9a9-2c49-5b764b343b0b
SecretID:         26efa357-2768-c72a-a120-0a38fefdf466
Description:      Bootstrap Token (Global Management)
Local:            false
Create Time:      2022-08-31 14:29:50.179459862 +0000 UTC
Policies:
00000000-0000-0000-0000-000000000001 - global-management

设置环境变量

Linux and Mac

$ export CONSUL_HTTP_TOKEN=26efa357-2768-c72a-a120-0a38fefdf466

Windows

$env:CONSUL_HTTP_TOKEN="26efa357-2768-c72a-a120-0a38fefdf466"
Terraform backend#
terraform {
backend "consul" {
    address = "127.0.0.1:8500"
    scheme  = "http"
    path    = "test/terraform.tfstate"
}
}

查看state

$ consul kv get test/terraform.tfstate
Re-creation of Resources#

当terraform创建的资源因某些原因出了问题,我们想重建这些资源,可以使用terraform taint命令,例如:

terraform taint aws_instance.web

这个命令会将web实例标记为需要重建,然后执行terraform apply命令,terraform会重建web实例。

terraform apply

资源重建后,terraform会将新的资源信息保存到state文件中,同时资源会被标记为untainted.

如果标记完taint后,反悔了,可以通过terraform untaint命令将资源的taint的标记去掉,例如:

terraform untaint aws_instance.web
import#

有时候我们想把一个已经存在的resource,但并没有被terraform管理的,纳入到terraform中来管理,这时候就需要用到import命令。

文档 https://www.terraform.io/cli/commands/import

import命令的格式如下:

terraform import [options] ADDR ID

其中ADDR是resource的地址,ID是resource的ID,比如一个aws_instance的ID就是instance的ID,一个aws_security_group的ID就是security group的ID。

Demo#
我们在aws上创建一个VPC,然后用terraform import把它纳入到terraform的管理中来, 通过aws的web console可以看到这个VPC的ID,比如

vpc-0af5cb081c28cf2e3

新建一个文件夹,比如 import_demo,然后在这个文件夹下新建一个 main.tf 文件,内容如下:

provider "aws" {
  region = "us-east-1"
}

# create vpc
resource "aws_vpc" "my-vpc" {

  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags                 = {
    Name = "my-vpc"
  }
}
$ terraform import aws_vpc.my-vpc vpc-0af5cb081c28cf2e3
aws_vpc.my-vpc: Importing from ID "vpc-0af5cb081c28cf2e3"...
aws_vpc.my-vpc: Import prepared!
  Prepared aws_vpc for import
aws_vpc.my-vpc: Refreshing state... [id=vpc-0af5cb081c28cf2e3]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
常用命令#
terraform state help
Usage: terraform [global options] state <subcommand> [options] [args]

This command has subcommands for advanced state management.

These subcommands can be used to slice and dice the Terraform state.
This is sometimes necessary in advanced cases. For your safety, all
state management commands that modify the state create a timestamped
backup of the state prior to making modifications.

The structure and output of the commands is specifically tailored to work
well with the common Unix utilities such as grep, awk, etc. We recommend
using those tools to perform more advanced state tasks.

Subcommands:
   list                List resources in the state
   mv                  Move an item in the state
   pull                Pull current state and output to stdout
   push                Update remote state from a local state file
   replace-provider    Replace provider in the state
   rm                  Remove instances from the state
   show                Show a resource in the state
$ terraform refresh -help
Usage: terraform [global options] refresh [options]

Update the state file of your infrastructure with metadata that matches
the physical resources they are tracking.

This will not modify your infrastructure, but it can modify your
state file to update metadata. This metadata might cause new changes
to occur when you generate a plan or call apply next.
$ terraform force-unlock --help
Usage: terraform [global options] force-unlock LOCK_ID

  Manually unlock the state for the defined configuration.

  This will not modify your infrastructure. This command removes the lock on the
  state for the current workspace. The behavior of this lock is dependent
  on the backend being used. Local state files cannot be unlocked by another
  process.

Options:

  -force                 Don't ask for input for unlock confirmation.
$ terraform show --help
Usage: terraform [global options] show [options] [path]

  Reads and outputs a Terraform state or plan file in a human-readable
  form. If no path is specified, the current state will be shown.

Options:

  -no-color           If specified, output won't contain any color.
  -json               If specified, output the Terraform plan or state in
                      a machine-readable form.

Data sources#

https://www.terraform.io/language/data-sources

Data sources allow Terraform to use information defined outside of Terraform, defined by another separate Terraform configuration, or modified by functions.

Resources are data sources

Providers have data sources

Alternative data sources

  • Templates

  • HTTP

  • External

  • Consul

Http data sources#

https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http

data "http" "example" {
  url = "https://checkpoint-api.hashicorp.com/v1/check/terraform"

  # Optional request headers
  request_headers = {
    Accept = "application/json"
  }
}
resource "aws_vpc" "vpc" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  tags = {
    Name = jsondecode(data.http.example.response_body)["product"]
  }
}

Collaboration#

如何在terraform中多人协作, 有下面的几个基本问题需要解决:

  • 如何共享state文件 (remote state)

  • 如何共享变量

    • 普通变量

    • 密码变量

  • 如何管理多环境(dev, test, prod)

    • 不同环境的state

    • 不同环境的变量

多文件/文件夹的形式#
multi-env-folder

然后在执行的时候,指定不同的变量文件和state文件

// Dev Environment
terraform apply -var-file=common.tfvars -var-file=dev/terraform.tfvars -state=dev/terraform.tfstate
// QA Environment
terraform apply -var-file=common.tfvars -var-file=qa/terraform.tfvars -state=qa/terraform.tfstate
// Prod Environment
terraform apply -var-file=common.tfvars -var-file=prod/terraform.tfvars -state=prod/terraform.tfstate
Workspace#
Multi Environment#

Variables

dev

qa

prod

cidr_block

10.0.0.0/16

10.1.0.0/16

10.2.0.0/16

subnet_count

1

1

2

code example code/multi-env-demo

Create workspaces#

默认workspace是default, 可以通过下面的命令创建新的workspace

terraform workspace new dev
terraform workspace new qa
terraform workspace new prod

创建新的workspace后,会自动切换到新的workspace, 可以通过下面的命令查看当前的workspace

terraform workspace show

查看当前所有的workspace

terraform workspace list

切换到指定的workspace

terraform workspace select dev

删除指定的workspace

terraform workspace delete dev
workspace的使用#

可以在``tf文件``中使用``terraform.workspace``来获取当前的workspace, 从而区分不同的Workspace

locals {
    common_tags = {
        Environment = "${terraform.workspace}"
    }
}

CI/CD#

Automate Terraform with GitHub Actions#

https://github.com/xiaopeng163/terraform-github-action

Terraform Cloud#

https://cloud.hashicorp.com/products/terraform

cloud-terraform
Create organization and workspace#

https://app.terraform.io/app/organizations/new

Workspace types#
  • CLI-driven workflow

  • VCS-driven workflow

  • API-driven workflow

Troubleshooting#

  • Validating Configurations

  • Enable verbose logging

  • Resource taints

  • Crash logs

Types of Errors#
  • Command Errors

  • Syntax validation

  • Provider validation

  • Deployment errors

Command Errors#
$ terraform plan -auto
╷
│ Error: Failed to parse command-line flags
│
│ flag provided but not defined: -auto
╵

For more help on using this command, run:
  terraform plan -help
Synctax Validation#
  • Terraform init first

  • check syntax and logic

  • does not check state

  • manual or automatic

  • automation checks in pipelines

一般发生在 terraform initterraform validate

Note

The Terraform configuration must be valid before initialization so that Terraform can determine which modules and providers need to be installed.

例如:

$ terraform init
There are some problems with the configuration, described below.

The Terraform configuration must be valid before initialization so that
Terraform can determine which modules and providers need to be installed.
╷
│ Error: Argument or block definition required
│
│ On network.tf line 6: An argument or block definition is required here. To set an argument, use the equals sign "=" to introduce the argument value.

Provider Validation and Deployment Errors ~~~~~~~~~~~~~~~~~~——————————

一般发生在plan和apply时

$ terraform plan
╷
│ Error: Unsupported attribute
│
│   on network.tf line 10, in resource "aws_subnet" "my_subnet":
│   10:   vpc_id     = aws_vpc.my_vpc.ids
│
│ This object has no argument, nested block, or exported attribute named "ids". Did you mean "id"?

Deployment errors

比如一些只有deploy时才会发现的错误。例如,S3 bucket name must be unique across all existing bucket names in Amazon S3.

Verbose Logging#

https://www.terraform.io/internals/debugging

  • TF_LOG=TRACE

  • TF_LOG_PATH

You can set TF_LOG to one of the log levels (in order of decreasing verbosity) TRACE, DEBUG, INFO, WARN or ERROR to change the verbosity of the logs.

$ export TF_LOG=DEBUG
$ terraform plan
2022-09-06T19:47:22.678Z [INFO]  Terraform version: 1.2.8
2022-09-06T19:47:22.678Z [DEBUG] using github.com/hashicorp/go-tfe v1.0.0
2022-09-06T19:47:22.678Z [DEBUG] using github.com/hashicorp/hcl/v2 v2.12.0
2022-09-06T19:47:22.678Z [DEBUG] using github.com/hashicorp/terraform-config-inspect v0.0.0-20210209133302-4fd17a0faac2
2022-09-06T19:47:22.678Z [DEBUG] using github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734
2022-09-06T19:47:22.678Z [DEBUG] using github.com/zclconf/go-cty v1.11.0
2022-09-06T19:47:22.678Z [INFO]  Go runtime version: go1.18.1
2022-09-06T19:47:22.678Z [INFO]  CLI args: []string{"/usr/local/bin/terraform_1.2.8", "plan"}
...
...
...

关闭logging

$ export TF_LOG=
$ terraform plan
...
crash log#

open issue on GitHub

crash

About the Exam#

一些链接和注意事项#

考试介绍和注册 https://www.hashicorp.com/certification/terraform-associate

  • 考试时间:60分钟

  • 题型:60道题:
    • 单选

    • 多选 (会提示你选几个)

    • 填空 (一般很简单,比如问terraform默认生成的本地state文件名是什么,答案就是terraform.tfstate)

    • 判断 (特殊的选择题,选择True或者False)

考试的难度: 个人认为难度不大,好好准备考80分以上应该没问题。

考试的大概过程#
  1. 你会收到一封邮件,里面有考试的链接注意事项,一定要认真阅读

  2. 考试可以提前30分钟launch,但是你必须在60分钟内完成考试,否则会被自动退出

  3. 提前launch考试是因为你需要做很多检查,比如但不限于
    • 下载安装一个考试专用浏览器

    • 检查你的电脑是否满足考试的要求,关闭不允许的软件

    • 通过摄像头展示你的ID

    • 通过摄像头展示你的桌面,周边环境

    • 考试的协调人员会跟你进行online chat,告诉你考试的注意事项等

  4. 考试开始。题目可以跳跃,标记,收藏,可以任意返回之前标记的题目重新作答

  5. 点击结束考试,会马上告诉你考试结果。你也会收到一封邮件,告诉你考试的结果

Azure with Terraform#

Azure command line interface#

Install the Azure command line interface (CLI) on your machine. You can find the instructions here: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest

Azure CLI is a command line tool that allows you to manage Azure resources. It is available for Windows, Linux, and macOS. You can use it to create and manage Azure resources from the command line. You can also use it to create and manage Azure resources from scripts.

Authenticating with Azure CLI#

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/azure_cli

az login

This will open a browser window and ask you to login to your Azure account. After you login, you will be asked to select the Azure subscription you want to use.

Authenticating using a Service Principal with a Client Secret#

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret

Creating a Service Principal using the Azure CLI#

首先先 az login 登录。拿到 subscription id

az account show

创建一个 service principal

az ad sp create-for-rbac --role="Contributor" --scopes="/subscriptions/<SUBSCRIPTION_ID>"

这个命令会返回一个 json,里面包含了 client_id, client_secret, tenant_id, 其中:

  • appId 就是 client_id

  • password 就是 client_secret

  • tenant 就是 tenant_id

Warning

以上的password一定要找个地方记下来,不要忘记

再加上我们的 subscription id,就可以在 terraform 中使用了。设置环境变量如下:

export ARM_CLIENT_ID="00000000-0000-0000-0000-000000000000"
export ARM_CLIENT_SECRET="00000000-0000-0000-0000-000000000000"
export ARM_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000"
export ARM_TENANT_ID="00000000-0000-0000-0000-000000000000"

如果是Windows,可以是如下设置

$env:ARM_CLIENT_ID="00000--0000-00-0-000"
$env:ARM_CLIENT_SECRET="00-00000-000000-000000"
$env:ARM_SUBSCRIPTION_ID="00-00000-000000-000000"
$env:ARM_TENANT_ID="00-00000-000000-000000"

Warning

以上信息如果不小心泄露了,可以通过下面的命令删除重建

1) 通过命令 az ad sp list --display-name azure-cli 可以找到我们创建的service principal的 id

2) 通过命令 az ad sp delete --id <id> 可以删除

create azure storage for terraform remote state#

https://learn.microsoft.com/en-us/azure/developer/terraform/store-state-in-azure-storage?tabs=azure-cli

需要创建以下几个东西,resource group,storage account,blob container

如果是Linux或者Mac

RESOURCE_GROUP_NAME=tfstate
STORAGE_ACCOUNT_NAME=tfstate$RANDOM
CONTAINER_NAME=tfstate

# Create resource group
az group create --name $RESOURCE_GROUP_NAME --location westeurope

# Create storage account
az storage account create --resource-group $RESOURCE_GROUP_NAME --name $STORAGE_ACCOUNT_NAME --sku Standard_LRS --encryption-services blob

# Create blob container
az storage container create --name $CONTAINER_NAME --account-name $STORAGE_ACCOUNT_NAME

About Me#

about

网名 麦兜搞IT 资深网络运维工程师,现居 荷兰 ,在某银行数据中心网络部门担任资深网络运维工程师,负责Net DevOps的落地实施 此前先后曾在 CiscoKPN 等公司工作10年之久,对运维自动化,DevOps有着丰富的实战经验。17年开始涉足在线教育,现有学生超过4万人。

🔭 I’m currently working as a 🛠 Network DevOps engineer @ing-bank Netherlands.

📚 I like creating tech training videos online (Udemy, YouTube, WeChat)

💬 How to reach me: GitHub, Twitter, LinkedIn

也欢迎中国大陆的朋友关注我的微信公众号,会不定期分享一些Docker/k8s的技术文章

wechat

Indices and tables#