[JAWS-UG CLI] JAWS-UG初心者支部#9 AWS CLI ハンズオン

# この記事について

[JAWS-UG 初心者支部#9 × CLI専門支部 #85](https://jawsug-bgnr.connpass.com/event/56839/)で実施するハンズオン用の手順書です。

静的Webサイトホスティングの設定を

* AWS CLI
* AWS CloudFormation(by AWS CLI)

の2つの方法で設定します。

同じ結果を異なる手段で得ることで、各ツールの特徴や使いどころを考えるきっかけになればと考えています。


# 0. 前提条件

* クライアントにaws-cliがインストールされていること
    * この手順は、Amazon Linux上で動作確認を実施しています
* アクセスキーが発行済みであること

これらの準備が完了していない場合、以下のドキュメントを参考にして環境を準備してください。

## aws-cliのインストール

以下のドキュメントに従ってaws-cliをインストールしてください。

[Installing the AWS Command Line Interface](http://docs.aws.amazon.com/cli/latest/userguide/installing.html)

## アクセスキーの発行

「Email and password (account root user)」でマネージメントコンソールにログイン後、以下のドキュメントに従ってaws-cliをインストールしてください。

本日のハンズオンでは、S3へのフルコントロール権限が必要です。（オプションを実施する場合は、CloudFormationのフルコントロール権限を付与してください。）

* ユーザーの作成
    * [IAM ユーザーの作成 (コンソール)](http://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_users_create.html#id_users_create_console)
* ユーザーに権限を付与
    * [管理ポリシーを直接アタッチすることでユーザーにアクセス権限を追加するには](http://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_users_change-permissions.html#by-direct-attach-policy)
* アクセスキーの発行
    * [アクセスキーの作成、修正、および表示（AWS マネジメントコンソール）](http://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/id_credentials_access-keys.html#Using_CreateAccessKey)


# 1. aws-cliの設定

aws-cliを実行する際に使用する認証情報を設定します。

本日のハンズオンでは、クライアントの設定ファイルに認証情報を保存します。

認証情報の使用／設定方法は複数存在します。また、複数の手段で設定済みの場合、仕様で定められた優先順位に基づいて認証情報が選択されます。

[Configuration Settings and Precedence](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#config-settings-and-precedence)

> 1. **Command Line Options** – region, output format and profile can be specified as command options to override default settings.
>
> 2. **Environment Variables** – AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, etc.
>
> 3. **The AWS credentials file** – located at ~/.aws/credentials on Linux, macOS, or Unix, or at C:\Users\USERNAME \.aws\credentials on Windows. This file can contain multiple named profiles in addition to a default profile.
>
> 4. **The CLI configuration file** – typically located at ~/.aws/config on Linux, macOS, or Unix, or at C:\Users\USERNAME \.aws\config on Windows. This file can contain a default profile, named profiles, and CLI specific configuration parameters for each.
>
> 5. **Instance profile credentials** – these credentials can be used on EC2 instances with an assigned instance role, and are delivered through the Amazon EC2 metadata service.

## 1.1. プロファイルの設定

### 設定の確認（設定前）

```bash:設定の確認
aws configure list
```

```text:結果
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key                <not set>             None    None
secret_key                <not set>             None    None
    region                <not set>             None    None
```

### プロファイルの作成

認証情報（アクセスキー）およびリージョンを設定ファイルに保存します。
これにより、コマンド実行の度に認証情報を指定したり、作業の度に環境変数を指定する必要がありません。

今日のハンズオンでは、任意のプロファイル名を指定します。プロファイルを利用することで、利用する認証情報を使い分けることが可能です。

```bash:プロファイル名の定義
AWS_PROFILE="jawsug-beginner"
```

```bash:認証情報の設定
aws configure --profile ${AWS_PROFILE}
```

```text
AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]: 
```


### デフォルトプロファイルの指定

デフォルトで利用するプロファイルを指定します。指定は、環境変数を使用します。

```bash:プロファイル名の定義
export AWS_DEFAULT_PROFILE=${AWS_PROFILE}
```

プロファイル以外にも環境変数を利用して認証情報やデフォルトで利用するリージョンの指定が可能です。詳細は以下のドキュメントを確認してください。

[Environment Variables](http://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html)

> **AWS_ACCESS_KEY_ID** – AWS access key.
>
> **AWS_SECRET_ACCESS_KEY** – AWS secret key. Access and secret key variables override credentials stored in credential and config files.
>
> **AWS_SESSION_TOKEN** – session token. A session token is only required if you are using temporary security credentials.
>
> **AWS_DEFAULT_REGION** – AWS region. This variable overrides the default region of the in-use profile, if set.
>
> **AWS_DEFAULT_PROFILE** – name of the CLI profile to use. This can be the name of a profile stored in a credential or config file, or default to use the default profile.
>
> **AWS_CONFIG_FILE** – path to a CLI config file.


### 設定の確認（設定後）

```bash:設定の確認
aws configure list
```

```text:結果
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile          jawsug-beginner           manual    --profile
access_key     ****************CNCA shared-credentials-file
secret_key     ****************YTgO shared-credentials-file
    region           ap-northeast-1      config-file    ~/.aws/config
```


### 設定ファイルの確認

```aws configure```コマンドで設定した認証情報は、以下のファイルに保存されています。確認してみましょう。

```bash:.aws/credentials
cat ~/.aws/credentials
```

```text:結果
[jawsug-beginner]
aws_access_key_id = XXXXXXXXXXXXXXXXXXXX
aws_secret_access_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

```bash:.aws/config
cat ~/.aws/config
```

```text:結果
[profile jawsug-beginner]
region = ap-northeast-1
```


# 2. Amazon S3で静的ウェブサイトホスティング

Amazon S3で静的ウェブサイトをホスティングします。

[例: 独自ドメインを使用して静的ウェブサイトをセットアップする](http://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html)


## 2.1. バケットの作成

### バケット名の指定

S3バケット名は、「グローバル」でユニークである必要があります。

また、バケット名は命名規則に則っている必要があります。

[Rules for Bucket Naming](http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html#bucketnamingrules)

```bash:バケット名の生成
PJ_NAME="<Unique project name>"
```

```bash:バケット名の生成
BUCKET_NAME="${PJ_NAME}-handson-$(date +%Y%m%d%H%M)" && echo ${BUCKET_NAME}
```

```text:結果
nakayama-handson-201705270743
```

### バケットの作成

コンテンツをホストするバケットを作成します。

```bash:バケットの作成
aws s3 mb s3://${BUCKET_NAME}
```

```text:結果
make_bucket: nakayama-handson-201705270743
```

### バケットの存在を確認

```bash:バケットの確認
aws s3 ls | grep ${BUCKET_NAME}
```

```text:結果
2017-05-27 07:44:56 nakayama-handson-201705270743
```


## 2.2. バケットポリシーの設定

バケット上のコンテンツに誰でもアクセスできるようにアクセス権を設定します。

### 設定の確認（設定前）

```bash:バケットポリシーの
aws s3api get-bucket-policy \
    --bucket ${BUCKET_NAME}
```

```text:結果
An error occurred (NoSuchBucketPolicy) when calling the GetBucketPolicy operation: The bucket policy does not exist
```

### バケットポリシーの生成

バケットポリシーをJSONで定義します。

**AWS CLIではインデントがくずれているとエラーになる場合があるのでご注意ください。**

```bash:バケットポリシーのファイル名を指定
POLICY_FILE_NAME="bucket-policy.json"
```

```bash:バケットポリシーの生成
cat << EOF > ${POLICY_FILE_NAME}
{
  "Version":"2012-10-17",
  "Statement":[{
    "Sid":"AddPerm",
    "Effect":"Allow",
    "Principal": "*",
    "Action":["s3:GetObject"],
    "Resource":["arn:aws:s3:::${BUCKET_NAME}/*"]
  }]
}
EOF

cat ${POLICY_FILE_NAME}
```

```json:結果
{
  "Version":"2012-10-17",
  "Statement":[{
    "Sid":"AddPerm",
    "Effect":"Allow",
    "Principal": "*",
    "Action":["s3:GetObject"],
    "Resource":["arn:aws:s3:::nakayama-handson-201705270743/*"]
  }]
}
```

### バケットポリシーの設定

作成したポリシーをバケットに適用します。

```bash:バケットポリシーの設定
aws s3api put-bucket-policy \
    --bucket ${BUCKET_NAME} \
    --policy file://${POLICY_FILE_NAME}
```

### 設定の確認（設定後）

```bash:バケットポリシーの
aws s3api get-bucket-policy \
    --bucket ${BUCKET_NAME}
```

```json:結果
{
    "Policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"AddPerm\",\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"s3:GetObject\",\"Resource\":\"arn:aws:s3:::nakayama-handson-201705270743/*\"}]}"
}
```

## 2.3. ウェブサイトホスティング設定

バケットのエンドポイント経由でコンテンツにアクセスできるように設定します。

### 設定の確認（設定前）

バケットにウェブサイトホスティングの設定がされていないことを確認します。

```bash:ウェブサイトホスティング設定の確認
aws s3api get-bucket-website \
    --bucket ${BUCKET_NAME}
```

```text:結果
An error occurred (NoSuchWebsiteConfiguration) when calling the GetBucketWebsite operation: The specified bucket does not have a website configuration
```

### ウェブサイトホスティング設定

バケットの/index.htmlがウェブサイトホスティングのインデックスページにする設定をします。

```bash:ウェブサイトホスティング設定
aws s3 website s3://${BUCKET_NAME}/ \
    --index-document index.html
```

### 設定の確認（設定後）

バケットのウェブサイトホスティング設定を確認します。

```bash:ウェブサイトホスティング設定の確認
aws s3api get-bucket-website \
    --bucket ${BUCKET_NAME}
```

```json:結果
{
    "IndexDocument": {
        "Suffix": "index.html"
    }
}
```

## 2.4. コンテンツのアップロード

バケットにコンテンツをアップロードします。

### コンテンツの生成

```bash:ファイル名の指定
HTML_FILE_NAME="index.html"
```

```bash:コンテンツの生成
cat << EOF > ${HTML_FILE_NAME}
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>My Website Home Page</title>
</head>
<body>
  <h1>Welcome to my website</h1>
  <p>Now hosted on Amazon S3!</p>
</body>
</html>
EOF

cat ${HTML_FILE_NAME}
```

```html:結果
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>My Website Home Page</title>
</head>
<body>
  <h1>Welcome to my website</h1>
  <p>Now hosted on Amazon S3!</p>
</body>
</html>
```

### バケットの内容を確認（アップロード前）

```bash:コンテンツの一覧
aws s3 ls s3://${BUCKET_NAME}/
```

### コンテンツのアップロード

```bash:コンテンツのアップロード
aws s3 cp ${HTML_FILE_NAME} s3://${BUCKET_NAME}/
```

```text:結果
upload: ./index.html to s3://nakayama-handson-201705270743/index.html
```

### バケットの内容を確認（アップロード後）

```bash:コンテンツの一覧
aws s3 ls s3://${BUCKET_NAME}/
```

```text:結果
2017-05-27 08:22:07        190 index.html
```

## 2.5. 動作確認

エンドポイント名を生成、実際にアクセスできることを確認します。

```bash:エンドポイント名の生成
BUCKET_ENDPOINT="http://${BUCKET_NAME}.s3-website-`aws s3api get-bucket-location --bucket ${BUCKET_NAME} --output text`.amazonaws.com" \
    && echo ${BUCKET_ENDPOINT}
```

```text:結果
http://nakayama-handson-201705270743.s3-website-ap-northeast-1.amazonaws.com
```

![My Website Home Page.png](https://qiita-image-store.s3.amazonaws.com/0/36230/9b485d87-6b37-485f-2681-1598deefe6d5.png)


# 3. 【オプション】CloudFormationで静的ウェブサイトホスティング

前の章と同じことをCloudFormatonを使って実施します。

[ウェブサイトホスティングの DeletionPolicy を持つ Amazon S3 バケットの作成](http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/quickref-s3.html#scenario-s3-bucket-website)


## 3.1. テンプレートの生成

### バケット名の指定

```bash:バケット名の指定
PJ_NAME="<Unique project name>"
BUCKET_NAME="${PJ_NAME}-handson-$(date +%Y%m%d%H%M)"&& echo ${BUCKET_NAME}
```

### テンプレートの生成

前の章で作成した環境を再現するCloudFormationテンプレートを生成します。

今回、YAMLでテンプレートを生成しています。インデントが意味を持つため、作成時には注意してください。


```bash:テンプレートファイル名の指定
TEMPLATE_FILE_NAME="static-website-hosting.yml"
```

```bash:テンプレートの生成
cat << EOF > ${TEMPLATE_FILE_NAME}
AWSTemplateFormatVersion: '2010-09-09'
Resources:
  S3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName : ${BUCKET_NAME}
      WebsiteConfiguration:
        IndexDocument: index.html
  S3BucketPolicy: 
    Type: "AWS::S3::BucketPolicy"
    Properties: 
      Bucket: 
        Ref: "S3Bucket"
      PolicyDocument: 
        Statement: 
          - 
            Action: 
              - "s3:GetObject"
            Effect: "Allow"
            Resource: 
              Fn::Join: 
                - ""
                - 
                  - "arn:aws:s3:::"
                  - 
                    Ref: "S3Bucket"
                  - "/*"
            Principal: "*"
Outputs:
  WebsiteURL:
    Value: !GetAtt S3Bucket.WebsiteURL
    Description: URL for the website hosted on S3
  S3BucketSecureURL:
    Value: !Sub
        - https://\${Domain}
        - Domain: !GetAtt S3Bucket.DomainName
    Description: Name of the S3 bucket to hold website content
EOF

cat ${TEMPLATE_FILE_NAME}
```

### 生成したテンプレートの検証

生成したテンプレートの構文等に不備がないか検証します。

```bash:テンプレートの検証
aws cloudformation validate-template \
    --template-body file://${TEMPLATE_FILE_NAME}
```


## 3.2. テンプレートを使ったプロビジョニング

### スタック名の指定

```bash:スタック名の指定
STACK_NAME="S3-WEBSITE-HOSTING-${BUCKET_NAME}" && echo ${STACK_NAME}
```

### 同名スタックの不存在を確認

```bash:同名スタックの不存在を確認
aws cloudformation describe-stacks \
    --query Stacks[?StackName==\'${STACK_NAME}\']
```

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

### スタックの作成

```bash:スタックの作成
aws cloudformation create-stack \
    --stack-name ${STACK_NAME} \
    --template-body file://${TEMPLATE_FILE_NAME}
```

```json:結果
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/S3-WEBSITE-HOSTING-nakayama-handson-201705270855/6b9824a0-42bb-11e7-af73-50a68a175ad2"
}
```

### スタックの作成完了を待機

```bash:スタックの作成完了を待機
aws cloudformation wait stack-create-complete \
    --stack-name ${STACK_NAME}
```


### スタックの状態を確認

"Outputs"にS3のエンドポイントが出力されていること、"StackStatus"がスタックの作成完了を示していることなどを確認します。

```bash:スタックの状態を確認
aws cloudformation describe-stacks \
    --stack-name ${STACK_NAME}
```

```json:結果
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:XXXXXXXXXXXX:stack/S3-WEBSITE-HOSTING-nakayama-handson-201705270855/6b9824a0-42bb-11e7-af73-50a68a175ad2",
            "Tags": [],
            "Outputs": [
                {
                    "Description": "Name of the S3 bucket to hold website content",
                    "OutputKey": "S3BucketSecureURL",
                    "OutputValue": "https://nakayama-handson-201705270855.s3.amazonaws.com"
                },
                {
                    "Description": "URL for the website hosted on S3",
                    "OutputKey": "WebsiteURL",
                    "OutputValue": "http://nakayama-handson-201705270855.s3-website-ap-northeast-1.amazonaws.com"
                }
            ],
            "CreationTime": "2017-05-27T09:03:58.965Z",
            "StackName": "S3-WEBSITE-HOSTING-nakayama-handson-201705270855",
            "NotificationARNs": [],
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false
        }
    ]
}
```


## 3.3. コンテンツのアップロード

バケットにコンテンツをアップロードします。

### コンテンツの生成

アップロードするコンテンツを生成します。

```bash:作業用ディレクトリの作成および移動
DIR_NAME="temp-$(date +%Y%m%d%H%M)"
mkdir ${DIR_NAME}
cd ${DIR_NAME}
```

```bash:ファイル名の指定
HTML_FILE_NAME="index.html"
```

```bash:コンテンツの生成
cat << EOF > ${HTML_FILE_NAME}
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>My Website Home Page</title>
</head>
<body>
  <h1>Welcome to my website</h1>
  <p>Now hosted on Amazon S3!</p>
</body>
</html>
EOF

cat ${HTML_FILE_NAME}
```

```html:結果
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
    <title>My Website Home Page</title>
</head>
<body>
  <h1>Welcome to my website</h1>
  <p>Now hosted on Amazon S3!</p>
</body>
</html>
```

### バケットの内容を確認（アップロード前）

現在バケットに存在するコンテンツを確認します。

```bash:コンテンツの一覧
aws s3 ls s3://${BUCKET_NAME}/
```

### コンテンツのアップロード

生成したコンテンツをアップロードします。

```bash:コンテンツのアップロード
aws s3 cp ${HTML_FILE_NAME} s3://${BUCKET_NAME}/
```

```text:結果
upload: ./index.html to s3://nakayama-handson-201705270855/index.html
```

### バケットの内容を確認（アップロード後）

現在バケットに存在するコンテンツを確認します。

```bash:コンテンツの一覧
aws s3 ls s3://${BUCKET_NAME}/
```

```text:結果
2017-05-27 09:13:39        190 index.html
```

## 3.4. 動作確認

ブラウザでエンドポイントにアクセスしてコンテンツが正常に表示されることを確認します。

```bash:エンドポイントの確認
WEBSITE_URL=$(aws cloudformation describe-stacks \
    --stack-name ${STACK_NAME} \
    --query Stacks[].Outputs[?OutputKey==\`WebsiteURL\`].OutputValue[] \
    --output text) \
    && echo ${WEBSITE_URL}
```

```text:結果
http://nakayama-handson-201705270855.s3-website-ap-northeast-1.amazonaws.com
```

# 3.5. スタックの削除

```bash:コンテンツの削除
aws s3 rm s3://${BUCKET_NAME}/index.html
```

```text:結果
delete: s3://XXXXXXXXXXXXX-handson-201705292018-cf/index.html
```

```bash:スタックの削除:
aws cloudformation delete-stack \
    --stack-name ${STACK_NAME}
```


本日のハンズオンは以上です。お疲れさまでした。


# 参考情報

* [AWS Command Line Interface User Guide](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-welcome.html)
* [AWS CLI Command Reference](http://docs.aws.amazon.com/cli/latest/reference/)
* [AWS CLI を使用してファイルを Amazon S3 にバックアップする](https://aws.amazon.com/jp/getting-started/tutorials/backup-to-s3-cli/)
* [aws/aws-cli](https://github.com/aws/aws-cli)
* [[JAWS-UG CLI] 総合案内](http://qiita.com/tcsh/items/14c3278f69ab073afe0f)
* [AWS::S3::Bucket](http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket.html)
* [AWS::S3::BucketPolicy](http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-s3-policy.html)