投稿日:
更新日:

ABEMA に内製モニタリングツールを導入した話

Authors

※ 本ブログは 「CyberAgent Developers Blog - ABEMA におけるマイクロサービスデプロイ状況可視化のための内製モニタリングツールの開発」 に掲載された内容です。


目次

overview.png

はじめに

2023 年 7 月より 約 2 ヶ月間、株式会社 AbemaTV Cloud Platform Team にて内定者バイトをしていた 24 卒 SRE 内定者の 後藤 廉(X(Twitter) : @ren510dev)です。 内定者バイト期間では、主に、ABEMA を構成するマイクロサービスのデプロイ状況を可視化するべく、独自のモニタリングツールの開発に従事しました。 また、既存の運用・監視体制への導入・統合、開発組織への推進等を実施し、Platform Engineering に挑戦しました。

今回のブログでは、配属部署やチーム内ミッションステートメントの紹介を交えつつ、私が内定者バイト期間中に取り組んだこと、成果を紹介します。

ABEMA Cloud Platform Team

ABEMA Cloud Platform Team(通称:クラプラ)は、名前の通り Cloud(GCP、AWS、Azure、SaaS、…etc)を活用して ABEMA における事業成果の最大化を担うチームです。 高可用性かつ生産性の高いシステム構築に向けて、開発グループへのベストプラクティスの立案・浸透、新しいクラウドソリューションの適切な活用の推進をミッションとしています。

主な業務として、ABEMA 全体で使用するインフラの管理や整備を行い、各所から寄せられる問い合わせやレビュー依頼の対応なども行います。 その他、クラウドソリューションやマネージドサービスを含むインフラレイヤーの 検証 / 設計 / 構築 / 運用、必要に応じて共通ライブラリや共通基盤システムの開発、セキュリティ / 脅威レベルの 分析 / 対策 などを行っており、領域を定めず幅広く扱います。

『コードが書けて当たり前、インフラ設計が出来て当たり前、その上でそれらを組み合わせて如何に ABEMA に適したソリューションを提供していけるか』 という中々熱い世界でした。

cloud-platform-team.png

チームメンバーは 6 名と少数精鋭で構成されており、個人的には、ABEMA のインフラを支える 「縁の下の力持ち」「最後の砦」 という印象でした。

impression.png

ABEMA が抱える運用課題

ABEMA は Kubernetes(GKE、EKS)をベースに 100 以上のマイクロサービスが動作しており、各コンポーネントが連携することでサービスが構成されています。 Kubernetes マニフェストの管理には GitHub を SSOT(Single Source of Truth) とし、Continuous Delivery(CD)ツールには、PipeCD が使用されています。

microservice.png

そのため、例えば単一のコンポーネントで障害が発生すると、他のコンポーネントに影響を及ぼしたり、延いては ABEMA 全体にも影響したりする恐れがあります。 昨今では、一部の内部コンポーネントで 5xx 等のエラーが発生する事象も確認されており、これらのエラーの多くは、リソースが変更されたタイミング、つまりリリースやデプロイが走ったタイミングで起きている可能性が高いとされていました。

しかし、既存のモニタリングシステムでは、障害原因を特定する際に、統合監視基盤である Grafana の他、PipeCD WebUI のログ、Cloud Logging 等を個別に確認する必要があり、調査や原因の切り分けに時間がかかるという課題がありました。

取り組むこと

これらの課題に対し、私は 各マイクロサービスのデプロイ状況を統合監視基盤である Grafana 上で可視化するためのモニタリングツール(eyes と命名)の開発 に着手しました。

eyes-repository.png

eyes-release.png

具体的には、Grafana ダッシュボードにデプロイステータス(成功、失敗、キャンセル)を示すアノテーションを追加し、そのアノテーションがどのリリースに紐付いているかを可視化するための仕組みを構築していきます。

これにより、障害発生時の原因の切り分けのハードルを下げ、迅速なエラー対処および障害対応の実現を目指します。

