Windowsでしか動かないコマンドラインツールをAWS上で1日1回動かしたい
やりたいこと
Windowsでしか動かないCLIアプリ1日1回動かしたい。
動かしたいWindowsアプリの状況
- 原則CLIのみで動作だが、初期設定時などのタイミングでGUI操作が必要になる。
- 今時ActiveXで提供されているライブラリを使わないといけない都合。GUIはライブラリ側で表示してる。
- 1回あたりの動作時間は数分程度。
- アプリの出力結果はローカルファイルやS3に格納できるが、最終的にはS3に格納したい。
作戦
- Windows EC2で動作させる。
- 初期設定時などのGUI操作はRDP接続で実施。
- 1日1回の実行はEventBridgeを用いてスケジュール化。
- Windows内でのタスクスケジューラを使って、該当アプリを含むバッチファイルを起動する。
- 繰り返し間隔を短くして、多重実行しない設定にすれば、EC2起動後すぐに走らせられるのではないかと推測。
- タスクスケジューラから起動するバッチファイルでは、処理の最後にシャットダウンを走らせる。
- 念の為、EventBridgeで、起動時刻+1時間後くらいにEC2をシャットダウンするようスケジュールしておく。
本質じゃないけどちょっとメモ。
CDKにて以下のイメージでEC2を作成
const machineImage = new ec2.WindowsImage(
ec2.WindowsVersion.WINDOWS_SERVER_2022_JAPANESE_FULL_BASE,
);
キーボードが英語レイアウト前提になっていたので、
Windowsの設定→時刻と言語→言語→優先する言語→日本語→オプション→ハードウェアキーボードレイアウト
で
「日本語キーボード (106/109 キー)」
に変更後再起動。
でも自分のMacからのRDP接続では相変わらず英語レイアウトになっている。
を参考にしてレジストリ変更することで日本語キーボードとして入力できた。これまた本質ではないけど。。
↑のイメージで作ったWindows EC2ってAWS CLIは入ってないんですね。
手動でAWS CLIをインストールして対応。
これまた本質ではないけど。。
キーペアは以下のようにすれば、作成した上でEC2インスタンスに紐付け可能。
※ここではキー名はスタック名にしてしまっているけど、そこはよきに。
const keyPair = new ec2.CfnKeyPair(this, 'KeyPair', {
keyName: this.stackName,
});
new ec2.Instance(this, 'Instance', {
//もろもろ省略
keyName: keyPair.ref,
});
キー自体に関しては、パラメータストア「/ec2/keypair/キーID」に格納されてる。
Windows内でのタスクスケジューラを使って、該当アプリを含むバッチファイルを起動する。
繰り返し間隔を短くして、多重実行しない設定にすれば、EC2起動後すぐに走らせられるのではないかと推測。
以下の設定でいけそう。
- 全般
- ユーザーがログオンしているかどうかにかかわらず実行する
- トリガー
- 設定
- 1回
- 詳細設定
- 繰り返し間隔:5分
- 継続期限:無期限
- 設定
- 設定
- タスクが既に実行中の場合に適用される規則:新しいインスタンスを開始しない
タスクスケジューラから起動するバッチファイルでは、処理の最後にシャットダウンを走らせる。
シャットダウンをバッチの最後に入れるのはやめた。
メンテナンスが困難になりそうなので。
1日1回の実行はEventBridgeを用いてスケジュール化。
念の為、EventBridgeで、起動時刻+1時間後くらいにEC2をシャットダウンするようスケジュールしておく。
CDKにてEC2の起動・停止を実装。
普通に実装するなら、EventBridge Schedulerを使うのが筋なんでしょうけど、まだL2 Constructがない…
じゃぁ、昔ながらのEventBridgeルールでやるか…となったのですが、EventBridgeルールのターゲットとしてSSMオートメーションを使おうとすると、やっぱりL2 Constructがない…
LambdaでEC2の起動・停止を書いてもいいけど、そのためにLambda使うのもなんだな…ということで、結局、EventBridgeルール→SSMオートメーションでのEC2起動・停止をL1 Construct使って実装することにしました。
const startStopRole = new iam.Role(this, 'StartStopRole', {
assumedBy: new iam.ServicePrincipal('events.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AmazonSSMAutomationRole'),
],
});
new events.CfnRule(this, 'StartRule', {
scheduleExpression: 'cron(30 12 * * ? *)',
targets: [
{
arn: `arn:aws:ssm:${region}::automation-definition/AWS-StartEC2Instance:$DEFAULT`,
id: 'StartEC2Instance',
input: `{"InstanceId": ["${instance.instanceId}"]}`,
roleArn: startStopRole.roleArn,
},
],
});
new events.CfnRule(this, 'StopRule', {
scheduleExpression: 'cron(45 12 * * ? *)',
targets: [
{
arn: `arn:aws:ssm:${region}::automation-definition/AWS-StopEC2Instance:$DEFAULT`,
id: 'StopEC2Instance',
input: `{"InstanceId": ["${instance.instanceId}"]}`,
roleArn: startStopRole.roleArn,
},
],
});