AnsibleでAWS操作 Simple Email Service編

AnsibleでAWS操作シリーズ

  1. aws-cliインストール編
  2. EC2インスタンス編
  3. S3バケット編
  4. CloudFrontディストリビューション編
  5. Simple Email Service編

関連記事

aws-cli コマンド一覧(随時追記)

やりたかったこと

  • SSL証明書発行時のドメイン認証メールをSESにて受信
  • 受信メールをS3バケットに保存
  • GUIを使わずに黒い画面でコマンドを「ッターーン!」してかっこつけたい

やったこと

前提

  • CIサーバー(ansible実行サーバー)構築済み
  • CLIサーバー(aws-cli実行サーバー)構築済み
  • Ansibleインストール済み
  • aws-cliインストール済み
  • 各サーバーへのSSH接続設定済み
  • 独自ドメイン取得済み
  • Route53の設定済み

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

作業フロー

1. ドメイン認証用のトークンを発行

command
ansible-playbook -i inventory/production create-aws-ses-token.yml

※戻り値の値を控えます

2. Route53のレコードセットの更新

command
ansible-playbook -i inventory/production update-aws-route53-record-set.yml

3. Route53の承認ステータスの確認

command
ansible-playbook -i inventory/production view-aws-ses-verification-status.yml

VerificationStatusがSuccessになることを確認します。
※多少時間がかかる場合があります

4. メール保存用のS3バケットを作成

command
ansible-playbook -i inventory/production setup-aws-s3-bucket.yml

5. SESルール周りのセットアップの作成

command
ansible-playbook -i inventory/production setup-aws-ses-rule.yml

ディレクトリ構成


├── ansible.cfg
├── create-aws-ses-token.yml
├── templates
│   └── production
│       ├── route53
│       │   └── record_set.j2
│       ├── s3api
│       │   └── s3-policy.j2
│       └── ses
│           └── rule-set.j2
├── inventory
│   └── production
│       └── inventory
├── roles
│   ├── active-aws-ses-rule-set
│   │   └── tasks
│   │       └── main.yml
│   ├── create-aws-s3-bucket
│   │   └── tasks
│   │       └── main.yml
│   ├── create-aws-ses-rule
│   │   └── tasks
│   │       └── main.yml
│   ├── create-aws-ses-rule-set
│   │   └── tasks
│   │       └── main.yml
│   ├── create-aws-ses-token
│   │   └── tasks
│   │       └── main.yml
│   ├── setup-aws-s3-bucket
│   │   └── tasks
│   │       └── main.yml
│   ├── update-aws-route53-record-set
│   │   └── tasks
│   │       └── main.yml
│   └── view-aws-ses-verification-status
│       └── tasks
│           └── main.yml
├── setup-aws-ses-rule.yml
├── setup-aws-s3-bucket.yml
├── update-aws-route53-record-set.yml
├── view-aws-ses-verification-status.yml
└── vars
    └── all.yml

Ansible構成ファイル

inventory

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

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

[all:vars]
ENV=production

vars

vars/all.yml
SERVER_IP: ${IPアドレス}
TEMP:
  DIRECTORY: /temp
DOMAIN:
  MAIN:
    NAME: ${ドメイン名}
  SUB:
    NAME: ${サブドメイン名}
AWS:
  ROUTE53:
    HOSTED_ZONE_ID: ${ホストゾーンID}
  S3:
    BUCKET:
      NAME: ${バケット名}
  SES:
      TOKEN: ${認証トークン}
      REGION: us-west-2
    RULE:
      NAME: ${ルール名}
      SET:
        NAME: ${ルールセット名}

templates

