Containerで動かすGoの常駐worker
前提
前のプロジェクトの時にAWS SQSのキューをポーリングして稼働するworkerをgoで作った時のお話になります。 goのworkerはcontainerで動かすといった感じです
課題
その時に課題だったのが、goのworkerを改修してリリースする時にcontainerを破棄して、新しく再構築します。
workerは常に稼働しているのでworkerが何か処理をしている時にOSレベルで破棄され、強制終了するとデータが不整合な状態になってしまうという課題があった
sudo kill -SIGTERM
終了シグナルが送られた時、そのままworkerが終了してしまう事の弊害です
上位にLoad Balancerなるものがある場合は対応が楽になりますが、今回はポーリングしてるworkerなのでちょっと状況が異なります
よくある形
常駐化の役割はsupervisorを活用するパターンがあります。
GitHub - Supervisor/supervisor: Supervisor process control system for UNIX
supervisorが、go workerの終了までwaitしてくれて...とかという感じでうまく立ち回ってくれる事を期待しましたが、検証の結果ダメでした。
この時もsudo kill -SIGTERM
終了シグナルを送られると、Supervisorは即座に終了してしまい、workerも即座に落ちてしまうのでこちらも不整合データが発生する可能性があります
課題に対するアプローチ
- まず、処理の途中を記憶して、再構築されたら、途中から処理を再開というようなリジューム機能のようなものはちょっと考慮すべき点がありすぎて回避した
- 構築するworkerの処理自体は何分もかかるものではなかったので、破棄される際は途中の処理が最後まで完了するまで待つようにした
- アプリケーション(go)レベルではsignalを監視して制御するようにした
という方針で解決する事にしました
実際に解決した時のコードをサンプルとして残しておきます github.com
ちょっと解説
- mainスレッドでは1つgoroutineを発生させて、OSのsignalを監視しています
- 終了シグナルをキャッチすると
stopCh
チャネルで伝達します taskA()
,taskB()
は本来のworkerの仕事を行っていますfunc task()
はtaskA()
,taskB()
の共通メソッドをdryにしただけのものです- 常駐させるため、
for
を活用します。SQSのキューのポーリングが切られたらdefault
のところで再度立ち上がり、無限ループします - 終了シグナルの
stopCh
を気にしながら動きますselect
の記載 - 終了シグナルをキャッチすると処理が終了したらreturnして、タスク終了チャネル
taskADoneCh
,taskBDoneCh
に伝達します
- mainスレッドは各タスクの終了 =
taskADoneCh
,taskBDoneCh
を待って終了します
代替も考えてみる
- ポーリングとworker部分を切り離して構築してしまうパターンです
- ECSのgoのところがworker部分になります
- go workerのリリースの時にまず、SQSのポーリングしているlambdaを一時的に切ってしまいます
- そうすると後続処理は終了さえ待てば、次に動く事はないので、安全にリリースが可能になります
- このパターンだとgo worker側では終了シグナルとかを気にしなくてよくなる分、codeがシンプルになると思います。
- ただし、deployの自動化を考えるとちょっと手数が入りますね...
- 意外とこれはこれでネックかもしれません
最後
まぁどちらのパターンにせよ、安全にworkerを動かせれると思います。