diff --git a/charts/checkmk/Chart.yaml b/charts/checkmk/Chart.yaml new file mode 100644 index 0000000..e5a9a97 --- /dev/null +++ b/charts/checkmk/Chart.yaml @@ -0,0 +1,18 @@ +apiVersion: v2 +name: checkmk +description: Checkmk monitoring platform helm chart for Kubernetes +type: application +version: 0.1.0 +appVersion: "2.5.0p6" +maintainers: + - name: Richard Tomik + email: richard.tomik@proton.me +keywords: + - monitoring + - checkmk + - infrastructure + - observability +home: https://github.com/rtomik/helm-charts +sources: + - https://checkmk.com + - https://hub.docker.com/r/checkmk/check-mk-community diff --git a/charts/checkmk/NOTES.txt b/charts/checkmk/NOTES.txt new file mode 100644 index 0000000..59f2203 --- /dev/null +++ b/charts/checkmk/NOTES.txt @@ -0,0 +1,56 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "checkmk.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "checkmk.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "checkmk.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "checkmk.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:5000/{{ .Values.config.siteId }}/check_mk/ to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 5000:5000 +{{- end }} + +2. Checkmk web interface is available at: + http:///{{ .Values.config.siteId }}/check_mk/ + +3. Default credentials: + Username: cmkadmin + Password: (as configured in config.adminPassword) + +4. Ports: + - Web interface: {{ .Values.service.port }} → container 5000 + - Agent Receiver: {{ .Values.service.agentReceiverPort }} → container 8000 + +{{- if .Values.persistence.enabled }} +5. Persistent storage: {{ if .Values.persistence.existingClaim }}{{ .Values.persistence.existingClaim }}{{ else }}{{ include "checkmk.fullname" . }}-sites{{ end }} ({{ .Values.persistence.size }}) + Mounted at /omd/sites — contains all site data, configs, and RRD files. +{{- else }} +5. WARNING: No persistent storage enabled. All monitoring data will be lost on pod restart. + Enable persistence in values.yaml for production use. +{{- end }} + +{{- if .Values.config.livestatusTcp }} +6. Livestatus TCP is enabled. Ensure appropriate NetworkPolicies are in place. +{{- end }} + +{{- if not .Values.config.adminPassword.existingSecret }} +7. SECURITY NOTE: For production use, store the admin password in a Kubernetes Secret: + kubectl create secret generic checkmk-secrets \ + --from-literal=cmk-password= + Then set config.adminPassword.existingSecret=checkmk-secrets in your values. +{{- else }} +7. Admin password read from existing secret: {{ .Values.config.adminPassword.existingSecret }} +{{- end }} + +For more information, see the official Checkmk Docker documentation: +https://docs.checkmk.com/latest/en/introduction_docker.html diff --git a/charts/checkmk/readme.md b/charts/checkmk/readme.md new file mode 100644 index 0000000..32ac215 --- /dev/null +++ b/charts/checkmk/readme.md @@ -0,0 +1,91 @@ +# Checkmk Helm Chart + +Helm chart for deploying [Checkmk](https://checkmk.com/) — an infrastructure and application monitoring platform — on Kubernetes. + +## Overview + +Checkmk uses OMD (Open Monitoring Distribution) to manage monitoring sites. This chart deploys the Community Edition using the official Docker image with: + +- Persistent storage for all site data (`/omd/sites`) +- RAM-backed tmpfs for the site temp directory (performance optimization from the official docs) +- Separate service ports for the web interface (5000) and agent receiver (8000) +- Admin password stored in a Kubernetes Secret + +## Prerequisites + +- Kubernetes 1.19+ +- Helm 3.0+ +- A default StorageClass or an existing PersistentVolumeClaim + +## Quick Start + +```bash +helm install checkmk ./charts/checkmk \ + --set config.adminPassword.value=mysecretpassword \ + --set ingress.enabled=true \ + --set ingress.hosts[0].host=checkmk.example.com \ + --set ingress.hosts[0].paths[0].path=/ \ + --set ingress.hosts[0].paths[0].pathType=Prefix +``` + +After installation, access the UI at `http:///cmk/check_mk/` with username `cmkadmin`. + +## Configuration + +### Admin Password (recommended: use existing secret) + +```bash +kubectl create secret generic checkmk-secrets \ + --from-literal=cmk-password= +``` + +```yaml +config: + adminPassword: + existingSecret: "checkmk-secrets" + passwordKey: "cmk-password" +``` + +### Site ID + +The `config.siteId` value sets the Checkmk site name and determines the URL path (`//check_mk/`). Defaults to `cmk`. + +### Livestatus TCP + +Enable Livestatus TCP for distributed monitoring setups or external integrations: + +```yaml +config: + livestatusTcp: true +``` + +### Persistence + +Site data (hosts, checks, RRD files) is stored in `/omd/sites`. A 5 Gi PVC is created by default. Adjust size or use an existing claim: + +```yaml +persistence: + size: 20Gi + storageClass: "fast-ssd" +``` + +## Key Values + +| Key | Default | Description | +|-----|---------|-------------| +| `image.tag` | `2.5.0p6` | Checkmk Community image tag | +| `config.siteId` | `cmk` | Monitoring site name | +| `config.timezone` | `UTC` | Container timezone | +| `config.adminPassword.value` | `changeme` | cmkadmin password (use existingSecret in production) | +| `config.livestatusTcp` | `false` | Enable Livestatus over TCP | +| `config.mailRelayHost` | `""` | SMTP relay for notifications | +| `service.port` | `5000` | Web interface port | +| `service.agentReceiverPort` | `8000` | Agent registration port | +| `persistence.enabled` | `true` | Enable persistent storage | +| `persistence.size` | `5Gi` | PVC size | + +## Security Notes + +Checkmk (OMD) requires root access inside the container to manage monitoring sites and switch to the site user account. The `podSecurityContext.runAsUser` is set to `0` and `containerSecurityContext.allowPrivilegeEscalation` is `true` by default. + +Place a reverse proxy (e.g. Traefik or nginx) in front for TLS termination rather than exposing the container port directly. diff --git a/charts/checkmk/templates/_helpers.tpl b/charts/checkmk/templates/_helpers.tpl new file mode 100644 index 0000000..728271a --- /dev/null +++ b/charts/checkmk/templates/_helpers.tpl @@ -0,0 +1,66 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "checkmk.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +*/}} +{{- define "checkmk.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- printf "%s" $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "checkmk.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "checkmk.labels" -}} +helm.sh/chart: {{ include "checkmk.chart" . }} +{{ include "checkmk.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "checkmk.selectorLabels" -}} +app.kubernetes.io/name: {{ include "checkmk.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Name of the secret holding CMK_PASSWORD +*/}} +{{- define "checkmk.secretName" -}} +{{- .Values.config.adminPassword.existingSecret | default (printf "%s-secrets" (include "checkmk.fullname" .)) }} +{{- end }} + +{{/* +Web UI path for health probes: //check_mk/login.py +*/}} +{{- define "checkmk.probePath" -}} +{{- printf "/%s/check_mk/login.py" .Values.config.siteId }} +{{- end }} + +{{/* +tmpfs mount path derived from site ID +*/}} +{{- define "checkmk.tmpPath" -}} +{{- printf "/opt/omd/sites/%s/tmp" .Values.config.siteId }} +{{- end }} diff --git a/charts/checkmk/templates/deployment.yaml b/charts/checkmk/templates/deployment.yaml new file mode 100644 index 0000000..311385c --- /dev/null +++ b/charts/checkmk/templates/deployment.yaml @@ -0,0 +1,135 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "checkmk.fullname" . }} + labels: + {{- include "checkmk.labels" . | nindent 4 }} + annotations: + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} +spec: + replicas: {{ .Values.replicaCount }} + revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} + selector: + matchLabels: + {{- include "checkmk.selectorLabels" . | nindent 6 }} + strategy: + type: Recreate + template: + metadata: + labels: + {{- include "checkmk.selectorLabels" . | nindent 8 }} + annotations: + {{- with .Values.podAnnotations }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.containerSecurityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 5000 + protocol: TCP + - name: agent-receiver + containerPort: 8000 + protocol: TCP + {{- if .Values.probes.startup.enabled }} + startupProbe: + httpGet: + path: {{ include "checkmk.probePath" . }} + port: http + initialDelaySeconds: {{ .Values.probes.startup.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.startup.periodSeconds }} + timeoutSeconds: {{ .Values.probes.startup.timeoutSeconds }} + failureThreshold: {{ .Values.probes.startup.failureThreshold }} + successThreshold: {{ .Values.probes.startup.successThreshold }} + {{- end }} + {{- if .Values.probes.liveness.enabled }} + livenessProbe: + httpGet: + path: {{ include "checkmk.probePath" . }} + port: http + periodSeconds: {{ .Values.probes.liveness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.liveness.failureThreshold }} + successThreshold: {{ .Values.probes.liveness.successThreshold }} + {{- end }} + {{- if .Values.probes.readiness.enabled }} + readinessProbe: + httpGet: + path: {{ include "checkmk.probePath" . }} + port: http + periodSeconds: {{ .Values.probes.readiness.periodSeconds }} + timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }} + failureThreshold: {{ .Values.probes.readiness.failureThreshold }} + successThreshold: {{ .Values.probes.readiness.successThreshold }} + {{- end }} + env: + - name: CMK_PASSWORD + valueFrom: + secretKeyRef: + name: {{ include "checkmk.secretName" . }} + key: {{ .Values.config.adminPassword.passwordKey }} + - name: CMK_SITE_ID + value: {{ .Values.config.siteId | quote }} + - name: TZ + value: {{ .Values.config.timezone | quote }} + {{- if .Values.config.livestatusTcp }} + - name: CMK_LIVESTATUS_TCP + value: "on" + {{- end }} + {{- if .Values.config.mailRelayHost }} + - name: MAIL_RELAY_HOST + value: {{ .Values.config.mailRelayHost | quote }} + {{- end }} + {{- with .Values.extraEnv }} + {{- toYaml . | nindent 12 }} + {{- end }} + volumeMounts: + - name: sites + mountPath: /omd/sites + # tmpfs for site temp dir — improves performance by using host RAM + # equivalent to Docker's --tmpfs /opt/omd/sites//tmp:uid=1000,gid=1000 + - name: tmp + mountPath: {{ include "checkmk.tmpPath" . }} + {{- with .Values.extraVolumeMounts }} + {{- toYaml . | nindent 12 }} + {{- end }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + volumes: + {{- if .Values.persistence.enabled }} + - name: sites + persistentVolumeClaim: + claimName: {{ if .Values.persistence.existingClaim }}{{ .Values.persistence.existingClaim }}{{ else }}{{ include "checkmk.fullname" . }}-sites{{ end }} + {{- else }} + - name: sites + emptyDir: {} + {{- end }} + - name: tmp + emptyDir: + medium: Memory + {{- with .Values.extraVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- 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/checkmk/templates/ingress.yaml b/charts/checkmk/templates/ingress.yaml new file mode 100644 index 0000000..0bd5f26 --- /dev/null +++ b/charts/checkmk/templates/ingress.yaml @@ -0,0 +1,43 @@ +{{- if .Values.ingress.enabled -}} +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: {{ include "checkmk.fullname" . }} + labels: + {{- include "checkmk.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.className }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + {{- if .secretName }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + pathType: {{ .pathType }} + backend: + service: + name: {{ include "checkmk.fullname" $ }} + port: + number: {{ $.Values.service.port }} + {{- end }} + {{- end }} +{{- end }} diff --git a/charts/checkmk/templates/pvc.yaml b/charts/checkmk/templates/pvc.yaml new file mode 100644 index 0000000..175504a --- /dev/null +++ b/charts/checkmk/templates/pvc.yaml @@ -0,0 +1,21 @@ +{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }} +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: {{ include "checkmk.fullname" . }}-sites + labels: + {{- include "checkmk.labels" . | nindent 4 }} + {{- with .Values.persistence.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + accessModes: + - {{ .Values.persistence.accessMode | quote }} + {{- if .Values.persistence.storageClass }} + storageClassName: {{ .Values.persistence.storageClass | quote }} + {{- end }} + resources: + requests: + storage: {{ .Values.persistence.size | quote }} +{{- end }} diff --git a/charts/checkmk/templates/secret.yaml b/charts/checkmk/templates/secret.yaml new file mode 100644 index 0000000..74e9f0b --- /dev/null +++ b/charts/checkmk/templates/secret.yaml @@ -0,0 +1,11 @@ +{{- if not .Values.config.adminPassword.existingSecret }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "checkmk.fullname" . }}-secrets + labels: + {{- include "checkmk.labels" . | nindent 4 }} +type: Opaque +data: + {{ .Values.config.adminPassword.passwordKey }}: {{ .Values.config.adminPassword.value | default "changeme" | b64enc }} +{{- end }} diff --git a/charts/checkmk/templates/service.yaml b/charts/checkmk/templates/service.yaml new file mode 100644 index 0000000..22c289c --- /dev/null +++ b/charts/checkmk/templates/service.yaml @@ -0,0 +1,25 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "checkmk.fullname" . }} + labels: + {{- include "checkmk.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + - port: {{ .Values.service.agentReceiverPort }} + targetPort: agent-receiver + protocol: TCP + name: agent-receiver + {{- if .Values.config.livestatusTcp }} + - port: 6557 + targetPort: 6557 + protocol: TCP + name: livestatus + {{- end }} + selector: + {{- include "checkmk.selectorLabels" . | nindent 4 }} diff --git a/charts/checkmk/values.yaml b/charts/checkmk/values.yaml new file mode 100644 index 0000000..ee088a1 --- /dev/null +++ b/charts/checkmk/values.yaml @@ -0,0 +1,131 @@ +## Global settings +nameOverride: "" +fullnameOverride: "" + +## Image settings +image: + repository: checkmk/check-mk-community + tag: "2.5.0p6" + pullPolicy: IfNotPresent + +## Deployment settings +replicaCount: 1 +revisionHistoryLimit: 3 + +# Pod security settings +# Checkmk (OMD) requires root to manage monitoring sites and switch to site users. +# fsGroup: 1000 matches Docker's --tmpfs uid=1000,gid=1000 so the site user can write to tmp. +podSecurityContext: + runAsNonRoot: false + runAsUser: 0 + fsGroup: 1000 + +containerSecurityContext: + allowPrivilegeEscalation: true + readOnlyRootFilesystem: false + +## Pod scheduling +nodeSelector: {} +tolerations: [] +affinity: {} + +## Pod annotations +podAnnotations: {} + +## Service settings +service: + type: ClusterIP + port: 5000 + agentReceiverPort: 8000 + +## Ingress settings +ingress: + enabled: false + className: "" + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + hosts: + - host: checkmk.domain.com + paths: + - path: / + pathType: Prefix + tls: + - hosts: + - checkmk.domain.com + # secretName: "existing-tls-secret" + +## Persistence settings for /omd/sites (all site data, configs, and RRDs) +persistence: + enabled: true + existingClaim: "" + storageClass: "" + accessMode: ReadWriteOnce + size: 5Gi + annotations: {} + +## Resource limits and requests +# resources: +# limits: +# cpu: 2000m +# memory: 2Gi +# requests: +# cpu: 500m +# memory: 512Mi + +## Application health checks +# startupProbe absorbs slow first-boot (site init + DB creation) so liveness/readiness +# don't fire until the site is actually up. Budget: 120 * 10s = 20 minutes max. +probes: + startup: + enabled: true + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 10 + failureThreshold: 120 + successThreshold: 1 + liveness: + enabled: true + periodSeconds: 30 + timeoutSeconds: 10 + failureThreshold: 6 + successThreshold: 1 + readiness: + enabled: true + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 3 + successThreshold: 1 + +## Checkmk configuration +config: + # Site name — also determines the URL path: //check_mk/ + siteId: "cmk" + + # Timezone (e.g. Europe/Berlin) + timezone: "UTC" + + # Enable Livestatus TCP access (for distributed monitoring or external tools). + # When enabled, port 6557 is added to the Service. + livestatusTcp: false + + # SMTP relay host for notifications (leave empty to disable) + mailRelayHost: "" + + ## Admin (cmkadmin) password + adminPassword: + # Use an existing Kubernetes secret + existingSecret: "" + passwordKey: "cmk-password" + # Or set directly (not recommended for production) + value: "changeme" + +# Extra environment variables +extraEnv: [] + # - name: CMK_LIVESTATUS_TCP + # value: "on" + +# Extra volume mounts +extraVolumeMounts: [] + +# Extra volumes +extraVolumes: []