├── .env ├── grafana-provisioning ├── dashboards │ ├── dashboard.yml │ └── artillery.json.example └── datasources │ └── datasource.yml ├── LICENSE ├── docker-compose.yml └── README.md /.env: -------------------------------------------------------------------------------- 1 | INFLUXDB_USERNAME=admin 2 | INFLUXDB_PASSWORD=admin 3 | 4 | GRAFANA_USERNAME=admin 5 | GRAFANA_PASSWORD=admin 6 | -------------------------------------------------------------------------------- /grafana-provisioning/dashboards/dashboard.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | providers: 3 | - name: InfluxDB 4 | folder: '' 5 | type: file 6 | disableDeletion: false 7 | editable: true 8 | options: 9 | path: /etc/grafana/provisioning/dashboards 10 | -------------------------------------------------------------------------------- /grafana-provisioning/datasources/datasource.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | datasources: 3 | - name: InfluxDB 4 | type: influxdb 5 | access: proxy 6 | database: db0 7 | user: admin 8 | password: admin 9 | url: http://influxdb:8086 10 | isDefault: true 11 | editable: true 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jeff Kehres 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | influxdb: 4 | image: influxdb:latest 5 | ports: 6 | - '8086:8086' 7 | volumes: 8 | - influxdb-storage:/var/lib/influxdb 9 | environment: 10 | - INFLUXDB_DB=db0 11 | - INFLUXDB_ADMIN_USER=${INFLUXDB_USERNAME} 12 | - INFLUXDB_ADMIN_PASSWORD=${INFLUXDB_PASSWORD} 13 | chronograf: 14 | image: chronograf:latest 15 | ports: 16 | - '127.0.0.1:8888:8888' 17 | volumes: 18 | - chronograf-storage:/var/lib/chronograf 19 | depends_on: 20 | - influxdb 21 | environment: 22 | - INFLUXDB_URL=http://influxdb:8086 23 | - INFLUXDB_USERNAME=${INFLUXDB_USERNAME} 24 | - INFLUXDB_PASSWORD=${INFLUXDB_PASSWORD} 25 | grafana: 26 | image: grafana/grafana:latest 27 | ports: 28 | - '3000:3000' 29 | volumes: 30 | - grafana-storage:/var/lib/grafana 31 | - ./grafana-provisioning/:/etc/grafana/provisioning 32 | depends_on: 33 | - influxdb 34 | environment: 35 | - GF_SECURITY_ADMIN_USER=${GRAFANA_USERNAME} 36 | - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD} 37 | volumes: 38 | influxdb-storage: 39 | chronograf-storage: 40 | grafana-storage: 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-compose-influxdb-grafana 2 | 3 | Multi-container Docker app built from the following services: 4 | 5 | * [InfluxDB](https://github.com/influxdata/influxdb) - time series database 6 | * [Chronograf](https://github.com/influxdata/chronograf) - admin UI for InfluxDB 7 | * [Grafana](https://github.com/grafana/grafana) - visualization UI for InfluxDB 8 | 9 | Useful for quickly setting up a monitoring stack for performance testing. Combine with [serverless-artillery](https://github.com/Nordstrom/serverless-artillery) and [artillery-plugin-influxdb](https://github.com/Nordstrom/artillery-plugin-influxdb) to create a performance testing environment in minutes. 10 | 11 | ## Quick Start 12 | 13 | To start the app: 14 | 15 | 1. Install [docker-compose](https://docs.docker.com/compose/install/) on the docker host. 16 | 1. Clone this repo on the docker host. 17 | 1. Optionally, change default credentials or Grafana provisioning. 18 | 1. Run the following command from the root of the cloned repo: 19 | ``` 20 | docker-compose up -d 21 | ``` 22 | 23 | To stop the app: 24 | 25 | 1. Run the following command from the root of the cloned repo: 26 | ``` 27 | docker-compose down 28 | ``` 29 | 30 | ## Ports 31 | 32 | The services in the app run on the following ports: 33 | 34 | | Host Port | Service | 35 | | - | - | 36 | | 3000 | Grafana | 37 | | 8086 | InfluxDB | 38 | | 127.0.0.1:8888 | Chronograf | 39 | 40 | Note that Chronograf does not support username/password authentication. Anyone who can connect to the service has full admin access. Consequently, the service is not publically exposed and can only be access via the loopback interface on the same machine that runs docker. 41 | 42 | If docker is running on a remote machine that supports SSH, use the following command to setup an SSH tunnel to securely access Chronograf by forwarding port 8888 on the remote machine to port 8888 on the local machine: 43 | 44 | ``` 45 | ssh [options] @ -L 8888:localhost:8888 -N 46 | ``` 47 | 48 | ## Volumes 49 | 50 | The app creates the following named volumes (one for each service) so data is not lost when the app is stopped: 51 | 52 | * influxdb-storage 53 | * chronograf-storage 54 | * grafana-storage 55 | 56 | ## Users 57 | 58 | The app creates two admin users - one for InfluxDB and one for Grafana. By default, the username and password of both accounts is `admin`. To override the default credentials, set the following environment variables before starting the app: 59 | 60 | * `INFLUXDB_USERNAME` 61 | * `INFLUXDB_PASSWORD` 62 | * `GRAFANA_USERNAME` 63 | * `GRAFANA_PASSWORD` 64 | 65 | ## Database 66 | 67 | The app creates a default InfluxDB database called `db0`. 68 | 69 | ## Data Sources 70 | 71 | The app creates a Grafana data source called `InfluxDB` that's connected to the default IndfluxDB database (e.g. `db0`). 72 | 73 | To provision additional data sources, see the Grafana [documentation](http://docs.grafana.org/administration/provisioning/#datasources) and add a config file to `./grafana-provisioning/datasources/` before starting the app. 74 | 75 | ## Dashboards 76 | 77 | By default, the app does not create any Grafana dashboards. An example dashboard that's configured to work with [artillery-plugin-influxdb](https://github.com/Nordstrom/artillery-plugin-influxdb) is located at `./grafana-provisioning/dashboards/artillery.json.example`. To use this dashboard, rename it to `artillery.json`. 78 | 79 | To provision additional dashboards, see the Grafana [documentation](http://docs.grafana.org/administration/provisioning/#dashboards) and add a config file to `./grafana-provisioning/dashboards/` before starting the app. 80 | -------------------------------------------------------------------------------- /grafana-provisioning/dashboards/artillery.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "id": 3, 19 | "links": [], 20 | "panels": [ 21 | { 22 | "aliasColors": {}, 23 | "bars": false, 24 | "dashLength": 10, 25 | "dashes": false, 26 | "datasource": "InfluxDB", 27 | "fill": 1, 28 | "gridPos": { 29 | "h": 9, 30 | "w": 12, 31 | "x": 0, 32 | "y": 0 33 | }, 34 | "id": 2, 35 | "legend": { 36 | "avg": false, 37 | "current": false, 38 | "max": false, 39 | "min": false, 40 | "show": true, 41 | "total": false, 42 | "values": false 43 | }, 44 | "lines": true, 45 | "linewidth": 1, 46 | "links": [], 47 | "nullPointMode": "null as zero", 48 | "percentage": false, 49 | "pointradius": 2, 50 | "points": false, 51 | "renderer": "flot", 52 | "seriesOverrides": [], 53 | "spaceLength": 10, 54 | "stack": false, 55 | "steppedLine": false, 56 | "targets": [ 57 | { 58 | "alias": "Request Rate", 59 | "groupBy": [ 60 | { 61 | "params": [ 62 | "1s" 63 | ], 64 | "type": "time" 65 | }, 66 | { 67 | "params": [ 68 | "null" 69 | ], 70 | "type": "fill" 71 | } 72 | ], 73 | "measurement": "latency", 74 | "orderByTime": "ASC", 75 | "policy": "default", 76 | "refId": "A", 77 | "resultFormat": "time_series", 78 | "select": [ 79 | [ 80 | { 81 | "params": [ 82 | "value" 83 | ], 84 | "type": "field" 85 | }, 86 | { 87 | "params": [], 88 | "type": "count" 89 | } 90 | ] 91 | ], 92 | "tags": [] 93 | } 94 | ], 95 | "thresholds": [], 96 | "timeFrom": null, 97 | "timeShift": null, 98 | "title": "Request Rate", 99 | "tooltip": { 100 | "shared": true, 101 | "sort": 0, 102 | "value_type": "individual" 103 | }, 104 | "type": "graph", 105 | "xaxis": { 106 | "buckets": null, 107 | "mode": "time", 108 | "name": null, 109 | "show": true, 110 | "values": [] 111 | }, 112 | "yaxes": [ 113 | { 114 | "format": "short", 115 | "label": null, 116 | "logBase": 1, 117 | "max": null, 118 | "min": null, 119 | "show": true 120 | }, 121 | { 122 | "format": "short", 123 | "label": null, 124 | "logBase": 1, 125 | "max": null, 126 | "min": null, 127 | "show": true 128 | } 129 | ], 130 | "yaxis": { 131 | "align": false, 132 | "alignLevel": null 133 | } 134 | }, 135 | { 136 | "aliasColors": {}, 137 | "bars": false, 138 | "dashLength": 10, 139 | "dashes": false, 140 | "datasource": null, 141 | "fill": 1, 142 | "gridPos": { 143 | "h": 9, 144 | "w": 12, 145 | "x": 12, 146 | "y": 0 147 | }, 148 | "id": 8, 149 | "legend": { 150 | "avg": false, 151 | "current": false, 152 | "max": false, 153 | "min": false, 154 | "show": true, 155 | "total": false, 156 | "values": false 157 | }, 158 | "lines": true, 159 | "linewidth": 1, 160 | "links": [], 161 | "nullPointMode": "null as zero", 162 | "percentage": false, 163 | "pointradius": 5, 164 | "points": false, 165 | "renderer": "flot", 166 | "seriesOverrides": [], 167 | "spaceLength": 10, 168 | "stack": false, 169 | "steppedLine": false, 170 | "targets": [ 171 | { 172 | "$$hashKey": "object:1250", 173 | "alias": "Non-2XX Count", 174 | "groupBy": [ 175 | { 176 | "params": [ 177 | "1s" 178 | ], 179 | "type": "time" 180 | }, 181 | { 182 | "params": [ 183 | "null" 184 | ], 185 | "type": "fill" 186 | } 187 | ], 188 | "measurement": "latency", 189 | "orderByTime": "ASC", 190 | "policy": "default", 191 | "refId": "B", 192 | "resultFormat": "time_series", 193 | "select": [ 194 | [ 195 | { 196 | "params": [ 197 | "value" 198 | ], 199 | "type": "field" 200 | }, 201 | { 202 | "params": [], 203 | "type": "count" 204 | } 205 | ] 206 | ], 207 | "tags": [ 208 | { 209 | "key": "response", 210 | "operator": "!=", 211 | "value": "200" 212 | } 213 | ] 214 | } 215 | ], 216 | "thresholds": [], 217 | "timeFrom": null, 218 | "timeShift": null, 219 | "title": "Errors", 220 | "tooltip": { 221 | "shared": true, 222 | "sort": 0, 223 | "value_type": "individual" 224 | }, 225 | "type": "graph", 226 | "xaxis": { 227 | "buckets": null, 228 | "mode": "time", 229 | "name": null, 230 | "show": true, 231 | "values": [] 232 | }, 233 | "yaxes": [ 234 | { 235 | "format": "short", 236 | "label": null, 237 | "logBase": 1, 238 | "max": null, 239 | "min": null, 240 | "show": true 241 | }, 242 | { 243 | "format": "short", 244 | "label": null, 245 | "logBase": 1, 246 | "max": null, 247 | "min": null, 248 | "show": true 249 | } 250 | ], 251 | "yaxis": { 252 | "align": false, 253 | "alignLevel": null 254 | } 255 | }, 256 | { 257 | "aliasColors": {}, 258 | "bars": false, 259 | "dashLength": 10, 260 | "dashes": false, 261 | "datasource": null, 262 | "fill": 1, 263 | "gridPos": { 264 | "h": 9, 265 | "w": 12, 266 | "x": 0, 267 | "y": 9 268 | }, 269 | "id": 6, 270 | "legend": { 271 | "avg": false, 272 | "current": false, 273 | "max": false, 274 | "min": false, 275 | "show": true, 276 | "total": false, 277 | "values": false 278 | }, 279 | "lines": true, 280 | "linewidth": 1, 281 | "links": [], 282 | "nullPointMode": "null as zero", 283 | "percentage": false, 284 | "pointradius": 5, 285 | "points": false, 286 | "renderer": "flot", 287 | "seriesOverrides": [], 288 | "spaceLength": 10, 289 | "stack": false, 290 | "steppedLine": false, 291 | "targets": [ 292 | { 293 | "$$hashKey": "object:1401", 294 | "alias": "Latency (mean)", 295 | "groupBy": [ 296 | { 297 | "params": [ 298 | "1s" 299 | ], 300 | "type": "time" 301 | }, 302 | { 303 | "params": [ 304 | "null" 305 | ], 306 | "type": "fill" 307 | } 308 | ], 309 | "measurement": "latency", 310 | "orderByTime": "ASC", 311 | "policy": "default", 312 | "refId": "A", 313 | "resultFormat": "time_series", 314 | "select": [ 315 | [ 316 | { 317 | "params": [ 318 | "value" 319 | ], 320 | "type": "field" 321 | }, 322 | { 323 | "params": [], 324 | "type": "mean" 325 | } 326 | ] 327 | ], 328 | "tags": [] 329 | }, 330 | { 331 | "$$hashKey": "object:1402", 332 | "alias": "Latency (max)", 333 | "groupBy": [ 334 | { 335 | "params": [ 336 | "1s" 337 | ], 338 | "type": "time" 339 | }, 340 | { 341 | "params": [ 342 | "null" 343 | ], 344 | "type": "fill" 345 | } 346 | ], 347 | "measurement": "latency", 348 | "orderByTime": "ASC", 349 | "policy": "default", 350 | "refId": "C", 351 | "resultFormat": "time_series", 352 | "select": [ 353 | [ 354 | { 355 | "params": [ 356 | "value" 357 | ], 358 | "type": "field" 359 | }, 360 | { 361 | "params": [], 362 | "type": "max" 363 | } 364 | ] 365 | ], 366 | "tags": [] 367 | } 368 | ], 369 | "thresholds": [], 370 | "timeFrom": null, 371 | "timeShift": null, 372 | "title": "Client Latency", 373 | "tooltip": { 374 | "shared": true, 375 | "sort": 0, 376 | "value_type": "individual" 377 | }, 378 | "type": "graph", 379 | "xaxis": { 380 | "buckets": null, 381 | "mode": "time", 382 | "name": null, 383 | "show": true, 384 | "values": [] 385 | }, 386 | "yaxes": [ 387 | { 388 | "format": "short", 389 | "label": null, 390 | "logBase": 1, 391 | "max": null, 392 | "min": null, 393 | "show": true 394 | }, 395 | { 396 | "format": "short", 397 | "label": null, 398 | "logBase": 1, 399 | "max": null, 400 | "min": null, 401 | "show": true 402 | } 403 | ], 404 | "yaxis": { 405 | "align": false, 406 | "alignLevel": null 407 | } 408 | } 409 | ], 410 | "refresh": "5s", 411 | "schemaVersion": 16, 412 | "style": "dark", 413 | "tags": [], 414 | "templating": { 415 | "list": [] 416 | }, 417 | "time": { 418 | "from": "now-5m", 419 | "to": "now" 420 | }, 421 | "timepicker": { 422 | "refresh_intervals": [ 423 | "5s", 424 | "10s", 425 | "30s", 426 | "1m", 427 | "5m", 428 | "15m", 429 | "30m", 430 | "1h", 431 | "2h", 432 | "1d" 433 | ], 434 | "time_options": [ 435 | "5m", 436 | "15m", 437 | "1h", 438 | "6h", 439 | "12h", 440 | "24h", 441 | "2d", 442 | "7d", 443 | "30d" 444 | ] 445 | }, 446 | "timezone": "", 447 | "title": "Artillery", 448 | "uid": "Abwq412mz", 449 | "version": 2 450 | } 451 | --------------------------------------------------------------------------------