--- # ============================================================================================ # GAME-PORT L4 EXPOSURE — why the shared HTTPS Gateway is NOT enough (same shape as apps/vms/unifi). # ============================================================================================ # The Minecraft servers Crafty creates listen on RAW TCP (Java) and UDP (Bedrock) game ports inside # the crafty pod's network namespace. The Gateway API `shared` Gateway is an L7 HTTPS terminator and # CANNOT express raw TCP on arbitrary ports or UDP at all. So the game ports are exposed as # type=LoadBalancer Services whose VIP is pulled from the CiliumLoadBalancerIPPool `default-pool` # (dev 192.168.1.53-.60; see CILIUM_LB_POOL_* in cluster-vars) and ARP-announced on the flat LAN by # the existing CiliumL2AnnouncementPolicy # `default-l2` (platform-config/cilium/*) — the SAME machinery the Gateway VIP and unifi already use. # No new Cilium config is needed; these Services just consume the pool. # # THIS PATH IS NOT AUTH-GATED — and that is correct: Minecraft's Java (Mojang/Microsoft auth) and # Bedrock (Xbox Live) protocols authenticate the PLAYER themselves. Only the Crafty WEB UI (admin # control plane) is Keycloak-gated (via the oauth2-proxy sidecar); the game ports carry player traffic # and must reach the server subprocesses directly. # # VIP: CRAFTY_L4_VIP (cluster-vars, R-ENV-02; dev 192.168.1.55) — the next free address in # default-pool (dev: .53 = shared Gateway, .54 = unifi; verified free live 2026-08-28). PINNED via # lbipam.cilium.io/ips so players have a stable target. The `crafty` Flux Kustomization substitutes it. # ONE VIP, TWO SERVICES: a LoadBalancer Service is single-protocol-friendly, so TCP and UDP split into # two Services sharing the IP via lbipam.cilium.io/sharing-key (same pattern as unifi). # # STARTER PORT SET (the operator has not created any servers yet): TCP 25565-25567 (three Java slots) # + UDP 19132-19134 (three Bedrock slots). To add capacity: create the server in Crafty bound to a NEW # port, then add that port to the matching Service below (and a containerPort in deployment.yaml for # documentation) and to the game-port ingress CiliumNetworkPolicy (networkpolicy.yaml). K8s Services # enumerate ports individually (no ranges), so add one `{ name, port, targetPort, protocol }` line each. # # externalTrafficPolicy: Local preserves the LAN client source IP and pins the VIP to the node running # the crafty pod (also what L2 announcement does). external-dns (source: service) publishes the A # record from the -tcp Service. apiVersion: v1 kind: Service metadata: name: crafty-l4-tcp namespace: crafty labels: app.kubernetes.io/name: crafty annotations: lbipam.cilium.io/sharing-key: "crafty" lbipam.cilium.io/ips: "${CRAFTY_L4_VIP}" # external-dns publishes A crafty-mc.home.keithjackson.dev -> the pinned VIP (Java server target). external-dns.alpha.kubernetes.io/hostname: "crafty-mc.${BASE_DOMAIN}" spec: type: LoadBalancer externalTrafficPolicy: Local selector: app.kubernetes.io/name: crafty ports: - { name: mc-java-0, port: 25565, targetPort: 25565, protocol: TCP } - { name: mc-java-1, port: 25566, targetPort: 25566, protocol: TCP } - { name: mc-java-2, port: 25567, targetPort: 25567, protocol: TCP } --- apiVersion: v1 kind: Service metadata: name: crafty-l4-udp namespace: crafty labels: app.kubernetes.io/name: crafty annotations: lbipam.cilium.io/sharing-key: "crafty" # same key + same IP => shares the VIP with -tcp lbipam.cilium.io/ips: "${CRAFTY_L4_VIP}" spec: type: LoadBalancer externalTrafficPolicy: Local selector: app.kubernetes.io/name: crafty ports: - { name: mc-bedr-0, port: 19132, targetPort: 19132, protocol: UDP } - { name: mc-bedr-1, port: 19133, targetPort: 19133, protocol: UDP } - { name: mc-bedr-2, port: 19134, targetPort: 19134, protocol: UDP }