AnsibleでAWS環境(RDS)を自動構築する

はじめに

AWSの無料枠範囲で遊んでいます。

無料枠を超えないようにするため、作っては削除。作っては削除。をコンソールでやるのがめんどくさくなりました。

そのため、AnsibleでAWSの環境構築を自動構築できるようにしよう!ということで、
Ansible Playbookのサンプルを作成してます。

この記事で作成したplaybookはgithubで公開しています。

https://github.com/rednes/ansible-aws-sample
(AWS02フォルダ)

AWSの何を作るか

以下の内容をAnsibleで自動構築できるようにします。

  • VPCの作成
  • Internet Gatewayの作成
  • サブネットの作成
  • ルートテーブルの作成
  • セキュリティグループの作成
  • EC2インスタンスの作成
  • サブネットグループの作成
  • RDSの作成
  • EC2インスタンスにWordPress環境の構築

前提

  • ansible, botoをインストールすること
  • AWSのサーバー構築に使用するIAMユーザが作成されていること
  • AWSマネジメントコンソールでキーペア登録していること

AWS構成図

今回Ansibleで構築するAWSの構成図はこんな感じです。

AWS構成図

ディレクトリ構成

├── ansible.cfg
├── group_vars
│   └── all.yml
├── host_vars
│   └── localhost.yml
├── hosts
│   ├── ec2.ini
│   └── ec2.py
├── roles
│   ├── ec2
│   │   └── tasks
│   │       ├── ec2.yml
│   │       ├── main.yml
│   │       ├── security_group.yml
│   │       └── vpc.yml
│   ├── rds
│   │   └── tasks
│   │       ├── main.yml
│   │       └── rds.yml
│   ├── wordpress
│   │   ├── defaults
│   │   │   └── main.yml
│   │   └── tasks
│   │       └── main.yml
│   └── yum
│       ├── defaults
│       │   └── main.yml
│       └── tasks
│           └── main.yml
└── site.yml

Playbookの解説

AnsibleでAWS環境(RDS以外)を構築する内容については、過去の記事を参考にしてください。
今回はRDSの構築についてだけ説明します。

RDSの環境はrdsのroleで作成しています。

roles/rds/tasks/main.yml
---
- include_tasks: rds.yml

main.ymlでは単純にrds.ymlをインクルードしているだけです。
rds.ymlでRDSインスタンスを作成しています。

サブネットとセキュリティグループはec2のroleであわせて作成しています。

1. 異なるAZでサブネットを二つ作成

ec2_vpc_subnetモジュールでサブネットを構築します。
サブネットではVPC, Availability Zone, CIDRを指定します。

RDSでは単独のAZでしか使用しない場合でも、複数のAZにまたがったサブネットグループを作成する必要があるため、
今回は使用していませんがわざわざ使用しないAZにサブネットを作成しています。

サブネットグループ作成時に使用するサブネットを識別するため、タグに「route: private」を設定しています。

roles/ec2/tasks/vpc.ymlの一部
- name: subnet作成
  ec2_vpc_subnet:
    region: "{{ aws.common.region }}"
    state: present
    vpc_id: "{{ vpc_net.vpc.id }}"
    az: "{{ aws.common.region }}{{ item.value.zone }}"
    cidr: "{{ item.value.cidr }}"
    map_public: "{{ item.value.map_public|default(True) }}"
    tags: "{{ item.value.tags }}"
  with_dict: "{{ aws.vpc.subnet }}"
  register: vpc_subnet
host_vars/localhost.ymlの一部
aws:
  common:
    region: ap-northeast-1
  vpc:
    subnet:
      subnet1:
        tags:
          Name: public-a
          route: public
        cidr: 10.0.1.0/24
        zone: a
      subnet2:
        tags:
          Name: private-a
          route: private
        cidr: 10.0.2.0/24
        map_public: False
        zone: a
      subnet3:
        tags:
          Name: private-c
          route: private
        cidr: 10.0.3.0/24
        map_public: False
        zone: c

2. セキュリティグループを作成

ec2_groupモジュールでセキュリティグループを構築します。
セキュリティグループでは主にVPCとインバウンドルールを指定します。

AnsibleDBという名称のセキュリティグループを作成して、
EC2からのみ接続できるように設定しています。

roles/ec2/tasks/security_group.ymlの一部
- name: security group作成
  ec2_group:
    name: "{{ item.value.name }}"
    description: "{{ item.value.description }}"
    tags:
      Name: "{{ item.value.name }}"
      vpc_id: "{{ vpc_net_fact.vpcs[0].id }}"
    region: "{{ aws.common.region }}"
    purge_rules: "{{ item.value.purge_rules|default(False) }}"
    rules: "{{ item.value.rules }}"
  with_dict: "{{ aws.vpc.security_group }}"
  register: security_group
host_vars/localhost.ymlの一部
aws:
  common:
    region: ap-northeast-1
  vpc:
    security_group:
      security_group1:
        name: AnsibleWeb
        description: EC2 group
        rules:
          - proto: tcp
            ports:
              - 22
            cidr_ip: 0.0.0.0/0
          - proto: tcp
            ports:
              - 80
              - 443
            cidr_ip: 0.0.0.0/0
      security_group2:
        name: AnsibleDB
        description: EC2 group
        rules:
          - proto: tcp
            ports:
              - 3306
            cidr_ip: 10.0.1.0/24

3. サブネットグループを作成

rds_subnet_groupモジュールでサブネットグループを構築します。
サブネットグループでは主にsubnet idを指定します。

ec2_vpc_subnet_factsモジュールでタグが「route: private」である全てのサブネットを取得して、
一つのサブネットグループを作成しています。

roles/rds/tasks/rds.ymlの一部
- name: subnet取得
  ec2_vpc_subnet_facts:
    region: "{{ aws.common.region }}"
    filters:
      "tag:route": private
  register: ec2_subnet_facts
  check_mode: no

- name: rds-subnet-group作成
  rds_subnet_group:
    name: "{{ aws.vpc.rds.subnet_group.name }}"
    region: "{{ aws.common.region }}"
    state: present
    description: "{{ aws.vpc.rds.subnet_group.description }}"
    subnets: "{{ ec2_subnet_facts.subnets | map(attribute='id') | list }}"
  register: rds_subnet_group
host_vars/localhost.ymlの一部
aws:
  common:
    region: ap-northeast-1
  vpc:
    rds:
      subnet_group:
        name: wp-dbsubnet
        description: WordPress DB Subnet

4. RDSを作成

rdsモジュールでRDSインスタンスを構築します。
セキュリティグループはec2_group_factsモジュールを利用して、
名称からIDを引っ張ってきて定義しています。

roles/rds/tasks/rds.ymlの一部
- name: RDS作成
  rds:
    command: create
    instance_name: "{{ aws.vpc.rds.db_name }}"
    username: "{{ aws.vpc.rds.db_user }}"
    password: "{{ aws.vpc.rds.db_password }}"
    db_name: wordpress
    region: "{{ aws.common.region }}"
    subnet: "{{ aws.vpc.rds.subnet_group.name }}"
    vpc_security_groups: "{{ ec2_group_facts.security_groups[0].group_id }}"
    db_engine: "{{ aws.vpc.rds.db_engine }}"
    engine_version: "{{ aws.vpc.rds.engine_version }}"
    license_model: "{{ aws.vpc.rds.license_model }}"
    instance_type: "{{ aws.vpc.rds.instance }}"
    size: "{{ aws.vpc.rds.size }}"
    tags:
      Name: "{{ aws.vpc.rds.db_name }}"
  register: rds
host_vars/localhost.ymlの一部
aws:
  common:
    region: ap-northeast-1
  vpc:
    rds:
      db_name: wordpressdb
      db_user: root
      db_password: password
      db_engine: MySQL
      engine_version: 5.6.37
      license_model: general-public-license
      instance: db.t2.micro
      size: 20

注意点

RDSのエンドポイントがRDSを作成するまでわからないため、まず最初にRDSインスタンスまで作成した後に
RDSのエンドポイントをgroup_vars/all.ymlに追記する必要があります。

group_vars/all.ymlの一部
---
my_vars:
  rds:
    endpoint: XXXX # RDSで作成したDBのエンドポイントを入力

RDS作成が完了したら以下の様にawscli等を使用してエンドポイントを取得し、
上述のall.ymlにエンドポイントを記載するとEC2からRDSのmysqlに接続してWordPress環境を構築できます。

$ aws rds describe-db-instances --query="DBInstances[].Endpoint"

続きを読む

phpでlocalstackのs3にファイルをアップロード

AWS周りのものをローカルで開発する場合 localstack を使うと便利だというのを聞いたので試してみました。 github.com localstackは、GUIはあまり提供されていない用で、主にawscliコマンドを使います。 s3にファイルをアップロードしたい場合は、 –endpoint-url=http:… 続きを読む

`ionic start`の"aws"って何なの? – ionic-angular AWS Mobile Hub Starterを使ってみた

:christmas_tree: オープンストリーム Advent Calendar 2017 の 9 日目です :santa::christmas_tree:

ionic startの”aws”って何なの?

みなさんは ionic start でIonicのプロジェクトを始めるとき、”aws”の文字を見て気になったことはありませんか?:confused:

ionic start aws

そうです、これです。
“ionic-angular AWS Mobile Hub Starter” とありますね。

今回はこの “aws” で生成されるIonicプロジェクト(Starter)について見て・触れてみようと思います。

AWS Mobile Hubとは?

先ほどの通り ionic startの”aws”で生成されるプロジェクトは AWS Mobile Hub に関係するプロジェクトです。

AWS Mobile Hubとは、ブラウザの操作だけでAWSを用いたモバイルアプリ開発がすぐスタートできるサービスになります。

画面は英語表記ですが(2017/12現在)、AWSのサービス名を知らなくとも、
:grinning:「Facebookのアカウント一つでアプリが使えるようにしたい!」
:smiley_cat:「スマートフォンで通知を受け取るアプリを作りたい!」
など、実現したいことに従ってクリックするだけで、AWSのサービスが展開されて機能がすぐに実現できます。

「モバイルアプリケーションを最短の時間で構築する方法」とある通りですね!

AWS Mobile Hubの詳細はこちらになります。

AWS Mobile Hub(モバイルアプリケーションの構築、テスト、モニタリング)| AWS
https://aws.amazon.com/jp/mobile/

