From e6f3536e9ec906e4c07c0b837c60f9d42cc7f485 Mon Sep 17 00:00:00 2001 From: Roger Sikorski Date: Mon, 27 Oct 2025 09:44:37 +0100 Subject: [PATCH] immich added --- .gitignore | 5 + .vscode/extensions.json | 10 + .vscode/settings.json | 8 + cluster/apps/immich/.envrc | 3 + .../apps/immich/ImageUpdateAutomation.yaml | 28 + cluster/apps/immich/LimitRange.yaml | 14 + cluster/apps/immich/configmap.yml | 13 + cluster/apps/immich/db/Cluster.yaml | 50 + cluster/apps/immich/db/ObjectStore.yaml | 0 cluster/apps/immich/db/ScheduledBackup.yaml | 17 + .../immich/exporter/GrafanaDashboard.yaml | 1851 +++++++++++++++++ .../exporter/ImageAutomation/ImagePolicy.yaml | 12 + .../ImageAutomation/ImageRepository.yaml | 12 + cluster/apps/immich/exporter/Service.yaml | 15 + .../apps/immich/exporter/ServiceMonitor.yaml | 18 + cluster/apps/immich/exporter/configmap.yaml | 12 + cluster/apps/immich/exporter/deployment.yaml | 65 + .../immich/exporter/secret/secret.yaml.dist | 10 + .../immich/machine-learning/DaemonSet.yaml | 84 + .../ImageAutomation/ImagePolicy.yaml | 15 + .../ImageAutomation/ImageRepository.yaml | 17 + cluster/apps/immich/machine-learning/pvc.yaml | 16 + .../apps/immich/machine-learning/service.yml | 15 + cluster/apps/immich/namespace.yaml | 12 + .../server/ImageAutomation/ImagePolicy.yaml | 15 + .../ImageAutomation/ImageRepository.yaml | 14 + cluster/apps/immich/server/IngressRoute.yml | 25 + cluster/apps/immich/server/Service.yaml | 21 + .../apps/immich/server/ServiceMonitor.yaml | 28 + cluster/apps/immich/server/deployment.yaml | 99 + cluster/apps/immich/server/pvc/app.yaml | 16 + .../immich/server/pvc/synology-photos.yaml | 16 + cluster/apps/immich/server/pvc/upload.yaml | 16 + .../valkey/ImageAutomation/ImagePolicy.yaml | 12 + .../ImageAutomation/ImageRepository.yaml | 16 + cluster/apps/immich/valkey/deployment.yaml | 75 + cluster/apps/immich/valkey/service.yaml | 15 + 37 files changed, 2670 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 cluster/apps/immich/.envrc create mode 100644 cluster/apps/immich/ImageUpdateAutomation.yaml create mode 100644 cluster/apps/immich/LimitRange.yaml create mode 100644 cluster/apps/immich/configmap.yml create mode 100644 cluster/apps/immich/db/Cluster.yaml create mode 100644 cluster/apps/immich/db/ObjectStore.yaml create mode 100644 cluster/apps/immich/db/ScheduledBackup.yaml create mode 100644 cluster/apps/immich/exporter/GrafanaDashboard.yaml create mode 100644 cluster/apps/immich/exporter/ImageAutomation/ImagePolicy.yaml create mode 100644 cluster/apps/immich/exporter/ImageAutomation/ImageRepository.yaml create mode 100644 cluster/apps/immich/exporter/Service.yaml create mode 100644 cluster/apps/immich/exporter/ServiceMonitor.yaml create mode 100644 cluster/apps/immich/exporter/configmap.yaml create mode 100644 cluster/apps/immich/exporter/deployment.yaml create mode 100644 cluster/apps/immich/exporter/secret/secret.yaml.dist create mode 100644 cluster/apps/immich/machine-learning/DaemonSet.yaml create mode 100644 cluster/apps/immich/machine-learning/ImageAutomation/ImagePolicy.yaml create mode 100644 cluster/apps/immich/machine-learning/ImageAutomation/ImageRepository.yaml create mode 100644 cluster/apps/immich/machine-learning/pvc.yaml create mode 100644 cluster/apps/immich/machine-learning/service.yml create mode 100644 cluster/apps/immich/namespace.yaml create mode 100644 cluster/apps/immich/server/ImageAutomation/ImagePolicy.yaml create mode 100644 cluster/apps/immich/server/ImageAutomation/ImageRepository.yaml create mode 100644 cluster/apps/immich/server/IngressRoute.yml create mode 100644 cluster/apps/immich/server/Service.yaml create mode 100644 cluster/apps/immich/server/ServiceMonitor.yaml create mode 100644 cluster/apps/immich/server/deployment.yaml create mode 100644 cluster/apps/immich/server/pvc/app.yaml create mode 100644 cluster/apps/immich/server/pvc/synology-photos.yaml create mode 100644 cluster/apps/immich/server/pvc/upload.yaml create mode 100644 cluster/apps/immich/valkey/ImageAutomation/ImagePolicy.yaml create mode 100644 cluster/apps/immich/valkey/ImageAutomation/ImageRepository.yaml create mode 100644 cluster/apps/immich/valkey/deployment.yaml create mode 100644 cluster/apps/immich/valkey/service.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da2ffc0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +secret.yml +secret.yaml +.local + +.env diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..c284d85 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "EditorConfig.EditorConfig", + // Code formatter using prettier + "esbenp.prettier-vscode", + "nefrob.vscode-just-syntax" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3bbb4a1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.formatOnPaste": false, + "vs-kubernetes": { + "disable-linters": ["resource-limits"] + } +} diff --git a/cluster/apps/immich/.envrc b/cluster/apps/immich/.envrc new file mode 100644 index 0000000..b866c39 --- /dev/null +++ b/cluster/apps/immich/.envrc @@ -0,0 +1,3 @@ +source "$(git rev-parse --show-toplevel)/.envrc" +export KUBECTL_NAMESPACE="$(grep "name:" namespace.yaml | awk '{print $2}')" +kubectl config set-context --current --namespace=$KUBECTL_NAMESPACE diff --git a/cluster/apps/immich/ImageUpdateAutomation.yaml b/cluster/apps/immich/ImageUpdateAutomation.yaml new file mode 100644 index 0000000..2f0e2e5 --- /dev/null +++ b/cluster/apps/immich/ImageUpdateAutomation.yaml @@ -0,0 +1,28 @@ +--- +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImageUpdateAutomation +metadata: + name: gitea + namespace: immich +spec: + interval: 30m + sourceRef: + kind: GitRepository + name: flux-system + namespace: flux-system + git: + checkout: + ref: + branch: main + commit: + author: + email: fluxcdbot@gitea.example.cloud + name: fluxcdbot + messageTemplate: + '{{range .Changed.Changes}}{{print .OldValue}} -> {{println + .NewValue}}{{end}}' + push: + branch: main + update: + path: ./cluster/apps/immich/ + strategy: Setters diff --git a/cluster/apps/immich/LimitRange.yaml b/cluster/apps/immich/LimitRange.yaml new file mode 100644 index 0000000..a7d177f --- /dev/null +++ b/cluster/apps/immich/LimitRange.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: LimitRange +metadata: + name: immich + namespace: immich +spec: + limits: + - default: + memory: 100Mi + defaultRequest: + cpu: 10m + memory: 100Mi + type: Container diff --git a/cluster/apps/immich/configmap.yml b/cluster/apps/immich/configmap.yml new file mode 100644 index 0000000..4804587 --- /dev/null +++ b/cluster/apps/immich/configmap.yml @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: immich + namespace: immich + labels: + app: immich +data: + # https://immich.app/docs/install/environment-variables/ + TZ: 'Europe/Berlin' + REDIS_HOSTNAME: 'immich-valkey.immich.svc.cluster.local' + IMMICH_TELEMETRY_INCLUDE: 'all' diff --git a/cluster/apps/immich/db/Cluster.yaml b/cluster/apps/immich/db/Cluster.yaml new file mode 100644 index 0000000..fc62189 --- /dev/null +++ b/cluster/apps/immich/db/Cluster.yaml @@ -0,0 +1,50 @@ +--- +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: immich-db + namespace: immich + labels: + app: immich-db + velero.io/exclude-from-backup: 'true' +spec: + # https://github.com/tensorchord/cloudnative-vectorchord/pkgs/container/cloudnative-vectorchord + imageName: ghcr.io/tensorchord/cloudnative-vectorchord:16-0.4.3 + instances: 2 + + resources: + limits: + memory: 4Gi + requests: + memory: 2Gi + cpu: 20m + + # longhorn: because of easier size restriction + storage: + size: 4Gi + storageClass: longhorn + + managed: + roles: + - name: app + # we make it as superuser otherwise it can happen that immich fails to start because if is checking / creating the extensions in postgres db + # it can be done manually but this makes life much easier + superuser: true + login: true + + postgresql: + parameters: + shared_buffers: 2GB + archive_timeout: 60min + shared_preload_libraries: + - 'vchord.so' + enableAlterSystem: true + + plugins: + - name: barman-cloud.cloudnative-pg.io + isWALArchiver: true + parameters: + barmanObjectName: immich-db + + monitoring: + enablePodMonitor: true diff --git a/cluster/apps/immich/db/ObjectStore.yaml b/cluster/apps/immich/db/ObjectStore.yaml new file mode 100644 index 0000000..e69de29 diff --git a/cluster/apps/immich/db/ScheduledBackup.yaml b/cluster/apps/immich/db/ScheduledBackup.yaml new file mode 100644 index 0000000..104c0d6 --- /dev/null +++ b/cluster/apps/immich/db/ScheduledBackup.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: postgresql.cnpg.io/v1 +kind: ScheduledBackup +metadata: + name: immich-db-daily + namespace: immich + labels: + app: immich-db +spec: + cluster: + name: immich-db + schedule: '0 0 2 * * *' + backupOwnerReference: self + immediate: true + method: plugin + pluginConfiguration: + name: barman-cloud.cloudnative-pg.io diff --git a/cluster/apps/immich/exporter/GrafanaDashboard.yaml b/cluster/apps/immich/exporter/GrafanaDashboard.yaml new file mode 100644 index 0000000..3084fec --- /dev/null +++ b/cluster/apps/immich/exporter/GrafanaDashboard.yaml @@ -0,0 +1,1851 @@ +--- +apiVersion: grafana.integreatly.org/v1beta1 +kind: GrafanaDashboard +metadata: + name: immich + namespace: immich + labels: + app: immich-exporter +spec: + allowCrossNamespaceImport: true # allow another namespace as monitoring + resyncPeriod: 60s + instanceSelector: + matchLabels: + app.kubernetes.io/instance: grafana-operator + folder: 'k8s monitoring' + # https://github.com/friendlyFriend4000/prometheus-immich-exporter/tree/master/grafana + json: > + { + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "10.1.5" + }, + { + "type": "panel", + "id": "piechart", + "name": "Pie chart", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "text": { + "titleSize": 6 + }, + "textMode": "name" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "exemplar": false, + "expr": "immich_server_info_version_number", + "instant": true, + "legendFormat": "{{version}}", + "range": false, + "refId": "A" + } + ], + "title": "Immich version", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 4, + "y": 0 + }, + "id": 10, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "immich_server_stats_user_count", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "User count", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 8, + "y": 0 + }, + "id": 18, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "sum(immich_server_stats_usage_by_users)", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Disk space used by Immich", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 12, + "y": 0 + }, + "id": 12, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "immich_server_info_totalDiskSize", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Total disk size", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 16, + "y": 0 + }, + "id": 14, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "immich_server_info_diskUse", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Total disk size usage", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 4, + "x": 20, + "y": 0 + }, + "id": 16, + "options": { + "colorMode": "none", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "exemplar": false, + "expr": "100 - immich_server_info_diskUsagePercentage", + "format": "time_series", + "instant": true, + "interval": "", + "legendFormat": "__auto", + "range": false, + "refId": "A" + } + ], + "title": "Remaining disk size", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "yellow", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 3 + }, + "hideTimeOverride": false, + "id": 24, + "options": { + "colorMode": "none", + "graphMode": "area", + "justifyMode": "center", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "exemplar": false, + "expr": "delta(immich_server_stats_usage_growth[24h])", + "instant": false, + "interval": "", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1d", + "title": "Daily data growth", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 3 + }, + "hideTimeOverride": false, + "id": 26, + "options": { + "colorMode": "none", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_usage_growth[1w])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1w", + "title": "Weekly data growth", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 3 + }, + "id": 8, + "options": { + "displayLabels": [ + "value" + ], + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true, + "values": [ + "percent", + "value" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "immich_server_stats_usage_by_users", + "legendFormat": "{{firstName}}", + "range": true, + "refId": "A" + } + ], + "title": "Disk usage by user", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 7 + }, + "hideTimeOverride": false, + "id": 28, + "options": { + "colorMode": "none", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_usage_growth[4w])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1M", + "title": "Monthly data growth", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 7 + }, + "hideTimeOverride": false, + "id": 30, + "options": { + "colorMode": "none", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_usage_growth[1y])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1y", + "title": "Yearly data growth", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 11 + }, + "hideTimeOverride": false, + "id": 32, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_photos_growth[24h])", + "interval": "", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1d", + "title": "Daily photos growth", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 11 + }, + "hideTimeOverride": false, + "id": 34, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_photos_growth[1w])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1w", + "title": "Weekly photos growth", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#FF9830", + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 11 + }, + "id": 4, + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value", + "percent" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "exemplar": false, + "expr": "immich_server_stats_photos_by_users", + "format": "time_series", + "instant": false, + "legendFormat": "{{firstName}}", + "range": true, + "refId": "A" + } + ], + "title": "Total photos by user", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 15 + }, + "hideTimeOverride": false, + "id": 36, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_photos_growth[4w])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1M", + "title": "Monthly photos growth", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 15 + }, + "hideTimeOverride": false, + "id": 38, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_photos_growth[1y])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1y", + "title": "Yearly photos growth", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "description": "amount of photos over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "right", + "axisSoftMin": 9000, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 1, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 19 + }, + "hideTimeOverride": false, + "id": 22, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_photos_by_users[4w])", + "legendFormat": "{{firstName}}", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1M", + "title": "Photos Growth", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "description": "amount of photos over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "right", + "axisSoftMin": 9000, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 1, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 26 + }, + "hideTimeOverride": false, + "id": 47, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_photos_by_users[1y])", + "legendFormat": "{{firstName}}", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1y", + "title": "Photos Growth", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 33 + }, + "hideTimeOverride": false, + "id": 42, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_videos_growth[24h])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1d", + "title": "Daily video growth", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 33 + }, + "hideTimeOverride": false, + "id": 40, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_videos_growth[1w])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1w", + "title": "Weekly videos growth", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 33 + }, + "id": 6, + "options": { + "displayLabels": [ + "percent" + ], + "legend": { + "displayMode": "list", + "placement": "right", + "showLegend": true, + "values": [ + "percent", + "value" + ] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "immich_server_stats_videos_by_users", + "legendFormat": "{{firstName}}", + "range": true, + "refId": "A" + } + ], + "title": "Total videos by user", + "type": "piechart" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 0, + "y": 37 + }, + "hideTimeOverride": false, + "id": 44, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_videos_growth[4w])\r\n", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1M", + "title": "Monthly videos growth", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 4, + "w": 6, + "x": 6, + "y": 37 + }, + "hideTimeOverride": false, + "id": 46, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "10.1.5", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_videos_growth[1y])", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1y", + "title": "Yearly video growth", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "description": "amount of videos over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "right", + "axisSoftMin": 9000, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 1, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 41 + }, + "hideTimeOverride": false, + "id": 49, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_videos_by_users[4w])", + "legendFormat": "{{firstName}}", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1M", + "title": "Videos Growth", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "description": "amount of videos over time", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "right", + "axisSoftMin": 9000, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 1, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 48 + }, + "hideTimeOverride": false, + "id": 50, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": null + }, + "editorMode": "code", + "expr": "delta(immich_server_stats_videos_by_users[1y])", + "legendFormat": "{{firstName}}", + "range": true, + "refId": "A" + } + ], + "timeFrom": "1y", + "title": "Videos Growth", + "type": "timeseries" + } + ], + "refresh": "30s", + "revision": 1, + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "immich", + "uid": "ZWWp3aa4k", + "version": 31, + "weekStart": "" + } diff --git a/cluster/apps/immich/exporter/ImageAutomation/ImagePolicy.yaml b/cluster/apps/immich/exporter/ImageAutomation/ImagePolicy.yaml new file mode 100644 index 0000000..8c713ae --- /dev/null +++ b/cluster/apps/immich/exporter/ImageAutomation/ImagePolicy.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImagePolicy +metadata: + name: immich-exporter + namespace: immich +spec: + imageRepositoryRef: + name: immich-exporter + policy: + semver: + range: '1.*.*' diff --git a/cluster/apps/immich/exporter/ImageAutomation/ImageRepository.yaml b/cluster/apps/immich/exporter/ImageAutomation/ImageRepository.yaml new file mode 100644 index 0000000..088b335 --- /dev/null +++ b/cluster/apps/immich/exporter/ImageAutomation/ImageRepository.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImageRepository +metadata: + name: immich-exporter + namespace: immich +spec: + # https://hub.docker.com/r/friendlyfriend/prometheus-immich-exporter/tags + image: friendlyfriend/prometheus-immich-exporter + interval: 24h + exclusionList: + - latest diff --git a/cluster/apps/immich/exporter/Service.yaml b/cluster/apps/immich/exporter/Service.yaml new file mode 100644 index 0000000..6c4a048 --- /dev/null +++ b/cluster/apps/immich/exporter/Service.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: immich-exporter + namespace: immich + labels: + app: immich-exporter +spec: + ports: + - name: http + port: 80 + targetPort: http + selector: + app: immich-exporter diff --git a/cluster/apps/immich/exporter/ServiceMonitor.yaml b/cluster/apps/immich/exporter/ServiceMonitor.yaml new file mode 100644 index 0000000..0c01405 --- /dev/null +++ b/cluster/apps/immich/exporter/ServiceMonitor.yaml @@ -0,0 +1,18 @@ +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: immich-exporter + namespace: immich + labels: + app: immich-exporter +spec: + selector: + matchLabels: + app: immich-exporter + endpoints: + - port: http # port name of service + scheme: http + interval: 15s + metricRelabelings: + - action: labeldrop + regex: (instance|pod) diff --git a/cluster/apps/immich/exporter/configmap.yaml b/cluster/apps/immich/exporter/configmap.yaml new file mode 100644 index 0000000..938c7a0 --- /dev/null +++ b/cluster/apps/immich/exporter/configmap.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: immich-exporter + namespace: immich + labels: + app: immich-exporter +data: + # https://immich.app/docs/install/environment-variables/ + IMMICH_HOST: 'immich-server.immich.svc.cluster.local' + IMMICH_PORT: '80' diff --git a/cluster/apps/immich/exporter/deployment.yaml b/cluster/apps/immich/exporter/deployment.yaml new file mode 100644 index 0000000..cce7c2a --- /dev/null +++ b/cluster/apps/immich/exporter/deployment.yaml @@ -0,0 +1,65 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: immich-exporter + namespace: immich + annotations: + reloader.stakater.com/auto: 'true' + labels: + app: immich-exporter +spec: + replicas: 1 + selector: + matchLabels: + app: immich-exporter + template: + metadata: + labels: + app: immich-exporter + spec: + automountServiceAccountToken: false + containers: + - name: immich-exporter + # https://github.com/friendlyFriend4000/prometheus-immich-exporter + image: friendlyfriend/prometheus-immich-exporter:1.2.3 # {"$imagepolicy": "immich:immich-exporter"} + imagePullPolicy: IfNotPresent + resources: + limits: + memory: 100Mi + requests: + memory: 25Mi + cpu: 1m + ports: + - name: http + containerPort: 8000 + protocol: TCP + env: + - name: TZ + value: 'Europe/Berlin' + envFrom: + - configMapRef: + name: immich-exporter + optional: false + - secretRef: + name: immich-exporter + optional: false + securityContext: + privileged: false + seccompProfile: + type: 'RuntimeDefault' + livenessProbe: + httpGet: + path: /metrics + port: http + initialDelaySeconds: 10 + periodSeconds: 30 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /metrics + port: http + initialDelaySeconds: 5 + periodSeconds: 15 + timeoutSeconds: 5 + failureThreshold: 3 diff --git a/cluster/apps/immich/exporter/secret/secret.yaml.dist b/cluster/apps/immich/exporter/secret/secret.yaml.dist new file mode 100644 index 0000000..3c11bd3 --- /dev/null +++ b/cluster/apps/immich/exporter/secret/secret.yaml.dist @@ -0,0 +1,10 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: immich-exporter + namespace: immich + labels: + app: immich-exporter +stringData: + IMMICH_API_TOKEN: change_me diff --git a/cluster/apps/immich/machine-learning/DaemonSet.yaml b/cluster/apps/immich/machine-learning/DaemonSet.yaml new file mode 100644 index 0000000..bde9116 --- /dev/null +++ b/cluster/apps/immich/machine-learning/DaemonSet.yaml @@ -0,0 +1,84 @@ +apiVersion: apps/v1 +kind: DaemonSet +metadata: + name: immich-machine-learning + namespace: immich + annotations: + reloader.stakater.com/auto: 'true' + labels: + app: immich-machine-learning +spec: + selector: + matchLabels: + app: immich-machine-learning + template: + metadata: + labels: + app: immich-machine-learning + spec: + automountServiceAccountToken: false + containers: + - name: immich-machine-learning + image: ghcr.io/immich-app/immich-machine-learning:v2.1.0 # {"$imagepolicy": "immich:immich-machine-learning"} + imagePullPolicy: IfNotPresent + resources: + limits: + memory: 8Gi + requests: + memory: 500Mi + cpu: 500m + env: + - name: DB_HOSTNAME + valueFrom: + secretKeyRef: + name: immich-db-app + key: host + optional: false + - name: DB_DATABASE_NAME + valueFrom: + secretKeyRef: + name: immich-db-app + key: dbname + optional: false + - name: DB_USERNAME + valueFrom: + secretKeyRef: + name: immich-db-app + key: username + optional: false + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: immich-db-app + key: password + optional: false + envFrom: + - configMapRef: + name: immich + optional: false + livenessProbe: + httpGet: + path: /ping + port: 3003 + initialDelaySeconds: 60 + readinessProbe: + httpGet: + path: /ping + port: 3003 + startupProbe: + httpGet: + path: /ping + port: 3003 + securityContext: + privileged: true # for intel quick sync + seccompProfile: + type: 'RuntimeDefault' + volumeMounts: + - name: immich-machine-learning + mountPath: /cache + nodeSelector: + intel.feature.node.kubernetes.io/gpu: 'true' + volumes: + - name: immich-machine-learning + persistentVolumeClaim: + claimName: immich-machine-learning diff --git a/cluster/apps/immich/machine-learning/ImageAutomation/ImagePolicy.yaml b/cluster/apps/immich/machine-learning/ImageAutomation/ImagePolicy.yaml new file mode 100644 index 0000000..4cab00b --- /dev/null +++ b/cluster/apps/immich/machine-learning/ImageAutomation/ImagePolicy.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImagePolicy +metadata: + name: immich-machine-learning + namespace: immich +spec: + imageRepositoryRef: + name: immich-machine-learning + policy: + # https://github.com/immich-app/immich/releases + semver: + range: '2.*.*' + filterTags: + pattern: 'v.*' diff --git a/cluster/apps/immich/machine-learning/ImageAutomation/ImageRepository.yaml b/cluster/apps/immich/machine-learning/ImageAutomation/ImageRepository.yaml new file mode 100644 index 0000000..01a8d2b --- /dev/null +++ b/cluster/apps/immich/machine-learning/ImageAutomation/ImageRepository.yaml @@ -0,0 +1,17 @@ +--- +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImageRepository +metadata: + name: immich-machine-learning + namespace: immich +spec: + # https://github.com/immich-app/immich/pkgs/container/immich-machine-learning + image: ghcr.io/immich-app/immich-machine-learning + interval: 24h + exclusionList: + - .*openvino + - .*cuda + - .*armnn + - pr* + - main + - main* diff --git a/cluster/apps/immich/machine-learning/pvc.yaml b/cluster/apps/immich/machine-learning/pvc.yaml new file mode 100644 index 0000000..0f627fa --- /dev/null +++ b/cluster/apps/immich/machine-learning/pvc.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: immich-machine-learning + namespace: immich + labels: + app: immich-machine-learning + velero.io/exclude-from-backup: 'true' # because this is nfs storage +spec: + accessModes: + - ReadWriteMany + storageClassName: k8s-nfs-ssd + resources: + requests: + storage: 10Gi diff --git a/cluster/apps/immich/machine-learning/service.yml b/cluster/apps/immich/machine-learning/service.yml new file mode 100644 index 0000000..e9ebbe3 --- /dev/null +++ b/cluster/apps/immich/machine-learning/service.yml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: immich-machine-learning + namespace: immich + labels: + app: immich-machine-learning +spec: + ports: + - name: http + targetPort: 3003 + port: 3003 + selector: + app: immich-machine-learning diff --git a/cluster/apps/immich/namespace.yaml b/cluster/apps/immich/namespace.yaml new file mode 100644 index 0000000..8c2305b --- /dev/null +++ b/cluster/apps/immich/namespace.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: immich + labels: + # https://kubernetes.io/docs/concepts/security/pod-security-standards/ + # possible values: privileged, baseline, restricted + # set privileged because of intel quick sync + pod-security.kubernetes.io/enforce: privileged + pod-security.kubernetes.io/audit: privileged + pod-security.kubernetes.io/warn: privileged diff --git a/cluster/apps/immich/server/ImageAutomation/ImagePolicy.yaml b/cluster/apps/immich/server/ImageAutomation/ImagePolicy.yaml new file mode 100644 index 0000000..7e2b597 --- /dev/null +++ b/cluster/apps/immich/server/ImageAutomation/ImagePolicy.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImagePolicy +metadata: + name: immich-server + namespace: immich +spec: + imageRepositoryRef: + name: immich-server + policy: + # https://github.com/immich-app/immich/releases + semver: + range: '2.*.*' + filterTags: + pattern: 'v.*' diff --git a/cluster/apps/immich/server/ImageAutomation/ImageRepository.yaml b/cluster/apps/immich/server/ImageAutomation/ImageRepository.yaml new file mode 100644 index 0000000..3af6091 --- /dev/null +++ b/cluster/apps/immich/server/ImageAutomation/ImageRepository.yaml @@ -0,0 +1,14 @@ +--- +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImageRepository +metadata: + name: immich-server + namespace: immich +spec: + # https://github.com/immich-app/immich/pkgs/container/immich-server + image: ghcr.io/immich-app/immich-server + interval: 24h + exclusionList: + - pr* + - main + - main* diff --git a/cluster/apps/immich/server/IngressRoute.yml b/cluster/apps/immich/server/IngressRoute.yml new file mode 100644 index 0000000..90c69ed --- /dev/null +++ b/cluster/apps/immich/server/IngressRoute.yml @@ -0,0 +1,25 @@ +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: immich-server + namespace: immich + labels: + app: immich-server +spec: + entryPoints: + - websecure + routes: + - match: Host(`immich.example.cloud`) + kind: Rule + services: + - name: immich-server + port: http + middlewares: + - name: security + namespace: traefik + tls: + secretName: wildcard-example-cloud-cert + options: + name: tls-security + namespace: traefik diff --git a/cluster/apps/immich/server/Service.yaml b/cluster/apps/immich/server/Service.yaml new file mode 100644 index 0000000..7c618b1 --- /dev/null +++ b/cluster/apps/immich/server/Service.yaml @@ -0,0 +1,21 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: immich-server + namespace: immich + labels: + app: immich-server +spec: + ports: + - name: http + port: 80 + targetPort: http + - name: api-metrics + targetPort: 8081 + port: 8081 + - name: microservices-metrics + targetPort: 8082 + port: 8082 + selector: + app: immich-server diff --git a/cluster/apps/immich/server/ServiceMonitor.yaml b/cluster/apps/immich/server/ServiceMonitor.yaml new file mode 100644 index 0000000..03d7cb7 --- /dev/null +++ b/cluster/apps/immich/server/ServiceMonitor.yaml @@ -0,0 +1,28 @@ +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: immich-server + namespace: immich + labels: + app: immich-server +spec: + selector: + matchLabels: + app: immich-server + namespaceSelector: + matchNames: + - immich + endpoints: + - port: api-metrics # port name of service + scheme: http + interval: 15s + metricRelabelings: + - action: labeldrop + regex: (instance|pod) + + - port: microservices-metrics # port name of service + scheme: http + interval: 15s + metricRelabelings: + - action: labeldrop + regex: (instance|pod) diff --git a/cluster/apps/immich/server/deployment.yaml b/cluster/apps/immich/server/deployment.yaml new file mode 100644 index 0000000..7dbe9cc --- /dev/null +++ b/cluster/apps/immich/server/deployment.yaml @@ -0,0 +1,99 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: immich-server + namespace: immich + annotations: + reloader.stakater.com/auto: 'true' + labels: + app: immich-server +spec: + replicas: 1 + selector: + matchLabels: + app: immich-server + template: + metadata: + labels: + app: immich-server + spec: + automountServiceAccountToken: false + containers: + - name: immich-server + image: ghcr.io/immich-app/immich-server:v2.1.0 # {"$imagepolicy": "immich:immich-server"} + imagePullPolicy: IfNotPresent + resources: + limits: + memory: 8Gi + requests: + memory: 1Gi + cpu: 1000m + ports: + - name: http + containerPort: 2283 + protocol: TCP + env: + - name: DB_HOSTNAME + valueFrom: + secretKeyRef: + name: immich-db-app + key: host + optional: false + - name: DB_DATABASE_NAME + valueFrom: + secretKeyRef: + name: immich-db-app + key: dbname + optional: false + - name: DB_USERNAME + valueFrom: + secretKeyRef: + name: immich-db-app + key: username + optional: false + - name: DB_PASSWORD + valueFrom: + secretKeyRef: + name: immich-db-app + key: password + optional: false + envFrom: + - configMapRef: + name: immich + optional: false + livenessProbe: + httpGet: + path: /api/server/ping + port: http + failureThreshold: 6 + initialDelaySeconds: 60 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /api/server/ping + port: http + failureThreshold: 6 + periodSeconds: 10 + securityContext: + privileged: true # for intel quick sync + volumeMounts: + - name: immich + mountPath: /data/ + - name: immich-upload + mountPath: /data/upload/ + - name: synology-photos + mountPath: /mnt/synology-photos/ + readOnly: false + nodeSelector: + intel.feature.node.kubernetes.io/gpu: 'true' + volumes: + # unraid NFFS + - name: immich + persistentVolumeClaim: + claimName: immich + - name: immich-upload + persistentVolumeClaim: + claimName: immich-upload + - name: synology-photos + persistentVolumeClaim: + claimName: immich-synology-photos diff --git a/cluster/apps/immich/server/pvc/app.yaml b/cluster/apps/immich/server/pvc/app.yaml new file mode 100644 index 0000000..56e21d4 --- /dev/null +++ b/cluster/apps/immich/server/pvc/app.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: immich + namespace: immich + labels: + app: immich-server + velero.io/exclude-from-backup: 'true' # because this is nfs storage and velero will try to create snapshots which are will fails +spec: + accessModes: + - ReadWriteMany + storageClassName: k8s-nfs-ssd + resources: + requests: + storage: 500Gi diff --git a/cluster/apps/immich/server/pvc/synology-photos.yaml b/cluster/apps/immich/server/pvc/synology-photos.yaml new file mode 100644 index 0000000..a7d6ce9 --- /dev/null +++ b/cluster/apps/immich/server/pvc/synology-photos.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: immich-synology-photos + namespace: immich + labels: + app: immich-server + velero.io/exclude-from-backup: 'true' # because this is nfs storage and velero will try to create snapshots which are will fails +spec: + accessModes: + - ReadWriteMany + storageClassName: k8s-nfs-hdd + resources: + requests: + storage: 100Gi diff --git a/cluster/apps/immich/server/pvc/upload.yaml b/cluster/apps/immich/server/pvc/upload.yaml new file mode 100644 index 0000000..34496a3 --- /dev/null +++ b/cluster/apps/immich/server/pvc/upload.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: immich-upload + namespace: immich + labels: + app: immich-server + velero.io/exclude-from-backup: 'true' # because this is nfs storage and velero will try to create snapshots which are will fails +spec: + accessModes: + - ReadWriteMany + storageClassName: k8s-nfs-hdd + resources: + requests: + storage: 2Ti diff --git a/cluster/apps/immich/valkey/ImageAutomation/ImagePolicy.yaml b/cluster/apps/immich/valkey/ImageAutomation/ImagePolicy.yaml new file mode 100644 index 0000000..2c76f14 --- /dev/null +++ b/cluster/apps/immich/valkey/ImageAutomation/ImagePolicy.yaml @@ -0,0 +1,12 @@ +--- +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImagePolicy +metadata: + name: immich-valkey + namespace: immich +spec: + imageRepositoryRef: + name: immich-valkey + policy: + semver: + range: '8.*.*' diff --git a/cluster/apps/immich/valkey/ImageAutomation/ImageRepository.yaml b/cluster/apps/immich/valkey/ImageAutomation/ImageRepository.yaml new file mode 100644 index 0000000..74a1ef4 --- /dev/null +++ b/cluster/apps/immich/valkey/ImageAutomation/ImageRepository.yaml @@ -0,0 +1,16 @@ +--- +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImageRepository +metadata: + name: immich-valkey + namespace: immich +spec: + image: docker.io/valkey/valkey + interval: 24h + exclusionList: + - latest + - main + - unstable* + - alpine* + - trixie + - bookworm diff --git a/cluster/apps/immich/valkey/deployment.yaml b/cluster/apps/immich/valkey/deployment.yaml new file mode 100644 index 0000000..b351c9d --- /dev/null +++ b/cluster/apps/immich/valkey/deployment.yaml @@ -0,0 +1,75 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: immich-valkey + namespace: immich + annotations: + reloader.stakater.com/auto: 'true' + labels: + app: immich-valkey +spec: + replicas: 1 + selector: + matchLabels: + app: immich-valkey + template: + metadata: + labels: + app: immich-valkey + spec: + automountServiceAccountToken: false + # securityContext: + # runAsUser: 10001 + # runAsGroup: 10001 + # fsGroup: 10001 + containers: + - name: immich-valkey + # https://hub.docker.com/r/valkey/valkey/ + # https://github.com/valkey-io/valkey/releases + image: docker.io/valkey/valkey:8.1.4 # {"$imagepolicy": "immich:immich-valkey"} + imagePullPolicy: IfNotPresent + resources: + limits: + memory: 1Gi + requests: + memory: 100Mi + cpu: 20m + ports: + - name: redis + containerPort: 6379 + protocol: TCP + livenessProbe: + exec: + command: + - sh + - -c + - redis-cli ping || exit 1 + failureThreshold: 5 + initialDelaySeconds: 20 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 6 + readinessProbe: + exec: + command: + - sh + - -c + - redis-cli ping || exit 1 + failureThreshold: 5 + initialDelaySeconds: 20 + periodSeconds: 5 + successThreshold: 1 + timeoutSeconds: 2 + securityContext: + privileged: false + seccompProfile: + type: 'RuntimeDefault' + volumeMounts: + - name: immich-valkey + mountPath: /data/ + imagePullSecrets: + - name: registry-dockerhub + volumes: + - name: immich-valkey + emptyDir: + sizeLimit: 1Gi diff --git a/cluster/apps/immich/valkey/service.yaml b/cluster/apps/immich/valkey/service.yaml new file mode 100644 index 0000000..6b79e6a --- /dev/null +++ b/cluster/apps/immich/valkey/service.yaml @@ -0,0 +1,15 @@ +--- +apiVersion: v1 +kind: Service +metadata: + name: immich-valkey + namespace: immich + labels: + app: immich-valkey +spec: + ports: + - name: redis + port: 6379 + targetPort: redis + selector: + app: immich-valkey