minecraft-router: NOTES.txt and exponential-notation fix (4.1.0+up1.46.4)
Teil von #157: multiplyQuantity turned every number from the null-safe fallback helpers into a float64 via fromYaml, and Go's "%v" switches float64 to exponential notation from about 1e6 upwards. A bare byte count like 1048576 became "1.048576e+06": the integer path aborted the render on the suffix check, while the non-integer path (a fractional limitFactor) rendered the invalid quantity silently and only failed at the API server. Both string conversions now go through the canonical formatNumber helper, identical to the fix already merged for template-chart, voucher-vault and zipline. Teil von #43: adds a NOTES.txt. minecraft-router has no Ingress and no URL, so the notes name the actual way in - the NodePort Service and the fact that the routing decision is made on the hostname from the Minecraft handshake - print the effective routing table, and state the traps: the nodePort outside the default range, mappings as the only source of routes (no Kubernetes auto-discovery in this chart), the restart a routing change costs, that a Ready router says nothing about any backend, and that API writes do not survive a restart. Conditional blocks for the out-of-range nodePort, scale-to-zero, more than one replica and server.env. No production domain anywhere; examples use example.com and cluster-internal names. Minor bump, appVersion unchanged.
This commit is contained in:
3 files changed
+160
-3
No files matched your search
@@ -5,7 +5,7 @@ icon: https://avatars.githubusercontent.com/u/988985
|
||||
|
||||
type: application
|
||||
|
||||
version: 4.0.0+up1.46.4
|
||||
version: 4.1.0+up1.46.4
|
||||
appVersion: "1.46.4"
|
||||
|
||||
maintainers:
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
{{- $replicas := include "minecraft-router.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.server.replicas) -}}
|
||||
{{- $outward := .Values.server.outwardPort | default 25565 -}}
|
||||
{{- $port := .Values.server.port | default 25565 -}}
|
||||
{{- $backendPort := .Values.server.backendPort | default 25565 -}}
|
||||
{{- $apiPort := .Values.server.apiPort | default 8080 -}}
|
||||
{{- $deploy := printf "%s-%s-deployment" .Release.Name .Chart.Name -}}
|
||||
{{- $svc := printf "%s-%s-service" .Release.Name .Chart.Name -}}
|
||||
{{- $cm := printf "%s-%s-configmap" .Release.Name .Chart.Name -}}
|
||||
{{- $mappings := .Values.mappings | default list -}}
|
||||
mc-router {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}.
|
||||
|
||||
ACCESS — A TCP PROXY, NOT A WEB SERVICE
|
||||
This release has no Ingress, no certificate and no URL. Minecraft clients
|
||||
speak raw TCP to the NodePort Service {{ $svc }}:
|
||||
|
||||
<any-node-ip>:{{ $outward }}
|
||||
|
||||
server.outwardPort ({{ $outward }}) is the port on the node and the one that goes into
|
||||
the client. server.port ({{ $port }}) is what mc-router listens on inside the pod and
|
||||
what the Service publishes cluster-internally; changing it does not change
|
||||
what a player dials.
|
||||
|
||||
Which backend a connection reaches is decided by the HOSTNAME the client
|
||||
typed, not by the port: mc-router reads the server address out of the
|
||||
Minecraft handshake and looks it up in the routing table below. Every mapped
|
||||
name therefore has to resolve to a node of this cluster (or to whatever sits
|
||||
in front of it), and the player has to enter that name. Connecting to the
|
||||
node's bare IP address carries no matching hostname and is routed nowhere.
|
||||
{{- if or (lt (int $outward) 30000) (gt (int $outward) 32767) }}
|
||||
|
||||
{{ $outward }} IS OUTSIDE THE DEFAULT NODE-PORT RANGE (30000-32767)
|
||||
This install needs a cluster whose API server runs with an extended
|
||||
--service-node-port-range; without it the API server rejects the Service and
|
||||
the release fails on that one object while everything else applies. The
|
||||
choice is deliberate — 25565 is the port Minecraft clients dial by default.
|
||||
Confirm the Service actually exists:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} get svc {{ $svc }}
|
||||
{{- end }}
|
||||
|
||||
ROUTING TABLE — {{ len $mappings }} MAPPING(S) ACTIVE IN THIS RELEASE
|
||||
{{ range $mapping := $mappings }} {{ $mapping.domain }} → {{ $mapping.internalUrl }}:{{ $mapping.port | default $backendPort }}{{ if $mapping.port }} (own port, overrides server.backendPort {{ $backendPort }}){{ end }}
|
||||
{{ end }}
|
||||
mappings is the ONLY source of routes here. The chart does not run mc-router
|
||||
in its Kubernetes auto-discovery mode — there is no ServiceAccount and
|
||||
automountServiceAccountToken is false, so the router cannot watch the API and
|
||||
never reads routing annotations on a backend Service. A server announced that
|
||||
way stays unreachable through this router until its name stands in mappings,
|
||||
and a name that is in mappings routes there no matter what any annotation
|
||||
elsewhere says. Where both exist, this table wins.
|
||||
|
||||
The table is rendered into the ConfigMap {{ $cm }}
|
||||
(key custom-routing-config.json) and mounted with subPath at
|
||||
/data/custom-routing-config.json. Two consequences:
|
||||
|
||||
- mc-router reads that file once at startup, and the kubelet never
|
||||
refreshes a subPath mount, so a routing change takes effect only through
|
||||
a pod restart. The Deployment carries a checksum/config annotation, so
|
||||
helm upgrade performs that restart for you — at the price of a few
|
||||
seconds in which no client can connect and open sessions are dropped.
|
||||
Treat a routing change as a short, planned outage.
|
||||
- internalUrl is dialed by name. Point it at the backend's Service
|
||||
(survival-service.minecraft.svc.cluster.local), not at a pod IP: the
|
||||
Service name survives a restart of the Minecraft server, the pod IP does
|
||||
not.
|
||||
|
||||
There is no fallback backend. The chart configures no default route, so a
|
||||
client arriving with a hostname outside the table is not connected to
|
||||
anything. mc-router itself knows the concept (its API has POST /defaultRoute)
|
||||
and parses every flag equally as an environment variable, so server.env is
|
||||
where you would set one.
|
||||
|
||||
WHAT A READY ROUTER DOES NOT PROVE
|
||||
All three probes call mc-router's own API — GET /routes on container port
|
||||
api-port ({{ $apiPort }}) — which answers as soon as the config file has been read. It
|
||||
answers just as happily when every backend behind it is down, and when a
|
||||
domain or an internalUrl in the table is misspelled: the router is up, the
|
||||
mapping is loaded, and the player still cannot connect. A green rollout here
|
||||
is a statement about the proxy, never about a Minecraft server. Nothing in
|
||||
this release watches the backends.
|
||||
|
||||
A backend that is unreachable also does not restart the router: mc-router
|
||||
accepts the client, fails to dial the target and closes the connection, which
|
||||
the player sees as an ordinary connection failure. The router's log is the
|
||||
only place that names the address it tried:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} logs deploy/{{ $deploy }}
|
||||
|
||||
The API is not published by the Service (which carries proxy-port only), so
|
||||
ask the pod directly:
|
||||
|
||||
kubectl -n {{ .Release.Namespace }} port-forward deploy/{{ $deploy }} {{ $apiPort }}:{{ $apiPort }}
|
||||
curl -s -H 'Accept: application/json' http://127.0.0.1:{{ $apiPort }}/routes
|
||||
|
||||
That is the same endpoint the probes use; it returns the live table, and
|
||||
/metrics returns the Prometheus metrics. The API also writes: POST /routes,
|
||||
POST /defaultRoute and DELETE /routes/{addr} change the routing of the
|
||||
RUNNING PROCESS only. Nothing written that way survives a restart — the
|
||||
mounted ConfigMap is the source of truth and the next rollout silently
|
||||
reinstates it. Fix routing in mappings, not over the API.
|
||||
|
||||
NO SECRETS, NO VOLUMES, NO STATE
|
||||
This chart creates no OnePasswordItem and mounts no Secret, so there is no
|
||||
1Password item to prepare and no field names to get right. The only mounted
|
||||
object is the routing ConfigMap, rendered entirely from mappings:
|
||||
"helm uninstall {{ .Release.Name }}" destroys nothing that is not already in your values
|
||||
file. The worlds live in the backend releases, not here.
|
||||
{{- if eq $replicas "0" }}
|
||||
|
||||
SCALED TO ZERO
|
||||
{{ if .Values.scaleDown }}scaleDown: true{{ else }}server.replicas: 0{{ end }} — no router pod runs, so every hostname above stops
|
||||
connecting although the Service and the ConfigMap stay in place. The backend
|
||||
Minecraft servers keep running and stay reachable through their own Services;
|
||||
only the way in through this router is gone.
|
||||
{{- end }}
|
||||
{{- if gt (atoi $replicas) 1 }}
|
||||
|
||||
MORE THAN ONE ROUTER REPLICA
|
||||
server.replicas is {{ $replicas }}. mc-router keeps no state between connections and every
|
||||
replica mounts the same routing ConfigMap, so the Service can spread clients
|
||||
across them. Two things change: an established connection is pinned to the
|
||||
replica that accepted it, so a rollout drops the open sessions pod by pod;
|
||||
and a runtime change through the API reaches only the one pod you forwarded
|
||||
to, leaving the replicas disagreeing until the next restart.
|
||||
{{- end }}
|
||||
{{- if .Values.server.env }}
|
||||
|
||||
server.env IS APPENDED TO WHAT THE CHART SETS
|
||||
The entries land after ROUTES_CONFIG and are rendered through tpl, so a value
|
||||
may contain template expressions. Because mc-router parses its flags with
|
||||
go-flagsfiller, this is also the place for every option the chart does not
|
||||
model explicitly — LOG_LEVEL, BACKEND_DIAL_TIMEOUT, CONNECTION_RATE_LIMIT.
|
||||
|
||||
Two things it will not do. The chart passes --port and --api-binding as
|
||||
command-line arguments, and those take precedence over the matching
|
||||
environment variables, so PORT or API_BINDING set here change nothing — use
|
||||
server.port and server.apiPort. And a plain "value" is stored in the pod spec
|
||||
in clear text, readable by everyone who can read pods in {{ .Release.Namespace }}; anything
|
||||
sensitive belongs in valueFrom.secretKeyRef.
|
||||
{{- end }}
|
||||
@@ -67,7 +67,7 @@ Handles plain numbers ("2", "0.5", 1073741824), decimal ("1.5Gi"), binary
|
||||
results are printed without a decimal point.
|
||||
*/}}
|
||||
{{- define "minecraft-router.multiplyQuantity" -}}
|
||||
{{- $quantity := printf "%v" .quantity -}}
|
||||
{{- $quantity := include "minecraft-router.formatNumber" .quantity -}}
|
||||
{{- $number := regexFind "^[0-9]+(\\.[0-9]+)?" $quantity -}}
|
||||
{{- if not $number -}}
|
||||
{{- fail (printf "cannot parse resource quantity %q" $quantity) -}}
|
||||
@@ -80,7 +80,24 @@ results are printed without a decimal point.
|
||||
{{- if eq $product (floor $product) -}}
|
||||
{{- printf "%d%s" (int64 $product) $suffix -}}
|
||||
{{- else -}}
|
||||
{{- printf "%v%s" $product $suffix -}}
|
||||
{{- printf "%s%s" (include "minecraft-router.formatNumber" $product) $suffix -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
{{/*
|
||||
Render a number as a plain decimal string, never in exponential notation.
|
||||
|
||||
Why this exists (issue #157): the null-safe fallback helpers pass values
|
||||
through fromYaml, which parses every number as float64. Go's "%v" switches
|
||||
float64 to exponential notation from about 1e6 upwards, so a bare byte count
|
||||
like 1048576 became "1.048576e+06" - not a valid Kubernetes quantity, and the
|
||||
render aborted. Strings ("512Mi") are passed through untouched.
|
||||
*/}}
|
||||
{{- define "minecraft-router.formatNumber" -}}
|
||||
{{- if kindIs "float64" . -}}
|
||||
{{- regexReplaceAll "\\.?0+$" (printf "%.10f" .) "" -}}
|
||||
{{- else -}}
|
||||
{{- printf "%v" . -}}
|
||||
{{- end -}}
|
||||
{{- end -}}
|
||||
|
||||
|
||||
Reference in new issue
Block a user