[JAWS-UG CLI] ECS #1 ECSクラスタとECSコンテナインスタンスの構築

前提条件
========


ECSへの権限
-----------

* ECSに対してフル権限があること。


EC2への権限
-----------

* EC2に対してフル権限があること。


デフォルトVPCの存在
-------------------

* デフォルトVPCが存在すること。

```bash:コマンド:
VPC_ID=` \
        aws ec2 describe-vpcs \
          --filters Name=isDefault,Values=true \
          --query 'Vpcs[].VpcId' \
          --output text \
` \
        && echo ${VPC_ID}
```

```text:結果(例):
      vpc-xxxxxxxx
```


AWS CLIのバージョン
-------------------

* 以下のバージョンで動作確認済

  * AWS CLI 1.7.36

```bash:コマンド:
aws --version
```

```text:結果(例):
aws-cli/1.7.36 Python/2.7.5 Darwin/13.4.0
```


0. 準備
=======

0.1. リージョンの決定
---------------------

```bash:変数の設定:
export AWS_DEFAULT_REGION='ap-northeast-1'
```


0.2. 変数の確認
---------------

```bash:変数の確認:
aws configure list
```

```text:結果(例):
            Name                    Value             Type    Location
            ----                    -----             ----    --------
         profile  ec2ecs_full-prjZ-mbp13               env    AWS_DEFAULT_PROFILE
      access_key     ****************LOAQ shared-credentials-file
      secret_key     ****************I1O1 shared-credentials-file
          region           ap-northeast-1              env    AWS_DEFAULT_REGION
```


0.3. 稼動インスタンスを確認
---------------------------

同一リージョンでインスタンスが起動していないことを確認します。

```bash:コマンド:
aws ec2 describe-instances \
        --filters Name=instance-state-name,Values=running
```

```json:結果:
      {
          "Reservations": []
      }
```


0.4. IAMインスタンスプロファイルの決定
--------------------------------------

```bash:コマンド:
aws iam list-instance-profiles \
  --query 'InstanceProfiles[].InstanceProfileName'
```

'ecsInstanceRole' が存在しない場合は、 http://qiita.com/tcsh/items/8daed4cf04d5bc56e2a7 を実施してください。

```bash:変数の設定:
IAM_INSTANCE_PROFILE_NAME='ecsInstanceRole'
```

```bash:コマンド:
aws iam get-instance-profile \
        --instance-profile-name ${IAM_INSTANCE_PROFILE_NAME}
```

```json:結果:
{
    "InstanceProfile": {
        "InstanceProfileId": "AIPAXXXXXXXXXXXXXXXXX", 
        "Roles": [
            {
                "AssumeRolePolicyDocument": {
                    "Version": "2008-10-17", 
                    "Statement": [
                        {
                            "Action": "sts:AssumeRole", 
                            "Principal": {
                                "Service": "ec2.amazonaws.com"
                            }, 
                            "Effect": "Allow", 
                            "Sid": ""
                        }
                    ]
                }, 
                "RoleId": "AROAXXXXXXXXXXXXXXXXX", 
                "CreateDate": "2015-07-05T01:23:45Z", 
                "RoleName": "ecsInstanceRole", 
                "Path": "/", 
                "Arn": "arn:aws:iam::XXXXXXXXXXXX:role/ecsInstanceRole"
            }
        ], 
        "CreateDate": "2015-07-05T01:23:45Z", 
        "InstanceProfileName": "ecsInstanceRole", 
        "Path": "/", 
        "Arn": "arn:aws:iam::XXXXXXXXXXXX:instance-profile/ecsInstanceRole"
    }
}
```

0.5. セキュリティグループの確認
-------------------------------

まず、セキュリティグループの一覧を確認します。

```bash:コマンド:
aws ec2 describe-security-groups \
        --query 'SecurityGroups[].GroupName'
```

'ec2-http-https-ssh-global-inbound'が存在しない場合は、 http://qiita.com/tcsh/items/1d6a499b03b776355ee2 を実施してください。