なお、Mobile Hubを実際に使った説明やサンプルは他の記事に譲ります。

プロジェクト開始直後を見てみる

blank を選んだときと比べ、プロジェクトのルートには cors-policy.xml, mobile-hub-project.zip が追加されています。

mobile-hub-project.zip は、このプロジェクトで使うAWSのリソースの設定が格納されています。
このプロジェクトのためにAWSをセットアップするために必要となります。

$ ls -l
-rw-r--r--    1 ysd  staff    3622 Dec  7 14:00 README.md
-rw-r--r--    1 ysd  staff    6173 Dec  7 23:02 config.xml
-rw-r--r--    1 ysd  staff     539 Dec  7 14:00 cors-policy.xml
-rw-r--r--    1 ysd  staff     113 Dec  7 23:02 ionic.config.json
-rw-r--r--    1 ysd  staff     775 Dec  7 14:00 mobile-hub-project.zip
drwxr-xr-x  470 ysd  staff   15980 Dec  7 23:02 node_modules
-rw-r--r--    1 ysd  staff  174985 Dec  7 23:02 package-lock.json
-rw-r--r--    1 ysd  staff    1125 Dec  7 23:01 package.json
drwxr-xr-x    7 ysd  staff     238 Dec  7 23:02 resources
drwxr-xr-x   10 ysd  staff     340 Dec  7 23:01 src
-rw-r--r--    1 ysd  staff     576 Dec  7 14:00 tsconfig.json
-rw-r--r--    1 ysd  staff     178 Dec  7 14:00 tslint.json

さらに src/assets の中にあらかじめAWSのJavaScript SDKが入っていました。

$ ls -l ./src/assets
total 10712
-rw-r--r--  1 ysd  staff    43875 Dec  7 14:00 amazon-cognito-identity.min.js
-rw-r--r--  1 ysd  staff   317909 Dec  7 14:00 amazon-cognito-identity.min.js.map
-rw-r--r--  1 ysd  staff   510404 Dec  7 14:00 aws-cognito-sdk.js
-rw-r--r--  1 ysd  staff   259601 Dec  7 14:00 aws-cognito-sdk.min.js
-rw-r--r--  1 ysd  staff   346715 Dec  7 14:00 aws-cognito-sdk.min.js.map
-rw-r--r--  1 ysd  staff  2658078 Dec  7 14:00 aws-sdk.js
-rw-r--r--  1 ysd  staff  1285371 Dec  7 14:00 aws-sdk.min.js
drwxr-xr-x  3 ysd  staff      102 Dec  7 23:01 icon
drwxr-xr-x  3 ysd  staff      102 Dec  7 23:01 imgs
-rw-r--r--  1 ysd  staff    51794 Dec  7 14:00 ionic-aws-logo.png

プロジェクトのルートに配置されるファイルと package.json で導入されるパッケージから、 https://github.com/ionic-team/starters/tree/master/ionic-angular/official/aws の内容が展開されるようです。

AWS Mobile Hub Starterを使ってみる

次に、プロジェクト同梱の README.md に従ってAWS Mobile Hub Starterを使ってみましょう。

pip, AWS CLIをインストールする

この後のステップでAWS CLIが必要になります。ターミナルを開いて次のコマンドを実行します。

$ pip install --upgrade pip
$ pip install awscli

Mobile Hub を始める

このサンプルで使うAWSのリソースをセットアップするために AWS Mobile Hub を使います。Mobile Hubでプロジェクトを作成し、このプロジェクトで使う/作成するAWSのリソースをまとめます。

Mobile Hubのコンソールを開くとプロジェクトの一覧が表示されます。ここで目につく Create や + Create Project を押したくなりますが、その隣の Import ボタンをクリックします。

Mobile Hub top

Importボタンを押すと、次のようにimportするプロジェクトの設定画面が表示されます。
ここで mobile-hub-project.zip を使います!

“Import your Mobile Hub project zip file” にある “You can also drag and drop your file here” にこの mobile-hub-project.zip をドラッグします。

Mobile Hub Import

AWS Mobile Hub Starterで使うAWSのリソースが mobile-hub-project.zip にあらかじめ記述されているため、このファイルからプロジェクトをインポートすることでAWSのリソースをセットアップできます。

その他の項目は次の通りになります。

  • Enter a name for your Mobile Hub project: Mobile Hubのプロジェクト名になります。 ionic start で設定したプロジェクト名でも可能です
  • Allow AWS Mobile Hub to administer resources on my behalf: Mobile HubでAWSのリソースをセットアップできるようにチェックを入れます
    • 既にMobile Hubを使っている場合は表示されないことがあります
  • Resources for your project will be created: リージョン名をクリックすると、Mobile HubでセットアップされるAWSのリソースのリージョンを指定することができます

設定例としてこのようになります :bulb:

Mobile Hub import sample.png

設定値を入力したら Import project ボタンをクリックします。
クリックするとAWSのリソースが一括で作成されます。1-2分ほどかかります。

完了するとMobile Hubのプロジェクトが開きます。

ここで作成されたAWSのリソースを見ると、
Amazon Pinpoint のプロジェクトが作成され…………(プッシュ通知などに使います)

Amazon Cognitoのユーザープールが作成され…………(ユーザーの認証などに使います)

Amazon Cognitoのフェデレーティッドアイデンティティが作成され…………(ユーザーの認証などに使います)

Amazon S3のバケットが3つ作成され…………(アプリの配布やユーザーデータの保存などに使います)

Amazon CloudFrontのディストリビューションが作成され…………(アプリの配布などに使います)

Amazon DynamoDBのテーブルが作成され…………

IAMロールが4つ作成されました。

通常はこのMobile Hubの画面で各項目を一つずつクリックして設定するところ、このAWS Mobile Hub Starterを使うことで一度に設定できます。

恐ろし… 一通りそろって便利ですね! ※この段階では無料利用枠に収まります。

料金 – AWS Mobile Hub | AWS
https://aws.amazon.com/jp/mobile/pricing/

AWS Mobile Hub Starterでセットアップされないリソース

先ほどの方法でMobile Hubがセットアップしないリソースは次の2つです。

  • Cloud Logic: Amazon API Gateway + AWS Lambda
  • Conversational Bots: Amazon Lex

Cloud Logicはモバイルアプリから送信されたデータを、サービス側で処理する……構造が実現できます。
一方のConversational Botsは、自動で会話できるチャットボットを作成するサービスになります。2017/12現在で米国英語のみの対応になりますが、アプリと会話して注文を受け付ける機能 :shopping_bags: などが実現できます。

Mobile Hubから aws-config.js をダウンロードする

しかし、設定はまだ終わっていません。 先ほど作成されたS3バケットが次の設定で重要になります。


(再掲)

hosting-mobilehub を含むS3バケットの名前を記録します。
そして、Ionicのプロジェクトに戻り、 BUCKET_NAME を先ほどのS3バケットの名前に置き換えて aws-config.js をダウンロードします。

aws s3 cp s3://BUCKET_NAME/aws-config.js src/assets

この例では次の通りになります。

$ aws s3 cp s3://myawsionic-hosting-mobilehub-1404708309/aws-config.js src/assets
download: s3://myawsionic-hosting-mobilehub-1404708309/aws-config.js to src/assets/aws-config.js

この、 aws-config.js はMobile Hubで作成されたAWSリソース達のARNが記録されています。
「モバイルアプリがアップロードしたファイルをどこのS3バケットに配置すべきか」などが決まります。

ユーザーがS3バケットにファイルをアップロードできるようにする

AWS Mobile Hub Starterの初期状態は、アプリを使っているユーザーがS3バケットにファイルをアップロードできる機能がありますが、この機能を使うためにはアップロード先のS3バケットで設定が必要になります。

userfiles を含むS3バケットを開きます。
次に、アクセス権限 -> CORSポリシー を開き、 README.md で説明された内容 もしくはプロジェクトルートにある cors-policy.xml の内容に差し替えて「保存」ボタンをクリックします。

userfiles S3 CORS setting

実行する

プロジェクトのルートで ionic serve を実行するとブラウザで動きを確認することができます。

ユーザーアカウントの登録

ionic serve 実行後に開くブラウザではログイン画面が表示されますが、AWSやIonic Proのアカウントを入力するわけではなく、このアプリ用のユーザーアカウントを登録します。

Login screen

“Create one.”のリンクをクリックし必要な項目を入力するとアカウントの登録ができますが、次のステップで確認コードがメールで送られてそれを入力する必要があるためメールアドレスが適当すぎると詰みます。
(詰んだ場合はブラウザをリロードするとログイン画面に戻ります)

email confirm screen

ユーザー登録完了後は特に何も表示されることなくログイン画面に遷移します。

ここで Amazon Cognitoのユーザープールを見るとユーザーが作成されていることや、確認コード入力の時点で詰んだことがわかります。

Cognito User Pool

タスクを追加する

ログインできると”Tasks”の画面が表示されます。

tasks screen

右上にある 「+」 ボタンをクリックして項目を追加できます。

input tasks screen

inputed tasks screen

項目を追加した後にAmazon DynamoDBを見ると、データベースに先ほどの内容が記録されていることが分かります。

DynamoDB Table Contents

ユーザーの写真を更新する

Settings -> Account を開き,「CHANGE PHOTO」をクリックするとユーザーの写真を更新することができます。

account edit screen

正しく更新されると即時反映されます。反映されない場合はS3バケットのCORSポリシーの設定を再度確認します。

ここでは userfiles を含むS3バケットを見るとユーザーの写真がアップロードされていることが分かります。 protected のディレクトリにあります。

結論(ionic startの”aws”って何だったの?)

AWSのサービスを用いたToDoアプリができるひな形になります。

Settings -> About this app を確認すると、実際に次のサービスが使われている旨が表示されます。

  • Cognito
  • DynamoDB
  • S3

言い換えると、AWS Mobile Hub StarterからIonicのプロジェクトをスタートすると、この3つのサービスがアプリ内ですぐに利用できます。

AWS Mobile Hub Starterを動かすだけで長くなりましたが、Ionicを使うと少し手を動かすだけで

  • ユーザー登録時に確認コードでメールアドレスの所在が保証でき
  • DynamoDBでクラウドに内容が保存できる
  • ToDoアプリ

が作れました! :smiley:

おわりに

