From 6c67a6e76c30c4f56d3aa4479e797966181f0387 Mon Sep 17 00:00:00 2001 From: Ayoub Nasr Date: Wed, 16 Oct 2024 10:34:25 +0200 Subject: [PATCH] chart, build, salt: bump loki chart to 6.16.0 --- CHANGELOG.md | 6 + buildchain/buildchain/versions.py | 4 +- charts/loki.yaml | 15 +- charts/loki/Chart.lock | 7 +- charts/loki/Chart.yaml | 13 +- charts/loki/README.md | 5 +- .../loki/charts/rollout-operator/.helmignore | 23 + .../loki/charts/rollout-operator/Chart.yaml | 8 + charts/loki/charts/rollout-operator/README.md | 71 + .../charts/rollout-operator/README.md.gotmpl | 38 + .../rollout-operator/templates/NOTES.txt | 10 + .../rollout-operator/templates/_helpers.tpl | 79 + .../templates/deployment.yaml | 74 + .../rollout-operator/templates/role.yaml | 28 + .../templates/rolebinding.yaml | 11 + .../rollout-operator/templates/service.yaml | 18 + .../templates/serviceaccount.yaml | 12 + .../templates/servicemonitor.yaml | 36 + .../loki/charts/rollout-operator/values.yaml | 83 + charts/loki/distributed-values.yaml | 71 + .../loki/docs/examples/enterprise/README.md | 2 +- charts/loki/docs/examples/oss/README.md | 2 +- charts/loki/scenarios/README.md | 19 + .../default-single-binary-values.yaml | 71 + charts/loki/scenarios/default-values.yaml | 16 + charts/loki/scenarios/images/added.png | Bin 0 -> 91760 bytes charts/loki/scenarios/images/img.png | Bin 0 -> 39609 bytes charts/loki/scenarios/images/modified.png | Bin 0 -> 80274 bytes charts/loki/scenarios/images/removed.png | Bin 0 -> 71762 bytes charts/loki/scenarios/ingress-values.yaml | 30 + .../scenarios/legacy-monitoring-values.yaml | 27 + .../simple-scalable-aws-kube-irsa-values.yaml | 67 + charts/loki/simple-scalable-values.yaml | 63 + charts/loki/single-binary-values.yaml | 79 + charts/loki/src/alerts.yaml.tpl | 2 +- charts/loki/src/helm-test/Dockerfile | 6 +- charts/loki/src/helm-test/canary_test.go | 113 +- charts/loki/templates/NOTES.txt | 163 +- charts/loki/templates/_helpers.tpl | 453 +- charts/loki/templates/admin-api/_helpers.yaml | 24 + .../admin-api/deployment-admin-api.yaml | 166 + .../admin-api/service-admin-api.yaml | 28 + .../loki/templates/backend/clusterrole.yaml | 2 +- .../templates/backend/clusterrolebinding.yaml | 5 +- .../backend/query-scheduler-discovery.yaml | 9 +- .../backend/statefulset-backend.yaml | 15 +- .../bloom-builder/_helpers-bloom-builder.tpl | 32 + .../deployment-bloom-builder.yaml | 150 + charts/loki/templates/bloom-builder/hpa.yaml | 55 + .../poddisruptionbudget-bloom-builder.yaml | 21 + .../service-bloom-builder-headless.yaml | 46 + .../bloom-builder/service-bloom-builder.yaml | 44 + .../bloom-gateway/_helpers-bloom-gateway.tpl | 58 + .../service-bloom-gateway-headless.yaml | 39 + .../statefulset-bloom-gateway.yaml | 181 + .../bloom-planner/_helpers-bloom-planner.tpl | 58 + .../service-bloom-planner-headless.yaml | 37 + .../statefulset-bloom-planner.yaml | 181 + .../poddisruptionbudget-chunks-cache.yaml | 16 + .../service-chunks-cache-headless.yaml | 1 + .../statefulset-chunks-cache.yaml | 1 + .../compactor/_helpers-compactor.tpl | 81 + .../compactor/service-compactor.yaml | 38 + .../compactor/statefulset-compactor.yaml | 193 + charts/loki/templates/config.yaml | 4 +- .../distributor/_helpers-distributor.tpl | 32 + .../distributor/deployment-distributor.yaml | 152 + charts/loki/templates/distributor/hpa.yaml | 54 + .../poddisruptionbudget-distributor.yaml | 21 + .../service-distributor-headless.yaml | 39 + .../distributor/service-distributor.yaml | 36 + charts/loki/templates/extra-manifests.yaml | 8 +- .../templates/gateway/_helpers-gateway.tpl | 2 +- .../templates/gateway/configmap-gateway.yaml | 2 +- .../deployment-gateway-enterprise.yaml | 142 + ...way.yaml => deployment-gateway-nginx.yaml} | 8 +- .../templates/gateway/service-gateway.yaml | 4 +- .../index-gateway/_helpers-index-gateway.tpl | 40 + .../poddisruptionbudget-index-gateway.yaml | 20 + .../service-index-gateway-headless.yaml | 34 + .../index-gateway/service-index-gateway.yaml | 35 + .../statefulset-index-gateway.yaml | 186 + .../templates/ingester/_helpers-ingester.tpl | 74 + charts/loki/templates/ingester/hpa.yaml | 55 + .../poddisruptionbudget-ingester-rollout.yaml | 21 + .../poddisruptionbudget-ingester.yaml | 27 + .../ingester/service-ingester-headless.yaml | 35 + .../service-ingester-zone-a-headless.yaml | 38 + .../service-ingester-zone-b-headless.yaml | 38 + .../service-ingester-zone-c-headless.yaml | 38 + .../templates/ingester/service-ingester.yaml | 36 + .../ingester/statefulset-ingester-zone-a.yaml | 232 ++ .../ingester/statefulset-ingester-zone-b.yaml | 232 ++ .../ingester/statefulset-ingester-zone-c.yaml | 232 ++ .../ingester/statefulset-ingester.yaml | 204 + .../loki/templates/loki-canary/_helpers.tpl | 4 +- .../loki/templates/loki-canary/daemonset.yaml | 14 +- .../loki/templates/loki-canary/service.yaml | 2 +- .../templates/loki-canary/serviceaccount.yaml | 2 +- .../memcached/_memcached-statefulset.tpl | 178 + .../templates/memcached/_memcached-svc.tpl | 42 + .../templates/monitoring/logs-instance.yaml | 2 +- .../_helpers-pattern-ingester.tpl | 58 + .../statefulset-pattern-ingester.yaml | 179 + .../provisioner/job-provisioner.yaml | 18 +- .../provisioner/role-provisioner.yaml | 2 +- .../provisioner/rolebinding-provisioner.yaml | 2 +- .../templates/querier/_helpers-querier.tpl | 32 + .../templates/querier/deployment-querier.yaml | 166 + charts/loki/templates/querier/hpa.yaml | 55 + .../querier/poddisruptionbudget-querier.yaml | 21 + .../templates/querier/service-querier.yaml | 36 + .../_helpers-query-frontend.tpl | 32 + .../deployment-query-frontend.yaml | 142 + charts/loki/templates/query-frontend/hpa.yaml | 55 + .../poddisruptionbudget-query-frontend.yaml | 21 + .../service-query-frontend-headless.yaml | 46 + .../service-query-frontend.yaml | 44 + .../_helpers-query-scheduler.tpl | 40 + .../deployment-query-scheduler.yaml | 140 + .../poddisruptionbudget-query-scheduler.yaml | 21 + .../service-query-scheduler.yaml | 38 + .../loki/templates/read/deployment-read.yaml | 7 +- .../loki/templates/read/statefulset-read.yaml | 18 +- .../poddisruptionbudget-results-cache.yaml | 16 + .../service-results-cache-headless.yaml | 1 + .../statefulset-results-cache.yaml | 1 + .../loki/templates/ruler/_helpers-ruler.tpl | 47 + .../loki/templates/ruler/configmap-ruler.yaml | 14 + .../ruler/poddisruptionbudget-ruler.yaml | 20 + .../loki/templates/ruler/service-ruler.yaml | 36 + .../templates/ruler/statefulset-ruler.yaml | 177 + charts/loki/templates/service-memberlist.yaml | 7 + .../templates/single-binary/statefulset.yaml | 93 +- .../deployment-table-manager.yaml | 16 +- charts/loki/templates/tests/test-canary.yaml | 4 +- .../tokengen/clusterrole-tokengen.yaml | 2 +- .../tokengen/clusterrolebinding-tokengen.yaml | 2 +- .../loki/templates/tokengen/job-tokengen.yaml | 16 +- charts/loki/templates/validate.yaml | 36 +- .../templates/write/statefulset-write.yaml | 17 +- charts/loki/test/config_test.go | 220 + charts/loki/values.yaml | 3704 +++++++++++++---- .../addons/logging/loki/deployed/chart.sls | 361 +- 144 files changed, 10336 insertions(+), 1158 deletions(-) create mode 100644 charts/loki/charts/rollout-operator/.helmignore create mode 100644 charts/loki/charts/rollout-operator/Chart.yaml create mode 100644 charts/loki/charts/rollout-operator/README.md create mode 100644 charts/loki/charts/rollout-operator/README.md.gotmpl create mode 100644 charts/loki/charts/rollout-operator/templates/NOTES.txt create mode 100644 charts/loki/charts/rollout-operator/templates/_helpers.tpl create mode 100644 charts/loki/charts/rollout-operator/templates/deployment.yaml create mode 100644 charts/loki/charts/rollout-operator/templates/role.yaml create mode 100644 charts/loki/charts/rollout-operator/templates/rolebinding.yaml create mode 100644 charts/loki/charts/rollout-operator/templates/service.yaml create mode 100644 charts/loki/charts/rollout-operator/templates/serviceaccount.yaml create mode 100644 charts/loki/charts/rollout-operator/templates/servicemonitor.yaml create mode 100644 charts/loki/charts/rollout-operator/values.yaml create mode 100644 charts/loki/distributed-values.yaml create mode 100644 charts/loki/scenarios/README.md create mode 100644 charts/loki/scenarios/default-single-binary-values.yaml create mode 100644 charts/loki/scenarios/default-values.yaml create mode 100644 charts/loki/scenarios/images/added.png create mode 100644 charts/loki/scenarios/images/img.png create mode 100644 charts/loki/scenarios/images/modified.png create mode 100644 charts/loki/scenarios/images/removed.png create mode 100644 charts/loki/scenarios/ingress-values.yaml create mode 100644 charts/loki/scenarios/legacy-monitoring-values.yaml create mode 100644 charts/loki/scenarios/simple-scalable-aws-kube-irsa-values.yaml create mode 100644 charts/loki/simple-scalable-values.yaml create mode 100644 charts/loki/single-binary-values.yaml create mode 100644 charts/loki/templates/admin-api/_helpers.yaml create mode 100644 charts/loki/templates/admin-api/deployment-admin-api.yaml create mode 100644 charts/loki/templates/admin-api/service-admin-api.yaml create mode 100644 charts/loki/templates/bloom-builder/_helpers-bloom-builder.tpl create mode 100644 charts/loki/templates/bloom-builder/deployment-bloom-builder.yaml create mode 100644 charts/loki/templates/bloom-builder/hpa.yaml create mode 100644 charts/loki/templates/bloom-builder/poddisruptionbudget-bloom-builder.yaml create mode 100644 charts/loki/templates/bloom-builder/service-bloom-builder-headless.yaml create mode 100644 charts/loki/templates/bloom-builder/service-bloom-builder.yaml create mode 100644 charts/loki/templates/bloom-gateway/_helpers-bloom-gateway.tpl create mode 100644 charts/loki/templates/bloom-gateway/service-bloom-gateway-headless.yaml create mode 100644 charts/loki/templates/bloom-gateway/statefulset-bloom-gateway.yaml create mode 100644 charts/loki/templates/bloom-planner/_helpers-bloom-planner.tpl create mode 100644 charts/loki/templates/bloom-planner/service-bloom-planner-headless.yaml create mode 100644 charts/loki/templates/bloom-planner/statefulset-bloom-planner.yaml create mode 100644 charts/loki/templates/chunks-cache/poddisruptionbudget-chunks-cache.yaml create mode 100644 charts/loki/templates/chunks-cache/service-chunks-cache-headless.yaml create mode 100644 charts/loki/templates/chunks-cache/statefulset-chunks-cache.yaml create mode 100644 charts/loki/templates/compactor/_helpers-compactor.tpl create mode 100644 charts/loki/templates/compactor/service-compactor.yaml create mode 100644 charts/loki/templates/compactor/statefulset-compactor.yaml create mode 100644 charts/loki/templates/distributor/_helpers-distributor.tpl create mode 100644 charts/loki/templates/distributor/deployment-distributor.yaml create mode 100644 charts/loki/templates/distributor/hpa.yaml create mode 100644 charts/loki/templates/distributor/poddisruptionbudget-distributor.yaml create mode 100644 charts/loki/templates/distributor/service-distributor-headless.yaml create mode 100644 charts/loki/templates/distributor/service-distributor.yaml create mode 100644 charts/loki/templates/gateway/deployment-gateway-enterprise.yaml rename charts/loki/templates/gateway/{deployment-gateway.yaml => deployment-gateway-nginx.yaml} (95%) create mode 100644 charts/loki/templates/index-gateway/_helpers-index-gateway.tpl create mode 100644 charts/loki/templates/index-gateway/poddisruptionbudget-index-gateway.yaml create mode 100644 charts/loki/templates/index-gateway/service-index-gateway-headless.yaml create mode 100644 charts/loki/templates/index-gateway/service-index-gateway.yaml create mode 100644 charts/loki/templates/index-gateway/statefulset-index-gateway.yaml create mode 100644 charts/loki/templates/ingester/_helpers-ingester.tpl create mode 100644 charts/loki/templates/ingester/hpa.yaml create mode 100644 charts/loki/templates/ingester/poddisruptionbudget-ingester-rollout.yaml create mode 100644 charts/loki/templates/ingester/poddisruptionbudget-ingester.yaml create mode 100644 charts/loki/templates/ingester/service-ingester-headless.yaml create mode 100644 charts/loki/templates/ingester/service-ingester-zone-a-headless.yaml create mode 100644 charts/loki/templates/ingester/service-ingester-zone-b-headless.yaml create mode 100644 charts/loki/templates/ingester/service-ingester-zone-c-headless.yaml create mode 100644 charts/loki/templates/ingester/service-ingester.yaml create mode 100644 charts/loki/templates/ingester/statefulset-ingester-zone-a.yaml create mode 100644 charts/loki/templates/ingester/statefulset-ingester-zone-b.yaml create mode 100644 charts/loki/templates/ingester/statefulset-ingester-zone-c.yaml create mode 100644 charts/loki/templates/ingester/statefulset-ingester.yaml create mode 100644 charts/loki/templates/memcached/_memcached-statefulset.tpl create mode 100644 charts/loki/templates/memcached/_memcached-svc.tpl create mode 100644 charts/loki/templates/pattern-ingester/_helpers-pattern-ingester.tpl create mode 100644 charts/loki/templates/pattern-ingester/statefulset-pattern-ingester.yaml create mode 100644 charts/loki/templates/querier/_helpers-querier.tpl create mode 100644 charts/loki/templates/querier/deployment-querier.yaml create mode 100644 charts/loki/templates/querier/hpa.yaml create mode 100644 charts/loki/templates/querier/poddisruptionbudget-querier.yaml create mode 100644 charts/loki/templates/querier/service-querier.yaml create mode 100644 charts/loki/templates/query-frontend/_helpers-query-frontend.tpl create mode 100644 charts/loki/templates/query-frontend/deployment-query-frontend.yaml create mode 100644 charts/loki/templates/query-frontend/hpa.yaml create mode 100644 charts/loki/templates/query-frontend/poddisruptionbudget-query-frontend.yaml create mode 100644 charts/loki/templates/query-frontend/service-query-frontend-headless.yaml create mode 100644 charts/loki/templates/query-frontend/service-query-frontend.yaml create mode 100644 charts/loki/templates/query-scheduler/_helpers-query-scheduler.tpl create mode 100644 charts/loki/templates/query-scheduler/deployment-query-scheduler.yaml create mode 100644 charts/loki/templates/query-scheduler/poddisruptionbudget-query-scheduler.yaml create mode 100644 charts/loki/templates/query-scheduler/service-query-scheduler.yaml create mode 100644 charts/loki/templates/results-cache/poddisruptionbudget-results-cache.yaml create mode 100644 charts/loki/templates/results-cache/service-results-cache-headless.yaml create mode 100644 charts/loki/templates/results-cache/statefulset-results-cache.yaml create mode 100644 charts/loki/templates/ruler/_helpers-ruler.tpl create mode 100644 charts/loki/templates/ruler/configmap-ruler.yaml create mode 100644 charts/loki/templates/ruler/poddisruptionbudget-ruler.yaml create mode 100644 charts/loki/templates/ruler/service-ruler.yaml create mode 100644 charts/loki/templates/ruler/statefulset-ruler.yaml create mode 100644 charts/loki/test/config_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 444408bd8c..8ffe9dc879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,12 @@ - Bump etcd version to [3.5.15](https://github.com/etcd-io/etcd/releases/tag/v3.5.15) (PR[#4417](https://github.com/scality/metalk8s/pull/4417)) +- Bump Loki chart version to + [6.16.0](https://github.com/grafana/loki/releases/tag/helm-loki-6.16.0) + The Loki image has been bumped accordingly to + [3.1.1](https://github.com/grafana/loki/releases/tag/v3.1.1) + (PR[#4450](https://github.com/scality/metalk8s/pull/4450)) + ## Release 128.0.1 (in development) ## Release 128.0.0 diff --git a/buildchain/buildchain/versions.py b/buildchain/buildchain/versions.py index 0f8308bdae..c5a8c375d3 100644 --- a/buildchain/buildchain/versions.py +++ b/buildchain/buildchain/versions.py @@ -264,8 +264,8 @@ def _version_prefix(version: str, prefix: str = "v") -> str: ), Image( name="loki", - version="2.9.6", - digest="sha256:6ca6e2cd3b6f45e0eb298da2920610fde63ecd8ab6c595d9c941c8559d1d9407", + version="3.1.1", + digest="sha256:e689cc634841c937de4d7ea6157f17e29cf257d6a320f1c293ab18d46cfea986", ), Image( name="fluent-bit", diff --git a/charts/loki.yaml b/charts/loki.yaml index 8dd5d5fc18..8852b4a28e 100644 --- a/charts/loki.yaml +++ b/charts/loki.yaml @@ -8,7 +8,12 @@ loki: registry: "" repository: '__image__(loki)' - existingSecretForConfig: loki + schemaConfig: + # needs to be removed asap when []() is fixed + remove: me + + configObjectName: loki + generatedConfigObjectName: "" podAnnotations: # Override default checksum as we want to manage it with salt @@ -21,6 +26,8 @@ read: write: replicas: 0 +deploymentMode: SingleBinary + singleBinary: targetModule: "all,table-manager" @@ -60,12 +67,12 @@ monitoring: grafanaAgent: installOperator: false - lokiCanary: - enabled: false - dashboards: enabled: false +lokiCanary: + enabled: false + gateway: enabled: false diff --git a/charts/loki/Chart.lock b/charts/loki/Chart.lock index e8c779c503..5d6d29141b 100644 --- a/charts/loki/Chart.lock +++ b/charts/loki/Chart.lock @@ -5,5 +5,8 @@ dependencies: - name: grafana-agent-operator repository: https://grafana.github.io/helm-charts version: 0.3.15 -digest: sha256:b7a42cd0e56544f6168a586fde03e26c801bb20cf69bc004a8f6000d93b98100 -generated: "2024-01-27T21:57:28.190462917+05:30" +- name: rollout-operator + repository: https://grafana.github.io/helm-charts + version: 0.13.0 +digest: sha256:d0e60c2879039ee5e8b7b10530f0e8790d6d328ee8afca71f01128627e921587 +generated: "2024-04-07T14:12:43.317329844-04:00" diff --git a/charts/loki/Chart.yaml b/charts/loki/Chart.yaml index e72782e89b..7527cb76f2 100644 --- a/charts/loki/Chart.yaml +++ b/charts/loki/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: 2.9.6 +appVersion: 3.1.1 dependencies: - alias: minio condition: minio.enabled @@ -11,17 +11,22 @@ dependencies: name: grafana-agent-operator repository: https://grafana.github.io/helm-charts version: 0.3.15 -description: Helm chart for Grafana Loki in simple, scalable mode +- alias: rollout_operator + condition: rollout_operator.enabled + name: rollout-operator + repository: https://grafana.github.io/helm-charts + version: 0.13.0 +description: Helm chart for Grafana Loki and Grafana Enterprise Logs supporting both + simple, scalable and distributed modes. home: https://grafana.github.io/helm-charts icon: https://grafana.com/docs/loki/latest/logo_and_name.png maintainers: - name: trevorwhitney - name: jeschkies -- name: slim-bean name: loki sources: - https://github.com/grafana/loki - https://grafana.com/oss/loki/ - https://grafana.com/docs/loki/latest/ type: application -version: 5.48.0 +version: 6.16.0 diff --git a/charts/loki/README.md b/charts/loki/README.md index 425773ec62..235c31643d 100644 --- a/charts/loki/README.md +++ b/charts/loki/README.md @@ -1,8 +1,8 @@ # loki -![Version: 5.48.0](https://img.shields.io/badge/Version-5.48.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 2.9.6](https://img.shields.io/badge/AppVersion-2.9.6-informational?style=flat-square) +![Version: 6.16.0](https://img.shields.io/badge/Version-6.16.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: 3.1.1](https://img.shields.io/badge/AppVersion-3.1.1-informational?style=flat-square) -Helm chart for Grafana Loki in simple, scalable mode +Helm chart for Grafana Loki and Grafana Enterprise Logs supporting both simple, scalable and distributed modes. ## Source Code @@ -16,5 +16,6 @@ Helm chart for Grafana Loki in simple, scalable mode |------------|------|---------| | https://charts.min.io/ | minio(minio) | 4.0.15 | | https://grafana.github.io/helm-charts | grafana-agent-operator(grafana-agent-operator) | 0.3.15 | +| https://grafana.github.io/helm-charts | rollout_operator(rollout-operator) | 0.13.0 | Find more information in the Loki Helm Chart [documentation](https://grafana.com/docs/loki/next/installation/helm). diff --git a/charts/loki/charts/rollout-operator/.helmignore b/charts/loki/charts/rollout-operator/.helmignore new file mode 100644 index 0000000000..0e8a0eb36f --- /dev/null +++ b/charts/loki/charts/rollout-operator/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/loki/charts/rollout-operator/Chart.yaml b/charts/loki/charts/rollout-operator/Chart.yaml new file mode 100644 index 0000000000..444204d97f --- /dev/null +++ b/charts/loki/charts/rollout-operator/Chart.yaml @@ -0,0 +1,8 @@ +apiVersion: v2 +appVersion: v0.11.0 +description: Grafana rollout-operator +home: https://github.com/grafana/rollout-operator +kubeVersion: ^1.10.0-0 +name: rollout-operator +type: application +version: 0.13.0 diff --git a/charts/loki/charts/rollout-operator/README.md b/charts/loki/charts/rollout-operator/README.md new file mode 100644 index 0000000000..3efeb2f1aa --- /dev/null +++ b/charts/loki/charts/rollout-operator/README.md @@ -0,0 +1,71 @@ +# Grafana rollout-operator Helm Chart + +Helm chart for deploying [Grafana rollout-operator](https://github.com/grafana/rollout-operator) to Kubernetes. + +# rollout-operator + +![Version: 0.13.0](https://img.shields.io/badge/Version-0.13.0-informational?style=flat-square) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square) ![AppVersion: v0.11.0](https://img.shields.io/badge/AppVersion-v0.11.0-informational?style=flat-square) + +Grafana rollout-operator + +## Requirements + +Kubernetes: `^1.10.0-0` + +## Installation + +This section describes various use cases for installation, upgrade and migration from different systems and versions. + +### Preparation + +These are the common tasks to perform before any of the use cases. + +```bash +# Add the repository +helm repo add grafana https://grafana.github.io/helm-charts +helm repo update +``` + +### Installation of Grafana Rollout Operator + +```bash +helm install -n grafana/rollout-operator +``` + +The Grafana rollout-operator should be installed in the same namespace as the statefulsets it is operating upon. +It is not a highly available application and runs as a single pod. + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| affinity | object | `{}` | | +| fullnameOverride | string | `""` | | +| hostAliases | list | `[]` | hostAliases to add | +| image.pullPolicy | string | `"IfNotPresent"` | | +| image.repository | string | `"grafana/rollout-operator"` | | +| image.tag | string | `""` | Overrides the image tag whose default is the chart appVersion. | +| imagePullSecrets | list | `[]` | | +| minReadySeconds | int | `10` | | +| nameOverride | string | `""` | | +| nodeSelector | object | `{}` | | +| podAnnotations | object | `{}` | Pod Annotations | +| podLabels | object | `{}` | Pod (extra) Labels | +| podSecurityContext | object | `{}` | | +| priorityClassName | string | `""` | | +| resources.limits.memory | string | `"200Mi"` | | +| resources.requests.cpu | string | `"100m"` | | +| resources.requests.memory | string | `"100Mi"` | | +| securityContext | object | `{}` | | +| serviceAccount.annotations | object | `{}` | Annotations to add to the service account | +| serviceAccount.create | bool | `true` | Specifies whether a service account should be created | +| serviceAccount.name | string | `""` | The name of the service account to use. If not set and create is true, a name is generated using the fullname template | +| serviceMonitor.annotations | object | `{}` | ServiceMonitor annotations | +| serviceMonitor.enabled | bool | `false` | Create ServiceMonitor to scrape metrics for Prometheus | +| serviceMonitor.interval | string | `nil` | ServiceMonitor scrape interval | +| serviceMonitor.labels | object | `{}` | Additional ServiceMonitor labels | +| serviceMonitor.namespace | string | `nil` | Alternative namespace for ServiceMonitor resources | +| serviceMonitor.namespaceSelector | object | `{}` | Namespace selector for ServiceMonitor resources | +| serviceMonitor.relabelings | list | `[]` | ServiceMonitor relabel configs to apply to samples before scraping https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#relabelconfig | +| serviceMonitor.scrapeTimeout | string | `nil` | ServiceMonitor scrape timeout in Go duration format (e.g. 15s) | +| tolerations | list | `[]` | | diff --git a/charts/loki/charts/rollout-operator/README.md.gotmpl b/charts/loki/charts/rollout-operator/README.md.gotmpl new file mode 100644 index 0000000000..0ac2d4747e --- /dev/null +++ b/charts/loki/charts/rollout-operator/README.md.gotmpl @@ -0,0 +1,38 @@ +# Grafana rollout-operator Helm Chart + +Helm chart for deploying [Grafana rollout-operator]({{ template "chart.homepage" . }}) to Kubernetes. + +{{ template "chart.header" . }} + +{{ template "chart.versionBadge" . }}{{ template "chart.typeBadge" . }}{{ template "chart.appVersionBadge" . }} + +{{ template "chart.description" . }} + +{{ template "chart.sourcesSection" . }} + +{{ template "chart.requirementsSection" . }} + +## Installation + +This section describes various use cases for installation, upgrade and migration from different systems and versions. + +### Preparation + +These are the common tasks to perform before any of the use cases. + +```bash +# Add the repository +helm repo add grafana https://grafana.github.io/helm-charts +helm repo update +``` + +### Installation of Grafana Rollout Operator + +```bash +helm install -n grafana/rollout-operator +``` + +The Grafana rollout-operator should be installed in the same namespace as the statefulsets it is operating upon. +It is not a highly available application and runs as a single pod. + +{{ template "chart.valuesSection" . }} diff --git a/charts/loki/charts/rollout-operator/templates/NOTES.txt b/charts/loki/charts/rollout-operator/templates/NOTES.txt new file mode 100644 index 0000000000..a76e5ba078 --- /dev/null +++ b/charts/loki/charts/rollout-operator/templates/NOTES.txt @@ -0,0 +1,10 @@ +Repo : {{ .Chart.Home }} + +Validation: + +Check the logs of the pod and ensure messages for reconcilliation of the statefulsets are present. +``` +kubectl logs -n {{ .Release.Namespace }} -l {{ include "cli.labels" . }} +``` +Example log line: +level=debug ts=2022-04-20T13:59:52.783051541Z msg="reconciling StatefulSet" statefulset=mimir-store-gateway-zone-a diff --git a/charts/loki/charts/rollout-operator/templates/_helpers.tpl b/charts/loki/charts/rollout-operator/templates/_helpers.tpl new file mode 100644 index 0000000000..bf3553abfe --- /dev/null +++ b/charts/loki/charts/rollout-operator/templates/_helpers.tpl @@ -0,0 +1,79 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "rollout-operator.name" -}} +{{- default (include "rollout-operator.chartName" .) .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "rollout-operator.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default (include "rollout-operator.chartName" .) .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Recalculate the chart name, because it may be sub-chart included as rollout_operator, +and _ is not valid in resource names. +*/}} +{{- define "rollout-operator.chartName" -}} +{{- print .Chart.Name | replace "_" "-" -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "rollout-operator.chart" -}} +{{- printf "%s-%s" (include "rollout-operator.chartName" .) .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "rollout-operator.labels" -}} +helm.sh/chart: {{ include "rollout-operator.chart" . }} +{{ include "rollout-operator.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "rollout-operator.selectorLabels" -}} +app.kubernetes.io/name: {{ include "rollout-operator.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "rollout-operator.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "rollout-operator.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} + + +{{- define "cli.labels" -}} +{{- $list := list -}} +{{- range $k, $v := ( include "rollout-operator.selectorLabels" . | fromYaml ) -}} +{{- $list = append $list (printf "%s=%s" $k $v) -}} +{{- end -}} +{{ join "," $list }} +{{- end -}} diff --git a/charts/loki/charts/rollout-operator/templates/deployment.yaml b/charts/loki/charts/rollout-operator/templates/deployment.yaml new file mode 100644 index 0000000000..d35b866d0e --- /dev/null +++ b/charts/loki/charts/rollout-operator/templates/deployment.yaml @@ -0,0 +1,74 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "rollout-operator.fullname" . }} + labels: + {{- include "rollout-operator.labels" . | nindent 4 }} +spec: + replicas: 1 + minReadySeconds: {{ .Values.minReadySeconds }} + selector: + matchLabels: + {{- include "rollout-operator.selectorLabels" . | nindent 6 }} + strategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "rollout-operator.selectorLabels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.priorityClassName }} + priorityClassName: {{ . }} + {{- end }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "rollout-operator.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: rollout-operator + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: + - -kubernetes.namespace={{ .Release.Namespace }} + ports: + - name: http-metrics + containerPort: 8001 + protocol: TCP + readinessProbe: + httpGet: + path: /ready + port: http-metrics + initialDelaySeconds: 5 + timeoutSeconds: 1 + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/charts/loki/charts/rollout-operator/templates/role.yaml b/charts/loki/charts/rollout-operator/templates/role.yaml new file mode 100644 index 0000000000..210c456d03 --- /dev/null +++ b/charts/loki/charts/rollout-operator/templates/role.yaml @@ -0,0 +1,28 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "rollout-operator.fullname" . }} +rules: +- apiGroups: + - "" + resources: + - pods + verbs: + - list + - get + - watch + - delete +- apiGroups: + - apps + resources: + - statefulsets + verbs: + - list + - get + - watch +- apiGroups: + - apps + resources: + - statefulsets/status + verbs: + - update diff --git a/charts/loki/charts/rollout-operator/templates/rolebinding.yaml b/charts/loki/charts/rollout-operator/templates/rolebinding.yaml new file mode 100644 index 0000000000..24fcd72663 --- /dev/null +++ b/charts/loki/charts/rollout-operator/templates/rolebinding.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "rollout-operator.fullname" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "rollout-operator.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ include "rollout-operator.serviceAccountName" . }} diff --git a/charts/loki/charts/rollout-operator/templates/service.yaml b/charts/loki/charts/rollout-operator/templates/service.yaml new file mode 100644 index 0000000000..60ce5b1e83 --- /dev/null +++ b/charts/loki/charts/rollout-operator/templates/service.yaml @@ -0,0 +1,18 @@ +{{- if .Values.serviceMonitor.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "rollout-operator.fullname" . }} + labels: + {{- include "rollout-operator.labels" . | nindent 4 }} +spec: + type: ClusterIP + clusterIP: None + ports: + - port: 8001 + targetPort: http-metrics + protocol: TCP + name: http-metrics + selector: + {{- include "rollout-operator.selectorLabels" . | nindent 4 }} +{{- end -}} diff --git a/charts/loki/charts/rollout-operator/templates/serviceaccount.yaml b/charts/loki/charts/rollout-operator/templates/serviceaccount.yaml new file mode 100644 index 0000000000..37698a4f4b --- /dev/null +++ b/charts/loki/charts/rollout-operator/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "rollout-operator.serviceAccountName" . }} + labels: + {{- include "rollout-operator.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/loki/charts/rollout-operator/templates/servicemonitor.yaml b/charts/loki/charts/rollout-operator/templates/servicemonitor.yaml new file mode 100644 index 0000000000..7810843403 --- /dev/null +++ b/charts/loki/charts/rollout-operator/templates/servicemonitor.yaml @@ -0,0 +1,36 @@ +{{- if .Values.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ include "rollout-operator.fullname" . }} + {{- with .Values.serviceMonitor.namespace }} + namespace: {{ . }} + {{- end }} + labels: + {{- include "rollout-operator.labels" . | nindent 4 }} + {{- with .Values.serviceMonitor.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- with .Values.serviceMonitor.namespaceSelector }} + namespaceSelector: + {{- toYaml . | nindent 4 }} + {{- end }} + selector: + matchLabels: + {{- include "rollout-operator.selectorLabels" . | nindent 6 }} + endpoints: + - port: http-metrics + {{- with .Values.serviceMonitor.interval }} + interval: {{ . }} + {{- end }} + {{- with .Values.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ . }} + {{- end }} + relabelings: + {{- with .Values.serviceMonitor.relabelings }} + {{- toYaml . | nindent 8 }} + {{- end }} + scheme: http +{{- end -}} diff --git a/charts/loki/charts/rollout-operator/values.yaml b/charts/loki/charts/rollout-operator/values.yaml new file mode 100644 index 0000000000..66f9486b1a --- /dev/null +++ b/charts/loki/charts/rollout-operator/values.yaml @@ -0,0 +1,83 @@ +# Default values for rollout-operator. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +image: + repository: grafana/rollout-operator + pullPolicy: IfNotPresent + # -- Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] + +# -- hostAliases to add +hostAliases: [] +# - ip: 1.2.3.4 +# hostnames: +# - domain.tld + +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # -- Specifies whether a service account should be created + create: true + # -- Annotations to add to the service account + annotations: {} + # -- The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +# -- Pod Annotations +podAnnotations: {} + +# -- Pod (extra) Labels +podLabels: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +resources: + limits: + # cpu: "1" + memory: 200Mi + requests: + cpu: 100m + memory: 100Mi + +minReadySeconds: 10 + +nodeSelector: {} + +tolerations: [] + +affinity: {} + +priorityClassName: "" + +serviceMonitor: + # -- Create ServiceMonitor to scrape metrics for Prometheus + enabled: false + # -- Alternative namespace for ServiceMonitor resources + namespace: null + # -- Namespace selector for ServiceMonitor resources + namespaceSelector: {} + # -- ServiceMonitor annotations + annotations: {} + # -- Additional ServiceMonitor labels + labels: {} + # -- ServiceMonitor scrape interval + interval: null + # -- ServiceMonitor scrape timeout in Go duration format (e.g. 15s) + scrapeTimeout: null + # -- ServiceMonitor relabel configs to apply to samples before scraping + # https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#relabelconfig + relabelings: [] diff --git a/charts/loki/distributed-values.yaml b/charts/loki/distributed-values.yaml new file mode 100644 index 0000000000..78a1f111cd --- /dev/null +++ b/charts/loki/distributed-values.yaml @@ -0,0 +1,71 @@ +--- +loki: + schemaConfig: + configs: + - from: 2024-04-01 + store: tsdb + object_store: s3 + schema: v13 + index: + prefix: loki_index_ + period: 24h + ingester: + chunk_encoding: snappy + tracing: + enabled: true + querier: + # Default is 4, if you have enough memory and CPU you can increase, reduce if OOMing + max_concurrent: 4 + +#gateway: +# ingress: +# enabled: true +# hosts: +# - host: FIXME +# paths: +# - path: / +# pathType: Prefix + +deploymentMode: Distributed + +ingester: + replicas: 3 +querier: + replicas: 3 + maxUnavailable: 2 +queryFrontend: + replicas: 2 + maxUnavailable: 1 +queryScheduler: + replicas: 2 +distributor: + replicas: 3 + maxUnavailable: 2 +compactor: + replicas: 1 +indexGateway: + replicas: 2 + maxUnavailable: 1 + +# optional experimental components +bloomPlanner: + replicas: 0 +bloomBuilder: + replicas: 0 +bloomGateway: + replicas: 0 + +# Enable minio for storage +minio: + enabled: true + +# Zero out replica counts of other deployment modes +backend: + replicas: 0 +read: + replicas: 0 +write: + replicas: 0 + +singleBinary: + replicas: 0 diff --git a/charts/loki/docs/examples/enterprise/README.md b/charts/loki/docs/examples/enterprise/README.md index d28b48ed98..82c0d28a2c 100644 --- a/charts/loki/docs/examples/enterprise/README.md +++ b/charts/loki/docs/examples/enterprise/README.md @@ -14,7 +14,7 @@ Deploy the secrets file to your k8s cluster with the command: `kubectl apply -f enterprise-secrets.yaml` ### Configure the Helm Chart -Open [overrides-enterprise-gcs.yaml](./overrides-enterprise-gcs.yaml) and replace `{YOUR_GCS_BUCKET}` with the name of your GCS bucket. If there are other things you'd like to configure, view the core [Values.yaml file](https://github.com/grafana/helm-charts/blob/main/charts/loki-simple-scalable/values.yaml) and override anything else you need to within the overrides-enterprise-gcs.yaml file. +Open [overrides-enterprise-gcs.yaml](./overrides-enterprise-gcs.yaml) and replace `{YOUR_GCS_BUCKET}` with the name of your GCS bucket. If there are other things you'd like to configure, view the core [Values.yaml file](https://github.com/grafana/loki/blob/main/production/helm/loki/values.yaml) and override anything else you need to within the overrides-enterprise-gcs.yaml file. ### Install the Helm chart diff --git a/charts/loki/docs/examples/oss/README.md b/charts/loki/docs/examples/oss/README.md index 0326de3f23..9a0a410c65 100644 --- a/charts/loki/docs/examples/oss/README.md +++ b/charts/loki/docs/examples/oss/README.md @@ -13,7 +13,7 @@ Deploy the secrets file to your k8s cluster. `kubectl apply -f loki-secrets.yaml` ### Configure the Helm Chart -Open examples/enterprise/overides-oss-gcs.yaml and replace `{YOUR_GCS_BUCKET}` with the name of your GCS bucket. If there are other things you'd like to configure, view the core [Values.yaml file](https://github.com/grafana/helm-charts/blob/main/charts/loki-simple-scalable/values.yaml) and override anything else you need to within the overrides-enterprise-gcs.yaml file. +Open examples/enterprise/overides-oss-gcs.yaml and replace `{YOUR_GCS_BUCKET}` with the name of your GCS bucket. If there are other things you'd like to configure, view the core [Values.yaml file](https://github.com/grafana/loki/blob/main/production/helm/loki/values.yaml) and override anything else you need to within the overrides-enterprise-gcs.yaml file. ### Install the Helm chart diff --git a/charts/loki/scenarios/README.md b/charts/loki/scenarios/README.md new file mode 100644 index 0000000000..1ec8692618 --- /dev/null +++ b/charts/loki/scenarios/README.md @@ -0,0 +1,19 @@ +These scenarios are used by Github Workflow: [Publish Rendered Helm Chart Diff](../../../../.github/workflows/helm-loki-ci.yml). + +Each scenario is used as the values file for the Loki Helm chart to render Kubernetes manifests in `base` and `PR's` branch to compare the content and report the diff on Pull Request as a comment([example](https://github.com/grafana/loki/pull/14127#issuecomment-2348360828)). It gives the ability to the reviewer to understand how the changes in the chart modify resulting manifests. + +![img.png](images/img.png) + +The workflow reports three types of changes for each scenario: + +1. Added files - the manifests that are added in the current PR and that did not exist in `base` branch. + +![added.png](images/added.png) + + +2. Modified files - the manifests that exist in both branches but the changes in PRs branch modify them. +![modified.png](images/modified.png) + +3. Removed files - the manifests that exist in `base` branch but do not exist in PRs branch. + +![removed.png](images/removed.png) \ No newline at end of file diff --git a/charts/loki/scenarios/default-single-binary-values.yaml b/charts/loki/scenarios/default-single-binary-values.yaml new file mode 100644 index 0000000000..78a1f111cd --- /dev/null +++ b/charts/loki/scenarios/default-single-binary-values.yaml @@ -0,0 +1,71 @@ +--- +loki: + schemaConfig: + configs: + - from: 2024-04-01 + store: tsdb + object_store: s3 + schema: v13 + index: + prefix: loki_index_ + period: 24h + ingester: + chunk_encoding: snappy + tracing: + enabled: true + querier: + # Default is 4, if you have enough memory and CPU you can increase, reduce if OOMing + max_concurrent: 4 + +#gateway: +# ingress: +# enabled: true +# hosts: +# - host: FIXME +# paths: +# - path: / +# pathType: Prefix + +deploymentMode: Distributed + +ingester: + replicas: 3 +querier: + replicas: 3 + maxUnavailable: 2 +queryFrontend: + replicas: 2 + maxUnavailable: 1 +queryScheduler: + replicas: 2 +distributor: + replicas: 3 + maxUnavailable: 2 +compactor: + replicas: 1 +indexGateway: + replicas: 2 + maxUnavailable: 1 + +# optional experimental components +bloomPlanner: + replicas: 0 +bloomBuilder: + replicas: 0 +bloomGateway: + replicas: 0 + +# Enable minio for storage +minio: + enabled: true + +# Zero out replica counts of other deployment modes +backend: + replicas: 0 +read: + replicas: 0 +write: + replicas: 0 + +singleBinary: + replicas: 0 diff --git a/charts/loki/scenarios/default-values.yaml b/charts/loki/scenarios/default-values.yaml new file mode 100644 index 0000000000..a79baee503 --- /dev/null +++ b/charts/loki/scenarios/default-values.yaml @@ -0,0 +1,16 @@ +--- +loki: + commonConfig: + replication_factor: 1 + useTestSchema: true + storage: + bucketNames: + chunks: chunks + ruler: ruler + admin: admin +read: + replicas: 1 +write: + replicas: 1 +backend: + replicas: 1 diff --git a/charts/loki/scenarios/images/added.png b/charts/loki/scenarios/images/added.png new file mode 100644 index 0000000000000000000000000000000000000000..ced9f9554a8f8c6518a70ddff1e0a5825d9c8b72 GIT binary patch literal 91760 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%0D~LRXwmLs;g4?LP>Qomfplq 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 literal 0 HcmV?d00001 diff --git a/charts/loki/scenarios/images/img.png b/charts/loki/scenarios/images/img.png new file mode 100644 index 0000000000000000000000000000000000000000..81ba701da26a046040e0106e3be5c0b35a1d1f8f GIT binary patch literal 39609 zcmd?Rg;!hK7XN#y&{EvJcxiDcQmie(4%R@6OCb#ucZX2HwG@g&N{ST^?i34!AjO>& zm*5sG@HXe%d(XYUzu^7e8v_O-ve%w#&b8*6>-$-ouos%j#CI9){_)2j#HuPF?LYpw zQTfLo*SrXC1MftX&U*au$GbmNLC;=#nQk=RDP`{PuD*QLVQcLCP@ulsgG+$NQGRyV1Y`HE^Pq}W-t>Rubx^E}1tzG?i2jD$$=Oxb%s6IX# z6H_N-3nY6^$OiJgNHn{zL>p%K_v^sCXpnsj8~D**p9YeR#O7@ZbJ_%5eK(K{@r0sM zHW*a&_p9eGl@V2)pYo{x`s{h=y2|ho9Yy8euL!}+VUsKh2;G0(D4zp71yLEk`>)%& zbK3ac`~SyHmDGouPL@+UuqDlM`6%H7z=!{KQTn9fW-u{h_^Sdv+D92a`8<)3xg;Oz zfBd3pkMKYz@OP9K=u!{mXbHkfryM#u;aYZ0Jk=#F+}-_Z^WOHHO%&u{^a6zgcu;&|mGM z&EFD<1=q3X0^dvy4RbCqmGQ9M_8F@W*MjZEbyPRwH|KNZ@z%nYJxTMQ6i9fFCamD| zMwnCG-7qabg!)1?c2rU~T8f|o@k`@}9_Sy78S^uRe=)FY>VnZcu5`IihE+3X_cL%* z0hhP{vovQEeaDX<@A{j9py&kZTsX`JIix8~Z1CHqkjXW=A20OPXu;|~9Wu~VfP8%n zN-IPpIJU5pH9Bp)+4kS|jY#*Lt@WA@I|I%; z=STF+4kzxV78zC31mHXa#>$%GmnbS#P(*KWE7$%M99 zFqpy<^K*VlXu!>QViNEub%gG}y&iX1raGKMPYtdgGxeoTmGI2?JV`X}V9r{<-QJ}HOe?aHP3Mu>kaU505piIC)Y zXd{1+NlI-yR(zOB?YmWLReP}yzw{HfpR6duI=xasi^I>RSGr|jN9FJn&W_)|U+=b% z!b+FCIBlRD_F5&d%tD@z{e`@v4aeF@^MxD>2&c&_h|%7C!97mLqq(8KM;g{1y~%O# zZF3bC1F}ycr>=y+q)?99djm1B3f>vki#b-B{pH?A-0Dd$HpiPnXBdW@UtUZ)oTwu3<&CBb@q?-`;JrlWsl3D*Al< zh#CHs%jlvqtA7R1B_=TAUBlB8$LVRqYuJiTT6yra`}PNvBW1e$gf9kF2BWS-x_0;_ zN9G9keRL3pNr`W!Fos`_pV4+aN)@jzL_;@xl^JZcJ2Fi0e!K!D$^NeBx=ej(u-coW zjjqIa>kIpSJeSQ?mKiPekoz!EM_fYXIa6KgmzXBChOpRbsPDe*2QPN0Ka8$Nd(uT! zoXc%x-H|RNSUKyDMh>;vTzDt@i}Mqh1XrjDYVq_yfx42Pd@^_`Y`EB{ zIkRS|N;=$?2W~d~#1Tesp5lDyo}E-KR&LqPn`7p0kMx*1oxcbor3qp2c*%Rvbl8vK zO_Xn1?BWKiJ*kp%ov-bfHd=Xn=iyO{xR!i*KGJTN65S2O&0M}dk*2D&HT6O!J+b!@ zUTRefS7tFP)A-dq=|aK_Y2JXlrn+4Ob!>XhzVhh5H}uOs<-vx56bx*`NF}VeZEswZ_@=o8`S_E!0zcrZ3Y(*Ul}r`1cJ>nHF8!(rTe6 zCVmst_TO51ek$Wt+@%`5)_eAD&Edc!y7HG6^!WOt;0j3yhDo7*;##eP7D?d)ha~9| zubtQqsO-MA>9}sf{7 zTW6~jAI#!wk}LY$g|&+1nY8B=^2L@_hUdW10ttwFZ2rAs47CIX<~ih%$`3Y|gP?l>Dm#{P?k}gsX9PC2 zEiP!r03*&$xlB{;I;uVrcYT{=7I#^R5ky~-KG`^)KcO{(cPJnqHr+&>9R=$2y=_1T zc(<2C+#r=L<}!Izzp!;{3FZwyb8kaFg!M~LTxGG)JlTum1^+<`&~jq)`BK6`+hg`- z$A;Ztj7e9G^Mc_Zsp7EDGh6aW1=e_TWu}>*wz$jVeiwuBblf1iWIAK(C!`z0ZPUCzGZQWdJ1NlcPKTk| z6ZS*tEe_$$`;7?@{KWgqAtyH}R=B*!QU(3e>2PhC03RjH_2LpU);#cecCVFejgJUT z$5FD9VP=%$ZL?Iqq;CT6r=Pl6EUz5-eR-<#M=-*}sYA45KKtW9@O$DCyN4YXl1fP- z;_N}2JZ3VLisOMOVJAMARIvWc%8xd*Uyqk{u!*ZxZ*8`9Bds&xqXafot60PROIP@% zwV?;5H3V(1TkIoHN6bGE!W!_Wi&>~_KtqFmk@=74AX>*H%jx!g^da|Kg>Taaon{d^ zDd_=SucG|-Kb(&UH(MJ9%-4i1VdC@r-FD-mXpg9eN*)b)P*m#M);98!kz+3;r34<` z==%sAO>t1lSm4p*y{vY5vN0aV=w0ah{jg@VxdBP%Ivl2PW`Q9zS8IcvpF&`wP3M^P zeImhP!6UifdKzo1*<_R(Kq9K*(C1;jCha^uv9$kL$<#N4;yZLAUJV12H=lO-vA*B59zP)N5jJC=4)(meZ8uBsQA?P92qh8~W_T_>t7hH=WpRA$H0ii!;d@RJ zIcu|lJF4HZO}+tLDle#RluxV@tJ>?k&)>PTQ?&qdo(ax5wA?(;cu3Q<&_L{NA`q9S zA&2S2mEsk$UKZ4Za@yu^v5)Y?qlxKCAid9u)mUfmw(jI!r5YP9qP zD3g%4Kllt|cOas_mmyJo9P{lri!%+wbOQZxN}r*jR^><@Svv z-f0e7+&gQ=;SHSo{Yj~4O?(tNN(g2th`4Q#)}kf6Jgp*N+5Hp)Xv#jf7H>dYf(S<) zx=ARy3(kXi0qOQ`p6&r{s^-FnoDQrj7c(8LE&J-es}k;9p9_Mva*4s4={tWQkC&3d z7y|DgLJ33=i=Vy)=oD8cGPB1L@(Z%Fr}1%I2TW5Td<5bFKeg46!+k)jf%~Llx4{st zN7cgMh|*`j;!uIBmrQb1cJGzmI5*i@3A$FTNKlIImMuVkid(@0tcQ@LGwb%9&xk6d zZqb-WyUSaeF%f-8P81?nzsbKA`t&oZc+NJ#aguY2aq5gN7bv7aY zkiBotq;H>HYb~w#;3Qd6X-g8STRW25@6J>(PB=iY&O+(iwKyDwHCbiD4V>(1D5?td zN#h{H1?9cuI}&~4^1Yazy>EzS^ARe?-^DKL8DYqY`N2V%jF(mE>~h{$t1p77B5-k- z7EiqksxYTarD*?p)l6O5RY?s3ZMFsxrCQ3|zKCX(Z{o5SsNZQNNT8~1OE1$71RSx^3^gw%&0Rf=L|g^XZym0L%p8=+Ey zR*DPwgt0|Aaqbt5EJeWa{Rg4vWwIwj_=)I5f$;>A%&qkV;R^z$& zwYG?lm}a@nLhZN7?-nFl-xa5wFGD|i?VqGN-(!j}>x&54Yp?MO=~iSP^+zK{T`B^nz!3aZ!DOo~t*rm?n0Tng5& zr{pXwK^;yek%59w41Eoh^xnWaLHuh5%Vwa4PdsU^&ramWIy@^y2H-lw^PGX`9)r2d zTfp*pp@rKJA|}2bs(Z1mG%Vngbw(>VmX7VebYp!~f;ZBPPFDQw++CPq;x%!QBgPG* zF8b2-MjGM1NB(waiC)SbbrgIUKk+M}ptv^EFJrH8NuqqMeD>zUfUY>D*RcIrwG)ae z;%!z$shBQcPqX!Ej%Q-yg1TKbWu@3X(TyO?0my>cde-3&|2(?~kJ&UoKExK~(wte0 zzg+iE+4IdG7v^!;whr`$-!~2kEvgg9z1lR9gX`VawBZ$5Gu4m~TfYTy)O<&T(V#AM zWfA#gJh7m*8r82rIumz$sR30Z2Xq~EV@hVzY?gv zQ=&e6y4%giVc4^seWS<(F%d0Xb1^Qo{%8HxDz{DBE+qlWOR ziWJb}<6u7?H=3vVcCW{d9!eukW9q}FaDQK#+s3=AEK=7QE>YAPDtb}^(qD-rscqM4F&7r?=#Z?LEijdhzDgw8Ri53Lx zZ#OkVo3HBNZD96)9f-B!+^0Ww&m?z0+_rA#j7mRuygm|~bl>-4c_ka=-rrwPvH)RXqf>!%1U)H>YSF^Ro!7j|8G6DG>g{!ONG@)lZu0oDFe4; zh@}hp1!>7+rP?x?5&tXA;&kH42_g6O{`#Hp1ozB=->ZxYYp*~b`7TqR5|8aqp%zho zVdLV$VB0exh|(KduWjAoL5?U)WM!qjS4sKCG?Bogfc*<(arY+Q67JyR-&5>tF;43= zi!0ICMOAImSR8`7*IfU34rDWDZ_Y@f=@~n54`O5AiZE%uybKSDVBxi5CE?*Mb2A+_ z^~Dn}Z3~GQyc$j^mEK+4Bvoe8mL?Ij5x&WR4&|Q8(v4I;T$%6fYh!OER80LDc79@i zR{G!pzn8j;;uFf#zmjg)QqY1U<*SJ|V8CtUZ*EFrI+r9!jik4P>g-3q+s1}XminTP z%X8z=Kc^5OnLAH+#?@L)L7`l5&$r(lB*jfb5?|CXz4si=+MM~hNk=wOHH!jgNJG!m zxVb;AlU0fx?Z`u^mLuL;MB1-Rj^Tr?ZOuQN8<|4w1O2 z^yjc)h0l>j_2W?;fr`jf*Or@%S#u2f47SNvu3X=$S11>BHSD>QiB1}EuaEZV(e1Cf zFqf0G$yz(yW&wm817=`S{qS13Z`d{~D7Lra)NO;R)h*)!~e7sqSMrW3nVb0cjRJC60ISiL89b;&Dv;7^*6%n-*IR$pz% z<=o20o+ldD%@GNSrTpEBBz}{)G>f zNq+f(Hi0=VibK(FvK>Uo79EF2nxV=+{T;b;%R-XowVd881;P<0_NZ~B7Z@E87ksUq z0W$+>5Yqf3)@qNP8KPJ8NRra9l$jE;%c%U}LGflPl!MDgN;92>6e zEVHk%Oyh_RD1KCQl(nWiJjrP(<~Duy12L#NUma%1m)6r`Xg{x4jCSOf6KKa|5ZUZWLNyK`%%P|lA_@;CX0)2^%z+n z=>W9sb2gUc3&be*vIrQR^=htgfDRhZpfhD%2c!HX?m@G_MGhmN z8u&o=hMDw8r$24@V6)Q62;OXv`+bv~iSRy}Ad?(ckg!$Atu18gHwRLrDY}=t7!zh_ zYD{yeppKm7&T6#)_T&Z$6FnWFoONtL5bMqiA6}ztruf+^^I+74o2eW{p0H z(0^7@O*JQ7GbirXCf<-kc;KXN zUzld0q-USTizcuNKP3-0j1&P?aOj?3P?M9w`-wjbnJK4+7% z#Py>sr0UlmujNVK*cHC*helSE3PEcp%rJ_dXB0?#z$LQ=Rs7p@zFR{&eGKNpl-=Am zs#cWGl3!%=ygXKw^Sg}=3nI^yn-A1UjecaAexBj^mpV)i_FarsKFuP|QBNNH49im* zrj0#C3q!(LH>!+wcVhw?xHP%}arTNxWXy1fR@+U?lUCkhMP`>(GO`XtX*B(wcWK~( z*CU~TtTRmZM$W%yqyEbbLpg(}tl0nd1m|wZi^@RRHZCys67WS-#@$6t*he2lQ1hiSm z*u0Yd_V1JjTT?UJQ)Mjey%Iwc?CTy=Rs@{$1PlT??3Cjr9=*S+InM61=bw9&f}P!3 zRudD76bYu3>hhV&FvUO5P8hF?^mn}nt5r`x^Mz|+Pm|*WY(P}2WgA0R`um1GqdlO( z7Gdx#f6qhYu$BMop&X5l<_m0d0%F)vZ)6sCfun_A9ON1$K(3Gk+M&-X-Cfri(EMp} z)2>=l(tCPMdtc^(kIdA3?wPB#SKUR1+LP4d`|7)kYkMeqjz>9Sq?&}K;x^JD5d?5v zY^a%O^} z#QXwb70eymv4BcofjIicihrv!dF6x-$IBx}THyC`wBkHUV)lWl|KW(m0xGIwx04m~ zIc9=4H%(i~tJ-14Q1fnw*SHgY8<3)CJZHVFl6gKz1r^m_UYte5_$_j1cz@!FQ+^c` zt`sySU}y9QVb}SXrL2Nwke~bU7H3GT#Mx^z{Kf0UE4EO`YM1qwMZN#Bh-S*M8PCA} zxtS-;>G|$Tz{N^Odl)4nIuc+_ir;cA-58hSp4zp^wW(%??y+mXS8?JY1C)?9QVDlu zZmm5Bo^S)g9{ zPb~Wq2!`P#y;a)4LDBf5;u-|h*HP4}&u)UpB)VUEyFp1VvK)^E0wQALyWNPRab4-- zcc@#-aWUpUsx0v@Iv($o(^U#JUmyOif$8Jg&c~E~$@}sO>Ebc-7~AcbH}ERhN0f+~Z2*OP~CbA=EVf(2VK8_lgk)u_H zwnds1=&bd^*AN&<|vXsa;mF~CK73EG*`R^u@?J6_? z^*=AvaN`Og4teR_T#e_|32!5nZ=jQh`AGpt3N^Ks3!kmfVlVEO#Tj#G?d?uwMG_+4 z|E?k|0sR9m8SSq$DjNjN*|7BoZV9l?0AoIbvaB_xi)F`) z9Hi-g@ye(wU|#?r;<<@e1>yK3Ota$CQD|n~(j_p0a_H#j9yFcJY|}i_EqpNRwGbsj zafXcXFO1#dU_DV0mXy>lCNe)-6l!)*{3m{KBj)Uy0LgYnhPK2~RAmx&Gyx<33w+5u za!aE5q<22Qp!i|u>ZFyAklbat!%SVy0(V*cexJ0&;ICUL`}g;ZZoLxw*D(fW727O; zs$XwS2<114rH2?nBR5_ELbeKVBD34CXW1(0z!05C_sUK8u$kBbhL;1=c0oy#A&f)9 zxZhb+spet++kc&&c=BmfMJF}yF&!jAh;!Z`QTFI~p+jmVV~X9dc|Uzy41pvxZiX#& ze_5(K#E#Ma#cO?TN_Co1|F1PB!whb7P&*rwqXY5xb=ys6RfIZny{^CC6VL!lgBiMx z(V-?&a`BS_LX>mJJw?S;A`fxr@9wNWHRR|G4V$d%l8R20`H5~yHu~Sj*BHS>ZSx8BQLGX|oa28Q;Q*(S|Bn-5^Lj)7 z$265;OAxij-$zlv1cMCLoHn9}aPGew&AVY8B}P$6|NmAq7K`7OG#(iuoflBSyYET)Y zncCH{sZ!tR@`ju$)YNac!{toM)!|{xZmcq^+Lj*R&!378MFU!ZdpSK7r}jI2Y2oL? zaB;iAtbPB_wU%J+|m+FghzOIYxnZk_*Vk2W`f+Bbi(iREGL=-yf|Oq;fV0v zHhSSO7CjA(ER~zjgBT8)&y+dMeH>URoRILm@Nga;=%mJuH7`nB9Z?F__{Y?<7*@Pq zT-&hb@Vt-@0d`(ZV81IE7-%e4$#B{QYfoJ7m|A|R3GCha;4Yb3lL<*GtFOTI+ZP04 z%sjZfmGJL+m1!}_w8r1MW^mI}FA3lQ7q{S9{NJ&duBPLqk}vb(id!PHmn+S0RUx`x zBrtyEHH(92qbjWlz2=MRj`?T;SufWp&%Oe_{-gAnH$DAE3z86_beh=arFMKf; zCwmS3p>sP?=^hSGP*dAU+i?|K?)ZX9sTGYQgM>vs2%h%gVd8N;Et`N zfZTpfAeeL_wUtWpY~a+`Y3iczyvF_FdLyRqGnaVfiOxX=ic0TQD{ut)t-@B-+_cd$ z{?P&iO{|zbs%L(MfaRqU5q{hZ<#pcd)j3D|R)OR6w24*vt@?qtwU0CynI}yXO~mI=?KnACGufW57}(G52dO-v>ci?6lJN z#%dl4gDDMjMbaLh1t-||OokcWyjR}am*lPCJg=G?KNg8q=FrNY+FxJEu&q0?b1Y@{ z2D8NVH-Dw_{BDq@%D7x^_xh``rUFGXB+fKw4KhyzR(`X0Alhd$Y`~r**jUlR6E)cT zIGe2{`j#^d3(wM9Y`$1zM{&TP!`Ih3FHW-(>}$ppT;h>SEqJ<|sYBm-iNQ+W+rh^x zd^m@@eFM2G25$_?ED9-<+0^Y+s)o@#mJo(*pW2A^%G$f@lJBO~^i^CtV* z%_Y$YY>WSKk7&Pmwy-vic;#dj5<|P9nPO}zZ~1Pyma$bz2+Y<7iI$!lgNmgtXwH0N zlj*P7l8FEyic&2pH`%v_Bxh-*b$V7`)?+txVhLfn&4(5Jq=c^4Kr)Mp{~80l;0T?a zg~p?>Cmy^`Pg^TMKedl{Z6sxZ6MiK3?j`gg!|I~hkUQJEv^nvaPdR9D zt~JCs*aJ70_}zBbK3jvWq8U-4z$erWD~dG)b0I%P7n0u`pGq%(YWu*C;nuA(2Tf_+ z3w^uaDBrVsVNZO6l+QS+aGxoHHVt`p`x+hAyrnGFv9@SBx}Wa^7fL?{4^E;I6K~%m zjnd-vw&*_|KKmiBCRGBw7i7&V59Azv7uzVbOZUo4jK6lG+w0J^7ZRdT3h_6 zee?SL#V>lMyqyMT6~b-rW88}(=a?P0L#tAS?($6K5(oH#)!e?v{c*21&9+T?MY7WE zx!f1r=)15qgFsjDWvi6vg*MTn1vveEE!yXE;(&*#x1eY`e}@7jZ(TzhGPNZ9R^ z^M6Zd)ZHP7Q)+ViCVuX(umG(W9;9=x*J0R_^&$24>O867<+#hrV!J^P_hJ22(2^T< zi1-6Ts4zqEJamA)dUTZss6sy1BRW8vJq0E9-t?^QJ-e_WnstiGS5bIx9#o8{Xj_0MraL2!`N0HWS@?a{$aN9WK>;T=fsyl0a zrs=V2u6$cLmd4oa@^A_j`G9tb=8@;UYuH3{H_Scy^~Xy(+Ax!zd_T;jSwLZU9#<45 zTA)KIT13uqa4}(Nwk9P1X~l?R1bEg@A@O#~%Y~?10dVRNUp|pSqhmf@o7i*h{UD_`z>P6Nm z^25n|{C4L>5FpP5ea|;c8aHx#mA%LV%-=-rw#X5~M}@RC?~$;^a_^Nn>gEj23r*>( zTPj8|dyc1%DAU;ojCN}+zI%|VwNQWitoXzCFa#NgT!~PAM~#}9pr5BEVr|Z+ZYZdo z{k5D3U7PeQp?dd2W2Kv23p}Bda=U%uIhzf6hbCWmU9qhy6P<{Fp`2z}4u@_j9%2#! z9N7%6aerewF8;U3s`dtM4*faV$Dbi#;_##jFHRv>IOZ4L zW6sJK$bfU(id)+bb6$}CTaGdkxu#M@g<8DpRF$E2Tk;IQ=!)my>6T)VE}GNw$SrK7 zpo9gE_(Z!oFIPrSNA~vW<5tn64E_u6kP}zjfIEYPeWB3OGlhu$g|8n;t?l4GsLok} zA3W);Zauf=cHxQhNkBRi>-wkMFfGjD6A%8SlbbhVg@HeIzi|Wg+2{S!n_aJ`SMlC4SItHVq4t5eMBBl1%T8oD+&UN5gdl(XKlTgn+KpqvyxzJC# ziTYT2XkyT&lIrOIfutpag0PU_U)zD64fch}G2*9pzWK$+vJhUJ1|n!yk|78+j^H1M zCbzx^m}arvi%N2hcHK7LV}c7XT_!3xdB4|`_>**{ZN;h8O}vYEu_mXf0bn8@N=p1# zI)8#uN+e*S>4=NEN>P+ZC+f`@m(6!l!VxkCYd;qC_LW_P*g493qv;Nh!403^$6|y^ zmv)DUFHgu?VVf+S7IAOExotA7p5Xz(ufOB29pCAEpa37me4NWNdrm0KusF(q4+J(I zE_xODfb7GrrOKIq*lMvqD#PJH5lQH zGNXqvEsu;D)7s(138k8wow!#YB1QvP%S}90>(Ya*6|OBZi|&Q>hEMX(?uvu4iUn|? z?uZ-R-Da$;--|xL!6^>&Nn*iK&V_&i;R|~>jdl9P`i8UO zjvp@qg*56tU)*JIY?W#K`Tg(y?+EOzwcWO`G&sWQl!J)a+ErqWY!4`bT~U|GKz_Jk zk2EZ!4pii5PnxNSVa=7gg>Mv2y+!`i_h z{ndvmq}OhWL>JMj(k*xpZ>Qz96O(Jh`+;T&^geDEK*gnMOH4>{&4M4PHzk{F~w3;Hq8-+J~@@Kh+=8N!hi#qH=o=_x4D`2 zd}K{|${h2ooqqREW=j{z*^e@;kfmurYgi9@26>t!7x~7QTrS>`iHHs4@LwyG`fyp~ zPu-rd824vlK%SOl=Cy#V^t9{`)H(F9=L++1D@s^5{svBKNa(|rC1{k+^Fs|WCMAqS zB}C7Am0ci8=|5uC{hy;2n73cJ?M}owrMssdQGzBw+83u@;0V#JdKQl|kJw2M_|DD; zk=(qfDECcNn$_Is|(Ksq4ZLnnv*gG@@scU1ZH&bJOUQM z3k*(RCi0D)1Odjj&PCoFE3%glo!=rNo1;fIL4Y;?WZb_sMnWsq3@P0e4S0~<7_ z%hJBCfij6`BSydCLN0HrkJ}p~jyA=H*T_)Arwf~J+Ndkz%bPLu?0;Nb?6}?lc1&+S z{WCIwwA`2OsEV?nbbCizsn6$g19s0=8jNL*Ba^463jTEq1K>=bx@<g#KJ5YNaO>7LQ##!lk9Tw$&AK*0v<;fqCe?Bno zJN`*kktQ143xE@cv?PbYD#}XJJYlr+UBjcc1rn=JU^YVFr7BUeoGJ8M=%zb8a&1dU z6}ho~qS_Z^IXehc1Z6<g=`niu>5cS7B;*xa5a2FFD zK>;jHWfsoj-nUf$pH67WH#LY+t(?HK4KD33~UTK z8|QboqnHq*635zAVy^?y+LYJt0$C>RpIAN#Gu(;(Ku;b_)}EzBiAAj6x;d&kJYQi@ z{g#6!n>gsvf*SsGYI%lbv6Q)P|70~UN@Eihw#ID_#5pn%exP67i%&j0B>Uv` z`eTxoQPIzEvqI@n($T%kjIP~{aq65El%~U8U^T5j0?sYxa9k~B@E0|w+p4H&V)Iqp zKt!zJ&RDp!->dfEsJK3NjmEsFJKsExu`wMWes;gPRstgiW}z*6?&!N+U=+$pk$-6& z7xa#(IhXmlLVSt*oNLU4mp@E~=y@IyOx(N>uE^gg-s(R0>*hy;BmeYXHXGhtA+u<7 zvu}}F8QJql@qf14z0LX%1kJewcoVv<`1um{0JSK+@6h}$GT(dmHQ)vd$yBlIkO_VL8w5kSwSc0awn9JiD zoa-Mmo9vA7P*wJ9tXIRuA(qdoK~)O@%`Dgs75-y^cn=x3a?7(Nv|hyq0YrAOE(c~T zjZ0aYYnEniGYh!)zVFxd$Y7$tLdW>KQDr;FuB?C3GXhik6GM3q$V8_kO?T^~)lLh9 z#9sAd5aqFR8cY^do?iAl?iuVj!~hsPN#nWKVvwUUuHVm^`XEvu{^LyYOku_*vgZeP z|M6Z?eqP75Lz0Kf^^6%)%N;X)`Jt1V!J=!-OYh&eB)FwVChJ7~+c{75#Nc|;ZQnbE ziNeAlBg zC)PBFC`a2)Ivgz~J=i_QVv2+BuXILyA**Ziwqh#R`u<1s4Uz~gN`}5Lj3cm0Wl3=7DJZmO$r);i ziF-^7mv0X-2rF{}i;Fa{{p;AzH6H(L*5eeMd`D7go$_&J-YrXwBXVp!dB>sCOsAG) zIhTD;jqY4-$mAUJKvLeV?H!6J+%a~cvsW`kCVNRA-xTv?`8WsKIEkmzA6+vQ`xX*( znd1^|yN;Hr_^VrHBdhk$XxFJ~yXjm)4Zjj77Ak|J7o;dsBLzpJMpId(!w<{zg2p2+ zX8mdxDJsdZU4hx_3BaJf{ZCkWrSEQn_=Rl01+a1Cw-SfzlUx|wm1IGd!cWHHQ0lu44iefJlBI9Fpp!%)y>O`G|nKs&E8 zZAg@W{U#H8;GcYk%Zm2N9!7G6_2cesZ$niy#-YD{Jo9`ZI4M*o!+w2=xui29B|dMd zT-5xDd@#k^_&Y=A=RZFBeYO}qEdK@}mU7@m?U8KP4E1~YPEAON71@qu|4Qut@#8fl z9YcV5M;z8WpYl)#L;#40!7sCfFaV(qEo1%EeK&tqc_9 z75QL<93<6gr7txRG<2GC7UoiO|2Ff?iBy_tEZtN?4$m2^Vp3!LM7HxHSwd5)6_1k` ztMB=)3lpceP>#qgXpcKY9y)0sZCX7>vcg1O`?>`WZRcBFJrZFOBoSGe)!b=}yMG$` zdtr8Nx~|bP*jMAb&M?pV{iw&hu@7d0QS{y6N{rp<*yR=B)tmhi8O#whvMy_CG7}_6S*)8#ir0!B84$PQJvB-RDn`q|F>9=z&(>3I#&2dqQKtpmFw@F57(ht=BlRP2$>RP>5uY9}=)8GOI1mAVnE$B$182ao zm$(AqViQ6(AL{=W_kCEoj~5unNo5b5qzv?` zoWX3Gyh<7nQ|1A@aPmsQ639~#Mh=a=f_*4JurZt#!&x?a>C5c*ltWK|#|8UJ7${zK zzrINKq+Usrm=N5O&X4a<*`2g4p{6x?iSRs zE6b&^uE2ew%K%ZOqhq-@6`vp7u!~}Z&Yo+STWg0X@Fs~UicXIMZ`kjQceYpmuolJFXT8lg2nycJO02bZ(4qCH=h=?mG zum&G}0z;Bn++0;G8hCVtiu4p0vSp3E*5E#q$MbRU(;@D9ppwA|3%Z)NGl}-SoVkB6 zt|tNgsM9BIcD^@O#)`Ge#cdoha=H#}vO7OogO;kUaLQ8Ii?@|+ApTe@2EvtDKefN0LjS8m8@X*ztF6hc zjDq;Ct;>sV7C<>2fwjVnzx{(S)0_$pED&XjgADl4E7j$d7>HjGaalejPgP#r+)`3} zv#7A5IQVzcuRYjKl0s_20%)#uTGC1r={8aJmR8dA)Xl*E0NI(ECQ#?h55SH+#ZZVE z11K)Uf9P=xE$8*s?8KCy`_V-F%fQ3N{LCbSWvqL!xu8l7>#;)bCiOO=>eQ`hwp4L@ z0i$ouPEMl`67fq?K+|8^8y?b(zzi{x(-s%UnS-TKX>Sfgv7Y8=% zXo=wdS3Wi>VM(X6t$?Qxc{{UP-H}`7@k2w8^u&wWkQKJ=$j^dHKanmy$SupKq}PjC z$L?+gyq>GFZEDPx~wK7*P?rNIpYUdt`l?u2>&%VJl#WU60crtng zWFJRkLPr$XW8!1`Z2{R^|M)i|2g))viqSFdRE%J;Id||>H}`+~@)Nu{;DH&d^Ssvt z!3nCE*W}Nx2-|QvTONV;b+Hr_ZoE+Dej0WgWCIZVBfdIduOI&95nMI=essF6A5IpR zaN?7{taGb7*#1hDj3n~`pIzY$zuDl!k1nrMfoH*`RFb)=0OcVa_-7Nt`rC?|SI2jz zy~k#jJOJc6T+#Q{!72Sa582)H9VvAAVU>4vB9?rQZ-q&_e_j*k8I65@*rhya!KhE^Oij&G&o4u};6CpFpLZFw&tuo6slqsVN;|5uzJfYC#_TAHtohnO?* zA}cs|^dHJ}E%u$y^`zTbE*Cie&}<=BW*&+s4XzP!dO9Hyt$dg1RsdhJjF}0*E35T%ER4v}ERbTY^<-QL|2sK}!}Ou@Whux2iG z_*pD7FMBLXFyh4zr=HO-;~P61F%Eyy%2`36=*okBi&eLPl^T+U5S0L+%tH(O&cb~t zy~_)>mA%*MfZ6Or&wA>5_J-PQU+$ZU@e`HYAigpD%}EvtAqGLTWWF2I+y$YE$|F%! zp(d%NvwF3oV((tW`1uoO7N6zuZ9DBf`^g}Fe)~OtAf1$pbrcMA#h4xf)u?4>`VQ7& zrtVPAo66Q;sY6xoP2JBEXD31l({Vxq_Sl9On;oiDx4bgtds zas3QA{N$Q*J^_CZ(Wuj;l$@yYxc7SC;rh!jkagW*_L~oA8K89gO0u9|Wz4bKzv@b6Ik^9RW_;bDfQ4$E^w)bN~ceEPzZaetI|VALz)ZHM-k zKvUaEi!n>1ghOi$8~&Xl#pJT8h0i`^fa95Q@W+t`GvY{92m!R^?A@Vw^?O;$)`jwn zd(+=ut^0qm?X6G5AcHB}AD2N!na zS=U^ENI$&!{4KYIs+z$`@dp4kPafWO{VFN&W4BDD!aUmJ&tZ>ziBC@mzTSIabVe+> zY1f}6Lq=KI1S3<8QlupoWVbjZtGPY*|FHL#QBl8N|F23aIfCQ>0!m1SFmw%|f*_)F zh)8!!42aYqCDJm0G)Q-MgD@aNcMdSpB@A&se1G@7zW4vEbJlru9-QDIwbtje=i2+a zUVFda*Z%M;sMt&sYRAAi<$4B>N`Rbjw^spKpld zWI$ZPc!$VWAC=!P6|%Q}?NSpOPL5f&Wa;fL3psAbeMCb1_HgqE@o5YxrEO0!s5xv_Q-`EiVj z%@ONOGvn>Zbm+9<)vq$n<_GfuN3HG0 z4jI1}@hmkM4wfDxi9qON>hv{}QpxZg*^OsYWq+qcH3u}ahNf6_A-Zf^uhDixUaF{N zSLSsqX0jRq0)d3QDj$Uk%K*hq0fdaSH$`MXc<8iali2F2>6*$T0bKN#H31F%?Dg<`-)_%2yzq`UiKTLNWMdUWDUgL!DvjXe6s7^$bCiJB zdzM}{!h6Vpp$Plgw}<(6PM=IXp}h5ni?WJP@ZcEajS=tv+xH|fFQ~LFa9)@jEnVR~ z1mz(SDbxLB=eadyX_yo8#JXS>2wZ^MN_DwFVOzR&>@bw)7!3>op)6me7pih-iIq%Z zhv=f)1Ys%-Lxa*@$Z=sny^VdBU<5WUGV`Hjj3qwML88HySqKjawE+27|vpqycb5dD2*55vH zR-J9@vSKP@)L{2XAkdUuxrlmLUA{84sK|3F(zBe-s~d$^oK%8j(*wfje?4=v0t2Kh zxZl&Z*9Fa)%z!YywsV;scQ?dSwEw^+o}mvQ<|o?-OARM{RznQF833HQANvxdasOtb00PfZ)NG9MNpOSQ|Yx?UNP zCN=DlK}lwz?>hepVW6;Q;NCJZo{6gJN9B7x`0PXVKA%H5&98;qO^+uhvmL8d0tMuw z)*zGXlE1h4aM6!kyBqVKDpU0g@(B@)fL9#l$)R`yuQRC1+Rxq5X5=Iawn?AxD8{Mg zFo42JK>t*uKg^N<_=RfCrp>_PCxlTqTWEvsL_0w9I31{JGBsHaP&kNov1p8PH1D`& z>}Ms?$Ok6$?j|M9z|M)kN3^kKTN-GeMKfR9nFiLl9cM)bR&Z;cPUg(zcGjL2&eNPo z?$0Un+#9IKOZJWo&LU?42EVY8`=6VQN_mdIn7uvpcR!^ti`v*%Vnpy=&pgdc)nsbF zu)U~>Qp+V&aE=O!AcIp}|60`@bngIFrR?k)O!&Lax`bb))tkpWDb!*UNXmD^D(Ux)+jS z=QH8@u8jO-fZ>Uz;>?esansG$FkFJHCj zA9g2YAvpAtUv(lLBe;F_n?AV%!Dd?}FRqqyd-CT%1=<#-XB+~wE?xOA3_{!}(ATd} z=aTZOh%<*it=s8+)h0Odai#~y{(_5*cJyK3ke-OI85iyKy;bGsFXw&li?R4^XCOt+ zBIa;VZoVODs`t_{#cow4&PXQ*^1cew9PUD_-N!knq?&)dtUq#R;&&d|1i zkY9Ls^J>VAA&*H*+B|juXgjcy9CLDWsVkuBmFVfQV0Q<552LbELsiswNEPj>7y(O# zJ3}6qaOzoJ{vZb!qcxvDuaIjK!C~QZynUJ8Maa#uc>E%3xjMKT&Py9mXa zF8gj%9s^zjO0d&+0mWZZcDy_4rL|KoIF#bNi~B1GdkYMkHe%I9PhUUaSOGX;H{@9d zu3CdB18=Niu6N-KTIa3!Kf6M=2cX@q2Raj12v_lvba`FZ!`V}y@C@(h<;M5O9_5~N zxeOzNxdZdkj8Jq%AH{@EIbj|LFM-$Wscu16dB4;7{f0(-9|Hb6} z|61~~+4ZH`4Kx0}?;HCUGaQ^7kIo(KQ(<~hdu}y)nRLmkdQV`)I}GTzZcuv!ZVx33 z*VVq?dh0v(HOcMr&vLxJ!(o_m_{h9puT4plmVx6)SCpj6F+tU7;E~Ht6j_uh?huO>RD|UHa%ZGe)+(s z&e(N7yX&Qk1L^C^dF8|1#sUbsvG1i-@EvTO(-63&_P#;K8dvH;7?)g~hOfC;|9*-r z!`kI?7+?Ic#SQw7BbWc=1WxVfEgiw=>esZMX~bD-%r{UO1?%X5D{ z+2MRus!xGT%79U*z1rTfa_R=Xv;Lo;_pZU3a76DBmg!|)$^Qs}$H8~RJWyk4Wz z9M4MMAqh!e4H#`kN1fW&8%II564~TB%c9a8%{9OKO`A=;2ligkITY%=hI;nOXW6yb zRPO=d>;25@gMxZCZV$Stzt_g@mvW}CKho#>M%dVaV6G1*SDU_ON$xl#91YjlD7v+O zn50hao?KcXPukfZ>hHxMrPP`Qfd%$$t}PCSEfP#m>c`imH}4|x-?CR}%;5V$VE>QH z<#O>mZ!j(b3bsXH6v!mX4crH-Dtr!7aV_veZd5V8Z{2?qG9t5{qb-~orHtOB-mvu; z$=81`qo`uJ*671Nm*`dfT*6iVF4{Sc26q;uV2ogjUOHEHfBXxk6 z$n;;SH}wy6p-rHJSoCbN1LsbIgIpQr)Sb_-1rP>Ed)^-fa!;f*$E)R{#Nu3R6%V_3~Sof2Ja z;>zQFL)2N6U5oSq1yDA{m*IybY6T%MKBLCkTHlk1uBI7qLV}Ss)#oV5bDho7Lc@k? zSNmrJzP;EW>T!{5!;1AYL6V}gIae8ysY)vsPC`eBQ%=(g$%PC*zfSysrn=0W4k;P0 z-WxC=-%tUy3j`ZxO%&;CuMfHt!ui$v6I_UNYkXlz1ZH>B^PrT>{I)W-3#5@9Xfh%u z0~F<;g7AN1ak~cl0{Q{wSs9Xhez*=YD4>E}+Y>Xxe=v*U5Gy zOjj%AL2C!SLZ$j_L0al{E@xdLj*U=&zH$M#aN+u-&O) zlB^u+{bT)zepC*8j|FE5@KJHzva>%{+o+@oLYqp4g_pjXL%e>%Z+E6L6}DVT zOGtrR!!ST=p~lT1cGcsAgLq$W8hrNdRHn2vn(TcjY^5^0_3ly6XZVVrOQVzUCJSxO zig=0I5GfpWg6=ma}YJK=g`(#;ED2dJiv%c&%UHaOc{;W&Y!< zTKAW5cM7fcBti39K_hH$z3zT0JDNd6{0*l^eZp($#eu<{%msvxb=}ZAwOiMSWrzYt zf}>Q=`Q@DN4# zwZxsz%}b3X@{D0#$hM)3n&&V7&3F^y!dJekLcm1?9LVaiOXfFu^Dh_s4 zBWEyC*FGRqUV9afXvcvz0q)o#rwjpwLJ0xe4-KY3@|Ij=V|_S2w{dghuTcaMMb&CR zg({==%VZ;7g~i)yl%)6loEqVhd-NKni;ZQ2H$x|pQk#IOjL+kiQ1ddTK)fL`PbV<{ z;g-#SuqrQQKrd+1sW22@gH7A0>hhz2OEb4uAjgu<&PSEPNB}J>8HMYv1Lp;51RpNp z`=!EAbnJ}oQKh~H5wL=*3De`GbO1fH;993Zns2wR8EyG5I`A*f9e@*eKXX9|^dz*0 zV1gC;1@vQ6#0Pz+n8@XpJvLgsYn22*>ZXL)iVgC!S4v7_HnuAwpzo^`_XH_2Q?~rZ ze8k5FXjXefOipcvjsb{7nKjKg_J|=P1Lz8cmhPX{>rRM{>w4aA%Z}!xteP#!QU*sy zirXzy|BUzM0A|#ff->UqMXoGl3baaice!w>Jnz@xvV6H%|CnSMhK+)A45)r!Mog0R zByKIqHKG4Xf*m-@CE9$O@?h_lPVi}LV)L9ACSCT28g_UGW>bq01TK7~;oeDR<6)Vj zF5fWi*Gv0TE&*tRl6cW*{R3c(iy`UX7H$0fs>$oCS?aJbzNTLe_k-+BAvx7NLwM1W z6g93Yrk*sLxM1pfygM3rP<29`moL55O7&ObkgHS$bfz_ zL8F{vP6CB|N?s8bwEa3-irdFEuu{e2;ymyXSP z^F@R#DPFzezU9Yziabc-cp(wt)h(N1t^`eFL9B!;m$fHV8EmowN5}6*M|Q2>$rmo3 z{E>(?L{js@F_@gmN>~-5)#RJCRv!+)-QV2pgPww_I6r4aXDv)t(#C2^ZFrAAnbiz7 zG+h|3iS-EunU)=$YHp8KjKOdQex;F_x!aN!r!naYj%~@CpzaZn8`}03AwD$qYDJ zdwxNQ=|8&DGsD7>e<-p~>Cz2ig?l0<9qE%1T>k;NJAsG~RBrdtF32VG})EOPF1+j=z=weE&Za6zXaja>dMp%%N~jVBX{$fZ3$ni2f4 zNEW36)b*1ySE4(h+il1Drb!@K=*b@%eker*zJL=sD`k~|_Ek+PE}VG)n7njX|9fbQ zxK8nS`@{nPRrj4fcyC3ukh1mC3WA36Y{}CL^>`hJ+d`nMAy8%*FdPe}KSn6wgfD!z=7PcjRj2DoWK9)Qhu?qBho^<=F>MMJ7Caqk zda_0etS3zUO9ukumPwh1e>ic-B2?z!+!Tu@N0fQ`CY_ZOtDqCXQi0MX*qgQHePkEF z5PwRt0|sgly=IM~7|S1@jCdUAU&nY?dol-pWd9$;tuYVKHkU<|!QZw{kuoHb>rD>o z3ZvuqKjRPK$=@Lu`@NDEs7BD!{+GW#6`)syRxaL}ET(LZdbz5;-gm?7{F)%j$k~N@(QDi6@Kkh&8H|##v;i=07+Z8YsHD*$Zln!DU zD2CnhYKukhjSCSJHgbFE;r$NDh_7!KbKa@G1`6MB|Q!yUV`!zP}bO zW&ZnGK1?_Z{joNr*8b?pl1?EzE{QNg`_HJZlCGM)WUZp?8PPx42uKb6nO*)GAP?cs-@oJDNlw??@NI!HQ zw!zP=dtAW-DT((ZT;{ldM!3bT18gxE+Y{Q;S83u1k~+%bDb?Xqww8Hk~DZz3^iaGwcw^y_4MoRRz7_FLA{@MbgZlkL=)w;MqR z_)uQtoogv;pp=dL_pBj+fZ&bfWvtG#YOO(;t%c%?JMW z>}%GO=SjcGa#Vu^%eX0cmdO>uaTLEkd-M561`C9rHQ?dl!yli1{m}UO9RI;iQ?Fyv zu37!39}<#8zJR2BV%mG##cKgkv3vNYNZjIdYs{u-r)I*}@A718Zh%5vQcUbDIaiGl znB(`Fc+%{-wHYI^Q(K{yntU8v0r;u)0e)7$=T^v%Fo3yNO+kQX`mDB*rV+vL@(bRh&p)Ayf8H3B3yMm9ISEOoB^%` zfxLiz*iDZc*HNOZBC#^Fyms5mW$OWZ`=m%xgPAPI32@NMKh%AHsxv#RNI*rTIPFiMP%@Y!Z#uXMx}YTQ+9pTQ(#s)Dlm$qHP3*#1uu+U%s{iZ=V_9E zIBHRu2vq@|;V{WPg;a6fP&5B;?GCT{W9 zGDMJPuNk|$oAL#oIf!a&*lD(iI~;JWPj)zaj{a)9(A+do;fC2cZmmmd56L{pWLl8) zTu-vl-ewo}i~6 z>eIQ2F}a0G(T6X3k6nsBElxIH9>eAOO}?;TX(7*~oOboAZMXZc)&j_yHKrYynU242 zRvil~vxvtI);SgTK&R7SCsR8#SMjer>eLqNLn9sSX8m;Cd93qyLm0h2t_ey09ac!$ zjx9X(wMlG$=GR(neZLQTWI?)pb)x9_eqo{EqJQT+1Z@8YP)SXHlB}Z1E(DDIR-R%T z%qP{9Vud20r8$p>O8X)DdYcOg6DGxodZqewreg`X?sX%>P#r81`- zE~yqeo+PC<6Jc|8{e+yzA3(m!w^2TssLXS)O*Uop{D6Hi?MZDgnZ2I^Qj^-&H=J%n z`hud(e*F8<231YXpZb$+vw_X`YaAAGU3$$GA>@KZ@YPw8rNlw#bk{gR}&6Zv9!6MT?W|=?_%Dy@0HLRwSI^lU}2l2Csci_KJPUbyiL4E$^~CJWk*dp$tj38K|7FMN#;v z2=sezx*3a|{H4%7iQ){wbPl0m+Gis@jF|rCv0t0J(DmT+JDVU>1$=`=T#2cOIR*1r zozt4Ol5@ABnCk~m|D|Ni)l$l?5va<{_|Z4rgdU-iof7_SPh8V!pK+HAx6AR2csa7) zpiKUPg;g9$5#LKGZ92hKh?7o70 z2$g2uc};*~9?{)NtJFg+Cp!75W(P+3UVPd)cigFE&x!gL73BDE`C$MRSj7;^Z9;?s zF1Ul{of(M7DUd(t&>%ISm}qYpZqL<3sM_U6{cxHx_PgQucSWm6UkTK zqlxAbnEAWxMylR-xI@Ad0q{j3eXu=M<<^VKKv54Q(y;km2BSX%S>ijyRv z^*!+&mj`uJ_hF8V=fv*n3?}-XWG(~oMTTM%2HtZ&P2tYbk(P7{P-+eJtN7@TP)RFj z_8GlK*pC$<9JwRVIR<1besEm>F%wBK$b0zC6!aICK}GKC!)qLsclgVTu`N*~?HDmx zo^5v-4-7vN!eWjjV=Ru7B`xZ-r{T>Af5=wMLD>i~v3a*5zApI+aJ7oLUn-F#ddgU7 zx=!Q|)yI;gOVu`|hog*ta>JoWeU#`cHds@RH^G@uNU_hlG^b6f)glYafIL@GS@?gd zZ!nZ+hUscQnJqU>U}peqA*?|qf(Y)A@$3A;+U5EiBKa7ySu=O#h3lWExBY45oikXT zYe=hZm^hLIEx$+XFRP5eL!6vTRw)cLIZ8c-I~{zmw$7P8ewiLz{|i1c%hwq_@T3Ux z3F7?N>9383z7py%^4)eX^Y^4_Lc${hzYM|0P?Nme^fBYmT7HJISr#9bH_vdUBj-)CIkcyuT^M#7r%dn5A$eIG{B@vgh9#l8G!sAxyEIx-nrTAl0bklUXl*vE zj7&PAg6LL=gy!d-SRDAAu4eIVxq&iU6bwJ;lN`x!P}JjJzaBP4>X3m(;%)DEu2kg;q@A+}ZG4dEbt&7Yovy;Y%jnw;siU@p{W9C#zw?0)8 zcZY9^dJe_Q^G@iHI;MDB;gw8u*&N4nuoLCS4M%2;E{viz(N;Lc^dE~4>;4pgP5{Y| zUB$VYSYwSr$0xk@x~%HAKKsf{HlBE=YmHksIQ?RHlQNN^z+9E%^s>}e7P6$o5SmRTW?>5{0_-W zS%r_h-HmEms=dFhcIG6DqsF*dmB%v>3Ss;yli_Kgp2P5(PnD0G8fST~5~aPBM_C*C zHx^h0V8zIYitrc&A@M_ZPdE&=Xx!qwx8FES z?TDXTgM}f)yVqCnQh1wQVCd<@X>d^#j|C4utN#Yy+6R2RJN`(*u=}_|n~gJ&U^wp& zyE>UpavavWok=e!PkW)m*K}2;{*GT?s=iDMATtH*q=UzgRXUGYnP&XVf|aL1-ABpI zXj^If!P}N3?lWCIo32n1Kf3YQq?X={yzCBWXeLGEOicD3G4JoXV zoS!r6q3efw;74)!BL%+aOm-#~cEBLWBDQ+HoX}Fv;<&$@ob-q$vQjBhYc8d=?fJiM#46G^(6^$JVFmL%U_w^-cUT_8xbz-7*^ zn1l*yyh2Alhrq)9MXI5yoRW{wL(l=KgQsDgNT6TAIsTu9R8Wjuy4Ozux5Nn!DD;F) z&nm(Usxk9bD_AQ`FYtNY{Vi_C9Z0eI#!~6x1ciNyv<1-%jW8T-CBoYT_-Ns9P?c&|mj*Axm1>*mgK>U4JsU&wG@2qsJE|O$dXa}& zq4i%yUZRwU`6DWDErf;`M-zo$R5Z5l`k6J{u9Pp#g3REF1*|y=j*JKFzY|Xvml3Cz z&|hEkvLpK8oNAs_j{=V8cgZIeZABhA>y~sQf_%uu>KHra?`nQ{j(ocGY{ETADq%Z0 zk6QDSN}>^BtQklsqkkwiu4f%m_|^!vQbjlq1#Y#T1)CeAVl${Vb}RABa<9iTM5^D6 zOCfP>gDePE4F^?W1X{}Mc2XJctJ%4CvMjg&mD=MHourLvm z#r##w0;GB%W`a`iSkz8SDR&gPg{C`xsW)&w2wr5-poLxc;DV!K_;WSS-o{h$g|I!ixt2VvabdiKkDFe}Ewk~yLNm$ZOh zm>&2yl(OQ-+Z<@KE7emaxo`3rb z)u#JU(q(Y1-2g;WDWi$!=?59T7Xk&M$9xbe)#bz!H14%q6U9xgbwx~RXQb?KJ9%CD zAh|j=`Jl|KI1Fj)RBI|!#+4(;&XRhPZGbDa`N!fDt67T6&ROO_oAk5flV;CAF_w56 zrN)OJpK&dSsdh%7@pgU9mwq;qJr_>(ve>U-w$@0#8j62r#ACgAh!Q~F{=+yEo>J~e z@y^&sjWLx>SJ>vCz!0um9j2pkfBiImXTs|H*%B_Tq4G#th^YL>Y@zSyDI(>uHS>h3 z;oikBhwbr`1PehlH-TAVSBwQW`fA_CY_;L>?t0Bnhw1N!LWD2GCU2+F%5wL;;}%i@ z)`Q5u>miuJtuMPHa)X8jwu?QZ&xwj>T@N1@ddoi~>PO4KC%4zhY{uG8hki;Ffg)An zv<@Y8R*h6PSb8&-ak7UjihRS*Q0yM~0u3>XYcWZ(a&Y==vn5FJob?W!wZ=?#la_kp zKsZO{AE+pc)_MPJ=wmm=w%%^!`lA=^3-Bvj)Jv$iMwnL_ABz-Bb0DPmJzpqs(BTla zXk*zSXHx0SD#O61OvEO}mT=jtYw392fyuK)KxPSWg&p5<;pBZmPWwms**gq;cb0wP zdw$hghEd^U%I3i8OYeB1JIwc!B6Y`Uw~ed23Z2Q>vl0sGPTGDb;7pBG!gkoXxT&-J29O!)`6~EOsoMGojbk_S*ri zWMO6wX91%QZl}%E_@uH}N2VE!wGo5%zVsgEYyfkRgQoQVd+~^5q%S!x?g_*dxqo;u z+4Jm7_@Dx0gV#f9GJL0>wmvQWsH**q77OE=!xamqV_`91X~cqdB@a@VP!b=S8TQR! zub8&rSxgk-;CG#u?yg?(+Nr@;_LL&(2rqL;bRT4%V00SosdNthjs1~2AeSs%8wngg zilLYCxI-){e)t~dQWOxs1&MZy8IT^dlB4WG($5DadQB}}hMRrgE+p8>V5CeUnD+Y4 zQ}xr~om_Fc3uv=MTY(CAhDo@dY37eSJ9uz4;=^P03~lJ`Lunp)u4qbKZfIZrq31l$ z!i>q!^Gc66GuSGFYuasZQhdw&LqTyKmU7YbaT+C!8FCUkPZK(Z(c4K{gO&O@>ye>a zFC=s>lTL-qz^W1EFY37QSt^gYD9UvbhvrW8t65mQiL4_`0ERI#488gK00JDFfulcE zfdT}4MJxXJumAhzUF3NlIAw=BFTC-2eZN|8IC zy%CiOPMCS-)TA%d)y8UT0BLcI#8Lvf{pR*tFAqMZiyFVQKiFon@xB7tHyZ0+X+k!f z=sxzet>O}&b%u+4q^gnEX14t`;KtW5BUCnPz8{9&!5lOJNMWKu>+jO_Ro=iMa`!U9 zYhpImt#uLS`g%_Ky0)B2PG$NAaWqK(POvC?HS`adR9{;PVM=Pmq|GubijC)+#BX1;_VjFE7WSDX;euJ`t!=!w~moIU=mW?$#IxnW-QzAB1HxBhyFfT z3{g$(zWvjqC@F#s67PR=ZTZ2W@ode(8CKhHZp_Je_1%A773@cSIR{RL&;wT-?T^1AyJ zovDt0{}9`z-#JsYc8|(ZeGhnr|`e`B^O%*w6YJ)LL48~yDwaxo+{ZT z&UYT=-9X5(1lG|GM+8U&y3re;`L?4DxYuI7&upQ|=OA;U*(c3&y+)1Z;;6Vmf_*d3(4T2d z#N4fzE+Cr@@PW#{sAU^c@oufGF191~Z!0rv>4S|;NTZN)APcdzjHcJ#FRX96uhz?a#0=dE&o*H=M)!@}P}C`?pFrz%L1h#4~ze zEP5fW=3+Yt7i5XYHn3x90gTI04H6@W%c{IJ-B6gZQ&(J!GUHUUwQ=HG0^(QeXI?Yg zHR`}TE>8V3aCI!_xml|0jDVy6WQiT`@bw1YB;zkGYYIHR++q8v@+uYVxoD)}^JKg; z(EG=dqR-g3j0CsqQg$gb;!y0z-=!5Vhdmscwom4*&4^H`7FN&r1RMp^EpOF@ z@7jnfQ#${(cz^}NlOaN{MmsbtNnX~Pl=5Jy{dqyIH^_>E0T(>)EW_Fs zirYCQ%aPzoBM>RHcQC-GF1=TREt*D(v5MU4p=(RAIh&q{+C+e^dd_Zvfm>gWm<#$% z6asPQnU`lFj4;$^9Nwls4GnRj{KOD&();A?(KNA2EPXDSf16aSrzH+lCsAtx{y;kI z<8M*z0~|M`04!3w?*=T|3ts`u9rpWQeJD9_xh8d+6k-M0FiY(qy>5Sz z%IC8`U|8q)7age+7iIHFz^+H?oxW=}(9R z>nyu$f|V~8kAkf(iSOU$v$LF*$c4S6SIe=0L!=zPGLyX_uT`F!&u z>%D$SgPER_Yvj%~p+KPWv@rd)=9d$JH3QfE$0@Lc6A7XrIq&;t^zQ+4|B*2YWWH|f zXjENy{ZO9D@4AK8sFPz@vDLh|EMhe3leG4_GUb@(r(?=37nc-&n?=3;OStk?{3RZC z)higEHd1USa3@i3Pe4j!LIl4*4;YhdS*zUDMcSQnRO`)4OvtBkMx~eJGMG!{lK1%V zwnT{q+n9d3Z9XO%zNufdmF&&BFk*~VidKP&a1~W{3uffmJ>ya+P_(O7Aj8huL>>-_ zd-I_U9ZfuljjN$U_RVMUa|jW{ON1CgI)fcD=R^r7Z=C`h5Ma^(>J>Lpd=nrDiX_)i z5rf#@(9IHw{qP?+k++NwF4lxVT+>0Al2NHOEZ z*exrf~Ap!*QE+L?Ft6YG8-R z?8{Q!KxwxWZ?iC=R7;j%S|?5p2qJ8&G^_RKsf%AF-^kNQWW%y4<_A@wYC+DQZ-Qhv=93^6Z)|3i zyIYJLaVi0Mw8y%xg@;H#H=b=uU(}WJPl#sBr84j6vEzM*;GN$s_+RqtNWGMcg zyj!y7Fr}TFa++;HrDUc9hYJQ5W+~#wh$%Mg`FX;i-D3C+jG7Ck=USL^C4P1KNl^5l z!v7%+bux!NvAWK@E$FerT%byCD#dG=V%7s8xA!8G1)nc!N6>UC2PGWTT5pSzpJ}XIh zcZ|jNXUnOUDm0oCPO%lQXm+3N)8^iVzgpUh*QYZ5@{oHsAdhND_3rj(LOfjlp4gEb z$a*Ci6uM5wOlp?X6alI-o#s7#rIc+$PvatGI!y%9m@s20LJ4^u?udDwN!mRaDKE02 zr)RacIIcM2+$u|rl!10y<8!>AF*xh(IWay9hjU`MHGcdSSsw1$H~$eXc}(fj&z#bn z7!X2qe!XGW>rQv93Q{G$r#(p$C%<22~Sadh}668p=n3?aybCF$o z$__L+%U)93nur#0)4x}DG~jH_yYL{((0lH94lRJ>#Gk8;pCt669Ubs5l-C%Jz{G=H^+>1g%^eqscTBk{bhD<)3>y1947Aka+I#4^> zwcE|IhdaD$2!Pto#VUYFnj1Bh9yTH5?`8k!t3nkll4g@>5Rrld&aa%KAWWPssr5lZ zie3om&c{Rd*OppW8J+syX&K^7bUP(|25zZd2ni3+w+}TydEQ1BGNz!XS*(-JXKP+q zCozI_)y_V=@YW6iT4u1+$JQa!d1|{2AXcCqYLx|_- z-2`(GRk9L>F57N~SX2PGRh?Hu`cx+VHOhQA^z_>QD!!gmkq0!~jOE((wq*b+Y$(K8 z((ToR405BM41YT4$ST17sp`s382ElrA#2F!Vxtcezh!H;MR8qWGLVDwS~L%cd&BR& zqav`H?0aR1+SGNTTl${gzC=^O2JBL4J@_< z_T2pY7R}!~yDs=E#CwVq zYq<_LlqU@XyE-jrsuB<6U`Wr?&vsUwN?*=$NjA2V_RDC(slSs?`TfV(mC49_n}_ug zncsTBEe~kni_6dA^r0`)9wHuF*Hxd>EY?D~%tN0gKDIVdojIOUu){GewovtU@-a+U zqZ~toZ2AKhIF^ZH<5dxgsQBF)_MfrOc&^nk7XJBPxYuLyYr&&6f2pCL1TM=1PgqjC zf6Pt({Y4eRFW$`D*2U+S;P3cmv7_EXQuPLBAAh+tT&X1ikMqmrOZ5-ib3c6eK{D!}l|k1b zc|7RhWRyl8J^1q9v}UalR$_wvmWaJz`nl={r_ zsZUO1Za)p4e9lsz^c#``B&`8#%v!9K*2ZI=oASi^ zLNPFpvNYcU>c*55oLrBy_4#D*&F1~{4=gfuyUsyoIlkYhdL(x5AJ~8V;nV++0WBl{ zd4lmp;_sv}ot{{VoMXE{+%pyLU`=(bR)WzqaR7}WeUpihDX=oK(q5-YBAsE`c`qyL zZ3zxE7DDGEiUNSP(~#k{8s`Mlen@QzmJR6=$%R#!b|Ew;A3IGte{tkWyvnD@bApsI zFC_yi4`w$OsHDGK?vYPR9+Fp^Rl2IeqBix1%bT%A4nRbn{~Hk&>r>b@0dOR)t2X<4 zs2|FGzl%`7_{66zCLT}kZ~L#GosE3G&puKW!YS10(scg>@wqi$oZau~d-r9UMk6`S zg0{>2$>V4eX(X5Rih-j;xG%+J7ICMnmJM5qB(&l@5079ZIkrveAvs`;W2YX78%b(q z!0sm$C!xkfN{S`tLTR2TZ`=fl^3##0(l!=%iCaTurb`#fy$pAngQr4ydf>f4|Fy7; zDY;Lg({#Fcm(lt^HO7XxOmi#K)!CxT&CWD*IXdbpAmy<*orXj~+GFSLTOclPp?BmG z40;?-)O@Xs@X3w1O5w} zd23e?zn!QP!0L3@Kk6oGJZt9J<~h1T!wM3w%&Ec$goI!*KCA3JmqCZ7-l*-b!z^T{ zeaCzBy|G>4;4QJ;D@^Q7^LxQ0fN>cdA@(z1FHrRMOJoQ7%!@!c_*P zx7~e0jat^@$PM&*{{a!_#woGZxyicqMs1}tx~&a9{G7I8i?00B=&;mLTf17-!_K-V zxr28mdhhl4zm*yA?kVG4XjAPBUU)-X1)Op(m{z(>?LgWuj8Bq*bAn?n>+h+&g7mcFd#WA0)I_G!Z0KE=SfNi}<@o;m zxZq^`ZB=nFXF6SSQOBF%s|ypC4{y=U7dy)<_`T1lNs2}v4!`Jb{q&;KL?ksg`(d9% z^cr#Ks}flw%`m;*ABI)~*?D*@+m(v8pQ;P|xV%*v-Z3Hg9$r~%=%Y=d3o9ke_7i-+ z3f^XBNH+I7*PWkY=R+P@-OQz~A+4NL&#-x+uho)0JI^qudSdM8p@(F@yzD0yI6+m! zuDDtpY()mS@5f!9?D?4aNtyYm!jN0QL4jT{FMcdR@q^3gf7K~lsKN>ni2duP?`Si4 zDnwI)B;&pUC|qE1lB7spoG(_$RYu`|EARUMna<;QN#T%NIz*DW%sM-|v`8pJ58vau zFtsDbRwHuyf~kjd3{f7sC0}kf`BF1wv!(MbmoG!U6~j8CSqO3cN>IJ0T8NBS)hk<0{59tk-j&DYKVnyW>$o^GlpBT#uA+|t2m zHr}}KRZvrtj_*&3$vJEoo{gSA5*xN5q6`Le$inaQdw-o$Wx9d!%7KROx)cynkkrR@ z47NIaO07^@WUevCXCkxQL?=?Wi&S{2`;?w)Kb*pJmm}=A#}?P3RZZ<1nQNR*7C(&5 zsVOAbpf)}&!OwWitCbZsh-2$L9jHp&W^?dhKp?;&V=MRSJ_Jn&40;L$7$3eG$XGg@ zc7V0(GJ|KRn*)tfOcnmWlixUFV#oV@5Z1qu1KG=aJ#r=T2GCpqLcWF}WAoLSpIFe?c*fM;YBmo{Xx_ zOE8t9zk&F-eydw=lr~Vdp5PF6L!eD|k3q&qk@9JC5w_Bc8?2I7uHLRF84S7KWcT$5 z4C&a3j=Gw(A?@+;I4NV;k@6*l-Ec2Sg6D!C&DJZo?@D_OhC!W^@vON>UMzhA=VF*T zUBcoNtPxkbh+eL|TSC3|``jNcPm8go0?`v=Pgx;I7UdXr+?Z)v{jmbd1;4pi9&RbzwBEp9wcGo_yo!J%iREFugcR(>lA{pJf`e9f zWbo$TgCj>rRFo1GxThOTc$AU4AWMx&nZlIrx*Gg(clIW?&0H-y>Bfm+gm*iNX{0e8 z{aO}xqM%6Niq<1-YSD95|3ZTX|!C)?BqzrTlv)kOPzK~^B z&LW0-`ndqVVInTlH9lib|MPGw1JE1jb`ZS*?7L^7 zi!MgYGJS9?&}t5uKuvcY%WxUwV)ns2!r1fOc^>#50m%Gz6Vxh`#Nd46&sF2{hB@Q( zR-g;z#$1Q6OW_X}pt^Mk>QjfjhO!TByBNckjJA9QL{MPtZ@V`WR4tOg;dp$Fj(#E1 zO>;)yU%KiIB@0EYf;+t+Kb>**pt;SZ3!<9t4i|j*KP(6heEvYTj2-R`H|N=k|7?>T nd+;Y-;ET;NGQ!9HWgiyoP$nh=QqE(-fO5S;9!ztA|N2(C@IQnAs`@GAs{?x zMMnjG;t|ryih$sUpd>5x%-v+CVf!2L*wpFe4a-!**;CiCIOR;da#orT&mK*^*e>C< z-YQp_O?;nth;JEL$orC?IwvP*i7Yk{Ga!eS8lf4f=%XgS6oH1(#~kKxT1@x7qy;^r z5l>tX>T!$y_N`O+^LNElxe77Vg_+xp@J7$R+vk*$Iwdp7Ok|dkJRKm*J|Au3;=?9b zP;4G+hSmL#B0rHlA0j8Q0zp| zINiSrZ4^pym5_VB?h@|mE&a;5SLf^Mbi}b7QJCrTI6tbv)u>0p?Iu@$@7i*v>h&jw zR}>3gY!YC0Snb|?L!DF9cl!zJny9?6|5Kh zH1OTF30t(t+9V^rn%HjS)q#&p%EILhUbrc>mmasK3TcUVjBgS1A+eGTZ*3~bxonCOmK7)$-B(EM~%+d8{8u=F_8RTY- z>OeEDCYo0D2}Rv@U-k%U1z*_EXYzTYPQ2Roa$7y(FDV#@_ z6i*B+n4f^bJI_|PTw>SWcljd!S{P3}eV$2MtY3V+-o~_z{d!kdZ2cEU#8z&h%Sv$?_#(hw*7Yg`F1Tmh$;B65&G>NWx`O4`;U?Sgbtp`rx z|HEqYTYJ#^IBgJ9c1DcYjqH}`pZ7>)=xOng`b&;3d}ZPK%TYBb-DlX_N%U?{OS6wG z`x0FBwpm^Zln@uE7Nx?x0Zk|!>oeS|HE8Rpa8eWsubYGdJ2~T+xwVb!yEPQr_l{WmONpK@7RbboyarT?(O&el` z+r#RDO4oo${C4IIHF)o*b{xUE;RD`P`cOG#iwpOaf>~h!j=b#3B%Kl!h}+(5N=dPc zEaj@ktzJ{?L#s4FM+c*`|Wv3+T=@;Wg@vf?%TURC^p$mOI;eaiFH*YpVoaFts4fO zh2CukMO1wdwfx)IRtuUQ&)ct4QC{x9@cHsg-G?5r;Aj@s+hktHbX*#*L`fGLN`>h4 z{W$M8G@0O)>JYV2Zqs7s^B{>}TaoiwW69nof1)S1TdUg$CHeXaNvXs~DwxSd}O`PiXGy)p)pOI8tr|CFn0a;(7A!j$9~Y7LObQf5Y87#BZ;TDrt1Xy{_KNsX~Z*x}SUqh|E8mN($wbZusPzNuB-c z@cM@<5zt9)7xqnE z;+mi1iS$P9aB0)wTD&e+0rwNbDlh0T{wsGTrHQ(?e`k8m{Y*E2N_l0yoWDYeI_8xM zQ4IUSw3bGafe%LMzW;^ZNOpKSMfd!ivL6vP#dx-t>~z z|KtmCK@T$%VejZm*}RLK4|_1Sg4TI%o$O0a_Uh6oE)dkM zzKT*@YCikB#f0C_DGI4^JajC^SLH``%H=MsW#rZo6-H2SkG4i@j9WmRRjD17{XF8%KqjhWtA$Wa6n zDMx>tA#6<7lvsPO`iowjtFcc}^QhfOL?^Xx4$E#U-j9N#Du=o0cyV&~^$a)dxvJMQ zi8La#ltqmmXLG;W=SKEf9I7`HmyQM`xSlw8eX9nR`S#OYnst2z0j?6g*lF{-P3g$k zir+5EbyON^&b`m2K;`_(X>IDZE6%XO^K5U)bKzEs$9^2?BD;;$)GIdnb}vkVKVRbb zU^c$dvNPJ^Xzf=!5ZXj!h`K#l71jU)o5iyq{{A>qvE$%xR(9nt$9f@e)x*36w75yo zu8-F@YYWriXZ-zP(x*u~cJ>L?@2-NDLM3jLGGE=_OvXoDgWLt+X0DG~BEk=U%w;m+ zy*I()oh&oAJiJNdP_-j9aZ!@V6T4}P`xF`F*fGvL5sXm8g?_*97^K6E|yBO7tSkn zx|(E4!qrq!t5LQ}OBPwv$Y$h*zLJLu2`kZeejg#N)-A2lFb(&ITXMw0Gf+^D|y)Cfq!9<$q?T|(LQ#S2u zlnc9F!3D3lk`rLL`Vh*g^|;HH>4JqwGe8LOW>}|Q33$L3aLRV36aAXEW7I=6Mdus2 zG31+v=UciHd2ca_mqmoP*ret*e?X2%^QwwCV>t}P6{V9z);q`0g6Qb$NjQ8yZ`L|u zV?P8I7Zq>G23sQh&t2=@Ju~MoK4UE-_L>@%7pgDV_aJSLH|TuTuY01dcHJuAUsXI8 z7YYM~#z;7vV&dBL{RtV5j0^6ci89nqbTLo+!=l*3w^FBY_(^wi)GA@iV+J8otDrF| zrcQFuT+G0 z<1(9-1{++^a203w+1fnT&q)`zj&AoNHGQiO7X?DxsO|SWx;WAA9{TP#-1;b|Iqml` z;*)*RqrPU!Mr$Q={S5YOCoOid7g8Awx#wC|z0 zR!!lc49THw0MBW}%czke}3@Gi%;_i}4=q+juJG`5yjXM+fiT13>jp8_$3 zSuGaJltC3NbHqVz*HNELjFOxp%wy|`6sR%8x=_fZL$9ZtsZ}g65!HnAfG8cDPua@A zw3h+#Rkmy<1e=V;HC0A=ln)>w@TBwnxZeo++)_;JqI|T0xZyPSR=g7;RBMpNH)$Ko6sum@q*^nO_Ak?^B!HKK9s5aB;(j&*%aC|emiC5hp-3(*e^Bu2JBICFb#cNifzG2e!jHh z-2c=9@vT${DNbOP2wl-bPEr&-&MwBPuCxgnF2dg@+UIl^8*V`wjHkpc5%tA7!m@gA zjploGHjn+0zyGFc;79dbxZGy!diHp}D6HdS0_|M}wJCIjde~{xLBkyPM!i01lq=*> z_+n%YO3K}i-Pn7qY=4|KjahL!Z5aj=HJc+unF_=c`_MyM$;At#Wbx6Vk1%=P7hN@c z2WD}9LgX7}BCAn<{x*f#1DLHpbP==;!#yl{j?5Jmq{Z2FAYwkqU|Y0}c8ex`2 zQ2;hG5*>x6?+-#N51YtCTBBH>F;Q!x$GH7$Itf-zxLb_PtRZ}Bf3U^{=LlQzkmF01 zF7EnJ_;QCPbW@Bo($XKXPgIGJi_i)*;oiqROn~AsfR#fVcOFh3^G0-dDduUfDCv>Y zYaRM$({Cc3F)Gf}%-LlFnoBBjXlH8db>V$@^jP2*c^SS^&Rz8TZWt$2`!aHRnwkM0 z4#Y`R3Eaar*g6nkjY~o&k#xR(=i1k5qSs%ak+x6-Pq%vN4zfwf3q86tAR}6IgQwUO zRx)?N-l&kQe#EJa<3$e0Y}cai`wq5pDzbFA$1%zKybZ(T*9AHOJLg2|oH`BQdK#AK z0BlRrMq}VpekK^aL0{`VfcJij0)rPGARgB8bl1wV+cTS(oFKV3kwYD;w#@MlbmH^! z$QOXC0{DW#87+yEh7&Y8gcLXtQO4ge5Qs?a>-?3ji#mBkF>bNMEejtBG~*`}(NP1{ z2oK?FVdMN2%|pFV9=@;ZAL(3gA3%(&G;--o$N>BKNtGu^i|#!kllut1bx%wgeLN>7 zFW8ijiO}dP?)&QJ3x}lHId7D}uLHHAP zD}upW%t(ep=B;ROHTPs%QX~gW{qtHNGFe37phJ|eh)wB4EJsMRmIBzKe7=%{)JlfR z*=M~!pzkiCUwD51w{alrOEe$+^38KA!WVy^{dINKxmx~8%G5tLMzDZXb{~I&LRpas zazrB+P0J~yW@{d_eULi{sU187O;3C(^#enM%#fuf_s(wu4&*86%aBc2wxR?zSKdkH z$qgm8${=hx+=GJCELK~bGFl46#*;Fy%hpZ?RsHIDTPyzK=S+zlzC2?D>eNkk1!tWb zreGWEzIsO;k6Zkh=cTe$_i?(N>hsDFlqqycX5*{f5+uazgbUW7+}NQ(-MOEbu|W>> z6?g1_1zeWkhJ{C;oyiU+Bz<%LKY6ZmSq)nM{K*|4ZPCv;0_=adQ6$gz|GD(PX*6DO zIj{fX$~3AJQg`UqHT8ae2PG_ar{7&4oGA!8zv>>giNGLYTJJ`S1OiZlS%H&1 zFyvX8^F+y#zc3#0#DcqX`Qfi0u-5!z4;2NzxjsQ)W*78FJ}w@LHbAe4qFCsQ3+dDB zq%=&2oJzn}5_bSfdOm;VtI?NwBI&~n&=}La;*cluP2rp6$$X=S>*e9l7@5qqIB` z45idb_nb$$Pf5n29UCtS+%FQwx-EzQacADgLkhqOTRISj2fE8w+N-x6`{JIG^&9=f zQl?qnB**ml&GU?4ew!zlt;5Lp>Y!7iI}T6iA4BpL?-b!pvuK z$C0fzxQx#A4uPyZEjj_CLS6hKPp|5#-D0_;+4XRRQj_Fjip$5?$a(IB1&{s%?*VeL z-l3MZc>Rl~iR>Jimjg%bCB}7W(FF1#VKY_Uo*umF$rN<)KTp>BeJk}2hkKs+O8HC& zxGx$2WSkwcL2uo`y2~hGy>$IE7x5~;7_{@B(+cMIk(AM;KJQdn&)SLEhTpv%)qSvh zy=jxkNJk;Y8sB~*j2o0&u1;lY+9VD!>)AqZ*IoZ*3WYyRY^t_UZ>mocPdY*KI%p1M zd6)qIvQQ`GjLp-WJQNLfiFTFait8YcAv^MKWUYI=J~ROoF?NZtS-B$AW`M zhhB!qG?@}qvjZ+TZ!2O->qAP!rYWsulA83K*f0~ENX%jyFvXZWT;033e?6|u-`J|sE661 zR3=y3OHV1R>HUP(KNV)NbQlR&ecCw>%5ald;(3Ct)GQD!vA9wIr@HQ39AXF#8DNZNLo`Ke%Sfl;%0%`N0zsY;1T3c)`JbN-*>3<)>;+;{RHjh zS+*UK?)R*nH4+n+mrBmJ>2dC&1hHI#^8Nf?+|C|xz50xli7nN{4Az)wPcllpQOkU5 z(-U`wX5=wNth%moJBu}qxdvK%S1FJPWILA>kdnr=T7I>Fo5IX3rYF&S67A<0L}@*i zBYH*HC0`orE)oyhlfR&@n?7v$XO2`EBWa#>gVM%x$r{>ptXWs%_pv7?!?#)P)9s^< zrN=yzd(_OL9oNyIr6a6G6U6C(Yt)NmSkeun>TdX%uro2&>w1#q?Bxr+Oy|7|7O|Ey zgfn|&Ck3A%r@BjQ{MUJh*AI_=Q-ODnzoicIyV2dM-Jz#O`}^0-0F(DMPz+e5&V4bM zyd%qR!rj8{$CJmM=q}BHq)ex!2|aY7<_U2sV48ayofXZR>4|6si!=7<= zaMNR2YEsjZIX=zu<|GR_8%WJK{lrI&J?v0_F&yk*;*G zq6$q|Wbs-XEr?xbx=_Nn1;AvWYEU^kI~;?j1LCeExgVm$KjKqDBxkt8R*zk&8d5Q> zKt2!E3BV?3xuCS6RGewNUXEWMKO`Ey*%8Jg$@0B{5DrDs*%c#C0mTr3esi_ot9ixg z6F7Z5{Wd1_zdy|YL15f)rI1Pr?Xu94oQc9Iv3!?TF3kpBJ!|}Xe5M%Lz#dxmgAsKJ z)lGLtrPkcX_#t_A1jK8!&Y)^Mp~nP$SLU2_U**I}{Tj2yBZ-`)uA`H1d!$L~YS1<$ zVBAS$qFnM#^CgM>9r&Y|=M{yHJ%!)ElB zHxDo>l;ZZ8eSAlGx~uP#K(cvaGINc`U5`~$KMY%`1WI>ST9Ch!Oh+GEO*C}0&m*W+ zB+oXcl_Ei5kPf{|06SiitrRxNIRrITzO`nRq4RrS3Hw|=Q7~}USI@N?+r`XqN(|xV z=Ml1oDi~n!ZqZwd>g<)$as2!XhE=8t0MExVlpKePi++^(BSyr60u^}VW zDWY7vNf;Fld;WKUGJ&O56?53UcAfK9Wr54yraO%rp%vjXbckeMMT~~MT>XH{O=Vrv zHMO=VBPC*~RceH%Sn7SA)42U-=fJ9HyGy45z3O|d$!#xT*n)I=Ch z5=Pw$pEDuj19vW*4GI!{%_;X68@L?=~rldow(&r)c&*QhrVXf6BrOUKj+ zZ3wd=#_4#!}y7p_kUOL0{)vhxUyVPKfbO zJ3Lh}Ct8HmvVCO$ZZANPOK_r}LW^X~R~2#7j-@0-KJk+;*)c%4BT*(Lj0DJ^Ym&nQ zB2gHMNKN=#Y(5cTSwY8@pqm*y(~{Nn*2-qxy@{TTEOCRXZ77|&3LIHjK@?h#H-kim z)zQKI_Do&1HE7cqyd;S42eXQi$@1iMPUF=#KDOc0h0i8=$%n0-{4NE%&!O`MgZb3e zB#w3-o6m2Gf4gTfAqw~JU_Pyk*UFcPcl6|39=x1LG~A7PLNZHo0oXDmF)8&#Lw->K zDobqP$Yh0wo%yR75f{}@zHGB_2$qG?cP(N1fWCtQSlP(G5ON0op>|R+lY+_s%|I{@c8ji6didZ%Seq ztezduyuwx}`0B~t_GQ3oisnU^rI8ve%oDWUF?kH$qeM~SBDLhLD^QEYlKH7kySj)0OiC|>GXs$bP-~nb(kZuVc3y;7~UKI zLwc2Aiy))#5e8)JT~UAv8{N{JRM~H9tE^%Qse(ou%VvD3h8~^YR^s3I!7bt7L$@VZ z&Bp}ZLsTAzN?kf%Lai|R>0Ylw31ac7O=@6<{)EJLUK)(*JJ+i~5iO-nVF-wLX>DIg zJ`SmCbBER_q)4pT;G)+zub69+Cq$q{@pmYcDZYn`V4ef7<~>^C1OG>^zrNlr^I-WA z=NmuW5a^R&Zga1W1xkDLiUw5ijUTaASLPZO@gio`3V1<4u1}{9%HU<3;vUaQ^1nX! zNkC3XWJA*tVpQE9aH5)~-4C38*!qB&HGe^~70a)@=n9k%`mG6Fy!cQS`Sgo%cDaC+xL*y$5B4^pTCxJ=RHb)<`(H?wkkVpb!AaqK|`wFjiRqSqm2hg^AF?s z!jnMTdx?Zk9QkF-e}bRdF%;WMHmP4By~%~w% zAG7qX2yI{LGuQbSVzGqKi-B2!Q8{bHB0GVO4eJRDslx`X4VNr$ik7+}C$$vXxax7` zoC~*9Aoo5p&iz~YI3a3NyOH@OBZpd&m^83ReyEU+@_;TRXBUsq>gP!a;jq%YD>ZGT zPW19_*c+<(l=yf{3T{vboWz^zgfO|(Sfo~WH4`(7;B4`$qLqObUqtqmd<97m8 zPckONbZK9AvswDexEg&TmQ<+l73V>U#ymKWtEId`_{pp%LaCiTq}VK2rC4neb9$}> z=U54xx!#VY(A4p!Q?c;!9*4-}Vj3W^cgZ8vb9ev5nGcG6j9D?%r+7jj zd}(>C$%Sl6Z03Xuc5x5R+30R?d2#!*HTZXXZwS+Um6So|FQ!2Mizz&c=NZUVBdtsc z=H>O0ed?3Xg3*6mar!YulnMESvT?E^9k*)eFbnL`U40OiFIsw2pL|z|B%}U6Om+JI zrNZTzBI@XKxRTQjdHb{xzV*eThsurEexjroPas+3wa4M)vO0gh_xa+u%lvq;VcM_) z(w$268DJJOFd4KUhs&4i{Tb~*afEfe*pow|()74iqBh0yX`;BHO0tl!y0}?=1$=A? zV8`p-3_XfxN`lLyTqZu&?^2zYfb=xHu5f|q?PwO#)GhGLx7uxo$#;u_X=kbre}V0~ zDBty!x2M?LG5|njZ>}z4-}@G={i{CSN+_YJ*P)!qO&!C{VEoNstFtydno2bMSDGLx z773?HfCc}-6;`j-93V+qDHt5}eaLVB{G4mOTa51XxoEZcopxmDFED?a;aYwd;cJB0$VU~z(XG(M)t1OYmMjvmObmAD|K}mmF z6YU8LyRTDwrVoV6n{ylQ{C6OpHZ$vqZj)Q!8fad-Bhqzc5n9RSK`Aiq9-54I{CrA4 zIHu90^8Azhb#M>uahyE>Se^vCl}sH8hVlSD7_Ik<{t+Lz(V*!u)5*X#?(SOg8to=! zXI+l~=u2tSO)bkl02LJ9`PZxaiGO6@lAgcvnp|+o;TVlO5bo(&G$98~LbndaXB^i< zN1WiP7wtyy^JQ-_uVWPNZtp&j8$*|f@sE)Pxw;MKt#-=6@ASt^XDtP)`~3OZBe8oYCXqlUnbE{5)5 zuNZ*P1hMOkpyn$7QJTg7Z%ecCr286eKxx+HUrV$3Zt6?IOxLTU^4_=J7s-ro)NZyH zQejZew}=4*?f(E2Z9E!BBK@gilcbu^(6Ac?1+sZ3+D@Mb%zi%qke6ua!+_M>!2&pI)qI6<%^t7xok0_|bO9 z98tBc>jZoqc_%t&h=fF;WxC~Y6}4bb(AadI`JVQHFq+iD-X$WyF+QnIqjQMgV+@x8 zsT<=v*#SH0esOpzRj^yZRx)cziA^4t5hjlykJEvTiXo4t?qMCaA(qeFbr4C{!R>Xt z0(vXeL1fV%+pI-W5tc18-JWEU+0zb^mo*1>BQY}A-W8H%w*D591Eu~Hk`Jg6I=C-E zj$tg*?yagn^6u)$2+auV20Pl1Gx4H3(>e3;@>w^@XACIVOi_9Ai9~5B((b*Zu1`BslqV6F@ zD2AB(FcDK{b+jQZ6ac2<7_w76;Pgl$&`KrBXry86+VBK$N5}~jIa`6|z&kq><>j}#065v?&%(;lHR*G_ zfJQu0Fwmtw6>5u&AJAF@ZjLrnG4u07o&t=(Jf>?9%f(B-8rtPg4UuF(^d0wDRmtw~ z{dWL%z(DVy=4k(@ce}(^wyTR}oLXZmG=~jlvrhw$iY4&s_y^!ZJY_@6f-9&an8h0l zoK7EKWTu?ug9*v3+ZyTdP$@#vA}PP)NRM87zM}(qT0^GZy?fD+a~~f6OF2FYLApwa)KZw&yQ3k>GN!@LRi zn|W)=mKjdUtsxu}mEH3e%i1wi6KcFAJt#zCH_*`^Fm6OP7&Is5^9y!u< z3UE!o=Ue;ILUMHZdE_i`4Sz{v!yjjV{Jjyo;hh1sNqoO$R0Zrbor2JIGgsDR8Gbj1 zQrq!@+uD@kv_;fE=hDXUq&EE^K}ngf`Ou7Y=f)c1-w+8#l5^mHpqQM)fhhdKys+024JGjYbUufH6m4y2db^d~Cw6@F-Mna(h0mE9p+ZFTk>c zVaXqSgQE#WoRE!_AGNU#z;^jH$q6UrVl+24Leh){hLNOo9@%<{0AO8-5ubn+@S!GG zy>!rkR^eSWz%BzhWu7b-Cx}g}06DS#RgJ<2UJQ&j#r10N%elT8oq)(1NvaeZc3vsr z!?SCPEM~^ixl6N*4h+3xdK#wdE5JGV*(*~snsAvgfIeDZe6{Mc;_naR=F+E>mBv7) zS#`dl3PjVI@Mi|*DQ9Kf4_Xe^Z%h(&p@fj9e~VhPm{`e2+b*|}eNYYW-ErBDH0O?0CtFVlo7dC4JR@3($*f)yXK>>KrUe%A-2 zwir&`;hz44lXat>3C2OBr=w zjXAzN+{^nKpZ2*QgfethZUjnqpvScSW-Lyi&CLj^XxHFg)1OjywH|D&H@VxK$oCOj zG*D7Vj>EijE7bz9@patSi_&E2pjk)AOHn*wuP0jgD;v@vGs;lJ{emHf$V>1J*D1zP z$To8%ZZ+<{4E@`Wq|5PG0nA-4P37FmL2lD7zK77qc%Cblf&1uC!GyB~WM^pAwg^6o zzTm`bH^;*io=Ee`Absr@S5weQM|dWK%7-S*i`})$=6j^){|~W@%6F-|{c}=p*0C!+ zIUUP`gBL;K zM>6`etKEI{0kr`)0JGXqJ9*yfZ{i|;5x4tMq#!CKp8}(P!*TQ38$fX;xTj=X@W4jv zNV3xESvR(4;yKhq-9{gvy2hcbU6q9N^SH=pnfRwuwFFjMo=-UV@J7G9LQz}eGhvs$ z?aEzvzx`sqipa{~c^pThGjh3lvZ+C3P$*sGduf)F5(BOd7E)wQsJ~A|K+Ug)-g*m_ zhg(I&qKBXnU3S-&Mr5+xL9gGUA#T4be?F~OYb;VI+;*+~QT_M=V%8EMLAY=525o@s zvX3ph_M4p4%;vuju{>0a_iTpgS1Td|?LRopv6>`)BxyDiZilv8B4_ak|Ao>p zkmPDuvPvbMq0#1mbjOfN6~ObjWU1)b88Jc;*(E^~YXR)h03*)J~y~oo3Y2SCQHfCv(}()tp4Rm#|8pN9^pc0 zu-XFNLI^e2%XIC>U5w=4AH3ARz4NPEv;T$ROySkyqL5V^n23kkudodu{$V&nqRoF9 zP9{pEFozZXqDZ~xhe9l>0h!+E6Jlxoj`bTq_ZNsL$nSr~wR~U3qx&KK)D@2u{zcak z%iTJZw?Nt?@?2EIqYU4dl(XgC3JPa#(oq$a9reC(N+~fAvd&5Rwh2?3H^TgKsE&0T zs}%$*>l~&Uh|T2&b-a_Zau>qwPcroj_tEZTSV>KkVnh-v1_YC2tKX=OEs7(YMz8pJ zw7i4eqPBlWEen<%-+;VFv6^*J4p;(VtUBpN#?8jPT$yIr~4F zF7UB074AOvUStC_1=s(%^#6D?rcBoQ(@eoNwoaL|A4(hHpOfEq8n;h>--gNH6sV<{ zfKShfRPAVdss%EB08MEH$3s6w_v_QP0CrP#HHer!(>Pt@Y{Rn`k`@lM!u8_qk~Zwc z07&FR{s0NlW3_%jmMHz|^S#=BYYqYn< z`o7)9GX1bR+nZ}YYrGA#82^d}lX=h8cur4sJ4fJatL(e6Si|Do8BNu`_A4y{1&j2t z6c4%kO|bPR2Vg6!U?_&X=pMEh`ic{PC{EMOezL`rH zJRDgizKN32C;{9L=-cg|;xXW8bo|xJ9U@m11yRRBa3~_qe!Q|%t~tPW1; znLJ-JRE6L^Y$c#=5?(1=nmyv|iu07-`Qqwz^h>b)uIbQqZ#scyn(MFH^1l{H6yk@* znHoN(?c)J2UB?d1eFXL-0DE~&7mdcCFXPPCr<$!Na+Ry;ikA$IMJldSf4R3-U}=py z#+2oy#cp_GQSP*CR%c@T?eY7Ht^uFJ?WMa`AqakKyg!p|dw5bRY*E0l!piP&);E*O zb$5EsC6b%>KNNK7T6F&hC4xXRB=EaB1#m1teQ3j)@Nj9i?3N*AA)>8}yNZ9$wDoO3 z{ORWZ+7=-j6{a-3-b#J87r~~Jx4lwSicA-&F7b^KW9oz8@jBopgG_!eE_@4URP z;VZ%HUyyzqX9)UAZE5S>EQO0*!%W~t<1`1&Xfy>H&$dc^_ECTF)E7pQ?RK@&+35mx zx37n|JTx0$D(v4^6wbnfvG zY0fV0TA%afEdB(~o=?7xAlDkHU~{*&SOwB1@HsR@$_wZlCB3aZAC^x+r|pMqZQ1}P zaP(`|G<%GBf$+{re6)Y3D_Z|8UD53R2VIfm(fhA-W%?g<=`Mq!NB?Utw{+oHa~!^zv;3J5zhPXI-K-<6WGE-l>Tm>VHG*5!WeV-8}-l zzz!>>99kJ}v!8TD_>Vx=V6sA$hQfz9tYFJ99_(G9CBim71&g;{E1h9LTEB}v6YF2R zk7W(O`+(egAHo00`v^Hh;0^&Gm4eDp2ZclJqkK)SE)?{?-beEMAMfK1nMB$|I{a1d z!Pp<&TCIZq>3z^tss`xoGT;ozvq^=ZFM37u;pW}-2lqH{pJUkho~ENk3`pF9-`jRT zD|G^1t$BYVU*~2zcss1qzm2*ETIb^;I2gqll1e3U>uL|}AjgZSkKSR6gkH!?4AQtG zvk=j#QDdMc9R5rX4ZrJk%U^DIgcFl03HneV_bfTE&-}Oe?_^o-qtKe5vJi(mYT1&^ zza_O0#}p#>*};8~_VoRp>e`&4V^hX7J6#vO2=}N{PUcG{4X!PhJELBFeW1U_155d4UfEv_YMP^Q^oop z?QjKjqAZhGX&hQ*006}0F97l6zv=;~h?2IJ0d~38MzYREE-uJd`d!WH_zbyS5a)-g z^G}5%jggDYAOewixjLY-Y7Nc0A*rGDv|H_Y79x#+f?AF}>-l0n38rWjPjIP>%KPAH zhj$*3^KeQBAe!T#%VQt@lYL+xp-(X4$qOw#SS0)b67QmvQE7X^02T7>$PV8cK0l! z;Ljd3{V%o$ErV5rGfyJJ-{yjRoIrlS_5f7}u>y@rcfJRbz<>2Ua^93vM#^G41M^e; zk^nM_5vXTwq3O38pz=rd$d;63c@^b#Tvty=zyC!aug~sSV7&rcB!mYbLn64Kjyz4= z5Q(fqR=Lp_Aje-XTivxVta=4${Wp}cs2QQ*2v_}!GLEUW#ZB9&+*F@lB=-g#9i|9l zpr18g%h_c$n#U3+sCKb?vp9|as<*~{St553{{fGn^o<-`s!ja;nHkw!q&DvBj}n07 zF%jppf{3_STh0z{(0H=IsKMYx`X}&X_~S4X(_WT}p(aWv*V18DpwW0((I8G;8W&p7{50_8F zuCw*(`P2F^p>5z`h9?JYAKSv`1EsODVd7(eq(_md`G)ldB?leuxk5WRJz#XSrr#SK zCOH3Mbc8B#|AodvQSSrPJN=PBt0l9rxB}G{v)->3;r!}{Co3+j4Cw(wl&GX zpujP%mV-w58-&aFKs1~jTFd8kR}C~A@joMOJeWe5ZqY@tmFuz4aj{ikn?I7@-rF0k ziE{Yv!!c-){0(ezSopBF9EoCuc#OHjUJdWfKXT%nP%kK30CY1k*3i_P>qJpR2zYVw#(HwJF_PKAaBTQ$*2u{M( z);Fl45B}Q(vnY2_98t(2jgg zebpDCxI?s-M8dIXj&7Nwq1^Cg{Fl%tyw!}vq-el_4c=-ONb!tJX(74q)=M7xgQB8n zJ+1bxzz{_<_D&SsgC6|hg0li{#@xeE?xIujbH6S+)qQdE^gA&lOzvLH*he=1smxw( z&fLLtywiF1g|VyBNVu$p&d$SEt)mQ#@KPHa>zQJXj{;69Vn`W`;VbvgT-w&7N9GL8 za>=AiI{)x8D(&vQj6@EuZ8l_o>=$EHNMw4#vHWcq%(I-F6(6uF;;Ra&kMSN5@D%>h zGPt|Q?h<}GAmM-SgBh?W%XC5?dAUFKUCK}H}Y)~&l z|M1w1?uOGSXuAu`NSRt4Ej2S-Fbmj7IOP~t>Y?{tNlI8T!s<3V@+8NuWc*d+|g{PAtfvpG8C zxHtM85;r87ck$EbfAoYXYX9j8(fp@u!K!$k?{n^U_4-HQo8J8g?gAJRVt1t~k_1G! zIWt?l4wW=M29sRm6wq#xWe7I8za3fv*E}-yK6%cw8eJC-i1VwV z+I0=f@3OC*N4FQBAg`b zMtVCycM8jZ|I!6%Vty^KKXgS`uB8CN!1FIA{rAbU$5z|-`M>zJK!ng5V-eCAZI@gysy?$NQKZ{de;ebZ0Xcu*3DCadc(Sz+ACJMNGJqxt0I^-!~VOBz4#~F zzvSz0p4{i_BXJ(swq$)qj(b-M5yV`@Dkr2Xg&YfBN2;~sxmeUsscRKe0K&ggsDkZ` z?ed%JwVV(o&@i;={4i_OX0_+Jr|f#p5}=2?%gexI)Q}b59f+~N!jO@5=Prk&Nq!>s{ScT`H}~%tCHkKxOO%wV-jeFKF^GJ&qd-9j-y;bX04+iOt=f(~ zlt9-Ti~2(=@Q{3@`5QV)SpdpV3M)jsh!1A((U5ti!6`2+&)k6@0_xH*gzq?aBh>ni z?^LR1K;Y_sjELiB)C?)!k+P9y(_Jk%dEFm>p^(k9l(dUmE8CWB1a`?FKty7iBb~y3 z=49aurWa}mHBdYrQn{>Ad-xh+$|HX4-?DzrhNKzr)%2u&(3(9Mdr}YA+ZQ-?Y{X{m z0px&RW4?HqDeuaij@$3k+1P~9gwPpqicR6N0kVI2%!EvlQ~F`HF{n4bxgKG<(127B8{zXqZERKI6$I@8OkareOY^ zZ**mcxI;g77Tj)kTyUim;IJg;BAQDuGC}HDpWzzU5hxovQA0d8+K}b}jLn_gqI#t5 z4Y`gIEL?`)40<&J-Q#vy5Qcr0oSQQRsyK1C)ZR$%bcU5hxNcNKRV=FerZ8SiQMKFJ zH4BcTwcsvQ|5?<1xpPSJp2hsbVbayw>-CfdMf*{f*ARyf(T?<$UNOy~ZC?MS|Iu{6% z(y-`mI1~5Y@BYV$59d1H&iul4F^M(C9CM6k{O;$u$GZ75caFNp0{;WLD}!3zXI)8`xaouT9f$Yv|L9Va_5}LTNLEg1&An{#Go%A~{^?3GB0d5~M-E07Okuds6^)sQ4yB+f8`BI4 zG`~&z0f#$Wd&v1TK>cpDWNhLc%tXN?!3b>kG*_%t!@lV0Me7E)`Pa2XZi^x2NdD%d zb^%7Utk2L3+7W9=id${oIJ)0~F#<457&WQ5iF~Bwj?b|+Zy!E7NfuK;>~Pid4a8 z6|Hz-Lv*m`Cf%gKevT<2w;Su(6KUnB^Z^Sx>M{v5L(pop*+yD7QD^L(UIoQ74Pnkv z#|n3dDK&+Z><0&vuZDS;J_I5rMwybKwl6axB&O4}2vDk0$M@Jv!X3aszO zfZcDM>AE>txwEJ);VR8oC1@xaIdi&C>=w9NyE-AxK$*be2Feuzm!(eELyb0WBKgBP zKAtszh2q_Cmi#xGkQ?m|oLim#lqQ3a{>ns0{1vpv!|s}(B3AJ92&fAw;yn$FO%t|r zRp~v94^ex@8sx-*#_OYOg(3|spIAla?|b8zR$kLs2_t=ICPGWV1N2TRfDo`qB9SRb zcueVB+7fKSDIRqE#vFL$1(TuoGJUWXio1QiMSWizg#&-omLz)VpQG2jQYdt*LJc_8 z?Pq0DCkHjqQ5Cze^cTpLU1EYLWZi_*V4A``5UmBm0ZK2w*V>hq_a_VwnZ?(^N7S3} zIBy1he6_URe8qkVerjEIC*$TAgg}}@J78w54oPWLu0g+6yJP3SV5m7HS>6L>?ytUs z{pmHxU4`4RNcU0%lY3`3{lvD-h$05=Pvs@<2PK`wcN^cD`?6$B!t_Z2kA(xcPQv~Q zuYv#FYk(6S(y~_2yf84yu22#?wxcY!P(%}>@?ubkzajA>f$$aqI3&7?`Pizgmpb*M z7w5+6Y*-hIBhYCG20$LeNvKQ7Kc)(xC%C@fM&SrUJnebVw3gP}pkYkPAs@JT!tI_$ zumtpwJU1{M->64HSk4C$;_g0k{f=%=+wkWI3;ta?GO23e^6T4rdGpqhP6h%g&66~) zW-kRX`ef4Bt^NWhl0p(!BoyUZ!bF)Cf@2gi)y11&-b8*10I!;vR)jpeeXN8@u9FG@ zMUuTHkaupAuc{OmcCnTZ&w3UpI7~$!0oFsYFS&jM=9W>8ziEBUJAP)b9%hPJZr++3+zElkNt?e$Ufm+4V2LA4V=x%mQD$Auws|omoN4U8wOAUK09K@TLI&w*=M-e&dxac#o$!45Z&O8X^tqfRFlaOM{A9Oad2v7&Dk;i6ftY zesg5ZPpTx&Li~F7`N3$v(od*6kd&as%C3SWDudjmb;ToyY7?VCn?q090F*NAnS&m4 z5<1wpa5QhMQQj6h(Nn!XBRtTj6-2p{QQIC*=OgPw4FV%=Ufq zZwpQwL6)OoNHa9#S;#F#Y};A!c{idL(3Q{o=z0rL6mP_P4Jr>NdU8eOEg! zhIoAM)WQ3muZ>1IobFj|1uspP)nlpH+M8rd?zX;e=?6r@S)1)b>g1IapUaO%AOze> z)KSd-{G|5Ws7|c4O-r$x7OAzJO{QfP+W?!GDr_u*x1cn_ukdB79!By09*p)dEcMKz zl!pv0dxLk;s$1Uye{Fi&g6E4~(r!u1BX5~13;Fp3{TuFYLoQi+*`}rB@WeQU*Jmk2 z)Mf(0fpf~5XM=hR+U>9u?}+4bi? zTc&YjRHL}gMuYHZZ2&c8qNxhyhl#}RKj7o}-^mHW2DcoaAI&gsEQjI=zFebK5t$o= z`QtW*=Plp0ns|R#AnWGIsTSu?yZ}dhS4+W&W7Q@Cv-QUS=83F(Q6L4b;JS z9xNLBAKXYWzZv$WkxB-b2-n2X66H~3$!R22PUeKy?u@p>Vspc*ug>PYM*lbd7-uno zKYyg&0-ks0>0*ic)1Q?CpgVOKXObkt{(ynq_PwtkwB%OHl+^L!EI6d0pF%!)3P?h_ z0p279;=hTF18f1>wD_7wSGFcIXb4~;^Sz-ie_Sy3{h z3E!qX$25cVcISW1ax?SEwLLmtXtDq6d~s)a4|pTTSPz=A3%;XpAXeZjU_btDdfQmP z(YT|N^O%z{(2idNSmmi-c{jFN^-rucJcyg_&ek?N%Wzbwf} z^^5aY2}EoruhU^%eVT^cAL{>563t^e>kyFVhg@Es`y0ke{%57>er;3Kk$lWa$|~f5ZjWuR@ty0yyR+$@V`6>& z^&ZRKt8w_wTN-to^ZxqZEIvS~<6hj!VEQ{4oTirO;`$ zJ?D+N0XJRjIPl%!*YR3^9LBn&){<5(ie-Ckox>H)8GNp?;df6*9;f?|h~60f4PlR8 zw~K!2*3T<=o~8`ePqdx74TX{v8eQ80!7);Ag{pa;TdL%Z(WfrJr(}s~H(pSy3UG>^ zb)yb2yjWhGKCOV5eqMl@)~Itw+D2CPO8RW+>@OrpQJvCz@p0}&xrHB~Tt9oD!r!}& z2^gTob|>Ro7`^i1aLc-)*shEF(uz$}mvpqYXptj(oioozm8F;|_v@k+SZBsn|6XT8 z=icSkB%s934c*wRm+~i)7m#2`-5zOX3qo^$8Xp_>;}AORO3Q=-KF zDUw&;Bsf3<3uvdX;Z)$R>5nvzk6PukhVX8PUmi+Yh6i)Z#-WxOT}x~{62Nrsdu80JjI$8_784f zi+>K*Se?KcPH`KNXe;+`T`I`ip41;Qo$$y7p4_ZlOFFUd^H667>=TAk9rC#V>i4th zqv|s3nt)?jNlm}bzS`#6UlWDU9|vO~>*zlB;o`ymn(5OX_QM19$>$p!Ol<^s2>Zj~ zRq05Q?p@*Z4bBlY9~BvhP)^?eIty^mXm^dr2>CX~h=DqRL!KO~9U9d+Gm-R$l+>3s z0C0G=%N4aWO7dJC{!vo%n0qrK?Oh!N7--YG$dEe$9;C?%Ei}Yd-lg?KH_0sdE*GbM z6E2US1(txTTt*8Q8~EWB#1es5@{B*@+%4PIc{8Brats*-2i9pyx{Jqf_lZj;vYLQ5 z%u?j^ZukdCGdCT5t;15<=SO@l_;_`=eXD3u-_*@`)hMUk%_!E6`~1ikMB2MH56WjP6q$wyamK-`$uy~q|@Kfsmo~Y7X%2$;8&C4Lt8i7hUmaJ7Cr=`nXD8#4$Lx$|^BZKu z(&=h+mU!lM#?(y#4Qmeq+NmD(!e1Q`&r?6Q;d#z>e8n|38v;NiFQLmHEMYvJXcb}v00 zjVUHKu~mb)>6?1>41R{<{(AWEul=tIv3Q%G|C573lK58+27&Im6nClA#0lM!CE^gmP)BS69YS`IX{8Z~cv5UTWAG=&P9?vOHnR#aPYNSbn>7Qrsa`{E}(Dlqfj=if<+)-vI z6^p!*g^C39{sPF6zSLz#VKg+2E3f)dn2fZQ@*p^!fWJi*&*B0odVrw{yd}*qlTKsF z$z$S@w54eEK`P>UKqtk|;@L5T#s6LXg}`2_mQ6FM&cq)J%dWu~VOzwIC?CXZNU^ch zmJy775_>-NpMJ;KueX1#(q*aUb9M1)YZAh{auGa|3y8iDmjGl)6hd=u(Kyv(0!KMf z%TKbq0=*Cn4e_h0ZR(P`lk=E34LMnNNDCjdK|DHME!V|~&iEm%R(@4}4KNFM4`$SU zQ%}3E)b%xn+%8Pz={oZeGIy9Tm4tMd`Ui*@=X+XCOO6m)kWw&EvGC`$n}*BAeS zoAoapUpsE{{ZGXzs1+Hx2&bdlp4?afg^=F4lnP=@;HKvSxYcHe8sg&|4S*2v=3TRd(^Aplkt8+=c zNcIEb`m|@*t{QTW2$q%MkLmQ@^C&WFd2J+Y`0h@7u1-`^^Xube;9f^?D{Pfiy+-}! zX^G^Yt1Y-4c2HMnOfD8com{Jf4S)D1_R{Q6;=?C-sTu{TvTPc|mg}jA;0_t;Fj)^- zemO!i0!-w8p$_Du%%L2C)$s?j&(XYH>IZyg?|U%|rMZ4$>w~LvBk#?2`@aq#rptJ6 z6O{jBTG`(5n+ba{ular)T0l6%Jwn)7va~R8qY_cO2*u@1rtiBpFx^0=)tZiLqK$s_ z+jlaTX&-oC%eIrjJBJ$7R4UzAe*>LGoN!ya{l2+^L){jkWb$5=nHf(>%dfqH+~-EQ zU{2E9sO%^rIxNAi#gBP_0>?WPLJLURLRkA4nDMh-gAv(V%MojnnPajj3-?Y>H9?Hi?wP8qVJ1 zNKQs2j0*Xj*-BPPLpP~!L0Dxfc8~3MK=xVDrPOC6bz$_zL(oEq(c+DTWkI2ow@s;7 zqtGmLA;%@5rLHvH(<}4ncRr6Jx#?HCuRJqxjvyXlcqGgHi<_5l(PAjHv^X*-%I^^x z3!1{hDrr#X-%Z0(BR3f|TVJMljP!pP!PQi_U1@%4@MCD;cJrZsXaM>Vk|j~5T8fw+ z8XDR)PIx^=77$jb0oc|z{U**I2el~=gwNnk@n?9CBgtW-+bA`cnZFUmr9*^qq|&KJ zA*N^5%#bD)QavPT$EkGpx;T||*)7Q-7>KtdBvMll8M6^txhCV#pk?57PqH9ReCc6E z84zoAjE1LEb`^KwT%B6o+GDEnMh}2sZK)b=8eUnzvML;Apm8Kg$_w7D5GqnkoLNA>3Ps$D;e&N;Usez;VZ`4n4dn`#7&gJ zmMRFf1dd$s6;@orS#GBtI;Bh^iYXgp6WPwg75FmAtUWP(f_I6ea*-L!GM;*Z%HG|6 z(M{v^XOU0`M7`!ciBWP4i`+R^qa zJkr-slg3|kC%3M{qkcR3X%Dv)@r|o;$tW`5=m*&5;dK6M!nhnl+cEGlmg^%fCWv5R zXtYFD#>a2PLqLAthPP-h+7(?B_nIv-tW|IHMkp8>pQTXRAQTczm3Zj5~c?8s# zD1S5e#vSIF$NPHXitIvvXbLk4YanE)2jEa6qcZFlz7J)rMi3@Eib9_|`e6$=hjg-i z6{WnXaS*?t#e|jXXRNR!XG|29s|w!;3y4ycrrBv%x0_GOz$uULjwJb>rU)v8@$<@R z)>5cQFK#{X3ak@i5fnt=q9TO1_IzO8-tj?Q1LJ*0% z7DBz|{~7UnwSDPBs#sLHsY~K-9S|^ zE<~paHCcJZ;-=V%0GA&P)O`}@!4XC=nu7MN1n6iSwXDJeG(rn$g}!w{HYQif=+b#H z+4e+J7!Hl?HRF0{UU}nDnWGMw+j$;mwnvB>r9x5Z$iA2`aH%bhmHuQVjejkz$NJlS zi@GvisBiq|@v+@+s-w5oX#Dp_lV#BesJw6&(3rQzaJ`U+AZASA{%(8G6u67!xp?=s zkd>>7v1ENvNe`VKLTkr|LW0QF9541@```S%cCqj`tK$eE2muRH%8X$b=N%7>_UNxG z#jv(*pIE=H5cNcD@XdRuBGQcJz*T9+uN>E-BEV7m?9GC!3WicWgu22N{%#O;7lsLh z7!)dcwH%>(Y=AIDi#cHJ8iM%C90l|OQSl};5ln|`AuX6Lt7Bq^ z3*Q>txO>coR0w|*LaD<5R^T|sdEm1miX8hPTP(}kr-GQ}9DqGtM}MP>f}){dtAtMX z0W@ISXm=|yql*&Imf8>24=5e)04{S_Wcj0CDVA(IYJA&+FkUc=;E@+gk^uiYVc}~v zofSW_5=vUz%SyXvbWDn@H1VXQs-=v)D`V$U$&7d;iROf&9JQvb+%GzH1xsSr6+k62 z0IO>?mpkMONP6%iN`O;OaTZ=2OdP4`N>o4wuE=uAlE*V3+?@Y&3Bx*dA5W;l`u9>a zPWyw&9zWw>*K!%Uj=VjEN67AvhuhI9r~B<6Qymu%<)Vj+e;u0x;z2xL#;yYav6>lh8xvqpYq4=0~_1&r*zf~rCc8;$O0k@WxzZa`~ms@+(HZt zDAxj9v?=4!q#?+`+k^an-BIH>+))jG@w)K9Sr_Hosr+Aus92UT7R#`F8Tb0nQ5b4u zDt~^;?4Ogl()p9oifa^>{?tG|{`bPH?53sipYMDtUe~>~JnXoMxN(-+&y-Y+e-XTy zhW78zrC}O+=?+die6HJ{(b<~|GGlJVuE)MuZJn2jb1Kwj2Hcmi{<6*Hflw6FMCes5 z>Fr5f-fPV-zrS&PsE+#fB0u@(me@ZPR&E5H?$Qcn@yR6k^SPT_q5;+a<8Oo?ViH4< zpm&OJIiZ`%7t`9T$v_t&|7C_xVi_;<>eZ@q0N=;Z&EGx~cOL(@&Ft*II+4|4Z8`756e>&MMLVW*8^n8%Xrr{u1 zF=FHk-Q~T&sEAztp9jR=_bdXfi+)vz{HOlG7e6=Kw)ENCcQtYNyMoyR95FmR2RY6;Qfa( z-l^T54gq_tmNrb;o2J9L;Y?YPt4)r~h!see+>S2Gz0b}D&vZIPK&j)J#Kw*}wO!_} zS~t=8u-u^h(q3PpRe_INQF;`OtQBn?jmRXlFFUCw1jhdkQf` zp7RP=zU(;ZxoP75*xayLU!|*=b)O)6W4Y)X7$~qexKxH!Y0O6;WU?5rdiHjz208LP z)#7edQ?H4Li)uFa!$wr0Hm*qdTi8CxZGEVV9y^mbPps=7k#jJ&d*R13?mF%z1~*6C{K_%o zZfLAJfN#7xxXYZ2CZI}72JC_R+E}rly$=?OXwa=?F5Jo92x~M$C^K`=WRyM!9z}>b zY~oNu0LyC@@=TP5gLow+(}`T)a=ezd*amF5WV+;__M`19O_B9rmQCb}cU?{JFwe(k z+B>#V3GN!Hjr&ziR?ZiCqiLi$JPW2VJNLWar*30|1Zt!hIEa&!!L=#q<5P(V;~qao z0t7)H?%vJ)%(rBeG0cYDv~)oXG!sNL8z(;g)u?o!QQm(VMGGo3<9THmT$AUILI1X^LX+ndUXiWLfR$HtBedBgN}OM9@PrzRZwS3y z;4GW``ac}{Jq!>o8?E@<3-D24>5#iSa8jl1ci)+`UPp}0l#ULyC`n;mlI2%cgd6h!-!|Cc+TPD~(X@l@Wllra0>@g!DCB_*ic(HtN6Sk~3_u{hks~M>k_u>9LgBN+epQyLry(=jg1K*s^mhH6fu4Fw62+r zP{l5;6?iWZB61=!Lf*UhEPoj6a$?=yXHcy;OOW8<{z!s|iK>%E^0L3DG^G@i>=`Rp zTIfTr7A5GR##8mN&^y9{*prxlddN)UV9#f{02*c zJR*5#RAlqk>fxvJNDY1~?)@>y2)j$R^(z;ZLfaKI$awhfSNO6JB=OQ-@_t)-)*>`3 zws(7q&-5Ypf%kTu5;Cf2;({@)b~Z9Bxa&iVNk-$lUtgLN+;-GtKbZN- z{kM_04kGm7V5Z--pMonxP|D=AL;jE4$!p) zk5H`Ovh8j**wVlJ!CcY32}1~K$jMQkPRPd5*5|~rnoqj(h16^OF2mDg!ak3=dT zz9w9EyKBYAiP1F9#+JASLkXn}{exh%9!UYsd=ile`0=bh`8TXi-X)xNsy5_i@FI*J z_d77%{T8p_ZC}yUO{Igl_jzhM?VcnwM(T%f|uv#H4w5z!`K*VliIqtCFMTWL5D3wi|BCN-Sdd%SP-N z(gKw+Ud|}e^oSopS#dj;8Li+gZi}K{p?+AD!uqA+5OD3F?gKqvY?_Ik_Z$9&u6Kuu;QW^;1FGrY>aBO6cuyb!0wDJ^*q-V%-vR7T+ zGW2<}8y-j60(l(oYfX=Wpx^G)Z^q08)!^0S>ln_LtOPPIy~O( zOIbv@ntE|giqJMR?_>y#onw+}FitBY%5pl*BBa}9#5|LZD8wPke$~P{A4{dN;^rZ< zR>Cfrf?h1YWHeTmAY4Kx0X>z2pra3oJ$!I(reHp1tmATL-M*}zd7KP?ZJJzf#J0JJ zxygLv2zWbx(<=sIUF@8Ud)5HrRA^~)?;c+I)HM7@=f@lTDC)vada$M z$av8&sim()f6Ia^W07q=zXGV6!o#x(K;bPe6*xfqJ5nc-e%k(i)n7;YNg>{apw{yC zy0*B4J-?{&YExcJ=6To&JITim7i#{YM#(txxbl^4Ynq&hS0Na5QQG+{t{A;KJXQp@ zPu>KsPCl{V6<6f_g3T0{Q+3+FAPOVu>Jt!DiS3S$pLS0-5{KyMG+8ZtAv{VLrA3i|1X9L8xb0i^*#X44 z=Ag5IDUr#7FSHDr(e{SA`6H

&DZ~!Gh8XWc`uuzB-&vDnKU;C#zEE25!Q_`cS`> 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 Sx|<8e0QT?obSiN?O8E!zq~jO> literal 0 HcmV?d00001 diff --git a/charts/loki/scenarios/images/removed.png b/charts/loki/scenarios/images/removed.png new file mode 100644 index 0000000000000000000000000000000000000000..219d64c32c983e72efdc914e7ef31343dba85fe5 GIT binary patch literal 71762 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 literal 0 HcmV?d00001 diff --git a/charts/loki/scenarios/ingress-values.yaml b/charts/loki/scenarios/ingress-values.yaml new file mode 100644 index 0000000000..ff5ff1efd9 --- /dev/null +++ b/charts/loki/scenarios/ingress-values.yaml @@ -0,0 +1,30 @@ +--- +gateway: + ingress: + enabled: true + annotations: {} + hosts: + - host: gateway.loki.example.com + paths: + - path: / + pathType: Prefix +loki: + commonConfig: + replication_factor: 1 + useTestSchema: true + storage: + bucketNames: + chunks: chunks + ruler: ruler + admin: admin +read: + replicas: 1 +write: + replicas: 1 +backend: + replicas: 1 +monitoring: + lokiCanary: + enabled: false +test: + enabled: false diff --git a/charts/loki/scenarios/legacy-monitoring-values.yaml b/charts/loki/scenarios/legacy-monitoring-values.yaml new file mode 100644 index 0000000000..ad520e57f2 --- /dev/null +++ b/charts/loki/scenarios/legacy-monitoring-values.yaml @@ -0,0 +1,27 @@ +--- +loki: + commonConfig: + replication_factor: 1 + useTestSchema: true + storage: + bucketNames: + chunks: chunks + ruler: ruler + admin: admin +read: + replicas: 1 +write: + replicas: 1 +backend: + replicas: 1 +monitoring: + enabled: true + selfMonitoring: + enabled: true + grafanaAgent: + installOperator: true + serviceMonitor: + labels: + release: "prometheus" +test: + prometheusAddress: "http://prometheus-kube-prometheus-prometheus.prometheus.svc.cluster.local.:9090" diff --git a/charts/loki/scenarios/simple-scalable-aws-kube-irsa-values.yaml b/charts/loki/scenarios/simple-scalable-aws-kube-irsa-values.yaml new file mode 100644 index 0000000000..28c6c3bbe9 --- /dev/null +++ b/charts/loki/scenarios/simple-scalable-aws-kube-irsa-values.yaml @@ -0,0 +1,67 @@ +loki: + # -- Storage config. Providing this will automatically populate all necessary storage configs in the templated config. + storage: + # Loki requires a bucket for chunks and the ruler. GEL requires a third bucket for the admin API. + # Please provide these values if you are using object storage. + bucketNames: + chunks: aws-s3-chunks-bucket + ruler: aws-s3-ruler-bucket + admin: aws-s3-admin-bucket + type: s3 + s3: + region: eu-central-1 + # -- Check https://grafana.com/docs/loki/latest/configuration/#schema_config for more info on how to configure schemas + schemaConfig: + configs: + - from: "2023-09-19" + index: + period: 1d + prefix: tsdb_index_ + object_store: s3 + schema: v13 + store: tsdb +###################################################################################################################### +# +# Enterprise Loki Configs +# +###################################################################################################################### + +# -- Configuration for running Enterprise Loki +enterprise: + # Enable enterprise features, license must be provided + enabled: true + # -- Grafana Enterprise Logs license + license: + contents: "content of licence" + tokengen: + annotations: { + eks.amazonaws.com/role-arn: arn:aws:iam::2222222:role/test-role + } + # -- Configuration for `provisioner` target + provisioner: + # -- Additional annotations for the `provisioner` Job + annotations: { + eks.amazonaws.com/role-arn: arn:aws:iam::2222222:role/test-role + } +###################################################################################################################### +# +# Service Accounts and Kubernetes RBAC +# +###################################################################################################################### +serviceAccount: + # -- Annotations for the service account + annotations: + eks.amazonaws.com/role-arn: arn:aws:iam::2222222:role/test-role + +# Configuration for the write pod(s) +write: + persistence: + storageClass: gp2 +# -- Configuration for the read pod(s) +read: + persistence: + storageClass: gp2 +# -- Configuration for the backend pod(s) +backend: + persistence: + storageClass: gp2 diff --git a/charts/loki/simple-scalable-values.yaml b/charts/loki/simple-scalable-values.yaml new file mode 100644 index 0000000000..78132b6d96 --- /dev/null +++ b/charts/loki/simple-scalable-values.yaml @@ -0,0 +1,63 @@ +--- +loki: + schemaConfig: + configs: + - from: 2024-04-01 + store: tsdb + object_store: s3 + schema: v13 + index: + prefix: loki_index_ + period: 24h + ingester: + chunk_encoding: snappy + tracing: + enabled: true + querier: + # Default is 4, if you have enough memory and CPU you can increase, reduce if OOMing + max_concurrent: 4 + +#gateway: +# ingress: +# enabled: true +# hosts: +# - host: FIXME +# paths: +# - path: / +# pathType: Prefix + +deploymentMode: SimpleScalable + +backend: + replicas: 3 +read: + replicas: 3 +write: + replicas: 3 + +# Enable minio for storage +minio: + enabled: true + +# Zero out replica counts of other deployment modes +singleBinary: + replicas: 0 + +ingester: + replicas: 0 +querier: + replicas: 0 +queryFrontend: + replicas: 0 +queryScheduler: + replicas: 0 +distributor: + replicas: 0 +compactor: + replicas: 0 +indexGateway: + replicas: 0 +bloomCompactor: + replicas: 0 +bloomGateway: + replicas: 0 diff --git a/charts/loki/single-binary-values.yaml b/charts/loki/single-binary-values.yaml new file mode 100644 index 0000000000..584f0fba1c --- /dev/null +++ b/charts/loki/single-binary-values.yaml @@ -0,0 +1,79 @@ +--- +loki: + commonConfig: + replication_factor: 1 + schemaConfig: + configs: + - from: 2024-04-01 + store: tsdb + object_store: s3 + schema: v13 + index: + prefix: loki_index_ + period: 24h + ingester: + chunk_encoding: snappy + tracing: + enabled: true + querier: + # Default is 4, if you have enough memory and CPU you can increase, reduce if OOMing + max_concurrent: 2 + +#gateway: +# ingress: +# enabled: true +# hosts: +# - host: FIXME +# paths: +# - path: / +# pathType: Prefix + +deploymentMode: SingleBinary +singleBinary: + replicas: 1 + resources: + limits: + cpu: 3 + memory: 4Gi + requests: + cpu: 2 + memory: 2Gi + extraEnv: + # Keep a little bit lower than memory limits + - name: GOMEMLIMIT + value: 3750MiB + +chunksCache: + # default is 500MB, with limited memory keep this smaller + writebackSizeLimit: 10MB + +# Enable minio for storage +minio: + enabled: true + +# Zero out replica counts of other deployment modes +backend: + replicas: 0 +read: + replicas: 0 +write: + replicas: 0 + +ingester: + replicas: 0 +querier: + replicas: 0 +queryFrontend: + replicas: 0 +queryScheduler: + replicas: 0 +distributor: + replicas: 0 +compactor: + replicas: 0 +indexGateway: + replicas: 0 +bloomCompactor: + replicas: 0 +bloomGateway: + replicas: 0 diff --git a/charts/loki/src/alerts.yaml.tpl b/charts/loki/src/alerts.yaml.tpl index 144e263f70..0aa37b708b 100644 --- a/charts/loki/src/alerts.yaml.tpl +++ b/charts/loki/src/alerts.yaml.tpl @@ -52,7 +52,7 @@ groups: message: | {{`{{`}} $labels.cluster {{`}}`}} {{`{{`}} $labels.namespace {{`}}`}} has had {{`{{`}} printf "%.0f" $value {{`}}`}} compactors running for more than 5m. Only one compactor should run at a time. expr: | - sum(loki_boltdb_shipper_compactor_running) by (namespace, cluster) > 1 + sum(loki_boltdb_shipper_compactor_running) by (cluster, namespace) > 1 for: "5m" labels: severity: "warning" diff --git a/charts/loki/src/helm-test/Dockerfile b/charts/loki/src/helm-test/Dockerfile index cf4420a2a6..9645b206b1 100644 --- a/charts/loki/src/helm-test/Dockerfile +++ b/charts/loki/src/helm-test/Dockerfile @@ -1,4 +1,5 @@ -FROM golang:1.21.3 as build +ARG GO_VERSION=1.23 +FROM golang:${GO_VERSION} as build # build via Makefile target helm-test-image in root # Makefile. Building from this directory will not be @@ -7,7 +8,6 @@ COPY . /src/loki WORKDIR /src/loki RUN make clean && make BUILD_IN_CONTAINER=false helm-test -FROM alpine:3.18.5 -RUN apk add --update --no-cache ca-certificates=20230506-r0 +FROM gcr.io/distroless/base-nossl:debug COPY --from=build /src/loki/production/helm/loki/src/helm-test/helm-test /usr/bin/helm-test ENTRYPOINT [ "/usr/bin/helm-test" ] diff --git a/charts/loki/src/helm-test/canary_test.go b/charts/loki/src/helm-test/canary_test.go index 24e9d6d018..002cae45b1 100644 --- a/charts/loki/src/helm-test/canary_test.go +++ b/charts/loki/src/helm-test/canary_test.go @@ -7,19 +7,41 @@ import ( "context" "errors" "fmt" + "io" + "net/http" "os" "testing" "time" "github.com/prometheus/client_golang/api" v1 "github.com/prometheus/client_golang/api/prometheus/v1" + promConfig "github.com/prometheus/common/config" "github.com/prometheus/common/model" + "github.com/prometheus/prometheus/model/labels" + "github.com/prometheus/prometheus/model/textparse" "github.com/stretchr/testify/require" ) +type testResultFunc func(t *testing.T, ctx context.Context, metric string, test func(model.SampleValue) bool, msg string) error + func TestCanary(t *testing.T) { - totalEntriesQuery := "sum(loki_canary_entries_total)" - totalEntriesMissingQuery := "sum(loki_canary_missing_entries_total)" + + var testResult testResultFunc + + // Default to directly querying a canary and looking for specific metrics. + testResult = testResultCanary + totalEntries := "loki_canary_entries_total" + totalEntriesMissing := "loki_canary_missing_entries_total" + + // For backwards compatibility and also for anyone who wants to validate with prometheus instead of querying + // a canary directly, if the CANARY_PROMETHEUS_ADDRESS is specified we will use prometheus to validate. + address := os.Getenv("CANARY_PROMETHEUS_ADDRESS") + if address != "" { + testResult = testResultPrometheus + // Use the sum function to aggregate the results from multiple canaries. + totalEntries = "sum(loki_canary_entries_total)" + totalEntriesMissing = "sum(loki_canary_missing_entries_total)" + } timeout := getEnv("CANARY_TEST_TIMEOUT", "1m") timeoutDuration, err := time.ParseDuration(timeout) @@ -32,30 +54,18 @@ func TestCanary(t *testing.T) { }) t.Run("Canary should have entries", func(t *testing.T) { - client := newClient(t) - eventually(t, func() error { - result, _, err := client.Query(ctx, totalEntriesQuery, time.Now(), v1.WithTimeout(timeoutDuration)) - if err != nil { - return err - } - return testResult(t, result, totalEntriesQuery, func(v model.SampleValue) bool { + return testResult(t, ctx, totalEntries, func(v model.SampleValue) bool { return v > 0 - }, fmt.Sprintf("Expected %s to be greater than 0", totalEntriesQuery)) + }, fmt.Sprintf("Expected %s to be greater than 0", totalEntries)) }, timeoutDuration, "Expected Loki Canary to have entries") }) t.Run("Canary should not have missed any entries", func(t *testing.T) { - client := newClient(t) - eventually(t, func() error { - result, _, err := client.Query(ctx, totalEntriesMissingQuery, time.Now(), v1.WithTimeout(timeoutDuration)) - if err != nil { - return err - } - return testResult(t, result, totalEntriesMissingQuery, func(v model.SampleValue) bool { + return testResult(t, ctx, totalEntriesMissing, func(v model.SampleValue) bool { return v == 0 - }, fmt.Sprintf("Expected %s to equal 0", totalEntriesMissingQuery)) + }, fmt.Sprintf("Expected %s to equal 0", totalEntriesMissing)) }, timeoutDuration, "Expected Loki Canary to not have any missing entries") }) } @@ -67,7 +77,13 @@ func getEnv(key, fallback string) string { return fallback } -func testResult(t *testing.T, result model.Value, query string, test func(model.SampleValue) bool, msg string) error { +func testResultPrometheus(t *testing.T, ctx context.Context, query string, test func(model.SampleValue) bool, msg string) error { + // TODO (ewelch): if we did a lot of these, we'd want to reuse the client but right now we only run a couple tests + client := newClient(t) + result, _, err := client.Query(ctx, query, time.Now()) + if err != nil { + return err + } if v, ok := result.(model.Vector); ok { for _, s := range v { t.Logf("%s => %v\n", query, s.Value) @@ -75,7 +91,6 @@ func testResult(t *testing.T, result model.Value, query string, test func(model. return errors.New(msg) } } - return nil } @@ -94,6 +109,64 @@ func newClient(t *testing.T) v1.API { return v1.NewAPI(client) } +func testResultCanary(t *testing.T, ctx context.Context, metric string, test func(model.SampleValue) bool, msg string) error { + address := os.Getenv("CANARY_SERVICE_ADDRESS") + require.NotEmpty(t, address, "CANARY_SERVICE_ADDRESS must be set to a valid kubernetes service for the Loki canaries") + + // TODO (ewelch): if we did a lot of these, we'd want to reuse the client but right now we only run a couple tests + client, err := promConfig.NewClientFromConfig(promConfig.HTTPClientConfig{}, "canary-test") + require.NoError(t, err, "Failed to create Prometheus client") + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, address, nil) + require.NoError(t, err, "Failed to create request") + + rsp, err := client.Do(req) + if rsp != nil { + defer rsp.Body.Close() + } + require.NoError(t, err, "Failed to scrape metrics") + + body, err := io.ReadAll(rsp.Body) + require.NoError(t, err, "Failed to read response body") + + p, err := textparse.New(body, rsp.Header.Get("Content-Type"), true, nil) + require.NoError(t, err, "Failed to create Prometheus parser") + + for { + e, err := p.Next() + if err == io.EOF { + return errors.New("metric not found") + } + + if e != textparse.EntrySeries { + continue + } + + l := labels.Labels{} + p.Metric(&l) + + // Currently we aren't validating any labels, just the metric name, however this could be extended to do so. + name := l.Get(model.MetricNameLabel) + if name != metric { + continue + } + + _, _, val := p.Series() + t.Logf("%s => %v\n", metric, val) + + // Note: SampleValue has functions for comparing the equality of two floats which is + // why we convert this back to a SampleValue here for easier use intests. + if !test(model.SampleValue(val)) { + return errors.New(msg) + } + + // Returning here will only validate that one series was found matching the label name that met the condition + // it could be possible since we don't validate the rest of the labels that there is mulitple series + // but currently this meets the spirit of the test. + return nil + } +} + func eventually(t *testing.T, test func() error, timeoutDuration time.Duration, msg string) { require.Eventually(t, func() bool { queryError := test() diff --git a/charts/loki/templates/NOTES.txt b/charts/loki/templates/NOTES.txt index ad192e7643..622b1a8c26 100644 --- a/charts/loki/templates/NOTES.txt +++ b/charts/loki/templates/NOTES.txt @@ -1,25 +1,184 @@ *********************************************************************** Welcome to Grafana Loki Chart version: {{ .Chart.Version }} + Chart Name: {{ .Chart.Name }} Loki version: {{ .Chart.AppVersion }} *********************************************************************** +** Please be patient while the chart is being deployed ** + +Tip: + + Watch the deployment status using the command: kubectl get pods -w --namespace {{ $.Release.Namespace }} + +If pods are taking too long to schedule make sure pod affinity can be fulfilled in the current cluster. + +*********************************************************************** Installed components: +*********************************************************************** + {{- if .Values.monitoring.selfMonitoring.enabled }} * grafana-agent-operator {{- end }} {{- if eq (include "loki.deployment.isSingleBinary" .) "true" }} -* loki +* loki {{- else -}} {{- if .Values.gateway.enabled }} * gateway {{- end }} {{- if .Values.minio.enabled }} -* minio +* minio {{- end }} +{{- if eq (include "loki.deployment.isScalable" .) "true" }} * read * write {{- if not .Values.read.legacyReadTarget }} * backend {{- end }} +{{- else }} +* compactor +* index gateway +* query scheduler +* ruler +* distributor +* ingester +* querier +* query frontend +{{- end }} +{{- end }} + + +{{- if eq (include "loki.deployment.isSingleBinary" .) "true" }} + +Loki has been deployed as a single binary. +This means a single pod is handling reads and writes. You can scale that pod vertically by adding more CPU and memory resources. + +{{- end }} + + +*********************************************************************** +Sending logs to Loki +*********************************************************************** + +{{- if .Values.gateway.enabled }} + +Loki has been configured with a gateway (nginx) to support reads and writes from a single component. + +{{- end }} + +You can send logs from inside the cluster using the cluster DNS: + +{{- if .Values.gateway.enabled }} + +http://{{ include "loki.gatewayFullname" . }}.{{ $.Release.Namespace }}.svc.cluster.local/loki/api/v1/push + +{{- else }} +{{- if eq (include "loki.deployment.isSingleBinary" .) "true" }} + +http://{{ include "loki.singleBinaryFullname" . }}.{{ $.Release.Namespace }}.svc.cluster.local:{{ .Values.loki.server.http_listen_port }}/loki/api/v1/push + +{{- end}} +{{- if eq (include "loki.deployment.isScalable" .) "true" }} + +http://{{ include "loki.writeFullname" . }}.{{ $.Release.Namespace }}.svc.cluster.local:{{ .Values.loki.server.http_listen_port }}/loki/api/v1/push + +{{- end }} +{{- if eq (include "loki.deployment.isDistributed" .) "true" }} + +http://{{ include "loki.distributorFullname" . }}.{{ $.Release.Namespace }}.svc.cluster.local:3100/loki/api/v1/push + +{{- end }} +{{- end }} + +You can test to send data from outside the cluster by port-forwarding the gateway to your local machine: +{{- if .Values.gateway.enabled }} + + kubectl port-forward --namespace {{ $.Release.Namespace }} svc/{{ include "loki.gatewayFullname" . }} 3100:{{ .Values.gateway.service.port }} & + +{{- else }} +{{- if eq (include "loki.deployment.isSingleBinary" .) "true" }} + + kubectl port-forward --namespace {{ $.Release.Namespace }} svc/{{ include "loki.singleBinaryFullname" . }} 3100:{{ .Values.loki.server.http_listen_port }} & + +{{- end}} +{{- if eq (include "loki.deployment.isScalable" .) "true" }} + + kubectl port-forward --namespace {{ $.Release.Namespace }} svc/{{ include "loki.writeFullname" . }} 3100:{{ .Values.loki.server.http_listen_port }} & + +{{- end }} +{{- if eq (include "loki.deployment.isDistributed" .) "true" }} + + kubectl port-forward --namespace {{ $.Release.Namespace }} svc/{{ include "loki.distributorFullname" . }} 3100:3100 & + +{{- end }} +{{- end }} + +And then using http://127.0.0.1:3100/loki/api/v1/push URL as shown below: + +``` +curl -H "Content-Type: application/json" -XPOST -s "http://127.0.0.1:3100/loki/api/v1/push" \ +--data-raw "{\"streams\": [{\"stream\": {\"job\": \"test\"}, \"values\": [[\"$(date +%s)000000000\", \"fizzbuzz\"]]}]}" +{{- if .Values.loki.auth_enabled }} \ +-H X-Scope-OrgId:foo +{{- end}} +``` + +Then verify that Loki did received the data using the following command: + +``` +curl "http://127.0.0.1:3100/loki/api/v1/query_range" --data-urlencode 'query={job="test"}' {{- if .Values.loki.auth_enabled }} -H X-Scope-OrgId:foo {{- end}} | jq .data.result +``` + +*********************************************************************** +Connecting Grafana to Loki +*********************************************************************** + +If Grafana operates within the cluster, you'll set up a new Loki datasource by utilizing the following URL: + +{{- if .Values.gateway.enabled }} + +http://{{ include "loki.gatewayFullname" . }}.{{ $.Release.Namespace }}.svc.cluster.local/ + +{{- else }} +{{- if eq (include "loki.deployment.isSingleBinary" .) "true" }} + +http://{{ include "loki.singleBinaryFullname" . }}.{{ $.Release.Namespace }}.svc.cluster.local:{{ .Values.loki.server.http_listen_port }}/ + +{{- end}} +{{- if eq (include "loki.deployment.isScalable" .) "true" }} + +http://{{ include "loki.readFullname" . }}.{{ $.Release.Namespace }}.svc.cluster.local:{{ .Values.loki.server.http_listen_port }}/ + +{{- end }} +{{- if eq (include "loki.deployment.isDistributed" .) "true" }} + +http://{{ include "loki.queryFrontendFullname" . }}.{{ $.Release.Namespace }}.svc.cluster.local:3100/ + +{{- end }} +{{- end }} + + + +{{- if .Values.loki.auth_enabled }} + +*********************************************************************** +Multi-tenancy +*********************************************************************** + +Loki is configured with auth enabled (multi-tenancy) and expects tenant headers (`X-Scope-OrgID`) to be set for all API calls. + +You must configure Grafana's Loki datasource using the `HTTP Headers` section with the `X-Scope-OrgID` to target a specific tenant. +For each tenant, you can create a different datasource. + +The agent of your choice must also be configured to propagate this header. +For example, when using Promtail you can use the `tenant` stage. https://grafana.com/docs/loki/latest/send-data/promtail/stages/tenant/ + +When not provided with the `X-Scope-OrgID` while auth is enabled, Loki will reject reads and writes with a 404 status code `no org id`. + +You can also use a reverse proxy, to automatically add the `X-Scope-OrgID` header as suggested by https://grafana.com/docs/loki/latest/operations/authentication/ + +For more information, read our documentation about multi-tenancy: https://grafana.com/docs/loki/latest/operations/multi-tenancy/ + +> When using curl you can pass `X-Scope-OrgId` header using `-H X-Scope-OrgId:foo` option, where foo can be replaced with the tenant of your choice. + {{- end }} diff --git a/charts/loki/templates/_helpers.tpl b/charts/loki/templates/_helpers.tpl index d799d3e07e..9a4ab135db 100644 --- a/charts/loki/templates/_helpers.tpl +++ b/charts/loki/templates/_helpers.tpl @@ -50,17 +50,24 @@ Params: Return if deployment mode is simple scalable */}} {{- define "loki.deployment.isScalable" -}} - {{- and (eq (include "loki.isUsingObjectStorage" . ) "true") (eq (int .Values.singleBinary.replicas) 0) }} + {{- and (eq (include "loki.isUsingObjectStorage" . ) "true") (or (eq .Values.deploymentMode "SingleBinary<->SimpleScalable") (eq .Values.deploymentMode "SimpleScalable") (eq .Values.deploymentMode "SimpleScalable<->Distributed")) }} {{- end -}} {{/* Return if deployment mode is single binary */}} {{- define "loki.deployment.isSingleBinary" -}} - {{- $nonZeroReplicas := gt (int .Values.singleBinary.replicas) 0 }} - {{- or (eq (include "loki.isUsingObjectStorage" . ) "false") ($nonZeroReplicas) }} + {{- or (eq .Values.deploymentMode "SingleBinary") (eq .Values.deploymentMode "SingleBinary<->SimpleScalable") }} {{- end -}} +{{/* +Return if deployment mode is distributed +*/}} +{{- define "loki.deployment.isDistributed" -}} + {{- and (eq (include "loki.isUsingObjectStorage" . ) "true") (or (eq .Values.deploymentMode "Distributed") (eq .Values.deploymentMode "SimpleScalable<->Distributed")) }} +{{- end -}} + + {{/* Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). @@ -230,32 +237,20 @@ s3: {{- end }} s3forcepathstyle: {{ .s3ForcePathStyle }} insecure: {{ .insecure }} + {{- with .disable_dualstack }} + disable_dualstack: {{ . }} + {{- end }} {{- with .http_config}} http_config: - {{- with .idle_conn_timeout }} - idle_conn_timeout: {{ . }} - {{- end}} - {{- with .response_header_timeout }} - response_header_timeout: {{ . }} - {{- end}} - {{- with .insecure_skip_verify }} - insecure_skip_verify: {{ . }} - {{- end}} - {{- with .ca_file}} - ca_file: {{ . }} - {{- end}} +{{ toYaml . | indent 4 }} {{- end }} {{- with .backoff_config}} backoff_config: - {{- with .min_period }} - min_period: {{ . }} - {{- end}} - {{- with .max_period }} - max_period: {{ . }} - {{- end}} - {{- with .max_retries }} - max_retries: {{ . }} - {{- end}} +{{ toYaml . | indent 4 }} + {{- end }} + {{- with .sse }} + sse: +{{ toYaml . | indent 4 }} {{- end }} {{- end -}} @@ -290,38 +285,18 @@ azure: endpoint_suffix: {{ . }} {{- end }} {{- end -}} +{{- else if eq .Values.loki.storage.type "alibabacloud" -}} +{{- with .Values.loki.storage.alibabacloud }} +alibabacloud: + bucket: {{ $.Values.loki.storage.bucketNames.chunks }} + endpoint: {{ .endpoint }} + access_key_id: {{ .accessKeyId }} + secret_access_key: {{ .secretAccessKey }} +{{- end -}} {{- else if eq .Values.loki.storage.type "swift" -}} {{- with .Values.loki.storage.swift }} swift: - {{- with .auth_version }} - auth_version: {{ . }} - {{- end }} - auth_url: {{ .auth_url }} - {{- with .internal }} - internal: {{ . }} - {{- end }} - username: {{ .username }} - user_domain_name: {{ .user_domain_name }} - {{- with .user_domain_id }} - user_domain_id: {{ . }} - {{- end }} - {{- with .user_id }} - user_id: {{ . }} - {{- end }} - password: {{ .password }} - {{- with .domain_id }} - domain_id: {{ . }} - {{- end }} - domain_name: {{ .domain_name }} - project_id: {{ .project_id }} - project_name: {{ .project_name }} - project_domain_id: {{ .project_domain_id }} - project_domain_name: {{ .project_domain_name }} - region_name: {{ .region_name }} - container_name: {{ .container_name }} - max_retries: {{ .max_retries | default 3 }} - connect_timeout: {{ .connect_timeout | default "10s" }} - request_timeout: {{ .request_timeout | default "5s" }} +{{ toYaml . | indent 2 }} {{- end -}} {{- else -}} {{- with .Values.loki.storage.filesystem }} @@ -447,8 +422,84 @@ ruler: {{- end }} {{- end }} +{{/* Enterprise Logs Admin API storage config */}} +{{- define "enterprise-logs.adminAPIStorageConfig" }} +storage: + {{- if .Values.minio.enabled }} + backend: "s3" + s3: + bucket_name: admin + {{- else if eq .Values.loki.storage.type "s3" -}} + {{- with .Values.loki.storage.s3 }} + backend: "s3" + s3: + bucket_name: {{ $.Values.loki.storage.bucketNames.admin }} + {{- end -}} + {{- else if eq .Values.loki.storage.type "gcs" -}} + {{- with .Values.loki.storage.gcs }} + backend: "gcs" + gcs: + bucket_name: {{ $.Values.loki.storage.bucketNames.admin }} + {{- end -}} + {{- else if eq .Values.loki.storage.type "azure" -}} + {{- with .Values.loki.storage.azure }} + backend: "azure" + azure: + account_name: {{ .accountName }} + {{- with .accountKey }} + account_key: {{ . }} + {{- end }} + {{- with .connectionString }} + connection_string: {{ . }} + {{- end }} + container_name: {{ $.Values.loki.storage.bucketNames.admin }} + {{- with .endpointSuffix }} + endpoint_suffix: {{ . }} + {{- end }} + {{- end -}} + {{- else if eq .Values.loki.storage.type "swift" -}} + {{- with .Values.loki.storage.swift }} + backend: "swift" + swift: + {{- with .auth_version }} + auth_version: {{ . }} + {{- end }} + auth_url: {{ .auth_url }} + {{- with .internal }} + internal: {{ . }} + {{- end }} + username: {{ .username }} + user_domain_name: {{ .user_domain_name }} + {{- with .user_domain_id }} + user_domain_id: {{ . }} + {{- end }} + {{- with .user_id }} + user_id: {{ . }} + {{- end }} + password: {{ .password }} + {{- with .domain_id }} + domain_id: {{ . }} + {{- end }} + domain_name: {{ .domain_name }} + project_id: {{ .project_id }} + project_name: {{ .project_name }} + project_domain_id: {{ .project_domain_id }} + project_domain_name: {{ .project_domain_name }} + region_name: {{ .region_name }} + container_name: {{ .container_name }} + max_retries: {{ .max_retries | default 3 }} + connect_timeout: {{ .connect_timeout | default "10s" }} + request_timeout: {{ .request_timeout | default "5s" }} + {{- end -}} + {{- else }} + backend: "filesystem" + filesystem: + dir: {{ .Values.loki.storage.filesystem.admin_api_directory }} + {{- end -}} +{{- end }} + {{/* -Calculate the config from structured and unstructred text input +Calculate the config from structured and unstructured text input */}} {{- define "loki.calculatedConfig" -}} {{ tpl (mergeOverwrite (tpl .Values.loki.config . | fromYaml) .Values.loki.structuredConfig | toYaml) . }} @@ -460,10 +511,10 @@ The volume to mount for loki configuration {{- define "loki.configVolume" -}} {{- if eq .Values.loki.configStorageType "Secret" -}} secret: - secretName: {{ tpl .Values.loki.externalConfigSecretName . }} -{{- else if eq .Values.loki.configStorageType "ConfigMap" -}} + secretName: {{ tpl .Values.loki.configObjectName . }} +{{- else -}} configMap: - name: {{ tpl .Values.loki.externalConfigSecretName . }} + name: {{ tpl .Values.loki.configObjectName . }} items: - key: "config.yaml" path: "config.yaml" @@ -524,33 +575,68 @@ Return if ingress supports pathType. Generate list of ingress service paths based on deployment type */}} {{- define "loki.ingress.servicePaths" -}} -{{- if (eq (include "loki.deployment.isScalable" .) "true") -}} +{{- if (eq (include "loki.deployment.isSingleBinary" .) "true") -}} +{{- include "loki.ingress.singleBinaryServicePaths" . }} +{{- else if (eq (include "loki.deployment.isDistributed" .) "true") -}} +{{- include "loki.ingress.distributedServicePaths" . }} +{{- else if and (eq (include "loki.deployment.isScalable" .) "true") (not .Values.read.legacyReadTarget ) -}} {{- include "loki.ingress.scalableServicePaths" . }} {{- else -}} -{{- include "loki.ingress.singleBinaryServicePaths" . }} +{{- include "loki.ingress.legacyScalableServicePaths" . }} +{{- end -}} {{- end -}} + + +{{/* +Ingress service paths for distributed deployment +*/}} +{{- define "loki.ingress.distributedServicePaths" -}} +{{- $distributorServiceName := include "loki.distributorFullname" . }} +{{- include "loki.ingress.servicePath" (dict "ctx" . "serviceName" $distributorServiceName "paths" .Values.ingress.paths.distributor )}} +{{- $queryFrontendServiceName := include "loki.queryFrontendFullname" . }} +{{- include "loki.ingress.servicePath" (dict "ctx" . "serviceName" $queryFrontendServiceName "paths" .Values.ingress.paths.queryFrontend )}} +{{- $rulerServiceName := include "loki.rulerFullname" . }} +{{- include "loki.ingress.servicePath" (dict "ctx" . "serviceName" $rulerServiceName "paths" .Values.ingress.paths.ruler)}} {{- end -}} {{/* -Ingress service paths for scalable deployment +Ingress service paths for legacy simple scalable deployment when backend components were part of read component. */}} {{- define "loki.ingress.scalableServicePaths" -}} -{{- include "loki.ingress.servicePath" (dict "ctx" . "svcName" "read" "paths" .Values.ingress.paths.read )}} -{{- include "loki.ingress.servicePath" (dict "ctx" . "svcName" "write" "paths" .Values.ingress.paths.write )}} +{{- $readServiceName := include "loki.readFullname" . }} +{{- include "loki.ingress.servicePath" (dict "ctx" . "serviceName" $readServiceName "paths" .Values.ingress.paths.queryFrontend )}} +{{- $writeServiceName := include "loki.writeFullname" . }} +{{- include "loki.ingress.servicePath" (dict "ctx" . "serviceName" $writeServiceName "paths" .Values.ingress.paths.distributor )}} +{{- $backendServiceName := include "loki.backendFullname" . }} +{{- include "loki.ingress.servicePath" (dict "ctx" . "serviceName" $backendServiceName "paths" .Values.ingress.paths.ruler )}} +{{- end -}} + +{{/* +Ingress service paths for legacy simple scalable deployment +*/}} +{{- define "loki.ingress.legacyScalableServicePaths" -}} +{{- $readServiceName := include "loki.readFullname" . }} +{{- include "loki.ingress.servicePath" (dict "ctx" . "serviceName" $readServiceName "paths" .Values.ingress.paths.queryFrontend )}} +{{- include "loki.ingress.servicePath" (dict "ctx" . "serviceName" $readServiceName "paths" .Values.ingress.paths.ruler )}} +{{- $writeServiceName := include "loki.writeFullname" . }} +{{- include "loki.ingress.servicePath" (dict "ctx" . "serviceName" $writeServiceName "paths" .Values.ingress.paths.distributor )}} {{- end -}} {{/* Ingress service paths for single binary deployment */}} {{- define "loki.ingress.singleBinaryServicePaths" -}} -{{- include "loki.ingress.servicePath" (dict "ctx" . "svcName" "singleBinary" "paths" .Values.ingress.paths.singleBinary )}} +{{- $serviceName := include "loki.singleBinaryFullname" . }} +{{- include "loki.ingress.servicePath" (dict "ctx" . "serviceName" $serviceName "paths" .Values.ingress.paths.distributor )}} +{{- include "loki.ingress.servicePath" (dict "ctx" . "serviceName" $serviceName "paths" .Values.ingress.paths.queryFrontend )}} +{{- include "loki.ingress.servicePath" (dict "ctx" . "serviceName" $serviceName "paths" .Values.ingress.paths.ruler )}} {{- end -}} {{/* Ingress service path helper function Params: ctx = . context - svcName = service name without the "loki.fullname" part (ie. read, write) + serviceName = fully qualified k8s service name paths = list of url paths to allow ingress for */}} {{- define "loki.ingress.servicePath" -}} @@ -562,39 +648,24 @@ Params: pathType: Prefix {{- end }} backend: - {{- $serviceName := include "loki.ingress.serviceName" (dict "ctx" $.ctx "svcName" $.svcName) }} {{- if $ingressApiIsStable }} service: - name: {{ $serviceName }} + name: {{ $.serviceName }} port: number: {{ $.ctx.Values.loki.server.http_listen_port }} {{- else }} - serviceName: {{ $serviceName }} + serviceName: {{ $.serviceName }} servicePort: {{ $.ctx.Values.loki.server.http_listen_port }} {{- end -}} {{- end -}} {{- end -}} -{{/* -Ingress service name helper function -Params: - ctx = . context - svcName = service name without the "loki.fullname" part (ie. read, write) -*/}} -{{- define "loki.ingress.serviceName" -}} -{{- if (eq .svcName "singleBinary") }} -{{- printf "%s" (include "loki.singleBinaryFullname" .ctx) }} -{{- else }} -{{- printf "%s-%s" (include "loki.name" .ctx) .svcName }} -{{- end -}} -{{- end -}} - {{/* Create the service endpoint including port for MinIO. */}} {{- define "loki.minio" -}} {{- if .Values.minio.enabled -}} -{{- printf "%s-%s.%s.svc:%s" .Release.Name "minio" .Release.Namespace (.Values.minio.service.port | toString) -}} +{{- .Values.minio.address | default (printf "%s-%s.%s.svc:%s" .Release.Name "minio" .Release.Namespace (.Values.minio.service.port | toString)) -}} {{- end -}} {{- end -}} @@ -697,10 +768,17 @@ http { {{- end }} server { + {{- if (.Values.gateway.nginxConfig.ssl) }} + listen 8080 ssl; + {{- if .Values.gateway.nginxConfig.enableIPv6 }} + listen [::]:8080 ssl; + {{- end }} + {{- else }} listen 8080; {{- if .Values.gateway.nginxConfig.enableIPv6 }} listen [::]:8080; {{- end }} + {{- end }} {{- if .Values.gateway.basicAuth.enabled }} auth_basic "Loki"; @@ -712,6 +790,9 @@ http { auth_basic off; } + ######################################################## + # Configure backend targets + {{- $backendHost := include "loki.backendFullname" .}} {{- $readHost := include "loki.readFullname" .}} {{- $writeHost := include "loki.writeFullname" .}} @@ -720,15 +801,11 @@ http { {{- $backendHost = include "loki.readFullname" . }} {{- end }} - {{- if gt (int .Values.singleBinary.replicas) 0 }} - {{- $backendHost = include "loki.singleBinaryFullname" . }} - {{- $readHost = include "loki.singleBinaryFullname" .}} - {{- $writeHost = include "loki.singleBinaryFullname" .}} - {{- end }} + {{- $httpSchema := .Values.gateway.nginxConfig.schema }} - {{- $writeUrl := printf "http://%s.%s.svc.%s:%s" $writeHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} - {{- $readUrl := printf "http://%s.%s.svc.%s:%s" $readHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} - {{- $backendUrl := printf "http://%s.%s.svc.%s:%s" $backendHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} + {{- $writeUrl := printf "%s://%s.%s.svc.%s:%s" $httpSchema $writeHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} + {{- $readUrl := printf "%s://%s.%s.svc.%s:%s" $httpSchema $readHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} + {{- $backendUrl := printf "%s://%s.%s.svc.%s:%s" $httpSchema $backendHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} {{- if .Values.gateway.nginxConfig.customWriteUrl }} {{- $writeUrl = .Values.gateway.nginxConfig.customWriteUrl }} @@ -740,24 +817,64 @@ http { {{- $backendUrl = .Values.gateway.nginxConfig.customBackendUrl }} {{- end }} + {{- $singleBinaryHost := include "loki.singleBinaryFullname" . }} + {{- $singleBinaryUrl := printf "%s://%s.%s.svc.%s:%s" $httpSchema $singleBinaryHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} + + {{- $distributorHost := include "loki.distributorFullname" .}} + {{- $ingesterHost := include "loki.ingesterFullname" .}} + {{- $queryFrontendHost := include "loki.queryFrontendFullname" .}} + {{- $indexGatewayHost := include "loki.indexGatewayFullname" .}} + {{- $rulerHost := include "loki.rulerFullname" .}} + {{- $compactorHost := include "loki.compactorFullname" .}} + {{- $schedulerHost := include "loki.querySchedulerFullname" .}} + + + {{- $distributorUrl := printf "%s://%s.%s.svc.%s:%s" $httpSchema $distributorHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) -}} + {{- $ingesterUrl := printf "%s://%s.%s.svc.%s:%s" $httpSchema $ingesterHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} + {{- $queryFrontendUrl := printf "%s://%s.%s.svc.%s:%s" $httpSchema $queryFrontendHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} + {{- $indexGatewayUrl := printf "%s://%s.%s.svc.%s:%s" $httpSchema $indexGatewayHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} + {{- $rulerUrl := printf "%s://%s.%s.svc.%s:%s" $httpSchema $rulerHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} + {{- $compactorUrl := printf "%s://%s.%s.svc.%s:%s" $httpSchema $compactorHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} + {{- $schedulerUrl := printf "%s://%s.%s.svc.%s:%s" $httpSchema $schedulerHost .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.http_listen_port | toString) }} + + {{- if eq (include "loki.deployment.isSingleBinary" .) "true"}} + {{- $distributorUrl = $singleBinaryUrl }} + {{- $ingesterUrl = $singleBinaryUrl }} + {{- $queryFrontendUrl = $singleBinaryUrl }} + {{- $indexGatewayUrl = $singleBinaryUrl }} + {{- $rulerUrl = $singleBinaryUrl }} + {{- $compactorUrl = $singleBinaryUrl }} + {{- $schedulerUrl = $singleBinaryUrl }} + {{- else if eq (include "loki.deployment.isScalable" .) "true"}} + {{- $distributorUrl = $writeUrl }} + {{- $ingesterUrl = $writeUrl }} + {{- $queryFrontendUrl = $readUrl }} + {{- $indexGatewayUrl = $backendUrl }} + {{- $rulerUrl = $backendUrl }} + {{- $compactorUrl = $backendUrl }} + {{- $schedulerUrl = $backendUrl }} + {{- end -}} # Distributor location = /api/prom/push { - proxy_pass {{ $writeUrl }}$request_uri; + proxy_pass {{ $distributorUrl }}$request_uri; } location = /loki/api/v1/push { - proxy_pass {{ $writeUrl }}$request_uri; + proxy_pass {{ $distributorUrl }}$request_uri; } location = /distributor/ring { - proxy_pass {{ $writeUrl }}$request_uri; + proxy_pass {{ $distributorUrl }}$request_uri; + } + location = /otlp/v1/logs { + proxy_pass {{ $distributorUrl }}$request_uri; } # Ingester location = /flush { - proxy_pass {{ $writeUrl }}$request_uri; + proxy_pass {{ $ingesterUrl }}$request_uri; } location ^~ /ingester/ { - proxy_pass {{ $writeUrl }}$request_uri; + proxy_pass {{ $ingesterUrl }}$request_uri; } location = /ingester { internal; # to suppress 301 @@ -765,62 +882,61 @@ http { # Ring location = /ring { - proxy_pass {{ $writeUrl }}$request_uri; + proxy_pass {{ $ingesterUrl }}$request_uri; } # MemberListKV location = /memberlist { - proxy_pass {{ $writeUrl }}$request_uri; + proxy_pass {{ $ingesterUrl }}$request_uri; } - # Ruler location = /ruler/ring { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $rulerUrl }}$request_uri; } location = /api/prom/rules { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $rulerUrl }}$request_uri; } location ^~ /api/prom/rules/ { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $rulerUrl }}$request_uri; } location = /loki/api/v1/rules { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $rulerUrl }}$request_uri; } location ^~ /loki/api/v1/rules/ { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $rulerUrl }}$request_uri; } location = /prometheus/api/v1/alerts { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $rulerUrl }}$request_uri; } location = /prometheus/api/v1/rules { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $rulerUrl }}$request_uri; } # Compactor location = /compactor/ring { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $compactorUrl }}$request_uri; } location = /loki/api/v1/delete { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $compactorUrl }}$request_uri; } location = /loki/api/v1/cache/generation_numbers { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $compactorUrl }}$request_uri; } # IndexGateway location = /indexgateway/ring { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $indexGatewayUrl }}$request_uri; } # QueryScheduler location = /scheduler/ring { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $schedulerUrl }}$request_uri; } # Config location = /config { - proxy_pass {{ $backendUrl }}$request_uri; + proxy_pass {{ $ingesterUrl }}$request_uri; } {{- if and .Values.enterprise.enabled .Values.enterprise.adminApi.enabled }} @@ -836,29 +952,28 @@ http { # QueryFrontend, Querier location = /api/prom/tail { - proxy_pass {{ $readUrl }}$request_uri; + proxy_pass {{ $queryFrontendUrl }}$request_uri; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location = /loki/api/v1/tail { - proxy_pass {{ $readUrl }}$request_uri; + proxy_pass {{ $queryFrontendUrl }}$request_uri; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } location ^~ /api/prom/ { - proxy_pass {{ $readUrl }}$request_uri; + proxy_pass {{ $queryFrontendUrl }}$request_uri; } location = /api/prom { internal; # to suppress 301 } location ^~ /loki/api/v1/ { - proxy_pass {{ $readUrl }}$request_uri; + proxy_pass {{ $queryFrontendUrl }}$request_uri; } location = /loki/api/v1 { internal; # to suppress 301 } - {{- with .Values.gateway.nginxConfig.serverSnippet }} {{ . | nindent 4 }} {{- end }} @@ -880,23 +995,121 @@ enableServiceLinks: false {{/* Determine compactor address based on target configuration */}} {{- define "loki.compactorAddress" -}} {{- $isSimpleScalable := eq (include "loki.deployment.isScalable" .) "true" -}} +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- $isSingleBinary := eq (include "loki.deployment.isSingleBinary" .) "true" -}} {{- $compactorAddress := include "loki.backendFullname" . -}} {{- if and $isSimpleScalable .Values.read.legacyReadTarget -}} {{/* 2 target configuration */}} {{- $compactorAddress = include "loki.readFullname" . -}} -{{- else if (not $isSimpleScalable) -}} +{{- else if $isSingleBinary -}} {{/* single binary */}} {{- $compactorAddress = include "loki.singleBinaryFullname" . -}} +{{/* distributed */}} +{{- else if $isDistributed -}} +{{- $compactorAddress = include "loki.compactorFullname" . -}} {{- end -}} {{- printf "http://%s:%s" $compactorAddress (.Values.loki.server.http_listen_port | toString) }} {{- end }} {{/* Determine query-scheduler address */}} {{- define "loki.querySchedulerAddress" -}} -{{- $isSimpleScalable := eq (include "loki.deployment.isScalable" .) "true" -}} {{- $schedulerAddress := ""}} -{{- if and $isSimpleScalable (not .Values.read.legacyReadTarget ) -}} -{{- $schedulerAddress = printf "query-scheduler-discovery.%s.svc.%s.:%s" .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.grpc_listen_port | toString) -}} +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed -}} +{{- $schedulerAddress = printf "%s.%s.svc.%s:%s" (include "loki.querySchedulerFullname" .) .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.grpc_listen_port | toString) -}} {{- end -}} {{- printf "%s" $schedulerAddress }} {{- end }} + +{{/* Determine querier address */}} +{{- define "loki.querierAddress" -}} +{{- $querierAddress := "" }} +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed -}} +{{- $querierHost := include "loki.querierFullname" .}} +{{- $querierUrl := printf "http://%s.%s.svc.%s:3100" $querierHost .Release.Namespace .Values.global.clusterDomain }} +{{- $querierAddress = $querierUrl }} +{{- end -}} +{{- printf "%s" $querierAddress }} +{{- end }} + +{{/* Determine index-gateway address */}} +{{- define "loki.indexGatewayAddress" -}} +{{- $idxGatewayAddress := ""}} +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- $isScalable := eq (include "loki.deployment.isScalable" .) "true" -}} +{{- if $isDistributed -}} +{{- $idxGatewayAddress = printf "dns+%s-headless.%s.svc.%s:%s" (include "loki.indexGatewayFullname" .) .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.grpc_listen_port | toString) -}} +{{- end -}} +{{- if $isScalable -}} +{{- $idxGatewayAddress = printf "dns+%s-headless.%s.svc.%s:%s" (include "loki.backendFullname" .) .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.grpc_listen_port | toString) -}} +{{- end -}} +{{- printf "%s" $idxGatewayAddress }} +{{- end }} + +{{/* Determine bloom-planner address */}} +{{- define "loki.bloomPlannerAddress" -}} +{{- $bloomPlannerAddress := ""}} +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- $isScalable := eq (include "loki.deployment.isScalable" .) "true" -}} +{{- if $isDistributed -}} +{{- $bloomPlannerAddress = printf "%s-headless.%s.svc.%s:%s" (include "loki.bloomPlannerFullname" .) .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.grpc_listen_port | toString) -}} +{{- end -}} +{{- if $isScalable -}} +{{- $bloomPlannerAddress = printf "%s-headless.%s.svc.%s:%s" (include "loki.backendFullname" .) .Release.Namespace .Values.global.clusterDomain (.Values.loki.server.grpc_listen_port | toString) -}} +{{- end -}} +{{- printf "%s" $bloomPlannerAddress}} +{{- end }} + +{{/* Determine bloom-gateway address */}} +{{- define "loki.bloomGatewayAddresses" -}} +{{- $bloomGatewayAddresses := ""}} +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- $isScalable := eq (include "loki.deployment.isScalable" .) "true" -}} +{{- if $isDistributed -}} +{{- $bloomGatewayAddresses = printf "dnssrvnoa+_grpc._tcp.%s-headless.%s.svc.%s" (include "loki.bloomGatewayFullname" .) .Release.Namespace .Values.global.clusterDomain -}} +{{- end -}} +{{- if $isScalable -}} +{{- $bloomGatewayAddresses = printf "dnssrvnoa+_grpc._tcp.%s-headless.%s.svc.%s" (include "loki.backendFullname" .) .Release.Namespace .Values.global.clusterDomain -}} +{{- end -}} +{{- printf "%s" $bloomGatewayAddresses}} +{{- end }} + +{{- define "loki.config.checksum" -}} +checksum/config: {{ include (print .Template.BasePath "/config.yaml") . | sha256sum }} +{{- end -}} + +{{/* +Return the appropriate apiVersion for PodDisruptionBudget. +*/}} +{{- define "loki.pdb.apiVersion" -}} + {{- if and (.Capabilities.APIVersions.Has "policy/v1") (semverCompare ">=1.21-0" .Capabilities.KubeVersion.Version) -}} + {{- print "policy/v1" -}} + {{- else -}} + {{- print "policy/v1beta1" -}} + {{- end -}} +{{- end -}} + +{{/* +Return the object store type for use with the test schema. +*/}} +{{- define "loki.testSchemaObjectStore" -}} + {{- if .Values.minio.enabled -}} + s3 + {{- else -}} + filesystem + {{- end -}} +{{- end -}} + +{{/* +Return the appropriate apiVersion for HorizontalPodAutoscaler. +*/}} +{{- define "loki.hpa.apiVersion" -}} + {{- if and (.Capabilities.APIVersions.Has "autoscaling/v2") (semverCompare ">= 1.19-0" .Capabilities.KubeVersion.Version) -}} + {{- print "autoscaling/v2" -}} + {{- else if .Capabilities.APIVersions.Has "autoscaling/v2beta2" -}} + {{- print "autoscaling/v2beta2" -}} + {{- else -}} + {{- print "autoscaling/v2beta1" -}} + {{- end -}} +{{- end -}} diff --git a/charts/loki/templates/admin-api/_helpers.yaml b/charts/loki/templates/admin-api/_helpers.yaml new file mode 100644 index 0000000000..e13ff8adbc --- /dev/null +++ b/charts/loki/templates/admin-api/_helpers.yaml @@ -0,0 +1,24 @@ +{{/* +adminApi fullname +*/}} +{{- define "enterprise-logs.adminApiFullname" -}} +{{ include "loki.fullname" . }}-admin-api +{{- end }} + +{{/* +adminApi common labels +*/}} +{{- define "enterprise-logs.adminApiLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: admin-api +target: admin-api +{{- end }} + +{{/* +adminApi selector labels +*/}} +{{- define "enterprise-logs.adminApiSelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: admin-api +target: admin-api +{{- end }} \ No newline at end of file diff --git a/charts/loki/templates/admin-api/deployment-admin-api.yaml b/charts/loki/templates/admin-api/deployment-admin-api.yaml new file mode 100644 index 0000000000..650c72fc15 --- /dev/null +++ b/charts/loki/templates/admin-api/deployment-admin-api.yaml @@ -0,0 +1,166 @@ +{{- if .Values.enterprise.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "enterprise-logs.adminApiFullname" . }} + labels: + {{- include "enterprise-logs.adminApiLabels" . | nindent 4 }} + {{- with .Values.adminApi.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + app.kubernetes.io/part-of: memberlist + annotations: + {{- with .Values.adminApi.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.adminApi.replicas }} + selector: + matchLabels: + {{- include "enterprise-logs.adminApiSelectorLabels" . | nindent 6 }} + strategy: + {{- toYaml .Values.adminApi.strategy | nindent 4 }} + template: + metadata: + labels: + {{- include "enterprise-logs.adminApiSelectorLabels" . | nindent 8 }} + {{- with .Values.adminApi.labels }} + {{- toYaml . | nindent 8 }} + {{- end }} + app.kubernetes.io/part-of: memberlist + annotations: + {{- if .Values.useExternalConfig }} + checksum/config: {{ .Values.externalConfigVersion }} + {{- else }} + checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }} + {{- end}} + {{- with .Values.adminApi.annotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ template "loki.serviceAccountName" . }} + {{- if .Values.adminApi.priorityClassName }} + priorityClassName: {{ .Values.adminApi.priorityClassName }} + {{- end }} + securityContext: + {{- toYaml .Values.adminApi.podSecurityContext | nindent 8 }} + initContainers: + # Taken from + # https://github.com/minio/charts/blob/a5c84bcbad884728bff5c9c23541f936d57a13b3/minio/templates/post-install-create-bucket-job.yaml + {{- if .Values.minio.enabled }} + - name: minio-mc + image: "{{ .Values.minio.mcImage.repository }}:{{ .Values.minio.mcImage.tag }}" + imagePullPolicy: {{ .Values.minio.mcImage.pullPolicy }} + command: ["/bin/sh", "/config/initialize"] + env: + - name: MINIO_ENDPOINT + value: {{ .Release.Name }}-minio + - name: MINIO_PORT + value: {{ .Values.minio.service.port | quote }} + volumeMounts: + - name: minio-configuration + mountPath: /config + {{- if .Values.minio.tls.enabled }} + - name: cert-secret-volume-mc + mountPath: {{ .Values.minio.configPathmc }}certs + {{ end }} + {{- end }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.adminApi.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: admin-api + image: "{{ template "loki.image" . }}" + imagePullPolicy: {{ .Values.enterprise.image.pullPolicy }} + args: + - -target=admin-api + - -config.file=/etc/loki/config/config.yaml + {{- if .Values.minio.enabled }} + - -admin.client.backend-type=s3 + - -admin.client.s3.endpoint={{ template "loki.minio" . }} + - -admin.client.s3.bucket-name=enterprise-logs-admin + - -admin.client.s3.access-key-id={{ .Values.minio.accessKey }} + - -admin.client.s3.secret-access-key={{ .Values.minio.secretKey }} + - -admin.client.s3.insecure=true + {{- end }} + {{- range $key, $value := .Values.adminApi.extraArgs }} + - "-{{ $key }}={{ $value }}" + {{- end }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: license + mountPath: /etc/loki/license + - name: storage + mountPath: /data + {{- if .Values.adminApi.extraVolumeMounts }} + {{ toYaml .Values.adminApi.extraVolumeMounts | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + readinessProbe: + {{- toYaml .Values.adminApi.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.adminApi.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.adminApi.containerSecurityContext | nindent 12 }} + env: + {{- if .Values.adminApi.env }} + {{ toYaml .Values.adminApi.env | nindent 12 }} + {{- end }} + {{- with .Values.adminApi.extraContainers }} + {{ toYaml . | nindent 8 }} + {{- end }} + nodeSelector: + {{- toYaml .Values.adminApi.nodeSelector | nindent 8 }} + affinity: + {{- toYaml .Values.adminApi.affinity | nindent 8 }} + tolerations: + {{- toYaml .Values.adminApi.tolerations | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.adminApi.terminationGracePeriodSeconds }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + - name: storage + emptyDir: {} + {{- if .Values.adminApi.extraVolumes }} + {{ toYaml .Values.adminApi.extraVolumes | nindent 8 }} + {{- end }} + {{- if .Values.minio.enabled }} + - name: minio-configuration + projected: + sources: + - configMap: + name: {{ .Release.Name }}-minio + - secret: + name: {{ .Release.Name }}-minio + {{- if .Values.minio.tls.enabled }} + - name: cert-secret-volume-mc + secret: + secretName: {{ .Values.minio.tls.certSecret }} + items: + - key: {{ .Values.minio.tls.publicCrt }} + path: CAs/public.crt + {{- end }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/loki/templates/admin-api/service-admin-api.yaml b/charts/loki/templates/admin-api/service-admin-api.yaml new file mode 100644 index 0000000000..c7daa2790a --- /dev/null +++ b/charts/loki/templates/admin-api/service-admin-api.yaml @@ -0,0 +1,28 @@ +{{- if .Values.enterprise.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ template "enterprise-logs.adminApiFullname" . }} + labels: + {{- include "enterprise-logs.adminApiLabels" . | nindent 4 }} + {{- with .Values.adminApi.service.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.adminApi.service.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + type: ClusterIP + ports: + - name: http-metrics + port: 3100 + protocol: TCP + targetPort: http-metrics + - name: grpc + port: 9095 + protocol: TCP + targetPort: grpc + selector: + {{- include "enterprise-logs.adminApiSelectorLabels" . | nindent 4 }} +{{- end }} \ No newline at end of file diff --git a/charts/loki/templates/backend/clusterrole.yaml b/charts/loki/templates/backend/clusterrole.yaml index 176ada0566..36c8a0fe0e 100644 --- a/charts/loki/templates/backend/clusterrole.yaml +++ b/charts/loki/templates/backend/clusterrole.yaml @@ -17,4 +17,4 @@ rules: {{- else }} rules: [] {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/charts/loki/templates/backend/clusterrolebinding.yaml b/charts/loki/templates/backend/clusterrolebinding.yaml index 1021fd0089..92f86a47d4 100644 --- a/charts/loki/templates/backend/clusterrolebinding.yaml +++ b/charts/loki/templates/backend/clusterrolebinding.yaml @@ -1,4 +1,5 @@ -{{- if and (not .Values.rbac.namespaced) }} +{{- $isSimpleScalable := eq (include "loki.deployment.isScalable" .) "true" -}} +{{- if (not .Values.rbac.namespaced) }} kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: @@ -21,4 +22,4 @@ roleRef: name: {{ .Values.rbac.useExistingRole }} {{- end }} apiGroup: rbac.authorization.k8s.io -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/charts/loki/templates/backend/query-scheduler-discovery.yaml b/charts/loki/templates/backend/query-scheduler-discovery.yaml index 527fa13cfa..4c357e53a4 100644 --- a/charts/loki/templates/backend/query-scheduler-discovery.yaml +++ b/charts/loki/templates/backend/query-scheduler-discovery.yaml @@ -4,11 +4,18 @@ apiVersion: v1 kind: Service metadata: - name: query-scheduler-discovery + name: {{ include "loki.querySchedulerFullname" . }}-discovery namespace: {{ $.Release.Namespace }} labels: {{- include "loki.backendSelectorLabels" . | nindent 4 }} prometheus.io/service-monitor: "false" + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.backend.service.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} spec: type: ClusterIP clusterIP: None diff --git a/charts/loki/templates/backend/statefulset-backend.yaml b/charts/loki/templates/backend/statefulset-backend.yaml index 97e110ea2d..534190d4a4 100644 --- a/charts/loki/templates/backend/statefulset-backend.yaml +++ b/charts/loki/templates/backend/statefulset-backend.yaml @@ -20,7 +20,11 @@ metadata: {{- end }} spec: {{- if not .Values.backend.autoscaling.enabled }} + {{- if eq .Values.deploymentMode "SingleBinary" }} + replicas: 0 + {{- else }} replicas: {{ .Values.backend.replicas }} + {{- end }} {{- end }} podManagementPolicy: {{ .Values.backend.podManagementPolicy }} updateStrategy: @@ -205,7 +209,7 @@ spec: {{- toYaml .Values.backend.resources | nindent 12 }} {{- with .Values.backend.affinity }} affinity: - {{- tpl . $ | nindent 8 }} + {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.backend.dnsConfig }} dnsConfig: @@ -231,12 +235,7 @@ spec: {{- toYaml .Values.backend.persistence.dataVolumeParameters | nindent 10 }} {{- end}} - name: config - {{- if .Values.loki.existingSecretForConfig }} - secret: - secretName: {{ .Values.loki.existingSecretForConfig }} - {{- else }} {{- include "loki.configVolume" . | nindent 10 }} - {{- end }} - name: runtime-config configMap: name: {{ template "loki.name" . }}-runtime @@ -267,6 +266,10 @@ spec: kind: PersistentVolumeClaim metadata: name: data + {{- with .Values.backend.persistence.annotations }} + annotations: + {{- toYaml . | nindent 10 }} + {{- end }} spec: accessModes: - ReadWriteOnce diff --git a/charts/loki/templates/bloom-builder/_helpers-bloom-builder.tpl b/charts/loki/templates/bloom-builder/_helpers-bloom-builder.tpl new file mode 100644 index 0000000000..46359dffdf --- /dev/null +++ b/charts/loki/templates/bloom-builder/_helpers-bloom-builder.tpl @@ -0,0 +1,32 @@ +{{/* +bloom-builder fullname +*/}} +{{- define "loki.bloomBuilderFullname" -}} +{{ include "loki.fullname" . }}-bloom-builder +{{- end }} + +{{/* +bloom-builder common labels +*/}} +{{- define "loki.bloomBuilderLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: bloom-builder +{{- end }} + +{{/* +bloom-builder selector labels +*/}} +{{- define "loki.bloomBuilderSelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: bloom-builder +{{- end }} + +{{/* +bloom-builder priority class name +*/}} +{{- define "loki.bloomBuilderPriorityClassName" -}} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.bloomBuilder.priorityClassName -}} +{{- if $pcn }} +priorityClassName: {{ $pcn }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/bloom-builder/deployment-bloom-builder.yaml b/charts/loki/templates/bloom-builder/deployment-bloom-builder.yaml new file mode 100644 index 0000000000..c04b3ae5ae --- /dev/null +++ b/charts/loki/templates/bloom-builder/deployment-bloom-builder.yaml @@ -0,0 +1,150 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if (and $isDistributed (gt (int .Values.bloomPlanner.replicas) 0)) -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "loki.bloomBuilderFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.bloomBuilderLabels" . | nindent 4 }} + {{- with .Values.loki.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if not .Values.bloomBuilder.autoscaling.enabled }} + replicas: {{ .Values.bloomBuilder.replicas }} +{{- end }} + strategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + selector: + matchLabels: + {{- include "loki.bloomBuilderSelectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomBuilder.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.bloomBuilderSelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomBuilder.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomBuilder.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.bloomBuilderPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.bloomBuilder.terminationGracePeriodSeconds }} + containers: + - name: bloom-builder + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + {{- if or .Values.loki.command .Values.bloomBuilder.command }} + command: + - {{ coalesce .Values.bloomBuilder.command .Values.loki.command | quote }} + {{- end }} + args: + - -config.file=/etc/loki/config/config.yaml + - -target=bloom-builder + {{- with .Values.bloomBuilder.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.bloomBuilder.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.bloomBuilder.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + readinessProbe: + {{- toYaml .Values.loki.readinessProbe | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + - name: temp + mountPath: /tmp + - name: data + mountPath: /var/loki + {{- with .Values.bloomBuilder.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.bloomBuilder.resources | nindent 12 }} + {{- if .Values.bloomBuilder.extraContainers }} + {{- toYaml .Values.bloomBuilder.extraContainers | nindent 8}} + {{- end }} + {{- with .Values.bloomBuilder.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomBuilder.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomBuilder.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + - name: temp + emptyDir: {} + - name: data + emptyDir: {} + {{- with .Values.bloomBuilder.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end -}} diff --git a/charts/loki/templates/bloom-builder/hpa.yaml b/charts/loki/templates/bloom-builder/hpa.yaml new file mode 100644 index 0000000000..2b04647d2a --- /dev/null +++ b/charts/loki/templates/bloom-builder/hpa.yaml @@ -0,0 +1,55 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.bloomBuilder.autoscaling.enabled }} +{{- $apiVersion := include "loki.hpa.apiVersion" . -}} +apiVersion: {{ $apiVersion }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "loki.bloomBuilderFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.bloomBuilderLabels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "loki.bloomBuilderFullname" . }} + minReplicas: {{ .Values.bloomBuilder.autoscaling.minReplicas }} + maxReplicas: {{ .Values.bloomBuilder.autoscaling.maxReplicas }} + metrics: + {{- with .Values.bloomBuilder.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + {{- if (eq $apiVersion "autoscaling/v2") }} + target: + type: Utilization + averageUtilization: {{ . }} + {{- else }} + targetAverageUtilization: {{ . }} + {{- end }} + {{- end }} + {{- with .Values.bloomBuilder.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + {{- if (eq $apiVersion "autoscaling/v2") }} + target: + type: Utilization + averageUtilization: {{ . }} + {{- else }} + targetAverageUtilization: {{ . }} + {{- end }} + {{- end }} + {{- with .Values.bloomBuilder.autoscaling.customMetrics }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.bloomBuilder.autoscaling.behavior.enabled }} + behavior: + {{- with .Values.bloomBuilder.autoscaling.behavior.scaleDown }} + scaleDown: {{ toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.bloomBuilder.autoscaling.behavior.scaleUp }} + scaleUp: {{ toYaml . | nindent 6 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/bloom-builder/poddisruptionbudget-bloom-builder.yaml b/charts/loki/templates/bloom-builder/poddisruptionbudget-bloom-builder.yaml new file mode 100644 index 0000000000..e66d762c0e --- /dev/null +++ b/charts/loki/templates/bloom-builder/poddisruptionbudget-bloom-builder.yaml @@ -0,0 +1,21 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed (gt (int .Values.bloomBuilder.replicas) 1) }} +{{- if kindIs "invalid" .Values.bloomBuilder.maxUnavailable }} +{{- fail "`.Values.bloomBuilder.maxUnavailable` must be set when `.Values.bloomBuilder.replicas` is greater than 1." }} +{{- else }} +apiVersion: {{ include "loki.pdb.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "loki.bloomBuilderFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.bloomBuilderLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "loki.bloomBuilderSelectorLabels" . | nindent 6 }} + {{- with .Values.bloomBuilder.maxUnavailable }} + maxUnavailable: {{ . }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/bloom-builder/service-bloom-builder-headless.yaml b/charts/loki/templates/bloom-builder/service-bloom-builder-headless.yaml new file mode 100644 index 0000000000..938925291a --- /dev/null +++ b/charts/loki/templates/bloom-builder/service-bloom-builder-headless.yaml @@ -0,0 +1,46 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if (and $isDistributed (or (gt (int .Values.bloomBuilder.replicas) 0)) .Values.bloomBuilder.autoscaling.enabled) -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.bloomBuilderFullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.bloomBuilderLabels" . | nindent 4 }} + {{- with .Values.bloomBuilder.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + prometheus.io/service-monitor: "false" + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.bloomBuilder.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + clusterIP: None + type: ClusterIP + publishNotReadyAddresses: true + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.bloomBuilder.appProtocol.grpc }} + appProtocol: {{ .Values.bloomBuilder.appProtocol.grpc }} + {{- end }} + - name: grpclb + port: 9096 + targetPort: grpc + protocol: TCP + {{- if .Values.bloomBuilder.appProtocol.grpc }} + appProtocol: {{ .Values.bloomBuilder.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.bloomBuilderSelectorLabels" . | nindent 4 }} +{{- end -}} diff --git a/charts/loki/templates/bloom-builder/service-bloom-builder.yaml b/charts/loki/templates/bloom-builder/service-bloom-builder.yaml new file mode 100644 index 0000000000..b3debb0889 --- /dev/null +++ b/charts/loki/templates/bloom-builder/service-bloom-builder.yaml @@ -0,0 +1,44 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if (and $isDistributed (gt (int .Values.bloomBuilder.replicas) 0)) -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.bloomBuilderFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.bloomBuilderLabels" . | nindent 4 }} + {{- with .Values.bloomBuilder.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.bloomBuilder.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + publishNotReadyAddresses: true + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.bloomBuilder.appProtocol.grpc }} + appProtocol: {{ .Values.bloomBuilder.appProtocol.grpc }} + {{- end }} + - name: grpclb + port: 9096 + targetPort: grpc + protocol: TCP + {{- if .Values.bloomBuilder.appProtocol.grpc }} + appProtocol: {{ .Values.bloomBuilder.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.bloomBuilderSelectorLabels" . | nindent 4 }} +{{- end -}} diff --git a/charts/loki/templates/bloom-gateway/_helpers-bloom-gateway.tpl b/charts/loki/templates/bloom-gateway/_helpers-bloom-gateway.tpl new file mode 100644 index 0000000000..f0cef4f179 --- /dev/null +++ b/charts/loki/templates/bloom-gateway/_helpers-bloom-gateway.tpl @@ -0,0 +1,58 @@ +{{/* +bloom gateway fullname +*/}} +{{- define "loki.bloomGatewayFullname" -}} +{{ include "loki.fullname" . }}-bloom-gateway +{{- end }} + +{{/* +bloom gateway common labels +*/}} +{{- define "loki.bloomGatewayLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: bloom-gateway +{{- end }} + +{{/* +bloom gateway selector labels +*/}} +{{- define "loki.bloomGatewaySelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: bloom-gateway +{{- end }} + +{{/* +bloom gateway readinessProbe +*/}} +{{- define "loki.bloomGateway.readinessProbe" -}} +{{- with .Values.bloomGateway.readinessProbe }} +readinessProbe: + {{- toYaml . | nindent 2 }} +{{- else }} +{{- with .Values.loki.readinessProbe }} +readinessProbe: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* +bloom gateway priority class name +*/}} +{{- define "loki.bloomGatewayPriorityClassName" }} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.bloomGateway.priorityClassName -}} +{{- if $pcn }} +priorityClassName: {{ $pcn }} +{{- end }} +{{- end }} + +{{/* +Create the name of the bloom gateway service account +*/}} +{{- define "loki.bloomGatewayServiceAccountName" -}} +{{- if .Values.bloomGateway.serviceAccount.create -}} + {{ default (print (include "loki.serviceAccountName" .) "-bloom-gateway") .Values.bloomGateway.serviceAccount.name }} +{{- else -}} + {{ default (include "loki.serviceAccountName" .) .Values.bloomGateway.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/charts/loki/templates/bloom-gateway/service-bloom-gateway-headless.yaml b/charts/loki/templates/bloom-gateway/service-bloom-gateway-headless.yaml new file mode 100644 index 0000000000..852e4cb100 --- /dev/null +++ b/charts/loki/templates/bloom-gateway/service-bloom-gateway-headless.yaml @@ -0,0 +1,39 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed -}} +{{- if (gt (int .Values.bloomGateway.replicas) 0) -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.bloomGatewayFullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.bloomGatewaySelectorLabels" . | nindent 4 }} + {{- with .Values.bloomGateway.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.bloomGateway.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.bloomGateway.appProtocol.grpc }} + appProtocol: {{ .Values.bloomGateway.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.bloomGatewaySelectorLabels" . | nindent 4 }} +{{- end -}} +{{- end -}} diff --git a/charts/loki/templates/bloom-gateway/statefulset-bloom-gateway.yaml b/charts/loki/templates/bloom-gateway/statefulset-bloom-gateway.yaml new file mode 100644 index 0000000000..7e97b8e93e --- /dev/null +++ b/charts/loki/templates/bloom-gateway/statefulset-bloom-gateway.yaml @@ -0,0 +1,181 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if (and $isDistributed (gt (int .Values.bloomGateway.replicas) 0)) -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "loki.bloomGatewayFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.bloomGatewayLabels" . | nindent 4 }} + {{- with .Values.loki.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.bloomGateway.replicas }} + podManagementPolicy: Parallel + updateStrategy: + rollingUpdate: + partition: 0 + serviceName: {{ include "loki.bloomGatewayFullname" . }}-headless + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + {{- if and (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) (.Values.bloomGateway.persistence.enableStatefulSetAutoDeletePVC) }} + persistentVolumeClaimRetentionPolicy: + whenDeleted: {{ .Values.bloomGateway.persistence.whenDeleted }} + whenScaled: {{ .Values.bloomGateway.persistence.whenScaled }} + {{- end }} + selector: + matchLabels: + {{- include "loki.bloomGatewaySelectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomGateway.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.bloomGatewaySelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomGateway.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomGateway.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.bloomGatewayPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.bloomGateway.terminationGracePeriodSeconds }} + {{- with .Values.bloomGateway.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: bloom-gateway + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + {{- if or .Values.loki.command .Values.bloomGateway.command }} + command: + - {{ coalesce .Values.bloomGateway.command .Values.loki.command | quote }} + {{- end }} + args: + - -config.file=/etc/loki/config/config.yaml + - -target=bloom-gateway + {{- with .Values.bloomGateway.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.bloomGateway.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.bloomGateway.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + {{- include "loki.bloomGateway.readinessProbe" . | nindent 10 }} + volumeMounts: + - name: temp + mountPath: /tmp + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + - name: data + mountPath: /var/loki + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.bloomGateway.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.bloomGateway.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.bloomGateway.extraContainers }} + {{- toYaml .Values.bloomGateway.extraContainers | nindent 8}} + {{- end }} + {{- with .Values.bloomGateway.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomGateway.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomGateway.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: temp + emptyDir: {} + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- if not .Values.bloomGateway.persistence.enabled }} + - name: data + emptyDir: {} + {{- end }} + {{- with .Values.bloomGateway.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.bloomGateway.persistence.enabled }} + volumeClaimTemplates: + {{- range .Values.bloomGateway.persistence.claims }} + - metadata: + name: {{ .name }} + {{- with .annotations }} + annotations: + {{- . | toYaml | nindent 10 }} + {{- end }} + spec: + accessModes: + - ReadWriteOnce + {{- with .storageClass }} + storageClassName: {{ if (eq "-" .) }}""{{ else }}{{ . }}{{ end }} + {{- end }} + resources: + requests: + storage: {{ .size | quote }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/charts/loki/templates/bloom-planner/_helpers-bloom-planner.tpl b/charts/loki/templates/bloom-planner/_helpers-bloom-planner.tpl new file mode 100644 index 0000000000..a4a8c6e4f9 --- /dev/null +++ b/charts/loki/templates/bloom-planner/_helpers-bloom-planner.tpl @@ -0,0 +1,58 @@ +{{/* +bloom planner fullname +*/}} +{{- define "loki.bloomPlannerFullname" -}} +{{ include "loki.fullname" . }}-bloom-planner +{{- end }} + +{{/* +bloom planner common labels +*/}} +{{- define "loki.bloomPlannerLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: bloom-planner +{{- end }} + +{{/* +bloom planner selector labels +*/}} +{{- define "loki.bloomPlannerSelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: bloom-planner +{{- end }} + +{{/* +bloom planner readinessProbe +*/}} +{{- define "loki.bloomPlanner.readinessProbe" -}} +{{- with .Values.bloomPlanner.readinessProbe }} +readinessProbe: + {{- toYaml . | nindent 2 }} +{{- else }} +{{- with .Values.loki.readinessProbe }} +readinessProbe: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* +bloom planner priority class name +*/}} +{{- define "loki.bloomPlannerPriorityClassName" }} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.bloomPlanner.priorityClassName -}} +{{- if $pcn }} +priorityClassName: {{ $pcn }} +{{- end }} +{{- end }} + +{{/* +Create the name of the bloom planner service account +*/}} +{{- define "loki.bloomPlannerServiceAccountName" -}} +{{- if .Values.bloomPlanner.serviceAccount.create -}} + {{ default (print (include "loki.serviceAccountName" .) "-bloom-planner") .Values.bloomPlanner.serviceAccount.name }} +{{- else -}} + {{ default (include "loki.serviceAccountName" .) .Values.bloomPlanner.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/charts/loki/templates/bloom-planner/service-bloom-planner-headless.yaml b/charts/loki/templates/bloom-planner/service-bloom-planner-headless.yaml new file mode 100644 index 0000000000..78e26336f3 --- /dev/null +++ b/charts/loki/templates/bloom-planner/service-bloom-planner-headless.yaml @@ -0,0 +1,37 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if (and $isDistributed (gt (int .Values.bloomPlanner.replicas) 0)) -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.bloomPlannerFullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.bloomPlannerSelectorLabels" . | nindent 4 }} + {{- with .Values.bloomPlanner.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.bloomPlanner.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.bloomPlanner.appProtocol.grpc }} + appProtocol: {{ .Values.bloomPlanner.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.bloomPlannerSelectorLabels" . | nindent 4 }} +{{- end -}} diff --git a/charts/loki/templates/bloom-planner/statefulset-bloom-planner.yaml b/charts/loki/templates/bloom-planner/statefulset-bloom-planner.yaml new file mode 100644 index 0000000000..8406542dab --- /dev/null +++ b/charts/loki/templates/bloom-planner/statefulset-bloom-planner.yaml @@ -0,0 +1,181 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if (and $isDistributed (gt (int .Values.bloomPlanner.replicas) 0)) -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "loki.bloomPlannerFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.bloomPlannerLabels" . | nindent 4 }} + {{- with .Values.loki.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.bloomPlanner.replicas }} + podManagementPolicy: Parallel + updateStrategy: + rollingUpdate: + partition: 0 + serviceName: {{ include "loki.bloomPlannerFullname" . }}-headless + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + {{- if and (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) (.Values.bloomPlanner.persistence.enableStatefulSetAutoDeletePVC) }} + persistentVolumeClaimRetentionPolicy: + whenDeleted: {{ .Values.bloomPlanner.persistence.whenDeleted }} + whenScaled: {{ .Values.bloomPlanner.persistence.whenScaled }} + {{- end }} + selector: + matchLabels: + {{- include "loki.bloomPlannerSelectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomPlanner.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.bloomPlannerSelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomPlanner.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomPlanner.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.bloomPlannerPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.bloomPlanner.terminationGracePeriodSeconds }} + {{- with .Values.bloomPlanner.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: bloom-planner + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + {{- if or .Values.loki.command .Values.bloomPlanner.command }} + command: + - {{ coalesce .Values.bloomPlanner.command .Values.loki.command | quote }} + {{- end }} + args: + - -config.file=/etc/loki/config/config.yaml + - -target=bloom-planner + {{- with .Values.bloomPlanner.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.bloomPlanner.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.bloomPlanner.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + {{- include "loki.bloomPlanner.readinessProbe" . | nindent 10 }} + volumeMounts: + - name: temp + mountPath: /tmp + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + - name: data + mountPath: /var/loki + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.bloomPlanner.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.bloomPlanner.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.bloomPlanner.extraContainers }} + {{- toYaml .Values.bloomPlanner.extraContainers | nindent 8}} + {{- end }} + {{- with .Values.bloomPlanner.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomPlanner.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.bloomPlanner.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: temp + emptyDir: {} + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- if not .Values.bloomPlanner.persistence.enabled }} + - name: data + emptyDir: {} + {{- end }} + {{- with .Values.bloomPlanner.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.bloomPlanner.persistence.enabled }} + volumeClaimTemplates: + {{- range .Values.bloomPlanner.persistence.claims }} + - metadata: + name: {{ .name }} + {{- with .annotations }} + annotations: + {{- . | toYaml | nindent 10 }} + {{- end }} + spec: + accessModes: + - ReadWriteOnce + {{- with .storageClass }} + storageClassName: {{ if (eq "-" .) }}""{{ else }}{{ . }}{{ end }} + {{- end }} + resources: + requests: + storage: {{ .size | quote }} + {{- end }} + {{- end }} +{{- end -}} diff --git a/charts/loki/templates/chunks-cache/poddisruptionbudget-chunks-cache.yaml b/charts/loki/templates/chunks-cache/poddisruptionbudget-chunks-cache.yaml new file mode 100644 index 0000000000..da95adf137 --- /dev/null +++ b/charts/loki/templates/chunks-cache/poddisruptionbudget-chunks-cache.yaml @@ -0,0 +1,16 @@ +{{- if .Values.chunksCache.enabled }} +apiVersion: {{ include "loki.pdb.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "loki.fullname" . }}-memcached-chunks-cache + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: memcached-chunks-cache +spec: + selector: + matchLabels: + {{- include "loki.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: memcached-chunks-cache + maxUnavailable: 1 +{{- end -}} diff --git a/charts/loki/templates/chunks-cache/service-chunks-cache-headless.yaml b/charts/loki/templates/chunks-cache/service-chunks-cache-headless.yaml new file mode 100644 index 0000000000..dc2ccd4b02 --- /dev/null +++ b/charts/loki/templates/chunks-cache/service-chunks-cache-headless.yaml @@ -0,0 +1 @@ +{{- include "loki.memcached.service" (dict "ctx" $ "valuesSection" "chunksCache" "component" "chunks-cache" ) }} diff --git a/charts/loki/templates/chunks-cache/statefulset-chunks-cache.yaml b/charts/loki/templates/chunks-cache/statefulset-chunks-cache.yaml new file mode 100644 index 0000000000..6a54c577ca --- /dev/null +++ b/charts/loki/templates/chunks-cache/statefulset-chunks-cache.yaml @@ -0,0 +1 @@ +{{- include "loki.memcached.statefulSet" (dict "ctx" $ "valuesSection" "chunksCache" "component" "chunks-cache" ) }} diff --git a/charts/loki/templates/compactor/_helpers-compactor.tpl b/charts/loki/templates/compactor/_helpers-compactor.tpl new file mode 100644 index 0000000000..75c21db167 --- /dev/null +++ b/charts/loki/templates/compactor/_helpers-compactor.tpl @@ -0,0 +1,81 @@ +{{/* +compactor fullname +*/}} +{{- define "loki.compactorFullname" -}} +{{ include "loki.fullname" . }}-compactor +{{- end }} + +{{/* +compactor common labels +*/}} +{{- define "loki.compactorLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: compactor +{{- end }} + +{{/* +compactor selector labels +*/}} +{{- define "loki.compactorSelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: compactor +{{- end }} + +{{/* +compactor image +*/}} +{{- define "loki.compactorImage" -}} +{{- $dict := dict "loki" .Values.loki.image "service" .Values.compactor.image "global" .Values.global.image "defaultVersion" .Chart.AppVersion -}} +{{- include "loki.lokiImage" $dict -}} +{{- end }} + +{{/* +compactor readinessProbe +*/}} +{{- define "loki.compactor.readinessProbe" -}} +{{- with .Values.compactor.readinessProbe }} +readinessProbe: + {{- toYaml . | nindent 2 }} +{{- else }} +{{- with .Values.loki.readinessProbe }} +readinessProbe: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* +compactor livenessProbe +*/}} +{{- define "loki.compactor.livenessProbe" -}} +{{- with .Values.compactor.livenessProbe }} +livenessProbe: + {{- toYaml . | nindent 2 }} +{{- else }} +{{- with .Values.loki.livenessProbe }} +livenessProbe: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* +compactor priority class name +*/}} +{{- define "loki.compactorPriorityClassName" }} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.compactor.priorityClassName -}} +{{- if $pcn }} +priorityClassName: {{ $pcn }} +{{- end }} +{{- end }} + +{{/* +Create the name of the compactor service account +*/}} +{{- define "loki.compactorServiceAccountName" -}} +{{- if .Values.compactor.serviceAccount.create -}} + {{ default (print (include "loki.serviceAccountName" .) "-compactor") .Values.compactor.serviceAccount.name }} +{{- else -}} + {{ default (include "loki.serviceAccountName" .) .Values.compactor.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/charts/loki/templates/compactor/service-compactor.yaml b/charts/loki/templates/compactor/service-compactor.yaml new file mode 100644 index 0000000000..f118b6cc9b --- /dev/null +++ b/charts/loki/templates/compactor/service-compactor.yaml @@ -0,0 +1,38 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.compactorFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.labels" . | nindent 4 }} + {{- with .Values.compactor.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + app.kubernetes.io/component: compactor + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.compactor.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.compactor.appProtocol.grpc }} + appProtocol: {{ .Values.compactor.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: compactor +{{- end }} diff --git a/charts/loki/templates/compactor/statefulset-compactor.yaml b/charts/loki/templates/compactor/statefulset-compactor.yaml new file mode 100644 index 0000000000..98fab0affc --- /dev/null +++ b/charts/loki/templates/compactor/statefulset-compactor.yaml @@ -0,0 +1,193 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "loki.compactorFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.compactorLabels" . | nindent 4 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.compactor.replicas }} + podManagementPolicy: Parallel + updateStrategy: + rollingUpdate: + partition: 0 + serviceName: {{ include "loki.compactorFullname" . }}-headless + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + {{- if and (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) (.Values.compactor.persistence.enableStatefulSetAutoDeletePVC) }} + persistentVolumeClaimRetentionPolicy: + whenDeleted: {{ .Values.compactor.persistence.whenDeleted }} + whenScaled: {{ .Values.compactor.persistence.whenScaled }} + {{- end }} + selector: + matchLabels: + {{- include "loki.compactorSelectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.compactor.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.compactorSelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.compactor.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} + {{- with .Values.compactor.topologySpreadConstraints }} + topologySpreadConstraints: + {{- tpl . $ | nindent 8 }} + {{- end }} + {{- end }} + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.compactor.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.compactorPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.compactor.terminationGracePeriodSeconds }} + {{- with .Values.compactor.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: compactor + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + {{- if or .Values.loki.command .Values.compactor.command }} + command: + - {{ coalesce .Values.compactor.command .Values.loki.command | quote }} + {{- end }} + args: + - -config.file=/etc/loki/config/config.yaml + - -target=compactor + {{- with .Values.compactor.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.compactor.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.compactor.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + {{- include "loki.compactor.readinessProbe" . | nindent 10 }} + {{- include "loki.compactor.livenessProbe" . | nindent 10 }} + volumeMounts: + - name: temp + mountPath: /tmp + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + - name: data + mountPath: /var/loki + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.compactor.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.compactor.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.compactor.lifecycle }} + lifecycle: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.compactor.extraContainers }} + {{- toYaml .Values.compactor.extraContainers | nindent 8}} + {{- end }} + {{- with .Values.compactor.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.compactor.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.compactor.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: temp + emptyDir: {} + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- if not .Values.compactor.persistence.enabled }} + - name: data + emptyDir: {} + {{- end }} + {{- with .Values.compactor.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.compactor.persistence.enabled }} + volumeClaimTemplates: + {{- range .Values.compactor.persistence.claims }} + - metadata: + name: {{ .name }} + {{- with .annotations }} + annotations: + {{- . | toYaml | nindent 10 }} + {{- end }} + spec: + accessModes: + - ReadWriteOnce + {{- with .storageClass }} + storageClassName: {{ if (eq "-" .) }}""{{ else }}{{ . }}{{ end }} + {{- end }} + resources: + requests: + storage: {{ .size | quote }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/config.yaml b/charts/loki/templates/config.yaml index 101abc353e..fe47590078 100644 --- a/charts/loki/templates/config.yaml +++ b/charts/loki/templates/config.yaml @@ -1,4 +1,4 @@ -{{- if not .Values.loki.existingSecretForConfig -}} +{{- if .Values.loki.generatedConfigObjectName -}} apiVersion: v1 {{- if eq .Values.loki.configStorageType "Secret" }} kind: Secret @@ -6,7 +6,7 @@ kind: Secret kind: ConfigMap {{- end }} metadata: - name: {{ tpl .Values.loki.externalConfigSecretName . }} + name: {{ tpl .Values.loki.generatedConfigObjectName . }} namespace: {{ $.Release.Namespace }} labels: {{- include "loki.labels" . | nindent 4 }} diff --git a/charts/loki/templates/distributor/_helpers-distributor.tpl b/charts/loki/templates/distributor/_helpers-distributor.tpl new file mode 100644 index 0000000000..c23179e905 --- /dev/null +++ b/charts/loki/templates/distributor/_helpers-distributor.tpl @@ -0,0 +1,32 @@ +{{/* +distributor fullname +*/}} +{{- define "loki.distributorFullname" -}} +{{ include "loki.fullname" . }}-distributor +{{- end }} + +{{/* +distributor common labels +*/}} +{{- define "loki.distributorLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: distributor +{{- end }} + +{{/* +distributor selector labels +*/}} +{{- define "loki.distributorSelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: distributor +{{- end }} + +{{/* +distributor priority class name +*/}} +{{- define "loki.distributorPriorityClassName" -}} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.distributor.priorityClassName -}} +{{- if $pcn }} +priorityClassName: {{ $pcn }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/distributor/deployment-distributor.yaml b/charts/loki/templates/distributor/deployment-distributor.yaml new file mode 100644 index 0000000000..be66bfc6b5 --- /dev/null +++ b/charts/loki/templates/distributor/deployment-distributor.yaml @@ -0,0 +1,152 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "loki.distributorFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.distributorLabels" . | nindent 4 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if not .Values.distributor.autoscaling.enabled }} + replicas: {{ .Values.distributor.replicas }} +{{- end }} + strategy: + rollingUpdate: + maxSurge: {{ .Values.distributor.maxSurge }} + maxUnavailable: 1 + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + selector: + matchLabels: + {{- include "loki.distributorSelectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.distributor.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.distributorSelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.distributor.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.distributor.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.distributorPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.distributor.terminationGracePeriodSeconds }} + containers: + - name: distributor + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + {{- if or .Values.loki.command .Values.distributor.command }} + command: + - {{ coalesce .Values.distributor.command .Values.loki.command | quote }} + {{- end }} + args: + - -config.file=/etc/loki/config/config.yaml + - -target=distributor + {{- if .Values.ingester.zoneAwareReplication.enabled }} + {{- if and (.Values.ingester.zoneAwareReplication.migration.enabled) (not .Values.ingester.zoneAwareReplication.migration.writePath) }} + - -distributor.zone-awareness-enabled=false + {{- else }} + - -distributor.zone-awareness-enabled=true + {{- end }} + {{- end }} + {{- with .Values.distributor.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.distributor.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.distributor.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + readinessProbe: + {{- toYaml .Values.loki.readinessProbe | nindent 12 }} + livenessProbe: + {{- toYaml .Values.loki.livenessProbe | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.distributor.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.distributor.resources | nindent 12 }} + {{- if .Values.distributor.extraContainers }} + {{- toYaml .Values.distributor.extraContainers | nindent 8}} + {{- end }} + {{- with .Values.distributor.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.distributor.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.distributor.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- with .Values.distributor.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end -}} diff --git a/charts/loki/templates/distributor/hpa.yaml b/charts/loki/templates/distributor/hpa.yaml new file mode 100644 index 0000000000..838a310048 --- /dev/null +++ b/charts/loki/templates/distributor/hpa.yaml @@ -0,0 +1,54 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.distributor.autoscaling.enabled }} +{{- $apiVersion := include "loki.hpa.apiVersion" . -}} +apiVersion: {{ $apiVersion }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "loki.distributorFullname" . }} + labels: + {{- include "loki.distributorLabels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "loki.distributorFullname" . }} + minReplicas: {{ .Values.distributor.autoscaling.minReplicas }} + maxReplicas: {{ .Values.distributor.autoscaling.maxReplicas }} + metrics: + {{- with .Values.distributor.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + {{- if (eq $apiVersion "autoscaling/v2") }} + target: + type: Utilization + averageUtilization: {{ . }} + {{- else }} + targetAverageUtilization: {{ . }} + {{- end }} + {{- end }} + {{- with .Values.distributor.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + {{- if (eq $apiVersion "autoscaling/v2") }} + target: + type: Utilization + averageUtilization: {{ . }} + {{- else }} + targetAverageUtilization: {{ . }} + {{- end }} + {{- end }} + {{- with .Values.distributor.autoscaling.customMetrics }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.distributor.autoscaling.behavior.enabled }} + behavior: + {{- with .Values.distributor.autoscaling.behavior.scaleDown }} + scaleDown: {{ toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.distributor.autoscaling.behavior.scaleUp }} + scaleUp: {{ toYaml . | nindent 6 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/distributor/poddisruptionbudget-distributor.yaml b/charts/loki/templates/distributor/poddisruptionbudget-distributor.yaml new file mode 100644 index 0000000000..806a447f9f --- /dev/null +++ b/charts/loki/templates/distributor/poddisruptionbudget-distributor.yaml @@ -0,0 +1,21 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed (gt (int .Values.distributor.replicas) 1) }} +{{- if kindIs "invalid" .Values.distributor.maxUnavailable }} +{{- fail "`.Values.distributor.maxUnavailable` must be set when `.Values.distributor.replicas` is greater than 1." }} +{{- else }} +apiVersion: {{ include "loki.pdb.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "loki.distributorFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.distributorLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "loki.distributorSelectorLabels" . | nindent 6 }} + {{- with .Values.distributor.maxUnavailable }} + maxUnavailable: {{ . }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/distributor/service-distributor-headless.yaml b/charts/loki/templates/distributor/service-distributor-headless.yaml new file mode 100644 index 0000000000..650b62959d --- /dev/null +++ b/charts/loki/templates/distributor/service-distributor-headless.yaml @@ -0,0 +1,39 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.distributorFullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.distributorSelectorLabels" . | nindent 4 }} + {{- with .Values.distributor.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + variant: headless + prometheus.io/service-monitor: "false" + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.distributor.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.distributor.appProtocol.grpc }} + appProtocol: {{ .Values.distributor.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.distributorSelectorLabels" . | nindent 4 }} +{{- end -}} diff --git a/charts/loki/templates/distributor/service-distributor.yaml b/charts/loki/templates/distributor/service-distributor.yaml new file mode 100644 index 0000000000..6a8995677c --- /dev/null +++ b/charts/loki/templates/distributor/service-distributor.yaml @@ -0,0 +1,36 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.distributorFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.distributorLabels" . | nindent 4 }} + {{- with .Values.distributor.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.distributor.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.distributor.appProtocol.grpc }} + appProtocol: {{ .Values.distributor.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.distributorSelectorLabels" . | nindent 4 }} +{{- end -}} diff --git a/charts/loki/templates/extra-manifests.yaml b/charts/loki/templates/extra-manifests.yaml index a9bb3b6ba8..7b69423467 100644 --- a/charts/loki/templates/extra-manifests.yaml +++ b/charts/loki/templates/extra-manifests.yaml @@ -1,4 +1,8 @@ -{{ range .Values.extraObjects }} +{{- range .Values.extraObjects }} --- +{{- if kindIs "map" . }} {{ tpl (toYaml .) $ }} -{{ end }} +{{- else }} +{{ tpl . $ }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/gateway/_helpers-gateway.tpl b/charts/loki/templates/gateway/_helpers-gateway.tpl index 272814b6c0..39890b12e9 100644 --- a/charts/loki/templates/gateway/_helpers-gateway.tpl +++ b/charts/loki/templates/gateway/_helpers-gateway.tpl @@ -2,7 +2,7 @@ gateway fullname */}} {{- define "loki.gatewayFullname" -}} -{{ include "loki.name" . }}-gateway +{{ include "loki.fullname" . }}-gateway {{- end }} {{/* diff --git a/charts/loki/templates/gateway/configmap-gateway.yaml b/charts/loki/templates/gateway/configmap-gateway.yaml index fe98c73dc3..1c981a73a5 100644 --- a/charts/loki/templates/gateway/configmap-gateway.yaml +++ b/charts/loki/templates/gateway/configmap-gateway.yaml @@ -1,4 +1,4 @@ -{{- if and .Values.gateway.enabled }} +{{- if and .Values.gateway.enabled (not (and .Values.enterprise.enabled .Values.enterprise.gelGateway)) }} apiVersion: v1 kind: ConfigMap metadata: diff --git a/charts/loki/templates/gateway/deployment-gateway-enterprise.yaml b/charts/loki/templates/gateway/deployment-gateway-enterprise.yaml new file mode 100644 index 0000000000..746fa6142b --- /dev/null +++ b/charts/loki/templates/gateway/deployment-gateway-enterprise.yaml @@ -0,0 +1,142 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- $isSimpleScalable := eq (include "loki.deployment.isScalable" .) "true" -}} +{{- if and .Values.gateway.enabled .Values.enterprise.enabled .Values.enterprise.gelGateway }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "loki.gatewayFullname" . }} + labels: + {{- include "loki.gatewayLabels" . | nindent 4 }} + {{- with .Values.enterpriseGateway.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.enterpriseGateway.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.enterpriseGateway.replicas }} + selector: + matchLabels: + {{- include "loki.gatewaySelectorLabels" . | nindent 6 }} + strategy: + {{- toYaml .Values.enterpriseGateway.strategy | nindent 4 }} + template: + metadata: + labels: + {{- include "loki.gatewaySelectorLabels" . | nindent 8 }} + {{- with .Values.enterpriseGateway.labels }} + {{- toYaml . | nindent 8 }} + {{- end }} + annotations: + {{- if .Values.useExternalConfig }} + checksum/config: {{ .Values.externalConfigVersion }} + {{- else }} + checksum/config: {{ include (print $.Template.BasePath "/config.yaml") . | sha256sum }} + {{- end}} + {{- with .Values.enterpriseGateway.annotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ template "loki.serviceAccountName" . }} + {{- if .Values.enterpriseGateway.priorityClassName }} + priorityClassName: {{ .Values.enterpriseGateway.priorityClassName }} + {{- end }} + securityContext: + {{- toYaml .Values.enterpriseGateway.podSecurityContext | nindent 8 }} + initContainers: + {{- toYaml .Values.enterpriseGateway.initContainers | nindent 8 }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.enterpriseGateway.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: gateway + image: "{{ template "loki.image" . }}" + imagePullPolicy: {{ .Values.enterprise.image.pullPolicy }} + args: + - -target=gateway + - -config.file=/etc/loki/config/config.yaml + {{- if .Values.minio.enabled }} + - -admin.client.backend-type=s3 + - -admin.client.s3.endpoint={{ template "loki.minio" . }} + - -admin.client.s3.bucket-name=enterprise-logs-admin + - -admin.client.s3.access-key-id={{ .Values.minio.accessKey }} + - -admin.client.s3.secret-access-key={{ .Values.minio.secretKey }} + - -admin.client.s3.insecure=true + {{- end }} + {{- if and $isDistributed .Values.enterpriseGateway.useDefaultProxyURLs }} + - -gateway.proxy.default.url=http://{{ template "loki.fullname" . }}-admin-api.{{ .Release.Namespace }}.svc:3100 + - -gateway.proxy.admin-api.url=http://{{ template "loki.fullname" . }}-admin-api.{{ .Release.Namespace }}.svc:3100 + - -gateway.proxy.distributor.url=dns:///{{ template "loki.fullname" . }}-distributor-headless.{{ .Release.Namespace }}.svc:9095 + - -gateway.proxy.ingester.url=http://{{ template "loki.fullname" . }}-ingester.{{ .Release.Namespace }}.svc:3100 + - -gateway.proxy.query-frontend.url=http://{{ template "loki.fullname" . }}-query-frontend.{{ .Release.Namespace }}.svc:3100 + - -gateway.proxy.ruler.url=http://{{ template "loki.fullname" . }}-ruler.{{ .Release.Namespace }}.svc:3100 + {{- end }} + {{- if and $isSimpleScalable .Values.enterpriseGateway.useDefaultProxyURLs }} + - -gateway.proxy.default.url=http://{{ template "enterprise-logs.adminApiFullname" . }}.{{ .Release.Namespace }}.svc:3100 + - -gateway.proxy.admin-api.url=http://{{ template "enterprise-logs.adminApiFullname" . }}.{{ .Release.Namespace }}.svc:3100 + - -gateway.proxy.compactor.url=http://{{ template "loki.backendFullname" . }}-headless.{{ .Release.Namespace }}.svc:3100 + - -gateway.proxy.distributor.url=dns:///{{ template "loki.writeFullname" . }}-headless.{{ .Release.Namespace }}.svc:9095 + - -gateway.proxy.ingester.url=http://{{ template "loki.writeFullname" . }}.{{ .Release.Namespace }}.svc:3100 + - -gateway.proxy.query-frontend.url=http://{{ template "loki.readFullname" . }}.{{ .Release.Namespace }}.svc:3100 + - -gateway.proxy.ruler.url=http://{{ template "loki.backendFullname" . }}-headless.{{ .Release.Namespace }}.svc:3100 + - -gateway.proxy.query-scheduler.url=http://{{ template "loki.backendFullname" . }}-headless.{{ .Release.Namespace }}.svc:3100 + {{- end }} + {{- range $key, $value := .Values.enterpriseGateway.extraArgs }} + - "-{{ $key }}={{ $value }}" + {{- end }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: license + mountPath: /etc/loki/license + - name: storage + mountPath: /data + {{- if .Values.enterpriseGateway.extraVolumeMounts }} + {{ toYaml .Values.enterpriseGateway.extraVolumeMounts | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + readinessProbe: + {{- toYaml .Values.enterpriseGateway.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.enterpriseGateway.resources | nindent 12 }} + securityContext: + {{- toYaml .Values.enterpriseGateway.containerSecurityContext | nindent 12 }} + env: + {{- if .Values.enterpriseGateway.env }} + {{ toYaml .Values.enterpriseGateway.env | nindent 12 }} + {{- end }} + {{- with .Values.enterpriseGateway.extraContainers }} + {{ toYaml . | nindent 8 }} + {{- end }} + nodeSelector: + {{- toYaml .Values.enterpriseGateway.nodeSelector | nindent 8 }} + affinity: + {{- toYaml .Values.enterpriseGateway.affinity | nindent 8 }} + tolerations: + {{- toYaml .Values.enterpriseGateway.tolerations | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.enterpriseGateway.terminationGracePeriodSeconds }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + - name: storage + emptyDir: {} + {{- if .Values.enterpriseGateway.extraVolumes }} + {{ toYaml .Values.enterpriseGateway.extraVolumes | nindent 8 }} + {{- end }} +{{- end }} \ No newline at end of file diff --git a/charts/loki/templates/gateway/deployment-gateway.yaml b/charts/loki/templates/gateway/deployment-gateway-nginx.yaml similarity index 95% rename from charts/loki/templates/gateway/deployment-gateway.yaml rename to charts/loki/templates/gateway/deployment-gateway-nginx.yaml index 4ffa0c935b..2b2d4c7bd7 100644 --- a/charts/loki/templates/gateway/deployment-gateway.yaml +++ b/charts/loki/templates/gateway/deployment-gateway-nginx.yaml @@ -1,4 +1,4 @@ -{{- if .Values.gateway.enabled }} +{{- if and .Values.gateway.enabled (not (and .Values.enterprise.enabled .Values.enterprise.gelGateway)) }} apiVersion: apps/v1 kind: Deployment metadata: @@ -61,8 +61,8 @@ spec: image: {{ include "loki.gatewayImage" . }} imagePullPolicy: {{ .Values.gateway.image.pullPolicy }} ports: - - name: http - containerPort: 8080 + - name: http-metrics + containerPort: {{ .Values.gateway.containerPort }} protocol: TCP {{- with .Values.gateway.extraEnv }} env: @@ -101,7 +101,7 @@ spec: {{- end }} {{- with .Values.gateway.affinity }} affinity: - {{- tpl . $ | nindent 8 }} + {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.gateway.dnsConfig }} dnsConfig: diff --git a/charts/loki/templates/gateway/service-gateway.yaml b/charts/loki/templates/gateway/service-gateway.yaml index 5cb7a55c3c..8c710263d7 100644 --- a/charts/loki/templates/gateway/service-gateway.yaml +++ b/charts/loki/templates/gateway/service-gateway.yaml @@ -28,9 +28,9 @@ spec: loadBalancerIP: {{ .Values.gateway.service.loadBalancerIP }} {{- end }} ports: - - name: http + - name: http-metrics port: {{ .Values.gateway.service.port }} - targetPort: http + targetPort: http-metrics {{- if and (eq "NodePort" .Values.gateway.service.type) .Values.gateway.service.nodePort }} nodePort: {{ .Values.gateway.service.nodePort }} {{- end }} diff --git a/charts/loki/templates/index-gateway/_helpers-index-gateway.tpl b/charts/loki/templates/index-gateway/_helpers-index-gateway.tpl new file mode 100644 index 0000000000..f42dff3d06 --- /dev/null +++ b/charts/loki/templates/index-gateway/_helpers-index-gateway.tpl @@ -0,0 +1,40 @@ +{{/* +index-gateway fullname +*/}} +{{- define "loki.indexGatewayFullname" -}} +{{ include "loki.fullname" . }}-index-gateway +{{- end }} + +{{/* +index-gateway common labels +*/}} +{{- define "loki.indexGatewayLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: index-gateway +{{- end }} + +{{/* +index-gateway selector labels +*/}} +{{- define "loki.indexGatewaySelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: index-gateway +{{- end }} + +{{/* +index-gateway image +*/}} +{{- define "loki.indexGatewayImage" -}} +{{- $dict := dict "loki" .Values.loki.image "service" .Values.indexGateway.image "global" .Values.global.image "defaultVersion" .Chart.AppVersion -}} +{{- include "loki.lokiImage" $dict -}} +{{- end }} + +{{/* +index-gateway priority class name +*/}} +{{- define "loki.indexGatewayPriorityClassName" -}} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.indexGateway.priorityClassName -}} +{{- if $pcn }} +priorityClassName: {{ $pcn }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/index-gateway/poddisruptionbudget-index-gateway.yaml b/charts/loki/templates/index-gateway/poddisruptionbudget-index-gateway.yaml new file mode 100644 index 0000000000..22ba1a0b4c --- /dev/null +++ b/charts/loki/templates/index-gateway/poddisruptionbudget-index-gateway.yaml @@ -0,0 +1,20 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed (gt (int .Values.indexGateway.replicas) 1) }} +{{- if kindIs "invalid" .Values.indexGateway.maxUnavailable }} +{{- fail "`.Values.indexGateway.maxUnavailable` must be set when `.Values.indexGateway.replicas` is greater than 1." }} +{{- else }} +apiVersion: {{ include "loki.pdb.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "loki.indexGatewayFullname" . }} + labels: + {{- include "loki.indexGatewayLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "loki.indexGatewaySelectorLabels" . | nindent 6 }} + {{- with .Values.indexGateway.maxUnavailable }} + maxUnavailable: {{ . }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/index-gateway/service-index-gateway-headless.yaml b/charts/loki/templates/index-gateway/service-index-gateway-headless.yaml new file mode 100644 index 0000000000..06506582f9 --- /dev/null +++ b/charts/loki/templates/index-gateway/service-index-gateway-headless.yaml @@ -0,0 +1,34 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.indexGatewayFullname" . }}-headless + labels: + {{- include "loki.indexGatewaySelectorLabels" . | nindent 4 }} + prometheus.io/service-monitor: "false" + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.indexGateway.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- with .Values.indexGateway.appProtocol.grpc }} + appProtocol: {{ . }} + {{- end }} + selector: + {{- include "loki.indexGatewaySelectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/loki/templates/index-gateway/service-index-gateway.yaml b/charts/loki/templates/index-gateway/service-index-gateway.yaml new file mode 100644 index 0000000000..822a0ce692 --- /dev/null +++ b/charts/loki/templates/index-gateway/service-index-gateway.yaml @@ -0,0 +1,35 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.indexGatewayFullname" . }} + labels: + {{- include "loki.indexGatewayLabels" . | nindent 4 }} + {{- with .Values.indexGateway.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.indexGateway.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- with .Values.indexGateway.appProtocol.grpc }} + appProtocol: {{ . }} + {{- end }} + selector: + {{- include "loki.indexGatewaySelectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/loki/templates/index-gateway/statefulset-index-gateway.yaml b/charts/loki/templates/index-gateway/statefulset-index-gateway.yaml new file mode 100644 index 0000000000..5797185ef0 --- /dev/null +++ b/charts/loki/templates/index-gateway/statefulset-index-gateway.yaml @@ -0,0 +1,186 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "loki.indexGatewayFullname" . }} + labels: + {{- include "loki.indexGatewayLabels" . | nindent 4 }} + {{- with .Values.loki.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.indexGateway.replicas }} + updateStrategy: + rollingUpdate: + partition: 0 + serviceName: {{ include "loki.indexGatewayFullname" . }}-headless + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + {{- if and (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) (.Values.indexGateway.persistence.enableStatefulSetAutoDeletePVC) }} + {{/* + Data on the read nodes is easy to replace, so we want to always delete PVCs to make + operation easier, and will rely on re-fetching data when needed. + */}} + persistentVolumeClaimRetentionPolicy: + whenDeleted: {{ .Values.indexGateway.persistence.whenDeleted }} + whenScaled: {{ .Values.indexGateway.persistence.whenScaled }} + {{- end }} + selector: + matchLabels: + {{- include "loki.indexGatewaySelectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.indexGateway.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.indexGatewaySelectorLabels" . | nindent 8 }} + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.indexGateway.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.indexGateway.joinMemberlist }} + app.kubernetes.io/part-of: memberlist + {{- end }} + spec: + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.indexGateway.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.indexGatewayPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.indexGateway.terminationGracePeriodSeconds }} + {{- with .Values.indexGateway.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: index-gateway + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + args: + - -config.file=/etc/loki/config/config.yaml + - -target=index-gateway + {{- with .Values.indexGateway.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + {{- if .Values.indexGateway.joinMemberlist }} + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- end }} + {{- with .Values.indexGateway.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.indexGateway.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + readinessProbe: + {{- toYaml .Values.loki.readinessProbe | nindent 12 }} + livenessProbe: + {{- toYaml .Values.loki.livenessProbe | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + - name: data + mountPath: /var/loki + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.indexGateway.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.indexGateway.resources | nindent 12 }} + {{- if .Values.indexGateway.extraContainers }} + {{- toYaml .Values.indexGateway.extraContainers | nindent 8}} + {{- end }} + {{- with .Values.indexGateway.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.indexGateway.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.indexGateway.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- with .Values.indexGateway.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if not .Values.indexGateway.persistence.enabled }} + - name: data + emptyDir: {} + {{- else if .Values.indexGateway.persistence.inMemory }} + - name: data + {{- if .Values.indexGateway.persistence.inMemory }} + emptyDir: + medium: Memory + {{- end }} + {{- if .Values.indexGateway.persistence.size }} + sizeLimit: {{ .Values.indexGateway.persistence.size }} + {{- end }} + {{- else }} + volumeClaimTemplates: + - metadata: + name: data + {{- with .Values.indexGateway.persistence.annotations }} + annotations: + {{- . | toYaml | nindent 10 }} + {{- end }} + spec: + accessModes: + - ReadWriteOnce + {{- with .Values.indexGateway.persistence.storageClass }} + storageClassName: {{ if (eq "-" .) }}""{{ else }}{{ . }}{{ end }} + {{- end }} + resources: + requests: + storage: {{ .Values.indexGateway.persistence.size | quote }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/ingester/_helpers-ingester.tpl b/charts/loki/templates/ingester/_helpers-ingester.tpl new file mode 100644 index 0000000000..418d4094d5 --- /dev/null +++ b/charts/loki/templates/ingester/_helpers-ingester.tpl @@ -0,0 +1,74 @@ +{{/* +ingester fullname +*/}} +{{- define "loki.ingesterFullname" -}} +{{ include "loki.fullname" . }}-ingester +{{- end }} + +{{/* +ingester common labels +*/}} +{{- define "loki.ingesterLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: ingester +{{- end }} + +{{/* +ingester selector labels +*/}} +{{- define "loki.ingesterSelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: ingester +{{- end }} + +{{/* +ingester priority class name +*/}} +{{- define "loki.ingesterPriorityClassName" -}} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.ingester.priorityClassName -}} +{{- if $pcn }} +priorityClassName: {{ $pcn }} +{{- end }} +{{- end }} + +{{- define "loki.ingester.readinessProbe" -}} +{{- with .Values.ingester.readinessProbe }} +readinessProbe: + {{- toYaml . | nindent 2 }} +{{- else }} +{{- with .Values.loki.readinessProbe }} +readinessProbe: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end }} +{{- end -}} + +{{- define "loki.ingester.livenessProbe" -}} +{{- with .Values.ingester.livenessProbe }} +livenessProbe: + {{- toYaml . | nindent 2 }} +{{- else }} +{{- with .Values.loki.livenessProbe }} +livenessProbe: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* +expects global context +*/}} +{{- define "loki.ingester.replicaCount" -}} +{{- ceil (divf .Values.ingester.replicas 3) -}} +{{- end -}} + +{{/* +expects a dict +{ + "replicas": replicas in a zone, + "ctx": global context +} +*/}} +{{- define "loki.ingester.maxUnavailable" -}} +{{- ceil (mulf .replicas (divf (int .ctx.Values.ingester.zoneAwareReplication.maxUnavailablePct) 100)) -}} +{{- end -}} \ No newline at end of file diff --git a/charts/loki/templates/ingester/hpa.yaml b/charts/loki/templates/ingester/hpa.yaml new file mode 100644 index 0000000000..6e1ee94263 --- /dev/null +++ b/charts/loki/templates/ingester/hpa.yaml @@ -0,0 +1,55 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.ingester.autoscaling.enabled }} +{{- $apiVersion := include "loki.hpa.apiVersion" . -}} +apiVersion: {{ $apiVersion }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "loki.ingesterFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.ingesterLabels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: StatefulSet + name: {{ include "loki.ingesterFullname" . }} + minReplicas: {{ .Values.ingester.autoscaling.minReplicas }} + maxReplicas: {{ .Values.ingester.autoscaling.maxReplicas }} + metrics: + {{- with .Values.ingester.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + {{- if (eq $apiVersion "autoscaling/v2") }} + target: + type: Utilization + averageUtilization: {{ . }} + {{- else }} + targetAverageUtilization: {{ . }} + {{- end }} + {{- end }} + {{- with .Values.ingester.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + {{- if (eq $apiVersion "autoscaling/v2") }} + target: + type: Utilization + averageUtilization: {{ . }} + {{- else }} + targetAverageUtilization: {{ . }} + {{- end }} + {{- end }} + {{- with .Values.ingester.autoscaling.customMetrics }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.ingester.autoscaling.behavior.enabled }} + behavior: + {{- with .Values.ingester.autoscaling.behavior.scaleDown }} + scaleDown: {{ toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.ingester.autoscaling.behavior.scaleUp }} + scaleUp: {{ toYaml . | nindent 6 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/ingester/poddisruptionbudget-ingester-rollout.yaml b/charts/loki/templates/ingester/poddisruptionbudget-ingester-rollout.yaml new file mode 100644 index 0000000000..000ab8569a --- /dev/null +++ b/charts/loki/templates/ingester/poddisruptionbudget-ingester-rollout.yaml @@ -0,0 +1,21 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed (gt (int .Values.ingester.replicas) 1) (.Values.ingester.zoneAwareReplication.enabled) }} +{{- if kindIs "invalid" .Values.ingester.maxUnavailable }} +{{- fail "`.Values.ingester.maxUnavailable` must be set when `.Values.ingester.replicas` is greater than 1." }} +{{- else }} +apiVersion: {{ include "loki.pdb.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "loki.ingesterFullname" . }}-rollout + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.ingesterLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + rollout-group: ingester + {{- with .Values.ingester.maxUnavailable }} + maxUnavailable: {{ . }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/ingester/poddisruptionbudget-ingester.yaml b/charts/loki/templates/ingester/poddisruptionbudget-ingester.yaml new file mode 100644 index 0000000000..1142c01021 --- /dev/null +++ b/charts/loki/templates/ingester/poddisruptionbudget-ingester.yaml @@ -0,0 +1,27 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed (gt (int .Values.ingester.replicas) 1) (or (not .Values.ingester.zoneAwareReplication.enabled) .Values.ingester.zoneAwareReplication.migration.enabled) }} +{{- if kindIs "invalid" .Values.ingester.maxUnavailable }} +{{- fail "`.Values.ingester.maxUnavailable` must be set when `.Values.ingester.replicas` is greater than 1." }} +{{- else }} +apiVersion: {{ include "loki.pdb.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "loki.ingesterFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.ingesterLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "loki.ingesterSelectorLabels" . | nindent 6 }} + {{/* zone aware ingesters get their own pod disruption budget, ignore them here */}} + matchExpressions: + - key: rollout-group + operator: NotIn + values: + - "ingester" + {{- with .Values.ingester.maxUnavailable }} + maxUnavailable: {{ . }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/ingester/service-ingester-headless.yaml b/charts/loki/templates/ingester/service-ingester-headless.yaml new file mode 100644 index 0000000000..8a8b92f2eb --- /dev/null +++ b/charts/loki/templates/ingester/service-ingester-headless.yaml @@ -0,0 +1,35 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed (or (not .Values.ingester.zoneAwareReplication.enabled) .Values.ingester.zoneAwareReplication.migration.enabled) }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.ingesterFullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.ingesterSelectorLabels" . | nindent 4 }} + prometheus.io/service-monitor: "false" + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.ingester.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.ingester.appProtocol.grpc }} + appProtocol: {{ .Values.ingester.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.ingesterSelectorLabels" . | nindent 4 }} +{{- end -}} diff --git a/charts/loki/templates/ingester/service-ingester-zone-a-headless.yaml b/charts/loki/templates/ingester/service-ingester-zone-a-headless.yaml new file mode 100644 index 0000000000..03add3b286 --- /dev/null +++ b/charts/loki/templates/ingester/service-ingester-zone-a-headless.yaml @@ -0,0 +1,38 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.ingester.zoneAwareReplication.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.ingesterFullname" . }}-zone-a-headless + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.ingesterLabels" . | nindent 4 }} + {{- with .Values.ingester.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.ingester.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + clusterIP: None + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.ingester.appProtocol.grpc }} + appProtocol: {{ .Values.ingester.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.ingesterSelectorLabels" . | nindent 4 }} + name: ingester-zone-a + rollout-group: ingester +{{- end -}} diff --git a/charts/loki/templates/ingester/service-ingester-zone-b-headless.yaml b/charts/loki/templates/ingester/service-ingester-zone-b-headless.yaml new file mode 100644 index 0000000000..607221922a --- /dev/null +++ b/charts/loki/templates/ingester/service-ingester-zone-b-headless.yaml @@ -0,0 +1,38 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.ingester.zoneAwareReplication.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.ingesterFullname" . }}-zone-b-headless + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.ingesterLabels" . | nindent 4 }} + {{- with .Values.ingester.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.ingester.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + clusterIP: None + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.ingester.appProtocol.grpc }} + appProtocol: {{ .Values.ingester.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.ingesterSelectorLabels" . | nindent 4 }} + name: ingester-zone-b + rollout-group: ingester +{{- end -}} diff --git a/charts/loki/templates/ingester/service-ingester-zone-c-headless.yaml b/charts/loki/templates/ingester/service-ingester-zone-c-headless.yaml new file mode 100644 index 0000000000..554144746a --- /dev/null +++ b/charts/loki/templates/ingester/service-ingester-zone-c-headless.yaml @@ -0,0 +1,38 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.ingester.zoneAwareReplication.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.ingesterFullname" . }}-zone-c-headless + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.ingesterLabels" . | nindent 4 }} + {{- with .Values.ingester.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.ingester.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + clusterIP: None + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.ingester.appProtocol.grpc }} + appProtocol: {{ .Values.ingester.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.ingesterSelectorLabels" . | nindent 4 }} + name: ingester-zone-c + rollout-group: ingester +{{- end -}} diff --git a/charts/loki/templates/ingester/service-ingester.yaml b/charts/loki/templates/ingester/service-ingester.yaml new file mode 100644 index 0000000000..94d6f83533 --- /dev/null +++ b/charts/loki/templates/ingester/service-ingester.yaml @@ -0,0 +1,36 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed (or (not .Values.ingester.zoneAwareReplication.enabled) .Values.ingester.zoneAwareReplication.migration.enabled) }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.ingesterFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.ingesterLabels" . | nindent 4 }} + {{- with .Values.ingester.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.ingester.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.ingester.appProtocol.grpc }} + appProtocol: {{ .Values.ingester.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.ingesterSelectorLabels" . | nindent 4 }} +{{- end -}} diff --git a/charts/loki/templates/ingester/statefulset-ingester-zone-a.yaml b/charts/loki/templates/ingester/statefulset-ingester-zone-a.yaml new file mode 100644 index 0000000000..13c7018e53 --- /dev/null +++ b/charts/loki/templates/ingester/statefulset-ingester-zone-a.yaml @@ -0,0 +1,232 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.ingester.zoneAwareReplication.enabled }} +{{- $replicas := (include "loki.ingester.replicaCount" .) -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "loki.ingesterFullname" . }}-zone-a + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.ingesterLabels" . | nindent 4 }} + app.kubernetes.io/part-of: memberlist + rollout-group: ingester + name: ingester-zone-a + annotations: + rollout-max-unavailable: "{{ include "loki.ingester.maxUnavailable" (dict "ctx" . "replicas" $replicas)}}" + {{- with .Values.loki.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.ingester.zoneAwareReplication.zoneA.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if not .Values.ingester.autoscaling.enabled }} + replicas: {{ $replicas }} +{{- end }} + podManagementPolicy: Parallel + serviceName: {{ include "loki.ingesterFullname" . }}-zone-a + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + {{- if and (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) (.Values.ingester.persistence.enableStatefulSetAutoDeletePVC) }} + {{/* + Data on the read nodes is easy to replace, so we want to always delete PVCs to make + operation easier, and will rely on re-fetching data when needed. + */}} + persistentVolumeClaimRetentionPolicy: + whenDeleted: {{ .Values.ingester.persistence.whenDeleted }} + whenScaled: {{ .Values.ingester.persistence.whenScaled }} + {{- end }} + selector: + matchLabels: + {{- include "loki.ingesterSelectorLabels" . | nindent 6 }} + name: ingester-zone-a + rollout-group: ingester + updateStrategy: + type: OnDelete + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.zoneAwareReplication.zoneA.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.ingesterSelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + name: ingester-zone-a + rollout-group: ingester + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} + {{- with .Values.ingester.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.ingesterPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.ingester.terminationGracePeriodSeconds }} + {{- with .Values.ingester.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: ingester + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + {{- if or .Values.loki.command .Values.ingester.command }} + command: + - {{ coalesce .Values.ingester.command .Values.loki.command | quote }} + {{- end }} + args: + - -config.file=/etc/loki/config/config.yaml + - -ingester.availability-zone=zone-a + - -ingester.unregister-on-shutdown=false + - -ingester.tokens-file-path=/var/loki/ring-tokens + - -target=ingester + {{- with .Values.ingester.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.ingester.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ingester.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + {{- include "loki.ingester.readinessProbe" . | nindent 10 }} + {{- include "loki.ingester.livenessProbe" . | nindent 10 }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + - name: data + mountPath: /var/loki + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.ingester.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ingester.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ingester.lifecycle }} + lifecycle: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.ingester.extraContainers }} + {{- toYaml .Values.ingester.extraContainers | nindent 8}} + {{- end }} + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: rollout-group + operator: In + values: + - ingester + - key: name + operator: NotIn + values: + - ingester-zone-a + topologyKey: kubernetes.io/hostname + {{- with .Values.ingester.zoneAwareReplication.zoneA.extraAffinity }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.zoneAwareReplication.zoneA.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- with .Values.ingester.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if not .Values.ingester.persistence.enabled }} + - name: data + emptyDir: {} + {{- else if .Values.ingester.persistence.inMemory }} + - name: data + {{- if .Values.ingester.persistence.inMemory }} + emptyDir: + medium: Memory + {{- end }} + {{- if .Values.ingester.persistence.size }} + sizeLimit: {{ .Values.ingester.persistence.size }} + {{- end }} + {{- else }} + volumeClaimTemplates: + {{- range .Values.ingester.persistence.claims }} + - metadata: + name: {{ .name }} + {{- with .annotations }} + annotations: + {{- . | toYaml | nindent 10 }} + {{- end }} + spec: + accessModes: + - ReadWriteOnce + {{- with .storageClass }} + storageClassName: {{ if (eq "-" .) }}""{{ else }}{{ . }}{{ end }} + {{- end }} + resources: + requests: + storage: {{ .size | quote }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/ingester/statefulset-ingester-zone-b.yaml b/charts/loki/templates/ingester/statefulset-ingester-zone-b.yaml new file mode 100644 index 0000000000..3af81ae647 --- /dev/null +++ b/charts/loki/templates/ingester/statefulset-ingester-zone-b.yaml @@ -0,0 +1,232 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.ingester.zoneAwareReplication.enabled }} +{{- $replicas := (include "loki.ingester.replicaCount" .) -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "loki.ingesterFullname" . }}-zone-b + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.ingesterLabels" . | nindent 4 }} + app.kubernetes.io/part-of: memberlist + rollout-group: ingester + name: ingester-zone-b + annotations: + rollout-max-unavailable: "{{ include "loki.ingester.maxUnavailable" (dict "ctx" . "replicas" $replicas)}}" + {{- with .Values.loki.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.ingester.zoneAwareReplication.zoneB.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if not .Values.ingester.autoscaling.enabled }} + replicas: {{ $replicas }} +{{- end }} + podManagementPolicy: Parallel + serviceName: {{ include "loki.ingesterFullname" . }}-zone-b + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + {{- if and (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) (.Values.ingester.persistence.enableStatefulSetAutoDeletePVC) }} + {{/* + Data on the read nodes is easy to replace, so we want to always delete PVCs to make + operation easier, and will rely on re-fetching data when needed. + */}} + persistentVolumeClaimRetentionPolicy: + whenDeleted: {{ .Values.ingester.persistence.whenDeleted }} + whenScaled: {{ .Values.ingester.persistence.whenScaled }} + {{- end }} + selector: + matchLabels: + {{- include "loki.ingesterSelectorLabels" . | nindent 6 }} + name: ingester-zone-b + rollout-group: ingester + updateStrategy: + type: OnDelete + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.zoneAwareReplication.zoneB.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.ingesterSelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + name: ingester-zone-b + rollout-group: ingester + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} + {{- with .Values.ingester.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.ingesterPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.ingester.terminationGracePeriodSeconds }} + {{- with .Values.ingester.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: ingester + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + {{- if or .Values.loki.command .Values.ingester.command }} + command: + - {{ coalesce .Values.ingester.command .Values.loki.command | quote }} + {{- end }} + args: + - -config.file=/etc/loki/config/config.yaml + - -ingester.availability-zone=zone-b + - -ingester.unregister-on-shutdown=false + - -ingester.tokens-file-path=/var/loki/ring-tokens + - -target=ingester + {{- with .Values.ingester.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.ingester.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ingester.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + {{- include "loki.ingester.readinessProbe" . | nindent 10 }} + {{- include "loki.ingester.livenessProbe" . | nindent 10 }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + - name: data + mountPath: /var/loki + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.ingester.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ingester.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ingester.lifecycle }} + lifecycle: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.ingester.extraContainers }} + {{- toYaml .Values.ingester.extraContainers | nindent 8}} + {{- end }} + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: rollout-group + operator: In + values: + - ingester + - key: name + operator: NotIn + values: + - ingester-zone-b + topologyKey: kubernetes.io/hostname + {{- with .Values.ingester.zoneAwareReplication.zoneB.extraAffinity }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.zoneAwareReplication.zoneB.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- with .Values.ingester.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if not .Values.ingester.persistence.enabled }} + - name: data + emptyDir: {} + {{- else if .Values.ingester.persistence.inMemory }} + - name: data + {{- if .Values.ingester.persistence.inMemory }} + emptyDir: + medium: Memory + {{- end }} + {{- if .Values.ingester.persistence.size }} + sizeLimit: {{ .Values.ingester.persistence.size }} + {{- end }} + {{- else }} + volumeClaimTemplates: + {{- range .Values.ingester.persistence.claims }} + - metadata: + name: {{ .name }} + {{- with .annotations }} + annotations: + {{- . | toYaml | nindent 10 }} + {{- end }} + spec: + accessModes: + - ReadWriteOnce + {{- with .storageClass }} + storageClassName: {{ if (eq "-" .) }}""{{ else }}{{ . }}{{ end }} + {{- end }} + resources: + requests: + storage: {{ .size | quote }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/ingester/statefulset-ingester-zone-c.yaml b/charts/loki/templates/ingester/statefulset-ingester-zone-c.yaml new file mode 100644 index 0000000000..30393fa4d2 --- /dev/null +++ b/charts/loki/templates/ingester/statefulset-ingester-zone-c.yaml @@ -0,0 +1,232 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.ingester.zoneAwareReplication.enabled }} +{{- $replicas := (include "loki.ingester.replicaCount" .) -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "loki.ingesterFullname" . }}-zone-c + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.ingesterLabels" . | nindent 4 }} + app.kubernetes.io/part-of: memberlist + rollout-group: ingester + name: ingester-zone-c + annotations: + rollout-max-unavailable: "{{ include "loki.ingester.maxUnavailable" (dict "ctx" . "replicas" $replicas)}}" + {{- with .Values.loki.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- with .Values.ingester.zoneAwareReplication.zoneC.annotations }} + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if not .Values.ingester.autoscaling.enabled }} + replicas: {{ $replicas }} +{{- end }} + podManagementPolicy: Parallel + serviceName: {{ include "loki.ingesterFullname" . }}-zone-c + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + {{- if and (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) (.Values.ingester.persistence.enableStatefulSetAutoDeletePVC) }} + {{/* + Data on the read nodes is easy to replace, so we want to always delete PVCs to make + operation easier, and will rely on re-fetching data when needed. + */}} + persistentVolumeClaimRetentionPolicy: + whenDeleted: {{ .Values.ingester.persistence.whenDeleted }} + whenScaled: {{ .Values.ingester.persistence.whenScaled }} + {{- end }} + selector: + matchLabels: + {{- include "loki.ingesterSelectorLabels" . | nindent 6 }} + name: ingester-zone-c + rollout-group: ingester + updateStrategy: + type: OnDelete + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.zoneAwareReplication.zoneC.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.ingesterSelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + name: ingester-zone-c + rollout-group: ingester + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} + {{- with .Values.ingester.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.ingesterPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.ingester.terminationGracePeriodSeconds }} + {{- with .Values.ingester.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: ingester + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + {{- if or .Values.loki.command .Values.ingester.command }} + command: + - {{ coalesce .Values.ingester.command .Values.loki.command | quote }} + {{- end }} + args: + - -config.file=/etc/loki/config/config.yaml + - -ingester.availability-zone=zone-c + - -ingester.unregister-on-shutdown=false + - -ingester.tokens-file-path=/var/loki/ring-tokens + - -target=ingester + {{- with .Values.ingester.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.ingester.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ingester.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + {{- include "loki.ingester.readinessProbe" . | nindent 10 }} + {{- include "loki.ingester.livenessProbe" . | nindent 10 }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + - name: data + mountPath: /var/loki + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.ingester.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ingester.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ingester.lifecycle }} + lifecycle: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.ingester.extraContainers }} + {{- toYaml .Values.ingester.extraContainers | nindent 8}} + {{- end }} + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchExpressions: + - key: rollout-group + operator: In + values: + - ingester + - key: name + operator: NotIn + values: + - ingester-zone-c + topologyKey: kubernetes.io/hostname + {{- with .Values.ingester.zoneAwareReplication.zoneC.extraAffinity }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.zoneAwareReplication.zoneC.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- with .Values.ingester.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if not .Values.ingester.persistence.enabled }} + - name: data + emptyDir: {} + {{- else if .Values.ingester.persistence.inMemory }} + - name: data + {{- if .Values.ingester.persistence.inMemory }} + emptyDir: + medium: Memory + {{- end }} + {{- if .Values.ingester.persistence.size }} + sizeLimit: {{ .Values.ingester.persistence.size }} + {{- end }} + {{- else }} + volumeClaimTemplates: + {{- range .Values.ingester.persistence.claims }} + - metadata: + name: {{ .name }} + {{- with .annotations }} + annotations: + {{- . | toYaml | nindent 10 }} + {{- end }} + spec: + accessModes: + - ReadWriteOnce + {{- with .storageClass }} + storageClassName: {{ if (eq "-" .) }}""{{ else }}{{ . }}{{ end }} + {{- end }} + resources: + requests: + storage: {{ .size | quote }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/ingester/statefulset-ingester.yaml b/charts/loki/templates/ingester/statefulset-ingester.yaml new file mode 100644 index 0000000000..9f3368a4b8 --- /dev/null +++ b/charts/loki/templates/ingester/statefulset-ingester.yaml @@ -0,0 +1,204 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed (or (not .Values.ingester.zoneAwareReplication.enabled) .Values.ingester.zoneAwareReplication.migration.enabled) }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "loki.ingesterFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.ingesterLabels" . | nindent 4 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if not .Values.ingester.autoscaling.enabled }} + replicas: {{ .Values.ingester.replicas }} +{{- end }} + podManagementPolicy: Parallel + updateStrategy: + rollingUpdate: + partition: 0 + serviceName: {{ include "loki.ingesterFullname" . }}-headless + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + {{- if and (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) (.Values.ingester.persistence.enableStatefulSetAutoDeletePVC) }} + {{/* + Data on the read nodes is easy to replace, so we want to always delete PVCs to make + operation easier, and will rely on re-fetching data when needed. + */}} + persistentVolumeClaimRetentionPolicy: + whenDeleted: {{ .Values.ingester.persistence.whenDeleted }} + whenScaled: {{ .Values.ingester.persistence.whenScaled }} + {{- end }} + selector: + matchLabels: + {{- include "loki.ingesterSelectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.ingesterSelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} + {{- with .Values.ingester.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.ingesterPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.ingester.terminationGracePeriodSeconds }} + {{- with .Values.ingester.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: ingester + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + {{- if or .Values.loki.command .Values.ingester.command }} + command: + - {{ coalesce .Values.ingester.command .Values.loki.command | quote }} + {{- end }} + args: + - -config.file=/etc/loki/config/config.yaml + - -ingester.availability-zone=zone-default + - -target=ingester + {{- with .Values.ingester.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.ingester.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ingester.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + {{- include "loki.ingester.readinessProbe" . | nindent 10 }} + {{- include "loki.ingester.livenessProbe" . | nindent 10 }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + - name: data + mountPath: /var/loki + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.ingester.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ingester.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ingester.lifecycle }} + lifecycle: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.ingester.extraContainers }} + {{- toYaml .Values.ingester.extraContainers | nindent 8}} + {{- end }} + {{- with .Values.ingester.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ingester.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- with .Values.ingester.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if not .Values.ingester.persistence.enabled }} + - name: data + emptyDir: { } + {{- else if .Values.ingester.persistence.inMemory }} + - name: data + {{- if .Values.ingester.persistence.inMemory }} + emptyDir: + medium: Memory + {{- end }} + {{- if .Values.ingester.persistence.size }} + sizeLimit: {{ .Values.ingester.persistence.size }} + {{- end }} + {{- else }} + volumeClaimTemplates: + {{- range .Values.ingester.persistence.claims }} + - metadata: + name: {{ .name }} + {{- with .annotations }} + annotations: + {{- . | toYaml | nindent 10 }} + {{- end }} + spec: + accessModes: + - ReadWriteOnce + {{- with .storageClass }} + storageClassName: {{ if (eq "-" .) }}""{{ else }}{{ . }}{{ end }} + {{- end }} + resources: + requests: + storage: {{ .size | quote }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/loki-canary/_helpers.tpl b/charts/loki/templates/loki-canary/_helpers.tpl index 2ea8dd7545..01e588c8d1 100644 --- a/charts/loki/templates/loki-canary/_helpers.tpl +++ b/charts/loki/templates/loki-canary/_helpers.tpl @@ -25,7 +25,7 @@ app.kubernetes.io/component: canary Docker image name for loki-canary */}} {{- define "loki-canary.image" -}} -{{- $dict := dict "service" .Values.monitoring.lokiCanary.image "global" .Values.global.image "defaultVersion" .Chart.AppVersion -}} +{{- $dict := dict "service" .Values.lokiCanary.image "global" .Values.global.image "defaultVersion" .Chart.AppVersion -}} {{- include "loki.baseImage" $dict -}} {{- end -}} @@ -33,7 +33,7 @@ Docker image name for loki-canary canary priority class name */}} {{- define "loki-canary.priorityClassName" -}} -{{- $pcn := coalesce .Values.global.priorityClassName .Values.monitoring.lokiCanary.priorityClassName .Values.read.priorityClassName -}} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.lokiCanary.priorityClassName .Values.read.priorityClassName -}} {{- if $pcn }} priorityClassName: {{ $pcn }} {{- end }} diff --git a/charts/loki/templates/loki-canary/daemonset.yaml b/charts/loki/templates/loki-canary/daemonset.yaml index 250d1a8ade..dc5c629689 100644 --- a/charts/loki/templates/loki-canary/daemonset.yaml +++ b/charts/loki/templates/loki-canary/daemonset.yaml @@ -1,4 +1,4 @@ -{{- with .Values.monitoring.lokiCanary -}} +{{- with .Values.lokiCanary -}} {{- if .enabled -}} --- apiVersion: apps/v1 @@ -51,12 +51,20 @@ spec: {{- else if $.Values.loki.auth_enabled }} - -user={{ $.Values.monitoring.selfMonitoring.tenant.name }} - -tenant-id={{ $.Values.monitoring.selfMonitoring.tenant.name }} + - -pass={{ $.Values.monitoring.selfMonitoring.tenant.password }} + {{- end }} + {{- if .push }} + - -push=true {{- end }} {{- with .extraArgs }} {{- toYaml . | nindent 12 }} {{- end }} securityContext: {{- toYaml $.Values.loki.containerSecurityContext | nindent 12 }} + volumeMounts: + {{- with $.Values.lokiCanary.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} ports: - name: http-metrics containerPort: 3500 @@ -107,5 +115,9 @@ spec: tolerations: {{- toYaml . | nindent 8 }} {{- end }} + volumes: + {{- with $.Values.lokiCanary.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} {{- end }} {{- end }} diff --git a/charts/loki/templates/loki-canary/service.yaml b/charts/loki/templates/loki-canary/service.yaml index d0fb34e38b..38022a3e31 100644 --- a/charts/loki/templates/loki-canary/service.yaml +++ b/charts/loki/templates/loki-canary/service.yaml @@ -1,4 +1,4 @@ -{{- with .Values.monitoring.lokiCanary -}} +{{- with .Values.lokiCanary -}} {{- if .enabled -}} --- apiVersion: v1 diff --git a/charts/loki/templates/loki-canary/serviceaccount.yaml b/charts/loki/templates/loki-canary/serviceaccount.yaml index dbcd2b345f..2c1f79a682 100644 --- a/charts/loki/templates/loki-canary/serviceaccount.yaml +++ b/charts/loki/templates/loki-canary/serviceaccount.yaml @@ -1,4 +1,4 @@ -{{- with .Values.monitoring.lokiCanary -}} +{{- with .Values.lokiCanary -}} {{- if .enabled -}} --- apiVersion: v1 diff --git a/charts/loki/templates/memcached/_memcached-statefulset.tpl b/charts/loki/templates/memcached/_memcached-statefulset.tpl new file mode 100644 index 0000000000..0664ba43c6 --- /dev/null +++ b/charts/loki/templates/memcached/_memcached-statefulset.tpl @@ -0,0 +1,178 @@ +{{/* +memcached StatefulSet +Params: + ctx = . context + valuesSection = name of the section in values.yaml + component = name of the component +valuesSection and component are specified separately because helm prefers camelcase for naming convetion and k8s components are named with snake case. +*/}} +{{- define "loki.memcached.statefulSet" -}} +{{ with (index $.ctx.Values $.valuesSection) }} +{{- if .enabled -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "loki.resourceName" (dict "ctx" $.ctx "component" $.component) }} + labels: + {{- include "loki.labels" $.ctx | nindent 4 }} + app.kubernetes.io/component: "memcached-{{ $.component }}" + name: "memcached-{{ $.component }}" + annotations: + {{- toYaml .annotations | nindent 4 }} + namespace: {{ $.ctx.Release.Namespace | quote }} +spec: + podManagementPolicy: {{ .podManagementPolicy }} + replicas: {{ .replicas }} + selector: + matchLabels: + {{- include "loki.selectorLabels" $.ctx | nindent 6 }} + app.kubernetes.io/component: "memcached-{{ $.component }}" + name: "memcached-{{ $.component }}" + updateStrategy: + {{- toYaml .statefulStrategy | nindent 4 }} + serviceName: {{ template "loki.fullname" $.ctx }}-{{ $.component }} + + template: + metadata: + labels: + {{- include "loki.selectorLabels" $.ctx | nindent 8 }} + app.kubernetes.io/component: "memcached-{{ $.component }}" + name: "memcached-{{ $.component }}" + {{- with $.ctx.Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + annotations: + {{- with $.ctx.Values.global.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + + spec: + serviceAccountName: {{ template "loki.serviceAccountName" $.ctx }} + {{- if .priorityClassName }} + priorityClassName: {{ .priorityClassName }} + {{- end }} + securityContext: + {{- toYaml $.ctx.Values.memcached.podSecurityContext | nindent 8 }} + initContainers: + {{- toYaml .initContainers | nindent 8 }} + nodeSelector: + {{- toYaml .nodeSelector | nindent 8 }} + affinity: + {{- toYaml .affinity | nindent 8 }} + topologySpreadConstraints: + {{- toYaml .topologySpreadConstraints | nindent 8 }} + tolerations: + {{- toYaml .tolerations | nindent 8 }} + terminationGracePeriodSeconds: {{ .terminationGracePeriodSeconds }} + {{- with $.ctx.Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .extraVolumes }} + volumes: + {{- toYaml .extraVolumes | nindent 8 }} + {{- end }} + containers: + {{- if .extraContainers }} + {{ toYaml .extraContainers | nindent 8 }} + {{- end }} + - name: memcached + {{- with $.ctx.Values.memcached.image }} + image: {{ .repository }}:{{ .tag }} + imagePullPolicy: {{ .pullPolicy }} + {{- end }} + resources: + {{- if .resources }} + {{- toYaml .resources | nindent 12 }} + {{- else }} + {{- /* Calculate requested memory as round(allocatedMemory * 1.2). But with integer built-in operators. */}} + {{- $requestMemory := div (add (mul .allocatedMemory 12) 5) 10 }} + limits: + memory: {{ $requestMemory }}Mi + requests: + cpu: 500m + memory: {{ $requestMemory }}Mi + {{- end }} + ports: + - containerPort: {{ .port }} + name: client + {{- /* Calculate storage size as round(.persistence.storageSize * 0.9). But with integer built-in operators. */}} + {{- $persistenceSize := (div (mul (trimSuffix "Gi" .persistence.storageSize | trimSuffix "G") 9) 10 ) }} + args: + - -m {{ .allocatedMemory }} + - --extended=modern,track_sizes{{ if .persistence.enabled }},ext_path={{ .persistence.mountPath }}/file:{{ $persistenceSize }}G,ext_wbuf_size=16{{ end }}{{ with .extraExtendedOptions }},{{ . }}{{ end }} + - -I {{ .maxItemMemory }}m + - -c {{ .connectionLimit }} + - -v + - -u {{ .port }} + {{- range $key, $value := .extraArgs }} + - "-{{ $key }}{{ if $value }} {{ $value }}{{ end }}" + {{- end }} + env: + {{- with $.ctx.Values.global.extraEnv }} + {{ toYaml . | nindent 12 }} + {{- end }} + envFrom: + {{- with $.ctx.Values.global.extraEnvFrom }} + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml $.ctx.Values.memcached.containerSecurityContext | nindent 12 }} + {{- if or .persistence.enabled .extraVolumeMounts }} + volumeMounts: + {{- if .persistence.enabled }} + - name: data + mountPath: {{ .persistence.mountPath }} + {{- end }} + {{- if .extraVolumeMounts }} + {{- toYaml .extraVolumeMounts | nindent 12 }} + {{- end }} + {{- end }} + + {{- if $.ctx.Values.memcachedExporter.enabled }} + - name: exporter + {{- with $.ctx.Values.memcachedExporter.image }} + image: {{ .repository}}:{{ .tag }} + imagePullPolicy: {{ .pullPolicy }} + {{- end }} + ports: + - containerPort: 9150 + name: http-metrics + args: + - "--memcached.address=localhost:{{ .port }}" + - "--web.listen-address=0.0.0.0:9150" + {{- range $key, $value := $.ctx.Values.memcachedExporter.extraArgs }} + - "--{{ $key }}{{ if $value }}={{ $value }}{{ end }}" + {{- end }} + resources: + {{- toYaml $.ctx.Values.memcachedExporter.resources | nindent 12 }} + securityContext: + {{- toYaml $.ctx.Values.memcachedExporter.containerSecurityContext | nindent 12 }} + {{- if .extraVolumeMounts }} + volumeMounts: + {{- toYaml .extraVolumeMounts | nindent 12 }} + {{- end }} + {{- end }} + {{- if .persistence.enabled }} + volumeClaimTemplates: + - metadata: + name: data + spec: + accessModes: [ "ReadWriteOnce" ] + {{- with .persistence.storageClass }} + storageClassName: {{ if (eq "-" .) }}""{{ else }}{{ . }}{{ end }} + {{- end }} + resources: + requests: + storage: {{ .persistence.storageSize | quote }} + {{- end }} +{{- end -}} +{{- end -}} +{{- end -}} + diff --git a/charts/loki/templates/memcached/_memcached-svc.tpl b/charts/loki/templates/memcached/_memcached-svc.tpl new file mode 100644 index 0000000000..8574151978 --- /dev/null +++ b/charts/loki/templates/memcached/_memcached-svc.tpl @@ -0,0 +1,42 @@ +{{/* +memcached Service +Params: + ctx = . context + valuesSection = name of the section in values.yaml + component = name of the component +valuesSection and component are specified separately because helm prefers camelcase for naming convetion and k8s components are named with snake case. +*/}} +{{- define "loki.memcached.service" -}} +{{ with (index $.ctx.Values $.valuesSection) }} +{{- if .enabled -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.resourceName" (dict "ctx" $.ctx "component" $.component) }} + labels: + {{- include "loki.labels" $.ctx | nindent 4 }} + app.kubernetes.io/component: "memcached-{{ $.component }}" + {{- with .service.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- toYaml .service.annotations | nindent 4 }} + namespace: {{ $.ctx.Release.Namespace | quote }} +spec: + type: ClusterIP + clusterIP: None + ports: + - name: memcached-client + port: {{ .port }} + targetPort: {{ .port }} + {{ if $.ctx.Values.memcachedExporter.enabled -}} + - name: http-metrics + port: 9150 + targetPort: 9150 + {{ end }} + selector: + {{- include "loki.selectorLabels" $.ctx | nindent 4 }} + app.kubernetes.io/component: "memcached-{{ $.component }}" +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/loki/templates/monitoring/logs-instance.yaml b/charts/loki/templates/monitoring/logs-instance.yaml index 58d5fb045c..5ae19179a1 100644 --- a/charts/loki/templates/monitoring/logs-instance.yaml +++ b/charts/loki/templates/monitoring/logs-instance.yaml @@ -27,4 +27,4 @@ spec: matchLabels: {{- include "loki.selectorLabels" $ | nindent 6 }} {{- end -}} -{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/loki/templates/pattern-ingester/_helpers-pattern-ingester.tpl b/charts/loki/templates/pattern-ingester/_helpers-pattern-ingester.tpl new file mode 100644 index 0000000000..5477214a0b --- /dev/null +++ b/charts/loki/templates/pattern-ingester/_helpers-pattern-ingester.tpl @@ -0,0 +1,58 @@ +{{/* +pattern ingester fullname +*/}} +{{- define "loki.patternIngesterFullname" -}} +{{ include "loki.fullname" . }}-pattern-ingester +{{- end }} + +{{/* +pattern ingester common labels +*/}} +{{- define "loki.patternIngesterLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: pattern-ingester +{{- end }} + +{{/* +pattern ingester selector labels +*/}} +{{- define "loki.patternIngesterSelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: pattern-ingester +{{- end }} + +{{/* +pattern ingester readinessProbe +*/}} +{{- define "loki.patternIngester.readinessProbe" -}} +{{- with .Values.patternIngester.readinessProbe }} +readinessProbe: + {{- toYaml . | nindent 2 }} +{{- else }} +{{- with .Values.loki.readinessProbe }} +readinessProbe: + {{- toYaml . | nindent 2 }} +{{- end }} +{{- end }} +{{- end -}} + +{{/* +pattern ingester priority class name +*/}} +{{- define "loki.patternIngesterPriorityClassName" }} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.patternIngester.priorityClassName -}} +{{- if $pcn }} +priorityClassName: {{ $pcn }} +{{- end }} +{{- end }} + +{{/* +Create the name of the pattern ingester service account +*/}} +{{- define "loki.patternIngesterServiceAccountName" -}} +{{- if .Values.patternIngester.serviceAccount.create -}} + {{ default (print (include "loki.serviceAccountName" .) "-pattern-ingester") .Values.patternIngester.serviceAccount.name }} +{{- else -}} + {{ default (include "loki.serviceAccountName" .) .Values.patternIngester.serviceAccount.name }} +{{- end -}} +{{- end -}} diff --git a/charts/loki/templates/pattern-ingester/statefulset-pattern-ingester.yaml b/charts/loki/templates/pattern-ingester/statefulset-pattern-ingester.yaml new file mode 100644 index 0000000000..4666dd6197 --- /dev/null +++ b/charts/loki/templates/pattern-ingester/statefulset-pattern-ingester.yaml @@ -0,0 +1,179 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed }} +{{- if (gt (int .Values.patternIngester.replicas) 0) -}} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "loki.patternIngesterFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.patternIngesterLabels" . | nindent 4 }} + {{- with .Values.loki.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.patternIngester.replicas }} + podManagementPolicy: Parallel + updateStrategy: + rollingUpdate: + partition: 0 + serviceName: {{ include "loki.patternIngesterFullname" . }}-headless + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + {{- if and (semverCompare ">= 1.23-0" .Capabilities.KubeVersion.Version) (.Values.patternIngester.persistence.enableStatefulSetAutoDeletePVC) }} + persistentVolumeClaimRetentionPolicy: + whenDeleted: {{ .Values.patternIngester.persistence.whenDeleted }} + whenScaled: {{ .Values.patternIngester.persistence.whenScaled }} + {{- end }} + selector: + matchLabels: + {{- include "loki.patternIngesterSelectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.patternIngester.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.patternIngesterSelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.patternIngester.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.patternIngester.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.patternIngesterPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.patternIngester.terminationGracePeriodSeconds }} + {{- with .Values.patternIngester.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: pattern-ingester + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + {{- if or .Values.loki.command .Values.patternIngester.command }} + command: + - {{ coalesce .Values.patternIngester.command .Values.loki.command | quote }} + {{- end }} + args: + - -config.file=/etc/loki/config/config.yaml + - -target=pattern-ingester + {{- with .Values.patternIngester.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.patternIngester.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.patternIngester.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + {{- include "loki.patternIngester.readinessProbe" . | nindent 10 }} + volumeMounts: + - name: temp + mountPath: /tmp + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + - name: data + mountPath: /var/loki + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.patternIngester.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.patternIngester.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.patternIngester.extraContainers }} + {{- toYaml .Values.patternIngester.extraContainers | nindent 8}} + {{- end }} + {{- with .Values.patternIngester.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.patternIngester.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: temp + emptyDir: {} + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- if not .Values.patternIngester.persistence.enabled }} + - name: data + emptyDir: {} + {{- end }} + {{- with .Values.patternIngester.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if .Values.patternIngester.persistence.enabled }} + volumeClaimTemplates: + {{- range .Values.patternIngester.persistence.claims }} + - metadata: + name: {{ .name }} + {{- with .annotations }} + annotations: + {{- . | toYaml | nindent 10 }} + {{- end }} + spec: + accessModes: + - ReadWriteOnce + {{- with .storageClass }} + storageClassName: {{ if (eq "-" .) }}""{{ else }}{{ . }}{{ end }} + {{- end }} + resources: + requests: + storage: {{ .size | quote }} + {{- end }} + {{- end }} +{{- end -}} +{{- end -}} \ No newline at end of file diff --git a/charts/loki/templates/provisioner/job-provisioner.yaml b/charts/loki/templates/provisioner/job-provisioner.yaml index deb6e73c17..5a6bc06371 100644 --- a/charts/loki/templates/provisioner/job-provisioner.yaml +++ b/charts/loki/templates/provisioner/job-provisioner.yaml @@ -37,11 +37,9 @@ spec: {{- end }} securityContext: {{- toYaml .Values.enterprise.provisioner.securityContext | nindent 8 }} - {{- if .Values.imagePullSecrets }} + {{- with .Values.imagePullSecrets }} imagePullSecrets: - {{- range .Values.imagePullSecrets }} - - name: {{ . }} - {{- end }} + {{- toYaml . | nindent 8 }} {{- end }} initContainers: - name: provisioner @@ -125,6 +123,18 @@ spec: {{- end }} - name: bootstrap mountPath: /bootstrap + {{- with .Values.enterprise.provisioner.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.enterprise.provisioner.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.enterprise.provisioner.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} restartPolicy: OnFailure serviceAccount: {{ include "enterprise-logs.provisionerFullname" . }} serviceAccountName: {{ include "enterprise-logs.provisionerFullname" . }} diff --git a/charts/loki/templates/provisioner/role-provisioner.yaml b/charts/loki/templates/provisioner/role-provisioner.yaml index e1a636ef7c..1335b0f315 100644 --- a/charts/loki/templates/provisioner/role-provisioner.yaml +++ b/charts/loki/templates/provisioner/role-provisioner.yaml @@ -1,4 +1,4 @@ -{{ if and .Values.enterprise.provisioner.enabled .Values.enterprise.enabled }} +{{ if and (and .Values.enterprise.provisioner.enabled .Values.enterprise.enabled) (not .Values.rbac.namespaced)}} apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: diff --git a/charts/loki/templates/provisioner/rolebinding-provisioner.yaml b/charts/loki/templates/provisioner/rolebinding-provisioner.yaml index e681e97a74..d87874dc93 100644 --- a/charts/loki/templates/provisioner/rolebinding-provisioner.yaml +++ b/charts/loki/templates/provisioner/rolebinding-provisioner.yaml @@ -1,4 +1,4 @@ -{{ if and .Values.enterprise.provisioner.enabled .Values.enterprise.enabled }} +{{ if and (and .Values.enterprise.provisioner.enabled .Values.enterprise.enabled) (not .Values.rbac.namespaced)}} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/loki/templates/querier/_helpers-querier.tpl b/charts/loki/templates/querier/_helpers-querier.tpl new file mode 100644 index 0000000000..aa557c5b8d --- /dev/null +++ b/charts/loki/templates/querier/_helpers-querier.tpl @@ -0,0 +1,32 @@ +{{/* +querier fullname +*/}} +{{- define "loki.querierFullname" -}} +{{ include "loki.fullname" . }}-querier +{{- end }} + +{{/* +querier common labels +*/}} +{{- define "loki.querierLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: querier +{{- end }} + +{{/* +querier selector labels +*/}} +{{- define "loki.querierSelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: querier +{{- end }} + +{{/* +querier priority class name +*/}} +{{- define "loki.querierPriorityClassName" -}} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.querier.priorityClassName -}} +{{- if $pcn }} +priorityClassName: {{ $pcn }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/querier/deployment-querier.yaml b/charts/loki/templates/querier/deployment-querier.yaml new file mode 100644 index 0000000000..80699f21fd --- /dev/null +++ b/charts/loki/templates/querier/deployment-querier.yaml @@ -0,0 +1,166 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "loki.querierFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.querierLabels" . | nindent 4 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if not .Values.querier.autoscaling.enabled }} + replicas: {{ .Values.querier.replicas }} +{{- end }} + strategy: + rollingUpdate: + maxSurge: {{ .Values.querier.maxSurge }} + maxUnavailable: 1 + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + selector: + matchLabels: + {{- include "loki.querierSelectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.querier.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.querierSelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.querier.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion }} + {{- with .Values.querier.topologySpreadConstraints }} + topologySpreadConstraints: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- end }} + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.querier.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.querierPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.querier.terminationGracePeriodSeconds }} + {{- with .Values.querier.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: querier + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + args: + - -config.file=/etc/loki/config/config.yaml + - -target=querier + {{- if .Values.ingester.zoneAwareReplication.enabled }} + {{- if and (.Values.ingester.zoneAwareReplication.migration.enabled) (not .Values.ingester.zoneAwareReplication.migration.readPath) }} + - -distributor.zone-awareness-enabled=false + {{- else }} + - -distributor.zone-awareness-enabled=true + {{- end }} + {{- end }} + {{- with .Values.querier.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.querier.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.querier.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + readinessProbe: + {{- toYaml .Values.loki.readinessProbe | nindent 12 }} + livenessProbe: + {{- toYaml .Values.loki.livenessProbe | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + - name: data + mountPath: /var/loki + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.querier.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.querier.resources | nindent 12 }} + {{- if .Values.querier.extraContainers }} + {{- toYaml .Values.querier.extraContainers | nindent 8}} + {{- end }} + {{- with .Values.querier.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.querier.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.querier.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.querier.dnsConfig }} + dnsConfig: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + - name: data + emptyDir: {} + {{- with .Values.querier.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/querier/hpa.yaml b/charts/loki/templates/querier/hpa.yaml new file mode 100644 index 0000000000..08d81cb590 --- /dev/null +++ b/charts/loki/templates/querier/hpa.yaml @@ -0,0 +1,55 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.querier.autoscaling.enabled }} +{{- $apiVersion := include "loki.hpa.apiVersion" . -}} +apiVersion: {{ $apiVersion }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "loki.querierFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.querierLabels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "loki.querierFullname" . }} + minReplicas: {{ .Values.querier.autoscaling.minReplicas }} + maxReplicas: {{ .Values.querier.autoscaling.maxReplicas }} + metrics: + {{- with .Values.querier.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + {{- if (eq $apiVersion "autoscaling/v2") }} + target: + type: Utilization + averageUtilization: {{ . }} + {{- else }} + targetAverageUtilization: {{ . }} + {{- end }} + {{- end }} + {{- with .Values.querier.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + {{- if (eq $apiVersion "autoscaling/v2") }} + target: + type: Utilization + averageUtilization: {{ . }} + {{- else }} + targetAverageUtilization: {{ . }} + {{- end }} + {{- end }} + {{- with .Values.querier.autoscaling.customMetrics }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.querier.autoscaling.behavior.enabled }} + behavior: + {{- with .Values.querier.autoscaling.behavior.scaleDown }} + scaleDown: {{ toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.querier.autoscaling.behavior.scaleUp }} + scaleUp: {{ toYaml . | nindent 6 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/querier/poddisruptionbudget-querier.yaml b/charts/loki/templates/querier/poddisruptionbudget-querier.yaml new file mode 100644 index 0000000000..9dff3cdf88 --- /dev/null +++ b/charts/loki/templates/querier/poddisruptionbudget-querier.yaml @@ -0,0 +1,21 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed (gt (int .Values.querier.replicas) 1) }} +{{- if kindIs "invalid" .Values.querier.maxUnavailable }} +{{- fail "`.Values.querier.maxUnavailable` must be set when `.Values.querier.replicas` is greater than 1." }} +{{- else }} +apiVersion: {{ include "loki.pdb.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "loki.querierFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.querierLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "loki.querierSelectorLabels" . | nindent 6 }} + {{- with .Values.querier.maxUnavailable }} + maxUnavailable: {{ . }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/querier/service-querier.yaml b/charts/loki/templates/querier/service-querier.yaml new file mode 100644 index 0000000000..15c9c6a06c --- /dev/null +++ b/charts/loki/templates/querier/service-querier.yaml @@ -0,0 +1,36 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.querierFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.querierLabels" . | nindent 4 }} + {{- with .Values.querier.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.querier.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.querier.appProtocol.grpc }} + appProtocol: {{ .Values.querier.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.querierSelectorLabels" . | nindent 4 }} +{{- end -}} diff --git a/charts/loki/templates/query-frontend/_helpers-query-frontend.tpl b/charts/loki/templates/query-frontend/_helpers-query-frontend.tpl new file mode 100644 index 0000000000..5aebde755e --- /dev/null +++ b/charts/loki/templates/query-frontend/_helpers-query-frontend.tpl @@ -0,0 +1,32 @@ +{{/* +query-frontend fullname +*/}} +{{- define "loki.queryFrontendFullname" -}} +{{ include "loki.fullname" . }}-query-frontend +{{- end }} + +{{/* +query-frontend common labels +*/}} +{{- define "loki.queryFrontendLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: query-frontend +{{- end }} + +{{/* +query-frontend selector labels +*/}} +{{- define "loki.queryFrontendSelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: query-frontend +{{- end }} + +{{/* +query-frontend priority class name +*/}} +{{- define "loki.queryFrontendPriorityClassName" -}} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.queryFrontend.priorityClassName -}} +{{- if $pcn }} +priorityClassName: {{ $pcn }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/query-frontend/deployment-query-frontend.yaml b/charts/loki/templates/query-frontend/deployment-query-frontend.yaml new file mode 100644 index 0000000000..6eda5c51df --- /dev/null +++ b/charts/loki/templates/query-frontend/deployment-query-frontend.yaml @@ -0,0 +1,142 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed -}} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "loki.queryFrontendFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.queryFrontendLabels" . | nindent 4 }} + {{- with .Values.loki.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if not .Values.queryFrontend.autoscaling.enabled }} + replicas: {{ .Values.queryFrontend.replicas }} +{{- end }} + strategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + selector: + matchLabels: + {{- include "loki.queryFrontendSelectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.queryFrontend.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.queryFrontendSelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.queryFrontend.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.queryFrontend.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.queryFrontendPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.queryFrontend.terminationGracePeriodSeconds }} + containers: + - name: query-frontend + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + {{- if or .Values.loki.command .Values.queryFrontend.command }} + command: + - {{ coalesce .Values.queryFrontend.command .Values.loki.command | quote }} + {{- end }} + args: + - -config.file=/etc/loki/config/config.yaml + - -target=query-frontend + {{- with .Values.queryFrontend.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.queryFrontend.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.queryFrontend.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + readinessProbe: + {{- toYaml .Values.loki.readinessProbe | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.queryFrontend.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.queryFrontend.resources | nindent 12 }} + {{- if .Values.queryFrontend.extraContainers }} + {{- toYaml .Values.queryFrontend.extraContainers | nindent 8}} + {{- end }} + {{- with .Values.queryFrontend.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.queryFrontend.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.queryFrontend.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- with .Values.queryFrontend.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end -}} diff --git a/charts/loki/templates/query-frontend/hpa.yaml b/charts/loki/templates/query-frontend/hpa.yaml new file mode 100644 index 0000000000..c326287bd8 --- /dev/null +++ b/charts/loki/templates/query-frontend/hpa.yaml @@ -0,0 +1,55 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.queryFrontend.autoscaling.enabled }} +{{- $apiVersion := include "loki.hpa.apiVersion" . -}} +apiVersion: {{ $apiVersion }} +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "loki.queryFrontendFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.queryFrontendLabels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "loki.queryFrontendFullname" . }} + minReplicas: {{ .Values.queryFrontend.autoscaling.minReplicas }} + maxReplicas: {{ .Values.queryFrontend.autoscaling.maxReplicas }} + metrics: + {{- with .Values.queryFrontend.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + {{- if (eq $apiVersion "autoscaling/v2") }} + target: + type: Utilization + averageUtilization: {{ . }} + {{- else }} + targetAverageUtilization: {{ . }} + {{- end }} + {{- end }} + {{- with .Values.queryFrontend.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + {{- if (eq $apiVersion "autoscaling/v2") }} + target: + type: Utilization + averageUtilization: {{ . }} + {{- else }} + targetAverageUtilization: {{ . }} + {{- end }} + {{- end }} + {{- with .Values.queryFrontend.autoscaling.customMetrics }} + {{- toYaml . | nindent 4 }} + {{- end }} + {{- if .Values.queryFrontend.autoscaling.behavior.enabled }} + behavior: + {{- with .Values.queryFrontend.autoscaling.behavior.scaleDown }} + scaleDown: {{ toYaml . | nindent 6 }} + {{- end }} + {{- with .Values.queryFrontend.autoscaling.behavior.scaleUp }} + scaleUp: {{ toYaml . | nindent 6 }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/query-frontend/poddisruptionbudget-query-frontend.yaml b/charts/loki/templates/query-frontend/poddisruptionbudget-query-frontend.yaml new file mode 100644 index 0000000000..f100405942 --- /dev/null +++ b/charts/loki/templates/query-frontend/poddisruptionbudget-query-frontend.yaml @@ -0,0 +1,21 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed (gt (int .Values.queryFrontend.replicas) 1) }} +{{- if kindIs "invalid" .Values.queryFrontend.maxUnavailable }} +{{- fail "`.Values.queryFrontend.maxUnavailable` must be set when `.Values.queryFrontend.replicas` is greater than 1." }} +{{- else }} +apiVersion: {{ include "loki.pdb.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "loki.queryFrontendFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.queryFrontendLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "loki.queryFrontendSelectorLabels" . | nindent 6 }} + {{- with .Values.queryFrontend.maxUnavailable }} + maxUnavailable: {{ . }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/query-frontend/service-query-frontend-headless.yaml b/charts/loki/templates/query-frontend/service-query-frontend-headless.yaml new file mode 100644 index 0000000000..8da9054155 --- /dev/null +++ b/charts/loki/templates/query-frontend/service-query-frontend-headless.yaml @@ -0,0 +1,46 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.queryFrontendFullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.queryFrontendLabels" . | nindent 4 }} + {{- with .Values.queryFrontend.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + prometheus.io/service-monitor: "false" + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.queryFrontend.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + clusterIP: None + type: ClusterIP + publishNotReadyAddresses: true + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.queryFrontend.appProtocol.grpc }} + appProtocol: {{ .Values.queryFrontend.appProtocol.grpc }} + {{- end }} + - name: grpclb + port: 9096 + targetPort: grpc + protocol: TCP + {{- if .Values.queryFrontend.appProtocol.grpc }} + appProtocol: {{ .Values.queryFrontend.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.queryFrontendSelectorLabels" . | nindent 4 }} +{{- end -}} diff --git a/charts/loki/templates/query-frontend/service-query-frontend.yaml b/charts/loki/templates/query-frontend/service-query-frontend.yaml new file mode 100644 index 0000000000..a2396950d9 --- /dev/null +++ b/charts/loki/templates/query-frontend/service-query-frontend.yaml @@ -0,0 +1,44 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed -}} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.queryFrontendFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.queryFrontendLabels" . | nindent 4 }} + {{- with .Values.queryFrontend.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.queryFrontend.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + publishNotReadyAddresses: true + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- if .Values.queryFrontend.appProtocol.grpc }} + appProtocol: {{ .Values.queryFrontend.appProtocol.grpc }} + {{- end }} + - name: grpclb + port: 9096 + targetPort: grpc + protocol: TCP + {{- if .Values.queryFrontend.appProtocol.grpc }} + appProtocol: {{ .Values.queryFrontend.appProtocol.grpc }} + {{- end }} + selector: + {{- include "loki.queryFrontendSelectorLabels" . | nindent 4 }} +{{- end -}} diff --git a/charts/loki/templates/query-scheduler/_helpers-query-scheduler.tpl b/charts/loki/templates/query-scheduler/_helpers-query-scheduler.tpl new file mode 100644 index 0000000000..1f64802428 --- /dev/null +++ b/charts/loki/templates/query-scheduler/_helpers-query-scheduler.tpl @@ -0,0 +1,40 @@ +{{/* +query-scheduler fullname +*/}} +{{- define "loki.querySchedulerFullname" -}} +{{ include "loki.fullname" . }}-query-scheduler +{{- end }} + +{{/* +query-scheduler common labels +*/}} +{{- define "loki.querySchedulerLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: query-scheduler +{{- end }} + +{{/* +query-scheduler selector labels +*/}} +{{- define "loki.querySchedulerSelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: query-scheduler +{{- end }} + +{{/* +query-scheduler image +*/}} +{{- define "loki.querySchedulerImage" -}} +{{- $dict := dict "loki" .Values.loki.image "service" .Values.queryScheduler.image "global" .Values.global.image "defaultVersion" .Chart.AppVersion -}} +{{- include "loki.lokiImage" $dict -}} +{{- end }} + +{{/* +query-scheduler priority class name +*/}} +{{- define "loki.querySchedulerPriorityClassName" -}} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.queryScheduler.priorityClassName -}} +{{- if $pcn }} +priorityClassName: {{ $pcn }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/query-scheduler/deployment-query-scheduler.yaml b/charts/loki/templates/query-scheduler/deployment-query-scheduler.yaml new file mode 100644 index 0000000000..11b2829ebe --- /dev/null +++ b/charts/loki/templates/query-scheduler/deployment-query-scheduler.yaml @@ -0,0 +1,140 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "loki.querySchedulerFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.querySchedulerLabels" . | nindent 4 }} + {{- with .Values.loki.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.queryScheduler.replicas }} + strategy: + rollingUpdate: + maxSurge: 0 + maxUnavailable: 1 + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + selector: + matchLabels: + {{- include "loki.querySchedulerSelectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.queryScheduler.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.querySchedulerSelectorLabels" . | nindent 8 }} + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.queryScheduler.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + app.kubernetes.io/part-of: memberlist + spec: + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.queryScheduler.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.querySchedulerPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.queryScheduler.terminationGracePeriodSeconds }} + containers: + - name: query-scheduler + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + args: + - -config.file=/etc/loki/config/config.yaml + - -target=query-scheduler + {{- with .Values.queryScheduler.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.queryScheduler.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.queryScheduler.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + readinessProbe: + {{- toYaml .Values.loki.readinessProbe | nindent 12 }} + livenessProbe: + {{- toYaml .Values.loki.livenessProbe | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- with .Values.queryScheduler.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.queryScheduler.resources }} + resources: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- if .Values.queryScheduler.extraContainers }} + {{- toYaml .Values.queryScheduler.extraContainers | nindent 8}} + {{- end }} + {{- with .Values.queryScheduler.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.queryScheduler.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.queryScheduler.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- with .Values.queryScheduler.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/query-scheduler/poddisruptionbudget-query-scheduler.yaml b/charts/loki/templates/query-scheduler/poddisruptionbudget-query-scheduler.yaml new file mode 100644 index 0000000000..ed8051fa92 --- /dev/null +++ b/charts/loki/templates/query-scheduler/poddisruptionbudget-query-scheduler.yaml @@ -0,0 +1,21 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed (gt (int .Values.queryScheduler.replicas) 1) }} +{{- if kindIs "invalid" .Values.queryScheduler.maxUnavailable }} +{{- fail "`.Values.queryScheduler.maxUnavailable` must be set when `.Values.queryScheduler.replicas` is greater than 1." }} +{{- else }} +apiVersion: {{ include "loki.pdb.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "loki.querySchedulerFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.querySchedulerLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "loki.querySchedulerSelectorLabels" . | nindent 6 }} + {{- with .Values.queryScheduler.maxUnavailable }} + maxUnavailable: {{ . }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/query-scheduler/service-query-scheduler.yaml b/charts/loki/templates/query-scheduler/service-query-scheduler.yaml new file mode 100644 index 0000000000..746c7bdfdf --- /dev/null +++ b/charts/loki/templates/query-scheduler/service-query-scheduler.yaml @@ -0,0 +1,38 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.querySchedulerFullname" . }} + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.querySchedulerLabels" . | nindent 4 }} + {{- with .Values.queryScheduler.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.queryScheduler.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + publishNotReadyAddresses: true + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpclb + port: 9095 + targetPort: grpc + protocol: TCP + {{- with .Values.queryScheduler.appProtocol.grpc }} + appProtocol: {{ . }} + {{- end }} + selector: + {{- include "loki.querySchedulerSelectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/loki/templates/read/deployment-read.yaml b/charts/loki/templates/read/deployment-read.yaml index ee9a15108a..245119cb44 100644 --- a/charts/loki/templates/read/deployment-read.yaml +++ b/charts/loki/templates/read/deployment-read.yaml @@ -120,7 +120,7 @@ spec: {{- end }} {{- with .Values.read.affinity }} affinity: - {{- tpl . $ | nindent 8 }} + {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.read.dnsConfig }} dnsConfig: @@ -144,12 +144,7 @@ spec: - name: data emptyDir: {} - name: config - {{- if .Values.loki.existingSecretForConfig }} - secret: - secretName: {{ .Values.loki.existingSecretForConfig }} - {{- else }} {{- include "loki.configVolume" . | nindent 10 }} - {{- end }} - name: runtime-config configMap: name: {{ template "loki.name" . }}-runtime diff --git a/charts/loki/templates/read/statefulset-read.yaml b/charts/loki/templates/read/statefulset-read.yaml index 6efa0ad559..7696d90e65 100644 --- a/charts/loki/templates/read/statefulset-read.yaml +++ b/charts/loki/templates/read/statefulset-read.yaml @@ -20,7 +20,11 @@ metadata: {{- end }} spec: {{- if not .Values.read.autoscaling.enabled }} + {{- if eq .Values.deploymentMode "SingleBinary" }} + replicas: 0 + {{- else }} replicas: {{ .Values.read.replicas }} + {{- end }} {{- end }} podManagementPolicy: {{ .Values.read.podManagementPolicy }} updateStrategy: @@ -133,7 +137,7 @@ spec: {{- end }} {{- with .Values.read.affinity }} affinity: - {{- tpl . $ | nindent 8 }} + {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.read.dnsConfig }} dnsConfig: @@ -155,13 +159,7 @@ spec: - name: tmp emptyDir: {} - name: config - {{- if .Values.loki.existingSecretForConfig }} - secret: - secretName: {{ .Values.loki.existingSecretForConfig }} - {{- else }} - configMap: - name: {{ include "loki.name" . }} - {{- end }} + {{- include "loki.configVolume" . | nindent 10 }} - name: runtime-config configMap: name: {{ template "loki.name" . }}-runtime @@ -182,6 +180,10 @@ spec: kind: PersistentVolumeClaim metadata: name: data + {{- with .Values.read.persistence.annotations }} + annotations: + {{- toYaml . | nindent 10 }} + {{- end }} spec: accessModes: - ReadWriteOnce diff --git a/charts/loki/templates/results-cache/poddisruptionbudget-results-cache.yaml b/charts/loki/templates/results-cache/poddisruptionbudget-results-cache.yaml new file mode 100644 index 0000000000..6bc393a87d --- /dev/null +++ b/charts/loki/templates/results-cache/poddisruptionbudget-results-cache.yaml @@ -0,0 +1,16 @@ +{{- if .Values.resultsCache.enabled }} +apiVersion: {{ include "loki.pdb.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "loki.fullname" . }}-memcached-results-cache + namespace: {{ .Release.Namespace }} + labels: + {{- include "loki.selectorLabels" . | nindent 4 }} + app.kubernetes.io/component: memcached-results-cache +spec: + selector: + matchLabels: + {{- include "loki.selectorLabels" . | nindent 6 }} + app.kubernetes.io/component: memcached-results-cache + maxUnavailable: 1 +{{- end -}} diff --git a/charts/loki/templates/results-cache/service-results-cache-headless.yaml b/charts/loki/templates/results-cache/service-results-cache-headless.yaml new file mode 100644 index 0000000000..ce9200856e --- /dev/null +++ b/charts/loki/templates/results-cache/service-results-cache-headless.yaml @@ -0,0 +1 @@ +{{- include "loki.memcached.service" (dict "ctx" $ "valuesSection" "resultsCache" "component" "results-cache" ) }} diff --git a/charts/loki/templates/results-cache/statefulset-results-cache.yaml b/charts/loki/templates/results-cache/statefulset-results-cache.yaml new file mode 100644 index 0000000000..042e74e1b2 --- /dev/null +++ b/charts/loki/templates/results-cache/statefulset-results-cache.yaml @@ -0,0 +1 @@ +{{- include "loki.memcached.statefulSet" (dict "ctx" $ "valuesSection" "resultsCache" "component" "results-cache" ) }} diff --git a/charts/loki/templates/ruler/_helpers-ruler.tpl b/charts/loki/templates/ruler/_helpers-ruler.tpl new file mode 100644 index 0000000000..2079e03b03 --- /dev/null +++ b/charts/loki/templates/ruler/_helpers-ruler.tpl @@ -0,0 +1,47 @@ +{{/* +ruler fullname +*/}} +{{- define "loki.rulerFullname" -}} +{{ include "loki.fullname" . }}-ruler +{{- end }} + +{{/* +ruler common labels +*/}} +{{- define "loki.rulerLabels" -}} +{{ include "loki.labels" . }} +app.kubernetes.io/component: ruler +{{- end }} + +{{/* +ruler selector labels +*/}} +{{- define "loki.rulerSelectorLabels" -}} +{{ include "loki.selectorLabels" . }} +app.kubernetes.io/component: ruler +{{- end }} + +{{/* +ruler image +*/}} +{{- define "loki.rulerImage" -}} +{{- $dict := dict "loki" .Values.loki.image "service" .Values.ruler.image "global" .Values.global.image "defaultVersion" .Chart.AppVersion -}} +{{- include "loki.lokiImage" $dict -}} +{{- end }} + +{{/* +format rules dir +*/}} +{{- define "loki.rulerRulesDirName" -}} +rules-{{ . | replace "_" "-" | trimSuffix "-" | lower }} +{{- end }} + +{{/* +ruler priority class name +*/}} +{{- define "loki.rulerPriorityClassName" -}} +{{- $pcn := coalesce .Values.global.priorityClassName .Values.ruler.priorityClassName -}} +{{- if $pcn }} +priorityClassName: {{ $pcn }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/ruler/configmap-ruler.yaml b/charts/loki/templates/ruler/configmap-ruler.yaml new file mode 100644 index 0000000000..b74f024b41 --- /dev/null +++ b/charts/loki/templates/ruler/configmap-ruler.yaml @@ -0,0 +1,14 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if $isDistributed }} +{{- range $dir, $files := .Values.ruler.directories }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "loki.rulerFullname" $ }}-{{ include "loki.rulerRulesDirName" $dir }} + labels: + {{- include "loki.rulerLabels" $ | nindent 4 }} +data: + {{- toYaml $files | nindent 2}} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/ruler/poddisruptionbudget-ruler.yaml b/charts/loki/templates/ruler/poddisruptionbudget-ruler.yaml new file mode 100644 index 0000000000..8241765186 --- /dev/null +++ b/charts/loki/templates/ruler/poddisruptionbudget-ruler.yaml @@ -0,0 +1,20 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed (gt (int .Values.ruler.replicas) 1) }} +{{- if kindIs "invalid" .Values.ruler.maxUnavailable }} +{{- fail "`.Values.ruler.maxUnavailable` must be set when `.Values.ruler.replicas` is greater than 1." }} +{{- else }} +apiVersion: {{ include "loki.pdb.apiVersion" . }} +kind: PodDisruptionBudget +metadata: + name: {{ include "loki.rulerFullname" . }} + labels: + {{- include "loki.rulerLabels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "loki.rulerSelectorLabels" . | nindent 6 }} + {{- with .Values.ruler.maxUnavailable }} + maxUnavailable: {{ . }} + {{- end }} +{{- end }} +{{- end }} diff --git a/charts/loki/templates/ruler/service-ruler.yaml b/charts/loki/templates/ruler/service-ruler.yaml new file mode 100644 index 0000000000..4d58ec85b4 --- /dev/null +++ b/charts/loki/templates/ruler/service-ruler.yaml @@ -0,0 +1,36 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.ruler.enabled }} +apiVersion: v1 +kind: Service +metadata: + name: {{ include "loki.rulerFullname" . }} + labels: + {{- include "loki.rulerSelectorLabels" . | nindent 4 }} + {{- with .Values.ruler.serviceLabels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.ruler.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} +spec: + type: ClusterIP + clusterIP: None + ports: + - name: http-metrics + port: 3100 + targetPort: http-metrics + protocol: TCP + - name: grpc + port: 9095 + targetPort: grpc + protocol: TCP + {{- with .Values.ruler.appProtocol.grpc }} + appProtocol: {{ . }} + {{- end }} + selector: + {{- include "loki.rulerSelectorLabels" . | nindent 4 }} +{{- end }} diff --git a/charts/loki/templates/ruler/statefulset-ruler.yaml b/charts/loki/templates/ruler/statefulset-ruler.yaml new file mode 100644 index 0000000000..8153a8bb38 --- /dev/null +++ b/charts/loki/templates/ruler/statefulset-ruler.yaml @@ -0,0 +1,177 @@ +{{- $isDistributed := eq (include "loki.deployment.isDistributed" .) "true" -}} +{{- if and $isDistributed .Values.ruler.enabled }} +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ include "loki.rulerFullname" . }} + labels: + {{- include "loki.rulerLabels" . | nindent 4 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + replicas: {{ .Values.ruler.replicas }} + revisionHistoryLimit: {{ .Values.loki.revisionHistoryLimit }} + serviceName: {{ include "loki.rulerFullname" . }} + selector: + matchLabels: + {{- include "loki.rulerSelectorLabels" . | nindent 6 }} + template: + metadata: + annotations: + {{- include "loki.config.checksum" . | nindent 8 }} + {{- with .Values.loki.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ruler.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "loki.rulerSelectorLabels" . | nindent 8 }} + app.kubernetes.io/part-of: memberlist + {{- with .Values.loki.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ruler.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ include "loki.serviceAccountName" . }} + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ruler.hostAliases }} + hostAliases: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- include "loki.rulerPriorityClassName" . | nindent 6 }} + securityContext: + {{- toYaml .Values.loki.podSecurityContext | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.ruler.terminationGracePeriodSeconds }} + {{- with .Values.ruler.initContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: ruler + image: {{ include "loki.image" . }} + imagePullPolicy: {{ .Values.loki.image.pullPolicy }} + args: + - -config.file=/etc/loki/config/config.yaml + - -target=ruler + {{- with .Values.ruler.extraArgs }} + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http-metrics + containerPort: 3100 + protocol: TCP + - name: grpc + containerPort: 9095 + protocol: TCP + - name: http-memberlist + containerPort: 7946 + protocol: TCP + {{- with .Values.ruler.extraEnv }} + env: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.ruler.extraEnvFrom }} + envFrom: + {{- toYaml . | nindent 12 }} + {{- end }} + securityContext: + {{- toYaml .Values.loki.containerSecurityContext | nindent 12 }} + readinessProbe: + {{- toYaml .Values.loki.readinessProbe | nindent 12 }} + volumeMounts: + - name: config + mountPath: /etc/loki/config + - name: runtime-config + mountPath: /etc/loki/runtime-config + - name: data + mountPath: /var/loki + - name: tmp + mountPath: /tmp/loki + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} + {{- range $dir, $_ := .Values.ruler.directories }} + - name: {{ include "loki.rulerRulesDirName" $dir }} + mountPath: /etc/loki/rules/{{ $dir }} + {{- end }} + {{- with .Values.ruler.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.ruler.resources | nindent 12 }} + {{- with .Values.ruler.extraContainers }} + {{- toYaml . | nindent 8}} + {{- end }} + {{- with .Values.ruler.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ruler.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ruler.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.ruler.dnsConfig }} + dnsConfig: + {{- toYaml . | nindent 8 }} + {{- end }} + volumes: + - name: config + {{- include "loki.configVolume" . | nindent 10 }} + - name: runtime-config + configMap: + name: {{ template "loki.name" . }}-runtime + {{- if .Values.enterprise.enabled }} + - name: license + secret: + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} + {{- else }} + secretName: enterprise-logs-license + {{- end }} + {{- end }} + {{- range $dir, $_ := .Values.ruler.directories }} + - name: {{ include "loki.rulerRulesDirName" $dir }} + configMap: + name: {{ include "loki.rulerFullname" $ }}-{{ include "loki.rulerRulesDirName" $dir }} + {{- end }} + - name: tmp + emptyDir: {} + {{- with .Values.ruler.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- if not .Values.ruler.persistence.enabled }} + - name: data + emptyDir: {} + {{- else }} + volumeClaimTemplates: + - metadata: + name: data + {{- with .Values.ruler.persistence.annotations }} + annotations: + {{- . | toYaml | nindent 10 }} + {{- end }} + spec: + accessModes: + - ReadWriteOnce + {{- with .Values.ruler.persistence.storageClass }} + storageClassName: {{ if (eq "-" .) }}""{{ else }}{{ . }}{{ end }} + {{- end }} + resources: + requests: + storage: {{ .Values.ruler.persistence.size | quote }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/service-memberlist.yaml b/charts/loki/templates/service-memberlist.yaml index cacb5b1e87..3d46f234d4 100644 --- a/charts/loki/templates/service-memberlist.yaml +++ b/charts/loki/templates/service-memberlist.yaml @@ -6,6 +6,13 @@ metadata: namespace: {{ $.Release.Namespace }} labels: {{- include "loki.labels" . | nindent 4 }} + annotations: + {{- with .Values.loki.serviceAnnotations }} + {{- toYaml . | nindent 4}} + {{- end }} + {{- with .Values.memberlist.service.annotations }} + {{- toYaml . | nindent 4}} + {{- end }} spec: type: ClusterIP clusterIP: None diff --git a/charts/loki/templates/single-binary/statefulset.yaml b/charts/loki/templates/single-binary/statefulset.yaml index 8922c89ab3..5e28902e56 100644 --- a/charts/loki/templates/single-binary/statefulset.yaml +++ b/charts/loki/templates/single-binary/statefulset.yaml @@ -79,6 +79,75 @@ spec: {{- end }} {{- end }} containers: + {{- if .Values.sidecar.rules.enabled }} + - name: loki-sc-rules + {{- if .Values.sidecar.image.sha }} + image: "{{ .Values.sidecar.image.repository }}:{{ .Values.sidecar.image.tag }}@sha256:{{ .Values.sidecar.image.sha }}" + {{- else }} + image: "{{ .Values.sidecar.image.repository }}:{{ .Values.sidecar.image.tag }}" + {{- end }} + imagePullPolicy: {{ .Values.sidecar.image.pullPolicy }} + env: + - name: METHOD + value: {{ .Values.sidecar.rules.watchMethod }} + - name: LABEL + value: "{{ .Values.sidecar.rules.label }}" + {{- if .Values.sidecar.rules.labelValue }} + - name: LABEL_VALUE + value: {{ quote .Values.sidecar.rules.labelValue }} + {{- end }} + - name: FOLDER + value: "{{ .Values.sidecar.rules.folder }}" + - name: RESOURCE + value: {{ quote .Values.sidecar.rules.resource }} + {{- if .Values.sidecar.enableUniqueFilenames }} + - name: UNIQUE_FILENAMES + value: "{{ .Values.sidecar.enableUniqueFilenames }}" + {{- end }} + {{- if .Values.sidecar.rules.searchNamespace }} + - name: NAMESPACE + value: "{{ .Values.sidecar.rules.searchNamespace | join "," }}" + {{- end }} + {{- if .Values.sidecar.skipTlsVerify }} + - name: SKIP_TLS_VERIFY + value: "{{ .Values.sidecar.skipTlsVerify }}" + {{- end }} + {{- if .Values.sidecar.rules.script }} + - name: SCRIPT + value: "{{ .Values.sidecar.rules.script }}" + {{- end }} + {{- if .Values.sidecar.rules.watchServerTimeout }} + - name: WATCH_SERVER_TIMEOUT + value: "{{ .Values.sidecar.rules.watchServerTimeout }}" + {{- end }} + {{- if .Values.sidecar.rules.watchClientTimeout }} + - name: WATCH_CLIENT_TIMEOUT + value: "{{ .Values.sidecar.rules.watchClientTimeout }}" + {{- end }} + {{- if .Values.sidecar.rules.logLevel }} + - name: LOG_LEVEL + value: "{{ .Values.sidecar.rules.logLevel }}" + {{- end }} + {{- if .Values.sidecar.livenessProbe }} + livenessProbe: + {{- toYaml .Values.sidecar.livenessProbe | nindent 12 }} + {{- end }} + {{- if .Values.sidecar.readinessProbe }} + readinessProbe: + {{- toYaml .Values.sidecar.readinessProbe | nindent 12 }} + {{- end }} + {{- if .Values.sidecar.resources }} + resources: + {{- toYaml .Values.sidecar.resources | nindent 12 }} + {{- end }} + {{- if .Values.sidecar.securityContext }} + securityContext: + {{- toYaml .Values.sidecar.securityContext | nindent 12 }} + {{- end }} + volumeMounts: + - name: sc-rules-volume + mountPath: {{ .Values.sidecar.rules.folder | quote }} + {{- end}} - name: loki image: {{ include "loki.image" . }} imagePullPolicy: {{ .Values.loki.image.pullPolicy }} @@ -125,6 +194,10 @@ spec: - name: license mountPath: /etc/loki/license {{- end }} + {{- if .Values.sidecar.rules.enabled }} + - name: sc-rules-volume + mountPath: {{ .Values.sidecar.rules.folder | quote }} + {{- end}} {{- with .Values.singleBinary.extraVolumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} @@ -135,7 +208,7 @@ spec: {{- end }} {{- with .Values.singleBinary.affinity }} affinity: - {{- tpl . $ | nindent 8 }} + {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.singleBinary.dnsConfig }} dnsConfig: @@ -153,12 +226,7 @@ spec: - name: tmp emptyDir: {} - name: config - {{- if .Values.loki.existingSecretForConfig }} - secret: - secretName: {{ .Values.loki.existingSecretForConfig }} - {{- else }} {{- include "loki.configVolume" . | nindent 10 }} - {{- end }} - name: runtime-config configMap: name: {{ template "loki.name" . }}-runtime @@ -171,6 +239,15 @@ spec: secretName: enterprise-logs-license {{- end }} {{- end }} + {{- if .Values.sidecar.rules.enabled }} + - name: sc-rules-volume + {{- if .Values.sidecar.rules.sizeLimit }} + emptyDir: + sizeLimit: {{ .Values.sidecar.rules.sizeLimit }} + {{- else }} + emptyDir: {} + {{- end -}} + {{- end -}} {{- with .Values.singleBinary.extraVolumes }} {{- toYaml . | nindent 8 }} {{- end }} @@ -180,6 +257,10 @@ spec: kind: PersistentVolumeClaim metadata: name: storage + {{- with .Values.singleBinary.persistence.annotations }} + annotations: + {{- toYaml . | nindent 10 }} + {{- end }} spec: accessModes: - ReadWriteOnce diff --git a/charts/loki/templates/table-manager/deployment-table-manager.yaml b/charts/loki/templates/table-manager/deployment-table-manager.yaml index aeb5b1affe..e3f6d0d94a 100644 --- a/charts/loki/templates/table-manager/deployment-table-manager.yaml +++ b/charts/loki/templates/table-manager/deployment-table-manager.yaml @@ -80,6 +80,10 @@ spec: volumeMounts: - name: config mountPath: /etc/loki/config + {{- if .Values.enterprise.enabled }} + - name: license + mountPath: /etc/loki/license + {{- end }} {{- with .Values.tableManager.extraVolumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} @@ -90,7 +94,7 @@ spec: {{- end }} {{- with .Values.tableManager.affinity }} affinity: - {{- tpl . $ | nindent 8 }} + {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tableManager.dnsConfig }} dnsConfig: @@ -106,12 +110,16 @@ spec: {{- end }} volumes: - name: config - {{- if .Values.loki.existingSecretForConfig }} + {{- include "loki.configVolume" . | nindent 10 }} + {{- if .Values.enterprise.enabled }} + - name: license secret: - secretName: {{ .Values.loki.existingSecretForConfig }} + {{- if .Values.enterprise.useExternalLicense }} + secretName: {{ .Values.enterprise.externalLicenseName }} {{- else }} - {{- include "loki.configVolume" . | nindent 10 }} + secretName: enterprise-logs-license {{- end }} + {{- end }} {{- with .Values.tableManager.extraVolumes }} {{- toYaml . | nindent 8 }} {{- end }} diff --git a/charts/loki/templates/tests/test-canary.yaml b/charts/loki/templates/tests/test-canary.yaml index a4f11e214a..9384865b7b 100644 --- a/charts/loki/templates/tests/test-canary.yaml +++ b/charts/loki/templates/tests/test-canary.yaml @@ -1,5 +1,5 @@ {{- with .Values.test }} -{{- if and .enabled $.Values.monitoring.selfMonitoring.enabled $.Values.monitoring.lokiCanary.enabled }} +{{- if $.Values.lokiCanary.enabled }} --- apiVersion: v1 kind: Pod @@ -21,6 +21,8 @@ spec: - name: loki-helm-test image: {{ include "loki.helmTestImage" $ }} env: + - name: CANARY_SERVICE_ADDRESS + value: "{{ .canaryServiceAddress }}" - name: CANARY_PROMETHEUS_ADDRESS value: "{{ .prometheusAddress }}" {{- with .timeout }} diff --git a/charts/loki/templates/tokengen/clusterrole-tokengen.yaml b/charts/loki/templates/tokengen/clusterrole-tokengen.yaml index 19dad8804b..c67cec8864 100644 --- a/charts/loki/templates/tokengen/clusterrole-tokengen.yaml +++ b/charts/loki/templates/tokengen/clusterrole-tokengen.yaml @@ -1,4 +1,4 @@ -{{ if and .Values.enterprise.tokengen.enabled .Values.enterprise.enabled }} +{{ if and (and .Values.enterprise.tokengen.enabled .Values.enterprise.enabled) (not .Values.rbac.namespaced)}} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/charts/loki/templates/tokengen/clusterrolebinding-tokengen.yaml b/charts/loki/templates/tokengen/clusterrolebinding-tokengen.yaml index 248337ea2e..deb368f299 100644 --- a/charts/loki/templates/tokengen/clusterrolebinding-tokengen.yaml +++ b/charts/loki/templates/tokengen/clusterrolebinding-tokengen.yaml @@ -1,4 +1,4 @@ -{{ if and .Values.enterprise.tokengen.enabled .Values.enterprise.enabled }} +{{ if and (and .Values.enterprise.tokengen.enabled .Values.enterprise.enabled) (not .Values.rbac.namespaced)}} --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/charts/loki/templates/tokengen/job-tokengen.yaml b/charts/loki/templates/tokengen/job-tokengen.yaml index b917395c3c..b0950d6f19 100644 --- a/charts/loki/templates/tokengen/job-tokengen.yaml +++ b/charts/loki/templates/tokengen/job-tokengen.yaml @@ -110,19 +110,21 @@ spec: restartPolicy: OnFailure serviceAccount: {{ template "enterprise-logs.tokengenFullname" . }} serviceAccountName: {{ template "enterprise-logs.tokengenFullname" . }} + {{- with .Values.enterprise.tokengen.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.enterprise.tokengen.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} {{- with .Values.enterprise.tokengen.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }} volumes: - name: config - {{- if .Values.enterprise.useExternalConfig }} - secret: - secretName: {{ .Values.enterprise.externalConfigName }} - {{- else }} - configMap: - name: {{ include "loki.name" . }} - {{- end }} + {{- include "loki.configVolume" . | nindent 10 }} - name: runtime-config configMap: name: {{ template "loki.name" . }}-runtime diff --git a/charts/loki/templates/validate.yaml b/charts/loki/templates/validate.yaml index 3a2e8ca79f..93e2490636 100644 --- a/charts/loki/templates/validate.yaml +++ b/charts/loki/templates/validate.yaml @@ -2,26 +2,40 @@ {{- fail "Top level 'config' is not allowed. Most common configuration sections are exposed under the `loki` section. If you need to override the whole config, provide the configuration as a string that can contain template expressions under `loki.config`. Alternatively, you can provide the configuration as an external secret." }} {{- end }} -{{- if and (not .Values.monitoring.selfMonitoring.enabled) .Values.test.enabled }} -{{- fail "Helm test requires self monitoring to be enabled"}} -{{- end }} - -{{- if and (not .Values.monitoring.lokiCanary.enabled) .Values.test.enabled }} +{{- if and (not .Values.lokiCanary.enabled) .Values.test.enabled }} {{- fail "Helm test requires the Loki Canary to be enabled"}} {{- end }} -{{- if and .Values.test.enabled (not .Values.test.prometheusAddress) }} -{{- fail "Helm test requires a prometheusAddress for an instance scraping the Loki canary's metrics"}} -{{- end }} - {{- $singleBinaryReplicas := int .Values.singleBinary.replicas }} {{- $isUsingFilesystem := eq (include "loki.isUsingObjectStorage" .) "false" }} {{- $atLeastOneScalableReplica := or (gt (int .Values.backend.replicas) 0) (gt (int .Values.read.replicas) 0) (gt (int .Values.write.replicas) 0) }} +{{- $atLeastOneDistributedReplica := or (gt (int .Values.ingester.replicas) 0) (gt (int .Values.distributor.replicas) 0) (gt (int .Values.querier.replicas) 0) (gt (int .Values.queryFrontend.replicas) 0) (gt (int .Values.queryScheduler.replicas) 0) (gt (int .Values.indexGateway.replicas) 0) (gt (int .Values.compactor.replicas) 0) (gt (int .Values.ruler.replicas) 0) }} {{- if and $isUsingFilesystem (gt $singleBinaryReplicas 1) }} {{- fail "Cannot run more than 1 Single Binary replica without an object storage backend."}} {{- end }} -{{- if and $isUsingFilesystem (and (eq $singleBinaryReplicas 0) $atLeastOneScalableReplica) }} -{{- fail "Cannot run Scalable targets (backend, read, write) without an object storage backend."}} +{{- if and $isUsingFilesystem (and (eq $singleBinaryReplicas 0) (or $atLeastOneScalableReplica $atLeastOneDistributedReplica)) }} +{{- fail "Cannot run scalable targets (backend, read, write) or distributed targets without an object storage backend."}} +{{- end }} + +{{- if and $atLeastOneScalableReplica $atLeastOneDistributedReplica (ne .Values.deploymentMode "SimpleScalable<->Distributed") }} +{{- fail "You have more than zero replicas configured for scalable targets (backend, read, write) and distributed targets. If this was intentional change the deploymentMode to the transitional 'SimpleScalable<->Distributed' mode" }} +{{- end }} + +{{- if and (gt $singleBinaryReplicas 0) $atLeastOneDistributedReplica }} +{{- fail "You have more than zero replicas configured for both the single binary and distributed targets, there is no transition mode between these targets please change one or the other to zero or transition to the SimpleScalable mode first."}} +{{- end }} + +{{- if and (gt $singleBinaryReplicas 0) $atLeastOneScalableReplica (ne .Values.deploymentMode "SingleBinary<->SimpleScalable") }} +{{- fail "You have more than zero replicas configured for both the single binary and simple scalable targets. If this was intentional change the deploymentMode to the transitional 'SingleBinary<->SimpleScalable' mode"}} {{- end }} + +{{- if and (or (not (empty .Values.loki.schemaConfig)) (not (empty .Values.loki.structuredConfig.schema_config))) .Values.loki.useTestSchema }} +{{- fail "loki.useTestSchema must be false if loki.schemaConfig or loki.structuredConfig.schema_config are defined."}} +{{- end }} + + +{{- if and (empty .Values.loki.schemaConfig) (empty .Values.loki.structuredConfig.schema_config) (not .Values.loki.useTestSchema) }} +{{- fail "You must provide a schema_config for Loki, one is not provided as this will be individual for every Loki cluster. See https://grafana.com/docs/loki/latest/operations/storage/schema/ for schema information. For quick testing (with no persistence) add `--set loki.useTestSchema=true`"}} +{{- end }} \ No newline at end of file diff --git a/charts/loki/templates/write/statefulset-write.yaml b/charts/loki/templates/write/statefulset-write.yaml index 5aa1e78eaf..75605c27c2 100644 --- a/charts/loki/templates/write/statefulset-write.yaml +++ b/charts/loki/templates/write/statefulset-write.yaml @@ -20,7 +20,11 @@ metadata: {{- end }} spec: {{- if not .Values.write.autoscaling.enabled }} + {{- if eq .Values.deploymentMode "SingleBinary" }} + replicas: 0 + {{- else }} replicas: {{ .Values.write.replicas }} + {{- end }} {{- end }} podManagementPolicy: {{ .Values.write.podManagementPolicy }} updateStrategy: @@ -132,7 +136,7 @@ spec: {{- if .Values.enterprise.enabled }} - name: license mountPath: /etc/loki/license - {{- end}} + {{- end }} {{- with .Values.write.extraVolumeMounts }} {{- toYaml . | nindent 12 }} {{- end }} @@ -143,7 +147,7 @@ spec: {{- end }} {{- with .Values.write.affinity }} affinity: - {{- tpl . $ | nindent 8 }} + {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.write.dnsConfig }} dnsConfig: @@ -167,12 +171,7 @@ spec: {{- toYaml .Values.write.persistence.dataVolumeParameters | nindent 10 }} {{- end}} - name: config - {{- if .Values.loki.existingSecretForConfig }} - secret: - secretName: {{ .Values.loki.existingSecretForConfig }} - {{- else }} {{- include "loki.configVolume" . | nindent 10 }} - {{- end }} - name: runtime-config configMap: name: {{ template "loki.name" . }}-runtime @@ -194,6 +193,10 @@ spec: kind: PersistentVolumeClaim metadata: name: data + {{- with .Values.write.persistence.annotations }} + annotations: + {{- toYaml . | nindent 10 }} + {{- end }} spec: accessModes: - ReadWriteOnce diff --git a/charts/loki/test/config_test.go b/charts/loki/test/config_test.go new file mode 100644 index 0000000000..6926c7b2a8 --- /dev/null +++ b/charts/loki/test/config_test.go @@ -0,0 +1,220 @@ +package test + +import ( + "os" + "os/exec" + "testing" + + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v2" +) + +type replicas struct { + Replicas int `yaml:"replicas"` +} +type loki struct { + Storage struct { + Type string `yaml:"type"` + } `yaml:"storage"` +} + +type values struct { + DeploymentMode string `yaml:"deploymentMode"` + Backend replicas `yaml:"backend"` + Compactor replicas `yaml:"compactor"` + Distributor replicas `yaml:"distributor"` + IndexGateway replicas `yaml:"indexGateway"` + Ingester replicas `yaml:"ingester"` + Querier replicas `yaml:"querier"` + QueryFrontend replicas `yaml:"queryFrontend"` + QueryScheduler replicas `yaml:"queryScheduler"` + Read replicas `yaml:"read"` + Ruler replicas `yaml:"ruler"` + SingleBinary replicas `yaml:"singleBinary"` + Write replicas `yaml:"write"` + + Loki loki `yaml:"loki"` +} + +func templateConfig(t *testing.T, vals values) error { + y, err := yaml.Marshal(&vals) + require.NoError(t, err) + require.Greater(t, len(y), 0) + + f, err := os.CreateTemp("", "values.yaml") + require.NoError(t, err) + + _, err = f.Write(y) + require.NoError(t, err) + + cmd := exec.Command("helm", "dependency", "build") + // Dependency build needs to be run from the parent directory where the chart is located. + cmd.Dir = "../" + var cmdOutput []byte + if cmdOutput, err = cmd.CombinedOutput(); err != nil { + t.Log("dependency build failed", "err", string(cmdOutput)) + return err + } + + cmd = exec.Command("helm", "template", "../", "--values", f.Name()) + if cmdOutput, err := cmd.CombinedOutput(); err != nil { + t.Log("template failed", "err", string(cmdOutput)) + return err + } + + return nil +} + +// E.Welch these tests fail because the templateConfig function above can't resolve the chart dependencies and I'm not sure how to fix this.... + +//func Test_InvalidConfigs(t *testing.T) { +// t.Run("running both single binary and scalable targets", func(t *testing.T) { +// vals := values{ +// SingleBinary: replicas{Replicas: 1}, +// Write: replicas{Replicas: 1}, +// Loki: loki{ +// Storage: struct { +// Type string `yaml:"type"` +// }{Type: "gcs"}, +// }, +// } +// require.Error(t, templateConfig(t, vals)) +// }) +// +// t.Run("running both single binary and distributed targets", func(t *testing.T) { +// vals := values{ +// SingleBinary: replicas{Replicas: 1}, +// Distributor: replicas{Replicas: 1}, +// Loki: loki{ +// Storage: struct { +// Type string `yaml:"type"` +// }{Type: "gcs"}, +// }, +// } +// require.Error(t, templateConfig(t, vals)) +// }) +// +// t.Run("running both scalable and distributed targets", func(t *testing.T) { +// vals := values{ +// Read: replicas{Replicas: 1}, +// Distributor: replicas{Replicas: 1}, +// Loki: loki{ +// Storage: struct { +// Type string `yaml:"type"` +// }{Type: "gcs"}, +// }, +// } +// require.Error(t, templateConfig(t, vals)) +// }) +// +// t.Run("running scalable with filesystem storage", func(t *testing.T) { +// vals := values{ +// Read: replicas{Replicas: 1}, +// Loki: loki{ +// Storage: struct { +// Type string `yaml:"type"` +// }{Type: "filesystem"}, +// }, +// } +// +// require.Error(t, templateConfig(t, vals)) +// }) +// +// t.Run("running distributed with filesystem storage", func(t *testing.T) { +// vals := values{ +// Distributor: replicas{Replicas: 1}, +// Loki: loki{ +// Storage: struct { +// Type string `yaml:"type"` +// }{Type: "filesystem"}, +// }, +// } +// +// require.Error(t, templateConfig(t, vals)) +// }) +//} +// +//func Test_ValidConfigs(t *testing.T) { +// t.Run("single binary", func(t *testing.T) { +// vals := values{ +// +// DeploymentMode: "SingleBinary", +// +// SingleBinary: replicas{Replicas: 1}, +// +// Backend: replicas{Replicas: 0}, +// Compactor: replicas{Replicas: 0}, +// Distributor: replicas{Replicas: 0}, +// IndexGateway: replicas{Replicas: 0}, +// Ingester: replicas{Replicas: 0}, +// Querier: replicas{Replicas: 0}, +// QueryFrontend: replicas{Replicas: 0}, +// QueryScheduler: replicas{Replicas: 0}, +// Read: replicas{Replicas: 0}, +// Ruler: replicas{Replicas: 0}, +// Write: replicas{Replicas: 0}, +// +// Loki: loki{ +// Storage: struct { +// Type string `yaml:"type"` +// }{Type: "filesystem"}, +// }, +// } +// require.NoError(t, templateConfig(t, vals)) +// }) +// +// t.Run("scalable", func(t *testing.T) { +// vals := values{ +// +// DeploymentMode: "SimpleScalable", +// +// Backend: replicas{Replicas: 1}, +// Read: replicas{Replicas: 1}, +// Write: replicas{Replicas: 1}, +// +// Compactor: replicas{Replicas: 0}, +// Distributor: replicas{Replicas: 0}, +// IndexGateway: replicas{Replicas: 0}, +// Ingester: replicas{Replicas: 0}, +// Querier: replicas{Replicas: 0}, +// QueryFrontend: replicas{Replicas: 0}, +// QueryScheduler: replicas{Replicas: 0}, +// Ruler: replicas{Replicas: 0}, +// SingleBinary: replicas{Replicas: 0}, +// +// Loki: loki{ +// Storage: struct { +// Type string `yaml:"type"` +// }{Type: "gcs"}, +// }, +// } +// require.NoError(t, templateConfig(t, vals)) +// }) +// +// t.Run("distributed", func(t *testing.T) { +// vals := values{ +// DeploymentMode: "Distributed", +// +// Compactor: replicas{Replicas: 1}, +// Distributor: replicas{Replicas: 1}, +// IndexGateway: replicas{Replicas: 1}, +// Ingester: replicas{Replicas: 1}, +// Querier: replicas{Replicas: 1}, +// QueryFrontend: replicas{Replicas: 1}, +// QueryScheduler: replicas{Replicas: 1}, +// Ruler: replicas{Replicas: 1}, +// +// Backend: replicas{Replicas: 0}, +// Read: replicas{Replicas: 0}, +// SingleBinary: replicas{Replicas: 0}, +// Write: replicas{Replicas: 0}, +// +// Loki: loki{ +// Storage: struct { +// Type string `yaml:"type"` +// }{Type: "gcs"}, +// }, +// } +// require.NoError(t, templateConfig(t, vals)) +// }) +//} diff --git a/charts/loki/values.yaml b/charts/loki/values.yaml index 70d853bca2..3f44de3b37 100644 --- a/charts/loki/values.yaml +++ b/charts/loki/values.yaml @@ -18,17 +18,24 @@ fullnameOverride: null clusterLabelOverride: null # -- Image pull secrets for Docker images imagePullSecrets: [] -kubectlImage: - # -- The Docker registry - registry: docker.io - # -- Docker image repository - repository: bitnami/kubectl - # -- Overrides the image tag whose default is the chart's appVersion - tag: null - # -- Overrides the image tag with an image digest - digest: null - # -- Docker image pull policy - pullPolicy: IfNotPresent +# -- Deployment mode lets you specify how to deploy Loki. +# There are 3 options: +# - SingleBinary: Loki is deployed as a single binary, useful for small installs typically without HA, up to a few tens of GB/day. +# - SimpleScalable: Loki is deployed as 3 targets: read, write, and backend. Useful for medium installs easier to manage than distributed, up to a about 1TB/day. +# - Distributed: Loki is deployed as individual microservices. The most complicated but most capable, useful for large installs, typically over 1TB/day. +# There are also 2 additional modes used for migrating between deployment modes: +# - SingleBinary<->SimpleScalable: Migrate from SingleBinary to SimpleScalable (or vice versa) +# - SimpleScalable<->Distributed: Migrate from SimpleScalable to Distributed (or vice versa) +# Note: SimpleScalable and Distributed REQUIRE the use of object storage. +deploymentMode: SimpleScalable +###################################################################################################################### +# +# Base Loki Configs including kubernetes configurations and configurations for Loki itself, +# see below for more specifics on Loki's configuration. +# +###################################################################################################################### +# -- Configuration for running Loki +# @default -- See values.yaml loki: # Configures the readiness probe for all of the Loki pods readinessProbe: @@ -77,14 +84,40 @@ loki: allowPrivilegeEscalation: false # -- Should enableServiceLinks be enabled. Default to enable enableServiceLinks: true - # -- Specify an existing secret containing loki configuration. If non-empty, overrides `loki.config` - existingSecretForConfig: "" + ###################################################################################################################### + # + # Loki Configuration + # + # There are several ways to pass configuration to Loki, listing them here in order of our preference for how + # you should use this chart. + # 1. Use the templated value of loki.config below and the corresponding override sections which follow. + # This allows us to set a lot of important Loki configurations and defaults and also allows us to maintain them + # over time as Loki changes and evolves. + # 2. Use the loki.structuredConfig section. + # This will completely override the templated value of loki.config, so you MUST provide the entire Loki config + # including any configuration that we set in loki.config unless you explicitly are trying to change one of those + # values and are not able to do so with the templated sections. + # If you choose this approach the burden is on you to maintain any changes we make to the templated config. + # 3. Use an existing secret or configmap to provide the configuration. + # This option is mostly provided for folks who have external processes which provide or modify the configuration. + # When using this option you can specify a different name for loki.generatedConfigObjectName and configObjectName + # if you have a process which takes the generated config and modifies it, or you can stop the chart from generating + # a config entirely by setting loki.generatedConfigObjectName to + # + ###################################################################################################################### + # -- Defines what kind of object stores the configuration, a ConfigMap or a Secret. # In order to move sensitive information (such as credentials) from the ConfigMap/Secret to a more secure location (e.g. vault), it is possible to use [environment variables in the configuration](https://grafana.com/docs/loki/latest/configuration/#use-environment-variables-in-the-configuration). # Such environment variables can be then stored in a separate Secret and injected via the global.extraEnvFrom value. For details about environment injection from a Secret please see [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/#use-case-as-container-environment-variables). configStorageType: ConfigMap - # -- Name of the Secret or ConfigMap that contains the configuration (used for naming even if config is internal). - externalConfigSecretName: '{{ include "loki.name" . }}' + # -- The name of the object which Loki will mount as a volume containing the config. + # If the configStorageType is Secret, this will be the name of the Secret, if it is ConfigMap, this will be the name of the ConfigMap. + # The value will be passed through tpl. + configObjectName: '{{ include "loki.name" . }}' + # -- The name of the Secret or ConfigMap that will be created by this chart. + # If empty, no configmap or secret will be created. + # The value will be passed through tpl. + generatedConfigObjectName: '{{ include "loki.name" . }}' # -- Config file contents for Loki # @default -- See values.yaml config: | @@ -99,6 +132,9 @@ loki: {{- toYaml . | nindent 2}} {{- end}} + pattern_ingester: + enabled: {{ .Values.loki.pattern_ingester.enabled }} + memberlist: {{- if .Values.loki.memberlistConfig }} {{- toYaml .Values.loki.memberlistConfig | nindent 2 }} @@ -135,37 +171,34 @@ loki: runtime_config: file: /etc/loki/runtime-config/runtime-config.yaml - {{- with .Values.loki.memcached.chunk_cache }} - {{- if and .enabled (or .host .addresses) }} + {{- with .Values.chunksCache }} + {{- if .enabled }} chunk_store_config: chunk_cache_config: + default_validity: {{ .defaultValidity }} + background: + writeback_goroutines: {{ .writebackParallelism }} + writeback_buffer: {{ .writebackBuffer }} + writeback_size_limit: {{ .writebackSizeLimit }} memcached: - batch_size: {{ .batch_size }} + batch_size: {{ .batchSize }} parallelism: {{ .parallelism }} memcached_client: - {{- if .host }} - host: {{ .host }} - {{- end }} - {{- if .addresses }} - addresses: {{ .addresses }} - {{- end }} - service: {{ .service }} + addresses: dnssrvnoa+_memcached-client._tcp.{{ template "loki.fullname" $ }}-chunks-cache.{{ $.Release.Namespace }}.svc + consistent_hash: true + timeout: {{ .timeout }} + max_idle_conns: 72 {{- end }} {{- end }} {{- if .Values.loki.schemaConfig }} schema_config: {{- toYaml .Values.loki.schemaConfig | nindent 2}} - {{- else }} + {{- end }} + + {{- if .Values.loki.useTestSchema }} schema_config: - configs: - - from: 2022-01-11 - store: boltdb-shipper - object_store: {{ .Values.loki.storage.type }} - schema: v12 - index: - prefix: loki_index_ - period: 24h + {{- toYaml .Values.loki.testSchemaConfig | nindent 2}} {{- end }} {{ include "loki.rulerConfig" . }} @@ -176,25 +209,28 @@ loki: retention_period: {{ .Values.tableManager.retention_period }} {{- end }} - {{- with .Values.loki.memcached.results_cache }} query_range: align_queries_with_step: true - {{- if and .enabled (or .host .addresses) }} - cache_results: {{ .enabled }} + {{- with .Values.loki.query_range }} + {{- tpl (. | toYaml) $ | nindent 2 }} + {{- end }} + {{- if .Values.resultsCache.enabled }} + {{- with .Values.resultsCache }} + cache_results: true results_cache: cache: - default_validity: {{ .default_validity }} + default_validity: {{ .defaultValidity }} + background: + writeback_goroutines: {{ .writebackParallelism }} + writeback_buffer: {{ .writebackBuffer }} + writeback_size_limit: {{ .writebackSizeLimit }} memcached_client: - {{- if .host }} - host: {{ .host }} - {{- end }} - {{- if .addresses }} - addresses: {{ .addresses }} - {{- end }} - service: {{ .service }} + consistent_hash: true + addresses: dnssrvnoa+_memcached-client._tcp.{{ template "loki.fullname" $ }}-results-cache.{{ $.Release.Namespace }}.svc timeout: {{ .timeout }} + update_interval: 1m + {{- end }} {{- end }} - {{- end }} {{- with .Values.loki.storage_config }} storage_config: @@ -243,6 +279,16 @@ loki: tracing: enabled: {{ .Values.loki.tracing.enabled }} + + {{- with .Values.loki.bloom_build }} + bloom_build: + {{- tpl (. | toYaml) $ | nindent 4 }} + {{- end }} + + {{- with .Values.loki.bloom_gateway }} + bloom_gateway: + {{- tpl (. | toYaml) $ | nindent 4 }} + {{- end }} # Should authentication be enabled auth_enabled: true # -- memberlist configuration (overrides embedded default) @@ -255,12 +301,16 @@ loki: server: http_listen_port: 3100 grpc_listen_port: 9095 + http_server_read_timeout: 600s + http_server_write_timeout: 600s # -- Limits config limits_config: reject_old_samples: true reject_old_samples_max_age: 168h max_cache_freshness_per_query: 10m split_queries_by_interval: 15m + query_timeout: 300s + volume_enabled: true # -- Provides a reloadable runtime configuration file for some specific configuration runtimeConfig: {} # -- Check https://grafana.com/docs/loki/latest/configuration/#common_config for more info on how to provide a common configuration @@ -270,10 +320,12 @@ loki: compactor_address: '{{ include "loki.compactorAddress" . }}' # -- Storage config. Providing this will automatically populate all necessary storage configs in the templated config. storage: - bucketNames: - chunks: chunks - ruler: ruler - admin: admin + # Loki requires a bucket for chunks and the ruler. GEL requires a third bucket for the admin API. + # Please provide these values if you are using object storage. + # bucketNames: + # chunks: FIXME + # ruler: FIXME + # admin: FIXME type: s3 s3: s3: null @@ -287,6 +339,7 @@ loki: http_config: {} # -- Check https://grafana.com/docs/loki/latest/configure/#s3_storage_config for more info on how to provide a backoff_config backoff_config: {} + disable_dualstack: false gcs: chunkBufferSize: 0 requestTimeout: "0s" @@ -323,6 +376,7 @@ loki: filesystem: chunks_directory: /var/loki/chunks rules_directory: /var/loki/rules + admin_api_directory: /var/loki/admin # -- Configure memcached as an external cache for chunk and results cache. Disabled by default # must enable and specify a host for each cache you would like to use. memcached: @@ -340,6 +394,18 @@ loki: default_validity: "12h" # -- Check https://grafana.com/docs/loki/latest/configuration/#schema_config for more info on how to configure schemas schemaConfig: {} + # -- a real Loki install requires a proper schemaConfig defined above this, however for testing or playing around + # you can enable useTestSchema + useTestSchema: false + testSchemaConfig: + configs: + - from: 2024-04-01 + store: tsdb + object_store: '{{ include "loki.testSchemaObjectStore" . }}' + schema: v13 + index: + prefix: index_ + period: 24h # -- Check https://grafana.com/docs/loki/latest/configuration/#ruler for more info on configuring ruler rulerConfig: {} # -- Structured loki configuration, takes precedence over `loki.config`, `loki.schemaConfig`, `loki.storageConfig` @@ -348,23 +414,37 @@ loki: query_scheduler: {} # -- Additional storage config storage_config: + boltdb_shipper: + index_gateway_client: + server_address: '{{ include "loki.indexGatewayAddress" . }}' + tsdb_shipper: + index_gateway_client: + server_address: '{{ include "loki.indexGatewayAddress" . }}' + bloom_shipper: + working_directory: /var/loki/data/bloomshipper hedging: at: "250ms" max_per_second: 20 up_to: 3 # -- Optional compactor configuration compactor: {} + # -- Optional pattern ingester configuration + pattern_ingester: + enabled: false # -- Optional analytics configuration analytics: {} # -- Optional querier configuration + query_range: {} + # -- Optional querier configuration querier: {} # -- Optional ingester configuration ingester: {} # -- Optional index gateway configuration index_gateway: - mode: ring + mode: simple frontend: scheduler_address: '{{ include "loki.querySchedulerAddress" . }}' + tail_proxy_url: '{{ include "loki.querierAddress" . }}' frontend_worker: scheduler_address: '{{ include "loki.querySchedulerAddress" . }}' # -- Optional distributor configuration @@ -372,11 +452,26 @@ loki: # -- Enable tracing tracing: enabled: false + bloom_build: + enabled: false + builder: + planner_address: '{{ include "loki.bloomPlannerAddress" . }}' + bloom_gateway: + enabled: false + client: + addresses: '{{ include "loki.bloomGatewayAddresses" . }}' +###################################################################################################################### +# +# Enterprise Loki Configs +# +###################################################################################################################### + +# -- Configuration for running Enterprise Loki enterprise: # Enable enterprise features, license must be provided enabled: false # Default verion of GEL to deploy - version: v1.8.6 + version: 3.1.1 # -- Optional name of the GEL cluster, otherwise will use .Release.Name # The cluster name must match what is in your GEL license cluster_name: null @@ -394,6 +489,8 @@ enterprise: externalLicenseName: null # -- Name of the external config secret to use externalConfigName: "" + # -- Use GEL gateway, if false will use the default nginx gateway + gelGateway: true # -- If enabled, the correct admin_client storage will be configured. If disabled while running enterprise, # make sure auth is set to `type: trust`, or that `auth_enabled` is set to `false`. adminApi: @@ -401,13 +498,9 @@ enterprise: # enterprise specific sections of the config.yaml file config: | {{- if .Values.enterprise.adminApi.enabled }} - {{- if or .Values.minio.enabled (eq .Values.loki.storage.type "s3") (eq .Values.loki.storage.type "gcs") (eq .Values.loki.storage.type "azure") }} admin_client: - storage: - s3: - bucket_name: {{ .Values.loki.storage.bucketNames.admin }} - {{- end }} - {{- end }} + {{ include "enterprise-logs.adminAPIStorageConfig" . | nindent 2 }} + {{ end }} auth: type: {{ .Values.enterprise.adminApi.enabled | ternary "enterprise" "trust" }} auth_enabled: {{ .Values.loki.auth_enabled }} @@ -447,6 +540,10 @@ enterprise: labels: {} # -- Additional annotations for the `tokengen` Job annotations: {} + # -- Affinity for tokengen Pods + affinity: {} + # -- Node selector for tokengen Pods + nodeSelector: {} # -- Tolerations for tokengen Job tolerations: [] # -- Additional volumes for Pods @@ -482,6 +579,12 @@ enterprise: labels: {} # -- Additional annotations for the `provisioner` Job annotations: {} + # -- Affinity for tokengen Pods + affinity: {} + # -- Node selector for tokengen Pods + nodeSelector: {} + # -- Tolerations for tokengen Pods + tolerations: [] # -- The name of the PriorityClass for provisioner Job priorityClassName: null # -- Run containers as user `enterprise-logs(uid=10001)` @@ -504,15 +607,111 @@ enterprise: pullPolicy: IfNotPresent # -- Volume mounts to add to the provisioner pods extraVolumeMounts: [] -# -- Options that may be necessary when performing a migration from another helm chart -migrate: - # -- When migrating from a distributed chart like loki-distributed or enterprise-logs - fromDistributed: - # -- Set to true if migrating from a distributed helm chart - enabled: false - # -- If migrating from a distributed service, provide the distributed deployment's - # memberlist service DNS so the new deployment can join its ring. - memberlistService: "" +# -- kubetclImage is used in the enterprise provisioner and tokengen jobs +kubectlImage: + # -- The Docker registry + registry: docker.io + # -- Docker image repository + repository: bitnami/kubectl + # -- Overrides the image tag whose default is the chart's appVersion + tag: null + # -- Overrides the image tag with an image digest + digest: null + # -- Docker image pull policy + pullPolicy: IfNotPresent +###################################################################################################################### +# +# Chart Testing +# +###################################################################################################################### + +# -- Section for configuring optional Helm test +test: + enabled: true + # -- Used to directly query the metrics endpoint of the canary for testing, this approach avoids needing prometheus for testing. + # This in a newer approach to using prometheusAddress such that tests do not have a dependency on prometheus + canaryServiceAddress: "http://loki-canary:3500/metrics" + # -- Address of the prometheus server to query for the test. This overrides any value set for canaryServiceAddress. + # This is kept for backward compatibility and may be removed in future releases. Previous value was 'http://prometheus:9090' + prometheusAddress: "" + # -- Number of times to retry the test before failing + timeout: 1m + # -- Additional labels for the test pods + labels: {} + # -- Additional annotations for test pods + annotations: {} + # -- Image to use for loki canary + image: + # -- The Docker registry + registry: docker.io + # -- Docker image repository + repository: grafana/loki-helm-test + # -- Overrides the image tag whose default is the chart's appVersion + tag: "ewelch-distributed-helm-chart-17db5ee" + # -- Overrides the image tag with an image digest + digest: null + # -- Docker image pull policy + pullPolicy: IfNotPresent +# The Loki canary pushes logs to and queries from this loki installation to test +# that it's working correctly +lokiCanary: + enabled: true + # -- If true, the canary will send directly to Loki via the address configured for verification -- + # -- If false, it will write to stdout and an Agent will be needed to scrape and send the logs -- + push: true + # -- The name of the label to look for at loki when doing the checks. + labelname: pod + # -- Additional annotations for the `loki-canary` Daemonset + annotations: {} + # -- Additional labels for each `loki-canary` pod + podLabels: {} + service: + # -- Annotations for loki-canary Service + annotations: {} + # -- Additional labels for loki-canary Service + labels: {} + # -- Additional CLI arguments for the `loki-canary' command + extraArgs: [] + # -- Environment variables to add to the canary pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the canary pods + extraEnvFrom: [] + # -- Volume mounts to add to the canary pods + extraVolumeMounts: [] + # -- Volumes to add to the canary pods + extraVolumes: [] + # -- Resource requests and limits for the canary + resources: {} + # -- DNS config for canary pods + dnsConfig: {} + # -- Node selector for canary pods + nodeSelector: {} + # -- Tolerations for canary pods + tolerations: [] + # -- The name of the PriorityClass for loki-canary pods + priorityClassName: null + # -- Image to use for loki canary + image: + # -- The Docker registry + registry: docker.io + # -- Docker image repository + repository: grafana/loki-canary + # -- Overrides the image tag whose default is the chart's appVersion + tag: null + # -- Overrides the image tag with an image digest + digest: null + # -- Docker image pull policy + pullPolicy: IfNotPresent + # -- Update strategy for the `loki-canary` Daemonset pods + updateStrategy: + type: RollingUpdate + rollingUpdate: + maxUnavailable: 1 +###################################################################################################################### +# +# Service Accounts and Kubernetes RBAC +# +###################################################################################################################### serviceAccount: # -- Specifies whether a ServiceAccount should be created create: true @@ -541,324 +740,1330 @@ rbac: # apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' # -- Whether to install RBAC in the namespace only or cluster-wide. Useful if you want to watch ConfigMap globally. namespaced: false -# -- Section for configuring optional Helm test -test: - enabled: true - # -- Address of the prometheus server to query for the test - prometheusAddress: "http://prometheus:9090" - # -- Number of times to retry the test before failing - timeout: 1m - # -- Additional labels for the test pods +###################################################################################################################### +# +# Network Policy configuration +# +###################################################################################################################### +networkPolicy: + # -- Specifies whether Network Policies should be created + enabled: false + # -- Specifies whether the policies created will be standard Network Policies (flavor: kubernetes) + # or Cilium Network Policies (flavor: cilium) + flavor: kubernetes + metrics: + # -- Specifies the Pods which are allowed to access the metrics port. + # As this is cross-namespace communication, you also need the namespaceSelector. + podSelector: {} + # -- Specifies the namespaces which are allowed to access the metrics port + namespaceSelector: {} + # -- Specifies specific network CIDRs which are allowed to access the metrics port. + # In case you use namespaceSelector, you also have to specify your kubelet networks here. + # The metrics ports are also used for probes. + cidrs: [] + ingress: + # -- Specifies the Pods which are allowed to access the http port. + # As this is cross-namespace communication, you also need the namespaceSelector. + podSelector: {} + # -- Specifies the namespaces which are allowed to access the http port + namespaceSelector: {} + alertmanager: + # -- Specify the alertmanager port used for alerting + port: 9093 + # -- Specifies the alertmanager Pods. + # As this is cross-namespace communication, you also need the namespaceSelector. + podSelector: {} + # -- Specifies the namespace the alertmanager is running in + namespaceSelector: {} + externalStorage: + # -- Specify the port used for external storage, e.g. AWS S3 + ports: [] + # -- Specifies specific network CIDRs you want to limit access to + cidrs: [] + discovery: + # -- (int) Specify the port used for discovery + port: null + # -- Specifies the Pods labels used for discovery. + # As this is cross-namespace communication, you also need the namespaceSelector. + podSelector: {} + # -- Specifies the namespace the discovery Pods are running in + namespaceSelector: {} + egressWorld: + # -- Enable additional cilium egress rules to external world for write, read and backend. + enabled: false + egressKubeApiserver: + # -- Enable additional cilium egress rules to kube-apiserver for backend. + enabled: false +###################################################################################################################### +# +# Global memberlist configuration +# +###################################################################################################################### + +# Configuration for the memberlist service +memberlist: + service: + publishNotReadyAddresses: false + annotations: {} +###################################################################################################################### +# +# adminAPI configuration, enterprise only. +# +###################################################################################################################### + +# -- Configuration for the `admin-api` target +adminApi: + # -- Define the amount of instances + replicas: 1 + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld + # -- Additional CLI arguments for the `admin-api` target + extraArgs: {} + # -- Additional labels for the `admin-api` Deployment labels: {} - # -- Additional annotations for test pods + # -- Additional annotations for the `admin-api` Deployment annotations: {} - # -- Image to use for loki canary + # -- Additional labels and annotations for the `admin-api` Service + service: + labels: {} + annotations: {} + # -- Run container as user `enterprise-logs(uid=10001)` + # `fsGroup` must not be specified, because these security options are applied + # on container level not on Pod level. + podSecurityContext: + runAsNonRoot: true + runAsGroup: 10001 + runAsUser: 10001 + containerSecurityContext: + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + allowPrivilegeEscalation: false + # -- Update strategy + strategy: + type: RollingUpdate + # -- Readiness probe + readinessProbe: + httpGet: + path: /ready + port: http-metrics + initialDelaySeconds: 45 + # -- Request and limit Kubernetes resources + # -- Values are defined in small.yaml and large.yaml + resources: {} + # -- Configure optional environment variables + env: [] + # -- Configure optional initContainers + initContainers: [] + # -- Conifgure optional extraContainers + extraContainers: [] + # -- Additional volumes for Pods + extraVolumes: [] + # -- Additional volume mounts for Pods + extraVolumeMounts: [] + # -- Affinity for admin-api Pods + affinity: {} + # -- Node selector for admin-api Pods + nodeSelector: {} + # -- Tolerations for admin-api Pods + tolerations: [] + # -- Grace period to allow the admin-api to shutdown before it is killed + terminationGracePeriodSeconds: 60 +###################################################################################################################### +# +# Gateway and Ingress +# +# By default this chart will deploy a Nginx container to act as a gateway which handles routing of traffic +# and can also do auth. +# +# If you would prefer you can optionally disable this and enable using k8s ingress to do the incoming routing. +# +###################################################################################################################### + +# Configuration for the gateway +gateway: + # -- Specifies whether the gateway should be enabled + enabled: true + # -- Number of replicas for the gateway + replicas: 1 + # -- Default container port + containerPort: 8080 + # -- Enable logging of 2xx and 3xx HTTP requests + verboseLogging: true + autoscaling: + # -- Enable autoscaling for the gateway + enabled: false + # -- Minimum autoscaling replicas for the gateway + minReplicas: 1 + # -- Maximum autoscaling replicas for the gateway + maxReplicas: 3 + # -- Target CPU utilisation percentage for the gateway + targetCPUUtilizationPercentage: 60 + # -- Target memory utilisation percentage for the gateway + targetMemoryUtilizationPercentage: + # -- See `kubectl explain deployment.spec.strategy` for more + # -- ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy + # -- Behavior policies while scaling. + behavior: {} + # scaleUp: + # stabilizationWindowSeconds: 300 + # policies: + # - type: Pods + # value: 1 + # periodSeconds: 60 + # scaleDown: + # stabilizationWindowSeconds: 300 + # policies: + # - type: Pods + # value: 1 + # periodSeconds: 180 + deploymentStrategy: + type: RollingUpdate image: - # -- The Docker registry + # -- The Docker registry for the gateway image registry: docker.io - # -- Docker image repository - repository: grafana/loki-helm-test - # -- Overrides the image tag whose default is the chart's appVersion - tag: null - # -- Overrides the image tag with an image digest + # -- The gateway image repository + repository: nginxinc/nginx-unprivileged + # -- The gateway image tag + tag: 1.27-alpine + # -- Overrides the gateway image tag with an image digest digest: null - # -- Docker image pull policy + # -- The gateway image pull policy pullPolicy: IfNotPresent -# Monitoring section determines which monitoring features to enable -monitoring: - # Dashboards for monitoring Loki - dashboards: - # -- If enabled, create configmap with dashboards for monitoring Loki - enabled: true - # -- Alternative namespace to create dashboards ConfigMap in - namespace: null - # -- Additional annotations for the dashboards ConfigMap - annotations: {} - # -- Labels for the dashboards ConfigMap - labels: - grafana_dashboard: "1" - # Recording rules for monitoring Loki, required for some dashboards - rules: - # -- If enabled, create PrometheusRule resource with Loki recording rules + # -- The name of the PriorityClass for gateway pods + priorityClassName: null + # -- Annotations for gateway deployment + annotations: {} + # -- Annotations for gateway pods + podAnnotations: {} + # -- Additional labels for gateway pods + podLabels: {} + # -- Additional CLI args for the gateway + extraArgs: [] + # -- Environment variables to add to the gateway pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the gateway pods + extraEnvFrom: [] + # -- Lifecycle for the gateway container + lifecycle: {} + # -- Volumes to add to the gateway pods + extraVolumes: [] + # -- Volume mounts to add to the gateway pods + extraVolumeMounts: [] + # -- The SecurityContext for gateway containers + podSecurityContext: + fsGroup: 101 + runAsGroup: 101 + runAsNonRoot: true + runAsUser: 101 + # -- The SecurityContext for gateway containers + containerSecurityContext: + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + allowPrivilegeEscalation: false + # -- Resource requests and limits for the gateway + resources: {} + # -- Containers to add to the gateway pods + extraContainers: [] + # -- Grace period to allow the gateway to shutdown before it is killed + terminationGracePeriodSeconds: 30 + # -- Affinity for gateway pods. + # @default -- Hard node anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: gateway + topologyKey: kubernetes.io/hostname + # -- DNS config for gateway pods + dnsConfig: {} + # -- Node selector for gateway pods + nodeSelector: {} + # -- Topology Spread Constraints for gateway pods + topologySpreadConstraints: [] + # -- Tolerations for gateway pods + tolerations: [] + # Gateway service configuration + service: + # -- Port of the gateway service + port: 80 + # -- Type of the gateway service + type: ClusterIP + # -- ClusterIP of the gateway service + clusterIP: null + # -- (int) Node port if service type is NodePort + nodePort: null + # -- Load balancer IPO address if service type is LoadBalancer + loadBalancerIP: null + # -- Annotations for the gateway service + annotations: {} + # -- Labels for gateway service + labels: {} + # Gateway ingress configuration + ingress: + # -- Specifies whether an ingress for the gateway should be created + enabled: false + # -- Ingress Class Name. MAY be required for Kubernetes versions >= 1.18 + ingressClassName: "" + # -- Annotations for the gateway ingress + annotations: {} + # -- Labels for the gateway ingress + labels: {} + # -- Hosts configuration for the gateway ingress, passed through the `tpl` function to allow templating + hosts: + - host: gateway.loki.example.com + paths: + - path: / + # -- pathType (e.g. ImplementationSpecific, Prefix, .. etc.) might also be required by some Ingress Controllers + # pathType: Prefix + # -- TLS configuration for the gateway ingress. Hosts passed through the `tpl` function to allow templating + tls: + - secretName: loki-gateway-tls + hosts: + - gateway.loki.example.com + # Basic auth configuration + basicAuth: + # -- Enables basic authentication for the gateway + enabled: false + # -- The basic auth username for the gateway + username: null + # -- The basic auth password for the gateway + password: null + # -- Uses the specified users from the `loki.tenants` list to create the htpasswd file. + # if `loki.tenants` is not set, the `gateway.basicAuth.username` and `gateway.basicAuth.password` are used. + # The value is templated using `tpl`. Override this to use a custom htpasswd, e.g. in case the default causes + # high CPU load. + # @default -- Either `loki.tenants` or `gateway.basicAuth.username` and `gateway.basicAuth.password`. + htpasswd: >- + {{ if .Values.loki.tenants }} + + + {{- range $t := .Values.loki.tenants }} + {{ htpasswd (required "All tenants must have a 'name' set" $t.name) (required "All tenants must have a 'password' set" $t.password) }} + + + {{- end }} + {{ else }} {{ htpasswd (required "'gateway.basicAuth.username' is required" .Values.gateway.basicAuth.username) (required "'gateway.basicAuth.password' is required" .Values.gateway.basicAuth.password) }} {{ end }} + # -- Existing basic auth secret to use. Must contain '.htpasswd' + existingSecret: null + # Configures the readiness probe for the gateway + readinessProbe: + httpGet: + path: / + port: http-metrics + initialDelaySeconds: 15 + timeoutSeconds: 1 + nginxConfig: + # -- Which schema to be used when building URLs. Can be 'http' or 'https'. + schema: http + # -- Enable listener for IPv6, disable on IPv4-only systems + enableIPv6: true + # -- NGINX log format + logFormat: |- + main '$remote_addr - $remote_user [$time_local] $status ' + '"$request" $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + # -- Allows appending custom configuration to the server block + serverSnippet: "" + # -- Allows appending custom configuration to the http block, passed through the `tpl` function to allow templating + httpSnippet: >- + {{ if .Values.loki.tenants }}proxy_set_header X-Scope-OrgID $remote_user;{{ end }} + # -- Whether ssl should be appended to the listen directive of the server block or not. + ssl: false + # -- Override Read URL + customReadUrl: null + # -- Override Write URL + customWriteUrl: null + # -- Override Backend URL + customBackendUrl: null + # -- Allows overriding the DNS resolver address nginx will use. + resolver: "" + # -- Config file contents for Nginx. Passed through the `tpl` function to allow templating + # @default -- See values.yaml + file: | + {{- include "loki.nginxFile" . | indent 2 -}} +# -- If running enterprise and using the default enterprise gateway, configs go here. +enterpriseGateway: + # -- Define the amount of instances + replicas: 1 + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld + # -- Additional CLI arguments for the `gateway` target + extraArgs: {} + # -- Additional labels for the `gateway` Pod + labels: {} + # -- Additional annotations for the `gateway` Pod + annotations: {} + # -- Additional labels and annotations for the `gateway` Service + # -- Service overriding service type + service: + type: ClusterIP + labels: {} + annotations: {} + # -- Run container as user `enterprise-logs(uid=10001)` + podSecurityContext: + runAsNonRoot: true + runAsGroup: 10001 + runAsUser: 10001 + fsGroup: 10001 + containerSecurityContext: + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + allowPrivilegeEscalation: false + # -- If you want to use your own proxy URLs, set this to false. + useDefaultProxyURLs: true + # -- update strategy + strategy: + type: RollingUpdate + # -- Readiness probe + readinessProbe: + httpGet: + path: /ready + port: http-metrics + initialDelaySeconds: 45 + # -- Request and limit Kubernetes resources + # -- Values are defined in small.yaml and large.yaml + resources: {} + # -- Configure optional environment variables + env: [] + # -- Configure optional initContainers + initContainers: [] + # -- Conifgure optional extraContainers + extraContainers: [] + # -- Additional volumes for Pods + extraVolumes: [] + # -- Additional volume mounts for Pods + extraVolumeMounts: [] + # -- Affinity for gateway Pods + affinity: {} + # -- Node selector for gateway Pods + nodeSelector: {} + # -- Tolerations for gateway Pods + tolerations: [] + # -- Grace period to allow the gateway to shutdown before it is killed + terminationGracePeriodSeconds: 60 +# -- Ingress configuration Use either this ingress or the gateway, but not both at once. +# If you enable this, make sure to disable the gateway. +# You'll need to supply authn configuration for your ingress controller. +ingress: + enabled: false + ingressClassName: "" + annotations: {} + # nginx.ingress.kubernetes.io/auth-type: basic + # nginx.ingress.kubernetes.io/auth-secret: loki-distributed-basic-auth + # nginx.ingress.kubernetes.io/auth-secret-type: auth-map + # nginx.ingress.kubernetes.io/configuration-snippet: | + # proxy_set_header X-Scope-OrgID $remote_user; + labels: {} + # blackbox.monitoring.exclude: "true" + paths: + # -- Paths that are exposed by Loki Distributor. + # If deployment mode is Distributed, the requests are forwarded to the service: `{{"loki.distributorFullname"}}`. + # If deployment mode is SimpleScalable, the requests are forwarded to write k8s service: `{{"loki.writeFullname"}}`. + # If deployment mode is SingleBinary, the requests are forwarded to the central/single k8s service: `{{"loki.singleBinaryFullname"}}` + distributor: + - /api/prom/push + - /loki/api/v1/push + - /otlp/v1/logs + # -- Paths that are exposed by Loki Query Frontend. + # If deployment mode is Distributed, the requests are forwarded to the service: `{{"loki.queryFrontendFullname"}}`. + # If deployment mode is SimpleScalable, the requests are forwarded to write k8s service: `{{"loki.readFullname"}}`. + # If deployment mode is SingleBinary, the requests are forwarded to the central/single k8s service: `{{"loki.singleBinaryFullname"}}` + queryFrontend: + - /api/prom/query + # this path covers labels and labelValues endpoints + - /api/prom/label + - /api/prom/series + - /api/prom/tail + - /loki/api/v1/query + - /loki/api/v1/query_range + - /loki/api/v1/tail + # this path covers labels and labelValues endpoints + - /loki/api/v1/label + - /loki/api/v1/labels + - /loki/api/v1/series + - /loki/api/v1/index/stats + - /loki/api/v1/index/volume + - /loki/api/v1/index/volume_range + - /loki/api/v1/format_query + - /loki/api/v1/detected_fields + - /loki/api/v1/detected_labels + - /loki/api/v1/patterns + # -- Paths that are exposed by Loki Ruler. + # If deployment mode is Distributed, the requests are forwarded to the service: `{{"loki.rulerFullname"}}`. + # If deployment mode is SimpleScalable, the requests are forwarded to k8s service: `{{"loki.backendFullname"}}`. + # If deployment mode is SimpleScalable but `read.legacyReadTarget` is `true`, the requests are forwarded to k8s service: `{{"loki.readFullname"}}`. + # If deployment mode is SingleBinary, the requests are forwarded to the central/single k8s service: `{{"loki.singleBinaryFullname"}}` + ruler: + - /api/prom/rules + - /api/prom/api/v1/rules + - /api/prom/api/v1/alerts + - /loki/api/v1/rules + - /prometheus/api/v1/rules + - /prometheus/api/v1/alerts + # -- Hosts configuration for the ingress, passed through the `tpl` function to allow templating + hosts: + - loki.example.com + # -- TLS configuration for the ingress. Hosts passed through the `tpl` function to allow templating + tls: [] +# - hosts: +# - loki.example.com +# secretName: loki-distributed-tls + +###################################################################################################################### +# +# Migration +# +###################################################################################################################### + +# -- Options that may be necessary when performing a migration from another helm chart +migrate: + # -- When migrating from a distributed chart like loki-distributed or enterprise-logs + fromDistributed: + # -- Set to true if migrating from a distributed helm chart + enabled: false + # -- If migrating from a distributed service, provide the distributed deployment's + # memberlist service DNS so the new deployment can join its ring. + memberlistService: "" +###################################################################################################################### +# +# Single Binary Deployment +# +# For small Loki installations up to a few 10's of GB per day, or for testing and development. +# +###################################################################################################################### + +# Configuration for the single binary node(s) +singleBinary: + # -- Number of replicas for the single binary + replicas: 0 + autoscaling: + # -- Enable autoscaling + enabled: false + # -- Minimum autoscaling replicas for the single binary + minReplicas: 1 + # -- Maximum autoscaling replicas for the single binary + maxReplicas: 3 + # -- Target CPU utilisation percentage for the single binary + targetCPUUtilizationPercentage: 60 + # -- Target memory utilisation percentage for the single binary + targetMemoryUtilizationPercentage: + image: + # -- The Docker registry for the single binary image. Overrides `loki.image.registry` + registry: null + # -- Docker image repository for the single binary image. Overrides `loki.image.repository` + repository: null + # -- Docker image tag for the single binary image. Overrides `loki.image.tag` + tag: null + # -- The name of the PriorityClass for single binary pods + priorityClassName: null + # -- Annotations for single binary StatefulSet + annotations: {} + # -- Annotations for single binary pods + podAnnotations: {} + # -- Additional labels for each `single binary` pod + podLabels: {} + # -- Additional selector labels for each `single binary` pod + selectorLabels: {} + service: + # -- Annotations for single binary Service + annotations: {} + # -- Additional labels for single binary Service + labels: {} + # -- Comma-separated list of Loki modules to load for the single binary + targetModule: "all" + # -- Labels for single binary service + extraArgs: [] + # -- Environment variables to add to the single binary pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the single binary pods + extraEnvFrom: [] + # -- Extra containers to add to the single binary loki pod + extraContainers: [] + # -- Init containers to add to the single binary pods + initContainers: [] + # -- Volume mounts to add to the single binary pods + extraVolumeMounts: [] + # -- Volumes to add to the single binary pods + extraVolumes: [] + # -- Resource requests and limits for the single binary + resources: {} + # -- Grace period to allow the single binary to shutdown before it is killed + terminationGracePeriodSeconds: 30 + # -- Affinity for single binary pods. + # @default -- Hard node anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: single-binary + topologyKey: kubernetes.io/hostname + # -- DNS config for single binary pods + dnsConfig: {} + # -- Node selector for single binary pods + nodeSelector: {} + # -- Tolerations for single binary pods + tolerations: [] + persistence: + # -- Enable StatefulSetAutoDeletePVC feature + enableStatefulSetAutoDeletePVC: true + # -- Enable persistent disk enabled: true - # -- Include alerting rules - alerting: true - # -- Specify which individual alerts should be disabled - # -- Instead of turning off each alert one by one, set the .monitoring.rules.alerting value to false instead. - # -- If you disable all the alerts and keep .monitoring.rules.alerting set to true, the chart will fail to render. - disabled: {} - # LokiRequestErrors: true - # LokiRequestPanics: true - # -- Alternative namespace to create PrometheusRule resources in - namespace: null - # -- Additional annotations for the rules PrometheusRule resource + # -- Size of persistent disk + size: 10Gi + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null + # -- Selector for persistent disk + selector: null + # -- Annotations for volume claim + annotations: {} +###################################################################################################################### +# +# Simple Scalable Deployment (SSD) Mode +# +# For small to medium size Loki deployments up to around 1 TB/day, this is the default mode for this helm chart +# +###################################################################################################################### + +# Configuration for the write pod(s) +write: + # -- Number of replicas for the write + replicas: 3 + autoscaling: + # -- Enable autoscaling for the write. + enabled: false + # -- Minimum autoscaling replicas for the write. + minReplicas: 2 + # -- Maximum autoscaling replicas for the write. + maxReplicas: 6 + # -- Target CPU utilisation percentage for the write. + targetCPUUtilizationPercentage: 60 + # -- Target memory utilization percentage for the write. + targetMemoryUtilizationPercentage: + # -- Behavior policies while scaling. + behavior: + # -- see https://github.com/grafana/loki/blob/main/docs/sources/operations/storage/wal.md#how-to-scale-updown for scaledown details + scaleUp: + policies: + - type: Pods + value: 1 + periodSeconds: 900 + scaleDown: + policies: + - type: Pods + value: 1 + periodSeconds: 1800 + stabilizationWindowSeconds: 3600 + image: + # -- The Docker registry for the write image. Overrides `loki.image.registry` + registry: null + # -- Docker image repository for the write image. Overrides `loki.image.repository` + repository: null + # -- Docker image tag for the write image. Overrides `loki.image.tag` + tag: null + # -- The name of the PriorityClass for write pods + priorityClassName: null + # -- Annotations for write StatefulSet + annotations: {} + # -- Annotations for write pods + podAnnotations: {} + # -- Additional labels for each `write` pod + podLabels: {} + # -- Additional selector labels for each `write` pod + selectorLabels: {} + service: + # -- Annotations for write Service annotations: {} - # -- Additional labels for the rules PrometheusRule resource + # -- Additional labels for write Service labels: {} - # -- Additional labels for PrometheusRule alerts - additionalRuleLabels: {} - # -- Additional groups to add to the rules file - additionalGroups: [] - # - name: additional-loki-rules - # rules: - # - record: job:loki_request_duration_seconds_bucket:sum_rate - # expr: sum(rate(loki_request_duration_seconds_bucket[1m])) by (le, job) - # - record: job_route:loki_request_duration_seconds_bucket:sum_rate - # expr: sum(rate(loki_request_duration_seconds_bucket[1m])) by (le, job, route) - # - record: node_namespace_pod_container:container_cpu_usage_seconds_total:sum_rate - # expr: sum(rate(container_cpu_usage_seconds_total[1m])) by (node, namespace, pod, container) - # ServiceMonitor configuration - serviceMonitor: - # -- If enabled, ServiceMonitor resources for Prometheus Operator are created - enabled: true - # -- Namespace selector for ServiceMonitor resources - namespaceSelector: {} - # -- ServiceMonitor annotations + # -- Comma-separated list of Loki modules to load for the write + targetModule: "write" + # -- Additional CLI args for the write + extraArgs: [] + # -- Environment variables to add to the write pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the write pods + extraEnvFrom: [] + # -- Lifecycle for the write container + lifecycle: {} + # -- The default /flush_shutdown preStop hook is recommended as part of the ingester + # scaledown process so it's added to the template by default when autoscaling is enabled, + # but it's disabled to optimize rolling restarts in instances that will never be scaled + # down or when using chunks storage with WAL disabled. + # https://github.com/grafana/loki/blob/main/docs/sources/operations/storage/wal.md#how-to-scale-updown + # -- Init containers to add to the write pods + initContainers: [] + # -- Containers to add to the write pods + extraContainers: [] + # -- Volume mounts to add to the write pods + extraVolumeMounts: [] + # -- Volumes to add to the write pods + extraVolumes: [] + # -- volumeClaimTemplates to add to StatefulSet + extraVolumeClaimTemplates: [] + # -- Resource requests and limits for the write + resources: {} + # -- Grace period to allow the write to shutdown before it is killed. Especially for the ingester, + # this must be increased. It must be long enough so writes can be gracefully shutdown flushing/transferring + # all data and to successfully leave the member ring on shutdown. + terminationGracePeriodSeconds: 300 + # -- Affinity for write pods. + # @default -- Hard node anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: write + topologyKey: kubernetes.io/hostname + # -- DNS config for write pods + dnsConfig: {} + # -- Node selector for write pods + nodeSelector: {} + # -- Topology Spread Constraints for write pods + topologySpreadConstraints: [] + # -- Tolerations for write pods + tolerations: [] + # -- The default is to deploy all pods in parallel. + podManagementPolicy: "Parallel" + persistence: + # -- Enable volume claims in pod spec + volumeClaimsEnabled: true + # -- Parameters used for the `data` volume when volumeClaimEnabled if false + dataVolumeParameters: + emptyDir: {} + # -- Enable StatefulSetAutoDeletePVC feature + enableStatefulSetAutoDeletePVC: false + # -- Size of persistent disk + size: 10Gi + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null + # -- Selector for persistent disk + selector: null + # -- Annotations for volume claim annotations: {} - # -- Additional ServiceMonitor labels +# -- Configuration for the read pod(s) +read: + # -- Number of replicas for the read + replicas: 3 + autoscaling: + # -- Enable autoscaling for the read, this is only used if `queryIndex.enabled: true` + enabled: false + # -- Minimum autoscaling replicas for the read + minReplicas: 2 + # -- Maximum autoscaling replicas for the read + maxReplicas: 6 + # -- Target CPU utilisation percentage for the read + targetCPUUtilizationPercentage: 60 + # -- Target memory utilisation percentage for the read + targetMemoryUtilizationPercentage: + # -- Behavior policies while scaling. + behavior: {} + # scaleUp: + # stabilizationWindowSeconds: 300 + # policies: + # - type: Pods + # value: 1 + # periodSeconds: 60 + # scaleDown: + # stabilizationWindowSeconds: 300 + # policies: + # - type: Pods + # value: 1 + # periodSeconds: 180 + image: + # -- The Docker registry for the read image. Overrides `loki.image.registry` + registry: null + # -- Docker image repository for the read image. Overrides `loki.image.repository` + repository: null + # -- Docker image tag for the read image. Overrides `loki.image.tag` + tag: null + # -- The name of the PriorityClass for read pods + priorityClassName: null + # -- Annotations for read deployment + annotations: {} + # -- Annotations for read pods + podAnnotations: {} + # -- Additional labels for each `read` pod + podLabels: {} + # -- Additional selector labels for each `read` pod + selectorLabels: {} + service: + # -- Annotations for read Service + annotations: {} + # -- Additional labels for read Service labels: {} - # -- ServiceMonitor scrape interval - # Default is 15s because included recording rules use a 1m rate, and scrape interval needs to be at - # least 1/4 rate interval. - interval: 15s - # -- ServiceMonitor scrape timeout in Go duration format (e.g. 15s) - scrapeTimeout: null - # -- ServiceMonitor relabel configs to apply to samples before scraping - # https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#relabelconfig - relabelings: [] - # -- ServiceMonitor metric relabel configs to apply to samples before ingestion - # https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#endpoint - metricRelabelings: [] - # -- ServiceMonitor will use http by default, but you can pick https as well - scheme: http - # -- ServiceMonitor will use these tlsConfig settings to make the health check requests - tlsConfig: null - # -- If defined, will create a MetricsInstance for the Grafana Agent Operator. - metricsInstance: - # -- If enabled, MetricsInstance resources for Grafana Agent Operator are created - enabled: true - # -- MetricsInstance annotations - annotations: {} - # -- Additional MetricsInstance labels - labels: {} - # -- If defined a MetricsInstance will be created to remote write metrics. - remoteWrite: null - # Self monitoring determines whether Loki should scrape its own logs. - # This feature currently relies on the Grafana Agent Operator being installed, - # which is installed by default using the grafana-agent-operator sub-chart. - # It will create custom resources for GrafanaAgent, LogsInstance, and PodLogs to configure - # scrape configs to scrape its own logs with the labels expected by the included dashboards. - selfMonitoring: - enabled: true - # -- Tenant to use for self monitoring - tenant: - # -- Name of the tenant - name: "self-monitoring" - # -- Namespace to create additional tenant token secret in. Useful if your Grafana instance - # is in a separate namespace. Token will still be created in the canary namespace. - secretNamespace: "{{ .Release.Namespace }}" - # Grafana Agent configuration - grafanaAgent: - # -- Controls whether to install the Grafana Agent Operator and its CRDs. - # Note that helm will not install CRDs if this flag is enabled during an upgrade. - # In that case install the CRDs manually from https://github.com/grafana/agent/tree/main/production/operator/crds - installOperator: true - # -- Grafana Agent annotations - annotations: {} - # -- Additional Grafana Agent labels - labels: {} - # -- Enable the config read api on port 8080 of the agent - enableConfigReadAPI: false - # -- The name of the PriorityClass for GrafanaAgent pods - priorityClassName: null - # -- Resource requests and limits for the grafanaAgent pods - resources: {} - # limits: - # memory: 200Mi - # requests: - # cpu: 50m - # memory: 100Mi - # -- Tolerations for GrafanaAgent pods - tolerations: [] - # PodLogs configuration - podLogs: - # -- PodLogs version - apiVersion: monitoring.grafana.com/v1alpha1 - # -- PodLogs annotations - annotations: {} - # -- Additional PodLogs labels - labels: {} - # -- PodLogs relabel configs to apply to samples before scraping - # https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#relabelconfig - relabelings: [] - # -- Additional pipeline stages to process logs after scraping - # https://grafana.com/docs/agent/latest/operator/api/#pipelinestagespec-a-namemonitoringgrafanacomv1alpha1pipelinestagespeca - additionalPipelineStages: [] - # LogsInstance configuration - logsInstance: - # -- LogsInstance annotations - annotations: {} - # -- Additional LogsInstance labels - labels: {} - # -- Additional clients for remote write - clients: null - # The Loki canary pushes logs to and queries from this loki installation to test - # that it's working correctly - lokiCanary: - enabled: true - # -- The name of the label to look for at loki when doing the checks. - labelname: pod - # -- Additional annotations for the `loki-canary` Daemonset + # -- Comma-separated list of Loki modules to load for the read + targetModule: "read" + # -- Whether or not to use the 2 target type simple scalable mode (read, write) or the + # 3 target type (read, write, backend). Legacy refers to the 2 target type, so true will + # run two targets, false will run 3 targets. + legacyReadTarget: false + # -- Additional CLI args for the read + extraArgs: [] + # -- Containers to add to the read pods + extraContainers: [] + # -- Environment variables to add to the read pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the read pods + extraEnvFrom: [] + # -- Lifecycle for the read container + lifecycle: {} + # -- Volume mounts to add to the read pods + extraVolumeMounts: [] + # -- Volumes to add to the read pods + extraVolumes: [] + # -- Resource requests and limits for the read + resources: {} + # -- Grace period to allow the read to shutdown before it is killed + terminationGracePeriodSeconds: 30 + # -- Affinity for read pods. + # @default -- Hard node anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: read + topologyKey: kubernetes.io/hostname + # -- DNS config for read pods + dnsConfig: {} + # -- Node selector for read pods + nodeSelector: {} + # -- Topology Spread Constraints for read pods + topologySpreadConstraints: [] + # -- Tolerations for read pods + tolerations: [] + # -- The default is to deploy all pods in parallel. + podManagementPolicy: "Parallel" + persistence: + # -- Enable StatefulSetAutoDeletePVC feature + enableStatefulSetAutoDeletePVC: true + # -- Size of persistent disk + size: 10Gi + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null + # -- Selector for persistent disk + selector: null + # -- Annotations for volume claim annotations: {} - # -- Additional labels for each `loki-canary` pod - podLabels: {} - service: - # -- Annotations for loki-canary Service - annotations: {} - # -- Additional labels for loki-canary Service - labels: {} - # -- Additional CLI arguments for the `loki-canary' command - extraArgs: [] - # -- Environment variables to add to the canary pods - extraEnv: [] - # -- Environment variables from secrets or configmaps to add to the canary pods - extraEnvFrom: [] - # -- Resource requests and limits for the canary - resources: {} - # -- DNS config for canary pods - dnsConfig: {} - # -- Node selector for canary pods - nodeSelector: {} - # -- Tolerations for canary pods - tolerations: [] - # -- The name of the PriorityClass for loki-canary pods - priorityClassName: null - # -- Image to use for loki canary - image: - # -- The Docker registry - registry: docker.io - # -- Docker image repository - repository: grafana/loki-canary - # -- Overrides the image tag whose default is the chart's appVersion - tag: null - # -- Overrides the image tag with an image digest - digest: null - # -- Docker image pull policy - pullPolicy: IfNotPresent - # -- Update strategy for the `loki-canary` Daemonset pods - updateStrategy: - type: RollingUpdate - rollingUpdate: - maxUnavailable: 1 -# Configuration for the write pod(s) -write: - # -- Number of replicas for the write +# -- Configuration for the backend pod(s) +backend: + # -- Number of replicas for the backend replicas: 3 autoscaling: - # -- Enable autoscaling for the write. + # -- Enable autoscaling for the backend. enabled: false - # -- Minimum autoscaling replicas for the write. - minReplicas: 2 - # -- Maximum autoscaling replicas for the write. + # -- Minimum autoscaling replicas for the backend. + minReplicas: 3 + # -- Maximum autoscaling replicas for the backend. maxReplicas: 6 - # -- Target CPU utilisation percentage for the write. + # -- Target CPU utilization percentage for the backend. targetCPUUtilizationPercentage: 60 - # -- Target memory utilization percentage for the write. + # -- Target memory utilization percentage for the backend. targetMemoryUtilizationPercentage: # -- Behavior policies while scaling. - behavior: - # -- see https://github.com/grafana/loki/blob/main/docs/sources/operations/storage/wal.md#how-to-scale-updown for scaledown details - scaleUp: - policies: - - type: Pods - value: 1 - periodSeconds: 900 - scaleDown: - policies: - - type: Pods - value: 1 - periodSeconds: 1800 - stabilizationWindowSeconds: 3600 + behavior: {} + # scaleUp: + # stabilizationWindowSeconds: 300 + # policies: + # - type: Pods + # value: 1 + # periodSeconds: 60 + # scaleDown: + # stabilizationWindowSeconds: 300 + # policies: + # - type: Pods + # value: 1 + # periodSeconds: 180 image: - # -- The Docker registry for the write image. Overrides `loki.image.registry` + # -- The Docker registry for the backend image. Overrides `loki.image.registry` registry: null - # -- Docker image repository for the write image. Overrides `loki.image.repository` + # -- Docker image repository for the backend image. Overrides `loki.image.repository` repository: null - # -- Docker image tag for the write image. Overrides `loki.image.tag` + # -- Docker image tag for the backend image. Overrides `loki.image.tag` tag: null - # -- The name of the PriorityClass for write pods + # -- The name of the PriorityClass for backend pods priorityClassName: null - # -- Annotations for write StatefulSet + # -- Annotations for backend StatefulSet annotations: {} - # -- Annotations for write pods + # -- Annotations for backend pods podAnnotations: {} - # -- Additional labels for each `write` pod + # -- Additional labels for each `backend` pod podLabels: {} - # -- Additional selector labels for each `write` pod + # -- Additional selector labels for each `backend` pod selectorLabels: {} service: - # -- Annotations for write Service + # -- Annotations for backend Service annotations: {} - # -- Additional labels for write Service + # -- Additional labels for backend Service labels: {} - # -- Comma-separated list of Loki modules to load for the write - targetModule: "write" - # -- Additional CLI args for the write + # -- Comma-separated list of Loki modules to load for the backend + targetModule: "backend" + # -- Additional CLI args for the backend + extraArgs: [] + # -- Environment variables to add to the backend pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the backend pods + extraEnvFrom: [] + # -- Init containers to add to the backend pods + initContainers: [] + # -- Volume mounts to add to the backend pods + extraVolumeMounts: [] + # -- Volumes to add to the backend pods + extraVolumes: [] + # -- Resource requests and limits for the backend + resources: {} + # -- Grace period to allow the backend to shutdown before it is killed. Especially for the ingester, + # this must be increased. It must be long enough so backends can be gracefully shutdown flushing/transferring + # all data and to successfully leave the member ring on shutdown. + terminationGracePeriodSeconds: 300 + # -- Affinity for backend pods. + # @default -- Hard node anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: backend + topologyKey: kubernetes.io/hostname + # -- DNS config for backend pods + dnsConfig: {} + # -- Node selector for backend pods + nodeSelector: {} + # -- Topology Spread Constraints for backend pods + topologySpreadConstraints: [] + # -- Tolerations for backend pods + tolerations: [] + # -- The default is to deploy all pods in parallel. + podManagementPolicy: "Parallel" + persistence: + # -- Enable volume claims in pod spec + volumeClaimsEnabled: true + # -- Parameters used for the `data` volume when volumeClaimEnabled if false + dataVolumeParameters: + emptyDir: {} + # -- Enable StatefulSetAutoDeletePVC feature + enableStatefulSetAutoDeletePVC: true + # -- Size of persistent disk + size: 10Gi + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null + # -- Selector for persistent disk + selector: null + # -- Annotations for volume claim + annotations: {} +###################################################################################################################### +# +# Microservices Mode +# +# For large Loki deployments ingesting more than 1 TB/day +# +###################################################################################################################### + +# -- Configuration for the ingester +ingester: + # -- Number of replicas for the ingester, when zoneAwareReplication.enabled is true, the total + # number of replicas will match this value with each zone having 1/3rd of the total replicas. + replicas: 0 + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld + autoscaling: + # -- Enable autoscaling for the ingester + enabled: false + # -- Minimum autoscaling replicas for the ingester + minReplicas: 1 + # -- Maximum autoscaling replicas for the ingester + maxReplicas: 3 + # -- Target CPU utilisation percentage for the ingester + targetCPUUtilizationPercentage: 60 + # -- Target memory utilisation percentage for the ingester + targetMemoryUtilizationPercentage: null + # -- Allows one to define custom metrics using the HPA/v2 schema (for example, Pods, Object or External metrics) + customMetrics: [] + # - type: Pods + # pods: + # metric: + # name: loki_lines_total + # target: + # type: AverageValue + # averageValue: 10k + behavior: + # -- Enable autoscaling behaviours + enabled: false + # -- define scale down policies, must conform to HPAScalingRules + scaleDown: {} + # -- define scale up policies, must conform to HPAScalingRules + scaleUp: {} + image: + # -- The Docker registry for the ingester image. Overrides `loki.image.registry` + registry: null + # -- Docker image repository for the ingester image. Overrides `loki.image.repository` + repository: null + # -- Docker image tag for the ingester image. Overrides `loki.image.tag` + tag: null + # -- Command to execute instead of defined in Docker image + command: null + priorityClassName: null + # -- Labels for ingester pods + podLabels: {} + # -- Annotations for ingester pods + podAnnotations: {} + # -- The name of the PriorityClass for ingester pods + # -- Labels for ingestor service + serviceLabels: {} + # -- Annotations for ingestor service + serviceAnnotations: {} + # -- Additional CLI args for the ingester + extraArgs: [] + # -- Environment variables to add to the ingester pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the ingester pods + extraEnvFrom: [] + # -- Volume mounts to add to the ingester pods + extraVolumeMounts: [] + # -- Volumes to add to the ingester pods + extraVolumes: [] + # -- Resource requests and limits for the ingester + resources: {} + # -- Containers to add to the ingester pods + extraContainers: [] + # -- Init containers to add to the ingester pods + initContainers: [] + # -- Grace period to allow the ingester to shutdown before it is killed. Especially for the ingestor, + # this must be increased. It must be long enough so ingesters can be gracefully shutdown flushing/transferring + # all data and to successfully leave the member ring on shutdown. + terminationGracePeriodSeconds: 300 + # -- Lifecycle for the ingester container + lifecycle: {} + # -- topologySpread for ingester pods. + # @default -- Defaults to allow skew no more than 1 node + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/component: ingester + # -- Affinity for ingester pods. Ignored if zoneAwareReplication is enabled. + # @default -- Hard node anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: ingester + topologyKey: kubernetes.io/hostname + # -- Pod Disruption Budget maxUnavailable + maxUnavailable: 1 + # -- Node selector for ingester pods + nodeSelector: {} + # -- Tolerations for ingester pods + tolerations: [] + # -- readiness probe settings for ingester pods. If empty, use `loki.readinessProbe` + readinessProbe: {} + # -- liveness probe settings for ingester pods. If empty use `loki.livenessProbe` + livenessProbe: {} + persistence: + # -- Enable creating PVCs which is required when using boltdb-shipper + enabled: false + # -- Use emptyDir with ramdisk for storage. **Please note that all data in ingester will be lost on pod restart** + inMemory: false + # -- List of the ingester PVCs + # @notationType -- list + claims: + - name: data + size: 10Gi + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null + # - name: wal + # size: 150Gi + # -- Enable StatefulSetAutoDeletePVC feature + enableStatefulSetAutoDeletePVC: false + whenDeleted: Retain + whenScaled: Retain + # -- Adds the appProtocol field to the ingester service. This allows ingester to work with istio protocol selection. + appProtocol: + # -- Set the optional grpc service protocol. Ex: "grpc", "http2" or "https" + grpc: "" + # -- Enabling zone awareness on ingesters will create 3 statefulests where all writes will send a replica to each zone. + # This is primarily intended to accelerate rollout operations by allowing for multiple ingesters within a single + # zone to be shutdown and restart simultaneously (the remaining 2 zones will be guaranteed to have at least one copy + # of the data). + # Note: This can be used to run Loki over multiple cloud provider availability zones however this is not currently + # recommended as Loki is not optimized for this and cross zone network traffic costs can become extremely high + # extremely quickly. Even with zone awareness enabled, it is recommended to run Loki in a single availability zone. + zoneAwareReplication: + # -- Enable zone awareness. + enabled: true + # -- The percent of replicas in each zone that will be restarted at once. In a value of 0-100 + maxUnavailablePct: 33 + # -- zoneA configuration + zoneA: + # -- optionally define a node selector for this zone + nodeSelector: null + # -- optionally define extra affinity rules, by default different zones are not allowed to schedule on the same host + extraAffinity: {} + # -- Specific annotations to add to zone A statefulset + annotations: {} + # -- Specific annotations to add to zone A pods + podAnnotations: {} + zoneB: + # -- optionally define a node selector for this zone + nodeSelector: null + # -- optionally define extra affinity rules, by default different zones are not allowed to schedule on the same host + extraAffinity: {} + # -- Specific annotations to add to zone B statefulset + annotations: {} + # -- Specific annotations to add to zone B pods + podAnnotations: {} + zoneC: + # -- optionally define a node selector for this zone + nodeSelector: null + # -- optionally define extra affinity rules, by default different zones are not allowed to schedule on the same host + extraAffinity: {} + # -- Specific annotations to add to zone C statefulset + annotations: {} + # -- Specific annotations to add to zone C pods + podAnnotations: {} + # -- The migration block allows migrating non zone aware ingesters to zone aware ingesters. + migration: + enabled: false + excludeDefaultZone: false + readPath: false + writePath: false +# -- Configuration for the distributor +distributor: + # -- Number of replicas for the distributor + replicas: 0 + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld + autoscaling: + # -- Enable autoscaling for the distributor + enabled: false + # -- Minimum autoscaling replicas for the distributor + minReplicas: 1 + # -- Maximum autoscaling replicas for the distributor + maxReplicas: 3 + # -- Target CPU utilisation percentage for the distributor + targetCPUUtilizationPercentage: 60 + # -- Target memory utilisation percentage for the distributor + targetMemoryUtilizationPercentage: null + # -- Allows one to define custom metrics using the HPA/v2 schema (for example, Pods, Object or External metrics) + customMetrics: [] + # - type: Pods + # pods: + # metric: + # name: loki_lines_total + # target: + # type: AverageValue + # averageValue: 10k + behavior: + # -- Enable autoscaling behaviours + enabled: false + # -- define scale down policies, must conform to HPAScalingRules + scaleDown: {} + # -- define scale up policies, must conform to HPAScalingRules + scaleUp: {} + image: + # -- The Docker registry for the distributor image. Overrides `loki.image.registry` + registry: null + # -- Docker image repository for the distributor image. Overrides `loki.image.repository` + repository: null + # -- Docker image tag for the distributor image. Overrides `loki.image.tag` + tag: null + # -- Command to execute instead of defined in Docker image + command: null + # -- The name of the PriorityClass for distributor pods + priorityClassName: null + # -- Labels for distributor pods + podLabels: {} + # -- Annotations for distributor pods + podAnnotations: {} + # -- Labels for distributor service + serviceLabels: {} + # -- Annotations for distributor service + serviceAnnotations: {} + # -- Additional CLI args for the distributor + extraArgs: [] + # -- Environment variables to add to the distributor pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the distributor pods + extraEnvFrom: [] + # -- Volume mounts to add to the distributor pods + extraVolumeMounts: [] + # -- Volumes to add to the distributor pods + extraVolumes: [] + # -- Resource requests and limits for the distributor + resources: {} + # -- Containers to add to the distributor pods + extraContainers: [] + # -- Grace period to allow the distributor to shutdown before it is killed + terminationGracePeriodSeconds: 30 + # -- Affinity for distributor pods. + # @default -- Hard node anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: distributor + topologyKey: kubernetes.io/hostname + # -- Pod Disruption Budget maxUnavailable + maxUnavailable: null + # -- Max Surge for distributor pods + maxSurge: 0 + # -- Node selector for distributor pods + nodeSelector: {} + # -- Tolerations for distributor pods + tolerations: [] + # -- Adds the appProtocol field to the distributor service. This allows distributor to work with istio protocol selection. + appProtocol: + # -- Set the optional grpc service protocol. Ex: "grpc", "http2" or "https" + grpc: "" +# -- Configuration for the querier +querier: + # -- Number of replicas for the querier + replicas: 0 + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld + autoscaling: + # -- Enable autoscaling for the querier, this is only used if `indexGateway.enabled: true` + enabled: false + # -- Minimum autoscaling replicas for the querier + minReplicas: 1 + # -- Maximum autoscaling replicas for the querier + maxReplicas: 3 + # -- Target CPU utilisation percentage for the querier + targetCPUUtilizationPercentage: 60 + # -- Target memory utilisation percentage for the querier + targetMemoryUtilizationPercentage: null + # -- Allows one to define custom metrics using the HPA/v2 schema (for example, Pods, Object or External metrics) + customMetrics: [] + # - type: External + # external: + # metric: + # name: loki_inflight_queries + # target: + # type: AverageValue + # averageValue: 12 + behavior: + # -- Enable autoscaling behaviours + enabled: false + # -- define scale down policies, must conform to HPAScalingRules + scaleDown: {} + # -- define scale up policies, must conform to HPAScalingRules + scaleUp: {} + image: + # -- The Docker registry for the querier image. Overrides `loki.image.registry` + registry: null + # -- Docker image repository for the querier image. Overrides `loki.image.repository` + repository: null + # -- Docker image tag for the querier image. Overrides `loki.image.tag` + tag: null + # -- Command to execute instead of defined in Docker image + command: null + # -- The name of the PriorityClass for querier pods + priorityClassName: null + # -- Labels for querier pods + podLabels: {} + # -- Annotations for querier pods + podAnnotations: {} + # -- Labels for querier service + serviceLabels: {} + # -- Annotations for querier service + serviceAnnotations: {} + # -- Additional CLI args for the querier extraArgs: [] - # -- Environment variables to add to the write pods + # -- Environment variables to add to the querier pods extraEnv: [] - # -- Environment variables from secrets or configmaps to add to the write pods + # -- Environment variables from secrets or configmaps to add to the querier pods extraEnvFrom: [] - # -- Lifecycle for the write container - lifecycle: {} - # -- The default /flush_shutdown preStop hook is recommended as part of the ingester - # scaledown process so it's added to the template by default when autoscaling is enabled, - # but it's disabled to optimize rolling restarts in instances that will never be scaled - # down or when using chunks storage with WAL disabled. - # https://github.com/grafana/loki/blob/main/docs/sources/operations/storage/wal.md#how-to-scale-updown - # -- Init containers to add to the write pods - initContainers: [] - # -- Containers to add to the write pods - extraContainers: [] - # -- Volume mounts to add to the write pods + # -- Volume mounts to add to the querier pods extraVolumeMounts: [] - # -- Volumes to add to the write pods + # -- Volumes to add to the querier pods extraVolumes: [] - # -- volumeClaimTemplates to add to StatefulSet - extraVolumeClaimTemplates: [] - # -- Resource requests and limits for the write + # -- Resource requests and limits for the querier resources: {} - # -- Grace period to allow the write to shutdown before it is killed. Especially for the ingester, - # this must be increased. It must be long enough so writes can be gracefully shutdown flushing/transferring - # all data and to successfully leave the member ring on shutdown. - terminationGracePeriodSeconds: 300 - # -- Affinity for write pods. Passed through `tpl` and, thus, to be configured as string - # @default -- Hard node and soft zone anti-affinity - affinity: | + # -- Containers to add to the querier pods + extraContainers: [] + # -- Init containers to add to the querier pods + initContainers: [] + # -- Grace period to allow the querier to shutdown before it is killed + terminationGracePeriodSeconds: 30 + # -- topologySpread for querier pods. + # @default -- Defaults to allow skew no more then 1 node + topologySpreadConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway + labelSelector: + matchLabels: + app.kubernetes.io/component: querier + # -- Affinity for querier pods. + # @default -- Hard node anti-affinity + affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: - {{- include "loki.writeSelectorLabels" . | nindent 10 }} + app.kubernetes.io/component: querier topologyKey: kubernetes.io/hostname - # -- DNS config for write pods - dnsConfig: {} - # -- Node selector for write pods + # -- Pod Disruption Budget maxUnavailable + maxUnavailable: null + # -- Max Surge for querier pods + maxSurge: 0 + # -- Node selector for querier pods nodeSelector: {} - # -- Topology Spread Constraints for write pods - topologySpreadConstraints: [] - # -- Tolerations for write pods + # -- Tolerations for querier pods tolerations: [] - # -- The default is to deploy all pods in parallel. - podManagementPolicy: "Parallel" + # -- DNSConfig for querier pods + dnsConfig: {} persistence: - # -- Enable volume claims in pod spec - volumeClaimsEnabled: true - # -- Parameters used for the `data` volume when volumeClaimEnabled if false - dataVolumeParameters: - emptyDir: {} - # -- Enable StatefulSetAutoDeletePVC feature - enableStatefulSetAutoDeletePVC: false + # -- Enable creating PVCs for the querier cache + enabled: false # -- Size of persistent disk size: 10Gi # -- Storage class to be used. @@ -867,174 +2072,231 @@ write: # If empty or set to null, no storageClassName spec is # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). storageClass: null - # -- Selector for persistent disk - selector: null -# Configuration for the table-manager -tableManager: - # -- Specifies whether the table-manager should be enabled - enabled: false + # -- Annotations for querier PVCs + annotations: {} + # -- Adds the appProtocol field to the querier service. This allows querier to work with istio protocol selection. + appProtocol: + # -- Set the optional grpc service protocol. Ex: "grpc", "http2" or "https" + grpc: "" +# -- Configuration for the query-frontend +queryFrontend: + # -- Number of replicas for the query-frontend + replicas: 0 + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld + autoscaling: + # -- Enable autoscaling for the query-frontend + enabled: false + # -- Minimum autoscaling replicas for the query-frontend + minReplicas: 1 + # -- Maximum autoscaling replicas for the query-frontend + maxReplicas: 3 + # -- Target CPU utilisation percentage for the query-frontend + targetCPUUtilizationPercentage: 60 + # -- Target memory utilisation percentage for the query-frontend + targetMemoryUtilizationPercentage: null + # -- Allows one to define custom metrics using the HPA/v2 schema (for example, Pods, Object or External metrics) + customMetrics: [] + # - type: Pods + # pods: + # metric: + # name: loki_query_rate + # target: + # type: AverageValue + # averageValue: 100 + behavior: + # -- Enable autoscaling behaviours + enabled: false + # -- define scale down policies, must conform to HPAScalingRules + scaleDown: {} + # -- define scale up policies, must conform to HPAScalingRules + scaleUp: {} image: - # -- The Docker registry for the table-manager image. Overrides `loki.image.registry` + # -- The Docker registry for the query-frontend image. Overrides `loki.image.registry` registry: null - # -- Docker image repository for the table-manager image. Overrides `loki.image.repository` + # -- Docker image repository for the query-frontend image. Overrides `loki.image.repository` repository: null - # -- Docker image tag for the table-manager image. Overrides `loki.image.tag` + # -- Docker image tag for the query-frontend image. Overrides `loki.image.tag` tag: null # -- Command to execute instead of defined in Docker image command: null - # -- The name of the PriorityClass for table-manager pods + # -- The name of the PriorityClass for query-frontend pods priorityClassName: null - # -- Labels for table-manager pods + # -- Labels for query-frontend pods podLabels: {} - # -- Annotations for table-manager deployment - annotations: {} - # -- Annotations for table-manager pods + # -- Annotations for query-frontend pods podAnnotations: {} - service: - # -- Annotations for table-manager Service - annotations: {} - # -- Additional labels for table-manager Service - labels: {} - # -- Additional CLI args for the table-manager + # -- Labels for query-frontend service + serviceLabels: {} + # -- Annotations for query-frontend service + serviceAnnotations: {} + # -- Additional CLI args for the query-frontend extraArgs: [] - # -- Environment variables to add to the table-manager pods + # -- Environment variables to add to the query-frontend pods extraEnv: [] - # -- Environment variables from secrets or configmaps to add to the table-manager pods + # -- Environment variables from secrets or configmaps to add to the query-frontend pods extraEnvFrom: [] - # -- Volume mounts to add to the table-manager pods + # -- Volume mounts to add to the query-frontend pods extraVolumeMounts: [] - # -- Volumes to add to the table-manager pods + # -- Volumes to add to the query-frontend pods extraVolumes: [] - # -- Resource requests and limits for the table-manager + # -- Resource requests and limits for the query-frontend resources: {} - # -- Containers to add to the table-manager pods + # -- Containers to add to the query-frontend pods extraContainers: [] - # -- Grace period to allow the table-manager to shutdown before it is killed + # -- Grace period to allow the query-frontend to shutdown before it is killed terminationGracePeriodSeconds: 30 - # -- Affinity for table-manager pods. Passed through `tpl` and, thus, to be configured as string - # @default -- Hard node and soft zone anti-affinity - affinity: | + # -- Affinity for query-frontend pods. + # @default -- Hard node anti-affinity + affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: - {{- include "loki.tableManagerSelectorLabels" . | nindent 10 }} + app.kubernetes.io/component: query-frontend topologyKey: kubernetes.io/hostname - preferredDuringSchedulingIgnoredDuringExecution: - - weight: 100 - podAffinityTerm: - labelSelector: - matchLabels: - {{- include "loki.tableManagerSelectorLabels" . | nindent 12 }} - topologyKey: failure-domain.beta.kubernetes.io/zone - # -- DNS config table-manager pods - dnsConfig: {} - # -- Node selector for table-manager pods + # -- Pod Disruption Budget maxUnavailable + maxUnavailable: null + # -- Node selector for query-frontend pods nodeSelector: {} - # -- Tolerations for table-manager pods + # -- Tolerations for query-frontend pods tolerations: [] - # -- Enable deletes by retention - retention_deletes_enabled: false - # -- Set retention period - retention_period: 0 -# Configuration for the read pod(s) -read: - # -- Number of replicas for the read - replicas: 3 - autoscaling: - # -- Enable autoscaling for the read, this is only used if `queryIndex.enabled: true` - enabled: false - # -- Minimum autoscaling replicas for the read - minReplicas: 2 - # -- Maximum autoscaling replicas for the read - maxReplicas: 6 - # -- Target CPU utilisation percentage for the read - targetCPUUtilizationPercentage: 60 - # -- Target memory utilisation percentage for the read - targetMemoryUtilizationPercentage: - # -- Behavior policies while scaling. - behavior: {} - # scaleUp: - # stabilizationWindowSeconds: 300 - # policies: - # - type: Pods - # value: 1 - # periodSeconds: 60 - # scaleDown: - # stabilizationWindowSeconds: 300 - # policies: - # - type: Pods - # value: 1 - # periodSeconds: 180 + # -- Adds the appProtocol field to the queryFrontend service. This allows queryFrontend to work with istio protocol selection. + appProtocol: + # -- Set the optional grpc service protocol. Ex: "grpc", "http2" or "https" + grpc: "" +# -- Configuration for the query-scheduler +queryScheduler: + # -- Number of replicas for the query-scheduler. + # It should be lower than `-querier.max-concurrent` to avoid generating back-pressure in queriers; + # it's also recommended that this value evenly divides the latter + replicas: 0 + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld image: - # -- The Docker registry for the read image. Overrides `loki.image.registry` + # -- The Docker registry for the query-scheduler image. Overrides `loki.image.registry` registry: null - # -- Docker image repository for the read image. Overrides `loki.image.repository` + # -- Docker image repository for the query-scheduler image. Overrides `loki.image.repository` repository: null - # -- Docker image tag for the read image. Overrides `loki.image.tag` + # -- Docker image tag for the query-scheduler image. Overrides `loki.image.tag` tag: null - # -- The name of the PriorityClass for read pods + # -- The name of the PriorityClass for query-scheduler pods priorityClassName: null - # -- Annotations for read deployment - annotations: {} - # -- Annotations for read pods - podAnnotations: {} - # -- Additional labels for each `read` pod + # -- Labels for query-scheduler pods podLabels: {} - # -- Additional selector labels for each `read` pod - selectorLabels: {} - service: - # -- Annotations for read Service - annotations: {} - # -- Additional labels for read Service - labels: {} - # -- Comma-separated list of Loki modules to load for the read - targetModule: "read" - # -- Whether or not to use the 2 target type simple scalable mode (read, write) or the - # 3 target type (read, write, backend). Legacy refers to the 2 target type, so true will - # run two targets, false will run 3 targets. - legacyReadTarget: false - # -- Additional CLI args for the read + # -- Annotations for query-scheduler pods + podAnnotations: {} + # -- Labels for query-scheduler service + serviceLabels: {} + # -- Annotations for query-scheduler service + serviceAnnotations: {} + # -- Additional CLI args for the query-scheduler extraArgs: [] - # -- Containers to add to the read pods - extraContainers: [] - # -- Environment variables to add to the read pods + # -- Environment variables to add to the query-scheduler pods extraEnv: [] - # -- Environment variables from secrets or configmaps to add to the read pods + # -- Environment variables from secrets or configmaps to add to the query-scheduler pods extraEnvFrom: [] - # -- Lifecycle for the read container - lifecycle: {} - # -- Volume mounts to add to the read pods + # -- Volume mounts to add to the query-scheduler pods extraVolumeMounts: [] - # -- Volumes to add to the read pods + # -- Volumes to add to the query-scheduler pods extraVolumes: [] - # -- Resource requests and limits for the read + # -- Resource requests and limits for the query-scheduler resources: {} - # -- Grace period to allow the read to shutdown before it is killed + # -- Containers to add to the query-scheduler pods + extraContainers: [] + # -- Grace period to allow the query-scheduler to shutdown before it is killed terminationGracePeriodSeconds: 30 - # -- Affinity for read pods. Passed through `tpl` and, thus, to be configured as string - # @default -- Hard node and soft zone anti-affinity - affinity: | + # -- Affinity for query-scheduler pods. + # @default -- Hard node anti-affinity + affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: - {{- include "loki.readSelectorLabels" . | nindent 10 }} + app.kubernetes.io/component: query-scheduler topologyKey: kubernetes.io/hostname - # -- DNS config for read pods - dnsConfig: {} - # -- Node selector for read pods + # -- Pod Disruption Budget maxUnavailable + maxUnavailable: 1 + # -- Node selector for query-scheduler pods nodeSelector: {} - # -- Topology Spread Constraints for read pods - topologySpreadConstraints: [] - # -- Tolerations for read pods + # -- Tolerations for query-scheduler pods + tolerations: [] + # -- Set the optional grpc service protocol. Ex: "grpc", "http2" or "https" + appProtocol: + grpc: "" +# -- Configuration for the index-gateway +indexGateway: + # -- Number of replicas for the index-gateway + replicas: 0 + # -- Whether the index gateway should join the memberlist hashring + joinMemberlist: true + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld + image: + # -- The Docker registry for the index-gateway image. Overrides `loki.image.registry` + registry: null + # -- Docker image repository for the index-gateway image. Overrides `loki.image.repository` + repository: null + # -- Docker image tag for the index-gateway image. Overrides `loki.image.tag` + tag: null + # -- The name of the PriorityClass for index-gateway pods + priorityClassName: null + # -- Labels for index-gateway pods + podLabels: {} + # -- Annotations for index-gateway pods + podAnnotations: {} + # -- Labels for index-gateway service + serviceLabels: {} + # -- Annotations for index-gateway service + serviceAnnotations: {} + # -- Additional CLI args for the index-gateway + extraArgs: [] + # -- Environment variables to add to the index-gateway pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the index-gateway pods + extraEnvFrom: [] + # -- Volume mounts to add to the index-gateway pods + extraVolumeMounts: [] + # -- Volumes to add to the index-gateway pods + extraVolumes: [] + # -- Resource requests and limits for the index-gateway + resources: {} + # -- Containers to add to the index-gateway pods + extraContainers: [] + # -- Init containers to add to the index-gateway pods + initContainers: [] + # -- Grace period to allow the index-gateway to shutdown before it is killed. + terminationGracePeriodSeconds: 300 + # -- Affinity for index-gateway pods. + # @default -- Hard node anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: index-gateway + topologyKey: kubernetes.io/hostname + # -- Pod Disruption Budget maxUnavailable + maxUnavailable: null + # -- Node selector for index-gateway pods + nodeSelector: {} + # -- Tolerations for index-gateway pods tolerations: [] - # -- The default is to deploy all pods in parallel. - podManagementPolicy: "Parallel" persistence: - # -- Enable StatefulSetAutoDeletePVC feature - enableStatefulSetAutoDeletePVC: true - # -- Size of persistent disk + # -- Enable creating PVCs which is required when using boltdb-shipper + enabled: false + # -- Use emptyDir with ramdisk for storage. **Please note that all data in indexGateway will be lost on pod restart** + inMemory: false + # -- Size of persistent or memory disk size: 10Gi # -- Storage class to be used. # If defined, storageClassName: . @@ -1042,106 +2304,84 @@ read: # If empty or set to null, no storageClassName spec is # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). storageClass: null - # -- Selector for persistent disk - selector: null -# Configuration for the backend pod(s) -backend: - # -- Number of replicas for the backend - replicas: 3 - autoscaling: - # -- Enable autoscaling for the backend. - enabled: false - # -- Minimum autoscaling replicas for the backend. - minReplicas: 3 - # -- Maximum autoscaling replicas for the backend. - maxReplicas: 6 - # -- Target CPU utilization percentage for the backend. - targetCPUUtilizationPercentage: 60 - # -- Target memory utilization percentage for the backend. - targetMemoryUtilizationPercentage: - # -- Behavior policies while scaling. - behavior: {} - # scaleUp: - # stabilizationWindowSeconds: 300 - # policies: - # - type: Pods - # value: 1 - # periodSeconds: 60 - # scaleDown: - # stabilizationWindowSeconds: 300 - # policies: - # - type: Pods - # value: 1 - # periodSeconds: 180 + # -- Annotations for index gateway PVCs + annotations: {} + # -- Enable StatefulSetAutoDeletePVC feature + enableStatefulSetAutoDeletePVC: false + whenDeleted: Retain + whenScaled: Retain + # -- Set the optional grpc service protocol. Ex: "grpc", "http2" or "https" + appProtocol: + grpc: "" +# -- Configuration for the compactor +compactor: + # -- Number of replicas for the compactor + replicas: 0 + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld image: - # -- The Docker registry for the backend image. Overrides `loki.image.registry` + # -- The Docker registry for the compactor image. Overrides `loki.image.registry` registry: null - # -- Docker image repository for the backend image. Overrides `loki.image.repository` + # -- Docker image repository for the compactor image. Overrides `loki.image.repository` repository: null - # -- Docker image tag for the backend image. Overrides `loki.image.tag` + # -- Docker image tag for the compactor image. Overrides `loki.image.tag` tag: null - # -- The name of the PriorityClass for backend pods + # -- Command to execute instead of defined in Docker image + command: null + # -- The name of the PriorityClass for compactor pods priorityClassName: null - # -- Annotations for backend StatefulSet - annotations: {} - # -- Annotations for backend pods - podAnnotations: {} - # -- Additional labels for each `backend` pod + # -- Labels for compactor pods podLabels: {} - # -- Additional selector labels for each `backend` pod - selectorLabels: {} - service: - # -- Annotations for backend Service - annotations: {} - # -- Additional labels for backend Service - labels: {} - # -- Comma-separated list of Loki modules to load for the read - targetModule: "backend" - # -- Additional CLI args for the backend + # -- Annotations for compactor pods + podAnnotations: {} + # -- Affinity for compactor pods. + # @default -- Hard node anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: compactor + topologyKey: kubernetes.io/hostname + # -- Labels for compactor service + serviceLabels: {} + # -- Annotations for compactor service + serviceAnnotations: {} + # -- Additional CLI args for the compactor extraArgs: [] - # -- Environment variables to add to the backend pods + # -- Environment variables to add to the compactor pods extraEnv: [] - # -- Environment variables from secrets or configmaps to add to the backend pods + # -- Environment variables from secrets or configmaps to add to the compactor pods extraEnvFrom: [] - # -- Init containers to add to the backend pods - initContainers: [] - # -- Volume mounts to add to the backend pods + # -- Volume mounts to add to the compactor pods extraVolumeMounts: [] - # -- Volumes to add to the backend pods + # -- Volumes to add to the compactor pods extraVolumes: [] - # -- Resource requests and limits for the backend + # -- readiness probe settings for ingester pods. If empty, use `loki.readinessProbe` + readinessProbe: {} + # -- liveness probe settings for ingester pods. If empty use `loki.livenessProbe` + livenessProbe: {} + # -- Resource requests and limits for the compactor resources: {} - # -- Grace period to allow the backend to shutdown before it is killed. Especially for the ingester, - # this must be increased. It must be long enough so backends can be gracefully shutdown flushing/transferring - # all data and to successfully leave the member ring on shutdown. - terminationGracePeriodSeconds: 300 - # -- Affinity for backend pods. Passed through `tpl` and, thus, to be configured as string - # @default -- Hard node and soft zone anti-affinity - affinity: | - podAntiAffinity: - requiredDuringSchedulingIgnoredDuringExecution: - - labelSelector: - matchLabels: - {{- include "loki.backendSelectorLabels" . | nindent 10 }} - topologyKey: kubernetes.io/hostname - # -- DNS config for backend pods - dnsConfig: {} - # -- Node selector for backend pods + # -- Containers to add to the compactor pods + extraContainers: [] + # -- Init containers to add to the compactor pods + initContainers: [] + # -- Grace period to allow the compactor to shutdown before it is killed + terminationGracePeriodSeconds: 30 + # -- Node selector for compactor pods nodeSelector: {} - # -- Topology Spread Constraints for backend pods - topologySpreadConstraints: [] - # -- Tolerations for backend pods + # -- Tolerations for compactor pods tolerations: [] - # -- The default is to deploy all pods in parallel. - podManagementPolicy: "Parallel" + # -- Set the optional grpc service protocol. Ex: "grpc", "http2" or "https" + appProtocol: + grpc: "" persistence: - # -- Enable volume claims in pod spec - volumeClaimsEnabled: true - # -- Parameters used for the `data` volume when volumeClaimEnabled if false - dataVolumeParameters: - emptyDir: {} - # -- Enable StatefulSetAutoDeletePVC feature - enableStatefulSetAutoDeletePVC: true + # -- Enable creating PVCs for the compactor + enabled: false # -- Size of persistent disk size: 10Gi # -- Storage class to be used. @@ -1150,392 +2390,858 @@ backend: # If empty or set to null, no storageClassName spec is # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). storageClass: null - # -- Selector for persistent disk - selector: null -# Configuration for the single binary node(s) -singleBinary: - # -- Number of replicas for the single binary + # -- Annotations for compactor PVCs + annotations: {} + # -- List of the compactor PVCs + # @notationType -- list + claims: + - name: data + size: 10Gi + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null + # - name: wal + # size: 150Gi + # -- Enable StatefulSetAutoDeletePVC feature + enableStatefulSetAutoDeletePVC: false + whenDeleted: Retain + whenScaled: Retain + serviceAccount: + create: false + # -- The name of the ServiceAccount to use for the compactor. + # If not set and create is true, a name is generated by appending + # "-compactor" to the common ServiceAccount. + name: null + # -- Image pull secrets for the compactor service account + imagePullSecrets: [] + # -- Annotations for the compactor service account + annotations: {} + # -- Set this toggle to false to opt out of automounting API credentials for the service account + automountServiceAccountToken: true +# -- Configuration for the bloom-gateway +bloomGateway: + # -- Number of replicas for the bloom-gateway replicas: 0 - autoscaling: - # -- Enable autoscaling - enabled: false - # -- Minimum autoscaling replicas for the single binary - minReplicas: 1 - # -- Maximum autoscaling replicas for the single binary - maxReplicas: 3 - # -- Target CPU utilisation percentage for the single binary - targetCPUUtilizationPercentage: 60 - # -- Target memory utilisation percentage for the single binary - targetMemoryUtilizationPercentage: + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld image: - # -- The Docker registry for the single binary image. Overrides `loki.image.registry` + # -- The Docker registry for the bloom-gateway image. Overrides `loki.image.registry` registry: null - # -- Docker image repository for the single binary image. Overrides `loki.image.repository` + # -- Docker image repository for the bloom-gateway image. Overrides `loki.image.repository` repository: null - # -- Docker image tag for the single binary image. Overrides `loki.image.tag` + # -- Docker image tag for the bloom-gateway image. Overrides `loki.image.tag` tag: null - # -- The name of the PriorityClass for single binary pods + # -- Command to execute instead of defined in Docker image + command: null + # -- The name of the PriorityClass for bloom-gateway pods priorityClassName: null - # -- Annotations for single binary StatefulSet - annotations: {} - # -- Annotations for single binary pods - podAnnotations: {} - # -- Additional labels for each `single binary` pod + # -- Labels for bloom-gateway pods podLabels: {} - # -- Additional selector labels for each `single binary` pod - selectorLabels: {} - service: - # -- Annotations for single binary Service - annotations: {} - # -- Additional labels for single binary Service - labels: {} - # -- Comma-separated list of Loki modules to load for the single binary - targetModule: "all" - # -- Labels for single binary service + # -- Annotations for bloom-gateway pods + podAnnotations: {} + # -- Affinity for bloom-gateway pods. + # @default -- Hard node anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: bloom-gateway + topologyKey: kubernetes.io/hostname + # -- Labels for bloom-gateway service + serviceLabels: {} + # -- Annotations for bloom-gateway service + serviceAnnotations: {} + # -- Additional CLI args for the bloom-gateway extraArgs: [] - # -- Environment variables to add to the single binary pods + # -- Environment variables to add to the bloom-gateway pods extraEnv: [] - # -- Environment variables from secrets or configmaps to add to the single binary pods + # -- Environment variables from secrets or configmaps to add to the bloom-gateway pods extraEnvFrom: [] - # -- Extra containers to add to the single binary loki pod - extraContainers: [] - # -- Init containers to add to the single binary pods - initContainers: [] - # -- Volume mounts to add to the single binary pods + # -- Volume mounts to add to the bloom-gateway pods extraVolumeMounts: [] - # -- Volumes to add to the single binary pods + # -- Volumes to add to the bloom-gateway pods extraVolumes: [] - # -- Resource requests and limits for the single binary + # -- readiness probe settings for ingester pods. If empty, use `loki.readinessProbe` + readinessProbe: {} + # -- liveness probe settings for ingester pods. If empty use `loki.livenessProbe` + livenessProbe: {} + # -- Resource requests and limits for the bloom-gateway resources: {} - # -- Grace period to allow the single binary to shutdown before it is killed + # -- Containers to add to the bloom-gateway pods + extraContainers: [] + # -- Init containers to add to the bloom-gateway pods + initContainers: [] + # -- Grace period to allow the bloom-gateway to shutdown before it is killed terminationGracePeriodSeconds: 30 - # -- Affinity for single binary pods. Passed through `tpl` and, thus, to be configured as string - # @default -- Hard node and soft zone anti-affinity - affinity: | + # -- Node selector for bloom-gateway pods + nodeSelector: {} + # -- Tolerations for bloom-gateway pods + tolerations: [] + # -- Set the optional grpc service protocol. Ex: "grpc", "http2" or "https" + appProtocol: + grpc: "" + persistence: + # -- Enable creating PVCs for the bloom-gateway + enabled: false + # -- Annotations for bloom-gateway PVCs + annotations: {} + # -- List of the bloom-gateway PVCs + # @notationType -- list + claims: + - name: data + # -- Size of persistent disk + size: 10Gi + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null + # -- Enable StatefulSetAutoDeletePVC feature + enableStatefulSetAutoDeletePVC: false + whenDeleted: Retain + whenScaled: Retain + serviceAccount: + create: false + # -- The name of the ServiceAccount to use for the bloom-gateway. + # If not set and create is true, a name is generated by appending + # "-bloom-gateway" to the common ServiceAccount. + name: null + # -- Image pull secrets for the bloom-gateway service account + imagePullSecrets: [] + # -- Annotations for the bloom-gateway service account + annotations: {} + # -- Set this toggle to false to opt out of automounting API credentials for the service account + automountServiceAccountToken: true +# -- Configuration for the bloom-planner +bloomPlanner: + # -- Number of replicas for the bloom-planner + replicas: 0 + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld + image: + # -- The Docker registry for the bloom-planner image. Overrides `loki.image.registry` + registry: null + # -- Docker image repository for the bloom-planner image. Overrides `loki.image.repository` + repository: null + # -- Docker image tag for the bloom-planner image. Overrides `loki.image.tag` + tag: null + # -- Command to execute instead of defined in Docker image + command: null + # -- The name of the PriorityClass for bloom-planner pods + priorityClassName: null + # -- Labels for bloom-planner pods + podLabels: {} + # -- Annotations for bloom-planner pods + podAnnotations: {} + # -- Affinity for bloom-planner pods. + # @default -- Hard node anti-affinity + affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: - {{- include "loki.singleBinarySelectorLabels" . | nindent 10 }} + app.kubernetes.io/component: bloom-planner topologyKey: kubernetes.io/hostname - # -- DNS config for single binary pods - dnsConfig: {} - # -- Node selector for single binary pods + # -- Labels for bloom-planner service + serviceLabels: {} + # -- Annotations for bloom-planner service + serviceAnnotations: {} + # -- Additional CLI args for the bloom-planner + extraArgs: [] + # -- Environment variables to add to the bloom-planner pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the bloom-planner pods + extraEnvFrom: [] + # -- Volume mounts to add to the bloom-planner pods + extraVolumeMounts: [] + # -- Volumes to add to the bloom-planner pods + extraVolumes: [] + # -- readiness probe settings for ingester pods. If empty, use `loki.readinessProbe` + readinessProbe: {} + # -- liveness probe settings for ingester pods. If empty use `loki.livenessProbe` + livenessProbe: {} + # -- Resource requests and limits for the bloom-planner + resources: {} + # -- Containers to add to the bloom-planner pods + extraContainers: [] + # -- Init containers to add to the bloom-planner pods + initContainers: [] + # -- Grace period to allow the bloom-planner to shutdown before it is killed + terminationGracePeriodSeconds: 30 + # -- Node selector for bloom-planner pods nodeSelector: {} - # -- Tolerations for single binary pods + # -- Tolerations for bloom-planner pods tolerations: [] + # -- Set the optional grpc service protocol. Ex: "grpc", "http2" or "https" + appProtocol: + grpc: "" persistence: + # -- Enable creating PVCs for the bloom-planner + enabled: false + # -- Annotations for bloom-planner PVCs + annotations: {} + # -- List of the bloom-planner PVCs + # @notationType -- list + claims: + - name: data + # -- Size of persistent disk + size: 10Gi + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null # -- Enable StatefulSetAutoDeletePVC feature - enableStatefulSetAutoDeletePVC: true - # -- Enable persistent disk - enabled: true - # -- Size of persistent disk - size: 10Gi - # -- Storage class to be used. - # If defined, storageClassName: . - # If set to "-", storageClassName: "", which disables dynamic provisioning. - # If empty or set to null, no storageClassName spec is - # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). - storageClass: null - # -- Selector for persistent disk - selector: null -# Use either this ingress or the gateway, but not both at once. -# If you enable this, make sure to disable the gateway. -# You'll need to supply authn configuration for your ingress controller. -ingress: - enabled: false - ingressClassName: "" - annotations: {} - # nginx.ingress.kubernetes.io/auth-type: basic - # nginx.ingress.kubernetes.io/auth-secret: loki-distributed-basic-auth - # nginx.ingress.kubernetes.io/auth-secret-type: auth-map - # nginx.ingress.kubernetes.io/configuration-snippet: | - # proxy_set_header X-Scope-OrgID $remote_user; - labels: {} - # blackbox.monitoring.exclude: "true" - paths: - write: - - /api/prom/push - - /loki/api/v1/push - read: - - /api/prom/tail - - /loki/api/v1/tail - - /loki/api - - /api/prom/rules - - /loki/api/v1/rules - - /prometheus/api/v1/rules - - /prometheus/api/v1/alerts - singleBinary: - - /api/prom/push - - /loki/api/v1/push - - /api/prom/tail - - /loki/api/v1/tail - - /loki/api - - /api/prom/rules - - /loki/api/v1/rules - - /prometheus/api/v1/rules - - /prometheus/api/v1/alerts - # -- Hosts configuration for the ingress, passed through the `tpl` function to allow templating - hosts: - - loki.example.com - # -- TLS configuration for the ingress. Hosts passed through the `tpl` function to allow templating - tls: [] -# - hosts: -# - loki.example.com -# secretName: loki-distributed-tls - -# Configuration for the memberlist service -memberlist: - service: - publishNotReadyAddresses: false -# Configuration for the gateway -gateway: - # -- Specifies whether the gateway should be enabled - enabled: true - # -- Number of replicas for the gateway - replicas: 1 - # -- Enable logging of 2xx and 3xx HTTP requests - verboseLogging: true + enableStatefulSetAutoDeletePVC: false + whenDeleted: Retain + whenScaled: Retain + serviceAccount: + create: false + # -- The name of the ServiceAccount to use for the bloom-planner. + # If not set and create is true, a name is generated by appending + # "-bloom-planner" to the common ServiceAccount. + name: null + # -- Image pull secrets for the bloom-planner service account + imagePullSecrets: [] + # -- Annotations for the bloom-planner service account + annotations: {} + # -- Set this toggle to false to opt out of automounting API credentials for the service account + automountServiceAccountToken: true +# -- Configuration for the bloom-builder +bloomBuilder: + # -- Number of replicas for the bloom-builder + replicas: 0 + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld autoscaling: - # -- Enable autoscaling for the gateway + # -- Enable autoscaling for the bloom-builder enabled: false - # -- Minimum autoscaling replicas for the gateway + # -- Minimum autoscaling replicas for the bloom-builder minReplicas: 1 - # -- Maximum autoscaling replicas for the gateway + # -- Maximum autoscaling replicas for the bloom-builder maxReplicas: 3 - # -- Target CPU utilisation percentage for the gateway + # -- Target CPU utilisation percentage for the bloom-builder targetCPUUtilizationPercentage: 60 - # -- Target memory utilisation percentage for the gateway - targetMemoryUtilizationPercentage: - # -- See `kubectl explain deployment.spec.strategy` for more - # -- ref: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#strategy - # -- Behavior policies while scaling. - behavior: {} - # scaleUp: - # stabilizationWindowSeconds: 300 - # policies: - # - type: Pods - # value: 1 - # periodSeconds: 60 - # scaleDown: - # stabilizationWindowSeconds: 300 - # policies: - # - type: Pods - # value: 1 - # periodSeconds: 180 - deploymentStrategy: - type: RollingUpdate + # -- Target memory utilisation percentage for the bloom-builder + targetMemoryUtilizationPercentage: null + # -- Allows one to define custom metrics using the HPA/v2 schema (for example, Pods, Object or External metrics) + customMetrics: [] + # - type: Pods + # pods: + # metric: + # name: loki_query_rate + # target: + # type: AverageValue + # averageValue: 100 + behavior: + # -- Enable autoscaling behaviours + enabled: false + # -- define scale down policies, must conform to HPAScalingRules + scaleDown: {} + # -- define scale up policies, must conform to HPAScalingRules + scaleUp: {} + image: + # -- The Docker registry for the bloom-builder image. Overrides `loki.image.registry` + registry: null + # -- Docker image repository for the bloom-builder image. Overrides `loki.image.repository` + repository: null + # -- Docker image tag for the bloom-builder image. Overrides `loki.image.tag` + tag: null + # -- Command to execute instead of defined in Docker image + command: null + # -- The name of the PriorityClass for bloom-builder pods + priorityClassName: null + # -- Labels for bloom-builder pods + podLabels: {} + # -- Annotations for bloom-builder pods + podAnnotations: {} + # -- Labels for bloom-builder service + serviceLabels: {} + # -- Annotations for bloom-builder service + serviceAnnotations: {} + # -- Additional CLI args for the bloom-builder + extraArgs: [] + # -- Environment variables to add to the bloom-builder pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the bloom-builder pods + extraEnvFrom: [] + # -- Volume mounts to add to the bloom-builder pods + extraVolumeMounts: [] + # -- Volumes to add to the bloom-builder pods + extraVolumes: [] + # -- Resource requests and limits for the bloom-builder + resources: {} + # -- Containers to add to the bloom-builder pods + extraContainers: [] + # -- Grace period to allow the bloom-builder to shutdown before it is killed + terminationGracePeriodSeconds: 30 + # -- Affinity for bloom-builder pods. + # @default -- Hard node anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: bloom-builder + topologyKey: kubernetes.io/hostname + # -- Pod Disruption Budget maxUnavailable + maxUnavailable: null + # -- Node selector for bloom-builder pods + nodeSelector: {} + # -- Tolerations for bloom-builder pods + tolerations: [] + # -- Adds the appProtocol field to the queryFrontend service. This allows bloomBuilder to work with istio protocol selection. + appProtocol: + # -- Set the optional grpc service protocol. Ex: "grpc", "http2" or "https" + grpc: "" +# -- Configuration for the pattern ingester +patternIngester: + # -- Number of replicas for the pattern ingester + replicas: 0 + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld image: - # -- The Docker registry for the gateway image - registry: docker.io - # -- The gateway image repository - repository: nginxinc/nginx-unprivileged - # -- The gateway image tag - tag: 1.24-alpine - # -- Overrides the gateway image tag with an image digest - digest: null - # -- The gateway image pull policy - pullPolicy: IfNotPresent - # -- The name of the PriorityClass for gateway pods + # -- The Docker registry for the pattern ingester image. Overrides `loki.image.registry` + registry: null + # -- Docker image repository for the pattern ingester image. Overrides `loki.image.repository` + repository: null + # -- Docker image tag for the pattern ingester image. Overrides `loki.image.tag` + tag: null + # -- Command to execute instead of defined in Docker image + command: null + # -- The name of the PriorityClass for pattern ingester pods priorityClassName: null - # -- Annotations for gateway deployment - annotations: {} - # -- Annotations for gateway pods - podAnnotations: {} - # -- Additional labels for gateway pods + # -- Labels for pattern ingester pods podLabels: {} - # -- Additional CLI args for the gateway + # -- Annotations for pattern ingester pods + podAnnotations: {} + # -- Affinity for pattern ingester pods. + # @default -- Hard node anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: pattern-ingester + topologyKey: kubernetes.io/hostname + # -- Labels for pattern ingester service + serviceLabels: {} + # -- Annotations for pattern ingester service + serviceAnnotations: {} + # -- Additional CLI args for the pattern ingester extraArgs: [] - # -- Environment variables to add to the gateway pods + # -- Environment variables to add to the pattern ingester pods extraEnv: [] - # -- Environment variables from secrets or configmaps to add to the gateway pods + # -- Environment variables from secrets or configmaps to add to the pattern ingester pods extraEnvFrom: [] - # -- Lifecycle for the gateway container - lifecycle: {} - # -- Volumes to add to the gateway pods - extraVolumes: [] - # -- Volume mounts to add to the gateway pods + # -- Volume mounts to add to the pattern ingester pods extraVolumeMounts: [] - # -- The SecurityContext for gateway containers - podSecurityContext: - fsGroup: 101 - runAsGroup: 101 - runAsNonRoot: true - runAsUser: 101 - # -- The SecurityContext for gateway containers - containerSecurityContext: - readOnlyRootFilesystem: true - capabilities: - drop: - - ALL - allowPrivilegeEscalation: false - # -- Resource requests and limits for the gateway + # -- Volumes to add to the pattern ingester pods + extraVolumes: [] + # -- readiness probe settings for ingester pods. If empty, use `loki.readinessProbe` + readinessProbe: {} + # -- liveness probe settings for ingester pods. If empty use `loki.livenessProbe` + livenessProbe: {} + # -- Resource requests and limits for the pattern ingester resources: {} - # -- Containers to add to the gateway pods + # -- Containers to add to the pattern ingester pods extraContainers: [] - # -- Grace period to allow the gateway to shutdown before it is killed + # -- Init containers to add to the pattern ingester pods + initContainers: [] + # -- Grace period to allow the pattern ingester to shutdown before it is killed terminationGracePeriodSeconds: 30 - # -- Affinity for gateway pods. Passed through `tpl` and, thus, to be configured as string - # @default -- Hard node and soft zone anti-affinity - affinity: | + # -- Node selector for pattern ingester pods + nodeSelector: {} + # -- Tolerations for pattern ingester pods + tolerations: [] + # -- Set the optional grpc service protocol. Ex: "grpc", "http2" or "https" + appProtocol: + grpc: "" + persistence: + # -- Enable creating PVCs for the pattern ingester + enabled: false + # -- Size of persistent disk + size: 10Gi + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null + # -- Annotations for pattern ingester PVCs + annotations: {} + # -- List of the pattern ingester PVCs + # @notationType -- list + claims: + - name: data + size: 10Gi + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null + # - name: wal + # size: 150Gi + # -- Enable StatefulSetAutoDeletePVC feature + enableStatefulSetAutoDeletePVC: false + whenDeleted: Retain + whenScaled: Retain + serviceAccount: + create: false + # -- The name of the ServiceAccount to use for the pattern ingester. + # If not set and create is true, a name is generated by appending + # "-pattern-ingester" to the common ServiceAccount. + name: null + # -- Image pull secrets for the pattern ingester service account + imagePullSecrets: [] + # -- Annotations for the pattern ingester service account + annotations: {} + # -- Set this toggle to false to opt out of automounting API credentials for the service account + automountServiceAccountToken: true +# -- Configuration for the ruler +ruler: + # -- The ruler component is optional and can be disabled if desired. + enabled: true + # -- Number of replicas for the ruler + replicas: 0 + # -- hostAliases to add + hostAliases: [] + # - ip: 1.2.3.4 + # hostnames: + # - domain.tld + image: + # -- The Docker registry for the ruler image. Overrides `loki.image.registry` + registry: null + # -- Docker image repository for the ruler image. Overrides `loki.image.repository` + repository: null + # -- Docker image tag for the ruler image. Overrides `loki.image.tag` + tag: null + # -- Command to execute instead of defined in Docker image + command: null + # -- The name of the PriorityClass for ruler pods + priorityClassName: null + # -- Labels for compactor pods + podLabels: {} + # -- Annotations for ruler pods + podAnnotations: {} + # -- Labels for ruler service + serviceLabels: {} + # -- Annotations for ruler service + serviceAnnotations: {} + # -- Additional CLI args for the ruler + extraArgs: [] + # -- Environment variables to add to the ruler pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the ruler pods + extraEnvFrom: [] + # -- Volume mounts to add to the ruler pods + extraVolumeMounts: [] + # -- Volumes to add to the ruler pods + extraVolumes: [] + # -- Resource requests and limits for the ruler + resources: {} + # -- Containers to add to the ruler pods + extraContainers: [] + # -- Init containers to add to the ruler pods + initContainers: [] + # -- Grace period to allow the ruler to shutdown before it is killed + terminationGracePeriodSeconds: 300 + # -- Affinity for ruler pods. + # @default -- Hard node anti-affinity + affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchLabels: - {{- include "loki.gatewaySelectorLabels" . | nindent 10 }} + app.kubernetes.io/component: ruler topologyKey: kubernetes.io/hostname - # -- DNS config for gateway pods - dnsConfig: {} - # -- Node selector for gateway pods + # -- Pod Disruption Budget maxUnavailable + maxUnavailable: null + # -- Node selector for ruler pods nodeSelector: {} - # -- Topology Spread Constraints for gateway pods - topologySpreadConstraints: [] - # -- Tolerations for gateway pods + # -- Tolerations for ruler pods tolerations: [] - # Gateway service configuration - service: - # -- Port of the gateway service - port: 80 - # -- Type of the gateway service - type: ClusterIP - # -- ClusterIP of the gateway service - clusterIP: null - # -- (int) Node port if service type is NodePort - nodePort: null - # -- Load balancer IPO address if service type is LoadBalancer - loadBalancerIP: null - # -- Annotations for the gateway service - annotations: {} - # -- Labels for gateway service - labels: {} - # Gateway ingress configuration - ingress: - # -- Specifies whether an ingress for the gateway should be created + # -- DNSConfig for ruler pods + dnsConfig: {} + persistence: + # -- Enable creating PVCs which is required when using recording rules enabled: false - # -- Ingress Class Name. MAY be required for Kubernetes versions >= 1.18 - ingressClassName: "" - # -- Annotations for the gateway ingress + # -- Size of persistent disk + size: 10Gi + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null + # -- Annotations for ruler PVCs annotations: {} - # -- Labels for the gateway ingress - labels: {} - # -- Hosts configuration for the gateway ingress, passed through the `tpl` function to allow templating - hosts: - - host: gateway.loki.example.com - paths: - - path: / - # -- pathType (e.g. ImplementationSpecific, Prefix, .. etc.) might also be required by some Ingress Controllers - # pathType: Prefix - # -- TLS configuration for the gateway ingress. Hosts passed through the `tpl` function to allow templating - tls: - - secretName: loki-gateway-tls - hosts: - - gateway.loki.example.com - # Basic auth configuration - basicAuth: - # -- Enables basic authentication for the gateway - enabled: false - # -- The basic auth username for the gateway - username: null - # -- The basic auth password for the gateway - password: null - # -- Uses the specified users from the `loki.tenants` list to create the htpasswd file - # if `loki.tenants` is not set, the `gateway.basicAuth.username` and `gateway.basicAuth.password` are used - # The value is templated using `tpl`. Override this to use a custom htpasswd, e.g. in case the default causes - # high CPU load. - htpasswd: >- - {{ if .Values.loki.tenants }} - - {{- range $t := .Values.loki.tenants }} - {{ htpasswd (required "All tenants must have a 'name' set" $t.name) (required "All tenants must have a 'password' set" $t.password) }} - - {{- end }} - {{ else }} {{ htpasswd (required "'gateway.basicAuth.username' is required" .Values.gateway.basicAuth.username) (required "'gateway.basicAuth.password' is required" .Values.gateway.basicAuth.password) }} {{ end }} - # -- Existing basic auth secret to use. Must contain '.htpasswd' - existingSecret: null - # Configures the readiness probe for the gateway - readinessProbe: - httpGet: - path: / - port: http - initialDelaySeconds: 15 - timeoutSeconds: 1 - nginxConfig: - # -- Enable listener for IPv6, disable on IPv4-only systems - enableIPv6: true - # -- NGINX log format - logFormat: |- - main '$remote_addr - $remote_user [$time_local] $status ' - '"$request" $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - # -- Allows appending custom configuration to the server block - serverSnippet: "" - # -- Allows appending custom configuration to the http block, passed through the `tpl` function to allow templating - httpSnippet: >- - {{ if .Values.loki.tenants }}proxy_set_header X-Scope-OrgID $remote_user;{{ end }} - # -- Override Read URL - customReadUrl: null - # -- Override Write URL - customWriteUrl: null - # -- Override Backend URL - customBackendUrl: null - # -- Allows overriding the DNS resolver address nginx will use. - resolver: "" - # -- Config file contents for Nginx. Passed through the `tpl` function to allow templating - # @default -- See values.yaml - file: | - {{- include "loki.nginxFile" . | indent 2 -}} -networkPolicy: - # -- Specifies whether Network Policies should be created - enabled: false - # -- Specifies whether the policies created will be standard Network Policies (flavor: kubernetes) - # or Cilium Network Policies (flavor: cilium) - flavor: kubernetes - metrics: - # -- Specifies the Pods which are allowed to access the metrics port. - # As this is cross-namespace communication, you also need the namespaceSelector. - podSelector: {} - # -- Specifies the namespaces which are allowed to access the metrics port - namespaceSelector: {} - # -- Specifies specific network CIDRs which are allowed to access the metrics port. - # In case you use namespaceSelector, you also have to specify your kubelet networks here. - # The metrics ports are also used for probes. - cidrs: [] - ingress: - # -- Specifies the Pods which are allowed to access the http port. - # As this is cross-namespace communication, you also need the namespaceSelector. - podSelector: {} - # -- Specifies the namespaces which are allowed to access the http port - namespaceSelector: {} - alertmanager: - # -- Specify the alertmanager port used for alerting - port: 9093 - # -- Specifies the alertmanager Pods. - # As this is cross-namespace communication, you also need the namespaceSelector. - podSelector: {} - # -- Specifies the namespace the alertmanager is running in - namespaceSelector: {} - externalStorage: - # -- Specify the port used for external storage, e.g. AWS S3 - ports: [] - # -- Specifies specific network CIDRs you want to limit access to - cidrs: [] - discovery: - # -- (int) Specify the port used for discovery - port: null - # -- Specifies the Pods labels used for discovery. - # As this is cross-namespace communication, you also need the namespaceSelector. - podSelector: {} - # -- Specifies the namespace the discovery Pods are running in - namespaceSelector: {} - egressWorld: - # -- Enable additional cilium egress rules to external world for write, read and backend. + # -- Set the optional grpc service protocol. Ex: "grpc", "http2" or "https" + appProtocol: + grpc: "" + # -- Directories containing rules files + directories: {} + # tenant_foo: + # rules1.txt: | + # groups: + # - name: should_fire + # rules: + # - alert: HighPercentageError + # expr: | + # sum(rate({app="foo", env="production"} |= "error" [5m])) by (job) + # / + # sum(rate({app="foo", env="production"}[5m])) by (job) + # > 0.05 + # for: 10m + # labels: + # severity: warning + # annotations: + # summary: High error rate + # - name: credentials_leak + # rules: + # - alert: http-credentials-leaked + # annotations: + # message: "{{ $labels.job }} is leaking http basic auth credentials." + # expr: 'sum by (cluster, job, pod) (count_over_time({namespace="prod"} |~ "http(s?)://(\\w+):(\\w+)@" [5m]) > 0)' + # for: 10m + # labels: + # severity: critical + # rules2.txt: | + # groups: + # - name: example + # rules: + # - alert: HighThroughputLogStreams + # expr: sum by(container) (rate({job=~"loki-dev/.*"}[1m])) > 1000 + # for: 2m + # tenant_bar: + # rules1.txt: | + # groups: + # - name: should_fire + # rules: + # - alert: HighPercentageError + # expr: | + # sum(rate({app="foo", env="production"} |= "error" [5m])) by (job) + # / + # sum(rate({app="foo", env="production"}[5m])) by (job) + # > 0.05 + # for: 10m + # labels: + # severity: warning + # annotations: + # summary: High error rate + # - name: credentials_leak + # rules: + # - alert: http-credentials-leaked + # annotations: + # message: "{{ $labels.job }} is leaking http basic auth credentials." + # expr: 'sum by (cluster, job, pod) (count_over_time({namespace="prod"} |~ "http(s?)://(\\w+):(\\w+)@" [5m]) > 0)' + # for: 10m + # labels: + # severity: critical + # rules2.txt: | + # groups: + # - name: example + # rules: + # - alert: HighThroughputLogStreams + # expr: sum by(container) (rate({job=~"loki-dev/.*"}[1m])) > 1000 + # for: 2m +memcached: + image: + # -- Memcached Docker image repository + repository: memcached + # -- Memcached Docker image tag + tag: 1.6.23-alpine + # -- Memcached Docker image pull policy + pullPolicy: IfNotPresent + # -- The SecurityContext override for memcached pods + podSecurityContext: + runAsNonRoot: true + runAsUser: 11211 + runAsGroup: 11211 + fsGroup: 11211 + # -- The name of the PriorityClass for memcached pods + priorityClassName: null + # -- The SecurityContext for memcached containers + containerSecurityContext: + readOnlyRootFilesystem: true + capabilities: + drop: [ALL] + allowPrivilegeEscalation: false +memcachedExporter: + # -- Whether memcached metrics should be exported + enabled: true + image: + repository: prom/memcached-exporter + tag: v0.14.2 + pullPolicy: IfNotPresent + resources: + requests: {} + limits: {} + # -- The SecurityContext for memcached exporter containers + containerSecurityContext: + readOnlyRootFilesystem: true + capabilities: + drop: [ALL] + allowPrivilegeEscalation: false + # -- Extra args to add to the exporter container. + # Example: + # extraArgs: + # memcached.tls.enable: true + # memcached.tls.cert-file: /certs/cert.crt + # memcached.tls.key-file: /certs/cert.key + # memcached.tls.ca-file: /certs/ca.crt + # memcached.tls.insecure-skip-verify: false + # memcached.tls.server-name: memcached + extraArgs: {} +resultsCache: + # -- Specifies whether memcached based results-cache should be enabled + enabled: true + # -- Specify how long cached results should be stored in the results-cache before being expired + defaultValidity: 12h + # -- Memcached operation timeout + timeout: 500ms + # -- Total number of results-cache replicas + replicas: 1 + # -- Port of the results-cache service + port: 11211 + # -- Amount of memory allocated to results-cache for object storage (in MB). + allocatedMemory: 1024 + # -- Maximum item results-cache for memcached (in MB). + maxItemMemory: 5 + # -- Maximum number of connections allowed + connectionLimit: 16384 + # -- Max memory to use for cache write back + writebackSizeLimit: 500MB + # -- Max number of objects to use for cache write back + writebackBuffer: 500000 + # -- Number of parallel threads for cache write back + writebackParallelism: 1 + # -- Extra init containers for results-cache pods + initContainers: [] + # -- Annotations for the results-cache pods + annotations: {} + # -- Node selector for results-cache pods + nodeSelector: {} + # -- Affinity for results-cache pods + affinity: {} + # -- topologySpreadConstraints allows to customize the default topologySpreadConstraints. This can be either a single dict as shown below or a slice of topologySpreadConstraints. + # labelSelector is taken from the constraint itself (if it exists) or is generated by the chart using the same selectors as for services. + topologySpreadConstraints: [] + # maxSkew: 1 + # topologyKey: kubernetes.io/hostname + # whenUnsatisfiable: ScheduleAnyway + # -- Tolerations for results-cache pods + tolerations: [] + # -- Pod Disruption Budget + podDisruptionBudget: + maxUnavailable: 1 + # -- The name of the PriorityClass for results-cache pods + priorityClassName: null + # -- Labels for results-cache pods + podLabels: {} + # -- Annotations for results-cache pods + podAnnotations: {} + # -- Management policy for results-cache pods + podManagementPolicy: Parallel + # -- Grace period to allow the results-cache to shutdown before it is killed + terminationGracePeriodSeconds: 60 + # -- Stateful results-cache strategy + statefulStrategy: + type: RollingUpdate + # -- Add extended options for results-cache memcached container. The format is the same as for the memcached -o/--extend flag. + # Example: + # extraExtendedOptions: 'tls,modern,track_sizes' + extraExtendedOptions: "" + # -- Additional CLI args for results-cache + extraArgs: {} + # -- Additional containers to be added to the results-cache pod. + extraContainers: [] + # -- Additional volumes to be added to the results-cache pod (applies to both memcached and exporter containers). + # Example: + # extraVolumes: + # - name: extra-volume + # secret: + # secretName: extra-volume-secret + extraVolumes: [] + # -- Additional volume mounts to be added to the results-cache pod (applies to both memcached and exporter containers). + # Example: + # extraVolumeMounts: + # - name: extra-volume + # mountPath: /etc/extra-volume + # readOnly: true + extraVolumeMounts: [] + # -- Resource requests and limits for the results-cache + # By default a safe memory limit will be requested based on allocatedMemory value (floor (* 1.2 allocatedMemory)). + resources: null + # -- Service annotations and labels + service: + annotations: {} + labels: {} + # -- Persistence settings for the results-cache + persistence: + # -- Enable creating PVCs for the results-cache enabled: false - egressKubeApiserver: - # -- Enable additional cilium egress rules to kube-apiserver for backend. + # -- Size of persistent disk, must be in G or Gi + storageSize: 10G + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null + # -- Volume mount path + mountPath: /data +chunksCache: + # -- Specifies whether memcached based chunks-cache should be enabled + enabled: true + # -- Batchsize for sending and receiving chunks from chunks cache + batchSize: 4 + # -- Parallel threads for sending and receiving chunks from chunks cache + parallelism: 5 + # -- Memcached operation timeout + timeout: 2000ms + # -- Specify how long cached chunks should be stored in the chunks-cache before being expired + defaultValidity: 0s + # -- Total number of chunks-cache replicas + replicas: 1 + # -- Port of the chunks-cache service + port: 11211 + # -- Amount of memory allocated to chunks-cache for object storage (in MB). + allocatedMemory: 8192 + # -- Maximum item memory for chunks-cache (in MB). + maxItemMemory: 5 + # -- Maximum number of connections allowed + connectionLimit: 16384 + # -- Max memory to use for cache write back + writebackSizeLimit: 500MB + # -- Max number of objects to use for cache write back + writebackBuffer: 500000 + # -- Number of parallel threads for cache write back + writebackParallelism: 1 + # -- Extra init containers for chunks-cache pods + initContainers: [] + # -- Annotations for the chunks-cache pods + annotations: {} + # -- Node selector for chunks-cache pods + nodeSelector: {} + # -- Affinity for chunks-cache pods + affinity: {} + # -- topologySpreadConstraints allows to customize the default topologySpreadConstraints. This can be either a single dict as shown below or a slice of topologySpreadConstraints. + # labelSelector is taken from the constraint itself (if it exists) or is generated by the chart using the same selectors as for services. + topologySpreadConstraints: [] + # maxSkew: 1 + # topologyKey: kubernetes.io/hostname + # whenUnsatisfiable: ScheduleAnyway + # -- Tolerations for chunks-cache pods + tolerations: [] + # -- Pod Disruption Budget + podDisruptionBudget: + maxUnavailable: 1 + # -- The name of the PriorityClass for chunks-cache pods + priorityClassName: null + # -- Labels for chunks-cache pods + podLabels: {} + # -- Annotations for chunks-cache pods + podAnnotations: {} + # -- Management policy for chunks-cache pods + podManagementPolicy: Parallel + # -- Grace period to allow the chunks-cache to shutdown before it is killed + terminationGracePeriodSeconds: 60 + # -- Stateful chunks-cache strategy + statefulStrategy: + type: RollingUpdate + # -- Add extended options for chunks-cache memcached container. The format is the same as for the memcached -o/--extend flag. + # Example: + # extraExtendedOptions: 'tls,no_hashexpand' + extraExtendedOptions: "" + # -- Additional CLI args for chunks-cache + extraArgs: {} + # -- Additional containers to be added to the chunks-cache pod. + extraContainers: [] + # -- Additional volumes to be added to the chunks-cache pod (applies to both memcached and exporter containers). + # Example: + # extraVolumes: + # - name: extra-volume + # secret: + # secretName: extra-volume-secret + extraVolumes: [] + # -- Additional volume mounts to be added to the chunks-cache pod (applies to both memcached and exporter containers). + # Example: + # extraVolumeMounts: + # - name: extra-volume + # mountPath: /etc/extra-volume + # readOnly: true + extraVolumeMounts: [] + # -- Resource requests and limits for the chunks-cache + # By default a safe memory limit will be requested based on allocatedMemory value (floor (* 1.2 allocatedMemory)). + resources: null + # -- Service annotations and labels + service: + annotations: {} + labels: {} + # -- Persistence settings for the chunks-cache + persistence: + # -- Enable creating PVCs for the chunks-cache enabled: false -# ------------------------------------- -# Configuration for `minio` child chart -# ------------------------------------- + # -- Size of persistent disk, must be in G or Gi + storageSize: 10G + # -- Storage class to be used. + # If defined, storageClassName: . + # If set to "-", storageClassName: "", which disables dynamic provisioning. + # If empty or set to null, no storageClassName spec is + # set, choosing the default provisioner (gp2 on AWS, standard on GKE, AWS, and OpenStack). + storageClass: null + # -- Volume mount path + mountPath: /data +###################################################################################################################### +# +# Subchart configurations +# +###################################################################################################################### +# -- Setting for the Grafana Rollout Operator https://github.com/grafana/helm-charts/tree/main/charts/rollout-operator +rollout_operator: + enabled: false + # -- podSecurityContext is the pod security context for the rollout operator. + # When installing on OpenShift, override podSecurityContext settings with + # + # rollout_operator: + # podSecurityContext: + # fsGroup: null + # runAsGroup: null + # runAsUser: null + podSecurityContext: + fsGroup: 10001 + runAsGroup: 10001 + runAsNonRoot: true + runAsUser: 10001 + seccompProfile: + type: RuntimeDefault + # Set the container security context + securityContext: + readOnlyRootFilesystem: true + capabilities: + drop: [ALL] + allowPrivilegeEscalation: false +# -- Configuration for the minio subchart minio: enabled: false replicas: 1 @@ -1557,11 +3263,15 @@ minio: purge: false persistence: size: 5Gi + annotations: {} resources: requests: cpu: 100m memory: 128Mi + # Allow the address used by Loki to refer to Minio to be overridden + address: null # Create extra manifests via values. Would be passed through `tpl` for templating +# objects can also be provided as multiline strings, useful for templating field names extraObjects: [] # - apiVersion: v1 # kind: ConfigMap @@ -1581,13 +3291,23 @@ extraObjects: [] # category: logs # annotations: # message: "loki has encountered errors" +# - | +# apiVersion: v1 +# kind: Secret +# type: Opaque +# metadata: +# name: loki-distributed-basic-auth +# data: +# {{- range .Values.loki.tenants }} +# {{ .name }}: {{ b64enc .password | quote }} +# {{- end }} sidecar: image: # -- The Docker registry and image for the k8s sidecar repository: kiwigrid/k8s-sidecar # -- Docker image tag - tag: 1.24.3 + tag: 1.27.5 # -- Docker image sha. If empty, no sha will be used sha: "" # -- Docker image pull policy @@ -1640,3 +3360,215 @@ sidecar: watchClientTimeout: 60 # -- Log level of the sidecar container. logLevel: INFO +############################################## WARNING ############################################################### +# +# DEPRECATED VALUES +# +# The following values are deprecated and will be removed in a future version of the helm chart! +# +############################################## WARNING ############################################################## + +# -- DEPRECATED Monitoring section determines which monitoring features to enable, this section is being replaced +# by https://github.com/grafana/meta-monitoring-chart +monitoring: + # Dashboards for monitoring Loki + dashboards: + # -- If enabled, create configmap with dashboards for monitoring Loki + enabled: false + # -- Alternative namespace to create dashboards ConfigMap in + namespace: null + # -- Additional annotations for the dashboards ConfigMap + annotations: {} + # -- Labels for the dashboards ConfigMap + labels: + grafana_dashboard: "1" + # -- DEPRECATED Recording rules for monitoring Loki, required for some dashboards + rules: + # -- If enabled, create PrometheusRule resource with Loki recording rules + enabled: false + # -- Include alerting rules + alerting: true + # -- Specify which individual alerts should be disabled + # -- Instead of turning off each alert one by one, set the .monitoring.rules.alerting value to false instead. + # -- If you disable all the alerts and keep .monitoring.rules.alerting set to true, the chart will fail to render. + disabled: {} + # LokiRequestErrors: true + # LokiRequestPanics: true + # -- Alternative namespace to create PrometheusRule resources in + namespace: null + # -- Additional annotations for the rules PrometheusRule resource + annotations: {} + # -- Additional labels for the rules PrometheusRule resource + labels: {} + # -- Additional labels for PrometheusRule alerts + additionalRuleLabels: {} + # -- Additional groups to add to the rules file + additionalGroups: [] + # - name: additional-loki-rules + # rules: + # - record: job:loki_request_duration_seconds_bucket:sum_rate + # expr: sum(rate(loki_request_duration_seconds_bucket[1m])) by (le, job) + # - record: job_route:loki_request_duration_seconds_bucket:sum_rate + # expr: sum(rate(loki_request_duration_seconds_bucket[1m])) by (le, job, route) + # - record: node_namespace_pod_container:container_cpu_usage_seconds_total:sum_rate + # expr: sum(rate(container_cpu_usage_seconds_total[1m])) by (node, namespace, pod, container) + # -- DEPRECATED ServiceMonitor configuration + serviceMonitor: + # -- If enabled, ServiceMonitor resources for Prometheus Operator are created + enabled: false + # -- Namespace selector for ServiceMonitor resources + namespaceSelector: {} + # -- ServiceMonitor annotations + annotations: {} + # -- Additional ServiceMonitor labels + labels: {} + # -- ServiceMonitor scrape interval + # Default is 15s because included recording rules use a 1m rate, and scrape interval needs to be at + # least 1/4 rate interval. + interval: 15s + # -- ServiceMonitor scrape timeout in Go duration format (e.g. 15s) + scrapeTimeout: null + # -- ServiceMonitor relabel configs to apply to samples before scraping + # https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#relabelconfig + relabelings: [] + # -- ServiceMonitor metric relabel configs to apply to samples before ingestion + # https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/api.md#endpoint + metricRelabelings: [] + # -- ServiceMonitor will use http by default, but you can pick https as well + scheme: http + # -- ServiceMonitor will use these tlsConfig settings to make the health check requests + tlsConfig: null + # -- If defined, will create a MetricsInstance for the Grafana Agent Operator. + metricsInstance: + # -- If enabled, MetricsInstance resources for Grafana Agent Operator are created + enabled: true + # -- MetricsInstance annotations + annotations: {} + # -- Additional MetricsInstance labels + labels: {} + # -- If defined a MetricsInstance will be created to remote write metrics. + remoteWrite: null + # -- DEPRECATED Self monitoring determines whether Loki should scrape its own logs. + # This feature currently relies on the Grafana Agent Operator being installed, + # which is installed by default using the grafana-agent-operator sub-chart. + # It will create custom resources for GrafanaAgent, LogsInstance, and PodLogs to configure + # scrape configs to scrape its own logs with the labels expected by the included dashboards. + selfMonitoring: + enabled: false + # -- Tenant to use for self monitoring + tenant: + # -- Name of the tenant + name: "self-monitoring" + # -- Password of the gateway for Basic auth + password: null + # -- Namespace to create additional tenant token secret in. Useful if your Grafana instance + # is in a separate namespace. Token will still be created in the canary namespace. + secretNamespace: "{{ .Release.Namespace }}" + # -- DEPRECATED Grafana Agent configuration + grafanaAgent: + # -- DEPRECATED Controls whether to install the Grafana Agent Operator and its CRDs. + # Note that helm will not install CRDs if this flag is enabled during an upgrade. + # In that case install the CRDs manually from https://github.com/grafana/agent/tree/main/production/operator/crds + installOperator: false + # -- Grafana Agent annotations + annotations: {} + # -- Additional Grafana Agent labels + labels: {} + # -- Enable the config read api on port 8080 of the agent + enableConfigReadAPI: false + # -- The name of the PriorityClass for GrafanaAgent pods + priorityClassName: null + # -- Resource requests and limits for the grafanaAgent pods + resources: {} + # limits: + # memory: 200Mi + # requests: + # cpu: 50m + # memory: 100Mi + # -- Tolerations for GrafanaAgent pods + tolerations: [] + # PodLogs configuration + podLogs: + # -- PodLogs version + apiVersion: monitoring.grafana.com/v1alpha1 + # -- PodLogs annotations + annotations: {} + # -- Additional PodLogs labels + labels: {} + # -- PodLogs relabel configs to apply to samples before scraping + # https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#relabelconfig + relabelings: [] + # -- Additional pipeline stages to process logs after scraping + # https://grafana.com/docs/agent/latest/operator/api/#pipelinestagespec-a-namemonitoringgrafanacomv1alpha1pipelinestagespeca + additionalPipelineStages: [] + # LogsInstance configuration + logsInstance: + # -- LogsInstance annotations + annotations: {} + # -- Additional LogsInstance labels + labels: {} + # -- Additional clients for remote write + clients: null +# -- DEPRECATED Configuration for the table-manager. The table-manager is only necessary when using a deprecated +# index type such as Cassandra, Bigtable, or DynamoDB, it has not been necessary since loki introduced self- +# contained index types like 'boltdb-shipper' and 'tsdb'. This will be removed in a future helm chart. +tableManager: + # -- Specifies whether the table-manager should be enabled + enabled: false + image: + # -- The Docker registry for the table-manager image. Overrides `loki.image.registry` + registry: null + # -- Docker image repository for the table-manager image. Overrides `loki.image.repository` + repository: null + # -- Docker image tag for the table-manager image. Overrides `loki.image.tag` + tag: null + # -- Command to execute instead of defined in Docker image + command: null + # -- The name of the PriorityClass for table-manager pods + priorityClassName: null + # -- Labels for table-manager pods + podLabels: {} + # -- Annotations for table-manager deployment + annotations: {} + # -- Annotations for table-manager pods + podAnnotations: {} + service: + # -- Annotations for table-manager Service + annotations: {} + # -- Additional labels for table-manager Service + labels: {} + # -- Additional CLI args for the table-manager + extraArgs: [] + # -- Environment variables to add to the table-manager pods + extraEnv: [] + # -- Environment variables from secrets or configmaps to add to the table-manager pods + extraEnvFrom: [] + # -- Volume mounts to add to the table-manager pods + extraVolumeMounts: [] + # -- Volumes to add to the table-manager pods + extraVolumes: [] + # -- Resource requests and limits for the table-manager + resources: {} + # -- Containers to add to the table-manager pods + extraContainers: [] + # -- Grace period to allow the table-manager to shutdown before it is killed + terminationGracePeriodSeconds: 30 + # -- Affinity for table-manager pods. + # @default -- Hard node and anti-affinity + affinity: + podAntiAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + - labelSelector: + matchLabels: + app.kubernetes.io/component: table-manager + topologyKey: kubernetes.io/hostname + # -- DNS config table-manager pods + dnsConfig: {} + # -- Node selector for table-manager pods + nodeSelector: {} + # -- Tolerations for table-manager pods + tolerations: [] + # -- Enable deletes by retention + retention_deletes_enabled: false + # -- Set retention period + retention_period: 0 diff --git a/salt/metalk8s/addons/logging/loki/deployed/chart.sls b/salt/metalk8s/addons/logging/loki/deployed/chart.sls index 8f7eb5a814..513fa98978 100644 --- a/salt/metalk8s/addons/logging/loki/deployed/chart.sls +++ b/salt/metalk8s/addons/logging/loki/deployed/chart.sls @@ -7,6 +7,40 @@ {% raw %} +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + labels: + app.kubernetes.io/component: memcached-chunks-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/name: loki + name: loki-memcached-chunks-cache + namespace: metalk8s-logging +spec: + maxUnavailable: 1 + selector: + matchLabels: + app.kubernetes.io/component: memcached-chunks-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/name: loki +--- +apiVersion: policy/v1 +kind: PodDisruptionBudget +metadata: + labels: + app.kubernetes.io/component: memcached-results-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/name: loki + name: loki-memcached-results-cache + namespace: metalk8s-logging +spec: + maxUnavailable: 1 + selector: + matchLabels: + app.kubernetes.io/component: memcached-results-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/name: loki +--- apiVersion: v1 automountServiceAccountToken: true kind: ServiceAccount @@ -16,8 +50,8 @@ metadata: app.kubernetes.io/managed-by: salt app.kubernetes.io/name: loki app.kubernetes.io/part-of: metalk8s - app.kubernetes.io/version: 2.9.6 - helm.sh/chart: loki-5.48.0 + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 heritage: metalk8s name: loki namespace: metalk8s-logging @@ -33,8 +67,8 @@ metadata: app.kubernetes.io/managed-by: salt app.kubernetes.io/name: loki app.kubernetes.io/part-of: metalk8s - app.kubernetes.io/version: 2.9.6 - helm.sh/chart: loki-5.48.0 + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 heritage: metalk8s name: loki-runtime namespace: metalk8s-logging @@ -47,8 +81,8 @@ metadata: app.kubernetes.io/managed-by: salt app.kubernetes.io/name: loki app.kubernetes.io/part-of: metalk8s - app.kubernetes.io/version: 2.9.6 - helm.sh/chart: loki-5.48.0 + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 heritage: metalk8s name: loki-clusterrole namespace: metalk8s-logging @@ -71,8 +105,8 @@ metadata: app.kubernetes.io/managed-by: salt app.kubernetes.io/name: loki app.kubernetes.io/part-of: metalk8s - app.kubernetes.io/version: 2.9.6 - helm.sh/chart: loki-5.48.0 + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 heritage: metalk8s name: loki-clusterrolebinding namespace: metalk8s-logging @@ -88,13 +122,74 @@ subjects: apiVersion: v1 kind: Service metadata: + annotations: {} + labels: + app.kubernetes.io/component: memcached-chunks-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 + heritage: metalk8s + name: loki-chunks-cache + namespace: metalk8s-logging +spec: + clusterIP: None + ports: + - name: memcached-client + port: 11211 + targetPort: 11211 + - name: http-metrics + port: 9150 + targetPort: 9150 + selector: + app.kubernetes.io/component: memcached-chunks-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/name: loki + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + annotations: {} + labels: + app.kubernetes.io/component: memcached-results-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 + heritage: metalk8s + name: loki-results-cache + namespace: metalk8s-logging +spec: + clusterIP: None + ports: + - name: memcached-client + port: 11211 + targetPort: 11211 + - name: http-metrics + port: 9150 + targetPort: 9150 + selector: + app.kubernetes.io/component: memcached-results-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/name: loki + type: ClusterIP +--- +apiVersion: v1 +kind: Service +metadata: + annotations: null labels: app.kubernetes.io/instance: loki app.kubernetes.io/managed-by: salt app.kubernetes.io/name: loki app.kubernetes.io/part-of: metalk8s - app.kubernetes.io/version: 2.9.6 - helm.sh/chart: loki-5.48.0 + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 heritage: metalk8s name: loki-memberlist namespace: metalk8s-logging @@ -120,8 +215,8 @@ metadata: app.kubernetes.io/managed-by: salt app.kubernetes.io/name: loki app.kubernetes.io/part-of: metalk8s - app.kubernetes.io/version: 2.9.6 - helm.sh/chart: loki-5.48.0 + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 heritage: metalk8s prometheus.io/service-monitor: 'false' variant: headless @@ -147,8 +242,8 @@ metadata: app.kubernetes.io/managed-by: salt app.kubernetes.io/name: loki app.kubernetes.io/part-of: metalk8s - app.kubernetes.io/version: 2.9.6 - helm.sh/chart: loki-5.48.0 + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 heritage: metalk8s name: loki namespace: metalk8s-logging @@ -170,6 +265,198 @@ spec: --- apiVersion: apps/v1 kind: StatefulSet +metadata: + annotations: {} + labels: + app.kubernetes.io/component: memcached-chunks-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 + heritage: metalk8s + name: memcached-chunks-cache + name: loki-chunks-cache + namespace: metalk8s-logging +spec: + podManagementPolicy: Parallel + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: memcached-chunks-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/name: loki + name: memcached-chunks-cache + serviceName: loki-chunks-cache + template: + metadata: + annotations: null + labels: + app.kubernetes.io/component: memcached-chunks-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/name: loki + name: memcached-chunks-cache + spec: + affinity: {} + containers: + - args: + - -m 8192 + - --extended=modern,track_sizes + - -I 5m + - -c 16384 + - -v + - -u 11211 + env: null + envFrom: null + image: memcached:1.6.23-alpine + imagePullPolicy: IfNotPresent + name: memcached + ports: + - containerPort: 11211 + name: client + resources: + limits: + memory: 9830Mi + requests: + cpu: 500m + memory: 9830Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + - args: + - --memcached.address=localhost:11211 + - --web.listen-address=0.0.0.0:9150 + image: prom/memcached-exporter:v0.14.2 + imagePullPolicy: IfNotPresent + name: exporter + ports: + - containerPort: 9150 + name: http-metrics + resources: + limits: {} + requests: {} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + initContainers: [] + nodeSelector: {} + securityContext: + fsGroup: 11211 + runAsGroup: 11211 + runAsNonRoot: true + runAsUser: 11211 + serviceAccountName: loki + terminationGracePeriodSeconds: 60 + tolerations: [] + topologySpreadConstraints: [] + updateStrategy: + type: RollingUpdate +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + annotations: {} + labels: + app.kubernetes.io/component: memcached-results-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 + heritage: metalk8s + name: memcached-results-cache + name: loki-results-cache + namespace: metalk8s-logging +spec: + podManagementPolicy: Parallel + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/component: memcached-results-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/name: loki + name: memcached-results-cache + serviceName: loki-results-cache + template: + metadata: + annotations: null + labels: + app.kubernetes.io/component: memcached-results-cache + app.kubernetes.io/instance: loki + app.kubernetes.io/name: loki + name: memcached-results-cache + spec: + affinity: {} + containers: + - args: + - -m 1024 + - --extended=modern,track_sizes + - -I 5m + - -c 16384 + - -v + - -u 11211 + env: null + envFrom: null + image: memcached:1.6.23-alpine + imagePullPolicy: IfNotPresent + name: memcached + ports: + - containerPort: 11211 + name: client + resources: + limits: + memory: 1229Mi + requests: + cpu: 500m + memory: 1229Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + - args: + - --memcached.address=localhost:11211 + - --web.listen-address=0.0.0.0:9150 + image: prom/memcached-exporter:v0.14.2 + imagePullPolicy: IfNotPresent + name: exporter + ports: + - containerPort: 9150 + name: http-metrics + resources: + limits: {} + requests: {} + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + initContainers: [] + nodeSelector: {} + securityContext: + fsGroup: 11211 + runAsGroup: 11211 + runAsNonRoot: true + runAsUser: 11211 + serviceAccountName: loki + terminationGracePeriodSeconds: 60 + tolerations: [] + topologySpreadConstraints: [] + updateStrategy: + type: RollingUpdate +--- +apiVersion: apps/v1 +kind: StatefulSet metadata: labels: app.kubernetes.io/component: single-binary @@ -177,8 +464,8 @@ metadata: app.kubernetes.io/managed-by: salt app.kubernetes.io/name: loki app.kubernetes.io/part-of: metalk8s - app.kubernetes.io/version: 2.9.6 - helm.sh/chart: loki-5.48.0 + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 heritage: metalk8s name: loki namespace: metalk8s-logging @@ -209,15 +496,34 @@ spec: - labelSelector: matchLabels: app.kubernetes.io/component: single-binary - app.kubernetes.io/instance: loki - app.kubernetes.io/name: loki topologyKey: kubernetes.io/hostname automountServiceAccountToken: true containers: + - env: + - name: METHOD + value: WATCH + - name: LABEL + value: loki_rule + - name: FOLDER + value: /rules + - name: RESOURCE + value: both + - name: WATCH_SERVER_TIMEOUT + value: '60' + - name: WATCH_CLIENT_TIMEOUT + value: '60' + - name: LOG_LEVEL + value: INFO + image: kiwigrid/k8s-sidecar:1.27.5 + imagePullPolicy: IfNotPresent + name: loki-sc-rules + volumeMounts: + - mountPath: /rules + name: sc-rules-volume - args: - -config.file=/etc/loki/config/config.yaml - -target=all,table-manager - image: {% endraw -%}{{ build_image_name("loki", False) }}{%- raw %}:2.9.6 + image: {% endraw -%}{{ build_image_name("loki", False) }}{%- raw %}:3.1.1 imagePullPolicy: IfNotPresent name: loki ports: @@ -252,6 +558,8 @@ spec: name: runtime-config - mountPath: /var/loki name: storage + - mountPath: /rules + name: sc-rules-volume enableServiceLinks: true securityContext: fsGroup: 10001 @@ -270,12 +578,17 @@ spec: volumes: - emptyDir: {} name: tmp - - name: config - secret: - secretName: loki + - configMap: + items: + - key: config.yaml + path: config.yaml + name: loki + name: config - configMap: name: loki-runtime name: runtime-config + - emptyDir: {} + name: sc-rules-volume updateStrategy: rollingUpdate: partition: 0 @@ -303,8 +616,8 @@ metadata: app.kubernetes.io/managed-by: salt app.kubernetes.io/name: loki app.kubernetes.io/part-of: metalk8s - app.kubernetes.io/version: 2.9.6 - helm.sh/chart: loki-5.48.0 + app.kubernetes.io/version: 3.1.1 + helm.sh/chart: loki-6.16.0 heritage: metalk8s metalk8s.scality.com/monitor: '' name: loki