利用するセキュリティグループ名を指定します。

```bash:変数の設定:
VPC_SG_NAME='ec2-http-https-ssh-global-inbound'
```

```bash:コマンド:
VPC_SG_ID=` \
        aws ec2 describe-security-groups \
          --filter Name=group-name,Values=${VPC_SG_NAME} \
          --query 'SecurityGroups[].GroupId' \
          --output text \
` \
        && echo ${VPC_SG_ID}
```

```text:結果(例):
sg-xxxxxxxx
```

```bash:変数の設定:
ARRAY_VPC_SG_ID="${VPC_SG_ID} ${ARRAY_VPC_SG_ID}" \
        && echo ${ARRAY_VPC_SG_ID}
```


0.6. キーペアの確認
-------------------

まず、キーペアの一覧を確認します。

```bash:コマンド:
aws ec2 describe-key-pairs \
        --query 'KeyPairs[].KeyName'
```

キーペアが存在しない場合は、 http://qiita.com/tcsh/items/59303d9506ca7d13f744 を実施してください。

利用するキーペア名を指定します。

```bash:変数の設定:
EC2_KEY_NAME=<キーペア名>
```

利用するキーペアの秘密鍵ファイルを指定します。

```bash:変数の設定:
FILE_SSH_KEY=<秘密鍵ファイル>
```


1. 事前作業
===========


1.1. インスタンスタイプの決定
-----------------------------

```bash:変数の設定:
EC2_INSTANCE_TYPE='t2.micro'
```


1.2. イメージIDの決定
---------------------

ECSに最適化されたAMIを選択します。

```bash:変数の設定:
AMZLINUX_VERSION='2015.03.d'
EC2_IMAGE_NAME="amzn-ami-${AMZLINUX_VERSION}-amazon-ecs-optimized"
```

```bash:コマンド:
EC2_IMAGE_ID=` \
        aws ec2 describe-images \
          --filters Name=name,Values="${EC2_IMAGE_NAME}" \
          --query 'Images[].ImageId' --output text \
` \
        && echo ${EC2_IMAGE_ID}
```

```text:結果(例):
      ami-XXXXXXXX
```


1.3. クラスタ名の決定
---------------------

```bash:変数の設定:
ECS_CLUSTER_NAME='handson-cluster'
```

同名のクラスタが存在しないことを確認します。

```bash:コマンド:
aws ecs describe-clusters \
  --clusters ${ECS_CLUSTER_NAME}
```

```json:結果(例):
      {
        "clusters": [],
        "failures": [
          {
              "reason": "MISSING",
              "arn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:cluster/handson-cluster"
          }
        ]
      }
```


1.4. User Dataの作成
--------------------

```bash:変数の設定:
FILE_EC2_USERDATA="ecs-userdata-${ECS_CLUSTER_NAME}.bash"
```

```bash:変数の確認:
cat << ETX

        ECS_CLUSTER_NAME:  ${ECS_CLUSTER_NAME}
        FILE_EC2_USERDATA: ${FILE_EC2_USERDATA}

ETX
```

```bash:コマンド:
cat << EOF > ${FILE_EC2_USERDATA}
#!/bin/bash
echo ECS_CLUSTER=${ECS_CLUSTER_NAME} > /etc/ecs/ecs.config
EOF

cat ${FILE_EC2_USERDATA}
```


2. クラスタの作成
=================

2.1. クラスタの作成
-------------------

```bash:変数の確認:
cat << ETX

        ECS_CLUSTER_NAME: ${ECS_CLUSTER_NAME}

ETX
```

```bash:コマンド:
aws ecs create-cluster \
        --cluster-name ${ECS_CLUSTER_NAME}
```

