mimiをGKEにしてみた話
mimi を GKE にしてみた話
今回は mimi を GKE に移行しました、という話です。「しました」と過去形ですが、実施からおおよそ半年。そこからアーキテクチャの構成変更や設定調整などを行って、だいぶ安定化してきました。私の作業もだいぶ慣れてきたこともありまして、ちょっと記事にしてみようということになりました。とはいえ、ベテランとはまだまだ言えないレベルではあります。おかしなところ、聞きたいところがありましたら、何なりとコメントをば書いてください。
mimi ってなに?
まず、mimi と書いてみても伝わらないと思うので、ご紹介から。mimi とは当社の運営する音声技術に関連するサービスの総称でして、この記事では音声認識、翻訳、音声合成などのクラウドAPIサービスを指します。お試しは無料、ブラウザからもテストができます。気になる方はこちらにありますので、ぜひとも触ってみてください。
一部のサービスでは、NICT (国立研究開発法人情報通信研究機構)さんからライセンスされたエンジンを運用しており、今回GKE移行の対象としたサービスがそういったサービスになります。ただ、機械学習モデルをそのままサーバーに乗せても音声APIとして使いやすいものにはならないので、インタフェースなどの工夫が必要です。またクラウドAPIサービスとしてセキュリティやスケーラビリティなど運用面も工夫が必要です。こうした要件を実現するために、インスタンスグループやロードバランサなどを設定して、ポコポコと朝に夕なにみなさんに叩かれても動くようにがんばっております。
例えば、音声認識の系はこんな感じの構成になっております。クライアントからリクエストを直接受ける「音声入力エンドポイント」。中間で言語毎の振り分けやパースなんかもやっている「ルーター」。音声認識エンジンのコネクションをプールしたり死活監視なんかを行う「中間サーバ」。大まかには上述の構成になっております。バックエンドのアーキテクチャについて、より詳しく知りたい方は「mimi音声クラウドAIのサービスバックエンドのアーキテクチャ」をご確認ください。
GKE で何するの?
これを GKE に移行しました、とだけ書くと不親切なので、ちょっくら説明をしますと。GKE と言いますのは Kubernetes (k8s) の Google Cloud による実装であります。デプロイ周りの構成や設定を宣言的に書くと、それに合った環境を作成してくれるツールです。オーケストレーションツールと言われたりします。
めんどくささがなくなる
GKE 以前、当社では Google Cloud の Compute Engine を使っており、こういった形式のスクリプトを使って運用をしていました。
#!/bin/bash -ex
# インスタンスグループの作成
gcloud compute instance-groups managed create nict-asr-ja \
--size=0 \
--template=nict-asr-ja-20250123 \
--zones=asia-northeast1-a,asia-northeast1-b,asia-northeast1-c
# バックエンドサービス向けヘルスチェッカの作成
gcloud compute \
health-checks create http \
nict-asr-ja-readiness \
--request-path=/readiness \
--check-interval=5s \
--timeout=1s \
--healthy-threshold=2 \
--unhealthy-threshold=1 \
--port=80
# バックエンドサービスの作成
gcloud compute backend-services create nict-asr-ja \
--health-checks=nict-asr-ja-readiness \
--load-balancing-scheme=INTERNAL \
--protocol=TCP --region=asia-northeast1
# バックエンドサービスへのインスタンスグループの設定
gcloud compute backend-services add-backend nict-asr-ja \
--instance-group=nict-asr-ja \
--region=asia-northeast1 \
--instance-group-region=asia-northeast1
# ロードバランサ向け IP アドレスの予約
gcloud compute addresses create nict-asr-ja \
--subnet=default \
--region=asia-northeast1
# ロードバランサの作成
gcloud compute forwarding-rules create nict-asr-ja \
--backend-service=nict-asr-ja \
--load-balancing-scheme=INTERNAL \
--network=default \
--subnet=default \
--address=nict-asr-ja \
--address-region=asia-northeast1 \
--region=asia-northeast1 \
--ports=80
とにかくgcloud
コマンドをひたすら叩きまくることで、Google Cloud のリソースを操作する術です。これはこれで必要十分だったのですが、不便な点もいくつかあります。
- 途中で失敗した時のやり直しがめんどくさい
- 途中までは作成/削除できているため、もう一回叩くとエラーが出てしまう(冪等性がない)
- 削除がめんどくさい
- 作成用スクリプトと削除用スクリプトをセットで作らなくちゃいけない
- レビューがめんどくさい
- 何がどう変わっているのか、差分を見づらいし、知識がないと妥当性の判断が大変
GKE にすると、これらの問題が一挙に解決するため、めんどくささを解消できます。この「めんどくさい」は一見すると怠けものの言い訳のように見えますが、どっこい、結構重要な問題なのです。
まず、単純にめんどくさいこと=工数がかかるということなので、当社のような人数の少ない会社だと開発のスピードに直結します。このめんどくささはレビュー時、デプロイ時にも響いてきます。
書く方も「なんで同じようなスクリプトを何回も書かなきゃいけないんだ」と思い、見る方も「これ、合ってるのか調べるの大変だなぁ」と思い、実行する時も「ヨシ!」と周りがなかなか言えなくなるので、「めんどくささをなくす」というのは重要なことです。
細かくスペック調整できる
VMインスタンスからコンテナに移行することのメリットもあります。例えば、マシンのスペックをより細かくイジれるようになります。上述の構成を見てみるとわかるでしょうが、エンジン周りはCPUを爆積みし、メモリも大量に確保しないと即死します。ところが、中間のサーバは中継しているだけなので、そんなに大きなスペックはいりません。Google Cloud で言えば、microだっていらない可能性があります。こういう時にスペックを細かくイジれるとお金を節約できるため、ありがたいメリットです。
どうやって GKE に移行したの?
一番重要だったのは「風呂敷を広げすぎない」こと。扱う範囲を一気に広げると、いつまで経っても終わらないし、ノウハウも貯まらない。とにかく最低限と思われる場所から GKE への移行を開始しました。
mimi にはいくつもサービスが詰まっているわけですが、初回は音声認識にフォーカスし、他は対象外としました。当社で一番負荷が高いのは音声認識で、一番更新頻度が高いのも音声認識だからです。
コンテナ化する際に問題となったのがエンジン部分。イメージ内に何十GBものファイルを抱えさせるのは Push/Pull に時間がかかるなどして扱いにくいのです。どうするのか、一つの課題でありました。今回は共有ストレージ (FileStore) をマウントすることで一旦解消しておりますが、これは課題も残しており、苦渋の決断と言ったところ。これについては後述します。
GKE にして困ったことはないの?
じゃあ、GKE にして全てがハッピーか。あらゆる問題が解決したかと言いますと、さすがにそれはない。むしろ、解決した問題の分だけ、不都合な部分も出てきています。
うっかり誤操作を誘発しそう
例えば、開発環境の pod を確認したくなったら、こういうコマンドを叩きます。
kubectl config use-context <dev-cluster>_<dev-project>
kubectl get pod
じゃあ、本番環境の pod を確認したくなったら、こうです。
kubectl config use-context <prod-cluster>_<prod-project>
kubectl get pod
最初のコマンドで環境を切り替えて、2回目以降はその環境で実行するわけです。怖い。とても怖い。開発環境と本番環境を交互に操作なんかしちゃいけないわけですが。しかし、障害発生中に開発環境の様子を見て、テストコマンドを実行してみる、なんてことはあるわけで。GUIやgcloud
の実行に比べると、GKE はうっかり誤操作を起こしがちな構成だと思うわけです。
当社では GKE を触る際には必ずsecure-kube
と呼ばれる自作のスクリプトを介して行う取り決めをしておりまして。例えば、下記のように実行すると。
secure-kube get pod
スクリプト内部では必ず都度context
をセットにして実行させております。
kubectl get pod --context <prod-cluster>_<prod-project>
また、環境名を判定して、本番環境であれば、Linuxお約束のスパイダーマンみたいなメッセージも表示させるなど、工夫はしております。
あなたはシステム管理者から通常の講習を受けたはずです。
これは通常、以下の3点に要約されます:
#1) 他人のプライバシーを尊重すること。
#2) タイプする前に考えること。
#3) 大いなる力には大いなる責任が伴うこと。
<prod-project> を操作しますが、本当によろしいですか?(yes/No):
しておるんですが、これも万全とは言い難いのが現状です。何故かというと、スクリプトはルールで決まっていますが、見知らぬところで叩かれたら強制はできないわけですし。context
のセットにしても。
secure-kube exec --stdin --tty shell-demo -- /bin/bash
こういったコマンドの場合が。
kubectl exec --stdin --tty shell-demo -- /bin/bash --context prod-cluster_prod-project
こう展開されてしまったため、うまく動かなかった、というケースもありました。全コマンドを試していないため、この手のパターンはまた出てくるでしょうし。万全とは言い難い、というのが現状です。
共有ストレージ難しい問題
「今回は共有ストレージをマウント」していると書きましたが。実はこれリードオンリーになっておりません。エンジンの通常の用法だと、既定のディレクトリにログやキャッシュが出力されるため、そのままリードオンリーにするわけにはいきません。エンジン側の設定について突っ込んだ調査をしたいのは山々なのですが。ちょっと手がつけられていない状態。当然、いくつか懸念があるわけです。
例えば、ストレージ容量は大丈夫なの? これは共有ストレージの容量に対して、書き込み量が少ないので大丈夫そう。キャッシュが共有ストレージにあって速度は大丈夫なの? これはシャドウテストを行って、現行系と差異がないことを確認しました。稼働している系そのものはOKそう。じゃあ、運用は?
運用にはやはりまだ心配があります。例えば、更新に気を遣うこと。モデルのアップデートを行う時には現行モデルの横に最新モデルを置くわけですが、うっかり現行モデルを触ってしまったら稼働中の系は死んでしまう可能性もあります。スクリプト経由でのみ更新することにしているため、うっかりミスは防止できそうですが。それだけでは防げない懸念もあります。いつかは容量がいっぱいになるわけで、過去の系を削除する作業は必要になります。そういった際に本当に「注意する」だけで大丈夫なのか。新たに別のストレージを立てるのか、容量を増やすのか。どういった運用にしていくのか、今後の課題は残っております。
で、よかったの?
「無理して GKE にしたけど、途中で撤退した」といった記事も見かけます。上で困った部分を書いたように、GKE も万能じゃありません。私自身の感覚としても、インスタンスグループの時代に比べて、コストが劇的に改善したか、デプロイがめちゃくちゃ楽になっているか、と聞かれると、難しい部分もあります。
例えば、VMインスタンスにはスリープ機能があり、スケールイン時に不要になったVMインスタンスを削除するのではなく、スリープしておくこともできるようになりました。音声認識エンジンは起動がかなり遅いため、こういった機能は使える方がありがたい。「GKE には使えない機能」があったりするわけです。
ただ、私個人の感想で言いますと、「GKE は肌に合う」と感じているのも確かです。何がよいと言って、「なんか失敗したな」とか「なんかよくわからんな」となったら、即座に消せることです。一回作ったものに拘泥せず、トライアンドエラーで作ったり消したりできる。
この操作感、プログラムのデバッグと同じなんであります。プログラムもだいたい一発で動くことはありません。一発で動くプログラムはバグが見つかっていないだけの欠陥品だと言ってもいい。基本は動かして、バグって、やり直す。これを繰り返すはずです。
ところが、何故か環境構築は後戻りしづらい雰囲気があります。間違えたらいくらでも戻してやり直せばいいのですが、そうはいかない。めんどくさいからです。GUIでもgcloud
コマンドでもUNDOがない。ctrl+zを叩いたら戻せればいいのに。戻せないとなると、途端に一個一個の操作が面倒で億劫になるわけです。
GKE は修正してapplyすればUNDOになりますし。なんかごちゃついてきたら、deleteすればいい。戻せるから、何度でも叩ける。チャレンジできる。インフラをプログラム的に操作できる GKE は開発体験がよい、というわけです。
迷っている会社さんがありましたら、お試しでも触ってみることをおすすめします。ダメならすぐにやめればいいのです。すぐやめられる、というのもよさの一つであります。
謝辞
本記事で紹介した GKE 移行に際し、株式会社Topotal様には多大なご協力をいただきました。おかげさまで、GKE 導入からアップデートを重ね、安定化まで漕ぎ着け、エラー率やコストの改善などの課題にも取り組めております。誠にありがとうございました。
Discussion