Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

init grafana service #108

Merged
merged 11 commits into from
Feb 22, 2024
62 changes: 62 additions & 0 deletions doc/grafana.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Grafana Open Source
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason why we can't just use Grafana here?

Copy link
Contributor Author

@conscious-puppet conscious-puppet Feb 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i copied the doc from here:

i think, they probably want to distinguish this from Grafana Cloud. I'll change* it to grafana, sure


Grafana open source is open source visualization and analytics software. It allows you to query, visualize, alert on, and explore your metrics, logs, and traces no matter where they are stored. It provides you with tools to turn your time-series database (TSDB) data into insightful graphs and visualizations.

## Getting Started

```nix
# In `perSystem.process-compose.<name>`
{
services.grafana."gf1".enable = true;
}
```

{#tips}
## Tips & Tricks

{#change-database}
### Changing Grafana database

By default, Grafana stores data in the `sqlite3` [database](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#database). It also supports `mysql` and `postgres`.

To change the database to `postgres`, we can use the following config:

1. Create `postgres` service.

```nix
services.postgres."pg1" = {
enable = true;
listen_addresses = "127.0.0.1";
port = 5435;
initialDatabases = [{
name = "grafana-db";
}];
initialScript.after = ''
CREATE USER gfuser with PASSWORD 'gfpassword' SUPERUSER;
'';
};
```

2. Create `grafana` service, and change its config to use the `postgres` database.

```nix
services.grafana."gf1" = {
enable = true;
http_port = 3001;
extraConf = {
database = {
type = "postgres";
host = "127.0.0.1:5435";
name = "grafana-db";
user = "gfuser";
password = "gfpassword";
};
};
};
```

3. Add a setting to start `grafana` only after `postgres` is running.

```nix
settings.processes."gf1".depends_on."pg1".condition = "process_healthy";
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
1. Create `postgres` service.
```nix
services.postgres."pg1" = {
enable = true;
listen_addresses = "127.0.0.1";
port = 5435;
initialDatabases = [{
name = "grafana-db";
}];
initialScript.after = ''
CREATE USER gfuser with PASSWORD 'gfpassword' SUPERUSER;
'';
};
```
2. Create `grafana` service, and change its config to use the `postgres` database.
```nix
services.grafana."gf1" = {
enable = true;
http_port = 3001;
extraConf = {
database = {
type = "postgres";
host = "127.0.0.1:5435";
name = "grafana-db";
user = "gfuser";
password = "gfpassword";
};
};
};
```
3. Add a setting to start `grafana` only after `postgres` is running.
```nix
settings.processes."gf1".depends_on."pg1".condition = "process_healthy";
```
```nix
{
services.postgres.pg1 = {
enable = true;
listen_addresses = “127.0.0.1”;
};
services.grafana.gf1 = {
enable = true;
extraConf.database = with config.services.postgres.pg1; {
type = “postgres”;
host = “${listen_addresses}:${port}”;
};
};
settings.processes."gf1".depends_on."pg1".condition = "process_healthy";
}
```

It would be nice to keep these tips/suggestions simple and short.
I have assumed that not specifying name and user for postgres db in grafana will use postgres by default for both, if not we can explicitly mention.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

initialDatabases and intialScript can be part of postgres doc (whenever we add it) and if one wants to play around with postgres configs, they can refer from there.

I say this because, we are better off keeping these mini-tutorials short (with default configs) and not overwhelm the reader.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, by default grafana looks for the user root in database. and for the database grafana. i have added config these two over your suggestion. please have a look

1 change: 1 addition & 0 deletions doc/services.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ short-title: Services
- [ ] Redis
- [ ] Redis Cluster
- [ ] Zookeeper
- [ ] [[grafana]]#
- [ ] ...

[gh]: https://github.com/juspay/services-flake
1 change: 1 addition & 0 deletions nix/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ in
./redis-cluster.nix
./redis.nix
./zookeeper.nix
./grafana
];
}
105 changes: 105 additions & 0 deletions nix/grafana/default.nix
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we create folder for a service when there are more than just the service definition file and test file. We can preserve the flat hierarchy here.

Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{ pkgs, lib, name, config, ... }:
let
inherit (lib) types;
iniFormat = pkgs.formats.ini { };
in
{
options = {
description = ''
Configure grafana.
'';
enable = lib.mkEnableOption name;

package = lib.mkPackageOption pkgs "grafana" { };

http_port = lib.mkOption {
type = types.int;
description = "Which port to run grafana on.";
default = 3000;
};

domain = lib.mkOption {
type = types.str;
description = "The public facing domain name used to access grafana from a browser.";
default = "localhost";
};

protocol = lib.mkOption {
type = types.str;
description = "Protocol (http, https, h2, socket).";
default = "http";
};

root_url = lib.mkOption {
type = types.str;
description = "The full public facing url.";
default = "${config.protocol}://${config.domain}:${builtins.toString config.http_port}";
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't expect the user to set this, you want to add readOnly = true; here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grafana actually allows to give a root_url with subpath. in that case, user may set this value.

ref: https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/#root_url

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the user simply not set extraConfig.server.root_url instead? This additional option might not be needed. I think the latest impl. of readiness_probe looks good as is


dataDir = lib.mkOption {
type = types.str;
description = "Directory where grafana stores its logs and data.";
default = "./data/${name}";
};

extraConf = lib.mkOption {
type = iniFormat.type;
description = "Extra configuration for grafana.";
default = { };
example = ''
{
security.admin_user = "patato";
security.admin_password = "potato";
}
'';
};

outputs.settings = lib.mkOption {
type = types.deferredModule;
internal = true;
readOnly = true;
default = {
processes."${name}" =
let
grafanaConfig = lib.recursiveUpdate
config.extraConf
{
server = {
inherit (config) protocol http_port domain root_url;
};
};
grafanaConfigIni = iniFormat.generate "defaults.ini" grafanaConfig;
startScript = pkgs.writeShellApplication {
name = "start-grafana";
runtimeInputs = [ config.package ];
text = ''
grafana server --config ${grafanaConfigIni} \
--homepath ${config.package}/share/grafana \
cfg:paths.data="$(readlink -m ${config.dataDir})"
'';
};
in
{
command = startScript;
readiness_probe = {
http_get = {
host = config.domain;
scheme = config.protocol;
port = config.http_port;
path = "/api/health";
};
initial_delay_seconds = 15;
period_seconds = 10;
timeout_seconds = 2;
success_threshold = 1;
failure_threshold = 5;
};
namespace = name;

# https://github.com/F1bonacc1/process-compose#-auto-restart-if-not-healthy
availability.restart = "on_failure";
};
};
};
};
}
31 changes: 31 additions & 0 deletions nix/grafana/grafana_test.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{ pkgs, config, ... }: {
services.grafana."gf1" =
{
enable = true;
http_port = 3000;
extraConf = {
security.admin_user = "patato";
security.admin_password = "potato";
};
};

settings.processes.test =
let
cfg = config.services.grafana."gf1";
in
{
# Tests based on: https://github.com/NixOS/nixpkgs/blob/master/nixos/tests/grafana/basic.nix
command = pkgs.writeShellApplication {
runtimeInputs = [ cfg.package pkgs.gnugrep pkgs.curl pkgs.uutils-coreutils-noprefix ];
text =
''
ADMIN=${cfg.extraConf.security.admin_user}
PASSWORD=${cfg.extraConf.security.admin_password}
curl -sSfN -u $ADMIN:$PASSWORD ${cfg.root_url}/api/org/users -i
curl -sSfN -u $ADMIN:$PASSWORD ${cfg.root_url}/api/org/users | grep admin\@localhost
'';
name = "grafana-test";
};
depends_on."gf1".condition = "process_healthy";
};
}
1 change: 1 addition & 0 deletions test/flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"${inputs.services-flake}/nix/redis_test.nix"
"${inputs.services-flake}/nix/redis-cluster_test.nix"
"${inputs.services-flake}/nix/zookeeper_test.nix"
"${inputs.services-flake}/nix/grafana/grafana_test.nix"
]);
};
};
Expand Down
Loading