forked from phase-rs/phase
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalues.yaml
More file actions
299 lines (282 loc) · 12.5 KB
/
Copy pathvalues.yaml
File metadata and controls
299 lines (282 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# Default values for phase-server. See README.md for the deployment model.
image:
# Published for linux/amd64 and linux/arm64 from release.yml.
repository: ghcr.io/phase-rs/phase-server
tag: "" # defaults to .Chart.appVersion prefixed with "v"
digest: "" # optional sha256:... — pins the image regardless of tag
pullPolicy: IfNotPresent
server:
# Matchmaking-broker-only mode (no server-hosted games/drafts). Upstream's
# public server runs lobby-only; a self-hosted server is usually wanted for
# server-hosted games, so the default is full mode.
lobbyOnly: false
# CORS for the HTTP routes (/health is fetched cross-origin by the client's
# "Test server" button). "*" or a single exact origin.
corsOrigin: "*"
# If set, /ws rejects browser upgrades whose Origin header differs (exact
# match, one origin). Origin-less clients (desktop app, curl) still connect.
allowedOrigin: ""
# Advertised in ServerHello so hosts can share "CODE@host" join strings.
# Must be an absolute URL with a host, e.g. https://play.example.com — the
# server validates it at startup (validate_public_url) and advertises nothing
# if it does not parse, so a bare host or scheme-only value silently costs
# clients their join URL. Defaults to https://<ingress.host> when the ingress
# is enabled; required otherwise.
publicUrl: ""
# Sets PHASE_DATA_MANIFEST_URL. Only needed if the image was built without
# PHASE_CHANNEL (e.g. upstream :preview images); release builds derive it.
dataManifestUrl: ""
# Refuse to download card data at startup (data dir must be pre-provisioned).
noDataDownload: false
logJson: true
rustLog: info
# Existing Secret holding PHASE_ADMIN_TOKEN. Leave empty to keep /admin/*
# unmounted (404). /admin is never routed through the Ingress; reach it with
# `kubectl port-forward svc/<release> 9374`.
adminTokenSecret: ""
adminTokenSecretKey: token
# Per-process admission caps (PHASE_MAX_CONNECTIONS / PHASE_MAX_GAMES). Empty
# keeps the server's own defaults (200 / 100). Lower them when running many
# small replicas so one process cannot absorb the whole fleet's traffic.
maxConnections: ""
maxGames: ""
extraEnv: []
# PHASE_DATA_DIR: card-data.json (~100 MiB, downloaded on first boot),
# draft-pools.json and the SQLite games.db (+WAL). Needs RWO block storage.
persistence:
enabled: true
size: 2Gi
storageClass: "" # "" = cluster default
existingClaim: ""
resources:
requests:
cpu: 250m
memory: 768Mi # measured idle RSS ~530 MiB (card DB)
limits:
memory: 2Gi
# Time for the graceful-shutdown session flush. Open WebSockets are not
# closed by the server, so the pod is SIGKILLed at the end of this window.
terminationGracePeriodSeconds: 30
# First boot may download ~100 MiB of card data before /health answers.
startupProbe:
periodSeconds: 10
failureThreshold: 60
# Image's `phase` user is uid/gid 999. The stock entrypoint chowns the data
# dir as root then drops privileges; here the binary runs directly as 999 and
# fsGroup makes the volume writable instead.
podSecurityContext:
runAsNonRoot: true
runAsUser: 999
runAsGroup: 999
fsGroup: 999
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
securityContext:
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
service:
type: ClusterIP
port: 9374
ingress:
enabled: true
className: traefik
host: phase.example.com
# Only /ws, /health and /p2p-draft-backup are routed; /admin never is.
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls: "true"
tls:
secretName: "" # defaults to <release>-tls
clusterIssuer: "" # cert-manager ClusterIssuer name
issuer: "" # or a namespaced Issuer
# Traefik Middleware CRDs (traefik.io/v1alpha1).
traefik:
middlewares:
enabled: true
# What "per source" means for the limits below. The default is the socket
# peer IP — which is a SNAT'd node IP when the ingress Service runs with
# externalTrafficPolicy: Cluster, collapsing every limit into one GLOBAL
# bucket (e.g. 25 WebSockets total). Either set the Service to
# externalTrafficPolicy: Local, or key on a trusted header: behind
# Cloudflare, cloudflare.enabled overrides this with CF-Connecting-IP.
sourceCriterion:
ipStrategy: {}
# Applied to every routed path.
rateLimit:
average: 20 # requests/second per source
burst: 50
inFlightReq:
amount: 25 # concurrent requests (WebSockets count) per source
# /p2p-draft-backup: unauthenticated 1 MiB JSON writes into SQLite that
# only a restart purges (>24h old). Drafts post a snapshot per pick, so a
# few per minute is plenty; this bounds PVC fill to ~12 MiB/min per source.
backup:
maxRequestBodyBytes: 1200000
rateLimit:
average: 1
period: 5s
burst: 10
headers:
stsSeconds: 31536000
# Middleware names (namespace-name@kubernetescrd) appended to all routes.
extra: []
cloudflare:
# Site is proxied through Cloudflare (orange cloud): rate limits key on the
# CF-Connecting-IP header instead of the (SNAT'd) socket peer. Rendering
# fails unless the origin is provably Cloudflare-only (authenticatedOriginPulls
# below) or trustHeaderWithoutOriginPulls acknowledges another control.
enabled: false
# Only set true if a firewall (Cloudflare IP ranges) or Cloudflare Tunnel
# already guarantees nothing else reaches the origin; otherwise a forged
# header per request sidesteps every per-client limit.
trustHeaderWithoutOriginPulls: false
# Require Cloudflare's Authenticated Origin Pull client certificate on the
# TLS handshake for this host, so the origin cannot be reached directly with
# a forged CF-Connecting-IP. Enable "Authenticated Origin Pulls" in the zone.
authenticatedOriginPulls:
enabled: false
# https://developers.cloudflare.com/ssl/static/authenticated_origin_pull_ca.pem
caPem: ""
# Horizontal scale-out: one StatefulSet, with a PVC and a hostname per ordinal.
#
# Why a StatefulSet and not `replicas: N`: every process owns its own games.db,
# and two processes on one database is destructive — the second restores every
# live game at boot, arms a reconnect grace it never had, and its reaper then
# retires the rows the first process is still playing. `volumeClaimTemplates` is
# what makes that impossible.
#
# Routing: the entry host balances across ready pods with a sticky cookie, and
# each ordinal additionally answers on its own hostname. A game's share string
# is CODE@<the minting pod's host>, so joining by code reaches the right pod.
# See README "Scaling out" before enabling this.
scaleOut:
enabled: false
# Services, IngressRoutes and certificate SANs are templated for ordinals
# 0..replicaMax-1 up front, so a new replica is routable the moment it is
# Ready. Raising this is a chart upgrade; an autoscaler only moves within it.
replicaMax: 3
# Starting replica count. Ignored when autoscaling.enabled — Helm would
# otherwise rewrite spec.replicas on every upgrade and undo the autoscaler.
replicas: 1
# Per-ordinal hostname; must contain {ordinal} exactly once. Empty derives
# `<first-label>-{ordinal}.<rest>` from ingress.host (phase.example.com ->
# phase-0.example.com). Keep ordinals at the SAME DNS level as the entry host:
# a wildcard edge certificate covers one label, so a second-level name gets a
# certificate that does not match it.
ordinalHostTemplate: ""
# Scheme for the advertised PUBLIC_URL of each ordinal. Must stay https while
# ingress.enabled is true: the IngressRoutes this chart renders all terminate
# TLS, so http would advertise an address the chart's own route does not serve.
scheme: https
# OrderedReady serialises first boot, and each pod downloads ~100 MiB of card
# data before it is Ready, so N pods would take N x that.
podManagementPolicy: Parallel
# Keep every ordinal's games.db when the set shrinks or the release is
# removed: a dead ordinal's PVC is what lets its games come back.
persistentVolumeClaimRetentionPolicy:
whenScaled: Retain
whenDeleted: Retain
sticky:
# Pins a browser to the pod that minted its game, which is what keeps the
# host's own derived sockets on that pod. It is a third-party cookie on a
# 101 response — see the README caveat before relying on it.
enabled: true
name: phase_server_pod
sameSite: none
secure: true
httpOnly: true
maxAge: 86400
# Extra Middlewares for the IngressRoutes, as {name, namespace} objects.
# traefik.middlewares.extra is the *annotation* syntax
# ("<ns>-<name>@kubernetescrd"), which the CRD provider rejects, so scaleOut
# takes its extras here instead.
extraMiddlewareRefs: []
tls:
# A cert-manager Certificate covering the entry host and every ordinal host,
# using ingress.tls.clusterIssuer / issuer.
enabled: true
# No secretName here: `ingress.tls.secretName` is the single authority and
# this path reuses it, so a second knob could only disagree with it.
# Issue one wildcard SAN (e.g. "*.example.com") instead of one SAN per
# ordinal. Only helps if your edge/DNS actually wants a wildcard.
wildcardDnsName: ""
# Demand-driven replica count for scaleOut, as a Prometheus recording rule plus
# an HPA that follows it. The POLICY lives in the rule, not in the HPA: a
# StatefulSet always removes its highest ordinal, so shrinking is only safe when
# that ordinal has nobody on it, and that condition cannot be expressed as a
# utilisation target. Needs prometheus-operator (for the PrometheusRule) and
# prometheus-adapter (to expose the result as an external metric) — see
# examples/prometheus-adapter-values.yaml.
autoscaling:
enabled: false
minReplicas: 1
# Pack games to this fraction of a replica's phase_games_capacity before
# asking for another one.
targetUtilization: 0.7
# Name the recording rule writes, and the external metric the HPA reads.
# They differ because prometheus-adapter renames on the way through.
recordName: "phase:wanted_replicas"
metricName: phase_wanted_replicas
# Rule group evaluation interval.
interval: 15s
prometheusRule:
enabled: true
labels: {} # e.g. a Prometheus `release` selector
behavior:
scaleDown:
# Long by default: scaling in is only ever gated on the top ordinal being
# empty, and a flap would kill a pod that just took a game.
stabilizationWindowSeconds: 600
scaleUp:
stabilizationWindowSeconds: 60
# Prometheus metrics on a second container port (PHASE_METRICS_PORT). These
# gauges describe capacity and occupancy — operator information — so the port is
# never routed through the Ingress; scrape it in-cluster.
metrics:
enabled: false
port: 9464
path: /metrics
interval: 30s
# PodMonitor/ServiceMonitor are only rendered when the prometheus-operator
# CRDs are installed; without them the chart falls back to nothing, so set
# metrics.annotations for annotation-based scrapers.
podMonitor:
enabled: true
labels: {} # extra labels, e.g. a Prometheus `release` selector
serviceMonitor:
enabled: false
labels: {}
# prometheus.io/{scrape,port,path} pod annotations, for scrapers that discover
# by annotation rather than by CRD.
annotations: false
# Namespace of the scraper. Only meaningful when networkPolicy.enabled: the
# policy denies everything else, so a wrong value here means "up 0" with a
# healthy pod.
scrapeNamespaceLabels:
kubernetes.io/metadata.name: monitoring
networkPolicy:
enabled: true
# Namespace of the ingress controller — the only allowed ingress source.
# Stock k3s runs its bundled Traefik in kube-system; a Helm-installed Traefik
# is usually in `traefik` or `traefik-system`. A wrong value fails silently:
# the pod is Ready (kubelet probes are not policed) but every request 504s.
ingressNamespaceLabels:
kubernetes.io/metadata.name: kube-system
# Allow HTTPS egress to the public internet for the card-data bootstrap
# (and nothing else besides DNS). Set false once data is on the PVC.
allowBootstrapEgress: true
# The binary is static musl; musl's resolver fails on search-list expansion
# under the kubelet default ndots:5 (measured on k3s: "could not resolve
# data.phase-rs.dev" while the absolute name works). The server only talks to
# public hosts, so no search domains are needed.
dnsConfig:
options:
- name: ndots
value: "1"
nodeSelector: {}
tolerations: []
affinity: {}
podAnnotations: {}