基準線を作る
このラボは本物のVM上で動きます
この箱はPodではなく、KubeVirtが起動した仮想マシンです。Linuxカーネルが別に動き、systemdが実際にサービスを管理し、dockerは模倣ではなく本物のDockerエンジンです。docker runで起動したコンテナは実際にプロセスになり、docker execもdocker logsもそのまま動作します。
以前は、このラボはPodの中で動いていました。カーネルの権限をすべて下ろした箱なので、コンテナを起動するステップが塞がれていて、そのためイメージのアーカイブを直接展開してみるという回り道で学んでいました。もう回り道は必要ありません。
知っておくことが2つあります。
- 最初の起動に1分ほどかかります。VMが起動してDockerをインストールするためです。Podのラボ(通常40秒)より遅くなります。
- ブラウザのプレビューはありません。VMに入ってくる接続は、採点用のポート1つだけが開いています。Webサーバーを起動したなら、VMの中で
curlで確認してください。
目標
実際のコンテナに負荷をかけて、RPS・p50・p95・p99・エラー率を取り出し、3回繰り返して、変動幅まで含めた再現可能なベースラインを/root/lt1/に作ります。
なぜ重要なのか
ベースラインがなければ、「遅くなった」という言葉は感想です。そしてベースラインは、数字だけでは足りません。同時実行数がいくつだったか、何件送ったか、いつ測ったかがなければ、来月に比較できないからです。指標を選ぶ基準も明確です。リクエスト10,000件のうち9,900件が50ms、100件が3,000msのとき、平均は79.5msで、実際に79.5msで応答を受けたリクエストは1件もありません。しかも、遅い100件が2倍悪化しても、平均は79.5 → 109.5msまでしか動きません。そのため、平均1つで判断せずに、p50・p95・p99を一緒に見て、最後には3回測って、変動幅より大きい差だけを「変化」と呼ぶことにします。
負荷ツールの出力の読み方
このイメージにはheyが入っています。出力には3つのセクションがあり、以降のステップはすべて、このセクションから値を読みます。
Summary:セクションのRequests/sec:行 → スループット。行の最後のフィールドが値です。Summary:セクションのAverage:行 → 平均レイテンシ(秒)。2番目のフィールドが値です。Latency distribution:セクションの95% in 0.0086 secsのような行 → パーセンタイル。3番目のフィールドが値です。Status code distribution:セクションの[200] 2000 responsesのような行 → ステータスコードごとの件数。2番目のフィールドが件数です。
出力全体をそのまま保存してください。採点ツールが、あなたが書いた値を、この元データと再び突き合わせます。
ステップ
先にmkdir -p /root/lt1を実行しておいてください。
- 負荷対象のコンテナを起動します。名前:
lt-web、ポート: ホスト127.0.0.1:8085→ コンテナ80、そしてcurl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8085/の結果が200である必要があります。\n 対象は応答時間に裾がある必要があります。ほとんどは速く、12件に1回ほど遅いサーバーをPythonで作って、python:3.12-alpineコンテナに入れてください。nginxで静的ファイルを返すと、1件が1msもかからず、平均とp95が同じ値になり、ステップ6で使う根拠がなくなります。 - スモーク実行の結果を
/root/lt1/smoke.txtに保存します。総レスポンスが50件以上である必要があり、Requests/sec・Latency distribution・Status code distributionの3つのセクションが、すべてファイルにある必要があります。 - ウォームアップと本測定を分けます。
/root/lt1/warmup.txt(ウォームアップ)と/root/lt1/run1.txt(本測定)をそれぞれ保存し、run1.txtの総レスポンスは200件以上である必要があります。 /root/lt1/metrics.jsonに、5つのフィールドを書きます。rps、p50、p95、p99、error_rateです。最初の4つの値はrun1.txtから読んだ値そのまま(秒単位)、error_rateは2xx以外のレスポンス数 ÷ 全レスポンス数の比率(0–1)です。/root/lt1/errors.txtに3行を書きます。non2xx=(整数)、total=(整数)、rate=(パーセンテージ、小数第2位)です。最初の2つの値は、ステータスコード分布で数えた値と正確に同じである必要があります。/root/lt1/why.mdに、3行と説明を書きます。average_s=(run1.txtのAverage)、p95_s=(run1.txtの95%パーセンタイル)、ratio=(p95 ÷ 平均、小数第2位)です。そして、平均だけを見ると何を見逃すのかを、本文に、「平均」という単語を含めて説明してください。- 同じ条件でもう2回測定して、
/root/lt1/run2.txt、/root/lt1/run3.txtを作成し、/root/lt1/runs.csvにまとめます。形式は、1行目がヘッダーrun,rps,p95、続いてデータ行はちょうど3つ(1,<rps>,<p95>の形)、最後にspread=の行を置きます。spreadは、最大RPSから最小RPSを引いた値 ÷ 最小RPS × 100で、小数第1位までです。 /root/lt1/baseline.jsonに、6つのフィールドを書きます。rps、p95、error_rate、concurrency、requests、measured_atです。rpsは3回の測定の中央値(真ん中の値)である必要があり、error_rateは0.01以下である必要があります(エラーが出ている状態の数字は、ベースラインになれません)。
参考
- 実行例:
hey -n 100 -c 2 http://127.0.0.1:8085/ > /root/lt1/smoke.txt 2>&1。-nは総リクエスト数、-cは同時実行数です。 - 値の取り出し:
grep -i 'Requests/sec' run1.txt | awk '{print $NF}'、grep '95% in' run1.txt | awk '{print $3}'、grep 'Average:' run1.txt | awk '{print $2}'。 - ステータスコードの合計は、
awk '/Status code distribution/{f=1;next} f && /responses/ {gsub(/[][]/," "); t+=$2} END{print t}' run1.txtで数えられます。 - 中央値は、
cut -d, -f2 runs.csv | sort -gのあとの真ん中の値です。 - よくある間違い1:
runs.csvのヘッダーを、数字で始まるように書いてしまうこと。データ行が3つを超えて、失敗します。 - よくある間違い2: 値を四捨五入して、適当に書いてしまうこと。採点ツールが元の出力から再計算して、5%以内かを確認します。
負荷対象のコンテナを起動する
負荷対象のコンテナを起動します。名前: lt-web、ポート: ホスト127.0.0.1:8085 → コンテナ80、そしてcurl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8085/の結果が200である必要があります。\n 対象は応答時間に裾がある必要があります。ほとんどは速く、12件に1回ほど遅いサーバーをPythonで作って、python:3.12-alpineコンテナに入れてください。nginxで静的ファイルを返すと、1件が1msもかからず、平均とp95が同じ値になり、ステップ6で使う根拠がなくなります。
名前は正確にlt-webで、ホスト127.0.0.1のポート8085をコンテナのポート80に接続する必要があります。ホスト側が1024未満だとバインドできないので、高いポートを使い、コンテナの内側はrootなので80をそのまま使ってもかまいません。対象は静的ファイルサーバーではなく、応答時間に裾があるサーバーである必要があります。そうしてこそ、ステップ6で平均とp95が分かれることを、自分の測定値で見られます。
スモーク実行でツールの出力を確認する
スモーク実行の結果を/root/lt1/smoke.txtに保存します。総レスポンスが50件以上である必要があり、Requests/sec・Latency distribution・Status code distributionの3つのセクションが、すべてファイルにある必要があります。
結果を/root/lt1/smoke.txtにそのまま保存します。サマリー・レイテンシ分布・ステータスコード分布の3つのセクションがすべて残る必要があるので、リダイレクトするときに一部だけを切り出してはいけません。最低50件以上を送ってください。
ウォームアップと本測定を分ける
ウォームアップと本測定を分けます。/root/lt1/warmup.txt(ウォームアップ)と/root/lt1/run1.txt(本測定)をそれぞれ保存し、run1.txtの総レスポンスは200件以上である必要があります。
warmup.txtとrun1.txtを別々に作ります。最初のリクエストは、キャッシュもコネクションもない状態を測るので、本測定に混ぜると分布が汚染されます。本測定は200件以上ないと、分布を見られません。
主要な指標5つをJSONにまとめる
/root/lt1/metrics.jsonに、5つのフィールドを書きます。rps、p50、p95、p99、error_rateです。最初の4つの値はrun1.txtから読んだ値そのまま(秒単位)、error_rateは2xx以外のレスポンス数 ÷ 全レスポンス数の比率(0–1)です。
/root/lt1/metrics.jsonの値は、run1.txtから読んだ値そのままである必要があります。でっち上げると、元データと突き合わせる過程で見つかります。エラー率は比率(0–1)で、ステータスコード分布から、2xx以外の件数を数えて求めます。
エラー率を手で計算する
/root/lt1/errors.txtに3行を書きます。non2xx=(整数)、total=(整数)、rate=(パーセンテージ、小数第2位)です。最初の2つの値は、ステータスコード分布で数えた値と正確に同じである必要があります。
/root/lt1/errors.txtに、non2xx / total / rateの3行を書きます。最初の2つの値は整数で正確に一致する必要があり、rateはパーセンテージです。ステータスコード分布のセクションの件数をすべて足すと、totalになります。
平均とp95の倍率を比較する
/root/lt1/why.mdに、3行と説明を書きます。average_s=(run1.txtのAverage)、p95_s=(run1.txtの95%パーセンタイル)、ratio=(p95 ÷ 平均、小数第2位)です。そして、平均だけを見ると何を見逃すのかを、本文に、「平均」という単語を含めて説明してください。
/root/lt1/why.mdに、average_s / p95_s / ratioの3行と、平均がなぜ足りないのかの説明を書きます。ratioは、p95を平均で割った値です。値はrun1.txtから読んでください。
3回の繰り返し測定と変動幅
同じ条件でもう2回測定して、/root/lt1/run2.txt、/root/lt1/run3.txtを作成し、/root/lt1/runs.csvにまとめます。形式は、1行目がヘッダーrun,rps,p95、続いてデータ行はちょうど3つ(1,<rps>,<p95>の形)、最後にspread=の行を置きます。spreadは、最大RPSから最小RPSを引いた値 ÷ 最小RPS × 100で、小数第1位までです。
同じ条件でrun2.txt、run3.txtをさらに作って、runs.csvにまとめます。変動幅は、最大と最小の差を最小で割ったパーセンテージです。この値より小さい差は、改善と呼べません。
ベースラインを確定する
/root/lt1/baseline.jsonに、6つのフィールドを書きます。rps、p95、error_rate、concurrency、requests、measured_atです。rpsは3回の測定の中央値(真ん中の値)である必要があり、error_rateは0.01以下である必要があります(エラーが出ている状態の数字は、ベースラインになれません)。
/root/lt1/baseline.jsonには、数字だけでなく、測定条件も一緒に入れて初めて、あとで比較できます。代表値は3回のうちの真ん中の値で、エラーが出ている状態の数字は、基準になれません。