下記ページのベストプラクティスを試してみました。
Terraform Best Practices in 2017
ディレクトリ構成は、ちょっと変えて、以下のようにし、
tfstate は s3 ではなく、local に保存して管理するようにしました。
. ├── modules │ ├── ec2 │ ├── iam │ ├── route-table │ ├── security-group │ ├── subnet-private │ ├── subnet-public │ ├── vpc │ └── vpc-endpoint └── targets ├── 10.network ├── 20.iam └── 30.ec2
作成したソースは、Bitbucket からダウンロードできます。
terraformコマンドで、以下の順番でAWS環境を構築します。
なお、今回、terraform コマンドは、オフィシャルのDockerイメージを使用します。
envfile に docker に渡す環境変数を定義します。
tfファイルにアクセスキーを書きたくなかったので環境変数で定義することにしました。
アクセスキーは、ネットワーク、IAM、EC2を作成できる権限が必要です。
envfileの内容は以下のとおり。
オフィシャルの Dockerイメージをプルします。
- ネットワーク作成(vpc, igw, subnet, route-table, security-group)
- IAMロール作成(EC2インスタンス用)
- EC2インスタンス作成
なお、今回、terraform コマンドは、オフィシャルのDockerイメージを使用します。
動作確認
上記Bitbucketのterraformサンプルは、AWSに本番環境と開発環境を構築する想定で作っています。
以下は、開発環境を構築する手順です。
本番環境は、workspace を prod に切り替えるだけで、手順は開発環境と同じです。
環境差分は、targetsディレクトリ配下の variable.tf の変数に定義しています。
以下の例では、"prod."で始まる変数が、本番環境用の定義です。
"dev." で始まる変数が、開発環境用の定義ですが、未定義の場合は、"default."で始まる変数が使われます。
以下の例では、"prod."で始まる変数が、本番環境用の定義です。
"dev." で始まる変数が、開発環境用の定義ですが、未定義の場合は、"default."で始まる変数が使われます。
variable "vpc" { type = "map" default = { default.cidr = "10.0.0.0/16" default.public-a = "10.0.10.0/24" default.public-b = "10.0.11.0/24" default.private-a = "10.0.21.0/24" default.private-b = "10.0.22.0/24" prod.cidr = "10.1.0.0/16" prod.public-a = "10.1.10.0/24" prod.public-b = "10.1.11.0/24" prod.private-a = "10.1.21.0/24" prod.private-b = "10.1.22.0/24" } }
envfile の用意
envfile に docker に渡す環境変数を定義します。
tfファイルにアクセスキーを書きたくなかったので環境変数で定義することにしました。
アクセスキーは、ネットワーク、IAM、EC2を作成できる権限が必要です。
envfileの内容は以下のとおり。
AWS_REGION=<リージョン> AWS_ACCESS_KEY_ID=<アクセスキー> AWS_SECRET_ACCESS_KEY=<シークレットアクセスキー>
terraform の Dockerイメージの pull
オフィシャルの Dockerイメージをプルします。
# docker pull hashicorp/terraform:light
ネットワーク作成
terraform で init を実行します。
カレントディレクトリを dockerコンテナ内の /work にマウントして terraform コマンドを実行します。
[root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/10.network hashicorp/terraform:light init Initializing modules... - module.e-vpc Getting source "../../modules/vpc" - module.e-subnet-public Getting source "../../modules/subnet-public" - module.e-subnet-private Getting source "../../modules/subnet-private" - module.e-route-table Getting source "../../modules/route-table" - module.e-security-group Getting source "../../modules/security-group" - module.e-vpc-endpoint Getting source "../../modules/vpc-endpoint" Initializing the backend... Successfully configured the backend "local"! Terraform will automatically use this backend unless the backend configuration changes. Initializing provider plugins... - Checking for available provider plugins on https://releases.hashicorp.com... - Downloading plugin for provider "aws" (1.14.0)... The following providers do not have any version constraints in configuration, so the latest version was installed. To prevent automatic upgrades to new major versions that may contain breaking changes, it is recommended to add version = "..." constraints to the corresponding provider blocks in configuration, with the constraint strings suggested below. * provider.aws: version = "~> 1.14" 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.
workspace を作成します。dev(開発環境) を新規で作成します。
[root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/10.network hashicorp/terraform:light workspace new dev Created and switched to workspace "dev"! You're now on a new, empty workspace. Workspaces isolate their state, so if you run "terraform plan" Terraform will not see any existing state for this configuration.
workspace で dev が選択されていることを確認します。
[root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/10.network hashicorp/terraform:light workspace list default * dev
[root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/10.network hashicorp/terraform:light plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. ------------------------------------------------------------------------ An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: + module.e-route-table.aws_route_table.private id: <computed> propagating_vgws.#: <computed> route.#: <computed> tags.%: "1" tags.Name: "demo-dev-private" vpc_id: "${lookup(var.m-vpc, \"vpc_id\")}" + module.e-route-table.aws_route_table.public id: <computed> propagating_vgws.#: <computed> route.#: "1" route.~1608397107.cidr_block: "0.0.0.0/0" route.~1608397107.egress_only_gateway_id: "" route.~1608397107.gateway_id: "${lookup(var.m-vpc, \"igw_id\")}" route.~1608397107.instance_id: "" route.~1608397107.ipv6_cidr_block: "" route.~1608397107.nat_gateway_id: "" route.~1608397107.network_interface_id: "" route.~1608397107.vpc_peering_connection_id: "" tags.%: "1" tags.Name: "demo-dev-public" vpc_id: "${lookup(var.m-vpc, \"vpc_id\")}" + module.e-route-table.aws_route_table_association.private-a id: <computed> route_table_id: "${aws_route_table.private.id}" subnet_id: "${lookup(var.m-subnet-private, \"subnet-private-a\")}" + module.e-route-table.aws_route_table_association.private-b id: <computed> route_table_id: "${aws_route_table.private.id}" subnet_id: "${lookup(var.m-subnet-private, \"subnet-private-a\")}" + module.e-route-table.aws_route_table_association.public-a id: <computed> route_table_id: "${aws_route_table.public.id}" subnet_id: "${lookup(var.m-subnet-public, \"subnet-public-a\")}" + module.e-route-table.aws_route_table_association.public-b id: <computed> route_table_id: "${aws_route_table.public.id}" subnet_id: "${lookup(var.m-subnet-public, \"subnet-public-b\")}" + module.e-security-group.aws_security_group.servers id: <computed> arn: <computed> description: "servers security group" egress.#: "1" egress.482069346.cidr_blocks.#: "1" egress.482069346.cidr_blocks.0: "0.0.0.0/0" egress.482069346.description: "" 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.#: "2" ingress.2541437006.cidr_blocks.#: "1" ingress.2541437006.cidr_blocks.0: "0.0.0.0/0" ingress.2541437006.description: "" 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" ingress.753360330.cidr_blocks.#: "0" ingress.753360330.description: "" ingress.753360330.from_port: "0" ingress.753360330.ipv6_cidr_blocks.#: "0" ingress.753360330.protocol: "-1" ingress.753360330.security_groups.#: "0" ingress.753360330.self: "true" ingress.753360330.to_port: "0" name: "demo-dev-servers" owner_id: <computed> revoke_rules_on_delete: "false" vpc_id: "${var.m-vpc[\"vpc_id\"]}" + module.e-subnet-private.aws_subnet.private-a id: <computed> assign_ipv6_address_on_creation: "false" availability_zone: "us-east-1a" cidr_block: "10.0.21.0/24" ipv6_cidr_block: <computed> ipv6_cidr_block_association_id: <computed> map_public_ip_on_launch: "false" tags.%: "1" tags.Name: "demo-dev-private-a" vpc_id: "${lookup(var.m-vpc, \"vpc_id\")}" + module.e-subnet-private.aws_subnet.private-b id: <computed> assign_ipv6_address_on_creation: "false" availability_zone: "us-east-1b" cidr_block: "10.0.22.0/24" ipv6_cidr_block: <computed> ipv6_cidr_block_association_id: <computed> map_public_ip_on_launch: "false" tags.%: "1" tags.Name: "demo-dev-private-b" vpc_id: "${lookup(var.m-vpc, \"vpc_id\")}" + module.e-subnet-public.aws_subnet.public-a id: <computed> assign_ipv6_address_on_creation: "false" availability_zone: "us-east-1a" cidr_block: "10.0.10.0/24" ipv6_cidr_block: <computed> ipv6_cidr_block_association_id: <computed> map_public_ip_on_launch: "false" tags.%: "1" tags.Name: "demo-dev-public-a" vpc_id: "${lookup(var.m-vpc, \"vpc_id\")}" + module.e-subnet-public.aws_subnet.public-b id: <computed> assign_ipv6_address_on_creation: "false" availability_zone: "us-east-1b" cidr_block: "10.0.11.0/24" ipv6_cidr_block: <computed> ipv6_cidr_block_association_id: <computed> map_public_ip_on_launch: "false" tags.%: "1" tags.Name: "demo-dev-public-b" vpc_id: "${lookup(var.m-vpc, \"vpc_id\")}" + module.e-vpc-endpoint.aws_vpc_endpoint.ec2 id: <computed> cidr_blocks.#: <computed> dns_entry.#: <computed> network_interface_ids.#: <computed> policy: <computed> prefix_list_id: <computed> private_dns_enabled: "true" route_table_ids.#: <computed> security_group_ids.#: <computed> service_name: "com.amazonaws.us-east-1.ec2" state: <computed> subnet_ids.#: <computed> vpc_endpoint_type: "Interface" vpc_id: "${var.m-vpc[\"vpc_id\"]}" + module.e-vpc-endpoint.aws_vpc_endpoint.s3 id: <computed> cidr_blocks.#: <computed> dns_entry.#: <computed> network_interface_ids.#: <computed> policy: <computed> prefix_list_id: <computed> private_dns_enabled: "false" route_table_ids.#: <computed> security_group_ids.#: <computed> service_name: "com.amazonaws.us-east-1.s3" state: <computed> subnet_ids.#: <computed> vpc_endpoint_type: "Gateway" vpc_id: "${var.m-vpc[\"vpc_id\"]}" + module.e-vpc.aws_internet_gateway.vpc-igw id: <computed> tags.%: "1" tags.Name: "demo-dev-igw" vpc_id: "${aws_vpc.vpc.id}" + module.e-vpc.aws_vpc.vpc id: <computed> assign_generated_ipv6_cidr_block: "false" cidr_block: "10.0.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_classiclink_dns_support: <computed> enable_dns_hostnames: "true" enable_dns_support: "true" instance_tenancy: <computed> ipv6_association_id: <computed> ipv6_cidr_block: <computed> main_route_table_id: <computed> tags.%: "1" tags.Name: "demo-dev-vpc" Plan: 15 to add, 0 to change, 0 to destroy. ------------------------------------------------------------------------ Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run.
apply を実行します。
[root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/10.network hashicorp/terraform:light apply
IAMロールの作成
上記「ネットワーク作成」と同じ手順で、ロールを作成します。[root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/20.iam hashicorp/terraform:light init [root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/20.iam hashicorp/terraform:light workspace new dev [root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/20.iam hashicorp/terraform:light workspace list [root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/20.iam hashicorp/terraform:light plan [root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/20.iam hashicorp/terraform:light apply
EC2インスタンスの作成
上記「ネットワーク作成」と同じ手順で、EC2インスタンスを作成します。[root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/30.ec2 hashicorp/terraform:light init [root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/30.ec2 hashicorp/terraform:light workspace new dev [root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/30.ec2 hashicorp/terraform:light workspace list [root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/30.ec2 hashicorp/terraform:light plan [root@centos701 aws-sample-terraform]# docker run -it --rm -v `pwd`:/work --env-file=./envfile -w /work/targets/30.ec2 hashicorp/terraform:light apply