[AWS CDK]Step Functionsで既存のECSタスク定義をRunTaskする時にちょっとだけ詰まりました

CDKとAWS Step Functionsが非常に楽しいですね。今回はステートマシンにECSのRunTaskを組み込む際に、既存のタスク定義を利用する方法の一つを書きたいと思います!
2023.02.10

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。AWS事業本部コンサルティング部に所属している今泉(@bun76235104)です。

みなさんAWS CDKでコード書いていますか?少ないコードでやりたいことが実現できるし、自分の好きなプログラミング言語で書けるのは非常に楽しいですよね!

Step FunctionsもL2 Constructが提供されているので、以下のように簡単なコードで直感的にフロー(ステートマシン)を組めるので便利ですよね(コードはドキュメントより引用)。

const definition = submitJob
  .next(waitX)
  .next(getStatus)
  .next(new sfn.Choice(this, 'Job Complete?')
    // Look at the "status" field
    .when(sfn.Condition.stringEquals('$.status', 'FAILED'), jobFailed)
    .when(sfn.Condition.stringEquals('$.status', 'SUCCEEDED'), finalStatus)
    .otherwise(waitX));

class EcsRunTaskを使うことで、ECSのタスク実行を組み込むことも数行でかけて便利です。

ところでこの方法は既存(CDKで作る前から存在するリソース)のタスク定義では利用できないことをご存じでしたか?

私も少しここで詰まったので、私がとった解決策を提示したいと思います。

私の解決策とそこに至るまでの経緯

先に結論(CustomStateを使いました)

見出しの通りCustomStateを利用することでEcsRunTaskと同様のことを実現できました!

以下のようにASL(Amazon States Language)の仕様に合わせて実行したいタスクを定義してCustomStateを実行してみました。

import * as sfn from "aws-cdk-lib/aws-stepfunctions";

// パラメーターの型を定義
type RunTaskStateParam = {
  Type: "Task";
  Resource: "arn:aws:states:::ecs:runTask.sync";
  Parameters: {
    LaunchType: "FARGATE";
    Cluster: string;
    TaskDefinition: string;
    NetworkConfiguration: {
      AwsvpcConfiguration: {
        Subnets: string[];
        SecurityGroups: string[];
      };
    };
  };
};
// CustomStateでステートメントを作成
const step1Param: RunTaskStateParam = {
  Type: "Task",
  Resource: "arn:aws:states:::ecs:runTask.sync",
  Parameters: {
    LaunchType: "FARGATE",
    Cluster: "${既存のECSのClusterのArn}",
    TaskDefinition: "${既存のECSのタスク定義のID}", //リビジョン番号不要
    NetworkConfiguration: {
      AwsvpcConfiguration: {
        Subnets: ["${既存のサブネットのID}"],
        SecurityGroups: ["${既存のSecurityGroupのID}"],
      },
    },
  },
};
const step1State = new sfn.CustomState(this, "RunExsitingTaskdef", {
  stateJson: step1Param,
});
const step2 = new sfn.Pass(this,'Step2')
// ステートマシンを作成
const definition = step1State.next(step2);
const state = new sfn.StateMachine(this, "", {
  definition,
});

※ 上記は例のため割愛していますが、既存のタスク定義の情報は SSM Parameter Storeなどを利用するとセキュアで取り回しもしやすいかと思います。

では、ここに至るまでの経緯も記載したいと思います。

既存のタスク定義でEcsRunTaskのコンストラクトを利用できないことに気づく

CDKでStep Functionsを使うのは初めてではなく、EcsRunTaskのL2 Constructの存在を知っていました。

そのため、今回も既存のタスク定義をfromTaskDefinitionArnで取得して以下のようにEcsRunTaskに渡せば終わりかな?と想像していました。

// 以下のようなコードを想像していました
// 実際は動作しないためご注意ください
const importTaskdef = TaskDefinition.fromTaskDefinitionArn(
  this,
  "importedTaskdef",
  "${タスク定義のArn}"
);
const step1State = new aws_stepfunctions_tasks.EcsRunTask(this, "Step1 RunEcsTask", {
  cluster: clusterArn,
  taskDefinition: importTaskdef,
  // 他のパラメーターは省略
})

しかし実際は fromTaskDefinitionArn で返されるのはITaskDefinitionであり、EcsRunTaskが求めているのは TaskDefinition 型でした。

公式のドキュメントを参照すると以下のように記載されています。

Note: this must be TaskDefinition, and not ITaskDefinition, as it requires properties that are not known for imported task definitions

はっきりとITaskDefinitionは利用できないと書いてありますね。

もう少し経緯をGitHubで調べていると以下のIssueを発見しました。

以下のようにコメントされています。

unfortunately, we concluded that there's no way to use an imported task definition in RunEcsEc2Task - it requires information that is simply not known for imported objects. Sorry about that. I'll be submitting a PR updating our documentation to make that 100% clear.

公式ドキュメントにも「インポートしたタスク定義だとプロパティが足りない」と記載されていましたが、同じ理由のようですね。

この時のPullRequestによりITaskDefinitionは利用できないとドキュメントにも明記されたようです。

CustomStateの存在にたまたま気づく

インポートしたタスク定義をEcsRunTaskのConstructで使えないのはわかったけど、じゃあどうすれば良いのさ。L1のConstructを使わないといけないのかなぁ?」という考えがよぎりました。

しかし「いや何か方法があるだろう」とドキュメントを眺めているとCustomStateをすぐに発見できました。

発見に際して劇的な何かや架空の同僚がアドバイスしてくれたわけではなくすみません。

今回のケースに限らず、ASL(Amazon States Language)でTaskなどを定義したい場面があるだろうと探したら見つかったというわけです。

最後におまけ:せっかくなのでPull Requset出してみた

私は思いました。

その日はコンディションが良かったのでたまたま↑の考えに至っただけで、コンディションが悪ければ代替案に気づかないこともあるのでは?」と。

もしかしたら世界に一人くらい私と同じように困っている人がいるかもしれません。

事実こちらのIssueでは2020年ごろにも以下のように、「じゃあどうすれば良いの?」という類のコメントがされています。

How to tackle this problem? or any other method

ということで、どうせドキュメントに数行追記するだけだし、こちらのPull Requestを出してみました。

OSSへのコントリビュートの手順は他の同僚もたくさん書いてくれているので割愛します。

AWS CDKの場合READMECONTRIBUTING.mdが充実しているので、頑張って英語を読めばなんとかなると思います。

小さなドキュメント追記でしたが、Pull Requestを作成して数時間のうちにマージされました!

小さな貢献だとしても、結構充実感があり面白かったので、ぜひ「こうだったら良いのになぁ」と思うことはIssueを立てたり、Pull Requestを作ってみてください!

以上今泉でした。