```json:結果(例):
      {
        "cluster": {
          "status": "ACTIVE",
          "clusterName": "handson-cluster",
          "registeredContainerInstancesCount": 0,
          "pendingTasksCount": 0,
          "runningTasksCount": 0,
          "activeServicesCount": 0,
          "clusterArn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:cluster/handson-cluster"
        }
      }
```


2.2. クラスタの確認
-------------------

```bash:コマンド:
aws ecs describe-clusters \
        --clusters ${ECS_CLUSTER_NAME}
```

```json:結果(例):
      {
        "clusters": [
          {
              "status": "ACTIVE",
              "clusterName": "handson-cluster",
              "registeredContainerInstancesCount": 0,
              "pendingTasksCount": 0,
              "runningTasksCount": 0,
              "activeServicesCount": 0,
              "clusterArn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:cluster/handson-cluster"
          }
        ],
        "failures": []
      }
```


3. インスタンス起動
===================


3.1. インスタンス起動
---------------------

```bash:変数の確認:
cat << ETX

        EC2_IMAGE_ID:      ${EC2_IMAGE_ID}
        EC2_INSTANCE_TYPE: ${EC2_INSTANCE_TYPE}
        ARRAY_VPC_SG_ID:   ${ARRAY_VPC_SG_ID}
        EC2_KEY_NAME:      ${EC2_KEY_NAME}
        FILE_EC2_USERDATA: ${FILE_EC2_USERDATA}
        IAM_INSTANCE_PROFILE_NAME: ${IAM_INSTANCE_PROFILE_NAME}

ETX
```

```bash:コマンド:
aws ec2 run-instances \
        --image-id ${EC2_IMAGE_ID} \
        --instance-type ${EC2_INSTANCE_TYPE} \
        --security-group-ids ${ARRAY_VPC_SG_ID} \
        --key-name ${EC2_KEY_NAME} \
        --associate-public-ip-address \
        --user-data file://${FILE_EC2_USERDATA} \
        --iam-instance-profile Name=${IAM_INSTANCE_PROFILE_NAME}
```

```json:結果(例):
      {
        "OwnerId": "XXXXXXXXXXXX",
        "ReservationId": "r-xxxxxxxx",
        "Groups": [],
        "Instances": [
          {
              "Monitoring": {
                  "State": "disabled"
              },
              "PublicDnsName": "",
              "RootDeviceType": "ebs",
              "State": {
                  "Code": 0,
                  "Name": "pending"
              },
              "EbsOptimized": false,
              "LaunchTime": "2015-07-05T01:23:45.678",
              "PrivateIpAddress": "172.xxx.xxx.xxx",
              "ProductCodes": [],
              "VpcId": "vpc-xxxxxxxx",
              "StateTransitionReason": "",
              "InstanceId": "i-xxxxxxxx",
              "ImageId": "ami-xxxxxxxx",
              "PrivateDnsName": "ip-172-xxx-xxx-xxx.ap-northeast-1.compute.internal",
              "KeyName": "prj08-ap-northeast-1-ec2",
              "SecurityGroups": [
                  {
                      "GroupName": "ec2-http-https-ssh-global-inbound",
                      "GroupId": "sg-xxxxxxxx"
                  }
              ],
              "ClientToken": "",
              "SubnetId": "subnet-xxxxxxxx",
              "InstanceType": "t2.micro",
              "NetworkInterfaces": [
                  {
                      "Status": "in-use",
                      "MacAddress": "xx:xx:xx:xx:xx:xx",
                      "SourceDestCheck": true,
                      "VpcId": "vpc-xxxxxxxx",
                      "Description": "",
                      "NetworkInterfaceId": "eni-xxxxxxxx",
                      "PrivateIpAddresses": [
                          {
                              "PrivateDnsName": "ip-172-xxx-xxx-xxx.ap-northeast-1.compute.internal",
                              "Primary": true,
                              "PrivateIpAddress": "172.xxx.xxx.xxx"
                          }
                      ],
                      "PrivateDnsName": "ip-172-xxx-xxx-xxx.ap-northeast-1.compute.internal",
                      "Attachment": {
                          "Status": "attaching",
                          "DeviceIndex": 0,
                          "DeleteOnTermination": true,
                          "AttachmentId": "eni-attach-xxxxxxxx",
                          "AttachTime": "2015-07-05T01:23:45.678Z"
                      },
                      "Groups": [
                          {
                              "GroupName": "ec2-http-https-ssh-global-inbound",
                              "GroupId": "sg-xxxxxxxx"
                          }
                      ],
                      "SubnetId": "subnet-xxxxxxxx",
                      "OwnerId": "XXXXXXXXXXXX",
                      "PrivateIpAddress": "172.xxx.xxx.xxx"
                  }
              ],
              "SourceDestCheck": true,
              "Placement": {
                  "Tenancy": "handson-cluster",
                  "GroupName": "",
                  "AvailabilityZone": "ap-northeast-1a"
              },
              "Hypervisor": "xen",
              "BlockDeviceMappings": [],
              "Architecture": "x86_64",
              "StateReason": {
                  "Message": "pending",
                  "Code": "pending"
              },
              "IamInstanceProfile": {
                  "Id": "AIPAXXXXXXXXXXXXXXXXX",
                  "Arn": "arn:aws:iam::XXXXXXXXXXXX:instance-profile/ecsInstanceRole"
              },
              "RootDeviceName": "/dev/xvda",
              "VirtualizationType": "hvm",
              "AmiLaunchIndex": 0
          }
        ]
      }
```


