【技术版】编码基础设施配置。使用 Terraform 部署 AWS 实例。

目录
我叫寺冈,是一名基础设施工程师。
这次,我们将着眼于技术方面,接续上次的知识部分。
基础设施即代码我们来亲自试一试。
上次只是有点长请查看以下知识博客:
【知识版】基础设施配置代码化。学习“基础设施即代码”的概念,让配置管理更智能。
在本文中,Terraform的工具
我想演示如何
■简单介绍一下:什么是 Terraform?
由 HashiCorp 公司开发的一款配置管理工具,它
使用代码自动构建和配置基础设施。
这家公司Vagrant(我一直都在使用)而闻名。HashiCorp
的开发非常活跃,更新也很频繁,因此我个人非常关注这款工具。
好的,我们马上开始吧。
■准备 Terraform 执行环境
首先,准备执行环境。这是开始之前必不可少的一步(我们
提供了一个 zip 文件,只需使用 wget 下载即可。简单方便。
解压缩并将其添加到系统的 PATH 环境变量中)。
$ wget https://releases.hashicorp.com/terraform/0.9.0/terraform_0.9.0_linux_amd64.zip $ unzip terraform_0.9.0_linux_amd64.zip $ mv terraform /usr/bin/ $ terraform -v Terraform v0.9.0
……完成了!
这就是设置执行环境的全部步骤。
■ 创建模板文件
简而言之,
创建具有特定配置的基础架构的步骤
模板文件是一个代码文档,它以 Terraform 可以理解的方式描述了
然后,Terraform 使用此文件来构建基础架构。
首先,准备一个工作目录,并在其中创建一个模板文件。
$ mkdir /var/tmp/terraform $ cd /var/tmp/terraform $ touch main.tf
模板文件扩展名应为“*.tf”。Terraform
会将具有此扩展名的文件识别为模板。
那么,废话不多说,以下是我创建的用于构建单个 EC2 实例的模板文件 ↓
(我提前准备好了。这简直就是个作弊码,连烹饪节目里都会用到。)
变量 "aws_access_key" {} 变量 "aws_secret_key" {} 变量 "region" { default = "ap-northeast-1" } 变量 "images" { default = { us-east-1 = "ami-1ecae776" us-west-2 = "ami-e7527ed7" us-west-1 = "ami-d114f295" eu-west-1 = } } 提供程序 "aws" { access_key = "${var.aws_access_key}" secret_key = "${var.aws_secret_key}" region = "${var.region}" } 资源 "aws_vpc" "default" { cidr_block = "172.30.0.0/16" instance_tenancy = "default" enable_dns_support = "true" enable_dns_hostnames = "false" tags { Name = "default" } } 资源"aws_internet_gateway" "gw" { vpc_id = "${aws_vpc.default.id}" } resource "aws_subnet" "public_subnet_a" { vpc_id = "${aws_vpc.default.id}" availability_zone = "ap-northeast-1a" cidr_block = "${cidrsubnet(aws_vpc.default.cidr_block, 4, 1)}" } resource "aws_route_table" "public_route" { vpc_id = "${aws_vpc.default.id}" route { cidr_block = "0.0.0.0/0" gateway_id = "${aws_internet_gateway.gw.id}" } } resource "aws_route_table_association" "public_route_a" { subnet_id = "${aws_subnet.public_subnet_a.id}" route_table_id = "${aws_route_table.public_route.id}" } resource "aws_security_group" "ec2_terraform_test" { name = "ec2_terraform_test" vpc_id = "${aws_vpc.default.id}" ingress { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } resource "aws_instance" "terraform-test" { ami = "${var.images["ap-northeast-1"]}" instance_type = "t2.micro" key_name = "XXXXXXXX" vpc_security_group_ids = [ "${aws_security_group.ec2_terraform_test.id}" ] subnet_id = "${aws_subnet.public_subnet_a.id}" associate_public_ip_address = "true" root_block_device { volume_type = "gp2" volume_size = "8" } tags { Name = "terraform-test" } } output "public ip" { value = "${aws_instance.terraform-test.public_ip}" }
我明白了。(
下面我将逐一解释代码的每个部分↓)
定义提供者
provider "aws" { access_key = "${var.aws_access_key}" secret_key = "${var.aws_secret_key}" region = "${var.region}" }
我们先从这部分开始。在 Terraform 中,首先需要配置提供程序。
实际上,Terraform 支持 Azure 和 Google Cloud 等云服务,因此
需要预先指定“要描述哪个提供程序的配置”。
在本例中,我们想在 AWS 上构建一个 EC2 实例,所以指定 AWS。
定义变量
变量“aws_access_key”{} 变量“aws_secret_key”{} 变量“region”{默认值=“ap-northeast-1”}
您可以使用 `variable` 代码块定义变量。
存储在这些变量中的值可以从其他代码块调用和使用。
这些变量中的值会在定义提供程序的部分被调用。
这指的是类似 `var.aws_access_key` 的部分。
顺便一提,查看代码时,你可能会疑惑:“变量中的值存储在哪里?” 但
使用 Terraform,你可以将要存储在变量中的值写在一个单独的文件中,
它会自动引用该文件并将值存储到变量中。
这个文件也有特定的扩展名,必须是“*.tfvars”。
aws_access_key = "AKIAXXXXXXXXXXXXXXXX" aws_secret_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
内容如下所示:“变量名="值"”。
资源定义
resource "aws_vpc" "default" { cidr_block = "172.30.0.0/16" instance_tenancy = "default" enable_dns_support = "true" enable_dns_hostnames = "false" tags { Name = "default" } }
本节介绍通常通过管理控制台创建的资源的配置。
在 Terraform 中,“VPC”和“EC2”都被视为资源。
定义 VPC 时,需要指定“cidr_block”。
定义安全组时,需要指定“ingress”和“egress”;
定义 EC2 实例时,需要指定“ami”和“instance_type”,等等。
需要注意的是,这些描述项并非代码编写者可以随意决定的。Terraform
有其特定的约定,如果编写错误,将会收到语法错误(
详情请参阅下面的官方文档,文档均为英文)。
输出
输出“公有 IP”{ 值 = "${aws_instance.terraform-test.public_ip}" }
如果在部署代码后没有收到响应,您将无法得知发生了什么。
通过编写输出块,您可以接收部署结果。
上面的代码用于检索已创建的 EC2 实例的公有 IP 地址。
■ 让我们实际部署它。但在此之前
当然,部署后结果会立即显现,
即使你编写的代码存在错误。(这对你的心理健康可不太好。)
所以,我们来试试 Terraform 的 dry run 功能。
这是一个非常实用的功能,可以让你预先验证代码是否存在错误,并检查执行计划。
如果存在任何语法错误,你会收到一条错误消息,方便你发现(
尝试输入“terraform plan”)。
$ terraform plan 在执行计划之前,正在刷新内存中的 Terraform 状态…… 刷新后的状态将用于计算此计划,但不会持久化到本地或远程状态存储。Terraform 执行计划已生成,如下所示。资源按字母顺序排列,以便快速浏览。绿色资源将被创建(如果资源已存在,则会先销毁再创建),黄色资源将被就地更改,红色资源将被销毁。青色条目是要读取的数据源。注意:您 + aws_instance.terraform-test ami: "ami-cbf90ecb" associate_public_ip_address: "true" availability_zone: "<computed> " ebs_block_device.#: "<computed> " ephemeral_block_device.#: "<computed> " instance_state: "<computed> " instance_type: "t2.micro" ipv6_addresses.#: "<computed> key_name: "XXXXXXXXXX" network_interface_id: "<computed> “ placement_group:”<computed> private_dns:<computed>私有 IP:<computed> public_dns:<computed> public_ip:<computed> " root_block_device.#: "1" root_block_device.0.delete_on_termination: "true" root_block_device.0.iops: "<computed> " root_block_device.0.volume_size: "8" root_block_device.0.volume_type: "gp2" security_groups.#: "<computed> " source_dest_check: "true" subnet_id: "${aws_subnet.public_subnet_a.id}" tags.%: "1" tags.Name: "terraform-test" tenancy: "<computed> " vpc_security_group_ids.#: "<computed> + aws_internet_gateway.gw vpc_id: "${aws_vpc.default.id}" + aws_route_table.public_route route.#: "1" route.~2599208424.cidr_block: "0.0.0.0/0" route.~2599208424.egress_only_gateway_id: "" route.~2599208424.gateway_id: "${aws_internet_gateway.gw.id}" route.~2599208424.instance_id: "" route.~2599208424.ipv6_cidr_block: "" route.~2599208424.nat_gateway_id: "" route.~2599208424.network_interface_id: "" route.~2599208424.vpc_peering_connection_id: "" vpc_id: "${aws_vpc.default.id}" + aws_route_table_association.public_route_a route_table_id: "${aws_route_table.public_route.id}" subnet_id: "${aws_subnet.public_subnet_a.id}" + aws_security_group.ec2_terraform_test description: "Managed by Terraform" egress.#: "1" egress.482069346.cidr_blocks.#: "1" egress.482069346.cidr_blocks.0: "0.0.0.0/0" egress.482069346.from_port: "0" egress.482069346.ipv6_cidr_blocks.#: "0" egress.482069346.prefix_list_ids.#: "0" egress.482069346.protocol: "-1" egress.482069346.security_groups.#: "0" egress.482069346.self: "false" egress.482069346.to_port: "0" ingress.#: "1" ingress.2541437006.cidr_blocks.#: "1" ingress.2541437006.cidr_blocks.0: "0.0.0.0/0" ingress.2541437006.from_port: "22" ingress.2541437006.ipv6_cidr_blocks.#: "0" ingress.2541437006.protocol: "tcp" ingress.2541437006.security_groups.#: "0" ingress.2541437006.self: "false" ingress.2541437006.to_port: "22" name: "ec2_terraform_test" owner_id: "<computed> " vpc_id: "${aws_vpc.default.id}" + aws_subnet.public_subnet_a assign_ipv6_address_on_creation: "false" availability_zone: "ap-northeast-1a" cidr_block: "172.30.16.0/20" ipv6_cidr_block_association_id: "<computed> " map_public_ip_on_launch: "false" vpc_id: "${aws_vpc.default.id}" + aws_vpc.default assign_generated_ipv6_cidr_block: "false" cidr_block: "172.30.0.0/16" default_network_acl_id: "<computed> " default_route_table_id: "<computed> " default_security_group_id: "<computed> " dhcp_options_id: "<computed> " enable_classiclink: "<computed> " enable_dns_hostnames: "false" enable_dns_support: "true" instance_tenancy: "default" ipv6_association_id: "<computed> "ipv6_cidr_block:"<computed> " main_route_table_id: "<computed> " tags.%: "1" tags.Name: "default" 计划:添加 7 个,更改 0 个,销毁 0 个。.
如果程序运行成功且无错误,
结果将显示实际部署时将添加哪些资源。
在本例中,结果显示“计划添加:7 个资源”,因此总共将添加 7 个资源。
如果您得到类似下面的结果,则很遗憾,您需要修复代码中的错误。
发生 2 个错误:* aws_security_group.ec2_terraform_test:egress.0:无效或未知的密钥:cidr_block * aws_security_group.ec2_terraform_test:ingress.0:无效或未知的密钥:cidr_block
■ 尝试实际部署。
既然我们已经确认试运行没有错误,那么现在让我们正式部署它。
部署也可以通过一条命令完成:只需输入“terraform apply”即可。
$ terraform apply aws_vpc.default: 创建中... assign_generated_ipv6_cidr_block: "" => "false" cidr_block: "" => "172.30.0.0/16" default_network_acl_id: "" => "<computed> " default_route_table_id: "" => "<computed> " default_security_group_id: "" => "<computed> " dhcp_options_id: "" => "<computed> " enable_classiclink: "" => "<computed> " enable_dns_hostnames: "" => "false" enable_dns_support: "" => "true" instance_tenancy: "" => "default" ipv6_association_id: "" => "<computed> "ipv6_cidr_block: "" => "<computed> " main_route_table_id: "" => "<computed> tags.%: "" => "1" tags.Name: "" => "default" aws_vpc.default: 创建完成 (ID: vpc-XXXXXXXX) aws_internet_gateway.gw: 正在创建... vpc_id: "" => "vpc-XXXXXXXX" aws_security_group.ec2_terraform_test: 正在创建... description: "" => "由 Terraform 管理" egress.#: "" => "1" egress.482069346.cidr_blocks.#: "" => "1" egress.482069346.cidr_blocks.0: "" => "0.0.0.0/0" egress.482069346.from_port: "" => "0" egress.482069346.ipv6_cidr_blocks.#: "" => "0" egress.482069346.prefix_list_ids.#: "" => "0" egress.482069346.protocol: "" => "-1" egress.482069346.security_groups.#: "" => "0" egress.482069346.self: "" => "false" egress.482069346.to_port: "" => "0" ingress.#: "" => "1" ingress.2541437006.cidr_blocks.#: "" => "1" ingress.2541437006.cidr_blocks.0: "" => "0.0.0.0/0" ingress.2541437006.from_port: "" => "22" ingress.2541437006.ipv6_cidr_blocks.#: "" => "0" ingress.2541437006.protocol: "" => "tcp" ingress.2541437006.security_groups.#: "" => "0" ingress.2541437006.self: "" => "false" ingress.2541437006.to_port: "" => "22" name: "" => "ec2_terraform_test" owner_id: "" => "<computed> " vpc_id: "" => "vpc-XXXXXXXX" aws_subnet.public_subnet_a: 创建中... assign_ipv6_address_on_creation: "" => "false" availability_zone: "" => "ap-northeast-1a" cidr_block: "" => "172.30.16.0/20" ipv6_cidr_block_association_id: "" => "<computed> " map_public_ip_on_launch: "" => "false" vpc_id: "" => "vpc-XXXXXXXX" aws_internet_gateway.gw: 创建完成 (ID: igw-XXXXXXXX) aws_route_table.public_route: 正在创建... route.#: "" => "1" route.3460203481.cidr_block: "" => "0.0.0.0/0" route.3460203481.egress_only_gateway_id: "" => "" route.3460203481.gateway_id: "" => "igw-XXXXXXXX" route.3460203481.instance_id: "" => "" route.3460203481.ipv6_cidr_block: "" => "" route.3460203481.nat_gateway_id: "" => "" route.3460203481.network_interface_id: "" => "" route.3460203481.vpc_peering_connection_id: "" => "" vpc_id: "" => "vpc-XXXXXXXX" aws_subnet.public_subnet_a: 创建完成 (ID: subnet-XXXXXXXX) aws_route_table.public_route: 创建完成 (ID: rtb-XXXXXXXX) aws_route_table_association.public_route_a: 正在创建... route_table_id: "" => "rtb-XXXXXXXX" subnet_id: "" => "subnet-XXXXXXXX" aws_route_table_association.public_route_a: 创建完成 (ID: rtbassoc-XXXXXXXX) aws_security_group.ec2_terraform_test:创建完成(ID:sg-XXXXXXXX) aws_instance.terraform-test:正在创建... ami:"" => "ami-cbf90ecb" associate_public_ip_address:"" => "true" availability_zone:"" => "<computed> " ebs_block_device.#: "" => "<computed> " ephemeral_block_device.#: "" => "<computed> " instance_state: "" => "<computed> " instance_type: "" => "t2.micro" ipv6_addresses.#: "" => "<computed> key_name: "" => "XXXXXXXX" network_interface_id: "" => "<computed> “ placement_group: “” => “<computed> " private_dns: "" => "<computed> " private_ip: "" => "<computed> " public_dns: "" => "<computed> " public_ip: "" => "<computed> " root_block_device.#: " => "1" root_block_device.0.delete_on_termination: " => "true" root_block_device.0.iops: " => "<computed> " root_block_device.0.volume_size: "" => "8" root_block_device.0.volume_type: "" => "gp2" security_groups.#: "" => "<computed> " source_dest_check: "" => "true" subnet_id: "" => "subnet-XXXXXXXX" tags.%: "" => "1" tags.Name: "" => "terraform-test" tenancy: "" => "<computed> " vpc_security_group_ids.#: "" => "1" vpc_security_group_ids.766820655: "" => "sg-XXXXXXXX" aws_instance.terraform-test: 仍在创建...(已耗时 10 秒) aws_instance.terraform-test: 仍在创建...(已耗时 20 秒) aws_instance.terraform-test: 创建完成(ID:i-XXXXXXXXXXXXXXXX) 应用完成!资源:新增 7 个,更改 0 个,销毁 0 个。您的基础设施状态已保存到以下路径。修改和销毁您的基础设施需要此状态,因此请妥善保管。要查看完整状态,请使用 `terraform show` 命令。 状态路径: 输出: 公网 IP = XX.XX.XXX.XX
是的,已在“输出”部分返回了所创建实例的公网 IP 地址。
让我们从管理控制台检查一下实例是否已实际创建。

……成功了(太棒了)
同时,一个名为“terraform.tfstate”的文件会被静默创建。
该文件以JSON格式存储“基础设施的当前状态”
,Terraform会将此文件与定义文件(.tf文件)以及资源的实际状态进行比较,以
确定对目标资源执行的操作,例如创建、修改或删除。
换句话说,如果此文件中存在不一致,则无法正常部署,因此
在处理时需要格外小心。
■概要
我尝试使用 Terraform 来“编写基础设施配置代码”。
编写的代码可读性很高,基本命令也很简单易用,所以我感觉它很容易上手。
这些工具经常与 DevOps 一词一起提及。
如果开发人员和运维人员要共同合作,防止系统变成黑匣子,
我非常希望能好好利用这个工具!
我不会告诉你这篇文章的篇幅比知识部分还要长。
就这些了,谢谢阅读!
0
