2017年1月19日木曜日

EC2(CentOS6)の構築を Terraform と Ansible でやってみる


前回の記事「Dockerコンテナで Ansible をテストする」で作成した Playbook を使用して EC2インスタンスを構築してみます。

新規のEC2インスタンス起動は、Terraform を使用し、OS設定変更、追加インストールは Ansible で実施します。


1. 既存のAWSリソースを Terraform で管理


Terraforming を使用すると、既存のAWSリソースを terraform の管理下に置けるようなので、試してみます。
既存の vpc とサブネットを Terraform の管理下に置いてみます。

最初は、既存の vpc です。
まず、既存のAWSリソースを参照して vpcのtf形式のファイルを作成します。 続けて、状態を管理する、terraform.tfstate を作成します。
[root@centos0702 terraform]# terraforming vpc > ./vpc.tf
[root@centos0702 terraform]# terraforming vpc --tfstate > ./terraform.tfstate

次はサブネットです。
tf形式のファイルを作成後、terraform.tfstate にサブネットをマージします。
[root@centos0702 terraform]# terraforming sn > ./vpc_sn.tf
[root@centos0702 terraform]# terraforming sn --tfstate --merge=./terraform.tfstate > ./work.tfstate
[root@centos0702 terraform]# mv ./work.tfstate ./terraform.tfstate

terraform コマンドの refresh を実行して、既存の環境に合わせて、terraform.tfstate を最新化します。
[root@centos0702 terraform]# terraform refresh
aws_subnet.subnet-f528a2ad-public-b: Refreshing state... (ID: subnet-f528a2ad)
aws_vpc.dev: Refreshing state... (ID: vpc-d480c3b1)
aws_subnet.subnet-ceb59087-public-a: Refreshing state... (ID: subnet-ceb59087)

terraform コマンドの plan を実行して Terraformと既存環境に差異があるかを確認します。
下記のように表示されたら差異はありません。
[root@centos0702 terraform]# terraform 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.

aws_vpc.dev: Refreshing state... (ID: vpc-d480c3b1)
aws_subnet.subnet-ceb59087-public-a: Refreshing state... (ID: subnet-ceb59087)
aws_subnet.subnet-f528a2ad-public-b: Refreshing state... (ID: subnet-f528a2ad)

No changes. Infrastructure is up-to-date. This means that Terraform
could not detect any differences between your configuration and
the real physical resources that exist. As a result, Terraform
doesn't need to do anything.


2. EC2インスタンスの作成


新規のEC2インスタンス用に tf形式のファイルを作成します。
内容は以下のとおり。
[root@centos0702 terraform]# cat ./ec2_centos6.tf
resource "aws_instance" "centos6" {
    count = 1
    ami = "ami-1c221e76"
    instance_type = "t2.micro"
    availability_zone = "us-east-1a"
    key_name = "virginia_key"
    iam_instance_profile = "admin"
    vpc_security_group_ids = [
        "sg-bbf176df"
    ]
    subnet_id = "subnet-ceb59087"
    associate_public_ip_address = "true"
    root_block_device {
        volume_type = "gp2"
        volume_size = "8"
        delete_on_termination = "true"
    }
    tags {
        Name = "centos6"
    }
}

plan を実行して変更箇所を確認します。
[root@centos0702 terraform]# terraform 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.

aws_subnet.subnet-f528a2ad-public-b: Refreshing state... (ID: subnet-f528a2ad)
aws_vpc.dev: Refreshing state... (ID: vpc-d480c3b1)
aws_subnet.subnet-ceb59087-public-a: Refreshing state... (ID: subnet-ceb59087)

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ aws_instance.centos6
    ami:                                       "ami-1c221e76"
    associate_public_ip_address:               "true"
    availability_zone:                         "us-east-1a"
    ebs_block_device.#:                        "<computed>"
    ephemeral_block_device.#:                  "<computed>"
    iam_instance_profile:                      "admin"
    instance_state:                            "<computed>"
    instance_type:                             "t2.micro"
    key_name:                                  "virginia_key"
    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-ceb59087"
    tags.%:                                    "1"
    tags.Name:                                 "centos6"
    tenancy:                                   "<computed>"
    vpc_security_group_ids.#:                  "1"
    vpc_security_group_ids.1754483564:         "sg-bbf176df"