……と言いたいのですが、動かしただけではAWS Mobile Hub Starterからどのようにアプリにしていくのか分かりにくいですね?

そこで、この先は AWS Mobile Hub Starterで用意されたAmazon PinpointとFirebaseを組み合わせてAndroidのプッシュ通知を受け取るサンプル を作ろうとしましたが、

AWSブログにて紹介されている方法でFirebaseの送信者IDを入れると、トークン取得の段階で次のエラーが出て正しく動作させることができませんでした。

console.error: Error with Push pluginError: AUTHENTICATION_FAILED

Push Notifications with Ionic and Amazon Pinpoint | AWS Mobile Blog
https://aws.amazon.com/jp/blogs/mobile/push-notifications-with-ionic-and-amazon-pinpoint/

その他の方法を調べましたが、Androidのビルド環境の問題なのかビルドができませんでした :confused:

Mobile HubでセットアップされるAWSの各サービスのドキュメントを読んでも、先ほどのAWSブログ以外にIonicを用いた実装例が見つかりませんでした。
そのため、「せっかく用意したPinpointでプッシュ通知を使ってみたい」「メールアドレスではなくFacebookとCognitoとIonicでユーザー登録を実現したい」など、IonicのAWS Mobile Hub Starterのコードで用意された範囲を超えるのは非常に難しい印象でした(個人の感想です)

紹介できなかったCloud Logicについても試してみたかったのですが、次回の機会に……

翌日 10 日目は @granoeste さんです!

セットアップしたAWSリソースのクリーンアップ

プロジェクトが不要な場合は、Mobile Hubコンソールのトップ画面から操作します :bulb:

削除したいプロジェクトの右上に「…」がありますので、カーソルを合わせて「Delete」をクリックします。

Mobile HubでセットアップされたAWSリソースが削除されアクセス拒否される旨の確認ダイアログが表示されるので、「Delete project」ボタンをクリックするとMobile Hubのプロジェクトが削除されます。

MobileHub project delete confirm dialog

しかし、完全にAWSリソースが削除されないため注意が必要です。

削除されるAWSリソース

  • Amazon Pinpoint のプロジェクト
  • Amazon Cognitoのフェデレーティッドアイデンティティ
  • Amazon S3のバケット(userdata, deployments)
  • プロジェクトに関わるIAMロール

削除されないAWSリソース

  • Amazon Cognitoのユーザープール
  • Amazon CloudFrontのディストリビューション
  • Amazon DynamoDBのテーブル
  • Amazon S3のバケット(hosting)
  • Mobile Hubのサービスが使うIAMロール

動作環境

今回の記事で使った環境はこちらになります :computer:

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.12.6
BuildVersion:   16G29

$ node -v
v8.1.4

$ npm -v
5.4.2

$ ionic --version
3.19.0

$ cordova --version
7.1.0

続きを読む

AWS CloudFormationを使ってAWS Fargateの環境を作成してみる

本記事は個人の意見であり、所属する組織の見解とは関係ありません。

こちらはAWS Fargate Advent Calendar 2017の6日目の記事です。
AWS Fargateが発表されて、一週間ぐらい経ちました。新しいサービス、機能を色々試してみるのは楽しいですよね!

今日は、Fargateを触ってみて、もう少し本格的に取り組んでみたいと感じた方向けにAWS CloudFormationを使ってAWS Fargateの環境を作成する流れについて確認してみたいと思います。

AWS CloudFormationとは

Cloudformationでは、AWSリソースの環境構築を設定テンプレートを元に自動化する事ができます。ECSで利用する場合、TaskdefinisionやServiceの設定なども記述する事ができます。Containerのデプロイをより簡単に行える様になり各種自動化を行いやすくなるメリットもあります。

今回はFargateのAdvent Calendarへの投稿ですので、詳細については、次のWebinerの資料を確認してみてください。

CloudFormationテンプレート作成

作成方針

Cloudformationのテンプレートは記載の自由度が高く、色々な記述の仕方ができるのですが、今回は分かりやすさを重視して次の様な構成で分割したテンプレートを作成してみました。

  • VPC作成用テンプレート

    • Fargate用のVPCを作成し、VPCの設定を行うテンプレート
    • PublicSubnetやPrivateSubnet、ルートテーブルなどを作成していきます。
  • SecurityGroup作成用テンプレート

    • TaskやALBで利用するSecurityGroupを作成します。
  • ECSクラスターを作成するテンプレート

  • ELBを設定するテンプレート

  • TaskDefinitionテンプレート

    • ECS上で起動するContainerに関する設定を行います。
  • Serviceテンプレート

分割の仕方も様々ですので、各自のユースケースにあわせて、色々と試してみてください。個人的には、ライフサイクルが異なるリソースは別テンプレートにするが好きです。逆に、開発環境やデモ環境を素早く立ち上げたい場合は1つのテンプレートの中に全て記載してしまうのもいいですよね。

VPC作成用テンプレート

テンプレートの一部は次の様な形になります。(折りたたんでいます)

Resources:
    VPC:
        Type: AWS::EC2::VPC
        Properties:
            CidrBlock: !Ref VpcCIDR
            Tags:
                - Key: Name
                  Value: !Ref EnvironmentName

    InternetGateway:
        Type: AWS::EC2::InternetGateway
        Properties:
            Tags:
                - Key: Name
                  Value: !Ref EnvironmentName


  <省略>


Outputs:

    VPC:
        Description: A reference to the created VPC
        Value: !Ref VPC
        Export:
          Name: !Sub ${EnvironmentName}-VPC

ポイントは、作成したリソースに関する情報を別リソースからもアクセスできる様に
OutpusセクションでExport属性をつけている事です。Export属性で定義しているNameを利用して、
別テンプレートからも対象リソースに対する参照を行う事ができます。

SecurityGroup作成用テンプレート

ALB用、Container用のSecurityGroupを作成し、必要なPortを許可しています。

こちらも長いので、折りたたんでいます。

Description: >
    This template deploys a security-groups.

Parameters:
  EnvironmentName:
    Description: An environment name that will be prefixed to resource names
    Type: String
    Default: advent-calendar-2017

Resources:
  LoadBalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !ImportValue advent-calendar-2017-VPC
      GroupDescription: SecurityGroup for ALB
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 80
          ToPort: 80
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-LoadBalancers


  ContainerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !ImportValue advent-calendar-2017-VPC
      GroupDescription: Security Group for Task
      SecurityGroupIngress:
        -
          SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup
          IpProtocol: -1
        -
          CidrIp: 0.0.0.0/0
          IpProtocol: tcp
          FromPort: 80
          ToPort: 80

      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName}-ContainerSecurityGroup

Outputs:
  LoadBalancerSecurityGroup:
    Description: A reference to the security group for load balancers
    Value: !Ref LoadBalancerSecurityGroup
    Export:
      Name: !Sub ${EnvironmentName}-LoadBalancerSecurityGroup

  ContainerSecurityGroup:
    Description: A reference to the security group for EC2 hosts
    Value: !Ref ContainerSecurityGroup
    Export:
      Name: !Sub ${EnvironmentName}-ContainerSecurityGroup

ECSクラスタ-を作成するテンプレート

非常にシンプルです。ただ、クラスタを定義して名前をつけるだけ。

Description: >
    This sample template deploys a ECS Cluster

Parameters:
  EnvironmentName:
    Description: An environment name that will be prefixed to resource names
    Type: String
    Default: advent-calendar-2017

Resources:
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${EnvironmentName}-cluster

Outputs:
  ECSCluster:
    Description: A referenc
    Value: !Ref ECSCluster
    Export:
      Name: !Sub ${EnvironmentName}-Cluster

ELBを設定するテンプレート

ALB、Listener、Targetgroupを作成しています。

Description: >
    This template deploys an Application Load Balancer.


Parameters:
  EnvironmentName:
    Description: An environment name that will be prefixed to resource names
    Type: String
    Default: advent-calendar-2017


Resources:
  LoadBalancer:
    Type: AWS::ElasticLoadBalancingV2::LoadBalancer
    Properties:
      Name: !Ref EnvironmentName
      Subnets:
        - !ImportValue advent-calendar-2017-PublicSubnet1
        - !ImportValue advent-calendar-2017-PublicSubnet2
      SecurityGroups:
        - !ImportValue advent-calendar-2017-LoadBalancerSecurityGroup
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName
      Scheme: internet-facing

  LoadBalancerListener:
    Type: AWS::ElasticLoadBalancingV2::Listener
    Properties:
      LoadBalancerArn: !Ref LoadBalancer
      Port: 80
      Protocol: HTTP
      DefaultActions:
        - Type: forward
          TargetGroupArn: !Ref DefaultTargetGroup

  DefaultTargetGroup:
    Type: AWS::ElasticLoadBalancingV2::TargetGroup
    Properties:
      Name: !Sub ${EnvironmentName}-targetgroup
      VpcId: !ImportValue advent-calendar-2017-VPC
      Port: 80
      Protocol: HTTP
      TargetType: ip

Outputs:

  LoadBalancer:
    Description: A reference to the Application Load Balancer
    Value: !Ref LoadBalancer
    Export:
      Name: !Sub ${EnvironmentName}-Loadbalancer

  LoadBalancerUrl:
    Description: The URL of the ALB
    Value: !GetAtt LoadBalancer.DNSName

  Listener:
    Description: A reference to a port 80 listener
    Value: !Ref LoadBalancerListener
    Export:
      Name: !Sub ${EnvironmentName}-Listener

  DefaultTargetGroup:
    Value: !Ref DefaultTargetGroup
    Export:
      Name: !Sub ${EnvironmentName}-DefaultTargetGroup

TaskDefinition設定

ようやくFargateに関連する設定が出てきました。ここでは、RequiresCompatibilities属性にFARGATEを指定し、
NetworkMode属性にawsvpcを指定しています。また、CPU、メモリの設定はContainerDefinitionsの外側で設定します。
Container Definitionsにおけるmemory/cpuの指定はオプションです。加えて、各Taskがログを出力するためのCloudwatch Logsの設定もここで行なっています。

Description: >
    This sample deploys a task

Parameters:
  EnvironmentName:
    Description: An environment name that will be prefixed to resource names
    Type: String
    Default: advent-calendar-2017