3.2. インスタンスIDの取得
-------------------------

起動中のインスタンスからインスタンスIDを取得します。

### 起動中の場合

```bash:コマンド(起動中の場合):
EC2_INSTANCE_ID=` \
        aws ec2 describe-instances \
          --filters Name=instance-state-name,Values=pending \
          --query 'Reservations[].Instances[].InstanceId' \
          --output text \
` \
        && echo ${EC2_INSTANCE_ID}
```

```text:結果(例):
      i-xxxxxxxx
```

### 起動完了後の場合

```bash:コマンド(起動完了後の場合):
ARRAY_EC2_INSTANCE_ID=` \
        aws ec2 describe-instances \
          --filters Name=instance-state-name,Values=running \
          --query 'Reservations[].Instances[].InstanceId' \
          --output text \
` \
        && echo ${ARRAY_EC2_INSTANCE_ID}
```

```bash:変数の設定:
EC2_INSTANCE_ID=$(echo ${ARRAY_EC2_INSTANCE_ID} | sed 's/ .*$//') \
        && echo ${EC2_INSTANCE_ID}
```

```text:結果(例):
      i-xxxxxxxx
```

3.3 インスタンスのステータス確認
---------------------------------

```bash:コマンド:
EC2_INSTANCE_STATE=` \
        aws ec2 describe-instances \
        --instance-ids ${EC2_INSTANCE_ID} \
        --query 'Reservations[].Instances[].State.Name' \
        --output text \
` \
        && echo ${EC2_INSTANCE_STATE}
```

```text:結果:
      running
```


4. インスタンスの確認
=====================


4.1. パブリックIPアドレスの取得
-------------------------------

```bash:コマンド:
EC2_PUBLIC_IP=` \
        aws ec2 describe-instances \
          --instance-id ${EC2_INSTANCE_ID} \
          --query "Reservations[].Instances[].PublicIpAddress" \
          --output text \
` \
        && echo ${EC2_PUBLIC_IP}
```

```text:結果(例):
      54.xxx.xxx.xxx
```


4.2. SSHログイン
----------------

```bash:変数の確認:
cat << ETX

        FILE_SSH_KEY:  ${FILE_SSH_KEY}
        EC2_PUBLIC_IP: ${EC2_PUBLIC_IP}

ETX
```

```bash:コマンド:
ssh -i ${FILE_SSH_KEY} ec2-user@${EC2_PUBLIC_IP}
```

