Four Keys とは?考え方から導入まで徹底検証してみた

Sreake事業部

2023.7.21

はじめに

Sreake事業部でインターンをしている村山です。私は以前に、2022年のAccelerate State of DevOps Reportについて調査を行いました。DevOps Reportでは、Four Keysという指標を用いて、チームのソフトウェアデリバリーのパフォーマンスについて評価をしています。今回は、Four Keysへの理解を深めると共にFour Keysの導入方法を学ぶため、Four Keysの計測システムについて調査・検証しました。

Four Keysとは

Four Keysとは、Google CloudのDevOps Research and Assessment(DORA)チームによって提案された、ソフトウェア開発チームのパフォーマンスを示す4つの指標のことです。Four Keysはソフトウェアデリバリーのスループットと安定性に関する指標で、具体的には以下のような指標です。

  • スループットに関する指標
    • デプロイの頻度(Deployment Frequency)
    • 変更のリードタイム(Lead Time for Changes)
  • 安定性に関する指標
    • 変更障害率(Change Failure Rate)
    • サービス復元時間・MTTR(Time to Restore Service)

これらの指標を活用することでチームのパフォーマンスの分析をし、ソフトウェアデリバリーのパフォーマンスの向上を図ることができます。詳細については、DevOps Reportのまとめを参照ください。

dora-team/fourkeysについて

dora-team/fourkeys は、DORAチームが提供する、ソフトウェア開発チームのパフォーマンスを評価するためのシステムでGitHubで公開されています。このシステムは、GitHubやGitLabなどのプロジェクトの中でプルリクエストやissueの発行などのイベントを収集し、それを基にFour Keysを測定します。システムは、プロジェクトやリポジトリに対してデータの収集のみを行い、それらを変更することはありません。また、このシステムを一度導入した後に利用を中止する際も、計測対象となっているリポジトリのwebhookを削除もしくは停止するだけで良いため、GitHubやGitLabのリポジトリへの影響は小さく、計測システムによる問題は生じにくいです。

システムの構成は以下の図のようになっています。

dora-team/fourkeys/images/fourkeys-design.pngより引用

また、このシステム及びFour Keysの使用を想定しているユーザとして、以下のような項目を上げています。

  • チームのソフトウェアデリバリーのパフォーマンスを測定したい
  • GitHubまたはGitLabにプロジェクトがある
  • プロジェクトにデプロイがある

3つ目の項目の「プロジェクトにデプロイがある」ことが必要である理由は、ライブラリのようにリリースがあるもののデプロイがないプロジェクトでは、Four Keysの計測が機能しないためです。

導入方法

Four Keys計測システムの導入の手順は以下のとおりです。

  1. dora-team/fourkeys のリポジトリをフォークもしくはクローン
  2. Cloud Buildで必要なイメージのビルド
  3. 必要なTerraform variablesの設定
  4. Terraformの実行によるリソースのデプロイ
  5. webhookの追加

0.導入前の準備

Four Keys計測システムのデプロイのために以下のものが必要です。

  • 課金が有効化されたGoogle Cloudプロジェクト
  • プロジェクト内でのオーナーロール
  • Google Cloud CLIとTerraformがインストールされた環境

1.リポジトリのクローン

Four Keys計測システムのリポジトリをクローンします。

$ git clone https://github.com/dora-team/fourkeys.git && cd fourkeys

Google Cloudの環境変数を設定します。今回はdirenvを用いて設定を行いました。

$ direnv edit .
> export PROJECT_ID="YOUR_PROJECT_ID"

2.イメージのビルド

Cloud Buildを使用してコンテナをビルドし、ダッシュボード、event-handlerのためにGoogle Container Registryにプッシュします。

$ gcloud builds submit dashboard --config=dashboard/cloudbuild.yaml --project $PROJECT_ID && \
gcloud builds submit event-handler --config=event-handler/cloudbuild.yaml --project $PROJECT_ID

実行時に以下のようなエラーが出ることがあります。

