nakamura244 blog

所属団体とは関係なく、個人的なblog

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

ちょっと解説

  1. mainスレッドでは1つgoroutineを発生させて、OSのsignalを監視しています
  2. 終了シグナルをキャッチするとstopChチャネルで伝達します
  3. taskA(),taskB()は本来のworkerの仕事を行っています
    1. func task()taskA(),taskB()の共通メソッドをdryにしただけのものです
    2. 常駐させるため、forを活用します。SQSのキューのポーリングが切られたらdefaultのところで再度立ち上がり、無限ループします
    3. 終了シグナルのstopChを気にしながら動きます selectの記載
    4. 終了シグナルをキャッチすると処理が終了したらreturnして、タスク終了チャネルtaskADoneCh,taskBDoneChに伝達します
  4. mainスレッドは各タスクの終了 = taskADoneCh,taskBDoneChを待って終了します

代替も考えてみる

f:id:tsuyoshi_nakamura:20190611103719j:plain

  • ポーリングとworker部分を切り離して構築してしまうパターンです
  • ECSのgoのところがworker部分になります
  • go workerのリリースの時にまず、SQSのポーリングしているlambdaを一時的に切ってしまいます
  • そうすると後続処理は終了さえ待てば、次に動く事はないので、安全にリリースが可能になります
  • このパターンだとgo worker側では終了シグナルとかを気にしなくてよくなる分、codeがシンプルになると思います。
  • ただし、deployの自動化を考えるとちょっと手数が入りますね...
    • 意外とこれはこれでネックかもしれません

最後

まぁどちらのパターンにせよ、安全にworkerを動かせれると思います。