```text:結果(例):
      The authenticity of host '54.xxx.xxx.xxx (54.xxx.xxx.xxx)' can't be established.
      RSA key fingerprint is xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx.
      Are you sure you want to continue connecting (yes/no)?
```

```text:入力
yes
```

```text:結果(例):
      Warning: Permanently added '54.xxx.xxx.xxx' (RSA) to the list of known hosts.

             __|  __|_  )
             _|  (     /   Amazon Linux AMI

      https://aws.amazon.com/amazon-linux-ami/2015.03-release-notes/
```


4.3. ECSエージェントの確認
--------------------------

```bash:コマンド(EC2インスタンス):
ps ax |grep [a]mazon-ecs-init
```

```text:結果(例):
      2379 ?        Ssl    0:00 /usr/libexec/amazon-ecs-init start
```


4.4. ECSクラスタ設定の確認
--------------------------

```bash:コマンド(EC2インスタンス):
cat /etc/ecs/ecs.config
```

```text:結果(例):
      ECS_CLUSTER='handson-cluster'
```


4.5. ECSメタ設定の確認
----------------------

```bash:コマンド(EC2インスタンス):
echo -e "\n" \
        && curl http://localhost:51678/v1/metadata \
        && echo -e "\n"
```

```text:結果(例):
      {"Cluster":"\ |ECS_CLUSTER_NAME|\ ","ContainerInstanceArn":"arn:aws:ecs:\ |AWS_DEFAULT_REGION|\ :XXXXXXXXXXXX:container-instance/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","Version":"Amazon ECS Agent - v1.2.1 (5da1555)"}
```

4.6. 起動ログの確認
---------------

```bash:コマンド(EC2インスタンス):
cat /var/log/ecs/ecs-init.log.* 
```

```text:結果:
2015-07-05T08:54:04Z [INFO] pre-start
2015-07-05T08:54:05Z [INFO] start
2015-07-05T08:54:05Z [INFO] No existing agent container to remove.
2015-07-05T08:54:05Z [INFO] Starting Amazon EC2 Container Service Agent
```


4.7. エージェントログの確認
---------------

```bash:コマンド(EC2インスタンス):
cat /var/log/ecs/ecs-agent.log.* 
```

```text:結果:
2015-07-05T08:54:06Z [INFO] Starting Agent: Amazon ECS Agent - v1.2.1 (5da1555)
2015-07-05T08:54:06Z [INFO] Loading configuration
2015-07-05T08:54:06Z [INFO] Checkpointing is enabled. Attempting to load state
2015-07-05T08:54:06Z [INFO] Loading state! module="statemanager"
2015-07-05T08:54:06Z [INFO] Registering Instance with ECS
2015-07-05T08:54:06Z [INFO] Registered! module="api client"
2015-07-05T08:54:06Z [INFO] Registration completed successfully. I am running as 'arn:was:ecs:ap-northeast-1:XXXXXXXXXXXX:container-instance/xxxxxxxx-xxxx-xxxx-xxxx-aa37ad9f3927' in cluster 'handson-cluster'
2015-07-05T08:54:06Z [INFO] Saving state! module="statemanager"
2015-07-05T08:54:06Z [INFO] Beginning Polling for updates
2015-07-05T08:54:06Z [INFO] Creating poll dialer module="ws client" host="ecs-a-1.ap-northeast-1.amazonaws.com"
2015-07-05T08:54:06Z [ERROR] Error getting message from ws backend module="ws client" err="websocket: close 1011 Server Error"
2015-07-05T08:54:06Z [INFO] Error from acs; backing off module="acs handler" err="websocket: close 1011 Server Error"
2015-07-05T08:54:07Z [INFO] Creating poll dialer module="ws client" host="ecs-a-1.ap-northeast-1.amazonaws.com"
2015-07-05T08:54:16Z [INFO] Saving state! module="statemanager"
2015-07-05T08:54:23Z [INFO] Handling http request module="Handlers" method="GET" from="172.xxx.xxx.xxx:34872" uri="/v1/metadata"
```


