Monitoring as Code for Kubernetes

この記事は?

この記事は、リクルートライフスタイル Advent Calendar 2019 の16日目の記事です!

自分が所属している CETチーム で、Kubernetes を中心としたアーキテクチャにおける Monitoring as Code を実現した監視基盤の紹介をしたいと思います。

自分の所属するチームでは、バッチ基盤や API 基盤を GKE 上に構築しています。 この記事では、Terraform・Grafana・Prometheus・Jsonnet を利用して、Kubernetes のリソース監視・Job の実行状態・API レイテンシのモニタリング・外形監視などを改善したアーキテクチャやフローを紹介したいと思います。

Who am I ?

@ko-da-k (Twitter: @ko_da_k) です。新卒2年目で、1年目は主にデータ解析やモデリング等を行っており、現在は Kubernetes を利用したバッチ基盤や API 基盤の実装を行っています。最近はもっぱら Go を書いています。

なぜモニタリングが必要なのか

モニタリングにおけるチーム課題

自分たちのチームでは、今までは作った基盤の監視体制やアラート対応が非常に属人化していました。

具体的には、以下のような状態でした。

  • 誰も対応しない オオカミ少年的な アラート通知が多発
    → アラート Channel を確認しなくなり、重要なアラートを見逃す
  • アラートに対する対応方法が不明瞭で属人化
    → 一部の技量の高い人やチームの在籍年数が長い人が、その場で障害対応してしまう。結果としてチームとしての知見が蓄積されず、新規参画者の障害対応が困難になる
  • コード化(チームで管理)されていない野良アラートや野良 Dashboard が乱立
    → それらが作られた経緯・意図・必然性がわからず結果的に放置されてしまう。また、監視が基盤の最新状態と乖離して意味をなくしてしまう

そこで、チームで 入門 監視 の輪読会を行い、チームとして改めて監視体制やアラートルールをどのようにしていくべきかを議論しました。 上記の本は、監視に対する方法や心構えが網羅的に書かれており、コンパクトながらもチームの監視に対する目線を合わせるのに非常に役立ちました。

Monitoring as Code による解決

入門 監視 でも言及されていますが、 前提として、「監視やアラートは常に見直し、更新していくもの」です。 そこで、チームでは常に監視が形骸化しないように、以下の 3 項目を心掛けていくことを定めました。

  1. サービスの正常な状態をモニタリングすること
  2. 障害対応を行ったらポストモーテムを作成して知見を貯めること
  3. 障害対応後はアラートルールの見直し・監視項目や監視ダッシュボードの修正を行い再発防止を防ぐこと

上記の内容は、入門 監視SRE 本 等の書籍に非常に丁寧に書かれているのでぜひ読みましょう!

もちろん、設定の自動化による監視項目変更のヒューマンエラーや工数を減らすことも大切です。 一方で、過去にどんな障害があって、それに対してどのようにシステム・監視体制・運用ルールなどを修正していったかをトラッキングできるようにしておくこと がチームへの監視の浸透や将来的な障害の防止に対してより重要なのではないかと感じています。

Monitoring as Code は上述した「継続的な監視の修正」を実現する良い基盤になると考えています。

ここまでの話を整理すると、監視基盤として実現したいのは以下のとおりです。

やるべきこと 解消される問題 Monitoring as Code の役割
目的の言語化・議論 オオカミ少年的なアラートの撲滅
野良アラート・野良 Dashboard の撲滅
ワークフローと経緯保存場所の提供(Pull Request)
対応方法の言語化 障害対応の属人化の撲滅 言語化の場所の提供と強制(コード)
継続的な更新 作りっぱなしの撲滅 ワークフローと経緯保存場所の提供(Pull Request)

Monitoring as Code の実装例

アーキテクチャ概要

ここからは、Terraform・Grafana・Prometheus・Jsonnet + CI/CD を利用したアーキテクチャの紹介をしていきます。 なお、本記事では Prometheus や Grafana の技術詳細については割愛します。よりこれらについて知りたい場合は、 入門 Prometheus 等の書籍をおすすめします。

以下に示すのは、Prometheus・Grafana を用いた監視基盤のアーキテクチャ概要です。

アーキテクチャ概要

チームでは、CI/CDツールとして Drone を利用しています。

チームの基盤は主に GCP 上に構築しています。 GCP 全体を構成する Terraform リポジトリの設定を元に、Drone 経由でクラスタを作成します。

そして、クラスタ内設定を管理するリポジトリにある Helm や Manifest 等を元に、Drone 経由で PrometheusGrafana 等のリソースを作成します。 (Prometheus は Istio のメトリクスを取れるように、Istio Helm Chart から設定を行っています)

最後に、データソース情報や Dashboard 等の構成ファイルを配置した Grafana の設定用リポジトリから、Drone 経由でクラスタの Grafana Dashboard やアラートを作成します。

Prometheus の設定

クラスタには、Node ExporterKube State Metrics を導入し、Kubernetes クラスタのリソース状況を監視しています。 具体的には、Pod の State や Node の CPU・メモリ・Disk IO、HPA のスケーリング状況等を主に監視しています。

また、 Blackbox Exporter を導入し、サービスの外形監視を行うようにしています。

Grafana の設定

上述の通り、Grafana のすべての設定は GitHub 上に Terraform と Jsonnet によってコード化しています。

terraform には grafana-provider が実装されているので、こちらを利用しています。

実際のフォルダ構成の一部はこのようになっております。

1
2
3
4
5
6
7
8
9
10
11
.
├── alert.tf
├── backend.tf
├── dashboard
│   ├── service1
│   │   └── service1.jsonnet
│   └── service2
│       └── service2.jsonnet
├── dashboard.tf
├── datasource.tf
└── provider.tf