目的

  • 特定のマイクロサービスのデプロイ状況の可視化と ABEMA 全体としてのデプロイ状況の可視化ができる
    • アノテーションを用いてダッシュボードにデプロイステータスを示すモーダルを表示する
  • Grafana ダッシュボード上でリソースの変更差分を追跡することができる
    • ダッシュボード上に追加されたアノテーションからリソースの変更履歴および GitHub のコミットを特定できる
  • 既存の運用体制を大きく変えることなくデプロイ可視化サービスを導入できる
    • 今回開発するツールの設定手順を最小限に抑え、導入のハードルを下げる

仕様策定

デプロイ可視化サービスを設計をするにあたり、まず、考慮しなければならないことを事前にトレーナーと相談し、開発チームの方にヒアリングをした上で設計をしていきました。

考慮すべきこと

  • ABEMA はおよそ 200 個以上(GKE Production の Deployment 換算)のマイクロサービスで構成されており、サービスのデプロイが各チームの判断のもと頻繁に走る
    • 何も考えずにアノテーションを追加し続けると、不必要なデプロイ情報で埋め尽くされ、従来の監視体制を阻害する
      特定のサービスを選択し、そのサービスに関するデプロイ情報のみを表示させたい
  • サービスによってモニタリングのダッシュボードが異なる
    • 全てのダッシュボードにアノテーションを追加する必要はない
      どのダッシュボードにデプロイ情報を表示するかを、サービス毎に選択できるようにしたい

要件・実現すること

  • 実際にデプロイされた時間を取得できる
    • リソースの変更(ロールアウト、ロールバック)が走った時点で、ダッシュボードにデプロイを示すアノテーションを追加する
  • 変更履歴を確認できる
    • 追加したアノテーションから GitHub のコミット履歴(リリース時の変更履歴)を確認できる
  • デプロイ可視化機能の 有効 / 無効 を選択できる
    • モニタリング基盤のオプトインツールとして提供する
  • マイクロサービス毎にデプロイ情報を確認できる
    • 特定のマイクロサービスを指定してデプロイ情報を可視化できる
  • イベント毎にデプロイ情報を確認できる
    • デプロイステータス(成功、失敗、キャンセル)を視覚的に判断できる

設計・ツールの選定

上述の要件を満たすツールとして、以下の OSS を選定し、これらの機能を組み合わせることにしました。

ABEMA では、CD ツールとして PipeCD を、モニタリングツールとして Grafana を採用しています。 そこで、デプロイ情報の収集ツールとして PipeCD Webhook を、可視化には Grafana アノテーション機能を利用します。

なぜ、GitHub Webhook ではなく PipeCD Webhook なのか

  • より正確にデプロイタイミングを取得するため

GitHub Webhook(GitHub Actions)で観測出来るのはマニフェストの更新タイミングのみで、実際には (PipeCD の WaitApproval 等により)デプロイパイプラインでの処理時間や Kubernetes のロールアウト時間の差が生じるためです。

また、ダッシュボード上で任意のマイクロサービスを選択できるよう、デプロイ可視化が有効であるサービスの情報(PipeCD アプリケーションに該当)を eyes 側で Prometheus client-go ライブラリを使用してカスタムメトリクスとして吐き出します。

なぜ、カスタムメトリクスを使用するのか

  • 既存のメトリクスを使用することによるノイズの多さ

デプロイされたサービスの情報(PipeCD アプリケーション名 等)は piped v0.44.3-rc0 以降で PipeCD メトリクスラベル 等からも取得可能となっています。 しかしながら、このメトリクスは PipeCD によって管理される全てのアプリケーション名が取得・表示されるため、デプロイ可視化が有効になったサービスのみをフィルタリングすることが困難です。

  • デプロイ可視化サービスとしてのスケールのしやすさ

