[Kubernetes] Prometheus MySQL Exporter インストール方法

Kubernetes (GKE) へ Prometheus の MySQL Exporter をインストールするための情報を記載します。この記事では以下を対象としています。

  • Kubernetes クラスタは Google Kubernetes Engine (GKE)
  • Workload Identity を用いて権限付与をセキュアにする
  • Prometheus Community の Helm Chart を利用する
  • Prometheus Operator を利用している
  • 対象となる MySQL は GCP の Cloud SQL である
  • Cloud SQL へのアクセスは Cloud SQL Proxy を利用している

上記の条件すべてに当てはまる必要はありませんが、条件が近いほうが役に立つと思います。

Prometheus Operator のインストールは以下の記事を参考にしてください。

[Kubernetes] Prometheus Operator を Helm でイントールする方法
[Kubernetes] Prometheus Operator を Helm でイントールする方法
https://fand.jp/technologies/how-to-install-prometheus-operator-with-helm/
Helm、Helmfile を用いて Prometheus Operator 版の Prometheus を Kubernetes へインストールする手順を説明します。

この記事を読むことで以下の理解に役立つ可能性があります。

  • MySQL Exporter の Helm CHart の設定カスタマイズ方法
  • ServiceMonitor による MySQL Exporter の登録

続いて、Prometheus MySQL Exporter のインストール手順の詳細を記載します。

完成品のコンフィグ

GCP のプロジェクトは dummy-project-0123456 とします。

helmfile.yaml

repositories:
  - name: prometheus-community
    url: https://prometheus-community.github.io/helm-charts

releases:
  - name: prometheus-mysql-exporter
    namespace: observability
    chart: prometheus-community/prometheus-mysql-exporter
    values:
      - ./values.yaml

values.yaml

serviceMonitor:
  enabled: true
  interval: 30s
  namespace: observability
  additionalLabels:
    # Changes may be required depending on Prometheus Operator setup conditions
    release: prometheus
  jobLabel: ""
  targetLabels: []
  podTargetLabels: []
  metricRelabelings: []
  relabelings: []

serviceAccount:
  create: true
  name: prometheus-mysql-exporter

mysql:
  existingSecret: "mysql-exporter-cloudsql-secret"

cloudsqlproxy:
  enabled: true
  image:
    repo: "gcr.io/cloudsql-docker/gce-proxy"
    tag: "1.33.0-alpine"
    pullPolicy: "IfNotPresent"
  # ConnectionName 要変更
  instanceConnectionName: "dummy-project-0123456:asia-northeast1:mysql-cloudsql"
  workloadIdentity:
    enabled: true
    serviceAccountEmail: "[email protected]unt.com"

Helm での構成管理をしやすくするために Helmfile を利用しています。

Helm 再入門 - Helmfile をインストールして特徴を整理する
Helm 再入門 - Helmfile をインストールして特徴を整理する
https://fand.jp/technologies/make-helm-comfortable-with-helmfile/
一度は Helm の利用をやめましたが Helm v3 と Helmfile で利便性が改善したので再評価してみました。

本記事作成の背景

Prometheus Operator を利用した Exporter のインストールをいくつか試していたところですが、MySQL Exporter の Helm Chart は思いの外インストールに時間がかかったので、悩んだところなどを記録することを目的としています。

チュートリアル的な step by step の記事構成にはなっていないため、ある程度の Prometheus Operator への理解がある方を対象としています。基礎的な内容は関連記事を作成していますので参考にしてください。

Kubernetes タグが設定されている本サイトの記事一覧

手順概要

  • Workload Identity を利用するためのサービスアカウントを作成する
  • MySQL サーバーに Exporter 用の参照ユーザーを作成する
  • MySQL Exporter の設定をしてデプロイする

サービスアカウントの作成

サービスアカウントには Kubernetes Service Account と Google Service Account の2つの概念が登場します。違いについては以下の記事を参考にしてください。

[GKE] サービスアカウントとサービスアカウントを連携する - Workload Identity
[GKE] サービスアカウントとサービスアカウントを連携する - Workload Identity
https://fand.jp/technologies/federate-google-service-accounts-with-kubernetes-sa-using-workload-identity/
GCPと Kubernets のサービスアカウントを連携できる IAM関連機能の Workload Identity について使い方を整理しました

カスタムロールの作成

ロールの定義を YAML ファイルで管理することができます。Git 管理を最大化するためにここではできるだけ YAML を利用するようにします。

カスタムロールの作成と管理  |  IAM のドキュメント  |  Google Cloud

Google Service Account

サービスアカウント名は prometheus-mysql-exporter とします。(正しくは values.yaml ファイルの serviceAccount.name にて設定している文字列と一致させてください)

gcloud iam service-accounts create prometheus-mysql-exporter \
    --display-name prometheus-mysql-exporter

ロールの作成

vi role.yaml ファイルを作成します。ここでは CloudSQL アクセスのための権限を2つ設定します。これによりシークレット情報(JSONキー)が不要になります。