公式 Document にも記載がありますが、それぞれの tf ファイルについて簡単に説明していきます

alert.tf

アラートする通知先の設定等が記載されます。例えば Slack への通知を行いたい場合は以下のような設定を記載します

1
2
3
4
5
6
7
8
9
10
11
resource "grafana_alert_notification" "sample" {
  name = "sample"
  type = "slack"
  settings {
    "url"        = "${local.slack_webhook_url}"
    "recipient"  = "${local.slack_channel}"
    "icon_emoji" = "${local.slack_icon_emoji}"
    "username"   = "${local.slack_username}"
  }
}

backend.tf

こちらは 他 provider 同様の terraform backend の設定です

dashboard/

Grafana で表示される Dashboard の設定ファイルが入っています。こちらは後述します

dashboard.tf

ダッシュボードをどのファイルから参照し、どのフォルダに配置するかを管理します。以下がサンプルです

ここで大事なのは、 depends_on でデータソースの設定を行うことです。 データソースを先に作成しないと Dashboard が適切に作成できません

1
2
3
4
5
6
7
8
9
10
resource "grafana_folder" "service1" {
  title = "service1"
}
resource "grafana_dashboard" "service1" {
  config_json = "${file("./dashboard/service1/service1.json")}"  # CI 上で、Jsonnet を JSON に変換するので JSON を指定しています
  depends_on  = ["grafana_data_source.sample1"]  # データソースを先に作成しないと Dashboard が作成できません
  folder = "${grafana_folder.service1.id}"
}

datasource.tf

データソースの接続先を指定します。

1
2
3
4
5
resource "grafana_data_source" "sample1" {
  type = "prometheus"
  name = "sample1"
  url  = "http://prometheus-server.sample1.svc.cluster.local"  # k8s の DNS に準拠
}

provider.tf

terraform が接続する Grafana の URL や認証設定を書きます

1
2
3
4
provider "grafana" {
  url  = "<grafana の Endpoint を記述>"
  auth = "<user>:<password>"  # このフォーマットで記述
}

Jsonnet の設定

Grafana の Dashboard はすべて JSON ファイルで構成されており、Terraform から Dashboard を作成する場合も、JSON ファイルを指定することが必要です。ただし、素の Grafana Dashboard の JSON ファイルは相当見づらく、diff も全く意味をなしません…

そこで、Jsonnet と そのライブラリである Grafonnet を利用することで、シンプルな Jsonnet ファイルから Grafana Dashboard JSON を生成するようにしています。

GitHub には Jsonnet のみを配置しておき、CI 上で JSON に変換したあと、terraform apply を実行することでリソース作成を行います。 以下は、Jsonnet で記述したサンプルになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# grafonnet の library import を行います。
# grafonnet と Jsonnet 導入済みの Image を利用するか、CI 上でパスを解決するようにします
# 本サンプルで利用していない アラート設定用 import 群も含んでいます
local grafana = import 'grafana.libsonnet';
local dashboard = grafana.dashboard;
local row = grafana.row;
local singlestat = grafana.singlestat;
local graphPanel = grafana.graphPanel;
local prometheus = grafana.prometheus;
local template = grafana.template;
local alertCondition = grafana.alertCondition;
dashboard.new(
  'sample board',
  refresh='10s',
)
.addPanel(
  graphPanel.new(
    'sample graph',
    datasource='sample1',
  )
  .addTarget(
    prometheus.target(
      'max(kube_deployment_labels {namespace="kube-system"}) by (deployment)', # promql を記述
      legendFormat='{{deployment}}',
    )
  ),
  gridPos={
    x: 0,
    y: 0,
    w: 12,  # grid layout で width は 24 が最大
    h: 6,
  }
)

dashboard を宣言してから、addPanel をつなげていくことで Dashboard を作成できます。上記のサンプルでは、以下のような Dashboard が作成されます

dashboard_sample.png

チームに Monitoring as Code を導入した結果

よかったこと

これらの基盤をチームに導入したことで、上述したとおり、「継続的な監視の修正」を行うためのアラートや Dashboard の修正がトラッキングでき、チームとして監視に対する知見を貯めることが可能になりました。

また、障害トラッキングや設定の自動化はもちろんですが、アラート設定をコード化することによって、チームでのアラートメッセージを共通化することが可能になりました。

私達のチームでは、アラートメッセージに サービス管理者・概要・手順書へのリンク 等を入れるように強制できるような Jsonnet 関数を作成しています。

こちらは現在移行中ですが、障害発生時の対応フローや調査方法が属人化せず、対応方法が明確になることで野良アラートが減ってくるようになりました。

今後考慮しないといけないこと

Grafana の素の JSON のレビューに比べて、レビュー時の可読性は向上しましたが、 「Jsonnet と Grafonnet の学習コストが高い」 という問題点があります。

監視自体は非常に大事ですが、本来の開発工数を削ってまで学習する必要があるのかは怪しく、学習コストが高いことで監視の属人化が進んでしまう可能性は否定できません。

今後の課題ですが、将来的には監視基盤についてのチームでの技術浸透や勉強会を行う必要もあるのかなと思っています。 また、もし運用に手が回りきらないなどの状況の場合、組織によっては SaaS を導入することも検討したほうがいいと思っています。

最後に

本記事では、Prometheus・Grafana を中心とした Monitoring as Code を実現するアーキテクチャを紹介しました。

Kubernetes 周りの監視は非常に奥が深く、これからも勉強していきたいなと思っています。

リクルートライフスタイルでは、一緒にサービス・プロダクト改善をしてくれる仲間を募集しています! 興味のある方はぜひご連絡をお待ちしております (Twitter: @ko_da_k)!

最後までお読みいただきありがとうございました!