AWS re:Invent 2016 Security Follow Up に行ってきた

こんにちは、ひろかずです。
AWS re:Invent 2016 Security Follow Up に行ってきたので、一筆書きます。

  • 例によって、リアルタイム執筆ですので、誤字、脱字、表記ゆれはご容赦ください。

スタートが19:00のせいか、半分くらいの人出からのスタートです。
最終的には8割位埋まっていました。

[AWSセッション]

「セキュリティ新サービス紹介① AWS Organizations」

桐山 隼人
アマゾン ウェブ サービス ジャパン株式会社
技術本部 レディネスソリューション部
セキュリティソリューションアーキテクト

AWS Organizations は…

  • 現在プレビュー申し込みできます。

サービス概要

複数のAWSアカウントの一元管理

  • アプリケーションや環境、チームのような単位でAWSアカウントをグループ管理
  • グループポリシーを適用

AWSアカウント管理の自動化

  • コンソール、SDK、CLIでの管理操作
  • 管理操作をCloudTrailでロギング

請求の簡素化

  • 複数アカウントの一括請求(Consolidated Billing)
  • Consolidated Billing ファミリーの自動移行

主要なコンセプト(用語)

組織

  • 一元管理可能な複数のAWSアカウントのセット

AWSアカウント

  • 管理される最小単位
  • IAMを用いてAWSリソースへアクセス

マスターアカウント

  • 組織内の他アカウントの作成招待削除ができる特別なアカウント
  • 組織内コントロールポリシーの適用
  • 組織における支払いアカウント

組織単位(OU)

  • 後述

管理ルート

  • 後述

利用の流れ

1. 組織の作成

  • マスターアカウントは支払いアカウントになるので、慎重に決めましょう

2. AWSアカウントの自動作成

  • マスターアカウントからの自動作成

入力要素

  • メアド(必須)
  • アカウント名(必須)
  • IAMロール名(必須)
  • BillingへのIAMユーザアクセス(任意設定)

モードは二つ

  • FullControlモード
  • Billingモード

FullControlモード

  • Billingモードを含む
  • Billingからの昇格には、全てのAWSのアカウントからの同意が必要

Billingモード
– 課金状況を見れる

3. 既存AWSのアカウントの組織への招待

  • 招待されたアカウントは、了解/拒否を選べる。(デフォルト拒否)
  • 参加した瞬間にポリシーが適用される

4. AWSアカウントのグループ化

AWSアカウントは組織単位(OU)に所属できる

  • アプリケーションとか環境とかチームとか
  • 階層になるので、最上位が 管理ルート になる。

5. 組織Controlポリシー

  • 適用対象は、組織全体、OU、AWSアカウント
  • ポリシーは、階層構造に基いて継承される。
  • ローカル管理者からは上書きできない
  • IAMと記述方式は一緒
  • IAMユーザ、IAMロールの権限は、 組織ControlポリシーとIAMポリシーの最大公約数

どんなことができるの?

例:サービスControlポリシー

  • 呼び出しを許可するAPIを定義(ホワイト、ブラックリスト)
  • 業界で認可されたサービスのみを許可する(本番環境)
  • 開発環境はちょっと緩くすることもアリ

「セキュリティ新サービス紹介② AWS Shield」

荒木 靖宏
アマゾン ウェブ サービス ジャパン株式会社
技術本部 レディネスソリューション部 部長

AWS Shieldとは?

  • AWSが管理(マネージ)するDDoSプロテクションサービス

攻撃の種別

  • ボリューム攻撃(UDP反射型:全体の6割を占める:SSDP反射型が一番多い、その他NTPやDNS、SNMPも)
  • ステート管理攻撃(SYNフラッドとか:2割くらい)
  • アプリケーションレイヤー(F5攻撃とか、slowles:15%くらい)

DDoSを緩和する取り組み

難しさはどこに?

  • 複雑な前準備(オペレータやISPとの調整、スクレイピングサービスへのトラフィック誘導)
  • 事前の帯域確保(工事時間がかかる)
  • アプリの見直し(仕様書起こすとか大変)

難しいのはマニュアルでの対応
AWSでスクレイピングしても、ユーザーからはレイテンシ向上にしか見えない。お金は?
AWSの解

  • ありきたり攻撃の自動保護(サービス差別化につながらない部分を持つ)
  • 可用性の確保

DDoS防御はAWSにあらかじめ組み込まれている

  • 各リージョン
  • 常時オン(外部Routingなし)
  • SYB/ACK、UDPフラッド反射型
  • 追加費用無し

お客様の声

  • 大きなDDoS対策
  • 見える化
  • アプリレイヤー守ってよ

AWSの解

  • 無料サービス(Standard)
  • 有料サービス(Advanced)

Standard

BLACK Watch

  • 決定論的フィルタリング
  • 不正な形式のTCPパケットはドロップする
  • インライン検査のスコアリングに基づくトラフィック優先順位付け(優先順位の低い攻撃トラフィックを優先的に破棄)

キャパ追加

  • Routingポリシーで内部的に分散

検知と緩和を継続(ヒューリスティック異常検出:通常のベースラインから異常を知る)

  • リクエスト数/s
  • 送信元IP
  • URL
  • UserAgent

Advanced

AWS内で完結する
常時オン
レイテンシは最小限
手頃な価格
対応しているのは以下サービス(EC2は入っていない。TCPのみ。UDPは使えない。)

  • ALB
  • CLB
  • CF
  • Route53

L7は、AWS WAFの利用

  • Standard:セルフサービス(自分でカスタムシグネチャを書く)
  • Standard/Advanced:DDoSエキスパートチームからのアドバイス
  • Advanced:積極的DRT(DDoS Response Team)が関与する

DRT(DDoS Response チーム)はなにやるの?

  • 常時モニタがDRTを呼び出す
  • トリアージ
  • DRTチームがルールを書いて提供

レポート

  • ニアリアルタイムで、パケット情報見れる(サンプル取得:全部じゃない)

DRTへのコンタクト

  • 第一報はサポート経由でしてね(エンタープライズサポート)