Plan: 1 to add, 0 to change, 0 to destroy.

apply でAWSリソースの変更を実施します。
[root@centos0702 terraform]# terraform apply
aws_subnet.subnet-ceb59087-public-a: Refreshing state... (ID: subnet-ceb59087)
aws_vpc.dev: Refreshing state... (ID: vpc-d480c3b1)
aws_subnet.subnet-f528a2ad-public-b: Refreshing state... (ID: subnet-f528a2ad)
aws_instance.centos6: Creating...
  ami:                                       "" => "ami-1c221e76"
  associate_public_ip_address:               "" => "true"
  availability_zone:                         "" => "us-east-1a"
  ebs_block_device.#:                        "" => "<computed>"
  ephemeral_block_device.#:                  "" => "<computed>"
  iam_instance_profile:                      "" => "admin"
  instance_state:                            "" => "<computed>"
  instance_type:                             "" => "t2.micro"
  key_name:                                  "" => "virginia_key"
  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-ceb59087"
  tags.%:                                    "" => "1"
  tags.Name:                                 "" => "centos6"
  tenancy:                                   "" => "<computed>"
  vpc_security_group_ids.#:                  "" => "1"
  vpc_security_group_ids.1754483564:         "" => "sg-bbf176df"
aws_instance.centos6: Still creating... (10s elapsed)
aws_instance.centos6: Still creating... (20s elapsed)
aws_instance.centos6: Still creating... (30s elapsed)
aws_instance.centos6: Still creating... (40s elapsed)
aws_instance.centos6: Creation complete

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.

State path: terraform.tfstate

EC2 インスタンスのインスタンスID、パブリックIP、ステータスを確認します。
[root@centos0702 terraform]# terraform show
aws_instance.centos6:
  id = i-06aa5902e65a0c49c
  ami = ami-1c221e76
  associate_public_ip_address = true
  availability_zone = us-east-1a
  disable_api_termination = false
  ebs_block_device.# = 0
  ebs_optimized = false
  ephemeral_block_device.# = 0
  iam_instance_profile = admin
  instance_state = running
  instance_type = t2.micro
  key_name = virginia_key
  monitoring = false
  network_interface_id = eni-d78ba639
  private_dns = ip-10-0-10-190.ec2.internal
  private_ip = 10.0.10.190
  public_dns = ec2-52-90-3-23.compute-1.amazonaws.com
  public_ip = 52.90.3.23
  root_block_device.# = 1
  root_block_device.0.delete_on_termination = true
  root_block_device.0.iops = 100
  root_block_device.0.volume_size = 8
  root_block_device.0.volume_type = gp2
  security_groups.# = 0
  source_dest_check = true
  subnet_id = subnet-ceb59087
  tags.% = 1
  tags.Name = centos6
  tenancy = default
  vpc_security_group_ids.# = 1
  vpc_security_group_ids.1754483564 = sg-bbf176df
aws_subnet.subnet-ceb59087-public-a:
  id = subnet-ceb59087
  availability_zone = us-east-1a
  cidr_block = 10.0.10.0/24
  map_public_ip_on_launch = true
  tags.% = 1
  tags.Name = public-a
  vpc_id = vpc-d480c3b1
aws_subnet.subnet-f528a2ad-public-b:
  id = subnet-f528a2ad
  availability_zone = us-east-1b
  cidr_block = 10.0.11.0/24
  map_public_ip_on_launch = true
  tags.% = 1
  tags.Name = public-b
  vpc_id = vpc-d480c3b1
aws_vpc.dev:
  id = vpc-d480c3b1
  cidr_block = 10.0.0.0/16
  default_network_acl_id = acl-c67209a3
  default_route_table_id = rtb-cdcc9ea8
  default_security_group_id = sg-bbf176df
  dhcp_options_id = dopt-ffa5459a
  enable_classiclink = false
  enable_dns_hostnames = true
  enable_dns_support = true
  instance_tenancy = default
  main_route_table_id = rtb-cdcc9ea8
  tags.% = 1
  tags.Name = dev