Resources:
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/ecs/logs/${EnvironmentName}-groups'

  ECSTask:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Cpu: 256
      ExecutionRoleArn: arn:aws:iam::XXXXXXXXXXXX:role/ecsTaskExecutionRole
      Family: !Sub ${EnvironmentName}-task
      Memory: 512
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE
      ContainerDefinitions:
        -
          Name: nginx
          Image: nginx:latest
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: us-east-1
              awslogs-stream-prefix: ecs
          MemoryReservation: 512
          PortMappings:
            -
              HostPort: 80
              Protocol: tcp
              ContainerPort: 80

Outputs:
  LogGroup:
      Description: A reference to LogGroup
      Value: !Ref LogGroup

  ECSTask:
    Description: A reference to Task
    Value: !Ref ECSTask

Service設定

ここではFargate上でTaskを起動させるために、LaunchType属性にFARGATEを指定しています。ここでTaskNameに指定しているXXの数字はTaskのRevisionに該当します。Taskの更新とともにここの数字を変える必要があるという点がポイントです。

Description: >
    This sample deploys a service

Parameters:
  EnvironmentName:
    Description: An environment name that will be prefixed to resource names
    Type: String
    Default: advent-calendar-2017

  TaskName:
    Description: A task name
    Type: String
    Default: advent-calendar-2017-task:XX

Resources:
  Service:
    Type: AWS::ECS::Service
    Properties:
      Cluster: !ImportValue advent-calendar-2017-Cluster
      DesiredCount: 2
      LaunchType: FARGATE
      LoadBalancers:
        -
          TargetGroupArn: !ImportValue advent-calendar-2017-DefaultTargetGroup
          ContainerPort: 80
          ContainerName: nginx
      NetworkConfiguration:
        AwsvpcConfiguration:
          SecurityGroups:
            -
              !ImportValue advent-calendar-2017-ContainerSecurityGroup
          Subnets:
            -
              !ImportValue advent-calendar-2017-PrivateSubnet1
            -
              !ImportValue advent-calendar-2017-PrivateSubnet2
      ServiceName: !Sub ${EnvironmentName}-service
      TaskDefinition: !Ref TaskName
Outputs:
  Service:
      Description: A reference to the service
      Value: !Ref Service

Cloudformation Stackを作成する

これで、Fargate環境の作成準備が整いました。ここからは順番にStackを作成していきます。

$ aws cloudformation create-stack --stack-name advent-calendar-2017-vpc 
--template-body file://Fargate-vpc.yml 
--region us-east-1

$ aws cloudformation create-stack --stack-name advent-calendar-2017-security-group 
--template-body file://Fargate-security-groups.yaml 
--region us-east-1

$ aws cloudformation create-stack --stack-name advent-calendar-2017-load-balancer 
--template-body file://Fargate-load-balancers.yaml 
--region us-east-1

$ aws cloudformation create-stack --stack-name advent-calendar-2017-cluster 
--template-body file://Fargate-cluster.yml 
--region us-east-1


$ aws cloudformation create-stack --stack-name advent-calendar-2017-task 
--template-body file://Fargate-taskdefinition.yml 
--region us-east-1

$ aws cloudformation create-stack --stack-name advent-calendar-2017-service 
--template-body file://Fargate-service.yml 
--region us-east-1

作成した環境を確認する

Cloudformationでの環境構築が終わりました。正しく構築できているか、ALB経由でアクセスして確認してみてください。
作成したALBのFQDNは、マネージメントコンソール上のEC2の画面>ロードバランサにアクセスして確認できます。
それ以外にも今回の例では、CLIでの次の様なコマンドで確認する事ができます。(少し無理やりですが。。。)

$ aws cloudformation describe-stacks --stack-name advent-calendar-2017-load-balancer  
--region us-east-1 | jq '.Stacks[].Outputs[] | select(.OutputKey == "LoadBalancerUrl")'

{
  "Description": "The URL of the ALB",
  "OutputKey": "LoadBalancerUrl",
  "OutputValue": "advent-calendar-2017-844241308.us-east-1.elb.amazonaws.com"
}


####awscli単独でやるなら、次の様にも書く事ができます。

aws cloudformation describe-stacks --stack-name advent-calendar-2017-load-balancer  
--region us-east-1 
--query 'Stacks[].Outputs[?OutputKey == `LoadBalancerUrl`].OutputValue'

ECSクラスター

次のコマンドで存在が確認できます。

$ aws ecs list-clusters --region us-east-1
{
    "clusterArns": [
        "arn:aws:ecs:us-east-1:925496135215:cluster/advent-calendar-2017-cluster"
    ]
}

サービスの状態

作成したサービスの状態は次の様なコマンドで確認できます。

aws ecs describe-services --services <service name> 
--cluster <cluster name> --region us-east-1

例えば、デプロイしているサービスの状況を確認する際には以下の様なコマンドで状態を取得可能です。
次のコマンド結果には、runningCountが2であり、desiredCountの設定通りにTaskが起動している事が確認できます。


$ aws ecs describe-services --services advent-calendar-2017-service 
--cluster advent-calendar-2017-cluster 
--region us-east-1 | jq .[][].deployments
[
  {
    "status": "PRIMARY",
    "networkConfiguration": {
      "awsvpcConfiguration": {
        "subnets": [
          "subnet-2541e678",
          "subnet-9297e0f6"
        ],
        "securityGroups": [
          "sg-326f1047"
        ]
      }
    },
    "pendingCount": 0,
    "createdAt": 1512499161.953,
    "desiredCount": 2,
    "taskDefinition": "arn:aws:ecs:us-east-1:XXXXXXXXXXXX:task-definition/advent-calendar-2017-task:3",
    "updatedAt": 1512500281.269,
    "id": "ecs-svc/9223370524355613851",
    "runningCount": 2
  }
]

デプロイしたServiceを更新する

Cloudformationを利用して作成していますので、更新もCloudformation経由で行います。

テンプレートを更新する。

今回はDesiredCountを修正してみました。

$ diff Fargate-service-update.yml Fargate-service.yml
20c20
<       DesiredCount: 4
---
>       DesiredCount: 2

Stackを更新する

次の様なコマンドでStackの更新が可能です。

$ aws cloudformation update-stack --stack-name advent-calendar-2017-service 
--template-body file://Fargate-service-update.yml 
--region us-east-1

しばらく待った後に再びServiceの状態を確認するとDsierdCount通りにTaskの数が増えている事が確認できます。

$ aws ecs describe-services --services advent-calendar-2017-service 
--cluster advent-calendar-2017-cluster 
--region us-east-1 | jq .[][].deployments
[
  {
    "status": "PRIMARY",
    "networkConfiguration": {
      "awsvpcConfiguration": {
        "subnets": [
          "subnet-2541e678",
          "subnet-9297e0f6"
        ],
        "securityGroups": [
          "sg-326f1047"
        ]
      }
    },
    "pendingCount": 0,
    "createdAt": 1512499161.953,
    "desiredCount": 4,
    "taskDefinition": "arn:aws:ecs:us-east-1:925496135215:task-definition/advent-calendar-2017-task:3",
    "updatedAt": 1512538215.582,
    "id": "ecs-svc/9223370524355613851",
    "runningCount": 4
  }
]

Taskをアップデートする。

テンプレートを更新する。

Taskが利用するメモリの容量を修正してみました。

$ diff Fargate-taskdefinition-update.yml Fargate-taskdefinition.yml 
25c25
<       Memory: 1024
---
>       Memory: 512

Stackを更新する

次の様なコマンドでTask用のStackの更新をします。

$ aws cloudformation update-stack --stack-name advent-calendar-2017-task 
--template-body file://Fargate-taskdefinition-update.yml 
--region us-east-1

TaskのRevisionが変化していますので、Serviceでも新しいRevisionを利用する様に、テンプレートを修正して、Service用のStackを更新します。

$ aws cloudformation update-stack --stack-name advent-calendar-2017-service 
--template-body file://Fargate-service.yml 
--region us-east-1

再度、サービスの状態を確認して、起動しているTaskが更新されている事を確認してみてください。

まとめ

Cloudformationを利用したECS,Fargateの操作はいかがだったでしょうか。今回の記事を書く為に、新規でCloudformationテンプレートを作成したのですが、これまでのECSで利用していたテンプレートとの違いは僅かでした。FargateをきっかけにECSに興味を持って頂けた方の参考になればうれしいです。

続きを読む

AWS Media ServicesをCLIから操作する

最近、AWSメディアサービスやってみています! https://qiita.com/tin-machine/items/73d1564eeeeda49a3f6c

でも、動画配信のパラメータを変更して検証するの… :tired_face: 超面倒なんですけど。おっさん辛い。

:persevere: そもそも、都度チャンネル作らなきゃならない設定はなんなの。辛み。
CLIから作りたくなってきた。aws-cliをアップデートするとメディアサービスも使えるようになります!

最初に aws-cliをアップデートする

pip install -U awscli

チャンネルidを取得する

先にチャンネルのIDを確認します。WebUIだと
[ MediaLive ]->[ Channels ]->作成したチャンネルのラジオボタンをクリック->[ ID ]の所の数字をメモ

aws medialive describe-channel --region ap-southeast-1 --channel-id チャンネルid > channel_sample.json

CLIからだと

aws medialive list-channels --region ap-southeast-1

で取得できます

チャンネル設定を変更する :relaxed:

上記の describe-channel はユニークであるべきidやチャンネルごとに振られるIPアドレス、ステータスなども出力されてしまいますので削除します。
* PipelinesRunningCount
* EgressEndpoints
* State
* Id
* Arn

既にある設定を削除する :smiling_imp:

注) 下記を実行すると、既にあるチャンネル設定が一つ消えます。高速に検証するため、チャンネル設定を消して作ってを行っていますが、サービスするようになったら変更する必要があるでしょう。

上記のチャンネルidを取得する方法を利用して、既存のチャンネル設定を削除しています。

aws medialive delete-channel --region ap-southeast-1 --channel-id $(aws medialive list-channels --region ap-southeast-1 | jq -r '.Channels[].Id')

jsonからチャンネルを作る :kissing_heart:

aws medialive create-channel  --region ap-southeast-1  --cli-input-json file://channel_sample.json

チャンネルをスタートする :movie_camera:

aws medialive start-channel --region ap-southeast-1 --channel-id $(aws medialive list-channels --region ap-southeast-1 | jq -r '.Channels[].Id')

チャンネルをストップする :no_entry_sign:

aws medialive stop-channel --region ap-southeast-1 --channel-id (aws medialive list-channels --region ap-southeast-1 | jq -r '.Channels[].Id')

続きを読む

KubernetesでAWS ALBを自動作成する〜ついでにRoute53 Record Setも

kube-ingress-aws-controllerを使います。

kube-ingress-aws-controllerとは

Zalandoが公開している、Kubernetes用のIngress Controllerの一つです。

ZalandoはKubernetes界隈では著名な、ヨーロッパでファッションECをやっている企業です。
Kubernetesコミュニティへの様々な形で貢献していて、今回紹介するkube-aws-ingerss-controllerや先日紹介したexternal-dnsもその一つです。

何かできるのか

KubernetesユーザがAWSを全く触らずとも

  • ALBの自動作成
  • ALBに割り当てるTLS証明書(ACM管理)を自動選択

をしてくれます。

使い方

KubernetesのIngressリソースを普段通りつくります。

kubectl create -f myingress.yaml

すると、1~2分ほどでIngressリソースに書いたホスト名でインターネットからアクセスできるようになります。

open https://myingress.exapmle.com

Ingress Controllerとは

KubernetesのIngressはL7ロードバランサのスペックのようなもので、そのスペックから実際にL7ロードバランサをセットアップするのがIngress Controllerの役割です。

coreos/alb-ingress-controllerとの違い

coreos/alb-ingress-controller

  • Ingressリソース一つに対して、1 ALBをつくります

zalando-incubator/kube-ingress-aws-controller

  • ACM証明書のドメイン一つに対して、ALBを割り当てます
  • 同じドメイン名に対するルートを含むIngressリソースは、一つのALBにまとめられます
  • ALBのターゲットグループにEC2インスタンスを割り当てるところまでしかやってくれない!ので、実際にIngressとして利用するためには他のIngress Controllerを併用する必要があります

kube-ingress-aws-controllerのセットアップ手順

Security Groupの作成

kube-ingress-aws-controllerはALBに割り当てるSecurity Groupまでは自動作成してくれないので、AWSコンソール等で作成します。

kube-ingerss-aws-controllerのドキュメントにはCloudFormationを使った手順がかいてあります。

同等のSecurity Groupをawscliで作る場合は以下のようなコマンドを実行します。

CLUSTER_NAME=...
VPC_ID=vpc-...

aws ec2 create-security-group \
  --description ${CLUSTER_NAME}-kube-aws-ingress-controller-alb \
  --group-name ${CLUSTER_NAME}-kube-aws-ingress-controller-alb \
  --vpc-id $VPC_ID | tee sg.json

SG_ID=$(jq -r '.GroupId' sg.json)

aws ec2 create-tags --resources $SG_ID --tags \
  "Key=\"kubernetes.io/cluster/$CLUSTER_NAME\",Value=owned" \
  "Key=\"kubernetes:application\",Value=kube-ingress-aws-controller"

aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --ip-permissions '[{"IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "IpRanges": [{"CidrIp": "0.0.0.0/0"}]}, {"IpProtocol": "tcp", "FromPort": 443, "ToPort": 443, "IpRanges": [{"CidrIp": "0.0.0.0/0"}]}]'

aws ec2 describe-security-groups --group-id $SG_ID

# あとで: 不要になったらクラスタやVPCの削除前に以下のように削除
aws ec2 delete-security-group --group-id $SG_ID

IAMポリシーの割当

kube-ingerss-aws-controllerのドキュメントにIAM Policy Statementの一覧がかいてありますが、要約すると

  • CloudFormationスタックのCRUD権限
  • ACM証明書、VPC、RouteTable、Subnet、Security Group、AutoScalingGroup、EC2 InstanceのGet/List/Describe権限
  • ALBのCRUD権限

が必要です。

kube-awsのcluster.yamlの場合は、以下のように書きます。

worker:
  nodePools:
  - iam:
      policy:
        statements:
        - effect: Allow
          actions:
          - "autoscaling:DescribeAutoScalingGroups"
          - "autoscaling:AttachLoadBalancers"
          - "autoscaling:DetachLoadBalancers"
          - "autoscaling:DetachLoadBalancerTargetGroup"
          - "autoscaling:AttachLoadBalancerTargetGroups"
          - "elasticloadbalancing:AddTags"
          - "elasticloadbalancing:DescribeLoadBalancers"
          - "elasticloadbalancing:CreateLoadBalancer"
          - "elasticloadbalancing:DeleteLoadBalancer"
          - "elasticloadbalancing:DescribeListeners"
          - "elasticloadbalancing:CreateListener"
          - "elasticloadbalancing:DeleteListener"
          - "elasticloadbalancing:DescribeTags"
          - "elasticloadbalancing:CreateTargetGroup"
          - "elasticloadbalancing:DeleteTargetGroup"
          - "elasticloadbalancing:DescribeTargetGroups"
          - "elasticloadbalancingv2:DescribeTargetGroups"
          - "elasticloadbalancingv2:DescribeLoadBalancers"
          - "elasticloadbalancingv2:CreateLoadBalancer"
          - "elasticloadbalancingv2:DeleteLoadBalancer"
          - "elasticloadbalancingv2:DescribeListeners"
          - "elasticloadbalancingv2:CreateListener"
          - "elasticloadbalancingv2:DeleteListener"
          - "elasticloadbalancingv2:DescribeTags"
          - "elasticloadbalancingv2:CreateTargetGroup"
          - "elasticloadbalancingv2:DeleteTargetGroup"
          - "ec2:DescribeInstances"
          - "ec2:DescribeSubnets"
          - "ec2:DescribeSecurityGroup"
          - "ec2:DescribeRouteTables"
          - "ec2:DescribeVpcs"
          - "acm:ListCertificates"
          - "acm:DescribeCertificate"
          - "iam:ListServerCertificates"
          - "iam:GetServerCertificate"
          - "cloudformation:Get*"
          - "cloudformation:Describe*"
          - "cloudformation:List*"
          - "cloudformation:Create*"
          - "cloudformation:Delete*"
          resources:
          - "*"

kube-aws-ingress-controllerをデプロイ

$ kubectl apply -f kube-aws-ingress-controller.yaml
kube-aws-ingress-controller.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: kube-ingress-aws-controller
  namespace: kube-system
  labels:
    application: kube-ingress-aws-controller
    component: ingress
spec:
  replicas: 1
  selector:
    matchLabels:
      application: kube-ingress-aws-controller
      component: ingress
  template:
    metadata:
      labels:
        application: kube-ingress-aws-controller
        component: ingress
    spec:
      containers:
      - name: controller
        image: registry.opensource.zalan.do/teapot/kube-ingress-aws-controller:latest
        env:
        - name: AWS_REGION
          value: ap-northeast-1

併用するIngress Controllerをデプロイ

今回はskipperを使ってみます。

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: skipper-ingress
  namespace: kube-system
  labels:
    component: ingress
spec:
  selector:
    matchLabels:
      application: skipper-ingress
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      name: skipper-ingress
      labels:
        component: ingress
        application: skipper-ingress
    spec:
      hostNetwork: true
      containers:
      - name: skipper-ingress
        image: registry.opensource.zalan.do/pathfinder/skipper:latest
        ports:
        - name: ingress-port
          containerPort: 9999
          hostPort: 9999
        args:
          - "skipper"
          - "-kubernetes"
          - "-kubernetes-in-cluster"
          - "-address=:9999"
          - "-proxy-preserve-host"
          - "-serve-host-metrics"
          - "-enable-ratelimits"
          - "-experimental-upgrade"
          - "-metrics-exp-decay-sample"
          - "-kubernetes-https-redirect=true"
        resources:
          limits:
            cpu: 200m
            memory: 200Mi
          requests:
            cpu: 25m
            memory: 25Mi
        readinessProbe:
          httpGet:
            path: /kube-system/healthz
            port: 9999
          initialDelaySeconds: 5
          timeoutSeconds: 5

WorkerノードのSecurity Group設定変更

今回はskipperをつかうことにしたので、kube-ingress-aws-controllerが作成したALBからアクセスする先はskipper(がいるEC2インスタンス)になります。

Security GroupへALBからskipperがいるEC2インスタンスへの通信をブロックしたままだとGateway Timeoutになってしまいます。そうならないように、ALB用につくったSGから、WorkerノードのSGへの9999番ポート(kube-ingress-aws-controllerと組み合わせて使うskipperのhostPortに指定した)の通信を許可しましょう。

ALB側SGのOutboundを絞っていないのであれば、Worker側SGのInboundを追加すればOKです。

Ingressリソースの作成

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx
spec:
  rules:
  - host: nginx-ingress.example.com
    http:
      paths:
      - backend:
          serviceName: nginx
          servicePort: http

---

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: ClusterIP
  ports:
  - name: http
    port: 80
    targetPort: 80
  selector:
    app: nginx

---

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80

ログの確認

$ k logs kube-ingress-aws-controller-6bbd8f9d6c-bcqph
2017/12/06 12:33:52 starting /bin/kube-ingress-aws-controller
2017/12/06 12:33:54 controller manifest:
2017/12/06 12:33:54     kubernetes API server:
2017/12/06 12:33:54     Cluster ID: k8s3
2017/12/06 12:33:54     vpc id: vpc-12345678
2017/12/06 12:33:54     instance id: i-07e29f841f676ca00
2017/12/06 12:33:54     auto scaling group name: k8s3-Nodepool1-MMF7MXKI9350-Workers-BZWB5IAV7JW8
2017/12/06 12:33:54     security group id: sg-8368a6fa
2017/12/06 12:33:54     private subnet ids: []
2017/12/06 12:33:54     public subnet ids: [subnet-12345678 subnet-23456789]
2017/12/06 12:33:54 Start polling sleep 30s

30秒経過後、以下のようにCloudFormationスタックが作成される。

2017/12/06 12:34:24 Found 1 ingresses
2017/12/06 12:34:24 Found 0 stacks
2017/12/06 12:34:24 Have 1 models
2017/12/06 12:34:24 creating stack for certificate "arn:aws:acm:ap-northeast-1:myawsaccountid:certificate/64f33935-05ac-4e6b-b1ae-58973d556a74" / ingress ["kube-system/nginx"]
2017/12/06 12:34:25 stack "arn:aws:cloudformation:ap-northeast-1:myawsaccountid:stack/k8s3-b9dbfe3/caf1f3a0-da81-11e7-9e21-500c28b97482" for certificate "arn:aws:acm:ap-northeast-1:myawsaccountid:certificate/64f33935-05ac-4e6b-b1ae-58973d556a74" created
2017/12/06 12:34:25 Start polling sleep 30s