費用

  • Standard:アドバイスに基づくスケール費用は 有料 :月額課金なし
  • Advance:アドバイスに基づくスケール費用は無料:$3000/月:一年継続コミット

[パートナーセッション]

「スポンサー側から見た AWS re:Invent の醍醐味と感じたセキュリティの流れ」

南原 正樹様
トレンドマイクロ株式会社 パートナービジネス推進本部
アライアンスパートナーグループ 担当課長代理

Trend MicroとAWS re:invent

ダイヤモンドスポンサー
並び順は一番最後(アルファベット順)
出展したのは、DeepSecurityとDSaaS
スポンサーアクティビティ(セッションは各1時間:youtubeに上ってるよ)

  • AWSに特化したセッション
  • CISOに聞いてみよう(コンプライアンス、レギュレーションの話)
  • ブースは、DevSecOpsを安全な空の旅をモチーフに表現

スポンサーで感じたこと(日本 vs 北米)

  • 日本は、捕まると長いので敬遠される。アンケート欠かされる。製品売ろう。
  • 北米は、コンタクト獲得と製品が分業。スキャンしたらノベルティたくさんくれる。コンプライアンス準拠。ビジネス視点。

来場者

  • 北米はカジュアルに自分のアイデアを語ってくる。Tシャツ大好き。

re:inventで見た気になるセキュリティの流れ

Security Automation

セキュリティオペレーションの自動化を目指すことでセキュリティ運用コストの低減と対応の迅速化を目指す。
セキュリティはインフラ構築と分離しない。併せて考える。

  • AWS WAFとLambdaとの連携(ブラックIPとか)
  • CloudTrailのIPを検査とか
  • GitHubに公開されてるよ!

Security for ServerLess

サーバレスを主体として組み上げた環境でのセキュリティのポイント

  • IAMを中心とした権限管理
  • 許可されたトラフィックの精査(サニタイズからログの精査まで)
  • 例えば、CF/WAF前、API Gateway中、Lambda後ろ、最後はDB

最後に

AWSはエコシステムとマーケットチャンス
セキュリティもニューノーマルからスーパーパワー
積極的に新しい取り組みに挑戦しよう(日本の方がセキュリティに深く真面目に考えている)

「攻撃傾向と企業が取るべきセキュリティ施策 in Las Vegas」

佐藤 裕貴様
三井物産セキュアディレクション株式会社
Alert Logic事業部 マネージャー

大橋 和正様
三井物産セキュアディレクション株式会社
プロフェッショナルサービス事業部
アカウントマネージャー

昨今の攻撃傾向

海外(Verizonレポート)

  • WebAPに対する攻撃が増えてきている。(前年度4倍)

日本(IPAレポート)

  • インターネットバンキング、標的型、ランサム、Webサービス不正ログイン、サイト改ざん

事前対策

セキュアコーディング
脆弱性診断
アクセス分離(SG、NACL、ELB)、2要素認証(IAMロール、MFA)

  • 監視ポイントが限定できて運用負荷を軽減

ログの収集

  • 平時のログ傾向を把握する
  • ログは不変であること(削除/改ざんの防止)
  • 以下の状態であること(アクセス性,検索性,継続モニタ)

セキュリティ運用の最適化

  • L1:SG,IAM
  • L2:IDS/WAF,脆弱性スキャン
  • L3:CloudTrail,SIEM

[エンドユーザー様セッション]

「ユーザーからみたre:Invent のこれまでと今後」

宮崎 幸恵 様
株式会社リクルートテクノロジーズ
ITソリューション統括部 インフラソリューション2部 RAFTEL2G

  • 2012年からre:invent皆勤賞
  • 一年おきにセキュリティ系サービスのビッグウェーブが来る傾向
  • Organizationsキター
  • 現地は熱い!体感!感じろ!

バッテリー切れでライブでかけませんでした!すみません!

「AWS re:Invent の衝撃 ~Large Scale Violence & Security Culture Shock~」

大橋 衛様
KDDI株式会社
技術統括本部 プラットフォーム開発本部
アジャイル開発センター フレームワークG 課長補佐

  • 現地は熱い!体感!感じろ!飯はマズイ!
  • 予防、検知、回復の話
  • 日本は、予防に重点を置いている制約型.検知が最小限で、回復は手動と尻すぼみ
  • 海外は、予防はsoso、検知にかなり力を入れて、回復は自動化とパワーのかけ方が日本と逆

バッテリー切れでライブでかけませんでした!すみません!

雑感

モバイルバッテリーがそろそろ必要なようです。

続きを読む

CloudWatch専用の通知bot、marbotを使ってみた

CloudWatchのslack連携というとLambdaでpostが鉄板ですが、より良くしようとするとLambdaをメンテしていく必要があります。

メッセージを視覚的に良くするならslackのattachmentsなどで工夫が必要だし、用途別に通知を分けるとかなると、Lambdaに分岐書いたりとかLambdaをコピペしたりなど。。。

たまたま、marbotというCloudWatchの専用botを見つけ、試しに導入してみたら結構イイ感じだったのでその紹介です。

marbotについて

公式サイト – marbot

CloudWatch専用の通知botで、アラートのslack通知や簡易的なエスカレーションがあります。
今のところは有料プランはなく、費用ゼロで使用できます。

AWSのChatbotコンテストで入賞してたみたいです。
https://aws.amazon.com/jp/blogs/news/congratulations-to-the-winners-of-the-serverless-chatbot-competition/

marbotの機能

marbotのFeaturesに説明がありますが、ザックリまとめると以下になります。

  • Slackへのアラート通知
  • アラートのエスカレーション
  • アラートの管理(チェック、パス、クローズ)
  • Dailyのアラートサマリ

導入方法

ここではCloudWatchの設定(SNSのtopicも登録済み)は済んでいるという前提で進めます。

  1. marbot# Add to Slackがあるので、認可してSlackに追加
  2. slackのchannelにmarbotを追加
    • 追加するとSNSで設定するEndpointが表示されます
    • 指定したchannelに参加しているメンバーがmarbot通知の対象になり、これについては後述
  3. CloudWatchのnotificationで指定しているSNSにsubscriptionを追加
    • HTTPS形式を選び、2.のEndpointを設定
  4. slack上でYou completed the SNS topic subscriptionとなっていれば、SNS連携は完了

