2018年4月10日火曜日

Terraformのベストプラクティスを試す


下記ページのベストプラクティスを試してみました。
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環境を構築します。

  1. ネットワーク作成(vpc, igw, subnet, route-table, security-group)
  2. IAMロール作成(EC2インスタンス用)
  3. EC2インスタンス作成

なお、今回、terraform コマンドは、オフィシャルのDockerイメージを使用します。

動作確認


上記Bitbucketのterraformサンプルは、AWSに本番環境と開発環境を構築する想定で作っています。

以下は、開発環境を構築する手順です。
本番環境は、workspace を prod に切り替えるだけで、手順は開発環境と同じです。

環境差分は、targetsディレクトリ配下の variable.tf の変数に定義しています。
以下の例では、"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

plan を実行します。
[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