Prometheus メトリクスのダッシュボードを Perses で作成する
概要
Perses とは
Perses は prometheus メトリクスを可視化するダッシュボードをコードから作成する Dashboard as Code (DaC) の概念を実現するための OSS です。
最初のコミットが 2021/1, 現在の star 数は ~ 900 程と比較的歴史の浅いプロジェクトですが、2024/8 には CNCF の sandbox プロジェクトに採択されました。PromCon Europe 2024 についた書かれた 2024/10 の CNCF ブログでも、開発初期段階ではあるが prometheus のネイティブなダッシュボードとして発展が期待されていると語られています。
On the visualization side, one of the interesting developments coming out of PromCon this year was Perses project that recently joined the CNCF. Julius hinted that while Grafana has been the default for many, Perses is offering a lightweight, Prometheus-native dashboarding experience, with GitOps capabilities and foundational open source philosophy. Perses is still in its early days, but it’s worth keeping an eye on as it evolves.
perses は Grafana と同様に prometheus メトリクスをフィルタリング、集計してダッシュボード形式で表示できる機能や、ダッシュボード内のパネルを柔軟にカスタマイズできる機能があります。そしてこれらのダッシュボードをコードから生成できる点が最大の特徴になっています。
モチベーション
とはいえ prometheus で収集したメトリクスを可視化するためのツールといえば Grafana が定番なので、わざわざ perses を新しく使用する必要性やメリットは何なのかという点がまず疑問として上がってくるかと思います。これに関しては Promcon について 2023/11 に書かれた CNCF ブログ内で、prometheus メトリクスを可視化するためのツールとして perses のような DaC を導入するに至るモチベーションが書かれています。
内容を簡単にまとめると以下のようになります。
- Grafana をアップグレードすると、スキーマの変更により既存のダッシュボードの表示が壊れることがよくあった。ダッシュボードの数が多い大規模な環境(ブログ内では 5000 以上とのこと)ではこれらを手動で修正するのは大きな負担となる。
- 上記の課題を解決するために、GitOps の手法や CI/CD と統合してコードからダッシュボードを作成できるようになれば効果的。
- Grafana ダッシュボードのデータ構造では IaC のように扱うのが難しいため、別のツールで実現する必要がある。
Grafana のダッシュボードやパネルの実態は json なので IaC や CI/CD をやろうと思えばできるかもしれませんが、確かに既存の仕組みではデプロイの自動化やプロパティの検証が難しい面があります。
例えば terraform では grafana 公式の grafana provider が提供されていますが、datasource や folder といったリソースは HCL の構文で記述できるのに対し dashboard は json の中身に相当する内容を encode して作成するようになっています。
resource "grafana_folder" "test" {
title = "My Folder"
uid = "my-folder-uid"
}
resource "grafana_dashboard" "test" {
folder = grafana_folder.test.uid
config_json = jsonencode({
"title" : "My Dashboard",
"uid" : "my-dashboard-uid"
})
}
ダッシュボード内に含まれるパネルが多くなってくると json も数千行レベルになってくるので、各プロパティの値を検証したり厳密に管理していくのはなかなかつらいものがあります。このような背景もあってダッシュボードも GitOps のオペレーションに組み込んで管理できる DaC の必要性が増してきたということが読み取れます。
その他にブログでは grafana の管理団体である grafana Lab が grafana や loki 等のライセンスを Apache License v2.0 から AGPLv3 に変更した点についても言及しています。
The foundational open source strategy is of particular importance to the community in light of Grafana’s relicensing last year from Apache 2.0 to the more restrictive and copyleft open source license AGPLv3, and the uncertainty of what other changes the future holds. This community concern around the Grafana relicensing triggered the foundation of CoreDash Project under the CNCF, seeking Apache 2.0 foundational open source alternatives. As a CNCF Ambassador, I’m particularly excited to see Perses targeting joining the CNCF to fill in this gap in the CNCF observability stack.
grafana が将来的にどのようになるかは不明ですが、オープンソースとして利用できる製品をコミュニティで維持していくという精神も perses の開発のモチベーションに関わっていそうです。これを意識してか、perses のドキュメントでは 100 % open source と明記されています。
Open Source#
Perses is 100% open source and community-driven. All components are available under the Apache 2.0 License on GitHub.Perses is a Cloud Native Computing Foundation sandbox project.
Dashboard as Code
perses では cue という(謎の)言語と Golang の 2 つでコードを記述することができます。
ダッシュボードの定義やプロパティをコードで記述し、ダッシュボードのリソース定義 (yaml) をコードから生成してデプロイするという方法で DaC を実現します。プログラミング言語でコードを記述してリソースの定義を生成するという点では aws CDK や pulumi に近いイメージとなっています。
perses のリソース
perses は grafana と同様にダッシュボードを管理するツールであるため、perses 内で定義されているリソースの多くは grafana と同じような概念となっています。今回の記事で扱う perses リソースについて以下で簡単に記載します。
project
project はワークスペースに相当する概念で、この中で複数のダッシュボードを管理します。
datasource
grafana の datasource とほぼ同じで、ダッシュボードに表示するメトリクスの参照先のデータソースを表します。現時点では prometheus しか指定できないようですが、データソースの仕様は plugin として定義されているため、将来的には他のデータソースやユーザーが独自に定義したデータソースも参照できるようになることが期待されます。
dashboard
grafana の dashboard とほぼ同じで、この中に複数のパネルを作成してメトリクスを可視化します。ダッシュボード内の panel のレイアウトが自由に変えられる点も grafana と同じ。
panel
panel は時系列グラフや棒グラフ、table といった 1 つのグラフを描写するオブジェクトに該当します。これも grafana の panel とほぼ同じ。
user
user は perses にリソースを作成したり web UI にアクセスする際の認証として使用されます。これも grafana の user とほぼ同じ。user がどのような操作を実行できるかといった権限は k8s と同じように記述できる role と rolebinding を使った RBAC で管理します。
使ってみる
docker で perses をインストールする
https://perses.dev/ の記載の通り perses は kubernetes-native に作られているので k8s クラスタ上にインストールできます。ドキュメントのインストール手順 には記載されていませんがクラスタ上にデプロイするための helm chart も用意されています。ただ試してみたところ、現時点では chart の values.yml で perses の構成ファイルのすべての項目を設定できるわけではないようです。
また、ドキュメントの通り operator pattern での管理も想定されています。
Use the Perses operator to manage your Perses deployments & dashboards as CRDs. Leverage on the datasource discovery to retrieve data from your datasource pods/services.
operator は perses/perses-operator で開発されていますが、これを k8s 上にデプロイするための helm chart は現時点ではまだなさそうです。
というわけで、今回は perses のコンテナイメージを利用して docker compose でインストールします。
perses に関する構成は単一の yaml ファイル内で記述して perses を実行する際の --config
で指定することで適用します。構成ファイルに設定可能な項目は configuration にまとめられていますが、プロパティによってはそれほど詳細な説明がないものあるのでひとまず helm chart でインストールした際に自動生成される config を参考に設定します。
security:
readonly: false
enable_auth: true
cookie:
same_site: lax
secure: false
authentication:
access_token_ttl: 24h
refresh_token_ttl: 24h
providers:
enable_native: true
authorization:
guest_permissions:
- actions: ["*"]
scopes: ["*"]
database:
file:
extension: "json"
folder: "/perses"
schemas:
datasources_path: /etc/perses/cue/schemas/datasources
interval: 5m
panels_path: /etc/perses/cue/schemas/panels
queries_path: /etc/perses/cue/schemas/queries
variables_path: /etc/perses/cue/schemas/variables
docker-compose.yml では config に上記の config.yml を指定します。
services:
perses:
container_name: perses
image: persesdev/perses:v0.48.0
ports:
- 8080:8080
command:
- --config=/etc/perses/config/config.yml
- --web.listen-address=:8080
- --web.hide-port=false
- --web.telemetry-path=/metrics
- --log.level=info
- --log.method-trace=true
volumes:
- type: bind
source: ./config.yml
target: /etc/perses/config/config.yml
- ./secret:/etc/perses/config
perses コンテナを起動。
docker compose up -d
CLI インストール
perses にリソースをデプロイする際は perses の API を実行することもできますが、デプロイを簡単に行うための perses
CLI が用意されています。デプロイは基本的に perses CLI を通じて行うので https://perses.dev/perses/docs/user-guides/dashboard-as-code/ に沿って perses の release からインストールしておきます。
wget https://github.com/perses/perses/releases/download/v0.48.0/perses_0.48.0_linux_amd64.tar.gz
tar zxvf perses_0.48.0_linux_amd64.tar.gz
sudo mv percli /usr/local/bin
コードを記述する言語に cue を使用する場合は cue
が必要になるのでインストールします。
wget https://github.com/cue-lang/cue/releases/download/v0.11.0/cue_v0.11.0_linux_amd64.tar.gz
tar zxvf cue_v0.11.0_linux_amd64.tar.gz
sudo mv cue /usr/local/bin
cue を実行する際は go
も必要になるのでこちらもインストール。
wget https://go.dev/dl/go1.23.3.linux-amd64.tar.gz
tar zxvf go1.23.3.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.23.3.linux-amd64.tar.gz
rm -rf go go1.23.3.linux-amd64.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.zshrc
source ~/.zshrc
prometheus の準備
grafana と同様にデータソースとなる prometheus は perses に同梱されていないので別途作成する必要があります。取得するメトリクスは何でもいいですが、ここでは Prometheus, Thanos による k8s クラスタ Metrics の記事を参考に k8s クラスタの node, pod メトリクスを収集するように設定しておきます。
セットアップ
DaC でダッシュボードを作れるようになるまでにいくつか準備が必要なのでここでまとめて行います。
まずはじめに、perses を起動した段階ではまだユーザーが存在していないので User リソースで適当なユーザーを作成します。
kind: User
metadata:
# User name for login
name: test
spec:
firstName: test # Optional
lastName: test # Optional
nativeProvider:
password: test # Optional
json に変換したのち、user create API
を使ってデプロイ。
$ cat user.yml | yq -P > user.json
$ curl -H "content-type: application/json" -d @user.json http://0.0.0.0:8080/api/v1/users
{"kind":"User","metadata":{"name":"test","createdAt":"2024-11-27T16:11:27.308967779Z","updatedAt":"2024-11-27T16:11:27.308967779Z","version":0},"spec":{"firstName":"test","lastName":"test","nativeProvider":{"password":"\u003csecret\u003e"}}}
これで percli login
で作ったユーザーの認証情報でログインできるようになります。
$ percli login http://0.0.0.0:8080 --username test --password test
successfully logged in http://0.0.0.0:8080
ログインした際の認証情報は ~/.perses/config.json
に保存されます。
{"rest_client_config":{"url":"http://0.0.0.0:8080","authorization":{"type":"Bearer","credentials":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNzMyNzI0MzI4LCJuYmYiOjE3MzI3MjM0Mjh9.yzoz4vwtXjTHA5OQ_tuQEWqdAVDrtZpcOEznC0aO9U2YkKFKfH2SUqRptUDM7Q7gE2eHv98w5-glIch9KIkPGA"},"tls_config":{"insecureSkipVerify":false}},"refresh_token":"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0IiwiZXhwIjoxNzMyODA5ODI4LCJuYmYiOjE3MzI3MjM0Mjh9.0hO6zJ4oQl24ScQww7UiaAT4_1j0Lz6thbCmB9_rH1EC5XfmsNHlGHnnrOWo0KCRRd8nCF_XI2AXcQTGMLxm8A","dac":{}}%
API を直接実行する際はこの中の credentials を token として使用できます。ただ perses CLI で必要な操作は実行できるのあまり使用する機会はないかも。
$ export TOKEN=$(cat ~/.perses/config.json | yq -r ".rest_client_config.authorization.credentials")
# 登録済みユーザーを API で取得
$ curl -sSH "Authorization: Bearer $TOKEN" http://0.0.0.0:8080/api/v1/users | yq -P
- kind: User
metadata:
name: test
createdAt: "2024-11-29T13:16:51.549195678Z"
updatedAt: "2024-11-29T13:16:51.549195678Z"
version: 0
spec:
firstName: test
lastName: test
nativeProvider:
password: <secret>
次に作業用の project を作成します。本格的な運用では用途ごとに project を分割するのが推奨されますが、検証では project が 1 つあれば充分なので以下の test-project
で作業していきます。
kind: "Project"
metadata:
name: test-project
いったんリソース作成権限を持つユーザーで認証すれば percli apply
でリソースをデプロイできるようになるので今後はこちらを利用します。
$ percli apply -f project.yml
object "Project" "test-project" has been applied
ダッシュボードを作る
設定が一通り完了したので、ここからコードを書いて DaC のアプローチでダッシュボードを作っていきます。コードを書く言語に cue か go のどちらを使うべきかについて特に記述などは見当たりませんでしたが、user guide によると将来的に go SDK ではすべてのプラグインがサポートされない可能性があるとのことです。
Just pick your favorite to start with DaC. If you don't have one, give a try to both!
Note
CUE is the language used for the data model of the plugins, which means you'll always be able to include any external plugin installed in your Perses server into your code when using the CUE SDK.
However, the Golang SDK may not support all the plugins: it's basically up to each plugin development team to provide a Go package to enable the DaC use case.
This statement applies also to any other language we might have a SDK for in the future.
試しに使う分にはどちらでも問題はなさそうなのでここでは cue を使ってコードを書いていきます。
ダッシュボードの作成
まずはじめに https://perses.dev/perses/docs/user-guides/dashboard-as-code/#repository-setup にそって cue module を初期化します。
cue mod init
次に perses 関連のモジュールをインストール。
percli dac setup --version 0.48.0
これにより cue.mod
ディレクトリ配下に perses のモジュールが配置されます。
まずは cue でのコードの書き方も合わせて簡単なダッシュボードを作ってみます。input.cue を参考に以下のような my_dashboard.cue
を作成。
package myDaC
import (
dashboardBuilder "github.com/perses/perses/cue/dac-utils/dashboard"
prometheusDs "github.com/perses/perses/cue/schemas/datasources/prometheus:model"
)
// ダッシュボードを作る際は基本的に dashboardBuilder を利用
dashboardBuilder & {
#name: "mytest"
#display: name: "Containers monitoring"
#project: "test-project"
#datasources: {myPromDemo: {
default: true
plugin: prometheusDs & {spec: {
directUrl: "http://192.168.3.207:31808" // prometheus の URL
}}
}}
#duration: "3h"
#refreshInterval: "30s"
}
次に percli dac build
コマンドで cue コードからリソース定義の yaml を生成します。
$ percli dac build -f my_dashboard.cue
Succesfully built my_dashboard.cue at built/my_dashboard_output.yaml
書いたコードでエラー等が発生しなければ cue コードからリソースの yaml を生成したものが built
ディレクトリに出力されます。このときのディレクトリ構造は以下。
.
├── built
│ └── my_dashboard_output.yaml
├── cue.mod
│ ├── module.cue
│ ├── pkg
│ │ └── github.com
│ │ └── perses
│ │ └── (perses の module)
│ └── usr
└── my_dashboard.cue
できた yaml を見てみると dashboard リソースの定義に合うように各プロパティが設定されていることがわかります。
kind: Dashboard
metadata:
name: mytest
createdAt: "0001-01-01T00:00:00Z"
updatedAt: "0001-01-01T00:00:00Z"
version: 0
project: test-project
spec:
display:
name: Containers monitoring
datasources:
myPromDemo:
default: true
plugin:
kind: PrometheusDatasource
spec:
directUrl: http://192.168.3.207:31808
panels: {}
layouts: []
duration: 3h
refreshInterval: 30s
リソース定義を perses にデプロイするには percli apply -f [file]
を実行します。
$ percli apply -f built/my_dashboard_output.yaml
object "Dashboard" "mytest" has been applied in the project "test-project"
これで perses 上にダッシュボードが作成されたので実際に perses の UI から確認してみます。ブラウザで http://0.0.0.0:8080
にアクセスし、先ほど作った test
ユーザーでログインすると以下のように test-project
配下にダッシュボードが作成されていることが確認できます。
ただ上記の cue コードではダッシュボードに panel を追加していないので中身を見ても何もありません。次は prometheus メトリクスを可視化するための panel を追加するように cue コードを修正していきます。
パネルの追加
パネルは grafana におけるパネル とほぼ同じで、promQL のクエリで取得した prometheus メトリクスを様々な形式で表示することができます。ここでは k8s クラスタを構成するノードのメモリ使用量を表示するパネルを作っていきます。
パネルを作成するには基本的に組み込みの panelBuilder
を使用します。panelBuilder では panel のタイトルやダッシュボード上での位置、パネル内にどのようなデータを描写するかといった情報を定義します。こちらも input.cue の記述例を参考に設定。
#memoryPanel: panelBuilder & {
spec: {
display: name: "Node memory utilization"
plugin: timeseriesChart & {
spec: querySettings: [
{
queryIndex: 0
colorMode: "fixed-single"
colorValue: "#0be300"
},
]
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: query: "node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes"
}
},
]
}
}
パネル内に表示するデータは queries に指定します。時系列データを表示する際は TimeSeriesQuery を指定。
spec.plugin には promQL のクエリに対応する plugin promQuery
を指定し、spec.query
にデータを取得するためのクエリ文を記載します。
上記で定義した memoryPanel
を dashboardBuilder 内の panelGroups.input
に含めることでダッシュボードにパネルを追加できます。全体の cue コードは以下。
package myDaC
import (
dashboardBuilder "github.com/perses/perses/cue/dac-utils/dashboard"
prometheusDs "github.com/perses/perses/cue/schemas/datasources/prometheus:model"
panelGroupsBuilder "github.com/perses/perses/cue/dac-utils/panelgroups"
panelBuilder "github.com/perses/perses/cue/dac-utils/prometheus/panel"
timeseriesChart "github.com/perses/perses/cue/schemas/panels/time-series:model"
promQuery "github.com/perses/perses/cue/schemas/queries/prometheus:model"
)
dashboardBuilder & {
#name: "mytest"
#display: name: "Containers monitoring"
#project: "test-project"
#datasources: {myPromDemo: {
default: true
plugin: prometheusDs & {spec: {
directUrl: "http://192.168.3.207:31808"
}}
}}
#duration: "3h"
#refreshInterval: "30s"
#panelGroups: panelGroupsBuilder & {
#input: [
{
#title: "Resource usage"
#cols: 3
#panels: [
#memoryPanel,
]
},
]
}
}
#memoryPanel: panelBuilder & {
spec: {
display: name: "Node memory utilization"
plugin: timeseriesChart & {
spec: querySettings: [
{
queryIndex: 0
colorMode: "fixed-single"
colorValue: "#0be300"
},
]
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: query: "node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes"
}
},
]
}
}
perses dac build
でビルドした yaml を見ると以下のようになっています。
kind: Dashboard
metadata:
name: mytest
createdAt: "0001-01-01T00:00:00Z"
updatedAt: "0001-01-01T00:00:00Z"
version: 0
project: test-project
spec:
display:
name: Containers monitoring
datasources:
myPromDemo:
default: true
plugin:
kind: PrometheusDatasource
spec:
directUrl: http://192.168.3.207:31808
panels:
"0_0":
kind: Panel
spec:
display:
name: Node memory utilization
plugin:
kind: TimeSeriesChart
spec:
querySettings:
- queryIndex: 0
colorMode: fixed-single
colorValue: '#0be300'
queries:
- kind: TimeSeriesQuery
spec:
plugin:
kind: PrometheusTimeSeriesQuery
spec:
query: node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes
layouts:
- kind: Grid
spec:
display:
title: Resource usage
items:
- x: 0
"y": 0
width: 8
height: 6
content:
$ref: '#/spec/panels/0_0'
duration: 3h
refreshInterval: 30s
新しく panels
が追加され、kind: Panel
で表されるパネルリソースがダッシュボードに追加されました。また、layouts
はダッシュボード上のパネルの位置を表しており、各パネルをどの位置にどのくらいのサイズで配置するかといった情報に対応しています。x, y
がパネルの座標、width, height
がパネルのサイズに対応しますが、このあたりはダッシュボードに設定した col や panel を元に自動で調整してくれます(プロパティで調整することも可能)。
上記の yaml を perses apply
でデプロイすると、想定通り先ほど作ったダッシュボードにクラスタ内のノードのメモリ使用量を表す panel が追加されました。
ただこのグラフは y 軸の単位が Byte のままだったり凡例が表示されてなかったりと少々見づらくなっているので、もう少し見やすいようにいくつか調整していきます。
プロパティの追加
パネルの見た目を調整するには cue コード上のプロパティを修正すれば良いですが、どのプロパティがどの設定項目に対応しているかを把握する必要があります。このあたりはいくつか調べ方が考えられますが以下の方法で確認するのが楽です。
まず UI から作ったパネルの Edit を選択すると、以下のように panel の Settings やクエリを編集できるようになります。
グラフが見やすくなるようにいくつか設定を変更した後に json
タブを選択すると、変更した値が反映された panel の json の中身が確認できます。これはリアルタイムで反映されるので、どの設定を修正したらどのプロパティが追加・変更されるのかわかります。
今回は y 軸の軸ラベルを表示、単位を Byte に設定。凡例を右側に追加、凡例のフォーマットを k8s ノード IP アドレスに設定、の 4 つの変更を加えましたが、json 内では以下のように追加されています。
"kind": "TimeSeriesChart",
"spec": {
"querySettings": [
{
"colorMode": "fixed-single",
"colorValue": "#0be300",
"queryIndex": 0
}
],
"legend": {
"position": "right"
},
"yAxis": {
"show": true,
"label": "memory utilization",
"format": {
"unit": "bytes"
}
},
...
"queries": [
{
"kind": "TimeSeriesQuery",
"spec": {
"plugin": {
"kind": "PrometheusTimeSeriesQuery",
"spec": {
"query": "node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes",
"seriesNameFormat": "{{instance}}"
}
これより軸の設定は TimeSeriesChart
リソースの legend と yAxis、凡例のフォーマットは PrometheusTimeSeriesQuery
リソースの seriesNameFormat を設定すれば良さそうということが推測できます。
実際に perses ドキュメントでワード検索すると TimeSeriesChart と PrometheusTimeSeriesQuery でそれぞれ上記のプロパティが存在していることがわかるので、これを踏まえた上で cue コードを修正します。
package myDaC
import (
dashboardBuilder "github.com/perses/perses/cue/dac-utils/dashboard"
prometheusDs "github.com/perses/perses/cue/schemas/datasources/prometheus:model"
panelGroupsBuilder "github.com/perses/perses/cue/dac-utils/panelgroups"
panelBuilder "github.com/perses/perses/cue/dac-utils/prometheus/panel"
timeseriesChart "github.com/perses/perses/cue/schemas/panels/time-series:model"
promQuery "github.com/perses/perses/cue/schemas/queries/prometheus:model"
)
dashboardBuilder & {
#name: "mytest"
#display: name: "Containers monitoring"
#project: "test-project"
#datasources: {myPromDemo: {
default: true
plugin: prometheusDs & {spec: {
directUrl: "http://192.168.3.207:31808"
}}
}}
#duration: "3h"
#refreshInterval: "30s"
#panelGroups: panelGroupsBuilder & {
#input: [
{
#title: "Resource usage"
#cols: 2
#panels: [
#memoryPanel,
]
},
]
}
}
#memoryPanel: panelBuilder & {
spec: {
display: name: "Node memory utilization"
plugin: timeseriesChart & {
spec: {
querySettings: [
{
queryIndex: 0
colorMode: "fixed-single"
colorValue: "#0be300"
},
+ ]
+ legend: {
+ position: "right"
+ }
+ yAxis: {
+ show: true
+ label: "Memory utilization"
+ format: unit: "bytes"
}
}
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes"
+ seriesNameFormat: "{{instance}}"
}
}
},
]
}
}
再度デプロイすると先ほどのパネルに軸ラベルや凡例がセットされてだいぶ見やすくなりました。
基本的に perses による DaC では以下のような工程を繰り返してダッシュボードを管理していきます。
- cue または go でコードを書く
-
perses dac build
でコードをからリソース定義を生成 -
perses apply
でデプロイ - 1 ~ 3 を繰り返して修正・更新
その他の機能
perses ドキュメントでは User Guide がありますが、どのようにダッシュボードを作って管理していくか等の使用例や運用方針については今のところ記載がなさそうで、これからどのように進めていけば良いか分かりづらくなっています。ひとまずドキュメントを参照ながら試した機能を以下に記載していきます。
複数のパネル
上記でパネル 1 つのダッシュボードができたので、今度はノードの CPU, memory, ストレージ使用量を表す 3 つのパネルを追加してみます。
似たようなパネルを複数作成する場合、凡例の位置や軸の設定などいくつか共通する部分をいちいち書いていくのは冗長なので cue の機能を使って共通部分をまとめて管理できるようにします。まずグラフの描写に関して配下のように設定することにします。
- 凡例の位置は右側に統一する。
- y 軸の単位はグラフで表示するメトリクスの種類に合わせて変更する。
これを実現するために、cue の definition を使って timeseriesChart
のグラフ設定を共通化するための schema timeseriesChartSettings
に定義します。今回の範囲ではメトリクスが取りうる単位は percent か byte だけなので unit は 3 つに限定しています。
#timeseriesChartSettings: {
querySettings: [
{
queryIndex: int | *0
colorMode: string | *"fixed-single"
colorValue: string | *"#0be300"
},
]
legend: {
position: string | *"right"
}
yAxis: {
format: {
unit: string | "percent" | "bytes" | "percent-decimal"
}
}
}
It’s normal for definitions to specify fields that don’t have concrete values, such as types.
cue の definition では具体的な数値を設定しないのが普通とのことですが、値を指定しない場合に適用される Default Values を設定することはできるので legend 等で指定しています。
メトリクスに関してはいずれも同じ prometheus から参照することがわかっているので、GlobalDatasource を定義してデフォルトのデータソースに指定します(global でなくてもよい)。デフォルトのデータソースを独立したリソースとして作成しておくことで、各ダッシュボードを定義する際に datasource の指定を省略できるようになります。
kind: "GlobalDatasource"
metadata:
name: prometheus
spec:
default: true
plugin:
kind: PrometheusDatasource
spec:
directUrl: http://192.168.3.207:31808
次にノードのストレージ使用率を表示するためのパネルを作ります。timeseriesChart に関しては上記で定義した timeseriesChartSettings
の設定を使用し、unit だけ percent
を明示的に指定します。query にはストレージ使用率を求めるクエリを指定。
#nodeStoragePanel: panelBuilder & {
spec: {
display: name: "Storage utilization"
plugin: timeseriesChart & {
spec: #timeseriesChartSettings & {yAxis: format: unit: "percent"}
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "(sum(node_filesystem_size_bytes{fstype!='tmpfs', fstype!='overlay'}) by (instance) - sum(node_filesystem_free_bytes{fstype!='tmpfs', fstype!='overlay'}) by (instance) ) / sum(node_filesystem_size_bytes{fstype!='tmpfs', fstype!='overlay'}) by (instance) * 100"
seriesNameFormat: "{{instance}}"
}
}
},
]
}
}
CPU 使用率を表示するためのパネルでは、以下のクエリでは値が 0 ~ 1 の percent で求められるため unit を percent-decimal
に指定します (クエリ内で x100 しても良いですが)。
#nodeCpuUtilizationPanel: panelBuilder & {
spec: {
display: name: "CPU utilization"
plugin: timeseriesChart & {
spec: #timeseriesChartSettings & {yAxis: format: unit: "percent-decimal"}
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "1 - (sum(rate(node_cpu_seconds_total{mode='idle'}[1m])) by (instance) / sum(rate(node_cpu_seconds_total[1m])) by (instance))"
seriesNameFormat: "{{instance}}"
}
}
},
]
}
}
コード全体は以下。
コード全体
package myDaC
import (
dashboardBuilder "github.com/perses/perses/cue/dac-utils/dashboard"
panelGroupsBuilder "github.com/perses/perses/cue/dac-utils/panelgroups"
panelBuilder "github.com/perses/perses/cue/dac-utils/prometheus/panel"
timeseriesChart "github.com/perses/perses/cue/schemas/panels/time-series:model"
promQuery "github.com/perses/perses/cue/schemas/queries/prometheus:model"
)
#timeseriesChartSettings: {
querySettings: [
{
queryIndex: int | *0
colorMode: string | *"fixed-single"
colorValue: string | *"#0be300"
},
]
legend: {
position: string | *"right"
}
yAxis: {
format: {
unit: string | "percent" | "bytes" | "percent-decimal"
}
}
}
#nodeStoragePanel: panelBuilder & {
spec: {
display: name: "Storage utilization"
plugin: timeseriesChart & {
spec: #timeseriesChartSettings & {yAxis: format: unit: "percent"}
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "(sum(node_filesystem_size_bytes{fstype!='tmpfs', fstype!='overlay'}) by (instance) - sum(node_filesystem_free_bytes{fstype!='tmpfs', fstype!='overlay'}) by (instance) ) / sum(node_filesystem_size_bytes{fstype!='tmpfs', fstype!='overlay'}) by (instance) * 100"
seriesNameFormat: "{{instance}}"
}
}
},
]
}
}
#nodeCpuUtilizationPanel: panelBuilder & {
spec: {
display: name: "CPU utilization"
plugin: timeseriesChart & {
spec: #timeseriesChartSettings & {yAxis: format: unit: "percent-decimal"}
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "1 - (sum(rate(node_cpu_seconds_total{mode='idle'}[1m])) by (instance) / sum(rate(node_cpu_seconds_total[1m])) by (instance))"
seriesNameFormat: "{{instance}}"
}
}
},
]
}
}
#nodeMemoryPanel: panelBuilder & {
spec: {
display: name: "Memory utilization"
plugin: timeseriesChart & {
spec: #timeseriesChartSettings & {yAxis: format: unit: "bytes"}
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes"
seriesNameFormat: "{{instance}}"
}
}
},
]
}
}
dashboardBuilder & {
#name: "node-dashboard"
#project: "test-project"
#duration: "1h"
#refreshInterval: "30s"
#panelGroups: panelGroupsBuilder & {
#input: [
{
#title: "Node usage"
#cols: 2
#height: 10
#panels: [
#nodeCpuUtilizationPanel,
#nodeMemoryPanel,
#nodeStoragePanel,
]
},
]
}
}
これをデプロイすると、ノード毎の CPU使用率、メモリ使用量、ストレージ使用率の 3 つのパネルが作成されます。
変数の利用
perses には grafana の variable とだいたい同じ概念の variable リソースが定義されており、パネルに表示するメトリクスを動的に変更したりする際に役立ちます。コード内に文字列をハードコーディングして変数として指定する static variable には以下のような種類があります。
- text variable: 単一の文字列を変数に指定
- Static List Variable: リスト形式で値を指定
この他に prometheus メトリクスに基づいて変数を指定できる variable もいくつか用意されています。
- Label Names Variable builder: prometheus メトリクスのラベル名を変数に設定できる
- PromQL Variable builder: prometheus メトリクスに対して promQL を実行し、その結果に基づいて変数を設定できる
その他、変数には scope の概念があり、単一の project のみで指定可能、複数の project で共通で使用可能といった有効範囲を指定できるようになっています。
変数の機能を使って、k8s クラスタの pod メトリクスを namespace 毎に切り替えて表示できるようにしてみましょう。namespace の一覧に関しては以下のような promQL クエリで取得できます。
count(kube_namespace_status_phase) by (namespace)
実際に prometheus UI 上でクエリを実行すると取得できることが確認できます。
cue コードで変数を指定するには varGroupBuilder を使って独自の変数グループを定義するのが楽です。この中では input
内に独自の変数をリスト形式で指定します。promQLVarBuilder
では name
に変数名、query
に上記のクエリを指定します。
import (
varGroupBuilder "github.com/perses/perses/cue/dac-utils/variable/group"
promQLVarBuilder "github.com/perses/perses/cue/dac-utils/prometheus/variable/promql"
)
#myVarsBuilder: varGroupBuilder & {
#input: [for i in #input {#datasourceName: "prometheus"}]
#input: [
promQLVarBuilder & {
#name: "namespace"
#query: "count(kube_namespace_status_phase) by (namespace)"
},
]
}
これでメトリクスから収集した namespace の一覧が namespace
という変数名で参照できるようになります。
クエリ内では $変数名
の表記で変数を参照できます。これ自体はプレースホルダーのようなもので、実際にダッシュボードで表記する際に値が代入されます。
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "sum(container_memory_working_set_bytes{container!=\"\", container!=\"POD\", namespace=\"$namespace\"}) by (pod)"
}
最後に、定義した variable をダッシュボードに含めるには dashboardBuilder の variables に変数を指定します。
dashboardBuilder & {
#name: "mytest"
#display: name: "Pod monitoring"
#project: "test-project"
#variables: #myVarsBuilder.variables
...
最終的なコードは以下
コード全文
package myDaC
import (
dashboardBuilder "github.com/perses/perses/cue/dac-utils/dashboard"
panelGroupsBuilder "github.com/perses/perses/cue/dac-utils/panelgroups"
panelBuilder "github.com/perses/perses/cue/dac-utils/prometheus/panel"
timeseriesChart "github.com/perses/perses/cue/schemas/panels/time-series:model"
promQuery "github.com/perses/perses/cue/schemas/queries/prometheus:model"
varGroupBuilder "github.com/perses/perses/cue/dac-utils/variable/group"
promQLVarBuilder "github.com/perses/perses/cue/dac-utils/prometheus/variable/promql"
)
dashboardBuilder & {
#name: "mytest"
#display: name: "Pod monitoring"
#project: "test-project"
#variables: #myVarsBuilder.variables
#duration: "1h"
#refreshInterval: "30s"
#panelGroups: panelGroupsBuilder & {
#input: [
{
#title: "Resource usage"
#cols: 2
#panels: [
#memoryPanel,
]
},
]
}
}
#myVarsBuilder: varGroupBuilder & {
#input: [for i in #input {#datasourceName: "prometheus"}]
#input: [
promQLVarBuilder & {
#name: "namespace"
#query: "count(kube_namespace_status_phase) by (namespace)"
},
]
}
#memoryPanel: panelBuilder & {
spec: {
display: name: "Pod memory used"
plugin: timeseriesChart & {
spec: {
querySettings: [
{
queryIndex: 0
colorMode: "fixed-single"
colorValue: "#0be300"
},
]
legend: {
position: "right"
}
yAxis: {
format: {
unit: "bytes"
}
}
}
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "sum(container_memory_working_set_bytes{container!=\"\", container!=\"POD\", namespace=\"$namespace\"}) by (pod)"
}
}
},
]
}
}
デプロイするとダッシュボードの左上に設定した namespace
変数が追加されます。以下の図では kube-system
が選択されているので、クエリ文の $namespace
には kube-system
が代入される結果 kube-system
内の pod のメモリ使用量が表示されるようになります。
kube-system namespace 内の pod 毎のメモリ使用量が表示される
値の候補は count(kube_namespace_status_phase) by (namespace)
のクエリによって取得された namespace 一覧から選択できます。
namespace を別の値に切り替えると表示されるメトリクスも動的に切り替わります。
metrics namespace 内の pod 毎のメモリ使用量が表示される
このように変数を有効に活用することでクエリの label やフィルタリングを動的に変更できるので、ダッシュボードに表示するデータをより柔軟に表現するのに役立ちます。
ファイルの分割
cue の仕様では go に近い感じで module や package を分割することができます。
ダッシュボード内に含める情報が増えてくると 1 つの cuue ファイルが肥大化してくるので、上記の機能を使ってリソース定義を別ファイルに分割できるか見ていきます。
まず今まで作業していたローカルのディレクトリを module にするため、cue.mod/module.cue
の module 名を変更します。cuetorial Modules and Packages によると module 名は github.com
のようなドメイン名が一般的とのことなので、ここでは github.com/perses-example
に設定します。ただこのドメインはあくまで形式上のものなので実際にコードを github 上にアップロードする必要はないです。
- module: "cue.example"
+ module: "github.com/perses-sample"
language: {
version: "v0.11.0"
}
次に timeseries/settings
のディレクトリ内に settings.cue
を作成。
$ tree timeseries
timeseries
└── settings
└── settings.cue
settings.cue
の中には先ほど時系列グラフの共通設定として定義していた #timeseriesChartSettings
を切り出します。
package settings
#timeseriesChartSettings: {
querySettings: [
{
queryIndex: int | *0
colorMode: string | *"fixed-single"
colorValue: string | *"#0be300"
},
]
legend: {
position: string | *"right"
}
yAxis: {
format: {
unit: string | "percent" | "bytes" | "percent-decimal"
}
}
}
そして元の cue コード内では #timeseriesChartSettings
の定義を削除し、代わりに上記のローカル module から定義をインポートするように import
に追加します。
package myDaC
import (
dashboardBuilder "github.com/perses/perses/cue/dac-utils/dashboard"
panelGroupsBuilder "github.com/perses/perses/cue/dac-utils/panelgroups"
panelBuilder "github.com/perses/perses/cue/dac-utils/prometheus/panel"
timeseriesChart "github.com/perses/perses/cue/schemas/panels/time-series:model"
promQuery "github.com/perses/perses/cue/schemas/queries/prometheus:model"
+ #timeseriesChartSettings "github.com/perses-sample/timeseries/settings"
)
- #timeseriesChartSettings: {
- querySettings: [
- {
- queryIndex: int | *0
- colorMode: string | *"fixed-single"
- colorValue: string | *"#0be300"
- },
- ]
- legend: {
- position: string | *"right"
- }
- yAxis: {
- format: {
- unit: string | "percent" | "bytes" | "percent-decimal"
- }
- }
- }
import の記法に関しては <module identifier>/<relative position of package within module>:<package name>
となっています (Import path も参照)。今回は settings.cue
の package 名とディレクトリ名が同じなため、package 名を省略しても正常に import できます。
最終的なコードは以下
コード全体
.
├── cue.mod
│ ├── module.cue
│ ├── pkg
│ │ └── github.com
│ │ └── ...
│ └── usr
├── my_dashboard_module.cue
└── timeseries
└── settings
└── settings.cue
package myDaC
import (
dashboardBuilder "github.com/perses/perses/cue/dac-utils/dashboard"
panelGroupsBuilder "github.com/perses/perses/cue/dac-utils/panelgroups"
panelBuilder "github.com/perses/perses/cue/dac-utils/prometheus/panel"
timeseriesChart "github.com/perses/perses/cue/schemas/panels/time-series:model"
promQuery "github.com/perses/perses/cue/schemas/queries/prometheus:model"
#timeseriesChartSettings "github.com/perses/timeseries/settings"
)
#nodeStoragePanel: panelBuilder & {
spec: {
display: name: "Storage utilization"
plugin: timeseriesChart & {
spec: #timeseriesChartSettings & {yAxis: format: unit: "percent"}
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "(sum(node_filesystem_size_bytes{fstype!='tmpfs', fstype!='overlay'}) by (instance) - sum(node_filesystem_free_bytes{fstype!='tmpfs', fstype!='overlay'}) by (instance) ) / sum(node_filesystem_size_bytes{fstype!='tmpfs', fstype!='overlay'}) by (instance) * 100"
seriesNameFormat: "{{instance}}"
}
}
},
]
}
}
#nodeCpuUtilizationPanel: panelBuilder & {
spec: {
display: name: "CPU utilization"
plugin: timeseriesChart & {
spec: #timeseriesChartSettings & {yAxis: format: unit: "percent-decimal"}
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "1 - (sum(rate(node_cpu_seconds_total{mode='idle'}[1m])) by (instance) / sum(rate(node_cpu_seconds_total[1m])) by (instance))"
seriesNameFormat: "{{instance}}"
}
}
},
]
}
}
#nodeMemoryPanel: panelBuilder & {
spec: {
display: name: "Memory utilization"
plugin: timeseriesChart & {
spec: #timeseriesChartSettings & {yAxis: format: unit: "bytes"}
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes"
seriesNameFormat: "{{instance}}"
}
}
},
]
}
}
dashboardBuilder & {
#name: "node-dashboard"
#project: "test-project"
#duration: "1h"
#refreshInterval: "30s"
#panelGroups: panelGroupsBuilder & {
#input: [
{
#title: "Node usage"
#cols: 2
#height: 10
#panels: [
#nodeCpuUtilizationPanel,
#nodeMemoryPanel,
#nodeStoragePanel,
]
},
]
}
}
ビルドはダッシュボードが定義されている my_dashboard_module.cue
のみ実行すれば ok
$ percli dac build -f my_dashboard_module.cue
Succesfully built my_dashboard_module.cue at built/my_dashboard_module_output.yaml
$ percli apply -f built/my_dashboard_module_output.yaml
object "Dashboard" "node-dashboard" has been applied in the project "test-project"
上記のようにリソースを定義するファイルを分割できるので、多くのダッシュボードを作成するような状況で共通の部分を適切に分割、インポートすることでより効率的に管理できるようになります。ただ perses ドキュメント等ではファイルを分割して管理する方法や方針が記載されてなさそうなのでこれが正当なやり方かどうかは不明。
パネルのレイアウトを変更する
panelGroupsBuilder を使ってパネルを作る場合、ダッシュボード上のパネルのレイアウトは col に設定した値に基づいてグリッド状に配置されます。例えば dashboard の panelGroupsBuilder で cols: 2
に設定して 3 つのパネルを指定すると 1 行に付き 2 つまでのパネルが配置されます。3 つ目のパネルは入り切らないため、2 行目に分割されます。
dashboardBuilder & {
#name: "node-dashboard"
#project: "test-project"
#duration: "1h"
#refreshInterval: "30s"
#panelGroups: panelGroupsBuilder & {
#input: [
{
#title: "Node usage"
#cols: 2
#height: 10
#panels: [
#nodeCpuUtilizationPanel,
#nodeMemoryPanel,
#nodeStoragePanel,
]
},
]
}
}
パネルの横幅はビルド時に動的に設定されますが上記の例ではいずれも width 12
に設定されています。一方で高さ height: 10
は panelGroupsBuilder に設定した値が継承されます。
layouts:
- kind: Grid
spec:
display:
title: Node usage
items:
- x: 0
"y": 0
width: 12
height: 10
content:
$ref: '#/spec/panels/0_0'
- x: 12
"y": 0
width: 12
height: 10
content:
$ref: '#/spec/panels/0_1'
- x: 0
"y": 10
width: 12
height: 10
content:
$ref: '#/spec/panels/0_2'
これからわかるように 1 行あたりの width の合計は 24 で、col に合わせて 24 / col
がパネルの width に設定されます。対応する処理は以下。
panel の width, height や x, y 座標は上記の panelGroupsBuilder の処理内で設定しているため各 panel をグリッド状に等間隔に配置する分には問題ないですが、パネルのサイズをパネル毎に設定したり配置を工夫したい場合は panelGroupsBuilder を使わずにこれらを設定する必要があります。
dashboardBuilder に対応する dashboard.cue を見ると、panelGroups の定義は以下のようになっています。
#panelGroups: [string]: {
layout: v1Dashboard.#Layout
panels: [string]: v1.#Panel
}
panelGroupsBuilder を使う場合は上記のプロパティが自動で設定されるので、逆に手動で設定すればパネルのレイアウトも手動で設定できそうだと推測されます。
v1Dashboard.#Layout は layout_patch.cue 内の #Layout
で定義されています。これを読んでいくと Layout のデータ構造は以下のようになっていることがわかります。
Layout: {
kind: "Grid"
spec: {
{
display: {
title: "panel group1 name"
collapse?: {
open: bool
}
}
items: [
{
x: int
y: int
height: int
width: int
content: {
$ref: "..."
},
....
]
}
}
}
panels の方の v1.#Panel の定義はおそらく https://github.com/perses/perses/blob/main/cue/model/api/v1/dashboard_go_gen.cue#L26 に該当。これは panelBuilder で作成されるデータがそのまま使用できます。
以上をまとめると、panelGroupsBuilder を使用しない場合には panelGroups
に以下のようなデータ構造を指定することでダッシュボードが作成できます。試しに 2 つのパネルを配置し、個々のパネルのサイズや座標を変更したダッシュボードを作ってみます。
#panelGroups: {
"group1": {
layout: #Layout
panels: {
"panelA": #panelBuilder
"panelB": #panelBuilder
...
}
}
"group2": {
layout: #Layout
panels: {
"panelA": #panelBuilder
"panelB": #panelBuilder
...
}
}
...
}
一方で Layout の中では自分でパネルの x, y やサイズを指定できるので、これにより個々のパネルの配置等を手動で調整できます。
コード全文
package myDaC
import (
dashboardBuilder "github.com/perses/perses/cue/dac-utils/dashboard"
panelBuilder "github.com/perses/perses/cue/dac-utils/prometheus/panel"
timeseriesChart "github.com/perses/perses/cue/schemas/panels/time-series:model"
promQuery "github.com/perses/perses/cue/schemas/queries/prometheus:model"
#timeseriesChartSettings "github.com/perses-sample/timeseries/settings"
)
#nodeCpuUtilizationPanel: panelBuilder & {
spec: {
display: name: "CPU utilization"
plugin: timeseriesChart & {
spec: #timeseriesChartSettings & {yAxis: format: unit: "percent-decimal"}
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "1 - (sum(rate(node_cpu_seconds_total{mode='idle'}[1m])) by (instance) / sum(rate(node_cpu_seconds_total[1m])) by (instance))"
seriesNameFormat: "{{instance}}"
}
}
},
]
}
}
#nodeMemoryPanel: panelBuilder & {
spec: {
display: name: "Memory utilization"
plugin: timeseriesChart & {
spec: #timeseriesChartSettings & {yAxis: format: unit: "bytes"}
}
queries: [
{
kind: "TimeSeriesQuery"
spec: plugin: promQuery & {
spec: {
query: "node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes"
seriesNameFormat: "{{instance}}"
}
}
},
]
}
}
#myLayout: {
kind: "Grid"
spec: {
{
display: {
title: "panel group1 name"
}
items: [
{
x: 0
y: 10
height: 10
width: 5
content: $ref: "#/spec/panels/cpuUtil"
},
{
x: 8
y: 10
height: 6
width: 12
content: $ref: "#/spec/panels/memoryUtil"
},
]
}
}
}
dashboardBuilder & {
#name: "custom-dashboard"
#project: "test-project"
#duration: "1h"
#refreshInterval: "30s"
#panelGroups: {
"group1": {
layout: #myLayout
panels: {
"cpuUtil": #nodeCpuUtilizationPanel
"memoryUtil": #nodeMemoryPanel
}
}
}
}
ビルドで生成される yaml を見ると Grid の座標が想定通りに設定されていることがわかります。また、0_0
など自動で設定されていた panel のインデックスも cpuUtil
など指定した値に設定されています。
kind: Dashboard
metadata:
name: custom-dashboard
createdAt: "0001-01-01T00:00:00Z"
updatedAt: "0001-01-01T00:00:00Z"
version: 0
project: test-project
spec:
panels:
cpuUtil:
kind: Panel
spec:
display:
name: CPU utilization
plugin:
kind: TimeSeriesChart
spec:
yAxis:
format:
unit: percent-decimal
queries:
- kind: TimeSeriesQuery
spec:
plugin:
kind: PrometheusTimeSeriesQuery
spec:
query: 1 - (sum(rate(node_cpu_seconds_total{mode='idle'}[1m])) by (instance) / sum(rate(node_cpu_seconds_total[1m])) by (instance))
seriesNameFormat: '{{instance}}'
memoryUtil:
kind: Panel
spec:
display:
name: Memory utilization
plugin:
kind: TimeSeriesChart
spec:
yAxis:
format:
unit: bytes
queries:
- kind: TimeSeriesQuery
spec:
plugin:
kind: PrometheusTimeSeriesQuery
spec:
query: node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes
seriesNameFormat: '{{instance}}'
layouts:
- kind: Grid
spec:
display:
title: panel group1 name
items:
- x: 0
"y": 10
height: 10
width: 5
content:
$ref: '#/spec/panels/cpuUtil'
- x: 8
"y": 10
height: 6
width: 12
content:
$ref: '#/spec/panels/memoryUtil'
duration: 1h
refreshInterval: 30s
デプロイすると想定通りパネル毎にサイズが変更されていたり、パネル間に空白が設定されています。
パネル毎の高さや幅、パネルの間隔を調整できる
panelGroupsBuilder を使う場合と比較すると記述はやや煩雑になりますが、上記の方法で個々のパネルの配置やサイズも制御することができます。
その他
CI/CD
現時点では percli dac build
でコードから yaml を生成 → percli apply
でデプロイという手順で DaC を実現するため、CI/CD に組み込むには CI/CD の処理の中で上記コマンドを実行する部分を追加することになります。ドキュメントでは CI/CD setup のセクションがありますがまだ TODO なので、将来的には CI/CD に統合するような処理や仕組みが追加されるかもしれません。
データの永続化
今回の検証ではデータは perses 内部に保存されているため、perses コンテナが削除されるとデータも失われます。データを永続化させるには perses の config で database に接続先のデータベースの情報を記載します。
security:
readonly: false
enable_auth: true
cookie:
same_site: lax
secure: false
authentication:
access_token_ttl: 24h
refresh_token_ttl: 24h
providers:
enable_native: true
authorization:
guest_permissions:
- actions: ["*"]
scopes: ["*"]
database:
sql:
user: perses
password: perses
db_name: perses
net: tcp
addr: mariadb:3306
schemas:
datasources_path: /etc/perses/cue/schemas/datasources
interval: 5m
panels_path: /etc/perses/cue/schemas/panels
queries_path: /etc/perses/cue/schemas/queries
variables_path: /etc/perses/cue/schemas/variables
docker compose では接続先の mysql コンテナを追加。
services:
perses:
container_name: perses
image: persesdev/perses:v0.48.0
ports:
- 8080:8080
command:
- --config=/etc/perses/config/config.yml
- --web.listen-address=:8080
- --web.hide-port=false
- --web.telemetry-path=/metrics
- --log.level=info
- --log.method-trace=true
volumes:
- type: bind
source: ./config-db.yml
target: /etc/perses/config/config.yml
- ./secret:/etc/perses/config
depends_on:
mysql:
condition: service_healthy
mysql:
container_name: mysql
image: mysql
ports:
- 3306:3306
volumes:
- ./data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: admin
MYSQL_DATABASE: perses
MYSQL_USER: perses
MYSQL_PASSWORD: perses
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-padmin"]
interval: 10s
timeout: 2s
retries:
start_period: 15s
これでデータが mysql に保存されて永続化されるようになります。
サポートされているデータベースの種類は明記されていないようですが、上記の通り mysql は使用できます(postgres を指定した場合はエラーになった)。
おわりに
perses を使って prometheus メトリクス用のダッシュボードを DaC で作成する方法をいくつか試しました。リソースの概念やダッシュボードで設定可能な項目は Grafana を踏襲しているため、Grafana を使い慣れているユーザーはそれほど苦戦せずに使い始めることができます。
DaC のためのコードは cue か go を使って書く必要がありますが、cue はけっこう表記の癖があるのではじめて触る場合にはやや学習コストが高いのが難点です。go に慣れていれば go SDK でも記述できるので問題ないかもしれませんが。
また、perses は sandbox プロジェクトで発展段階ということもありドキュメントの記述や情報がやや不足気味に感じました。記述が不明瞭な部分は実際にコードを書いて試したり github 上で該当する情報がないか検索する必要があります。とはいえ PromCon でも何度か取り上げられているプロジェクトであり、以前に紹介した Thanos と合わせて Prometheus ecosystem の一部として注目を集めています。Prometheus 3.0 も最近リリースされたばかりなので、Prometheus-native なダッシュボードの DaC システムとして今後の発展が期待されます。
Discussion