作成されたAWSリソースの確認

1〜2分待ってスタックがCREATE_COMPLETE状態になれば成功。

コンソールでCloudFormationスタックのResourcesの内容を見ると、何がつくられたのかがわかる。

image.png

つくられるのは以下の4つ。

  • HTTPListener: 80番ポート用のListener
  • HTTPSListener: 443番ポート用のListener
  • LB: ALB
  • TG: kube-ingress-aws-controllerがデプロイされているノードが登録されたTargetGroup

ALB

リスナー

443番ポート用のListenerには、Ingressリソースに書いたドメインに対応するACM証明書が選択されています。

今回は*.example.com用のワイルドカード証明書を事前に用意しておいたのですが、Ingressにnginx-ingress.example.comというホスト名を設定したところ、ちゃんとワイルドカード証明書を探し出してくれました(かしこい)。

image.png

ターゲットグループ

kube-aws-ingress-controllerがデプロイされたノードのASGをコンソールでみてみると、ターゲットグループに割り当てられていました。EC2インスタンスを直接TargetGroupに登録していくような方法だとインスタンスが落ちた場合などが怖いですが、ちゃんとしてますね。

image.png

Route53 RecordSetの作成

これだけだとALBが作成されただけなので、nginx-ingress.example.comでアクセスできないはずです。

しかし、昨日デプロイしたexternal-dnsがIngressリソースとALBを検知して、勝手にRecordSetをつくってくれていました。

stern_external-dns.log
external-dns-768686fd4c-zpnlx external-dns time="2017-12-06T12:43:53Z" level=info msg="Desired change: CREATE nginx-ingress.example.com A"
external-dns-768686fd4c-zpnlx external-dns time="2017-12-06T12:43:53Z" level=info msg="Desired change: CREATE nginx-ingress.example.com TXT"
external-dns-768686fd4c-zpnlx external-dns time="2017-12-06T12:43:53Z" level=info msg="Record in zone example.com. were successfully updated"

image.png

ちゃんとALBへのA(lias)レコードを作成してくれていますね。

 インターネットからアクセスしてみる

nginx-ingress.example.comにブラウザからアクセスしてみて、以下のようなnginxのウェルカムページが表示されてば成功です。おつかれさまでした。

image.png

まとめ

kube-ingress-aws-controllerを使うと、Kubernetesユーザはkubectl createするだけでALBとRecordSetをよしなにセットアップしてくれます。
ALBの作成・管理やRoute53 RecordSetの作成のためにいちいちインフラエンジニアを呼び出したくない!というようなセルフサービス好きの会社さんでは特に役立つのではないでしょうか?!

トラブルシューティング

unable to get details for instance “i-0346d738155e965d8”

IAMポリシーが足りないときに出るエラーです。

$ k logs kube-ingress-aws-controller-7f7974ff58-6bvv8
2017/12/06 07:11:51 starting /bin/kube-ingress-aws-controller
2017/12/06 07:11:53 unable to get details for instance "i-0346d738155e965d8": NoCredentialProviders: no valid providers in chain. Deprecated.
    For verbose messaging see aws.Config.CredentialsChainVerboseErrors

required security group was not found

Security Groupがないか、またはSecurityGroupのタグが間違っているか、EC2インスタンスにkubernetes.io/cluster/クラスタ名=ownedというタグがついていない場合のエラーです。

$ k logs kube-ingress-aws-controller-7f7974ff58-xqgrq
2017/12/06 08:10:40 starting /bin/kube-ingress-aws-controller
2017/12/06 08:10:41 required security group was not found

CloudFormationで「At least two subnets in two different Availability Zones must be specified」

KubernetesのWorkerノードのASGが単一のAZに割り当てられているときのエラー。ALBの仕様で、最低2つのAZが必要。

kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:06 Start polling sleep 30s
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:36 Found 1 ingresses
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:37 Found 0 stacks
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:37 Have 1 models
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:37 creating stack for certificate "arn:aws:acm:ap-northeast-1:myaccountid:certificate/64f33935-05ac-4e6b-b1ae-58973d556a74" / ingress ["kube-system/nginx"]
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:37 stack "arn:aws:cloudformation:ap-northeast-1:myaccountid:certificate:stack/k8s3-b9dbfe3/360e1830-da7d-11e7-99f7-500c596c228e" for certificate "arn:aws:acm:ap-northeast-1:myaccountid:certificate:certificate/64f33935-05ac-4e6b-b1ae-58973d556a74" created

image.png

instance is missing the “aws:autoscaling:groupName” tag

ASG以外で作ったEC2インスタンスにkube-ingress-aws-controllerがデプロイされてしまったときのエラー。

$ k logs kube-ingress-aws-controller-7f7974ff58-m6ss2
2017/12/06 12:25:59 starting /bin/kube-ingress-aws-controller
2017/12/06 12:25:59 instance is missing the "aws:autoscaling:groupName" tag

kube-aws-ingress-controllerは、デフォルトではASGに設定されたSubnetをALBのSubnetに流用する。
そのためにASGを探すとき、EC2インスタンスについたaws:autoscaling:groupNameというASGが自動的につけてくれるタグをヒントにするため、ASG以外でつくったEC2インスタンスではこのエラーが出てしまう。

Ref: Spot Fleet support · Issue #105 · zalando-incubator/kube-ingress-aws-controller

Issueも出ているが、まだASG以外は対応していない。ワークアラウンドとしては、kube-ingress-aws-controllerのaffinityでASGでつくったノードにだけスケジュールされるようにすることが考えられる。

kube-awsの場合、awsNodeLabels機能をオンにすると、ASGでつくったノードには”kube-aws.coreos.com/autoscalinggroup”というラベルが付与されるので、それを前提にすると以下のようなaffinityをかけばOK。

              affinity:
                nodeAffinity:
                  requiredDuringSchedulingIgnoredDuringExecution:
                    nodeSelectorTerms:
                    - matchExpressions:
                      - key: "kube-aws.coreos.com/autoscalinggroup"
                        operator: "Exists"

504 Gateway Time-out

ALB経由でnginxにアクセスしようとしてこのエラーがかえってきた場合、ALB用につくったセキュリティグループからkube-aws-ingress-controllerが動いているEC2インスタンスへのアクセスを許可できていない可能性があります。

EC2インスタンス側のSGに、ALB用SGからの9999番ポート(kube-ingress-aws-controllerと組み合わせて使うskipperのhostPortに指定した)への通信をを許可するようなInboundルールを追加しましょう。

続きを読む

AWS Batchを使ってみた

AWS Batchとは

AWS re:Invent 2016 で発表されたサービスで、2017/06より東京リージョンがリリースされました。

どんなサービスなのか簡潔に言うと、
ECSを利用して、処理が実行している間だけ自動でEC2インスタンスを起動し、終わったら自動でterminateしてくれるサービスです。

今回、試しに簡単な処理を実行するものを作ってみたので、その手順を書きたいと思います。

AWS Lambdaとの違い

処理を実行する時だけ起動するという点では、よく似ているサービスにAWS Lambdaがあります。

関連: Serverless FrameworkでAWS Lamda関数を作成する

しかし、似ているようで、全く別のサービスとなっています。
以下が、Lambdaとの相違点です。

  • Lambdaは最長5分しか実行できないが、AWS Batchは実行時間に上限はない
  • LambdaはFaaSなので開発言語が限定されているが、Batchは環境構築を自分で行うので自由
  • Batch単体で定期実行はできないので、別途定期的にキックするトリガーを用意する必要がある

まず、サーバーの環境構築は自分で行う必要があります。
具体的には、あらかじめDockerイメージを作成しておいて、
処理起動時に、AWS Batchが指定されたDockerレポジトリからイメージを取得してコンテナを立ち上げるという流れになります。

自分で環境構築するため、Lambdaのように開発言語を限定されないので、自由度が高いです。

また、最長実行時間に上限がないため、時間のかかる処理に向いています。

AWS Batchのオートスケーリング

オートスケーリングは、AWSがよしなにやってくれます。
自分で制御することも可能です。
その他、以下のような特徴があります。

  • クラスタ数の上限を自分で設定することも可能

    • インスタンス数ではなくvCPU数で指定する
  • 使用されるEC2インスタンスタイプを指定することもAWSにおまかせすることもできる

    • t2系などは選択できない
    • m4 familyのような柔軟な設定も可能(その中からAWSが最適なものを選ぶ)
    • optimal(おまかせ)を選択すると最新の C、M、および R インスタンスファミリーから最適なものが選択される
  • 立ち上がったインスタンスは、実際にECSやEC2の画面で確認できる

構成図

今回構築した環境の構成図です。

qiita-aws-batch.png

今回は上図の構成で構築してみました。
処理のフローは以下になります。

  1. jobをsubmitし、Batchのqueueに登録(FIFO)

    • スケジューラがqueueのjobの優先順位を把握する
  2. ECSがEC2インスタンスが起動させる

    • 既に起動している場合は、起動しているインスタンスが使用される
    • 既にEC2が起動している場合は、ほとんどタイムラグなくjobが実行されるが、起動していない場合は、job実行開始が、インスタンスが起動する時間分タイムラグとなる
    • 立ち上がったインスタンスは、ECSやEC2の画面で確認できる
  3. AWS ECRに登録されているコンテナイメージを使って、EC2内にコンテナを立ち上げる
  4. コンテナ内で処理を実行する
  5. 処理が終わったら、EC2インスタンスを削除する
  6. 後続のjobがある場合は、削除されずにそのまま使用される

トリガーについて

まず、AWS Batchは単体で定期実行することができないので、別途トリガーを用意する必要があります。
方法は色々ありますが、AWSのサービスを利用するのであれば、トリガーを投げるLambdaをCloudwatchで定期実行させるのが簡単かと思います。

job

AWS Batchは、キックされるとjobにキューを溜めます。
基本的にFIFO(First In, First Out)ですが、他のjobとの依存関係や優先度を指定することも可能です。

ECSで処理実行

jobが実行されると、ECSが、EC2クラスタインスタンスを立ち上げます。

