この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは。AWS事業本部コンサルティング部に所属している今泉(@bun76235104)です。
私はこれまでIaC(Infrastructure as Code)ツールとしてTerraformを使うことが多かったのですが、今回CDKを利用する機会に恵まれました。
そこで、皆大好き AWS Step FunctionsをCDKで組んでみようということで、ECSのタスク定義を動かしてみました。
フローについては後述しますが、以下のようなフローのステートマシンを作成します。
なお、以降の検証は以下環境で実施していますのでご留意ください。
Key | Value |
---|---|
OS | macOS Monterey |
Node Version | 18.12.1 |
CDK Version | 2.37.1 |
言語 | TypeScript |
下準備 VPCなどのリソース作成
今回ECSのRun Taskを実行させるため、以下のような構成を作成しました。
StepFunctionsからECS on Fargateでタスクを実行する形です。
今回はStepFunctionsの作成がメインとなるためVPCなどのリソースのコードについては割愛しますが、コード全文はこちらのリポジトリで閲覧できます。
なお今回はプライベートサブネットからFargateでコンテナを起動できるように、以下VPCエンドポイントを追加しています。
Fargate利用時に必要なVPCエンドポイントについてはこちらのブログをご確認ください。
CDKでStep Functions を定義する
作るものの確認
StepFunctionsを作成するためのクラスを定義していきます。
再掲となりますが、今回作成するStep Functionsのステートマシンは以下のとおりです。
Step1 OKRun
では以下のような定義のDockerコンテナを動かします。
FROM alpine:latest
ENTRYPOINT [ "ash", "-c", "echo shuld end with code0 && true"]
true
コマンドにより、終了コード0で正常にコンテナが終了するイメージです。
はたまた、Step2 NGRun
では以下のような定義のDockerコンテナを動かします。
FROM alpine:latest
ENTRYPOINT [ "ash", "-c", "echo shuld end with code1 && false"]
こちらは対照的に false
コマンドにより終了コード1でコンテナが異常終了するイメージです。
Step1 Fail
と Step2 Fail
はそれぞれのステップで失敗した場合の例外処理となっています。
まとめると以下のようになります。
Step1 OKRun
ではコンテナ(Fargate Task)が正常終了するはず- 正常終了するので
Step1 Fail
には進まないでほしい
- 正常終了するので
Step2 NGRun
ではコンテナ(Fargate Task)が異常終了する(終了コード1のため)- 異常終了するので
Step2 Fail
に進んでほしい
- 異常終了するので
進行してほしいルートはこうです。
やりたいことを確認できたので、CDKのコードをみていきます。
FargateタスクをStepFunctionsで実行するためのコード
CDKから必要なコードを抜粋した部分は以下のとおりです。
まず、StepFunctionsでFargateのタスクを実行するにあたり、VPCなどの情報が必要であるため、必要なパラメータを以下interfaceにまとめています。
lib/resources/interfaces/step_functions_param.ts
import { Construct } from 'constructs';
import { Cluster } from 'aws-cdk-lib/aws-ecs';
import { Vpc } from 'aws-cdk-lib/aws-ec2';
import { FargateTaskDefinition } from 'aws-cdk-lib/aws-ecs';
export interface StepFunctionsParam {
scope: Construct;
vpc: Vpc;
okTaskDef: FargateTaskDefinition;
ngTaskDef: FargateTaskDefinition;
cluster: Cluster;
}
okTaskDef
がStep1 OKRun
で実行させたいコンテナ実行情報を持つタスク定義ngTaskDef
がStep2 NGRun
で実行させたいコンテナ実行情報を持つタスク定義
となっています。
また、ECSクラスタの情報も cluster
パラメータで渡しています。
次にこれらパラメータを受け取って、StepFunctionsのステートマシンを作成するクラスがこちらです。
lib/resources/step_functions.ts
import { Construct } from 'constructs';
import { aws_stepfunctions_tasks } from 'aws-cdk-lib';
import {
Errors,
IntegrationPattern,
Pass,
TaskStateBase,
Fail,
StateMachine,
} from 'aws-cdk-lib/aws-stepfunctions';
import { EcsFargateLaunchTarget } from 'aws-cdk-lib/aws-stepfunctions-tasks';
import { StepFunctionInvokeAction } from 'aws-cdk-lib/aws-codepipeline-actions';
import { SecurityGroup } from 'aws-cdk-lib/aws-ec2';
import { TaskDefinition } from 'aws-cdk-lib/aws-ecs';
import { StepFunctionsParam } from './interfaces/step_functions_param';
import { Resource } from './abstract/resource';
export class StepFunc extends Resource {
readonly params: StepFunctionsParam;
constructor(params: StepFunctionsParam) {
super();
this.params = params;
}
public createResources() {
const sg = this.createSG();
// Step1
const okTask = this.getRunTaskParam('Step1 OKRun', this.params.okTaskDef, [
sg,
]);
const failStep1 = new Fail(this.params.scope, 'Step1Fail'); okTask.addCatch(failStep1);
// Step2
const ngTask = this.getRunTaskParam('Step2 NGRun', this.params.ngTaskDef, [
sg,
]);
const failStep2 = new Fail(this.params.scope, 'Step2Fail');
ngTask.addCatch(failStep2);
const definition = okTask.next(ngTask);
// createSteate
new StateMachine(this.params.scope, 'ExampleStepStateMachine', {
definition,
});
}
private createSG(): SecurityGroup {
// RunTaskの際に利用するSG
const sg = new SecurityGroup(this.params.scope, 'RunTaskSG', {
vpc: this.params.vpc,
securityGroupName: 'stepExampleRuntaskSG',
allowAllOutbound: true,
});
return sg;
}
private getRunTaskParam(
id: string,
taskdef: TaskDefinition,
sg: [SecurityGroup]
) {
const runTask = new aws_stepfunctions_tasks.EcsRunTask(
this.params.scope,
id,
{
integrationPattern: IntegrationPattern.RUN_JOB,
cluster: this.params.cluster,
taskDefinition: taskdef,
assignPublicIp: false,
launchTarget: new EcsFargateLaunchTarget(),
subnets: {
subnets: this.params.vpc.isolatedSubnets,
},
securityGroups: sg,
}
);
return runTask;
}
}
createResources
の部分が主要ロジックです。
public createResources() {
// ECS RunTaskで利用するセキュリティグループを作成
const sg = this.createSG();
// Step1でタスク定義から実行するためのジョブを追加
const okTask = this.getRunTaskParam('Step1 OKRun', this.params.okTaskDef, [
sg,
]);
// Step1の例外処理を追加
const failStep1 = new Fail(this.params.scope, 'Step1Fail');
okTask.addCatch(failStep1);
// Step2でタスク定義をもとに実行するためのジョブを追加
const ngTask = this.getRunTaskParam('Step2 NGRun', this.params.ngTaskDef, [
sg,
]);
// Step2の例外処理を追加
const failStep2 = new Fail(this.params.scope, 'Step2Fail');
ngTask.addCatch(failStep2);
const definition = okTask.next(ngTask);
// createSteate
new StateMachine(this.params.scope, 'ExampleStepStateMachine', {
definition,
});
}
呼び出す側のクラスを抜粋すると以下のようになっています。
lib/step_example-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Ecr } from './resources/ecr';
import { Network } from './resources/network';
import { Ecs } from './resources/ecs';
import { StepFunc } from './resources/step_functions';
export class StepExampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// ↑ここより上でVPCなどのリソースを作成しているが省略
// StepFunc
const sf = new StepFunc({
scope: this,
okTaskDef: ecs.okTaskDef,
ngTaskDef: ecs.ngTaskDef,
cluster: ecs.cluster,
vpc: network.vpc,
});
sf.createResources();
}
}
呼び出し部分を省略せずに記載すると以下のようになっています。
VPCやECRなどの必要なリソースを先に作成して、StepFunctionsのリソース作成に利用しています。
lib/step_example-stack.ts
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { Ecr } from './resources/ecr';
import { Network } from './resources/network';
import { Ecs } from './resources/ecs';
import { StepFunc } from './resources/step_functions';
export class StepExampleStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// ECR
const ecr = new Ecr(this);
ecr.createResources();
// Network
const network = new Network(this);
network.createResources();
// ECS taskdef
const ecs = new Ecs({
scope: this,
okImage: ecr.getOKImage(),
ngImage: ecr.getNGImage(),
vpc: network.vpc,
});
ecs.createResources();
// StepFunc
const sf = new StepFunc({
scope: this,
okTaskDef: ecs.okTaskDef,
ngTaskDef: ecs.ngTaskDef,
cluster: ecs.cluster,
vpc: network.vpc,
});
sf.createResources();
}
}
以上がStepFunctionsに関連する部分の抜粋となります。
繰り返しとなりますがコード全文を確認したい場合以下リポジトリをご確認ください。
CDKでリソース作成 & StepFunctionsのステートマシンを実行してみる
それではCDKを実行してみます。
cdk synth
これによりCloudFormationの定義を確認し、問題なさそうですので以下コマンドでデプロイします。
cdk deploy
無事リソースが作成されました。
さっそく実行してみます!
始まりました...
想定どおり、Step2 NGRun
でコンテナが異常終了したため、Step2 Fail
で例外をキャッチしています!
なんとか意図どおりの挙動となりました。
まとめ
- CDKでFargateのタスクを実行するStepFunctionsのステートマシンを作ってみた
TypeScript+CDK
をVSCodeで開発する体験が良すぎる- 型補完がやばい(語彙力)
今回サンプルのコードは今時点の私が書いたコードであるため、美しない点が多々あると思いますので、是非もっと良い構成を考えて教えてください!