Safie Engineers' Blog!

Safieのエンジニアが書くブログです

カメラ映像録画サーバのデプロイを改善した話

こんにちは。サーバサイドエンジニアの村田 (@naofumimurata) です。

本記事では、セーフィーのシステムでカメラ映像の録画機能を担うアプリケーションのデプロイを改善した話を共有したいと思います。

セーフィーの録画・配信システム

セーフィーはクラウド防犯カメラ・録画サービスを提供しています。

バックエンドのシステムとしては、まずカメラから映像を常時受け取りストレージに保存するアプリケーション(本記事では以降「カメラサーバ」と呼びます)が稼働しています。カメラサーバでは映像の保存の他にもカメラの情報管理や制御などを行なっています。ユーザはSafie Viewerというフロントアプリケーション(Web/モバイル)を利用し、カメラサーバに入ってきた映像を別の動画配信アプリケーションを介して閲覧することができます。

カメラサーバはJava、動画配信アプリケーションはGolangで実装されています。

インフラ環境は全てAWSを利用しており、カメラサーバはEC2インスタンス上で稼働しています。現在は約20万台以上のカメラが接続しており、カメラサーバだけで約2,000台以上のEC2インスタンスが稼働しています。

インフラ構成の特徴的な点として、負荷分散のために水平分割の構成を取っています。一定カメラ台数(約13000台程度)ごとにそれを処理するためのインフラ構成(ネットワーク、DB、redis、サーバ類)一式を構築しています。社内ではこの水平分割の単位を「ゾーン」と呼んでおり、本記事でもこの用語を使います。

カメラサーバのデプロイの課題

セーフィーのシステムにおいてカメラサーバは特に重要な役割を担っていますが、そのデプロイには大きな課題がありました。ここでは具体的なデプロイの流れとどういった課題があったかについて紹介します。

デプロイの流れ

カメラサーバのデプロイではざっくり以下のことを行う必要があります。

  1. アプリケーションのビルド
  2. AMIのビルド
    • ゴールデンイメージを用意しスケーリングで利用
  3. jarファイルの配布
  4. redis, DB上の接続、管理情報を更新
  5. カメラサーバに対して停止リクエストを送る
    • カメラサーバが担当しているデバイスを他のカメラサーバに移動させる
  6. プロセス再起動

改善前の構成

単純にビルド、成果物の配布、プロセス再起動だけではないステップ(2、3、5番)があるのが特徴的かと思います。

まず、アプリケーションのビルドを行います。カメラサーバはJavaで実装されているのでjarファイルを生成します。次にAMIのビルドを行います。カメラサーバはゴールデンイメージ運用を行なっており、jarファイルを含むAMIを用意しスケーリングで利用しています。スケーリングはEC2 Auto Scalingではなく自前でスケーリングを行うアプリケーションが別に存在するという特殊な構成になっているのですが、ここでは詳しい紹介は割愛します。生成したjarファイルをインスタンスに配布した後、プロセス再起動の前に幾つかやることがあります。

まず、redisとDBのデータ更新を行います。カメラサーバへのカメラ割り当て状況や、カメラの接続状態・割り当て履歴といった情報をredis、DBで管理しており他のバックエンドアプリケーションからも利用されています。デプロイ時にはそのカメラサーバをサービス全体から外す必要があるため、正しい情報に更新を行います。

次にカメラサーバに対してRPCでシャットダウンの要求を送ります。プロセス再起動前にこれを行う目的はカメラの接続品質向上のためです。カメラサーバはシャットダウンの要求を受け取ると担当しているカメラに対して他のカメラサーバへの再接続を指示します。全てのカメラが別のカメラサーバに移動するのを待った後にプロセスを終了させています。

無事にシャットダウン要求が処理された後にプロセスを再起動し新しいバージョンのアプリケーションが起動します。

実行環境

上記の作業は全てスクリプト化されており、シェルスクリプトとAnsibleとPythonスクリプトを組み合わせて実現されていました。スクリプトの実行はデプロイ作業用のEC2インスタンス上で手動で実行する必要がありました。

問題