試しに、awscli で EC2 インスタンスのステータスを確認してみます。
[root@centos0702 terraform]# aws ec2 describe-instances --instance-ids i-06aa5902e65a0c49c --query 'Reservations[].Instances[].{status:State.Name}' --output table
-------------------
|DescribeInstances|
+-----------------+
|     status      |
+-----------------+
|  running        |
+-----------------+


3. Ansibleの Playbook 実行


Ansible の Playbook を実行します。
パブリックIPと、キーペアの秘密鍵を指定します。
[root@centos0702 ansible]# env ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -i ,52.90.3.23 --private-key ./virginia_key.pem ./centos6_basic.yml

PLAY [all] *********************************************************************

TASK [setup] *******************************************************************
ok: [52.90.3.23]

TASK [install libselinux-python] ***********************************************
changed: [52.90.3.23]

TASK [disable selinux] *********************************************************
changed: [52.90.3.23]

TASK [disable ipv6] ************************************************************
changed: [52.90.3.23]

TASK [set locale to /etc/sysconfig/i18n] ***************************************
changed: [52.90.3.23]

TASK [set zone to /etc/sysconfig/clock] ****************************************
changed: [52.90.3.23]

TASK [set localtime] ***********************************************************
changed: [52.90.3.23]

TASK [install ntp] *************************************************************
changed: [52.90.3.23]

TASK [enable ntp service] ******************************************************
changed: [52.90.3.23]

TASK [disable firewall] ********************************************************
changed: [52.90.3.23] => (item=iptables)
changed: [52.90.3.23] => (item=ip6tables)

TASK [awscli - epel] ***********************************************************
changed: [52.90.3.23]

TASK [awscli - python-pip] *****************************************************
changed: [52.90.3.23]

TASK [awscli - pip install] ****************************************************
changed: [52.90.3.23]

TASK [codedeploy agent - yum] **************************************************
changed: [52.90.3.23] => (item=[u'git', u'gcc', u'openssl-devel', u'readline-devel', u'zlib-devel'])

TASK [codedeploy agent - git clone rbenv] **************************************
changed: [52.90.3.23]

TASK [codedeploy agent - git clone ruby_build] *********************************
changed: [52.90.3.23]

TASK [codedeploy agent - /etc/profile.d/rbenv.sh] ******************************
changed: [52.90.3.23] => (item={u'lin': u'export RBENV_ROOT=/opt/rbenv', u'reg': u'^export RBENV_ROOT='})
changed: [52.90.3.23] => (item={u'lin': u'export PATH="${RBENV_ROOT}/bin:${PATH}"', u'reg': u'^export PATH='})
changed: [52.90.3.23] => (item={u'lin': u'eval "$(rbenv init -)"', u'reg': u'^eval '})

TASK [codedeploy agent - rbenv install 2.2.4] **********************************
changed: [52.90.3.23]
 [WARNING]: Consider using 'become', 'become_method', and 'become_user' rather than running sudo


TASK [codedeploy agent - rbenv grobal 2.2.4] ***********************************
changed: [52.90.3.23]

TASK [codedeploy agent - /usr/bin/ruby] ****************************************
changed: [52.90.3.23]

TASK [codedeploy agent - wget (N.Virginia)] ************************************
changed: [52.90.3.23]

TASK [codedeploy agent - script chmod] *****************************************
changed: [52.90.3.23]

TASK [codedeploy agent - install auto] *****************************************
changed: [52.90.3.23]

TASK [ssm agent - wget (N.Virginia)] *******************************************
changed: [52.90.3.23]

TASK [ssm agent - rpm] *********************************************************
changed: [52.90.3.23]

PLAY RECAP *********************************************************************
52.90.3.23                 : ok=25   changed=24   unreachable=0    failed=0

