- 投稿日:2019-12-15T23:49:19+09:00
[AWS] VPCでネットワーク構築2
[AWS] VPCでネットワーク構築1
の続きですここでは作成したパブリックサブネットにWEBサーバ用のEC2インスタンスを作成していきます
1.インスタンスの作成
・ネットワークはVPC領域を選択
・サブネットはパブリックサブネットを選択
・プライマリIPは10.0.1.10を割り当てときます
・次のステップをクリック2.ストレージの追加
3.タグの追加
・タグの追加をクリック、Webサーバとして使うのでkeyにName、valueにWebサーバーと入力
・次のステップをクリック4.セキュリティグループの設定
・セキュリティグループにWEB-SGと入力
・確認と作成をクリックし、インスタンス作成の確認画面に移動するので起動をクリック5.キーペアの作成
・新しいキーペアの作成を選択
・キーペア名をmy-keyとしておきます
・キーペアのダウンロードをクリックして作成したキーペアをダウンロード
・インスタンスの作成をクリック6.Elastic IPを割り当てて、ssh接続してみる
7.DNSサーバーを構成する
・サービス > EC2 > インスタンスに戻ってインスタンスの説明タグを確認してみると、パブリックDNSとプライベートDNSが設定されているのが確認できます
これでWEBサーバの準備は完了です
次にプライベートサブネットを作ってDBサーバを配置していきたいと思います
- 投稿日:2019-12-15T23:29:20+09:00
[AWS] VPCでネットワーク構築1
AWSでVPCネットワークを構築するメモ
今回はVPC領域をパブリックとプライベートのサブネットに分割して、それぞれにWEBサーバとDBサーバを構築してみました
参考:Amazon Web Services 基礎からのネットワーク&サーバー構築ちなみにVPCとは
Virtual Private Cloud (VPC) は、AWS アカウント専用の仮想ネットワークです。VPC は、AWS クラウドの他の仮想ネットワークから論理的に切り離されており、AWS のリソース(例えば Amazon EC2 インスタンス)を VPC 内に起動できます。
完成イメージ
1.VPCを作る
・リージョンを東京にしときます
・今回はIPv4で作成
・10.0.0.0 ~ 10.0.255.255までのipアドレスを割り振れるようにしておきました2.パブリックサブネットを作る
・VPCには1で作った領域を設定
3.インターネットゲートウェイを作る
・インターネットに接続するためにインターネットゲートウェイを作成
・今回は名前タグを空のまま4.インターネットゲートウェイとVPCを紐づける
・1で作ったVPCを選択
5.ルートテーブルを作る
・ネットワークにデータを流すために「ルーティング情報」の設定が必要
・宛先IPアドレスの値を見て、どのネットワークに流すべきかという設定がルートテーブル
・1で作ったVPCを選択6.ルートテーブルをサブネットに割り当てる
・2で作ったサブネットを5で作ったルートテーブルに関連付ける
7.デフォルトゲートウェイにインターネットゲートウェイを設定する
・デフォルトゲートウェイ(0.0.0.0/0)を3で作成したゲートウェイに設定する
これでVPC領域とパブリックサブネットの設定は終わりです
次はパブリックサブネットにWEBサーバ用のEC2インスタンスを作成していきたいと思います
- 投稿日:2019-12-15T23:19:39+09:00
SlackのWebhookをプロキシする仕組みを作る
Slackはさまざまなカスタマイズ機能を持っているのが魅力のツールです。例えばBotを作ったり、カスタムのslash commandを作ったりすることで、プラットフォームの拡張ができます。
Slack Botの作り方はいくつかあるのですが、Slackのリッチな機能を最大限に引き出すには、SlackからのWebhookを受けることが必要になってきます。すなわち、ボタンなどが付いたリッチなメッセージの投稿は難しくないのですが、投稿したメッセージのボタンやメニュー操作は、SlackからWebhookの形で通知される仕組みになっています。
※ この辺りの仕組みの詳細については、まとまっている記事がいくつもあるので省略します。
Slackは当然publicなサービスなので、インターネット経由でWebhookが飛んできます。これを受けて処理するためには、Bot用の公開APIサーバを書くことになるでしょう。
公開サーバ? 何か問題でも?
それじゃあ、単にサーバを立てればいいじゃないかというのはもっともですが、サーバを公開するのはいくつか面倒な点があります。
- 公開サーバはセキュリティリスクがある
- もちろん、ちゃんと管理すればいいのですが、publicに露出するものが増えると相応にリスクが増加してしまうのはどうしようもないことです。できればやりたくありません
- とりわけ会社業務で使う場合、イントラネットにある社内システムや社内ネットワークとの接続性を考慮する必要が出てくる
- 当然、Botであるからには、社内のいろいろなものと連携させたくなります。
SlackのRTM APIというWebSocketベースのAPIでchatメッセージを受信する場合には、こういった悩みは発生しませんでした(メッセージをpullするAPIを使うので、基本的にインターネットへ接続できるBot実行環境がありさえすればよかった)。できる限りそのお手軽さを保ったまま、セキュリティなどの煩雑な問題をコントロールする方法がないか、ということを考えたくなります。
そうだプロキシしよう
というわけで、次のようなシステムを考えることにしました。
- Webhookを直接Botサーバに流すのではなく、プロキシ(リバースプロキシ)を経由させる
- プロキシは単にリクエストを横流しするのではなく、リクエストの検証を行い、不正なリクエストを弾く
- Slackから事前に払い出された秘密鍵を元に、指定の手順でHMACを計算することで、リクエストの正当性の検証が可能です
- Verifying requests from Slack
- SDKがあるので、Slack社オフィシャルの実装をそのまま引っこ抜いてくることができます
具体的には、AWSのAPI GatewayとLambdaを組み合わせた、簡単なプロキシを書きます。
パターンとして変則的ではありますが、結果的にAWSのDDoS対策ホワイトペーパーで述べられている、API Gatewayに公開箇所を絞るというプラクティスに沿っているように思います。
Typically, when you must expose an API to the public, there is a risk that the API frontend could be targeted by a DDoS attack. To help reduce the risk, you can use Amazon API Gateway as a “front door” to applications running on Amazon EC2, AWS Lambda, or elsewhere.
By using Amazon API Gateway, you don’t need your own servers for the API frontend and you can obfuscate other components of your application. By making it harder to detect your application’s components, you can help prevent those AWS resources from being targeted by a DDoS attack.実装例
今回は(ちょっと慣れない言語なのですが)Node.jsでlambdaを書いてみることにします。
フレームワークとしてserverless frameworkを使うとお手軽にAPI Gateway + Lambdaの構成を作ることができました。https://github.com/saka1/slack-webhook-gatekeeper
バックエンドのSlack App名に対応したURLパスに対してWebhookが飛んでくると、秘密鍵を使ってWebhookの検証をします。あらかじめ、Slack App名と秘密鍵の対応関係はparameter storeに入れておきます。正当なWebhookだった場合にはupstreamにリクエストを飛ばしてプロキシ動作をします。URLパスは複数登録することができるように作りました。
学んだこと
- AWSのparameter storeはあんまり高速ではなさそう(getに数百msかかる?)だったので、lambdaでキャッシュしたほうが無難そうでした
- プロキシとBotは1:Nの関係にあるため、プロキシシステムは全てのBot Appの秘密情報を管理しないといけなくなります(そうしなければリクエストの検証ができません)
- 理想的ではないので、何かうまい回避方法があればなあと思っています
API Gatewayからlambdaを呼ぶには「lambda統合」「lambdaプロキシ統合」の2つがあるらしいのですが、前者を使ったほうがよさそうでした。というか飛んでくるWebhookの検証でHMAC計算が入るため、1バイトでもゴミが入ると正しく動きません。そもそも前者は用途に対して複雑すぎました。
まとめ
この記事では、Slack Botの前段に置くプロキシシステムを検討し、その実装までをやってみました。ざっくり机上で検討 → ざっと実装した割には案外使えそうなものが考案できて、個人的には満足しています。
- 投稿日:2019-12-15T23:10:13+09:00
EC2で名前解決の回数が多くて、名前解決に失敗するときの対策
とある大規模WEBシステムをオンプレからAWSに移行したとき遭遇した題名の件のTIPSです。
データストアにエンドポイント名(=固定IPをもたない)、RDSやElastiCahceなどを使う、中~大規模系のシステムをEC2で利用する際は、必須の対策かもです。
システム構成
システム構成としては、よくあるパターンのものとなります。
WEBアプリケーションサーバは、Elastic Beanstalk や Lambdaなどがよいとおもいつつも、WEBアプリケーションの複雑性からEC2をオートスケールで用意しました。
一方で、データストアは、ElastiCacheとAurora Mysqlを利用しています。
EC2及びデータストアは、潤沢なインスタンスサイズを利用していました。
処理量
ざっくりですが、WEBアプリケーションに対するアクセス、そのときに発生するElastiCahce及びAurora Mysqlといったデータストアのエンドポイントへのアクセスが、秒間数千を超えるときがあるレベルです。
事前対策
マネージドサービスであるElastiCacheとAurora Mysqlはパラメータグループを微調整した程度でした。
EC2に関しては、主要ミドルウェアのPHPとApacheの一般的な制限系の項目(memory_limitとかmax_clientなど)の上限値変更と、大規模系のためのカーネルの設定(ipv4.tcp_syncookiesとかipv4.ip_local_port_rangeなど)を行っていました。
事象
今回、下記のようにデータストアに接続しようとしたが名前解決に失敗して接続できなかった旨のエラーが、処理量が多くなるとログに出力されていた。
PHP Warning: Memcache::connect(): php_network_getaddresses: getaddrinfo failed: Name or service not known PHP Warning: PDO::__construct(): php_network_getaddresses: getaddrinfo failed: Name or service not known PHP Warning: mysql_connect(): php_network_getaddresses: getaddrinfo failed: Name or service not known対策
AWSさんが2019年10月に対策を公開してくれたました。
参考:https://aws.amazon.com/jp/premiumsupport/knowledge-center/dns-resolution-failures-ec2-linux/
OSの種類などによって微妙に対策が異なりますが、私の場合は、AmazonLinuxだったので、AutomateDnsmasq.sh をダウンロードして、実行して、ほぼ対策は完了しました。
最初、それだけだと、Linuxのコンソールでローカルで動いているdnsmasqに名前解決にいっても、WEBアプリケーションは利用しないという罠にかかりましたが、これはPHPを再起動したら治りました。
キャッシュの時間は、基本的に、DNSレコードのTTLを利用するそうなので、キャッシュ時間をそれ以上伸ばしたい場合は、下記を利用ください。この設定はうまく動作しないときがあるという情報がありましたが、いじったらキャッシュ時間伸びていたので、この環境では動いているみたいでした。
max-cache-ttl
ただ、もちろん、マネージドサービス側はIPが変わる可能性があるるので、そのリスクは考慮して調整してください。
- 投稿日:2019-12-15T23:10:13+09:00
EC2で、秒間数千回~レベルでElastiCacheやRDSに接続して、名前解決に失敗するときの対策
とある大規模WEBシステムをオンプレからAWSに移行したとき遭遇した題名の件のTIPSです。
データストア(RDSやElastiCahceなど)の接続に、エンドポイント(=固定IPをもたない)を使う、EC2を利用した中~大規模のシステムでは、必須の対策かもです。
システム構成
システム構成としては、よくあるパターンのものとなります。
WEBアプリケーションサーバは、Elastic Beanstalk や Lambdaなどがよいとおもいつつも、WEBアプリケーションの複雑性からEC2をオートスケールで用意しました。
一方で、データストアは、ElastiCacheとAurora Mysqlを利用しています。
EC2及びデータストアは、潤沢なインスタンスサイズを利用していました。
処理量
ざっくりですが、WEBアプリケーションに対するアクセス、そのときに発生するElastiCahce及びAurora Mysqlといったデータストアのエンドポイントへのアクセスが、秒間数千を超えるときがあるレベルです。
事前対策
マネージドサービスであるElastiCacheとAurora Mysqlはパラメータグループを微調整した程度でした。
EC2に関しては、主要ミドルウェアのPHPとApacheの一般的な制限系の項目(memory_limitとかmax_clientなど)の上限値変更と、大規模系のためのカーネルの設定(ipv4.tcp_syncookiesとかipv4.ip_local_port_rangeなど)を行っていました。
事象
今回、下記のようにデータストアに接続しようとしたが名前解決に失敗して接続できなかった旨のエラーが、処理量が多くなるとログに出力されていました。
PHP Warning: Memcache::connect(): php_network_getaddresses: getaddrinfo failed: Name or service not known PHP Warning: PDO::__construct(): php_network_getaddresses: getaddrinfo failed: Name or service not known PHP Warning: mysql_connect(): php_network_getaddresses: getaddrinfo failed: Name or service not known対策
AWSさんが2019年10月に対策を公開してくれたました。
参考:https://aws.amazon.com/jp/premiumsupport/knowledge-center/dns-resolution-failures-ec2-linux/
OSの種類などによって微妙に対策が異なりますが、私の場合は、AmazonLinuxだったので、AutomateDnsmasq.sh をダウンロードして、実行して、ほぼ対策は完了しました。
最初、それだけだと、Linuxのコンソールでローカルで動いているdnsmasqに名前解決にいっても、WEBアプリケーションは利用しないという罠にかかりましたが、これはPHPを再起動したら治りました。
キャッシュの時間は、基本的に、DNSレコードのTTLを利用するそうなので、キャッシュ時間をそれ以上伸ばしたい場合は、下記を利用ください。この設定はうまく動作しないときがあるという情報がありましたが、いじったらキャッシュ時間伸びていたので、この環境では動いているみたいでした。
max-cache-ttlただ、もちろん、マネージドサービス側はIPが変わる可能性があるるので、そのリスクは考慮して調整してください。
※基本的には「max-cache-ttl」はいじらない方がいいとおもいます。
- 投稿日:2019-12-15T23:00:05+09:00
AWS の利用料金の合計を Cost Explorer APIで取得する
概要
AWS の利用料総額を、Slack 通知する Lambda 関数を Python 3 で作成します。
Python/AWS 共に初心者レベルで修行中の身のため、極力単純な内容にしています。
勉強のため軽く見たいけど応用例みたいな記事しかなくて困ったので、そういった方の参考になれば幸いです。AWS 使用料金について
Cost Explorer API を利用するため、1回の実行当たり 0.01 USD が最低でも発生します。
連続実行などはされないかと思いますがご注意ください。また、この記事では AWS Cost Explorer 画面の(おそらく)デフォルトで選択されている 非ブレンドコスト ではなく、ブレンドコスト を Cost Explorer API にて取得します。
Slack 設定
Slack 通知するための Incomming Webhook 設定は こちらの記事 がわかりやすいので、詳細は省きます。
Incomming Webhook の URL をテキストエディタなどに控えておけば OK です。ポリシーの作成
Cost Explorer の読み取り権限が必要なので、その権限がついたポリシーを作成します。
AWS IAM 画面の ポリシーの作成 ボタンを押下して、以下を設定。
- サービス: Cost Explorer Service
- アクション: 読み込みにチェック
(読み取り権限をすべて付与していますが、利用状況によるものの以下でも十分そうです)
"ce:GetReservationUtilization", "ce:GetDimensionValues", "ce:GetCostAndUsage", "ce:GetReservationCoverage", "ce:GetReservationPurchaseRecommendation", "ce:GetCostForecast", "ce:GetTags"ポリシーの確認 ボタンを押下して、以下を入力。
- 名前: CostExplorerReadOnly
- 説明: CostCheck By lambda
ポリシーを作成して、この作業は完了です。
Lambda の設定
Lambda 関数の作成
Lambda の ダッシュボード or 関数 の、関数の作成 ボタンを押下。
以下の設定をして 関数の作成 ボタンを押下。
- 一から作成 を選択。
- 関数名: cost_check などそれっぽい名前
- ランタイム: Python 3.7
実行ロール設定
作成された Lambda 関数の 実行ロールの部分で、特に設定をしていなければ service-role/cost_check-role-hoge のようなロールが作成&設定されているかと思います。
このロールに Cost Explorer の読み取りポリシーをアタッチします。
- IAM コンソールで cost_check-role-hoge ロールを表示します のリンクを押下。
- 表示された画面で、ポリシーをアタッチします ボタンを押下。
- 先ほど作成した CostExplorerReadOnly ポリシーを選択して、ポリシーのアタッチを押下。
環境変数
実行ロールの少し上にある 環境変数 にて、以下の2つを設定します。
ドル円換算に利用する1ドル何円かと、事前に設定した Slack の Webhookk URL です。
- JPY: 110
- WEBHOOK_URL:
https://hooks.slack.com/services/(以下略)
関数コード
以下となります。
各々の処理でそれほど難しいことはしていないので、ちょっと悩んだ所だけ解説します。
- 費用を取得する cost_check
- Slack で表示するメッセージ作成の create_message
- Slack に通知する post_slack
import json import urllib.request import os import boto3 import math from datetime import datetime, date, timedelta def cost_check(): client = boto3.client('ce') today = datetime.today() # 合計金額を知りたいので、フィルタは最小限 response = client.get_cost_and_usage( TimePeriod={ 'Start': datetime.strftime(today.replace(day=1), '%Y-%m-%d'), 'End': datetime.strftime(today, '%Y-%m-%d') }, Granularity='MONTHLY', Metrics=['BlendedCost'], ) return response['ResultsByTime'][0]['Total']['BlendedCost']['Amount'] def create_message(cost, jpy): # cost と jpy から日本円換算。日本円表記なので切り上げで処理。 cost_jpy = str(math.ceil(float(cost) * int(jpy))) message = "当月のAWS使用料金は `$" + cost + "` です。" \ + "\n日本円換算で `約" + cost_jpy + "円`となります" return message def post_slack(webhook_url, message): # ユーザ名、アイコン含み Slack 投稿内容を設定 send_data = { "username": "Slack 通知時に表示される名前", "icon_emoji": ":moneybag:", "text": message, } #print(send_data) send_text = "payload=" + json.dumps(send_data) request = urllib.request.Request( webhook_url, data=send_text.encode("utf-8"), method="POST" ) with urllib.request.urlopen(request) as response: response_body = response.read().decode("utf-8") def lambda_handler(event, context): # 環境変数取得 webhook_url = os.environ['WEBHOOK_URL'] jpy = os.environ['JPY'] # 当月費用を取得 cost = cost_check() # Slack 通知するメッセージ生成 message = create_message(cost,jpy) # Slack 通知 post_slack(webhook_url,message)ちょっと悩んだところ
cost_check 内の get_cost_and_usage のレスポンス処理です。
return の前で print(response) などしてレスポンスの中身を見ると、以下のような形となっています。{ 'ResultsByTime': [ { 'TimePeriod': { 'Start': '2019-12-01', 'End': '2019-12-15' }, 'Total': { 'BlendedCost': { 'Amount': '0.2763438289', 'Unit': 'USD' } }, 'Groups': [], 'Estimated': True } ], 'ResponseMetadata': (略) }JSON 形式のレスポンスから、ResultsByTime の Total の BlendedCost の Amount を取得する!
ということで、当初は
response['ResultsByTime']['Total']['BlendedCost']['Amount']
なんて指定をしていてうまくいかず。
JSONを分解してみて [0] が必要なことに気づきました。。。
response['ResultsByTime'][0]['Total']['BlendedCost']['Amount']
テスト
画面上部の テスト ボタンを押下した後、イベントテンプレート Hello World のテストが表示されているかと思います。
以下の設定をして、作成 ボタンを押下します。
- イベント名: costchecktest
- テストの中身: {} のみ
画面上部の 保存 ボタンで内容を保存した後、テスト ボタンを押下して、テスト実行します。
テスト完了後、画面上部に実行結果が表示されます。
成功
ではない場合、表示されているログから修正箇所を探り、修正します。実行トリガの設定
実行結果のすぐ下、Designer の トリガーを追加 ボタンを押下します。
CloudWatch Events を選択した後、新規ルールの作成を選択します。
今回は毎月1日から5日おき(1,6,11...,31日)の日本時間 10:00 に Lambda 関数が呼び出されるようなルールを作成します。
具体的には以下の内容を設定します。
- ルール名: 5Day_1000JST など、わかりやすい名前
- ルールの説明: execute 1,6,11...,31 1000JST など、説明っぽいもの。
- ルールタイプ: スケジュール式
- スケジュール式: cron(0 1 */5 * ? *)
- トリガーの有効化: チェックを入れる
上記設定をして 追加 ボタンを押せば、あとは CloudWatch Events から呼び出されて Lambda 関数が自動実行される日を待つだけです。
曜日部分を * ではなく ? にするのが若干ハマりどころですが、公式ドキュメント を読んでおけば迷いはないはずです(迷った)
また、Cron 式は UTC にて実行されるため、JST で実行するにはマイナス9時間する必要があります(当初はしてなくて思いもよらない時間に実行されてびっくりした)終わりに
Python 自体の辞書やリストの扱いにも不慣れで、JSON を扱うにも一苦労でした。
色々細かなものを作成して、このあたりの処理については慣れていきたいですね。油断していたら日付ギリギリぐらいになってしまいました。
来週は余裕をもって投稿したいと思います。
- 投稿日:2019-12-15T21:47:38+09:00
[AWS][CFn]IAMの設定
はじめに
こんにちは。なじむです。
引き続き、クラスメソッドさんのブログ記事、AWSアカウントを作ったら最初にやるべきこと ~令和元年版~を参考にさせていただき、AWSでの初期設定をCloudFormationで実施していきます。
今回は「IAMの設定」をCloudFormationで実施していきます。IAMはAWSを使用する上で一番重要と言っても過言ではないと思います。その設定が少しでも楽になれば。前提
今回は特定のIPからのアクセスのみを有効にします。ただし、特定のIPからのアクセスのみに制限してしまうと、AWS内部の通信(例えばEBSの暗号化)も制限してしまうことになるため以下の条件で設定します。
- コンソールにログインした後、スイッチロールを行い任意のロールにスイッチする
- 上述のスイッチロールは特定のIPからしか実行できないよう制限する
- スイッチロールした後はIPアドレスの制限を設けず、任意の操作を実行可能とする
今回は例としてAdministartorAccessのIAMポリシーを使用して、すべての権限を持つグループを作成します。
サンプルコードで実施しているのは以下です。
- IAMグループ(Administrator)を作成する
- IAMポリシー(OnlyMyIPAccess, AssumeAdministrator)を作成する
- IAMロール(Administrator)を作成する
サンプルコード
--- AWSTemplateFormatVersion: 2010-09-09 Description: IAM - Group, Role, Policy for Administrator #------------------------------ # Resources: Your resource list #------------------------------ Resources: # IAM(Group) Resource ## Administrator GroupAdministrator: Type: AWS::IAM::Group Properties: GroupName: Administrator ManagedPolicyArns: - !Ref AssumeAdministrator - !Ref OnlyMyIPAccess # IAM(Policy) Resource ## Group's Policy -- MyIPAccess OnlyMyIPAccess: Type: AWS::IAM::ManagedPolicy Properties: PolicyDocument: Version: 2012-10-17 Statement: - Effect: Deny Action: "*" Resource: "*" Condition: NotIpAddress: aws:SourceIp: - xxx.xxx.xxx.xxx/32 # ここは任意のIPアドレスに修正してください。 ## Group's Policy -- Administrator AssumeAdministrator: Type: AWS::IAM::ManagedPolicy Properties: PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: sts:AssumeRole Resource: !GetAtt RoleAdministrator.Arn # IAM(Role) Resource ## Administrator RoleAdministrator: Type: AWS::IAM::Role Properties: RoleName: Administrator AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: sts:AssumeRole Principal: AWS: !Sub ${AWS::AccountId} ManagedPolicyArns: - arn:aws:iam::aws:policy/AdministratorAccess Path: "/"実行結果
では、いつも通り作成したリソースを見ていきます。
IAMグループ(Administrator)を作成する
今回は作成したリソースではなく、動作を見ていきましょう。
まとめ
今回は自社のオフィス以外からはアクセスさせたくないという場面を想定して特定のIPからのみにログインを絞る方法でIAMを構築してみました。ただ、この方法だと1度スイッチロールした後、その状態を保持したままIPが変わってもログインは保持されるため操作が可能となります(そんな場面はまれだと思いますが)。その場合、IAMロールの認証が切れる1時間後か、ユーザがログアウトするのを待つしかないかなと思います。
IAMの構成については、個人的にはネストスタックを使用すると少し構造が分かりやすくなるかなと思うので、次回はIAMの設定をネストスタックでやってみるをお送りしようと思いますノシ
- 投稿日:2019-12-15T21:42:43+09:00
AWS CDKを使ってVPC/EC2/S3バケットを構築する
AWS CDKを使った基本的な環境を構築したくなったので、記事を投稿してみました。
また、退職して少しの間ニートになったので、CDK関連の記事を今後投稿してみようと思ってます。作成する環境構成
以下のような環境を構築するためのCDKソースコードを作成します。
基本的な構成なのですが、CDKでコーディングすると楽しいのでおすすめです。
- CDKバージョン: v1.18.0
- TypeScript: v3.7.3
AWS CDKのインストール
sudo npm install -g aws-cdkcdk ---version 1.18.0 (build bc924bc)mkdir mini-cdk cd mini-cdk cdk init app --language=typescriptnpm install @aws-cdk/aws-ec2 @aws-cdk/aws-s3 @aws-cdk/aws-s3-deployment @aws-cdk/aws-iam# 作成するスタッックのファイル touch lib/mini-vpc-stack.ts touch lib/mini-ec2-stack.ts touch lib/mini-s3-stack.ts # 都合上削除 rm test/mini-cdk.test.tsVPCスタックの作成
VPCスタックを作成します。
注意点として、利用するAZの数とNatGatewayに気を付けた方がよいです。natGateways: 0
として明示しないと、AZごとにNatGatewayが作成されてしまいます。個人で使う分には利用コストが高くなってしまうので注意が必要です。lib/mini-vpc-stack.tsimport cdk = require("@aws-cdk/core"); import ec2 = require("@aws-cdk/aws-ec2"); export class MiniVpcStack extends cdk.Stack { public readonly vpc: ec2.Vpc; constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPCを作成する this.vpc = this.createVpc(); // GatewayEndpointをセットアップする this.setupGatewayEndpoint(); // InterfaceEndpointをセットアップする this.setupInterfaceEndpoint(); } private createVpc() { return new ec2.Vpc(this, "MiniVpc", { cidr: "172.17.0.0/16", natGateways: 0, // NatGatewayはお金がかかるので使わない maxAzs: 1, // Availability Zoneの数 subnetConfiguration: [ { name: "Public", cidrMask: 20, subnetType: ec2.SubnetType.PUBLIC }, { name: "Isolated", cidrMask: 20, subnetType: ec2.SubnetType.ISOLATED } ] }); } private setupGatewayEndpoint() { // IsolatedのサブネットからS3に直接アクセスできるようにVPCエンドポイントを利用する this.vpc.addGatewayEndpoint("S3EndpointForIsolatedSubnet", { service: ec2.GatewayVpcEndpointAwsService.S3, subnets: [{ subnetType: ec2.SubnetType.ISOLATED }] }); // IsolatedのサブネットからDynamoDBに直接アクセスできるようにVPCエンドポイントを利用する // 今回は利用しません。 this.vpc.addGatewayEndpoint("DynamoDbEndpointForIsolatedSubnet", { service: ec2.GatewayVpcEndpointAwsService.DYNAMODB, subnets: [{ subnetType: ec2.SubnetType.ISOLATED }] }); } private setupInterfaceEndpoint() { // InterafceEndpoint用のセキュリティグループ const interfaceEndpointSecurityGroup = new ec2.SecurityGroup( this, "InterfaceEndpointSecurityGroup", { securityGroupName: "InterfaceEndpointSecurityGroup", vpc: this.vpc } ); interfaceEndpointSecurityGroup.addIngressRule( ec2.Peer.anyIpv4(), ec2.Port.tcp(443) ); // Isolated SubnetからECRに接続するためのエンドポイント // 今回は利用しません。 this.vpc.addInterfaceEndpoint("ECRInterfaceEndpoint", { securityGroups: [interfaceEndpointSecurityGroup], service: ec2.InterfaceVpcEndpointAwsService.ECR, subnets: { subnetType: ec2.SubnetType.ISOLATED } }); this.vpc.addInterfaceEndpoint("ECR_DockerInterfaceEndpoint", { securityGroups: [interfaceEndpointSecurityGroup], service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER }); } }
setupInterfaceEndpoint
メソッドでECRにアクセスするためのエンドポイントを作成していますが、今回は関係ありません。実装の参考情報として解釈してください。EC2スタックの作成
Publicサブネットに1つ、Isolatedサブネットに1つインスタンスを作成するスタックを作成します。
IsolatedサブネットのEC2インスタンスからS3バケットにアクセスできるようにロールをアタッチしています。lib/mini-ec2-stack.tsimport cdk = require("@aws-cdk/core"); import ec2 = require("@aws-cdk/aws-ec2"); import iam = require("@aws-cdk/aws-iam"); interface MiniEc2StackProps extends cdk.StackProps { readonly vpc: ec2.Vpc; // MiniVpcStackのプロパティをvpcを受け取りたいので設定している readonly keyName: string; // キーペア } export class MiniEc2Stack extends cdk.Stack { private readonly vpc: ec2.Vpc; private readonly keyName: string; private publicEc2SecurityGroup: ec2.SecurityGroup; private isolatedEc2SecurityGroup: ec2.SecurityGroup; private isolatedEc2Role: iam.Role; constructor(scope: cdk.Construct, id: string, props: MiniEc2StackProps) { super(scope, id, props); this.vpc = props.vpc; this.keyName = props.keyName; // PublicSubnetのEC2インスタンスをセットアップする this.setupPublicEc2Instance(); // IsolatedSubnetのEC2インスタンスをセットアップする this.setupIsolatedEc2Role(); this.setupIsolatedEc2Instance(); } // PublicSubnetにEC2インスタンスを作成する private setupPublicEc2Instance() { this.publicEc2SecurityGroup = this.createPublicEc2InstanceSecurityGroup(); const userData = ec2.UserData.forLinux(); userData.addCommands("sudo yum update -y"); new ec2.Instance(this, "MiniPublicEc2Instance", { vpc: this.vpc, instanceName: "MiniPublicEc2Instance", keyName: this.keyName, instanceType: ec2.InstanceType.of( ec2.InstanceClass.T3, ec2.InstanceSize.NANO ), machineImage: new ec2.AmazonLinuxImage({ edition: ec2.AmazonLinuxEdition.STANDARD, generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 }), vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, userData, securityGroup: this.publicEc2SecurityGroup }); } // IsolatedSubnetにEC2インスタンスを作成する private setupIsolatedEc2Instance() { this.isolatedEc2SecurityGroup = this.createIsolatedEc2InstanceSecurityGroup(); const userData = ec2.UserData.forLinux(); userData.addCommands("sudo yum update -y"); new ec2.Instance(this, "MiniIsolatedEc2Instance", { instanceName: "MiniIsolatedEc2Instance", vpc: this.vpc, keyName: this.keyName, instanceType: ec2.InstanceType.of( ec2.InstanceClass.T3, ec2.InstanceSize.NANO ), machineImage: new ec2.AmazonLinuxImage({ edition: ec2.AmazonLinuxEdition.STANDARD, generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 }), vpcSubnets: { subnetType: ec2.SubnetType.ISOLATED }, securityGroup: this.isolatedEc2SecurityGroup, role: this.isolatedEc2Role }); } private setupIsolatedEc2Role() { this.isolatedEc2Role = new iam.Role(this, "IsolatedEc2InstanceRole", { roleName: "IsolatedEc2InstanceRole", assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com") }); // ロールにS3へのアクセスするポリシーをアタッチする this.isolatedEc2Role.addToPolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ "s3:GetObject", "s3:PutObject", "s3:ListBucket", "s3:ListAllMyBuckets" ], resources: ["*"] }) ); } // PublicサブネットのEC2インスタンスのセキュリティグループを作成する private createPublicEc2InstanceSecurityGroup() { const sg = new ec2.SecurityGroup(this, "MiniPublicEc2InstanceSg", { vpc: this.vpc, securityGroupName: "MiniPublicEc2InstanceSg" }); sg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22)); return sg; } // IsolatedサブネットのEC2インスタンスのセキュリティグループを作成する private createIsolatedEc2InstanceSecurityGroup() { const sg = new ec2.SecurityGroup(this, "MiniIsolatedEc2InstanceSg", { vpc: this.vpc, securityGroupName: "MiniIsolatedEc2InstanceSg" }); sg.addIngressRule(this.publicEc2SecurityGroup, ec2.Port.allTraffic()); return sg; } }S3バケットスタックの作成
ここでは、バケットを2つ作成することにしましたw。
1つはホスティング用のバケット、もうひとつは内部用のバケットです。データをあとで入れるのがめんどくさかったので、都合上、
@aws-s3-deployment
を使うことにしました。
以下のコマンドで適当なファイルを初期生成しています。mkdir -p data/hosting mkdir -p data/internal echo "Hello MiniCdk!" > data/hosting/index.html echo "This is Internal Bucket!" > data/internal/test.txtS3バケットスタックを作成するソースコードは以下の通りです。
lib/mini-s3-stack.tsimport cdk = require("@aws-cdk/core"); import s3 = require("@aws-cdk/aws-s3"); import s3Deploy = require("@aws-cdk/aws-s3-deployment"); export class MiniS3BucketStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); this.setupInternalBucket(); this.setupHostingBucket(); } // 内部で使うバケットを作成する setupInternalBucket() { const bucket = new s3.Bucket(this, "InternalBucket"); new s3Deploy.BucketDeployment(this, "DeployInternalBucket", { sources: [s3Deploy.Source.asset("./data/internal")], destinationBucket: bucket }); } // ホスティング用に使うバケットを作成する setupHostingBucket() { const bucket = new s3.Bucket(this, "HostingBucket", { websiteIndexDocument: "index.html", publicReadAccess: true, versioned: true, removalPolicy: cdk.RemovalPolicy.DESTROY }); new s3Deploy.BucketDeployment(this, "DeployHostingBucket", { sources: [s3Deploy.Source.asset("./data/hosting")], destinationBucket: bucket }); } }エンドポイントの作成
ここまで作成したスタックのエンドポイントとなるファイルを作成します。
bin/mini-cdk.ts#!/usr/bin/env node import "source-map-support/register"; import cdk = require("@aws-cdk/core"); import { MiniVpcStack } from "../lib/mini-vpc-stack"; import { MiniEc2Stack } from "../lib/mini-ec2-stack"; import { MiniS3BucketStack } from "../lib/mini-s3-stack"; main(); function main() { const app = new cdk.App(); // EC2インスタンスにアクセスするためのキーペアを指定する // マネジメントコンソールから事前にキーペアを作成しておいてください。 const keyName = app.node.tryGetContext("keyName"); // VPCスタック const miniVpcStack = new MiniVpcStack(app, "MiniVpcStack"); // EC2スタック new MiniEc2Stack(app, "MiniEc2Stack", { vpc: miniVpcStack.vpc, keyName }); // S3バケットスタック new MiniS3BucketStack(app, "MiniS3BucketStack"); app.synth(); }キーペアをデプロイ時に指定するので、マネジメントコンソールかCLIから作成しておいてください。
ここでは、mini-cdk
という名前で作成しました。ビルドとデプロイ
ビルドして
js
ファイルを生成します。npm run buildデプロイ対象のスタック一覧を確認します。
cdk list MiniS3BucketStack MiniVpcStack MiniEc2Stackここまでで準備が整ったので、スタックをまとめてデプロイして環境を構築します。
export AWS_DEFAULT_REGION=ap-northeast-1 cdk -c keyName=mini-cdk deploy *Stack --require-approval never --profile dev
-c
オプションでコンテキストパラメータとしてキーペア名を指定しています。--require-approval never
は[y/n]を手動で承認するのが手間だった(CodeBuildとかを使った自動化などを想定)ので付けています。--profile
は各自のものに読み替えてください。構築したあとの確認作業
IsolatedサブネットのEC2インスタンスから確認します。
$ aws s3 ls --region ap-northeast-1 2019-12-15 12:03:53 minis3bucketstack-hostingbucket5dac2127-4r11qt9ir0vr 2019-12-15 12:03:27 minis3bucketstack-internalbucketda21a343-1hy903e2t0zu7 # 内部用に作ったバケット $ aws s3 ls s3://minis3bucketstack-internalbucketda21a343-1hy903e2t0zu7/ --region ap-northeast-1 2019-12-15 12:04:52 24 test.txt # ホスティング用に作ったバケット $ aws s3 ls s3://minis3bucketstack-hostingbucket5dac2127-4r11qt9ir0vr/ --region ap-northeast-1 2019-12-15 12:04:52 15 index.html他にもホスティングURLでバケットにアクセスできるか等確認がありますが、割愛させていただきます。
以上、CDKを使ったVPC/EC2/S3の構築でした。
次回は、Fargateについて書いてみようと思います。
- 投稿日:2019-12-15T21:42:43+09:00
AWS CDKを使ってVPC/EC2/S3バケットを作成する
AWS CDKを使った基本的な環境を構築したくなったので、記事を投稿してみました。
また、退職して少しの間ニートになったので、CDK関連の記事を今後投稿してみようと思ってます。作成する環境構成
以下のような環境を構築するためのCDKソースコードを作成します。
基本的な構成なのですが、CDKでコーディングすると楽しいのでおすすめです。
- CDKバージョン: v1.18.0
- TypeScript: v3.7.3
AWS CDKのインストール
sudo npm install -g aws-cdkcdk ---version 1.18.0 (build bc924bc)mkdir mini-cdk cd mini-cdk cdk init app --language=typescriptnpm install @aws-cdk/aws-ec2 @aws-cdk/aws-s3 @aws-cdk/aws-s3-deployment @aws-cdk/aws-iam# 作成するスタッックのファイル touch lib/mini-vpc-stack.ts touch lib/mini-ec2-stack.ts touch lib/mini-s3-stack.ts # 都合上削除 rm test/mini-cdk.test.tsVPCスタックの作成
VPCスタックを作成します。
注意点として、利用するAZの数とNatGatewayに気を付けた方がよいです。natGateways: 0
として明示しないと、AZごとにNatGatewayが作成されてしまいます。個人で使う分には利用コストが高くなってしまうので注意が必要です。lib/mini-vpc-stack.tsimport cdk = require("@aws-cdk/core"); import ec2 = require("@aws-cdk/aws-ec2"); export class MiniVpcStack extends cdk.Stack { public readonly vpc: ec2.Vpc; constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); // VPCを作成する this.vpc = this.createVpc(); // GatewayEndpointをセットアップする this.setupGatewayEndpoint(); // InterfaceEndpointをセットアップする this.setupInterfaceEndpoint(); } private createVpc() { return new ec2.Vpc(this, "MiniVpc", { cidr: "172.17.0.0/16", natGateways: 0, // NatGatewayはお金がかかるので使わない maxAzs: 1, // Availability Zoneの数 subnetConfiguration: [ { name: "Public", cidrMask: 20, subnetType: ec2.SubnetType.PUBLIC }, { name: "Isolated", cidrMask: 20, subnetType: ec2.SubnetType.ISOLATED } ] }); } private setupGatewayEndpoint() { // IsolatedのサブネットからS3に直接アクセスできるようにVPCエンドポイントを利用する this.vpc.addGatewayEndpoint("S3EndpointForIsolatedSubnet", { service: ec2.GatewayVpcEndpointAwsService.S3, subnets: [{ subnetType: ec2.SubnetType.ISOLATED }] }); } private setupInterfaceEndpoint() { // InterafceEndpoint用のセキュリティグループ const interfaceEndpointSecurityGroup = new ec2.SecurityGroup( this, "InterfaceEndpointSecurityGroup", { securityGroupName: "InterfaceEndpointSecurityGroup", vpc: this.vpc } ); interfaceEndpointSecurityGroup.addIngressRule( ec2.Peer.anyIpv4(), ec2.Port.tcp(443) ); // Isolated SubnetからECRに接続するためのエンドポイント this.vpc.addInterfaceEndpoint("ECRInterfaceEndpoint", { securityGroups: [interfaceEndpointSecurityGroup], service: ec2.InterfaceVpcEndpointAwsService.ECR, subnets: { subnetType: ec2.SubnetType.ISOLATED } }); this.vpc.addInterfaceEndpoint("ECR_DockerInterfaceEndpoint", { securityGroups: [interfaceEndpointSecurityGroup], service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER }); } }
setupInterfaceEndpoint
メソッドでECRにアクセスするためのエンドポイントを作成していますが、今回は関係ありません。実装の参考情報として解釈してください。EC2スタックの作成
Publicサブネットに1つ、Isolatedサブネットに1つインスタンスを作成するスタックを作成します。
IsolatedサブネットのEC2インスタンスからS3バケットにアクセスできるようにロールをアタッチしています。lib/mini-ec2-stack.tsimport cdk = require("@aws-cdk/core"); import ec2 = require("@aws-cdk/aws-ec2"); import iam = require("@aws-cdk/aws-iam"); interface MiniEc2StackProps extends cdk.StackProps { readonly vpc: ec2.Vpc; // MiniVpcStackのプロパティをvpcを受け取りたいので設定している readonly keyName: string; // キーペア } export class MiniEc2Stack extends cdk.Stack { private readonly vpc: ec2.Vpc; private readonly keyName: string; private publicEc2SecurityGroup: ec2.SecurityGroup; private isolatedEc2SecurityGroup: ec2.SecurityGroup; private isolatedEc2Role: iam.Role; constructor(scope: cdk.Construct, id: string, props: MiniEc2StackProps) { super(scope, id, props); this.vpc = props.vpc; this.keyName = props.keyName; // PublicSubnetのEC2インスタンスをセットアップする this.setupPublicEc2Instance(); // IsolatedSubnetのEC2インスタンスをセットアップする this.setupIsolatedEc2Role(); this.setupIsolatedEc2Instance(); } // PublicSubnetにEC2インスタンスを作成する private setupPublicEc2Instance() { this.publicEc2SecurityGroup = this.createPublicEc2InstanceSecurityGroup(); const userData = ec2.UserData.forLinux(); userData.addCommands("sudo yum update -y"); new ec2.Instance(this, "MiniPublicEc2Instance", { vpc: this.vpc, instanceName: "MiniPublicEc2Instance", keyName: this.keyName, instanceType: ec2.InstanceType.of( ec2.InstanceClass.T3, ec2.InstanceSize.NANO ), machineImage: new ec2.AmazonLinuxImage({ edition: ec2.AmazonLinuxEdition.STANDARD, generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 }), vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, userData, securityGroup: this.publicEc2SecurityGroup }); } // IsolatedSubnetにEC2インスタンスを作成する private setupIsolatedEc2Instance() { this.isolatedEc2SecurityGroup = this.createIsolatedEc2InstanceSecurityGroup(); const userData = ec2.UserData.forLinux(); userData.addCommands("sudo yum update -y"); new ec2.Instance(this, "MiniIsolatedEc2Instance", { instanceName: "MiniIsolatedEc2Instance", vpc: this.vpc, keyName: this.keyName, instanceType: ec2.InstanceType.of( ec2.InstanceClass.T3, ec2.InstanceSize.NANO ), machineImage: new ec2.AmazonLinuxImage({ edition: ec2.AmazonLinuxEdition.STANDARD, generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2 }), vpcSubnets: { subnetType: ec2.SubnetType.ISOLATED }, securityGroup: this.isolatedEc2SecurityGroup, role: this.isolatedEc2Role }); } private setupIsolatedEc2Role() { this.isolatedEc2Role = new iam.Role(this, "IsolatedEc2InstanceRole", { roleName: "IsolatedEc2InstanceRole", assumedBy: new iam.ServicePrincipal("ec2.amazonaws.com") }); // ロールにS3へのアクセスするポリシーをアタッチする this.isolatedEc2Role.addToPolicy( new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: [ "s3:GetObject", "s3:PutObject", "s3:ListBucket", "s3:ListAllMyBuckets" ], resources: ["*"] }) ); } // PublicサブネットのEC2インスタンスのセキュリティグループを作成する private createPublicEc2InstanceSecurityGroup() { const sg = new ec2.SecurityGroup(this, "MiniPublicEc2InstanceSg", { vpc: this.vpc, securityGroupName: "MiniPublicEc2InstanceSg" }); sg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22)); return sg; } // IsolatedサブネットのEC2インスタンスのセキュリティグループを作成する private createIsolatedEc2InstanceSecurityGroup() { const sg = new ec2.SecurityGroup(this, "MiniIsolatedEc2InstanceSg", { vpc: this.vpc, securityGroupName: "MiniIsolatedEc2InstanceSg" }); sg.addIngressRule(this.publicEc2SecurityGroup, ec2.Port.allTraffic()); return sg; } }S3バケットスタックの作成
ここでは、バケットを2つ作成することにしましたw。
1つはホスティング用のバケット、もうひとつは内部用のバケットです。データをあとで入れるのがめんどくさかったので、都合上、
@aws-s3-deployment
を使うことにしました。
以下のコマンドで適当なファイルを初期生成しています。mkdir -p data/hosting mkdir -p data/internal echo "Hello MiniCdk!" > data/hosting/index.html echo "This is Internal Bucket!" > data/internal/test.txtS3バケットスタックを作成するソースコードは以下の通りです。
lib/mini-s3-stack.tsimport cdk = require("@aws-cdk/core"); import s3 = require("@aws-cdk/aws-s3"); import s3Deploy = require("@aws-cdk/aws-s3-deployment"); export class MiniS3BucketStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); this.setupInternalBucket(); this.setupHostingBucket(); } // 内部で使うバケットを作成する setupInternalBucket() { const bucket = new s3.Bucket(this, "InternalBucket"); new s3Deploy.BucketDeployment(this, "DeployInternalBucket", { sources: [s3Deploy.Source.asset("./data/internal")], destinationBucket: bucket }); } // ホスティング用に使うバケットを作成する setupHostingBucket() { const bucket = new s3.Bucket(this, "HostingBucket", { websiteIndexDocument: "index.html", publicReadAccess: true, versioned: true, removalPolicy: cdk.RemovalPolicy.DESTROY }); new s3Deploy.BucketDeployment(this, "DeployHostingBucket", { sources: [s3Deploy.Source.asset("./data/hosting")], destinationBucket: bucket }); } }エンドポイントの作成
ここまで作成したスタックのエンドポイントとなるファイルを作成します。
bin/mini-cdk.ts#!/usr/bin/env node import "source-map-support/register"; import cdk = require("@aws-cdk/core"); import { MiniVpcStack } from "../lib/mini-vpc-stack"; import { MiniEc2Stack } from "../lib/mini-ec2-stack"; import { MiniS3BucketStack } from "../lib/mini-s3-stack"; main(); function main() { const app = new cdk.App(); // EC2インスタンスにアクセスするためのキーペアを指定する // マネジメントコンソールから事前にキーペアを作成しておいてください。 const keyName = app.node.tryGetContext("keyName"); // VPCスタック const miniVpcStack = new MiniVpcStack(app, "MiniVpcStack"); // EC2スタック new MiniEc2Stack(app, "MiniEc2Stack", { vpc: miniVpcStack.vpc, keyName }); // S3バケットスタック new MiniS3BucketStack(app, "MiniS3BucketStack"); app.synth(); }キーペアをデプロイ時に指定するので、マネジメントコンソールかCLIから作成しておいてください。
ここでは、mini-cdk
という名前で作成しました。ビルドとデプロイ
ビルドして
js
ファイルを生成します。npm run buildデプロイ対象のスタック一覧を確認します。
cdk list MiniS3BucketStack MiniVpcStack MiniEc2Stackここまでで準備が整ったので、スタックをまとめてデプロイして環境を構築します。
export AWS_DEFAULT_REGION=ap-northeast-1 cdk -c keyName=mini-cdk deploy *Stack --require-approval never --profile dev
-c
オプションでコンテキストパラメータとしてキーペア名を指定しています。--require-approval never
は[y/n]を手動で承認するのが手間だった(CodeBuildとかを使った自動化などを想定)ので付けています。--profile
は各自のものに読み替えてください。構築したあとの確認作業
IsolatedサブネットのEC2インスタンスから確認します。
$ aws s3 ls --region ap-northeast-1 2019-12-15 12:03:53 minis3bucketstack-hostingbucket5dac2127-4r11qt9ir0vr 2019-12-15 12:03:27 minis3bucketstack-internalbucketda21a343-1hy903e2t0zu7 # 内部用に作ったバケット $ aws s3 ls s3://minis3bucketstack-internalbucketda21a343-1hy903e2t0zu7/ --region ap-northeast-1 2019-12-15 12:04:52 24 test.txt # ホスティング用に作ったバケット $ aws s3 ls s3://minis3bucketstack-hostingbucket5dac2127-4r11qt9ir0vr/ --region ap-northeast-1 2019-12-15 12:04:52 15 index.html他にもホスティングURLでバケットにアクセスできるか等確認がありますが、割愛させていただきます。
以上、CDKを使ったVPC/EC2/S3の構築でした。
次回は、Fargateについて書いてみようと思います。
- 投稿日:2019-12-15T20:43:11+09:00
AWS re:Invent 2019で発表されたサービス
AWS re:Invent 2019
- 公式サイト:re:Invent
- 期間:2019/12/01 - 12/06
- 場所:ラスベガス
発表されたサービス
コンピューティング
- EC2
- Inf1 インスタンス
- 機械学習推論用
- M6g / C6g / R6g インスタンス
- Arm ベースのインスタンス
- Image Builder
- AMI 作成
- Nitro
- EC2 のディスクアクセス / ネットワークを高速化
- ECS
- Cluster Auto Scaling
- キャパシティプロバイダー
- Fargate
- Fargate Spot
- コスト削減
- Kubernetes サポート
- Compute Optimizer
- 最適なリソースを利用しているかアドバイス
- Outposts
- オンプレに AWS を配置
- 一般提供開始
- Local Zone
- 各地域に AWS を配置
- カリフォルニアで提供開始
- Wavelength
- 5G ネットワークのエッジに配置
- Lambda
- Provisioned Concurrency
アナリティクス
- Redshift
- RA3 ノード
- Federated Query
- Elasticsearch Service
- UltraWarm
- Kinesis Video Stream
- WebRTC による双方向通信
- Outposts - EMR
- オンプレで EMR が利用可能
- Redshift data lake export
- クエリ結果を S3 に保存
アプリケーション統合
- Step Functions Express Workflows
- EventBridge
- スキーマレジストリ
SaaS
- Chime
- 参加者によって低遅延になるリージョンの自動選択
- Chime Meetings App for Slack
- Slack から直接 Chime を開始
- Connect
- Contact Lens
- 機械学習によりお客様の感情、傾向等を分析
データベース
- Managed Apache Cassandra
- プレビュー
- Outposts - RDS
- オンプレで RDS が利用可能
- RDS Proxy
- データベースプロキシ
- Neptune
- Neptune
- Jupyter ノートブックからクエリ
開発ツール
- Builders' Library
- Amazon.com ではどのようにやっているか
- Amplify
- iOS版、Android版をリリース
IoT
- IoT Site Wise
- プレビュー
- 産業機器からのデータの大規模な収集、保存、整理、モニタリング
- IoT Site Wise
機械学習
- Rekognition
- Custom Labels
- 独自のラベルを付与
- CodeGuru
- コードレビュー自動化
- DeepRacer
- マルチカーレース
- オブジェクト回避
- Augmented AI(A2I)
- 機械学習推論の人間によるレビューワークフローを追加
- SageMaker
- Processing
- 前処理、後処理のマネージドサービス
- Experiments
- 学習の実験を作成、比較
- Notebook Experience
- プレビュー
- ワンクリックで機械学習用 Notebook を起動
- Studio
- 機械学習向け IDE
- 学習、デバッグ、デプロイ、モニタリング
- Debugger
- 機械学習の問題を分析、デバッグ、修正
- Autopilot
- 自動学習
- Model Monitor
- モデルを監視
- データドリフトを検知
- Operators for Kubernetes
- Kubernetes 環境からモデルをトレーニング、調整、デプロイ
- Deep Graph Library サポート
- Deep Java Library
- Java によるディープラーニングライブラリ
- ディープラーニング AMI
- 最新のフレームワークに対応
- DeepComposer
- 音楽作成
- Fraud Detector
- 不正アクティビティの検出
- Kendra
- マネージドエンタープライズサーチ
- 社内ドキュメントを検索
- Transcribe
- Medical
- 臨床医と患者の音声をテキストに変換
マネージメント / ガバナンス
- License Manager
- ライセンス使用状況を自動検出
Marketplace
- Discovery API
- Marketplace を検索できる API を提供
- Seller Private Offer
- 個別契約
マイグレーション
- Windows Server 移行サポート
モバイル
- Amplify DataStore
- デバイスのオフラインストレージ
- API Gateway HTTP API
- コスト削減
- シンプル化
量子コンピュータ
- Braket
- 量子コンピュータのアルゴリズムを構築、テスト、実行
ネットワーク
- Transit Gateway
- 異なるリージョン間でピアリングが可能に
- VPC 間のマルチキャストルーティングが可能に
- Transit Gateway Network Manager
- AWS 全体とオンプレでネットワークを一元管理
- Global Accelerator
- VPN パフォーマンスを向上
- VPC Ingress Routing
- VPC で送受信されるトラフィックを仮想アプライアンスを通してリダイレクト
セキュリティ
- Detective
- セキュリティ問題や不正アクティビティの原因を分析、調査
- IAM Access Analyzer
- リソースへの意図したアクセスのみがポリシーで提供されているかを簡単に確認
- Security Hub
- IAM Access Analyzer と統合
ストレージ
- S3 Access Points
- S3 にアクセスポイントを追加
- Access Analyzer for S3
- アクセスポリシーによって S3 リソースへの意図したアクセスのみが提供されているかを簡単に確認
- EBS Direct API
- EBS スナップショットコンテンツへのアクセス
- 投稿日:2019-12-15T20:20:29+09:00
CircleCIを利用してAWS SAMで自動デプロイする(Python)
概要
AWS SAM + Pythonで開発したサーバーレスアプリケーションをCircleCIからデプロイする機会があったので、解説したいと思います。
下記の記事を参考にさせて頂きましたが、今回の設定では一部修正しないとうまく動作しない部分がありましたので、その点について記載したいと思います。
CircleCI と GitHub で AWS SAM のサーバーレスアプリを自動デプロイしてみた (開発環境 & 本番環境) | Developers.IOPythonでの経験が少ないため、間違った記載がありましたらご指摘いただけると嬉しいです?♀️
普段はAWS SAM + Goを利用することが多いです。ソースコード
https://github.com/kobayashi-m42/aws-sam-circleci-deploy-sample
解説
CircleCIの設定ファイルはこんな感じで作成しました。
実行タイプにmachine(VM)を選択し、pipenvを利用した仮装環境でビルドとデプロイを行なっています。
.circleci/config.ymldeploy_template: &deploy_template machine: true working_directory: ~/project/app steps: - checkout - run: name: install python 3.6.0 command: | pyenv install 3.6.0 pyenv global 3.6.0 - run: name: sam build command: | pip install pipenv python -m venv venv source venv/bin/activate pipenv install make build - run: name: deploy AWS Key command: | source venv/bin/activate make package make deploy version: 2.1 jobs: deploy-dev: <<: *deploy_template deploy-prd: <<: *deploy_template workflows: version: 2.1 aws-sam-deploy: jobs: - deploy-dev: context: aws-dev filters: branches: only: - /develop/ - deploy-prd: context: aws-prd filters: branches: only: /master/Python仮装環境の構築
pipenvを利用して仮装環境を構築します。
pipenvについては下記の記事が参考になりましたので、掲載させて頂きます。
Pipenvを使ったPython開発まとめ仮装環境にAWS CLIとAWS SAM CLIを追加します。
CircleCIでもこの仮装環境を利用してAWS SAMのビルドとデプロイを行います。$ pipenv install awscli $ pipenv install aws-sam-cliビルド
sam build
コマンドを利用して、ビルドを行います。
sam build - AWS Serverless Application Modelsam build --template template.yaml --use-container今回は、オプションに
--use-container
を指定します。
このオプションを指定することで、Lambdaの動作環境と同等のコンテナ内でビルドすることができます。サンプルのソースコードには追加していませんが、Curatorを利用する際に
--use-container
オプションを追加しなかった場合、ビルドでエラーとなりました。関数がネイティブでコンパイルされたプログラムを持つパッケージに依存している場合は、SAM ビルドコマンドに --use-container フラグを指定することもできます。この --use-container フラグは、Lambda のような環境でローカルに関数をコンパイルするので、それらをクラウドにデプロイするときには正しいフォーマットになります。
パッケージ&デプロイ
sam package
、sam deploy
コマンドを利用してデプロイを行います。
AWS SAMをデプロイするためには、事前にS3バケットを用意しておく必要があります。CircleCIの設定ファイル
ポイントは、下記の2点です。
- 実行タイプにmachine(VM)を選択
- コンテキストの使用して複数のAWSアカウントにデプロイを行う
詳細は
config.yml
をご確認ください。実行タイプにmachine(VM)を選択
sam build
に--use-container
オプションを追加しているため、コンテナ内でビルドが実行されます。実行タイプに
Docker
を指定し、setup_remote_docker
を設定することで、CircleCIでコンテナを使用することができると思ったのですが、ファイルをマウントすることができずにビルドが失敗してしまいました。そのため、machineを指定しています。参考:Docker コマンドの実行手順 - CircleCI
複数のAWSアカウントにデプロイを行う
開発環境を本番環境でAWSアカウントを分けているケースが多いと思います。
今回はCircleCIのContexts機能を利用することで、両方のアカウントにデプロイできるように設定を行なっています。こちらの記事を参考にさせて頂きました。
CircleCIで複数のAWSアカウントを扱う方法参考記事
CircleCI と GitHub で AWS SAM のサーバーレスアプリを自動デプロイしてみた (開発環境 & 本番環境) | Developers.IO
Pipenvを使ったPython開発まとめ
CircleCIで複数のAWSアカウントを扱う方法
- 投稿日:2019-12-15T19:19:29+09:00
GitHub Actions上でTerraform CI/CD環境を構築する
はじめに
Github Actionsの正式版がリリースされて1ヶ月ほど経ちました。中々さわれてなかったのですが、TerraformのCI/CD環境構築の調査で動かしたのでその時のまとめです。
サンプルプロジェクト
とりあえず試すだけであれば、Terraform公式が用意しているTerraform GitHub Actionsを使えばすぐに終わってしまいます。
それだけだと面白くないのでなるべく実際の運用を想定して動かしていきます。とはいえあらゆるパターンを試すのは難しいので以下のようなサンプルプロジェクトを仮定します。
- providerはAWS
- moduleを使う
- workspaceを使う
- リポジトリ内に複数のmain.tfがある
ディレクトリ構成は以下。(ファイルの中身は本題ではないので割愛)
├── modules │ ├── ec2 │ │ ├── main.tf │ │ └── variables.tf │ └── iam-role │ ├── main.tf │ └── variables.tf ├── service1 │ ├── ec2 │ │ ├── main.tf │ │ └── variable.tf │ └── iam-role │ ├── main.tf │ └── variable.tf └── service2 ├── ec2 │ ├── main.tf │ └── variable.tf └── iam-role ├── main.tf └── variable.tfゴール
上に書いた構成のサンプルに対して以下の1〜4を行う。
- masterブランチへのプルリクエスト作成をトリガーに以下の3つ(以降、自動テストと呼ぶ)を実行する。
- terraform fmt
- terraform validate
- terraform plan
- 一度のプルリクエストで複数のworkspaceに対してまとめて自動テストを実行する。
- masterブランチへのプッシュ(マージ)をトリガーに自動テストと
terraform apply
を実行する。自動テストのwrokflowが成功した場合のみapplyを実行する。- 自動テストおよびapplyは毎回リポジトリ内の全てのmain.tfに対して実行するのではなく、変更があったファイルに関係するmain.tfに対して実行する。
1. masterブランチへのPR作成をトリガーに自動テストを実行
workflowの作成
早速workflowを作成していきますが、Terraformの公式が用意してくれているTerraform GitHub Actionsを使っていきます。
以下の公式のサンプルを修正していく形で進めます。.guthub/workflows/terraform.ymlname: 'Terraform GitHub Actions' on: - pull_request jobs: terraform: name: 'Terraform' runs-on: ubuntu-latest steps: - name: 'Checkout' uses: actions/checkout@master - name: 'Terraform Format' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'fmt' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: 'Terraform Init' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'init' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: 'Terraform Validate' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'validate' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: 'Terraform Plan' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'plan' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}AWSのcredential
AWSのリソースを扱うためのcredentialをsecretsから取得するようにします。
secrets.xxx
でGitHub上で設定したSecretsの情報できます。
GitHub側の設定はリポジトリの Settings > Secrets で設定してあげればOKです。
stepsの中にあったenvを一つ上の階層に移動させ、そこにAWSのクレデンシャルの情報を設定しています。Checkoutのstepでは使用しませんが、何度も書くのも冗長なので上に持ってきています。
.guthub/workflows/terraform.ymlname: 'Terraform GitHub Actions' on: - pull_request jobs: terraform: name: 'Terraform' runs-on: ubuntu-latest env: # 追加 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # 移動 AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} # 追加 AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} # 追加 steps: - name: 'Checkout' uses: actions/checkout@master - name: 'Terraform Format' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'fmt' - name: 'Terraform Init' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'init' - name: 'Terraform Validate' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'validate' - name: 'Terraform Plan' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'plan'workspaceを指定する
まずはdevというworkspaceで動くようにしてみます。 Terraform GitHub Actionsでは
TF_WORKSPACE
という環境変数でworspaceを指定できるのでenvに追加します。.guthub/workflows/terraform.yml(一部抜粋)env: TF_WORKSPACE: dev # 追加実行するディレクトリを指定する
main.tfが複数に別れているので、どのmain.tfがあるディレクトリで実行するかを指定します。
Github Actionsに準備されているstrategy.matrixを指定してあげれば簡単に複数の設定に対してjobを実行することができます。
今回のサンプルだと4つmain.tfがあるのでそれぞれstrategy.matrix
を指定してそれをtf_actions_working_dir
で渡します。.guthub/workflows/terraform.yml(一部抜粋)jobs: terraform: name: 'Terraform' runs-on: ubuntu-latest strategy: # 追加 strategyのmatrixでworkspace設定。この組み合わせの数だけjobが実行される matrix: workdir: [./service1/ec2, ./service2/ec2, ./service1/iam-role, ./service2/iam-role] env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} TF_WORKSPACE: dev steps: - name: 'Checkout' uses: actions/checkout@master - name: 'Terraform Format' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'fmt' tf_actions_working_dir: ${{ matrix.workdir }} # 追加 - name: 'Terraform Init' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'init' tf_actions_working_dir: ${{ matrix.workdir }} # 追加 - name: 'Terraform Validate' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'validate' tf_actions_working_dir: ${{ matrix.workdir }} # 追加 - name: 'Terraform Plan' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'plan' tf_actions_working_dir: ${{ matrix.workdir }} # 追加masterへのプルリクエストをトリガーにする
以下のようにbranchesのフィルターを設定すればOK。(以下の設定の場合はPR作成時以外にもプルリクエストを作成したブランチにプッシュした場合などいくつかのイベントでトリガーされます。)
.guthub/workflows/terraform.yml(一部抜粋)name: 'Terraform GitHub Actions' on: pull_request: branches: - master動作確認
ここまでの設定で動作を確認してみます。masterブランチに対してプルリクエストを作成すると、無事にActionsが実行されるはずです。
strategy.matrix
で指定したディレクトリの数だけjobが実行されているのがわかります。2. 一度のプルリクエストで複数のworkspaceに対してまとめて自動テストを実行する
実行ディレクトリを指定した時と同じように
strategy.matrix
を使えば簡単に実現できます。
devとprodの二つのworkspaceがあると想定して以下のように設定します。.guthub/workflows/terraform.yml(一部抜粋)terraform: name: 'Terraform' runs-on: ubuntu-latest strategy: matrix: env: [dev, prod] # 追加 workdir: [./service1/ec2, ./service2/ec2, ./service1/iam-role, ./service2/iam-role] env: TF_WORKSPACE: ${{ matrix.env }} # matrixから値を取得するように修正動作確認
ここまでの設定で動作確認します。masterブランチに対してプルリクエストを作成すると今度は
4(workdir) × 2(env) = 8
のjobが動きます。3. masterブランチへのプッシュ(マージ)をトリガーに自動テストとapplyを実行する
masterブランチへのプッシュ(マージ)をトリガーに実行
pull_requestと同じ階層にpushを追加してbranchesにmasterブランチを指定します。
これによりmasterブランチへのプルリクエスト or masterブランチへのプッシュの条件でjobが実行されるようになります。.guthub/workflows/terraform.yml(一部抜粋)name: 'Terraform GitHub Actions' on: pull_request: branches: - master push: # 追加 branches: - master今回は一つのymlで定義しましたが、プルリクエストの場合とプッシュの場合でymlを複数に分けてしまうのもありだと思います。
apply用のstepを追加
applyも他と同様にTerraform GitHub Actionsに用意されているのでそれを使います。
ただしapplyはmasterへ修正が反映された時のみ動いてほしいのでif
で条件を追加しています。github contextでjobを実行のトリガーとなったイベント情報などを取得できるので、masterブランチへのイベントの場合のみ実行されるようにします。.guthub/workflows/terraform.yml(一部抜粋)- name: 'Terraform Plan' uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'plan' tf_actions_working_dir: ${{ matrix.workdir }} - name: 'Terraform Apply' # ここから下を追加 if: github.ref == 'refs/heads/master' # プルリクエストの作成では動かないように uses: hashicorp/terraform-github-actions@master with: tf_actions_version: 0.12.17 tf_actions_subcommand: 'apply' tf_actions_working_dir: ${{ matrix.workdir }}planまでのstepが成功した場合のみapplyを実行
デフォルトで前のstepが失敗したは次のstepは実行されないため特に設定しなくてもplanが成功した時のみapplyが実行されます。
動作確認
ここまでの設定で動作確認します。プルリクエストを作成してマージするとapplyまで実行されています。
4. ディレクトリ内のファイルが修正されたmain.tfに対してのみjobを実行する
ここまででプルエスト作成でplanを実行してmasterブランチをマージしてapplyするという流れができましたが、実際の運用を想定した時に以下のような課題で出てきそうです。
- main.tfの数やworkspaceのが増えると一度の修正で実行されるjobの数が増え、jobの並列実行数が増加し続けGitHub Actionsの上限までいってしまう。
- 修正と関係ないmain.tfに対してもplan、applyが実行されるので、修正内容に関わらず一番実行時間が長いjobの分だけ時間がかかってしまう。
これらを解決するためにディレクトリ内の修正されたファイルに関連するjobのみ実行するような設定をしていきます。
pathsフィルタの設定
pathsフィルタを使うことで特定のディレクトリやファイルに変更が合った場合にjobを実行することができます。例えば以下のように設定するとmasterブランチへのプルリクエスト かつ service1ディレクトリとmodulesディレクトリ内に変更が合った場合にjobが実行されます。
.guthub/workflows/terraform.yml(一部抜粋)name: 'Terraform GitHub Actions' on: pull_request: branches: - master paths: # 追加 - service1/** - modules/**ちなみに差分をどのように検知しているかは、プルリクエストやすでにあるブランチへのプッシュ、新規ブランチへのプッシュによって以下のように動作するようです。
Pull requests: Three-dot diffs are a comparison between the most recent version of the topic branch and the commit where the topic branch was last synced with the base branch.
Pushes to existing branches: A two-dot diff compares the head and base SHAs directly with each other.
Pushes to new branches: A two-dot diff against the parent of the ancestor of the deepest commit pushed.pathsフィルタを利用して
./service1
ディレクトリ以下のファイルと./modules
ディレクトリ以下のファイルに変更がある場合、./service1
以下のmain.tfを実行する./service2
ディレクトリ以下のファイルと./modules
ディレクトリ以下のファイルに変更がある場合、./service2
以下のmain.tfを実行するのように動作させるため元のterrafrom.ymlを以下のように二つに分割していきます。
.github/workflows/service1.ymlname: 'Terraform Service1' on: pull_request: branches: - master paths: # service1に関係する変更のみを検知 - service1/** - modules/** push: branches: - master paths: # service1に関係する変更のみを検知 - service1/** - modules/** jobs: terraform: name: 'Terraform' runs-on: ubuntu-latest strategy: matrix: env: [dev, prod] workdir: [./service1/ec2, ./service1/iam-role] # service1のディレクトリのみを指定 # env以下は元のterraform.ymlと同じ.github/workflows/service2.ymlname: 'Terraform Service2' on: pull_request: branches: - master paths: # service2に関係する変更のみを検知 - service2/** - modules/** push: branches: - master paths: # service2に関係する変更のみを検知 - service1/** - modules/** jobs: terraform: name: 'Terraform' runs-on: ubuntu-latest strategy: matrix: env: [dev, prod] workdir: [./service2/ec2, ./service2/iam-role] # service1のディレクトリのみを指定 # env以下は元のterraform.ymlと同じ各main.tfの依存関係などを考慮し出すと複雑になりそうなど課題はありますが、pathsフィルタを使えば修正ファイルに応じたjobを実行できそうです。
動作確認
修正後の状態(service1.yml、service2.yml)で試しに
service1/ec2/variable.tf
のみ修正してプルリクエストを作成すると./service1
ディレクトリ内のmain.tfに関するjobが実行されることが確認できました。まとめ
簡単なTerraformリポジトリを想定してGitHub Actions上でCI/CDを作り、どんな設定ができるか試していきました。
初めてGitHub Actionsを使いましたがドキュメントもしっかり書かれていてつまることも少なく進めらかなり使いやすいと感じました。他にも以下のあたりがいい感じでした。
- GitHub上のサービスだけあって、GitHubのコンテキストの情報を簡単に取得できる
- 無料で並列実行までできるのすごい
- maxrixによる並列実行がかなり便利
- pathsフィルタがかなり便利
個人的にはapplyする時はCircle CIで用意されているManual Approvalのような手動で実行を承認する機能が欲しいところですが、要望も多そうなのでそのうち追加されるのではと思っています。
今回想定したTerraformリポジトリはシンプルなので、実運用にそのまま使えるかという難しいかもしれませんが、どんなことができそうかイメージをつけられたのがよかったです。
参考
GitHub Actionsドキュメント
terraform-github-actionsドキュメント
terraform-github-actions
- 投稿日:2019-12-15T19:07:58+09:00
Amazon Personalize/Forecastハンズオンの私的補講
- Amazon Personalize
- Amazon Forecast
こちらの2つのサービスはAWSで提供されている機械学習サービスで、機械学習に詳しくなくても簡単に始められることが特徴です。
この2つのサービスを体験するためのハンズオン資料がAWSより公開されています。
https://pages.awscloud.com/event_JAPAN_Hands-on-Amazon-Personalize-Forecast-2019.html先日こちらのハンズオンを資料を元に実際にやってみて、個人的に気になった箇所などを補足としてまとめてみます。
Amazon Personalize
ソリューションのレシピ(学習アルゴリズム)
- Automatic (AutoML)
- 適切なアルゴリズムをPersonalizeが判断して採用する
- 指定したレシピリストの中から選択する設定も可能
- 現状はHRNNとHRNN-Metadataの2択?今後レシピが増えると選択肢が増すかも
- HRNN
- Hierarchical Recurrent Neural Networks
- UserとItemの関係(Interactions)のみを利用
- 時間経過で重み付けされる
- HRNN-Metadata
- Interactionsの他にUsersやItemsの属性情報を利用
- datasetに少なくともUsersかItemsのどちらかをインポートしておかないと選択出来ない
- HRNN-ColdStart
- 頻繁にItemsやInteractionsを追加する場合に利用
- 関連性の低いItemやInteractionを除外したサブセットを作成することで高速に新しいItemのレコメンドが可能になる
- datasetにItemsをインポートしておかないと選択出来ない
- Popularity Count
- 人気のItemをそのまま返す
- 全てのユーザに同じ結果を返す
- Personalized Ranking
- インプットとしてItemのリストを渡すと、ユーザごとに最適化されたランキングを返す
- SIMS
- Itemの協調フィルタリングとして動作する
- インプットとしてItemを渡すと類似のItemを返す
レシピごとのハイパーパラメータの詳細についてはドキュメントを参照
参考: HRNN レシピAPIでの推論実行
レコメンドの実行はAWS APIで実行出来ます。
現在のところHTTPエンドポイントとしてデプロイする機能などはなさそうですので、リアルタイム推論のAPIを構築したい場合はServerlessAPIなどでLambdaなどから呼び出す形がよさそうです。
Personalizeのリアルタイム実行コマンドはpersonalize-runtime
で、推論のサブコマンドはget-recommendations
、ランキングのサブコマンドはget-personalized-ranking
です。HRNN推論
AWS CLIでリクエスト実行
aws personalize-runtime get-recommendations \ --campaign-arn arn:aws:personalize:ap-northeast-1:xxxxxxx:campaign/my-campaign \ --user-id 1 \ --num-results 3レスポンス
{ "itemList": [ { "itemId": "163" }, { "itemId": "2355" }, { "itemId": "353" } ] }SIMS
AWS CLIでリクエスト実行
aws personalize-runtime get-recommendations \ --campaign-arn arn:aws:personalize:ap-northeast-1:xxxxxxx:campaign/sims-campaign \ --item-id 163 \ --num-results 3レスポンス
{ "itemList": [ { "itemId": "198" }, { "itemId": "22" }, { "itemId": "173" } ] }Personalized Ranking
AWS CLIでリクエスト実行
aws personalize-runtime get-personalized-ranking \ --campaign-arn arn:aws:personalize:ap-northeast-1:xxxxxxx:campaign/ranking-campaign \ --user-id 1 \ --input-list 22 192 239レスポンス
{ "personalizedRanking": [ { "itemId": "22" }, { "itemId": "239" }, { "itemId": "192" } ] }料金
- S3
0.023USD/GB/mo
少額なので基本気にする額ではないですが、データセットが大きい場合はそれなりに掛かってくる可能性もあります。
不要であればハンズオン終了時に削除は忘れずに。
- Amazon Personalize
サインアップ直後であれば無料期間が適用される可能性があります。
継続的に課金されそうなのはデータストレージ料金くらいのようです。
エンドポイント維持費用などは無いようなので慌てて削除する必要もなさそうですが、こちらもハンズオンが終わり次第ひととおり削除しておくのが無難です。以下は2019年12月現在の料金抜粋です。
ハンズオンを超えて利用する場合は必ず公式を確認してください。
料金 データ取り込み 0.05 USD/GB トレーニング時間 0.24 USD/トレーニング時間 推論実行(リアルタイム) 0.20 USD/TPS時間 推論実行(バッチ) 0.067 USD/レコメンデーション1000件 トレーニング時間
4v CPU と 8 GiB メモリを使用する 1 時間のコンピューティング能力
実際のインスタンスタイプはデータ等に応じてPersonalizeの方で自動選択するため、実際のトレーニング実行時間とは異なるTPS時間
TPS = 1秒あたりのリクエストの平均 (5分ごとの平均)
TPS時間 = 最小プロビジョンドTPS、または実際のTPSのどちらか大きい値(1時間単位での平均)バッチ推論
UserごとのPersonalizeやRankingの場合はUser数に対してカウント、SIMSなどのItemの場合はItem数に対してカウント
Amazon Forecast
サンプルデータの注意点
ハンズオン資料のsmallデータを利用しましたが、このsmallデータには時系列データが
2015-01-01 00:00:00
の分まで存在します。
資料では予測フェーズでのLookup forecastの際に、予測範囲の開始指定が以下のように記述されていますが、Start: 2015/01/01 0時0分 (インポートしたデータ 2014/12/31 23時の次)実際にインポートしたデータとは1時間のズレがあります。
ConsoleからLookup forecastを実行する分には問題なく実行できていますが、実際に生成される予測値は2015-01-01 01:00:00
からのデータとなっています。
AWS APIからこの範囲で実行した場合は、指定範囲エラーとなり実行できません。
start-dateは2015-01-01 01:00:00
を指定する必要があります。
ちなみにコンソールで開始時刻を2014-12-31 23:00:00
を指定して実行すると以下のようになります。Consoleでは
2015-01-01 00:00:00
までの実データと、2015-01-01 01:00:00
からの予測データが合わせて表示されていることが分かります。Predictorのアルゴリズム
- Automatic (AutoML)
- 適切なアルゴリズムをForecastが判断して採用する
- Personalizeにあったような選択肢機能はなさそう
- ETS
- 指数平滑法
- 季節性のあるデータに強い
- 時間の経過とともに重みが減少する
- ARIMA
- 自己回帰和分移動平均
- 季節性、定常性のあるデータに強い
- 時間経過に依存しない
- DeepAR+
- RNNを利用した学習アルゴリズム
- ARIMAやETSのような比較的シンプルな統計アルゴリズムよりも複雑な予測が可能
- NPTS
- ノンパラメトリック時系列アルゴリズム
- 時系列データが断続的であったりスパースなデータの場合に強い
- Prophet
- 局所的なベイズ構造時系列モデル
- 複数の強い季節性を持つ場合や大きな外れ値がある場合に強い
アルゴリズムごとの詳しい特性やハイパーパラメータの詳細についてはドキュメントを参照
参考: ETSAPIでの予測実行
予測実行のコマンドは
forecastquery
、サブコマンドはquery-forecast
です。AWS CLIでリクエスト実行
aws forecastquery query-forecast \ --forecast-arn arn:aws:forecast:ap-northeast-1:xxxxxxx:forecast/test_forecast \ --start-date 2015-01-01T01:00:00Z \ --end-date 2015-01-01T02:00:00Z \ --filters "{\"item_id\":\"client_1\"}"レスポンス
{ "Forecast": { "Predictions": { "p10": [ { "Timestamp": "2015-01-01T01:00:00", "Value": 18.008272171020508 }, { "Timestamp": "2015-01-01T02:00:00", "Value": 16.235431671142578 } ], "p50": [ { "Timestamp": "2015-01-01T01:00:00", "Value": 19.338420867919922 }, { "Timestamp": "2015-01-01T02:00:00", "Value": 17.989978790283203 } ], "p90": [ { "Timestamp": "2015-01-01T01:00:00", "Value": 20.668569564819336 }, { "Timestamp": "2015-01-01T02:00:00", "Value": 19.744525909423828 } ] } } }料金
こちらも初めてForecastを使う場合は無料範囲で利用できる可能性があります。
料金 データ取り込み 0.088 USD/GB トレーニング時間 0.24 USD/トレーニング時間 予測生成 0.60 USD/予測1000件 トレーニング時間
内部で並列に学習が行われる場合などもあるため、実際の実行時間とは異なる
Predictorの作成とForecastの作成どちらもこの価格が適用される予測
予測件数は、予測期間に依らず、1つの時系列で1件
予測を生成したユーザの数と、作成した分位点(Forecast types)に依存する
Forecast typesは、デフォルトでP10,P50,P90の3分位点
5ユーザに対してデフォルトの3分位点で予測を生成した場合、予測件数は15件こちらもストレージ以外に維持費用がかかるものはなさそうですが、不要であればハンズオン終了時点で忘れずに一通り削除しましょう。
- 投稿日:2019-12-15T18:39:22+09:00
AWS CLIのコマンド補完する設定
Zshに設定する
パスを確認する
$ which aws_completer /usr/local/bin/aws_completer
.zshrc
に以下追加するsource '/usr/local/bin/aws_zsh_completer.sh'
.zshrc
の内容を反映する。source ~/.zshrcBashに設定する
パスを確認する。
$ which aws_bash_completer /usr/local/bin/aws_bash_completer
.bashrc
に以下追加する。complete -C '/usr/local/aws/bin/aws_completer' aws
.bashrc
の内容を反映する。source ~/.bashrc
- 投稿日:2019-12-15T18:15:45+09:00
ECSでTCPソケットエラー 事象と対応
ECS + Nginx,GunicornでTCPソケットエラー
- Jmeterで負荷をかけると503 Bad Gatewayが多発した
- どうやらECSには以下の制約があるそう。
net.core.somaxconn=128
現時点では Fargate では変更できないので、タスクを多く起動して頂くことで解決して下さい。
【週刊 Ask An Expert #04】AWS Loft Tokyo で受けた質問まとめ #AWSLoft
- 他にもDBのチューニングも考慮した方が良い
ディスクI/O
RDSのパフォーマンスインサイトでIndexの分析
パフォーマンスインサイトではIndexの使用率やクエリの結果が確認可能
対応方法の検討
1 ECSを予め増やしておく
一番ナンセンスだが、即時で対応が可能。
ELB + ECSの環境であれば、最小コンテナ数と最大コンテナ数の設定を変えるだけで済む。必要に応じて、ELBのPre-Warmingをすること!2 ELBでのコネクション数を閾値にコンテナを動的に増やす
ELBのActiveConnectionCountを閾値にして、コンテナ数を増やす。
ECSのポリシーの追加-アラームを追加で設定できる
3 スケジューリングでECSをオートスケールする
利用開始時間の前にECSをスケールアウトさせておく。
アプリケーションオートスケーリング-lambda-コンテナの数
余談
EC2の監視間隔はデフォルトで5分間隔だが、ECSはデフォルト1分間隔で監視できる。
- 投稿日:2019-12-15T17:20:57+09:00
AWS Cloudwatchチートシート
アラームを評価する
[期間]アラームの各データポイントを作成するためにメトリクスや式を評価する期間です。これは秒単位で表されます。期間として 1 分を選択した場合、1 分ごとに 1 つのデータポイントが存在します。
[評価期間] は、アラームの状態を決定するまでに要する直近の期間 (データポイント) の数です。
[Datapoints to Alarm (アラームを発生させるデータポイント数)] は、アラームが ALARM 状態に移るためにしきい値を超える必要がある評価期間内のデータポイントの数です。しきい値を超えたデータポイントは連続している必要はありませんが、すべてが [評価期間] に相当する直近のデータポイント数に含まれている必要があります。
- 投稿日:2019-12-15T16:58:00+09:00
典型的なwebサービスのawsを使ったインフラ構成を考えてみるS3, ECS
概要
フロントエンドをVueを使って作り、APIなどのバックエンドをpythonのFlaskという軽量なフレームワークを使って作りました。今回は、ローカル環境下で動いていたアプリケーションをgithubから自動的にAWSの方にdeployすることができるようなインフラ構成を設計してみます。
不十分な点などありましたら、アドバイスいただけると幸いです。
使うもの
AWS関連
- EC2
- RDS
- ECR
- ECS(fargate)
- S3
- CloudFront
- Route53
- IAM
CI/CD関連
- Circle CI
システム構成図
まず、public subnetとprivate subnetを持つVPCを作成します。外部からのアクセスを許容するpublic subnetには、ロードバランサーと踏み台(ログイン)サーバーを設置します。外部から直接アクセスできないprivate subnetには、APIサーバーとデータベースを置きます。
次に、API serverであるFlaskアプリとproxy serverとして利用するNginxは、コンテナ化して、ECS(Forgate)で管理します。Forgateを使うことで、自動でコンテナのスケーリングや再起動などをしてくれます。RDSは、AWSのamazon Auroraを利用します。こちらも、定期的にレプリカを作成してくれるので、万が一データを損失する場合やデータベースにアクセスできなくなった場合にも安心です。amazon Auroraにアクセスできるのは、(APIサーバーと)踏み台サーバーのEC2からのみで、外部からデータベースをいじることはできません。Login serverのEC2のインスタンスは、秘密鍵が保存されているローカルPCからのみアクセスできます。
フロントエンドのコードは、S3にデプロイし、CloudFront経由で配信します。あとは、Route53でのドメイン設定や、Certificate Managerでの証明書取得、ロードバランサーの設置など、細々した設定をしてあげると完成です。
CI/CD関連
Circle CIが非常に優秀で、githubにpushすると、ECRにpushして、ECSにdeployまでしてくれます。具体的には、公式ドキュメントを一読することをお勧めします。
config.ymlversion: 2.1 orbs: aws-ecr: circleci/aws-ecr@0.0.2 aws-ecs: circleci/aws-ecs@0.0.3 workflows: build-and-deploy: jobs: - aws-ecr/build_and_push_image: account-url: "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com" repo: "${AWS_RESOURCE_NAME_PREFIX}" region: ${AWS_DEFAULT_REGION} tag: "${CIRCLE_SHA1}" - aws-ecs/deploy-service-update: requires: - aws-ecr/build_and_push_image aws-region: ${AWS_DEFAULT_REGION} family: "${AWS_RESOURCE_NAME_PREFIX}-service" cluster-name: "${AWS_RESOURCE_NAME_PREFIX}-cluster" container-image-name-updates: "container=${AWS_RESOURCE_NAME_PREFIX}-service,tag=${CIRCLE_SHA1}"上記
.circleci/config.yml
をgithubにpushするとcircleCIの設定が適応されます。CircleCIで定めた環境変数は、CircleCIのダッシュボードのEnvironment Variables
から設定します(後述)。
S3へのdeploy
- IAMでS3へのアップロード用のuserを作り、
AmazonS3FullAccess
権限を与える- aws cliからuploadするscriptsを書く
こちらに関しては、たくさん説明記事があるので、細かい内容は省略します。
参考: Amazon S3でSPAをサクッと公開するインフラ構築までの流れ
VPCの作成
- IPv4 CIDR 10.2.0.0/16
subnetの作成
- IPv4 CIDR 10.2.0.0/20(public-subnet-a)
- IPv4 CIDR 10.2.16.0/20(public-subnet-c)
- IPv4 CIDR 10.2.32.0/20(private-subnet-a)
- IPv4 CIDR 10.2.48.0/20(private-subnet-c)
アベイラビリティゾーンA, Cに2つずつ、public subnetと private subnetを設置します。CIDRの設定に気をつけてください。
Internet Gatewayの生成
- Internet Gatewayを生成して、先ほど生成したVPCにアタッチする
- 2つのpublic subnetをInternetGWに紐付ける
NAT Gatewayの作成
- NAT Gatewayを作成して、public subnetの一方にアタッチする
- これにより、private subnetへアクセスできるようになる
デフォルトでは、subnetを生成するとプライベートルートテーブルが選択されます。他の使用可能なルートテーブルを生成し、送信先 0.0.0.0/0 がインターネットゲートウェイ (igw-xxxxxxxx) にルーティングされるようにします。その後、public subnetとそのルートテーブルを紐付けます。
※VPCとsubnet,Internet GWなどの詳細な設定方法は公式ドキュメントを参照してください。
ECRでレポジトリを作成
circleciに権限を付与する
IAMでcircleciによるdeploy用のuserを作成します。
今回は、ECR/ECSに関する権限を付与します。
- AmazonEC2ContainerRegistryFullAccess
- AWSCodeDeployRoleForECS
- AmazonEC2ContainerServiceFullAccess
- AmazonECSTaskExecutionRolePolicy
- AWSDeepRacerCloudFormationAccessPolicy
Environment Variablesの設定
- AWS_ACCOUNT_ID(ex:754569708956)
- AWS_DEFAULT_REGION(ex:ap-northeast-1)
- AWS_RESOURCE_NAME_PREFIX(ex:flask-app)
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
※AWS_RESOURCE_NAME_PREFIXは、ECRのレポジトリの名前と同じにしないとエラーが出ます。
nginxコンテナをアップロード
- こちらは、頻繁に変更しないと思うので、circleCIには含めず手動で行う
- 詳細は公式ページを参照のこと
- confファイルで、nginxはproxy serverとしての設定する
ディレクトリ構成nginx ├── Dockerfile └── conf └── default.confDockerfileFROM nginx COPY conf/default.conf /etc/nginx/conf.d/default.conf EXPOSE 80 ENTRYPOINT nginx -g 'daemon off;'default.confserver { listen 80; server_name localhost; location / { #root /usr/share/nginx/html; #index index.html index.htm; proxy_pass http://127.0.0.1:5000; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } }ECS Fargateの作成
- fargateでclusterを作成する VPCは先ほど作った物を使いますので、ここで新しく作成する必要はありません。
fargateでtaskを作成する
タスク実行ロールは、ecsTaskExecutionRole
を指定します。ECRで登録したdocker imageのURLを貼り付けます。コンテナのportを公開するのを忘れないようにしましょう(flask container port:5000)。fargateでserviceを作成する
- subnetは先ほど作ったprivate subnetを二つ割り当てます
- fargate service用のsecurity groupを作成する(port:80)
- EC2でロードバランサーを作成する
- ELB用のsecurity groupを作成する(port:80)
- target groupを作成する(port:80, ターゲットの種類:ip)
- ロードバランス用のコンテナではnginxのcontainerを指定して、ターゲットグループは先ほど作った物を指定
※ flask-app用のコンテナの名前は、AWS_RESOURCE_NAME_PREFIX-serviceと同じ物にしないと、circleCIのdeployの時にエラーになります
RDBとの連携
- 踏み台サーバーを立てる(EC2)
- mysql-clientをinstallする
- RDSでamazon Auroraを選択する
- aurora DB用のsecurity groupを作成する(port:3306)
- private subnetをまとめたsubnet groupを作成する
- 踏み台サーバーからendpointに向けてログインできるか確認する
- ECSのコンテナの方で環境変数を設定する
- DB_NAME
- DB_USER
- PASSWORD
- HOST
エラーハンドリング
CannotPullContainerError: Error response from daemon: Get... : net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)
NAT GWがきちんと設定されていなかったら、このエラーが出ます。NATの設定を見直してみましょう。参考Task failed ELB health checks in (target-group...)
ELBのhealth checkに失敗すると表示されます。ELBを設定した時に、defaultでは、/がhealth checkのendpointになります。APIサーバーで/のPATHでGETを用意していていなかったら、ここでエラーになるので、ELBの設定の時に、PATHを変更するか、APIサーバーの方で、Health check用のAPIを作るようにします。まとめ
AWSのサービスをうまく利用することで、再現性が高くスケーラブルなアプリケーションを作成することができました。また、terraformなどを使って、awsの設定自体も出来るだけ、コードに落とせるようにしたらいいなあと思いました。
- 投稿日:2019-12-15T16:47:44+09:00
AWS環境構築(実装編)~VPC~
概要
AWSについての設定の備忘録。今回はVPCの実装を試みる。
そもそもVPCって何って人はこちら(今回使用する用語の解説もあります。)。実装
VPC作成
- コンソールからVPCを選択。
- VPCの作成。
CIDRに10.0.0.0/16
テナンシーはデフォルト
このテナンシーは専有にすると料金上昇。サブネット作成
VPCに先ほどのVPC
Availability Zoneにap-northeast-1a
CIDRに10.0.0.0/24さらにもう一つサブネットを追加
VPCに先ほどのVPC
Availability Zoneにap-northeast-1c
CIDRに10.0.1.0/24インターネットゲートウェイ作成
- コンソールからインターネットゲートウェイを選択。
- インターネットゲートウェイの作成。
- このままだとVPCがattachされてないので、attachする。
- 作成したインターネットゲートウェイを選択して、アクションから「VPCにアタッチ」を選択
ルートテーブル作成
- コンソールからルートテーブルを選択
- ルートテーブルの作成
- 先ほどのVPCを選択して作成。
- 画面下のルートの部分からルートの編集を選択。
- アドレス0.0.0.0/0, ターゲットにインターネットゲートウェイを選び、先ほど作成したものを選ぶ。
- アクションからサブネットを関連づける
以上で設定を完了。
- 投稿日:2019-12-15T16:42:28+09:00
イベント風景の自動Tweetとパラパラ漫画をつくろう from 長野
こんにちは!IoTLT Advent Calendar 2019 19日目担当のchinoppyです!
今回は、長野版IoT縛りの勉強会時に使用した、イベント風景自動Tweet/その写真を使って作ったパラパラ漫画と、構築したシステムについて紹介しますー
めざすもの
イベント風景自動Tweet
パラパラ漫画
撮影間隔が30分だったので、ちょっと物足りないですが・・・
システム構築
つかったもの
ハード
- Raspberry Pi 3 Model B
- OS:raspbian
- Raspberry Pi Camera Module V2 カメラモジュール
- SORACOM SIM、USBドングル AK-020
クラウド
- SORACOM
- SORACOM Beam
- AWS
- IoT Core
- Lambda
- STS
- S3
構成
処理内容
- Raspberry Piで写真撮影
- SORACOM経由でIoT Coreへトークンの取得リクエストをする
- STS経由でトークンを取得し、Raspberry Piへ送る
- 2で取得したトークンを使用して、S3へアップロード
- S3のTriggerでLambdaを実行し、Tweetする!
※ちなみに、今回、SORACOMを使用して、IoT Core経由でトークンを取得するようにしていますが、、、
※Raspberry Pi側にAWSのセキュリティ情報(シークレットキーなど)を保持させても問題ない環境でWifiが利用可能であれば、
※2、3は不要で、直接、S3へアップロードするのが楽かと思います!環境構築
【Raspberry Pi】
ソースの詳細はこちら
https://github.com/peacemaker07/iot_making_for_raspberry_pi
※READMEなどちゃんと整備できていなくてすみません。。。
主になるソースはこちら
https://github.com/peacemaker07/iot_making_for_raspberry_pi/blob/master/main_camera.py
前提
- OSはインストールしてあり、SSHで接続できる
- カメラは接続済み
- USBドングルにSOARCOMのSIMがはいっていて、SORACOMへ接続済み
- 参考 : SORACOM Airの設定
Raspberry Piで写真撮影
この部分で、撮影タイミングの場合は撮影し、撮影タイミングではない場合は処理終了
main_camera.py・・・ # カメラ撮影 camera_obj = Camera(camera_shadow) if not camera_obj.is_shooting(): return camera_obj.shooting() ・・・撮影時に使用するライブラリはpicameraし、撮影してます
utils/camera.pyfrom picamera import PiCamera ・・・ def _shooting(self): ・・・ camera = PiCamera() ・・・ # プレビュー開始 camera.start_preview() # Camera warm-up time time.sleep(5) # 撮影 camera.capture(image_path, quality=self.shadow.quality) # プレビュー終了しカメラクローズ camera.stop_preview() camera.close() ・・・SORACOM経由でIoT Coreへトークンの取得リクエストをする
トークンの取得のリクエストと、取得はここで行っています
main_camera.py・・・ # tokenの取得要求 msg_token_req = MsgTokenReq(imsi=imsi) # token取得するためsubscribe実行 result_sub = tasks_mqtt.run_subscribe_by_mqtt.delay(host, port, msg_token_req.get_sub_topic()) time.sleep(2) try: mqtt_client = CommMqtt(host, port) mqtt_client.connect() result = mqtt_client.publish(msg_token_req) mqtt_client.disconnect() except: print("error") while not result_sub.ready(): time.sleep(1) value = redis_client.get('token') ・・・SORACOM→IoT Core→Lambda→STS の戻りを待たないといけないので、celeryを使用して待ち、取得したトークンをredis経由で取得します。
S3へアップロード
アップロードはこの部分で行っています
main_camera.py・・・ # 撮影画像をS3へputする send_images_data = SendImagesData(imsi, camera_obj) # TODO S3 bucket名を可変にする comm_aws_s3 = CommAwsS3('sample-iot-making-dev', [send_images_data]) ok_list, ng_list = comm_aws_s3.put_s3(payload_str) ・・・取得したトークンを使用して、アップロードしてます
定期的に実行
今回は、cronを使用しています
※リポジトリをpiユーザのホームディレクトリにした場合です$ cd /home/pi/iot_making_for_raspberry_pi # ライブラリインストール $ pip3 install -r requirements.txt # cron $ crontab -e */10 * * * * python3 ~/iot_making_for_raspberry_pi/main_camera.py【AWS】
severless frameworkを使って構築しました
ソースの詳細はこちら
https://github.com/peacemaker07/iot_making_for_sls
※こちらもREADMEなど整備できていなくすみません。。。
READMEのInstall部分、Usageの「deploy」を行い環境を構築
これで構成図のAWS部分は完成その他:IoT Coreでモノの登録を行う
デバイス証明書(あとにでてくるSORACOMでも使用)とShadowの機能を使うため、モノの登録を行います
こちらを参照に、モノの名前は「SIMのIMSI」
https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/register-device.html
IoT CoreのShadowを使う
設定値をShadowに持たせることにより、クラウドから設定を変更することができるようになります
IoT Coreのモノの中にある「シャドウ」に以下を登録
{ "desired": { "sensor": { "interval": 10 }, "camera": { "quality": 10, "shoot_timing": 1, "resolution_width": 800, "resolution_height": 600, "tmp_dir": "/tmp", "is_shooting": true, "is_change_status": true } }, "reported": { "sensor": { "interval": 10 }, "camera": { "resolution_width": 800, "is_shooting": true, "resolution_height": 600, "tmp_dir": "/tmp", "shoot_timing": 1, "quality": 10, "is_change_status": true } } }
- desired
- デバイスへ要求する値
- reported
- デバイスの状態
「shoot_timing」を変更すると撮影タイミングを変えられます
ただし、Raspberry Pi側のcronでの実行間隔に依存します。。。例)
- cronが10分毎
- ⇒shoot_timing=1 だと10分毎に撮影
- ⇒shoot_timing=3 だと30分毎に撮影
S3のTriggerでLambdaを実行し、Tweetする!
S3へ画像がputされたときに、Lambdaを起動するようにしています
serverless.yml・・・ # # S3 TriggerでLambdaを起動 # https://serverless.com/framework/docs/providers/aws/events/s3/ # notification_twitter: handler: handler.notification_twitter role: S3TriggerRoleForLambda events: - s3: bucket: ${file(./serverless.env.yml):NAME_PREFIX}-iot-making-${opt:stage} event: s3:ObjectCreated:* rules: - suffix: .jpg existing: true ・・・Lambdaはこちら
https://github.com/peacemaker07/iot_making_for_sls/blob/master/handler/notification_twitter.py
「event」内にbucket、objキーの情報があるので、S3から取得しTweetしてます
【SORACOM】
前提
- Webのコンソールは使用できる状態
- 使用するSIMは登録済み
SORACOM経由でIoT Coreへトークンの取得リクエストをする
この部分でBeamを使用するため、設定を行います
なのですが、まず、IoT Coreで取得したデバイス証明書の登録をします
「X.509証明書」を選択し必須項目を入力して登録
Beamの設定をします
- ホスト名は、IoT Coreのカスタムエンドポイントを入力
- 認証情報は、先ほど登録した二章情報を選択し設定
撮影に関しては、これで設定完了しTweetできるようになりました?
パラパラ漫画について
こちらは、S3へ保存された写真をダウンロードして、iMovieで動画にしましたw
AWS CLIを使用すると簡単にダウンロードできます
$ aws s3 cp --recursive --exclude '*' --include 'camera_20190420*' s3://[bucket名]/[IMSI]/camera/ ./ --profile [pfofile名]こんなかたちにすると、正規表現でダウンロードできるので楽です!
課題
- Raspberry Pi部分:S3へ画像送信時、bucket名が固定となっている
- ツイートの内容はコードを修正しないといけない。。。
- 送信間隔がcronでの間隔単位でしか設定できない。。。
別のイベントでも重宝してます?
- 投稿日:2019-12-15T16:29:09+09:00
lambdaのNode.jsバージョンを上げるときはログのフォーマット変更にも注意
バージョンでログ出力が違うから注意
2019年末にlambdaのNode.js 8.10がEOLを迎えます。
ログ出力の部分で微妙に動作が違うので念の為確認してからバージョンアップしましょう。
特にログ出力をライブラリで行っている場合、そちらの実装がどうなっているか見ておいた方が良いです。
kibanaとかでパースするロジックに変更が必要になるかも。Node.js 8.10
関数コード
exports.handler = async (event) => { // TODO implement const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; // デフォルトのテンプレートにconsole.log入れただけ console.log(`"This is Node.js 8.10 log."`); return response; };Execution Result
Function Logs:
START RequestId: fcfedef6-ed16-4236-a407-5cc3d38bb2fe Version: $LATEST
2019-12-15T06:42:13.112Z fcfedef6-ed16-4236-a407-5cc3d38bb2fe "This is Node.js 8.10 log."
END RequestId: fcfedef6-ed16-4236-a407-5cc3d38bb2fe
REPORT RequestId: fcfedef6-ed16-4236-a407-5cc3d38bb2fe Duration: 0.49 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 58 MB
2019-12-15T06:42:13.112Z fcfedef6-ed16-4236-a407-5cc3d38bb2fe "This is Node.js 8.10 log."
が
実行日時 リクエストID console.logで出力した文字列
になってますね。
Node.js 10.x
関数コード
exports.handler = async (event) => { // TODO implement const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; console.log(`"This is Node.js 10.x log."`); return response; };Execution Result
Function Logs:
START RequestId: b7b8a64d-0bcf-405e-8257-0fc6d3f62419 Version: $LATEST
2019-12-15T06:46:11.635Z b7b8a64d-0bcf-405e-8257-0fc6d3f62419 INFO "This is Node.js 10.x log."
END RequestId: b7b8a64d-0bcf-405e-8257-0fc6d3f62419
REPORT RequestId: b7b8a64d-0bcf-405e-8257-0fc6d3f62419 Duration: 59.33 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 76 MB Init Duration: 174.25 ms
2019-12-15T06:46:11.635Z b7b8a64d-0bcf-405e-8257-0fc6d3f62419 INFO "This is Node.js 10.x log."
が
実行日時 リクエストID ログレベル console.logで出力した文字列
と、出力されるようになっています。
8.10と比較するとログレベルが出力されるようになっていることに注意しましょう。Node.js 12.x
Execution Result
Function Logs:
START RequestId: 786d807b-6bb9-4267-88f7-4df1f4955a7f Version: $LATEST
2019-12-15T06:47:52.767Z 786d807b-6bb9-4267-88f7-4df1f4955a7f INFO "This is Node.js 12.x log."END RequestId: 786d807b-6bb9-4267-88f7-4df1f4955a7f
REPORT RequestId: 786d807b-6bb9-4267-88f7-4df1f4955a7f Duration: 2.80 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 70 MB Init Duration: 111.41 ms
2019-12-15T06:47:52.767Z 786d807b-6bb9-4267-88f7-4df1f4955a7f INFO "This is Node.js 12.x log."END RequestId: 786d807b-6bb9-4267-88f7-4df1f4955a7f
が
実行日時 リクエストID ログレベル console.logで出力した文字列END RequestId: リクエストID
と、10.xとも違う、おそらくAWS側も意図していない動作をしてるように見受けられます。
(コピペミスとかでもなかったです)とはいえ、CloudWatchには意図どおりに出力されてそうなので特に問題にはならないと思います。
補足
consoleオブジェクトの関数に応じてログレベルを出力するようになっているようです。
Node.js 8.10
関数コード
exports.handler = async (event) => { // TODO implement const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; console.log(`"This is Node.js 8.10 log."`); console.info(`"This is Node.js 8.10 info log."`); console.warn(`"This is Node.js 8.10 warn log."`); console.error(`"This is Node.js 8.10 error log."`); return response; };Execution Result
Function Logs:
START RequestId: c2a0c056-7623-4c4f-bbad-6b458800cd7d Version: $LATEST
2019-12-15T07:18:36.692Z c2a0c056-7623-4c4f-bbad-6b458800cd7d "This is Node.js 8.10 log."
2019-12-15T07:18:36.692Z c2a0c056-7623-4c4f-bbad-6b458800cd7d "This is Node.js 8.10 info log."
2019-12-15T07:18:36.692Z c2a0c056-7623-4c4f-bbad-6b458800cd7d "This is Node.js 8.10 warn log."
2019-12-15T07:18:36.692Z c2a0c056-7623-4c4f-bbad-6b458800cd7d "This is Node.js 8.10 error log."
END RequestId: c2a0c056-7623-4c4f-bbad-6b458800cd7d
REPORT RequestId: c2a0c056-7623-4c4f-bbad-6b458800cd7d Duration: 0.50 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 58 MBログを見てもどんなレベルのログかが分からない。
Node.js 10.x
関数コード
exports.handler = async (event) => { // TODO implement const response = { statusCode: 200, body: JSON.stringify('Hello from Lambda!'), }; console.log(`"This is Node.js 10.x log."`); console.info(`"This is Node.js 10.x info log."`); console.trace(`"This is Node.js 10.x trace log."`); console.debug(`"This is Node.js 10.x debug log."`); console.warn(`"This is Node.js 10.x warn log."`); console.error(`"This is Node.js 10.x error log."`); console.fatal(`"This is Node.js 10.x fatal log."`); return response; };Execution Result
Function Logs:
START RequestId: d12b8a86-62eb-4caa-9d09-ed66b1ec08ed Version: $LATEST
2019-12-15T07:15:02.460Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed INFO "This is Node.js 10.x log."
2019-12-15T07:15:02.465Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed INFO "This is Node.js 10.x info log."
2019-12-15T07:15:02.465Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed TRACE "This is Node.js 10.x trace log."
2019-12-15T07:15:02.465Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed DEBUG "This is Node.js 10.x debug log."
2019-12-15T07:15:02.465Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed WARN "This is Node.js 10.x warn log."
2019-12-15T07:15:02.465Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed ERROR "This is Node.js 10.x error log."
2019-12-15T07:15:02.465Z d12b8a86-62eb-4caa-9d09-ed66b1ec08ed FATAL "This is Node.js 10.x fatal log."
END RequestId: d12b8a86-62eb-4caa-9d09-ed66b1ec08ed
REPORT RequestId: d12b8a86-62eb-4caa-9d09-ed66b1ec08ed Duration: 47.48 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 76 MB Init Duration: 151.14 msどんなレベルのログが一目瞭然だし、ログのパースするときに便利そう。
8.10と比較すると種類も増えてます。他にもあるかも。リンク
- 投稿日:2019-12-15T16:00:57+09:00
300回プログラムじゃんけんでを戦わせるWebアプリを作った
作ったアプリ
https://janken-programmer.web.app/
遊び方
これはじゃんけんの出す"手"をプログラミングして戦わせるアプリです。
例えばグーだけを出すA君のプログラムと
グーとパーを交互に出すB君のプログラム
を300回戦わせるとB君が勝ちます
こんな感じでじゃんけんのアルゴリズムを考えて戦わせるアプリです
例
A君のプログラム
HAND = 0B君のプログラム
HAND = 1 if COUNT % 2 == 0: HAND = 0これをWebで遊べるアプリです。
システム構成
今回使ったものは以下の通りになりました。
- Vue.js
- Buefy
- AWS
- serverless framework
- dynamodb
- firebase
- circleCI
フロントエンドの部分はVueとBeufyを使いました。
同じような画面を作ることが多かったのでサクサク作れました。バックエンドではAWSのサービスをserverless frameworkで構築しました。
アクセスが不安定なサービスになりそうなのでこちらのフレームワークを使いました。ホスティングと認証にはfirebaseを使用しました。
本当はAWSなのですべてまとめたほうがいいのかもしれませんが
firebaseは無料で結構使えるのでこちらを採用しました。無料枠などもあり、すべて無料でできました。
本当はRDBを使いたかったのですが、お金がないので無理やりdynamodbで作りました(反省)機能
じゃんけんプログラム
じゃんけんのアルゴリズムを実装するにあたり、システム変数的なものを用意することにしました。
変数名 型 概要 HAND Intger 0~2 の数値を代入することでグーチョキパーを出せる WIN Intger 勝った数 LOSE Intger 負けた数 DROW Intger あいこの数 P List 自分が出した手の履歴 E List 相手が出した手の履歴 COUNT Intger 対戦数 これを実装したことにより100回負けたらグーを出すみたいなプログラムを書けます
HAND = 1 if LOSE >= 100: HAND = 0コーディング画面
ハイライトやインデントの機能が欲しかったため今回は「Codemirror」を使用させていただきました。
Vueで使用する場合は「vue-codemirror」を使うと単一コンポーネントで使用できるため、気持ちよく作れました。
ランキング機能
ランキングの実装には「イロレーティング」を使いました。
イロレーティングは対戦型の競技に使用されており、相対的な強さを評価します。
そのため、今回のランキングではこちらを使用し投稿されたプログラムを総当たりさせて順位を決定しました。
引用:イロレーティング
https://ja.wikipedia.org/wiki/%E3%82%A4%E3%83%AD%E3%83%AC%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0認証機能
今回は一人が複数のプログラムを投稿できないように認証機能を追加しました。
Firebase Authenticationを使用しGithubで認証できるようにしました。
感想
自分の想定しているものが一通りできたので、だいぶ満足しています。
ただセキュリティやじゃんけんのプログラムに不備があるかもしれないのでこれからも運営、開発をしていきたいと思います。
- 投稿日:2019-12-15T15:39:01+09:00
AWS環境構築(用語編)~VPC~
AWSの基本サービスVPC
以下に自分用のメモもかねてAWSの基本的なサービスの一つであるVPCについてまとめてみた。
VPC(Virtual Private Cloud)
公式ドキュメントを参考に簡単にまとめました。
Amazon VPCとはAWSリソースを起動するための仮想的なネットワークです。(そもそもネットワークとはなんぞやという方はTCP/IPあたりの知識を身に着けることをお勧めします。)
VPCでIPアドレス範囲を設定できます。このIPアドレスは大まかにいうと、入出国審査での分類みたいなものです。君は入れる、入れない的な。
さらにサブネットというものを追加することで、より細かい設定が可能です。(空港でも日本人とその他の国籍の方で審査別れますよね。)各サブネットではさらにそれぞれセキュリティの設定をすることができますが、ここでは詳細は省きます。
ちなみにVPCの設定で出てくる用語について簡単に説明すると以下の通りです。
主にアルファベットの略語についてです(覚えられない...)。-AZ
アベイラビリティーゾーンの略。実際にデータが保存されているデータセンターの集まりです。
最近日本でスマホゲームなどがプレイできなくなったのは物理的にAZ障害が起きたからです。
AWSのAZ(アベイラビリティーゾーン)とは?AZ障害が起きたときどうすればよいのかここの記事は面白いです。-CIDR
Classless Inter-Domain Routingの略(サイダー)。IPアドレスのブロックを任意単位で区切ることができる。従来より柔軟性があった便利だよねという感じ。
図わかりやすい。-tenancy
インスタンスをどこで実行するかを決める。-Internet Gateway(公式)
その名の通り。役割は公式の和文から引用しますが
"1 つは、インターネットでルーティング可能なトラフィックの送信先を VPC のルートテーブルに追加することです。もう 1 つは、パブリック IPv4 アドレスが割り当てられているインスタンスに対してネットワークアドレス変換 (NAT) を行うことです。"
とあります。要は入出国における実際のcheckみたいなイメージ。その他の関連記事
- 投稿日:2019-12-15T15:27:08+09:00
S3で署名Verを4にしたらエラー
AWS SDK for PHP v2を使ってるもんで、署名Verを2から4にする必要があった
そしたらファイルのアップロードで以下の様なエラーが出てアップロードできないThe request signature we calculated does not match the signature you provided. Check your key and signing method.修正自体はfactoryするときに'signature' => 'v4'を渡すくらいで大した作業でもなく。
キーが間違ってるから確認して的なエラーメッセージだけど、ファイル一覧は正常に動くしそもそも今まで動いてたのでACCESS_KEYとSECRET_KEYが間違ってる訳でもなさそう。ファイルアップのソースはこんな感じ
// S3バケット名 $backet = 'backet_name'; // アップするS3のパス $s3dir = '/hoge/fuga'; // アップ時のファイル名 $file = 'aaa.txt'; // アップするローカル側のファイルフルパス $upfilepath = ’/tmp/tmp.txt'; $s3->upload( $backet.'/'.$s3dir, $file, fopen($upfilepath, 'rb'), 'public-read' );uploadメソッドの第一引数は本来バケット名を渡すはずがバケット名 + ファイルパスになってる・・・
正しい引数に修正することでエラーは出なくなった$s3->upload( $backet, $s3dir.'/'.$file, fopen($upfilepath, 'rb'), 'public-read' );署名Ver2は第一引数と第二引数はただのファイルパスの情報だったので、バケット名とファイルパスをどこで区切って渡してもアップロードできた。多分
署名Ver4はこれらの引数を元にハッシュ値を作成するので、バケットとファイルパスをちゃんと渡さないとハッシュ値が変わっちゃってアップロードに失敗する。多分解決したものの、署名Ver2が使えなくなるのは延期になったし、以前に作ったバケットはずっとVer2使えるみたいな話もあるので対応する必要なかったかも?
- 投稿日:2019-12-15T15:14:13+09:00
Step Functions & Fargate バッチでやらかしたこと
この記事について
この記事は Opt Technologies Advent Calendar 2019 17日目の記事です。
担当プロダクトで利用したStep Functionsにて、失敗を通じて得た知見をまとめたものです。自分について
中途入社して1年ちょっとのエンジニア。チームマネージャになって半年くらい。
入社してからクラウドインフラを学習し始め現在もチーム内で主に担当をしていますが、アプリケーションコードも状況に応じて実装する感じです。
昨年はこんな記事書きました。やらかし背景
著者の担当プロダクトにて、外部APIから取得できるそこそこのサイズのデータをプロダクト側のDBに格納する処理があります。
ELT的なステップを踏んで処理することを検討しStep Functionsを採用しました。
- Extractorが外部APIからデータファイル(csv)を取得しS3バケットに格納
- Loaderがデータファイルを中間テーブルに格納
- Transformerが変換SQLを実行し成形済みデータにしてテーブルに格納
![]()
一般的にはStep FunctionsではLambdaで処理を記述すると思うのですが、
- 当時Lambdaの起動時間限界が5分と短く不安が大きかった
- Webサーバや、今回の処理とは別のいくつかのバッチ処理をECS(Fargate)で実現していたため、そちらに寄せておいた方がコード管理上楽そうだった
という理由で、デファクトから外れることを認識しつつも、ここではFargateバッチを採用しました。
ところが、これが原因でいくつかのつらみを生んでしまったのでした。
つらみ1: 立ち上がりが遅い
当たり前といえば当たり前なのですが、Fargateは起動に時間がそこそこかかります。我々のプロダクトのバッチだとおおよそ45sec程度。
Step Functionsの立ち上がり直後にSlackへ通知をするような処理が必要なのですが、通知が来るまでに約1min程度かかりました。つらいといえばつらいんですが、要件的に致命的な問題にならないため、ひとまずこちらは気にせずそのまま突き進みました。
つらみ2: 処理結果を次処理で利用することができない
当初はこんな感じで処理を進める予定でした。
ところが、Fargateバッチの出力は以下のようになります。
Fargate出力{ "name": "Extractor", "output": { "Attachments": [ { "Details": [ { "Name": "subnetId", "Value": "subnet-XXXXXXXXXXXXXXXXX" }, { "Name": "networkInterfaceId", "Value": "eni-XXXXXXXXXXXXXXXXX" }, { "Name": "macAddress", "Value": "XX:XX:XX:XX:XX:XX" }, { "Name": "privateIPv4Address", "Value": "xx.xx.xx.xx" } ], "Id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "Status": "DELETED", "Type": "eni" } ], (略) } }標準出力に吐いた結果とか出てくることを期待しましたが、淡い期待でした。
ECSタスクに関する詳細情報が出てきます。
なんとか他の手段で次のステートへ受け渡す方法を考えてみましたが、結局見つからず。苦肉の策として、ステートマシンの入力側で対処しました。
ステートマシンの入力{ "StagingTable": "STAGING_XXXXXXXX_YYYYMMDDHHmmssfff", "S3Path": "Sync/Staging/XXXXXXXX/YYYYMMDDHHmmssfff/" }ステートマシンへの入力の時点で取得ファイルの格納S3ディレクトリ名と、作成する中間テーブル名を渡しています。
衝突を避けるため、外部APIから取得するデータのアカウントID(XXXXXXXX
)と
実行日時(ミリ秒単位:YYYYMMDDHHmmssfff
)で分けました。
既にだいぶつらい感じの作りですが…なんとかこれにより共通のデータに対してアクセスすることには成功できました。さて、これでようやく実装できるぞ、と思って動かしてみたら、次の問題が発生しました。
つらみ3: ThrottlingExceptionの大量発生
さてこれで元気に動いてくれそうだなということで実装しまして。
では…ということで動かしてみたら、狙い通りうまくいきました!
ここでめでたしめでたし、となればいいのですが、現実は非情なものでした。今回の処理は並行実行を結構な数で行うケースが想定されます。
その確認のため負荷試験を行ったところ、ボコボコとエラーで停止してしまいました。そのときのエラーがこちら。
{ "resourceType": "ecs", "resource": "runTask.sync", "error": "ECS.AmazonECSException", "cause": "Rate exceeded. (Service: AmazonECS; Status Code: 400; Error Code: ThrottlingException; Request ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx)" }調べてみると、どうもECSタスク起動のためのECS APIコール数が
秒間1回まで
という制限にひっかかっていたらしく、
リトライ設定をしても結構な確率で再衝突しました。結果リトライ上限に達し無事死亡……。取り急ぎの対応としては、リトライのバックオフレート設定です。
"Retry": [ { "ErrorEquals": [ "States.Timeout" ], "MaxAttempts": 0 }, { "ErrorEquals": [ "States.ALL" ], "IntervalSeconds": 10, "MaxAttempts": 5, "BackoffRate": 3.0 } ]リトライタイミングがそれなりに散ってくれたので、ステートマシン全体の処理完了時間を犠牲にはしましたが、動作するようになりました。
ちなみにこちら、Lambdaの場合は
ほぼ無制限
…同時実行数も1,000
など、圧倒的に有利ですね。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/limits.htmlまとめ
Lambdaを選択できるときは迷わずLambdaを選択するのが良さそうですね……。
とはいえ現在もLambdaの制限時間は15分で、これを超えそうなバッチもいくつかある現状。
激重なバッチについては残念ながら現状維持になりそうです。
ですが、例えばSlackへの通知だとか、今回は説明都合で割愛したDBへの軽微な変更処理バッチなど、
速度面に不安のない処理については、チームで粛々とLambda化の対応を進めています。Step FunctionsはLambda,Fargateの他にも、AWS BatchやSQS、EMR、DynamoDBなどが利用可能ですが、
組み合わせるとどんなことが起きるかについては利用前に事前調査をしなくちゃな、と思いました。
当然といえば当然のことですが、いやー、反省です。
- 投稿日:2019-12-15T15:13:56+09:00
AWSマネジメントコンソールに「IAMユーザー」でのログイン後「IAMロール」にスイッチする為の設定メモ
IAMユーザで、AWSマネジメントコンソールにログイン後、特定のIAMロールにスイッチロールできるようにする為の設定メモ
記載内容
- マネジメントコンソールログイン用IAMユーザーの作成
- スイッチ先のIAMロールの作成
- IAMユーザからIAMロールにスイッチ許可する為のAssumeRoleの設定
- 上記設定後のIAMユーザログイン/IAMロールへのスイッチ確認
前提
- 個人利用を想定し、ルートアカウントしか存在しない状態からの設定を想定
- ログイン用IAMユーザーは「login_user」、スイッチ先IAMロールは「admin_role」とする
1. マネジメントコンソールログイン用IAMユーザの作成
2. スイッチ先のIAMロールの作成
3. IAMユーザーからIAMロールにスイッチできるようにする為のAssumeRoleの設定
JSON{ "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Action": "sts:AssumeRole", "Resource": "arn:aws:iam::アカウントID:role/admin_role" } }
Policy{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::アカウントID:user/login_user", "Service": "ec2.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
4. 上記設定後のIAMユーザログイン/IAMロールへのスイッチ確認
- 投稿日:2019-12-15T13:14:23+09:00
AWS CloudWatch - Alarm 異常検出 のテンプレート
こちらの記事はケーシーエスキャロット Advent Calendar 2019の15日目の記事です。
ちょっと遡って5日前の記事はgaramanさんのくずし字を読む (準備編)でした。
子供の頃、百人一首をやった記憶はありますが、普通の日本語で書かれていました。
その為、くずし字
をあまり見た記憶がありませんが、プログラムで解析しようとする発想がスゴいです。さて、本日はメトリクスの値を監視する AWS CloudWatch - Alarm に関して記載してみようと思います。
CloudWatch - Alarm の監視方法としては、指定した閾値を
- 以上 (GreaterThanOrEqualToThreshold)
- 以下 (LessThanOrEqualToThreshol)
- より上 (GreaterThanThreshold)
- より下 (LessThanThreshold)
となった場合にアラーム状態へ遷移する
静的しきい値
のみだったのですが、今年の夏ごろから異常検出
という方法も可能になりました。この
異常検出
をコンソールから設定する方法は、異常検出に基づいて CloudWatch アラームを作成する等のサイトに記載がありますが、CloudFormation でデプロイする際のテンプレート記載例が見当たらなかった(探し方が悪いかもですが)ので記載してみようと思います。CPU 使用率アラームを異常検出へ書き換えた例。
template.ymlCPUAlarmForAnomaly: Type: AWS::CloudWatch::Alarm Properties: AlarmName: anomaly-alarm AlarmDescription: CPU alarm for my instance AlarmActions: Ref: "logical name of an AWS::SNS::Topic resource" Metrics: - Id: m1 ReturnData: true MetricStat: Period: 60 Stat: Average Metric: MetricName: CPUUtilization Namespace: AWS/EC2 Dimensions: - Name: InstanceId Value: Ref: "logical name of an AWS::EC2::Instance resource" - Id: a1 Expression: ANOMALY_DETECTION_BAND(m1, 2) EvaluationPeriods: 3 ThresholdMetricId: a1 ComparisonOperator: GreaterThanUpperThreshold以上! …だとそっけなさ過ぎるので、簡単に説明を記載すると、
- Id: m1 ReturnData: true MetricStat: Period: 60 Stat: Average Metric: MetricName: CPUUtilization Namespace: AWS/EC2 Dimensions: - Name: InstanceId Value: Ref: "logical name of an AWS::EC2::Instance resource"の部分が監視するメトリクスの宣言となります。
注意点としては、静的しきい値
と比較すると統計方法
の記載が異なる(Statistic
⇔Stat
)ことです。そして、
- Id: a1 Expression: ANOMALY_DETECTION_BAND(m1, 2)の部分で、監視対象メトリクスの値を異常検出と見なす想定値の範囲を指定(
2
)しています。
異常検出
方式で Alarm として設定してみると、上図のような感じで実際のメトリクスの値に沿って想定値の範囲(グレーの部分)が設定(想定)されます。詳細はCloudWatch の異常検出の使用を参照。
因みに、監視方法(ComparisonOperator)も
静的しきい値
と異なっていて
- 想定値の範囲を上回った場合 (GreaterThanUpperThreshold)
- 想定値の範囲を下回った場合 (LessThanLowerThreshold)
- 想定値の範囲を上回った or 下回った場合 (LessThanLowerOrGreaterThanUpperThreshold)
の3つから指定可能です。
この
異常検出
の使いどころとしては、しきい値が設定しにくい(時間帯や日によって異なる等)場合に良いかもしれませんね。
- 投稿日:2019-12-15T12:55:13+09:00
S3バケットポリシーでAssumeロールのアクセス設定を行う
はじめに
S3のバケットポリシーでAssumeロールのアクセス制御設定の方法をまとめる。
詳細
アカウントAでIAMユーザー
hoge
からAssumeロールしアカウントBのS3バケットにアクセスする状況を想定。
S3バケットのバケットポリシーでは、そのAssumeロールでのみアクセス許可しその他のアクセスは拒否する設定を行う。バケットポリシー設定方法は以下の2つ
1.アクセス制御にAssumeロール元のIAMユーザーarnを使用する
2.アクセス制御にAssumeロールのIAMロールIDを使用する方法1
アクセス制御にAssumeロール前のIAMユーザーarnを使用する場合。
バケットポリシー{ "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::<バケット名>/*", "NotPrincipal": { "AWS": [ "arn:aws:iam::<アカウントID>:role/<ロール名>", "arn:aws:sts::<アカウントID>:assumed-role/<ロール名>/<元のIAMユーザー名>" ] }, } ] }
Principal
またはNotPrincipal
ではワイルドカード*
を使用できないため、
会社等、複数のユーザーが同じAssumeロールを使用する場合、ユーザーごとにNotPrincipal
を書く必要があり、この方法は向かない。方法2
アクセス制御にAssumeロールのIAMロールIDを使用する場合。
IAMロールIDはコンソール上では確認できず、AWS CLIでのみ確認可能。
以下のコマンドを実行し、IAMロールIDを確認する。$ aws iam get-role --role-name <IAMロール名>バケットポリシーに以下を記載する。
バケットポリシー{ "Version": "2012-10-17", "Statement": [ { "Sid": "2", "Effect": "Deny", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::<バケット名>/*", "Condition": { "StringNotLike": { "aws:userid": "<IAMロールID>:*" } } } ] }
- 投稿日:2019-12-15T12:53:31+09:00
Cognito のIDプールでS3のユーザー毎フォルダにアクセスしてみる
背景
AWS使ってサーバーレスで自分用の家計簿的なwebサービスを勉強も兼ねて開発中。
2つ前の記事
1つ前の記事
せっかくCognito使ったので、ユーザー毎のS3フォルダにアクセスする事をしてみたい。構成
フロント:Vue.js + Element.ui
サーバーサイド:CloudFront + S3 + APIGateway + Lambda + DynamoDB + Cognitoやってみた
今回、様々なページを参考にさせてもらい、その情報を組み合わせました。もはやどの部分がどのページの参考なのかこんがらがっています。結構トライ&エラー発生し、各種設定行ったり戻ったりしました。今回は結果のみ共有させてもらいます。
まず、ブラウザからS3にアクセスする為、CORSの設定をします。
S3バケットのアクセス権限=>CORSの設定<?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>*</AllowedOrigin> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>PUT</AllowedMethod> <MaxAgeSeconds>3000</MaxAgeSeconds> <AllowedHeader>Authorization</AllowedHeader> <AllowedHeader>Content-*</AllowedHeader> <AllowedHeader>Host</AllowedHeader> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration>ログインユーザー毎にS3へのアクセスを許可する為、CognitoIDプール、認証済みユーザー用のIAMポリシーに以下設定。※以下の指定でhogehoge-bucketと指定していますが既に存在するようです。が、そのバケットを意味してないです。実際には自分作成のバケット名です。
認証済みユーザー用のIAMポリシー{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "cognito-identity:*", "mobileanalytics:PutEvents", "cognito-sync:*" ], "Resource": "*" }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:ListBucket", "s3:DeleteObject", "s3:GetBucketLocation" ], "Resource": [ "arn:aws:s3:::hogehoge-bucket/cognito/myhome-account/${cognito-identity.amazonaws.com:sub}", "arn:aws:s3:::hogehoge-bucket/cognito/myhome-account/${cognito-identity.amazonaws.com:sub}/*" ] } ] }そして処理本体、VueファイルのjavaScript部分
テスト本体import AWS from 'aws-sdk' import awsconfig from '../cognito/config' import cognito from '@/cognito' const S3_USERBACKETNAME = 'hogehoge-bucket' const PROVIDER_KEY = 'cognito-idp.' + awsconfig.Region + '.amazonaws.com/' + awsconfig.UserPoolId /* ・・中略・・ */ s3test: function () { const cognitoUser = this.$cognito.userPool.getCurrentUser() cognitoUser.getSession((err, session) => { if (!err && session.isValid()) { const itoken = session.getIdToken().getJwtToken() // Initialize the Amazon Cognito credentials provider AWS.config.region = awsconfig.Region AWS.config.credentials = new AWS.CognitoIdentityCredentials({ IdentityPoolId: awsconfig.IdentityPoolId, Logins: { [PROVIDER_KEY]: itoken } }) var identityId = AWS.config.credentials.identityId var s3key = 'cognito/myhome-account/' + identityId + '/testfile.txt' // refresh AWS credentials AWS.config.credentials.refresh((err) => { if (err) { console.log(err) } else { var s3 = new AWS.S3({ params: { Bucket: S3_USERBACKETNAME } }) const updateParams = { Key: s3key, Body: 'teststring', ContentType: 'application/text' } s3.upload(updateParams, function (err, data) { if (err) { console.log('error : ', err) } else { console.log('success : ' + S3_USERBACKETNAME + '/' + s3key) } }) } }) } }) }これでテスト用の適当な文字列のファイルをS3のユーザー毎フォルダにアップ出来ました。間違ったフォルダを指定した時は AccessDenied: Access Denied で失敗する事も確認できました。
特にはまったポイント
- credentialを取得する時に、一時トークンを渡すが、cognitoを使う場合にどんなキー(上記ソースのPROVIDER_KEY部分)を指定するのか情報を見つけるのに非常に時間がかかった。参考
- ポリシーで指定するcognito-identity.amazonaws.com:subが実際には何の値を示すのか見つけるのに非常に時間がかかった。参考
参考にさせてもらったページ
ブラウザから Amazon S3 への写真のアップロード
サインイン後に ID プールを使用して AWS サービスへアクセスする
[ Serverless ] Cognito、S3、Lambdaで認証機能付きのWebサイトを作ってみました
【Cognito】Amazon Cognito Identity SDK for JavaScriptで動くサンプルを作ってみた #2/2【JavaScript】
WebブラウザからAmazon S3に直接ファイルをアップロードする
AWS Cognitoを使ってサーバレスアプリケーションに認証をつける
- 投稿日:2019-12-15T12:32:37+09:00
AWS SAM CLI 再入門 2019.12
この記事は AWS Advent Calendar 2019 16日目の記事です。
はじめに
2018年の5月にaws-sam-local 改め aws-sam-cli の新機能 sam init を試すという記事を投稿しました。
AWS SAM CLI は GitHub 上で開発がおこなわれていますが、リリースのペースが早く
日々機能追加や改善が行われています。結果として上記の記事の内容は一部古いものとなっています。
本記事では12/15時点の最新バージョンである v0.37.0 までのリリースを踏まえた使い方をご紹介します。AWS SAM と AWS SAM CLI
Serverless Application Model(SAM)はLambdaやAPI Gateway, DynamoDBのようなサービスを活用した
サーバーレスアプリケーションのデプロイに特化した、AWS CloudFormation の拡張機能です。
通常のCloudFormationのテンプレートと同様にYAML/JSONでテンプレートを定義しますが、
素のテンプレートより簡潔に記載できるのが特徴です。AWS SAM CLI は SAM を使用したサーバーレスアプリケーションの作成と管理を簡単に行うことができる
コマンドラインツールです。AWS SAM CLIのインストール
AWS SAM CLI のすべての機能を活用するには、前提としてAWS CLI と Dockerの
インストールが必要ですが、本記事では割愛します。macOS
Homebrewを使用してAWS SAM CLIをインストールすることができます。
$ brew tap aws/tap $ brew install aws-sam-cli $ sam --version SAM CLI, version 0.37.0Windows
以下のURLからインストーラーをダウンロードできます。
https://github.com/awslabs/aws-sam-cli/releases/latest/download/AWS_SAM_CLI_64_PY3.msi > sam --version SAM CLI, version 0.37.0その他の環境
pip でもインストールできます。
$ pip install -U aws-sam-cli $ sam --version SAM CLI, version 0.37.0ローカルでの開発とテスト
sam init
sam init コマンドを使用すると、アプリケーションのプロジェクトを初期化できます。
v0.30.0 以降では対話型にサンプルアプリケーションをセットアップしたり、
GitHubやローカル上のテンプレートを読み込んで初期化することもできるようになっています。以下は対話型でnode.js12.xのランタイムを指定したサンプルアプリケーションを作成している例です。
$ sam init Which template source would you like to use? 1 - AWS Quick Start Templates 2 - Custom Template Location Choice: 1 Which runtime would you like to use? 1 - nodejs12.x 2 - python3.8 3 - ruby2.5 4 - go1.x 5 - java11 6 - dotnetcore2.1 7 - nodejs10.x 8 - nodejs8.10 9 - nodejs6.10 10 - python3.7 11 - python3.6 12 - python2.7 13 - java8 14 - dotnetcore2.0 15 - dotnetcore1.0 Runtime: 1 Project name [sam-app]: Quick start templates may have been updated. Do you want to re-download the latest [Y/n]: n ----------------------- Generating application: ----------------------- Name: sam-app Runtime: nodejs12.x Dependency Manager: npm Application Template: hello-world Output Directory: . Next steps can be found in the README file at ./sam-app/README.mdその他のバリエーション
# すべてのパラメータを指定 $ sam init --runtime nodejs12.x --dependency-manager npm --app-template hello-world --name sam-app # GitHub上のサンプルプロジェクトを指定 $ sam init --location gh:aws-samples/cookiecutter-aws-sam-python # ZIPファイルやローカルのテンプレートフォルダを指定 $ sam init --location /path/to/template.zip $ sam init --location https://example.com/path/to/template.zip $ sam init --location /path/to/template/foldernodejs12.x でのサンプルアプリケーションの場合以下のようなファイル構成となります。
sam-app │ .gitignore │ README.md │ template.yaml # SAMテンプレート │ ├─events │ event.json # イベントソースがLambda関数に送信するサンプルペイロード │ └─hello-world # Lambda関数を配置 │ .npmignore │ app.js │ package.json │ └─tests #ユニットテストファイルを配置 └─unit test-handler.jssam build
Lambda関数のソースコードをビルドし、デプロイ用のアーティファクトを作成することができます。
$ sam build Building resource 'HelloWorldFunction' Running NodejsNpmBuilder:NpmPack Running NodejsNpmBuilder:CopyNpmrc Running NodejsNpmBuilder:CopySource Running NodejsNpmBuilder:NpmInstall Running NodejsNpmBuilder:CleanUpNpmrc Build Succeeded Built Artifacts : .aws-sam\build Built Template : .aws-sam\build\template.yaml Commands you can use next ========================= [*] Invoke Function: sam local invoke [*] Deploy: sam deploy --guidedsam local invoke
ローカルでLambda関数を起動できます。
ローカルに docker-lambda のイメージがない場合は最初にダウンロードされるので少し時間がかかります。
docker-lambda が sam build で作成されたアーティファクトをマウントして
AWS上でのLmabda関数の実行をシュミレートします。$ sam local invoke Invoking app.lambdaHandler (nodejs12.x) Fetching lambci/lambda:nodejs12.x Docker container image...... Mounting C:\test\sam-app\.aws-sam\build\HelloWorldFunction as /var/task:ro,delegated inside runtime container START RequestId: 42ac82fe-710e-1c38-13c8-ad22f0117ab1 Version: $LATEST END RequestId: 42ac82fe-710e-1c38-13c8-ad22f0117ab1[0m REPORT RequestId: 42ac82fe-710e-1c38-13c8-ad22f0117ab1 Init Duration: 169.23 ms Duration: 4.30 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 37 MB {"statusCode":200,"body":"{\"message\":\"hello world\"}"}sam local generate-event
特定のイベントソースのイベント情報をLambda関数に渡して処理したい場合
--event オプションでjsonファイルを指定します。
もしくは sam local generate-event コマンドでさまざまなイベントソースのペイロードを
作成することができます。
その際、サービスごとに複数のイベントタイプを指定することができます。DynamoDB ストリームのupdateイベント情報を生成し、Lambda関数を起動する例
$ sam local generate-event dynamodb update > ./events/dynamodb_event.json $ sam local invoke HelloWorldFunction --event ./events/dynamodb_event.jsonその他 v0.37.0 時点で生成に対応しているイベントソース
$ sam local generate-event --help ~省略~ Commands: alexa-skills-kit alexa-smart-home apigateway batch cloudformation cloudfront cloudwatch codecommit codepipeline cognito config connect dynamodb kinesis lex rekognition s3 sagemaker ses sns sqs stepfunctionssam local start-lambda
Lambda関数の呼び出しをエミュレートするローカルンドポイントを起動することができます。
AWSCLIでローカルエンドポイントを指定したり、DynamoDB Localなどの開発ツールと
連携させたりすることで開発、テストを便利におこなうことができます。$ sam local start-lambda Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template through the endpoint. 2019-12-12 14:00:04 * Running on http://127.0.0.1:3001/ (Press CTRL+C to quit) # AWSCLIから呼び出し $ aws lambda invoke --function-name HelloWorldFunction --endpoint http://127.0.0.1:3001/ outfile.txt { "StatusCode": 200 }参考: Step Functions Local が出た!SAMやDynamoDB Localと一緒に使ってみる
https://qiita.com/hayao_k/items/14ff145a5d5c5191a18fsam local start-api
API呼び出しを含めたサーバーレスアプリケーション全体のテストを行うために
API Getewayのローカルインスタンスを起動することができます。
起動中にLambda関数への変更を行った場合は自動的にリロードされるため
start-apiを再実行する必要はありません。
SAMテンプレート自体を変更した場合はリスタートする必要があります。$ sam local start-api Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET] You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template 2019-12-12 14:18:08 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit) # 起動したエンドポイントに curl で接続すると正常に応答し、hello worldを返す $ curl http://127.0.0.1:3000/hello {"message":"hello world"}sam validate
SAMテンプレートがSAMの仕様に沿った記述になっているか検証することができます。
$ sam validate C:\test\sam-app\template.yaml is a valid SAM TemplateAWSへのデプロイ
sam deploy
SAMで作成したアプリケーションのデプロイを行うことができます。
以前はAWS SAM CLIでデプロイを行うには複数のステップが必要だったのですが、v0.33.1 以降
単一の sam deploy コマンドでデプロイを操作できるようになりました。具体的には必要な設定を事前にプロジェクトルートディレクトリにファイル(samconfig.toml)
として保続しておくか、-g, --guided オプションで対話的にデプロイをすすめることが可能です。
初回にこのオプションを使用してデプロイすることで、選択した構成を samconfig.toml
として保存させることもできます。
Lambdaデプロイパッケージ用のS3バケットもAWS SAM CLI が自動で作成し、管理します。$ sam deploy -g Configuring SAM deploy ====================== Looking for samconfig.toml : Not found Setting default arguments for 'sam deploy' ========================================= Stack Name [sam-app]: mystack ★CloudFormation スタック名の指定 AWS Region [us-east-1]: ap-northeast-1 ★リージョンの指定 #Shows you resources changes to be deployed and require a 'Y' to initiate deploy Confirm changes before deploy [y/N]: y ★デプロイ前にCloudformationの変更セットを確認するか #SAM needs permission to be able to create roles to connect to the resources in your template Allow SAM CLI IAM role creation [Y/n]: y ★SAM CLI に IAMロールの作成を許可するか(CAPABILITY_IAM) Save arguments to samconfig.toml [Y/n]: y ★この設定をsamconfig.tomlとして保存するか Looking for resources needed for deployment: Not found. Creating the required resources... Successfully created! Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxxxxxx A different default S3 bucket can be set in samconfig.toml Saved arguments to config file Running 'sam deploy' for future deployments will use the parameters saved above. The above parameters can be changed by modifying samconfig.toml Learn more about samconfig.toml syntax at https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html Deploying with following values =============================== Stack name : mystack Region : ap-northeast-1 Confirm changeset : True Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxxxxxx Capabilities : ["CAPABILITY_IAM"] Parameter overrides : {} Initiating deployment ===================== Uploading to mystack/c04c27151371faba23ccb6ca6ae9bbdf 127490 / 127490.0 (100.00%) Uploading to mystack/63baf4f76aff04a2c9ecb11bc3918a7d.template 1130 / 1130.0 (100.00%) Waiting for changeset to be created.. CloudFormation stack changeset ------------------------------------------------------------------------------------------------------------------------------------------------ Operation LogicalResourceId ResourceType ------------------------------------------------------------------------------------------------------------------------------------------------ + Add HelloWorldFunctionHelloWorldPermissionProd AWS::Lambda::Permission + Add HelloWorldFunctionRole AWS::IAM::Role + Add HelloWorldFunction AWS::Lambda::Function + Add ServerlessRestApiDeployment47fc2d5f9d AWS::ApiGateway::Deployment + Add ServerlessRestApiProdStage AWS::ApiGateway::Stage + Add ServerlessRestApi AWS::ApiGateway::RestApi ------------------------------------------------------------------------------------------------------------------------------------------------ Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:0123456789012:changeSet/samcli-deploy1576228579/a9ee2685-452f-47d0-851f-92e2ff217c9c Previewing CloudFormation changeset before deployment ====================================================== Deploy this changeset? [y/N]: y ★変更セットを確認し、デプロイ実行 2019-12-13 18:16:38 - Waiting for stack create/update to complete CloudFormation events from changeset ------------------------------------------------------------------------------------------------------------------------------------------------- ResourceStatus ResourceType LogicalResourceId ResourceStatusReason ------------------------------------------------------------------------------------------------------------------------------------------------- CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole - CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole Resource creation Initiated CREATE_COMPLETE AWS::IAM::Role HelloWorldFunctionRole - CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction - CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction Resource creation Initiated CREATE_COMPLETE AWS::Lambda::Function HelloWorldFunction - CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi - CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi Resource creation Initiated CREATE_COMPLETE AWS::ApiGateway::RestApi ServerlessRestApi - CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionHelloWorldPermis Resource creation Initiated sionProd CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionHelloWorldPermis - sionProd CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5 - f9d CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5 Resource creation Initiated f9d CREATE_COMPLETE AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5 - f9d CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage - CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage Resource creation Initiated CREATE_COMPLETE AWS::ApiGateway::Stage ServerlessRestApiProdStage - CREATE_COMPLETE AWS::Lambda::Permission HelloWorldFunctionHelloWorldPermis - sionProd CREATE_COMPLETE AWS::CloudFormation::Stack mystack - ------------------------------------------------------------------------------------------------------------------------------------------------- Stack mystack outputs: ------------------------------------------------------------------------------------------------------------------------------------------------- OutputKey-Description OutputValue ------------------------------------------------------------------------------------------------------------------------------------------------- HelloWorldFunctionIamRole - Implicit IAM Role created for Hello World arn:aws:iam::0123456789012:role/mystack- function HelloWorldFunctionRole-1OUZSV1ENAMCB HelloWorldApi - API Gateway endpoint URL for Prod stage for Hello https://xxxxxxxxxx.execute-api.ap- World function northeast-1.amazonaws.com/Prod/hello/ HelloWorldFunction - Hello World Lambda Function ARN arn:aws:lambda:ap-northeast-1:0123456789012:function:mystack- HelloWorldFunction-1IFZY75A4HM3V ------------------------------------------------------------------------------------------------------------------------------------------------- Successfully created/updated stack - mystack in ap-northeast-1作成された samconfig.toml は以下のようになります。
tomlとは設定ファイルを定義するためのフォーマットの1種で、CloudWatch Agentなどでも採用されています。samconfig.toml[default] [default.deploy] [default.deploy.parameters] stack_name = "mystack" s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxxxxxx" s3_prefix = "mystack" region = "ap-northeast-1" confirm_changeset = true capabilities = "CAPABILITY_IAM"samconfig.tomlがプロジェクトのルートディレクトリに存在していれば
sam deploy コマンドだけでデプロイを行うことができます。$ sam deploy Deploying with following values =============================== Stack name : mystack Region : ap-northeast-1 Confirm changeset : True Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-xxxxxxxxxxxxx Capabilities : ["CAPABILITY_IAM"] Parameter overrides : {} Initiating deployment ===================== Uploading to mystack/65113397247fb02f6ff4bad985a3240c 2421730 / 2421730.0 (100.00%) Uploading to mystack/38b6c0616d6e55bdcc1a0863b917b078.template 1130 / 1130.0 (100.00%) Waiting for changeset to be created.. ~以下省略~OutputValueに出力されたAPI Gatewayのエンドポイントに接続すると、正常に応答が得られました。
curl https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/ {"message":"hello world"}
sam package
SAMで作成したアプリケーションをパッケージ化できます。
具体的には以下のことを行っています。
- Lambda関数デプロイ用のZIPファイルを作成し、指定したS3バケットにアップロード
- CodeUriの参照を、ローカルからアップロードしたS3のパスに書き換え
これらの処理は v0.33.1 以降であれば、sam deploy コマンド内で暗黙的実行されているため
個別にsam package コマンドを使用する必要はありません。
v0.33.1 以前のように sam package と sam deploy 個別に実行しデプロイすることも可能です。$ sam package --template-file template.yaml \ --s3-bucket <bucket-name> --output-template-file packaged.yaml Uploading to 65113397247fb02f6ff4bad985a3240c 2421730 / 2421730.0 (100.00%) Successfully packaged artifacts and wrote output template to file packaged.yaml. Execute the following command to deploy the packaged template sam deploy --template-file C:\test\sam-app\packaged.yaml --stack-name <YOUR STACK NAME> $ sam deploy --template-file packaged.yaml --stack-name mystack --capabilities CAPABILITY_IAM返されたSAMテンプレートを確認するとCodeUriがS3のパスに書き換わっていることがわかります。
Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: - CodeUri: hello-world/ + CodeUri: s3://<bucket-name>/65113397247fb02f6ff4bad985a3240c Handler: app.lambdaHandler Runtime: nodejs12.xsam publish
SAMで作成したアプリケーションをAWS Serverless Application Repositoryに公開できます。
publish を実行するには SAMテンプレート内のMetadataに公開のために必要な情報を
定義する必要があります。LICENSE.txt および、README.md も用意しておく必要があります。template.yaml# 以下のメタデータが必要 Metadata: AWS::ServerlessRepo::Application: Name: my-app Description: hello world Author: user1 SpdxLicenseId: Apache-2.0 LicenseUrl: LICENSE.txt ReadmeUrl: README.md Labels: ['tests'] HomePageUrl: https://github.com/user1/my-app-project SemanticVersion: 0.0.1 SourceCodeUrl: https://github.com/user1/my-app-projectまたServerless Application Repository が アーティファクトが格納されているs3バケットを
参照することができるように、対象のバケットに以下のようなポリシーの追加が必要です。policy.json{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "serverlessrepo.amazonaws.com" }, "Action": "s3:GetObject", "Resource": "arn:aws:s3:::<your-bucket-name>/*" } ] }テンプレートファイル内のLicenseUrl, ReadmeUrl, CodeUriはS3上のファイルを指定する必要があるため
前述の sam package コマンドでパッケージ化後、publishを行います。$ sam package --template-file template.yaml \ --s3-bucket <bucket-name> --output-template-file packaged.yaml ~出力は省略~LICENSE.txt および README.md も package コマンドにより S3にアップロードされ
Urlが書き換わっていることがわかります。Metadata: AWS::ServerlessRepo::Application: Name: my-app Description: hello world Author: user1 SpdxLicenseId: Apache-2.0 - LicenseUrl: LICENSE.txt + LicenseUrl: s3://<bukcet-name>/d41d8cd98f00b204e9800998ecf8427e - ReadmeUrl: README.md + ReadmeUrl: s3://kosu-test/2a258235395cbaff804bbaab61a6d4ec ~以下略~パッケージ後のテンプレートとリージョンを指定して publish します。
アプリケーションはプライベートアプリケーションとしてSARに登録されます。$ sam publish --template packaged.yaml --region ap-northeast-1 Created new application with the following metadata: { "Name": "my-app", "Description": "hello world", "Author": "user1", "SpdxLicenseId": "Apache-2.0", "LicenseUrl": "s3://<bucket-name>/d41d8cd98f00b204e9800998ecf8427e", "ReadmeUrl": "s3://<bucket-name/2a258235395cbaff804bbaab61a6d4ec", "Labels": [ "tests" ], "HomePageUrl": "https://github.com/user1/my-app-project", "SemanticVersion": "0.0.1", "SourceCodeUrl": "https://github.com/user1/my-app-project" } Click the link below to view your application in AWS console: https://console.aws.amazon.com/serverlessrepo/home?region=ap-northeast-1#/published-applications/arn:aws:serverlessrepo:ap-northeast-1:0123456789012:applications~my-appSARのコンソールを確認すると、正常にアプリケーションが登録され
プライベートアプリケーションとして利用可能になっています。
その他の機能
sam logs
デプロイ済みの Lambda 関数のログを取得できます。
--tail でログをたれ流したり、--filter "文字列" でフィルタリングすることもできます。
以下では CloudFormation のスタック名と関数名を指定しています。$ sam logs -n HelloWorldFunction --stack-name mystack 2018/07/20/[$LATEST]c1e4c40fbbf2495aa91ecff5b21ce24d 2018-07-20T07:24:37.835000 START RequestId: f5664465-8bed-11e8-994b-8d4b4bdcc800 Version: $LATEST 2018/07/20/[$LATEST]c1e4c40fbbf2495aa91ecff5b21ce24d 2018-07-20T07:24:38.395000 END RequestId: f5664465-8bed-11e8-994b-8d4b4bdcc800 2018/07/20/[$LATEST]c1e4c40fbbf2495aa91ecff5b21ce24d 2018-07-20T07:24:38.395000 REPORT RequestId: f5664465-8bed-11e8-994b-8d4b4bdcc800 Duration: 542.93 ms Billed Duration: 600 ms Memory Size: 128 MB Max Memory Used: 22 MBもしくは関数名を直接指定してもよいので、AWS SAM CLI や CloudFormation で
デプロイされたかどうかは関係なく、全てのLanmbda関数に対して実行できます。
また フィルタリングした結果を tail するなどの組み合わせもできるので
トラブルシューティングを迅速に行うことができます。$ sam logs -n ECRScanToSlack --tail --filter "INFO" 2019/12/12/[$LATEST]76c0c056a02541a8bf7b2568e9e8b06d 2019-12-12T08:32:26.793000 [INFO] 2019-12-12T08:32:26.792Z 506de805-fb06-426f-b6b0-e89f2eb23660 Found credentials in environment variables. 2019/12/12/[$LATEST]76c0c056a02541a8bf7b2568e9e8b06d 2019-12-12T08:32:28.411000 [INFO] 2019-12-12T08:32:28.408Z 506de805-fb06-426f-b6b0-e89f2eb23660 Message posted. 2019/12/12/[$LATEST]76c0c056a02541a8bf7b2568e9e8b06d 2019-12-12T08:34:32.572000 [INFO] 2019-12-12T08:34:32.572Z 3dcde807-f150-4056-bb12-b791b77d0731 Message posted. 2019/12/12/[$LATEST]76c0c056a02541a8bf7b2568e9e8b06d 2019-12-12T08:34:35.812000 [INFO] 2019-12-12T08:34:35.811Z 5ac31616-be63-4402-90ab-4d9b0b74a1a6 Message posted.Lambda Layersへの対応
AWS SAM CLI は Labmda Layersを使用した関数のローカル実行にも対応しています。(v0.8.1以降)
以下は Lambda Layers を含むSAMテンプレートの例です。template.yaml# 該当箇所を抜粋 Resources: HelloWorldFunction: Type: AWS::Serverless::Function Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.8 Layers: - arn:aws:lambda:ap-northeast-1:0123456789012:layer:python-boto3:3 # Layerの指定ローカル実行時、AWS SAM CLIは対象のLayerのパッケージをダウンロードし、ローカルにキャッシュします。
その後、docker-lambda のイメージをベース に対象Layerのキャッシュがオーバーレイされた
Dockerイメージが自動でビルドされ、そのイメージを使用して実行が行われます。$ sam local invoke Invoking app.lambda_handler (python3.8) Downloading arn:aws:lambda:ap-northeast-1:0123456789012:layer:python-boto3 [####################################] 8076250/8076250 Image was not found. Building image... Requested to skip pulling images ... Mounting C:\test\sam-app2\hello_world as /var/task:ro,delegated inside runtime container START RequestId: bfee13e1-74be-1953-d236-aa7e0bc13d8e Version: $LATEST END RequestId: bfee13e1-74be-1953-d236-aa7e0bc13d8e REPORT RequestId: bfee13e1-74be-1953-d236-aa7e0bc13d8e Init Duration: 310.57 ms Duration: 4.80 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 23 MB {"statusCode":200,"body":"{\"message\": \"hello world\"}"} # samcli/lambda が Layer が展開された後のdocker-lambdaのイメージ $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE samcli/lambda python3.8-a84bc9cd09799899970128014 c37be1104bea 2 hours ago 493MB lambci/lambda python3.8 95403fd346c3 11 days ago 448MBAWS Toolkit と AWS SAM CLIによるデバッグ
各種IDEに対応した AWS Toolkit は AWS SAMとの親和性が高く、
SAMアプリケーションの作成、デバッグ、デプロイを行うことができます。
以下は AWS Toolkit for Visual Studio Code 拡張でNode.jsランタイムの
Lambda関数のデバッグを行う例です。デバッグの起動構成に以下のような構成を追加します。(.vscode/launch.json)
launch.json{ "version": "0.2.0", "configurations": [ { "name": "Attach to SAM CLI", "type": "node", "request": "attach", "address": "localhost", "port": 5858, "localRoot": "${workspaceRoot}/hello-world", "remoteRoot": "/var/task", "protocol": "inspector", "stopOnEntry": false } ] }-d, --debug-port オプションを指定し、sam local invoke します。
$ sam local invoke -e ./events/event.json -d 5858 Invoking app.lambdaHandler (nodejs10.x) Fetching lambci/lambda:nodejs10.x Docker container image...... Mounting C:\vscode\sam-app\hello-world as /var/task:ro,delegated inside runtime container Debugger listening on ws://0.0.0.0:5858/3e80f80b-bbaa-4a6c-b087-7f2f3fd46b6b For help, see: https://nodejs.org/en/docs/inspectorLambda関数がデバッグモードで起動しますので、Visual Studio Code側で設定した
起動設定からデバッグを開始することができます。
参考
AWS Serverless Application Model - Developer Guide
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/what-is-sam.html
GitHub - aws-sam-cli Releases
https://github.com/awslabs/aws-sam-cli/releases以上です。
参考になれば幸いです。
- 投稿日:2019-12-15T12:27:16+09:00
Aurora PostgreSQL 11.4 互換版がひっそりと出ていたので試してみた
こちらは PostgreSQL Advent Calendar 2019 17 日目のエントリです。
昨日(16 日目)は tom-sato さんでした。
実は、元々 Alibaba Cloud の POLARDB PostgreSQL 互換版で oss_fdw 機能を試そうとしたのですが、残念ながら実装が追い付いていなかったようなので、今回は AWS Aurora に戻ってきました。
そして、当アドベントカレンダーの中で一番内容の薄いエントリとなります(このアドベントカレンダー作ったの誰だよ?←私だ!)。
Aurora PostgreSQL 11.4 互換版
re:Invent 2019 開催直前の 2019/11 下旬にリリースされていました。
今回は東京リージョン(ap-northeast-1)も同時リリースだったようです。
PostgreSQL 11 の新機能については、こちらにあるページのリンク先を参照してください。
- PostgreSQL11の新機能(Let's Postgres)
とはいえ、本家 PostgreSQL 11 は 2018/10 にリリース、次バージョンの PostgreSQL 12 も 2019/10 にリリースされているので、ほぼ 1 年遅れのタイミングとなります。
試す内容(1):10.5 互換版登場時に試したパラレルスキャンを R5 インスタンスで
こちらの記事と同じ内容を実行してみます。
ただし、Aurora は、2019/2 に R5 インスタンスをサポートしています。
※PostgreSQL 互換版の R5 インスタンスサポートが同時だったかどうは記憶にございません…T3 インスタンスは遅れてサポートされたようですが。
前回は R4 インスタンスで 10.5 互換版を試しましたが、今回は、R5 インスタンス(db.r5.large)で
- 10.7 互換版
- 11.4 互換版
を試します。
また、
の結果(POLARDB PostgreSQL 11.2 互換版)も引用します。
結果
インスタンスタイプ/バージョン シーケンシャル 1 回目 シーケンシャル 2 回目 インデックス 1 回目 インデックス 2 回目 db.r4.large/Aurora 10.5 ※昨年 5,707.766 2,921.991 42,457.903 493.760 4C16GB/POLARDB 11.2 5,388.365 968.011 6,091.151 264.254 db.r5.large/Aurora 10.7 4,405.818 2,462.651 29,931.351 439.518 db.r5.large/Aurora 11.4 4,048.994 2,314.962 32,709.778 426.766 それぞれの 1 回目が共有バッファにデータが載っていない状態、2 回目が載っている状態です。
- R5 インスタンスは R4 と比べて高速(特にストレージアクセス)
- PostgreSQL 10 と 11 の間では、大きな差は無し
- Aurora と POLARDB の差は CPU コア数とストレージ構成の違いによるもの?
なお、Aurora 10.7 と 11.4 で 11.4 のほうが結果が悪くなっていますが、何度か実行していると「外れインスタンス」を掴むことがあり、これが影響している可能性があります(時間の都合で少ない試行回数の平均を取っています)。
Aurora R5 と POLARDB の違いですが、
- CPU Core は Aurora が 2 に対し POLARDB が 4(AWS での m5.xlarge に近い)
- ストレージは Aurora が 3AZ-6Copy、POLARDB が 1AZ-3Copy。
- 当該インスタンスのストレージ I/O 帯域は Aurora が 4.75Gbps に対して POLARDB が 4Gbps
- 2019/12/16 現在、AWS の日本語マニュアルの情報は古いです(× 3.5Gbps →〇 4.75Gbps)
なので、
- メモリ上のデータを使った処理は CPU Core 数の分 POLARDB 有利(パラレルスキャンの負荷など)
- ランダムで細かいストレージアクセスがある場合も POLARDB 有利(別 AZ からの ACK を待たなくても良いから?)
- シーケンシャルなストレージアクセスの場合は Aurora のほうが多少有利?(遅延よりもI/O 帯域?)
ということでしょうか。
試す内容(2):パーティショニング
PostgreSQL 10 / 11 の新機能と言えば「宣言的パーティショニング」ですね(やや強引)。
というわけで、今回は、
- 先のテストパターンのうち、インデックススキャンのほうのテーブル定義にパーティショニングを含める
- 10 の宣言的パーティショニングでは PK が設定できないため number 列の PK を外す
- 11 では親テーブルに PK を設定できるのでそちらの形で試す(PK の一部にパーティションキーを含める必要があるので
id
列を単独のインデックスではなく PK の 2 列目に含める)で試してみます。
なお、完全に手抜きですが、
WHERE id < 10
の条件に合わせてid
が0
~9
のパーティションと10
以上のパーティションの 2 つに分けただけ、です。※元記事に書いた通り、
id
というネーミングが完全に罠になっています(ごめんなさい)。10用のテーブル定義CREATE TABLE table_i (number SERIAL NOT NULL, id INT, value INT) PARTITION BY RANGE (id); CREATE TABLE table_i_1 PARTITION OF table_i FOR VALUES FROM (0) TO (10); CREATE TABLE table_i_2 PARTITION OF table_i FOR VALUES FROM (10) TO (100); CREATE INDEX idx_id_1 ON table_i_1 (id); CREATE INDEX idx_id_2 ON table_i_2 (id);11用のテーブル定義CREATE TABLE table_i (number SERIAL NOT NULL, id INT, value INT) PARTITION BY RANGE (id); CREATE TABLE table_i_1 PARTITION OF table_i FOR VALUES FROM (0) TO (10); CREATE TABLE table_i_2 PARTITION OF table_i FOR VALUES FROM (10) TO (100); ALTER TABLE table_i ADD PRIMARY KEY(number, id);ちなみに、パーティショニングで気になる
INSERT
の速度ですが、パーティショニング無しと比較して概ね 10 ~ 20% 程度余分に時間が掛かりました(ばらつきが大きいので値を掲示するのは控えます)。結果
バージョン/パーティショニング有無 インデックス 1 回目 インデックス 2 回目 Aurora 10.7/無 29,931.351 439.518 Aurora 10.7/有 540.669 331.789 Aurora 11.4/無 32,709.778 426.766 Aurora 11.4/有 539.339 355.797 パーティショニング無しの結果は(1)からの引用です。
- 共有バッファに載っていないと劇的に高速化
WHERE
句の条件と完全にマッチする形で分割したため- パーティショニング有りでも 10 と 11 の差はほぼ無し
でした。
Aurora の場合、テーブル空間を別ストレージに分離する手法は取れないため、10 に対する 11 のアドバンテージは性能向上ではなくて
- パーティション間のデータ移動ができる
- デフォルトパーティションを作ることができる
- 外部キーなどの制約を使うことができる
あたりでしょうか。
余談
Aurora PostgreSQL 互換版、この↓機能のおかげで便利にはなったものの、「共有バッファにデータが載っていない」状態でテストすることが難しくなってしまいました(設定で OFF にしても「あれ?載ってるのでは?」という挙動が何度も…)。
PostgreSQL Advent Calendar 2019 の明日(18 日目)は sira さんです。