Pairs における batch の EKS 移行

Hikaru Sasamoto
Eureka Engineering
Published in
11 min readDec 8, 2022

この記事は、「Eureka Advent Calendar 2022」8日目の記事です。

はじめまして。今年の7月に eureka の SRE チームにジョインした @hisamouna です。

大学時代はアメリカンフットボール漬けだったパワー系エンジニアです。
ポジションは Safety でした。

本ブログでは、私たち SRE チームが取り組んでいる batch の EKS 移行について話します。

本日12/8時点では、移行自体は完了しておらず、検証環境で動作を確認している状態です。

Table of Contents

· 対象読者
· 背景
· EKS 移行することで問題をどう解決したか
· スケジューリング型 batch on EKS
· メッセージレシーブ型 batch on EKS

対象読者

  • batch 基盤を運用している方
  • Kubernets で batch を動かしたい方

背景

Pairs ではポイント付与や退会ユーザの操作など様々なユースケースで batch を動かしています。日本向けサービス用だけでもおよそ150個あります。

Pairs で batch と呼ばれるものには2パターンあります。
① スケジュール実行される batch
② SQS をポーリングして、メッセージがあった時に実行される batch

便宜上、それぞれスケジューリング型 batch 、メッセージレシーブ型 batch と呼ぶことにします。

これまでは両方とも ECS on Fargate を利用していました。
メッセージング型 batch を AWS Batch で実現しなかった背景としては、当時 Terraform に対応していなかった点や SQS を AWS Batch の Job queue へ移行する工数などがありました。

スケジューリング型 batch の構成
メッセージレシーブ型 batch の構成

特に滞りなく稼働していましたが、いくつか問題点がありました。

問題点1. ECRのimage pull イベントが大量に発火してしまう

ECS on Fargate では現状 image キャッシュができません。issue は出ているのでいずれは解消されるかもしれませんが、未定です。

image pull 自体のコストは PrivateLink を活用することで問題になっていませんが、別のところでコストが潜んでいました。

CloudTrail といったイベント検知ツールに関わるコストです。CloudTrail での ECR image pull イベント検出回数が非常に多く、余計なコストが嵩んできてしまっています。

問題点2. 同一種 batch の排他実行や実行 task 数制御のために工夫している
batch によっては重複実行を許容していないケースがあるため、実行タイミングになっても、それ以前にスケジュールされた batch がまだ動いている場合は新規の実行を取りやめるように制御したいケースがあります。今は、ロックファイルを作り EFS マウントして ECS task 間で共有することで、排他制御しています。

メッセージレシーブ型 batch を実行する際は、Visible な メッセージの数を取得し、batch 1 task あたりの想定処理メッセージ数を元に、必要なECS task 数を計算し、 run task します。Amazon SQS に基づくスケーリングを参考に設計しています。この場合に気にしないといけないこととして、ECS の run task api の quota や Database の負荷などがあり、実行数が増えすぎないように制御する必要がありました。
そのために、現在の ECS task の running 数を取得し、batch ごとに定義している制限値と比較して、必要以上に ECS task を起動させないよう細かくハンドリングしています。

自前でこれらの制御をしているので、現状運用やメンテナンスのコストがかかっていますが、可能であれば何かマネージドされている仕組みによって実現したいと考えていました。

Pairs 本体や管理画面は既に EKS on EC2 に移行しているため、コンテナオーケストレーションの技術スタック統一という意味でも、batch の EKS 移行を行うという決断をチームで行いました。

EKS 移行することで問題をどう解決したか

Node 上にコンテナ image をキャッシュする
Kubernetesでは ImagePullPolicy を IfNotPresentにすることで、Node上にコンテナimageをキャッシュすることができます。
これにより、ECRのimage pullイベントが大量に発火してしまう問題が解消されました。

排他制御を実現する
Kubernetes の job には排他制御するオプションが存在します。
concurrencyPolicy を Forbid にすることで、並列実行を禁止し、新しい pod の実行をスキップすることができます。
Replace にすることで新しい pod で入れ替えることもできるので、常に最新の結果で上書きすることも可能です。

スケジューリング型 batch on EKS

スケジューリング型 batch は、Argo WorkflowsCron Workflowsを採用しています。

Argo Workflows を採用した背景としては、

  • manifest をテンプレート化できる。
  • ダッシュボードがあるので履歴を追いやすい。リトライしやすい。
  • argocdとの親和性。ダッシュボードの認証手法を使いまわせるなど。

の3点があります。

manifest テンプレート化
背景でも説明したように batch の数が数百あるので、それぞれに対して manifest を書くのを避けたいと考えていました。
また、dev チームが manifest を書くことを想定しているので、可読性や書きやすさは大事なポイントでした。

  • テンプレート作成