ERROR: (gcloud.builds.submit) INVALID_ARGUMENT: could not resolve source: googleapi: Error 403: <project-number>@cloudbuild.gserviceaccount.com does not have storage.objects.get access to the Google Cloud Storage object. Permission 'storage.objects.get' denied on resource (or it may not exist)., forbidden

その場合は、IAMロールからcloudbuild.gserviceaccount.comのアカウントにStorage オブジェクト閲覧者(roles/storage.objectViewer)を追加します。

次にパーサーのビルドとプッシュをします。パーサーが用意されているサービスは、

  • Argo CD
  • CircleCI
  • Cloud Build
  • GitHub
  • GitLab
  • PagerDuty
  • Tekton

であり、今回は例としてgithubのパーサーをビルドします。

$ gcloud builds submit bq-workers --config=bq-workers/parsers.cloudbuild.yaml --project $PROJECT_ID --substitutions=_SERVICE=github

その他のサービスのパーサを使用する場合は、以下のようにサービス名を入力してください。

--substitutions=_SERVICE={service}

3.Terraform variablesの設定

Terraform の tfvars のファイルのリネームを行います。

$ cd terraform/example && mv terraform.tfvars.example terraform.tfvars

variables.tf を参考に terraform.tfvars を編集します。

parserには、先程ビルドしたサービスを入力します。

project_id = "{PROJECT_ID}"
region = "us-central1"
bigquery_region = "US"
parser = ["github"]

GitLabの場合、parserに gitlab を追加します。

4.リソースのデプロイ

exampleディレクトリでTerraformの実行します。

$ terraform init
$ terraform plan
$ terraform apply

これにより、Cloud RunのサービスやBigQueryのdatasetやroutinesを始めとしたFour Keysの計測に必要なリソースが作成されます。

5.webhookの追加

このシステムでは、各サービスのwebhookから送信されるイベントを基にFour Keysを計測します。webhookから受信したイベントは、変更に関するイベント、デプロイに関するイベント、障害に関するイベントに分類されます。GitHubのイベントであれば、pushは変更、deployment_statusはデプロイ、issuesは障害といったように分類されます。変更、デプロイ、障害に分類した後に、それぞれのメタデータを組み合わせてFour Keysを算出します。実際のFour Keysの算出については、dora-team/fourkeys/METRICS.md をご参照ください。

以下の図は、GitHubのEventsがFour Keysとどう関係しているかを示したものです。

GitHub

GitHubを使用する場合、Four Keysで使用するイベントは以下のとおりです

計測対象としたいGitHubのリポジトリからSettings → Webhooks → Add webhook

各項目については以下のように設定します。

  • Payload URL 以下のコマンドの実行結果を入力します。
$ gcloud run services list --project $PROJECT_ID | grep event-handler | awk '{print $4}'
  • Content type application/json を選択します。
  • Secret 以下のコマンドの実行結果を入力します。
$ gcloud secrets versions access 1 --secret=event-handler --project $PROJECT_ID
  • SSL verification Enable を選択します。
  • Which events would you like to trigger this webhook? Send me everything. を選択します。

GitHubのpush、pull request、mergeについては、特に追加の操作を必要せずともイベントの登録がされます。ただし、issueに関してはFour Keysの要素として計測に含ませるためには、以下のような操作が必要です。

  1. issueを立てたら Incident という名前のLabelsを追加します。(incidentではなく、Incident)
  2. issueの本文またはコメントに以下のようにコミットのハッシュ値を記述する必要があります。
root cause: {SHA of the commit}

以下のスクリーンショットのようになっていれば、このissueはFour Keysの計測のための要素として扱われます。

GitLab

GitLab用のパーサーをプッシュしていない場合は、以下のコマンドでContainer Registryにプッシュします。

$ gcloud builds submit bq-workers --config=bq-workers/parsers.cloudbuild.yaml --project $PROJECT_ID --substitutions=_SERVICE=gitlab

terraform.tfvarsを以下のように変更します。

project_id = "{PROJECT_ID}"
region = "us-central1"
bigquery_region = "US"
parser = ["gitlab"]