さて、ここまで紹介してきたカメラサーバのデプロイ構成ですが、サービスの成長に伴い様々な問題が現れてきました。

時間がかかる

まず一番大きな問題は、デプロイにもの凄く時間がかかるということでした。実際にどのくらい時間がかかっていたかというと、全てのカメラサーバのデプロイを完了するのに最短で30時間強かかっていました。

これには幾つかの要因があるのですが、まず根本的なところとして2000台を超えるインスタンスがあり対象が多いということがありますが、それに加えてスクリプトの作りの問題で並列度が著しく低いということがありました。

前述の通り、セーフィーの録画配信周りのシステムは「ゾーン」と呼ばれる単位で構築されており、デプロイもゾーン単位で行う必要があるのですが、1ゾーンずつ逐次でしか実行できないようになっていました。1ゾーン内では複数インスタンス並列で実行されるのですが、サービスの成長と共にゾーンの数が多くなったため全体として見るとかなり並列度が低いという状態となっていました。

1ゾーンで約2、3時間程度かかっており、ゾーン数が15を超えたあたりからトータルで30時間を超えるようになっていました。

作業負荷が高い

次にデプロイ作業を行うエンジニアの負荷がとても高いという問題がありました。まず、デプロイが自動化されておらず手動でデプロイスクリプトを実行する必要がありました。くわえて、スクリプトも実行したら放置で済むというわけではなく頻繁に途中で失敗するようになっており、それへの対応のため作業者がつきっきりで画面を見ている必要がありました。

デプロイスクリプトが途中で失敗する原因としては、前述の通りデプロイ時に接続カメラを別のカメラサーバに移動させる処理があり、これが結構時間がかかる(なかなか移動してくれないカメラがいたりする)ためでした。実際のところここの待ち時間はカメラ依存であるため仕方がないのですが、デプロイスクリプトの待ち時間の判定がシビアで頻繁にタイムアウトし失敗判定になるという状態になっていました。

途中で失敗してしまった場合は、エンジニアが実際にカメラサーバのインスタンスに入ってログを確認し問題ないかをチェックし、問題なければ再開するということを行う必要があり、頻繁に失敗することが作業負荷を著しくあげていました。

メンテナンス性が悪い

最後はデプロイの実行環境およびツールに関することで、メンテナンス性が悪いという問題がありました。

前述の通り、デプロイスクリプトはシェルスクリプトとAnsibleとPythonスクリプトが組み合わせてできているのですが、かなり複雑怪奇になっており何が実行されているのかを把握するのが難しい状態になっていました。特にPythonスクリプトの部分に関しては別のアプリケーション実装の機能を部分的に呼び出すような特殊な構成になっており、挙動を変えるには別のアプリケーションの修正を行う必要があるという状態になっていました。

結果どういう状態になったか

このような問題によって、以下のような現象が発生するようになりました

  • リリーススケジュールが後ろ倒しになる
  • カメラサーバの変更を回避するようになる

まずデプロイにもの凄く時間がかかるので、カメラサーバに変更を加えた場合それによってプロダクトのリリーススケジュールが大きく後ろ倒しになることがありました。最短で約30時間強かかると書きましたが、これは休まず寝ずに作業した場合の話で、実際にはエンジニアが日中にスケジュールが空いている時間で作業することになるので、デプロイしたいと思ってから実際に完了するまでもっと時間がかかります。金曜リリースを避けつつ、セーフィーでは本番環境へのデプロイは複数人体制で行うようにしているため、スケジュールを調整したりすると実際には2、3週間から長い場合1ヶ月程度かかっていました。

そしてもの凄く作業負荷が高いので、みんなカメラサーバのデプロイをやりたくなくなりました。その結果カメラサーバへの変更を避けるようになり、本来ならカメラサーバにあるべき機能などを別のところで実装しだし、カメラサーバへの変更を回避するようになりました。

改善に向けた取り組み

こういった問題を解決するために、カメラサーバのデプロイ改善に取り組むことにしました。有志のメンバーでワーキンググループを組成し現状の調査から解決まで取り組みました。

GitHub Actions + AWS CodeDeployの構成に