awscli で EC2 インスタンスを再起動します。
これで構築は完了です。
[root@centos0702 ansible]# aws ec2 stop-instances --instance-ids i-06aa5902e65a0c49c
{
    "StoppingInstances": [
        {
            "InstanceId": "i-06aa5902e65a0c49c",
            "CurrentState": {
                "Code": 64,
                "Name": "stopping"
            },
            "PreviousState": {
                "Code": 16,
                "Name": "running"
            }
        }
    ]
}
[root@centos0702 ansible]# aws ec2 describe-instances --instance-ids i-06aa5902e65a0c49c --query 'Reservations[].Instances[].{status:State.Name}' --output table
-------------------
|DescribeInstances|
+-----------------+
|     status      |
+-----------------+
|  stopped        |
+-----------------+
[root@centos0702 ansible]# aws ec2 start-instances --instance-ids i-06aa5902e65a0c49c                                      {
    "StartingInstances": [
        {
            "InstanceId": "i-06aa5902e65a0c49c",
            "CurrentState": {
                "Code": 0,
                "Name": "pending"
            },
            "PreviousState": {
                "Code": 80,
                "Name": "stopped"
            }
        }
    ]
}
[root@centos0702 ansible]# aws ec2 describe-instances --instance-ids i-06aa5902e65a0c49c --query 'Reservations[].Instances[].{status:State.Name}' --output table
-------------------
|DescribeInstances|
+-----------------+
|     status      |
+-----------------+
|  running        |
+-----------------+

terraform の状態管理を最新化して、パブリックIPを確認します。
[root@centos0702 terraform]# terraform refresh
aws_instance.centos6: Refreshing state... (ID: i-06aa5902e65a0c49c)
aws_subnet.subnet-f528a2ad-public-b: Refreshing state... (ID: subnet-f528a2ad)
aws_vpc.dev: Refreshing state... (ID: vpc-d480c3b1)
aws_subnet.subnet-ceb59087-public-a: Refreshing state... (ID: subnet-ceb59087)
[root@centos0702 terraform]# terraform show | grep public_ip
  associate_public_ip_address = true
  public_ip = 52.90.228.249
  map_public_ip_on_launch = true
  map_public_ip_on_launch = true

EC2インスタンスにログインできるか確認します。
[root@centos0702 ansible]# ssh -l centos -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i ./virginia_key.pem 52.90.228.249
Last login: Tue Jan 17 18:06:38 2017 from 153.212.112.174
[centos@ip-10-0-10-190 ~]$ exit
logout
Connection to 52.90.228.249 closed.


4. EC2インスタンスの破棄


不要になったEC2インスタンスを terraform で破棄します。
破棄するEC2インスタンスを指定して 実行計画を ec2_destory.plan ファイルに保存します。
[root@centos0702 terraform]# ./terraform plan -destroy -target=aws_instance.centos6 -out ./ec2_destory.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.

aws_instance.centos6: Refreshing state... (ID: i-06aa5902e65a0c49c)

The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Your plan was also saved to the path below. Call the "apply" subcommand
with this plan file and Terraform will exactly execute this execution
plan.

Path: ./ec2_destory.plan

- aws_instance.centos6


Plan: 0 to add, 0 to change, 1 to destroy.

実行計画をAWSリソースに適用して、EC2インスタンスをターミネートします。
[root@centos0702 terraform]# terraform apply ./ec2_destory.plan
aws_instance.centos6: Destroying...
aws_instance.centos6: Still destroying... (10s elapsed)
aws_instance.centos6: Still destroying... (20s elapsed)
aws_instance.centos6: Still destroying... (30s elapsed)
aws_instance.centos6: Still destroying... (40s elapsed)
aws_instance.centos6: Still destroying... (50s elapsed)
aws_instance.centos6: Still destroying... (1m0s elapsed)
aws_instance.centos6: Still destroying... (1m10s elapsed)
aws_instance.centos6: Destruction complete

Apply complete! Resources: 0 added, 0 changed, 1 destroyed.

awscli でステータスを確認します。
[root@centos0702 terraform]# aws ec2 describe-instances --instance-ids i-06aa5902e65a0c49c --query 'Reservations[].Instances[].{status:State.Name}' --output table
-------------------
|DescribeInstances|
+-----------------+
|     status      |
+-----------------+
|  terminated     |
+-----------------+