変更したリソースをデプロイします。

$ cd terraform/example
$ terraform plan
$ terraform apply

GitLabのリポジトリからSettings > Webhooks

  • URL 以下のコマンドの実行結果を入力します。
$ gcloud run services list --project $PROJECT_ID | grep event-handler | awk '{print $4}'
  • Secret token
$ gcloud secrets versions access 1 --secret=event-handler --project $PROJECT_ID
  • Trigger 必要なものを選択します。(全てでも可)
  • SSL Enableを選択します。

モックデータの生成(オプション)

ここでは、モックデータの生成を行います。実際のリポジトリのデータを用いての検証が難しい場合は、ここに記載した方法で、モックデータを用いて検証を行うことができます。前の手順で実際のリポジトリに対してwebhookの追加を行っている場合は、この手順は不要かもしれません。

モックデータの生成手順

モックデータの生成には、data-generator/generate_data.pyを用います。generate_data.pyにはいくつかの引数があります。各引数の概要については以下のとおりです。

  • —event_timespan
    • 現在から何秒遡った時点からイベントの生成を行うか[単位:秒]
    • —event_timespan=604800とすると1週間前〜現在の間でeventが生成されます
    • default=604800
  • —num_events
    • 生成するeventの数
    • default=40
  • —num_issues
    • 生成するissueの数
    • default=2
  • —vc_ststem
    • 使用するバージョン管理システム
    • githubgitlabを指定
    • 指定が必須

generate_data.pyの実行のためにイベントハンドラのURLとシークレットを環境変数に追加します。

$ direnv edit
> export WEBHOOK=`gcloud run services list --project $PROJECT_ID | grep event-handler | awk '{print $4}'`
> export SECRET=`gcloud secrets versions access 1 --secret=event-handler --project $PROJECT_ID`

環境変数を追加したらデータを生成します。

$ python3 data-generator/generate_data.py --vc_system=github

引数について、例えば以下のようにすると、実行した日付から1ヶ月(30日)程度遡った時点からの10個のchangesがevent-handlerに送信されます。このchangesの中には10個のissuesも含まれています。

$ python3 data-generator/generator_data.py --event_timespan=2592000 --num_events=10 --num_issues=10 --vc_systeam='github'
10 changes successfully sent to event-handler

データの確認

データが正常に収集されているか、またwebhookの設定が正しくできているかを確認するためにBigQuery内のデータを確認します。webhookの追加を行った場合は、webhookを追加したリポジトリで、issueを立てたり、変更のプッシュを行います。モックデータの生成を行った場合は特に操作は必要ありません。

その後、bqコマンドで

$ bq query --project_id $PROJECT_ID 'SELECT * FROM four_keys.events_raw WHERE source = "{source}";'

もしくは、BigQueryのコンソールにて

SELECT * FROM four_keys.events_raw WHERE source = '{source}';

とすると登録されたイベントを確認することができます。

GitHubのモックデータを生成し取得した場合は以下のような結果が得られます。

