Terraformを小さく初めてみよう
そもそもTerraformって?
AWSやCGPやAzureなどなどに対応したマルチクラウドなIaaS構築をコード化できるもの
Terraformをいじってみる
Terraform歴はざっと2日のペーペーですが、本番で小さく始められそうなのでその備忘禄。
環境
・Terraform v0.6.9
・CentOS6.6
※インストールはいたるところで書かれているので割愛します。簡単です。
安定のClassmethodさんブログ参考にしました↓
AWSでTerraformに入門 | Developers.IO
ここら辺で使ってみる
うちは小さいサービスが次々生まれるので、都度VPC作ってSubnetをAZごとに作ってRouteTable作って監視サーバーいるVPCとPeering張ってそこからの通信をSecurityGroupで開けたりとか・・
この辺いつも同じ構成なわけで、もうやりたかない。。
が、手でポチポチやってたので人に引き継ぎやすいジョブにしないと、、
マネコンのキャプチャ取って手順書くとかないし、やはりAWSだしCloudFormationか?でも実行時しかエラー判断できないしjsonだしコメント入れられないから適当にやっといてと言いづらい・・
こんな感じを作る↓
AnsibleとSpinnakerは試してみた
Ansibleは既に使ってたので、AnsibleのAWSモジュールでなんとか出来たらいいなぁと思ってました(これで済めば1番やっといてと言いやすいw)。ただ半年くらい前はPeering作れなかったり、R53のプライベートゾーンにVPCアタッチできなかったり(これはAnsible2.0で出来るみたい)なので片手落ちなものが出来るのでやめときました。
http://docs.ansible.com/ansible/route53_module.html
次はNetflixがSpinnakerというOSSを出したので試しました。UIはリッチでマルチクラウドで良さそうでしたが結構重い、、キャッシュにクセがありメンバーもはまるかなぁってのと裏でCassandraを使っているらしくCassandraにトラウマがあったのでやめました;
遠回りしましたが、じゃあTerraformでも試すべかと。
いい感じに熟してきてると思います。情報も多く改善もされまくってますね。
よいなぁ~と思った点
- terraform plan xxx.tfでドライラン出来るので慣れてない人にも優しい
- HCL(Hashicorp Configuration Language)が可読性高く慣れてなくても読める
- AnsibleよりCloudリソース多い(そりゃそうだよね)
- 依存関係は自動でよろしくやってくれる(depends_onで明示的にもできる)
- かる~い
- terraform graph | dot -Tpng > ./graph.pngでグラフが作れる。こんな感じ↓ リソースの量が増えればカオスなマップになるでしょうねきっと^^;
※実際に下の方に張ったテンプレートの実行結果をグラフ化しました。
※事前にsudo yum install -y graphviz graphviz-gdしておく
- crash.logが出る。1回クラッシュしたけどこんな感じ出てくれた↓
[root@tfsvr defaultvpc]# tail crash.log 2016/01/11 21:44:13 [DEBUG] vertex provider.aws (close), got dep: aws_route.r_default2 2016/01/11 21:44:13 [DEBUG] vertex provider.aws (close), got dep: aws_route.r_default3 2016/01/11 21:44:13 [DEBUG] vertex root, got dep: provider.aws (close) 2016/01/11 21:44:13 [DEBUG] vertex root, got dep: provisioner.local-exec 2016/01/11 21:44:13 [DEBUG] vertex root, got dep: provisioner.remote-exec 2016/01/11 21:44:13 [DEBUG] waiting for all plugin processes to complete... 2016/01/11 21:44:13 [DEBUG] /usr/local/src/terraform_0.6.9/terraform-provisioner-remote-exec: plugin process exited 2016/01/11 21:44:13 [DEBUG] /usr/local/src/terraform_0.6.9/terraform-provisioner-local-exec: plugin process exited 2016/01/11 21:44:13 [DEBUG] /usr/local/src/terraform_0.6.9/terraform-provisioner-chef: plugin process exited 2016/01/11 21:44:13 [DEBUG] /usr/local/src/terraform_0.6.9/terraform-provisioner-file: plugin process exited
むむむって思った点
- 既存環境にはテンプレート当てれない(多分)。
ステート持つのでステートをファイルにエクスポートしてからテンプレート起こすみたなことが出来そうな気がしないでもないですが今後に期待 - 冪等性はない!?のかな
冪等性があるとも謳ってないけど。
そもそもイケてないテンプレートの書き方をしたのですが、ルーティングテーブルを作成する"aws_route_table"でルートAを追加し、ルーティングテーブルエントリを作成する"aws_route"で同じルートテーブルIDに対してルートBを追加した場合
・まずドライランは成功します。
・1回目はaws_route_tableで追加されるルートAとaws_routeのルートBが両方追加されたルートテーブルができます。
・2回目はaws_routeに書かれたルートBがaws_route_tableにはないので削除されました。(毎回aws_route_tableの後にaws_routeが読まれればいいのですが違うのかな?)
・3回目は1回目と同じく全てのルートAもルートBも追加されます。あとは繰り返しですが何度やっても同じ構成に収束はしないですね;
※結局はaws_route_tableにルートAもルートBも書くべきってだけの話ですが、できれば標準のTerraformリソース使ってる範囲ではエラーで怒られたい
まずは小さく始めよう^^
とりあえずは、冒頭に戻って最初の想いを忘れずに、使い切りでいいので"AWSあるある構成"を誰がやっっても同じになる。そして確認も簡単にできればOK。
以下のようなテンプレートを作りました。
・VPC 1
・Subnet 2(AZごと)
・SecurityGroup 2(EC2用、ELB用)
・ELB 1
・EC2 2(MultiAZ、ELB追加済)
・InternetGateWay 1
・RouteTable 1
・VPCPeering 1
・既存RouteTableにルート追加
・既存SecurityGroupにルール追加
・既存R53にVPCアタッチ
※鍵は鍵を配置したOSをAMIにしてしまってるのでTerraformでは作ってません。ただ作れます。
変数はファイルで外出しし、変数だけ変えて汎用的に使えるようにしました(引数で渡したり環境変数に持たせたりもできます)。-var-fileで変数ファイルを食わせます。
applyで実行ですがテンプレートファイルとなるxxx.tfファイルを指定しないとカレントディレクトリのxxx.tfファイルを勝手に読み込みます。テンプレートファイルは分割もOKです。
用途別にディレクトリを分けるのがいいかと思います。
[root@tfsvr defaultvpc]# pwd /usr/local/src/terraform/defaultvpc [root@tfsvr defaultvpc]# terraform apply default.tf -var-file /root/terraform.tfvars
"xxxx"でマスクしているところはご自身の環境に合わせた値を入れて、"example"を"サービス名"に置換してもらえば使えるかと。多分・・
作成も削除もだいたい3分弱でできました
SGとかEC2のオプションやIPは一例ですので、それぞれポリシーに合わせて適宜変えてもらえればと
何かあればコメントください><
[root@tfsvr defaultvpc]# cat /root/terraform.tfvars ## ## よくあるAWS構成Template ## 1vpc,2subnet(multiaz),igw,routetable,peering*2,securitygroup*2,ec2*2,elb ## ## usage: ## exampleをサービス名に変更 ## (例:service1のInternetGatewayならexamplegw ==> service1gw) ## IPアドレスをサービスに割り当てたものに変更 ## (例:service1のVPCならネットワークアドレスを172.1.0.0/16 == > 172.2.0.0/16) ## ## access_key = "xxxxxxxxxxx" secret_key = "xxxxxxxxxxx" ### AWS region_oregon = "us-west-2" az_oregon1 = "us-west-2a" az_oregon2 = "us-west-2b" ##region_tokyo = "ap-northeast-1" ##az_tokyo1 = "ap-northeast-1a" ##az_tokyo2 = "ap-northeast-1c" awsid_example = "xxxxxxxxxxx" ### IGW igw_example = "example" ### VPC vpc_example = "example" ciderblock_example = "172.1.0.0/16" dhcpoptions_example = "dopt-xxxxxxxxxxx" vpc_kanshi ="vpc-xxxxxxxxxxx" vpcpeering_example1 = "kanshi--example" ### Subnet subnet_example1 = "example-2a" subnet_example2 = "example-2b" ciderblock_example1 = "172.1.0.0/24" ciderblock_example2 = "172.1.1.0/24" ### RouteTable rt_example = "example" ciderblock_any = "0.0.0.0/0" ciderblock_kanshi = "xxxxxxxxxxx/16" rt_kanshi ="rtb-xxxxxxxxxxx" ### SG sg_example1 = "example-web" sg_example2 = "example-lb" ciderblock_office1 = "xxxxxxxxxxx/32" sg_zabbix = "sg-xxxxxxxxxxx" ### EC2 instancetype_example = "t2.micro" ec2_example1 = "example-web01" ec2_example2 = "example-web02" ### ELB elb_example = "example-weblb" ### R53 route53_privatezone = "xxxxxxxxxxx"
[root@tfsvr defaultvpc]# cat default.tf variable "access_key" {} variable "secret_key" {} #variable "region_tokyo" {} variable "region_oregon" {} variable "igw_example" {} variable "vpc_example" {} variable "subnet_example1" {} variable "subnet_example2" {} variable "instancetype_example" {} variable "ciderblock_example" {} variable "ciderblock_example1" {} variable "ciderblock_example2" {} variable "rt_example" {} #variable "az_tokyo1" {} #variable "az_tokyo2" {} variable "az_oregon1" {} variable "az_oregon2" {} variable "ciderblock_any" {} variable "elb_example" {} variable "ec2_example1" {} variable "ec2_example2" {} variable "dhcpoptions_example" {} variable "vpc_kanshi" {} variable "vpcpeering_example1" {} variable "vpcpeering_example2" {} variable "awsid_example" {} variable "ciderblock_kanshi" {} variable "rt_kanshi" {} variable "sg_example1" {} variable "sg_example2" {} variable "sg_kanshizabbix" {} variable "ciderblock_office1" {} variable "route53_privatezone" {} variable "images" { default = { us-east-1 = "" us-west-2 = "ami-xxxxxxxx" us-west-1 = "" eu-west-1 = "" eu-central-1 = "" ap-southeast-1 = "" ap-southeast-2 = "" ap-northeast-1 = "" } } provider "aws" { access_key = "${var.access_key}" secret_key = "${var.secret_key}" region = "${var.region_oregon}" } ### IGW resource "aws_internet_gateway" "defaultgw" { vpc_id = "${aws_vpc.defaultvpc.id}" tags { Name = "${var.igw_example}" } } ### VPC resource "aws_vpc" "defaultvpc" { cidr_block = "${var.ciderblock_example}" enable_dns_hostnames = true tags { Name = "${var.vpc_example}" } } ### DHCP Option resource "aws_vpc_dhcp_options_association" "defaultdns" { vpc_id = "${aws_vpc.defaultvpc.id}" dhcp_options_id = "${var.dhcpoptions_example}" } ### R53 VPC Add resource "aws_route53_zone_association" "private" { zone_id = "${var.route53_privatezone}" vpc_id = "${aws_vpc.defaultvpc.id}" } ### Subnet resource "aws_subnet" "defaultsubnet1" { vpc_id = "${aws_vpc.defaultvpc.id}" cidr_block = "${var.ciderblock_example1}" availability_zone = "${var.az_oregon1}" tags { Name = "${var.subnet_example1}" } } resource "aws_subnet" "defaultsubnet2" { vpc_id = "${aws_vpc.defaultvpc.id}" cidr_block = "${var.ciderblock_example2}" availability_zone = "${var.az_oregon2}" tags { Name = "${var.subnet_example2}" } } ### Route resource "aws_route" "r_kanshi" { route_table_id = "${var.rt_kanshi}" destination_cidr_block = "${var.ciderblock_example}" vpc_peering_connection_id = "${aws_vpc_peering_connection.kanshipeering.id}" } ### RouteMainTable association resource "aws_main_route_table_association" "a" { vpc_id = "${aws_vpc.defaultvpc.id}" route_table_id = "${aws_route_table.r.id}" } ### RouteTable resource "aws_route_table" "r" { vpc_id = "${aws_vpc.defaultvpc.id}" route { cidr_block = "${var.ciderblock_any}" gateway_id = "${aws_internet_gateway.defaultgw.id}" } route { cidr_block = "${var.ciderblock_kanshi}" vpc_peering_connection_id = "${aws_vpc_peering_connection.kanshipeering.id}" } tags { Name = "${var.rt_example}" } } ### VPC Peering resource "aws_vpc_peering_connection" "kanshipeering" { peer_owner_id = "${var.awsid_example}" peer_vpc_id = "${aws_vpc.defaultvpc.id}" vpc_id = "${var.vpc_kanshi}" auto_accept = true tags { Name = "${var.vpcpeering_example1}" } } ### Instance resource "aws_instance" "defaultinstance1" { ami = "${var.images.us-west-2}" availability_zone = "${var.az_oregon1}" instance_type = "${var.instancetype_example}" subnet_id = "${aws_subnet.defaultsubnet1.id}" security_groups = ["${aws_security_group.defaultec2sg.id}"] associate_public_ip_address = "true" root_block_device = { volume_type = "gp2" volume_size = "20" } tags { Name = "${var.ec2_example1}" } } resource "aws_instance" "defaultinstance2" { ami = "${var.images.us-west-2}" availability_zone = "${var.az_oregon2}" instance_type = "${var.instancetype_example}" subnet_id = "${aws_subnet.defaultsubnet2.id}" security_groups = ["${aws_security_group.defaultec2sg.id}"] associate_public_ip_address = "true" root_block_device = { volume_type = "gp2" volume_size = "20" } tags { Name = "${var.ec2_example2}" } } ### SecurityGroup resource "aws_security_group" "defaultec2sg" { name = "exampleec2sg" description = "Used in the terraform" vpc_id = "${aws_vpc.defaultvpc.id}" # kanshi access from anywhere ingress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["${var.ciderblock_kanshi}"] } # office access from anywhere ingress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["${var.ciderblock_office1}"] } # elb HTTP access from anywhere ingress { from_port = 80 to_port = 80 protocol = "tcp" security_groups = ["${aws_security_group.defaultelbsg.id}"] } tags { Name = "${var.sg_example1}" } } resource "aws_security_group" "defaultelbsg" { name = "exampleelbsg" description = "Used in the terraform" vpc_id = "${aws_vpc.defaultvpc.id}" # office HTTP access from anywhere ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["${var.ciderblock_office1}"] } tags { Name = "${var.sg_example2}" } } resource "aws_security_group_rule" "kanshizabbixsg" { type = "ingress" from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["${var.ciderblock_example}"] security_group_id = "${var.sg_kanshizabbix}" } ### ELB resource "aws_elb" "defaultelb" { name = "${var.elb_example}" listener { instance_port = 80 instance_protocol = "http" lb_port = 80 lb_protocol = "http" } health_check { healthy_threshold = 2 unhealthy_threshold = 2 timeout = 5 target = "HTTP:80/" interval = 10 } # The instance is registered automatically instances = ["${aws_instance.defaultinstance1.id}","${aws_instance.defaultinstance2.id}"] subnets = ["${aws_subnet.defaultsubnet1.id}","${aws_subnet.defaultsubnet2.id}"] security_groups = ["${aws_security_group.defaultelbsg.id}"] cross_zone_load_balancing = true }
所感
なにやるにもTerraformでやるようになってしまった。もうぽちぽちする気が起きなくなった。