json/production/ses/rule-set.j2
{
  "Name": "{{ AWS.SES.RULE.NAME }}",
  "Enabled": true,
  "TlsPolicy": "Optional",
  "Recipients": [
    "admin@{{ DOMAIN.MAIN.NAME }}",
    "admin@{{ DOMAIN.SUB.NAME }}"
  ],
  "Actions": [
    {
      "S3Action": {
        "BucketName": "{{ AWS.S3.BUCKET.NAME }}"
        }
    }
  ],
  "ScanEnabled": true
}
s3-policy.j2
{
  "Version":"2012-10-17",
  "Statement":[{
      "Sid":"AddPerm",
      "Effect":"Allow",
        "Principal": "*",
      "Action":["s3:PutObject"],
      "Resource":["arn:aws:s3:::{{ AWS.S3.SES.NAME }}/*"]
    }
  ]
}
record_set
{
  "Comment": "DomainRecords",
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "AliasTarget": {
          "HostedZoneId": "Z2FDTNDATAQYW2",
          "EvaluateTargetHealth": false,
          "DNSName": "{{ DOMAIN.MAIN.NAME }}"
        },
        "Type": "A",
        "Name": "{{ DOMAIN.MAIN.NAME }}"
      }
    },
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "{{ DOMAIN.SUB.NAME }}",
        "Type": "A",
        "TTL": 1200,
        "ResourceRecords": [
          {
            "Value": "{{ SERVER_IP }}"
          }
        ]
      }
    },
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "_amazonses.{{ DOMAIN.MAIN.NAME }}.",
        "Type": "TXT",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": ""{{ AWS.SES.TOKEN }}""
          }
        ]
      }
    },
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "{{ DOMAIN.MAIN.NAME }}.",
        "Type": "MX",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "10 inbound-smtp.{{ AWS.SES.REGION }}.amazonaws.com."
          }
        ]
      }
    }
  ]
}

playbook

create-aws-ses-token.yml
- hosts: cliservers
  roles:
    - create-aws-ses-token
  vars_files:
    - vars/all.yml
setup-aws-ses-rule.yml
- hosts: cliservers
  roles:
    - create-aws-ses-rule-set
    - create-aws-ses-rule
    - active-aws-ses-rule-set
  vars_files:
    - vars/all.yml
setup-aws-s3-bucket.yml
- hosts: cliservers
  roles:
    - create-aws-s3-bucket
    - setup-aws-s3-bucket
  vars_files:
    - vars/all.yml
update-aws-route53-record-set.yml
- hosts: cliservers
  roles:
    - update-aws-route53-record-set
  vars_files:
    - vars/all.yml
view-aws-ses-verification-status.yml
- hosts: cliservers
  roles:
    - view-aws-ses-verification-status
  vars_files:
    - vars/all.yml

tasks

role/active-aws-ses-rule-set/tasks/main.yml
- name: Active Receipt Rule Set
  shell: |
    aws ses set-active-receipt-rule-set 
    --region={{ AWS.SES.REGION }} 
    --rule-set-name {{ AWS.SES.RULE.SET.NAME }}
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/create-aws-s3-bucket/tasks/main.yml
- name: Create Bucket
  shell: "aws s3 mb s3://{{ AWS.S3.BUCKET.NAME }}"
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/create-aws-ses-rule-set/tasks/main.yml
- name: Create Receipt Rule Set
  shell: |
    aws ses create-receipt-rule-set 
    --region={{ AWS.SES.REGION }} 
    --rule-set-name {{ AWS.SES.RULE.SET.NAME }} 
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/create-aws-ses-rule/tasks/main.yml
- name: Create Replaced File
  template: 
    src={{ ENV }}/ses/rule-set.j2
    dest={{ TEMP.DIRECTORY }}/rule-set.json
  tags:
    - always

- name: Create Receipt Rule
  shell: |
    aws ses create-receipt-rule 
    --region={{ AWS.SES.REGION }} 
    --rule-set-name {{ AWS.SES.RULE.SET.NAME }} 
    --rule file://{{ TEMP.DIRECTORY }}/rule-set.json
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/create-aws-ses-token/tasks/main.yml
- name: "Create SES Token"
  shell: |
    aws ses verify-domain-identity  
    --domain "{{ DOMAIN.MAIN.NAME }}" 
    --region={{ AWS.SES.REGION }}
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/setup-aws-s3-bucket/tasks/main.yml
- name: Create Replaced File
  template: 
    src={{ ENV }}/s3api/s3-policy.j2
    dest={{ TEMP.DIRECTORY }}/s3-policy.json
  tags:
    - always

