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

じゃあの。

続きを読む

[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、デューデリジェンス

続きを読む

http_proxyの設定があるとlocalstackの起動でエラーになる

localstack を起動するときに、環境変数 http_proxy (または HTTP_PROXY) が設定されている環境では、以下のように elasticsearch の起動チェックで、何度か WARNING:infra.py:Service “elasticsearch” not yet available, retrying… の警告が出た後、エラーで終了することがある。

WARNING:infra.py:Service "elasticsearch" not yet available, retrying...
ERROR:localstack.services.es.es_starter:Elasticsearch health check failed (retrying...): (以降略)

また、Docker on Mac でもデフォルトがsytsem proxyを参照するようになっているため、OSのネットワーク設定でプロキシが有効になっている場合でも、同様の問題が発生する。

WARNING:infra.pyc:Service "kinesis" not yet available, retrying...
ERROR:infra.pyc:Error checking state of local environment (after some retries): Traceback (most recent call last):
File "/opt/code/localstack/localstack/services/infra.py", line 285, in check_infra
raise e
TypeError: 'NoneType' object has no attribute '__getitem__'
Traceback (most recent call last):
File "bin/localstack", line 79, in <module>
infra.start_infra()
File "/opt/code/localstack/localstack/services/infra.py", line 359, in start_infra
raise e
TypeError: 'NoneType' object has no attribute '__getitem__'

これは、起動チェックでモックサーバに接続しに行くところがプロキシサーバ経由となり、モックサーバに接続できないことが原因である。
このような場合、プロキシサーバを使用しないように環境変数の設定を解除すればエラーは解消するはずである。あるいは、環境変数 no_proxy
no_proxy=<コンテナのhostname>,localhost,127.0.0.1,[::1]
のようにセットすれば、ヘルスチェックがプロキシをバイパスするようになる。

この問題は、#239で解決済みであり、リポジトリから最新版を落としてくるか、PR#244のパッチを適用することで解消する。

続きを読む

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経由での通知を次の記事では書きます。

続きを読む