マスクしてますが、ここにEndpointが表示されます。

invite-image

あとはCloudWatchのイベントが発火すると、marbotから通知がslackへ送信されます。

marbotのアラート通知

marbotからのアラート通知ですが、基本はchannelメンバーへのダイレクトメッセージです。
ここではアラート発生の流れと、アラートへのアクション、エスカレーションについて説明します。

アラートへのアクション

marbotからのアラート通知には、Acknowledge,Pass,Closeいずれかのアクションが出来るようになっています。
実際のインシデント想定をした場合、こんな感じでしょう。

  • Acknowledge : アラートの認知、障害内容のチェック開始など
  • Pass : 誰かにパス
  • Close : 終了

アラート発生のフロー

実際にやってみたところ、以下の流れになりました。

  1. アラート発生 🔔
  2. channelのオンラインメンバーにmarbotからアラート通知のダイレクトメッセージ
    • オンラインメンバーが複数の場合、誰か一人に通知するっぽい
  3. アラートへのアクション
    • Acknowledge => marbotがAcknowledgedと認知
    • Pass => marbotが次のオンラインメンバーへ通知
    • Close => marbotがClosedと認知

marbotのアラート通知はこんな感じになります。

alert-image

エスカレーション

marbotは特定の条件で、エスカレーションとしてchannelへ全体通知します。

  • オンラインメンバーが誰もいなかった
  • 通知を受けたメンバーがアクションを起こさなかった(5分固定っぽい)
  • 全員がPassした

つまり、誰も気づかなかった、誰も行動を起こさなかったらchannelへの全体通知されることになります。

escalate-image

Dailyのアラートサマリ

marbotは一日のアラートを集計し、日々channelへこんな感じのまとめを投稿してくれます。
(これはテストで適当に発生させたアラート)

summary-image

marbotの運用を考えてみる

marbotはslackのchannelをグループ管理としてみなしているようです。
なので、アラートを受けるべきメンバーを集めたchannelにmarbotを入れれば、アラートのコントロールもしやすくなるかと思います。

例えばこんな感じにわけてみるとか。

  • myservice : サービス関係者全員
  • myservice-dev : サービスのエンジニアAll
  • myservice-incident : 通知に必ず対応すべき関係者
    • marbot参加

ちなみにインテグレーション数ですが、channel個別にmarbotを招待できるので登録は1つで済みます。

Architectureについて

この記事に、アーキテクチャの概要が説明されていました。
https://cloudonaut.io/marbot-aws-serverless-chatbot-competition/

  • AWS Lambda and Amazon API Gateway
  • Amazon DynamoDB
  • Amazon Kinesis Streams and Amazon Kinesis Analytics
  • Amazon SQS
  • Amazon SNS and AWS CloudWatch

LambdaとAPI Gateway、DynamoDBはサーバレスの鉄板なので、すぐに想像できましたが、目を引いたのはSQSとKinesisです。

記事を読んで理解できましたが、アラート通知でn分スルーされたらchannelへ通知などを実装するために、SQSをタイマーとして使っているようです。なるほど。

KinesisはDailyサマリーの算出用途ですね。

まとめ

CloudWatchの通知をSlackに連携していない、またはslackに連携しているけどアラートを放置しがちなケースでは、marbotのエスカレーションが効果的になるかも。

続きを読む

API Gateway+Lambda+Serverless+TravisでAPI開発のCI/CDパイプラインを構築する

概要

スクリーンショット 2017-02-21 10.15.16.png

API GatewayとLambdaを使ったAPI開発時のCI/CDについての記事です。
僕がnode.jsを普段から使用しているため、解説はnodeがベースになっています。

https://github.com/horike37/serverless-api-integration-test-sample
ソースはすべてGitHubに上がってますのでそちらもご確認ください。

CIで実施する内容

以下の内容をCIとして行うことを考えます。

  • ESLintによる構文チェック
  • Mocha, Chaiを使用したユニットテスト
  • APIをAWSへデプロイしてテストを行うインテグレーションテスト

CDで実施する内容

以下のようなルールでデプロイのサイクルを回します。

  • Gitのdevelopmentブランチへのpushをテスト環境へのデプロイと想定。

    • 構文チェックとユニットテストを実施。
    • ビルドが通れば、ServerlessのdevelopmentステージにAPIをデプロイ。
  • Gitのmasterを最新ソースの集約場所として想定して、pushを実施。
    • 構文チェックとユニットテストとインテグレーションテストを実施。
    • ビルドが通ってもデプロイは実施しない。
  • Gitのtagへのpushを本番環境へのデプロイと想定。
    • 構文チェックとユニットテストとインテグレーションテストを実施。
    • ビルドが通れば、ServerlessのproductionステージにAPIをデプロイ。

Continuous Integration

ESLintによる構文チェック

http://eslint.org/
JavaScript界隈ではもっともメジャーな構文チェックツールだと思います。
構文ルールを定義して、構文エラーや記法を統一させることでソースの可読性や品質を向上させます。

https://github.com/horike37/serverless-api-integration-test-sample/blob/master/.eslintrc.js#L2
今回はルールとしてArbnbを採用しました。どの構文ルールを採用するかはチームの好みで決めれば良いと思います。

Mocha, Chaiを使用したユニットテスト

単体テストを実施します。メソッドや関数毎に入力値と出力値をチェックして関数単位での品質チェックを行います。

https://github.com/horike37/serverless-api-integration-test-sample/tree/master/lib
このようにLambdaがメインで実行する部分から、ビジネスロジックをclassに切り出してあげるとテストが書きやすくなるのでそうすることが多いです。

APIをAWSへデプロイしたテストを行うインテグレーションテスト

インテグレーションテストはサーバレスアーキテクチャの特徴とも言えるテストです。