既存ツールのメトリクスを使用する場合、追加で必要な情報を載せる必要が出てきた際に、都度、OSS 等の外部サービスに Issue や PR を作成する必要があります。 カスタムメトリクスを使用することで、ABEMA の運用体制に合ったメトリクスを自由に追加でき、カスタマイズ性の観点からサービスとしてもスケールさせやすいと考えました。

アーキテクチャ

ABEMA はサービスの成長と共にアーキテクチャの拡張を行い、現在ではマルチリージョン、マルチクラウドで運用されています。

  • ABEMA モニタリング基盤の概要 monitoring-all.png

モニタリングのアーキテクチャは特徴的で、各 Kubernetes クラスタで動作する Prometheus がそれぞれ収集したメトリクスを、台湾リージョンで稼働する VictoriaMetrics に集約し、Grafana で一括可視化しています。

ここで、Prometheus と Grafana の間に VictoriaMetrics を挟んでいるのは歴史的背景によるもので、長期に渡って保存された膨大なメトリクスを効率的に参照するため、Prometheus から Thanos, VictoriaMetrics へと監視基盤構成を移行していったようです。

監視基盤構成の変遷に関しては、ABEMA Developer Conference 2021 でも詳しく説明されています。

impression-02.png

以下に、デプロイ可視化サービス(eyes)のアーキテクチャを示します。

  • 台湾リージョンにおけるデプロイ可視化サービス(eyes)のアーキテクチャ構成 eyes.png
  1. 開発者が GitHub のリビジョンブランチに変更を加えると、PipeCD の EventWatcher という仕組みによってデプロイを実行します。
  2. 次に、PipeCD Webhook を利用して、変更されたサービス情報を eyes に通知します。 この時、デプロイ可視化サービスを有効化にするかどうかを判定するためのフラグを Webhook に含めます。(※ 後述)
  3. eyes は、取得した Webhook の情報を元に、Grafana アノテーションを定義し、Annotations HTTP API を使用してダッシュボードにデプロイ情報を追加していきます。
  4. その後、変更されたアプリケーション情報をカスタムメトリクスとして吐き出し、Prometheus Operator(Service Monitor)によって Pull 方式で取り込んでもらいます。
  5. 最後に、開発者は Grafana Variables でデプロイ情報を確認したいサービスを選択し、ダッシュボード上のアノテーションから確認します。

処理フロー

デプロイ可視化サービス(eyes)の処理フローの詳細を紹介します。

PipeCD Webhook の受信 / Grafana アノテーションの追加

① piped の config マニフェストに Webhook の設定を追加します。

設定方法は こちら の PipeCD のドキュメントを参考にしました。

### piped.yaml ###
apiVersion: pipecd.dev/v1beta1
kind: Piped
spec:
  notifications:
    routes:
      - name: eyes-webhook-service
        events: # 成功 / 失敗 / キャンセル の 3 つを受信する
          - DEPLOYMENT_SUCCEEDED
          - DEPLOYMENT_FAILED
          - DEPLOYMENT_CANCELLED
        receiver: eyes-webhook-service-receiver
    receivers:
      - name: eyes-webhook-service-receiver
        webhook:
          url: http://eyes.eyes.svc.cluster.local:8081/api/hooks/services # Webhook 受信用のエンドポイントを /api/hooks/services に設定

当初、Rollback イベントについても可視化したいと考えていましたが、Rollback は、DEPLOYMENT_FAILED または、DEPLOYMENT_CANCELLED が発生すると、自動的に実行されるため、除くことにしました。

また、Issue でもメンテナの方より助言を頂きました。

② PipeCD Webhook に含まれる Type, Metadata.deployment.application_name, Metadata.deployment.labels を取得する

PipeCD Webhook には、PipeCD アプリケーションマニフェスト(app.pipecd.yaml)の情報が含まれており、以下のような形式になっています。(v0.45.3 時点)

