diff --git a/buildchain/buildchain/image.py b/buildchain/buildchain/image.py index 8d68f4e899..bc7cc6ade8 100644 --- a/buildchain/buildchain/image.py +++ b/buildchain/buildchain/image.py @@ -184,6 +184,9 @@ def _operator_image(name: str, **kwargs: Any) -> targets.OperatorImage: 'kube-scheduler', 'nginx-ingress-defaultbackend-amd64', ], + constants.GRAFANA_REPOSITORY: [ + 'loki', + ], constants.INGRESS_REPOSITORY: [ 'nginx-ingress-controller', ], diff --git a/buildchain/buildchain/iso.py b/buildchain/buildchain/iso.py index 04d14201fb..75082acd71 100644 --- a/buildchain/buildchain/iso.py +++ b/buildchain/buildchain/iso.py @@ -55,6 +55,7 @@ Path('examples/new-node.yaml'), Path('examples/new-node_vagrant.yaml'), Path('examples/prometheus-sparse.yaml'), + Path('examples/loki-sparse.yaml'), ), destination_directory=constants.ISO_ROOT, task_dep=['_iso_mkdir_root'] diff --git a/buildchain/buildchain/salt_tree.py b/buildchain/buildchain/salt_tree.py index f0a6d27888..609abcfde7 100644 --- a/buildchain/buildchain/salt_tree.py +++ b/buildchain/buildchain/salt_tree.py @@ -269,6 +269,17 @@ def _get_parts(self) -> Iterator[str]: renderer=targets.Renderer.SLS, ), + Path('salt/metalk8s/addons/logging/deployed/init.sls'), + Path('salt/metalk8s/addons/logging/deployed/namespace.sls'), + Path('salt/metalk8s/addons/logging/loki/config/loki.yaml'), + Path('salt/metalk8s/addons/logging/loki/deployed/chart.sls'), + Path('salt/metalk8s/addons/logging/loki/deployed/init.sls'), + Path('salt/metalk8s/addons/logging/loki/deployed/', + 'loki-configuration-secret.sls'), + Path('salt/metalk8s/addons/logging/loki/deployed/', + 'service-configuration.sls'), + Path('salt/metalk8s/addons/logging/loki/deployed/storageclass.sls'), + Path('salt/metalk8s/addons/prometheus-adapter/deployed/chart.sls'), Path('salt/metalk8s/addons/prometheus-adapter/deployed/init.sls'), diff --git a/buildchain/buildchain/versions.py b/buildchain/buildchain/versions.py index d0844f7d29..81c67acc11 100644 --- a/buildchain/buildchain/versions.py +++ b/buildchain/buildchain/versions.py @@ -217,6 +217,11 @@ def _version_prefix(version: str, prefix: str = 'v') -> str: version='latest', digest=None, ), + Image( + name='loki', + version='1.5.0', + digest='sha256:922b3f412fdd9a8fb01115b6aebf5dac162647ce1c5ee3637ce1e2cff69e097b', + ), ) CONTAINER_IMAGES_MAP = {image.name: image for image in CONTAINER_IMAGES} diff --git a/charts/loki.yaml b/charts/loki.yaml new file mode 100644 index 0000000000..29a3926d0b --- /dev/null +++ b/charts/loki.yaml @@ -0,0 +1,250 @@ +image: + repository: '__image__(loki)' + tag: 1.5.0 + pullPolicy: IfNotPresent + + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + +ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: [] + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +## Affinity for pod assignment +## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +affinity: {} +# podAntiAffinity: +# requiredDuringSchedulingIgnoredDuringExecution: +# - labelSelector: +# matchExpressions: +# - key: app +# operator: In +# values: +# - loki +# topologyKey: "kubernetes.io/hostname" + +## StatefulSet annotations +annotations: {} + +# enable tracing for debug, need install jaeger and specify right jaeger_agent_host +tracing: + jaegerAgentHost: + +# we disable the whole config section as we will manage it through CSC +#config: +# auth_enabled: false +# ingester: +# chunk_idle_period: 3m +# chunk_block_size: 262144 +# chunk_retain_period: 1m +# max_transfer_retries: 0 +# lifecycler: +# ring: +# kvstore: +# store: inmemory +# replication_factor: 1 +# +# ## Different ring configs can be used. E.g. Consul +# # ring: +# # store: consul +# # replication_factor: 1 +# # consul: +# # host: "consul:8500" +# # prefix: "" +# # http_client_timeout: "20s" +# # consistent_reads: true +# limits_config: +# enforce_metric_name: false +# reject_old_samples: true +# reject_old_samples_max_age: 168h +# schema_config: +# configs: +# - from: 2018-04-15 +# store: boltdb +# object_store: filesystem +# schema: v9 +# index: +# prefix: index_ +# period: 168h +# server: +# http_listen_port: 3100 +# storage_config: +# boltdb: +# directory: /data/loki/index +# filesystem: +# directory: /data/loki/chunks +# chunk_store_config: +# max_look_back_period: 0s +# table_manager: +# retention_deletes_enabled: false +# retention_period: 0s + +## Additional Loki container arguments, e.g. log level (debug, info, warn, error) +extraArgs: {} + # log.level: debug + +livenessProbe: + httpGet: + path: /ready + port: http-metrics + initialDelaySeconds: 45 + +## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +networkPolicy: + enabled: false + +## The app name of loki clients +client: {} + # name: + +## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ +nodeSelector: {} + +## ref: https://kubernetes.io/docs/concepts/storage/persistent-volumes/ +## If you set enabled as "True", you need : +## - create a pv which above 10Gi and has same namespace with loki +## - keep storageClassName same with below setting +persistence: + enabled: true + accessModes: + - ReadWriteOnce + size: 10Gi + annotations: {} + storageClassName: metalk8s-loki + # subPath: "" + # existingClaim: + +## Pod Labels +podLabels: {} + +## Pod Annotations +podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "http-metrics" + +podManagementPolicy: OrderedReady + +## Assign a PriorityClassName to pods if set +# priorityClassName: + +rbac: + create: true + pspEnabled: true + +readinessProbe: + httpGet: + path: /ready + port: http-metrics + initialDelaySeconds: 45 + +replicas: '__var__(loki.spec.deployment.replicas)' + +resources: {} +# limits: +# cpu: 200m +# memory: 256Mi +# requests: +# cpu: 100m +# memory: 128Mi + +securityContext: + fsGroup: 10001 + runAsGroup: 10001 + runAsNonRoot: true + runAsUser: 10001 + +service: + type: ClusterIP + nodePort: + port: 3100 + annotations: {} + labels: {} + +serviceAccount: + create: true + name: + annotations: {} + +terminationGracePeriodSeconds: 4800 + +## Tolerations for pod assignment +## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +tolerations: + - key: "node-role.kubernetes.io/bootstrap" + operator: "Exists" + effect: "NoSchedule" + - key: "node-role.kubernetes.io/infra" + operator: "Exists" + effect: "NoSchedule" + +# The values to set in the PodDisruptionBudget spec +# If not set then a PodDisruptionBudget will not be created +podDisruptionBudget: {} +# minAvailable: 1 +# maxUnavailable: 1 + +updateStrategy: + type: RollingUpdate + +serviceMonitor: + enabled: true + interval: "" + additionalLabels: + release: prometheus-operator + annotations: {} + # scrapeTimeout: 10s + +initContainers: [] +## Init containers to be added to the loki pod. +# - name: my-init-container +# image: busybox:latest +# command: ['sh', '-c', 'echo hello'] + +extraContainers: [] +## Additional containers to be added to the loki pod. +# - name: reverse-proxy +# image: angelbarrera92/basic-auth-reverse-proxy:dev +# args: +# - "serve" +# - "--upstream=http://localhost:3100" +# - "--auth-config=/etc/reverse-proxy-conf/authn.yaml" +# ports: +# - name: http +# containerPort: 11811 +# protocol: TCP +# volumeMounts: +# - name: reverse-proxy-auth-config +# mountPath: /etc/reverse-proxy-conf + + +extraVolumes: [] +## Additional volumes to the loki pod. +# - name: reverse-proxy-auth-config +# secret: +# secretName: reverse-proxy-auth-config + +## Extra volume mounts that will be added to the loki container +extraVolumeMounts: [] + +extraPorts: [] +## Additional ports to the loki services. Useful to expose extra container ports. +# - port: 11811 +# protocol: TCP +# name: http +# targetPort: http + +# Extra env variables to pass to the loki container +env: [] diff --git a/charts/loki/.helmignore b/charts/loki/.helmignore new file mode 100644 index 0000000000..50af031725 --- /dev/null +++ b/charts/loki/.helmignore @@ -0,0 +1,22 @@ +# 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 +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/loki/Chart.yaml b/charts/loki/Chart.yaml new file mode 100644 index 0000000000..eb5aabd05d --- /dev/null +++ b/charts/loki/Chart.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +appVersion: v1.5.0 +description: 'Loki: like Prometheus, but for logs.' +engine: gotpl +home: https://grafana.com/loki +icon: https://github.com/grafana/loki/raw/master/docs/logo.png +kubeVersion: ^1.10.0-0 +maintainers: +- email: lokiproject@googlegroups.com + name: Loki Maintainers +name: loki +sources: +- https://github.com/grafana/loki +version: 0.30.2 diff --git a/charts/loki/README.md b/charts/loki/README.md new file mode 100644 index 0000000000..9d0cd692af --- /dev/null +++ b/charts/loki/README.md @@ -0,0 +1,62 @@ +# Loki Helm Chart + +## Prerequisites + +Make sure you have Helm [installed](https://helm.sh/docs/using_helm/#installing-helm) and +[deployed](https://helm.sh/docs/using_helm/#installing-tiller) to your cluster. Then add +Loki's chart repository to Helm: + +```bash +$ helm repo add loki https://grafana.github.io/loki/charts +``` + +You can update the chart repository by running: + +```bash +$ helm repo update +``` + +## Deploy Loki only + +```bash +$ helm upgrade --install loki loki/loki +``` + +## Run Loki behind https ingress + +If Loki and Promtail are deployed on different clusters you can add an Ingress in front of Loki. +By adding a certificate you create an https endpoint. For extra security enable basic authentication on the Ingress. + +In Promtail set the following values to communicate with https and basic auth + +``` +loki: + serviceScheme: https + user: user + password: pass +``` + +Sample helm template for ingress: +``` +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: +annotations: + kubernetes.io/ingress.class: {{ .Values.ingress.class }} + ingress.kubernetes.io/auth-type: "basic" + ingress.kubernetes.io/auth-secret: {{ .Values.ingress.basic.secret }} +name: loki +spec: +rules: +- host: {{ .Values.ingress.host }} + http: + paths: + - backend: + serviceName: loki + servicePort: 3100 +tls: +- secretName: {{ .Values.ingress.cert }} + hosts: + - {{ .Values.ingress.host }} +``` + diff --git a/charts/loki/templates/NOTES.txt b/charts/loki/templates/NOTES.txt new file mode 100644 index 0000000000..abe023a700 --- /dev/null +++ b/charts/loki/templates/NOTES.txt @@ -0,0 +1,3 @@ +Verify the application is working by running these commands: + kubectl --namespace {{ .Release.Namespace }} port-forward service/{{ include "loki.fullname" . }} {{ .Values.service.port }} + curl http://127.0.0.1:{{ .Values.service.port }}/api/prom/label diff --git a/charts/loki/templates/_helpers.tpl b/charts/loki/templates/_helpers.tpl new file mode 100644 index 0000000000..5e12a470f1 --- /dev/null +++ b/charts/loki/templates/_helpers.tpl @@ -0,0 +1,61 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "loki.name" -}} +{{- default .Chart.Name .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 "loki.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .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 -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "loki.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create the name of the service account +*/}} +{{- define "loki.serviceAccountName" -}} +{{- if .Values.serviceAccount.create -}} + {{ default (include "loki.fullname" .) .Values.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the app name of loki clients. Defaults to the same logic as "loki.fullname", and default client expects "promtail". +*/}} +{{- define "client.name" -}} +{{- if .Values.client.name -}} +{{- .Values.client.name -}} +{{- else if .Values.client.fullnameOverride -}} +{{- .Values.client.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default "promtail" .Values.client.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/charts/loki/templates/ingress.yaml b/charts/loki/templates/ingress.yaml new file mode 100644 index 0000000000..f68cda8ea5 --- /dev/null +++ b/charts/loki/templates/ingress.yaml @@ -0,0 +1,45 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "loki.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "loki.name" . }} + chart: {{ template "loki.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: +{{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} +{{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ . }} + backend: + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/networkpolicy.yaml b/charts/loki/templates/networkpolicy.yaml new file mode 100644 index 0000000000..5d73832296 --- /dev/null +++ b/charts/loki/templates/networkpolicy.yaml @@ -0,0 +1,26 @@ +{{- if .Values.networkPolicy.enabled }} +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ template "loki.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "loki.name" . }} + chart: {{ template "loki.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +spec: + podSelector: + matchLabels: + name: {{ template "loki.fullname" . }} + app: {{ template "loki.name" . }} + release: {{ .Release.Name }} + ingress: + - from: + - podSelector: + matchLabels: + app: {{ template "client.name" . }} + release: {{ .Release.Name }} + - ports: + - port: {{ .Values.service.port }} +{{- end }} diff --git a/charts/loki/templates/pdb.yaml b/charts/loki/templates/pdb.yaml new file mode 100644 index 0000000000..c64ad507ed --- /dev/null +++ b/charts/loki/templates/pdb.yaml @@ -0,0 +1,17 @@ +{{- if .Values.podDisruptionBudget -}} +apiVersion: policy/v1beta1 +kind: PodDisruptionBudget +metadata: + name: {{ template "loki.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "loki.name" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + chart: {{ template "loki.chart" . }} +spec: + selector: + matchLabels: + app: {{ template "loki.name" . }} +{{ toYaml .Values.podDisruptionBudget | indent 2 }} +{{- end }} diff --git a/charts/loki/templates/podsecuritypolicy.yaml b/charts/loki/templates/podsecuritypolicy.yaml new file mode 100644 index 0000000000..ce1c1c109a --- /dev/null +++ b/charts/loki/templates/podsecuritypolicy.yaml @@ -0,0 +1,41 @@ +{{- if .Values.rbac.pspEnabled }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "loki.fullname" . }} + labels: + app: {{ template "loki.name" . }} + chart: {{ template "loki.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +spec: + privileged: false + allowPrivilegeEscalation: false + volumes: + - 'configMap' + - 'emptyDir' + - 'persistentVolumeClaim' + - 'secret' + - 'projected' + - 'downwardAPI' + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + rule: 'MustRunAsNonRoot' + seLinux: + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + - min: 1 + max: 65535 + readOnlyRootFilesystem: true + requiredDropCapabilities: + - ALL +{{- end }} diff --git a/charts/loki/templates/role.yaml b/charts/loki/templates/role.yaml new file mode 100644 index 0000000000..b7bfb29d68 --- /dev/null +++ b/charts/loki/templates/role.yaml @@ -0,0 +1,20 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "loki.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "loki.name" . }} + chart: {{ template "loki.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +{{- if .Values.rbac.pspEnabled }} +rules: +- apiGroups: ['extensions'] + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: [{{ template "loki.fullname" . }}] +{{- end }} +{{- end }} + diff --git a/charts/loki/templates/rolebinding.yaml b/charts/loki/templates/rolebinding.yaml new file mode 100644 index 0000000000..41fc5039fe --- /dev/null +++ b/charts/loki/templates/rolebinding.yaml @@ -0,0 +1,20 @@ +{{- if .Values.rbac.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "loki.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "loki.name" . }} + chart: {{ template "loki.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "loki.fullname" . }} +subjects: +- kind: ServiceAccount + name: {{ template "loki.serviceAccountName" . }} +{{- end }} + diff --git a/charts/loki/templates/secret.yaml b/charts/loki/templates/secret.yaml new file mode 100644 index 0000000000..40d50c8ee8 --- /dev/null +++ b/charts/loki/templates/secret.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Secret +metadata: + name: {{ template "loki.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "loki.name" . }} + chart: {{ template "loki.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} +data: + loki.yaml: {{ tpl (toYaml .Values.config) . | b64enc}} diff --git a/charts/loki/templates/service-headless.yaml b/charts/loki/templates/service-headless.yaml new file mode 100644 index 0000000000..644f527625 --- /dev/null +++ b/charts/loki/templates/service-headless.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "loki.fullname" . }}-headless + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "loki.name" . }} + chart: {{ template "loki.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + variant: headless +spec: + clusterIP: None + ports: + - port: {{ .Values.service.port }} + protocol: TCP + name: http-metrics + targetPort: http-metrics +{{- if .Values.extraPorts }} +{{ toYaml .Values.extraPorts | indent 4}} +{{- end }} + selector: + app: {{ template "loki.name" . }} + release: {{ .Release.Name }} diff --git a/charts/loki/templates/service.yaml b/charts/loki/templates/service.yaml new file mode 100644 index 0000000000..2eb419c02f --- /dev/null +++ b/charts/loki/templates/service.yaml @@ -0,0 +1,43 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ template "loki.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "loki.name" . }} + chart: {{ template "loki.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- with .Values.service.labels }} + {{- toYaml . | nindent 4 }} + {{- end }} + annotations: + {{- toYaml .Values.service.annotations | nindent 4 }} +spec: + type: {{ .Values.service.type }} +{{- if (and (eq .Values.service.type "ClusterIP") (not (empty .Values.service.clusterIP))) }} + clusterIP: {{ .Values.service.clusterIP }} +{{- end }} +{{- if (and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerIP))) }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} +{{- end }} +{{- if .Values.service.loadBalancerSourceRanges }} + loadBalancerSourceRanges: + {{- range $cidr := .Values.service.loadBalancerSourceRanges }} + - {{ $cidr }} + {{- end }} +{{- end }} + ports: + - port: {{ .Values.service.port }} + protocol: TCP + name: http-metrics + targetPort: http-metrics +{{- if (and (eq .Values.service.type "NodePort") (not (empty .Values.service.nodePort))) }} + nodePort: {{ .Values.service.nodePort }} +{{- end }} +{{- if .Values.extraPorts }} +{{ toYaml .Values.extraPorts | indent 4}} +{{- end }} + selector: + app: {{ template "loki.name" . }} + release: {{ .Release.Name }} diff --git a/charts/loki/templates/serviceaccount.yaml b/charts/loki/templates/serviceaccount.yaml new file mode 100644 index 0000000000..ccbad8d926 --- /dev/null +++ b/charts/loki/templates/serviceaccount.yaml @@ -0,0 +1,15 @@ +{{- if .Values.serviceAccount.create }} +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: {{ template "loki.name" . }} + chart: {{ template "loki.chart" . }} + heritage: {{ .Release.Service }} + release: {{ .Release.Name }} + annotations: + {{- toYaml .Values.serviceAccount.annotations | nindent 4 }} + name: {{ template "loki.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} +{{- end }} + diff --git a/charts/loki/templates/servicemonitor.yaml b/charts/loki/templates/servicemonitor.yaml new file mode 100644 index 0000000000..f3b4abe532 --- /dev/null +++ b/charts/loki/templates/servicemonitor.yaml @@ -0,0 +1,35 @@ +{{- if .Values.serviceMonitor.enabled }} +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: {{ template "loki.fullname" . }} + labels: + app: {{ template "loki.name" . }} + chart: {{ template "loki.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + {{- if .Values.serviceMonitor.additionalLabels }} +{{ toYaml .Values.serviceMonitor.additionalLabels | indent 4 }} + {{- end }} + {{- if .Values.serviceMonitor.annotations }} + annotations: +{{ toYaml .Values.serviceMonitor.annotations | indent 4 }} + {{- end }} +spec: + selector: + matchLabels: + app: {{ template "loki.name" . }} + release: {{ .Release.Name | quote }} + variant: headless + namespaceSelector: + matchNames: + - {{ .Release.Namespace | quote }} + endpoints: + - port: http-metrics + {{- if .Values.serviceMonitor.interval }} + interval: {{ .Values.serviceMonitor.interval }} + {{- end }} + {{- if .Values.serviceMonitor.scrapeTimeout }} + scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }} + {{- end }} +{{- end }} diff --git a/charts/loki/templates/statefulset.yaml b/charts/loki/templates/statefulset.yaml new file mode 100644 index 0000000000..0b22337125 --- /dev/null +++ b/charts/loki/templates/statefulset.yaml @@ -0,0 +1,128 @@ +apiVersion: apps/v1 +kind: StatefulSet +metadata: + name: {{ template "loki.fullname" . }} + namespace: {{ .Release.Namespace }} + labels: + app: {{ template "loki.name" . }} + chart: {{ template "loki.chart" . }} + release: {{ .Release.Name }} + heritage: {{ .Release.Service }} + annotations: + {{- toYaml .Values.annotations | nindent 4 }} +spec: + podManagementPolicy: {{ .Values.podManagementPolicy }} + replicas: {{ .Values.replicas }} + selector: + matchLabels: + app: {{ template "loki.name" . }} + release: {{ .Release.Name }} + serviceName: {{ template "loki.fullname" . }}-headless + updateStrategy: + {{- toYaml .Values.updateStrategy | nindent 4 }} + template: + metadata: + labels: + app: {{ template "loki.name" . }} + name: {{ template "loki.name" . }} + release: {{ .Release.Name }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + annotations: + checksum/config: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + serviceAccountName: {{ template "loki.serviceAccountName" . }} + {{- if .Values.priorityClassName }} + priorityClassName: {{ .Values.priorityClassName }} + {{- end }} + securityContext: + {{- toYaml .Values.securityContext | nindent 8 }} + initContainers: + {{- toYaml .Values.initContainers | nindent 8 }} + {{- if .Values.image.pullSecrets }} + imagePullSecrets: + {{- range .Values.image.pullSecrets }} + - name: {{ . }} + {{- end}} + {{- end }} + containers: + - name: {{ .Chart.Name }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + args: + - "-config.file=/etc/loki/loki.yaml" + {{- range $key, $value := .Values.extraArgs }} + - "-{{ $key }}={{ $value }}" + {{- end }} + volumeMounts: + {{- if .Values.extraVolumeMounts }} + {{ toYaml .Values.extraVolumeMounts | nindent 12}} + {{- end }} + - name: config + mountPath: /etc/loki + - name: storage + mountPath: "/data" + subPath: {{ .Values.persistence.subPath }} + ports: + - name: http-metrics + containerPort: {{ .Values.config.server.http_listen_port }} + protocol: TCP + livenessProbe: + {{- toYaml .Values.livenessProbe | nindent 12 }} + readinessProbe: + {{- toYaml .Values.readinessProbe | nindent 12 }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + securityContext: + readOnlyRootFilesystem: true + env: + {{- if .Values.env }} + {{- toYaml .Values.env | nindent 12 }} + {{- end }} + {{- if .Values.tracing.jaegerAgentHost }} + - name: JAEGER_AGENT_HOST + value: "{{ .Values.tracing.jaegerAgentHost }}" + {{- end }} +{{- if .Values.extraContainers }} +{{ toYaml .Values.extraContainers | indent 8}} +{{- end }} + nodeSelector: + {{- toYaml .Values.nodeSelector | nindent 8 }} + affinity: + {{- toYaml .Values.affinity | nindent 8 }} + tolerations: + {{- toYaml .Values.tolerations | nindent 8 }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + volumes: + - name: config + secret: + secretName: {{ template "loki.fullname" . }} +{{- if .Values.extraVolumes }} +{{ toYaml .Values.extraVolumes | indent 8}} +{{- end }} + {{- if not .Values.persistence.enabled }} + - name: storage + emptyDir: {} + {{- else if .Values.persistence.existingClaim }} + - name: storage + persistentVolumeClaim: + claimName: {{ .Values.persistence.existingClaim }} + {{- else }} + volumeClaimTemplates: + - metadata: + name: storage + annotations: + {{- toYaml .Values.persistence.annotations | nindent 8 }} + spec: + accessModes: + {{- toYaml .Values.persistence.accessModes | nindent 8 }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} + storageClassName: {{ .Values.persistence.storageClassName }} + {{- end }} + diff --git a/charts/loki/values.yaml b/charts/loki/values.yaml new file mode 100644 index 0000000000..05b662366e --- /dev/null +++ b/charts/loki/values.yaml @@ -0,0 +1,241 @@ +image: + repository: grafana/loki + tag: 1.5.0 + pullPolicy: IfNotPresent + + ## Optionally specify an array of imagePullSecrets. + ## Secrets must be manually created in the namespace. + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + # pullSecrets: + # - myRegistryKeySecretName + +ingress: + enabled: false + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: [] + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +## Affinity for pod assignment +## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity +affinity: {} +# podAntiAffinity: +# requiredDuringSchedulingIgnoredDuringExecution: +# - labelSelector: +# matchExpressions: +# - key: app +# operator: In +# values: +# - loki +# topologyKey: "kubernetes.io/hostname" + +## StatefulSet annotations +annotations: {} + +# enable tracing for debug, need install jaeger and specify right jaeger_agent_host +tracing: + jaegerAgentHost: + +config: + auth_enabled: false + ingester: + chunk_idle_period: 3m + chunk_block_size: 262144 + chunk_retain_period: 1m + max_transfer_retries: 0 + lifecycler: + ring: + kvstore: + store: inmemory + replication_factor: 1 + + ## Different ring configs can be used. E.g. Consul + # ring: + # store: consul + # replication_factor: 1 + # consul: + # host: "consul:8500" + # prefix: "" + # http_client_timeout: "20s" + # consistent_reads: true + limits_config: + enforce_metric_name: false + reject_old_samples: true + reject_old_samples_max_age: 168h + schema_config: + configs: + - from: 2018-04-15 + store: boltdb + object_store: filesystem + schema: v9 + index: + prefix: index_ + period: 168h + server: + http_listen_port: 3100 + storage_config: + boltdb: + directory: /data/loki/index + filesystem: + directory: /data/loki/chunks + chunk_store_config: + max_look_back_period: 0s + table_manager: + retention_deletes_enabled: false + retention_period: 0s + +## Additional Loki container arguments, e.g. log level (debug, info, warn, error) +extraArgs: {} + # log.level: debug + +livenessProbe: + httpGet: + path: /ready + port: http-metrics + initialDelaySeconds: 45 + +## ref: https://kubernetes.io/docs/concepts/services-networking/network-policies/ +networkPolicy: + enabled: false + +## The app name of loki clients +client: {} + # name: + +## ref: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/ +nodeSelector: {} + +## ref: https://kubernetes.io/docs/concepts/storage/persistent-volumes/ +## If you set enabled as "True", you need : +## - create a pv which above 10Gi and has same namespace with loki +## - keep storageClassName same with below setting +persistence: + enabled: false + accessModes: + - ReadWriteOnce + size: 10Gi + annotations: {} + # subPath: "" + # existingClaim: + +## Pod Labels +podLabels: {} + +## Pod Annotations +podAnnotations: + prometheus.io/scrape: "true" + prometheus.io/port: "http-metrics" + +podManagementPolicy: OrderedReady + +## Assign a PriorityClassName to pods if set +# priorityClassName: + +rbac: + create: true + pspEnabled: true + +readinessProbe: + httpGet: + path: /ready + port: http-metrics + initialDelaySeconds: 45 + +replicas: 1 + +resources: {} +# limits: +# cpu: 200m +# memory: 256Mi +# requests: +# cpu: 100m +# memory: 128Mi + +securityContext: + fsGroup: 10001 + runAsGroup: 10001 + runAsNonRoot: true + runAsUser: 10001 + +service: + type: ClusterIP + nodePort: + port: 3100 + annotations: {} + labels: {} + +serviceAccount: + create: true + name: + annotations: {} + +terminationGracePeriodSeconds: 4800 + +## Tolerations for pod assignment +## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +tolerations: [] + +# The values to set in the PodDisruptionBudget spec +# If not set then a PodDisruptionBudget will not be created +podDisruptionBudget: {} +# minAvailable: 1 +# maxUnavailable: 1 + +updateStrategy: + type: RollingUpdate + +serviceMonitor: + enabled: false + interval: "" + additionalLabels: {} + annotations: {} + # scrapeTimeout: 10s + +initContainers: [] +## Init containers to be added to the loki pod. +# - name: my-init-container +# image: busybox:latest +# command: ['sh', '-c', 'echo hello'] + +extraContainers: [] +## Additional containers to be added to the loki pod. +# - name: reverse-proxy +# image: angelbarrera92/basic-auth-reverse-proxy:dev +# args: +# - "serve" +# - "--upstream=http://localhost:3100" +# - "--auth-config=/etc/reverse-proxy-conf/authn.yaml" +# ports: +# - name: http +# containerPort: 11811 +# protocol: TCP +# volumeMounts: +# - name: reverse-proxy-auth-config +# mountPath: /etc/reverse-proxy-conf + + +extraVolumes: [] +## Additional volumes to the loki pod. +# - name: reverse-proxy-auth-config +# secret: +# secretName: reverse-proxy-auth-config + +## Extra volume mounts that will be added to the loki container +extraVolumeMounts: [] + +extraPorts: [] +## Additional ports to the loki services. Useful to expose extra container ports. +# - port: 11811 +# protocol: TCP +# name: http +# targetPort: http + +# Extra env variables to pass to the loki container +env: [] diff --git a/charts/render.py b/charts/render.py index 64b31423ef..3ca72c5926 100755 --- a/charts/render.py +++ b/charts/render.py @@ -116,6 +116,15 @@ def fixup_doc(doc): return doc +def remove_doc(doc, remove_manifests): + for to_remove in remove_manifests: + if doc.get('kind') == to_remove[0] and \ + doc.get('metadata').get('name') == to_remove[1]: + return True + + return False + + def keep_doc(doc): if not doc: return False @@ -202,6 +211,32 @@ def main(): ) parser.add_argument('values', help="Our custom chart values") + class ActionServiceConfigArgs(argparse.Action): + def __call__(self, parser, args, values, option_string=None): + if len(values) > 3: + raise argparse.ArgumentTypeError( + 'Argument "{0}" requires between 1 and 3 arguments' + .format(option_string) + ) + + name = values.pop(0) + try: + configmap = values.pop(0) + except IndexError: + configmap = 'metalk8s-{0}-config'.format(name) + try: + path = values.pop(0) + except IndexError: + path = 'metalk8s/addons/{0}/config/{1}.yaml'.format( + args.name, name + ) + + option = getattr(args, self.dest) + if option is None: + setattr(args, self.dest, [[name, configmap, path]]) + else: + option.append([name, configmap, path]) + ''' To use this argument, follow the format below: --service-config service_name service_configmap_name @@ -214,16 +249,25 @@ def main(): # Todo: Add kind & apiVersion to the service-config nargs parser.add_argument( '--service-config', - action='append', - nargs=2, + action=ActionServiceConfigArgs, + nargs='+', required=False, dest="service_configs", - help="Example: --service-config grafana metalk8s-grafana-config" + help="Example: --service-config grafana metalk8s-grafana-config " + "metalk8s/addons/prometheus-operator/config/grafana.yaml" ) parser.add_argument( '--drop-prometheus-rules', help="YAML formatted file to drop some pre-defined Prometheus rules" ) + parser.add_argument( + '--remove-manifest', + action='append', + nargs=2, + dest="remove_manifests", + metavar=('KIND', 'NAME'), + help="Remove a given manifest from the resulting chart", + ) parser.add_argument('path', help="Path to the chart directory") args = parser.parse_args() @@ -252,26 +296,25 @@ def fixup(doc): ) ) if doc else None - if args.service_configs: - import_csc_yaml = '\n'.join( - ("{{% import_yaml 'metalk8s/addons/{0}/config/{1}.yaml' as " - "{1}_defaults with context %}}").format( - args.name, service_config[0] - ) for service_config in args.service_configs + import_csc_yaml = [] + config = [] + for name, configmap, path in args.service_configs: + import_csc_yaml.append( + "{{% import_yaml '{0}' as {1}_defaults with context %}}".format( + path, name + ) ) - - config = '\n'.join( - ("{{%- set {0} = salt.metalk8s_service_configuration" - ".get_service_conf('{1}', '{2}', {0}_defaults) %}}").format( - service_config[0], args.namespace, service_config[1] - ) for service_config in args.service_configs + config.append( + "{{%- set {0} = salt.metalk8s_service_configuration" + ".get_service_conf('{1}', '{2}', {0}_defaults) %}}".format( + name, args.namespace, configmap + ) ) - else: - import_csc_yaml = '' - config = '' sys.stdout.write(START_BLOCK.format( - csc_defaults=import_csc_yaml, configlines=config).lstrip() + csc_defaults='\n'.join(import_csc_yaml), + configlines='\n'.join(config) + ).lstrip() ) sys.stdout.write('\n') @@ -279,7 +322,7 @@ def fixup(doc): for doc in yaml.safe_load_all(template): if keep_doc(doc): doc = fixup(doc) - if doc: + if doc and not remove_doc(doc, args.remove_manifests): manifests.append(doc) stream = io.StringIO() diff --git a/eve/create-volumes.sh b/eve/create-volumes.sh index eef6274686..d82bdd46bf 100755 --- a/eve/create-volumes.sh +++ b/eve/create-volumes.sh @@ -58,6 +58,9 @@ echo "Creating storage volumes" sed "s/NODE_NAME/${NODE_NAME}/" \ "${PRODUCT_MOUNT}/examples/prometheus-sparse.yaml" | \ kubectl apply -f - +sed "s/NODE_NAME/${NODE_NAME}/" \ + "${PRODUCT_MOUNT}/examples/loki-sparse.yaml" | \ + kubectl apply -f - wait_for_pv() { local -r pv="$1" @@ -72,6 +75,7 @@ wait_for_pv() { wait_for_pv "$NODE_NAME-alertmanager" wait_for_pv "$NODE_NAME-prometheus" +wait_for_pv "$NODE_NAME-loki" wait_for_pod() { local -r name="$1" namespace="$2" pod="$3" @@ -89,3 +93,5 @@ wait_for_pod "AlertManager" \ metalk8s-monitoring alertmanager-prometheus-operator-alertmanager-0 wait_for_pod "Prometheus" \ metalk8s-monitoring prometheus-prometheus-operator-prometheus-0 +wait_for_pod "Loki" \ + metalk8s-logging loki-0 diff --git a/examples/loki-sparse.yaml b/examples/loki-sparse.yaml new file mode 100644 index 0000000000..3482184bb5 --- /dev/null +++ b/examples/loki-sparse.yaml @@ -0,0 +1,13 @@ +# This file contains manifests to create `sparseLoopDevice` volumes for +# Loki to be deployed. +# Make sure to change the `nodeName` to the node name of your bootstrap node. +--- +apiVersion: storage.metalk8s.scality.com/v1alpha1 +kind: Volume +metadata: + name: NODE_NAME-loki +spec: + nodeName: NODE_NAME + storageClassName: metalk8s-loki + sparseLoopDevice: + size: 10Gi diff --git a/salt/metalk8s/addons/logging/deployed/init.sls b/salt/metalk8s/addons/logging/deployed/init.sls new file mode 100644 index 0000000000..9c4ea8df4d --- /dev/null +++ b/salt/metalk8s/addons/logging/deployed/init.sls @@ -0,0 +1,3 @@ +include: + - .namespace + - metalk8s.addons.logging.loki.deployed diff --git a/salt/metalk8s/addons/logging/deployed/namespace.sls b/salt/metalk8s/addons/logging/deployed/namespace.sls new file mode 100644 index 0000000000..0ddf9203ae --- /dev/null +++ b/salt/metalk8s/addons/logging/deployed/namespace.sls @@ -0,0 +1,10 @@ +#! metalk8s_kubernetes + +apiVersion: v1 +kind: Namespace +metadata: + name: metalk8s-logging + labels: + app.kubernetes.io/managed-by: metalk8s + app.kubernetes.io/part-of: metalk8s + heritage: metalk8s diff --git a/salt/metalk8s/addons/logging/loki/config/loki.yaml b/salt/metalk8s/addons/logging/loki/config/loki.yaml new file mode 100644 index 0000000000..58c31a1e4b --- /dev/null +++ b/salt/metalk8s/addons/logging/loki/config/loki.yaml @@ -0,0 +1,44 @@ +--- +# Configuration of the Loki service +apiVersion: addons.metalk8s.scality.com +kind: LokiConfig +spec: + deployment: + replicas: 1 + config: + auth_enabled: false + chunk_store_config: + max_look_back_period: 0s + ingester: + chunk_block_size: 262144 + chunk_idle_period: 3m + chunk_retain_period: 1m + lifecycler: + ring: + kvstore: + store: inmemory + replication_factor: 1 + max_transfer_retries: 0 + limits_config: + enforce_metric_name: false + reject_old_samples: true + reject_old_samples_max_age: 168h + schema_config: + configs: + - from: 2018-04-15 + index: + period: 168h + prefix: index_ + object_store: filesystem + schema: v9 + store: boltdb + server: + http_listen_port: 3100 + storage_config: + boltdb: + directory: /data/loki/index + filesystem: + directory: /data/loki/chunks + table_manager: + retention_deletes_enabled: false + retention_period: 0s diff --git a/salt/metalk8s/addons/logging/loki/deployed/chart.sls b/salt/metalk8s/addons/logging/loki/deployed/chart.sls new file mode 100644 index 0000000000..76ad471c52 --- /dev/null +++ b/salt/metalk8s/addons/logging/loki/deployed/chart.sls @@ -0,0 +1,285 @@ +#!jinja | metalk8s_kubernetes + +{%- from "metalk8s/repo/macro.sls" import build_image_name with context %} +{% import_yaml 'metalk8s/addons/logging/loki/config/loki.yaml' as loki_defaults with context %} +{%- set loki = salt.metalk8s_service_configuration.get_service_conf('metalk8s-logging', 'metalk8s-loki-config', loki_defaults) %} + +{% raw %} + +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + labels: + app: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + chart: loki-0.30.2 + heritage: metalk8s + release: loki + name: loki + namespace: metalk8s-logging +spec: + allowPrivilegeEscalation: false + fsGroup: + ranges: + - max: 65535 + min: 1 + rule: MustRunAs + hostIPC: false + hostNetwork: false + hostPID: false + privileged: false + readOnlyRootFilesystem: true + requiredDropCapabilities: + - ALL + runAsUser: + rule: MustRunAsNonRoot + seLinux: + rule: RunAsAny + supplementalGroups: + ranges: + - max: 65535 + min: 1 + rule: MustRunAs + volumes: + - configMap + - emptyDir + - persistentVolumeClaim + - secret + - projected + - downwardAPI +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + annotations: {} + labels: + app: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + chart: loki-0.30.2 + heritage: metalk8s + release: loki + name: loki + namespace: metalk8s-logging +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + chart: loki-0.30.2 + heritage: metalk8s + release: loki + name: loki + namespace: metalk8s-logging +rules: +- apiGroups: + - extensions + resourceNames: + - loki + resources: + - podsecuritypolicies + verbs: + - use +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + chart: loki-0.30.2 + heritage: metalk8s + release: loki + name: loki + namespace: metalk8s-logging +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: loki +subjects: +- kind: ServiceAccount + name: loki +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + chart: loki-0.30.2 + heritage: metalk8s + release: loki + variant: headless + name: loki-headless + namespace: metalk8s-logging +spec: + clusterIP: None + ports: + - name: http-metrics + port: 3100 + protocol: TCP + targetPort: http-metrics + selector: + app: loki + release: loki +--- +apiVersion: v1 +kind: Service +metadata: + annotations: {} + labels: + app: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + chart: loki-0.30.2 + heritage: metalk8s + release: loki + name: loki + namespace: metalk8s-logging +spec: + ports: + - name: http-metrics + port: 3100 + protocol: TCP + targetPort: http-metrics + selector: + app: loki + release: loki + type: ClusterIP +--- +apiVersion: apps/v1 +kind: StatefulSet +metadata: + annotations: {} + labels: + app: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + chart: loki-0.30.2 + heritage: metalk8s + release: loki + name: loki + namespace: metalk8s-logging +spec: + podManagementPolicy: OrderedReady + replicas: {% endraw -%}{{ loki.spec.deployment.replicas }}{%- raw %} + selector: + matchLabels: + app: loki + release: loki + serviceName: loki-headless + template: + metadata: + annotations: + checksum/config: f6917e98336282f93b8cf80b99ca1e78f7adfd297b98c367c1cc420a1cb8d1ac + prometheus.io/port: http-metrics + prometheus.io/scrape: 'true' + labels: + app: loki + name: loki + release: loki + spec: + affinity: {} + containers: + - args: + - -config.file=/etc/loki/loki.yaml + env: null + image: {% endraw -%}{{ build_image_name("loki", False) }}{%- raw %}:1.5.0 + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /ready + port: http-metrics + initialDelaySeconds: 45 + name: loki + ports: + - containerPort: 3100 + name: http-metrics + protocol: TCP + readinessProbe: + httpGet: + path: /ready + port: http-metrics + initialDelaySeconds: 45 + resources: {} + securityContext: + readOnlyRootFilesystem: true + volumeMounts: + - mountPath: /etc/loki + name: config + - mountPath: /data + name: storage + subPath: null + initContainers: [] + nodeSelector: {} + securityContext: + fsGroup: 10001 + runAsGroup: 10001 + runAsNonRoot: true + runAsUser: 10001 + serviceAccountName: loki + terminationGracePeriodSeconds: 4800 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/bootstrap + operator: Exists + - effect: NoSchedule + key: node-role.kubernetes.io/infra + operator: Exists + volumes: + - name: config + secret: + secretName: loki + updateStrategy: + type: RollingUpdate + volumeClaimTemplates: + - metadata: + annotations: {} + name: storage + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 10Gi + storageClassName: metalk8s-loki +--- +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + app: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + chart: loki-0.30.2 + heritage: metalk8s + release: prometheus-operator + name: loki + namespace: metalk8s-logging +spec: + endpoints: + - port: http-metrics + namespaceSelector: + matchNames: + - metalk8s-logging + selector: + matchLabels: + app: loki + release: loki + variant: headless + +{% endraw %} diff --git a/salt/metalk8s/addons/logging/loki/deployed/init.sls b/salt/metalk8s/addons/logging/loki/deployed/init.sls new file mode 100644 index 0000000000..4a8b5eba88 --- /dev/null +++ b/salt/metalk8s/addons/logging/loki/deployed/init.sls @@ -0,0 +1,5 @@ +include: + - .service-configuration + - .loki-configuration-secret + - .chart + - .storageclass diff --git a/salt/metalk8s/addons/logging/loki/deployed/loki-configuration-secret.sls b/salt/metalk8s/addons/logging/loki/deployed/loki-configuration-secret.sls new file mode 100644 index 0000000000..557c8848c8 --- /dev/null +++ b/salt/metalk8s/addons/logging/loki/deployed/loki-configuration-secret.sls @@ -0,0 +1,26 @@ +{% import_yaml 'metalk8s/addons/logging/loki/config/loki.yaml' as loki_defaults with context %} + +{%- set loki = salt.metalk8s_service_configuration.get_service_conf( + 'metalk8s-logging', 'metalk8s-loki-config', loki_defaults + ) +%} + +Create Loki Configuration Secret: + metalk8s_kubernetes.object_present: + - manifest: + apiVersion: v1 + kind: Secret + metadata: + labels: + app: loki + app.kubernetes.io/managed-by: salt + app.kubernetes.io/name: loki + app.kubernetes.io/part-of: metalk8s + chart: loki-0.30.2 + heritage: metalk8s + release: loki + name: loki + namespace: metalk8s-logging + stringData: + loki.yaml: |- + {{ loki.spec.config | tojson }} diff --git a/salt/metalk8s/addons/logging/loki/deployed/service-configuration.sls b/salt/metalk8s/addons/logging/loki/deployed/service-configuration.sls new file mode 100644 index 0000000000..376176b681 --- /dev/null +++ b/salt/metalk8s/addons/logging/loki/deployed/service-configuration.sls @@ -0,0 +1,33 @@ +include: + - ...deployed.namespace + +{%- set loki_config = salt.metalk8s_kubernetes.get_object( + kind='ConfigMap', + apiVersion='v1', + namespace='metalk8s-logging', + name='metalk8s-loki-config', + ) +%} + +{%- if loki_config is none %} + +Create metalk8s-loki-config ConfigMap: + metalk8s_kubernetes.object_present: + - manifest: + apiVersion: v1 + kind: ConfigMap + metadata: + name: metalk8s-loki-config + namespace: metalk8s-logging + data: + config.yaml: |- + apiVersion: addons.metalk8s.scality.com + kind: LokiConfig + spec: {} + +{%- else %} + +metalk8s-loki-config ConfigMap already exist: + test.succeed_without_changes: [] + +{%- endif %} diff --git a/salt/metalk8s/addons/logging/loki/deployed/storageclass.sls b/salt/metalk8s/addons/logging/loki/deployed/storageclass.sls new file mode 100644 index 0000000000..ba5aa5e96f --- /dev/null +++ b/salt/metalk8s/addons/logging/loki/deployed/storageclass.sls @@ -0,0 +1,18 @@ +#! metalk8s_kubernetes + +apiVersion: storage.k8s.io/v1 +kind: StorageClass +metadata: + name: metalk8s-loki + labels: + app.kubernetes.io/managed-by: salt + app.kubernetes.io/part-of: metalk8s +provisioner: kubernetes.io/no-provisioner +reclaimPolicy: Retain +volumeBindingMode: WaitForFirstConsumer +mountOptions: + - rw + - discard +parameters: + fsType: ext4 + mkfsOptions: '["-m", "0"]' diff --git a/salt/metalk8s/deployed.sls b/salt/metalk8s/deployed.sls index 4e8a8272bf..8310ea1418 100644 --- a/salt/metalk8s/deployed.sls +++ b/salt/metalk8s/deployed.sls @@ -12,3 +12,4 @@ include: - metalk8s.addons.ui.deployed - metalk8s.addons.dex.deployed - metalk8s.addons.prometheus-adapter.deployed + - metalk8s.addons.logging.deployed diff --git a/salt/metalk8s/service-configuration/deployed/init.sls b/salt/metalk8s/service-configuration/deployed/init.sls index 851a3e7786..6e6b2f4572 100644 --- a/salt/metalk8s/service-configuration/deployed/init.sls +++ b/salt/metalk8s/service-configuration/deployed/init.sls @@ -8,3 +8,4 @@ include: - metalk8s.addons.prometheus-operator.deployed.service-configuration - metalk8s.addons.dex.deployed.service-configuration + - metalk8s.addons.logging.loki.deployed.service-configuration diff --git a/tests/post/features/logging.feature b/tests/post/features/logging.feature new file mode 100644 index 0000000000..ed464619b4 --- /dev/null +++ b/tests/post/features/logging.feature @@ -0,0 +1,15 @@ +@post @ci @local @logging +Feature: Logging stack is up and running + Scenario: List Pods + Given the Kubernetes API is available + Then the 'pods' list should not be empty in the 'metalk8s-logging' namespace + + Scenario: Expected Pods + Given the Kubernetes API is available + Then we have 1 running pod labeled 'app=loki' in namespace 'metalk8s-logging' + + Scenario: Pushing log to Loki directly + Given the Kubernetes API is available + And the Loki API is available + When we push an example log to Loki + Then we can query this example log from Loki diff --git a/tests/post/steps/test_logging.py b/tests/post/steps/test_logging.py new file mode 100644 index 0000000000..10725a5229 --- /dev/null +++ b/tests/post/steps/test_logging.py @@ -0,0 +1,136 @@ +import json +import time +import uuid + +import pytest +from pytest_bdd import scenario, given, when, then + +from tests import utils + +# Fixtures {{{ + +@pytest.fixture(scope='function') +def context(): + return {} + + +# }}} +# Scenario {{{ + + +@scenario('../features/logging.feature', 'List Pods') +def test_list_pods(host): + pass + + +@scenario('../features/logging.feature', 'Expected Pods') +def test_expected_pods(host): + pass + + +@scenario('../features/logging.feature', 'Pushing log to Loki directly') +def test_push_log_to_loki(host): + pass + + +# }}} +# Given {{{ + +@given("the Loki API is available") +def check_loki_api(k8s_client): + def _check_loki_ready(): + try: + response = k8s_client.connect_get_namespaced_service_proxy_with_path( + 'loki:http-metrics', 'metalk8s-logging', + path='ready' + ) + except Exception as exc: # pylint: disable=broad-except + assert False, str(exc) + assert response == 'ready\n' + + utils.retry( + _check_loki_ready, + times=10, + wait=2, + name="checking Loki API ready" + ) + + +# }}} +# When {{{ + +@when("we push an example log to Loki") +def push_log_to_loki(k8s_client, context): + context['test_log_id'] = str(uuid.uuid1()) + + # With current k8s client we cannot pass Body so we need to + # use `call_api` directly + # https://github.com/kubernetes-client/python/issues/325 + path_params = { + 'name': 'loki:http-metrics', + 'namespace': 'metalk8s-logging', + 'path': 'loki/api/v1/push' + } + body = { + "streams": [ + { + "stream": { + "reason": "TestLog", + "identifier": context['test_log_id'] + }, + "values": [ + [str(int(time.time() * 1e9)), "My Simple Test Log Line"] + ] + } + ] + } + response = k8s_client.api_client.call_api( + '/api/v1/namespaces/{namespace}/services/{name}/proxy/{path}', + 'POST', + path_params, + [], + {"Accept": "*/*"}, + body=body, + response_type='str', + auth_settings=["BearerToken"] + ) + assert response[1] == 204, response + + +# }}} +# Then {{{ + +@then("we can query this example log from Loki") +def query_log_from_loki(k8s_client, context): + # With current k8s client we cannot pass query_params so we need to + # use `call_api` directly + path_params = { + 'name': 'loki:http-metrics', + 'namespace': 'metalk8s-logging', + 'path': 'loki/api/v1/query' + } + query_params = { + 'query': '{identifier="' + context['test_log_id'] + '"}' + } + response = k8s_client.api_client.call_api( + '/api/v1/namespaces/{namespace}/services/{name}/proxy/{path}', + 'GET', + path_params, + query_params, + {"Accept": "*/*"}, + response_type=object, + auth_settings=["BearerToken"] + ) + + assert response[0]['status'] == 'success' + + result_data = response[0]['data']['result'] + + assert result_data, \ + 'No test log found in Loki with identifier={}'.format( + context['test_log_id'] + ) + assert result_data[0]['stream']['identifier'] == context['test_log_id'] + + +# }}}