サーバレスアーキテクチャは複数のサービスで構成されているケースが多く、ユニットテストだけではアーキテクチャ全体のテストを網羅できません。実際のクラウド環境へリソースをデプロイして結合テストを行い、すべて成功すれば、それを破棄します。

このデプロイ管理にはServerlessを使用しています。

https://github.com/horike37/serverless-api-integration-test-sample/blob/master/integration-test/test.js#L18
https://github.com/horike37/serverless-api-integration-test-sample/blob/master/integration-test/test.js#L41
テストの最初と最後にアーキテクチャのデプロイと削除を行っています。このメソッドの実体は、sls deploysls removeです。

it('should return correct values from all apis', () => {
     const testEndpoint = `${endpoint}/hello`;

     return fetch(testEndpoint, { method: 'GET' })
       .then(response => response.json())
       .then((json) => expect(json.message).to.equal('Go Serverless v1.0! Your function executed successfully!'));
  });

https://github.com/horike37/serverless-api-integration-test-sample/blob/master/integration-test/test.js#L35-L37
そしてここがテストのメインの部分です。デプロイされたAPIへリクエストを送り、その返り値をchaiでチェックを行います。
こうすることで何本APIを作ったとしても自動テストが可能になるというメリットがあります。

Continuous Delivery

デプロイスクリプト

以下の様なスクリプトでデプロイを実施します。Gitのtagにpushされた際にproduction環境へ。Gitのdevelopmentブランチにpushされた際にdeployment環境へ。それぞれビルドが正しく通ればデプロイされるようになっています。

bin/deploy.sh
#!/bin/bash
set -e
BRANCH=${TRAVIS_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}
if [[ $TRAVIS_TAG ]]; then
  STAGE="production"
elif [[ $BRANCH == 'development' ]]; then
  STAGE="development"
fi

if [ -z ${STAGE+x} ]; then
  echo "Not deploying changes";
  exit 0;
fi

echo "Deploying from branch $BRANCH to stage $STAGE"
npm prune --production  #remove devDependencies
sls deploy --stage $STAGE --region $AWS_REGION

ServerlessのIAM権限