{
  "Type": 4,
  "Metadata": {
    "deployment": {
      "id": "***",
      "application_id": "***",
      "application_name": "sample",
      "piped_id": "***",
      "project_id": "***",
      "git_path": {
        "repo": {
          "id": "***",
          "remote": "[email protected]:***.git",
          "branch": "master"
        },
        "path": "***",
        "config_filename": "app.pipecd.yaml",
        "url": "https://github.com/***/tree/master/***"
      },
      "cloud_provider": "kubernetes-default",
      "platform_provider": "kubernetes-default",
      "labels": {
        "env": "***",
        "grafanaDashboardUIDs": "5Aiamcj4k,Fdhtxwaizf"
      },
      "trigger": {
        "commit": {
          "hash": "***",
          "message": "***",
          "author": "Ren",
          "branch": "master",
          "url": "https://github.com/***/commit/***",
          "created_at": 1691115168
        },
        "timestamp": 1691115188
      },
      "status_reason": "The deployment has been planned",
      "created_at": 1691115188,
      "updated_at": 1691115209
    }
  }
}

※ ここで、labels の値は、app.pipecd.yaml の spec.labels[] で指定した値が反映されます。

  • Type
  • application_name
    • PipeCD アプリケーション名
    • ダッシュボード上で任意のサービスを特定するために使用する
  • labels.grafanaDashboardUIDs
    • デプロイ情報を示すアノテーションを追加するダッシュボードの UID リスト
    • このカスタムラベルを使用して、デプロイ可視化サービスが有効化されているアプリケーションであるかを判断する
    • 一つ以上のダッシュボード UID が追加されている時、eyes はデプロイ情報をそのダッシュボードに追加する
      • ※ UID は特に変更を加えない限り不変であり、一意に Grafana ダッシュボードを特定できる

③ 変更されたリソース名を特定した後、Grafana Annotation Tags に以下のような Key-Value データを含める

  • status
    • デプロイステータス
    • DEPLOYMENT_SUCCEEDED OR DEPLOYMENT_FAILED OR DEPLOYMENT_CANCELLED
  • application_name
    • PipeCD アプリケーション名
  • dashboard_uid
    • Grafana ダッシュボードの UID
// newCustomAnnotation creates an annotation format and post the Grafana API.
func newCustomAnnotation(client *Client, info *domain.DeployInformation) error {
	tags := make([]string, 0, len(info.DashboardUIDs))

	// OccurrenceTime
	annotationCreateTime := int64(info.OccurrenceTime) * epochMilliseconds // convert epoch seconds to epoch milliseconds
	log.Printf("Info - Resource modification time (deployment time): %s", helper.EpochSeconds(info.OccurrenceTime).ConvertToISO8601())

	// EventType
	switch info.EventType {
	case pipecd.NotificationEventType_EVENT_DEPLOYMENT_TRIGGERED:
	case pipecd.NotificationEventType_EVENT_DEPLOYMENT_PLANNED:
	case pipecd.NotificationEventType_EVENT_DEPLOYMENT_APPROVED:
	case pipecd.NotificationEventType_EVENT_DEPLOYMENT_ROLLING_BACK:
	case pipecd.NotificationEventType_EVENT_DEPLOYMENT_SUCCEEDED:
		tags = append(tags, domain.TagKeyStatus+"DEPLOYMENT_SUCCEEDED")
	case pipecd.NotificationEventType_EVENT_DEPLOYMENT_FAILED:
		tags = append(tags, domain.TagKeyStatus+"DEPLOYMENT_FAILED")
	case pipecd.NotificationEventType_EVENT_DEPLOYMENT_CANCELLED:
		tags = append(tags, domain.TagKeyStatus+"DEPLOYMENT_CANCELLED")
	case pipecd.NotificationEventType_EVENT_DEPLOYMENT_WAIT_APPROVAL:
	case pipecd.NotificationEventType_EVENT_DEPLOYMENT_TRIGGER_FAILED:
	case pipecd.NotificationEventType_EVENT_APPLICATION_SYNCED:
	case pipecd.NotificationEventType_EVENT_APPLICATION_OUT_OF_SYNC:
	case pipecd.NotificationEventType_EVENT_APPLICATION_HEALTHY:
	case pipecd.NotificationEventType_EVENT_PIPED_STARTED:
	case pipecd.NotificationEventType_EVENT_PIPED_STOPPED:
	default:
		return fmt.Errorf("incomplete PipeCD status received")
	}

	// ApplicationName
	tags = append(tags, domain.TagKeyApplicationName+info.ApplicationName)

	for _, dashboardUID := range info.DashboardUIDs {
		tags = append(tags, domain.TagKeyDashboardUID+dashboardUID)

		param := &grafana.Annotation{
			DashboardUID: dashboardUID,
			Time:         annotationCreateTime, // Epoch milliseconds
			Tags:         tags,
			Text:         info.Text,
		}

		_, err := client.GrafanaClient.NewAnnotation(param)
		if err != nil {
			return fmt.Errorf("new annotation response should contain the ID of the new annotation: %w", err)
		}

	}

	return nil
}

