Closed7

定期的に全てのEC2インスタンスをオフにするLambdaを書く

d yoshikawad yoshikawa

現在isuconの練習をしていて、うっかりEC2インスタンスを上げっぱなしにしてしまうのを防ぎたい。

ちゃんと練習環境を作ろうとするとlargeとかxlargeのインスタンスを3つみたいな感じになる。これらを長時間起動し続けると(個人の勉強代としては)洒落にならない課金になってしまう。

まず手元にCDK環境を作る。

cdk initしよう|AWS CDK×LINE BOTハンズオン~アプリとインフラをコード管理しよう~

mkdir isucon-env-auto-stopper
cd isucon-env-auto-stopper
npx cdk init --language typescript
d yoshikawad yoshikawa

プロジェクト直下にLambda関数用のディレクトリとファイルを作成。

mkdir lambda-fns
touch lambda-fns/stop-ec2-instances.ts

業務で真面目にやるならCDKコードとLambdaアプリケーションコードはリポジトリを分けるか、npm workspacesを使ったモノレポ構成にするなどで別管理するが、今回は手早さを重視してCDKプロジェクトの下にLambdaコードを置いてしまう。

AWS SDK v3をインストールする。

npm i @aws-sdk/client-ec2
d yoshikawad yoshikawa

prettierを入れておく。

npm i -D prettier

configを作る。

touch .prettierrc.json

中身は以下。すべてデフォルト設定とする。

{}
d yoshikawad yoshikawa

CDKコード:

適当に、6時間置きにLambdaを実行するとした。

lib/isucon-env-auto-stopper-stack.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";

export class IsuconEnvAutoStopperStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const isuconEnvAutoStopperFn = new cdk.aws_lambda_nodejs.NodejsFunction(
      this,
      "isuconEnvAutoStopperFn",
      {
        entry: "lambda-fns/stop-ec2-instances.ts",
        timeout: cdk.Duration.seconds(30),
      },
    );
    isuconEnvAutoStopperFn.addToRolePolicy(
      new cdk.aws_iam.PolicyStatement({
        effect: cdk.aws_iam.Effect.ALLOW,
        actions: ["ec2:DescribeInstances", "ec2:StopInstances"],
        resources: ["*"],
      }),
    );

    new cdk.aws_events.Rule(this, "isuconEnvAutoStopperRule", {
      schedule: cdk.aws_events.Schedule.rate(cdk.Duration.hours(6)),
      targets: [
        new cdk.aws_events_targets.LambdaFunction(isuconEnvAutoStopperFn),
      ],
    });
  }
}

Lambdaコード:

lambda-fns/stop-ec2-instances.ts
import {
  DescribeInstancesCommand,
  EC2Client,
  StopInstancesCommand,
} from "@aws-sdk/client-ec2";

const ec2Client = new EC2Client();

export const handler = async () => {
  const describeOutput = await ec2Client.send(
    new DescribeInstancesCommand({
      Filters: [{ Name: "instance-state-name", Values: ["running"] }],
    }),
  );
  const instances = describeOutput.Reservations?.map(
    ({ Instances }) => Instances,
  ).flat();
  const instanceIds = instances?.map((instance) => instance?.InstanceId);
  if (instanceIds == null) {
    throw new Error("instanceIdsがundefinedです");
  }

  const instanceIdsWithoutUndefined = instanceIds.filter<string>(
    (instanceId): instanceId is string => instanceId != null,
  );

  await ec2Client.send(
    new StopInstancesCommand({
      InstanceIds: instanceIdsWithoutUndefined,
    }),
  );
};

esbuildを入れておく。

npm i -D esbuild

デプロイ:

npx cdk deploy
このスクラップは2023/08/28にクローズされました