なぜansible?
ansible2.2からaws lambdaがデプロイできるようになったので試してみました
良かったこと
- 自分がansibleを普段使っていたので学習コスト低かった.
- host: localhost, connection: localにするとローカルで動かすスクリプトとして使える.
- lambdaに同梱する 追加パッケージの用意, zipで固めるなどもansibleで記述できる.
- テンプレート使っていろいろ処理・変数を取り回せる
- vaultで秘匿情報とか埋め込める
- ansibleのモジュールを使うとaws cliを叩くより扱いやすい.
辛かったこと
- まだansible付属のmoduleだけじゃ無理なことが多い
- 足りない部分はaws cliを叩くことに
- 幸運なことにaws cliのレスポンスがjsonなのでansibleで加工しやすい
- 各モジュールの返り値(registerで受け取れる物) の仕様が一定じゃないので辛い時がある.
とりあえず試しにansibleで毎日12時にawsの料金をslackに通知するlambdaをデプロイしてみます.
(ここではlambdaのコードには触れてないです. githubには入れてあります)
実験環境:ansible==2.2.0.0
コード:https://github.com/kikusu/lambda-aws-billing-to-slack
zipでコードを固める
自分はLamdaに追加パッケージを入れる場合は.gitignoreし易いようにsite-package
を切ってそこにれています.
├ site-packages/
│ └ (追加pkg)
├ lambda_function.py
└ requirements.txt
pipで必要なpkgを取ってきた後全部zipに圧縮します.
pip install -r requirements.txt -t site-package
zip -ur lambda.zip *
ansibleで書くとこんな感じ
- name: install required pkg
pip:
requirements: "requirements.txt"
extra_args: "-t site-packages"
chdir: "{{ source_dir }}"
- name: create lambda.zip
shell: zip -ur lambda.zip *
register: create_lambda_zip
changed_when: create_lambda_zip.rc != 12
failed_when: create_lambda_zip.rc not in [0, 12]
args:
chdir: "{{ source_dir }}"
zipに-u
つけているのは更新がない時zipが変わらないようにしてlambdaのversionupのデプロイが走らないようにするためです.
Lambda用にRoleを作る
ansibleのiam
, iam_policy
を使ってlambdaを実行できるroleを作ります.
- name: create role
iam:
iam_type: role
name: "{{ role_name }}"
state: present
trust_policy:
Version: '2012-10-17'
Statement:
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: lambda.amazonaws.com
- Action: sts:AssumeRole
Effect: Allow
Principal:
Service: events.amazonaws.com
- name: attatch policy
iam_policy:
iam_type: role
iam_name: "{{ role_name }}"
state: present
policy_name: "{{ policy_name }}"
policy_json:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: '*'
- Effect: Allow
Action:
- cloudwatch:GetMetricStatistics
Resource: '*'
- Effect: Allow
Action:
- s3:GetObject
Resource: '*'
- name: get role arn
command: "aws iam get-role --role-name {{ role_name }}"
changed_when: false
register: role
- set_fact:
role_arn: "{{ (role.stdout|from_json).Role.Arn }}"
各statementにlambdaを実行するために必要な権限を追加しておいてください.
roleを作ったあとroleのarnをawsコマンドで取得します.レスポンスがjsonなので簡単にエスケープすることができます.
v2.2.0.0現在, role作成時のみiam moduleからARNが取得できる状態になっており2回以降はモジュールからARNが取得できないのでawsコマンドで取得するようにします.
LambdaをDeployする
lambdaモジュールを使ってデプロイします. 簡単ですね.
- name: create lambda function
lambda:
name: "{{ lambda_name }}"
zip_file: "{{ source_dir }}/lambda.zip"
handler: lambda_function.lambda_handler
runtime: python2.7
role: "{{ role_arn }}"
timeout: 5
- name: get lambda arn
command: "aws lambda get-function --function-name {{ lambda_name }}"
changed_when: false
register: lambda
- set_fact:
lambda_arn: "{{ (lambda.stdout|from_json).Configuration.FunctionArn }}"
lambdaモジュールはlambda関数に変更が無かろうがARNを取得することができます.
ただしv2.2.0.0現在,その取得できるARNの値が変更の有無で違います.
# 変更無
"function_arn": "arn:aws:lambda:*******:********:function:AWSBillingToSlack",
# 変更有
"function_arn": "arn:aws:lambda:*******:********:function:AWSBillingToSlack:2",
変更があった場合はARNの最後にバージョンの数字がついてきます.orz
changedかどうかでarnの値を整形する方法もあると思いますが 一旦lambdaの方もawsコマンドを叩いてARNを取得するようにしました.
定時実行の設定をする
後はcloudwatch eventでcronイベントをセットしてlambda実行権限を付与すれば終わりです.
cronの時間設定はUTCなのでJSTと違うことに注意です.
add-permissionはモジュールがなさそうなのでawsコマンドを使って実行します.
- name: create cloudwatch event
cloudwatchevent_rule:
name: "{{ lambda_name }}"
schedule_expression: cron(0 3 * * ? *)
description: "{{ lambda_name }} cron"
role_arn: "{{ role_arn }}"
targets:
- id: "{{ lambda_name }}"
arn: "{{ lambda_arn }}"
register: event
- name: aws lambda add-permission
command: >-
aws lambda add-permission
--function-name {{ lambda_name }}
--statement-id 'cron'
--action "lambda:InvokeFunction"
--principal events.amazonaws.com
--source-arn {{ event.rule.arn }}
register: add_permission
changed_when: "{{ 'provided already exists' not in add_permission.stderr }}"
failed_when: "{{ add_permission.rc != 0 }} and {{ 'provided already exists' not in add_permission.stderr }}"
やってみて
- デプロイ前に事前ビルド(zipに固めるところまで)できるのは良い
- テンプレートで role名やarnを引き回せるのは楽
- 各モジュールのレスポンスの方針が一定だと嬉しい (変更がある場合でもない場合でもARNを返すとか)
- シェルスクリプトより保守しやすい