はじめに
サーバレスの仕組みを理解するためにこちらの本をベースに学習した内容を記述します。
2020年にaws cliのdockerイメージが公開されたことによりこちらを利用しておりますので、一部dockerに合わせた内容となっています。
AWS CLIインストール
macに直接入れてもよかったのですが、docker imageがあったのでそちらを使ってみることにしました。
$ docker run --rm -it amazon/aws-cli --version
// imageがない場合は、pullされます
aws-cli/2.2.2 Python/3.8.8 Linux/4.19.121-linuxkit docker/x86_64.amzn.2 prompt/off
AWS認証情報の作成
この手順は、既に手元PCでaws-cliが入っていたりする方は不要です。
なお、これは一度ミスっており、記事にしています。
1.マウントする場所を作る
私は、普段開発で使っているworkディレクトリ配下にdockeraws/.aws
を作りました。
$ mkdir {任意の場所}/dockeraws
$ mkdir {任意の場所}/dockeraws/.aws
$ cd {任意の場所}/dockeraws/.aws
2.~/.aws/credentialsにマウントするファイル作成
ファイルの構成はこちらを参考にしました。
設定ファイルと認証情報ファイルの設定
$ vi credentials
[default]
aws_access_key_id=XXXXXXXXXXXX
aws_secret_access_key=XXXXXXXXXXXXXXXXXXXXXXXX
3.~/.aws/configにマウントするファイル作成
ファイルの構成はこちらを参考にしました。
$ vi config
[default]
region=ap-northeast-1
output=json
4.動作確認
$ docker run --rm -it -v {任意の場所}/dockeraws/.aws:/root/.aws amazon/aws-cli configure list
Name Value Type Location
---- ----- ---- --------
profile <not set> None None
access_key ****************2DLO shared-credentials-file
secret_key ****************Re2n shared-credentials-file
region ap-northeast-1 config-file ~/.aws/config
問題なさそうです。
aliasの設定
docker imageを使う場合、docker run --rm -it amazon/aws-cli
がaws
とイコールになります。
長いのでエイリアスを設定することでdockerを意識せずに使えるようになります。
また、認証情報のマウントを含めておかないと認証情報を要するコマンド実行時にエラーとなります。
マウントしない場合、こちらと同じ目に遭います。
$ alias aws='docker run --rm -it -v {任意の場所}/dockeraws/.aws:/root/.aws amazon/aws-cli'
// エイリアス設定前
$ aws --version
-bash: aws: command not found
// エイリアス設定後
$ aws --version
aws-cli/2.2.2 Python/3.8.8 Linux/4.19.121-linuxkit docker/x86_64.amzn.2 prompt/off
なお、時が経った時、macにawscliをインストールしたと勘違いする可能性があるため、実際にはawsから少し変えています。以後記事内ではこちらを使っていきます。
$ alias dockeraws='docker run --rm -it -v /Users/yukokanai/work/aws/dockeraws/.aws:/root/.aws amazon/aws-cli'
Amazon Cloud Watchをトリガーに自動処理をする
やっと本題です。今回試す構成は以下の通り。
No. | サービス | 役割 |
---|---|---|
1 | Amazon Kinesis DataStreams | ※無料枠なし |
2 | Amazon CloudWatch | ・Kinesisの書き込み件数(putレコード量)をモニタリングし一定量のレコードの場合、SNSへアラーム通知 |
3 | Amazon SNS | ・アラーム通知先(トピック) |
4 | Amazon Lambda | ・SNSトピックをサブスクライブして監視(トピック更新がトリガーで実行) ・Kinesisへリシャーディング、つまりシャード数を増やす。 |
1.Amazon Kinesis DataStreamsのストリームを作成する
ストリーム名は、my-sample
、初期シャード数は1
とします。
$ dockeraws kinesis create-stream --stream-name my-sample --shard-count 1
次にこのストリームに書き込むpythonスクリプトを作成します。
ホストを汚したくないので、ここでもdockerを使います。
なお、私はjupyter/notebookが使えるdockerfileを使っていますのでそこにboto3
を追加します。
(通常は、単純なpythonイメージで実現できると思いますので、Dockerfileはあくまで参考として載せます)
FROM ubuntu:latest
RUN apt-get update && apt-get install -y \
sudo \
wget \
vim
WORKDIR /opt
RUN wget https://repo.continuum.io/archive/Anaconda3-2019.10-Linux-x86_64.sh && \
sh /opt/Anaconda3-2019.10-Linux-x86_64.sh -b -p /opt/anaconda3 && \
rm -f /opt/Anaconda3-2019.10-Linux-x86_64.sh
ENV PATH /opt/anaconda3/bin:$PATH
# Pythonのpakage管理ツール
RUN pip install --upgrade pip
RUN pip install boto3 //今回追加
WORKDIR /
CMD ["jupyter", "lab", "--ip=0.0.0.0", "--allow-root", "--LabApp.token=''"]
//boto3を追加したのでビルド
$ docker build -f ../Dockerfile.jupyterlab . -t miharasatsuki/jupyter-notebook
// 起動
$ docker run -p 8888:8888 -v /Users/yukokanai/work/aws/dockeraws/.aws:/root/.aws --name my-lab miharasatsuki/jupyter-notebook
起動できたら以下にアクセスして、スクリプトを作っていきます。
http://localhost:8888/
import boto3
import datetime
import time
import uuid
kinesis = boto3.client('kinesis')
stream_name = 'my-sample'
partition_key = str(uuid.uuid4())
data = datetime.datetime.utcnow().strftime('%s')
for i in range(15):
kinesis.put_record(
StreamName=stream_name,
Data=data,
PartitionKey=partition_key)
実行してエラーがないことを確認しました。
2.Amazon SNSでトピックを作成する
アラームの通知先であるSNSトピックを作成します。
$ dockeraws sns create-topic --name my-sample
{
"TopicArn": "arn:aws:sns:ap-northeast-1:1234567890:my-sample"
}
3.Amazon CloudWatchでアラームを設定する
こちらのリファレンスを参考にしつつ設定します。
$ dockeraws cloudwatch put-metric-alarm \
--alarm-name my-kinesis-mon --metric-name IncomingRecords \
--namespace AWS/Kinesis --alarm-description "Alarm when Request exceeds 10 per minute" \
--statistic Sum --period 60 --threshold 10 --comparison-operator GreaterThanThreshold \
--dimensions "Name=StreamName,Value=my-sample" --evaluation-periods 1 \
--alarm-actions arn:aws:sns:ap-northeast-1:1234567890:my-sample
アラーム状態を変更してテストします。レスポンスはありません。
$ dockeraws cloudwatch set-alarm-state --alarm-name my-kinesis-mon \
--state-reason 'initializing' --state-value ALARM
4.Lambda関数の作成
こちらもpythonスクリプトで実装していきますが、Lambdaを使うにあたりIAM権限設定が必要なのでまずそちらを行なっていきます。
まずは、実行ロールを作成します。
こちらのリファレンスを参考にしつつ設定します。
ポリシーの内容をJSONかYAMLのドキュメントファイルとして渡さないといけないようなので作成します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
作成したファイルを使ってIAMロールを作成します。
ただし、コンテナから参照されるファイルなので、ここでファイルパスをホストにしてしまうとファイルがないと言われます。
これを避けるため、作成したファイルを一時的にマウントします。
なお、file://
は指定したいディレクトリの前につけます。
$ dockeraws iam create-role --role-name resharding_function_role \
--assume-role-policy-document file:///Users/yukokanai/work/aws/iamPolicy/trustpolicy.json
Error parsing parameter '--assume-role-policy-document': Unable to load paramfile file:///Users/yukokanai/work/aws/iamPolicy/trustpolicy.json: [Errno 2] No such file or directory: '/Users/yukokanai/work/aws/iamPolicy/trustpolicy.json'
$ docker run --rm -it \
-v /Users/yukokanai/work/aws/iamPolicy/trustpolicy.json:/tmp/trustpolicy.json \
-v /Users/yukokanai/work/aws/dockeraws/.aws:/root/.aws amazon/aws-cli \
iam create-role --role-name resharding_function_role \
--assume-role-policy-document file:///tmp/trustpolicy.json
{
"Role": {
"Path": "/",
"RoleName": "resharding_function_role",
"RoleId": "AROAWHUBMU2PMEM4ANAZK",
"Arn": "arn:aws:iam::1234567890:role/resharding_function_role",
"CreateDate": "2021-05-05T11:50:02+00:00",
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
}
}
続いて、ポリシーの適用です。またjsonを描いていきます。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"cloudwatch:*",
"logs:*",
"kinesis:*"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
$ docker run --rm -it \
-v /Users/yukokanai/work/aws/iamPolicy/permission.json:/tmp/permission.json \
-v /Users/yukokanai/work/aws/dockeraws/.aws:/root/.aws amazon/aws-cli \
iam put-role-policy --role-name resharding_function_role \
--policy-name basic-permission \
--policy-document file:///tmp/permission.json
Lambdaで使うスクリプトを作成します。
import boto3
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
kinesis = boto3.client('kinesis')
cloudwatch = boto3.client('cloudwatch')
def lambda_handler(event, context):
event_data = json.dumps(event,ensure_ascii=False, indent=4, sort_keys=True, separators=(',', ': '))
logger.info("Event" + event_data)
message = json.loads(event['Records'][0]['Sns']['Message'])
logger.info("Message: " + str(message))
alarm_name = message['AlarmName']
stream_name = message['Trigger']['Dimensions'][0]['value']
if alarm_name == 'my-kinesis-mon':
#現在のオープンシャード数を取得
stream_summary = kinesis.describe_stream_summary(
StreamName=stream_name
)
current_open_shard_count = stream_summary['StreamDescriptionSummary']['OpenShardCount']
#シャードを2倍に変更
target_shard_count=current_open_shard_count * 2
response = kinesis.update_shard_count(
StreamName=stream_name,
TargetShardCount=target_shard_count,
ScalingType='UNIFORM_SCALING'
)
#現在のアラームの閾値をシャード数×1000の80%に設定
new_threshold = target_shard_count*1000*0.8
#不具合 logger.info("Set a threshold value to " + new_threshold)
logger.info("Set a threshold value to " + str(new_threshold))
response = cloudwatch.put_metric_alarm(
AlarmName='my-kinesis-mon',
MetricName='IncomingRecords',
Namespace='AWS/Kinesis',
Period=60,
EvaluationPeriods=1,
ComparisonOperator='GreaterThanThreshold',
Threshold=new_threshold,
Statistic='Sum'
)
余談
なお、上記は基本サンプルコードそのままでname変えていましたが、不具合があり動きませんでしたので、
44行目あたりをコメントアウトし、str()
しています。
以下、Lambdaのログ
[ERROR] TypeError: can only concatenate str (not "float") to str
Traceback (most recent call last):
File "/var/task/resharding-function.py", line 44
デプロイパッケージにします。
$ cd resharding-function/
$ zip -r9 ~/work/aws/resharding-function/resharding-function.zip *
adding: resharding-function.py (deflated 52%)
Lambdaファンクションを作成します。
その前に、aws-cli、意外にファイル指定の実行が多いのでaliasのマウント先を増やして、必要なファイルを突っ込んでいく方式に切り替えます。
$ mkdir /Users/yukokanai/work/aws/mount_tmp
$ alias dockeraws='docker run --rm -it -v /Users/yukokanai/work/aws/mount_tmp/.:/tmp/. -v /Users/yukokanai/work/aws/dockeraws/.aws:/root/.aws amazon/aws-cli'
$ mv ~/work/aws/resharding-function/resharding-function.zip /Users/yukokanai/work/aws/mount_tmp/
$ dockeraws lambda create-function --function-name my-resharding-function \
--zip-file fileb:///tmp/resharding-function.zip \
--role arn:aws:iam::1234567890:role/resharding_function_role \
--handler resharding-function.lambda_handler --runtime python3.8
{
"FunctionName": "my-resharding-function",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:1234567890:function:my-resharding-function",
"Runtime": "python3.8",
"Role": "arn:aws:iam::1234567890:role/resharding_function_role",
"Handler": "resharding-function.lambda_handler",
"CodeSize": 1040,
"Description": "",
"Timeout": 3,
"MemorySize": 128,
"LastModified": "2021-05-05T12:38:12.416+0000",
"CodeSha256": "PVlBjGTCr8I0a8gPUXuNk3sPxY7ku7fWvfRBwrylwfY=",
"Version": "$LATEST",
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "287bfee2-4259-489c-82c6-d5186d6c9874",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
}
作成したLambda関数をアラーム通知先のトピックに紐付けます。
$ dockeraws lambda add-permission --function-name my-resharding-function \
--statement-id 1 --action "lambda:InvokeFunction" \
--principal sns.amazonaws.com \
--source-arn arn:aws:sns:ap-northeast-1:1234567890:my-sample
{
"Statement": "{\"Sid\":\"1\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"sns.amazonaws.com\"},\"Action\":\"lambda:InvokeFunction\",\"Resource\":\"arn:aws:lambda:ap-northeast-1:1234567890:function:my-resharding-function\",\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:sns:ap-northeast-1:1234567890:my-sample\"}}}"
}
$ dockeraws sns subscribe \
--topic-arn arn:aws:sns:ap-northeast-1:1234567890:my-sample \
--protocol lambda --notification-endpoint \
arn:aws:lambda:ap-northeast-1:1234567890:function:my-resharding-function
{
"SubscriptionArn": "arn:aws:sns:ap-northeast-1:1234567890:my-sample:dc04eb52-1c99-4ce7-aafe-a0481846d4e6"
}
5.アラームのテスト
最初に作成したスクリプトを使ってテストしてみましょう。
スクリプト実行後、シャード数が増えていることを確認します。
$ dockeraws kinesis describe-stream-summary --stream-name my-sample
{
"StreamDescriptionSummary": {
"StreamName": "my-sample",
"StreamARN": "arn:aws:kinesis:ap-northeast-1:1234567890:stream/my-sample",
"StreamStatus": "ACTIVE",
"RetentionPeriodHours": 24,
"StreamCreationTimestamp": "2021-05-05T07:17:29+00:00",
"EnhancedMonitoring": [
{
"ShardLevelMetrics": []
}
],
"EncryptionType": "NONE",
"OpenShardCount": 1,
"ConsumerCount": 0
}
}
スクリプトを実行します。
$ dockeraws kinesis describe-stream-summary --stream-name my-sample
{
"StreamDescriptionSummary": {
"StreamName": "my-sample",
"StreamARN": "arn:aws:kinesis:ap-northeast-1:1234567890:stream/my-sample",
"StreamStatus": "ACTIVE",
"RetentionPeriodHours": 24,
"StreamCreationTimestamp": "2021-05-05T07:17:29+00:00",
"EnhancedMonitoring": [
{
"ShardLevelMetrics": []
}
],
"EncryptionType": "NONE",
"OpenShardCount": 64,
"ConsumerCount": 0
}
}
おわりに
サーバレスちょっとやってみようという気分で手を出すと大変な感じでした。
ありがとうございました。
料金のかかるKinesisは削除しました。