Import manifests reverse-engineered from live cluster
Captured 2026-07-26 from rpi-master (k3s v1.30.3) and cleaned of runtime fields. Six apps as per-app kustomizations: vaultwarden, searxng, home-assistant, nginx, pihole(+unbound), unified-streaming. Intentional divergences from live state: - pihole WEBPASSWORD and USP_LICENSE_KEY moved from inline plaintext env to secretKeyRef (secrets gitignored; templates in secret.example.yaml) - HA ingress defaultBackend fixed (pointed at nonexistent service) - unifiedstreaming-svc kept as ClusterIP (LoadBalancer could never bind port 80 behind svclb-traefik) Validated against the live cluster with kubectl apply --dry-run=server: no immutable-field conflicts; one-time kubectl replace procedure for the two env->secretKeyRef migrations documented in README. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ec9b422f87
commit
8afac39d03
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Real secrets never go in git — only *.example.yaml templates do.
|
||||||
|
apps/**/secret.yaml
|
||||||
|
*.secret.yaml
|
||||||
100
README.md
100
README.md
@ -1,3 +1,101 @@
|
|||||||
# homelab-k3s
|
# homelab-k3s
|
||||||
|
|
||||||
k3s deployments and change management for homelab env.
|
k3s deployments and change management for the homelab Raspberry Pi cluster
|
||||||
|
(rpi-master + rpi-worker-1/2/3, k3s v1.30.3, flannel/traefik/klipper-lb
|
||||||
|
defaults).
|
||||||
|
|
||||||
|
Manifests were reverse-engineered from the live cluster on 2026-07-26 and
|
||||||
|
cleaned of runtime fields. Except where a file header says **DIVERGENCE**,
|
||||||
|
they are faithful to what was running.
|
||||||
|
|
||||||
|
## Layout
|
||||||
|
|
||||||
|
Each app under `apps/` is a self-contained kustomization
|
||||||
|
(namespace + PV/PVC + deployment + service + ingress):
|
||||||
|
|
||||||
|
| App | Namespace | Hostname(s) |
|
||||||
|
|---|---|---|
|
||||||
|
| vaultwarden | bitwarden | pm.wittyoneoff.com |
|
||||||
|
| searxng | default | searxng.wittyoneoff.com, origin-searxng.wittyoneoff.com |
|
||||||
|
| home-assistant | home-assistant | ha.wittyoneoff.com, ha-origin.wittyoneoff.com |
|
||||||
|
| nginx | nginx | www.wittyoneoff.com, www.ramonaajj.com |
|
||||||
|
| pihole (+unbound) | pihole | admin on :8000 via klipper-lb, DNS on :53 |
|
||||||
|
| unified-streaming | unified-streaming | unified.wittyoneoff.com |
|
||||||
|
|
||||||
|
Not in this repo: socktop (own repo + pipeline), rancher/monitoring/logging
|
||||||
|
(rancher-managed), traefik (k3s packaged component).
|
||||||
|
|
||||||
|
## Applying
|
||||||
|
|
||||||
|
```sh
|
||||||
|
kubectl apply -k . # everything
|
||||||
|
kubectl apply -k apps/home-assistant # one app
|
||||||
|
```
|
||||||
|
|
||||||
|
### One-time secret bootstrap (per cluster)
|
||||||
|
|
||||||
|
Three apps read secrets that are NOT in git. Each app dir has a
|
||||||
|
`secret.example.yaml`; copy to `secret.yaml` (gitignored), fill in, apply.
|
||||||
|
|
||||||
|
| Secret | Namespace | Used for |
|
||||||
|
|---|---|---|
|
||||||
|
| `pihole-admin` | pihole | pi-hole admin `WEBPASSWORD` |
|
||||||
|
| `usp-license` | unified-streaming | `USP_LICENSE_KEY` |
|
||||||
|
| `searxng-1723974683-config` | default | searxng `settings.yml` |
|
||||||
|
|
||||||
|
**One-time migration (pihole + unified-streaming):** the live cluster (as of
|
||||||
|
2026-07-26) has WEBPASSWORD / USP_LICENSE_KEY as inline plaintext `value`s.
|
||||||
|
`kubectl apply` (client- or server-side) cannot patch an env var from `value`
|
||||||
|
to `valueFrom` — the API rejects the merged object. Validated procedure:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# 1. create the two secrets (see secret.example.yaml in each app dir)
|
||||||
|
# 2. one-time replace (restarts the pods):
|
||||||
|
kubectl replace -f apps/pihole/deployment-pihole.yaml
|
||||||
|
kubectl replace -f apps/unified-streaming/deployment.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, `kubectl apply -k .` works cleanly for everything (verified with
|
||||||
|
`--dry-run=server` against the live cluster, 2026-07-26 — zero immutable-field
|
||||||
|
conflicts).
|
||||||
|
|
||||||
|
## Cluster assumptions (state that lives outside these manifests)
|
||||||
|
|
||||||
|
- **Node labels** — scheduling relies on them: workers carry `cpu=arm`
|
||||||
|
(all nodes do), the control-plane node carries `controller=true`
|
||||||
|
(most apps use `controller NotIn (true)` to stay off the master), pis carry
|
||||||
|
`model=raspi` (pihole/unbound prefer it). Re-label with:
|
||||||
|
`kubectl label node <n> cpu=arm model=raspi` /
|
||||||
|
`kubectl label node rpi-master controller=true`.
|
||||||
|
- **GlusterFS** — every PV is `hostPath` under `/mnt/gvolume0/…`, which is the
|
||||||
|
gluster volume mounted identically on all nodes. The mount must exist before
|
||||||
|
pods schedule; PVs bind by explicit `volumeName` + `storageClassName: ""`.
|
||||||
|
- **Ingress/LB** — traefik (k3s default) terminates all HTTP on 80/443 via
|
||||||
|
svclb on every node. Because of that, no other LoadBalancer service can
|
||||||
|
claim host port 80 (that's why unifiedstreaming-svc is ClusterIP).
|
||||||
|
- **Home Assistant config** — lives on the PV, not in git.
|
||||||
|
`http.trusted_proxies` must include `10.42.0.0/16` (the cluster pod CIDR)
|
||||||
|
or HA 400s everything the moment traefik reschedules to another node.
|
||||||
|
|
||||||
|
## Known quirks (faithful to live state, fix at leisure)
|
||||||
|
|
||||||
|
- Most images are `:latest`/`:stable` — deploys are not reproducible until
|
||||||
|
pinned. vaultwarden is pinned (1.35.4).
|
||||||
|
- searxng keeps its helm-generated name suffix (`searxng-1723974683`) because
|
||||||
|
deployment selectors are immutable; its helm release record still exists in
|
||||||
|
the cluster — don't run `helm upgrade/uninstall` on it, this repo is the
|
||||||
|
source of truth now.
|
||||||
|
- searxng's `TZ=America/Los Angeles` (missing underscore) is invalid tzdata —
|
||||||
|
container falls back to UTC.
|
||||||
|
- vaultwarden and home-assistant carry helm-chart-era label/name shapes but
|
||||||
|
their release records are gone; they are plain manifests now.
|
||||||
|
- pihole runs `privileged: true` with `PIHOLE_UID=0`.
|
||||||
|
|
||||||
|
## History
|
||||||
|
|
||||||
|
- 2026-07-25: cluster outage (rpi-master undervoltage → CNI cache corruption).
|
||||||
|
Recovery runbook in the Notes vault:
|
||||||
|
`rpi-master_undervoltage_CNI_corruption_incident-2026-07-25.md`.
|
||||||
|
- 2026-07-26: initial import of manifests from live cluster; HA ingress
|
||||||
|
defaultBackend fixed; unifiedstreaming-svc LoadBalancer→ClusterIP;
|
||||||
|
pihole/USP secrets moved to secretKeyRef (pending first apply).
|
||||||
|
|||||||
74
apps/home-assistant/deployment.yaml
Normal file
74
apps/home-assistant/deployment.yaml
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Originally installed via helm chart home-assistant-13.3.0 (release record no
|
||||||
|
# longer in cluster); managed as a raw manifest since. Selector labels are
|
||||||
|
# immutable — do not change them.
|
||||||
|
# NOTE: /config/configuration.yaml (on the PV, not in git) must keep
|
||||||
|
# http.trusted_proxies: [10.42.0.0/16] so traefik can reach HA from any node.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: home-assistant
|
||||||
|
namespace: home-assistant
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/instance: home-assistant
|
||||||
|
app.kubernetes.io/name: home-assistant
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/instance: home-assistant
|
||||||
|
app.kubernetes.io/name: home-assistant
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/instance: home-assistant
|
||||||
|
app.kubernetes.io/name: home-assistant
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: cpu
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- arm
|
||||||
|
- key: controller
|
||||||
|
operator: NotIn
|
||||||
|
values:
|
||||||
|
- "true"
|
||||||
|
containers:
|
||||||
|
- name: home-assistant
|
||||||
|
image: ghcr.io/home-assistant/home-assistant:stable
|
||||||
|
imagePullPolicy: Always
|
||||||
|
env:
|
||||||
|
- name: TZ
|
||||||
|
value: UTC
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8123
|
||||||
|
protocol: TCP
|
||||||
|
startupProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8123
|
||||||
|
failureThreshold: 30
|
||||||
|
periodSeconds: 5
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8123
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 8123
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /config
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: home-assistant
|
||||||
37
apps/home-assistant/ingress.yaml
Normal file
37
apps/home-assistant/ingress.yaml
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# NOTE: the live object's defaultBackend pointed at a service named
|
||||||
|
# "home-assistant", which does not exist (the real service is
|
||||||
|
# "home-assistant-svc"). Fixed here — this is the one intentional divergence
|
||||||
|
# from the cluster state captured 2026-07-26.
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: home-assistant
|
||||||
|
namespace: home-assistant
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
defaultBackend:
|
||||||
|
service:
|
||||||
|
name: home-assistant-svc
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
rules:
|
||||||
|
- host: ha.wittyoneoff.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: home-assistant-svc
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
- host: ha-origin.wittyoneoff.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: home-assistant-svc
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
8
apps/home-assistant/kustomization.yaml
Normal file
8
apps/home-assistant/kustomization.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- storage.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
4
apps/home-assistant/namespace.yaml
Normal file
4
apps/home-assistant/namespace.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: home-assistant
|
||||||
15
apps/home-assistant/service.yaml
Normal file
15
apps/home-assistant/service.yaml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: home-assistant-svc
|
||||||
|
namespace: home-assistant
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/instance: home-assistant
|
||||||
|
app.kubernetes.io/name: home-assistant
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 8123
|
||||||
|
protocol: TCP
|
||||||
30
apps/home-assistant/storage.yaml
Normal file
30
apps/home-assistant/storage.yaml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# /config lives on GlusterFS (/mnt/gvolume0 mounted on every node).
|
||||||
|
# Known risk: HA recorder DB on gluster + non-atomic renames → occasional
|
||||||
|
# .storage write errors. See homelab notes.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: home-assistant-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 10Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
volumeMode: Filesystem
|
||||||
|
hostPath:
|
||||||
|
path: /mnt/gvolume0/home-assistant
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: home-assistant
|
||||||
|
namespace: home-assistant
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
storageClassName: ""
|
||||||
|
volumeName: home-assistant-pv
|
||||||
59
apps/nginx/deployment.yaml
Normal file
59
apps/nginx/deployment.yaml
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# Created via Rancher originally — the workload.user.cattle.io selector label
|
||||||
|
# is immutable on the live object; do not change it.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx
|
||||||
|
namespace: nginx
|
||||||
|
labels:
|
||||||
|
workload.user.cattle.io/workloadselector: apps.deployment-nginx-nginx
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
workload.user.cattle.io/workloadselector: apps.deployment-nginx-nginx
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 25%
|
||||||
|
maxUnavailable: 25%
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
workload.user.cattle.io/workloadselector: apps.deployment-nginx-nginx
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: cpu
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- arm
|
||||||
|
- key: controller
|
||||||
|
operator: NotIn
|
||||||
|
values:
|
||||||
|
- "true"
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
volumeMounts:
|
||||||
|
- name: vol-vnbxq
|
||||||
|
mountPath: /usr/share/nginx/html
|
||||||
|
subPath: www
|
||||||
|
- name: vol-vnbxq
|
||||||
|
mountPath: /etc/nginx/conf
|
||||||
|
subPath: conf
|
||||||
|
- name: vol-vnbxq
|
||||||
|
mountPath: /etc/nginx/conf.d
|
||||||
|
subPath: conf.d
|
||||||
|
volumes:
|
||||||
|
- name: vol-vnbxq
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: nginx
|
||||||
28
apps/nginx/ingress.yaml
Normal file
28
apps/nginx/ingress.yaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: nginx
|
||||||
|
namespace: nginx
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: www.wittyoneoff.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: nginx
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
- host: www.ramonaajj.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: nginx
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
8
apps/nginx/kustomization.yaml
Normal file
8
apps/nginx/kustomization.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- storage.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
4
apps/nginx/namespace.yaml
Normal file
4
apps/nginx/namespace.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: nginx
|
||||||
14
apps/nginx/service.yaml
Normal file
14
apps/nginx/service.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: nginx
|
||||||
|
namespace: nginx
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
workload.user.cattle.io/workloadselector: apps.deployment-nginx-nginx
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
35
apps/nginx/storage.yaml
Normal file
35
apps/nginx/storage.yaml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Static site content + nginx config live on GlusterFS:
|
||||||
|
# /mnt/gvolume0/nginx/www → /usr/share/nginx/html
|
||||||
|
# /mnt/gvolume0/nginx/conf → /etc/nginx/conf
|
||||||
|
# /mnt/gvolume0/nginx/conf.d → /etc/nginx/conf.d
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: nginx-gvolume0
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 50Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
- ReadOnlyMany
|
||||||
|
- ReadWriteMany
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
volumeMode: Filesystem
|
||||||
|
hostPath:
|
||||||
|
path: /mnt/gvolume0/nginx
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: nginx
|
||||||
|
namespace: nginx
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
- ReadOnlyMany
|
||||||
|
- ReadWriteMany
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 50Gi
|
||||||
|
storageClassName: ""
|
||||||
|
volumeName: nginx-gvolume0
|
||||||
86
apps/pihole/deployment-pihole.yaml
Normal file
86
apps/pihole/deployment-pihole.yaml
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
# DIVERGENCE from live state (intentional): the live deployment has WEBPASSWORD
|
||||||
|
# as a plaintext env value. Here it comes from the `pihole-admin` secret —
|
||||||
|
# create it (see secret.example.yaml) BEFORE applying this, or the pod will
|
||||||
|
# fail with CreateContainerConfigError.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: pihole
|
||||||
|
namespace: pihole
|
||||||
|
labels:
|
||||||
|
app: pihole
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: pihole
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 0
|
||||||
|
maxUnavailable: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: pihole
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 1
|
||||||
|
preference:
|
||||||
|
matchExpressions:
|
||||||
|
- key: cpu
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- arm
|
||||||
|
- key: model
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- raspi
|
||||||
|
containers:
|
||||||
|
- name: pihole
|
||||||
|
image: pihole/pihole:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: TZ
|
||||||
|
value: America/Los_Angeles
|
||||||
|
- name: WEBPASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: pihole-admin
|
||||||
|
key: WEBPASSWORD
|
||||||
|
- name: PIHOLE_UID
|
||||||
|
value: "0"
|
||||||
|
ports:
|
||||||
|
- containerPort: 53
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 53
|
||||||
|
protocol: UDP
|
||||||
|
- containerPort: 67
|
||||||
|
protocol: UDP
|
||||||
|
- containerPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
- containerPort: 443
|
||||||
|
protocol: TCP
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 2Gi
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
volumeMounts:
|
||||||
|
- name: gv-pihole
|
||||||
|
mountPath: /etc/pihole
|
||||||
|
- name: gv-pihole-dnsmasq-volume
|
||||||
|
mountPath: /etc/dnsmasq.d
|
||||||
|
volumes:
|
||||||
|
- name: gv-pihole
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: pihole-gvolume0
|
||||||
|
- name: gv-pihole-dnsmasq-volume
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: pihole-dnsmasq-volume
|
||||||
54
apps/pihole/deployment-unbound.yaml
Normal file
54
apps/pihole/deployment-unbound.yaml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: unbound
|
||||||
|
namespace: pihole
|
||||||
|
labels:
|
||||||
|
app: unbound
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: unbound
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 25%
|
||||||
|
maxUnavailable: 25%
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: unbound
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
preferredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
- weight: 1
|
||||||
|
preference:
|
||||||
|
matchExpressions:
|
||||||
|
- key: cpu
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- arm
|
||||||
|
- key: model
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- raspi
|
||||||
|
containers:
|
||||||
|
- name: unbound
|
||||||
|
image: docker.io/mvance/unbound-rpi:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- name: svc-53-udp
|
||||||
|
containerPort: 53
|
||||||
|
protocol: UDP
|
||||||
|
- name: svc-53-tcp
|
||||||
|
containerPort: 53
|
||||||
|
protocol: TCP
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 250m
|
||||||
|
memory: 50Mi
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 1Gi
|
||||||
10
apps/pihole/kustomization.yaml
Normal file
10
apps/pihole/kustomization.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
# secret.example.yaml is intentionally NOT listed — copy it to secret.yaml
|
||||||
|
# (gitignored) and apply manually once per cluster.
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- storage.yaml
|
||||||
|
- deployment-pihole.yaml
|
||||||
|
- deployment-unbound.yaml
|
||||||
|
- service.yaml
|
||||||
4
apps/pihole/namespace.yaml
Normal file
4
apps/pihole/namespace.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: pihole
|
||||||
13
apps/pihole/secret.example.yaml
Normal file
13
apps/pihole/secret.example.yaml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Copy to secret.yaml (gitignored), set the real admin password, apply once:
|
||||||
|
# kubectl apply -f secret.yaml
|
||||||
|
# Or create directly without a file:
|
||||||
|
# kubectl create secret generic pihole-admin -n pihole \
|
||||||
|
# --from-literal=WEBPASSWORD='...'
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: pihole-admin
|
||||||
|
namespace: pihole
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
WEBPASSWORD: CHANGE-ME
|
||||||
53
apps/pihole/service.yaml
Normal file
53
apps/pihole/service.yaml
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# pihole-svc is type LoadBalancer → k3s klipper-lb (svclb) binds host ports
|
||||||
|
# 8000/53/67 on the nodes it schedules on. NodePorts are pinned so they
|
||||||
|
# survive re-creation.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: pihole-svc
|
||||||
|
namespace: pihole
|
||||||
|
spec:
|
||||||
|
type: LoadBalancer
|
||||||
|
externalTrafficPolicy: Cluster
|
||||||
|
selector:
|
||||||
|
app: pihole
|
||||||
|
ports:
|
||||||
|
- name: http-admin
|
||||||
|
port: 8000
|
||||||
|
targetPort: 80
|
||||||
|
nodePort: 31120
|
||||||
|
protocol: TCP
|
||||||
|
- name: tcp-53
|
||||||
|
port: 53
|
||||||
|
targetPort: 53
|
||||||
|
nodePort: 32580
|
||||||
|
protocol: TCP
|
||||||
|
- name: udp-53
|
||||||
|
port: 53
|
||||||
|
targetPort: 53
|
||||||
|
nodePort: 31281
|
||||||
|
protocol: UDP
|
||||||
|
- name: udp-67
|
||||||
|
port: 67
|
||||||
|
targetPort: 67
|
||||||
|
nodePort: 31025
|
||||||
|
protocol: UDP
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: unbound-service
|
||||||
|
namespace: pihole
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: unbound
|
||||||
|
ports:
|
||||||
|
- name: udp-53
|
||||||
|
port: 53
|
||||||
|
targetPort: 53
|
||||||
|
protocol: UDP
|
||||||
|
- name: tcp-53
|
||||||
|
port: 53
|
||||||
|
targetPort: 53
|
||||||
|
protocol: TCP
|
||||||
55
apps/pihole/storage.yaml
Normal file
55
apps/pihole/storage.yaml
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: pihole-gvolume0-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 10Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
volumeMode: Filesystem
|
||||||
|
hostPath:
|
||||||
|
path: /mnt/gvolume0/pihole
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: pihole-dnsmasq-volume-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 10Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
volumeMode: Filesystem
|
||||||
|
hostPath:
|
||||||
|
path: /mnt/gvolume0/pihole-dnsmasq-volume
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: pihole-gvolume0
|
||||||
|
namespace: pihole
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
storageClassName: ""
|
||||||
|
volumeName: pihole-gvolume0-pv
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: pihole-dnsmasq-volume
|
||||||
|
namespace: pihole
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
storageClassName: ""
|
||||||
|
volumeName: pihole-dnsmasq-volume-pv
|
||||||
78
apps/searxng/deployment.yaml
Normal file
78
apps/searxng/deployment.yaml
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# Helm release searxng-1723974683 (chart searxng-1.0.0) in the *default*
|
||||||
|
# namespace — the release is still recorded in the cluster, so if you ever
|
||||||
|
# `helm upgrade` it, helm and this manifest will fight. Prefer retiring the
|
||||||
|
# helm release (helm delete --no-hooks would delete resources; instead just
|
||||||
|
# treat this repo as the source of truth and never run helm against it).
|
||||||
|
# Names keep the timestamped release suffix because selectors are immutable.
|
||||||
|
# NOTE (faithful to live state): TZ value "America/Los Angeles" is missing the
|
||||||
|
# underscore — an invalid tzdata name, so searxng falls back to UTC. Kept as-is;
|
||||||
|
# fix to America/Los_Angeles when convenient.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: searxng-1723974683
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/instance: searxng-1723974683
|
||||||
|
app.kubernetes.io/name: searxng
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/instance: searxng-1723974683
|
||||||
|
app.kubernetes.io/name: searxng
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 25%
|
||||||
|
maxUnavailable: 25%
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/instance: searxng-1723974683
|
||||||
|
app.kubernetes.io/name: searxng
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: searxng-1723974683
|
||||||
|
image: searxng/searxng:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
env:
|
||||||
|
- name: AUTOCOMPLETE
|
||||||
|
value: google
|
||||||
|
- name: BASE_URL
|
||||||
|
value: http://searxng.wittyoneoff.com/
|
||||||
|
- name: INSTANCE_NAME
|
||||||
|
value: witty-one-off-searxng
|
||||||
|
- name: TZ
|
||||||
|
value: America/Los Angeles
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
failureThreshold: 30
|
||||||
|
periodSeconds: 5
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /healthz
|
||||||
|
port: 8080
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
volumeMounts:
|
||||||
|
- name: searxng-config
|
||||||
|
mountPath: /etc/searxng/settings.yml
|
||||||
|
subPath: settings.yml
|
||||||
|
volumes:
|
||||||
|
- name: searxng-config
|
||||||
|
secret:
|
||||||
|
secretName: searxng-1723974683-config
|
||||||
33
apps/searxng/ingress.yaml
Normal file
33
apps/searxng/ingress.yaml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: searxng-ingress
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
defaultBackend:
|
||||||
|
service:
|
||||||
|
name: searxng-1723974683
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
rules:
|
||||||
|
- host: searxng.wittyoneoff.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: searxng-1723974683
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
- host: origin-searxng.wittyoneoff.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: searxng-1723974683
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
8
apps/searxng/kustomization.yaml
Normal file
8
apps/searxng/kustomization.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
# secret.example.yaml is intentionally NOT listed — copy it to secret.yaml
|
||||||
|
# (gitignored) and apply manually once per cluster.
|
||||||
|
resources:
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
14
apps/searxng/secret.example.yaml
Normal file
14
apps/searxng/secret.example.yaml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# Copy to secret.yaml (gitignored), set a real secret_key, and apply once:
|
||||||
|
# kubectl apply -f secret.yaml
|
||||||
|
# The secret already exists in the cluster; this file only documents its shape.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: searxng-1723974683-config
|
||||||
|
namespace: default
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
settings.yml: |
|
||||||
|
server:
|
||||||
|
secret_key: CHANGE-ME
|
||||||
|
use_default_settings: true
|
||||||
18
apps/searxng/service.yaml
Normal file
18
apps/searxng/service.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: searxng-1723974683
|
||||||
|
namespace: default
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/instance: searxng-1723974683
|
||||||
|
app.kubernetes.io/name: searxng
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/instance: searxng-1723974683
|
||||||
|
app.kubernetes.io/name: searxng
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8080
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
69
apps/unified-streaming/deployment.yaml
Normal file
69
apps/unified-streaming/deployment.yaml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# DIVERGENCE from live state (intentional): the live deployment has
|
||||||
|
# USP_LICENSE_KEY as a plaintext env value. Here it comes from the
|
||||||
|
# `usp-license` secret — create it (see secret.example.yaml) BEFORE applying
|
||||||
|
# this. The Rancher-era selector label is immutable; do not change it.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: unifiedstreaming
|
||||||
|
namespace: unified-streaming
|
||||||
|
labels:
|
||||||
|
workload.user.cattle.io/workloadselector: apps.deployment-unified-streaming-unifiedstreaming
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
workload.user.cattle.io/workloadselector: apps.deployment-unified-streaming-unifiedstreaming
|
||||||
|
strategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxSurge: 25%
|
||||||
|
maxUnavailable: 25%
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: unifiedstreaming
|
||||||
|
workload.user.cattle.io/workloadselector: apps.deployment-unified-streaming-unifiedstreaming
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: cpu
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- arm
|
||||||
|
- key: controller
|
||||||
|
operator: NotIn
|
||||||
|
values:
|
||||||
|
- "true"
|
||||||
|
containers:
|
||||||
|
- name: unifiedstreaming
|
||||||
|
image: unifiedstreaming/origin:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
env:
|
||||||
|
- name: USP_LICENSE_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: usp-license
|
||||||
|
key: USP_LICENSE_KEY
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 80
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 80
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
volumeMounts:
|
||||||
|
- name: vol-apn2a
|
||||||
|
mountPath: /var/www/unified-origin
|
||||||
|
volumes:
|
||||||
|
- name: vol-apn2a
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: unified-streaming-pvc
|
||||||
18
apps/unified-streaming/ingress.yaml
Normal file
18
apps/unified-streaming/ingress.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: unified-streaming-ing
|
||||||
|
namespace: unified-streaming
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: unified.wittyoneoff.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: unifiedstreaming-svc
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
10
apps/unified-streaming/kustomization.yaml
Normal file
10
apps/unified-streaming/kustomization.yaml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
# secret.example.yaml is intentionally NOT listed — copy it to secret.yaml
|
||||||
|
# (gitignored) and apply manually once per cluster.
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- storage.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
4
apps/unified-streaming/namespace.yaml
Normal file
4
apps/unified-streaming/namespace.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: unified-streaming
|
||||||
16
apps/unified-streaming/secret.example.yaml
Normal file
16
apps/unified-streaming/secret.example.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Copy to secret.yaml (gitignored), paste the real USP license key, apply once:
|
||||||
|
# kubectl apply -f secret.yaml
|
||||||
|
# Or create directly:
|
||||||
|
# kubectl create secret generic usp-license -n unified-streaming \
|
||||||
|
# --from-literal=USP_LICENSE_KEY='<base64-looking license blob>'
|
||||||
|
# The current key can be read from the live deployment:
|
||||||
|
# kubectl get deploy -n unified-streaming unifiedstreaming \
|
||||||
|
# -o jsonpath='{.spec.template.spec.containers[0].env[?(@.name=="USP_LICENSE_KEY")].value}'
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: usp-license
|
||||||
|
namespace: unified-streaming
|
||||||
|
type: Opaque
|
||||||
|
stringData:
|
||||||
|
USP_LICENSE_KEY: CHANGE-ME
|
||||||
18
apps/unified-streaming/service.yaml
Normal file
18
apps/unified-streaming/service.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# ClusterIP on purpose: this was type LoadBalancer for ~2 years, but klipper-lb
|
||||||
|
# can never bind host port 80 (svclb-traefik owns it on every node), so the
|
||||||
|
# svclb pods sat Pending forever. Traffic flows through the ingress. Changed
|
||||||
|
# to ClusterIP 2026-07-26.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: unifiedstreaming-svc
|
||||||
|
namespace: unified-streaming
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: unifiedstreaming
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
32
apps/unified-streaming/storage.yaml
Normal file
32
apps/unified-streaming/storage.yaml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Origin content (ism/ismv etc.) on GlusterFS, served by all replicas.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: unified-streaming-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 100Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
- ReadOnlyMany
|
||||||
|
- ReadWriteMany
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
volumeMode: Filesystem
|
||||||
|
hostPath:
|
||||||
|
path: /mnt/gvolume0/unified-streaming
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: unified-streaming-pvc
|
||||||
|
namespace: unified-streaming
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
- ReadOnlyMany
|
||||||
|
- ReadWriteMany
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 100Gi
|
||||||
|
storageClassName: ""
|
||||||
|
volumeName: unified-streaming-pv
|
||||||
75
apps/vaultwarden/deployment.yaml
Normal file
75
apps/vaultwarden/deployment.yaml
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Originally installed via helm chart vaultwarden-5.1.0 (release record no longer
|
||||||
|
# in cluster); managed as a raw manifest since. Selector labels are immutable —
|
||||||
|
# do not change them.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: bitwarden-vaultwarden
|
||||||
|
namespace: bitwarden
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/instance: bitwarden
|
||||||
|
app.kubernetes.io/name: vaultwarden
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
revisionHistoryLimit: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/instance: bitwarden
|
||||||
|
app.kubernetes.io/name: vaultwarden
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/instance: bitwarden
|
||||||
|
app.kubernetes.io/name: vaultwarden
|
||||||
|
spec:
|
||||||
|
affinity:
|
||||||
|
nodeAffinity:
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: cpu
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- arm
|
||||||
|
- key: controller
|
||||||
|
operator: NotIn
|
||||||
|
values:
|
||||||
|
- "true"
|
||||||
|
containers:
|
||||||
|
- name: bitwarden-vaultwarden
|
||||||
|
image: vaultwarden/server:1.35.4
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: DATA_FOLDER
|
||||||
|
value: config
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
- name: websocket
|
||||||
|
containerPort: 3012
|
||||||
|
protocol: TCP
|
||||||
|
startupProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 80
|
||||||
|
failureThreshold: 30
|
||||||
|
periodSeconds: 5
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 80
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 80
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /config
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: bitwarden-gvolume0
|
||||||
23
apps/vaultwarden/ingress.yaml
Normal file
23
apps/vaultwarden/ingress.yaml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: bitwarden
|
||||||
|
namespace: bitwarden
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
defaultBackend:
|
||||||
|
service:
|
||||||
|
name: bitwarden-vaultwarden-svc
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
|
rules:
|
||||||
|
- host: pm.wittyoneoff.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: bitwarden-vaultwarden-svc
|
||||||
|
port:
|
||||||
|
number: 8080
|
||||||
8
apps/vaultwarden/kustomization.yaml
Normal file
8
apps/vaultwarden/kustomization.yaml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- storage.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
4
apps/vaultwarden/namespace.yaml
Normal file
4
apps/vaultwarden/namespace.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: bitwarden
|
||||||
19
apps/vaultwarden/service.yaml
Normal file
19
apps/vaultwarden/service.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: bitwarden-vaultwarden-svc
|
||||||
|
namespace: bitwarden
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/instance: bitwarden
|
||||||
|
app.kubernetes.io/name: vaultwarden
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8080
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
- name: websocket
|
||||||
|
port: 3012
|
||||||
|
targetPort: websocket
|
||||||
|
protocol: TCP
|
||||||
30
apps/vaultwarden/storage.yaml
Normal file
30
apps/vaultwarden/storage.yaml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# GlusterFS-backed volume: /mnt/gvolume0 is the gluster mount present on every node.
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: bitwarden-pv
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 10Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
- ReadWriteMany
|
||||||
|
- ReadOnlyMany
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
volumeMode: Filesystem
|
||||||
|
hostPath:
|
||||||
|
path: /mnt/gvolume0/bitwarden
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: bitwarden-gvolume0
|
||||||
|
namespace: bitwarden
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
storageClassName: ""
|
||||||
|
volumeName: bitwarden-pv
|
||||||
9
kustomization.yaml
Normal file
9
kustomization.yaml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- apps/vaultwarden
|
||||||
|
- apps/searxng
|
||||||
|
- apps/home-assistant
|
||||||
|
- apps/nginx
|
||||||
|
- apps/pihole
|
||||||
|
- apps/unified-streaming
|
||||||
Loading…
Reference in New Issue
Block a user