次に、ECSがDockerレポジトリ(今回はECR)からDockerイメージを取得して、
EC2クラスタインスタンス上にDockerコンテナを立ち上げます。

このコンテナ上で、処理が実行されます。
処理の重さに応じてオートスケーリングが行われます。

図の例では、データソースから取得したデータ加工して、HDFSに書き出すという処理が行われています。

処理が終了したら、EC2クラスタインスタンスが削除されます。

環境構築手順

AWS ECRにDockerイメージの登録

まずは、以下の記事を参考に、Dockerイメージを作成して、AWS ECRに登録してください。
AWS ECRにdockerイメージを登録する

AWS Batchの環境構築

では、AWS Batch環境構築をします。

Cloud Formationを利用して環境構築をするので、
まず、awscliなどをインストールした、Cloud Formationを実行できる環境を用意してください。
Cloud Formationの環境構築手順については割愛します。

Cloud Formation実行

yamlを作成

以下のyamlを作成します。
適宜、ご自分の環境にあった設定に読み替えてください。

aws-batch.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Build AWS Batch environment

Parameters:
  SubnetIds:
    Description: Subnets For ComputeEnvironment
    Type: List<AWS::EC2::Subnet::Id>
    Default: subnet-xxxxxxxx,subnet-yyyyyyyy
  SecurityGroupIds:
    Description: SecurityGroups For ComputeEnvironment
    Type: List<AWS::EC2::SecurityGroup::Id>
    Default: sg-zzzzzzzz
  ContainerImage:
    Description: Uri of container image to use in ECS
    Type: String
    Default: xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/sample-aws-batch-repo
  # あらかじめ、key pairを作成しておいてください
  KeyPair:
    Description: key pair
    Type: String
    Default: sample_key

Resources:
  ####### IAM #######
  # ECSインスタンスを実行するrole
  ecsInstanceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - ec2.amazonaws.com
          Action:
          - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role
        - arn:aws:iam::aws:policy/AmazonEC2FullAccess
        - arn:aws:iam::aws:policy/AmazonECS_FullAccess
        - arn:aws:iam::aws:policy/CloudWatchLogsFullAccess
        - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
      Path: "/"
  # Set InstanceProfile
  ecsInstanceProfile:
    Type: "AWS::IAM::InstanceProfile"
    Properties:
      Roles:
        - !Ref ecsInstanceRole

  # AWS Batchを実行するrole
  AWSBatchServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Principal:
            Service:
            - batch.amazonaws.com
            - ecs-tasks.amazonaws.com
          Action:
          - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole
      Path: "/service-role/"

  ####### ComputeEnvironment #######
  SampleComputeEnv:
    Type: "AWS::Batch::ComputeEnvironment"
    Properties:
      Type: MANAGED
      # 作成したIAMから取得
      ServiceRole: !GetAtt AWSBatchServiceRole.Arn
      ComputeEnvironmentName: sample-batch-compute-env
      ComputeResources:
        Ec2KeyPair: !Ref KeyPair
        # 最大vcpu数、最大インスタンス数となります
        MaxvCpus: 256
        # 最小vcpu 0にすると自動でインスタンスをterminateしてくれる
        MinvCpus: 0
        # 起動するインスタンスの希望するvCPUの数
        DesiredvCpus: 1
        SecurityGroupIds: !Ref SecurityGroupIds
        Type: EC2
        Subnets: !Ref SubnetIds
        # 作成したVPCから取得
        InstanceRole: !GetAtt ecsInstanceProfile.Arn
        InstanceTypes:
          # 使用するインスタンスタイプ、optimalならおまかせ
          - optimal
        Tags: {"Name": "Batch Instance - sample"}
      State: ENABLED

  SampleJobQueue:
    Type: AWS::Batch::JobQueue
    Properties:
      ComputeEnvironmentOrder:
        - Order: 1
          ComputeEnvironment: !Ref SampleComputeEnv
      State: ENABLED
      # jobの優先順位
      Priority: 1
      JobQueueName: sample-batch-queue

  SampleJobDefinition:
    Type: AWS::Batch::JobDefinition
    Properties:
      Type: container
      JobDefinitionName: sample-batch-definition
      ContainerProperties:
        Command:
          - sh
          - /usr/local/init.sh
        # memoryの上限
        Memory: 4048
        Vcpus: 2
        Image: Image: !Ref ContainerImage
      RetryStrategy:
        Attempts: 1

syntax check

yamlのsyntax checkをします。

$ aws cloudformation validate-template --template-body file://aws-batch.yml

実行

実行して、環境を作成します。

$ aws cloudformation create-stack \
      --stack-name sample-batch \
      --template-body file://aws-batch.yml \
      --capabilities CAPABILITY_IAM \

確認

作成されたことを確認します。

$ aws cloudformation describe-stacks --stack-name sample-batch

"StackStatus": "CREATE_COMPLETE", になっていることを確認してください。

削除

削除する場合は以下のコマンドを実行してください。

$ aws cloudformation delete-stack --stack-name sample-batch

jobを実行する

今回は、awscliを手動で実行してトリガーとします。

まず、以下のコマンドで、jobの設定ファイルとなるjsonのテンプレートを生成します。

$ aws batch submit-job --generate-cli-skeleton > submit-job.json

生成されたjsonファイルを、以下のように編集します。
パラメータや、環境変数を設定することができます。

submit.json
{
    "jobName": "sample-batch-job",
    "jobQueue": "sample-batch-queue",
    "jobDefinition": "sample-batch-definition",
    "parameters": {
        "KeyName": "string"
    },
    "containerOverrides": {
        "environment": [
            {
                "name": "access_token",
                "value": "abcdef1234"
            }
        ]
    }
}
項目 設定値
jobName job名
jobQueue 作成したJobQueueを指定
jobDefinition 作成したJobDefinitionを指定
parameters パラメータ
environment 環境変数

以下のコマンドでjobを実行します。

$ aws batch submit-job --cli-input-json file://submit-job.json

response

{
    "jobId": "a94985d8-52d6-4ea0-95ac-2ccb0dcabc25",
    "jobName": "sample-batch-job"
}

job実行

jobが実行されると、以下の順でjobが移動していきます。
submitted -> runnable -> starting -> running -> succeeded

Screen Shot 2017-12-03 at 17.50.53.png

また、runnableのタブに入ったまま、runningへ移動しない場合は、ECSの起動周りで問題が起きている可能性が高いので、EC2インスタンスにsshでログインして、原因を調査してください。

EC2インスタンス確認

EC2インスタンス一覧の画面で、

Batch Instance - sampleという名前でインスタンスが起動していることが確認できます。

Screen Shot 2017-12-03 at 16.28.47.png

実行結果Statusを確認

AWS BatchのJob画面で、実行結果のStatusを確認できます。
以下のように、succeededのタブにjobが移動していれば成功です。
3つ結果がありますが、今回の結果は一番下の選択されているものです。

Screen Shot 2017-12-03 at 18.20.05.png

もし、failedのタブに入れば、失敗しているので、failedタブのJob IDをクリックして、エラーの原因を調査してください。

Cloudwatchで出力結果の確認

出力結果がある場合は、Cloudwatch の /aws/batch/job ロググループで確認することができます。

Screen Shot 2017-12-03 at 18.16.17.png

Batch完了後

自動でインスタンスがterminateされます。

Screen Shot 2017-12-03 at 16.44.07.png

まとめ

一通り触ってみて、バッチ処理を実行することはできました。
jobが登録されてからEC2インスタンスが立ち上がるので、リアルタイムで実行する処理には向いていませんが、非同期で時間のかかる処理を実行するのには向いてそうです。

別途、オートスケーリングの検証などもしてみたいと思います。

以上

参考

続きを読む

boto3でS3にファイルアップロードする方法

Pythonで画像ファイルをS3にアップロードしてくなったので、以下方法でやってみて簡単にできました。

環境

python 3.5.1
Mac OS 10.11.4

credentialの設定を忘れずに

AWS cliコマンドをまずは使えるようにしておきます。

$ pip install awscli

credentialを設定しておく

$ aws configure

aws configureコマンドを打つと、accesskey,secret key,regionの設定ができるので、
AWSコンソール画面にログインし、事前に確認しておき、ここで設定しておきます。

すると .aws/credentialsにprofileが設定されるので、下準備はOK

boto3をインストールしておく

Amazonへの操作ができるようboto3をインストールします。

$ pip install boto3

以下のようにファイルコードを記述します。
upload_file APIを使います。

import json
import boto3

bucket_name = "my-bucket-name"
s3 = boto3.resource('s3')

s3.Bucket(bucket_name).upload_file('/Users/tottu22/Downloads/local.jpg', 'server.jpg')

※ダウンロードフォルダにあるlocal.jpgファイルをS3のmy-bucket-nameフォルダにserver.jpgファイルとしてアップロードする例

これでファイルをアップロードは完了

指定したバケットにファイルがアップロードされていることを確認してみてください。

続きを読む

EC2でインスタンスメタデータが取得できないときのチェックポイント

前書き

なんかの拍子でEC2上で動いてるWindowsServer機からインスタンスメタデータが取得できなくなった

TL;DR

ルーティングテーブル異常
静的ルートで169.254.169.254にルーティングしている物を削除しReboot

そもそもインスタンスメタデータとは

インスタンスメタデータは、インスタンスに関するデータで、実行中のインスタンスを設定または管理するために使用します。インスタンスメタデータはいくつかのカテゴリに分けられます。
(http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/ec2-instance-metadata.html)

実行中のインスタンスに関する様々なデータがawscliとか使わずにHTTPで取れるため、簡単に扱える

症状

壁紙に埋め込まれている情報が少ない

Windows機でリモートデスクトップを使って接続すると壁紙にインスタンスIDとかが表示されるかと思うがその表示数が少ない
正常な例
正常な例だが、ここからInstanceIDとかが表示されなくなる

メタデータサーバーに接続できない

単純にhttp://169.254.169.254/に繋がらない

調査

色々調査したが、今回はルーティングテーブルが怪しかった
Windows機なのでroute printしたところ

正常な例
C:Windowssystem32>route print
===========================================================================
インターフェイス一覧
  5...XX XX XX XX XX XX ......Intel(R) 82599 Virtual Function
  1...........................Software Loopback Interface 1
  6...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter
  3...00 00 00 00 00 00 00 e0 Teredo Tunneling Pseudo-Interface
===========================================================================

