【登壇】Interact 2019で紹介したデモ(PowerShell Lambda)の解説 #msinteract19 #IN06
しばたです。
先週登壇したInteract 2019で紹介したデモについて、環境構築の部分をセッション中では説明できなかったので本記事で補足します。
デモの内容について
デモの内容については上記リンク先の記事にもありますが、クラウド管理の例としてPowerShell Lambdaを使い特定のタグ(AutoShutdown = true
)が付いたEC2インスタンスを停止させる関数を紹介しました。
内容としては割とあるあるな奴です。
PowerShell Lambdaの基本構成
最初にAWS Lambdaの基本として以下のリソースが必要となります。
- Lambda関数本体
- 関数を起動するためのトリガー
- 関数を実行する為の権限(IAMロール)
大抵の場合上記リソースはすべてCloudFormationで作成することができます。
私も最初は必要なリソースをすべてCloudFormationで作成しようとしたのですが、関数本体のリソースであるAWS::Lambda::Function リソースのCodeプロパティでソースコードを直接記述可能(ZipFIle
を選択可能)なのがNode.jsとPythonであり、その他の言語で事前にS3に関数のコードをアップロードしておく必要があるため、その手間を考えるとCloudFormationを使わず、AWSLambdaPSCoreモジュールのPublish-AWSPowerShellLambda
コマンドレットを使った方が良いだろうと判断しました。
このため紹介したデモでは事前に最低限必要なリソースのみCloudFormationで作成する手順としています。
ちなみになのですが、AWS CLIのaws cloudformation packageコマンドを使うとローカルファイルをS3にアップロードしつつスタックを作ることが可能なのですが、これに等価なPowerShellコマンドレットは提供されていませんでした...
デモ環境の作成
ここからデモ環境の作成手順を説明していきます。
1. CloudFormationの実行
- https://github.com/stknohg/PowerShellSamples/blob/master/community/interact2019/lambda-base-cfn.yaml
にあるCloudFormationテンプレートをPowerShellから実行して関数の実行に必要な
- IAMロール
- CloudWatchイベント
を作成します。
Import-Module AWSPowerShell.NetCore
Set-DefaultAWSRegion -Region ap-northeast-1
Set-AWSCredential -ProfileName "set your profile"
# 事前準備
# CFnからあらかじめ必要なリソースを作成
$params = @{
StackName = "interact2019-lambda-demo-base";
TemplateBody = (Get-Content -LiteralPath '.\lambda-base-cfn.yaml' -Raw);
Parameter = &{
$p1 = New-Object Amazon.CloudFormation.Model.Parameter
$p1.ParameterKey = "BaseName"
$p1.ParameterValue = "interact2019-lambda-demo"
return @($p1)
};
Capability = "CAPABILITY_NAMED_IAM"
}
New-CFNStack @params
New-CFNStackコマンドレットで新しいスタックを作成することができます。
-TemplateBody
パラメータにテンプレートの内容を設定する必要があるのでGet-Content
でファイルから内容を読み込んでいます。
テンプレートに引き渡すパラメーターは-Parameter
で指定するのですが、これがAmazon.CloudFormation.Model.Parameter
型のオブジェクトとなっており少し面倒です。
今回はIAMロールを作成するので-Capability
パラメーターにCAPABILITY_NAMED_IAM
をセットしておく必要もあります。
コマンドレットを実行するとスタックIDを返しますので後は結果を確認すればOKです。
2. 関数本体のデプロイ
以前にも説明したとおり、Lambda関数をデプロイするにはPublish-AWSPowerShellLambda
コマンドレットを使います。
Import-Module AWSLambdaPSCore
$params = @{
Name = "interact2019-lambda-demo";
ScriptPath = ".\lambda\Function.ps1"
IAMRoleArn = ("arn:aws:iam::{0}:role/interact2019-lambda-demo-role" -f ((Get-STSCallerIdentity).Account))
}
Publish-AWSPowerShellLambda @params
事前準備などについては以前の記事をご覧ください。
今回は前項の手順でIAMロールを作成済みですので-IAMRoleArn
パラメーターにIAMロールのARNを指定してあります。
Get-STSCallerIdentity
コマンドレットで呼び出し元のアカウントIDなどの情報を取得しています。
するとスクリプトを含んだアセンブリのコンパイルとデプロイを行ってくれます。
この状態でLambda関数の定義を確認すると下図の様になります。
関数本体とIAMロールは表示されましたが、トリガー(今回はCloudWatchイベント)がまだ関数と紐づいていません。
3. 関数にトリガーを紐づける
そこで続けてAdd-LMPermissionコマンドレットを使い最初に作成したCloudWatchイベントからLambda関数を呼び出すための許可(紐づけ)をしてやります。
# CloudWatch events のトリガーを許可
$params = @{
FunctionName = "interact2019-lambda-demo";
Action = "lambda:InvokeFunction";
Principal = "events.amazonaws.com";
SourceArn = ("arn:aws:events:ap-northeast-1:{0}:rule/interact2019-lambda-demo-event" -f ((Get-STSCallerIdentity).Account));
StatementId = "interact2019-lambda-demo-event";
}
Add-LMPermission @params
トリガーの種類によって使うパラメーターは若干変わりますがCloudWatchイベントの場合はこんな感じです。
実行すると結果のJSONが返されます。
ここはあまりPowerShellらしくない感じです。
これで関数とトリガーが紐づきましたが、まだトリガー(CloudWatchイベント)側に実行する関数の指定が無いのでエラーがでています。
こちらはCloudWatchイベントの設定ですので、Write-CWETargetコマンドレットを使いターゲット指定をしてやります。
-Target
パラメーターはAmazon.CloudWatchEvents.Model.Target
型のオブジェクトを指定してやる必要があり、関数のARNとリビジョンIDが必要になるのでGet-LMFunctionConfigurationを使い取得しています。
# CloudWatch eventsのターゲットにLambda関数を追加
$params = @{
Rule = "interact2019-lambda-demo-event";
Target = &{
$func = Get-LMFunctionConfiguration -FunctionName "interact2019-lambda-demo"
$target = New-object Amazon.CloudWatchEvents.Model.Target
$target.Arn = $func.FunctionArn
$target.Id = $func.RevisionId
return $target
};
}
Write-CWETarget @params
これですべての作業が完了しトリガー定義のエラーも解消されています。
関数本体について
最後に関数本体のコードについて簡単に解説します。
Lambda関数内でのモジュールの利用
Lambda関数内でPowerShellモジュールを利用する場合は#Requires -Modules
文を記述します。
#Requires -Modules @{ModuleName='AWSPowerShell.NetCore';ModuleVersion='3.3.522.0'}
この記述によりデプロイ時に該当のモジュールを同梱し、関数実行前にロードする様になります。
今回はEC2インスタンスを停止する関数ですのでAWSPowerShell.NetCoreモジュールを使っています。
該当EC2インスタンスの取得
Get-EC2Instanceの-Filter
パラメーターを指定することでAutoShutdownタグ
の値がtrue
となるEC2インスタンスを取得しています。
$instacnes = (Get-EC2Instance -Filter @{Name = 'tag:AutoShutdown'; Value = 'true' }).Instances
if ($null -eq $instacnes -or $instacnes.Count -eq 0) {
Write-Host 'No instance found.'
return 0
}
EC2インスタンスのシャットダウン
ループ内でGet-EC2InstanceStatusを使いインスタンスの状態を取得、状態がrunning
であればStop-EC2Instanceを使いインスタンスを停止しています。
# ループしてシャットダウン
foreach ($i in $instacnes) {
$status = Get-EC2InstanceStatus -InstanceId $i.InstanceId
if ($status.InstanceState.Name -eq 'running') {
Write-Host ("Attempt to stop EC2 instance {0}." -f ($i.InstanceId))
Stop-EC2Instance -InstanceId $i.InstanceId
}
else {
Write-Host ("EC2 instance {0} is not running. Skip to shutdown." -f ($i.InstanceId))
}
}
余談ですがStop-EC2Instance
は-Terminate
パラメーターを使用するとインスタンスを終了(削除)することができます。