Files
ingress/k8s/lux/deployment.yaml

169 lines
6.3 KiB
YAML

# Hanzo Ingress — L7 reverse proxy for lux-k8s (DaemonSet, all nodes)
# TLS: ACME DNS challenge via Cloudflare
# Routes: K8s Ingress + CRD providers (no file provider)
# hostNetwork=true: pods bind directly to node ports 80/443 (no LB needed)
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: hanzo-ingress
namespace: lux-system
labels:
app: hanzo-ingress
app.kubernetes.io/name: hanzo-ingress
app.kubernetes.io/part-of: lux-network
spec:
selector:
matchLabels:
app: hanzo-ingress
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
template:
metadata:
labels:
app: hanzo-ingress
spec:
serviceAccountName: hanzo-ingress
imagePullSecrets:
- name: ghcr-secret
hostNetwork: true
dnsPolicy: ClusterFirstWithHostNet
terminationGracePeriodSeconds: 30
containers:
- name: ingress
image: ghcr.io/hanzoai/ingress:1.7.41
imagePullPolicy: IfNotPresent
args:
# Providers
- --providers.kubernetesingress=true
- --providers.kubernetesingress.allowemptyservices=true
- --providers.kubernetesingress.allowexternalnameservices=true
- --providers.kubernetesingress.disableingressclasslookup=true
- --providers.kubernetescrd=true
- --providers.kubernetescrd.allowcrossnamespace=true
- --providers.kubernetescrd.allowexternalnameservices=true
- --providers.kubernetescrd.allowemptyservices=true
# Entrypoints
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entrypoint.to=:443
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.web.http.redirections.entrypoint.permanent=true
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls=true
- --entrypoints.websecure.http.tls.certresolver=letsencrypt
# ACME / Let's Encrypt via Cloudflare DNS-01
- --[email protected]
- --certificatesresolvers.letsencrypt.acme.storage=/data/acme.json
- --certificatesresolvers.letsencrypt.acme.dnschallenge=true
- --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
- --certificatesresolvers.letsencrypt.acme.dnschallenge.resolvers=1.1.1.1:53,8.8.8.8:53
# Health + observability
- --ping=true
- --ping.entryPoint=web
- --api.dashboard=false
- --log.level=INFO
- --accesslog=true
- --accesslog.fields.defaultmode=keep
- --accesslog.fields.headers.defaultmode=drop
# The key /data/acme.json is sealed under comes from this brand's own
# KMS, under this brand's own org — never from this file and never
# from a Kubernetes Secret:
#
# ingress/acme-seal 32 bytes, AES-256-GCM, seals the ACME state
#
# Reading it needs one credential of its own, and that one IS a
# Secret, because it is what opens KMS. The reference is REQUIRED:
# this pod is configured for KMS, so a missing credential is a
# missing configuration and the kubelet naming the absent Secret is
# the clearest way to say so. Materialise it with a KMSSecret before
# applying this manifest.
#
# The Cloudflare credential the DNS-01 challenge is answered with
# stays where the cluster already holds it. Moving it to a
# zone-scoped token is its own change: it swaps the mechanism lego
# authenticates with, and lego prefers the account pair over any
# token, so the two do not coexist.
env:
- name: INGRESS_KMS_ENDPOINT
value: "https://kms.hanzo.ai"
- name: INGRESS_KMS_ORG
value: "lux"
- name: INGRESS_KMS_PATH
value: "ingress"
- name: INGRESS_KMS_CLIENT_ID
valueFrom:
secretKeyRef:
name: ingress-kms
key: clientId
- name: INGRESS_KMS_CLIENT_SECRET
valueFrom:
secretKeyRef:
name: ingress-kms
key: clientSecret
- name: CF_API_EMAIL
valueFrom:
secretKeyRef:
name: cloudflare-api-credentials
key: email
- name: CF_API_KEY
valueFrom:
secretKeyRef:
name: cloudflare-api-credentials
key: apiKey
ports:
- name: web
containerPort: 80
hostPort: 80
protocol: TCP
- name: websecure
containerPort: 443
hostPort: 443
protocol: TCP
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: "2"
memory: 512Mi
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
allowPrivilegeEscalation: false
capabilities:
add:
- NET_BIND_SERVICE
drop:
- ALL
livenessProbe:
httpGet:
path: /ping
port: 80
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ping
port: 80
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 5
volumeMounts:
- name: data
mountPath: /data
volumes:
# /data/acme.json holds the ACME account key and every leaf private key
# this node has been issued. It is sealed (AES-256-GCM under a key read
# from KMS at startup and held only in memory), so the file on the node
# is inert. Still node-local: ACME_SHARED_STORE_NAMESPACE moves the
# state to one Kubernetes object with one elected writer, sealed there
# too.
- name: data
hostPath:
path: /var/lib/hanzo-ingress
type: DirectoryOrCreate