④ Grafana Annotation の設定にステータス毎に以下のように設定する

  • アノテーションのフィルタルール:デプロイ成功 annotation-success.png

  • アノテーションのフィルタルール:デプロイ失敗 annotation-failed.png

  • アノテーションのフィルタルール:デプロイキャンセル annotation-cancel.png

ここで、${value} という テンプレート を使用することで、Grafana Variables から直接、値を読み込ませることができます。 また、ダッシュボード UID をフィルタールールとして追加しておくことで、特定のダッシュボードに紐付くデプロイ情報のみを絞り込むことができます。

つまり、開発者が事前にデプロイ情報を有効化したダッシュボードでアプリケーションを指定すると、その情報を Annotation Tags Filter Rule が and 条件で読み込み、選択したアプリケーションに関するデプロイ情報のみを表示することができるというわけです。

  • Variable ApplicationName からデプロイ情報を確認したいサービスを選択 variables.png

⑤ Variables のトグルボタンを切り替える(表示/非表示)ことで、選択したサービスのデプロイ情報をステータス毎に確認できる

開発者は、アノテーションをマウスオーバーすることで、リソースの変更履歴および GitHub のコミットに容易にアクセスすることができるようになります。

  • Grafana ダッシュボード上にデプロイ情報を示すアノテーションを追加 annotation-1.png

Grafana アノテーションには、Go の HTML テンプレートを組み込むことができるので、簡単に形を整えて以下の情報を追加しました。

func generateAnnotationText(trigger domain.Trigger) string {
    annotationText := fmt.Sprintf(`
        <ul>
          <li>Commit.Author: %s</li>
          <li>Commit.Branch: %s</li>
          <li>Commit.Message: %s</li>
          <li>Commit.Hash: %s</li>
          <li>Commit.URL: <a href="%s">%s</a></li>
          <li>Commit.CreatedAt: %s</li>
          <li>PipeCD.Trigger.Timestamp: %s</li>
        </ul>`,
        trigger.Commit.Author,
        trigger.Commit.Branch,
        trigger.Commit.Message,
        trigger.Commit.Hash,
        trigger.Commit.URL,
        trigger.Commit.URL,
        helper.EpochSeconds(trigger.Commit.CreatedAt).ConvertToISO8601(),
        helper.EpochSeconds(trigger.Timestamp).ConvertToISO8601())
    return annotationText
}
キー概要
Commit.Authorコミッタ
Commit.Branchリビジョンブランチ
Commit.Messageコミットメッセージ
Commit.Hashコミットハッシュ
Commit.URLコミット URL
Commit.CreatedAtコミット作成時刻
PipeCD.Trigger.TimestampPipeCD キック時刻

カスタムメトリクスの生成

eyes は、デプロイ可視化サービスが有効化されたアプリケーションの、アプリケーション名ダッシュボード UID をカスタムメトリクス eyes_custom_metric のラベルに含めて、エクスポート(/metrics でリッスン)します。