TravisにてServerlessが動作するように専用のユーザを発行します。今回は最低限で以下の様なIAMポリシーを与えています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt543534534",
            "Effect": "Allow",
            "Action": [
                "iam:GetRole",
                "iam:CreateRole",
                "iam:DeleteRole",
                "iam:PutRolePolicy",
                "iam:DeleteRolePolicy",
                "iam:PassRole",
                "logs:*",
                "s3:*",
                "lambda:*",
                "cloudformation:*",
                "apigateway:*"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

そして、TravisからAWSリソースへアクセスするためにcredentialをTravisへ設定します。
もちろん生のcredentialを.travis.ymlへ追記することはご法度です。

Travisのコマンドにより暗号化し、credentialがTravisの環境上でしか使えないようにします。

$ travis encrypt AWS_ACCESS_KEY_ID=xxxxxxxxxxx --add
$ travis encrypt AWS_SECRET_ACCESS_KEY=xxxxxxxxxxx --add

このコマンドにより。.travis.ymlに暗号化した状態でcredentialが設定されます。
これはTravis上でビルドを実行した際に環境変数として設定されます。

Travisの設定ファイル

最終的にはこんな感じに.travis.ymlが仕上がりました。

travis.yml
language: node_js
node_js:
  - '4.3'
env:
  global:
    - AWS_REGION=us-east-1
    - SLS_DEBUG=true
    - secure: lSxO7tZ0c/FA8VL72042dqQZ+tRjsS93iVYxMr1ghP/0tBxdmrhhYdAD9UrSv/Kk+Y1jRlkpQ2uaARHoy+6ZqmhchX32HpIYBQVJ/ntMSgv37gFbrNTOfSFoATMTRy6RT2UKaIWAa4xnmDxaQNFPx4X5l9Y25RdivoR+WXrEPd4eCCTVL/23bABSIySSTs+VGqQIppE4Jw5ibbcSoTLsuj00nK+VrmYHNlTSiEuKIxgFC1Ix0hqayJ/kely0DqYW/CY/vCCf0V4yazJo9fG1EFfrHsSIAKKeGRY7WMnLPJ7hJGwRiVV1/atMx/5kRPKOADcRTfoh3noXS3/sd1hbGjTwnJVRVrYiUocHwuNbo1TpW1On85jXEdvnKY9JYelFEnXnWn6A2bRMhgL/zul/WuSPCGq7HpsGMRhXrEiEYJ9YhnVNiUTaV2amoOClMOpHFnStMfTJVg7NJ8mBF4XOzODvhAzyPFDWdJ94Ejl1LAnGOAp/wBQVbFswKPdwdosFU6LyirQkA4k0q7C4zXYywyQtrY7H9w7FtKo+U4596GQAQtvzQz6GS42c1WBX0fIrMu1VXc+KmwCUBEVmvBxLS7c0DJUI61atDFGq7788K7IMWw83lIFjJULdwv1qU4uBi3MvPm2OHCdRAzBGEYIC87zfcYI/gi41rh3bj/C0wiI=
    - secure: LIh0lkl/t72EbMd47WgEXqnoG3REp+oPhIfDR5Cs8SMO4sacvo2j4pRkRKIwwpdKozxdgLEMl1rwDoHyYPH77FzvDnwiufpaYgQs278wmi+6ZvoC9nhgdn2sT7cFnYuYAO8dC7G/NHzXogAVmiObf3I+hzNLjDqWwWVjqPm41p4P4c2EJUVo0nVlcaUOf8elS1j6zp+ZL1EQo4Fm4IumDgNpZUP4bSq8CcVPvF0ynMlslI8XNMnBOiYmG+644qILScyPK1Q2SPdMLqL5YXHuYfE0aCpFcWOcNZIalBmaxPqFNW+QHQvaYiwoENx/i91KS3U2mqfcNYY4o9viih47PFsaddvtBeB83Wfls7GIZ/XmvBKREuS5Gwhz930DbAvUNQT1ylS9Y6TTIIWIbe3Qmv6ngd9TrDHlnbVhQYgar9ur+TgvLyhs5YLAeLn85c3Z3GYN5JUuCq5bclzh4I+myagaWJPoujS1nT+vKLUW8hu5MkxApn+uFUo/OIW1WG4qho/ddh7RoJbI9oTebWjpXhLwd1pSET1yBVSEetORluh0pW7r5A425Rm80B58Mg/x0NNmM1x6DBrCZ5R8d+Bam6C7P4WxxrRex7vFcqRWeKlJNINO+rtPW3Uuo/s9yC9980uQ00eY0kqhouR8ol6xs/4Xye1AHovPR2unzEe37fM=
before_install:
  - chmod +x ./bin/deploy.sh
  - npm i -g serverless@1.6.1
install:
  - travis_retry npm install
script:
  - npm run lint
  - npm run test
  - if [[ $TRAVIS_BRANCH == "master" ]] || [[ $TRAVIS_TAG ]]; then npm run integration-test; fi
after_success:
  - cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js && rm -rf ./coverage
  - ./bin/deploy.sh

実行結果

developmentブランチへのpush

https://travis-ci.org/horike37/serverless-api-integration-test-sample/builds/203491545
ESLintとユニットテストのビルドが成功し、developmentステージへデプロイされています。

masterブランチへのpush

https://travis-ci.org/horike37/serverless-api-integration-test-sample/builds/203489525
ESLintとユニットテストとインテグレーションテストが成功しましたが、デプロイはされていません。

tagへのpush

https://travis-ci.org/horike37/serverless-api-integration-test-sample/builds/203484838

0.1というタグにpushをしています。
ESLintとユニットテストとインテグレーションテストが成功し、puroductionステージにデプロイされています。

如何でしたでしょうか。こんな感じでCI/CDパイプラインを構築することですべてが自動化され品質も担保されるようになりました。よろしければ是非参考にしてみてください!

では、良いパイプラインライフを!

参考文献

CodePipelineでServerless Frameworkのデプロイを管理する

続きを読む

Amazon Elastic BeanstalkでサクッとElixir製サーバーをAWSにデプロイする

はじめに

Amazon Elastic Beanstalk では、 Dockerを使ってよしなにアプリケーションをAWSにデプロイできる。
Docker ImageがS3に上がり、サーバーはEC2にデプロイされる。
これを使ってElixir製のping叩いたらpong返してくるだけのアプリをデプロイしてみる。

mix で新規プロジェクトを作成

今回はelixir:1.4.1を用いる。

$ mix new testex

ライブラリの追加

今回は cowboy(HTTPサーバ), poison (JSON ライブラリ), distillery(release 用ライブラリ)を使う。

mix.exs に以下のように追加して

defmodule Testex.Mixfile do
  use Mix.Project

  def project do
    [app: :testex,
     version: "0.1.0",
     elixir: "~> 1.4",
     build_embedded: Mix.env == :prod,
     start_permanent: Mix.env == :prod,
     deps: deps()]
  end

  def application do
    # Specify extra applications you'll use from Erlang/Elixir
    [applications: [:logger, :cowboy, :poison],
      mod: {Testex,[]}]
  end

  defp deps do
   [{:cowboy, github: "ninenines/cowboy"},
    {:poison, "~> 3.0.0"},
    {:distillery, "~> 1.1.0", runtime: false}]
  end
end
$ mix deps.get
$ mix release

$ _build/dev/rel/testex/bin/testex console

これで elixir が走るようになるはず。

cowboy で http サーバを走らせる

ポートやルーティングの設定

lib/testex.ex
defmodule Testex do
  def start(_type, _args) do
  import Supervisor.Spec, warn: false
  children = []
  dispatch = :cowboy_router.compile([
      {:_, [
           {"/ping", Testex.Handlers.Ping, []},
      ]}
    ])
    {:ok, _} = :cowboy.start_clear(:http,
                                   100,
                                   [{:port, 4000}],
                                   %{env: %{dispatch: dispatch}})
  opts = [strategy: :one_for_one, name: Testex.Supervisor]
  Supervisor.start_link(children, opts)
  end
end

ホントに pong を返すだけ

lib/testex/handlers/testex.ex

defmodule Testex.Handlers.Ping do
    def init(req, opts) do
    body = %{"type" => "pong"} |> Poison.encode!()
    req2 = :cowboy_req.reply 200, %{"content-type" => "application/json"}, body, req
    {:ok, req2, opts}
    end
end

AWS用のDocker 設定ファイルを作成

Dockerrun.aws.json
{
  "AWSEBDockerrunVersion": 1,
  "volumes": [
    {
      "name": "testex",
      "host": {
        "sourcePath": "/app"
      }
    }
  ],
  "containerDefinitions": [
    {
      "name": "testex",
      "essential": true,
      "portMappings": [
        {
          "hostPort": 80,
          "containerPort": 4000
        }
      ]
    }
  ]
}

WORKDIR として /app を明記するのがポイント

Dockerfileの作成

Dockerfile

# elixir の Docker Image を引っ張ってくる。今回は Phoenix を使わないので node 等は不要
FROM trenpixster/elixir:1.4.1

# WORKDIR の作成
RUN mkdir /app
WORKDIR /app

# mix deps.get
ADD mix.* ./
RUN MIX_ENV=prod mix local.rebar
RUN MIX_ENV=prod mix local.hex --force
RUN MIX_ENV=prod mix deps.get

# Install app
ADD . .
RUN MIX_ENV=prod mix release

# Exposes this port from the docker container to the host machine
EXPOSE 4000

# distillery の run command を実行する。今回はフォアグラウンドで
CMD MIX_ENV=prod _build/prod/rel/testex/bin/testex foreground

eb コマンドの実行

あらかじめ eb をインストールする。 pip で入る。
http://docs.aws.amazon.com/ja_jp/elasticbeanstalk/latest/dg/eb-cli3-install.html

これで、

$ eb init # awsアカウント情報を入力
$ eb create # サーバ名等を入力。Dockerfileがあるとeb側でよしなに判断してくれる。
$ eb deploy testex

すると本番にデプロイされる。

おわり

これはお試しですが、実際に運用するなら、本番・開発環境を切り分けたり、CORS対策としてAPI Gatewayなどを別途で使う必要あり。

サンプルコード

https://github.com/GigantechHQ/ex-server-sample

続きを読む

Lambda + Apex + API Gateway Slackで緊急連絡先を表示するslashコマンドを作ろう

概要

  • Slackで/tel_listと打ったら緊急連絡先が出るようにする
  • serverless実装

構成図

スクリーンショット 2017-02-20 13.59.25.png

  • slackでtel_list打つ
  • api gatewayがPOSTリクエスト受け取る
  • 指定したlambdaを実行。そこでKMSでslackのtoken認証する
  • lambdaでpython実行し、slackに結果を送る

内容

①apexでlambda関数を管理する

  • ここではKMSのtokenがまだできてないので、コードは書きません
  • apexの使い方やインストール等はこちらをご覧ください
$ mkdir apex_slack_tel_list && cd apex_slack_tel_list
$ apex init
Project name: apex_slack_tel_list
Project description: apex_slack_tel_list is able to show TEL list when anyone was in trouble

### 一度deployする
$ cd apex_slack_tel_list
$ apex deploy

②IAM(KMS)の設定

  • IAM->暗号化キー->リージョンを選んでください->キーの作成

    • エイリアス:apex_lambda_slack_tel_list
    • キーマテリアルオリジン:KMS
    • 次へ
      • タグの追加はなしで次のステップへ

        • apex_slack_tel_list_lambda_function
        • キーの管理者が削除できるようにをチェック入れる
        • 次のステップへ
          • apex_slack_tel_list_lambda_function
          • 次のステップへ

スクリーンショット 2017-02-20 14.21.27.png

  • 完了したらここのARN部分を控えてください。
  • arn:aws:kms:リージョン名:数字:key/key_idとなってます。

③IAMのroleにARNに対する権限追加

  • IAM->ロール->apex_slack_tel_list_lambda_function->ポリシー名->編集
        {
            "Effect": "Allow",
            "Action": [
                "kms:Decrypt"
            ],
            "Resource": [
                "arn:aws:kms:ap-northeast-1:xxxxxxxxxx:key/xxxxxxxxxxxxx"
            ]
        }

④SlackでSlashコマンドの登録

  • slackのapp->Apps & Integration->Slashで検索->Add Configuration->/tel_list で登録

スクリーンショット 2017-02-20 14.24.58.png

  • 登録が終わったらtokenを控えてください

⑤Slackのtokenを暗号化

$ aws kms encrypt --key-id arnに書いてるkey_id --plaintext="slackのtoken"
{
    "KeyId": "arn:aws:kms:ap-northeast-1:xxxxxxxx:key/xxxxxxxxxxxx",
    "CiphertextBlob": "暗号化されたtoken"

⑥apexでコードを書いてdeployする

$ vim functions/hello/main.py
# -*- coding: utf-8 -*-
import boto3
from base64 import b64decode
from urlparse import parse_qs
import logging

ENCRYPTED_EXPECTED_TOKEN = "暗号化されたtoken"

kms = boto3.client('kms')
expected_token = kms.decrypt(CiphertextBlob = b64decode(ENCRYPTED_EXPECTED_TOKEN))['Plaintext']

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def handle(event, context):
    req_body = event['body']
    params = parse_qs(req_body)
    token = params['token'][0]
    if token != expected_token:
        logger.error("Request token (%s) does not match exptected", token)
        raise Exception("Invalid request token")
    tel_list = [
        'sion:000-0000-0000',
        'cojp:111-1111-1111',
    ]

    format_tel_list = 'n'.join(tel_list)
    return { "text": "```%s```" % (format_tel_list) }
### いらないファイルを削除し、最終的にはこんなディレクトリになります
$ tree
├── functions
│   └── hello
│       └── main.py
└── project.json

### deploy
$ cd apex_slack_tel_list
$ apex deploy
  • lambdaからテストしてみましょう。成功するはずです。

⑦API GatewayでAPIを作成

  • API GATEWAY->API->APIの作成

    • 新しいAPI
    • API名:apex_slack_tel_list
    • 説明:lambda経由でslackで緊急連絡先を表示させてます
    • 作成を押す

スクリーンショット 2017-02-17 15.51.10.png

⑧リソースを作成する

  • apex_slack_tel_list->アクション->リソースの作成

    • リソース名:slack
    • リソースパス:slack
    • リソースの作成

スクリーンショット 2017-02-20 14.38.34.png

⑨メソッドを作成する

  • POST
  • apex_slack_tel_list->アクション->メソッドの作成
    • Lambda関数
    • region: 選んでください
    • Lambda関数: apex_slack_tel_list_hello
    • 保存を押す
  • slack/->出来上がったPOSTをクリック->統合リクエスト->本文マッピングテンプレート
    • application/x-www-form-urlencoded
    • テンプレートに書きを登録し保存。(今回は不要ですが、urlDecodeで日本語をPOSTしてもエラーが起きないようにしてます)
{ "body": $util.urlDecode($input.json("$")) }

⑩API-Gatewayのdeployする

  • リソース->アクション->APIのdeploy

    • デプロイされるステージにprod
    • デプロイ
  • 作成が終わったら、ステージ->prod->URLの呼び出し のurlを保存。
  • これを先ほどのslackに登録してください。

⑪実際にslackで打ってみよう

/tel_list
  • すると下記のように自分自身だけに緊急連絡先が表示されるかと思います。

スクリーンショット 2017-02-20 14.49.35.png

最後に

  • Hubot用にサーバを立てなくて良いし、serverless便利

続きを読む

Serverless Framework + TypeScriptでデバッグを行う

概要

AWS Lambda + TypeScriptで開発を行う際にデバッグを行う方法をまとめた記事です。

環境情報

この記事では以下の環境で開発を行っています。

今回詳しい環境構築の方法は割愛させて頂きます。(私自身がまだ試行錯誤している段階なので、情報をまとめ次第環境構築の記事を別途投稿します。)

下記に現在利用しているgithubのrepositoryを記載しておきます。
※このrepository自体まだ試行錯誤を繰り返している段階ですので内容は徐々に変化していく点はご了承下さい。

なお、npm packageを管理する為に yarn を利用しています。

ローカルでAWS Lambdaを実行出来る環境を作る

効率良くデバッグを行うにはローカルでAWS Lambdaを実行出来る環境を構築する必要があります。

ローカルでAWS Lambdaを実行する為には今のところ2種類の方法があります。

今回この記事で扱うのは serverless-webpackserverless webpack serve を利用する方法です。

serverless-webpack のインストールを行う

まずは serverless-webpack のインストールを行います。

プロジェクトルートで以下のコマンドを実行しましょう。

serverless-webpackのインストール
yarn add serverless-webpack

次にserverless.ymlに以下の記述を追加します。

serverless.yml
plugins:
  - serverless-webpack

node-inspector をインストールする

node-inspector はNodeをChrome上でステップ実行出来るようにするツールです。

プロジェクトルートで以下のコマンドを実行しましょう。

node-inspectorのインストール
yarn add node-inspector

source-map-support のインストール

TypeScriptはその性質上、TypeScriptのコードがそのまま解釈されて実行されている訳ではなく、一旦JavaScriptにコンパイルされ、コンパイルされたコード(JavaScript)が実行されるという流れになります。

その為、ブレークポイントを設定する際にTypeScriptのコードに対して設定出来ないと非常に不便です。

それを可能にするのが sourceMap という仕組みです。

source-map-support はNode上でそれを利用出来るようにする仕組みです。

以下のコマンドでインストールを実行しましょう。

source-map-supportのインストール
yarn add source-map-support
yarn add @types/source-map-support

さらにLambdaが実行される前に以下のコードを追加してしておきます。

Lambdaが実行される前に以下のコードを追加しておく
import * as sourceMapSupport from "source-map-support";

sourceMapSupport.install();

これで sourceMap が出力されるようになります。

ここまでで事前準備は完了となります。

serverless-webpack でローカルサーバを起動する

以下のコマンドでローカルサーバを起動させます。

ローカルサーバの起動
serverless webpack serve

成功すると下記のような形でサーバが起動します。

serverless_webpack_serve.png

ローカルサーバのprocessIDを取得する

次にローカルサーバのprocessIDを取得します。

processIDを取得
pgrep -l node

ここでは 2110 node が出力されたと仮定します。

実行中のprocessIDに対してUSR1シグナルを送信する

Node でデバッガを起動する為にはNodeの実行processに対してUSR1シグナルを送信します。
そこで先程取得したprocessIDに対してUSR1シグナルを送信します。

kill -s USR1 2110

すると実行中のサーバコンソールに Starting debugger agent.
Debugger listening on port 5858
と表示されるかと思います。

serverless_webpack_serve2.png

node-inspector を起動

先程インストールしたnode-inspectorを起動します。

node-inspectorの起動
./node_modules/.bin/node-inspector

node-inspector.png

これで http://127.0.0.1:8080/?port=5858 にChromeでアクセスすれば良いのですが、私の場合はvagrantのIPに192.168.33.60を割り当てていたので以下のURLで接続を行います。

http://192.168.33.60:8080/?port=5858

接続してしばらく時間が経つと下記のような画面が表示されます。

node-inspector2.png

ブレークポイントを指定して実行する

左側に表示されているディレクトリツリーに webpack:// と記載されている部分があります。
ここからブレークポイントを設定してLambdaの実行を行ってみます。

今回の例では下記の画像のように src/functions/auth.ts 125行目にブレークポイントを設定しています。

node-inspector4.png

実行すると下記のようにブレークポイントで止まっている事が確認出来るかと思います。
右側のメニューから変数の中身等も視覚化されています。

node-inspector5.png

※右上の再生ボタンを押すと最後まで実行が完了します。

最後に

以上がServerless Framework + TypeScriptでデバッグを行う為の手順でした。
この記事では serverless-webpack を利用していますが、近々 serverless-offline を利用する方法に切り替えようと思っています。

理由は下記の通りです。

  • serverless-webpack の開発速度が2017年に入ってから若干落ちている。
  • serverless webpack serve はAPI Gatewayの動作と若干異なる部分がある。(Custom Authorizerが無効になっている等)

serverless-offline を利用する場合、 serverless-webpack に頼らずに、普通にwebpackを使ってbuildを行う形に変更しようと考えています。

※これらの内容は構築が完了次第、別の記事を投稿しようと思います。

とは言えこの記事の内容でも、都度AWSにデプロイしてconsoleでデバッグを行うよりは遥かに効率が良いので、同じ組み合わせで開発を行っている方々の参考になれば幸いと思い、この記事を書かせて頂きました。

それから私自身、TypeScriptやServerlessアーキテクチャに対して高度な技術を持っている訳ではありません、これを見て何かお気づきの方はコメントを貰えると嬉しいです。

以上になります。最後まで読んで頂きありがとうございました。

続きを読む

Tensorflow 1.0でGPU利用 with AWS

そろそろ春の足音がしだして、相変わらずの何もできてないっぷりに愕然とする今日このごろです。

さすがに何もしていないのはまずいので、以前触った TensorFlow を再び触り始めています。
相変わらずのGoogleクオリティで、APIが色々と変わっているような気がしますが、いろいろ忘れているので調度良いかなと。

何か一つ作ってみようと、作ってみている最中です(いずれ記事に・・・)が、DeepLearningとかコモディティ化してきていると言われているとはいえ、理解しながら作ろうとすると、もれなく数式とコンニチワするので、文字通りに頭の痛い日々です。でも楽しい。

Amazon LinuxでGPU利用する

今作ってみているものは、学習をCPUだけでやると時間がかかってしょうがない+学習用データが大きすぎて洒落にならないため、GPUで実施したくなります。
GPUは一応部屋のPCについているとはいえ、CUDAのインストールトラウマもあって、流石に普段使いのPCにインストールするのが面倒です。

ちなみに現時点で、CPUだけで学習すると1ステップ辺り、1データで2分とかかかります。20000ステップとか考えたくもないですね。電気代的な意味で。

AzureでもGCPでもいいけれども、使い慣れてるAWSのGPUインスタンスを使ってみます(使ったことがなかった)。
(というか、個人とかの場合クラウドを使わないと現実的な時間で終わんないような)

まともに利用するとかなりの金額になりそうだったので、SpotFleetで用意しました。Spot Fleetになってから初めて利用しますが、ちゃんとドキュメントを読まないと、capacityの意味が?ってなりますね。でも6割り引きくらいで利用できるのはあまりに魅力的・・・。

さて、CUDAはつい最近知識をアップデートしましたが、かなりインストールが楽になっていてびっくりです。
そのなかでも楽をするために、Ubuntu16.04のAMIを利用することにしました。CUDA/cuDNNのライブラリをnvidiaのページからダウンロードします。cuDNNはアカウントが必要なので、作る必要があります。

CUDAライブラリは、runfileにしておくと楽です、が、今回はdebにしてしまったので、次のようになります。

$ sudo dpkg -i <ダウンロードしたパッケージ名>
$ sudo apt-get update
$ sudo apt-get install cuda

cudnnライブラリは、cudaをインストールした先に展開します。debでインストールした場合は /usr/local/lib/cuda に入るので、その下になるように入れましょう。

$ tar xf cudnn-*.tgz
$ sudo cp cuda/include/* /usr/local/cuda/include
$ sudo cp cuda/lib64/* /usr/local/cuda/lib64
Tensorflowが1.0になりましたが、GPU版の要求CUDAが8.0(最新)になり、同時に要求されるnvidia-driverのバージョンが375系になるという、非常に悲しい出来事がありました。過去を振り返らないnvidiaとGoogleらしい決断とでもいうのでしょうか。
そして悲劇として、Tokyoリージョンで利用できるg2インスタンスだと、K520というGPUを積んでいるのですが、これがで、367.*系のドライバでしか動かないのです・・・。
そのため、tensorflowのバージョンを下げるか、USリージョンでp2インスタンスを利用する必要があります。
今回は涙をのんでUSリージョンにP2を立てました。Tokyoにも来てくれることを願います。

そして、.bash_profileLD_LIBRARY_PATH を設定します。これを忘れるとライブラリの読み込みでエラーになって( ゚д゚)ハッ!てなります。

if [[ -z $LD_LIBRARY_PATH ]]; then
    export LD_LIBRARY_PATH=/usr/local/cuda/lib64
else
    export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH
fi

ちゃんと動作しているかどうかは、非常に簡単なTensorflowプログラムを動かしてみるのが一番手っ取り早いです。

import tensorflow as tf
sess = tf.session()
hello = sess.run(tf.constant('Hello, world!'))
print(hello)

うまく動作すると、次のようなログが出ます。

I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcublas.so.8.0 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcudnn.so.5 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcufft.so.8.0 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcuda.so.1 locally
I tensorflow/stream_executor/dso_loader.cc:135] successfully opened CUDA library libcurand.so.8.0 locally
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE3 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.1 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX2 instructions, but these are available on your machine and could speed up CPU computations.
W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use FMA instructions, but these are available on your machine and could speed up CPU computations.
I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:910] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
I tensorflow/core/common_runtime/gpu/gpu_device.cc:885] Found device 0 with properties:
name: Tesla K80
major: 3 minor: 7 memoryClockRate (GHz) 0.8235
pciBusID 0000:00:1e.0
Total memory: 11.17GiB
Free memory: 11.11GiB
I tensorflow/core/common_runtime/gpu/gpu_device.cc:906] DMA: 0
I tensorflow/core/common_runtime/gpu/gpu_device.cc:916] 0:   Y
I tensorflow/core/common_runtime/gpu/gpu_device.cc:975] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla K80, pci bus id: 0000:00:1e.0

メモリ11GiBて・・・。さすがサーバー用のGPUは驚異的です。

Tensorflowのプロファイルを取りたい

GPUを利用していても、実際に早くなったのかどうか、は計測してみないとわからないものです。まぁ大体は速くなるんですけど、想定より遅いとかもありますし。

この場合、gperfとかそういったもので取得することも出来るようですが、軟弱な我々としては、やはりChromeとかFirefoxで慣れたタイムライン表示とかがいいです。可視化最高。TensorflowはGoogle主導で開発しているためかどうかはわからないけれど、これを行うための機能がすでにあります。

既存のTensorFlowプログラムに、以下のようなコードを追記します。

from tensorflow.python.client import timeline

with tf.Session() as sess:
    run_options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
    run_metadata = tf.RunMetadata()

    sess.run(
        training_op,
        feed_dict=feed,
        run_metadata=run_metadata,
        options=run_options)

    # write train
    tl = timeline.Timeline(run_metadata.step_stats)
    ctf = tl.generate_chrome_trace_format()
    with open('timeline.json', 'w') as f:
        f.write(ctf)

こうやると、timeline.jsonというのがローカルに出来ます。これを、Chromeのアドレスバーで chrome://tracing といれて出るページで読み込ませてやると、見慣れた?タイムライン表示が行えます。内容的にはTensorflowのoperation単位になってるようです。

ただし、事前に LD_LIBRARY_PATH/usr/local/cuda-8.0/extras/CUPTI/lib64 を追加しておく必要があります。この中にあるlibcuptiが読み込めないとエラーになるためです。

GPUの活用は正義(多分)

最初はg2インスタンスでやろうと思っていたのですが、まさかの使えない問題でp2インスタンスを利用することになるとは思いませんでした・・・。USリージョンだとSSHがまさしく「あー海越えてるなー」って速度になるんでストレスフルですね!

とりあえず、学習とかモデルとか、その辺りを勉強しながら進めていく、というのはなんだか久しぶりで、かなり新鮮です。データセットを集める手法の確立とかも大事ですが、そこはまぁ頑張ればいいので・・・。

新しいものをすぐ使いたくなるような人(主に自分)の助けになれば幸いです。

続きを読む