Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
af7c5e845a | ||
|
|
063f9bcd23 | ||
|
|
df9a68d0ba | ||
|
|
a07af6bf07 | ||
|
|
3a91c19b5c |
@@ -0,0 +1,43 @@
|
||||
# Edge route for the API gateway.
|
||||
#
|
||||
# Lives here rather than in the central k8s/bootstrap/ingress/ingress.yaml
|
||||
# because that Application syncs in wave 1, before namespace `api` exists.
|
||||
#
|
||||
# nginx terminates TLS with the wildcard *.riotpiao.com cert (served as its
|
||||
# default-ssl-certificate, so no per-rule `tls:` block is needed) and forwards
|
||||
# plain HTTP to kong-proxy. Kong then does the real routing, from Ingresses
|
||||
# carrying `ingressClassName: kong`.
|
||||
#
|
||||
# Catch-all `/` on purpose: everything under this host belongs to Kong. Listing
|
||||
# per-API paths here would duplicate Kong's routing table inside nginx, and the
|
||||
# two copies would drift.
|
||||
#
|
||||
# In-cluster callers should prefer http://kong-proxy.api.svc.cluster.local
|
||||
# directly. Resolving api.riotpiao.com sends them out to nginx and back in,
|
||||
# which is a pointless hairpin unless they need TLS or the public hostname.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: api
|
||||
namespace: api
|
||||
annotations:
|
||||
# An API gateway carries streaming responses (SSE, gRPC-web, LLM token
|
||||
# streams). nginx's 60s default read timeout and its response buffering
|
||||
# would truncate or stall those.
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
|
||||
nginx.ingress.kubernetes.io/proxy-buffering: "off"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
rules:
|
||||
- host: api.riotpiao.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: kong-proxy
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,97 @@
|
||||
# Kong Gateway — cluster-internal API gateway (namespace `api`).
|
||||
#
|
||||
# Chart: kong/kong 3.4.1 (appVersion 3.9). Only overrides are listed; every key
|
||||
# here was checked against `helm show values kong/kong --version 3.4.1`, because
|
||||
# Helm silently ignores unknown keys — a typo is a no-op, not an error.
|
||||
#
|
||||
# ── Topology ────────────────────────────────────────────────────────────────
|
||||
# external: client -> nginx (TLS, wildcard *.riotpiao.com) -> kong-proxy:80
|
||||
# internal: pod -> kong-proxy.api.svc.cluster.local:80
|
||||
#
|
||||
# nginx stays the single edge and the only LoadBalancer (192.168.1.160). Kong is
|
||||
# the policy/routing layer behind it, so it needs no LB IP and no TLS of its own
|
||||
# — hence ClusterIP and proxy.tls disabled. Giving Kong its own IP from
|
||||
# homelab-pool would mean duplicating cert-manager wiring and diverging from the
|
||||
# CoreDNS convention that sends every *.riotpiao.com host to nginx.
|
||||
#
|
||||
# ── Routing model ───────────────────────────────────────────────────────────
|
||||
# Consumers publish an Ingress with `ingressClassName: kong`; the controller
|
||||
# turns it into a Kong route. `nginx` remains the default IngressClass, so this
|
||||
# is strictly opt-in and no existing Ingress changes behaviour.
|
||||
|
||||
# Without this the release name is prefixed onto everything (`kong-kong-proxy`).
|
||||
# Pinning it keeps the Service name stable and independent of the release name,
|
||||
# which matters because the nginx Ingress in k8s/bootstrap/ingress/ingress.yaml
|
||||
# references it by name.
|
||||
fullnameOverride: kong
|
||||
|
||||
# Two replicas so a node drain or rollout doesn't take the gateway down. Kong is
|
||||
# stateless in DB-less mode, so replicas are pure redundancy.
|
||||
replicaCount: 2
|
||||
|
||||
env:
|
||||
# DB-less. Config comes from Kubernetes objects via the ingress controller, so
|
||||
# git stays the source of truth. A Postgres-backed Kong would put live routing
|
||||
# config in a database mutated through the Admin API — state outside git, plus
|
||||
# migration Jobs on every upgrade.
|
||||
database: "off"
|
||||
|
||||
ingressController:
|
||||
enabled: true
|
||||
ingressClass: kong
|
||||
# The chart's ingress-class template is gated on
|
||||
# `.Capabilities.APIVersions.Has "networking.k8s.io/v1/IngressClass"`, so a
|
||||
# bare `helm template` renders nothing. ArgoCD passes --api-versions from the
|
||||
# live cluster, so it does render there — verify `kubectl get ingressclass
|
||||
# kong` after the first sync rather than assuming it.
|
||||
createIngressClass: true
|
||||
# Deliberately empty: setting is-default-class here would hijack every Ingress
|
||||
# in the cluster that omits ingressClassName. nginx keeps that role.
|
||||
ingressClassAnnotations: {}
|
||||
|
||||
proxy:
|
||||
enabled: true
|
||||
# Chart default is LoadBalancer, which would claim an IP from homelab-pool.
|
||||
type: ClusterIP
|
||||
http:
|
||||
enabled: true
|
||||
servicePort: 80
|
||||
containerPort: 8000
|
||||
# nginx already terminated TLS; a second handshake to the same cluster buys
|
||||
# nothing and would need Kong to hold its own certificate.
|
||||
tls:
|
||||
enabled: false
|
||||
|
||||
# No Service for the Admin API. The controller reaches it over localhost inside
|
||||
# the pod, so exposing it would only create an unauthenticated write path to the
|
||||
# gateway's entire configuration.
|
||||
admin:
|
||||
enabled: false
|
||||
|
||||
# Kong Manager UI — chart default is `enabled: true` with type NodePort, which
|
||||
# would open a port on every node. Not wanted.
|
||||
manager:
|
||||
enabled: false
|
||||
|
||||
resources:
|
||||
requests:
|
||||
cpu: 200m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 1Gi
|
||||
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: 1
|
||||
|
||||
# Spread the two replicas across nodes; `ScheduleAnyway` so a single-node
|
||||
# situation degrades to co-location instead of leaving a pod Pending.
|
||||
topologySpreadConstraints:
|
||||
- maxSkew: 1
|
||||
topologyKey: kubernetes.io/hostname
|
||||
whenUnsatisfiable: ScheduleAnyway
|
||||
labelSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: kong
|
||||
app.kubernetes.io/instance: kong
|
||||
@@ -0,0 +1,11 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
# Explicit allowlist so kong-values.yaml in this directory is NOT treated as a
|
||||
# manifest — it is Helm input consumed by the chart source of the `kong`
|
||||
# Application, not a Kubernetes object. Anything new added here must be listed
|
||||
# or it is silently dropped with no error and no drift shown.
|
||||
resources:
|
||||
- ingress.yaml
|
||||
# No top-level `namespace:` transformer on purpose: ingress.yaml sets its own
|
||||
# namespace, and the transformer rewrites metadata.namespace on every resource
|
||||
# it builds, which is a trap for anything cross-namespace added later.
|
||||
@@ -30,10 +30,16 @@ replicaCount: 1
|
||||
env:
|
||||
AUTH_PROVIDERS: "oidc,credentials"
|
||||
AUTH_OIDC_ISSUER: "https://authentik.riotpiao.com/application/o/homarr/"
|
||||
# AUTH_OIDC_URI (authorize endpoint) is REQUIRED in addition to ISSUER — homarr
|
||||
# hides the "Sign in with Authentik" button entirely when it's absent (per the
|
||||
# authentik Homarr integration + homarr SSO docs). This was the missing var.
|
||||
AUTH_OIDC_URI: "https://authentik.riotpiao.com/application/o/authorize/"
|
||||
AUTH_OIDC_CLIENT_NAME: "Authentik"
|
||||
AUTH_OIDC_GROUPS_ATTRIBUTE: "groups"
|
||||
AUTH_OIDC_SCOPE_OVERWRITE: "openid email profile groups"
|
||||
AUTH_OIDC_AUTO_LOGIN: "false"
|
||||
# Link the OIDC identity to an existing homarr account with the same email.
|
||||
OAUTH_ALLOW_DANGEROUS_EMAIL_ACCOUNT_LINKING: "true"
|
||||
BASE_URL: "https://homarr.riotpiao.com"
|
||||
NEXTAUTH_URL: "https://homarr.riotpiao.com"
|
||||
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
# macOS VM (Docker-OSX) hosting the BlueBubbles server.
|
||||
#
|
||||
# ── Why a VM and not a container ────────────────────────────────────────────
|
||||
# Containers share the host kernel. macOS binaries are Mach-O and need XNU plus
|
||||
# Cocoa/IOKit, which a Linux kernel cannot provide, so no macOS container exists
|
||||
# or can exist. Docker-OSX is QEMU running a macOS guest, packaged in a
|
||||
# container — a VM in a box, not a macOS container.
|
||||
#
|
||||
# ── Why this works on worker-2 ──────────────────────────────────────────────
|
||||
# Verified on the existing hardware: amd64, `vmx` (Intel VT-x) present, and
|
||||
# /dev/kvm exists on Talos nodes (KVM is compiled into Talos' kernel, not a
|
||||
# module). Bare metal, so no nested virtualisation needed.
|
||||
#
|
||||
# ── Read this before relying on it ──────────────────────────────────────────
|
||||
# 1. Setup is INTERACTIVE. First boot runs the macOS installer: connect over
|
||||
# VNC (:5999), erase the disk in Disk Utility, install, create a user, sign
|
||||
# into iMessage, THEN install BlueBubbles inside the guest. This manifest
|
||||
# only provides the machine; it does not provision macOS.
|
||||
# 2. iMessage activation on non-Apple hardware is a coin flip. BlueBubbles'
|
||||
# own guidance: "test sending an iMessage to yourself. If it does not
|
||||
# succeed, it's likely best to restart from the beginning."
|
||||
# 3. Apple's macOS licence permits virtualisation only on Apple hardware. This
|
||||
# is a Hackintosh. Use a throwaway Apple ID, not a primary one.
|
||||
# 4. BlueBubbles labels this path "not for beginners", "no guarantees or
|
||||
# warranty".
|
||||
#
|
||||
# Private API (reactions, typing indicators, edit/unsend) needs SIP disabled
|
||||
# inside the guest and is NOT required for plain send/receive. Skip it.
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: macos-bluebubbles
|
||||
labels:
|
||||
app.kubernetes.io/name: macos-bluebubbles
|
||||
app.kubernetes.io/part-of: sms
|
||||
spec:
|
||||
replicas: 1
|
||||
# Recreate: the qcow2 disk is RWO and a second pod must never attach it
|
||||
# concurrently — two QEMU processes on one image corrupts it.
|
||||
strategy:
|
||||
type: Recreate
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: macos-bluebubbles
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: macos-bluebubbles
|
||||
app.kubernetes.io/part-of: sms
|
||||
spec:
|
||||
# Dedicated node. The taint keeps everything else off worker-2; this
|
||||
# toleration is what lets the VM on. Both halves are required.
|
||||
nodeSelector:
|
||||
workload: imessage
|
||||
tolerations:
|
||||
- key: workload
|
||||
operator: Equal
|
||||
value: imessage
|
||||
effect: NoSchedule
|
||||
containers:
|
||||
- name: macos
|
||||
image: sickcodes/docker-osx:latest@sha256:3a3c82c79bc4e73531f819ccdfa4053b3084efd7c1f645678dbf8b4b3a24369c
|
||||
# QEMU needs /dev/kvm; Talos enforces `baseline` cluster-wide, so this
|
||||
# only schedules because the sms namespace is labelled privileged.
|
||||
securityContext:
|
||||
privileged: true
|
||||
env:
|
||||
# Generates a unique serial / board-serial / UUID / MAC and persists
|
||||
# them to bootdisk.qcow2. This synthetic identity is what iMessage
|
||||
# activates against — it must stay stable across restarts, which is
|
||||
# why the PVC matters.
|
||||
- name: GENERATE_UNIQUE
|
||||
value: "true"
|
||||
# Identity is only plausible if it matches a real product line.
|
||||
- name: DEVICE_MODEL
|
||||
value: "iMacPro1,1"
|
||||
- name: RAM
|
||||
value: "12"
|
||||
- name: CORES
|
||||
value: "6"
|
||||
- name: EXTRA
|
||||
# Expose the BlueBubbles server port from the guest to the pod.
|
||||
# Guest :1234 (BlueBubbles default) -> pod :1234.
|
||||
value: "-device virtio-net-pci,netdev=net0 -netdev user,id=net0,hostfwd=tcp::1234-:1234"
|
||||
ports:
|
||||
- name: vnc
|
||||
containerPort: 5999
|
||||
protocol: TCP
|
||||
- name: bluebubbles
|
||||
containerPort: 1234
|
||||
protocol: TCP
|
||||
resources:
|
||||
requests:
|
||||
cpu: "6"
|
||||
memory: 14Gi
|
||||
limits:
|
||||
cpu: "12"
|
||||
memory: 20Gi
|
||||
volumeMounts:
|
||||
- name: macos-disk
|
||||
mountPath: /home/arch/OSX-KVM/disk
|
||||
- name: kvm
|
||||
mountPath: /dev/kvm
|
||||
# No readiness probe on purpose. The guest takes many minutes to boot,
|
||||
# and until macOS + BlueBubbles are installed BY HAND there is nothing
|
||||
# listening on 1234. A probe here would crash-loop the pod through the
|
||||
# entire interactive install.
|
||||
volumes:
|
||||
- name: macos-disk
|
||||
persistentVolumeClaim:
|
||||
claimName: macos-disk
|
||||
- name: kvm
|
||||
hostPath:
|
||||
path: /dev/kvm
|
||||
type: CharDevice
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: sms
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- storageclass.yaml
|
||||
- pvc-macos.yaml
|
||||
- deployment-macos.yaml
|
||||
- service.yaml
|
||||
- networkpolicy.yaml
|
||||
@@ -0,0 +1,19 @@
|
||||
# iMessage delivery for the cluster.
|
||||
#
|
||||
# BlueBubbles' server is a macOS Electron app paired with an Objective-C helper
|
||||
# that hooks Messages.app private APIs — it cannot be containerised on Linux,
|
||||
# because containers share the host kernel and macOS needs XNU + Cocoa. The only
|
||||
# way to run it on Talos is a full macOS VM under QEMU/KVM (Docker-OSX), which
|
||||
# needs a privileged pod with /dev/kvm.
|
||||
#
|
||||
# Hence privileged PodSecurity: the cluster default from the Talos controlplane
|
||||
# is `enforce: baseline`, which forbids privileged containers and host devices.
|
||||
# Scope is limited to this namespace.
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: sms
|
||||
labels:
|
||||
pod-security.kubernetes.io/enforce: privileged
|
||||
pod-security.kubernetes.io/audit: privileged
|
||||
pod-security.kubernetes.io/warn: privileged
|
||||
@@ -0,0 +1,22 @@
|
||||
# Default-deny. This namespace runs a privileged QEMU VM signed into an Apple
|
||||
# ID and exposes an unauthenticated VNC console; nothing should reach it except
|
||||
# opted-in clients.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: sms-default-deny
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/part-of: sms
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector: {}
|
||||
podSelector:
|
||||
matchLabels:
|
||||
sms-client: "true"
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 1234
|
||||
@@ -0,0 +1,25 @@
|
||||
# Persistent macOS disk image + generated hardware identity (bootdisk.qcow2).
|
||||
#
|
||||
# This volume is NOT disposable: it holds the VM's serial number, board serial,
|
||||
# UUID and MAC, which together form the identity iMessage was activated against.
|
||||
# Losing it means re-running activation, which is the least reliable step of the
|
||||
# whole setup.
|
||||
#
|
||||
# Docker-OSX documents 128GB minimum for the guest image; 200Gi leaves room for
|
||||
# the installer, the base system, and qcow2 growth.
|
||||
#
|
||||
# ⚠️ Single replica (see storageclass.yaml — capacity and IO both rule out 3).
|
||||
# Losing worker-2's disk therefore means losing the activated identity and
|
||||
# redoing iMessage activation. Once the guest is installed and activated, take
|
||||
# a Longhorn snapshot/backup of this volume; that is the only redundancy here.
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: macos-disk
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn-imessage-local
|
||||
resources:
|
||||
requests:
|
||||
storage: 200Gi
|
||||
@@ -0,0 +1,31 @@
|
||||
# VNC is how you drive the interactive macOS install. Deliberately ClusterIP —
|
||||
# it is an unauthenticated console onto a machine holding a live Apple ID
|
||||
# session. Reach it with `kubectl port-forward`, never an Ingress.
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: macos-vnc
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: macos-bluebubbles
|
||||
ports:
|
||||
- name: vnc
|
||||
port: 5999
|
||||
targetPort: vnc
|
||||
---
|
||||
# The BlueBubbles REST API, once installed inside the guest. This is the stable
|
||||
# name cluster services use, so callers never depend on the pod IP or on whether
|
||||
# the backend is this VM or a real Mac mini later.
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: bluebubbles
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: macos-bluebubbles
|
||||
ports:
|
||||
- name: http
|
||||
port: 1234
|
||||
targetPort: bluebubbles
|
||||
@@ -0,0 +1,32 @@
|
||||
# Dedicated StorageClass for the macOS VM disk.
|
||||
#
|
||||
# The default `longhorn` class does not work here, for two independent reasons:
|
||||
#
|
||||
# 1. Replica count. Default is 3, and Longhorn schedules against
|
||||
# storageMaximum - storageScheduled with over-provisioning at 100%. Free
|
||||
# space is cp-1 146Gi / cp-2 8Gi / cp-3 146Gi / worker-1 292Gi, so a 200Gi
|
||||
# volume has only one node that can hold even a single replica — a 3-replica
|
||||
# volume fails outright with ReplicaSchedulingFailure.
|
||||
# 2. Binding mode. `Immediate` provisions the volume the moment the PVC is
|
||||
# created, before any pod is scheduled. Combined with strict-local that
|
||||
# pins the data to an arbitrary node, not the one the VM runs on.
|
||||
#
|
||||
# So: one replica, kept local to the VM, bound only once the pod has a node.
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: longhorn-imessage-local
|
||||
provisioner: driver.longhorn.io
|
||||
allowVolumeExpansion: true
|
||||
reclaimPolicy: Delete
|
||||
# The pod is pinned to worker-2 by nodeSelector; wait for it to be scheduled so
|
||||
# the replica is placed on that node and not somewhere else.
|
||||
volumeBindingMode: WaitForFirstConsumer
|
||||
parameters:
|
||||
# A qcow2 backing a live VM is latency-sensitive and rewritten constantly.
|
||||
# Serving it over the network from another node's disk would be the single
|
||||
# worst thing for guest responsiveness, so force it local.
|
||||
numberOfReplicas: "1"
|
||||
dataLocality: "strict-local"
|
||||
staleReplicaTimeout: "30"
|
||||
fsType: "ext4"
|
||||
@@ -1,42 +0,0 @@
|
||||
# Wave 0 — Nginx Ingress Controller
|
||||
# Foundational infrastructure required for all ingress resources and ArgoCD UI access.
|
||||
# Must be wave 0 to ensure LoadBalancer IP is available before other apps deploy.
|
||||
---
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ingress-nginx
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "0"
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: homelab
|
||||
revisionHistoryLimit: 3
|
||||
sources:
|
||||
- repoURL: https://kubernetes.github.io/ingress-nginx
|
||||
chart: ingress-nginx
|
||||
targetRevision: "4.15.1"
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/bootstrap/ingress/nginx-values.yaml
|
||||
- repoURL: [email protected]:Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ingress-nginx
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
retry:
|
||||
limit: 3
|
||||
backoff:
|
||||
duration: 10s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
@@ -1,26 +1,8 @@
|
||||
# Wave 0 — networking policies layered on the Cilium CNI + CoreDNS that the
|
||||
# cluster bootstrap already installed (substrate). These are raw manifests only.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: cilium-policy
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "0"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: [email protected]:Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/bootstrap/cilium
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: kube-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
# coredns-config Application removed: CoreDNS (incl. homelab hostname rewrites)
|
||||
# is owned by Talos via an inlineManifest (terraform/files/coredns/Corefile).
|
||||
# Managing the coredns ConfigMap from ArgoCD too would let the two reconcilers
|
||||
# fight and revert the rewrites.
|
||||
# Wave 0 — networking substrate is Talos-owned (terraform inlineManifests), not
|
||||
# ArgoCD:
|
||||
# - CoreDNS Corefile + hostname rewrites -> terraform/files/coredns/Corefile
|
||||
# - Cilium LB-IPAM pool + L2 announcement -> terraform/files/cilium/*.yaml
|
||||
# Both were previously ArgoCD apps here whose empty `resources: []`
|
||||
# kustomizations never actually applied them (live objects came from manual
|
||||
# kubectl). Managing them from ArgoCD too would let two reconcilers fight. This
|
||||
# file intentionally defines no Applications now.
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# Wave 7 — Kong, the cluster's internal API gateway (namespace `api`).
|
||||
#
|
||||
# Sits between nginx and the backend services: nginx owns the edge and TLS,
|
||||
# Kong owns routing policy, auth and rate limiting. Wave 7 puts it after the
|
||||
# data/messaging tiers it fronts and before the wave-8 applications that
|
||||
# publish routes into it.
|
||||
#
|
||||
# DB-less: routing config comes from Kubernetes objects (Ingress with
|
||||
# `ingressClassName: kong`, plus KongPlugin/KongConsumer CRDs), so git remains
|
||||
# the source of truth and there are no migration Jobs on upgrade.
|
||||
#
|
||||
# CRDs ship in the chart's crds/ directory; ArgoCD applies those by default
|
||||
# (helm.skipCrds is left false).
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: kong
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "7"
|
||||
spec:
|
||||
project: homelab
|
||||
revisionHistoryLimit: 3
|
||||
sources:
|
||||
- repoURL: https://charts.konghq.com
|
||||
chart: kong
|
||||
targetRevision: "3.4.1"
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/k8s/apps/api/kong-values.yaml
|
||||
- repoURL: [email protected]:Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
ref: values
|
||||
# The nginx Ingress for api.riotpiao.com. Kept in this Application rather
|
||||
# than the central k8s/bootstrap/ingress/ingress.yaml because that one syncs
|
||||
# in wave 1, before namespace `api` exists.
|
||||
- repoURL: [email protected]:Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/api
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: api
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
# The chart's CRDs exceed the annotation size limit that client-side
|
||||
# apply relies on; server-side apply avoids the
|
||||
# "metadata.annotations: Too long" failure CRDs commonly hit.
|
||||
- ServerSideApply=true
|
||||
retry:
|
||||
limit: 3
|
||||
backoff:
|
||||
duration: 10s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
@@ -84,6 +84,37 @@ spec:
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
---
|
||||
# iMessage/SMS delivery. Raw manifests: a privileged macOS VM (Docker-OSX)
|
||||
# running the BlueBubbles server, plus its dedicated local StorageClass.
|
||||
#
|
||||
# Pinned to worker-2 via nodeSelector `workload: imessage` + a matching
|
||||
# toleration for that node's taint. Until worker-2 is provisioned this app
|
||||
# syncs everything except the pod, which stays Pending — that is expected.
|
||||
#
|
||||
# No CreateNamespace: namespace.yaml carries `pod-security: privileged`, which
|
||||
# the VM needs (/dev/kvm, privileged), and an ArgoCD-created namespace would
|
||||
# not have those labels.
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: sms
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "8"
|
||||
spec:
|
||||
project: homelab
|
||||
source:
|
||||
repoURL: [email protected]:Riotpiaole/riotpiao.homelab.com.git
|
||||
targetRevision: main
|
||||
path: k8s/apps/sms
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: sms
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
---
|
||||
# Consolidated: homarr + homarr-patches → homarr
|
||||
# Helm chart + values + PostSync hook patch (fix-probes-job.yaml)
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: kube-system
|
||||
resources: []
|
||||
# Cilium deployed via Helm chart
|
||||
@@ -1,39 +0,0 @@
|
||||
# k8s/cilium/l2-announcement-policy.yaml
|
||||
# CiliumL2AnnouncementPolicy — without this, LB-IPAM (lb-ipam-pool.yaml)
|
||||
# assigns IPs to LoadBalancer Services but nothing ARPs for them on the LAN,
|
||||
# so they're unreachable from outside the cluster even though `kubectl get
|
||||
# svc` shows a real EXTERNAL-IP. Confirmed both forgejo's .165 and
|
||||
# shadowsocks' .166 were 100% packet loss / incomplete ARP before this.
|
||||
#
|
||||
# loadBalancerIPs: true makes Cilium announce every Service's LB-IPAM IP via
|
||||
# ARP from whichever node currently holds the lease for it (one node per IP,
|
||||
# decided by leaderElection — not all nodes simultaneously, which would
|
||||
# otherwise cause ARP flapping/duplicate-IP confusion on the LAN).
|
||||
#
|
||||
# externalIPs/loadBalancerIPs split exists because Cilium also supports
|
||||
# announcing Service externalIPs (a different field, unused in this repo);
|
||||
# we only need loadBalancerIPs since every exposed Service here is type
|
||||
# LoadBalancer via lb-ipam-pool.yaml.
|
||||
#
|
||||
# requires kube-proxy replacement (already the case — see
|
||||
# k8s/talos-iam or helmfile.yaml.gotmpl kubeProxyReplacement=true) and a
|
||||
# Cilium build with L2 announcements enabled (default since v1.14).
|
||||
#
|
||||
# Apply once after cluster bootstrap, alongside lb-ipam-pool.yaml:
|
||||
# kubectl apply -f k8s/cilium/l2-announcement-policy.yaml
|
||||
#
|
||||
# Verify:
|
||||
# kubectl get ciliuml2announcementpolicy
|
||||
# ping 192.168.1.165 && ping 192.168.1.166 # both should now respond
|
||||
# arp -a | grep 192.168.1.16 # should resolve to a real MAC
|
||||
apiVersion: cilium.io/v2alpha1
|
||||
kind: CiliumL2AnnouncementPolicy
|
||||
metadata:
|
||||
name: homelab-l2-announce
|
||||
spec:
|
||||
loadBalancerIPs: true
|
||||
interfaces:
|
||||
- eno1
|
||||
# No nodeSelector restriction — all 3 nodes already run workloads
|
||||
# (allowSchedulingOnControlPlanes: true in controlplane.yaml), and with
|
||||
# 3 zone-labeled nodes, redundancy for per-IP leader election is maintained.
|
||||
@@ -1,36 +0,0 @@
|
||||
# k8s/cilium/lb-ipam-pool.yaml
|
||||
# CiliumLoadBalancerIPPool — tells Cilium LB-IPAM which IPs it can assign
|
||||
# to LoadBalancer services in this cluster.
|
||||
#
|
||||
# CIDR 192.168.1.160/28 covers .160–.175 on the LAN:
|
||||
# .160 talos-cp-1 (node — not assignable to services)
|
||||
# .161 reserved
|
||||
# .162 talos-worker-1 (node — not assignable to services)
|
||||
# .163–.175 free for LoadBalancer services
|
||||
#
|
||||
# Current service IP assignments (via io.cilium/lb-ipam-ips annotation):
|
||||
# 192.168.1.165 forgejo-gitea-http (cicd)
|
||||
# 192.168.1.165 forgejo-gitea-ssh (cicd) — same IP, different ports
|
||||
# 192.168.1.166 shadowsocks (vpn)
|
||||
#
|
||||
# Apply once after cluster bootstrap:
|
||||
# kubectl apply -f k8s/cilium/lb-ipam-pool.yaml
|
||||
#
|
||||
# Verify assignment:
|
||||
# kubectl get svc -n cicd forgejo-gitea-http forgejo-gitea-ssh
|
||||
# # EXTERNAL-IP should change from <pending> to 192.168.1.165
|
||||
|
||||
apiVersion: "cilium.io/v2alpha1"
|
||||
kind: CiliumLoadBalancerIPPool
|
||||
metadata:
|
||||
name: homelab-pool
|
||||
spec:
|
||||
blocks:
|
||||
- cidr: "192.168.1.160/28"
|
||||
# DO NOT add any 10.6.0.0/24 block here. That is the WireGuard subnet
|
||||
# (10.6.0.1 = talos-cp-1 tunnel IP, 10.6.0.2 = DNS — see
|
||||
# cluster-config/controlplane.yaml). A 10.6.0.x block let Cilium LB-IPAM
|
||||
# auto-assign the CP's own tunnel IP to a Service, which broke the
|
||||
# WireGuard tunnel and locked out the default kubectl context. It also
|
||||
# can't work over WireGuard anyway — L2 announcements only ARP on eno1
|
||||
# (the LAN interface), not wg0. Keep this pool LAN-only.
|
||||
@@ -1,22 +0,0 @@
|
||||
# Service alias for CoreDNS compatibility
|
||||
# CoreDNS rewrites *.riotpiao.com → ingress-nginx-controller.ingress-nginx.svc
|
||||
# But bootstrap deployed as ingress-nginx-bootstrap-controller
|
||||
# This alias makes both names work
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ingress-nginx-controller
|
||||
namespace: ingress-nginx
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: ingress-nginx
|
||||
app.kubernetes.io/instance: ingress-nginx-bootstrap
|
||||
app.kubernetes.io/component: controller
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
- name: https
|
||||
port: 443
|
||||
targetPort: https
|
||||
@@ -300,6 +300,10 @@ spec:
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
# NOTE: api.riotpiao.com (Kong) is deliberately NOT here. Its namespace `api` is
|
||||
# created in wave 7, and this Application syncs in wave 1 — an Ingress into a
|
||||
# namespace that doesn't exist yet would fail and mark this whole app
|
||||
# SyncFailed. It lives in k8s/apps/api/ingress.yaml, synced with Kong itself.
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
|
||||
@@ -2,6 +2,12 @@ apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
# No top-level namespace - resources declare their own namespaces
|
||||
resources:
|
||||
- ingress-nginx-controller-alias.yaml # Service alias for CoreDNS compatibility
|
||||
# ingress-nginx-controller-alias.yaml REMOVED — it was a ClusterIP Service named
|
||||
# ingress-nginx-controller with a stale selector (instance: ingress-nginx-bootstrap,
|
||||
# a release that no longer exists). ingress-config's selfHeal kept re-applying it
|
||||
# over the helm release's real LoadBalancer Service of the same name, reverting it
|
||||
# to a ClusterIP with zero endpoints -> LB IP .160 unannounced -> cluster-wide
|
||||
# outage. CoreDNS rewrites *.riotpiao.com to ingress-nginx-controller.ingress-nginx
|
||||
# .svc, which is the helm Service directly — no alias needed.
|
||||
- riotpiao-com-cert.yaml # Certificate for *.riotpiao.com (ingress-nginx namespace)
|
||||
- ingress.yaml # Ingress rules for all services (multiple namespaces)
|
||||
|
||||
@@ -14,6 +14,18 @@ valkey-cluster:
|
||||
redis:
|
||||
enabled: false
|
||||
|
||||
# External SSH access for git over the LAN. The chart's ssh Service becomes a
|
||||
# LoadBalancer with a stable IP from the Cilium homelab-pool (192.168.1.160/28,
|
||||
# L2-announced) so `git clone ssh://[email protected]:2222/...` works from the
|
||||
# LAN. gitea's sshd listens on 2222 in-pod; port 2222 is exposed directly to
|
||||
# avoid needing privileged :22.
|
||||
service:
|
||||
ssh:
|
||||
type: LoadBalancer
|
||||
port: 2222
|
||||
annotations:
|
||||
lbipam.cilium.io/ips: "192.168.1.161"
|
||||
|
||||
gitea:
|
||||
admin:
|
||||
existingSecret: forgejo-admin
|
||||
@@ -22,8 +34,10 @@ gitea:
|
||||
server:
|
||||
DOMAIN: forgejo.riotpiao.com
|
||||
ROOT_URL: https://forgejo.riotpiao.com
|
||||
SSH_DOMAIN: forgejo.riotpiao.com
|
||||
SSH_PORT: 22
|
||||
# SSH clone URLs advertise git.riotpiao.com:2222 (the LoadBalancer above).
|
||||
SSH_DOMAIN: git.riotpiao.com
|
||||
SSH_PORT: 2222
|
||||
SSH_LISTEN_PORT: 2222
|
||||
|
||||
database:
|
||||
DB_TYPE: postgres
|
||||
|
||||
@@ -2,7 +2,7 @@ apiVersion: monitoring.coreos.com/v1
|
||||
kind: PrometheusRule
|
||||
metadata:
|
||||
name: forgejo-rules
|
||||
namespace: forgejo
|
||||
namespace: cicd
|
||||
spec:
|
||||
groups:
|
||||
- name: forgejo.rules
|
||||
|
||||
@@ -20,8 +20,36 @@ grafana:
|
||||
enabled: false
|
||||
|
||||
# ── Alertmanager ──────────────────────────────────────────────────────────────
|
||||
# Enabled with a default (null) receiver — every firing PrometheusRule lands in
|
||||
# the Alertmanager UI and Grafana's Alerting view; no external Slack/email/
|
||||
# PagerDuty notifier is wired yet (add a receiver + route later). Storage pinned
|
||||
# to az-a (sole Longhorn node) like Prometheus so the RWO PVC can attach.
|
||||
alertmanager:
|
||||
enabled: false
|
||||
enabled: true
|
||||
alertmanagerSpec:
|
||||
nodeSelector:
|
||||
topology.kubernetes.io/zone: az-a
|
||||
tolerations:
|
||||
- key: node-role.kubernetes.io/control-plane
|
||||
operator: Exists
|
||||
effect: NoSchedule
|
||||
storage:
|
||||
volumeClaimTemplate:
|
||||
spec:
|
||||
storageClassName: longhorn
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
config:
|
||||
route:
|
||||
group_by: ["alertname", "namespace"]
|
||||
group_wait: 30s
|
||||
group_interval: 5m
|
||||
repeat_interval: 4h
|
||||
receiver: "null"
|
||||
receivers:
|
||||
- name: "null"
|
||||
|
||||
# ── Prometheus ────────────────────────────────────────────────────────────────
|
||||
prometheus:
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# CiliumL2AnnouncementPolicy — ARPs each LoadBalancer IP (from lb-ippool) on the
|
||||
# LAN so the EXTERNAL-IP is actually reachable. Without it, LB-IPAM assigns IPs
|
||||
# but nothing answers ARP (100% packet loss / incomplete ARP). One node per IP
|
||||
# holds the lease (leaderElection) to avoid ARP flapping. Requires kube-proxy
|
||||
# replacement (enabled) and Cilium L2 announcements (default since v1.14).
|
||||
apiVersion: cilium.io/v2alpha1
|
||||
kind: CiliumL2AnnouncementPolicy
|
||||
metadata:
|
||||
name: homelab-l2-announce
|
||||
spec:
|
||||
loadBalancerIPs: true
|
||||
interfaces:
|
||||
- eno1
|
||||
@@ -0,0 +1,15 @@
|
||||
# CiliumLoadBalancerIPPool — the IPs Cilium LB-IPAM may assign to LoadBalancer
|
||||
# Services. CIDR 192.168.1.160/28 covers .160–.175 on the LAN.
|
||||
# .160 ingress-nginx (LoadBalancer)
|
||||
# .161 forgejo-ssh (LoadBalancer)
|
||||
# .162–.175 free
|
||||
# Do NOT add a 10.6.0.0/24 block — that is the WireGuard subnet; letting LB-IPAM
|
||||
# hand out a CP tunnel IP breaks the tunnel, and L2 ARP only works on the LAN
|
||||
# interface (eno1) anyway. Keep this pool LAN-only.
|
||||
apiVersion: cilium.io/v2alpha1
|
||||
kind: CiliumLoadBalancerIPPool
|
||||
metadata:
|
||||
name: homelab-pool
|
||||
spec:
|
||||
blocks:
|
||||
- cidr: "192.168.1.160/28"
|
||||
@@ -33,6 +33,11 @@
|
||||
rewrite name homarr.riotpiao.com ingress-nginx-controller.ingress-nginx.svc.cluster.local
|
||||
rewrite name portainer.riotpiao.com ingress-nginx-controller.ingress-nginx.svc.cluster.local
|
||||
rewrite name longhorn.riotpiao.com ingress-nginx-controller.ingress-nginx.svc.cluster.local
|
||||
# Kong API gateway. Points at nginx, not kong-proxy, for the same reason as
|
||||
# the rest: a direct rewrite would skip TLS termination. Pods that don't
|
||||
# need TLS should call kong-proxy.api.svc.cluster.local instead of using
|
||||
# this name at all.
|
||||
rewrite name api.riotpiao.com ingress-nginx-controller.ingress-nginx.svc.cluster.local
|
||||
|
||||
kubernetes cluster.local in-addr.arpa ip6.arpa {
|
||||
pods insecure
|
||||
|
||||
+24
-20
@@ -22,26 +22,28 @@ resource "local_file" "controlplane_configs" {
|
||||
filename = "${path.module}/../cluster-config/${each.key}.yaml"
|
||||
|
||||
content = templatefile("${path.module}/templates/controlplane.tftpl", {
|
||||
version = "v1alpha1"
|
||||
hostname = each.value.hostname
|
||||
token = var.machine_token
|
||||
ca_crt = var.machine_ca_crt
|
||||
ca_key = var.machine_ca_key
|
||||
lan_ip = each.value.lan_ip
|
||||
lan_subnet = each.value.lan_subnet
|
||||
lan_gateway = each.value.lan_gateway
|
||||
kubelet_image = local.kubelet_image
|
||||
cluster_dns_ip = local.cluster_dns_ip
|
||||
install_disk = each.value.install_disk
|
||||
factory_image = local.factory_image
|
||||
talos_version = var.talos_version
|
||||
longhorn_disks = each.value.longhorn_disks
|
||||
dns_servers = var.cluster_config.dns_servers
|
||||
forgejo_registry_ip = var.forgejo_registry_ip
|
||||
forgejo_hostname = var.forgejo_hostname
|
||||
zone = each.value.zone
|
||||
allow_scheduling = each.value.allow_scheduling
|
||||
coredns_corefile = file("${path.module}/files/coredns/Corefile")
|
||||
version = "v1alpha1"
|
||||
hostname = each.value.hostname
|
||||
token = var.machine_token
|
||||
ca_crt = var.machine_ca_crt
|
||||
ca_key = var.machine_ca_key
|
||||
lan_ip = each.value.lan_ip
|
||||
lan_subnet = each.value.lan_subnet
|
||||
lan_gateway = each.value.lan_gateway
|
||||
kubelet_image = local.kubelet_image
|
||||
cluster_dns_ip = local.cluster_dns_ip
|
||||
install_disk = each.value.install_disk
|
||||
factory_image = local.factory_image
|
||||
talos_version = var.talos_version
|
||||
longhorn_disks = each.value.longhorn_disks
|
||||
dns_servers = var.cluster_config.dns_servers
|
||||
forgejo_registry_ip = var.forgejo_registry_ip
|
||||
forgejo_hostname = var.forgejo_hostname
|
||||
zone = each.value.zone
|
||||
allow_scheduling = each.value.allow_scheduling
|
||||
coredns_corefile = file("${path.module}/files/coredns/Corefile")
|
||||
cilium_lb_ippool = file("${path.module}/files/cilium/lb-ippool.yaml")
|
||||
cilium_l2_announcement = file("${path.module}/files/cilium/l2-announcement.yaml")
|
||||
|
||||
# Cloudflare Tunnel cert SANs (talos :50000 and kube-apiserver :6443)
|
||||
cloudflare_talos_sans = each.value.cloudflare_talos_sans
|
||||
@@ -101,6 +103,8 @@ resource "local_file" "worker_configs" {
|
||||
forgejo_hostname = var.forgejo_hostname
|
||||
zone = each.value.zone
|
||||
gpu_count = each.value.gpu_count
|
||||
node_labels = each.value.node_labels
|
||||
node_taints = each.value.node_taints
|
||||
extra_disks = each.value.extra_disks
|
||||
swap_size = each.value.swap_size
|
||||
ephemeral_max_size = each.value.ephemeral_max_size
|
||||
|
||||
@@ -164,6 +164,18 @@ cluster:
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: kube-system
|
||||
# Cilium LoadBalancer IPAM pool + L2 announcement policy. Substrate networking
|
||||
# (owned here alongside the Cilium install), single source of truth in
|
||||
# terraform/files/cilium/*.yaml. Provides LAN LoadBalancer IPs for ingress-nginx
|
||||
# (.160) and forgejo-ssh (.161). Was previously an ArgoCD app whose empty
|
||||
# kustomization never actually applied it (the live pool came from manual
|
||||
# kubectl); moved here so LB-IPAM exists before any LoadBalancer Service syncs.
|
||||
- name: cilium-lb-ippool
|
||||
contents: |
|
||||
${indent(8, cilium_lb_ippool)}
|
||||
- name: cilium-l2-announcement
|
||||
contents: |
|
||||
${indent(8, cilium_l2_announcement)}
|
||||
# CoreDNS Corefile with homelab hostname rewrites (single source of truth in
|
||||
# terraform/files/coredns/Corefile). In-cluster pods resolve *.riotpiao.com to
|
||||
# the nginx ingress controller so OIDC auto-discovery against
|
||||
|
||||
@@ -71,11 +71,20 @@ machine:
|
||||
nodeLabels:
|
||||
topology.kubernetes.io/region: homelab
|
||||
topology.kubernetes.io/zone: ${zone}
|
||||
node-role.kubernetes.io/gpu-node: ""
|
||||
%{ if gpu_count > 0 ~}
|
||||
node-role.kubernetes.io/gpu-node: ""
|
||||
nvidia.com/gpu: "true"
|
||||
gpu-count: "${gpu_count}"
|
||||
%{ endif ~}
|
||||
%{ for k, v in node_labels ~}
|
||||
${k}: "${v}"
|
||||
%{ endfor ~}
|
||||
%{ if length(node_taints) > 0 ~}
|
||||
nodeTaints:
|
||||
%{ for t in node_taints ~}
|
||||
${t.key}: "${t.value}:${t.effect}"
|
||||
%{ endfor ~}
|
||||
%{ endif ~}
|
||||
|
||||
cluster:
|
||||
id: ${cluster_id}
|
||||
|
||||
+17
-8
@@ -123,14 +123,23 @@ variable "controlplane_configs" {
|
||||
|
||||
variable "worker_configs" {
|
||||
type = map(object({
|
||||
hostname = string
|
||||
lan_ip = string
|
||||
lan_subnet = string
|
||||
lan_gateway = string
|
||||
install_disk = string
|
||||
network_interface = optional(string, "eno1")
|
||||
zone = string
|
||||
gpu_count = optional(number, 0)
|
||||
hostname = string
|
||||
lan_ip = string
|
||||
lan_subnet = string
|
||||
lan_gateway = string
|
||||
install_disk = string
|
||||
network_interface = optional(string, "eno1")
|
||||
zone = string
|
||||
gpu_count = optional(number, 0)
|
||||
# Extra node labels beyond the topology/GPU defaults.
|
||||
node_labels = optional(map(string), {})
|
||||
# Taints make a node dedicated: only pods carrying a matching toleration
|
||||
# schedule there. effect is NoSchedule | PreferNoSchedule | NoExecute.
|
||||
node_taints = optional(list(object({
|
||||
key = string
|
||||
value = string
|
||||
effect = string
|
||||
})), [])
|
||||
factory_image = optional(string)
|
||||
swap_size = optional(string, "")
|
||||
ephemeral_max_size = optional(string, "700GiB")
|
||||
|
||||
Reference in New Issue
Block a user