$ bq query --project_id $PROJECT_ID 'SELECT * FROM four_keys.events_raw WHERE source = "githubmock";'
I0526 17:06:18.490597 7994432320 bigquery_client.py:730] There is no apilog flag so non-critical logging is disabled.
+-------------------+------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+-----------------------------------------------+------------------+------------+
|    event_type     |                    id                    |                                                                                                                                                                                    metadata                                                                                                                                                                                     |    time_created     |                   signature                   |      msg_id      |   source   |
+-------------------+------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+-----------------------------------------------+------------------+------------+
| push              | 14869f721b28470613b3e48f1ef914e84da1004d | {"head_commit": {"id": "14869f721b28470613b3e48f1ef914e84da1004d", "timestamp": "2023-05-01 13:11:21.041844"}, "before": "0000000000000000000000000000000000000000", "commits": [{"id": "14869f721b28470613b3e48f1ef914e84da1004d", "timestamp": "2023-05-01 13:11:21.041844"}]}                                                                                                | 2023-05-01 13:11:21 | sha1=bced825ae5673be4a500962514a200e1705e3425 | 7810301835866693 | githubmock |
| push              | 7c2d3ca9db40ffe522fd5426fc899cb23ec42afa | {"head_commit": {"id": "7c2d3ca9db40ffe522fd5426fc899cb23ec42afa", "timestamp": "2023-05-06 16:51:38.041838"}, "before": "35e3c5bd44b09247f80059492abcec05d710deb1", "commits": [{"id": "7c2d3ca9db40ffe522fd5426fc899cb23ec42afa", "timestamp": "2023-05-06 16:51:38.041838"}, {"id": "14869f721b28470613b3e48f1ef914e84da1004d", "timestamp": "2023-05-01 13:11:21.041844"}]} | 2023-05-06 16:51:38 | sha1=d5cde86e72beef8d460f9f52d727613ff213c1af | 7810342125069700 | githubmock |
| issues            | foobar/421                               | {"issue": {"created_at": "2023-05-06 16:51:38.041838", "updated_at": "2023-05-25 16:14:43.344797", "closed_at": "2023-05-25 16:14:43.344813", "number": 421, "labels": [{"name": "Incident"}], "body": "root cause: 7c2d3ca9db40ffe522fd5426fc899cb23ec42afa"}, "repository": {"name": "foobar"}}                                                                               | 2023-05-25 16:14:43 | sha1=4bfad7474acfef812e92a48ed936483ff31d96ca | 7810324170837091 | githubmock |
| issues            | foobar/707                               | {"issue": {"created_at": "2023-05-02 23:44:46.041865", "updated_at": "2023-05-25 16:14:42.853831", "closed_at": "2023-05-25 16:14:42.853846", "number": 707, "labels": [{"name": "Incident"}], "body": "root cause: c6c2c04e7ec40665a50e5223d91c9b1e41c7a7e5"}, "repository": {"name": "foobar"}}                                                                               | 2023-05-25 16:14:42 | sha1=e718103722626826912549e44076e9fb34fffa91 | 7810293759038638 | githubmock |
| deployment_status | f08c6a6a18d7b328bdabfe68822db86f550814dc | {"deployment_status": {"updated_at": "2023-05-02 23:44:46.041865", "id": "f08c6a6a18d7b328bdabfe68822db86f550814dc", "state": "success"}, "deployment": {"sha": "c6c2c04e7ec40665a50e5223d91c9b1e41c7a7e5"}}                                                                                                                                                                    | 2023-05-02 23:44:46 | sha1=cfaa08aeee6debecb9cf48488d98f19c93b6a7ee | 7810319670040262 | githubmock |
| deployment_status | 5fd9c20a36c5e0068cf497ac25fb0c6ae9824ef6 | {"deployment_status": {"updated_at": "2023-05-15 09:36:20.041867", "id": "5fd9c20a36c5e0068cf497ac25fb0c6ae9824ef6", "state": "success"}, "deployment": {"sha": "2f5022ee0735acb813512bc5eea63bf6516ea523"}}                                                                                                                                                                    | 2023-05-15 09:36:20 | sha1=cdbf012126f8cee810a3bcdee8748f20063c176a | 7810323108452113 | githubmock |
+-------------------+------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------+-----------------------------------------------+------------------+------------+

ダッシュボードの確認

https://console.cloud.google.com/run?project={PROJECT_ID}

から、fourkeys-grafana-dashboardを開くと

画像のような項目が表示されるので、そこにあるURLにアクセスします。

以下のようなエラーが出た場合

Cloud Resource Manager API has not been used in project {PROJECT_NUMBER} before or it is disabled. Enable it by visiting <https://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project=875283697981> then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.


https://console.developers.google.com/apis/api/cloudresourcemanager.googleapis.com/overview?project={PROJECT_NUMBER}
から、Cloud Resource Manager APIを有効にします。

Grafanaが開かれたら Home > Dashboards > Four Keysの順に開くことで、Four Keysのダッシュボードが表示されます。

