├── .editorconfig ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── examples ├── basic-with-image-renderer.jsonnet ├── basic-with-mixin.jsonnet ├── basic.jsonnet ├── custom-ini.jsonnet ├── dashboard-definition.jsonnet ├── dashboard-folder-definition.jsonnet └── plugins.jsonnet └── grafana ├── grafana.libsonnet └── jsonnetfile.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.{json,jsonnet}] 10 | indent_style = spaces 11 | indent_size = 4 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ci 3 | on: 4 | - push 5 | - pull_request 6 | env: 7 | golang-version: '1.20' 8 | jobs: 9 | generate: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: 14 | - macos-latest 15 | - ubuntu-latest 16 | name: Generate 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: actions/setup-go@v4 20 | with: 21 | go-version: ${{ env.golang-version }} 22 | - run: go install github.com/google/go-jsonnet/cmd/jsonnetfmt@latest 23 | - run: make generate 24 | build: 25 | runs-on: ubuntu-latest 26 | name: Build 27 | steps: 28 | - uses: actions/checkout@v3 29 | - uses: actions/setup-go@v4 30 | with: 31 | go-version: ${{ env.golang-version }} 32 | - run: go install github.com/google/go-jsonnet/cmd/jsonnet@latest 33 | - run: make build 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | artifacts/ 2 | vendor/ 3 | jsonnetfile.lock.json 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Frederic Branczyk 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=/bin/bash -o pipefail 2 | 3 | all: build generate 4 | 5 | fmt: 6 | @echo -e "\033[1m>> Formatting all jsonnet files\033[0m" 7 | find . -name 'vendor' -prune -o -name '*.libsonnet' -print -o -name '*.jsonnet' -print | xargs -n 1 -- jsonnetfmt -i 8 | 9 | generate: fmt docs 10 | git diff --exit-code 11 | 12 | docs: embedmd 13 | @echo -e "\033[1m>> Generating docs\033[0m" 14 | embedmd -w README.md 15 | 16 | embedmd: 17 | @echo -e "\033[1m>> Ensuring embedmd is installed\033[0m" 18 | go install github.com/campoy/embedmd@latest 19 | 20 | build: jb 21 | cd grafana && jb install 22 | $(MAKE) compile 23 | 24 | compile: 25 | jsonnet -J grafana/vendor -J . examples/basic.jsonnet 26 | 27 | jb: 28 | @echo -e "\033[1m>> Ensuring jb (jsonnet-bundler) is installed\033[0m" 29 | go install github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb@latest 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Grafana 2 | 3 | This project is about running [Grafana](https://grafana.com/) on [Kubernetes](https://kubernetes.io/) with [Prometheus](https://prometheus.io/) as the datasource in a very opinionated and entirely declarative way. This allows easily operating Grafana highly available as if it was a stateless application - no need to run a clustered database for your dashboarding solution anymore! 4 | 5 | Note that at this point this is primarily about getting into the same state as [kube-prometheus](https://github.com/coreos/prometheus-operator/tree/master/contrib/kube-prometheus) currently is. It is about packaging up Grafana as a reusable component, without dashboards. Dashboards are to be defined when using this Grafana package. 6 | 7 | ## What and why is happening here? 8 | 9 | This repository exists because the Grafana stack in [kube-prometheus](https://github.com/coreos/prometheus-operator/tree/master/contrib/kube-prometheus) has gotten close to unmaintainable due to the many steps of generation and it's a very steep learning curve for newcomers. 10 | 11 | Since Grafana v5, Grafana can be provisioned with dashboards from files. This project is primarily about generating a set of useful Grafana dashboards for use with and on Kubernetes using with Prometheus as the datasource. 12 | 13 | In this repository everything is generated via jsonnet: 14 | 15 | * The [Grafana dashboard sources configuration](https://github.com/brancz/kubernetes-grafana/blob/master/grafana/configs/dashboard-sources/dashboards.libsonnet). 16 | * The Grafana dashboard datasource configuration, is part of the [configuration](https://github.com/brancz/kubernetes-grafana/blob/master/grafana/grafana.libsonnet#L17-L25), and is then simply [rendered to json](https://github.com/brancz/kubernetes-grafana/blob/master/grafana/grafana.libsonnet#L47). 17 | * The Grafana dashboard definitions are defined as part of the [configuration](https://github.com/brancz/kubernetes-grafana/blob/master/grafana/grafana.libsonnet#L29). For example, dashboard definitions can be developed with the help of [grafana/grafonnet-lib](https://github.com/grafana/grafonnet-lib). 18 | * The [Grafana Kubernetes manifests](https://github.com/brancz/kubernetes-grafana/tree/master/grafana) with the help of [ksonnet/ksonnet-lib](https://github.com/ksonnet/ksonnet-lib). 19 | 20 | With a single jsonnet command the whole stack is generated and can be applied against a Kubernetes cluster. 21 | 22 | ## Prerequisites 23 | 24 | You need a running Kubernetes cluster in order to try this out, with the kube-prometheus stack deployed on it as have Docker installed to and be able to mount volumes correctly (this is **not** the case when using the Docker host of minikube). 25 | 26 | For trying this out provision [minikube](https://github.com/kubernetes/minikube) with these settings: 27 | 28 | ``` 29 | minikube start --kubernetes-version=v1.9.3 --memory=4096 --bootstrapper=kubeadm --extra-config=kubelet.authentication-token-webhook=true --extra-config=kubelet.authorization-mode=Webhook --extra-config=scheduler.address=0.0.0.0 --extra-config=controller-manager.address=0.0.0.0 30 | ``` 31 | 32 | ## Usage 33 | 34 | Use this package in your own infrastructure using [`jsonnet-bundler`](https://github.com/jsonnet-bundler/jsonnet-bundler): 35 | 36 | ``` 37 | jb install github.com/brancz/kubernetes-grafana/grafana 38 | ``` 39 | 40 | An example of how to use it could be: 41 | 42 | [embedmd]:# (examples/basic.jsonnet) 43 | ```jsonnet 44 | local grafana = import 'grafana/grafana.libsonnet'; 45 | 46 | { 47 | _config:: { 48 | namespace: 'monitoring-grafana', 49 | }, 50 | 51 | grafana: grafana($._config) + { 52 | service+: { 53 | spec+: { 54 | ports: [ 55 | port { 56 | nodePort: 30910, 57 | } 58 | for port in super.ports 59 | ], 60 | }, 61 | }, 62 | }, 63 | } 64 | ``` 65 | 66 | This builds the entire Grafana stack with your own dashboards and a configurable namespace. 67 | 68 | Simply run: 69 | 70 | ``` 71 | $ jsonnet -J vendor example.jsonnet 72 | ``` 73 | 74 | ### Customizing 75 | 76 | #### Adding dashboards 77 | 78 | This setup is optimized to work best when Grafana is used declaratively, so when adding dashboards they are added declaratively as well. In jsonnet there are libraries available to avoid having to repeat boilerplate of Grafana dashboard json. An example with the [grafana/grafonnet-lib](https://github.com/grafana/grafonnet-lib): 79 | 80 | [embedmd]:# (examples/dashboard-definition.jsonnet) 81 | ```jsonnet 82 | local grafonnet = import 'github.com/grafana/grafonnet-lib/grafonnet/grafana.libsonnet'; 83 | local dashboard = grafonnet.dashboard; 84 | local row = grafonnet.row; 85 | local prometheus = grafonnet.prometheus; 86 | local template = grafonnet.template; 87 | local graphPanel = grafonnet.graphPanel; 88 | 89 | local grafana = import 'grafana/grafana.libsonnet'; 90 | 91 | { 92 | _config:: { 93 | namespace: 'monitoring-grafana', 94 | dashboards+: { 95 | 'my-dashboard.json': 96 | dashboard.new('My Dashboard') 97 | .addTemplate( 98 | { 99 | current: { 100 | text: 'Prometheus', 101 | value: 'Prometheus', 102 | }, 103 | hide: 0, 104 | label: null, 105 | name: 'datasource', 106 | options: [], 107 | query: 'prometheus', 108 | refresh: 1, 109 | regex: '', 110 | type: 'datasource', 111 | }, 112 | ) 113 | .addRow( 114 | row.new() 115 | .addPanel( 116 | graphPanel.new('My Panel', span=6, datasource='$datasource') 117 | .addTarget(prometheus.target('vector(1)')), 118 | ) 119 | ), 120 | }, 121 | }, 122 | 123 | grafana: grafana($._config) + { 124 | service+: { 125 | spec+: { 126 | ports: [ 127 | port { 128 | nodePort: 30910, 129 | } 130 | for port in super.ports 131 | ], 132 | }, 133 | }, 134 | }, 135 | } 136 | ``` 137 | 138 | #### Organizing dashboards 139 | 140 | If you have many dashboards and would like to organize them into folders, you can do that as well by specifying them in `folderDashboards` rather than `dashboards`. 141 | 142 | [embedmd]:# (examples/dashboard-folder-definition.jsonnet) 143 | ```jsonnet 144 | local grafonnet = import 'github.com/grafana/grafonnet-lib/grafonnet/grafana.libsonnet'; 145 | local dashboard = grafonnet.dashboard; 146 | local row = grafonnet.row; 147 | local prometheus = grafonnet.prometheus; 148 | local template = grafonnet.template; 149 | local graphPanel = grafonnet.graphPanel; 150 | 151 | local grafana = import 'grafana/grafana.libsonnet'; 152 | 153 | { 154 | _config:: { 155 | namespace: 'monitoring-grafana', 156 | folderDashboards+: { 157 | Services: { 158 | 'regional-services-dashboard.json': (import 'dashboards/regional-services-dashboard.json'), 159 | 'global-services-dashboard.json': (import 'dashboards/global-services-dashboard.json'), 160 | }, 161 | AWS: { 162 | 'aws-ec2-dashboard.json': (import 'dashboards/aws-ec2-dashboard.json'), 163 | 'aws-rds-dashboard.json': (import 'dashboards/aws-rds-dashboard.json'), 164 | 'aws-sqs-dashboard.json': (import 'dashboards/aws-sqs-dashboard.json'), 165 | }, 166 | ISTIO: { 167 | 'istio-citadel-dashboard.json': (import 'dashboards/istio-citadel-dashboard.json'), 168 | 'istio-galley-dashboard.json': (import 'dashboards/istio-galley-dashboard.json'), 169 | 'istio-mesh-dashboard.json': (import 'dashboards/istio-mesh-dashboard.json'), 170 | 'istio-pilot-dashboard.json': (import 'dashboards/istio-pilot-dashboard.json'), 171 | }, 172 | }, 173 | }, 174 | 175 | grafana: grafana($._config) + { 176 | service+: { 177 | spec+: { 178 | ports: [ 179 | port { 180 | nodePort: 30910, 181 | } 182 | for port in super.ports 183 | ], 184 | }, 185 | }, 186 | }, 187 | } 188 | ``` 189 | 190 | #### Dashboards mixins 191 | 192 | Using the [kubernetes-mixin](https://github.com/kubernetes-monitoring/kubernetes-mixin)s, simply install: 193 | 194 | ``` 195 | $ jb install github.com/kubernetes-monitoring/kubernetes-mixin 196 | ``` 197 | 198 | And apply the mixin: 199 | 200 | [embedmd]:# (examples/basic-with-mixin.jsonnet) 201 | ```jsonnet 202 | local kubernetesMixin = import 'github.com/kubernetes-monitoring/kubernetes-mixin/mixin.libsonnet'; 203 | local grafana = import 'grafana/grafana.libsonnet'; 204 | 205 | { 206 | _config:: { 207 | namespace: 'monitoring-grafana', 208 | dashboards: kubernetesMixin.grafanaDashboards, 209 | }, 210 | 211 | grafana: grafana($._config) + { 212 | service+: { 213 | spec+: { 214 | ports: [ 215 | port { 216 | nodePort: 30910, 217 | } 218 | for port in super.ports 219 | ], 220 | }, 221 | }, 222 | }, 223 | } 224 | ``` 225 | 226 | To generate, again simply run: 227 | 228 | ``` 229 | $ jsonnet -J vendor example-with-mixin.jsonnet 230 | ``` 231 | 232 | This yields a fully configured Grafana stack with useful Kubernetes dashboards. 233 | 234 | #### Config customization 235 | 236 | Grafana can be run with many different configurations. Different organizations have different preferences, therefore the Grafana configuration can be arbitrary modified. The configuration happens via the the `$._config.grafana.config` variable. The `$._config.grafana.config` field is compiled using jsonnet's `std.manifestIni` function. Additionally you can specify your organizations' LDAP configuration through `$._config.grafana.ldap` variable. 237 | 238 | For example to modify Grafana configuration and set up LDAP use: 239 | 240 | [embedmd]:# (examples/custom-ini.jsonnet) 241 | ```jsonnet 242 | local grafana = import 'grafana/grafana.libsonnet'; 243 | 244 | { 245 | local customIni = 246 | grafana({ 247 | _config+:: { 248 | namespace: 'monitoring-grafana', 249 | grafana+:: { 250 | config: { 251 | sections: { 252 | metrics: { enabled: true }, 253 | 'auth.ldap': { 254 | enabled: true, 255 | config_file: '/etc/grafana/ldap.toml', 256 | allow_sign_up: true, 257 | }, 258 | }, 259 | }, 260 | ldap: ||| 261 | [[servers]] 262 | host = "127.0.0.1" 263 | port = 389 264 | use_ssl = false 265 | start_tls = false 266 | ssl_skip_verify = false 267 | 268 | bind_dn = "cn=admin,dc=grafana,dc=org" 269 | bind_password = 'grafana' 270 | 271 | search_filter = "(cn=%s)" 272 | 273 | search_base_dns = ["dc=grafana,dc=org"] 274 | |||, 275 | }, 276 | }, 277 | }), 278 | 279 | apiVersion: 'v1', 280 | kind: 'List', 281 | items: 282 | customIni.dashboardDefinitions.items + 283 | [ 284 | customIni.config, 285 | customIni.dashboardSources, 286 | customIni.dashboardDatasources, 287 | customIni.deployment, 288 | customIni.serviceAccount, 289 | customIni.service { 290 | spec+: { ports: [ 291 | port { 292 | nodePort: 30910, 293 | } 294 | for port in super.ports 295 | ] }, 296 | }, 297 | ], 298 | } 299 | ``` 300 | 301 | #### Plugins 302 | 303 | The config object allows specifying an array of plugins to install at startup. 304 | 305 | [embedmd]:# (examples/plugins.jsonnet) 306 | ```jsonnet 307 | local grafana = import 'grafana/grafana.libsonnet'; 308 | 309 | { 310 | _config:: { 311 | namespace: 'monitoring-grafana', 312 | plugins: ['camptocamp-prometheus-alertmanager-datasource'], 313 | }, 314 | 315 | grafana: grafana($._config) + { 316 | service+: { 317 | spec+: { 318 | ports: [ 319 | port { 320 | nodePort: 30910, 321 | } 322 | for port in super.ports 323 | ], 324 | }, 325 | }, 326 | }, 327 | } 328 | ``` 329 | 330 | # Roadmap 331 | 332 | There are a number of things missing for the Grafana stack and tooling to be fully migrated. 333 | 334 | **If you are interested in working on any of these, please open a respective issue to avoid duplicating efforts.** 335 | 336 | 1. A tool to review Grafana dashboard changes on PRs. While reviewing jsonnet code is a lot easier than the large Grafana json sources, it's hard to imagine what that will actually end up looking like once rendered. Ideally a production-like environment is spun up and produces metrics to be graphed, then a tool could take a screenshot and [Grafana snapshot](http://docs.grafana.org/plugins/developing/snapshot-mode/) of the rendered Grafana dashboards. That way the changes can not only be reviewed in code but also visually. Similar to point 2 this should eventually be it's own project. 337 | -------------------------------------------------------------------------------- /examples/basic-with-image-renderer.jsonnet: -------------------------------------------------------------------------------- 1 | local grafana = import 'grafana/grafana.libsonnet'; 2 | 3 | local imageRenderer = { 4 | name: 'grafana-image-renderer', 5 | image: 'grafana/grafana-image-renderer:1.0.9', 6 | ports: [{ name: 'http', containerPort: 8081 }], 7 | resources: { 8 | requests: { cpu: '100m', memory: '100Mi' }, 9 | limits: { cpu: '200m', memory: '200Mi' }, 10 | }, 11 | }; 12 | 13 | { 14 | _config:: { 15 | namespace: 'monitoring-grafana', 16 | env: [ 17 | { name: 'GF_RENDERING_SERVER_URL', value: 'http://localhost:' + imageRenderer.ports[0].containerPort + '/render' }, 18 | { name: 'GF_RENDERING_CALLBACK_URL', value: 'http://localhost:' + $.grafana._config.port }, 19 | ], 20 | }, 21 | 22 | grafana: grafana($._config) + { 23 | service+: { 24 | spec+: { 25 | ports: [ 26 | port { 27 | nodePort: 30910, 28 | } 29 | for port in super.ports 30 | ], 31 | }, 32 | }, 33 | }, 34 | } 35 | -------------------------------------------------------------------------------- /examples/basic-with-mixin.jsonnet: -------------------------------------------------------------------------------- 1 | local kubernetesMixin = import 'github.com/kubernetes-monitoring/kubernetes-mixin/mixin.libsonnet'; 2 | local grafana = import 'grafana/grafana.libsonnet'; 3 | 4 | { 5 | _config:: { 6 | namespace: 'monitoring-grafana', 7 | dashboards: kubernetesMixin.grafanaDashboards, 8 | }, 9 | 10 | grafana: grafana($._config) + { 11 | service+: { 12 | spec+: { 13 | ports: [ 14 | port { 15 | nodePort: 30910, 16 | } 17 | for port in super.ports 18 | ], 19 | }, 20 | }, 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /examples/basic.jsonnet: -------------------------------------------------------------------------------- 1 | local grafana = import 'grafana/grafana.libsonnet'; 2 | 3 | { 4 | _config:: { 5 | namespace: 'monitoring-grafana', 6 | }, 7 | 8 | grafana: grafana($._config) + { 9 | service+: { 10 | spec+: { 11 | ports: [ 12 | port { 13 | nodePort: 30910, 14 | } 15 | for port in super.ports 16 | ], 17 | }, 18 | }, 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /examples/custom-ini.jsonnet: -------------------------------------------------------------------------------- 1 | local grafana = import 'grafana/grafana.libsonnet'; 2 | 3 | { 4 | local customIni = 5 | grafana({ 6 | _config+:: { 7 | namespace: 'monitoring-grafana', 8 | grafana+:: { 9 | config: { 10 | sections: { 11 | metrics: { enabled: true }, 12 | 'auth.ldap': { 13 | enabled: true, 14 | config_file: '/etc/grafana/ldap.toml', 15 | allow_sign_up: true, 16 | }, 17 | }, 18 | }, 19 | ldap: ||| 20 | [[servers]] 21 | host = "127.0.0.1" 22 | port = 389 23 | use_ssl = false 24 | start_tls = false 25 | ssl_skip_verify = false 26 | 27 | bind_dn = "cn=admin,dc=grafana,dc=org" 28 | bind_password = 'grafana' 29 | 30 | search_filter = "(cn=%s)" 31 | 32 | search_base_dns = ["dc=grafana,dc=org"] 33 | |||, 34 | }, 35 | }, 36 | }), 37 | 38 | apiVersion: 'v1', 39 | kind: 'List', 40 | items: 41 | customIni.dashboardDefinitions.items + 42 | [ 43 | customIni.config, 44 | customIni.dashboardSources, 45 | customIni.dashboardDatasources, 46 | customIni.deployment, 47 | customIni.serviceAccount, 48 | customIni.service { 49 | spec+: { ports: [ 50 | port { 51 | nodePort: 30910, 52 | } 53 | for port in super.ports 54 | ] }, 55 | }, 56 | ], 57 | } 58 | -------------------------------------------------------------------------------- /examples/dashboard-definition.jsonnet: -------------------------------------------------------------------------------- 1 | local grafonnet = import 'github.com/grafana/grafonnet-lib/grafonnet/grafana.libsonnet'; 2 | local dashboard = grafonnet.dashboard; 3 | local row = grafonnet.row; 4 | local prometheus = grafonnet.prometheus; 5 | local template = grafonnet.template; 6 | local graphPanel = grafonnet.graphPanel; 7 | 8 | local grafana = import 'grafana/grafana.libsonnet'; 9 | 10 | { 11 | _config:: { 12 | namespace: 'monitoring-grafana', 13 | dashboards+: { 14 | 'my-dashboard.json': 15 | dashboard.new('My Dashboard') 16 | .addTemplate( 17 | { 18 | current: { 19 | text: 'Prometheus', 20 | value: 'Prometheus', 21 | }, 22 | hide: 0, 23 | label: null, 24 | name: 'datasource', 25 | options: [], 26 | query: 'prometheus', 27 | refresh: 1, 28 | regex: '', 29 | type: 'datasource', 30 | }, 31 | ) 32 | .addRow( 33 | row.new() 34 | .addPanel( 35 | graphPanel.new('My Panel', span=6, datasource='$datasource') 36 | .addTarget(prometheus.target('vector(1)')), 37 | ) 38 | ), 39 | }, 40 | }, 41 | 42 | grafana: grafana($._config) + { 43 | service+: { 44 | spec+: { 45 | ports: [ 46 | port { 47 | nodePort: 30910, 48 | } 49 | for port in super.ports 50 | ], 51 | }, 52 | }, 53 | }, 54 | } 55 | -------------------------------------------------------------------------------- /examples/dashboard-folder-definition.jsonnet: -------------------------------------------------------------------------------- 1 | local grafonnet = import 'github.com/grafana/grafonnet-lib/grafonnet/grafana.libsonnet'; 2 | local dashboard = grafonnet.dashboard; 3 | local row = grafonnet.row; 4 | local prometheus = grafonnet.prometheus; 5 | local template = grafonnet.template; 6 | local graphPanel = grafonnet.graphPanel; 7 | 8 | local grafana = import 'grafana/grafana.libsonnet'; 9 | 10 | { 11 | _config:: { 12 | namespace: 'monitoring-grafana', 13 | folderDashboards+: { 14 | Services: { 15 | 'regional-services-dashboard.json': (import 'dashboards/regional-services-dashboard.json'), 16 | 'global-services-dashboard.json': (import 'dashboards/global-services-dashboard.json'), 17 | }, 18 | AWS: { 19 | 'aws-ec2-dashboard.json': (import 'dashboards/aws-ec2-dashboard.json'), 20 | 'aws-rds-dashboard.json': (import 'dashboards/aws-rds-dashboard.json'), 21 | 'aws-sqs-dashboard.json': (import 'dashboards/aws-sqs-dashboard.json'), 22 | }, 23 | ISTIO: { 24 | 'istio-citadel-dashboard.json': (import 'dashboards/istio-citadel-dashboard.json'), 25 | 'istio-galley-dashboard.json': (import 'dashboards/istio-galley-dashboard.json'), 26 | 'istio-mesh-dashboard.json': (import 'dashboards/istio-mesh-dashboard.json'), 27 | 'istio-pilot-dashboard.json': (import 'dashboards/istio-pilot-dashboard.json'), 28 | }, 29 | }, 30 | }, 31 | 32 | grafana: grafana($._config) + { 33 | service+: { 34 | spec+: { 35 | ports: [ 36 | port { 37 | nodePort: 30910, 38 | } 39 | for port in super.ports 40 | ], 41 | }, 42 | }, 43 | }, 44 | } 45 | -------------------------------------------------------------------------------- /examples/plugins.jsonnet: -------------------------------------------------------------------------------- 1 | local grafana = import 'grafana/grafana.libsonnet'; 2 | 3 | { 4 | _config:: { 5 | namespace: 'monitoring-grafana', 6 | plugins: ['camptocamp-prometheus-alertmanager-datasource'], 7 | }, 8 | 9 | grafana: grafana($._config) + { 10 | service+: { 11 | spec+: { 12 | ports: [ 13 | port { 14 | nodePort: 30910, 15 | } 16 | for port in super.ports 17 | ], 18 | }, 19 | }, 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /grafana/grafana.libsonnet: -------------------------------------------------------------------------------- 1 | local defaults = { 2 | local defaults = self, 3 | namespace: 'default', 4 | version: '7.5.10', 5 | image: 'docker.io/grafana/grafana:' + defaults.version, 6 | commonLabels:: { 7 | 'app.kubernetes.io/name': 'grafana', 8 | 'app.kubernetes.io/version': defaults.version, 9 | 'app.kubernetes.io/component': 'grafana', 10 | }, 11 | selectorLabels:: { 12 | [labelName]: defaults.commonLabels[labelName] 13 | for labelName in std.objectFields(defaults.commonLabels) 14 | if !std.setMember(labelName, ['app.kubernetes.io/version']) 15 | }, 16 | replicas: 1, 17 | port: 3000, 18 | resources: { 19 | requests: { cpu: '100m', memory: '100Mi' }, 20 | limits: { cpu: '200m', memory: '200Mi' }, 21 | }, 22 | 23 | dashboards: {}, 24 | rawDashboards: {}, 25 | folderDashboards: {}, 26 | folderUidGenerator(folder): '', 27 | datasources: [{ 28 | name: 'prometheus', 29 | type: 'prometheus', 30 | access: 'proxy', 31 | orgId: 1, 32 | url: 'http://prometheus-k8s.' + defaults.namespace + '.svc:9090', 33 | version: 1, 34 | editable: false, 35 | }], 36 | // Forces pod restarts when dashboards are changed 37 | dashboardsChecksum: false, 38 | config: { 39 | sections: { 40 | date_formats: { default_timezone: 'UTC' }, 41 | }, 42 | }, 43 | ldap: null, 44 | plugins: [], 45 | env: [], 46 | containers: [], 47 | }; 48 | 49 | function(params) { 50 | local g = self, 51 | _config:: defaults + params, 52 | _metadata:: { 53 | name: 'grafana', 54 | namespace: g._config.namespace, 55 | labels: g._config.commonLabels, 56 | }, 57 | 58 | serviceAccount: { 59 | apiVersion: 'v1', 60 | kind: 'ServiceAccount', 61 | metadata: g._metadata, 62 | automountServiceAccountToken: false, 63 | }, 64 | 65 | service: { 66 | apiVersion: 'v1', 67 | kind: 'Service', 68 | metadata: g._metadata, 69 | spec: { 70 | selector: g.deployment.spec.selector.matchLabels, 71 | ports: [ 72 | { name: 'http', targetPort: 'http', port: 3000 }, 73 | ], 74 | }, 75 | }, 76 | 77 | config: { 78 | apiVersion: 'v1', 79 | kind: 'Secret', 80 | metadata: g._metadata { 81 | name: 'grafana-config', 82 | }, 83 | type: 'Opaque', 84 | stringData: { 85 | 'grafana.ini': std.manifestIni(g._config.config), 86 | } + if g._config.ldap != null then { 'ldap.toml': g._config.ldap } else {}, 87 | }, 88 | 89 | dashboardDefinitions: { 90 | apiVersion: 'v1', 91 | kind: 'ConfigMapList', 92 | items: [ 93 | { 94 | local dashboardName = 'grafana-dashboard-' + std.strReplace(name, '.json', ''), 95 | apiVersion: 'v1', 96 | kind: 'ConfigMap', 97 | metadata: g._metadata { 98 | name: dashboardName, 99 | }, 100 | data: { [name]: std.manifestJsonEx(g._config.dashboards[name], ' ') }, 101 | } 102 | for name in std.objectFields(g._config.dashboards) 103 | ] + [ 104 | { 105 | local dashboardName = 'grafana-dashboard-' + std.strReplace(name, '.json', ''), 106 | apiVersion: 'v1', 107 | kind: 'ConfigMap', 108 | metadata: g._metadata { 109 | name: dashboardName, 110 | }, 111 | data: { [name]: std.manifestJsonEx(g._config.folderDashboards[folder][name], ' ') }, 112 | } 113 | for folder in std.objectFields(g._config.folderDashboards) 114 | for name in std.objectFields(g._config.folderDashboards[folder]) 115 | ] + ( 116 | if std.length(g._config.rawDashboards) > 0 then 117 | [ 118 | 119 | { 120 | local dashboardName = 'grafana-dashboard-' + std.strReplace(name, '.json', ''), 121 | apiVersion: 'v1', 122 | kind: 'ConfigMap', 123 | metadata: g._metadata { 124 | name: dashboardName, 125 | }, 126 | data: { [name]: g._config.rawDashboards[name] }, 127 | } 128 | for name in std.objectFields(g._config.rawDashboards) 129 | ] 130 | else 131 | [] 132 | ), 133 | }, 134 | 135 | dashboardSources: 136 | local dashboardSources = { 137 | apiVersion: 1, 138 | providers: 139 | ( 140 | if std.length(g._config.dashboards) + 141 | std.length(g._config.rawDashboards) > 0 then [ 142 | { 143 | name: '0', 144 | orgId: 1, 145 | folder: 'Default', 146 | folderUid: g._config.folderUidGenerator('Default'), 147 | type: 'file', 148 | options: { 149 | path: '/grafana-dashboard-definitions/0', 150 | }, 151 | }, 152 | ] else [] 153 | ) + 154 | [ 155 | { 156 | name: folder, 157 | orgId: 1, 158 | folder: folder, 159 | folderUid: g._config.folderUidGenerator(folder), 160 | type: 'file', 161 | options: { 162 | path: '/grafana-dashboard-definitions/' + folder, 163 | }, 164 | } 165 | for folder in std.objectFields(g._config.folderDashboards) 166 | ], 167 | }; 168 | 169 | { 170 | kind: 'ConfigMap', 171 | apiVersion: 'v1', 172 | metadata: g._metadata { 173 | name: 'grafana-dashboards', 174 | }, 175 | data: { 'dashboards.yaml': std.manifestJsonEx(dashboardSources, ' ') }, 176 | }, 177 | 178 | dashboardDatasources: { 179 | apiVersion: 'v1', 180 | kind: 'Secret', 181 | metadata: g._metadata { 182 | name: 'grafana-datasources', 183 | }, 184 | type: 'Opaque', 185 | stringData: { 186 | 'datasources.yaml': std.manifestJsonEx( 187 | { 188 | apiVersion: 1, 189 | datasources: g._config.datasources, 190 | }, ' ' 191 | ), 192 | }, 193 | }, 194 | 195 | deployment: 196 | local configVolume = { 197 | name: 'grafana-config', 198 | secret: { secretName: g.config.metadata.name }, 199 | }; 200 | local configVolumeMount = { 201 | name: configVolume.name, 202 | mountPath: '/etc/grafana', 203 | readOnly: false, 204 | }; 205 | 206 | local storageVolume = { 207 | name: 'grafana-storage', 208 | emptyDir: {}, 209 | }; 210 | local storageVolumeMount = { 211 | name: storageVolume.name, 212 | mountPath: '/var/lib/grafana', 213 | readOnly: false, 214 | }; 215 | 216 | local datasourcesVolume = { 217 | name: 'grafana-datasources', 218 | secret: { secretName: g.dashboardDatasources.metadata.name }, 219 | }; 220 | local datasourcesVolumeMount = { 221 | name: datasourcesVolume.name, 222 | mountPath: '/etc/grafana/provisioning/datasources', 223 | readOnly: false, 224 | }; 225 | 226 | local dashboardsVolume = { 227 | name: 'grafana-dashboards', 228 | configMap: { name: g.dashboardSources.metadata.name }, 229 | }; 230 | local dashboardsVolumeMount = { 231 | name: dashboardsVolume.name, 232 | mountPath: '/etc/grafana/provisioning/dashboards', 233 | readOnly: false, 234 | }; 235 | // A volume on /tmp is needed to let us use 'readOnlyRootFilesystem: true' 236 | local pluginTmpVolume = { 237 | name: 'tmp-plugins', 238 | emptyDir: { 239 | medium: 'Memory', 240 | }, 241 | }; 242 | local pluginTmpVolumeMount = { 243 | mountPath: '/tmp', 244 | name: 'tmp-plugins', 245 | readOnly: false, 246 | }; 247 | 248 | local volumeMounts = 249 | [ 250 | storageVolumeMount, 251 | datasourcesVolumeMount, 252 | dashboardsVolumeMount, 253 | pluginTmpVolumeMount, 254 | ] + 255 | [ 256 | { 257 | local dashboardName = std.strReplace(name, '.json', ''), 258 | name: 'grafana-dashboard-' + dashboardName, 259 | mountPath: '/grafana-dashboard-definitions/0/' + dashboardName, 260 | readOnly: false, 261 | } 262 | for name in std.objectFields(g._config.dashboards + g._config.rawDashboards) 263 | ] + 264 | [ 265 | { 266 | local dashboardName = std.strReplace(name, '.json', ''), 267 | name: 'grafana-dashboard-' + dashboardName, 268 | mountPath: '/grafana-dashboard-definitions/' + folder + '/' + dashboardName, 269 | readOnly: false, 270 | } 271 | for folder in std.objectFields(g._config.folderDashboards) 272 | for name in std.objectFields(g._config.folderDashboards[folder]) 273 | ] + ( 274 | if std.length(g._config.config) > 0 then [configVolumeMount] else [] 275 | ); 276 | 277 | local volumes = 278 | [ 279 | storageVolume, 280 | datasourcesVolume, 281 | dashboardsVolume, 282 | pluginTmpVolume, 283 | ] + 284 | [ 285 | { 286 | local dashboardName = 'grafana-dashboard-' + std.strReplace(name, '.json', ''), 287 | name: dashboardName, 288 | configMap: { name: dashboardName }, 289 | } 290 | for name in std.objectFields(g._config.dashboards) 291 | ] + 292 | [ 293 | { 294 | local dashboardName = 'grafana-dashboard-' + std.strReplace(name, '.json', ''), 295 | name: dashboardName, 296 | configMap: { name: dashboardName }, 297 | } 298 | for folder in std.objectFields(g._config.folderDashboards) 299 | for name in std.objectFields(g._config.folderDashboards[folder]) 300 | ] + 301 | [ 302 | { 303 | local dashboardName = 'grafana-dashboard-' + std.strReplace(name, '.json', ''), 304 | name: dashboardName, 305 | configMap: { name: dashboardName }, 306 | } 307 | for name in std.objectFields(g._config.rawDashboards) 308 | ] + 309 | if std.length(g._config.config) > 0 then [configVolume] else []; 310 | 311 | local plugins = ( 312 | if std.length(g._config.plugins) == 0 then 313 | [] 314 | else 315 | [{ name: 'GF_INSTALL_PLUGINS', value: std.join(',', g._config.plugins) }] 316 | ); 317 | 318 | local grafanaContainer = { 319 | name: 'grafana', 320 | image: g._config.image, 321 | env: g._config.env + plugins, 322 | volumeMounts: volumeMounts, 323 | ports: [{ 324 | name: 'http', 325 | containerPort: g._config.port, 326 | }], 327 | readinessProbe: { 328 | httpGet: { 329 | path: '/api/health', 330 | port: grafanaContainer.ports[0].name, 331 | }, 332 | }, 333 | resources: g._config.resources, 334 | securityContext: { 335 | capabilities: { drop: ['ALL'] }, 336 | allowPrivilegeEscalation: false, 337 | readOnlyRootFilesystem: true, 338 | seccompProfile: { type: 'RuntimeDefault' }, 339 | }, 340 | }; 341 | 342 | { 343 | apiVersion: 'apps/v1', 344 | kind: 'Deployment', 345 | metadata: g._metadata, 346 | spec: { 347 | replicas: g._config.replicas, 348 | selector: { 349 | matchLabels: g._config.selectorLabels, 350 | }, 351 | template: { 352 | metadata: { 353 | labels: g._config.commonLabels, 354 | annotations: { 355 | [if std.length(g._config.config) > 0 then 'checksum/grafana-config']: std.md5(std.toString(g.config)), 356 | 'checksum/grafana-datasources': std.md5(std.toString(g.dashboardDatasources)), 357 | [if g._config.dashboardsChecksum then 'checksum/grafana-dashboards']: std.md5(std.toString(g.dashboardDefinitions)), 358 | 'checksum/grafana-dashboardproviders': std.md5(std.toString(g.dashboardSources)), 359 | }, 360 | }, 361 | spec: { 362 | containers: [grafanaContainer] + g._config.containers, 363 | volumes: volumes, 364 | serviceAccountName: g.serviceAccount.metadata.name, 365 | nodeSelector: { 366 | 'kubernetes.io/os': 'linux', 367 | }, 368 | securityContext: { 369 | fsGroup: 65534, 370 | runAsNonRoot: true, 371 | runAsUser: 65534, 372 | }, 373 | }, 374 | }, 375 | }, 376 | }, 377 | } 378 | -------------------------------------------------------------------------------- /grafana/jsonnetfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": [ 4 | { 5 | "source": { 6 | "git": { 7 | "remote": "https://github.com/grafana/grafonnet-lib.git", 8 | "subdir": "grafonnet" 9 | } 10 | }, 11 | "version": "master" 12 | } 13 | ], 14 | "legacyImports": false 15 | } 16 | --------------------------------------------------------------------------------