公开AWS挑剔的程序员参加Terraform实践课程时的日志
这篇文章是我参加公司内举办的@reoring主办的Terraform实操课程时的操作记录。虽然我尽力写得能够让其他人也能够重现这个实操课程,但由于我对Terraform和AWS并不是很熟悉,如果有错误的地方请指正。
读者目标人群
-
- プログラマーでTerraformでAWSの環境構築を自動化してみたい人。
ちなみに、筆者
AWS: 管理画面でEC2立てたり、S3バケット作ったりはできるが、VPSに逃げがち。
プログラミング歴: PHP15年。他にGo言語などもやったことある。
インフラ: 小規模なウェブアプリをホスティングするために、アプリに関係がある周辺のインフラ知識だけつまみ食いしたレベル。
通过实际操作获得的东西
-
- TerraformでAWS上にEC2インスタンスを建てられるようになる。(hello worldレベル)
- Terraformコードを再利用性・保守性があるものにするモジュールの作り方が分かるようになる。
Terraform实践活动
各位,让我们开始Terraform实践吧。
安装必要的工具。
$ brew install tfenv awscli packer
# 特定のバージョンのterraformをインストールする
$ tfenv install 0.12.7
开发环境
对于Terraform的初学者来说,最好提前准备好以下环境,包括可以提供语法高亮和语法检查功能的编辑器。
-
- IntelliJ IDEA Community(無料)をインストールしておく。
https://www.jetbrains.com/idea/download/#section=mac
IntelliJを起動したら、HashiCorp Terraform/HCL Language supportプラグインをインストールする。
Preferences→Pluginsを開き、Marketplaceタブを選択→「terraform」で検索→「HashiCorp Terraform/HCL Language support」の「Install」をクリックする。
下载教材
$ git clone git@github.com:reoring/terraform-handson.git
$ cd terraform-handson
-
- ハンズオンで使った資料: terraformハンズオン – Qiita
本稿を使って追体験するだけなら、こちらの資料を読む必要はありません。
将认证信息放入AWS配置文件中
我要输入自己的IAM认证信息。(在工作坊中,主办方提供了专门的用于工作坊的认证信息。)
$ aws configure --profile terraform-hello-world
AWS Access Key ID [None]: **************
AWS Secret Access Key [None]: **********************************
Default region name [None]: ap-southeast-1
Default output format [None]:
你可以为个人资料取一个喜欢的名字。在这里我们选择了terraform-hello-world。请记住它,因为我们将在之后使用它。
为了进行实验,选择了新加坡地区(ap-southeast-1)。
确认是否输入了验证信息
$ cat ~/.aws/credentials
[terraform-hello-world]
aws_access_key_id = ***************
aws_secret_access_key = *********************************
使用Packer构建包含Docker的Amazon Linux 2的AMI。
首先进入Packer的目录。
$ cd amazonlinux2-with-docker
我想在新加坡区域创建AMI,所以需要更改Packer的设置。
"variables": {
- "aws_region": "ap-northeast-1",
+ "aws_region": "ap-southeast-1",
"aws_profile": "{{env `AWS_PROFILE`}}"
},
执行以下命令将在EC2上进行构建并创建AMI:
$ AWS_PROFILE=terraform-hello-world packer build amazon-linux2-docker.json
大约需要2分钟。一旦开始构建,AWS EC2控制台(管理页面)将显示AMI。
初始化Terraform
$ cd ../terraform-ec2
$ terraform init
这个 init 命令会读取执行位置下的 tf 文件,并创建一个名为 .terraform 的文件夹。
.terraform
└── plugins
└── darwin_amd64
├── lock.json
└── terraform-provider-aws_v2.26.0_x4
这是下载所需的插件来处理依赖于IaaS(如AWS)的提供商。
将variables.tf中的AWS配置文件名更改为自己的。
variable "aws_profile" {
type = string
description = "AWSのプロファイル名"
- default = "sandbox"
+ default = "terraform-hello-world"
}
如果不做这个操作,就会出现错误。
Error: error validating provider credentials: error calling sts:GetCallerIdentity: NoCredentialProviders: no valid providers in chain. Deprecated.
For verbose messaging see aws.Config.CredentialsChainVerboseErrors
on main.tf line 5, in provider "aws":
5: provider "aws" {
确认terraform的计划
记住自己的IP地址。
$ curl https://httpbin.org/ip
{
"origin": "117.102.178.110, 117.102.178.110"
}
当被问及如何限制VPC的源IP连接时,您可以输入自己查找到的IP地址,再加上”/32″,32表示网络掩码,表示只有该主机一个。
$ terraform plan
var.admin_ip
Enter a value: 117.102.178.110/32
解决问题
在本地找不到SSH的公钥。
Error: Error in function call
on key-pair.tf line 3, in resource "aws_key_pair" "master-key":
3: public_key = file(var.path_to_public_key)
|----------------
| var.path_to_public_key is "~/.ssh/id_rsa.pub"
Call to function "file" failed: no file exists at /Users/suin/.ssh/id_rsa.pub.
如果出现这个错误,请调整密钥路径。
variable "path_to_public_key" {
type = string
- default = "~/.ssh/id_rsa.pub"
+ default = "~/.ssh/id_myrsa.pub"
}
另外,ECDSA 密钥无法使用。
计划的内容
+が追加されるもの
Terraform will perform the following actions:
# aws_instance.web will be created
+ resource "aws_instance" "web" {
+ ami = "ami-0ebd006a05952cc33"
+ arn = (known after apply)
+ associate_public_ip_address = true
+ availability_zone = (known after apply)
+ cpu_core_count = (known after apply)
+ cpu_threads_per_core = (known after apply)
-
- 上のvar.admin_ipで設定した117.102.178.110/32はセキュリティグループのingressの設定に現れる。
-
- ingressはincommingな通信のこと。
117.102.178.110/32からは繋がるという意味。
+ ingress = [
+ {
+ cidr_blocks = [
+ "117.102.178.110/32",
]
为什么要询问var.admin_ip?
由于variables.tf中没有默认值。
variable "admin_ip" {
type = string
// ここに default = "117.102.178.110/32" と書くと聞かれなくなる
}
另外,也可以在terraform.tfvars中设置值。
admin_ip = "117.102.178.110/32"
terraform.tfvars这个文件名很特殊,存在该文件时使用plan命令设置-var-file terraform.tfvars选项具有相同的意义。其他文件名需要使用-var-file选项来指定文件名。
# production専用のtfvarsを作った場合の例
$ terraform plan -var-file terraform.production.tfvars
首先,什么是计划?
以下是可以將tf文件與當前的AWS環境對比,並顯示執行計劃的工具。該計劃僅參考AWS環境並不進行任何更改。
计划的输出结果中,下面的摘要部分非常重要。
Plan: 3 to add, 0 to change, 0 to destroy.
如果存在更改(change)或销毁(destroy)的情况,我们需要进行仔细检查。尽管只是改变了名称,但在AWS中可能存在先销毁再添加的操作,例如安全组的名称等。即使重新创建安全组只需要一瞬间,但可能会导致中断等副作用。如果存在这样的副作用,我们必须在维护时间内进行处理,所以要养成查看摘要的好习惯。
执行
执行terraform apply
$ terraform apply
...[略]...
Plan: 3 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:
输入“是”即可开始反映。
过了一段时间,就会显示执行结果。
...[略]...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
instance_ip = 18.138.255.66
这个instance_ip是根据反映来创建的EC2实例的全局IP地址。
输出的信息将基于在outputs.tf文件中设置的内容。
output "instance_ip" {
value = aws_instance.web.public_ip
}
让我们尝试确认一下是否可以使用SSH登录。
ssh -i ~/.ssh/id_rsa ec2-user@18.138.255.66
摧毁
要抵消apply所创建的环境,需要执行destroy命令来将其消除。
terraform destory
主.tf的代码阅读
阅读main.tf文件并理解HCL语言。
// メタ情報
terraform {
required_version = "= 0.12.7" // terraform 0.12.7でしかこの設定実行できませんよという意味
}
// ここからAWSの設定
provider "aws" {
region = var.aws_region
profile = var.aws_profile
}
// EC2インスタンス作成に仕様するAMIの情報をAWSからとってくる宣言。
// data "データタイプ" "変数名" の書式で定義していく。とってきたデータは変数名に代入される。
// JavaScriptで言ったら const amazonLinux2 = awsAmi({ most_recent: true... }) みたいなイメージ。
data "aws_ami" "amazon-linux2" {
// とってくるAMIの条件設定
most_recent = true // 最新のAMI1件
owners = ["self"]
// AMI名で絞り込み
filter {
name = "name"
values = ["docker-amazon-linux2-*"]
}
// 仮想化タイプで絞り込む
filter {
name = "virtualization-type"
values = ["hvm"]
} // あえてSQLでいうと、次のようなイメージ:
// SELECT * FROM aws_ami
// WHERE name LIKE "docker-amazon-linux2-%"
// AND virtualization-type = "hvm"
// ORDER BY created_at DESC LIMIT 1;
// 他に指定できる条件は公式サイト参照: https://www.terraform.io/docs/providers/aws/d/ami.html#attributes-reference
}
resource "aws_instance" "web" {
// インスタンスの設定をお好みでここに書く
ami = data.aws_ami.amazon-linux2.image_id // 10行目で定義したデータを参照
instance_type = "t2.micro"
associate_public_ip_address = true
key_name = aws_key_pair.master-key.key_name
vpc_security_group_ids = [aws_security_group.web.id]
// 他に指定できる設定値は公式サイト参照: https://www.terraform.io/docs/providers/aws/d/instance.html
}
挑战模块化
挑战代码复用,通过将资源定义模块化。
使资源的定义变得可再利用
将main.tf文件中的以下部分模块化,以便可以重新利用。
// main.tfより抜粋
resource "aws_instance" "web" {
ami = data.aws_ami.amazon-linux2.image_id
instance_type = "t2.micro"
associate_public_ip_address = true
key_name = aws_key_pair.master-key.key_name
vpc_security_group_ids = [aws_security_group.web.id]
}
一旦能够达到可再利用的状态后,就可以通过复制粘贴的方式来批量生产具有类似功能的较少副本的EC2实例。
module "web" {
source = "./modules/instance"
ami_id = data.aws_ami.amazon-linux2.image_id
instance_type = "t2.micro"
enable_public_ip = true
key_name = aws_key_pair.master-key.key_name
security_group_id = aws_security_group.web.id
}
module "web2" {
source = "./modules/instance"
ami_id = data.aws_ami.amazon-linux2.image_id
instance_type = "t2.micro"
enable_public_ip = true
key_name = aws_key_pair.master-key.key_name
security_group_id = aws_security_group.web.id
}
module "web3" {
source = "./modules/instance"
ami_id = data.aws_ami.amazon-linux2.image_id
instance_type = "t2.micro"
enable_public_ip = true
key_name = aws_key_pair.master-key.key_name
security_group_id = aws_security_group.web.id
}
让我们尝试模块化
首先,挖掘名为modules/instance的目录,并在其中创建三个tf文件:
mkdir -p modules/instance
touch modules/instance/{main,outputs,variables}.tf
会有这样的结构。
├── main.tf
├── modules ... 今作ったディレクトリ
│ └── instance
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
请按照以下方式编辑文件内容:
resource "aws_instance" "ec2_instance" {
ami = var.ami_id
instance_type = var.instance_type
associate_public_ip_address = var.enable_public_ip
key_name = var.key_name
vpc_security_group_ids = [var.security_group_id]
}
variable "security_group_id" {
type = string
}
variable "key_name" {
type = string
}
variable "enable_public_ip" {
type = bool
}
variable "instance_type" {
type = string
}
variable "ami_id" {
type = string
}
output "instance_ip" {
value = aws_instance.ec2_instance.public_ip
}
这样模块化完成了。
将 main.tf 文件进行修改,以便使用这个模块。
// コメントアウト
//resource "aws_instance" "web" {
// ami = data.aws_ami.amazon-linux2.image_id
// instance_type = "t2.micro"
//
// associate_public_ip_address = true
// key_name = aws_key_pair.master-key.key_name
//
// vpc_security_group_ids = [aws_security_group.web.id]
//}
module "web" {
source = "./modules/instance"
ami_id = data.aws_ami.amazon-linux2.image_id
instance_type = "t2.micro"
enable_public_ip = true
key_name = aws_key_pair.master-key.key_name
security_group_id = aws_security_group.web.id
}
接下来,还需要对outputs.tf进行修改。
output "instance_ip" {
- value = aws_instance.web.public_ip
+ value = module.web.instance_ip
}
以上是模块化挑战的结束。
最后,运行terraform init命令即可开始使用模块。
terraform init
只需要一种选择的中文翻译:第三方制造的产品已经公开。
在Cloud Posse上,有许多第三方模块可以使用。你可以选择使用这些模块,也可以通过阅读代码来学习技巧。
问答
如果main.tf变得很长怎么办?
分离文件。例如,将安全组资源设置提取出来,创建security-group.tf文件。不需要在main.tf中进行链接(类似于JavaScript或Go语言中的import操作)。会读取目录下的所有tf文件。
CloudFormation有什么区别?
Terraform 支持各种 IaaS,而 CloudFormation 只支持 AWS。
CloudFormation用于扎实地编写JSON或YAML。Terraform使用HCL语言,这是一种易于编写的DSL。
CloudFormation是一种过程式编程语言。Terraform是一种声明式编程语言。
HCL是什么?
HCL是HashiCorp Configuration Language的编程语言。Terraform的tf文件也可以用这种语言编写。
HCL有两个版本,分别是HCL1和HCL2。需要注意的是,尽管HCL2最近发布,但在网络上有关HCL1的信息较多。
HCL2具备与HCL1向下兼容的特性。
terraform.tfstate可以使用git管理吗?
在 Git 中不进行管理。由于包含了机密信息,例如基础架构配置的实际情况等,尽量避免公开。特别是在团队共同维护基础架构时,可以使用名为 Terraform 的后端机制将 terraform.tfstate 同步到 S3 并在团队中共享。
如何检查tf文件的语法错误?
您可以使用 “terraform validate” 命令对配置文件进行静态检查。
如何才能自动格式化tf文件?
在Terraform中可以使用terraform fmt命令来自动格式化配置文件的代码。类似于go fmt的功能。
在这个实践中,我们使用Packer创建了AMI,但是Packer是必需的吗?
当然可以使用AWS提供的AMI。这种情况下,Packer是不需要的。
个人感受
-
- 思っていたよりTerraformは簡単だった。(たぶんひとりでやると難しく、教わると簡単というタイプのツールだと感じた)
-
- AWSは別途覚えないとならないが、Terraform自体はプログラマならすぐ覚えられる言語だと思った。
-
- TerraformのエコシステムにはGoの香りを感じた。go getとterraform getとか。
@reoring ありがとう?