IPv4 ルート テーブル
===========================================================================
アクティブ ルート:
ネットワーク宛先        ネットマスク          ゲートウェイ       インターフェイス  メトリック
----------------------------------(省略)-----------------------------------
===========================================================================
固定ルート:
  ネットワーク アドレス          ネットマスク  ゲートウェイ アドレス  メトリック
  169.254.169.254  255.255.255.255      XXX.XXX.XXX.XXX      15
  169.254.169.250  255.255.255.255      XXX.XXX.XXX.XXX      15
  169.254.169.251  255.255.255.255      XXX.XXX.XXX.XXX      15
===========================================================================

IPv6 ルート テーブル
===========================================================================
アクティブ ルート:
 If メトリック ネットワーク宛先      ゲートウェイ
----------------------------------(省略)-----------------------------------
===========================================================================
固定ルート:
  なし

C:Windowssystem32>

このように一部固定ルートがAWSから設定されているが、この中で一部ルート(169.254.169.254を含む)が明らかに異常値(重複していたり、存在しないゲートウェイにルーティングされていた)となっていた。

対策

該当ルートをroute delete 169.254.169.254し、再起動することにより正常なルーティング情報が自動的にセットされる

続きを読む

HTTP/2対応のElastic Beanstalk環境を構築する

はじめに

サービスの紹介

2017/10/16 にリリースした Snapmap(スナップマップ) は、
「いつ」「なにを」「どこで」「どうやって」撮影したのかが分かるというコンセプトのサービスです。

そんな Snapmap ですが、画像共有サービスなのでサイト全体は HTTP/2 で表示するようになっています。
その効果1もあり、サイトのパフォーマンスを計測するとこんな感じで結構速いです。

Elastic Beanstalkとは

Snapmap は Elastic Beanstalk を使ってアプリケーションサーバーの環境を構築しています。
Elastic Beanstalk は AWS のサービスの一つで、デプロイ・サーバー監視・スケーリングの全てをまるっとやってくれます。

新規サービスで考慮しておくべきことの多くをショートカットできるのはとても魅力的で、インフラにコストを掛けたくなかった今回の案件にぴったりでした。

HTTP/2にする、その前に

Elastic Beanstalk で構築したアプリケーションサーバーを HTTP/2 に対応させるには、ロードバランサーを Application Load Balancer にする必要があります2
ただ、普通に Web の画面からポチポチ作成すると Classic Load Balancerで環境を構築されてしまいます(2017/12/1 現在)。
さらに、悲しいことに環境構築後にロードバランサーの種類は変更できません

なので、一番初めから EB CLI を使って作成する必要があります。
すでに構築してしまっている場合は諦めて別環境を再作成しましょう。

というわけで、自分の備忘録として今回は一から手順を書いていきたいと思います。

手順

AWS CLI / EB CLIのインストール

EB CLI のインストールがまだであれば以下のコマンドでインストールできます。(AWS CLI も後で必要なのでついでにインストールします)

$ pip install --upgrade pip
$ pip install awscli --user
$ pip install awsebcli --user

AWSの設定

aws configure を利用して、AWSへのアクセス情報を設定します。

$ aws configure
AWS Access Key ID [None]: AKIA*********EXAMPLE # アクセスキーを指定
AWS Secret Access Key [None]: wJa***************************EXAMPLEKEY # シークレットアクセスキーを指定
Default region name [None]: ap-northeast-1
Default output format [None]: json

アクセスキーをまだ発行していなければこちらを参考に。

Elastic Beanstalkの環境作成

準備ができたので Elastic Beanstalk の環境を作っていきます。

まず初期設定を行います。
デプロイ用ディレクトリを作成して eb init を実行し、リージョンとアプリケーションを選択します。

$ mkdir hogehoge
$ cd hogehoge
$ eb init

Select a default region
1) us-east-1 : US East (N. Virginia)
2) us-west-1 : US West (N. California)
3) us-west-2 : US West (Oregon)
4) eu-west-1 : EU (Ireland)
5) eu-central-1 : EU (Frankfurt)
6) ap-south-1 : Asia Pacific (Mumbai)
7) ap-southeast-1 : Asia Pacific (Singapore)
8) ap-southeast-2 : Asia Pacific (Sydney)
9) ap-northeast-1 : Asia Pacific (Tokyo)
10) ap-northeast-2 : Asia Pacific (Seoul)
11) sa-east-1 : South America (Sao Paulo)
12) cn-north-1 : China (Beijing)
13) us-east-2 : US East (Ohio)
14) ca-central-1 : Canada (Central)
15) eu-west-2 : EU (London)
(default is 3): 9 # 東京リージョンを選ぶ

Select an application to use
1) [ Create new Application ]
(default is 1): 1 # 新規アプリを選ぶ

Enter Application Name
(default is "hogehoge"): hogehoge # ディレクトリ名がデフォルトになっているのでアプリ名を入力する
Application shibuya_test has been created.

Select a platform.
1) Node.js
2) PHP
3) Python
4) Ruby
5) Tomcat
6) IIS
7) Docker
8) Multi-container Docker
9) GlassFish
10) Go
11) Java
12) Packer
(default is 1): 4 # プラットフォームを選ぶ

Select a platform version.
1) Ruby 2.4 (Puma)
2) Ruby 2.3 (Puma)
3) Ruby 2.2 (Puma)
4) Ruby 2.1 (Puma)
5) Ruby 2.0 (Puma)
6) Ruby 2.4 (Passenger Standalone)
7) Ruby 2.3 (Passenger Standalone)
8) Ruby 2.2 (Passenger Standalone)
9) Ruby 2.1 (Passenger Standalone)
10) Ruby 2.0 (Passenger Standalone)
11) Ruby 1.9.3
(default is 1): 1 # バージョンを選ぶ
Cannot setup CodeCommit because there is no Source Control setup, continuing with initialization
Do you want to set up SSH for your instances?
(Y/n): Y # 対象サーバーにSSHアクセスしたいならYes

Select a keypair.
1) snapmap_bastion
13) [ Create new KeyPair ]
(default is 1): 1 # キーペアを選ぶ

次に環境の作成ですが、ここで注意したいのが、1 つの VPC とアベイラビリティーゾーンが異なる 2 つのサブネットが必要という点です。
可用性確保のためにそういう仕様になっているっぽいです。

つまり先に VPC とサブネットは作成しておく必要があります。
作成ができたら eb create を実行し、環境を作成します。

eb create xxxxxxxx 
    --elb-type application 
    --vpc.id vpc-xxxxxxxx  # 作成したVPC
    --vpc.elbsubnets subnet-xxxxxxxx,subnet-xxxxxxxx  # 作成したサブネット
    --vpc.ec2subnets subnet-xxxxxxxx,subnet-xxxxxxxx  # 作成したサブネット
    --vpc.elbpublic 
    --vpc.publicip

これでサンプルアプリのサイトが構築されます。こんな感じ。

うまく起動したら、環境作成はいったん完了です。

ebextensionsを設置

次にソースコードを HTTP/2 対応させます。

こちらを参考に、ソースコードのプロジェクト直下にHTTPをHTTPSにリダイレクトさせるための .ebextensions ファイルを作ります。

.ebextensions/01_aws.config
option_settings:
  aws:elbv2:listener:443:
    DefaultProcess: default
    ListenerEnabled: true
    Protocol: HTTPS
    SSLCertificateArns: rn:aws:acm:ap-northeast-1:*************************** # 先ほど発行した証明書のARN
  aws:elbv2:listener:80:
    DefaultProcess: http
    ListenerEnabled: true
    Protocol: HTTP
  aws:elasticbeanstalk:environment:process:default:
    Port: 80
    Protocol: HTTP
    StickinessEnabled: true
    StickinessLBCookieDuration: 43200
    HealthCheckPath: /
    HealthCheckTimeout: 30
    HealthCheckInterval: 60
    MatcherHTTPCode: 200
  aws:elasticbeanstalk:environment:process:http:
    Port: 81
    Protocol: HTTP
    HealthCheckPath: /
    HealthCheckTimeout: 30
    HealthCheckInterval: 60
    MatcherHTTPCode: 301
.ebextensions/02_nginx.config
files:
  /etc/nginx/conf.d/redirect.conf:
    mode: "000644"
    owner: root
    group: root
    content: |
      server {
        listen 81;
        rewrite ^ https://$host$request_uri permanent;
      }

Elastic Beanstalk の関連ファイルを無視するよう .gitignore に以下を追加します。

.gitignore
# Elastic Beanstalk Files
.elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml

ソースコードの変更は以上です。

ソースコードの取得

作成したデプロイ用ディレクトリにソースコードを配置します。

$ cd hogehoge # デプロイ用ディレクトリに移動する
$ git init
$ git remote add origin git@github.com:CoachUnited/hogehoge.git
$ rm .gitignore # 自動で作成されてしまうので消す
$ git pull origin master

次に環境変数を設定し、再度デプロイします。

$ eb setenv RAILS_ENV=production HOGE=fuga 〜 # 必要な環境変数をつらつらと
$ eb deploy # ソースコードをデプロイ

これでサイトが正しく表示されれば OK です。
次は SSL 対応をしていきます。

証明書の取得

こちらを参考に AWS Certificate Manager を利用して、サイトドメインで利用する証明書を取得します。

ちなみに、テスト環境もSSL化したい場合はワイルドカード証明書(*.snapmap.jp)を作成すればOKですが、ワイルドカード証明書はルートドメイン(snapmap.jp)には使えないので注意してください。

(なんと、11月の時点でSESでメールを受け取る必要がなくなったそうです :tada:

Route53を設定

あとは Route53 でサイトドメインの Hosted Zone から A レコードを作成して、ElasitcBeanstalk で作成された環境のドメインを割り当ててください。

これですべて完了です。
ブラウザでアクセスして HTTP/2 になっていれば成功です。お疲れ様でした。

まとめ

手順自体はそこそこ長いですが、実際に作業してみるととても簡単に、そして一度作成してしまえば後は AWS におまかせできます。
スモールスタートのプロジェクトには ElasticBeanstalk 超超オススメです!


  1. Vue.js や materializecss の利用も速度向上に大きく寄与しています。 

  2. 画像は CloudFront で配信されているため、Supported HTTP Versions を「HTTP/2, HTTP/1.1, HTTP/1.0」にするだけで HTTP/2 化できます。 

続きを読む