Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

Amazon EKSクラスタ上で動作するSidekiqをGraceful Shutdownさせる

こんにちは。スタディプラスのSREグループの水口です。

2022年5月に入社してからStudyplus Engineering Podcastには何度か出演しておりましたが、テックブログは今回初めて書きます。

この記事では、SidekiqをKubernetes(Amazon EKS)で動作させる際、Graceful Shutdownさせるために必要な前提知識を整理します。

背景

スタディプラスでは、老朽化しつつあるAmazon EC2で稼働していたサービスをAmazon EKSに載せ替えております。今回取り上げるバッチサーバーのSidekiqをAmazon EKSに移行させるにあたって以下の課題がありました。

  • 当社サービスの仕様上、25秒以内(後述)に完了しないSidekiqのジョブが存在しており、Sidekiqのデフォルト設定だとジョブが強制終了されてしまう
  • 強制終了されたジョブは再実行されるが、冪等性が担保されていないジョブが一部存在する
  • ジョブの冪等性を担保し、短時間で完了するように修正する人的リソース、時間的な余裕がない

本来はSidekiqのジョブもまとめて改善すべき課題ではありますが、運用上多くのメリットを享受できるため、いったんAmazon EKS移行を先行して進めることとしました。

Amazon EKSに移行する際、注意したポイントは大別して以下の3点です。

  • Sidekiqプロセスの停止
  • Kubernetes Podの停止
  • Kubernetes Nodeの停止

Sidekiqプロセスの停止

SIGTERM + timeout オプション

SidekiqプロセスはSIGTERMを受け取ると、デフォルトでは25秒のタイムアウトの範囲内でプロセスを終了します。このときジョブの取得は停止しますが、SIGTSTPを受け取ったとき同様に現在のジョブの処理は継続します。*1*2

このタイムアウトはSidekiq起動時のtimeoutオプションで変更できます。これによって、最も長いジョブの処理時間より長く設定することでジョブの処理完了までにプロセスが強制終了されてしまうことを防ぐことができます。

# 終了まで600秒の猶予時間を持たせる
bundle exec sidekiq -C config/sidekiq.yml -e production -t 600

Kubernets Podの停止

Deploymentのマニフェストについて

マニフェストを記述する際、SidekiqのDeploymentでは、preStopフックでSidekiqプロセスにSIGTERMを送るようにすると、上述の方法でタイムアウトを設定している範囲内であればGraceful Shutdownが実現できます。*3

lifecycle:
  preStop:
    exec:
      command: ["/bin/sh","-c", "kill -TERM $(ps aux | grep sidekiq | grep busy | awk '{ print $2 }')"]
  • 追記(2023/9/28)
    Sidekiq公式のWikiの記載によると、 command: ["/bin/sh","-c", "bundle exec sidekiq -t 100 -e production --config config/sidekiq_custom.yml"] のようなシェルコマンドでSidekiqを起動することが推奨されていません。 TERM シグナルがSidekiqに直接ではなくシェルプロセスにのみ送付されるためです。

github.com

terminationGracePeriodSecondsについて

上述のようにpreStopフックで kill -TERM を実行する場合、terminationGracePeriodSeconds(default: 30)に気をつける必要があります。
Podの終了処理が開始されてからterminationGracePeriodSecondsの間にSidekiqプロセスが終了しない場合、Sidekiqのtimeoutオプションを長めに設定していたとしても、SIGKILLを受け取ったPodが強制的に終了されます。
そのため、Sidekiqのtimeoutオプションと合わせてterminationGracePeriodSecondsも延長しておく必要があります。目安としてはSidekiqのタイムアウトよりも5秒長く確保するのがよいでしょう。*4

また、Podのライフサイクルについては以下の2記事がとても参考になりました。

zenn.dev

kubernetes.io

Kubernetes Nodeの停止

SidekiqのPodがスケジュールされているNodeが不意に終了されてしまわないように、Kubernetes(当社の場合Amazon EKS)では以下に注意する必要があります。

Cluster Autoscaler について

Cluster AutoscalerでのNodeのスケールインが発生するとき、--max-graceful-termination-sec(default: 600)の範囲内でPodが終了しない場合、Nodeは強制的に終了します*5。しかし、弊社のEKSクラスタは複数サービスが同居するマルチテナントのクラスタとなっており、特定サービスの処理時間が長いSidekiqのためだけに設定するのは避けたいため、別の方法を検討しました。

"cluster-autoscaler.kubernetes.io/safe-to-evict": "false" のannotationをSidekiqのDeploymentに記述することで、SidekiqのPodがスケジュールされているNodeをCluster Autoscalerによる操作の対象外としました。*6

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sidekiq-deployment
  annotations:
    cluster-autoscaler.kubernetes.io/safe-to-evict: 'false'
spec:
  replicas: ...

Amazon EC2 スポットインスタンス について

スポットインスタンスはAWSクラウド内の使用されていない待機中のEC2を安価で利用できるサービスです。コスト最適化のために有効なサービスである一方、予期しないNodeの中断のおそれがあるため、Sidekiq用のPodをスケジュールするNodeでの利用は避けました。

スポットインスタンスが中断されるまで2分の猶予があるため、すべてのジョブが115秒以内に終了する、冪等性が担保されているという条件を満たす場合は採用の余地がありそうです。

【参考】既存実装について

Amazon EKS移行前の実装についてもご紹介します。

Amazon EC2でSidekiqを動かしていたときはSidekiq EnterpriseEnt Rolling Restartsを活用してSidekiqのRolling Restartを実現しておりました。

その動作にはeinhornという専用のプロセスマネージャーが利用されています。Ent Rolling Restartsを実行すると古いSidekiqプロセスはジョブの取得を停止しますが、すべてのジョブが完了するまでプロセスを終了しません。本題ではないのでこれ以上踏み込みませんが、これによって安全にRolling Restartを行うことができておりました。

当社のSidekiq Enterpriseの利用方法については以下の記事にも書かれております。よろしければご覧ください。

tech.studyplus.co.jp

tech.studyplus.co.jp

おわりに

以上の点に注意することによってEKSクラスタで稼働するSidekiqのGraceful Shutdownを実現できました。当社の全サービスのAmazon EKS移行はまだ終えておりませんし、移行済のサービスについても課題が残っております。改善をしつつ、引き続き進めていきます。

【宣伝】Podcastのご紹介

スタディプラスでは所属エンジニアが出演するStudyplus Engineering Podcastを配信しており、私も定期的にホストを務めております。最新第16回に私は出演しておりませんが、Kaigi on RailsでRBSに関する登壇をしたエンジニア山田さんに話を聞く回となっております。是非聞いてみてください。

spotifyanchor-web.app.link