title: Prometheus MySQL Expoter
description: Give permissions to the MySQL Exporter
stage: GA
includedPermissions:
  - cloudsql.instances.connect
  - cloudsql.instances.get
# 自身のGCPプロジェクトIDを取得する
PROJECT_ID=$(gcloud projects list --filter="$(gcloud config get-value project)" --format="value(PROJECT_ID)"); echo $PROJECT_ID

# ROLEのIDを任意の文字列で設定する
ROLE_ID=prometheusMySQLExporter; echo $ROLE_ID

# YAMLファイルを用いてロールを作成する
gcloud iam roles create $ROLE_ID \
    --project=$PROJECT_ID \
    --file=role.yaml

続いて、Google Service Account に ロールをマッピングします。

# 次の手順で利用するサービスアカウントの識別子(メールアドレス形式)を取得
SERVICE_ACCOUNT_EMAIL=$(gcloud iam --project=$PROJECT_ID service-accounts list --filter prometheus-mysql-exporter --format 'value([email])'); echo $SERVICE_ACCOUNT_EMAIL

# 作成したロールのID(完全文字列)を取得する
ROLE_FULL_ID=$(gcloud iam --project=$PROJECT_ID roles list --filter ${ROLE_ID} --format 'value([name])'); echo $ROLE_FULL_ID

# サービスアカウントとロールをマッピングする
gcloud projects add-iam-policy-binding $PROJECT_ID \
    --member serviceAccount:${SERVICE_ACCOUNT_EMAIL} \
    --role $ROLE_FULL_ID

Workload Identity で使えるようにマッピング

上記までは GCP サービス内の概念での操作でしたが、ここで Kubernetes のサービスアカウントが登場します。この時点では MySQL Exporter の Chart をインストールしていないため、 Kubernetes 上のサービスアカウントは存在していませんが、ただの文字列なので先に作業をしてしまいます。

値は以下の通りとします。

  • デプロイ先の名前空間 (Namespace) は observability とする
  • 作成する Kubernetes Service Account は prometheus-mysql-exporter とする
# 名前空間 と Kubernetes Service Account をスラッシュで連結した文字列を作成
MYSQL_EXPORTER_SA=observability/prometheus-mysql-exporter

# 
gcloud iam service-accounts add-iam-policy-binding \\
    --role roles/iam.workloadIdentityUser \\
    --member "serviceAccount:${PROJECT_ID}.svc.id.goog[${MYSQL_EXPORTER_SA}]" \\
    $SERVICE_ACCOUNT_EMAIL

これで、Google Service Account と Kubernetes Service Account の間の IAM バインディングを有効にできます。(Google Service Account に対して、 $PROJECT_ID.svc.id.goog[observation/prometheus-mysql-exporter] 形式のWorkload Identity 権限が設定されます)

roles/iam.workloadIdentityUser の意味は以下の記事で説明しています。 [GKE] サービスアカウントとサービスアカウントを連携する - Workload Identity

MySQL ユーザーの作成

ユーザー作成は MySQL Exporter の GitHub の説明通りに作成します。

https://github.com/prometheus/mysqld_exporter

CREATE USER 'exporter'@'localhost' IDENTIFIED BY 'XXXXXXXX' WITH MAX_USER_CONNECTIONS 3;
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'localhost';

MySQL Exporter のデプロイ

記事の冒頭に記載した通り、YAMLファイルが2つあります。

  • helmfile.yaml
  • values.yaml

最初に完成品を記載しているのですが、コピペだけでは応用がきかないので、以下に説明を補足します。

MySQL ユーザー情報の与え方

重要なのは、最終的には環境変数 DATA_SOURCE_NAME に何らかの方法で MySQLへの接続情報を与える必要があるということです。

そのための手段がいくつかあります。

  • values.yaml に文字列を Plain Text で記載する
  • env: から参照するための key を与える
  • envFrom: から参照する形で DATA_SOURCE_NAME の環境変数を直接与えてしまう

これらの振る舞いを実装しているのは、以下の template が該当します。

https://github.com/prometheus-community/helm-charts/blob/e09e9879ad75f1e787eadbeb9d73f1aafb86c226/charts/prometheus-mysql-exporter/templates/deployment.yaml#L61-L80

ドキュメントにはこれらの全体概要をつかむための情報が記載されておらず少々苦労しました。

インストールが完了したあとに見つけたのですが、以下のサイトを最初に見ておけば、もう少し手早く作業ができたように思います。

MySQL Exporter - ExporterHub

MySQL Exporter - ExporterHub

The MySQL exporter is required to monitor MySQL metrics as databases are a critical resource with downtime causing significant financial and …

MySQL ユーザー情報の作成方法

いくつかの方法があることを説明しましたが、今回は envFrom: から参照するための手順とします。

values.yaml は Git で管理することを考えるとパスワードを記載したくないので却下、同様の理由で kind: Secret リソースを作成するのも却下、そうなると残るのは kubectl コマンドを用いて引数で Secret を作成することになります。

ここまでくると env と envFrom の違いは誤差ではないかと思いますが、個人的にはパスワード情報は特定の(安全な)場所に書き出しておき、そのファイルから Secret を生成する方法が好みです。

