LIVESENSE ENGINEER BLOG

リブセンスエンジニアの活動や注目していることを発信しています

マイナーなSaaSのCIを作っているんだが俺はもうダメかもしれない

はじめに

インフラGの@yjszkです。先日は青森競輪と盛岡競馬に行ってきて負けました、盛岡のジャンボ焼き鳥が美味しかったです。
さて、前回の記事ではCronitorというサービスのコード化と、CIの構築を行ったことを書きました。 それを実際に運用してみたところ、いくつかの問題が発生しました。今回は、それに対して、現在進行形で苦労している話を書きます。

CIの概要

前回の記事にあるように、CIを構築しました。 GitHub Actionsを使用し、PRにコミットが積まれるとDry-Runが走り、マージされると本番反映が走るという形です。詳しくは以下になります。

  • 変更点をYAMLに記述し、GitHubにPush
    • 単一YAMLだと3000行くらいになったので、可読性のため、サービスごとにYAMLを分割しています、これを一つのYAMLにまとめる処理をします
      • 例えば、マッハバイトであれば、mb-hoge-staging.yaml、mb-hoge-production.yamlという形で分割しています
  • CIの中で現在の設定をYAMLにエクスポート
  • 変更したYAMLと現在の設定のYAMLを突合して差分を出力
    • dyffというOSSツールを採用しました、当初YAMLをDiffで比較するのに、RAWにdiff -uでやろうとしたのですが、dyffはよりいい形で出力してくれます
    • 差分はPRにコメントで追記するActionを使用しました
  • YAMLの差分をPython上で処理するためにDeepDiffを採用し、差分を取得
  • 取得した差分を元に、CronitorのAPIを叩き、設定を更新

出てきた課題と対策

ライブラリのtimeout値が固定値な上に短い

運用を開始してしばらくすると、以下のエラーで設定の適用がされていませんでした。

requests.exceptions.ConnectionError: HTTPSConnectionPool(host='cronitor.io', port=443): Max retries exceeded with url: /api/monitors.yaml (Caused by ReadTimeoutError("HTTPSConnectionPool(host='cronitor.io', port=443): Read timed out. (read timeout=10)"))

確かめたところ、公式ライブラリのtimeout値が10秒でした。特に引数が指定できるとかでもない。仕方ないのでリポジトリをforkしてtimeout値を変更しました。
地味にCIを実行するリポジトリから、forkしたPrivateのリポジトリを参照するのにハマってしまい、時間を食ってしまいました。これに関してはGitHubのDeploy KeyがRSAだとエラーになり、Ed25519だと問題なかっただけでした。

ドキュメントにないパラメータがダマで増えた

ある時、すべてのモニターに差分が発生しました。見たことない、ドキュメントに書いてないパラメータconsecutive_alert_thresholdが設定されていることになっていました。
これは、Cronitorのサービス側で、発表はしていないがアップデートしたものらしく、サポートに問い合わせてから判明しました。 再度設定を全てエクスポートし、現状と合わせる作業が必要になりました。機能追加は嬉しいですが、突然の追加は流石に目がぐるぐるマークになりました。
これに関して、発表の有無は考えないとして、例えばTerraformのアップデートでも同一の作業が発生するので、仕方ないことだと思います。

モニターのゾンビ化

手動で作ったモニターを削除し、同じIDでAPI経由で作成したところ、モニターが削除も使用もできなくなりました。
サポートに問い合わせたところ、

復元が必要な場合に備えてジョブデータを保持しているため、同じキーで新しいジョブを作成することができない

とありました。回復処理を行っていただき、使用可能になりました。これに関しては同じIDで作らなければいいだけなので、運用で回避可能です。

想定したように設定が反映されずに手動で変更

pausedという通知を一時的に抑制するパラメータがあるのですが、コードから変更しようとしたところ、適用されませんでした。
調べたところ、これはRead Only Attributesであり、名前の通りコードからの変更に対応していませんでした。
ドキュメントは穴が開くほど見つめるべきです。

YAMLのdiffツール(dyff)の自己主張が激しい

  • dyffというOSSのDIFFツールを使っています、例えばhogeというモニターを削除した場合、差分が以下のように出力されます。
jobs
  - one map entry removed:
    hoge:
      name: hoge
      consecutive_alert_threshold: 10
      grace_seconds: 180
      group: hoge-group
      platform: "linux cron"
      realert_interval: "every 8 hours"
      schedule: "30 1 * * *"
      timezone: Asia/Tokyo
      environments:
      - production
      notify:
      - dev
  • このツール、デフォルトだとAAの自己主張がすごいです。
$ dyff between remote_config.yaml local_config.yaml
     _        __  __
   _| |_   _ / _|/ _|  between remote_config.yaml
 / _' | | | | |_| |_       and local_config.yaml
| (_| | |_| |  _|  _|
 \__,_|\__, |_| |_|   returned no differences
        |___/
  • --omit-header -c OFFオプションを渡すと止めることができます。
$ dyff between --omit-header -c OFF remote_config.yaml local_config.yaml 

結局CI化するべきだったのか?

得られたメリット

インフラGではIaC関連のリポジトリはモノリポとして運用しています。例えばECSのバッチの設定とcronitorのモニターの設定も両方入っているので、バッチの追加の際に一つのPRでCronitorによる監視の設定も追加できるようになりました。これですとレビュワーも楽です。
また、CI化の前に棚卸しをしたのですが、全く使用していないモニターが100近くあり、これらを大幅に減らすことができたのもメリットでした。

正直な感想と今後

作っておいてアレですが、長期の使用に耐えうるかというと微妙と言わざるを得ません。上記にあげたようにデメリットが多く、結局CI化するべきだったのか?という疑問が残りました。これに関しては継続して運用し、判断したいと考えています。
私自身はCI化とは別の方法で管理する考えに傾きつつあります。 頻繁に変更するものでもないし、定期的に棚卸しすればいいのでは?と思うようになりました。
例えば、日次でバッチの一覧をGASを使ってスプレッドシートに一覧化するなど、CI以外にもAPIを使った管理方法もあるかなと思っています。ただこれも、問題を先送りにしているだけなので、なかなか難しいです。継続して良い方法を考えていきたいと思います。ありがとうございました。