[JAWS-UG CLI]Amazon AppStream 2.0 入門 (1) VPC環境の作成

# この記事について

[JAWS-UG CLI専門支部 #76 AppStream 2.0入門](https://jawsug-cli.doorkeeper.jp/events/52024)で実施するハンズオン用の手順書です。


# 前提条件

## 必要な権限

作業にあたっては、以下の権限を有したIAMユーザもしくはIAMロールを利用してください。

- AppStream(2.0)のフルコントロール権限
- EC2のフルコントロール権限
- CloudFormationの関するフルコントロール権限
- IAMの関するフルコントロール権限


# 0. 準備

## 0.1. リージョンを指定

```bash:コマンド
export AWS_DEFAULT_REGION="ap-northeast-1"
```

## 0.2. 資格情報を確認

```bash:コマンド
aws configure list
```

インスタンスプロファイルを設定したEC2インスタンスでアクセスキーを設定せずに実行した場合、以下のようになります。

```text:結果
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************QSAA         iam-role
secret_key     ****************c1xY         iam-role
    region                us-west-2              env    AWS_DEFAULT_REGION
```

## 0.3. バージョン確認

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

```text:結果
aws-cli/1.11.35 Python/2.7.12 Linux/4.4.35-33.55.amzn1.x86_64 botocore/1.4.92
```

## 0.4. バージョンアップ（必要に応じて）

```bash:コマンド
sudo pip install -U awscli
```


# 1. VPC環境の構築

AppStream 2.0の環境（Fleet、詳細は後述）はVPC上のPrivate Subnet上に構築します。
クライアント環境がインターネットアクセスを必要とする場合、併せてNAT Gateway（もしくはNAT Instance）も必要となります。
（Public SubnetにFleetを構築してもPublic IPやEIPはアタッチされません。）


[Network Settings for Fleet Instances](http://docs.aws.amazon.com/ja_jp/appstream2/latest/developerguide/managing-network-note.html)

今回は、CloudFormationでVPC環境を一括で構築します。

VPC関連の構成は、以下の資料をご確認ください。

[JAWS-UG CLI専門支部 #76 Amazon AppStream 2.0 入門](http://www.slideshare.net/NobuhiroNakayama/jawsug-cli-76-amazon-appstream-20)


## 1.1. CloudFormation テンプレートの生成

```bash:コマンド
CF_TEMPLATE_FILE_NAME="VPC4AppAstream20.yml"
```

```bash:コマンド
cat << EOF > ${CF_TEMPLATE_FILE_NAME}
AWSTemplateFormatVersion: "2010-09-09"
Description: JAWS-UG CLI AppStream 2.0 Hands-on
Resources:
    VPC4Appstream:
        Type: AWS::EC2::VPC
        Properties:
            CidrBlock: "10.0.0.0/16"
    IGW4Appstream:
        Type: AWS::EC2::InternetGateway
    AttachIGW:
        Type: AWS::EC2::VPCGatewayAttachment
        Properties:
            VpcId:
                Ref: VPC4Appstream
            InternetGatewayId:
                Ref: IGW4Appstream
    PublicSubnetA4Appstream:
        Type: AWS::EC2::Subnet
        Properties: 
            AvailabilityZone: 
                Fn::Select: 
                    - 0
                    - Fn::GetAZs: ""
            CidrBlock: "10.0.0.0/24"
            MapPublicIpOnLaunch: true
            VpcId: 
                Ref: VPC4Appstream
    PublicSubnetC4Appstream:
        Type: AWS::EC2::Subnet
        Properties: 
            AvailabilityZone: 
                Fn::Select: 
                    - 1
                    - Fn::GetAZs: ""
            CidrBlock: "10.0.1.0/24"
            MapPublicIpOnLaunch: true
            VpcId: 
                Ref: VPC4Appstream
    PrivateSubnetA4Appstream:
        Type: AWS::EC2::Subnet
        Properties: 
            AvailabilityZone: 
                Fn::Select: 
                    - 0
                    - Fn::GetAZs: ""
            CidrBlock: "10.0.128.0/24"
            VpcId: 
                Ref: VPC4Appstream
    PrivateSubnetC4Appstream:
        Type: AWS::EC2::Subnet
        Properties: 
            AvailabilityZone: 
                Fn::Select: 
                    - 1
                    - Fn::GetAZs: ""
            CidrBlock: "10.0.129.0/24"
            VpcId: 
                Ref: VPC4Appstream
    EIP4NAT:
        Type: AWS::EC2::EIP
        Properties:
            Domain: vpc
    NATGW4AppStream:
        Type: AWS::EC2::NatGateway
        Properties:
            AllocationId:
                Fn::GetAtt:
                    - EIP4NAT
                    - AllocationId
            SubnetId:
                Ref: PublicSubnetA4Appstream
    PublicRT4AppStream:
        Type: AWS::EC2::RouteTable
        Properties:
            VpcId:
                Ref: VPC4Appstream
    PrivateRT4AppStream:
        Type: AWS::EC2::RouteTable
        Properties:
            VpcId:
                Ref: VPC4Appstream
    PublicDefaultRoute:
        Type: AWS::EC2::Route
        Properties: 
            DestinationCidrBlock: "0.0.0.0/0"
            GatewayId: 
                Ref: IGW4Appstream
            RouteTableId: 
                Ref: PublicRT4AppStream
    PrivateDefaultRoute:
        Type: AWS::EC2::Route
        DependsOn: NATGW4AppStream
        Properties: 
            DestinationCidrBlock: "0.0.0.0/0"
            NatGatewayId: 
                Ref: NATGW4AppStream
            RouteTableId: 
                Ref: PrivateRT4AppStream
    PublicSubnetARouteTableAssociation:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
            SubnetId:
                Ref: PublicSubnetA4Appstream
            RouteTableId:
                Ref: PublicRT4AppStream
    PublicSubnetCRouteTableAssociation:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
            SubnetId:
                Ref: PublicSubnetC4Appstream
            RouteTableId:
                Ref: PublicRT4AppStream
    PrivateSubnetARouteTableAssociation:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
            SubnetId:
                Ref: PrivateSubnetA4Appstream
            RouteTableId:
                Ref: PrivateRT4AppStream
    PrivateSubnetCRouteTableAssociation:
        Type: AWS::EC2::SubnetRouteTableAssociation
        Properties:
            SubnetId:
                Ref: PrivateSubnetC4Appstream
            RouteTableId:
                Ref: PrivateRT4AppStream
Outputs: 
    PrivateSubnetID1: 
        Value: !Ref PrivateSubnetA4Appstream
    PrivateSubnetID2: 
        Value: !Ref PrivateSubnetC4Appstream
    IPv4Address4Nat: 
        Value: !Ref EIP4NAT
EOF

cat ${CF_TEMPLATE_FILE_NAME}
```


## 1.2. CloudFormation テンプレートの検証

**※ macOS Sierraで前述のヒアドキュメントを実行すると、検証に失敗する場合があるようです。変数展開は行っていないので、検証に失敗した場合はエディタに直接コピペしてみてください。**

```bash:コマンド
aws cloudformation validate-template \
    --template-body file://${CF_TEMPLATE_FILE_NAME}
```

```json:結果
{
    "Description": "JAWS-UG CLI AppStream 2.0 Hands-on",
    "Parameters": []
}
```


## 1.3. CloudFormation Stackの作成

### CloudFormation Stack名の指定

```bash:コマンド
CF_STACK_NAME="VPC4AppStream20"
```


### 同名のCloudFormation Stackがないことを確認

```bash:コマンド
aws cloudformation describe-stacks \
    --query Stacks[?StackName==\'${CF_STACK_NAME}\']
```

```json:結果
[]
```


### CloudFormation Stackの作成

```bash:コマンド
aws cloudformation create-stack \
    --stack-name ${CF_STACK_NAME} \
    --template-body file://${CF_TEMPLATE_FILE_NAME}
```

```json:結果
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:788063364413:stack/VPC4AppStream20/6331d230-cf33-11e6-8944-500c44f24c82"
}
```


### CloudFormation Stackの作成完了を待機

3分程度で作成が完了すると思います。

```bash:コマンド
aws cloudformation wait stack-create-complete \
    --stack-name ${CF_STACK_NAME}
```

```text:結果
（返値無し）
```


## 1.4. 作成したCloudFormation Stackの確認

```bash:コマンド
aws cloudformation describe-stacks \
    --stack-name ${CF_STACK_NAME}
```

```json:結果
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:788063364413:stack                                                                                                                                                            /VPC4AppStream20/6331d230-cf33-11e6-8944-500c44f24c82",
            "Description": "JAWS-UG CLI AppStream 2.0 Hands-on",
            "Tags": [],
            "Outputs": [
                {
                    "OutputKey": "PrivateSubnetID2",
                    "OutputValue": "subnet-61ef4139"
                },
                {
                    "OutputKey": "PrivateSubnetID1",
                    "OutputValue": "subnet-55731523"
                },
                {
                    "OutputKey": "IPv4Address4Nat",
                    "OutputValue": "52.199.195.74"
                }
            ],
            "CreationTime": "2016-12-31T08:30:29.604Z",
            "StackName": "VPC4AppStream20",
            "NotificationARNs": [],
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false
        }
    ]
}
```


## 1.5. パラメータの確認

以降の手順で必要になるパラメータを抽出します。

```bash:コマンド
OUTPUTKEY_SUBNETID1="PrivateSubnetID1"
OUTPUTKEY_SUBNETID2="PrivateSubnetID2"
OUTPUTKEY_IPV4ADDRESS4NAT="IPv4Address4Nat"
```

```bash:コマンド
SUBNETID1=$(aws cloudformation describe-stacks \
    --stack-name ${CF_STACK_NAME} \
    --query Stacks[].Outputs[?OutputKey==\`${OUTPUTKEY_SUBNETID1}\`].OutputValue[] \
    --output text) \
    && echo ${SUBNETID1}
```

```text:結果
subnet-********
```


```bash:コマンド
SUBNETID2=$(aws cloudformation describe-stacks \
    --stack-name ${CF_STACK_NAME} \
    --query Stacks[].Outputs[?OutputKey==\`${OUTPUTKEY_SUBNETID2}\`].OutputValue[] \
    --output text) \
    && echo ${SUBNETID2}
```

```text:結果
subnet-********
```


```bash:コマンド
IPV4ADDRESS4NAT=$(aws cloudformation describe-stacks \
    --stack-name ${CF_STACK_NAME} \
    --query Stacks[].Outputs[?OutputKey==\`${OUTPUTKEY_IPV4ADDRESS4NAT}\`].OutputValue[] \
    --output text) \
    && echo ${IPV4ADDRESS4NAT}
```

```text:結果
**.**.**.**
```


以上