- 投稿日:2019-12-19T23:53:12+09:00
AI Platform(GCP)でGPU 100個同時に使いテンションあがった
ひゃっはー
概要
機械学習基盤系のサービスとしてAWSのSageMakerが有名かと思いますが
本投稿ではGCPの類似サービスAI Platformを使ってGPU 100並列同時に動かした話をします。AI Platformでできること
SageMakerと似た感じで、AI Platformでは以下のようにうたっています。
(公式ドキュメントより引用)AI Platform を使用して、ML ワークフローの次の段階を管理できます。
- データで ML モデルをトレーニングする。
- モデルをトレーニング
- モデルの精度を評価する
- ハイパーパラメータを調整する
- トレーニング済みモデルをデプロイする。
- モデルに予測リクエストを送信する。
- オンライン予測
- バッチ予測(TensorFlow のみ)
- 継続的に予測をモニタリングする。
- モデルとモデル バージョンを管理する。
いわゆる機械学習基盤として使えますよ便利ですね、ってサービスなんですが
単に時間のかかるバッチジョブ(MLの場合、特に学習ジョブ)をサーバレスで走行する環境として使うだけでも割とその恩恵を受けられます。
(というかマジメに機械学習基盤として使うにはちょっとまだ機能連携が足りていないような...)GPUをGCEなどのIaaSで利用するとインスタンスの起動・停止がめんどいので
学習が走行されている時間だけ課金されるマネージドなジョブ実行ができるのはありがたいです。バッチを走行するだけなら他にも方法あるやん、って思われるかもしれないですが
意外と「GPUが使える」かつ「時間制限がない」ってサービスはあまりなくて
他の選択肢だとGKEでk8s jobくらいなのかな、と思います(他にもあったらごめんなさい...)なので、GKEよりは手軽にML学習のジョブを走らせられるのは、それだけでもメリットと感じます。
GPUの同時利用上限
AI Platformには同時に利用できるGPU数に上限が設けられています。
- Tesla K80 GPUs: 30
- Tesla P100 GPUs: 30
- Tesla V100 GPUs: 8
SageMakerのデフォルト上限は下記のようです。(最新版は公式ドキュメントを参照してください)
インスタンスタイプ GPU 同時利用インスタンス数上限 ml.p2.xlarge K80 * 1 1 ml.p2.8xlarge K80 * 8 1 ml.p2.16xlarge K80 * 16 1 ml.p3.2xlarge V100 * 1 2 ml.p3.8xlarge V100 * 4 2 ml.p3.16xlarge V100 * 8 2 合計のGPU数はさほどAI Platformと変わらないように思えますが
1つのインスタンスにGPUが複数搭載されていても、GPU分散で学習させないと意味がないです。
そういう意味だと、1GPUのジョブを何個も同時に実行したい場合にはSageMakerのデフォルトではちょっと足りないです。
(もちろんSageMakerでも上限を増やす申請はできます)上限を増やす
通常、GCPの上限を増やす際の申請は、GCPコンソール上の「IAMと管理」=>「割り当て」から行うのですが
AI PlatformのGPU上限はなぜか別管理されており、ここのGoogleフォームから行います。
(公式ドキュメント参照)で、私はここで
"70 P100s", "30 V100s"と申請し、無事通りました。
(申請から承認まで大体1週間くらいかかりました)これでGPUめっちゃ同時に使う環境が手に入りました。
ユースケース
ところでGPUを同時にたくさん利用したいケースってどんなんでしょうか。
- モデル実装、パラメータ、データ、等のバリエーションを短期間で試したいとき
- GPU分散で学習させたいとき
- 上記のようなタスクが複数人同時に発生するとき
Qiitaで釣りっぽいタイトルを書きたいときとかですかね。用法・用量を守って正しく使いましょう。
ちなみに私はデータのバリエーションがたくさんあって、一気に複数のモデルを作って比較したい
というちゃんとした理由は一応ありました。学習ジョブの実行
AI Platformでの学習ジョブ実行手順はざっくり以下です。
- 「pythonのコマンドとして実行できる形式でパッケージ」 or 「Dockerイメージを作成」
- 「パッケージをGCSにアップロード」 or 「DockerイメージをGCRにpush」
- 学習データをGCSにアップロード
- gcloudコマンドでAI Platformジョブを開始
以前はDockerコンテナでの実行はベータ版だったのですが
今はGAになっていますし、Dockerがやりやすいでしょう。以下ではDockerコンテナを用いた手順を記載
と思ったのですが、時間がないので公式チュートリアルが充実しているので
そちらにリンクを貼っておき、「?」となりそうなところだけ補足します。(みんな大好きMNISTです)
基本的にチュートリアルのコピペで動くはずです。また、単にGPUを使ったジョブ実行を体験したいだけなら、↑のチュートリアルすべてを実施する必要はありません。
具体的には
だけで可能です。
Dockerfile
チュートリアルなんで素直にそのまま使えばいいのですが、ちょっとだけ補足。
FROM nvidia/cuda:9.0-cudnn7-runtime古い。けどまぁ無視していいかと。
例えばTensorflow 1.12以上を使いたいときとかはCUDA 10のイメージを指定しましょう。RUN wget -nv \ https://dl.google.com/dl/cloudsdk/release/google-cloud-sdk.tar.gz && \ mkdir /root/tools && \ tar xvzf google-cloud-sdk.tar.gz -C /root/tools && \ rm google-cloud-sdk.tar.gz && \ /root/tools/google-cloud-sdk/install.sh --usage-reporting=false \ --path-update=false --bash-completion=false \ --disable-installation-options && \ rm -rf /root/.config/* && \ ln -s /root/.config /config && \ # Remove the backup directory that gcloud creates rm -rf /root/tools/google-cloud-sdk/.install/.backup # Path configuration ENV PATH $PATH:/root/tools/google-cloud-sdk/bin # Make sure gsutil will use the default service account RUN echo '[GoogleCompute]\nservice_account = default' > /etc/boto.cfgDockerコンテナではなく、Pythonパッケージをアップロードする方法でやる場合は
すでに色々とインストールされているインスタンスで実行されるのですが
Dockerコンテナ内では当然必要なツール等は自分でインストールしておく必要があります。AI PlatformはGCSを入出力ファイル置き場として使うので、
ここではそれに必要なgcloud SDKのインストール、およびgsutilコマンドの認証設定を行っています。ジョブの実行
DockerイメージをGCRにpushしたら、あとはgcloudコマンドでAI Platformのジョブを実行するだけです。かんたんですね。
$ export MODEL_DIR=pytorch_model_gpu_$(date +%Y%m%d_%H%M%S) $ export REGION=us-central1 $ export JOB_NAME=custom_container_job_gpu_$(date +%Y%m%d_%H%M%S)と変数を定義しておいて、下記を実行。
$ gcloud ai-platform jobs submit training $JOB_NAME \ --scale-tier BASIC_GPU \ --region $REGION \ --master-image-uri $IMAGE_URI \ -- \ --epochs=5 \ --model-dir=gs://$BUCKET_NAME/$MODEL_DIR
--scale-tier
ジョブを実行するインスタンスのスペックを指定します。(スケール階層と呼ばれてます)
BASIC_GPUはk80が1個アサインされたインスタンスが使われます。
指定できるスケール階層は こちら。
--region
ジョブを実行するリージョンを選びます。
アクセスするGCSと同一リージョンを指定しなければならない、という謎制約があります。(ファイル転送の速度を考慮してくれてる、というポジティブ解釈)
また、リージョンによっては使えるGPUの種類が違ったりします。
--以降の引数は、gcloudコマンドの引数ではなく、実行するpythonコマンドの引数として与えられます。
チュートリアルのソースコードを見ればわかるのですが
--epochsと--model-dirという引数がコマンドに渡されて実行されることになります。
自作コマンドを作るときも、こんな感じで可変なパラメータを引数化します。上記のgcloudコマンドを実行して
jobId: custom_container_job_gpu_20191219_142126 state: QUEUEDと出力されれば、ジョブ発行は成功です。
チュートリアルでは
$ gcloud ai-platform jobs describe $JOB_NAME $ gcloud ai-platform jobs stream-logs $JOB_NAMEでジョブの進捗を確認しよう、となっていますが
GCPコンソール上で AI Platformのジョブ一覧を見たほうが簡単かもです。あとはGPUの数だけジョブを発行しまくれば、ひゃっはーできます。
最後に
タイトルから釣りっぽい投稿になってしまい、反省しています。
そもそもGPUを100個同時に使うケースはあまりないにしても、
GPUを使いたいとき使いたいだけ利用できるサーバレスタイプの環境として、
という観点だけでもAI Platformを使ってみる価値はあるかもです。また、今回取り上げませんでしたがハイパーパラメータの自動調整機能を使うと、複数GPUを一気に使えるメリットもあるってもんです。
MNISTを100並列で動かしてどうすんねん、という感じなので、もっと有効に使っていきましょう。
(一応私ももっとマジメに使ってます)
- 投稿日:2019-12-19T22:43:25+09:00
めちゃくちゃ簡単 Amazon Rekognition
AIとか画像解析とか聞くとなんか難しそうだけど、Amazonが提供しているサービスを使うと簡単に実装できるので、試してみた!
Amazon Rekognitionとは
Amazon Rekognition では、イメージ分析とビデオ分析をアプリケーションに簡単に追加することができます。Amazon Rekognition API にイメージやビデオを指定するだけで、このサービスによってモノ、人物、テキスト、シーン、アクティビティを識別できます。不適切なコンテンツも検出できます。また、Amazon Rekognition では、高精度な顔分析および顔認識も可能です。顔の検出、分析、比較は、ユーザー検証、カタログ作成、人数計数、公共安全など、多岐にわたって活用できます。
ひとまず実装してみる
環境を作成するのが手間なのでLambdaを使って実装。
プログラムはLambdaで提供があるPython 3.7。顔認識
人物が写っている2枚の写真から同一人物かどうかを判定する。
S3にアップされている写真を比較するサンプルプログラム。from boto3 import Session def lambda_handler(event, context): session = Session(region_name="ap-northeast-1") rekognition = session.client("rekognition") response=rekognition.compare_faces( SourceImage={'S3Object':{'Bucket':"XXXXX",'Name':"XXXXX"}}, TargetImage={'S3Object':{'Bucket':"XXXXX",'Name':"XXXXX"}}, SimilarityThreshold=80) return response簡単にプログラムの解説。
from boto3 import SessionAWSの各種サービスを簡単に仕様できるライブラリboto3を使って実装。
※ライブライはC++、Go、Java、JavaScript,.Net、Node.js、PHP、Rubyがあるsession = Session(region_name="ap-northeast-1") rekognition = session.client("rekognition")サービスを使用するためには必ずSessionというAWSとの接続情報が必要。
リージョンはアジア東京を指定。
rekognitionを使用するためのオブジェクト取得。response=rekognition.compare_faces( SourceImage={'S3Object':{'Bucket':"XXXXX",'Name':"XXXXX"}}, TargetImage={'S3Object':{'Bucket':"XXXXX",'Name':"XXXXX"}}, SimilarityThreshold=80)これがAmazonから提供されている「顔認証」関数。
API仕様 こちらからSourceImageに認識したい顔だけが写っている写真
TargetImageに解析したい写真
SimilarityThresholdに認識度。高いほど厳しいチェックになる。0-100で指定。responseは
FaceMatches に同一人物を判断した顔の情報
UnmatchedFaces に違うと判断した顔の情報Confidenceがその判断の信頼度
{ "SourceImageFace": { "BoundingBox": { "Width": 0.30209746956825256, "Height": 0.21446102857589722, "Left": 0.319732129573822, "Top": 0.23807010054588318 }, "Confidence": 99.99998474121094 }, "FaceMatches": [ { "Similarity": 99.37952423095703, "Face": { "BoundingBox": { "Width": 0.21386872231960297, "Height": 0.16094279289245605, "Left": 0.6417301297187805, "Top": 0.6225799322128296 }, "Confidence": 99.99987030029297, "Landmarks": [ { "Type": "eyeLeft", "X": 0.7082532644271851, "Y": 0.6811327934265137 }, { "Type": "eyeRight", "X": 0.7683542370796204, "Y": 0.7155389189720154 }, { "Type": "mouthLeft", "X": 0.6812899112701416, "Y": 0.7383925914764404 }, { "Type": "mouthRight", "X": 0.7310847640037537, "Y": 0.7682420015335083 }, { "Type": "nose", "X": 0.6889581680297852, "Y": 0.7370006442070007 } ], "Pose": { "Roll": 38.84079360961914, "Yaw": -19.29507827758789, "Pitch": -41.210079193115234 }, "Quality": { "Brightness": 44.140357971191406, "Sharpness": 78.64350128173828 } } } ], "UnmatchedFaces": [ { "BoundingBox": { "Width": 0.01333685964345932, "Height": 0.013453098013997078, "Left": 0.9252238869667053, "Top": 0.4759874939918518 }, "Confidence": 97.7153091430664, "Landmarks": [ { "Type": "eyeLeft", "X": 0.9277710914611816, "Y": 0.4811204969882965 }, { "Type": "eyeRight", "X": 0.933621883392334, "Y": 0.4823104441165924 }, { "Type": "mouthLeft", "X": 0.9270203113555908, "Y": 0.4870522618293762 }, { "Type": "mouthRight", "X": 0.931785523891449, "Y": 0.4880983531475067 }, { "Type": "nose", "X": 0.9278116226196289, "Y": 0.484792023897171 } ], "Pose": { "Roll": 13.193720817565918, "Yaw": -30.132854461669922, "Pitch": -10.473925590515137 }, "Quality": { "Brightness": 75.9936752319336, "Sharpness": 73.32209777832031 } }, { "BoundingBox": { "Width": 0.2887393832206726, "Height": 0.32251936197280884, "Left": 0.1142626702785492, "Top": 0.45462948083877563 }, "Confidence": 99.99995422363281, "Landmarks": [ { "Type": "eyeLeft", "X": 0.1551831066608429, "Y": 0.5991319417953491 }, { "Type": "eyeRight", "X": 0.25697842240333557, "Y": 0.6217619776725769 }, { "Type": "mouthLeft", "X": 0.14137881994247437, "Y": 0.7185965180397034 }, { "Type": "mouthRight", "X": 0.22245647013187408, "Y": 0.7383744716644287 }, { "Type": "nose", "X": 0.14210543036460876, "Y": 0.6684256792068481 } ], "Pose": { "Roll": 17.434614181518555, "Yaw": -41.510498046875, "Pitch": -21.136119842529297 }, "Quality": { "Brightness": 41.628482818603516, "Sharpness": 95.51618957519531 } }, { "BoundingBox": { "Width": 0.007370786275714636, "Height": 0.008144599385559559, "Left": 0.6609883904457092, "Top": 0.49813687801361084 }, "Confidence": 83.50224304199219, "Landmarks": [ { "Type": "eyeLeft", "X": 0.6622864603996277, "Y": 0.5042642951011658 }, { "Type": "eyeRight", "X": 0.6650356650352478, "Y": 0.5022475719451904 }, { "Type": "mouthLeft", "X": 0.6650884747505188, "Y": 0.5060529112815857 }, { "Type": "mouthRight", "X": 0.6673902273178101, "Y": 0.5043438076972961 }, { "Type": "nose", "X": 0.6656265258789062, "Y": 0.505027174949646 } ], "Pose": { "Roll": -43.10660934448242, "Yaw": -4.3733038902282715, "Pitch": -33.09136962890625 }, "Quality": { "Brightness": 73.02571868896484, "Sharpness": 53.330047607421875 } }, { "BoundingBox": { "Width": 0.009758301079273224, "Height": 0.010332607664167881, "Left": 0.966498851776123, "Top": 0.46405404806137085 }, "Confidence": 61.05154037475586, "Landmarks": [ { "Type": "eyeLeft", "X": 0.9691260457038879, "Y": 0.4726991653442383 }, { "Type": "eyeRight", "X": 0.9677429795265198, "Y": 0.4707190990447998 }, { "Type": "mouthLeft", "X": 0.9723362326622009, "Y": 0.4712704122066498 }, { "Type": "mouthRight", "X": 0.9711670279502869, "Y": 0.4696311056613922 }, { "Type": "nose", "X": 0.9699736833572388, "Y": 0.4713567793369293 } ], "Pose": { "Roll": -119.80672454833984, "Yaw": -8.657889366149902, "Pitch": 21.94735336303711 }, "Quality": { "Brightness": 84.52898406982422, "Sharpness": 73.32209777832031 } }, { "BoundingBox": { "Width": 0.018424568697810173, "Height": 0.020794881507754326, "Left": 0.7489200830459595, "Top": 0.47518208622932434 }, "Confidence": 87.87468719482422, "Landmarks": [ { "Type": "eyeLeft", "X": 0.7618342638015747, "Y": 0.48698189854621887 }, { "Type": "eyeRight", "X": 0.7628127336502075, "Y": 0.4866960644721985 }, { "Type": "mouthLeft", "X": 0.7631105184555054, "Y": 0.4888738989830017 }, { "Type": "mouthRight", "X": 0.7637985944747925, "Y": 0.4886566698551178 }, { "Type": "nose", "X": 0.7641363143920898, "Y": 0.48711803555488586 } ], "Pose": { "Roll": -34.46097946166992, "Yaw": 60.46308135986328, "Pitch": -14.032490730285645 }, "Quality": { "Brightness": 67.51453399658203, "Sharpness": 83.14741516113281 } }, { "BoundingBox": { "Width": 0.011087420396506786, "Height": 0.011487020179629326, "Left": 0.9760779142379761, "Top": 0.4641396105289459 }, "Confidence": 97.86558532714844, "Landmarks": [ { "Type": "eyeLeft", "X": 0.978825032711029, "Y": 0.469203919172287 }, { "Type": "eyeRight", "X": 0.9811084270477295, "Y": 0.4689272344112396 }, { "Type": "mouthLeft", "X": 0.9797573685646057, "Y": 0.47199031710624695 }, { "Type": "mouthRight", "X": 0.9815858006477356, "Y": 0.47177186608314514 }, { "Type": "nose", "X": 0.979856014251709, "Y": 0.47067731618881226 } ], "Pose": { "Roll": -11.149984359741211, "Yaw": -42.128475189208984, "Pitch": -1.7187515497207642 }, "Quality": { "Brightness": 79.50706481933594, "Sharpness": 73.32209777832031 } }, { "BoundingBox": { "Width": 0.008290473371744156, "Height": 0.009163682349026203, "Left": 0.6165273785591125, "Top": 0.5080556273460388 }, "Confidence": 73.62310791015625, "Landmarks": [ { "Type": "eyeLeft", "X": 0.6195554137229919, "Y": 0.5123717188835144 }, { "Type": "eyeRight", "X": 0.6210446357727051, "Y": 0.5128311514854431 }, { "Type": "mouthLeft", "X": 0.6189116835594177, "Y": 0.5142983794212341 }, { "Type": "mouthRight", "X": 0.6200852394104004, "Y": 0.5146732926368713 }, { "Type": "nose", "X": 0.6193997263908386, "Y": 0.5135700702667236 } ], "Pose": { "Roll": 20.73882293701172, "Yaw": -33.038211822509766, "Pitch": -11.055394172668457 }, "Quality": { "Brightness": 41.28157424926758, "Sharpness": 67.22731018066406 } } ], "ResponseMetadata": { "RequestId": "d638e263-eeb5-4cbf-807d-aa6328f8096b", "HTTPStatusCode": 200, "HTTPHeaders": { "content-type": "application/x-amz-json-1.1", "date": "Thu, 19 Dec 2019 12:20:36 GMT", "x-amzn-requestid": "d638e263-eeb5-4cbf-807d-aa6328f8096b", "content-length": "5545", "connection": "keep-alive" }, "RetryAttempts": 0 } }導入事例
千株式会社の写真販売サービス「はいチーズ」。幼稚園や保育園でのイベントの写真販売の際に、スマホから「自分の子どもが映った写真」だけを選んで抽出できるサービスを提供。
テキスト検出
from boto3 import Session def lambda_handler(event, context): session = Session(region_name="ap-northeast-1") rekognition = session.client("rekognition") response=rekognition.detect_text(Image={'S3Object':{'Bucket':"XXXXX",'Name':"XXXXX"}}) return response簡単にプログラムの解説。
from boto3 import SessionAWSの各種サービスを簡単に仕様できるライブラリboto3を使って実装。
※ライブライはC++、Go、Java、JavaScript,.Net、Node.js、PHP、Rubyがあるsession = Session(region_name="ap-northeast-1") rekognition = session.client("rekognition")サービスを使用するためには必ずSessionというAWSとの接続情報が必要。
リージョンはアジア東京を指定。
rekognitionを使用するためのオブジェクト取得。response=rekognition.detect_text(Image={'S3Object':{'Bucket':"XXXXX",'Name':"XXXXX"}})これがAmazonから提供されている「テキスト検出」関数。
API仕様 こちらからImageに解析したい画像を渡すだけ。
この画像を解析した結果。
行と単語ごとに解析される。
「J389 NLT」は行(line)で「J389 NLT」、単語で「J389」「NLT」。
DetectedTextが検出したテキスト
Confidenceが信頼度{ "TextDetections": [ { "DetectedText": "C", "Type": "LINE", "Id": 0, "Confidence": 72.02649688720703, "Geometry": { "BoundingBox": { "Width": 0.04000072553753853, "Height": 0.022570611909031868, "Left": 0.25833332538604736, "Top": 0.1862500011920929 }, "Polygon": [ { "X": 0.25833332538604736, "Y": 0.1862500011920929 }, { "X": 0.2983340620994568, "Y": 0.18744535744190216 }, { "X": 0.29803428053855896, "Y": 0.21001596748828888 }, { "X": 0.2580335736274719, "Y": 0.20882061123847961 } ] } }, { "DetectedText": "J389 NLT", "Type": "LINE", "Id": 1, "Confidence": 99.67211151123047, "Geometry": { "BoundingBox": { "Width": 0.16278892755508423, "Height": 0.054150763899087906, "Left": 0.47471365332603455, "Top": 0.7062126398086548 }, "Polygon": [ { "X": 0.47471365332603455, "Y": 0.7062126398086548 }, { "X": 0.6375026106834412, "Y": 0.7274553775787354 }, { "X": 0.6343620419502258, "Y": 0.781606137752533 }, { "X": 0.4715730845928192, "Y": 0.7603633999824524 } ] } }, { "DetectedText": "C", "Type": "WORD", "Id": 2, "ParentId": 0, "Confidence": 72.02649688720703, "Geometry": { "BoundingBox": { "Width": 0.040019527077674866, "Height": 0.022515427321195602, "Left": 0.25833332538604736, "Top": 0.1862500011920929 }, "Polygon": [ { "X": 0.25833332538604736, "Y": 0.1862500011920929 }, { "X": 0.2983333468437195, "Y": 0.1875 }, { "X": 0.29750001430511475, "Y": 0.20999999344348907 }, { "X": 0.25833332538604736, "Y": 0.20874999463558197 } ] } }, { "DetectedText": "J389", "Type": "WORD", "Id": 3, "ParentId": 1, "Confidence": 99.63874053955078, "Geometry": { "BoundingBox": { "Width": 0.08508982509374619, "Height": 0.05266508460044861, "Left": 0.4749999940395355, "Top": 0.706250011920929 }, "Polygon": [ { "X": 0.4749999940395355, "Y": 0.706250011920929 }, { "X": 0.559166669845581, "Y": 0.71875 }, { "X": 0.5550000071525574, "Y": 0.7712500095367432 }, { "X": 0.4716666638851166, "Y": 0.7587500214576721 } ] } }, { "DetectedText": "NLT", "Type": "WORD", "Id": 4, "ParentId": 1, "Confidence": 99.70547485351562, "Geometry": { "BoundingBox": { "Width": 0.07040064036846161, "Height": 0.05260571464896202, "Left": 0.5674999952316284, "Top": 0.7200000286102295 }, "Polygon": [ { "X": 0.5674999952316284, "Y": 0.7200000286102295 }, { "X": 0.637499988079071, "Y": 0.7275000214576721 }, { "X": 0.6341666579246521, "Y": 0.7799999713897705 }, { "X": 0.5649999976158142, "Y": 0.7724999785423279 } ] } } ], "ResponseMetadata": { "RequestId": "b4280fa0-2982-4ac7-8ce3-02beb83b986f", "HTTPStatusCode": 200, "HTTPHeaders": { "content-type": "application/x-amz-json-1.1", "date": "Thu, 19 Dec 2019 13:26:41 GMT", "x-amzn-requestid": "b4280fa0-2982-4ac7-8ce3-02beb83b986f", "content-length": "2131", "connection": "keep-alive" }, "RetryAttempts": 0 } }料金
API を使用して画像を分析するたびに料金が発生。
だいたい、 1 か月あたり 100 万枚分析して 1000 USD。料金例は こちらから
捕捉
ACCESS_KEY、SECRET_KEYを指定せずに正常にsessionが取得できているのはLambdaを使っているからですね。
ポリシーを作成し、Lambda実行するロールに割り当ている。{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "rekognition:DetectText", "rekognition:CompareFaces", "s3:*" ], "Resource": "*" } ] }rekognition:DetectText、rekognition:CompareFacesの使用とs3への全ての操作を許可。
これがないと下記のエラーが出る。
"errorMessage": "An error occurred (AccessDeniedException) when calling the CompareFaces operation: User: arn:aws:sts::294326415501:assumed-role/testRekognitionPythonFace-role-jcjxzbmq/testRekognitionPythonFace is not authorized to perform: rekognition:CompareFaces",
- 投稿日:2019-12-19T22:36:56+09:00
Terraform で CodePipeline を管理する場合の注意点
CodePipeline を作っていてでハマったことの備忘録。
Terraform で管理する場合の注意点
- OAuth Token の差分が plan/apply のたびに発生する
- API get-CodePipeline が OAuth Token を取得できないため、Terraform や他のどんなツールを使っても冪等にできない
aws codepipeline get-codepipelineを実行すると、****という表示になって隠されている- 根本的な解決方法がなく、今も Open Issue になっている[1]
- GitHub webhook も定義する場合は、GitHub で発行した OAuth Token の scope に
admin:repo_hookも必要[2]- GitHub webhook は update できない
- 一度作成したリソースを修正したい場合は、 taint してから apply する
- Custom Action を定義できない
- これも API の制約で、 custom action を updade/get する手段がなく、 create/delete をする手段しか提供されていないため、どんなツールを使っても冪等に保つことができない
- ユースケース) Action Provider に Jenkins Custom を定義する場合など
- したがってコンソールか CLI をつかって定義することになる
AWS Console or CLI で管理する場合の注意点
- AWSコンソール上から作業できないワークフローがある
- (GitHub など外部の CVS の) OAuth トークンの登録 [3]
- 裏で暗黙的に作成されるリソースがある
- GitHub webhook
- GitHub のリポジトリに push されたときに AWS の Webhook 用エンドポイントを呼び出すためのもの
- CodePipeline の webhook
- GitHub => AWS CodePipeline を起動させるための Webhook エンドポイント
https://codepipeline.ap-northeast-1.amazonaws.com/...aws codepipeline list-webhooksで確認できる。試行錯誤しているとゴミが残りがち共通の注意点
- CodePipeline の定義を更新すると、実行中の action はすべてキャンセルされる
- (定義を変更せずに) CodePipeline で実行中のアクションを中止する方法はない
参考
- [1] https://github.com/terraform-providers/terraform-provider-aws/issues/2796
- [2] https://developer.github.com/v3/repos/hooks/#create-a-hook
- [3] https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/GitHub-create-personal-token-CLI.html
- [4] https://stackoverflow.com/questions/50925732/how-to-stop-an-execution-or-set-set-timeout-for-an-action-in-aws-codepipeline
- [5] https://qiita.com/hareku/items/90ed026256242c65e178
- 投稿日:2019-12-19T22:15:46+09:00
超ミニマムなチュートリアルを通してCodeDeployを理解する2(AutoScaling編)
前回記事の続きです。
超ミニマムなチュートリアルを通してCodeDeployを理解する。
本記事で取り上げた通り、CodeDeployによるデプロイは以下の要領で行われます。
appspec.ymlとデプロイしたいファイルをzip化してS3に格納しておく。- CodeDeployではデプロイ先と対象のファイルや設定を指定する。
今回は、ロードバランサ配下のAutoScalingGroup(ASG)のインスタンスを対象として
index.htmlをデプロイすることを試してみます。1. ASGを用意する。
別記事にまとめたので参考としてください。
→ 独自VPCでEC2のAutoScalingくらい秒で組めるようになりたいただし、注意すべき変更箇所があります。
前回のCodeDeployの記事のStepにも含めましたが、デプロイさせるインスタンスにはCodeDeployのAgentが導入されている必要があります。
起動設定の中の高度な詳細から以下のシェルを起動時に実行してもらうようにしましょう。この設定をしない状態(=Agentが入っていない状態)で後述のデプロイのコマンドを叩くと長い間「進行中」となった後にエラー終了します。
高度な詳細#!/bin/sh sudo yum update -y sudo yum install ruby -y sudo yum install wget -y cd /home/ec2-user wget https://aws-codedeploy-ap-northeast-1.s3.ap-northeast-1.amazonaws.com/latest/install chmod +x ./install sudo ./install auto sudo yum install httpd -y sudo service httpd start sudo chkconfig httpd onここではAgent導入とApacheの導入・起動を設定しております。
(なお、このAgent導入方法はAmazonLinuxが対象です。AmazonLinux2は少し記述が違います。この辺りは公式記載あり)インスタンスが立ち上がったらどれか1つにsshで入って確認をしましょう。
1. Agentの確認
以下のコマンドでAgentが入っているかを確認します。$ sudo service codedeploy-agent status The AWS CodeDeploy agent is running as PID 142952. httpdの確認
ブラウザから直接インスタンスのIPにアクセスするor動いてるプロセスを確認する。$ ps ax | grep httpd 14371 ? Ss 0:00 /usr/sbin/httpd 14374 ? S 0:00 /usr/sbin/httpd 14375 ? S 0:00 /usr/sbin/httpd 14376 ? S 0:00 /usr/sbin/httpd 14377 ? S 0:00 /usr/sbin/httpd 14378 ? S 0:00 /usr/sbin/httpd 14379 ? S 0:00 /usr/sbin/httpd 14380 ? S 0:00 /usr/sbin/httpd 14382 ? S 0:00 /usr/sbin/httpd 14492 pts/0 S+ 0:00 grep --color=auto httpd3. ロードバランサとのリンクの確認
ロードバランサのDNS名をブラウザからアクセスしてApacheのデフォルトの画面が表示されればOKです。これで、ELB + AutoScaingの準備は完了です。index.htmlをデプロイしていきましょう。
なお、今回は説明のためにCodeDeployASGという名のAutoScalingGroupを用意しました。
前回と同様に2台のインスタンス構成です。2. デプロイするファイルを用意する。
ローカルでやることは超ミニマムなチュートリアルを通してCodeDeployを理解する。の記事と同じです。
実行済みの場合はスキップしてOKです。
- AWS CLIを入れるorアップデートする。
- デプロイ対象のindex.htmlを作成する。
- CodeDeployのデプロイ設定を記述したappspec.ymlを作成する。
3. S3バケットを準備する。
超ミニマムなチュートリアルを通してCodeDeployを理解する。の記事と同じです。
実行済みの場合はスキップしてOKです。4. ASGへのデプロイ
ここからが本題です。
現在、testCodeDeployディレクトリの中身は以下のようになっているはずです。
$ ls appspec.yml index.htmlアプリケーションの作成
まずは、アプリケーションを作成します。ここでは
TestASGとします。$ aws deploy create-application --application-name TestASG { "applicationId": "f33128d3-81bd-49f7-9b97-28f635995xxx" }リビジョンの紐付け
デプロイ対象をzip化して、まるごと先ほど作成したS3バケットへ放り込みます。(1つのコマンドでできる。)
ここではファイル名をtestCodeDeploy.zipとします。
ただし、この操作は、前回記事のS3バケットにすでに用意してある場合はzip化とput-objectの操作はいらなくなり、register-application-revisionのみ行えば大丈夫です。S3が空の場合$ aws deploy push \ --application-name Test \ --s3-location s3://testcodedeploy/testCodeDeploy.zip \ --ignore-hidden-files To deploy with this revision, run: aws deploy create-deployment --application-name Test --s3-location bucket=testcodedeploy,key=testCodeDeploy.zip,bundleType=zip,eTag=2dbc9aab3836f21317a9eed7a36f1xxx --deployment-group-name <deployment-group-name> --deployment-config-name <deployment-config-name> --description <description>S3に既にzipがある場合$ aws deploy register-application-revision \ --application-name TestASG \ --s3-location bucket=testcodedeploy,key=testCodeDeploy.zip,bundleType=zip特に何も返値はないので不安な気持ちになりますがあってます。大丈夫。
デプロイメントグループの作成
次は、デプロイメントグループの作成です。ここでは
TestASGGroupと命名します。前回は--ec2-tag-filtersでデプロイ対象のインスタンスを名指しで指定したのに対し、今回は--auto-scaling-groupsで指定します。
service-role-arnには、AWSがデフォルトで提供しているロールCodeDeployServiceRoleを使用します。$ aws deploy create-deployment-group \ --application-name TestASG \ --auto-scaling-groups CodeDeployASG \ --deployment-group-name TestASGGroup \ --deployment-config-name CodeDeployDefault.OneAtATime \ --service-role-arn arn:aws:iam::xxxxxxxxxxxx:role/CodeDeployServiceRole { "deploymentGroupId": "91350391-950f-497b-87c4-95abd8454xxx" }デプロイ実行
いよいよデプロイです。
$ aws deploy create-deployment \ --application-name TestASG \ --deployment-config-name CodeDeployDefault.OneAtATime \ --deployment-group-name TestASGGroup \ --s3-location bucket=testcodedeploy,bundleType=zip,key=testCodeDeploy.zip { "deploymentId": "d-QHW602861" }これで実際に対象のEC2の
/var/www/html下にindex.htmlが置かれているはずです。
先ほどアクセスしたEC2のIPアドレスにブラウザからアクセスしてみましょう。Hello World!が表示されれば成功です。実際にCodeDeployコンソールからデプロイメントを見てみると成功となっているでしょう。
インスタンスの終了とかがあるので時間がかかるかと想像していましたが、デプロイは10秒ほどで完了しました。デプロイ方式の種類
- CodeDeployDefault.AllAtOnce
- 一度に全てのインスタンスへデプロイ。
- CodeDeployDefault.HalfAtATime
- 一度に半分ずつデプロイ。
- CodeDeployDefault.OneAtATime
- 一度に1つずつデプロイ。
詳しくは公式を参考に→https://docs.aws.amazon.com/ja_jp/codedeploy/latest/userguide/deployment-configurations.html
今後試したいこと
- EC2へのデプロイ
- AutoScalingGroupへのデプロイ
- lambdaへのデプロイ
- CodeBuild等との一連の連携
- appspec.ymlのhooksの利用
- 投稿日:2019-12-19T22:03:14+09:00
EC2とロードバランサでwebサービスを公開する
はじめに
学生がAWSからサーバを借りてウェブサービスを公開する時、EC2 + 固定IPで公開する場合が多いかと思います。
ですが、AWSの夏インターンに参加した際にAWSにはELB(Elastic Load Balancing)というサービスがあり、必ずそれを設置していました。
ここでは、EC2単体ではない一歩進んだWebサービス公開の方法を紹介します。ロードバランサ設置のメリット
WEBサーバが1台でもELBをおいた方がいい理由はこちら書かれています
- https://dev.classmethod.jp/cloud/aws/benefit_elb_with_one_ec2/負荷分散目的で設置されるロードバランサですが、次の理由でサーバが1台でもロードバランサを設置した方が良いらしいです。
- インスタンス(仮想マシンのこと)の差し替えが容易
- ELB(ロードバランサのこと)のヘルスチェックで死活監視が可能。別のサービスで通知も可能に
- 無料SSL証明書(ドメイン証明書)が利用できる
- などなど
AWSアカウントの準備
これまでは、クレジットカードが無ければAWSのアカウントを作ることはできませんでした。
でももし、あなたが学生ならばAWS Educateで、クレジットカード不要で無料枠のサービスを使うことができます。EC2インスタンスの準備
- AWSのEC2コンソールを開く
- 「インスタンスの作成」をクリック!
![]()
- 無料枠で使えるインスタンスを選択(ここでは、Amazon Linux 2 AMI (HVM), SSD Volume Type)
![]()
- インスタンスタイプはここではt2.microを選択
![]()
- 次のステップはデフォルトで「次のステップ: ストレージの追加」をクリック
- ステップ4: ストレージの追加 も設定はデフォルトで「次のステップ: タグの追加」をクリック
- 以下のタグを設定したうえで、「次の手順: セキュリティグループの設定」をクリック
キー 値 Name (任意の名前)
- セキュリティグループの設定を以下のようにする(ルールの追加で追加できる) セキュリティーの警告が出るかもしれないが、「起動」を行う
タイプ プロトコル ポート範囲 ソース SSH TCP 22 0.0.0.0/0 HTTP TCP 80 0.0.0.0/0
- 新しいキーペアの作成で、SSHのためのキーペアを作成するを選択し、インスタンスを作成
ロードバランサの設置
- EC2コンソールを開く
- 左側のメニューバーから「ロードバランサー」選択
- 「ロードバランサーの作成」を選択する
- 「Application Load Balancer」の「作成」をクリック
- 名前は何か任意につけ、「リスナー」でHTTP 80番ポートとなっていることを確認。アベイラビリティーゾーン(AZ)では、先ほどのEC2があるところを選択。進むわからない場合はあとでも追加できるのでとりあえずどれかを選択。「セキュリティグループの設定」をクリック
6.「新しいセキュリティグループを作成する」 を選択し、任意のセキュリティーグループ名と、次のように設定し、「次の手順: ルーティングの設定」 をクリック
タイプ プロトコル ポート範囲 ソース HTTP TCP 80 0.0.0.0/0 7.以下のように設定し、残りの設定はデフォルトのままで、「次の手順: ターゲットの登録」をクリック
ターゲットグループ 名前 ターゲットの種類 新しいターゲットグループ 先ほどのセキュリティーグループ名 インスタンス 8.最初作ったEC2インスタンスを選択し、「登録済みに追加」。次の手順に進む
9 「作成」!サーバーアプリケーションのインストール
- 先ほど作ったインスタンスにSSH接続
- 「EC2ダッシュボード」->「接続」にssh接続の方法が書いてあります
- 以下のコマンドでApacheをインストール・再起動
sudo yum -y install httpd sudo service httpd startアクセス
- まずはEC2のパブリックIP(EC2コンソールから確認)にアクセスしてみましょう
- 次にロードバランサのDNS名でアクセスしてみましょう
/var/www/html/が公開ディレクトリです.何か置いてみましょうRoute53を使用してELBにドメインを設定
というのをしたかったのですが、学生用のAWS Educateでの、Route 53で発行できる無料の新規ドメイン名がわからなかったので、やっていません。
終わりに
N番煎じだったり、セキュリティのあたりなど様々な設定をもっとしていかないといけないと思いますが、ひとまず一連の流れはこのようになります。
- 投稿日:2019-12-19T21:35:24+09:00
AWS CLIでAMIリージョン間コピーを行うコマンド
AWS CLIの初期設定は終わっているものとします。
EC2:インスタンスID確認コマンド
まずはAMIを作成するEC2インスタンスを確認。
タグ:Name、値がMyInstanceのインスタンスの情報を取得する場合
aws ec2 describe-instances --filters "Name=tag:Name,Values=MyInstance"インスタンスIDを指定して取得する場合
aws ec2 describe-instances --instance-ids {instance-id}AMI作成コマンド
aws ec2 create-image --instance-id {instanceID} --name {ami-name} --description {description} --reboot{ami-name}は任意のAMI名。
AMI作成時はデータの整合性を保証するために再起動を行うのがデフォルトの動きになっている。--no-rebootを指定すると再起動しないが、データの整合性は保証されない。
コマンド実行すると新しいAMIのIDが返ってくる。AMIにタグを作る
aws ec2 create-tags --resources {ami-id} --tags Key={key-name},Value={key-value}AMIリージョン間コピー
aws ec2 copy-image --source-image-id {ami-id} --source-region {source-region-name} --region {region-name} --name {ami-name}コマンドを実行すると新しいAMIのIDが返ってくる。
AMI一覧確認コマンド
自分で作成したAMIの一覧
aws ec2 describe-images --owners selfAMI削除コマンド
aws ec2 deregister-image --image-id {ami-id}補足
別リージョンのAMIを操作する場合はコマンドに--region {RegionName} を付加しないとエラーとなる(指定しない場合aws configureで設定したデフォルトリージョンとなるため)。
- 投稿日:2019-12-19T19:05:02+09:00
openssl コマンドを使用して AWSのsmtp サーバからメールを送信する
前提条件
- 送信元 From メールアドレスも送信先 To メールアドレスも SES で認証済みであること
メール送信できる IAM ユーザーを作成する
policy{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ses:SendRawEmail", "Resource": "*" } ] }SMTP ユーザ名、パスワードを作成する
SMTP ユーザー名は AWS アクセスキー ID と同じ
SMTP パスワードを IAM シークレットアクセスキーから生成する
以下のコードで変換する(Bash)
export AWS_SECRET_ACCESS_KEY={IAM シークレットアクセスキー} 後、bash実行
#!/usr/bin/env bash # These variables are required to calculate the SMTP password. VERSION='\x02' MESSAGE='SendRawEmail' # Check to see if OpenSSL is installed. If not, exit with errors. if ! [[ -x "$(command -v openssl)" ]]; then echo "Error: OpenSSL isn't installed." >&2 exit 1 # If OpenSSL is installed, check to see that the environment variable has a # length greater than 0. If not, exit with errors. elif [[ -z "${AWS_SECRET_ACCESS_KEY}" ]]; then echo "Error: Couldn't find environment variable AWS_SECRET_ACCESS_KEY." >&2 exit 1 fi # If we made it this far, all of the required elements exist. # Calculate the SMTP password. (echo -en $VERSION; echo -n $MESSAGE \ | openssl dgst -sha256 -hmac $AWS_SECRET_ACCESS_KEY -binary) \ | openssl enc -base64生成されたSMTP パスワードを確認する
AWS アクセスキー ID と生成した SMTP パスワードを base64 エンコードする
AWS アクセスキー ID を base64 エンコード
echo -n "{AWS アクセスキー ID}" | base64SMTP パスワードを base64 エンコード
echo -n "{SMTPパスワード}" | base64
AWS SMTP サーバへの TLS 接続をテスト
openssl s_client -crlf -quiet -starttls smtp -connect email-smtp.us-east-1.amazonaws.com:587接続後、メール送信をテスト
AUTH LOGIN {base64 エンコード後AWS アクセスキー ID} {base64 エンコード後AWS シークレットアクセスキー}
- 235 Authentication successful. と表示後、以下を入力
MAIL FROM: {送信元メールアドレス} RCPT TO: {送信先メールアドレス} DATA from:{送信元メールアドレス} to:{送信先メールアドレス} Subject: Test Subject Test Mail . QUIT
- メールが受信できることを確認する
参考リンク
- 投稿日:2019-12-19T18:46:49+09:00
Lambda関数をそのままAWS Batchに乗せ換える(Python3.6限定)
この記事は アラタナアドベントカレンダー2019 19日目の記事です。
Serverlessでサービスを展開していると、どうしてもRDBを使いたくなったり、どこかでバッチ処理をしたくなったりしますよね。
RDBについては、RDS ProxyがLambdaから使用可能になりそうでいい兆しですね。
バッチ処理についてはかつてからAWS Batchというのが用意されています。
これはECRに用意したDockerを使って処理を実行できる環境です。Queueも用意されているのでかなり自由なバッチ設計ができると思います。
さて、Serverlessで環境を構築している場合、まず選択肢として、AWSBatchよりもLambdaを考えると思います。
StepFunctionやQueue、S3などのイベント、並行処理を駆使して構築する場合もあるでしょう。
ただ、Lambdaの実行制限は現状15分でそれを超える場合は何か手を考えなくてはいけません。
処理を分割する、並行処理を考える等あると思いますが、選択肢としてAWS Batchへの移行も入ってくるかと。
しかし、問題はECR上にDockerを用意しなくてはいけない点です。
AWS Batchを使用する場合、Lambdaで使っているソースコードをわざわざDockerに乗せて、ECRのレポジトリに突っ込まなくてはいけない。
これは非常に面倒臭いです。
そこでServerlessFrameworkを使っている場合限定ですが、いいPluginを見つけました。
※ ただしこちらは不具合が多いので注意。。。
このプラグインはServerlessFrameworkからデプロイする際に、Lambdaで使用するソースをそのままDockerに乗せ、ECRにPushしてくれて、
Batchの設定まで行ってくれる夢のようなPluginです。イベントや環境変数もそのまま使えます(Batchを起動するLambdaが自動で作られ、イベントや環境変数の設定も引き継がれる)。
serverless.ymlをちょっと書き換えるだけで全部構築してくれます。ただ、先の大元のPluginはちょっとバグが多かったので、せめてPython3.6で動くように手を入れたものがこちらになります。
※ 大元をForkしてまだプルリクを出していないのですが、取り込まれたらリンクを修正します。
使い方はGithubのReadmeに詳しく書いてあります。
基本serverless.ymlでiamにbatchを許可するのと、該当のfunctionにbatchの項目を付け足すだけで済むはずです。ぜひ使ってみてください\(^o^)/
Python3.6以外では動く保証はないですが、同じような修正をすればいけると思うので、ForkしてPRしてもらえればいいかと思います。
あらためて、大元のjustinram11さんありがとうございます!ナイスアイデアです!
- 投稿日:2019-12-19T18:38:52+09:00
[Datadog]IntegrationがないツールでもDatadogからアラート通知したい
はじめに
こんにちは、なじむです。
今回はこれまでのCFnの記事からは離れて、Datadog Advent Calendar 2019の12/20(金)分の記事です。DatadogにはたくさんのIntegrationがありますが、たまに無いものもあります。「えーイベントの通知は全部Datadogでやりたい」という時に使えるかもしれない「なんちゃってIntegration」です。今回はコンテナセキュリティツールであるSysdigを使用します。やりたいこと
障害発生時の運用として以下を考えていました。
障害発生時に確認するサイト(ダッシュボード)はDatadogに一本化したい!
いくつかの監視ツールを使用していると「ホスト障害の時はDatadogを見る」、「コンテナ障害の時はSysdigを見る」というように障害の起きた個所で見るべきサイトが異なってしまう場合があります。その運用だと負荷が高くなったり(特に夜間の寝ぼけた時はミスしやすい…)、後でイベントの一覧を見るときに見るべきサイトを間違えたりするため、最初に見るべきサイトはDatadogに統一したいと考えました。
前提
以下の環境を想定しています。
- DatadogでSNS Integrationを有効にしていること
- アラートを検知した場合の通知先(今回はSysdigの通知先)でSNSが使用できること
実装の流れ
通知までの大きな流れとしては以下です(絵を描けば良かった…)
- Sysdigでアラート発生→SNSにアラートを通知
- SNSでDatadogのイベントを作成
- 作成されたイベントを基にDatadogのMonitorをトリガー→メールやチャットに通知
実装方法
今回の肝は「SNSでDatadogのイベントを作成」のため、その方法を書いていきます。やり方は2つ考えました。SNS Integrationを使用した簡易な方法と、Lambdaを使用した柔軟な通知が行う方法です。
- SNS Integrationを使用する方法
- Lamdbdaを使用する方法
SNS Integrationを使用する方法
この方法はとても簡単で、以下の手順を実施すれば実現可能です。
サンプルコード(CloudFormation)
ではいつも通りCloudFormationでSNSを設定していきます。
--- AWSTemplateFormatVersion: 2010-09-09 Description: SNS - post Datadog event from sysdig #------------------------------ # Parameters: Set your argv. #------------------------------ Parameters: DatadogWebhookURL: Type: String Description: Datadog webhook URL (ex. https://app.datadoghq.com/intake/webhook/sns?api_key=xxxxxxxxx) #------------------------------ # Resources: Your resource list #------------------------------ Resources: # SNS Resource ## Topic SNSTopic: Type: AWS::SNS::Topic Properties: DisplayName: sysdig2datadog # ここは任意の名前に変更してください Subscription: - Endpoint: !Ref DatadogWebhookURL Protocol: https TopicName: sysdig2datadog # ここは任意の名前に変更してください ## TopicPolicy SNSTopicPolicy: Type: AWS::SNS::TopicPolicy Properties: PolicyDocument: { "Version": "2008-10-17", "Id": "__default_policy_ID", "Statement": [ { "Sid": "__default_statement_ID", "Effect": "Allow", "Principal": { "AWS": "*" }, "Action": [ "SNS:GetTopicAttributes", "SNS:SetTopicAttributes", "SNS:AddPermission", "SNS:RemovePermission", "SNS:DeleteTopic", "SNS:Subscribe", "SNS:ListSubscriptionsByTopic", "SNS:Publish", "SNS:Receive" ], "Resource": {"Ref": "SNSTopic"}, "Condition": { "StringEquals": { "AWS:SourceOwner": {"Ref": "AWS::AccountId"} } } }, { "Sid": "__console_pub_0", "Effect": "Allow", "Principal": { "AWS": [ "273107874544" ] }, "Action": "SNS:Publish", "Resource": {"Ref": "SNSTopic"} } ] } Topics: - !Ref SNSTopic実行結果
動作確認
実際にSysdigでアラートを検知して、それがDatadogで表示されるかを確認してみましょう。
Lamdbdaを使用する方法
メッセージ内容をより詳細にカスタマイズしたい場合はhttps(webhook)ではなく、Lambdaを使用してカスタマイズしてください。Lambdaを使用すると細かいパラメータ(アラートタイプや件名、メッセージの内容等)が指定できるのがとても良いです。
サンプルコード(lambda)
簡単なコード(python3)としては以下のようになります。APIKEYなどはLambdaの環境変数から引っ張ってきています。
from datadog import initialize, api import os APIKEY = os.environ['APIKey'] APPKEY = os.environ['APPKey'] options = { 'api_key': APIKEY, 'app_key': APPKEY } initialize(**options) def lambda_handler(event, context): title = event['Records'][0]['Sns']['Subject'] text = event['Records'][0]['Sns']['Message'] alert_type = 'error' tags = ['source:sysdig'] api.Event.create(title=title, text=text, alert_type=alert_type, tags=tags)まとめ
「何かあったら先ずはDatadogを見る!」とした場合、Integrationがない時は自分でイベントを作成して通知できるよ、という記事でした。
DatadogはIntegrationが豊富で、機能もどんどん追加されています。そして何といっても安い。Freeプランもあるため、個人で触ってみるのが気軽にできるのも良いですね。
書いた後で、SNSじゃなくてもDatadogのAPIが叩ければ良いので「SNSが使用できること」が前提じゃなくても良いよなぁと思いました。ただ、お手軽に実装する場合はSNS Integrationを使うと楽かなと思います。
何かの参考になれば幸いですノシ
- 投稿日:2019-12-19T18:28:39+09:00
VXLAN を使って、EC2 で Broadcast を試してみる
たまーに、VPC内でブロードキャストを使いたい時があるのですが、対応していないので自力でなんとかする必要があります。
方法の一つとして VXLAN を利用できるので必要最小限のメモを残しておきます。構成
- シンプルに EC2(10.0.0.0/24) を2台用意
- VXLAN を使って Overlay Network(192.168.1.0/24) を構築
- 192.168.1.0/24 でブロードキャストをためす
- OS は Amazon Linux 2
EC2-A
- ENI IP: 10.0.12.137/24
- vxlan interface ip: 192.168.1.1/24
- AZ: us-east1-1c
EC2-B
- ENI IP: 10.0.12.99/24
- vxlan interface ip: 192.168.1.2/24
- AZ: us-east1-1c
設定
本当に必要最小限の設定だけします。
それぞれのEC2で、vxlan interface を作成し、IPをつけます。EC2-A[ec2-user@ip-10-0-12-137 ~]$ sudo ip link add vxlan0 type vxlan id 10 dev eth0 [ec2-user@ip-10-0-12-137 ~]$ sudo ip addr add 192.168.1.1/24 broadcast 192.168.1.255 dev vxlan0 [ec2-user@ip-10-0-12-137 ~]$ sudo ip link set vxlan0 up [ec2-user@ip-10-0-12-137 ~]$ sudo bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 10.0.12.99 [ec2-user@ip-10-0-12-137 ~]$ bridge fdb show 33:33:00:00:00:01 dev eth0 self permanent 01:00:5e:00:00:01 dev eth0 self permanent 33:33:ff:cd:b4:59 dev eth0 self permanent 00:00:00:00:00:00 dev vxlan0 dst 10.0.12.99 self permanentEC2-B[ec2-user@ip-10-0-12-99 ~]$ sudo ip link add vxlan0 type vxlan id 10 dev eth0 [ec2-user@ip-10-0-12-99 ~]$ sudo ip addr add 192.168.1.2/24 broadcast 192.168.1.255 dev vxlan0 [ec2-user@ip-10-0-12-99 ~]$ sudo ip link set vxlan0 up [ec2-user@ip-10-0-12-99 ~]$ sudo bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 10.0.12.137 [ec2-user@ip-10-0-12-99 ~]$ bridge fdb show 33:33:00:00:00:01 dev eth0 self permanent 01:00:5e:00:00:01 dev eth0 self permanent 33:33:ff:5b:d5:2f dev eth0 self permanent 00:00:00:00:00:00 dev vxlan0 dst 10.0.12.137 self permanentboradcast に ping 投げて動作確認しようと思うので "/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts" の値を変更しておきます。ただ、Smurf攻撃の対策のためにも、検証後は設定を戻しておきましょう。
Smurf攻撃
https://ja.wikipedia.org/wiki/Smurf%E6%94%BB%E6%92%83EC2-A[ec2-user@ip-10-0-12-137 ~]$ sudo sh -c 'echo 0 >/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts'EC2-B[ec2-user@ip-10-0-12-99 ~]$ sudo sh -c 'echo 0 >/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts'動作確認
とりあえず、 ふつーに ping でユニキャストの通信確認してみます。
問題なく Overlay Network(192.168.1.0/24) で通信できてます。EC2-B[ec2-user@ip-10-0-12-99 ~]$ ping -b 192.168.1.1 PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data. 64 bytes from 192.168.1.1: icmp_seq=1 ttl=255 time=0.281 ms 64 bytes from 192.168.1.1: icmp_seq=2 ttl=255 time=0.222 ms 64 bytes from 192.168.1.1: icmp_seq=3 ttl=255 time=0.205 ms 64 bytes from 192.168.1.1: icmp_seq=4 ttl=255 time=0.185 ms最後に、本題のブロードキャスト
EC2-B[ec2-user@ip-10-0-12-99 ~]$ ping -b 192.168.1.255 WARNING: pinging broadcast address PING 192.168.1.255 (192.168.1.255) 56(84) bytes of data. 64 bytes from 192.168.1.2: icmp_seq=1 ttl=255 time=0.017 ms 64 bytes from 192.168.1.1: icmp_seq=1 ttl=255 time=0.184 ms (DUP!) 64 bytes from 192.168.1.2: icmp_seq=2 ttl=255 time=0.027 ms 64 bytes from 192.168.1.1: icmp_seq=2 ttl=255 time=0.293 ms (DUP!) 64 bytes from 192.168.1.2: icmp_seq=3 ttl=255 time=0.029 ms 64 bytes from 192.168.1.1: icmp_seq=3 ttl=255 time=0.203 ms (DUP!)お片づけ
/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts" の値を元に戻す
EC2-A[ec2-user@ip-10-0-12-137 ~]$ sudo sh -c 'echo 1 >/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts'EC2-B[ec2-user@ip-10-0-12-99 ~]$ sudo sh -c 'echo 1 >/proc/sys/net/ipv4/icmp_echo_ignore_broadcasts'以上です。
- 投稿日:2019-12-19T18:23:07+09:00
AWS Developer Associate 合格記録
どした
AWS Certified Developer Associate を取得したので
どんなことやったのかなど、いつものように記録します。about me
インフラエンジニアで、開発知識は少ないです。(2019/12時点)
運用、開発者系のAWS資格では、SysOps Administrator Associateのみ保持しています。今まで、ネットワーキング設計やサーバー構築など、いわゆるAWSでいう
ソリューションアーキテクト側の経験を実質1年以上 積んでいます。今回の対象分野で言えば、ElasticBeanstalk, Lambda, CloudFormation等の経験はありますがまだ浅い状態です。
つまり、この記事はあくまでインフラエンジニア目線での学習記録になります。about DVA試験
AWS 認定開発者 – アソシエイト
https://aws.amazon.com/jp/certification/certified-developer-associate/・AWSクラウドの基本的な考え方、主要なサービス、ベストプラクティス、使用方法などの理解
・AWSサービスを利用したアプリケーションの開発、デプロイ、デバッグに関する各サービス知識
が必要です。何をやったのか
試験ガイドとサンプル問題
こちらにAWS公式から「AWS 認定試験に備える」情報が記載されています。
「試験ガイドとサンプル問題」は、はじめに必ず確認ですね。
https://aws.amazon.com/jp/certification/certification-prep/また、サンプル問題で言えば、以下のQiita記事が助かりました。
AWS認定デベロッパー アソシエイト 練習問題を解こう
https://qiita.com/Junpei_Takagi/items/9fa884e35990768c66deサンプル問題に対して根拠などをシンプルに記載されており、学習のヒントを見つけることができました。
サンプルと言えど、答えしかないとなかなかどこから入ればいいか時間がかかったりしますからね。。
※ちなみに模擬試験は、答えもワカラナイ結果、サンプル問題を解いて確認してことで、「いま何が足りないか」を明確にできた気がします。
模擬試験
下調べをした際に、試験を受けた皆さんが各種記事、ブログでよく書かれていたことは
Solution Architectなどの他のAssociate試験を受けたことがある人は、
まず模擬試験を受けてみるべきだと言うことでした。はい、その通りでした。(いわゆる腕試し的な)
結果は50%代で、足りないのはAPIやSQSのオプション機能、そしてDynamoDBの超基本DB系知識だということが判明したので、そこから始められました。書籍
まず、以下の本で基本的なサービスの機能、動作を主に図で覚えました。
現状で足りない基本知識の補完作業です。以前他のAssociate本でも活用しましたが、今回が一番役に立ちました。
SQS, Lambda, DynamoDBで特に助かりました。やはり図があるとスピーディに理解ができますね。AWS認定アソシエイト3資格対策~ソリューションアーキテクト、デベロッパー、SysOpsアドミニストレーター~
https://www.amazon.co.jp/dp/4865941991
※直リンク問題集
こちらは定番で、AWS問題集サイトです。
DVAに関しても少し問題が出ています。
https://aws.koiwaclub.com/問題数も少ないので知識的な意味でも完璧にしてしまいます。
udemy
有名なサイトですが、私が購入したのは以下2点です。
・Ultimate AWS Certified Developer Associate 2020
https://www.udemy.com/course/aws-certified-developer-associate-dva-c01/
・AWS 認定デベロッパー アソシエイト模擬試験問題集
https://www.udemy.com/course/aws-31955/1つ目は、AWSドキュメントの長い文章を読む前に図示された説明をみる事で、頭に入りやすくするためです。
2つ目の問題集は、単純に演習がやりたかったからです。
この問題集は本番よりもレベルが高い問題もあると感じたので、あくまで演習や知識補完と考えていいと思います。
私はProfessionalも受ける予定なので、むしろ今後に使えるかな、と感じました。結果&まとめ
無事合格しました。
手応え的にはギリギリだったかもしれません。今回の全体の感想はこんな感じです。
・学習してきた内容は本当にコアな内容だったから、やってよかった
・概念的な知識は、Solution Architectの学習で十分(CloudWatchなど、開発者も使用するような被るサービスもある)
・アプリケーションデプロイ時の用件に対して、DBやSQS、デプロイ系サービスのどのオプションを使えばいいのか、単純に知識がまだ少し足りない部分があった
・Code系サービス(CodeDeployなど)経験が少ないため、少しでも具体的に聞かれるとわからない事がある
・ECSをもっと色々使おう...
・ElasticBeanstalkって意外と色々ある
・Professionalがもう心配また、最近よくAWS試験を受けていますが試験当日のルーチンは大事だなと強く思いました。
今後は、AWS資格に関しては残りのDevOps Engineer Professionalを取得してから、Specialtyへ進みます。
また、色々できなかったAWSの検証系をどんどんやってみて、公開しようと思います。(ツッコミ、welcome)
- 投稿日:2019-12-19T18:08:41+09:00
NoSQL×PaaSで運用するナレッジベース+WebAPI
これはなに
NoSQLとPaaSでナレッジベース+WebAPIを構築した際のノウハウのまとめです。
文字列タグ指向無向グラフ型ナレッジベースというアーキテクチャを実装する場合に、
Heroku + Redis + FastAPI で構築した例と、
AWS(DynamoDB + Lambda + API Gateway) で構築した例を紹介します。コード部分は全て
Python3.8.0を使用しています。※ 現在AWS編が未完成です。ごめんなさい。
ナレッジベースとは?
ナレッジベースには様々な定義がありますが、この記事では
「知識をコンピュータが読み取り可能な形式で格納したデータベース」を指します。
知識ベースナレッジデータベースKBなどの呼ばれ方もあります。
文字列タグ指向無向グラフ型ナレッジベース
今回例として構築するナレッジベースです。
名前だけでは分かりにくいと思うので、イメージ図を用意しました。
(可視化は未実装のためマインドマップツール coggle で作成)なお、このアーキテクチャは流通しているものではなく、
GraphQLから着想を得て独自に設計したものです。
(サーベイはしていないのでもしかしたら既存かもしれません)「文字列タグ指向」について
このナレッジベースでは文字列(とその集合)データのみを扱い、
全ての文字列をタグとして扱います。上の図の例では、
Webサービス名アカウントIDURL記事タイトル概念プログラミング言語
などの各文字列を1つのタグとして扱っています。仕様上、文字列には空白や改行文字などは含まないものとします。
「無向グラフ型」について
このナレッジベースでは、関係のあるタグを結び付けるようにします。
例えば
フレームワークというタグには
RailsLaravelDjangoFlask
というタグが紐付いているというデータが取得可能で、例えば
QiitaとPythonのどちらのタグも紐付いているのは
https://qiita.com/1ntegrale9/items/94ec4437f763aa623965
というタグ(QiitaのPythonに関する記事URL)、のようにデータを取得できます。上の図では頂点(文字列)がタグ、辺が関係を表しています。
そして無向なので双方参照が可能です。
また包含関係などは考慮しないので、重み付けはありません。参考記事:グラフ理論の基礎 - Qiita
このナレッジベースの役割
「2つの文字列を格納する」という単純な操作の繰り返しだけで、
辞書的な知識体系(集合知)を形成することです。そして爆速で育て上げるためにWebAPIが必要になります。
構築例:Redis + FastAPI + Heroku
手軽に無料で運用したい場合はこちらを採用します。
Heroku の初期設定や Redis の基本操作はこちらで解説しています。
Heroku×Redis×Python で始める NoSQL DB 入門 - QiitaRedis
オンメモリで読み書きが高速なKVSです。永続化にも対応しています。
1つのタグに複数のタグを紐付けたいので、集合型のみを使います。ライブラリのインストール
Python で扱うため、redis-py を利用します。
python3 -m pip install redis hiredishiredis-py は C 実装の高速なパーサのラッパーです。
redis-py 側が hiredis を検知してパーサを切り替えてくれるので入れておきます。Redisに接続
以下のコードで接続の初期化を行います。
Heroku Redis が自動で設定してくれる環境変数REDIS_URLを使います。import redis, os conn = redis.from_url(os.environ['REDIS_URL'], decode_responses=True)デフォルトだと日本語の表示に不具合があるので、
decode_responses=Trueは必須です。全てのタグを取得
keys()を使って取得します。def get_all_tags(): return sorted(conn.keys())タグが一覧で見れると便利なので用意しておきます。
ただ規模が大きくなると負荷が高くなるので注意が必要です。紐付くタグを取得する
smembers(key)使って取得します。def get_related_tags(tag): r.smembers(tag) if r.exists(tag) else []念のため、存在しないタグを指定された場合は空配列を返すようにします。
存在確認にはexists(key)を使います。2つのタグを紐付けて格納する
sadd(key, value)を使って集合型データを格納します。
双方向に紐付けたいので、key-value を入れ替えて2回実行します。def set_relation_tags(tag1, tag2): return conn.pipeline().sadd(t1, t2).sadd(t2, t1).execute()Redis はトランザクションをサポートしており、redis-py の場合は
pipeline()からexecute()までのチェーンにすることで、
トランザクション内での一括実行ができます。また、pipeline メソッドによるアトミックな実行は、個別実行よりも高速のようです。
PythonでRedisを効率的に使う(redis-pyのパフォーマンスをあげるには) - [Dd]enzow(ill)? with DB and PythonFastAPI
FastAPI は Python の Web フレームワークの1つで、
シンプルな WebAPI を少ないコードで実装できるのと、
API ドキュメントを設定なしで自動生成してくれるのが特徴です。Flask Responder Starlette DRF などはオーバースペックで、
Bottle は逆に機能が足りず、FastAPIがちょうどよくハマりました。ライブラリのインストール
python3 -m pip install fastapi uvicorn email-validatorUvicorn は高速なASGIサーバーです。FastAPIの起動に使います。
Gunicorn の typo ではないです。email-validator は入れておかないと起動時に怒られます。何故か。
アプリケーションの初期化
非常にシンプルです。
main.pyfrom fastapi import FastAPI app = FastAPI()引数の
titleとdiscriptionを設定すると、
上記画像のような自動生成される API Doc にタイトルと説明部が反映されます。main.pyapp = FastAPI( title='collective-intelligence', description='文字列タグ指向無向グラフ型ナレッジベース', )また、
docs_urlを指定すると、API Doc のURLを変更できます。
デフォルトでは/docsですが、ルートにしておくのも良いでしょう。main.pyapp = FastAPI(docs_url='/')全てのタグを取得
シンプルに HTTPメソッド(GET) と URL と返り値を書くだけです。
リストか辞書を返り値にすることで JSON レスポンスになります。main.py@app.get('/api') def read_all_tags(): return get_all_tags()この定義が API Doc に自動で反映されます。
更に右上のTry it outからリクエストを実行できます。指定のタグに紐付くタグを取得
タグには記号も含む任意の文字列を想定しており、
クエリストリングでは対応できないため、POST にしておきます。main.py@app.post('/api/pull') def read_related_tags(tag: str): return get_related_tags(tag)引数に指定した
tag: strをリクエストボディから受け付けます。
型アノテーションを付けていますが、これを利用してリクエストのバリデーションを行います。
適合しない場合、422 Validation Errorがレスポンスになります。2つのタグを紐付けて格納する
FastAPI は pydantic という、
型アノテーションを活用するためのライブラリを内包しています。
これを利用して独自の型を定義し、バリデーションに利用します。main.pyfrom pydantic import BaseModel class Tags(BaseModel): tag1: str tag2: str @app.post('/api/push') def create_tags_relationship(tags: Tags): set_tags_relationship(tags.tag1, tags.tag2) return {tag: get_related_tags(tag) for _, tag in tags}定義した型は Schema として API Doc に反映されます。
FastAPI の起動
先ほど紹介した Uvicorn で起動します。
main.py内のappで初期化した場合はmain:appと指定します。
--reloadオプションにより、ファイル変更時にリロードして反映してくれます。$ uvicorn main:app --reload INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) INFO: Started reloader process [57749] INFO: Started server process [57752] INFO: Waiting for application startup. INFO: Application startup complete.
http://127.0.0.1:8000またはhttp://127.0.0.1:8000/docsにアクセスすると、
API Doc が表示されることが確認できると思います。Heroku
Webアプリケーションを手軽にデプロイできるPaaSです。
多くの言語・フレームワークに対応しており、
PostgreSQL や Redis も一定枠まで無料でホスティングしてくれます。最初に以下の手順が必要です。
- アカウント登録 Heroku | Sign up
- カード登録 Account · Billing | Heroku
- アプリ作成 Create New App | Heroku
- Redisアドオン追加 Heroku Redis - Add-ons - Heroku Elements
必要なファイルの用意
以下のファイルが必要になります。
これを GitHub リポジトリに用意します。$ tree . ├── main.py # アプリケーション ├── Procfile # プロセス実行コマンド定義ファイル ├── requirements.txt # 依存ライブラリ定義ファイル └── runtime.txt # Pythonバージョン定義ファイルProcfileweb: uvicorn main:app --host 0.0.0.0 --port $PORTrequirements.txtfastapi email-validator uvicorn redis hiredisruntime.txtpython-3.8.0実際のディレクトリ も参考にしてください。
アプリケーションのデプロイ
Dashboard の Deploy タブからデプロイ作業を行います。
GitHub と連携してリポジトリを紐付け、Manual Deployを実行します。
Automatic deploysも設定しておくと、master への push 時に自動でデプロイしてくれます。build が無事に完了したら、
登録されたプロセスをConfigure Dynosから ON にしておきます。Dashboard 右上の
Open appからデプロイされたアプリケーションを確認できます。構築例:AWS(DynamoDB + Lambda + API Gateway)
執筆中のため公開をお待ちください
スケーラビリティを意識するならこちらを採用します。
柔軟にデータ構造を変えることも可能です。初めての、LambdaとDynamoDBを使ったAPI開発 - Qiita
API Gateway + Lambda + DynamoDB - QiitaAmazon DynamoDB
RDBと同様に1テーブル1プライマリキーが基本です。
プライマリキーはデータを一意に識別するためのキーで、「パーティションキー」または
「パーティションキーとソートキーの複合キー」のことです。
ソートキーを追加することによってパーティションキーのユニーク制限を緩和できます。開始方法 - Amazon DynamoDB | AWS
初めてのサーバーレスアプリケーション開発 ~DynamoDBにテーブルを作成する~ | Developers.IO
無料枠で頑張るためにDynamoDBのキャパシティを理解する - ITと筋トレの二刀流テーブルの設計
パーティションキー:タグ
ソートキー:timestampテーブルの作成
AWS Lambda
初めてのサーバーレスアプリケーション開発 ~LambdaでDynamoDBの値を取得する~ | Developers.IO
GitHub Actionsを使ってAWS Lambdaへ自動デプロイ (詳説+デモ手順付きver) - Qiita2つのタグを紐付けて格納する
Lambda が呼び出されると lambda_handler 関数が実行される
import boto3, time from decimal import Decimal def lambda_handler(event, context): timestamp = Decimal(time.time()) table = boto3.resource('dynamodb').Table('collective-intelligence') with table.batch_writer() as batch: # 複数putする場合はbatch_writerを使うと良い batch.put_item(Item={ 'tag': event['tag1'], 'related_tag': event['tag2'], 'timestamp': timestamp }) batch.put_item(Item={ 'tag': event['tag2'], 'related_tag': event['tag1'], 'timestamp': timestamp }) return {'statusCode': 201}指定のタグに紐付くタグを取得
import boto3 from boto3.dynamodb.conditions import Key def lambda_handler(event, context): table = boto3.resource('dynamodb').Table('collective-intelligence') response = table.query(KeyConditionExpression=Key('tag').eq(event['tag'])) # tag指定で検索 tags = set(item['related_tag'] for item in response['Items']) # set型に格納して重複を削る return {'statusCode': 200, 'body': list(tags)} # JSONレスポンスのためlist型にキャストAmazon API Gateway
WebAPIの作成と管理をしてくれる
初めてのサーバーレスアプリケーション開発 ~API GatewayからLambdaを呼び出す~ | Developers.IO
ゼロから作りながら覚えるAPI Gateway環境構築 | Developers.IO
Amazon API Gateway チュートリアル - Amazon API Gatewayリソースとメソッドの作成
/push と /pull で POST を作成
リクエストの検証を設定
Lambdaを実行する前に弾けるとコストが下がってよい
- モデル(JSON Schema)の定義
- 設定->リクエストの検証に「本文の検証」を設定
- リクエスト本文にモデルを設定
JSON Schema Tool
リクエストおよびレスポンスマッピングのモデルおよびマッピングテンプレートを作成する - Amazon API Gateway
APIGatewayの新機能Request Validationを試してみた - エムティーアイ エンジニアブログメソッドの作成
メソッドの選択
メソッド管理画面
PULLモデルの作成
PUSHモデルの作成
リクエストの検証を設定
PULL API のテスト
PUSH API のテスト
利用料金に関して
Billing 画面の請求書から確認します。
まだ本稼働しているわけではないですが、
テストで数百件のリクエスト/レスポンスの送受信を行った結果0円だったので、
お試しで使う分には恐れることはなさそうです。GCP vs AWS
GCP(Firestore)とAWS(DynamoDB)で悩みましたがDynamoDBを採用しました。
GCP側で選択する場合、4つのデータストアサービスから悩むことになると思いますが、
趣味で使うならFirestore一択だと思います。
データベースを選択: Cloud Firestore または Realtime Database | Firebase終わりに
これらはほぼ独学で得た内容ですが、
新しい技術を習得するスキルは会社のモダンな環境で得られたものだと思います。
強いエンジニアが新技術をガンガン使っていく現場で働けるというのは最高の体験です。また、Heroku側の構成のものを公開しています。
公開時点でデータは空ですが、自由に触ってみてください。
https://collective-intelligence.herokuapp.com/
- 投稿日:2019-12-19T17:19:44+09:00
AWS初心者がFirehoseを使ってみたので感想
Fringe81アドベントカレンダー2019の20日目です。
tuaaasa a.k.a ちゃりがお送りします。始めに述べておきますが、私は半年ほど前からAWSを使い始めた初心者です。
そんな私が先日業務内で、
AWS Kinesisの機能の一つであるKinesis Data Firehoseに触れました。
今回はその感想をまとめます。
CloudFormationを用いてデプロイしたので、合わせてそちらも話します。Kiensis
https://aws.amazon.com/jp/kinesis/
より以下抜粋ストリーミングデータをリアルタイムで収集、処理、分析することが簡単になる
また、完全マネージドサービスなので、ストリームデータをKinesisに任せちゃえばいい感じにやってくれます。
本題のFirehose
https://aws.amazon.com/jp/kinesis/data-firehose/
より以下抜粋今回私は
Redshiftにロードしました。嬉しいこと
完全マネージドサービスのおかげで、難しいことを考える必要がなかった
データロードの重複防止とかエラー時のリトライとか、煩わしいことをFirehoseがやってくれる点非常に助かりますね。
また、エラーログも残してくれるのもうれしいです。つらいこと
Redshiftへのパブリックアクセスを可能にしてあげなければいけない
Amazon Redshift クラスターへの VPC アクセス
これですね。
不正データが1件でも入ってくると直近の正常なデータも全て欠損してしまう
FirehoseでRedshiftにロードする場合、
- 一旦S3にアップロード
manifestという複数のデータを一つにまとめたデータの集合を作成manifestをCOPYコマンドでロードという形をとっています。
これのおがげで不正データが含まれるmanifestはエラー対象となりロードされないのですね。対処法
1回COPYコマンドあたり、1万件まではエラー処理させないオプションを追加することで正常なデータのみロードさせました。
ただ、1万1件目はエラーとなってしまうのと、
不正データの内容がFirehoseでロギングできないことには注意が必要です。おまけ
CloudFormationで設定できない項目がある
RedshiftにロードするためのS3アップロードとは別に、
ストリームデータのバックアップができる。現在()この機能をCloudFormationでは設定できない模様。(以下2019年10月15日に返答されたサポートメールの内容)
おわり
実際に稼働していないので、実績ができたらまた報告しようかなと思います。
ありがとうございました。参考
CloudWatch LogsのログをprivateなVPCにあるRedshiftに投入
AWS Kinesis Firehoseでログ欠損しないために
- 投稿日:2019-12-19T17:09:08+09:00
AWSの起動 templateをCLIで更新
サーバの実行環境はDockerコンテナで管理するが当たり前になった昨今ですが、数年前にサービスインして保守フェーズの案件ではEC2インスタンスで直接サービスを動かす構成は、まだまだ、現役です。
今回は、オートスケーリンググループで起動させるEC2インスタンスを起動テンプレートで管理しており、起動テンプレートのバージョンを上げて、対象のAMIを新しいものに変更する作業を AWS CLIで行います。
使用する環境変数名
今回は AMI と EBSのスナップショットIDを新しくします。
環境変数名 値の意味 TEMPLATE_NAME 更新したいテンプレート名 TEMPLATE_ID TEMPLATE_NAME の launch-template-id PREV_TEMPLATE_VER 作業時点でのデフォルトバージョン TEMPLATE_DESC 起動テンプレートの新しいバージョンの説明 AMI_ID 起動テンプレートの新しいバージョンが利用する AMI の ID EBS_SNAPSHOT_ID 起動テンプレートの新しいバージョンが利用する EBSのスナップショットID 作業コマンド
# 起動テンプレートのIDを取得 $ export TEMPLATE_NAME="my_template" $ export TEMPLATE_ID=$( aws ec2 describe-launch-templates--launch-template-names ${TEMPLATE_NAME} | \ jq -r '.LaunchTemplates[].LaunchTemplateId' \ ) $ echo $TEMPLATE_ID lt-1234567890abcdef # 起動テンプレートのデフォルトバージョンを取得 $ export PREV_TEMPLATE_VER=$( aws ec2 describe-launch-templates --launch-template-names ${TEMPLATE_NAME} | \ jq -r '.LaunchTemplates[].DefaultVersionNumber' \ ) $ echo $PREV_TEMPLATE_VER 7 # 今の起動テンプレートをファイルに保存しておく $ aws ec2 describe-launch-template-versions \ --launch-template-id ${TEMPLATE_ID} \ --versions ${PREV_TEMPLATE_VER} \ > launch-template-befor.json # AMIを指定して起動テンプレートの新しいバージョンを作成する export NEW_TEMPLATE_JSON=$(aws ec2 create-launch-template-version \ --launch-template-id ${TEMPLATE_ID} \ --version-description "${TEMPLATE_DESC}" \ --source-version ${PREV_TEMPLATE_VER} \ --launch-template-data '{"BlockDeviceMappings":[{"DeviceName": "/dev/xvda","Ebs":{"DeleteOnTermination": true,"SnapshotId":"'${EBS_SNAPSHOT_ID}'","VolumeSize": 30,"VolumeType": "gp2"}}],"ImageId":"'${AMI_ID}'"}') # 新しいバージョン番号を取得 export NEW_TEMPLATE_VER=$(echo ${NEW_TEMPLATE_JSON} | jq -r '.LaunchTemplateVersion.VersionNumber') # 新しいバージョンをファイルに保存しておく $ aws ec2 describe-launch-template-versions --launch-template-id ${TEMPLATE_ID} --versions ${NEW_TEMPLATE_VER} > launch-template-after.json
aws ec2 create-launch-template-versionの引数--launch-template-dataの値は 公式ドキュメント を参照してください。差分確認
作業前後で
aws ec2 describe-launch-template-versionsの結果を比較して、想定した変更差分であることを確認します。$ diff launch-template-befor.json launch-template-after.json 6,8c6,8 < "VersionNumber": 7, < "VersionDescription": "change instance to t2.medium", < "CreateTime": "2018-12-14T04:19:43.000Z", --- > "VersionNumber": 10, > "VersionDescription": "update app. ver 1.2.3", > "CreateTime": "2019-12-19T06:23:15.000Z", 10c10 < "DefaultVersion": true, --- > "DefaultVersion": false, 21c21 < "SnapshotId": "snap-01234567890123456", --- > "SnapshotId": "snap-abcdefabcdefabcde", 40c40 < "ImageId": "ami-01234567890123456", --- > "ImageId": "ami-abcdefabcdefabcde",
- 投稿日:2019-12-19T15:54:47+09:00
Lambda@Edgeについてまとめる
はじめに
Lambda@Edgeを初めて使い、色々戸惑うことが多かったため調べたことをまとめる。
また、都度追加していく。Lambda@Edgeとは
Lambda@EdgeとはCloudFrontの機能の1つで、CloudFrontのエッジサーバでコードを実行するLambda関数のこと。ユーザーに近い場所でコードが実行されることでアプリケーションのパフォーマンスが向上する。
CloudFrontのイベントをトリガーとしてコードを実行する。トリガー
上述の通り、CloudFrontのイベントをトリガーとし、イベントがあったCloudFrontと同リージョンで起動する。指定できるCloudFrontのイベントは以下の4つ。
1.ビューワーリクエスト
2.ビューワーレスポンス
3.オリジンリクエスト
4.オリジンレスポンス
Lambda@Edgeのトリガーをビューワーリクエストに設定すると、CloudFrontがキャッシュを保有しているかに関わらずビューワーからリクエストがある度にLambdaが起動する。キャッシュがある場合に起動させたくない場合はトリガーをオリジンリクエストに設定する。ログ
Lambda@EdgeのログはLambda同様CloudWatchLogsに保存されるが、Lambda関数が起動したエッジサーバがあるリージョンのCloudWatchLogsに保存される。
Lambda@Edgeの作成する際の注意
リージョン
Lambda@Edgeはバージニア北部リージョンでLambda関数を作成する。
作成方法はLambda関数と同様
恥ずかしい話ですが、Lmadba@Edgeを使用する前、Lambda@Edgeというサービスがあるものだと思っていたんですが、Lambda関数をバージニア北部リージョンで作成し、CloudFrontのイベントをトリガーとすることでLambda@Edgeになるということみたい。
IAMロール
Lambda@EdgeにアタッチするIAMロールは、サービスプリンシパル
lambda.amazonaws.comとedgelambda.amazonaws.comが引き受けることができるロールである必要があるため、以下の信頼ポリシーを記述したIAMロールを作成する。ロールの信頼ポリシー{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "lambda.amazonaws.com", "edgelambda.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] }IAMポリシーを作成
IAMポリシー{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "iam:CreateServiceLinkedRole" ], "Resource": "*" }, { "Effect": "Allow", "Action": [ "lambda:GetFunction", "lambda:EnableReplication" ], "Resource": "arn:aws:lambda:us-east-1:xxxxxxxxxxxx:function:<関数名>:<バージョン>" }, { "Effect": "Allow", "Action": [ "cloudfront:UpdateDistribution" ], "Resource":"arn:aws:cloudfront::xxxxxxxxxxxx:distribution/<ディストリビューションID>" } ] }関数の編集後はCloudFrontにデプロイする
関数を編集して保存後CloudFrontにデプロイして、編集内容が各エッジサーバに反映される。
ちなみに、Lambda関数はバージョン管理しておりデプロイする際に自動でバージョンアップされ、それがデプロイされる。参考
- 投稿日:2019-12-19T14:49:54+09:00
【執筆中】Aurora Data API を TypeScript + typeorm から 使う物語
リンク先として先んじて公開した、執筆中の記事です
概要
特定のツイートのリツイート情報をクローリングするツールを、以下の構成で実装した。
その過程で踏み抜いた、5000兆個くらいある落とし穴の倒し方を書き残す。
踏みすぎて疲れた。
- AWS
- Aurora Serverless (MySQL5.6)
- ServerlessFramework
- Lambda
- Node.js
- TypeScript
- typeorm
前提と関連記事
「Aurora Serverless DB を作って Node.js(TS) から使う」 を前提としています。
「Aurora Serverless MySQL(5.6) で日本語データを扱えるようにする」 も設定しています。typeormとは
TypeScript の class として Entity を定義すると、
自分でSQL書かなくても一通りなんでもできる OR Mapper だよ。
べんりだね!環境
Aurora エンジンAurora (MySQL)-5.6.10aAWS SDK2.590.0Node.jsv10.15.0npm6.6.0typeorm0.2.21typeorm-aurora-data-api-driver1.1.8- Serverless Framework
↓で作った serverless.yml
serverless create --template aws-nodejs-typescriptpackage.json
- 投稿日:2019-12-19T14:49:54+09:00
Aurora Data API を TypeScript + typeorm から 使う物語
概要
Rest API を経由してツイッターのリツイート情報を取得し、
Aurora DB に格納するサンプルツールtwittererを、以下の構成で実装した。その過程で 5000兆個くらいある落とし穴を踏み抜いたので倒し方を書こうと思ったが、
1日経ったら過程をほとんど忘れたのでちゃんと動く結果を主に書き残す。前提と関連記事
「Aurora Serverless DB を作って Node.js(TS) から使う」 を前提としています。
「Aurora Serverless MySQL(5.6) で日本語データを扱えるようにする」 も設定しています。typeormとは
TypeScript の class として Entity を定義すると、
自分でSQL書かなくても一通りなんでもできる OR Mapper だよ。TypeORMはNode.js開発のスタンダードになるか?
こちらの紹介記事がわかりやすいと思いました。
べんりだね!環境
Aurora エンジンAurora (MySQL)-5.6.10aAWS SDK2.590.0Node.jsv10.15.0npm6.6.0typeorm0.2.21typeorm-aurora-data-api-driver1.1.8Serverless Frameworkプロジェクトの構成と解説
主要な構成
twitterer/ ├── serverless.yml ├── ormconfig.js // typeormの設定ファイル ├── package.json ├── webpack.config.js | ├── twitterer-handler.ts └── src/ ├── twitterer-express.ts ├── twitterer-service.ts ├── entities/ | └── twitterer-types.ts ├── helpers/ | └── typeorm-helper.ts └── db/ // typeormにより出力されたスクリプトが蓄積される ├── migrations/ └── subscribers/serverless.yml
基本的には以下でしたときのまま。
serverless create --template aws-nodejs-typescriptポリシーの設定
Lambda Role から Data API を使うために追加したポリシー。
必要なポリシーセットがわからず苦労していたserverless.ymlprovider: iamRoleStatements: - Effect: "Allow" Action: - "secretsmanager:GetSecretValue" - "secretsmanager:PutResourcePolicy" - "secretsmanager:PutSecretValue" - "secretsmanager:DeleteSecret" - "secretsmanager:DescribeSecret" - "secretsmanager:TagResource" Resource: "arn:aws:secretsmanager:*:*:secret:*" - Effect: "Allow" Action: - "dbqms:CreateFavoriteQuery" - "dbqms:DescribeFavoriteQueries" - "dbqms:UpdateFavoriteQuery" - "dbqms:DeleteFavoriteQueries" - "dbqms:GetQueryString" - "dbqms:CreateQueryHistory" - "dbqms:DescribeQueryHistory" - "dbqms:UpdateQueryHistory" - "dbqms:DeleteQueryHistory" - "rds-data:ExecuteSql" - "rds-data:ExecuteStatement" - "rds-data:BatchExecuteStatement" - "rds-data:BeginTransaction" - "rds-data:CommitTransaction" - "rds-data:RollbackTransaction" - "secretsmanager:CreateSecret" - "secretsmanager:ListSecrets" - "secretsmanager:GetRandomPassword" - "tag:GetResources" Resource: "arn:aws:rds:ap-northeast-1:XXXXXXXXXXXX:cluster:XXXXXXXXXXXX"webpack
そのまんまだと、なぜか
typeorm-aurora-data-api-driverがpackされなくてハマったので追記custom: webpack: webpackConfig: ./webpack.config.js includeModules: packagePath: package.json forceInclude: - typeorm-aurora-data-api-driverhandler
express で受けるので、以下のように書く
serverless.ymlfunctions: twitterer: handler: twitterer-handler.v1 events: - http: ANY / - http: "ANY /{proxy+}"package.json
scripts
package.json"scripts": { "debug": "$(npm bin)/ts-node-dev --clear --respawn ./twitterer-handler.ts", "migration:generate": "ts-node $(npm bin)/typeorm migration:generate -n migration", "migration:run": "ts-node $(npm bin)/typeorm migration:run " },dependencies
package.json"dependencies": { "aws-sdk": "^2.590.0", "body-parser": "^1.19.0", // expressで意図したrequest bodyを受け取るため "cors": "^2.8.5", // web viewから蹴ることも想定して "express": "^4.17.1", "serverless-http": "^2.3.0", "source-map-support": "^0.5.10", "twitter": "^1.7.1", "typeorm": "^0.2.21", "typeorm-aurora-data-api-driver": "^1.1.8" // typeormからData APIを使える }, "devDependencies": { "@types/aws-lambda": "^8.10.17", "@types/express": "^4.17.2", "@types/node": "^10.12.18", "@types/twitter": "^1.7.0", "@typescript-eslint/eslint-plugin": "^2.11.0", "@typescript-eslint/parser": "^2.11.0", "copy-webpack-plugin": "^5.1.1", // ormconfig.js をpackするために使う "eslint": "^6.7.2", "eslint-config-prettier": "^6.7.0", "eslint-plugin-prettier": "^3.1.1", "fork-ts-checker-webpack-plugin": "^3.0.1", "prettier": "^1.19.1", "serverless-webpack": "^5.2.0", "ts-loader": "^5.3.3", "ts-node-dev": "^1.0.0-pre.44", "typescript": "^3.2.4", "webpack": "^4.29.0", "webpack-node-externals": "^1.7.2" }webpack.config.js
そのままだと、
ormconfig.jsがpackされなくてハマったので、以下のように追記webpack.config.js// import 部分 const CopyWebpackPlugin = require("copy-webpack-plugin"); // plugins 部分 plugins: [ new CopyWebpackPlugin(["ormconfig.js"]), ],ormconfig.js
ormconfig.jsmodule.exports = { type: "aurora-data-api", region: "ap-northeast-1", // Aurora Serverless DB の arn resourceArn: "arn:aws:rds:ap-northeast-1:XXXXXXXXXXXX:cluster:XXXXXXXXXXXX", // DB にアクセスするために作った Secret の arn secretArn: "arn:aws:secretsmanager:ap-northeast-1:XXXXXXXXXXXX:secret:XXXXXXXXXXXX", // デフォルトでつなぐDB(schema) database: "twitterer", entities: [__dirname + "/src/entities/**/*.ts"], migrations: [__dirname + "/src/db/migrations/**/*.ts"], subscribers: [__dirname + "/src/db/subscribers/**/*.ts"], cli: { entitiesDir: "src/entities/", migrationsDir: "src/db/migrations/", subscribersDir: "src/db/subscribers/", }, };twitterer-handler.ts
twitterer-handler.tsimport "source-map-support/register"; import { TwittererExpress } from "./src/twitterer-express"; const serverless = require("serverless-http"); export const v1 = serverless(TwittererExpress);twitterer-express.ts
公開用に加工してます、エラーハンドリングとか適当なので許してちょ。
twitterer-express.tsimport bodyParser from "body-parser"; import cors from "cors"; import express, { Request, Router } from "express"; import { NextFunction, Response } from "express-serve-static-core"; import { RetweetEntity } from "./entities/twitterer-types"; import { TwittererService } from "./twitterer-service"; /** * initialize */ const { app, r } = (() => { TwittererService.init().then(); const app = express(); const r: Router = Router(); app.use(cors()); app.use(bodyParser.json()); app.use(r); // ローカル実行用 app.listen("8080", () => console.log(`Start listening on port 8080`)); return { app, r }; })(); export const TwittererExpress = app; /** * define routes */ const P = "/twitterer/v1"; r.post(`${P}/tweets/:tweetId/retweets/clawl`, clawlRetweet); r.get(`${P}/tweets/:tweetId/retweets`, getRetweets); /*****************************************************************/ async function clawlRetweet(req: Request, res: Response, next: NextFunction) { const { tweetId } = req.params; try { const retweets = await TwittererService.clawlRetweets(tweetId); res.status(200).send(retweets); next(); } catch (e) { console.error(e); res.status(424 /* failed dependency */).send(JSON.stringify(e)); } } async function getRetweets(req: Request, res: Response, next: NextFunction) { const { tweetId } = req.params; const retweets = await RetweetEntity.find({ where: { tweetId } }); const resRetweets = retweets.map(e => ({ retweetId: e.retweetId, userScreenName: e.userScreenName, userName: e.userName, })); res.status(200).send({ count: retweets.length, retweets: resRetweets, }); next(); }twitterer-service.ts
createConnection() で利用するすべての entities を指定しないと、
ローカルでは動いてもデプロイ後動かなくなるtwitterer-service.tsimport Twitter from "twitter"; import { BaseEntity, Connection, createConnection, getConnection, getConnectionOptions } from "typeorm"; import { TypeormHelper } from "./typeorm-helper"; import { RetweetEntity } from "./entities/twitterer-types"; export class TwittererService { /** * init aurora connnection */ static async init() { // 後述の TypeormHelper.patchBug(Connection); const connectionOptions = await getConnectionOptions(); const conn = await createConnection({ ...connectionOptions, // 利用するEntityをこうして書かないと、デプロイ後動かない。 // (entityがrepositoryに見つかりませんよ、みたいなエラー出る) entities: [RetweetEntity], }); BaseEntity.useConnection(conn); } static get client(): Twitter { return new Twitter({ consumer_key: "XXXXXXXXXX", consumer_secret: "XXXXXXXXXX", access_token_key: "XXXXXXXXXX", access_token_secret: "XXXXXXXXXX", }); } static async clawlRetweets(tweetId: string): Promise<RetweetEntity[]> { const clawledAt = new Date(); // 本当は保持していたカーソルの読み込みとかいろいろやってる const twitterRes = await this.client.get(`statuses/retweets/${tweetId}.json`, {}); const retweets: RetweetEntity[] = []; for (const e of twitterRes as any) { const retweet = new RetweetEntity(); retweet.tweetId = e.retweeted_status.id_str; retweet.retweetId = e.id_str; retweet.userId = e.user.id_str; retweet.userName = e.user.name; retweet.userScreenName = e.user.screen_name; retweet.retweetedAt = new Date(e.created_at); retweet.clawledAt = clawledAt; retweet.rawJson = e; retweets.push(retweet); } await getConnection().transaction(async e => { await e.save(retweets); // 本当はカーソルの更新とかいろいろやってる }); return retweets; } }typeorm-helper.ts
何故かデプロイ後動かない というエラーに悩まされた結果たどり着いたソリューション。
めっっっっちゃここでハマった。二度とハマりたくない
patch-packageを使おうとしたけどwebpackとの組み合わせに難航したのでモンキーパッチtypeorm-helper.tsimport { EntityMetadata, EntitySchema } from "typeorm"; export class TypeormHelper { /** * デプロイすると動かなくなる糞バグのモンキーパッチ * https://github.com/typeorm/typeorm/issues/3427 */ static patchBug(typeormConnection: any) { // this is a copypasta of the existing typeorm Connection method // with one line changed // @ts-ignore typeormConnection.prototype.findMetadata = function( target: Function | EntitySchema<any> | string ): EntityMetadata | undefined { // @ts-ignore return this.entityMetadatas.find(metadata => { // @ts-ignore if (metadata.target.name === target.name) { // in latest typeorm it is metadata.target === target return true; } if (target instanceof EntitySchema) { return metadata.name === target.options.name; } if (typeof target === "string") { if (target.indexOf(".") !== -1) { return metadata.tablePath === target; } else { return metadata.name === target || metadata.tableName === target; } } return false; }); }; } }twitterer-types.ts
typeormではlength指定なしの文字列カラムは varchar(255) となる。
こちらの記事でも触れたように、このままではキーカラム767bytes制限に阻まれて使うことができない。対策には、
- キーカラムの長さを短くするか、
- 上記記事の内容と合わせて テーブルに
ROW_FORMAT=DYNAMICを指定する必要がある。前者はめんどかったので後者で実現しようとしたが、typeorm には ROW_FORMAT を指定できない、なんてことだ
ということでSQLインジェクションをつかって無理やり解決twitterer-types.tsimport { BaseEntity, Column, Entity, PrimaryColumn } from "typeorm"; // typeormには ROW_FORMAT の指定オプションが無いため、 SQL インジェクションを使うクソリューション @Entity({ name: "retweet", engine: "InnoDB ROW_FORMAT=DYNAMIC" }) export class RetweetEntity extends BaseEntity { @PrimaryColumn() tweetId: string; @PrimaryColumn() retweetId: string; @Column() userId: string; @Column() userName: string; @Column() userScreenName: string; @Column() retweetedAt: Date; @Column() clawledAt: Date; // AuroraServerless は MySQL5.6 しか使えないため、実際はTEXT型になる // (JSON は MySQL5.7から) @Column("simple-json") rawJson: any; }マイグレーションSQLの生成と実行
npm run migration:generate npm run migration:rungenerateでできるスクリプト
現状のDBの状態とEntityの定義を比較し、差分を埋めるために必要なSQLを作ってくれる。
テーブルのない状態で実行するとCREATE文が生成される
あとはこれをgitで管理して環境ごとに適用したりして便利につかうわけですねさっきSQLインジェクションした
ROW_FORMAT=DYNAMICもしっかり入ってるねimport {MigrationInterface, QueryRunner} from "typeorm"; export class migration1576639222255 implements MigrationInterface { name = 'migration1576639222255' public async up(queryRunner: QueryRunner): Promise<any> { await queryRunner.query("CREATE TABLE `retweet` (`tweetId` varchar(255) NOT NULL, `retweetId` varchar(255) NOT NULL, `userId` varchar(255) NOT NULL, `userName` varchar(255) NOT NULL, `userScreenName` varchar(255) NOT NULL, `retweetedAt` datetime NOT NULL, `clawledAt` datetime NOT NULL, `rawJson` text NOT NULL, PRIMARY KEY (`tweetId`, `retweetId`)) ENGINE=InnoDB ROW_FORMAT=DYNAMIC", undefined); } public async down(queryRunner: QueryRunner): Promise<any> { await queryRunner.query("DROP TABLE `retweet`", undefined); } }ローカルで実行してみる
npm run debug curl -X POST http://localhost:8080/twitterer/v1/tweets/<TWEET_ID>/clawl curl -X GET http://localhost:8080/twitterer/v1/tweets/<TWEET_ID>デプロイして確かめてみる
sls deploy curl -X GET https://XXXXXXXX/twitterer/v1/tweets/<TWEET_ID>まとめ
正直落とし穴踏みすぎて、全部網羅できたか覚えてない。
もし問題あれば教えて下さい。
普段はFirestoreとか使ってるんだけど
- 小規模ツールでいちいちfirebase プロジェクト増やすのめんどいな
- やっぱSQL使いたいときもあるよね
ってことで取り組んでみました。
typeorm使うと、自分でSQL書かなくていい!ちょう楽ちん!!
このテンプレートをつかって、今後の開発が爆速になりそう。
- 投稿日:2019-12-19T14:46:46+09:00
Datadogを全社的に導入した時にやったこと
本記事は、VISITS Advent Calendar 2019の21日目の記事です。
VISITS Technologiesでインフラエンジニアをしている@syogunです。
本日は、全社的にDatadogを導入したので、何をやったのか、何がよくなったのか等を振り返りたいと思います。
これから、Datadogを導入しようと考えている方に少しでも参考になればと考えています。
それでは参りましょう。なぜ導入したのか?
私が使いたかったからです!本気です。
弊社エンジニア @ham さんの スタートアップに転職して思ったこと にもありましたが、
スタートアップ企業は、技術選定の自由度が高い印象です。(言うまでもなく個人的な見解です)
もちろん同僚や上司を納得させ、最後までケツを持つ覚悟が必要だと思います。具体的な理由としては、Datadogだけでかなりの領域の監視がカバーできるという点です。
インフラレイヤー以外にも外形監視、APM、ログ監視等の機能もあり、何かあった時は基本的にDatadog上で調査できる点は非常に魅力的でした。やったこと
ここからは私が導入時にやったことを振り返っていきます。
Integration
弊社ではAWSやGCPといったクラウドサービス上でシステムが動いています。
AWSしか触ったことのないサーバーエンジニアがGCPで本番運用してみた(前編)
AWSしか触ったことのないサーバーエンジニアがGCPで本番運用してみた(後編)Datadogでは、AWSやGCPのインテグレーション設定を行うだけで、かなりのメトリクスが取得できます。
今のところすべてのインテグレーションは無料で、種類も豊富なのでおすすめです。インテグレーション
Datadog インテグレーション一覧(めっちゃあるやん..!)AWS,GCPのインテグレーション方法に関しては、以下の記事をご参照ください。
Datadog AWS インテグレーション手順
Datadog GCP インテグレーション手順Multi Organization
Datadogには、Multi Organization機能があります。
弊社では、ideagramやdesign thinking testといった複数プロダクトがあるんですが、各プロダクトごとにDatadogのOrganizationを作成して運用しています。
そうすることで、各プロダクトのエンジニアは、自身が開発するOrganizationに参加すればいい状態を作っています。
Organization切り替え時に再度認証(私はGoogle認証)が必要なところは若干手間です。改善に期待。Synthetics(外形監視)
Syntheticsという外形監視機能があります。弊社ではこのSyntheticsの実行結果をSLOの目標値として使用しています。
SyntheticsにはAPIテストとブラウザテストの2種類があります。APIテスト
これはシンプルな外形監視です。
監視対象のURL、ロケーション、コンディション(responstimeやstatus code等を記載)を設定するだけです。
コンディション設定は以下のようなイメージです。
実行結果は以下のようなイメージです。
DNSの名前解決時間やTTFBなどの情報が確認できます。
ブラウザテスト
ブラウザテストは、ブラウザテストしたいサービスの画面遷移をレコーディングするだけでセットアップできます。
また、ユーザIDやパスワードといったアカウント情報もDatadog上に保存可能で、保存したアカウント情報をブラウザテストが使用してログインするといったことも可能です。
なお、ブラウザテストはAPIテストと比較してお値段が少々お高いので、実行回数にはお気をつけください。
料金はこちらAPM
APMを導入することで、インフラのメトリクスやログだけだと分からなかったアプリケーションレイヤーのボトルネックを特定するのに役立っています。
弊社CTO @mayah のアドベントカレンダーでもご紹介したのですが、Kaizen Weekと称して1週間を技術的負債を集中的に取り除く活動を定期的に開催しています。
そのKaizen Weekで弊社エンジニア @kshibata101 さんがDatadog APMを活用したパフォーマンスチューニングを行い、Best KAIZEN Awardを受賞しました。
また、APMはアプリケーションのエラーメッセージも確認できるので、トラブルシューティング時にとても役立ちます。
APMの導入方法に関しては、以下の記事をご参照ください。
Instrument your applicationまた、APMに関しては私を含め弊社メンバーがQiitaに記事を投稿してます。お時間ありましたら、そちらも併せてご参照ください。
GoのアプリケーションにDatadogAPMを導入する。
GAEにDatadog APMを導入するInfrastructure
イングレーションのところでもご紹介しましたが、ALBやRDSといったメトリクスがモリモリ取得できます。
こちらのメトリクスを基にアラートが柔軟に設定可能です。Containers
一部環境ではAWS FargateやGKEを導入しているんですが、コンテナの監視に強いのもDatadogの魅力です。
GKEの場合は、DeaemonSetでDatadogをデプロイ、Fargateの場合はSideCar方式でデプロイすればそれぞれコンテナのメトリクスが取得できます。
それぞれの導入方法に関しては、以下の記事をご参照ください。
How to monitor Google Kubernetes Engine with Datadog
Monitor AWS Fargate applications with DatadogServerless
Lambda等のメトリクスもインテグレーションを有効化するだけで自動で取得します。
インテグレーション設定1つでメトリクスが取得できるのは魅力ですね。
ContainerやServerlessの料金は、こちらMonitors
Managed Monitors
アラートはMonitorsから設定可能で、あらゆる情報(メトリクス、ログ、イベント等)からアラートの設定できます。
従来のシンプルな閾値監視(例えばCPU使用率90%以上とか)のみでなく、
Anomaly DetectionやRorecast Alertといった機械学習を使用した閾値も設定できることも魅力です。
閾値を固定化しづらいシチュエーションや、いつもと違う動きを検知したいといったニーズを満たせそうです。
このあたりは、
Anomaly Detection
Outliers Alert
Forecast Alert
に詳しく書いてありますので、よかったらご参照ください。
今後こういった技術がどんどんブラッシュアップされ、人間がアラート設定しなくなる未来が私には見えました。SLO
Datadogを導入するにあたって、各プロダクトごとにSLI/SLOを決めて、それに沿った監視を行うことにしました。
SLI/SLOに関しては、本題から少し逸れるため詳しくは記載しませんが、Syntheticsによる5分毎のAPI Testが以下の条件を満たすこと ・レスポンスタイム:Response Timeがxxx msで完了すること ・ステータスコード:200を返すこと ・上記条件を正常とみなし、可用率が99.9%以上となること。といった感じで各プロダクトのエンジニアとPMと相談しながら決めました。(このあたりはまだ手探りです)
DatadogにはSLO機能があり、SLOの達成状況を可視化できるため、活用しています。
何をSLIにするかを設定し、目標とする稼働率(SLO)やSLO対象期間を設定することで、SLO達成状況が可視化できます。
また、SLOに基づいたError Budgetも出るので、リリース計画に参考にもできそうです。
例えば、「プロダクトAはError Budgetが残り少ないので今スプリントは、新規機能リリースはやめて改善タスクやバグチケットを中心にやっていこう」といった会話も可能できそうです。
Log Management
Log Managementは、さまざまなログを集約できます。
なんといっても便利なのは、使いやすいフィルタリング機能ではないでしょうか。
ログはjson化しておけば、Datadog側でよろしくParseしてくれますし、必要に応じてDatadog側でParse処理も書けたりするので柔軟に対応可能です。
また、ログから独自のメトリクスも生成できるようなので便利ですね。
以下、いくつか活用事例をご紹介します。ALBやGCLBのログ
AWSのALBやGCPのGCLBのアクセスログをLog Managemenで可視化できます。もちろんこのログを基にアラートを設定することも可能です。
注意点としては、アクセス数が多いサービスの場合、S3->Datadogの転送料金が以外にかかってしまうので要注意です。
Datadog側でログをアーカイブするLog Archivesという機能があるので、保存期間を短くしてコストカットする手もあるかと思います。導入方法に関しては、以下の記事をご参照ください。
Amazon Load Balancer
Collect Google Stackdriver logs with Datadogアプリケーションのログ
アプリケーションのログもLog ManagemenでParseしてくれます。
例えばGAEを使用している場合、Stackdriverに出力されたログをそのままLog Managemenに投げるだけでいい感じにParseしてくれます。
また各ログをクリックすると、こんな感じで詳細を確認できます。
Slow Queryのログ
RDSのSlow QueryもParseしてくれます。
Log ManagemenにSlow Queryログを転送することによって
- どういった種類のSlow Queryが一番多いのか?
- 一番実行時間が長いSlow Queryはどれか?
- どの時間帯にSlow Queryが発生しているのか?
といった情報が可視化できます。Queryの実行時間や種類でフィルタリングできるのですごく便利です。
JavaScriptのログ
これはまだテスト運用中ですが、JavaScriptのログもLog Managemenに転送しています。
こうすることで、今まではDatadogで検知できなかったFrontendのエラーも検知できるようになります。
JavaScriptの導入方法に関しては、以下の記事をご参照ください。
Monitor JavaScript console logs and user activity with Datadogこの記事を書いている途中でIntroducing Datadog Real User Monitoringなる機能がローンチされて、実はそっち方がいいじゃないかと思ってます。またさわる機会があれば記事書きたいと思います、
Dashboards
Datadogの魅力の一つはDashboardが柔軟に作れるという点ではないでしょうか。
例えば、イングレーション設定するだけで、以下のようなDashboardが自動的に作成されます。便利です。
新規Dashboard作成は、結構センスのいる作業(そして私はセンスなし)ですが、一度作ってコツが掴めけばある程度は作れる用になる気がします。
DatadogのDashboardには、TimeboardとScreenboardの2種類があります。
これどういう使い分けをするのか?最初は分からなかったんですが、今は自分なりにこう理解しています。(私がそう思っているだけです)Screenboard
ステータス確認用のDashboard。
具体的には、サービスが生きているのか死んでいるのか、KPIやSLOを達成ているのかしていないのか等。
Screenboardは、現在のステータスを一目で確認できるDashboardという位置付けにしています。
Screenboardでは、何か異常があった時に何かが起こっていることは分かりますが、具体的に何が原因なのかは分かりません。
弊社では以下のようなDashboardにしてます。
Timeboard
Timeboardはトラブルシューティング用のDashboardで、Screenboardで
何かが起こっていることを検知して、実際に何が起こったのかを調査するためのDashboardという位置付けにしています。
このDashboardはとても便利、ALB、EC2、RDSといった別々のメトリクスを一画面でかつ同じ時間軸で見れるので相関関係が確認しやすいです。
私のお気に入りは、Datadogがアラート名で検索すると、そのアラートが発生した時間とメトリクスをオーバーレイしてくれます。
詳細は、以前私が書いたDatadogのDashboard(Timeboard)が便利!もご参照ください。コード化(terraform化)
Datadogは、Terraformによってコード管理できます。
弊社ではGCPで似たような構成をしているプロダクトがいくつかあり、その場合Terraformでコード化しておくことで、MonitorやDashboardを簡単に複製できる仕組みを整えています。
このあたりは、気力があればまた書きたいと思います。導入方法に関しては、以下の記事をご参照ください。
Managing Datadog with Terraform社内布教活動
監視は一度作ったら終わりではなく、開発メンバーでPDCAを回して継続して改善していく必要があります。
私の場合、Datadogのセットアップは基本1人で進めてきたため、私以外のエンジニアがDatadogをさわる機会がなかったので、いくつか社内布教活動を行いました。
以下、事例です。エンジニア勉強会
弊社では週1回、エンジニアの勉強会を行なっているため、そこで勉強会を開催しました。
Datadogは、多機能がゆえにメニューが豊富なので、どこを見ればいいんだ?ってなりがちです。
勉強会ではDatadogで見て欲しいポイントや機能を絞って、使い方を中心にレクチャーしました。MTGでアラートを振り返る
週次開催されるプロダクトMTG等で、Datadogが発報したアラートから気になるものがあれば議題にあげて、開発メンバーに確認してもらうようにしています。(全プロダクトではないです)
このアラートは既知なのか?既知であれば対応中のものなのか?このままアラートとして出し続けて良いものなのか?等を議論し、アラートが狼少年化しないよう日々PDCAを回しています。アラートが来たら騒ぐ
Datadogのアラートを設定した私は何のアラートなのか予想がつきますが、その他の開発メンバーは(特に最初の方は)内容を理解できていないことが多いと思います。
私の役目はDatadogからアラートがきたら、アラートの内容を確認して、確認した結果をコメントするようにしています。
そうすることで、(心優しい)開発メンバーがエラー内容について補足してくれたりして、今後対応方針をその場で決めたりしています。KPT
ここからはKPTで振り返りを。
Keep
- インフラからフロントエンドまで異常を検知できる仕組みが整いつつある。
- アラートを日々改善することで徐々にノイズは減ってきている。(狼少年化しないことが大事)
- 私以外のエンジニアも監視に携わる機会が増えてきた。
- Datadogの開発速度が早く、新機能を試したくなる。楽しい。
- 監視をDatadogに統一することで、学習コストが減った気がする。
Problem
- (Datadogの話から少し逸れますが)SLOの運用がうまくいっていない。
- Datadog自体のコストの監視ができていない。
- アラート自体、まだまだ改善の余地はある。
Try
- SLOのあり方や具体的な数値については、もう1度PMや開発メンバーとMTGした方が良さそう。またSLOの定期的な見直しや運用ルールの明確化が必要かも。
- (特に)ログはアクセス数が増えるにしたがってコストがかさむので、何からコストをチェックできる仕組みを作る。
- 継続してアラートのチューニングやアプリケーションの改修を行い、本当に必要なアラート(何かしらのアクションが必要なもの)だけが飛ぶようにしていく。
まとめ
今回、全社横断的にDatadogを導入したことで、各プロダクトで最低限の監視すべてきものがある程度標準化できたのではないかと考えています。そこから各プロダクト固有の監視を追加、アップデートしていけば、より良い監視に近づいていける気がします。
また、今では調査に必要な情報はDatadogに集約されているため、 「何かあればDatadogだけを見れば分かる」という状態ができつつあります。これはトラブルシューティング時の初動が早くなり、操作にも慣れているので問題の特定が早くなると言う効果があるんじゃなかろうか?とは感じています。
Datadog自体の開発速度も早く、監視がどんどん便利になっている感があり、よりクリエティブな開発に集中したい方にはオススメのツールでした。
まだまだ書きたいことはありましたが、またの機会にしたいと思います。
ではまた!!
- 投稿日:2019-12-19T14:45:50+09:00
Aurora Serverless MySQL(5.6) で日本語データを使えるようにする
文字コードを変えよう
Aurora Serverless を作ってみたものの、
latinという文字コード(character set)が標準で
日本語や?を扱えなかったので使えるようにする。環境(2019/12/19)
- Aurora Serverless
- MySQL
- aurora 5.6
設定方法
パラメータグループを作成
RDS > パラメータグループ
画像はAurora Serverless むけの設定なので注意。パラメータを設定
上部の「パラメータを編集」を押下し、以下のように設定していく
character_set
ただの utf8 だとこういうふうになるので、
utf8mb4を選択。character_set_client = utf8mb4 character_set_connection = utf8mb4 character_set_database = utf8mb4 character_set_results = utf8mb4 character_set_server = utf8mb4index column size の拡張(optional)
utf8mb4 を設定して、
varchar(255)のキーカラムとか作ると
mysql Index column size too large. The maximum column size is 767 bytes.
と、こういう理由で怒られる。そんな長いキーカラム作るなよって話もあるんだけど、
そういうの気にせずに typeorm から使いたいのでその制限を拡張する。innodb_file_format = Barracuda innodb_file_per_table = 1 innodb_large_prefix = 1パラメータグループを反映
対象のデータベースの「変更」から、作成したパラメータグループを選択して保存
しばらくすると反映されるはず結果の確認
show variables like "character%"すると見えるなんか utf8mb4 じゃないけどまあ問題なく使えてるから大丈夫なんでしょう。。
- 投稿日:2019-12-19T14:45:50+09:00
Aurora Serverless MySQL(5.6) で日本語データを扱えるようにする
文字コードを変えよう
Aurora Serverless を作ってみたものの、
latinという文字コード(character set)が標準で
日本語や?を扱えなかったので使えるようにする。環境(2019/12/19)
- Aurora Serverless
- MySQL
- aurora 5.6
設定方法
パラメータグループを作成
RDS > パラメータグループ
画像はAurora Serverless むけの設定なので注意。パラメータを設定
上部の「パラメータを編集」を押下し、以下のように設定していく
character_set
ただの utf8 だとこういうふうになるので、
utf8mb4を選択。character_set_client = utf8mb4 character_set_connection = utf8mb4 character_set_database = utf8mb4 character_set_results = utf8mb4 character_set_server = utf8mb4index column size の拡張(optional)
utf8mb4 を設定して、
varchar(255)のキーカラムとか作ると
mysql Index column size too large. The maximum column size is 767 bytes.
と、こういう理由で怒られる。そんな長いキーカラム作るなよって話もあるんだけど、
そういうの気にせずに typeorm から使いたいのでその制限を拡張する。innodb_file_format = Barracuda innodb_file_per_table = 1 innodb_large_prefix = 1拡張したindex column sizeを使うために
CREATE文で、
ROW_FORMAT=DYNAMICを指定する必要があった。CREATE TABLE `retweet` (`tweetId` varchar(255) NOT NULL, ...) ENGINE=InnoDB ROW_FORMAT=DYNAMICパラメータグループを反映
対象のデータベースの「変更」から、作成したパラメータグループを選択して保存
しばらくすると反映されるはず結果の確認
show variables like "character%"すると見えるなんか utf8mb4 じゃないけどまあ問題なく使えてるから大丈夫なんでしょう。。
- 投稿日:2019-12-19T14:05:41+09:00
AWSでYOLOv3を動かす
目的
AWS上でYOLOを動かす。
AWSの知識があまりないため、解決策の1例としてご覧ください。使用したもの
[YOLO] https://github.com/ptxyasu/keras-yolo3
[AWS] EC2 https://aws.amazon.com/jp/ec2/流れ
1.AWSに環境構築
2.AWSに必要なデータ設置
3.yolo実行AWS EC2について
今回は開発環境と一致させるため、Ubuntu 16.04のAMIを選択。
インスタンスタイプは無料枠のt2.microとした。問題1 ボリューム不足
環境構築を行い、keras-yolo3をcloneしようとするとボリュームがないというエラーが発生。
解決策1 EBSのボリュームの変更
ボリュームのサイズを8GiBから少しずつ変更。
6時間に1回程度しか変更できないことに注意。しかし、増やすだけ料金がかかることにも注意!!
結果的に20GiBまで増やした。問題2 メモリ不足(おそらく)
エラー文を調べてもあまり情報は出てこず。
解決策2 インスタンスタイプの変更
t2.microの仮想CPU1つ、メモリ1GiBが問題かなと感じた。
そこでインスタンスタイプをt2.microからt3a.2xlargeへ変更。
これに関してはt2.largeやt2.xlargeへの変更でも良かったかも。
料金がすごく高くなってしまうので注意!問題3 yolo実行でのエラー
エラー文を読むとおそらく以下が問題。
AWS内にfontが存在しないのだと思う。yolo_video.pyfont = ImageFont.truetype(font='font/FiraMono-Medium.otf', size=np.floor(8e-3 * image.size[1] + 0.5).astype('int32'))解決策3 プログラムの変更
今回検出画像ではなく、クラス名とその座標が得られればいいので描写に関する部分は消すことで対応。
yolo_video.pydef detect_image(self, image): start = timer() if self.model_image_size != (None, None): assert self.model_image_size[0]%32 == 0, 'Multiples of 32 required' assert self.model_image_size[1]%32 == 0, 'Multiples of 32 required' boxed_image = letterbox_image(image, tuple(reversed(self.model_image_size))) else: new_image_size = (image.width - (image.width % 32), image.height - (image.height % 32)) boxed_image = letterbox_image(image, new_image_size) image_data = np.array(boxed_image, dtype='float32') print(image_data.shape) image_data /= 255. image_data = np.expand_dims(image_data, 0) # Add batch dimension. out_boxes, out_scores, out_classes = self.sess.run( [self.boxes, self.scores, self.classes], feed_dict={ self.yolo_model.input: image_data, self.input_image_shape: [image.size[1], image.size[0]], K.learning_phase(): 0 }) print('Found {} boxes for {}'.format(len(out_boxes), 'img')) for i, c in reversed(list(enumerate(out_classes))): predicted_class = self.class_names[c] box = out_boxes[i] score = out_scores[i] predicted_value = self.get_concentration(predicted_class) value = max(value,predicted_value) label = '{} {:.2f}'.format(predicted_class,score) top, left, bottom, right = box top = max(0, np.floor(top + 0.5).astype('int32')) left = max(0, np.floor(left + 0.5).astype('int32')) bottom = min(image.size[1], np.floor(bottom + 0.5).astype('int32')) right = min(image.size[0], np.floor(right + 0.5).astype('int32')) print(label, (left, top), (right, bottom)) if top - label_size[1] >= 0: text_origin = np.array([left, top - label_size[1]]) else: text_origin = np.array([left, top + 1]) # My kingdom for a good redistributable image drawing library. end = timer() print(end - start) return image def close_session(self): self.sess.close()問題4 物体が検出されない
これまでの方法を行うことで、yoloの実行まではできた。
しかし、 "Found 0 boxes for img" となり、物体が認識されない、、解決策4
サーバーに置く学習モデルのミス? → モデルには問題なし
画像やプログラムを開く際の権限? → 権限を与えても変化なし
開発環境が複数GPUに対し、AWSでは複数CPU → 本来関係なさそうだが少し影響あるのかも(要検証)結果
実行までは至ったが、物体が検出されず。
ボリュームの拡大とインスタンスタイプの変更で料金が5000円程度も上がってしまったため、インスタンスを削除。検出されない原因の解明と最適なインスタンスタイプの検討をしてからまた取り組みます。
エラーの心あたりある方いたらコメント欲しいです!お願いいたします。
- 投稿日:2019-12-19T13:30:41+09:00
WindowsとUbuntuでAWS Tools for PowerShellを構成する手順書
WindowsとUbuntu on WSL上でAWS Tools for PowerShellを構成したので、最小限の手順書にまとめてみた。基本的に公式情報を見てという感じだけど、順番にどこを見てなにをすればよいかを示し、OSごとにちょっと変わる部分(PowerShellのデフォルトの実行ポリシーとか)に言及したつもり。
公式のドキュメントではなく、現時点の情報を元にした内容であり、as isの形でご利用いただける方向けに投稿しておきます。右上の歯車マークから「Markdownで本文を見る」をすると元のテキストが採れるのでコピペに便利だと思います。
概要
PowerShellはMicrosoft社の開発したシェル環境およびスクリプト言語です。PowerShell 6からはオープンソース化され、LinuxやMac OSでも動作するようになりました。またMicrosoft Azure、Amazon Web Service、Google Cloud Platform、VMwareなどが公式の管理ツールをPowerShell用のモジュールとして提供しています。
ここでは、AWSをPowerShellから操作できるように、以下の流れでAWS Tools for PowerShellを構成します。
- OSごとの手順で、PowerShellをインストールします。
- PowerShell上で、AWS Tools for PowerShell(
AWSPowerShell.NetCoreモジュール)をインストールします。PowerShellのインストール
以下のOSごとの手順で、PowerShellをインストールします。
WindowsへのPowerShellのインストール
WindowsへのPowerShellのインストール方法は「Windows への PowerShell Core のインストール - PowerShell | Microsoft Docs」を参照してください。
通常は、以下を行います。
- リリースページのLatest releaseから
PowerShell-x.x.x-win-x64.msiファイルを取得- 取得したファイルを実行し、ウィザードに従ってインストールを完了
LinuxへのPowerShellのインストール
LinuxへのPowerShellのインストール方法は、「Linux への PowerShell Core のインストール - PowerShell | Microsoft Docs」を参照してください。
例えば、Ubuntu 18.04では以下を行います。
- 上記ページの「Ubuntu 18.04」の項を参照する
- 記述に従い、Microsoftの
universeリポジトリを使用可能にする- 記述に従い、
sudo apt-get install -y powershellでPowerShellをインストールするMacOSへのPowerShellのインストール
MacOSへのPowerShell のインストール方法は、「macOS への PowerShell Core のインストール - PowerShell | Microsoft Docs」を参照してください。
AWS Tools for PowerShellの構成
本手順は、PowerShellでAWSを管理する際に必要な準備です。
AWS Tools for PowerShellのインストール
PowerShellのコンソールを起動します。
- Windowsでは、スタートメニューから
PowerShell>PowerShell 6 (x64)を選択します。- MacOS、Linuxでは、シェル環境から
pwshを実行します。まず外部から取得するスクリプトやモジュールを、署名付きのものであれば実行できるようになっているか、実行ポリシーを確認します。PowerShell上で
Get-ExecutionPolicyを行います(なおPS>はPowerShellのプロンプトを表わしています)。PS> Get-ExecutionPolicy RemoteSigned実行ポリシーが
RestrictedやAllSignedの場合はインターネットからダウンロードされたスクリプトおよび構成ファイルが実行できないので、以下を実行してRemoteSignedに変更します。すでにRemoteSignedまたはそれより寛容なポリシー(例えばUbuntuでは通常Unrestricted)の場合は、変更不要です。PS> Set-ExecutionPolicy RemoteSignedまたAWS Tools for PowerShellの配布リポジトリであるPowerShell Galleryが信頼済みか確認します。
PS> Get-PSRepository Name InstallationPolicy SourceLocation ---- ------------------ -------------- PSGallery Untrusted https://www.powershellgallery.com/api/v2
Untrustedの時は、以下を実行して信頼済みにします。PS> Set-PSRepository -Name PSGallery -InstallationPolicy Trustedこれでインストールできる環境が整ったので、
Install-Moduleコマンドにより、AWS Tools for PowerShell Coreをインストールします。PS> Install-Module -Name AWSPowerShell.NetCore -AllowClobberAWS Tools for PowerShell Coreは、自動では読み込まれません。
Import-Moduleコマンドにより読み込みます。その後Get-ModuleによりAWS Tools for PowerShell Coreを読み込めていることを確認しておきます。PS> Import-Module AWSPowerShell.NetCore PS> Get-Module AWS* ModuleType Version Name ExportedCommands ---------- ------- ---- ---------------- Binary 4.0.1.1 AWSPowerShell.NetCore {Add-AASScalableTarget, Add-A…なお、PowerShell環境ではコマンドレット名やファイル名はもちろん、引数でも補完機能が利用できること多くあります。
Import-Moduleではモジュール名をすべて入力する代わりにAWSまで入力してTABキーを押下すると、このモジュール名が補完されて出てくるはずです。参照
以下を元情報としています。本手順が古くなった場合や正しく動かないと思われたときには、こちらをご参照ください。
- Windows への PowerShell Core のインストール - PowerShell | Microsoft Docs
- Windows に AWS Tools for PowerShell をインストールする - AWS Tools for PowerShell
- Linux への PowerShell Core のインストール - PowerShell | Microsoft Docs
- macOS への PowerShell Core のインストール - PowerShell | Microsoft Docs
- Linux または MacOS での AWS Tools for PowerShell のインストール - AWS Tools for PowerShell
- 投稿日:2019-12-19T13:21:37+09:00
AWS CLIでAmazon Forecastの全リソース削除
備忘録的な投稿。順番に消していかないと削除できないのでCLIでやることにした。
Forecastを削除
for i in `aws forecast list-forecasts --max-items 100 --profile hoge | jq -r '.Forecasts[].ForecastArn'` do aws forecast delete-forecast --forecast-arn ${i} --profile hoge done
jq -rじゃないとダブルクォートを認識してしまって上手く行かないので注意。Predictorを削除
for i in `aws forecast list-predictors --max-items 100 --profile hoge | jq -r '.Predictors[].PredictorArn'` do aws forecast delete-predictor --predictor-arn ${i} --profile hoge doneDatasetImportJobを削除
for i in `aws forecast list-dataset-import-jobs --max-items 100 --profile hoge | jq -r '.DatasetImportJobs[].DatasetImportJobArn'` do aws forecast delete-dataset-import-job --dataset-import-job-arn $i --profile hoge doneDatasetを削除
for i in `aws forecast list-datasets --max-items 100 --profile hoge | jq -r '.Datasets[].DatasetArn'` do aws forecast delete-dataset --dataset-arn $i --profile hoge doneDatasetGroupを削除
for i in `aws forecast list-dataset-groups --max-items 100 --profile hoge | jq -r '.DatasetGroups[].DatasetGroupArn'` do aws forecast delete-dataset-group --dataset-group-arn $i --profile hoge doneこれで全部消せたはず。なお、サクッとコンソール上で消せるのでS3に上げたデータは手で削除した。
参考ページ
- 投稿日:2019-12-19T13:14:01+09:00
Amazon SageMaker Studio が起動できなかった時の対処法
はじめに
Amazon SageMaker Studio が AWS re:Invent 2019 でプレビューリリースとなりました。
さあ、早速検証!と思いましたが、SageMaker Studio が開けないエラーが発生しました。
再起動を試そうにも、SageMaker Studio の削除方法がわからず...実は、現時点(2019/12/18時点)では、コンソール上からSageMaker Studio の作成し直し等はできません。
そのため AWS CLI を利用して、SageMaker Studio を削除する必要があります。
domain の削除にもいくつか手順があったので、今回はその方法をご紹介いたします。SageMaker Studio の削除方法
私の場合は以下の画面から動けなくなり、Restart Now を押しても、起動ができない状況でした。
SageMaker Studio のドメインに関連するアプリケーションが存在するか確認し削除
関連するアプリケーションが存在することで、ドメインが削除できない場合があります。
以下のコードでアプリケーションの有無を確認します。aws sagemaker list-apps --region us-east-2もし、関連するアプリケーションがあった場合は、以下のコードでアプリケーションを削除します。
aws sagemaker delete-app --domain-id <domain-id> --user-profile-name <user-profile-name> --app-type <app-type> --app-name <app-name> --region us-east-2SageMaker Studio のドメインに関連するユーザープロファイルが存在するか確認し削除
関連するユーザープロファイルが存在すると、ドメインが削除できません。
以下のコードで ユーザープロファイルの有無を確認します。
※ユーザープロファイルは、コンソールからも確認できます。aws sagemaker list-user-profiles --region us-east-2関連するユーザープロファイルがある場合には、以下のコマンドで削除します。
aws sagemaker delete-user-profile --domain-id <domain-id> --user-profile-name <user-profile-name> --region us-east-2SageMaker Studioの削除
これでようやく、Sagemaker Studio 削除ができます。
以下のコマンドを実行し、SageMaker Studio を削除します。aws sagemaker delete-domain --domain-id <domain-id> --region us-east-2最後に
これで、SageMaker Studio を削除することができました。
SageMaker Studio をまた利用したい場合は新たに SageMaker Studio を立ち上げてください。
- 投稿日:2019-12-19T12:53:11+09:00
AWSアカウント内で特定CIDRが含まれているセキュリティグループを検索するCLIコマンド
これは何
- 表題の通りです。
AWS CLIコマンド
https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-security-groups.html
以下のコマンド。ドキュメントに記載のある通り、
Values=xxx.xxx.xxx.xxx/yyには対象のCIDRを指定する。Values=xxx.xxx.xxx.xxx/yy,xxx.xxx.xxx.xxx/yyのように複数指定可能。$ aws ec2 describe-security-groups \ --filter "Name=ip-permission.cidr,Values=xxx.xxx.xxx.xxx/yy" \ --query "SecurityGroups[*].[GroupName]" --output textオプションの
--filterや--query、--outputについては以下を参照。https://dev.classmethod.jp/cloud/aws/aws-cli-filter-and-query-howto/
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-usage-output.html
--queryの記法はJMESPath。以下を参照。何をやっているのか
取得範囲のフィルタリング
--filterオプションで「特定CIDRが含まれているセキュリティグループ」するグループ名の一覧を取得
--filterだけだと出力にセキュリティグループの各種属性が含まれたままになる。必要なのはセキュリティグループの名前だけなので、--queryオプションで GroupName キーの値のみを抽出する。出力形式の指定
--output textと指定すれば、プレーンテキストとしてデータが出力される。
- 投稿日:2019-12-19T12:26:13+09:00
AWSでサーバレスな定期バッチ環境を作るには結局どれ使えばいいの?(Lambda vs Fargate vs Batch)
LIFULLで技術マネージャーをしています。たまにはエンジニアぽいところを見せてほしいという社内からの
プレッシャー激励に促されて、AWSにおける知見を整理して共有します。はじめに
システムを運用する上で、日次や月次の決まったタイミングで動作する定期バッチを必要とするケースがあると思います。定期実行なので、インフラリソースをそのタイミングだけ利用するサーバレスなバッチを作るのが経済的にも地球環境的にも優しいですよね。
ということで、AWSのサービスを利用して実現しようというお話なんですが、選択肢の多さに結局どれを使えばいいんだっけ?ってことになりかねません。そこで考えられる選択肢として Lambda、 Fargate、 Batch の3つを取り上げて、定期バッチ環境を作る上でのメリット、デメリットをまとめたいと思います。
サービス比較
記事投稿時点での特徴を独断でまとめたものがこちらです。
Lambda Fargate Batch 環境構築の容易さ ◯ △ △ 実行環境の拡張性 × ◯ ◯ マシンリソースの拡張性 × △ ◯ 機密情報の扱いやすさ △ ◯ × スケジューリング設定 ◯ ◯ ◯ ロギング設定 △ ◯ △ それでは、一つずつ説明していきます。
環境構築の容易さ
Lambdaはコードをアップロードさえすれば、実行環境が構築でき、その導入障壁の低さが魅力と言えます。
FargateとBatchはコンテナベースのコンピューティングサービスであり、事前にバッチアプリケーションを含んだDockerイメージを用意しておく必要があります。実行環境の拡張性
Lambdaは標準で使用できるランタイム(プログラム言語とバージョン)に限りがあります。ただ主要言語はほぼサポートされており、カスタムランタイムも作成できるため、さほど不自由さを感じないのではないでしょうか。
また、そのままだと標準ライブラリしか使えないため、 Serverless Framework や AWS SAM を使用して、ローカルの実行環境をパッケージ化してデプロイする運用が一般的かと思います。ただ、OS依存のネイティブライブラリの導入にはさらに手間がかかるなど、拡張性と利便性がトレードオフであるといえます。
FargateとBatchは前述の通り、あらかじめ自前で用意したコンテナ上でアプリケーションを動作させるため、自由度の高い実行環境を構築することが可能です。マシンリソースの拡張性
Lambdaは実行時間に制限があるというのは有名な話ですね。またメモリ割り当てなど、さまざまなリソースに制限が適用されており、変更することができません。※ 詳しくは AWS Lambdaの制限 を参照してください。
FargateはCPUとメモリの割り当て可能な組み合わせを選択して利用します。現在の利用可能範囲は 0.25vCPU × 512MBメモリ 〜 4vCPU × 30GBメモリ となっています。
また、ストレージはコンテナのルートボリューム(10GB)とマウント利用するコンテナ間共有ボリューム(4GB)の2種類が利用可能ですが、この容量制限を変更することはできません。※ 詳しくは AWS Fargte - タスクCPUとメモリ | AWS Fargte - タスクストレージ を参照してください。
Batchはあらかじめ設定したコンピューティング環境でジョブを実行するサービスですが、そのリソースはEC2インスタンスになります。こちらはT系以外の全てのインスタンスを選択可能です。
また、ストレージはデフォルトでコンテナのルートボリューム(10GB)が利用可能とFargateと同じです。これはDockerのベースサイズのデフォルト値が10GBであることに起因するのだと思いますが、この容量制限はFargateと違い拡張することが可能です。拡張方法は Qiita - AWS Batch で使う EC2 インスタンスのストレージ容量を増やす の記事を参考にしてください。機密情報の扱いやすさ
3サービス共通して、環境変数を設定してアプリケーション側で利用できる仕組みを提供しています。ただし、パスワードやAWSアクセスキーなどの機密情報を環境変数としてプレーンテキストで取り扱うことは推奨されていません。ここでは機密情報の扱いやすさという軸で比較してみます。
Lambdaは AWS KMS による環境変数の暗号化と復号化の仕組みを提供しています。ただし、暗号化の設定がCouldFormationに対応していなかったり、アプリケーション側に復号化の実装が必要だったりと使い勝手がいいとは決して言えません。
Fargateは Parameter Store に登録したSecureStringをタスク起動時に復号化して、環境変数に設定してくれるという非常に便利な機能を提供してくれています。これだけでもFargateを選択する価値はあると私は思っています。※ 詳しくは Developers.IO - ECSでごっつ簡単に機密情報を環境変数に展開できるようになりました! を参照してください。
Batchは機密情報を取り扱うための仕組みが今のところサポートされていないようです。自前でParameter Storeや AWS Secrets Manager に登録した機密情報をアプリケーション側で復号化して使用するといった作り込みが必要になります。スケジューリング設定
3サービス共通して、CloudWatch Eventsと紐づけることで、定期実行するためのスケジューリング設定が可能です。またCloudFormationでのスケジューリング設定も可能です。(長らく
Batchはスケジュール設定がCFn未対応でしたが、本記事を執筆している時点では有難いことに対応してました)ロギング設定
Lambdaはアプリケーションでの標準出力、エラー出力をCloudWatch Logsに出力することが可能です。ロググループは/aws/lambda/<function name>固定となります。
Fargateにはログドライバーの仕組みがあり、これによって柔軟にログ出力先を設定することができます。awslogsログドライバーを使用すれば、アプリケーションでの標準出力、エラー出力をCloudWatch Logsに出力することが可能です。CloudWatch Logsのロググループ名も任意に指定できます。※ 詳しくは AWS Fargate - ログ記録 を参照してください。
Batchはアプリケーションでの標準出力、エラー出力をCloudWatch Logsに出力することが可能です。ロググループは/aws/batch/job固定となります。まとめ
ユースケースに合わせてどのサービスを利用するかを決めるべきですが、上記のメリット、デメリットを踏まえて、定期バッチ環境の構築における取捨選択のポイントを独断でまとめてみました。
サービス 取捨選択のポイント Lambda 使用するライブラリが限定的であり、小規模処理をとりあえず動かしたい場合 Fargate 機密情報を必要とするような、多少手の込んだ中規模処理を動かしたい場合 Batch 機械学習のようなマシンリソースが潤沢に必要な大規模処理を動かしたい場合 ※ あくまで定期バッチをテーマにしたまとめとして取り扱ってください。(本記事では各サービスの備えている利点を全て取り上げた訳ではありません)
サンプル
定期バッチ環境を構築するサンプルコードを紹介して終わりにします。
今回共通して、AWS SAM用のテンプレートを使用します。(Lambda以外はCFnのテンプレートとほぼ同一です)Lambdaサンプル
lambda_template.ymlAWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: 'Lambda Sample Settings' Parameters: BaseName: Type: String Default: lambda-sample LambdaEnv: Type: String AllowedValues: [dev, prod] Default: dev LambdaSubnetId: Type : AWS::EC2::Subnet::Id LambdaSecurityGroupId: Type: AWS::EC2::SecurityGroup::Id Resources: LambdaFunction: Type: AWS::Serverless::Function Properties: FunctionName: !Sub ${BaseName}-function Handler: handler.sample Runtime: ruby2.5 Role: !GetAtt LambdaIamRole.Arn Environment: Variables: LAMBDA_ENV: !Ref LambdaEnv VpcConfig: SubnetIds: - !Ref LambdaSubnetId SecurityGroupIds: - !Ref LambdaSecurityGroupId Events: SampleEvent: Type: Schedule Properties: Name: !Sub ${BaseName}-event1 Schedule: cron(0 3 1 * ? *) LambdaIamRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole LambdaLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${LambdaFunction}VPC内でLambda関数を実行するシンプルなサンプルです。 AWS::Logs::LogGroup を明示的に指定しなくても実行時にロググループを作成してくれますが、このようにCloudFormationのスタックと紐づけることで、個別に管理する必要がなくなるのでおすすめです。
以下の通り、事前にインストールしたsamコマンドを使用してデプロイします。サブネットIDやセキュリティグループのハードコーディングは避けたいので、
parameter-overridesオプションで渡しています。$ sam package --template-file lambda_template.yml --output-template-file packaged-template.yml --s3-bucket sam-sample $ sam deploy --template-file packaged-template.yml --stack-name lambda-sample --capabilities CAPABILITY_IAM --parameter-overrides LambdaSubnetId=subnet-***************** LambdaSecurityGroupId=sg-*****************Fargateサンプル
fargate_template.ymlAWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: 'Fargate Sample Settings' Parameters: BaseName: Type: String Default: fargate-sample FargateEnv: Type: String AllowedValues: [dev, prod] Default: dev ECSSubnetId: Type : AWS::EC2::Subnet::Id ECSSecurityGroupId: Type: AWS::EC2::SecurityGroup::Id Resources: ECSCluster: Type: AWS::ECS::Cluster Properties: ClusterName: !Sub ${BaseName}-cluster ECSTaskDefinition: Type: AWS::ECS::TaskDefinition Properties: Cpu: 2048 Memory: 4096 Family: !Sub ${BaseName}-task RequiresCompatibilities: - FARGATE ExecutionRoleArn: !GetAtt ECSTaskExecutionRole.Arn NetworkMode: awsvpc ContainerDefinitions: - Name: !Sub ${BaseName}-container Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${BaseName}:latest WorkingDirectory: /root/workspace/ Environment: - Name: FARGATE_ENV Value: !Ref FargateEnv Secrets: - Name: AWS_ACCESS_KEY ValueFrom: /fargate-secrets/AWS_ACCESS_KEY - Name: AWS_SECRET_KEY ValueFrom: /fargate-secrets/AWS_SECRET_KEY LogConfiguration: LogDriver: awslogs Options: awslogs-group: !Ref BaseName awslogs-region: !Ref AWS::Region awslogs-stream-prefix: !Ref BaseName TaskScheduleEvents1: Type: AWS::Events::Rule Properties: Name: !Sub ${BaseName}-event1 ScheduleExpression: cron(0 3 1 * ? *) State: ENABLED Targets: - Id: !Sub ${BaseName}-target Arn: !GetAtt ECSCluster.Arn RoleArn: !GetAtt ECSEventRole.Arn Input: !Sub '{"containerOverrides":[{"name":"${BaseName}-container","command":["ruby", "./handler.rb"]}]}' EcsParameters: TaskDefinitionArn: !Ref ECSTaskDefinition TaskCount: 1 LaunchType: FARGATE NetworkConfiguration: AwsVpcConfiguration: AssignPublicIp: DISABLED SecurityGroups: - !Ref ECSSecurityGroupId Subnets: - !Ref ECSSubnetId ECSTaskExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess ECSEventRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceEventsRole FargateLogsGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Ref BaseNameコンテナのWORKDIR直下にある
handler.rbバッチを実行するサンプルです。あらかじめ用意したDockerイメージのURIを AWS::ECS::TaskDefinition ContainerDefinition の
Imageで設定しています。また、機密情報であるAWS_ACCESS_KEYとAWS_SECRET_KEYをParameter Storeに事前に設定して、環境変数として利用する想定です。ポイントとして、バッチコマンドを AWS::Events::Rule Target の
containerOverridesで上書き設定しています。AWS::Events::Ruleを複数定義することで、コンテナ上にある様々な定期バッチのスケジューリングが同一テンプレートファイルで可能になります。こちらも同様にsamコマンドを使用してデプロイします。
$ sam package --template-file fargate_template.yml --output-template-file packaged-template.yml --s3-bucket sam-sample $ sam deploy --template-file packaged-template.yml --stack-name fargate-sample --capabilities CAPABILITY_IAM --parameter-overrides ECSSubnetId=subnet-***************** ECSSecurityGroupId=sg-*****************Batchサンプル
batch_template.ymlAWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: 'Batch Sample Settings' Parameters: BaseName: Type: String Default: batch-sample BatchEnv: Type: String AllowedValues: [dev, prod] Default: dev BatchSubnetId: Type : AWS::EC2::Subnet::Id BatchSecurityGroupId: Type: AWS::EC2::SecurityGroup::Id EC2KeyPair: Type: AWS::EC2::KeyPair::KeyName EC2AmiId: Type: AWS::EC2::Image::Id Resources: BatchComputeEnv: Type: AWS::Batch::ComputeEnvironment Properties: Type: MANAGED ServiceRole: !GetAtt BatchServiceRole.Arn ComputeEnvironmentName: !Sub ${BaseName}-compute-env ComputeResources: MaxvCpus: 2 MinvCpus: 0 Subnets: - !Ref BatchSubnetId SecurityGroupIds: - !Ref BatchSecurityGroupId InstanceRole: !GetAtt EcsInstanceProfile.Arn Ec2KeyPair: !Ref EC2KeyPair Type: EC2 InstanceTypes: - r5.large ImageId: !Ref EC2AmiId State: ENABLED BatchJobQueue: Type: AWS::Batch::JobQueue Properties: JobQueueName: !Sub ${BaseName}-job-queue ComputeEnvironmentOrder: - Order: 1 ComputeEnvironment: !Ref BatchComputeEnv State: ENABLED Priority: 1 BatchJobDefinition: Type: AWS::Batch::JobDefinition Properties: Type: container JobDefinitionName: !Sub ${BaseName}-job-definition ContainerProperties: Command: ["Ref::SciptLang", "Ref::Command"] Memory: 15000 Vcpus: 2 Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${BaseName}:latest Environment: - Name: BATCH_ENV Value: !Ref BatchEnv TaskScheduleEvents1: Type: AWS::Events::Rule Properties: Name: !Sub ${BaseName}-event1 ScheduleExpression: cron(0 3 1 * ? *) State: ENABLED Targets: - Id: !Sub ${BaseName}-target Arn: !Ref BatchJobQueue RoleArn: !GetAtt BatchEventRole.Arn Input: '{"Parameters" : {"SciptLang": "ruby", "Command": "./handler.rb"}}' BatchParameters: JobDefinition: !Ref BatchJobDefinition JobName: !Sub ${BaseName}-job BatchServiceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: batch.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole EcsInstanceRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role EcsInstanceProfile: Type: AWS::IAM::InstanceProfile Properties: Roles: - !Ref EcsInstanceRole BatchEventRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSBatchServiceEventTargetRole
Fargateと同様にコンテナのWORKDIR直下にあるhandler.rbバッチを実行するサンプルです。あらかじめ用意したDockerイメージのURIを AWS::Batch::JobDefinition ContainerProperties の
Imageで設定しています。また、マシンリソースが潤沢に必要な大規模処理を想定して、ストレージを拡張したAMI IDを AWS::Batch::ComputeEnvironment ComputeResources のImageIdで設定しています。一つ目のポイントとして、EC2 vCPUの最小数を AWS::Batch::ComputeEnvironment ComputeResources の
MinvCpusで0に設定しています。これによりバッチ実行時にEC2インスタンスが起動し、バッチ終了後に自動でEC2インスタンスが削除されるというサーバレスな運用を実現します。二つ目のポイントとして、バッチコマンドを AWS::Batch::JobDefinition ContainerProperties の
Commandで["Ref::SciptLang", "Ref::Command"]のように参照形式で設定しています。これは AWS::Events::Rule Target のParametersで実体を設定することで、コンテナ上にある複数の定期バッチのスケジューリングを同一テンプレートファイルで可能にしています。(Fargateと同じようにcontainerOverridesでシンプルにバッチコマンドを上書き設定できるかと思ってましたが、難しかったためこのようなハック的なやり方を採用しました)こちらも同様にsamコマンドを使用してデプロイします。
$ sam package --template-file batch_template.yml --output-template-file packaged-template.yml --s3-bucket sam-sample $ sam deploy --template-file packaged-template.yml --stack-name batch-sample --capabilities CAPABILITY_IAM --parameter-overrides BatchSubnetId=subnet-***************** BatchSecurityGroupId=sg-***************** EC2KeyPair=********** EC2AmiId=ami-*****************さいごに
以上で終了です。AWSでサーバレスな定期バッチ環境を作るうえでの、お役に立てれば幸いです。
実際のところ、
Batchの定期バッチ運用に関しては、スケジューリングしたLambda経由でジョブをサブミットすることで実現してました。今回この記事を執筆するにあたり、CFnに対応しているやん!じゃあ試してみようとなったのですが、思いのほか茨の道で大変でした。なんとか動く状態になって良かったです。では、またX年後くらいにお会いしましょう。
- 投稿日:2019-12-19T12:06:45+09:00
Capistranoで自動デプロイ時に起きるエラーについて〜AWS〜
はじめに
Capistranoを使って、AWSに自動デプロイをかけた時に出るエラーのお話
本件は主に再起動時に起きるエラーです
最後にデプロイ時に確認する事項をまとめていますので、そちらも軽くご参照いただければと思いますErroe内容
bundle exec cap production deploy実行後
terminalMac_ikuy:"hogehoge" User$ bundle exec cap production deploy #<Thread:0x00007f8b2b50bb18@/Users/mac_ikuy/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/sshkit-1.20.0/lib/sshkit/runners/parallel.rb:10 run> terminated with exception (report_on_exception is true): Traceback (most recent call last): 1: from /Users/mac_ikuy/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/sshkit-1.20.0/lib/sshkit/runners/parallel.rb:11:in `block (2 levels) in execute' /Users/mac_ikuy/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/sshkit-1.20.0/lib/sshkit/runners/parallel.rb:15:in `rescue in block (2 levels) in execute': Exception while executing as ec2-user@18.177.78.202: Authentication failed for user ec2-user@xx.xxx.xxx.xxx (SSHKit::Runner::ExecuteError) (Backtrace restricted to imported tasks) cap aborted! SSHKit::Runner::ExecuteError: Exception while executing as ec2-user@xx.xxx.xxx.xxx: Authentication failed for user ec2-user@xx.xxx.xxx.xxx Caused by: Net::SSH::AuthenticationFailed: Authentication failed for user ec2-user@xx.xxx.xxx.xxx Tasks: TOP => rbenv:validate (See full trace by running task with --trace)こんな感じのエラーが出ます
今回の話の結論からいうと、SSHキーが消えてしまっているので、.sshにて下記コマンドを入力しますterminal$ cd ~/.ssh .ssh $ ssh-add ~/.ssh/hoge.pemhoge.pemにはインスタンスに対応したpemキーを入力してください
Error解決しないときは
エラー文に慣れない方はなんかいっぱい書いてあってうわぁって思うかもしれませんが、ポイントは"Caused by"です
これのせいで、ってことですね
"Exception while executing"を直訳すると「実行中の例外」です
何か例外が起きてしまったんだなってことですね
AWSへのデプロイ時の基本注意点としては、
- mysql/nginexの再起動
- unicorn killの確認
です
前者は[ec2-user@ip-xxx ~]$ sudo service nginx restart [ec2-user@ip-xxx ~]$ sudo service mysqld restart後者は
[ec2-user@ip-xxx ~]$ ps aux | grep unicorn ec2-user 4223 0.0 0.0 11xxxx master ec2-user 4175 0.0 0.0 11xxxx ~~~~省略~~~~ ec2-user 4236 0.0 0.0 11xxxx ~~~~省略~~~~ [ec2-user@ip-xxx ~]$ kill 4223 #masterのNO.上記ができているにも関わらずデプロイできないときは、インスタンスを一度停止しましょう
"停止"が完了したら"開始"をしましょう
大概のエラーはこれでで解決できます。まとめ
解決方法のまとめです
- "Net::SSH::AuthenticationFailed: Authentication failed"が出たときはsshキーが消えている可能性があるので、addしてやる
- mysql/nginexの再起動
- unicornのkill
- インスタンスの再起動
- 投稿日:2019-12-19T11:54:33+09:00
MSK(Amazon Managed Streaming for Apache Kafka)へのトピック登録
1.はじめに
Amazon MSK(Amazon Managed Streaming for Apache Kafka)は、Apache Kafkaのマネージドサービスです。Amazon MSKの設定変更は、Amazon CLI経由で行うことができます。
デフォルトでは、Kafka Brokerに対して、Kafka Producerはトピックを生成することができせん。なぜならば、auto.create.topics.enable = trueとなっていないためです。
したがって、本投稿では、まずはAmazon CLI経由での設定手順について解説します。その後、トピックを登録します。2.設定方法
2-1.セキュリティグループの設定
Amazon CLI経由でAmazon MSKを操作するためには、Kafka Clusterを構築するさいに、Amazon CLIで操作する端末(Amazon CLI端末)をアクセス可能なセキュリティグループに所属させる必要があります。
Kafka Clusterを構築するさいには、Amazon CLI端末が含まれるセキュリティグループをアクセス対象として含めるように注意してください。2-2.arnの確認
Amazon Management Consolから、Amazon MSKのarnを確認します。この投稿のためだけに作成したので、arnは既に有効値とはなっていないです。この場合、arnは以下の値となります。
arn:aws:kafka:ap-northeast-1:196585472650:cluster/Amazon-MSK-Test/d5a6aebf-8858-43db-bfe4-4a7288d93775-22-3.bootstrap-brokers(Zookeeper)の接続先を確認
describe-clusterコマンドで、bootstrap-brokers(Zookeeper)の接続先を確認する。この場合、"z-1.amazon-msk-test.sxm7ay.c2.kafka.ap-northeast-1.amazonaws.com:2181,z-3.amazon-msk-test.sxm7ay.c2.kafka.ap-northeast-1.amazonaws.com:2181,z-2.amazon-msk-test.sxm7ay.c2.kafka.ap-northeast-1.amazonaws.com:2181"となります。
aws kafka describe-cluster --region ap-northeast-1 --cluster-arn arn:aws:kafka:ap-northeast-1:196585472650:cluster/Amazon-MSK-Test/d5a6aebf-8858-43db-bfe4-4a7288d93775-2 { "ClusterInfo": { "EncryptionInfo": { "EncryptionInTransit": { "ClientBroker": "TLS", "InCluster": true }, "EncryptionAtRest": { "DataVolumeKMSKeyId": "arn:aws:kms:ap-northeast-1:196585472650:key/d94c7909-3392-482a-908d-d25f9a31abb9" } }, "BrokerNodeGroupInfo": { "BrokerAZDistribution": "DEFAULT", "ClientSubnets": [ "subnet-05e68568d43a7c6d3", "subnet-091efbcc876bdefb1", "subnet-05a73c6877da30604" ], "StorageInfo": { "EbsStorageInfo": { "VolumeSize": 1 } }, "SecurityGroups": [ "sg-0204c59a2c6992390", "sg-0fc8fe66209887218", "sg-0c4b89523751d689c" ], "InstanceType": "kafka.m5.large" }, "ClusterName": "Amazon-MSK-Test", "CurrentBrokerSoftwareInfo": { "KafkaVersion": "2.3.1", "ConfigurationRevision": 1, "ConfigurationArn": "arn:aws:kafka:ap-northeast-1:196585472650:configuration/SalesClusterConfiguration2/9c66b8dc-836e-4348-a2a3-b2bd19717b0b-2" }, "Tags": { "Name": "Kafka-Cluster" }, "CreationTime": "2019-12-19T02:09:06.63Z", "NumberOfBrokerNodes": 3, "ZookeeperConnectString": "z-1.amazon-msk-test.sxm7ay.c2.kafka.ap-northeast-1.amazonaws.com:2181,z-3.amazon-msk-test.sxm7ay.c2.kafka.ap-northeast-1.amazonaws.com:2181,z-2.amazon-msk-test.sxm7ay.c2.kafka.ap-northeast-1.amazonaws.com:2181", "State": "ACTIVE", "CurrentVersion": "K2EUQ1WTGCTBG2", "ClusterArn": "arn:aws:kafka:ap-northeast-1:196585472650:cluster/Amazon-MSK-Test/d5a6aebf-8858-43db-bfe4-4a7288d93775-2", "EnhancedMonitoring": "DEFAULT", "OpenMonitoring": { "Prometheus": { "NodeExporter": { "EnabledInBroker": false }, "JmxExporter": { "EnabledInBroker": false } } } } }2-4.設定ファイルの作成
Amazon CLI端末の任意のディレクトリに、設定変更用のファイルを作成します。設定ファイルのフォーマットは、Amazon MSKが指定した形式にする必要があります。ここを参照してください。
/home/ec2-user/test/configuration.txtauto.create.topics.enable = true message.max.bytes = 734003202-5.Amazon MSK公式のpythonツールの準備
設定ファイルと同一ディレクトリ階層に、以下のpythonファイルを作成します。今回の場合は、
configuration.txtと同一ディレクトリであるhome/ec2-user/に配置します。
pythonファイルの雛形は、ここを参照してください。/home/ec2-user/unction.pyimport boto3 client = boto3.client('kafka') config_file = open('/home/ec2-user/test/configuration.txt', 'r') server_properties = config_file.read() response = client.create_configuration( #この値はAWS アカウントでユニークにする必要があります。 Name='SalesClusterConfiguration3', Description='The configuration to use on all sales clusters.', KafkaVersions=['1.1.1', '2.1.0','2.3.1'], ServerProperties=server_properties ) print(response)boto3ライブラリがない場合は、pipコマンドでインストールしてください。
pip install boto32-6.設定反映
pythonコマンドを実行することで設定を反映することができます。以下の通り、
'HTTPStatusCode': 200が返信された場合、設定変更が可能です。python function.py {u'LatestRevision': {u'CreationTime': datetime.datetime(2019, 12, 19, 2, 42, 53, 2000, tzinfo=tzlocal()), u'Description': u'The configuration to use on all sales clusters.', u'Revision': 1}, u'Arn': u'arn:aws:kafka:ap-northeast-1:196585472650:configuration/SalesClusterConfiguration3/30ffe529-2733-486d-bfed-1f1641db9edf-2', u'CreationTime': datetime.datetime(2019, 12, 19, 2, 42, 53, 2000, tzinfo=tzlocal()), u'Name': u'SalesClusterConfiguration3', 'ResponseMetadata': {'RetryAttempts': 0, 'HTTPStatusCode': 200, 'RequestId': '939d9052-a6f7-4fa4-b66f-91fe737a8057', 'HTTPHeaders': {'x-amzn-requestid': '939d9052-a6f7-4fa4-b66f-91fe737a8057', 'x-amz-cf-pop': 'NRT12-C2', 'content-length': '347', 'via': '1.1 4cb3df5349fbb69c930b315b7d0a5272.cloudfront.net (CloudFront)', 'x-cache': 'Miss from cloudfront', 'x-amz-apigw-id': 'E7iC8G5xNjMFeFw=', 'x-amzn-trace-id': 'Root=1-5dfae3ac-0cd2e93c1fe9319eb9166bc2;Sampled=0', 'connection': 'keep-alive', 'x-amz-cf-id': 'zSz8M9gaSNl4bIuNUdbDfY_Sm4cEWCoZF3RfuAX22aQBZxHJOUI4ug==', 'date': 'Thu, 19 Dec 2019 02:42:53 GMT', 'content-type': 'application/json'}}}2-7.トピック登録
Kafka Producerからトピックを登録します。
Created topic "XXXXXXX".と結果が返ってきたらトピック生成完了です。kafka-topics --zookeeper "z-1.amazon-msk-test.sxm7ay.c2.kafka.ap-northeast-1.amazonaws.com:2181,z-3.amazon-msk-test.sxm7ay.c2.kafka.ap-northeast-1.amazonaws.com:2181,z-2.amazon-msk-test.sxm7ay.c2.kafka.ap-northeast-1.amazonaws.com:2181" --create --topic first-test --partitions 3 --replication-factor 3 Created topic "first-test".以上
- 投稿日:2019-12-19T11:48:02+09:00
OpsworksにEC2を登録できない問題への対応
事象
インスタンス登録コマンドを発行すると途中で接続が切れ登録が失敗する。
$ aws opsworks register --infrastructure-class ec2 --region us-east-1 --stack-id <STACK_ID> --ssh-username ec2-user --ssh-private-key <PRIVATE_KEY> <INSTANCE_ID> % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 7929k 100 7929k 0 0 47.5M 0 --:--:-- --:--:-- --:--:-- 47.5M [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Initializing AWS OpsWorks environment [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Checking if OS is supported [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Running on supported OS [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Setup motd [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Executing: ln -sf --backup /etc/motd.opsworks-static /etc/motd [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Using assets bucket from environment: 'opsworks-instance-assets-ap-northeast-1.s3.amazonaws.com'. [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Installing Ruby for the agent [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Executing: /tmp/opsworks-agent-installer.XbghhcTVDCDTtrGW/opsworks-agent-installer/opsworks-agent/bin/installer_wrapper.sh -r -R opsworks-instance-assets-ap-northeast-1.s3.amazonaws.com Connection to <Elastic IP> closed.調査
登録対象のEC2インスタンス側でログを確認する。
[root@ip-172-30-1-37 ec2-user]# tail -f /var/log/aws/opsworks/user-data.log [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Checking if OS is supported /tmp/opsworks-agent-installer.XbghhcTVDCDTtrGW/opsworks-agent-installer/boot-registration: line 48: lsb_release: command not found [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Running on supported OS [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Setup motd [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Executing: ln -sf --backup /etc/motd.opsworks-static /etc/motd [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Using assets bucket from environment: 'opsworks-instance-assets-ap-northeast-1.s3.amazonaws.com'. [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Installing Ruby for the agent [Thu, 19 Sep 2019 08:14:44 +0000] opsworks-init: Executing: /tmp/opsworks-agent-installer.XbghhcTVDCDTtrGW/opsworks-agent-installer/opsworks-agent/bin/installer_wrapper.sh -r -R opsworks-instance-assets-ap-northeast-1.s3.amazonaws.com [Thu, 19 Sep 2019 08:14:44 +0000] installer-wrapper: Using opsworks-instance-assets-ap-northeast-1.s3.amazonaws.com for assets. [Thu, 19 Sep 2019 08:14:44 +0000] installer-wrapper: Unsupported OS version: amazon 2018.03OS versionが未サポートと言われるが、そんなはずはない。(https://aws.amazon.com/jp/about-aws/whats-new/2019/01/aws-opsworks-stacks-now-supports-amazon-linux-2--amazon-linux-20/)
原因が分からないのでググると以下のフォーラムにたどり着く。
https://forums.aws.amazon.com/thread.jspa?threadID=250571
Opsworks Agentが古いのでは?というコメントが見つかる。
対応
以下の設定を確認すると2016年付近のAgentを利用していたので、最新のバージョンを利用するように変更。
Stack Settings > OpsWorks Agent version ---> Latest version
再度登録コマンド発行。
$ aws opsworks register --infrastructure-class ec2 --region us-east-1 --stack-id <STACK_ID> --ssh-username ec2-user --ssh-private-key <PRIVATE_KEY> <INSTANCE_ID> % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 7933k 100 7933k 0 0 44.0M 0 --:--:-- --:--:-- --:--:-- 44.0M [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Initializing AWS OpsWorks environment [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Checking if OS is supported [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Running on supported OS [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Setup motd [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Executing: ln -sf --backup /etc/motd.opsworks-static /etc/motd [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Using assets bucket from environment: 'opsworks-instance-assets-ap-northeast-1.s3.amazonaws.com'. [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Installing Ruby for the agent [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Executing: /tmp/opsworks-agent-installer.YmkVCDV5L5hS1Ok3/opsworks-agent-installer/opsworks-agent/bin/installer_wrapper.sh -r -R opsworks-instance-assets-ap-northeast-1.s3.amazonaws.com [Thu, 19 Sep 2019 08:22:23 +0000] opsworks-init: Starting the installer Instance successfully registered. Instance ID: <Instance ID> Connection to <Elastic IP> closed.登録できた。
参考までにEC2側のログは以下の通り。
[Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Initializing AWS OpsWorks environment [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Checking if OS is supported /tmp/opsworks-agent-installer.YmkVCDV5L5hS1Ok3/opsworks-agent-installer/boot-registration: line 47: lsb_release: command not found [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Running on supported OS [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Setup motd [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Executing: ln -sf --backup /etc/motd.opsworks-static /etc/motd [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Using assets bucket from environment: 'opsworks-instance-assets-ap-northeast-1.s3.amazonaws.com'. [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Installing Ruby for the agent [Thu, 19 Sep 2019 08:22:21 +0000] opsworks-init: Executing: /tmp/opsworks-agent-installer.YmkVCDV5L5hS1Ok3/opsworks-agent-installer/opsworks-agent/bin/installer_wrapper.sh -r -R opsworks-instance-assets-ap-northeast-1.s3.amazonaws.com [Thu, 19 Sep 2019 08:22:21 +0000] installer-wrapper: Using opsworks-instance-assets-ap-northeast-1.s3.amazonaws.com for assets. [Thu, 19 Sep 2019 08:22:21 +0000] installer-wrapper: Downloading agent ruby package from https://opsworks-instance-assets-ap-northeast-1.s3.amazonaws.com/packages/amazon/2018.03/opsworks-agent-ruby-2.2.3-1.x86_64.rpm [Thu, 19 Sep 2019 08:22:21 +0000] downloader: Successfully created temporary download directory. (/tmp/opsworks-downloader.I6n5yywV) [Thu, 19 Sep 2019 08:22:21 +0000] downloader: File size test passed. [Thu, 19 Sep 2019 08:22:21 +0000] downloader: Checksum proof skipped. [Thu, 19 Sep 2019 08:22:21 +0000] downloader: Successfully downloaded https://opsworks-instance-assets-ap-northeast-1.s3.amazonaws.com/packages/amazon/2018.03/opsworks-agent-ruby-2.2.3-1.x86_64.rpm [Thu, 19 Sep 2019 08:22:21 +0000] installer-wrapper: Installing agent ruby Preparing... ######################################## Updating / installing... opsworks-agent-ruby-2.2.3-1 ######################################## [Thu, 19 Sep 2019 08:22:23 +0000] installer-wrapper: Cleaning up [Thu, 19 Sep 2019 08:22:23 +0000] opsworks-init: Starting the installerまとめ
https://docs.aws.amazon.com/ja_jp/opsworks/latest/userguide/registered-instances-register.html
登録フローの中でAgent Packageをダウンロードする部分があるが、そのダウンロードするパッケージのバージョンが古いため、登録コマンド(ローカル)のバージョンと不整合を起こしていたと思われる。
ログからは全く読めなかったので結構ハマってしまった。
- 投稿日:2019-12-19T11:07:39+09:00
CloudFormation、RDS部分でnot emptyエラーに振り回された話
CloudFormation初めました・・・
VPC/EC2/ALB/Subnet/EIP/IGW/NAT/ これらの作成はわりとさくさくいきました。
ここにRDS Auroraを足そうとしたときに、スタック作成時のエラー解決ができず、気づいたら累計30時間ぐらい使っていた話です。結論から言うと、GUIからだと自動で作ってくれる Type: AWS::RDS::DBClusterParameterGroup の記述が漏れていました。
CloudFormationの公式ドキュメントや、有志の記事を参考にyamlを書いて、トライ&エラーを繰り返していました。
スタック作成時にAllocatedStorage not empty.エラーが表示されました。
ドキュメントでは必須:いいえ だし、GUIではAuroraの場合ここを設定する必要ないので、ドキュメントかAPIのバグで、実は必須なんだと判断し、追記しました。このエラーはでなくなりましたが、次々に必須:いいえであるはずの項目のエラーが・・・どんどん追記していきます。
最終的にType: AWS::RDS::DBInstanceのStorageType でどの有効な値: standard | gp2 | io1を入れても、not valiedのエラーがでるようになり、わけわからん状態に。
社内や有志のRDS Auroraを使うyamlと差分を見ていったところ、自分のyamlにType: AWS::RDS::DBClusterParameterGroupが無いことに気づき、追記して解消しました。
ドキュメントでは必須:いいえ だし、GUIではAuroraの場合ここを設定する必要ない
CloudFormationのTypeのレベルは、自動ではやってくれなくて、記述が必要ということですね(たぶん)



























































