1. はじめに
最終更新日: 2022 年 7 月 14 日
アプリケーションのオブザーバビリティ
オブザーバビリティと継続的プロファイラ
オブザーバビリティとは、システムの属性を表す用語です。オブザーバビリティを備えたシステムでは、チームが積極的にシステムをデバッグできます。そこで、オブザーバビリティの 3 本柱について説明します。ログ、指標、トレースは、システムがオブザーバビリティを取得するための基本的な計測手段です。
また、オブザーバビリティの 3 つの柱に加え、継続的なプロファイリングもオブザーバビリティの重要な要素であり、業界のユーザーベースを拡大しています。Cloud Profiler は生成元の一つであり、アプリケーションのコールスタックのパフォーマンス指標をドリルダウンするための簡単なインターフェースを提供します。
この Codelab はシリーズのパート 2 で、継続的なプロファイラ エージェントを計測する方法について説明します。パート 1 では、OpenTelemetry と Cloud Trace を使用した分散トレースについて解説し、パート 1 ではマイクロサービスのボトルネックの特定について詳しく学習します。
作成するアプリの概要
この Codelab では、「Shakespeare アプリ」のサーバー サービスで継続的なプロファイラ エージェントを計測します。(別名: Shakesapp)です。Shakesapp のアーキテクチャは次のとおりです。
- Loadgen が HTTP でクエリ文字列をクライアントに送信する
- クライアントが loadgen から gRPC のサーバーにクエリを渡す
- サーバーがクライアントからクエリを受け取り、Google Cloud Storage からすべての Shakespare 作品をテキスト形式で取得して、クエリを含む行を検索し、クライアントに一致する行の番号を返す
パート 1 で、ボトルネックがサーバー サービスのどこかに存在することを発見しましたが、原因を正確に特定することはできませんでした。
学習内容
- Profiler エージェントを埋め込む方法
- Cloud Profiler でボトルネックを調査する方法
この Codelab では、アプリケーションで継続的なプロファイラ エージェントを計測可能にする方法について説明します。
必要なもの
- Go の基本的な知識
- Kubernetes の基本的な知識
2. 設定と要件
セルフペース型の環境設定
Google アカウント(Gmail または Google Apps)をお持ちでない場合は、1 つ作成する必要があります。Google Cloud Platform のコンソール(console.cloud.google.com)にログインし、新しいプロジェクトを作成します。
すでにプロジェクトが存在する場合は、コンソールの左上にあるプロジェクト選択プルダウン メニューをクリックします。
[新しいプロジェクト] をクリックします。] ボタンをクリックし、新しいプロジェクトを作成します。
まだプロジェクトが存在しない場合は、次のような最初のプロジェクトを作成するためのダイアログが表示されます。
続いて表示されるプロジェクト作成ダイアログでは、新しいプロジェクトの詳細を入力できます。
プロジェクト ID を忘れないようにしてください。プロジェクト ID は、すべての Google Cloud プロジェクトを通じて一意の名前にする必要があります(上記の名前はすでに使用されているため使用できません)。以降、この Codelab では PROJECT_ID と呼びます。
次に、Google Cloud リソースを使用して Cloud Trace API を有効にするために、Cloud Console で課金を有効にします(まだ有効にしていない場合)。
この Codelab をすべて実行しても費用はかかりませんが、より多くのリソースを使用する場合や実行したままにする場合は、コストが高くなる可能性があります(このドキュメントの最後にある「クリーンアップ」セクションをご覧ください)。Google Cloud Trace、Google Kubernetes Engine、Google Artifact Registry の料金は、公式ドキュメントに記載されています。
- Google Cloud のオペレーション スイートの料金 |オペレーション スイート
- 料金 |Kubernetes Engine のドキュメント
- Artifact Registry の料金 |Artifact Registry のドキュメント
Google Cloud Platform の新規ユーザーの皆さんには、$300 の無料トライアルをご利用いただけます。その場合は、この Codelab を完全に無料でご利用いただけます。
Google Cloud Shell のセットアップ
Google Cloud と Google Cloud Trace はノートパソコンからリモートで操作できますが、この Codelab では Cloud 上で動作するコマンドライン環境である Google Cloud Shell を使用します。
この Debian ベースの仮想マシンには、必要な開発ツールがすべて揃っています。永続的なホーム ディレクトリが 5 GB 用意されており、Google Cloud で稼働するため、ネットワークのパフォーマンスと認証が大幅に向上しています。つまり、この Codelab に必要なのはブラウザだけです(はい、Chromebook で動作します)。
Cloud コンソールから Cloud Shell を有効にするには、「Cloud Shell をアクティブにする」アイコン をクリックします(環境のプロビジョニングと接続には少し時間がかかります)。
Cloud Shell に接続すると、すでに認証は完了しており、プロジェクトに各自の PROJECT_ID
が設定されていることがわかります。
gcloud auth list
コマンド出力
Credentialed accounts: - <myaccount>@<mydomain>.com (active)
gcloud config list project
コマンド出力
[core] project = <PROJECT_ID>
なんらかの理由でプロジェクトが設定されていない場合は、次のコマンドを実行します。
gcloud config set project <PROJECT_ID>
PROJECT_ID
が見つからない場合は、設定手順で使用した ID を確認するか、Cloud コンソール ダッシュボードで確認します。
Cloud Shell では、デフォルトで環境変数もいくつか設定されます。これらの変数は、以降のコマンドを実行する際に有用なものです。
echo $GOOGLE_CLOUD_PROJECT
コマンド出力
<PROJECT_ID>
最後に、デフォルトのゾーンとプロジェクト構成を設定します。
gcloud config set compute/zone us-central1-f
さまざまなゾーンを選択できます。詳しくは、リージョンとゾーン。
Go 言語の設定
この Codelab では、すべてのソースコードに Go を使用します。Cloud Shell で次のコマンドを実行して、Go のバージョンが 1.17 以降であることを確認します。
go version
コマンド出力
go version go1.18.3 linux/amd64
Google Kubernetes クラスタを設定する
この Codelab では、Google Kubernetes Engine(GKE)でマイクロサービスのクラスタを実行します。この Codelab のプロセスは次のとおりです。
- ベースライン プロジェクトを Cloud Shell にダウンロードする
- マイクロサービスをコンテナに構築する
- Google Artifact Registry(GAR)にコンテナをアップロードする
- GKE にコンテナをデプロイする
- トレース計測用にサービスのソースコードを変更する
- ステップ 2 に進む
Kubernetes Engine を有効にする
まず、Shakesapp が GKE で実行される Kubernetes クラスタを設定するため、GKE を有効にする必要があります。[Kubernetes Engine] メニューに移動します。[有効にする]ボタンを押します
これで、Kubernetes クラスタを作成する準備が整いました。
Kubernetes クラスタを作成する
Cloud Shell で、次のコマンドを実行して Kubernetes クラスタを作成します。Artifact Registry リポジトリの作成に使用するリージョンの下にゾーン値が存在することを確認してください。リポジトリ リージョンがゾーンをカバーしていない場合は、ゾーン値 us-central1-f
を変更します。
gcloud container clusters create otel-trace-codelab2 \ --zone us-central1-f \ --release-channel rapid \ --preemptible \ --enable-autoscaling \ --max-nodes 8 \ --no-enable-ip-alias \ --scopes cloud-platform
コマンド出力
Note: Your Pod address range (`--cluster-ipv4-cidr`) can accommodate at most 1008 node(s). Creating cluster otel-trace-codelab2 in us-central1-f... Cluster is being health-checked (master is healthy)...done. Created [https://container.googleapis.com/v1/projects/development-215403/zones/us-central1-f/clusters/otel-trace-codelab2]. To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-f/otel-trace-codelab2?project=development-215403 kubeconfig entry generated for otel-trace-codelab2. NAME: otel-trace-codelab2 LOCATION: us-central1-f MASTER_VERSION: 1.23.6-gke.1501 MASTER_IP: 104.154.76.89 MACHINE_TYPE: e2-medium NODE_VERSION: 1.23.6-gke.1501 NUM_NODES: 3 STATUS: RUNNING
Artifact Registry と skaffold の設定
これで、Kubernetes クラスタをデプロイする準備が整いました。次に、push コンテナとデプロイ コンテナ用の Container Registry を準備します。これらの手順では、Artifact Registry(GAR)とそれを使用するように skaffold を設定する必要があります。
Artifact Registry の設定
[Artifact Registry] のメニューに移動します。[有効にする]ボタンを押します
しばらくすると、GAR のリポジトリ ブラウザが表示されます。[リポジトリを作成] をクリックします。リポジトリの名前を入力します。
この Codelab では、新しいリポジトリに trace-codelab
という名前を付けます。アーティファクトの形式は「Docker」です。ロケーションタイプは“リージョン”ですGoogle Compute Engine のデフォルト ゾーンに設定したリージョンに近いリージョンを選択します。たとえば、この例では「us-central1-f」を選択しています。ここでは「us-central1 (アイオワ)」を選択します。次に [作成]をクリックして] ボタンを離します。
「trace-codelab」というアクセスできます。
後でここに戻ってレジストリパスを確認します。
Skaffold の設定
Skaffold は、Kubernetes 上で動作するマイクロサービスの構築に便利なツールです。小規模なコマンドセットを使用して、アプリケーションのコンテナのビルド、push、デプロイのワークフローを処理します。Skaffold では、デフォルトでコンテナ レジストリとして Docker Registry が使用されるため、コンテナの push 時に GAR を認識するように skaffold を構成する必要があります。
Cloud Shell をもう一度開き、skaffold がインストールされていることを確認します。(Cloud Shell はデフォルトで skaffold を環境にインストールします)。次のコマンドを実行して、skaffold のバージョンを確認します。
skaffold version
コマンド出力
v1.38.0
これで、skaffold が使用するデフォルトのリポジトリを登録できるようになりました。レジストリパスを取得するには、Artifact Registry ダッシュボードに移動し、前のステップで設定したリポジトリの名前をクリックします。
ページの上部にパンくずリストが表示されます。 アイコンをクリックして、レジストリパスをクリップボードにコピーします。
コピーボタンをクリックすると、ブラウザの下部に次のようなダイアログが表示されます。
"us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab"コピーされました
Cloud Shell に戻ります。ダッシュボードからコピーした値を指定して、skaffold config set default-repo
コマンドを実行します。
skaffold config set default-repo us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab
コマンド出力
set value default-repo to us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab for context gke_stackdriver-sandbox-3438851889_us-central1-b_stackdriver-sandbox
また、レジストリを Docker 構成に構成する必要があります。次のコマンドを実行します。
gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
コマンド出力
{ "credHelpers": { "gcr.io": "gcloud", "us.gcr.io": "gcloud", "eu.gcr.io": "gcloud", "asia.gcr.io": "gcloud", "staging-k8s.gcr.io": "gcloud", "marketplace.gcr.io": "gcloud", "us-central1-docker.pkg.dev": "gcloud" } } Adding credentials for: us-central1-docker.pkg.dev
これで、GKE で Kubernetes コンテナを設定する次のステップに進むことができました。
概要
このステップでは、Codelab 環境を設定します。
- Cloud Shell を設定する
- Container Registry 用の Artifact Registry リポジトリを作成する
- Container Registry を使用するように skaffold を設定する
- Codelab マイクロサービスが実行される Kubernetes クラスタを作成した
次のステップ
次のステップでは、サーバー サービスで継続的なプロファイラ エージェントをインストルメント化します。
3. マイクロサービスのビルド、push、デプロイ
Codelab の資料をダウンロードする
前のステップで、この Codelab のすべての前提条件を設定しました。これで、その上でマイクロサービス全体を実行する準備が整いました。Codelab の資料は GitHub でホストされているため、次の git コマンドを使用して Cloud Shell 環境にダウンロードします。
cd ~ git clone https://github.com/ymotongpoo/opentelemetry-trace-codelab-go.git cd opentelemetry-trace-codelab-go
プロジェクトのディレクトリ構造は次のとおりです。
. ├── README.md ├── step0 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step1 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step2 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step3 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step4 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src ├── step5 │ ├── manifests │ ├── proto │ ├── skaffold.yaml │ └── src └── step6 ├── manifests ├── proto ├── skaffold.yaml └── src
- マニフェスト: Kubernetes マニフェスト ファイル
- proto: クライアントとサーバー間の通信の proto 定義
- src: 各サービスのソースコードのディレクトリ
- skaffold.yaml: skaffold の構成ファイル
この Codelab では、step4
フォルダにあるソースコードを更新します。また、step[1-6]
フォルダのソースコードで最初から変更を確認することもできます。(パート 1 はステップ 0 からステップ 4、パート 2 はステップ 5 と 6 に対応しています)
skaffold コマンドを実行
これで、作成した Kubernetes クラスタにコンテンツ全体をビルド、push、デプロイする準備が整いました。これは複数のステップを含んでいるように見えますが、実際には skaffold がすべての処理を行います。次のコマンドを使用して試してみましょう。
cd step4 skaffold dev
このコマンドを実行すると、すぐに docker build
のログ出力が表示され、レジストリに正常に push されたことを確認できます。
コマンド出力
... ---> Running in c39b3ea8692b ---> 90932a583ab6 Successfully built 90932a583ab6 Successfully tagged us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step1 The push refers to repository [us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice] cc8f5a05df4a: Preparing 5bf719419ee2: Preparing 2901929ad341: Preparing 88d9943798ba: Preparing b0fdf826a39a: Preparing 3c9c1e0b1647: Preparing f3427ce9393d: Preparing 14a1ca976738: Preparing f3427ce9393d: Waiting 14a1ca976738: Waiting 3c9c1e0b1647: Waiting b0fdf826a39a: Layer already exists 88d9943798ba: Layer already exists f3427ce9393d: Layer already exists 3c9c1e0b1647: Layer already exists 14a1ca976738: Layer already exists 2901929ad341: Pushed 5bf719419ee2: Pushed cc8f5a05df4a: Pushed step1: digest: sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe size: 2001
すべてのサービス コンテナが push されると、Kubernetes の Deployment が自動的に開始されます。
コマンド出力
sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 size: 1997 Tags used in deployment: - serverservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/serverservice:step4@sha256:8acdbe3a453001f120fb22c11c4f6d64c2451347732f4f271d746c2e4d193bbe - clientservice -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/clientservice:step4@sha256:b71fce0a96cea08075dc20758ae561cf78c83ff656b04d211ffa00cedb77edf8 - loadgen -> us-central1-docker.pkg.dev/psychic-order-307806/trace-codelab/loadgen:step4@sha256:eea2e5bc8463ecf886f958a86906cab896e9e2e380a0eb143deaeaca40f7888a Starting deploy... - deployment.apps/clientservice created - service/clientservice created - deployment.apps/loadgen created - deployment.apps/serverservice created - service/serverservice created
デプロイ後、各コンテナの stdout に出力された実際のアプリケーション ログが次のように表示されます。
コマンド出力
[client] 2022/07/14 06:33:15 {"match_count":3040} [loadgen] 2022/07/14 06:33:15 query 'love': matched 3040 [client] 2022/07/14 06:33:15 {"match_count":3040} [loadgen] 2022/07/14 06:33:15 query 'love': matched 3040 [client] 2022/07/14 06:33:16 {"match_count":3040} [loadgen] 2022/07/14 06:33:16 query 'love': matched 3040 [client] 2022/07/14 06:33:19 {"match_count":463} [loadgen] 2022/07/14 06:33:19 query 'tear': matched 463 [loadgen] 2022/07/14 06:33:20 query 'world': matched 728 [client] 2022/07/14 06:33:20 {"match_count":728} [client] 2022/07/14 06:33:22 {"match_count":463} [loadgen] 2022/07/14 06:33:22 query 'tear': matched 463
この時点では、サーバーからのメッセージがすべて表示されます。これで、サービスの分散トレースのために OpenTelemetry を使用してアプリケーションを計測する準備が整いました。
サービスの計測を開始する前に、Ctrl+C を使用してクラスタをシャットダウンしてください。
コマンド出力
... [client] 2022/07/14 06:34:57 {"match_count":1} [loadgen] 2022/07/14 06:34:57 query 'what's past is prologue': matched 1 ^CCleaning up... - W0714 06:34:58.464305 28078 gcp.go:120] WARNING: the gcp auth plugin is deprecated in v1.22+, unavailable in v1.25+; use gcloud instead. - To learn more, consult https://cloud.google.com/blog/products/containers-kubernetes/kubectl-auth-changes-in-gke - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
概要
このステップでは、ご使用の環境で Codelab の資料を準備し、skaffold が想定どおりに実行されることを確認しました。
次のステップ
次のステップでは、loadgen サービスのソースコードを変更して、トレース情報を計測できるようにします。
4. Cloud Profiler エージェントのインストルメンテーション
継続的プロファイリングのコンセプト
継続的プロファイリングの概念を説明する前に、まずプロファイリングを理解する必要があります。プロファイリングは、アプリケーションを動的に分析する方法(動的プログラム分析)の一つで、通常はアプリケーション開発時の負荷テストなどのプロセスで実行されます。これは、特定の期間におけるシステム指標(CPU 使用率やメモリ使用量など)を測定するためのシングル ショット アクティビティです。デベロッパーは、プロファイル データを収集した後、コードの外部で分析します。
継続的プロファイリングは、通常のプロファイリングを拡張したアプローチです。長時間実行されるアプリケーションに対して短時間のプロファイルを定期的に実行し、一連のプロファイル データを収集します。次に、バージョン番号、デプロイゾーン、測定時間など、アプリケーションの特定の属性に基づいて、統計分析が自動的に生成されます。このコンセプトの詳細については、こちらのドキュメントをご覧ください。
ターゲットは実行中のアプリケーションであるため、プロファイル データを定期的に収集し、統計データの後処理を行うバックエンドに送信する方法があります。これが Cloud Profiler エージェントです。このエージェントをサーバー サービスに組み込みます。
Cloud Profiler エージェントを埋め込む
Cloud Shell エディタを開くには、Cloud Shell の右上にあるボタン を押します。左側のペインにあるエクスプローラから
step4/src/server/main.go
を開き、main 関数を見つけます。
step4/src/server/main.go
func main() { ... // step2. setup OpenTelemetry tp, err := initTracer() if err != nil { log.Fatalf("failed to initialize TracerProvider: %v", err) } defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Fatalf("error shutting down TracerProvider: %v", err) } }() // step2. end setup svc := NewServerService() // step2: add interceptor interceptorOpt := otelgrpc.WithTracerProvider(otel.GetTracerProvider()) srv := grpc.NewServer( grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor(interceptorOpt)), grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor(interceptorOpt)), ) // step2: end adding interceptor shakesapp.RegisterShakespeareServiceServer(srv, svc) healthpb.RegisterHealthServer(srv, svc) if err := srv.Serve(lis); err != nil { log.Fatalf("error serving server: %v", err) } }
main
関数には、Codelab パート 1 で行った OpenTelemetry と gRPC のセットアップ コードがあります。ここでは、Cloud Profiler エージェントの計測を追加します。initTracer()
で行ったように、読みやすくするために initProfiler()
という関数を作成します。
step4/src/server/main.go
import ( ... "cloud.google.com/go/profiler" // step5. add profiler package "cloud.google.com/go/storage" ... ) // step5: add Profiler initializer func initProfiler() { cfg := profiler.Config{ Service: "server", ServiceVersion: "1.0.0", NoHeapProfiling: true, NoAllocProfiling: true, NoGoroutineProfiling: true, NoCPUProfiling: false, } if err := profiler.Start(cfg); err != nil { log.Fatalf("failed to launch profiler agent: %v", err) } }
profiler.Config{}
オブジェクトで指定されたオプションを詳しく見てみましょう。
- サービス: Profiler のダッシュボードで選択して切り替えることができるサービス名
- ServiceVersion: サービス バージョン名。この値に基づいてプロファイル データセットを比較できます。
- NoHeapProfiling: メモリ使用量のプロファイリングを無効にします。
- NoAllocProfiling: メモリ割り当てプロファイリングを無効にします。
- NoGoroutineProfiling: goroutine プロファイリングを無効にします。
- NoCPUProfiling: CPU プロファイリングを無効にします。
この Codelab では、CPU プロファイリングのみを有効にします。
あとは、main
関数でこの関数を呼び出すだけです。インポート ブロックで Cloud Profiler パッケージをインポートしてください。
step4/src/server/main.go
func main() { ... defer func() { if err := tp.Shutdown(context.Background()); err != nil { log.Fatalf("error shutting down TracerProvider: %v", err) } }() // step2. end setup // step5. start profiler go initProfiler() // step5. end svc := NewServerService() // step2: add interceptor ... }
go
キーワードを指定して initProfiler()
関数を呼び出していることに注意してください。profiler.Start()
がブロックされているため、別の goroutine で実行する必要があります。ビルドの準備が整いましたデプロイ前に必ず go mod tidy
を実行してください。
go mod tidy
新しいサーバー サービスを使用してクラスタをデプロイします。
skaffold dev
通常、Cloud Profiler にフレームグラフが表示されるまでに数分かかります。「profiler」と入力します。をクリックし、Profiler のアイコンをクリックします。
次のようなフレームグラフが表示されます。
概要
このステップでは、Cloud Profiler エージェントをサーバー サービスに埋め込み、フレームグラフが生成されることを確認しました。
次のステップ
次のステップでは、フレームグラフを使用してアプリケーションのボトルネックの原因を調査します。
5. Cloud Profiler のフレームグラフを分析する
フレームグラフとは何ですか?
フレームグラフは、プロファイル データを可視化する方法の一つです。詳細な説明についてはドキュメントをご覧ください。概要は次のとおりです。
- 各バーはアプリでのメソッド/関数呼び出しを表す
- 垂直方向はコールスタックです。コールスタックが上から下に増加する
- 水平方向はリソースの使用量です。長くなるほど悪くなります。
それを踏まえて、取得したフレームグラフを見てみましょう。
フレームグラフの分析
前のセクションでは、フレームグラフの各バーが関数/メソッドの呼び出しを表し、その長さが関数/メソッドのリソース使用量を表すことを学びました。Cloud Profiler のフレームグラフでは、バーが降順または左から右に長さで並べ替えられます。最初はグラフの左上から確認を開始できます。
この例では、grpc.(*Server).serveStreams.func1.2
が CPU 時間の大半を消費していることが明らかであり、コールスタックを上から下に見ると、サーバー サービスの gRPC サーバー ハンドラである main.(*serverService).GetMatchCount
がほとんどの時間を費やしています。
GetMatchCount の下に、一連の正規表現関数 regexp.MatchString
と regexp.Compile
が表示されます。これらは標準パッケージのものです。つまり、パフォーマンスを含む多くの観点で十分にテストする必要があります。しかし、ここでの結果は、regexp.MatchString
と regexp.Compile
で CPU 時間リソースの使用量が高いことを示しています。このようなことから、ここでは regexp.MatchString
の使用がパフォーマンスの問題に関係していると仮定します。それでは、関数が使用されているソースコードを見てみましょう。
step4/src/server/main.go
func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) { resp := &shakesapp.ShakespeareResponse{} texts, err := readFiles(ctx, bucketName, bucketPrefix) if err != nil { return resp, fmt.Errorf("fails to read files: %s", err) } for _, text := range texts { for _, line := range strings.Split(text, "\n") { line, query := strings.ToLower(line), strings.ToLower(req.Query) isMatch, err := regexp.MatchString(query, line) if err != nil { return resp, err } if isMatch { resp.MatchCount++ } } } return resp, nil }
これは regexp.MatchString
が呼び出される場所です。ソースコードを読むと、ネストされた for-loop 内で関数が呼び出されていることにお気づきでしょう。そのため、この関数の使用は誤っている可能性があります。regexp の GoDoc を検索してみましょう。
ドキュメントによると、regexp.MatchString
はすべての呼び出しで正規表現パターンをコンパイルします。リソースの大量消費の原因はこうです
概要
このステップでは、フレームグラフを分析して、リソース消費の原因を推定しました。
次のステップ
次のステップでは、サーバー サービスのソースコードを更新し、バージョン 1.0.0 からの変更を確認します。
6. ソースコードを更新してフレームグラフの差分を確認する
ソースコードを更新する
前のステップでは、regexp.MatchString
の使用がリソースの大量消費に関係していると仮定しました。この問題を解決しましょうコードを開き、その部分を少し変更します。
step4/src/server/main.go
func (s *serverService) GetMatchCount(ctx context.Context, req *shakesapp.ShakespeareRequest) (*shakesapp.ShakespeareResponse, error) { resp := &shakesapp.ShakespeareResponse{} texts, err := readFiles(ctx, bucketName, bucketPrefix) if err != nil { return resp, fmt.Errorf("fails to read files: %s", err) } // step6. considered the process carefully and naively tuned up by extracting // regexp pattern compile process out of for loop. query := strings.ToLower(req.Query) re := regexp.MustCompile(query) for _, text := range texts { for _, line := range strings.Split(text, "\n") { line = strings.ToLower(line) isMatch := re.MatchString(line) // step6. done replacing regexp with strings if isMatch { resp.MatchCount++ } } } return resp, nil }
ご覧のように、正規表現パターンのコンパイル プロセスが regexp.MatchString
から抽出され、ネストされた for ループの外に移動します。
このコードをデプロイする前に、initProfiler()
関数でバージョン文字列を更新してください。
step4/src/server/main.go
func initProfiler() { cfg := profiler.Config{ Service: "server", ServiceVersion: "1.1.0", // step6. update version NoHeapProfiling: true, NoAllocProfiling: true, NoGoroutineProfiling: true, NoCPUProfiling: false, } if err := profiler.Start(cfg); err != nil { log.Fatalf("failed to launch profiler agent: %v", err) } }
では、その仕組みを見ていきましょう。skaffold コマンドを使用してクラスタをデプロイする。
skaffold dev
しばらくしてから、Cloud Profiler ダッシュボードを再読み込みして、表示を確認します。
バージョン 1.1.0 のプロファイルのみが表示されるように、バージョンを "1.1.0"
に変更してください。おわかりのように、GetMatchCount のバーの長さが短くなり、CPU 時間の使用率が低下しました(つまり、バーが短くなりました)。
1 つのバージョンのフレームグラフを確認するだけでなく、2 つのバージョンの差分を比較することもできます。
[比較対象] の値を変更する[Version] のプルダウン リストから[比較バージョン]の値を変更します「1.0.0」に変更します。
このようなフレームグラフが表示されます。グラフの形状は 1.1.0 と同じですが、色は異なります。比較モードの場合、色の意味は次のようになります。
- 青: 値(リソース消費量)を削減
- オレンジ: 取得した値(リソース消費量)
- グレー: 中間色
凡例を踏まえて、関数を詳しく見てみましょう。拡大したいバーをクリックすると、グルーピング内の詳細情報が表示されます。バー main.(*serverService).GetMatchCount
をクリックしてください。また、バーにカーソルを合わせると、比較の詳細が表示されます。
合計 CPU 時間が 5.26 秒から 2.88 秒に短縮されたことがわかります(合計は 10 秒 = サンプリング ウィンドウ)。これは大きな改善点です。
プロファイル データの分析から、アプリケーションのパフォーマンスを向上させることができます。
概要
このステップでは、サーバー サービスを編集し、Cloud Profiler の比較モードが改善されたことを確認しました。
次のステップ
次のステップでは、サーバー サービスのソースコードを更新し、バージョン 1.0.0 からの変更を確認します。
7. 追加のステップ: Trace ウォーターフォールで改善されたことを確認する
分散トレースと継続的プロファイリングの違い
Codelab のパート 1 では、リクエストパスのマイクロサービス全体でボトルネック サービスを特定できることと、特定のサービスにおけるボトルネックの正確な原因を特定できないことを確認しました。このパート 2 の Codelab では、継続的プロファイリングによって、1 つのサービス内のボトルネックをコールスタックから特定できることを学びました。
このステップでは、分散トレース(Cloud Trace)のウォーターフォール グラフを確認し、継続的プロファイリングとの違いを確認します。
このウォーターフォール グラフは、「love」というクエリを含むトレースの 1 つです。合計で約 6.7 秒(6,700 ミリ秒)かかります。
同じクエリを改良した後です。おわかりのように、合計レイテンシは 1.5 秒(1,500 ミリ秒)になり、以前の実装から大幅に改善されています。
ここで重要なのは、分散トレースのウォーターフォール チャートでは、あらゆる場所のスパンを計測しなければコールスタック情報を利用できないことです。また、分散トレースはサービス全体のレイテンシに注目するだけですが、継続的なプロファイリングは単一のサービスのコンピュータ リソース(CPU、メモリ、OS スレッド)に焦点を当てます。
別の側面として、分散トレースはイベントベースであり、連続プロファイルは統計的です。トレースごとにレイテンシ グラフが異なり、レイテンシの変化の傾向を確認するには、分布などの異なる形式が必要です。
概要
このステップでは、分散トレースと継続的プロファイリングの違いを確認しました。
8. 完了
OpenTelemery を使用して分散トレースを作成し、Google Cloud Trace でマイクロサービス全体のリクエストのレイテンシを確認できました。
より長い演習が必要な場合は、以下のトピックをご自身で試してください。
- 現在の実装では、ヘルスチェックによって生成されたすべてのスパンが送信されます。(
grpc.health.v1.Health/Check
)Cloud Trace からそれらのスパンをどのように除外しますか?こちらのヒントをご覧ください。 - イベントログをスパンと関連付けて、Google Cloud Trace と Google Cloud Logging でどのように機能するかを確認する。こちらのヒントをご覧ください。
- 一部のサービスを別の言語のサービスに置き換えて、その言語の OpenTelemetry で計測してみます。
また、プロファイラについてさらに詳しくお知りになりたい場合は、パート 2 にお進みください。その場合は、以下のクリーンアップの説明をスキップできます。
クリーンアップ
この Codelab の後、Google Kubernetes Engine、Google Cloud Trace、Google Artifact Registry で予期しない請求が発生しないように、Kubernetes クラスタを停止し、プロジェクトを削除してください。
まず、クラスタを削除します。skaffold dev
でクラスタを実行している場合は、Ctrl+C キーを押すだけで済みます。skaffold run
でクラスタを実行している場合は、次のコマンドを実行します。
skaffold delete
コマンド出力
Cleaning up... - deployment.apps "clientservice" deleted - service "clientservice" deleted - deployment.apps "loadgen" deleted - deployment.apps "serverservice" deleted - service "serverservice" deleted
クラスタを削除したら、メニューペインで [IAM と管理>[設定]、[シャットダウン] の順にクリックします] ボタンを離します。
次に、ダイアログのフォームに(プロジェクト名ではなく)プロジェクト ID を入力し、シャットダウンを確認します。