ここで、ダッシュボード UID 毎にメトリクスラベルを生成することで、アプリケーション名 と ダッシュボード UID を 1 対 1 で結び付けます。

// NewAnnotation adds annotations to Grafana dashboards and exports custom metrics.
// However, it only works if the deployment visibility service (eyes) is enabled.
func (i *Interactor) NewAnnotation(ctx context.Context, webhookData *domain.HooksData) error {
    // 省略

    // add metics label
    for _, dashboardUID := range deployInformation.DashboardUIDs {
    	metrics.UpdateEyesCustomMetric(
    		&domain.HandleApplication{
    			ApplicationName: deployInformation.ApplicationName,
    			DashboardUID:    dashboardUID,
    		},
    	)
    }

    // 省略
}
// Metrics labels managed by eyes_custom_metric.
type HandleApplication struct {
	ApplicationName string
	DashboardUID    string
}
// Defining custom metrics.
var eyesCustomMetric = prometheus.NewGaugeVec(
	prometheus.GaugeOpts{
		Name: "eyes_custom_metric",
		Help: "Application metrics managed by eyes",
	},
	[]string{applicationNameKey, dashboardUIDKey},
)

// UpdateEyesCustomMetric adds label to eyesCustomMetric.
func UpdateEyesCustomMetric(h *domain.HandleApplication) {
	eyesCustomMetric.WithLabelValues(h.ApplicationName, h.DashboardUID).Set(0)
}
  • eyes /metrics エンドポイントでのメトリクス確認 eyes_side_metrics.png
  • Grafana エクスプローラで eyes が吐いたメトリクスを確認 grafana_side_metrics.png

そして、ダッシュボード毎に Grafana Variables のクエリに以下の PromQL を設定することで、任意のダッシュボードに紐付くアプリケーションのみを選択できるようになります。

  • Grafana Variables に設定する PromQL
label_values(eyes_custom_metric{dashboard_uid="<設定するGrafanaダッシュボードのUID>"}, application_name)
  • Grafana Variables の設定 variables-config.png

これにより、デプロイ可視化が有効になったアプリケーション名 を ダッシュボード毎に 表示させることができます。

  • ダッシュボードに合わせて表示するアプリケーションを変更する per-dashboard.png

マルチリージョン展開

先で述べた通り、ABEMA はマルチクラウド、マルチリージョンで運用されています。 メトリクスは、各クラスタで動作する Prometheus によって収集され、台湾リージョンで稼働する VictoriaMetrics に集約されます。 この時 Prometheus は、リージョンを跨いで Remote Write を行うため、GCP では Cloud NAT、AWS では NAT Gateway によって送信元 IP アドレスを固定し、Cloud Armor によってフィルタリングルールを適用しています。

また、Grafana にアクセスするためのエンドポイントにはアクセス制御として IAP(Identity-Aware Proxy)認証がかかっています。

アーキテクチャの検討

前提として、今回開発した eyes は、PipeCD から Webhook を受け取り、カスタムメトリクスを Prometheus に吐き出す必要があります。 PipeCD は Kubernetes クラスタ毎に配備されていますが、事前調査で PipeCD Webhook に IAP を突破するための仕組みを追加するのは、複雑かつ手間であるという懸念がありました。

そこで、各リージョンに PipeCD とセットで eyes を配置し、アノテーションを追加する際の HTTP リクエストのみに IAP を突破するための仕組みを追加するという方針を取ることにしました。

以下に、マルチリージョンを考慮した、モニタリングアーキテクチャの全体像を示します。

multiregion.png

モニタリングの主要拠点は台湾リージョンであり、Grafana もここに配置されています。

台湾リージョンで稼働する eyes は、クラスターネットワークを通じて Grafana にアクセス可能なため、アノテーションを付与する際の HTTP リクエストを Service エンドポイント に向けて投げます。 また、他リージョンで稼働する eyes は Workload Identity を使用して、認証用のトークンを HTTP ヘッダ(Authorization: Bearer)に付加し、Grafana の Ingress エンドポイント に向けて投げることで IAP を突破します。