この画像は、様々なモックデータの生成にて生成したモックデータのダッシュボードです。

このダッシュボードでの各指標の文字の色は2019年のDevOps Reportに基づいており、Four Keysとパフォーマンスとの関係は以下の表のようになっています。

Four Keys / パフォーマンスエリート(紫)高(緑)中(黄色)低(赤)
デプロイ頻度(Deployment Frequency)1日に複数回のデプロイ1日に1回から1週間に1回の間1ヶ月に1回1ヶ月に1回から6ヶ月に1回の間
変更のリードタイム
(Lead Time for Changes)
1日未満1日から1週間の間1週間から1ヶ月の間1ヶ月以上
サービス復元時間
(Time to Restore Service)
1時間未満1日未満1週間未満1週間以上
変更障害率
(Change Failure Rate)
無し0%-15%16%-45%45%-

ただ、Four Keysでのパフォーマンスの分類は標本調査のクラスタリングによる結果であり、毎年わずかにFour Keysとパフォーマンスとの関係は変化しています。また、このツールで使用されている2019年の結果と2022年の結果とではいくつか異なる点があります。2022年は以下の表のような結果となっています。

Four Keys / パフォーマンス
デプロイ頻度(Deployment Frequency)随時(1日に複数回のデプロイ)1週間に1回から1ヶ月に1回の間1ヶ月に1回から6ヶ月に1回の間
変更のリードタイム
(Lead Time for Changes)
1日から1週間の間1週間から1ヶ月の間1ヶ月から6ヶ月の間
サービス復元時間
(Time to Restore Service)
1日未満1日から1週間の間1週間から1ヶ月の間
変更障害率
(Change Failure Rate)
0%-15%16%-30%48%-60%

私感

Four Keysの計測システムの調査・検証を通して、以下のような良い点と注意が必要な点があると感じました。

良い点

  • 導入が容易でFour Keysの可視化をすぐに行える
  • 計測対象とするプロジェクト、リポジトリへの影響がほぼ無い
  • イベントを収集できるサービスが多い
    • ソースコード管理はGitHub、CircleCIとArgo CDでCI/CD、インシデント管理はPagerDutyといったことも、パーサーが用意されているので可能かもしれません
    • その他のサービスでもパーサーを自前で作成することもできます

注意が必要な点

  • デプロイの無いプロジェクト、リポジトリには適用できない
    • このシステムの注意が必要な点というよりもFour Keysの特性上、デプロイがないものに対してはFour Keysの各指標を算出することができません
    • ライブラリの開発を行うプロジェクトなどには適用が難しいかもしれません
  • GitHubのissueイベントの登録にラベルとコメントの追加の作業が必要
  • 導入前の状態は計測に含まれない
    • 今回の手順では、システムの導入以前に行われたコミットやデプロイなどはFour Keysの計測に含まれません
    • システムの導入以前の状態を含めてFour Keysの計測を行いたい場合は、導入以前のイベント等をBigQueryに登録する必要があります

この計測システム自体は、前述した注意が必要な点にさえ気をつければ、導入はとても容易であると感じました。ただ、Four Keysの計測システムという特性上、デプロイの無いプロジェクトではうまく動作しない上に、計測から得られる結果も、2019年のあらゆる企業のデータをクラスタリングしたものから算出される結果です。今後の展望として、デプロイの無いプロジェクトでも動作するような指標や計測システム、またパフォーマンスの算出元のクラスタリング結果の変更や、クラスタリングに基づくパフォーマンスの算出以外の方法があればと感じました。

チームやプロジェクトでパフォーマンスの測定を初めて行う場合、導入が容易なこのFour Keysの計測をまず開始し、そこからそれぞれの目的に合わせて別の指標や計測システムを使用していくと良いかもしれません。

参考文献

ブログ一覧へ戻る

お気軽にお問い合わせください

SREの設計・技術支援から、
SRE運用内で使用する
ツールの導入など、
SRE全般についてご支援しています。

資料請求・お問い合わせ