4.8. ログアウト
---------------

ログアウトします。

```bash:コマンド(EC2インスタンス):
exit
```

```text:結果(例):
      logout
      Connection to 54.xxx.xxx.xxx closed.
```


5. クラスタの確認
=================


5.1. クラスタの確認
-------------------

```bash:コマンド:
aws ecs describe-clusters \
  --clusters ${ECS_CLUSTER_NAME}
```

```json:結果(例):
       {
         "clusters": [
           {
             "status": "ACTIVE",
             "clusterName": "handson-cluster",
             "registeredContainerInstancesCount": 1,
             "pendingTasksCount": 0,
             "runningTasksCount": 0,
             "activeServicesCount": 0,
             "clusterArn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:cluster/handson-cluster"
           }
         ],
         "failures": []
      }
```


5.2. コンテナインスタンスの確認
-------------------------------

```bash:コマンド:
ECS_INSTANCES=$( \
        aws ecs list-container-instances \
          --cluster ${ECS_CLUSTER_NAME} \
          --query 'containerInstanceArns[]' \
          --output text | \
        sed 's|^arn:aws:ecs:.*:container-instance/||' \
) \
        && echo ${ECS_INSTANCES}
```

```text:結果(例):
      xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
```

```bash:コマンド:
aws ecs describe-container-instances \
  --cluster ${ECS_CLUSTER_NAME} \
  --container-instances "${ECS_INSTANCES}" 
```

```json:結果(例):
      {
        "failures": [],
        "containerInstances": [
          {
              "status": "ACTIVE",
              "registeredResources": [
                  {
                      "integerValue": 1024,
                      "longValue": 0,
                      "type": "INTEGER",
                      "name": "CPU",
                      "doubleValue": 0.0
                  },
                  {
                      "integerValue": 996,
                      "longValue": 0,
                      "type": "INTEGER",
                      "name": "MEMORY",
                      "doubleValue": 0.0
                  },
                  {
                      "name": "PORTS",
                      "longValue": 0,
                      "doubleValue": 0.0,
                      "stringSetValue": [
                          "22",
                          "2376",
                          "2375",
                          "51678"
                      ],
                      "type": "STRINGSET",
                      "integerValue": 0
                  },
                  {
                      "name": "PORTS_UDP",
                      "longValue": 0,
                      "doubleValue": 0.0,
                      "stringSetValue": [],
                      "type": "STRINGSET",
                      "integerValue": 0
                  }
              ],
              "ec2InstanceId": "i-xxxxxxxx",
              "agentConnected": true,
              "containerInstanceArn": "arn:aws:ecs:ap-northeast-1:XXXXXXXXXXXX:container-instance/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
              "pendingTasksCount": 0,
              "remainingResources": [
                  {
                      "integerValue": 1024,
                      "longValue": 0,
                      "type": "INTEGER",
                      "name": "CPU",
                      "doubleValue": 0.0
                  },
                  {
                      "integerValue": 996,
                      "longValue": 0,
                      "type": "INTEGER",
                      "name": "MEMORY",
                      "doubleValue": 0.0
                  },
                  {
                      "name": "PORTS",
                      "longValue": 0,
                      "doubleValue": 0.0,
                      "stringSetValue": [
                          "22",
                          "2376",
                          "2375",
                          "51678"
                      ],
                      "type": "STRINGSET",
                      "integerValue": 0
                  },
                  {
                      "name": "PORTS_UDP",
                      "longValue": 0,
                      "doubleValue": 0.0,
                      "stringSetValue": [],
                      "type": "STRINGSET",
                      "integerValue": 0
                  }
              ],
              "runningTasksCount": 0,
              "versionInfo": {
                  "agentVersion": "1.2.1",
                  "agentHash": "5da1555",
                  "dockerVersion": "DockerVersion: 1.6.2"
              }
          }
        ]
      }
```


完了
====