デプロイの構成を見直し、GitHub ActionsとAWS CodeDeployを利用した構成へとまるっと作り替えを行いました。ツールの選定理由としては、GitHub ActionsとAWS CodeDeployどちらも既に利用実績があったことが大きなポイントでした。

これまで作業用インスタンスで行っていたアプリケーションのビルドをGitHub Actions上で自動で行い、成果物の配布とアプリの再起動処理はCodeDeployのスクリプトとしてカメラサーバインスタンス上で実行するような構成にしました。

また、併せてカメラサーバのEC2インスタンスをEC2 Auto Scaling Group (ASG)の管理下に入れるように変更しました。これによりASGとCode Deployの連携機能でASGで起動したインスタンスにはCodeDeployの最新のリビジョンが自動デプロイされるようになったため、ゴールデンイメージが不要となりAMIをビルドするステップを消すことができました。

アプリケーションの終了・再起動に関連する処理はこれまでPythonスクリプトで行っていましたが、別アプリケーションの機能に密結合な実装となっており、これをカメラサーバインスタンス上に配置して実行できるようにするのは後々のメンテナンス性を考えて避けたかったため、機能を切り出したものをGolangで新たに実装しました。Golangの場合、バイナリを配置するだけで動かせるので、アプリケーションの成果物と一緒に配布し実行させる構成にしました。

また、問題だったデプロイスクリプトの失敗判定などもGolang実装に移行する段階で修正し、無駄に失敗判定にならないようにしました。

改善後の構成

監視の強化

これまではデプロイ時の正常性の確認は、作業者がEC2インスタンスに入ってログなどを目視で確認するというかなり温かみのあるやり方で行っていたため、作業負荷が非常に高いという問題がありました。

デプロイ構成を一新するにあたりここについても改善するべく、CodeDeployのデプロイスクリプトでの正常性確認ステップに加えて、メトリクス監視の強化を行いました。

セーフィーではPrometheusによるメトリクス監視の仕組みが既にありインスタンスレベルでの監視は行われていましたが、今回はアプリケーションにPrometheus Exporterを新たに実装、正常性を確認するためのメトリクスを追加し、問題があればアラートが発報し作業者が都度ログを確認する作業などが不要となるようにしました。

成果

試行錯誤の末、約1年ほど前に新たなデプロイ構成に移行が完了しました。 その結果、以下のような成果が得られました。

デプロイ時間の短縮

一番の大きな問題だったデプロイにかかる時間ですが、これは大幅に短縮できました。

これまでは最短約30時間強かかっていましたが、改善後は最短で3時間強程度でできるようになりました。新しい構成ではゾーン毎に並列で実行できるようになったため、ゾーン数によらず同時に全ゾーンに対してデプロイを行えるようになったところが大きなポイントでした。

作業負荷の軽減

作業負荷の問題についても大きく改善できました。これまでは、作業者が手動でデプロイを行い画面につきっきりで作業を行う必要がありましたが、新しい構成ではGitHubのイベントをトリガーに自動でデプロイが実行されるようになり、デプロイ中も基本的に放置しておけば良くなりました。

デプロイ頻度の向上

デプロイの頻度も大きく改善しました。セーフィーでは検証環境と本番環境の2つの環境があるのですが、検証環境では9倍程度、本番環境では3倍程度向上しました。

まとめ

本記事では、セーフィーのシステムで特に重要な役割を担うカメラサーバのデプロイの問題を改善した事例を共有させていただきました。大きな問題で改善に向けて様々な苦労がありましたが、無事にやり切ることができ、実際に改善効果も得られてよかったかなと思います。デプロイ構成の移行にあたっては互換性の維持を重視し、なるべく実行する処理は変えずに改善することを意識していたため、そもそもの処理の内容自体にまだまだ改善の余地が残っていると思っており、その辺りの根本的な改善も継続してやっていきたいと考えています。

さいごに、セーフィーではエンジニアを募集しております。ご興味が出た際はぜひご応募いただけたらと思います! https://safie.co.jp/teams/engineering/

最後までお読みいただき、ありがとうございました。

© Safie Inc.