Managing Secrets using kubectl | Kubernetes

具体的には以下の通りです。

# SECRET_FILE は各自のワークスペースのセキュアな場所とする(暗号化されたディレクトリなど)
SECRET_FILE=$HOME/credentials/cloudsql/datasource.txt

# テキストファイルに接続情報を書き出す
echo -n 'username:[email protected](localhost:3306)/' > $SECRET_FILE

# テキストファイルの文字列から DATA_SOURCE_NAME を作成する
kubectl -n observability create secret generic mysql-exporter-cloudsql-secret --from-file=DATA_SOURCE_NAME=$SECRET_FILE

# DATA_SOURCE_NAME が期待した値になっているかを確認する
kubectl -n observability get secret mysql-exporter-cloudsql-secret -ojsonpath='{.data.DATA_SOURCE_NAME}' | base64 -d -

デプロイ

# デプロイされるリリースを確認する
helmfile diff

# デプロイする
helmfile apply

設定のカスタマイズ

設定可能な値は以下を参考にしてください。

https://github.com/prometheus-community/helm-charts/blob/e09e9879ad75f1e787eadbeb9d73f1aafb86c226/charts/prometheus-mysql-exporter/values.yaml

Workload Identity の利用

Workload Identity の利用の有無は以下の設定が該当します。

cloudsqlproxy:
  enabled: true
  # ...
  workloadIdentity:
    enabled: true
    serviceAccountEmail: "[email protected]"

Workload Identity を利用しない場合は、CloudSQL の認証ファイルが必要になることが以下の template から読み取れます。

https://github.com/prometheus-community/helm-charts/blob/e09e9879ad75f1e787eadbeb9d73f1aafb86c226/charts/prometheus-mysql-exporter/templates/deployment.yaml#L95-L112

Kubernetes Service Account の作成

Kubernetes ServiceAccount の作成は以下の template で行われます。

https://github.com/prometheus-community/helm-charts/blob/e09e9879ad75f1e787eadbeb9d73f1aafb86c226/charts/prometheus-mysql-exporter/templates/serviceaccount.yaml

workloadIdentity の設定が有効であれば自動で作成される対象になっています。

ServiceAccount の名前は設定しなくても勝手に生成してくれるのですが、デフォルトだと長い文字列になってしまうので、最初に固定文字を決めておいたほうが Google Service Account の作成作業も効率的に行えるのでよいのではないかと思います。

まとめ

MySQL Exporter を Helm を利用してインストールしました。

インストール手順として最も単純なのは values.yaml にパスワードを記載する方法ですが、セキュアな構成管理をするうえでは Workload Identity を利用は欠かせないでしょう。結果的にシークレットキーの管理が不要になり、シンプルな構成(デプロイするリソースの削減)が実現できました。

インストールが上手く行かない場合は認証情報の設定に失敗している可能性も考えられるので、ログを利用してひとつひとつ確認して解消していきましょう。

参考情報

いくつかのエラーの説明

msg=“Error parsing my.cnf” file=/home/.my.cnf err=“failed reading ini file: open /home/.my.cnf: no such file or directory”

ts=2022-11-23T08:35:25.987Z caller=mysqld_exporter.go:277 level=info msg="Starting mysqld_exporter" version="(version=0.14.0, branch=HEAD, revision=ca1b9af82a471c849c529eb8aadb1aac73e7b68c)"
ts=2022-11-23T08:35:25.987Z caller=mysqld_exporter.go:278 level=info msg="Build context" (gogo1.17.8,[email protected],date20220304-16:25:15)=(MISSING)
ts=2022-11-23T08:35:25.988Z caller=mysqld_exporter.go:284 level=info msg="Error parsing my.cnf" file=/home/.my.cnf err="failed reading ini file: open /home/.my.cnf: no such file or directory"
  • .my.cnf が登場し、最初は MySQL データベースサーバーの my.cnf かと思って混乱したが、MySQL Exporter における .my.cnf とはDB接続情報を記載するためのファイルを指している
  • DATA_SOURCE_NAME の書式が間違っていた時に遭遇。環境変数にDB接続情報が定義されていないときには .my.cnf を利用する動きになると思われる

msg=“Error opening connection to database” err=“invalid DSN: missing the slash separating the database name”

prometheus-mysql-exporter-7fbf7bcfc6-cf7ch prometheus-mysql-exporter ts=2022-11-23T08:58:48.896Z caller=exporter.go:136 level=error msg="Error opening connection to database" err="invalid DSN: missing the slash separating the database name"
  • DATA_SOURCE_NAME 形式の文字列では最後にスラッシュを明示する必要がある

msg=“Error pinging mysqld” err=“Error 1102: Incorrect database name ‘\n’”

prometheus-mysql-exporter-7fbf7bcfc6-mk9wt prometheus-mysql-exporter ts=2022-11-23T09:01:57.634Z caller=exporter.go:149 level=error msg="Error pinging mysqld" err="Error 1102: Incorrect database name '\n'"