apiVersion: argoproj.io/v1alpha1
kind: WorkflowTemplate
metadata:
name: workflow-template
spec:
entrypoint: task
templates:
— name: task
container:
image: alpine:3.6
command:
— echo
— “{{inputs.parameters.batch-type}}”
imagePullPolicy: IfNotPresent
  • テンプレートを利用するCron Workflows
    argumentsでbatchごとに異なるパラメータを注入することができます。
apiVersion: argoproj.io/v1alpha1
kind: CronWorkflow
metadata:
name: batch-a
spec:
schedule: “* * * * *”
timezone: “Asia/Tokyo”
concurrencyPolicy: “Allow”
workflowSpec:
workflowTemplateRef:
name: workflow-template
arguments:
parameters:
— name: batch-type
value: “batch-a”

メッセージレシーブ型 batch on EKS

EKS に batchを持っていくとなった場合に、一点課題がありました。
メッセージレシーブ型 batch では、 SQS をポーリングして、最適な数の ECS task を起動させるために Lambda を使用しています。
Lambda を EKS でも使うとなると、認証やクラスタ内リソースの操作のためにコード改修が必要になってくることがわかっていました。代替案を検討した結果、他チームで実績のあった KEDA 採用をすることにしました。

KEDA

KEDA はイベントドリブンのオートスケーラーです。

イベントトリガーとして、SQS や Pub/Sub などのメッセージ受信や Datadog の metrics を定義することができます。
イベント条件を満たすと、Deployment や Job リソースをスケールさせることができます。

KEDA を使うことで、問題点2の実行 task 数制御を行うことができました。

以下、実装例です。

イベント条件を triggers で定義します。

triggers:
- type: aws-sqs-queue
metadata:
queueURL: https://sqs.eu-west-1.amazonaws.com/account_id/QueueName
queueLength: "3"
awsRegion: "eu-west-1"

SQS にメッセージがキューイングされたことを検知すると、スケーリングが実行されます。 queueLength で設定した値が 1 pod あたりで処理可能なメッセージ数です。Visible なメッセージ数を queueLength で割ることで必要なレプリカ数を決定できます。

例) Visible なメッセージが30個ある時、 30 / 3 = 10 が DesiredReplicas となる。

batch の処理が完了したらプロセスを落としたいので、 ScaledJob を使いました。無限ループする系の処理であれば、ScaledObject がおすすめです。

apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: batch
spec:
minReplicaCount: 0
maxReplicaCount: 20
pollingInterval: 30
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTargetRef:
backoffLimit: 0
template:
spec:
containers:
- name: batch
image: alpine:3.6
command:
- echo
- "batch"

maxReplicaCount を設定することで必要以上に Job が作られれないようにしています。

実際に、SQS に 16メッセージ送信してみました。queueLength を3にしているので、6台( ≒ 16/3)の pod が起動してくれれば OK です。

$ kubectl get job
NAME COMPLETIONS DURATION AGE
batch-462fp 0/1 78s 78s
batch-cb54w 0/1 78s 78s
batch-dr95c 0/1 48s 48s
batch-ns6r9 0/1 108s 108s
batch-pml9m 0/1 78s 78s
batch-x2ww6 0/1 108s 108s

$ kubectl get pod
NAME READY STATUS RESTARTS AGE
batch-462fp-qfzzt 1/1 Running 0 79s
batch-cb54w-6jkdh 1/1 Running 0 79s
batch-dr95c-fpnhr 1/1 Running 0 49s
batch-ns6r9-j9xzn 1/1 Running 0 109s
batch-pml9m-nngrk 1/1 Running 0 79s
batch-x2ww6-m9bbn 1/1 Running 0 109s

想定通り、job リソースが作られ、それぞれの pod を作成し、batch 処理が実行されました。

KEDA が Amazon SQS に基づくスケーリングを満たす作りだったので、すんなり導入することができました。ちなみに、CloudWatch のメトリクスを使って、Kubernetes のリソースをスケールさせたい場合、AWS 公式も KEDA を使うことを推奨していました。

KEDA では、manifest のテンプレート化ができないので、今後はこの辺りを改善できないか検討中です。

まとめ
今回は、Argo Workflows と KEDA を使うことで要件を全て満たすことができました。

Kubernetes は周辺エコシステムが揃っていることが魅力の一つです。
カスタムコントローラーで機能拡張をすることも可能なので、今後複雑な要件が入ったときでも対応可能なのは、プラットフォームサービスとして心強いです。

とはいえ、なんでもEKS にしよう ! はあまりおすすめできないです。
求められる要件、組織の規模に応じて、ECS, EKS または Lambda を使い分けれれば良いのかなと考えています。

明日は、@takumattt さんによる 「EmacsでSwiftUIのプレビューを表示する」 です !

--

--