Step FunctionsでECSのタスク(Fargate)を動かす時に, 詰まったポイントを中心にtipsとしてまとめます. 僕自身あまり詳しくない+日本語苦手なので, 読みづらい部分があると思うんですけど, 我慢して読んでください.
はじめに
Lambdaの制限
Lamnda関数で時間のかかる処理を動かす時(APIを叩くとか?), 何かと問題があります. 例えば..
- 実行時間制限を設定しないといけないけど, 見込みを立てるのが難しい
- 15分を超える処理を実行できない
とか?
解決策?
そこでひとつのLamnda関数で処理を行うのではなく, 複数のLambda関数に分散させて,
- LambdaからLambdaを呼び出すとか
- Step FunctionsにLambda関数を組み込むとか
色々やってみたんですけど, 結局は実行時間の解決になってないし, そもそも実行時間に不安があるなら ECSタスク(Fargate)で動かしたらいいのでは? という結論に至りました.
今回のテーマ
で, そもそもStep Functions上で動かしていたLambda関数をECSタスク(Fargate)に移行させたという背景もあるので,
「Step Functionsから ECS Task(Fargate) を動かすときのTips」というテーマに至ります.
技術的な背景
Step Functions
- AWSが提供しているワークフローサービス
- FargateやLambdaなどAWS上のサービスをタスクとして, フローの中に組み込むことができる
ECS(Elastic Container Service)とは
AWSが提供するコンテナオーケストレーションサービス, kubernetesみたいな? ただしAWSにはEKS(Elastic Kubernetes Service)があるので, kubernetesとは違うはず.
構成要素
- クラスタ ... 実行の基盤 複数のサブネットにラップをかけるみたいなイメージ?
- サービス ... タスクをよりデーモンっぽくすることができる? ただしタスクでもデーモンのような動作は可能 ポートの開放などはサービスで設定する..たしか
- タスク ... タスク定義に基づいて起動するコンテナの集まり, docker-composeで起動する複数のコンテナ群みたいな?
- タスク定義 ... タスクを定義するテンプレートみたいなもの, docker-compose.ymlみたいな?
ECS Task(Fargate)
- ECS上で動作するコンテナ向けのサーバレスなサービス
LambdaとECS Task(Fargate)の比較
Fargate | Lambda | |
---|---|---|
実行時間 | 制約なし | 最大15分以内の実行時間, また自分で実行時間制限を指定する必要がある |
コスト | 実行時間に対してコストが発生. Lambdaに比べるとコストが高い | 必要な時のみ起動するため, コストが低い. 毎月のリクエストが100万件までは無制限で無料枠になるので, 少ないリクエスト数であれば実質無料 |
学習コスト | ECS(コンテナオーケストレーション)についての理解が必須. またDockerの知識も必要 | 実行OSや制限を抑えれば, その他の学習することはあまりない. コンテナイメージを実行することもあるが, Lambda用のコンテナイメージのDockerfileが用意されているため, 学習コストはほぼゼロ |
参考: スタートアップCTOが解説するECS Fargateのユースケース&Lambdaの使い分け!😎AWSのサーバーレスコンピューティングを使いこなしましょう🐕 -> https://www.ragate.co.jp/blog/articles/11589
- Lambda: 短い処理に向いている. 実行時間が長い処理や重い処理には向いていない
- Fargate: コンテナに対しての自由度が高いので, LambdaでできないことをFargateが包括しているというイメージ
個人的な使い方としては, 基本はLambdaを使って, Lambdaで無理だったらFarateを使うくらいの意識でいいのかな..という感じです. Lambdaでは制限が多いけど, Lambda専用のDockerfileが用意されており実装が楽, 逆にFargateは, 自由度が高いものの, コンテナイメージを自前で用意しないといけない..みたいな感じだと思います.
今回作ったもの
GitHub APIを叩いて, 指定したリポジトリ内のIssueの割合を可視化(matplotlib)+S3上に保存するフローを作成しています. 作った理由は特にありません.
全体の概要図
Step Function自体は, タスクとしてFargateを呼び出しているだけでシンプルな構成になっています.
基本的にECS側では, タスク定義の作成やロールの割り当て 以外はやることがない印象です.
タスク定義についても, docker-compose.ymlを書いてるみたいなイメージで, docker-compose.ymlを触ったことがある人ならすんなり設定を進められると思います(たぶん).
生成した円グラフ
Issueに割り当てられたラベルの割合を元に作成しています.
今回必要なもの
- ECS
- ECSクラスター
- ECSタスク定義
- ECSタスク実行ロール
- Step Functions
- VPCサブネット
- セキュリティグループ
- ECR
- 自作のコンテナイメージ
- APIキー
- GitHub APIキー
- etc...
つまづいたポイント
タスクとサービスの違い,というよりは使い方
結論 -> タスクは単体で動作させることができる.
とりあえず大事なのはサービスはタスクの上位概念ではないということかなと思います. 個人的にはここに詰まってしましました. どちらもタスク定義書を元に, コンテナを動かしてるだけです. なのでタスク単体でも動かすことができます.
参考: AWS ECSを構成する3つの概念(Cluster, Service, Task)https://scrapbox.io/keroxp/AWS_ECS%E3%82%92%E6%A7%8B%E6%88%90%E3%81%99%E3%82%8B3%E3%81%A4%E3%81%AE%E6%A6%82%E5%BF%B5%EF%BC%88Cluster,_Service,_Task%EF%BC%89
Fargate(ECSタスク)でのコマンドの上書き
まずは, Dockerにおけるコマンドの上書きについて,
Dockerにおけるコマンド+コマンドの必要性
$ docker run -it --rm --name aoimaru ubuntu:latest ls #最後の"ls"がコマンド
このコマンドを実行することで, ubuntu環境下でのlsコマンドを再現することができます <- ここポイント
要は, サーバを再現しているのではなく, 特定環境下でのコマンド(プロセス)を再現しているだけ. だからDockerでは1コンテナ1プロセスが推奨されています. ここが理解できれば, 上のコマンドで最後に"ls"を指定する必要がすんなり理解できるかなと思います.
コマンドは, Dockerfileでも指定するんですけど, コマンドラインではコマンドの上書きができます.
例えば, Dockerfileの末尾がこんなDockerfileがあるとします.
...
ENTRYPOINT ["python", "app.py"]
CMD ["aoimaru", "atcoder"]
この時デフォルトでは, $ python app.py aoimaru atcoder というランタイムが実行されます.
で, 例えば以下のようなコマンド
$ docker run -it --rm original original:latest EC-CUBE ec-cube
を実行すると, $ python app.py EC-CUBE ec-cube というランタイムが実行されます.
同じように, コマンドラインではなく, Fargateでコマンドを上書きした上で, 実行する時, ここに落とし穴があります
結論 -> 上書きを行うとき, スペースを空けない方がいい
[EC-CUBE,ec-cube] # OK
[EC-CUBE, ec-cube] # NG
↑ ここ
上記のようにスペースがある場合, コマンドライン的には, このような認識になってしまいます.
落とし穴が分かりやすいように, 引数はダブルクォーテーションで囲みます
$ python app.py "EC-CUBE" " ec-cube"
↑ ここに余計なスペースが入る
こうなると, 引数として正しく認識してくれない.
解決策 -> ランタイム側で無駄なスペースを削除する方法もあるが, 値を渡す段階でスペースを入れない方が無難です.
その他権限周りについて
基本的には, エラー(権限不足)が吐かれたタイミングでの対応でいいと思います.
ただし, Step Functionsについては, フローの中で他のAWSサービス(Farateなど)を実行するため"iam:PassRole"などが必要です
実装
実際のプログラム(Python)は省略します.
Fargateで動かすコンテナイメージのDockerfile
FROM python:3.9.0
RUN set -xe && \
pip install matplotlib \
boto3 \
PyGithub \
# matplotlibで日本語を使うためのライブラリ,
japanize-matplotlib && \
echo "backend: Agg" >> /etc/matplotlibrc && \
mkdir /opt/app
WORKDIR /opt/app
COPY ./src/app.py .
ENTRYPOINT ["python", "app.py"]
CMD [引数の指定]
Step Functions
{
"StartAt": "GithubIssue",
"Status" : {
"GithubIssue": {
"Type": "Task",
"Resource": "arn:aws:states::ecs:runTask",
"Parameters": {
"LaunchType": "FARGATE",
"Cluster": "$クラスターのARN",
"TaskDefinition": "$タスク定義のARN",
"Overrides": {
"ContainerOverrides": [
{
"Name": "$コンテナ名",
"Commands.$": "$.{Step Functionsでの入力Json}"
}
]
},
"NetworkConfiguration": {
"AwsvpcConfiguration": {
"Subnets": [
"$サブネットのID"
],
"SrcurityGroups": [
"$セキュリティグループのID"
],
"AssignPublicIp": "ENABLED"
}
}
},
"End": true,
"Comment": "任意のコメント"
}
}
}
おわりに
タスクとサービスの違いがややこしい印象です. コマンドの上書きのスペースについても発見するのにそこそこ時間を割いてしまいました.
この記事が, このようなちょっとした詰まりポイントの解決や, 初めてFargateを触ってみる際の参考になれば幸いです.
参考文献
-
スタートアップCTOが解説するECS Fargateのユースケース&Lambdaの使い分け!😎AWSのサーバーレスコンピューティングを使いこなしましょう🐕 -> https://www.ragate.co.jp/blog/articles/11589 [1]
-
AWS ECSを構成する3つの概念(Cluster, Service, Task ) https://scrapbox.io/keroxp/AWS_ECS%E3%82%92%E6%A7%8B%E6%88%90%E3%81%99%E3%82%8B3%E3%81%A4%E3%81%AE%E6%A6%82%E5%BF%B5%EF%BC%88Cluster,_Service,_Task%EF%BC%89 [2]