AnsibleでAWS操作　Lambda編

# AnsibleでAWS操作シリーズ

1. [aws-cliインストール編](http://qiita.com/bakira/items/d94fc79a032b117bf87f)
1. [EC2インスタンス編](http://qiita.com/bakira/items/ae56e8e03eed4dc75a01)
1. [S3バケット編](http://qiita.com/bakira/items/a1dfe79cfd0e3acaa1a6)
1. [CloudFrontディストリビューション編](http://qiita.com/bakira/items/611e3a51677b751f8366)
1. [Simple Email Service編](http://qiita.com/bakira/items/8147fcc7cb684beedc63)
1. [Certificate Manager編](http://qiita.com/bakira/items/50bdd853ce71953e1600)
1. Lambda編

# 関連記事

[aws-cli　コマンド一覧(随時追記)](http://qiita.com/bakira/items/8a9481c99ed56a367415)

# やりたかったこと

- `Lambda`関数の登録
- `S3`バケットにオブジェクトがPutされたタイミングでCloudFrontにInvalidationをかけ、全てのキャッシュをリセット
- ~~GUIを使わずに黒い画面でコマンドを「**ッターーン!**」してかっこつけたい~~

# やったこと

## 前提

- CIサーバー(`ansible`実行サーバー)構築済み
- CLIサーバー(`aws-cli`実行サーバー)構築済み
- `Ansible`インストール済み
- `aws-cli`インストール済み
- 各サーバーへのSSH接続設定済み
- `CF`と`S3`の設定済み
- `Lambda`実行用の`iam`作成済み

※ `${~}`は各環境に合わせて値を設定してください。

## 作業フロー

### 1.　関数をLambdaにアップロード

```bash:command
ansible-playbook -i inventory/production upload-aws-lambda-function.yml
```

戻り値のarnをAWS.LAMBDA.ARNに指定する

### 2.　lambda関数への実行権限の不要

```bash:command
ansible-playbook -i inventory/production update-aws-lambda-function.yml
```

### 3.　S3バケットへのイベント設定

```bash:command
ansible-playbook -i inventory/production update-aws-s3-event.yml
```

## ディレクトリ構成
```text

├── ansible.cfg
├── templates
│   └── production
│       ├── lambda
│       │   └── lambda_function.j2
│       └── s3api
│           └── event.j2
├── inventory
│   └── production
│       └── inventory
├── roles
│   ├── update-aws-lambda-function
│   │   └── tasks
│   │       └── main.yml
│   ├── upload-aws-lambda-function
│   │   └── tasks
│   │       └── main.yml
│   └── update-aws-s3api-event
│       └── tasks
│           └── main.yml
├── update-aws-lambda-function.yml
├── upload-aws-lambda-function.yml
├── update-aws-s3api-event.yml
└── vars
    └── all.yml
```

## Ansible構成ファイル

### inventory

```ini:inventory/production/inventory
[ciservers]
${CIサーバーホスト}

[cliservers]
${CLIサーバーホスト}

[all:vars]
ENV=production

```

### vars

```yaml:vars/all.yml
AWS:
  CLOUD_FRONT:
    DISTRIBUTION:
      ID: ${ディストリビューションID}
  IAM:
    LAMDA:
      ROLE:
        ARN: ${ラムだ関数実行ロールARN}
  LAMBDA:
    ARN: ${lambda関数のarn}
    CLOUD_FRONT:
      INVALIDATION:
        NAME: ${Lambda関数名}
        STATEMENT: ${ステートメント名}
        SOURCE_ARN: ${ソースARN}
  S3:
    BUCKET:
      NAME: ${対象バケット名}
  S3API:
    LAMBDA_FUNCTION:
      ID: ${一意となるID}
TEMP:
  DIRECTORY: /temp
```

### templates

```py:production/lambda/lambda_function.j2
from __future__ import print_function
import boto3
import time
def lambda_handler(event, context):
    client = boto3.client('cloudfront')
    invalidation = client.create_invalidation(DistributionId='{{ AWS.CLOUD_FRONT.DISTRIBUTIO.IDN }}',
    InvalidationBatch={
        'Paths': {
            'Quantity': 1,
            'Items': ['/*']
        },
        'CallerReference': str(time.time())
    })
    for items in event["Records"]:
        path = "/" + items["s3"]["object"]["key"]
        print(path)
```

内容としては、 **対象のバケットに一つでもオブジェクトがPutされたら全てのキャッシュを削除** します。

※最初は、**Putされたオブジェクトだけ** に`Invalidation`をかけていましたが、  **静的ホスティング** として利用していて、かつ **デフォルトインデックスドキュメント** を指定 していた場合に、　「**/」にアクセスした際の **リダイレクトキャッシュ** がInvaidationされずに古いキャッシュが残ってしまっていました。

原因としては、CloudFrontのキャッシュの[仕様](http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/LinkFormat.html#InvalidatingDirectories)上、 **ブラウザのURL単位** でキャッシュをしているようです。

なので、PutされたオブジェクトのみInvalidationをしてもリダイレクトキャッシュは残ってしまっていたようです。

やり方はあると思いますが、そこは調べる時間が取れなかったので一旦 **リダイレクトキャッシュ含む全てのオブジェクト** に対して`Invalidation`をかけています。

```json:production/s3api/event.j2
{
    "LambdaFunctionConfigurations": [
        {
            "LambdaFunctionArn": "{{ AWS.LAMBDA.ARN }}",
            "Id": "{{ AWS.S3API.LAMBDA_FUNCTION.ID }}",
            "Events": [
                "s3:ObjectCreated:*"
            ]
        }
    ]
}
```

### playbook

```yaml:upload-aws-lambda-function.yml
- hosts: cliservers
  roles:
    - upload-aws-lambda-function
  vars_files:
    - vars/all.yml
```

```yaml:update-aws-lambda-function.yml
- hosts: cliservers
  roles:
    - update-aws-lambda-function
  vars_files:
    - vars/all.yml
```

```yaml:update-aws-s3api-event.yml
- hosts: cliservers
  roles:
    - update-aws-s3api-event
  vars_files:
    - vars/all.yml
```

### tasks

```yaml:role/upload-aws-lambda-function/tasks/main.yml
- name: Create File
  template: 
    src={{ ENV }}/lambda/lambda_function.j2
    dest={{ TEMP.DIRECTORY }}/lambda/lambda_function.py
  tags:
    - always
    - 
- name: "Create Zip"
  shell: zip -r {{ TEMP.DIRECTORY }}/lambda_function.zip *
  args:
    chdir: "{{ TEMP.DIRECTORY }}/lambda"
  tags:
    - always

- name: "Upload Zip"
  shell: |
    aws lambda create-function \
    --function-name {{ AWS.LAMBDA.CLOUD_FRONT.INVALIDATION.NAME }} \
    --zip-file fileb://{{ TEMP.DIRECTORY }}/lambda_function.zip \
    --role {{ AWS.IAM.LAMDA.ROLE.ARN }} \
    --handler lambda_function.lambda_handler \
    --runtime python2.7 \
    --timeout 10 \
    --memory-size 1024
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
```

```yaml:role/update-aws-lambda-function/tasks/main.yml
- name: "Add Permission"
  shell: |
    aws lambda add-permission \
    --function-name "{{ AWS.LAMBDA.CLOUD_FRONT.INVALIDATION.NAME }}" \
    --statement-id "{{ AWS.LAMBDA.CLOUD_FRONT.INVALIDATION.STATEMENT }}" \
    --action "lambda:InvokeFunction" \
    --principal "s3.amazonaws.com" \
    --source-arn "{{ AWS.LAMBDA.CLOUD_FRONT.INVALIDATION.SOURCE_ARN }}"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
```

```yaml:role/update-aws-s3api-event/tasks/main.yml
- name: Create File
  template: 
    src={{ ENV }}/s3api/event.j2
    dest={{ TEMP.DIRECTORY }}/event.json
  tags:
    - always
    - 
- name: "Update Zip"
  shell: |
    aws s3api put-bucket-notification-configuration \
    --bucket {{AWS.S3.BUCKET.NAME }} \
    --notification-configuration file://{{ TEMP.DIRECTORY }}/event.json
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
```

## 終わりに

これで、対象の`S3`バケットにオブジェクトを`Put`すると`CloudFront`に`Invalidation`がかかり、キャッシュがリセットされると思います。

また、Lambda実行iamに`CloudWatch`の権限をつけることで、`Lambda`関数の実行ログが **自動** で出力されるので、監視も非常にしやすいです♪

`S3`+`CloudFront`の組み合わせは静的サイトを作成する際にはパフォーマンス面で非常に強力です。

しかし、しっかりとキャッシュを意識しないと更新内容が即時に反映されずに逆効果になってしまう場合もあります。

都度手動でインバリデーションをかけるのは運用的にも大変ですし、ヒューマンエラーも起きやすくなるので、是非`Lambda`を利用して自動化しておきましょう♪


じゃあの。