- name: Create Policy
  shell: |
    aws s3api put-bucket-policy 
    --bucket {{ AWS.S3.BUCKET.NAME }} 
    --policy file://{{ TEMP.DIRECTORY }}/s3-policy.json
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/update-aws-route53-record-set/tasks/main.yml
- name: Create Replaced File
  template: 
    src={{ ENV }}/route53/record_set.j2
    dest={{ TEMP.DIRECTORY }}/record_set.json
  tags:
    - always

- name: Update Record Set
  shell: |
    aws route53 change-resource-record-sets 
    --hosted-zone-id {{ AWS.ROUTE53.HOSTED_ZONE_ID }} 
    --change-batch file://{{ TEMP.DIRECTORY }}/record_set.json
  register: result
  changed_when: False

- debug: var=result.stdout_lines
  when: result | success
  tags:
    - always
role/view-aws-ses-verification-status/tasks/main.yml
- name: View Verification Status
  shell: |
    aws ses get-identity-verification-attributes 
    --identities ${DOMAIN.MAIN.NAME}
  register: result
  changed_when: False

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

終わりに

上記作業が正常に終了すれば、SSL証明書発行時のドメイン認証メールをSESにて受信及びS3バケットへの保存が可能になりました。

ただ、S3バケット上で保存している電子メールは そのままだと解読が出来ない ので、オブジェクトをローカル等にダウンロードした上で内容を表示する必要があります。(Lambdaを使えば転送とかもできるかも?)

ここまでの設定をすればACMにて無料のSSL証明書を発行する準備が出来たので、
CloudFront+S3+ACMによる無料SSLサイトを構築することが可能 です。

AWSサービス同士はとても連携しやすく、同サービス内だからこそのメリットもたくさんあるので他にも良い組み合わせがあったら記事にまとめていこうと思います♪

じゃあの。

続きを読む

AWS IoT Buttonで"ゆれ"とツイートする

AWS IoT Buttonというものがあります。

こんなの↓