この構成によるメリットをまとめると次のようになります。

  • 親和性 の観点より
    • 認証の突破方式を考慮するのが一箇所で済む(Ingress エンドポイントに設けられた IAP 一箇所のみ)
    • 既存のモニタリングアーキテクチャや認証方式に大きな変更を加える必要がない
  • 可用性・信頼性 の観点より
    • 台湾リージョンのみに eyes を配置した場合、リージョン障害が発生すると、その間他リージョンで稼働するサービスのデプロイ情報も一切収集できなくなる
    • 各リージョンに分散配置しておくことで、単一リージョンの障害に対応する

この構成を取る上で、Pod 内(Deployment リソース)のリクエストが IAP を通過するための仕組みを考える必要があります。

本来、Workload Identity を使用すれば認証を潜り抜けられますが、eyes は独自に実装を行っているため、Pod に KSA(Kubernetes Service Account)を付与するだけでなく、HTTP リクエストも自分でカスタマイズする必要がありました。

これに関しては、GCP の ドキュメント に OIDC トークンを取得して、IAP で保護されたリソースにアクセスするための方法が書いてあります。

// makeIAPRequest makes a request to an application protected by Identity-Aware
// Proxy with the given audience.
func makeIAPRequest(w io.Writer, request *http.Request, audience string) error {
        // request, err := http.NewRequest("GET", "http://example.com", nil)
        // audience := "IAP_CLIENT_ID.apps.googleusercontent.com"
        ctx := context.Background()

        // client is a http.Client that automatically adds an "Authorization" header
        // to any requests made.
        client, err := idtoken.NewClient(ctx, audience)
        if err != nil {
                return fmt.Errorf("idtoken.NewClient: %w", err)
        }

        response, err := client.Do(request)
        if err != nil {
                return fmt.Errorf("client.Do: %w", err)
        }
        defer response.Body.Close()
        if _, err := io.Copy(w, response.Body); err != nil {
                return fmt.Errorf("io.Copy: %w", err)
        }

        return nil
}

ここで、google.golang.org/api/idtoken パッケージの idtoken.NewClient というメソッドを呼び出すと、Authorization: Bearer がリクエストの際に自動的に HTTP ヘッダに付加され、認証トークンを追加してくれます。

eyes は以下に示す構成によって OIDC Provider から GCP リソースへのアクセストークンを取得し、IAP 認証を突破して HTTP リクエストを Grafana に送信します。

  • リージョンを跨いだ HTTP リクエスト workload-identity.png

デプロイ可視化サービスの導入

デプロイ可視化サービスを導入する際は、app.pipecd.yaml の spec.labels[]grafanaDashboardUIDs というラベルを設定することで有効化できるようにしています。

複数のダッシュボードを追加する場合は、コンマ ( , ) で区切ります。

### app.pipecd.yaml ###
apiVersion: pipecd.dev/v1beta1
kind: KubernetesApp
spec:
  name: sample
  labels:
    env: ***
    # ↓ このラベルを追加するだけで指定したダッシュボードにデプロイを可視化することができます
    grafanaDashboardUIDs: "8MppxQ-mg,8MppxQ-mh" # k8s - Pods deployment, k8s - Statefulset Pods
  quickSync:
    prune: true
  input:
    namespace: sample
    kustomizeVersion: 4.5.7
    kustomizeOptions:
      load-restrictor: LoadRestrictionsNone

eyes を使用するためのダッシュボード整備は基本的に内定者バイト期間中に終えており、今後、ダッシュボードが追加された際も、eyes を使用するためのダッシュボードの設定の手順書を開発者向けドキュメントツールに残しました。 (ダッシュボードがめちゃくちゃ多くて、設定を入れるのがかなり大変だった…)

