アプリケーションエンジニアとしてGKEで動くサービスの負荷に立ち向かう

はじめまして。
HRBrain「永遠の二番手 ルイージ」こと、まつき(@ymatzki)です。(嘘です。)
弊社が誇る文豪のmano先生の次を務めさせていただきます。

今回は表題の通り、「アプリケーションエンジニア」として負荷検証をどのように行っているかを紹介したいと思います。

この記事はHRBrainアドベントカレンダーの第二日目の記事です。

qiita.com

背景

「どうやって負荷検証を行っているか」の前に、「なぜ負荷検証を行っているか」の背景を説明させていただきます。

弊社は企業向けにHRTechのSaaSを開発・運用しています。
近頃は企業のHRTechの重要性の高まりに加え、弊社つよつよのビジネスチーム(社内ではレベニューと呼んでいます)の活躍により、大手企業様にも導入していただけるケースが増えてきました。

www.hrbrain.jp

一方でユーザが増えることで当然システムの負荷も増えます。
それにより、今まではそれほど意識する必要がなかった負荷対策を開発段階から意識せざるを得ない状況になりました。

この状況変化に伴いアプリケーション開発チームとして、リリース前の開発段階で負荷検証を行うようになりました。

負荷検証のフロー

負荷検証はざっくり以下の流れで行っています。

  1. LoadTest環境にアプリケーションをデプロイ
  2. 負荷試験実施とボトルネックの発見・解消

余談ですが、これはあくまでも「新機能リリース前にアプリケーション開発チームが行う負荷検証の流れ」であり、 この流れとは別に既存機能の負荷検証なども行っています。

1. LoadTest環境にアプリケーションをデプロイ

まず負荷試験前の準備ですが、
以前の記事にも出てきたように弊社にはLoadTest環境という負荷試験用の環境が存在します。

times.hrbrain.co.jp

このLoadTest環境へのアプリケーションのデプロイは非常に簡単で、
gitでloadtestというブランチ名を作成しpushすることで自動でデプロイされ、負荷試験が可能になります。

非常に手軽に利用できるが故にLoadTest環境の利用タイミングが被るときがあります。
現状は「LoadTest環境を使っているかどうかをSlack呼びかけて確認する」というアナログな方法をとっていますが、今のところはそれほど問題なく運用できています。

2. 負荷試験実施とボトルネックの発見・解消

次に負荷試験の実施についてですが、
先日行った実際の負荷試験内容で説明させていただきます。

今回の負荷試験は、弊社サービス社員名簿に実装した「CSVファイルでデータをインポートする」という機能に対して行いました。
負荷試験用のデータとして行と列の多いCSVファイルを用意しました。

負荷試験を行うということは負荷試験の結果をモニタリングする必要があります。
モニタリングにはCloud ProfilerとCloud Logging、K8sのeventを使い、 結果としてボトルネックとOOMのリスクを発見することができました。

Cloud Profilerでボトルネックを調査

Cloud Profileをご存知でしょうか?
端的に言えばCPUやメモリのパフォーマンスを分析し、可視化するツールです。

cloud.google.com

弊社のServerは主にGoで実装され、GKEのK8sクラスタで動いています。
よって、GCPの公式ドキュメントに沿って変更していけばそこまで難なく導入が可能でした。

cloud.google.com

実際の負荷試験の中のプロファイラの結果は以下です。

f:id:hrb-yusaku-matsuki:20201202115012p:plain
Cloud Profile

ImportMember...という関数が、メモリを使っていることが見て取れます。

Cloud LoggingとK8sのeventでOOMを検知

実はこのとき、アプリケーションが要求するmemoryがPodに設定しているmemoryのlimitを超えていたため、 OOMでcontainerがkillされていました。

当初はCloud Loggingを見てもkilledというエラーメッセージが残っているだけで、なにが発生しているか不明でした。

f:id:hrb-yusaku-matsuki:20201201223150p:plain
Cloud Loggingによるログ

そこで公式ドキュメントを参考にK8s側の設定の確認とnodeで発生しているeventを確認しました。

kubernetes.io

公式ドキュメント通りに kubectl describe nodes を実行するとまさにOOMによりcontainerが強制終了されていることがわかりました。

$ kubectl describe nodes
...
Events:
  Type     Reason      Age   From                                                                 Message
  ----     ------      ----  ----                                                                 -------
...
  Warning  OOMKilling  98s  kernel-monitor, gke-shared-central-clus-standard-pool-xxxxxxxx-xxxx  Memory cgroup out of memory: Kill process 481010 (app) score 2043 or sacrifice child

あとは先述したプロファイラを見ながらMemoryを多く消費している箇所のアタリを付けパフォーマンス・チューニングし無事機能をリリースすることができました。

まとめ

新機能リリース前のパフォーマンスチューニングの一部をご紹介させていただきました。
さもアプリケーションチームが自力でパフォーマンスイシューを発見したかのように書きましたが、SREチームに協力してもらい解決までたどり着きました。

いまHRBrainは新しい機能をガンガン開発しつつ、パフォーマンスに配慮した開発を行うというフェーズに入っています。
この難しい局面に共にトライし、HRBrain、引いては日本の企業を更に成長させたいという志を持った方を募集中です!