(出典:https://aws.amazon.com/jp/iotbutton/

簡単に言えばAmazon Dash Buttonの処理をカスタマイズ出来る版ってな感じのシロモノです。
これがあれば、あんなことやそんなことが出来ると夢が拡がる一方ですが、
取り敢えずはAWS IoT初体験として「押したら”ゆれ”とツイートするボタン」を試しに作ってみました。
特に意味はありません。

ちなみに2017年8月時点ではまだ日本での販売はされておらず、
今回入手したものは本家の米Amazonから購入しています。うっかり5個買っちゃった。

概観図

tweet_button1.png

Twitterのアプリ登録

Twitterへ投稿する処理を実装するためには、あらかじめアプリ登録を済ませておく必要があります。
具体的な手順は下記のとおりです(一連の手順やWebページのUIは変更される可能性有)。

  1. ボタンを押したとき呟くことになるアカウントでTwitterへログインする
  2. https://apps.twitter.com へアクセス
  3. 右上の「Create New App」を押す
  4. Name、Description、Websiteを適当に入力する
    • Websiteは必須項目なので何かしらを入力(https://twitter.com など)、Callback URLは空でOK
  5. Twitter Developer Agreementを確認してチェックを付けた後、「Create your Twitter application」を押す
  6. 登録成功した感じのページに遷移したらタブの「Keys and Access Tokens」を選択
  7. 下の方にある「Create my access token」のボタンを押す

ここまでの操作により、下に赤枠で示した4つの情報が確認できればTwitter側は準備完了です。

※Access Tokenは途中にあるハイフンも含むのでコピペの際に間違って消さないよう注意

AWS IoT Buttonのセットアップ

AWS IoT Buttonを利用するためにはAWSアカウントが必要です。
まだアカウントを作成していない場合はこちらなどから頑張ってサインアップします。

AWSコンソールへサインイン出来たら、
ここに書いてある通りにセットアップを実行します。

一つ注意点として、セットアップ手順を進めるPCが無線LANにアクセスできない端末の場合、
無線LANアクセス可能なPCへ証明書と鍵を送ってButtonの設定を完了させる必要があります。
これにもたついているとButtonへの接続が切れて入力やり直しとかになったりします。
まぁ今時そんなPCはまず無いと思いますが、念のため。
なんでこのPC無線LAN子機内蔵してないんだ……

Lambda関数の処理を実装

AWS IoT Buttonのセットアップ手順でLambda関数の作成までは行ったので、
その関数の処理をTwitterへ投稿する処理に変更します。
上でセットアップしたButtonが押されると、ここで書いた処理が実行されるという感じになるわけですね。

下記の手順は既にNode.js & npmがインストールされているという前提になります。

まず、作業ディレクトリを作成して、

mkdir iotbutton
cd iotbutton

Twitterのライブラリをインストールし、

npm install twitter

Twitterへ”ゆれ”と投稿する処理を書いてindex.jsとして保存した後、
(clientのパラメータにはTwitterでアプリ登録した際に確認したキーを指定)

const Twitter = require('twitter');

const client = new Twitter({
    consumer_key: 'xxxxxxxx',
    consumer_secret: 'xxxxxxxx',
    access_token_key: 'xxxxxxxx',
    access_token_secret: 'xxxxxxxx'
});

exports.handler = (event, context, callback) => {
    client.post('statuses/update', {status: 'ゆれ'}, function(error, tweet, response) {
        if (error) {
            console.log(error);
            callback(error);
            return;
        }
        console.log(tweet);
        callback();
    });
};

最後にnode_modulesindex.jsをzipで固めてLambda関数のコードとしてアップロードするだけです。
とっても簡単!

一度アップロードした後は、AWSコンソール上からインラインでコードの編集が出来るようになります。

動作確認

これで準備は全て整いましたので、早速試してみます。
ただし、ゆれてもいないときに一人ゆれツイートを投稿して誤爆しちゃったみたいな感じになるのは嫌なので
今だけ”This is a test tweet by Lambda.”に文言を変えています。

まずはButtonを押下。
少し間を空けてからタイムラインを更新すると……

tweet_button2.png

成功です!
これでもういつ地震が来ても大丈夫。

制限事項とか残課題とか

  • Buttonを押してから呟きが投稿されるまでに5秒~10秒くらいのタイムラグがある。自分で投稿した方が速そう
  • セットアップしたWi-Fiアクセスポイントのある場所でしか使えない。
  • 間違えて押すと悲しいことに。 → 2回押されたらキャンセル or 削除させる?

雑感

タイムラグが致命的なため、地震ツイートRTA用途には使えなさそうなのが非常に残念でした。

それはともかくとして、セットアップの容易さとAWSとの連携による汎用性の高さを併せ持つ
このButton(1個約20$)は魅力的で可能性を感じるプロダクトだと改めて実感しました。
早く日本でも販売開始してほしい。

余談ですが、国産のIoTプロダクトではMESHというのもありますね。
機会があればこっちも触ってみるかもしれません。

続きを読む

[2017夏版] AWS Start-up ゼミ参考資料リンク集をマークダウンに起こしました

TL;DR

こんにちは。AWSリハビリ中のnntsuguです。

[AWS Start-up ゼミ] よくある課題を一気に解説!〜御社の技術レベルがアップする 2017 夏期講習〜

先日参加させていただいたAWS Stat-upゼミ、講師のSA塚田さんの資料がとても良かった。
AWS上でサービスを構築運用する上での勘所がユースケースベースで整理されていて、モヤモヤしていた部分がかなりスッキリししました。

資料内にある参考資料や動画へのLink、とても勉強になるのですが、

  • PDFやSlideShareだとスマホから参照しづらい
  • 未読管理をしやすくするため

マークアップに起こしました。

毎朝ジムで走りながら参考資料の動画を見て聞いています。とても捗ります。

参考資料は主にBlack Beltの資料&動画アーカイブ、AWS Summit/Dev Dayの資料で構成されています。

ユーザ動向を分析したい

CI/CDをちゃんとしたい

コンテナを使いたい

運用監視ちゃんとしたい

システム負荷下げたい

(モバイルアプリの)Growth Hackしたい

コスト下げたい

  • AWS Black Belt Online Seminar資料&動画

    • クラウドのためのアーキテクチャ設計-ベストプラクティス-(資料|動画)
    • Auto Scaling (資料 | 動画)
    • Amazon EC2 Spot Instances (資料 | 動画)
    • サーバーレスによるアーキテクチャパターンのご紹介 (資料 | 動画)
  • AWS Summit/Dev Day講演資料 (2016 | 2017)
    • AWS のコスト最適化入門 (2017)(資料 | 動画)
    • [インティメート・マージャー様] AWS Summit 2017 講演資料 Amazon ECS と SpotFleet を活用した低コストでスケーラブルなジョ
      ブワーカーシステム(資料 | 動画)
    • AWS Well-Architected フレームワークによるクラウド ベスト プラク
      ティス (2017) (資料 | 動画)

その他

IPOとBuy Out、デューデリジェンス

続きを読む

AWS CodePipelineからMicrosoft Teamsに承認依頼を飛ばす(超簡単版)

はじめに

AWS CodePipelineではPipelineの途中に「手動承認」のアクションを定義できます。

[ Build ]
    ↓
[ Beta環境にデプロイ ]
    ↓
(Beta環境で動作確認)
    ↓
[ 手動承認 ]
    ↓
[ Production環境にデプロイ ]

手動承認はManagementConsoleの以下のような画面でコメントとともに承認または拒否します。

2017-08-15_23h22_30.png

Codepipelineでは手動承認が必要になったタイミングでAmazon Simple Notification Service(SNS)を経由して通知することができます。

本記事では、この通知先をMicrosoft Teamsの任意のチャネルに設定してみます。

Teamsのチャネルへの通知方法2種類

チャネルへの通知方法は2つあります。一つはメールを使用する方法。もう一つはコネクタを使用する方法です。

本記事では、とても簡単な「メールを使用する方法」を書きます。「コネクタを使用する方法」は別記事で書きます。

前提条件

  • CodePipelineとSNS Topicが作成済みで手動承認の通知先にSNS Topicが設定済みであること。

  • Teamsが利用可能であること。

設定方法

Teamsで通知先チャネルのメールアドレスを取得

Teams上で、通知したいチャネルのメールアドレスを取得します。

2017-08-18_14h09_54.png

SNS TopicにSubscriptionを作成

AWSのManagementConsoleのSNSからTopis -> (CodePipelineの通知先Topic) -> Create subscriptionを選択します。

Protocolに”Email”を選択し、Endpointにチャネルのメールアドレスを設定します。

2017-08-18_14h14_25.png

Confirmする

Subscriptionを作成すると、Teamsの指定したチャネルにメッセージが届きます。

2017-08-18_14h17_05.png

メッセージ中の”Confirm Subscription”リンクをクリックすると、以下のページがブラウザで表示され、通知が届くようになります。

2017-08-18_14h18_27.png

CodePipelineを実行する

CodePipelineを実行し、手動承認待ちの状態になると、Teamsにメッセージが届きます。

2017-08-18_15h09_36.png

これで十分通知の役割は果たしているのですが、Teams上の見た目をもう少しいい感じにするために、Lambda経由での通知を次の記事では書きます。

続きを読む