既存の運用体制を大きく変えることなく、かつ破壊的な変更を入れないことで、導入のハードルを下げられるように意識しました。

まとめ

最後に、タスクをやるにあたって大変だったことと、学びについて書きたいと思います。

大変だったこと・直面した課題

正直、設計方針では常に迷うことが多かったです。

当初、PipeCD や Grafana と聞いて、既存の仕組みを使えばあっさり終わるのではないかと思っていましたが、蓋を開けてみると、既存の機能では対応しきれない場面がいくつもありました。

特に PipeCD Webhook にデプロイのタイミングを示すタイムスタンプが含まれていなかったことが、一番の障壁でした。 今回は、PipeCD がイベントステータスを更新(triggered → planned → success / failed)した際に、Control Plane 側で管理されている DB の時間を更新した時刻(Metadata.deployment.updated_at)をデプロイされた時刻として扱うことで、デプロイタイムスタンプを得ました。

しかし、実際に PipeCD WebUI に表示されるタイムスタンプと、およそ 20 ~ 40 秒程度ずれていることが判明しました。 こちらの問題に対し、PipeCD のコードを一通り読み、対処策を模索しました。

今後、PipeCD に Issue を立てて、引き続き OSS にコミットする形で個人的に対応を行っていきます。

また、今回のタスクを遂行するにあたり、PipeCD(CyberAgent Developer Productivity 室)や Grafana など、部署外の方々とのやりとりが多かったのですが、自分のやりたいことや、相手に求めることをうまく伝えられず、ヒアリングやディスカッションに苦戦しました。

これは、凄く大変なことでしたが、これまでに無い経験を得ることができ、非常に為になりました。

内定者アルバイトを終えて

今回の内定者バイトでは、2 ヶ月という短い期間の中、ABEMA 全体のサービス運用に直接貢献できるプラットフォームの構築に、設計の段階から導入まで一気通貫して携わることが出来ました。

また、以前より、ABEMA のモニタリングシステムには興味がありましたが、今回の内定者バイトを通じて、アーキテクチャの構成等についてかなり理解を深めることが出来ました。

これまでの開発経験では、ツールやフレームワークに頼ることでソリューションの多くはある程度実現できましたが、ABEMA のような大規模サービスでは、既存機能だけでは網羅しきれず、自ら OSS に手を加えたり、外部関係者と密に連携をする必要があるのだと実感しました。 私も、実際に今回のタスクを通じて、PipeCD や Grafana のコードを覗き、メンテナの方々とミーティングをしたり、ディスカッションを行ったりすることで、OSS への理解を深めることが出来ました。 新たに CNCF Projects に参画できたことも成果の一つだと実感しています。

大規模サービスならではの課題や要望に触れることができて、とても刺激的だったし、楽しかったです。

私は以前より、縁の下の力持ちとして、堅牢なシステムを構築すると共に、常に攻めの姿勢を忘れないエンジニアとしてサービスに貢献をしていく というエンジニア像を啓蒙していました。 クラプラは、ABEMA という大規模なサービスを護るだけでなく、技術検証や共通基盤システムの開発など、エンジニアを支えるエンジニアリング もしていて、まさに自分が望んでいた環境でした。

クラウドサービスや各種 SaaS, OSS 等と密に連携をしており、それらのツールの運用者との距離も非常に近いことから最新の技術に触れたり、動き方によっては会社の内外を問わず、エンジニアとして世の中にも貢献していけるのではないかと思いました。


ライター

goto_ren.png Trainee:Ren Goto(@ren510dev
2024 年 SRE として 株式会社サイバーエージェントに新卒入社予定
Kubernetes, Overlay Network, CNCF Sandbox Projects, DevOps などに興味があります。

---

tetsuya_yamamoto.png Trainer:Tetsuya Yamamoto(@_tetsuya28
2021 年新卒入社のインフラエンジニア
現在は AbemaTV の Cloud Platform Group で AWS / GCP のクラウド基盤の構築・運用など担当しています。