From ba4aa7befed9669d7e085d02790c9e5d2ec6ea43 Mon Sep 17 00:00:00 2001 From: Ruk Doe Date: Sun, 18 Oct 2026 12:14:08 +0900 Subject: [PATCH 1/2] =?UTF-8?q?Gateway=20=E3=81=AE=20nodePort=20=E3=81=AE?= =?UTF-8?q?=E6=89=8B=E5=BD=93=E3=81=A6=E3=82=92=E5=A4=96=E3=81=99(nodeport?= =?UTF-8?q?.yaml=E3=83=BB=E7=AF=84=E5=9B=B2=E3=81=AE=E8=A8=AD=E5=AE=9A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit k3s の頃(2026-09-08)は nodePort 80/443 の L7LB リダイレクトが公開経路そのもので、 nodePort をずらすと 80 番が死んだ。Talos で確かめ直したところ、外からの 80/443 は Envoy が hostNetwork で掴んだソケットに直接届く。 - VM(talos-boot): 範囲を既定に戻し nodePort がランダム(32399/32657)でも、外・ノード自身 (hostNetwork の Pod)・Pod のどこからも http 301 / https 200 - 本番: port-80 の nodePort を 30080 にずらしても 301 / 200(すぐ 80 に戻した) 消すもの: bootstrap/gateway/nodeport.yaml(PostSync の Job と RBAC)、API サーバの service-node-port-range、Cilium の nodePort.range、talos-boot の patch。 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JbmWyUB9jcBcYCnNyiGBSV --- .github/workflows/talos-boot.yml | 9 +--- apps/k8up/etcd-snapshot.yaml | 3 +- bootstrap/cilium/values.yaml | 47 ++++++-------------- bootstrap/gateway/nodeport.yaml | 74 -------------------------------- docs/decisions.md | 8 ++-- docs/talos.md | 2 +- recovery/README.md | 7 ++- talos/patches/apiserver.yaml | 7 +-- 8 files changed, 24 insertions(+), 133 deletions(-) delete mode 100644 bootstrap/gateway/nodeport.yaml diff --git a/.github/workflows/talos-boot.yml b/.github/workflows/talos-boot.yml index cd3aaf5..b1d9c2c 100644 --- a/.github/workflows/talos-boot.yml +++ b/.github/workflows/talos-boot.yml @@ -256,17 +256,10 @@ jobs: YAML # webhook が受け付けるようになるまで断られる until kubectl apply -f /tmp/issuer.yaml 2>/dev/null; do sleep 5; done - # nodeport.yaml は ArgoCD の PostSync 用(VM では下で直に patch する)なので当てない until kubectl apply -f bootstrap/gateway/gateway.yaml -f bootstrap/gateway/redirect-https.yaml -f bootstrap/argocd/httproute.yaml; do sleep 5; done kubectl -n kube-system wait --for=condition=Ready certificate --all --timeout=3m kubectl -n kube-system wait --for=condition=Programmed gateway/doany --timeout=3m - # bootstrap/gateway/nodeport.yaml と同じ手当て(本番では ArgoCD の PostSync)。nodePort が 80/443 でないと L7LB に入らない(bootstrap/cilium/values.yaml) - until kubectl -n kube-system get svc cilium-gateway-doany >/dev/null 2>&1; do sleep 5; done - kubectl -n kube-system patch svc cilium-gateway-doany --type=json -p '[ - {"op":"replace","path":"/spec/ports/0/nodePort","value":80}, - {"op":"replace","path":"/spec/ports/1/nodePort","value":443} - ]' - # 公開経路を実際に通す。http は https への 301、https は argocd-server まで + # 公開経路を実際に通す。Service の nodePort はランダムなまま(bootstrap/cilium/values.yaml の `nodePort`)。http は https への 301、https は argocd-server まで code() { curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$@" || true; } for i in $(seq 36); do H=$(code -H 'Host: ac.doany.io' http://127.0.0.1:18080/) diff --git a/apps/k8up/etcd-snapshot.yaml b/apps/k8up/etcd-snapshot.yaml index 83f4be6..687a375 100644 --- a/apps/k8up/etcd-snapshot.yaml +++ b/apps/k8up/etcd-snapshot.yaml @@ -2,8 +2,7 @@ # # **戻すときの主経路ではない。** 主経路は「git から Argo CD で建て直して PV を k8up で戻す」 # (recovery/README.md。2026-10-17 に実機で通した)。こちらは git に無い状態 -# (Argo CD が作った Application の同期状態、cert-manager の発行済み証明書、 -# API サーバが割り当てた nodePort など)まで含めて**一瞬で戻したいとき**の近道。 +# (Argo CD が作った Application の同期状態、cert-manager の発行済み証明書など)まで含めて**一瞬で戻したいとき**の近道。 # 戻し方は docs/talos.md「etcd スナップショットからの復旧」の `bootstrap --recover-from`。 # # Talos API への権限は `os:etcd:backup` だけ(talos/patches/etcd-backup.yaml)。 diff --git a/bootstrap/cilium/values.yaml b/bootstrap/cilium/values.yaml index 0ed8683..371d975 100644 --- a/bootstrap/cilium/values.yaml +++ b/bootstrap/cilium/values.yaml @@ -4,7 +4,7 @@ # machine config の inlineManifest にする(更新は render.sh → apply-config → upgrade-k8s。 # talos/README.md「上げ方 / 当て直し方」)。`cilium-config` の ConfigMap を直接いじらない ── # 次の upgrade-k8s で黙って戻る(2026-09-07 まで値が git に無く、`node-port-range: "80,443"` が -# ConfigMap 直いじりでしか入っていなかった。戻ると公開している Web が全部落ちる)。 +# ConfigMap 直いじりでしか入っていなかった。当時は、戻ると公開している Web が全部落ちた)。 # kube-proxy は使わない(Talos 側は talos/patches/cni.yaml で止めている)。 kubeProxyReplacement: true @@ -46,34 +46,20 @@ externalIPs: enabled: true nodePort: enabled: true - # 80 と 443 だけ。Gateway の Service がこの 2 つを nodePort として握っている。 - # 範囲を広げると、その中に入る hostPort を Cilium が張らなくなる - # (AdGuard の 853、Mattermost calls の 8443(いまは LiveKit)、3proxy の 3129 が一斉に落ちた)。 + # 範囲は既定(30000-32767)。Gateway の Service の nodePort は API サーバが割り当てるランダムな値のままでよい。 + # 外からの 80/443 は Envoy が hostNetwork で掴んだソケットに直接届く(下の gatewayAPI.hostNetwork)。 # - # hostNetwork にしても外せない。Service は NodePort 型のまま残り(下の - # gatewayAPI.hostNetwork のコメント)、この 2 つの NodePort フロントエンドが - # 経路そのものになっている。2026-09-08 に実測した。 + # 2026-09-08〜10-17(k3s の頃)は違った。nodePort 80/443 の L7LB リダイレクトが経路そのもので、port-80 の nodePort を + # 30080 にずらすと 80 番が全部死んだ。そのため範囲を `"80,443"`、API サーバの `service-node-port-range` を `80-32767` にし、 + # nodePort を 80/443 に揃える Job を置いていた。副作用として、ノード自身から 80/443 に繋ぐと応答が返らなかった + # (apps/netbird/routing-peer.yaml、apps/forgejo/node-access.yaml)。 # - # `cilium-dbg bpf lb list` にはこう出る: + # 2026-10-18 に Talos で確かめ直した。VM では範囲を既定に戻し nodePort をランダム(32399/32657)にしても、外からも、 + # ノード自身(hostNetwork の Pod)からも、Pod からも http 301 / https 200。本番でも port-80 の nodePort を 30080 に + # ずらして 301 / 200 のままだった。それで 3 つとも外した。 # - # 10.0.0.2:80/TCP 0.0.0.0:0 (1189) [NodePort, l7-load-balancer] (L7LB Proxy Port: 80) - # - # Envoy は確かにホストの `0.0.0.0:80` を LISTEN しているが、そのソケットに - # 直接来た接続は通らない。実通信は L7LB リダイレクト経由で Envoy に入る。 - # 試しに port-80 の nodePort だけ 30080 に振り直したところ、外部・LAN・Pod・ホストの - # すべてから 80 番が死んだ(443 は無事。すぐ戻した)。 - # - # 帰結が 3 つある: - # - # 1. ここと API サーバの `service-node-port-range=80-32767`(talos/patches/apiserver.yaml)は外せない - # 2. 復元では当て直しが要る。Cilium が Service を作り直すと API サーバが - # 範囲内のランダムな値を割り当てる。`bootstrap/gateway/nodeport.yaml`(ArgoCD の PostSync)がパッチを当てる - # 3. 副作用として、ノード自身からノードの 80/443 に繋ぐと応答が返らない。 - # これは実害を出した(2026-09-08)。hostNetwork の Pod も「ノード自身」で、netbird のルーティングピアが - # `nb.doany.io:443` に繋げず `Management: Disconnected` で止まった - # (VPN 越しの DNS も LAN への RDP も落ちる)。あちらを hostNetwork から外して直した - # (apps/netbird/routing-peer.yaml)。hostNetwork の Pod を足すときは必ずこれを思い出すこと。 - range: "80,443" + # **範囲を広げないこと。** 範囲に入る hostPort を Cilium は張らない(`80-32767` にしたとき + # AdGuard の 853、LiveKit の 8443、3proxy の 3129 が一斉に落ちた)。 # Gateway API。CRD は v1.6.1 が要る(v1.5.1 だと GatewayClass が無言で止まる)。 gatewayAPI: @@ -91,14 +77,7 @@ gatewayAPI: # Service は消えず NodePort になる(`operator/pkg/model/translation/gateway-api/translator.go` # の `toServiceType`: `if HostNetworkConfig.Enabled { return ServiceTypeNodePort }`)。 # - # nodePort の手当ては消えなかった(2026-09-08 訂正) - # - # この変更は当初「手で当てていた nodePort 80/443 が要らなくなる」つもりで入れた。 - # それは誤りだった。Envoy はホストの `0.0.0.0:80` を LISTEN するが、 - # そこに直接来た接続は通らない。実通信は Cilium の L7LB リダイレクト経由で入る。 - # 詳しくは上の `nodePort.range` のコメント(実測つき)。 - # - # したがって nodePort の当て直し(bootstrap/gateway/nodeport.yaml)は今も要る(#91 で消したが戻した)。 + # nodePort の値は経路に関係しない(上の `nodePort` のコメント。k3s の頃は関係していた)。 # # ポートの指定は無い。Gateway のリスナー(80 / 443)がそのままホストに bind される。 # `httpPort` / `httpsPort` / `sharedListenerPort` は `ingressController.hostNetwork` diff --git a/bootstrap/gateway/nodeport.yaml b/bootstrap/gateway/nodeport.yaml deleted file mode 100644 index bc89e5a..0000000 --- a/bootstrap/gateway/nodeport.yaml +++ /dev/null @@ -1,74 +0,0 @@ -# **Gateway の Service の nodePort を 80/443 に揃える。** git に書けない唯一の値で、しかも**公開経路そのもの** -# (Cilium の L7LB リダイレクト。../cilium/values.yaml の `nodePort` に実測あり)。Service は Cilium の operator が -# 作るもので、CiliumGatewayClassConfig にも nodePort の欄が無い(Cilium 1.20 の docs で確認)。Cilium が Service を -# 作り直すと API サーバが範囲内のランダムな値を割り当てるので、**まっさらなクラスタでは必ずズレている。** -# 一度揃えれば operator は戻さない(2026-10-17、live の managedFields は operator だけで 80/443 のまま)。 -# -# ArgoCD が `bootstrap` を同期するたびに流す(PostSync)。冪等。Service がまだ無ければ Job が落ち、 -# 同期の retry(../argocd/repos.yaml の `bootstrap`)でやり直す。2026-10-17 まで bootstrap-apply.yml の 1 step だった。 -# **Service が作り直されたら(Gateway の作り直しなど)、ArgoCD で `bootstrap` を手で Sync する。** selfHeal は -# ArgoCD の管理外の Service には効かないので、次に git が変わるまで 80/443 に戻らない。 -apiVersion: v1 -kind: ServiceAccount -metadata: - name: gateway-nodeport - namespace: kube-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: Role -metadata: - name: gateway-nodeport - namespace: kube-system -rules: - - apiGroups: [""] - resources: [services] - resourceNames: [cilium-gateway-doany] - verbs: [get, patch] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: gateway-nodeport - namespace: kube-system -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: gateway-nodeport -subjects: - - kind: ServiceAccount - name: gateway-nodeport - namespace: kube-system ---- -apiVersion: batch/v1 -kind: Job -metadata: - name: gateway-nodeport - namespace: kube-system - annotations: - argocd.argoproj.io/hook: PostSync - argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded -spec: - backoffLimit: 6 - template: - spec: - serviceAccountName: gateway-nodeport - restartPolicy: Never - securityContext: - runAsNonRoot: true - runAsUser: 65532 - seccompProfile: {type: RuntimeDefault} - containers: - - name: kubectl - image: registry.k8s.io/kubectl:v1.37.1 - args: - - -n - - kube-system - - patch - - svc - - cilium-gateway-doany - - --type=json - - -p - - '[{"op":"replace","path":"/spec/ports/0/nodePort","value":80},{"op":"replace","path":"/spec/ports/1/nodePort","value":443}]' - securityContext: - allowPrivilegeEscalation: false - capabilities: {drop: [ALL]} diff --git a/docs/decisions.md b/docs/decisions.md index 6ef6c8f..b0184df 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -109,7 +109,6 @@ Talos には helm-controller が無く、inlineManifests は Helm を実行で - **平文の残りは ArgoCD(2026-10-17 まで GitHub Actions の `bootstrap-apply.yml`)。** 鶏と卵にならない ── ArgoCD が動くのに要るもの(本体・repo の資格情報・入口の `apps`)は全部 machine config にあり、この層に頼っていない。 この層が要る CRD も machine config が入れる(`InfisicalSecret` の CRD だけ `apps/` から来るので、最初の何回かは retry)。 - 唯一 git に書けない Gateway の nodePort 80/443 は PostSync の Job([bootstrap/gateway/nodeport.yaml](../bootstrap/gateway/nodeport.yaml)) - **SOPS の Secret を machine config に入れる。** 復号鍵はクラスタにも CI にも渡さないので、ArgoCD からは当てられない。 machine config の入力(`talos/secrets.yaml`)もクラスタ CA の秘密鍵を含む SOPS なので信頼水準は変わらず、起動時点で存在する - **Helm を実行する場所はクラスタの外(手元の生成時)だけ。** Cilium は CNI なので inlineManifests 以外に置きようがなく、 @@ -153,7 +152,8 @@ bootstrap のあとに `helm install` する案は、再構築のたびに「CNI | 認証 | oauth2-proxy を前段のプロキシに(下記) | — | - **Cilium 1.20 は Gateway API v1.6.1 の CRD を要求する。** 古いと GatewayClass が `Waiting for controller` のまま止まる -- **ノード自身からは 80/443 に届かない。** Gateway の nodePort が L7LB リダイレクトとして載っているため(外から来る通信の経路そのもの) +- **ノード自身からは 80/443 に届かなかった(k3s の頃)。** Gateway の nodePort 80/443 が L7LB リダイレクトとして載っていたため。 + 2026-10-18 に nodePort を既定の範囲に戻した(`bootstrap/cilium/values.yaml` の `nodePort`) - **`terminationGracePeriodSeconds` が長い Pod**(denpa と tuner-agent、6 時間)は Recreate と組むと入れ替わらない。`--grace-period=0 --force` で消す ### LoadBalancer をどう置き換えるか(2026-09-06 決定・実施済み) @@ -164,7 +164,7 @@ hostPort ならノードの全アドレスで受けられ、送信元 IP も保 | 対象 | hostPort | | --- | --- | -| Gateway(`cilium-gateway-doany`) | 80 / 443。Envoy が hostNetwork で bind するが、**実通信は nodePort の L7LB リダイレクト経由**(`bootstrap/cilium/values.yaml` の `nodePort`)。nodePort 80/443 は `bootstrap/gateway/nodeport.yaml`(ArgoCD の PostSync)が当て直す | +| Gateway(`cilium-gateway-doany`) | 80 / 443。Envoy が hostNetwork で bind し、外からの通信はそこに直接届く。Service の nodePort はランダムなままでよい(`bootstrap/cilium/values.yaml` の `nodePort`。k3s の頃は 80/443 に揃える必要があった) | | adguardhome | 53 UDP・53 TCP・853 TCP | | matrix の LiveKit | 8443 UDP・8443 TCP | | 3proxy(tls-terminator サイドカー) | 3129 TCP | @@ -173,7 +173,7 @@ hostPort ならノードの全アドレスで受けられ、送信元 IP も保 `ss` には出ないので確認は `cilium-dbg service list | grep HostPort` - **hostPort と RollingUpdate は両立しない**(新しい Pod が Pending で止まる)。AdGuard と 3proxy は `strategy: Recreate` - **Gateway はアドレスが付くまで `Programmed=False`。** `gatewayAPI.hostNetwork` でノードの IP が `status.addresses` に入る -- **`nodePort.range` は `80,443` の 2 つだけ。** Cilium は nodePort の範囲に入る hostPort を張らない(`80-32767` にしたら 853・8443・3129 が一斉に落ちた)。 +- **`nodePort.range` を広げない(既定の 30000-32767)。** Cilium は nodePort の範囲に入る hostPort を張らない(`80-32767` にしたら 853・8443・3129 が一斉に落ちた)。 前段を入れ替えるときは、自分で配っている chart の入口(denpa の `httpRoute`)も数えること ### 3proxy と AdGuard の DoH はサイドカーで TLS を終端する diff --git a/docs/talos.md b/docs/talos.md index f42043a..9c2eaa9 100644 --- a/docs/talos.md +++ b/docs/talos.md @@ -107,7 +107,7 @@ PSA のラベルまで戻った。**PV の中身は戻らない**(etcd は「PVC このあと PV データを k8up で戻す。順序は etcd が先(PV オブジェクトが無いと戻す先が決まらない)。 **主経路ではない。** 主経路は git から建て直すほう([recovery/README.md](../recovery/README.md)。スナップショットの鮮度に左右されない)。 -こちらは git に無い状態(Application の同期状態、発行済みの証明書、API サーバが割り当てた nodePort など)まで一度に戻したいときの近道。 +こちらは git に無い状態(Application の同期状態、発行済みの証明書など)まで一度に戻したいときの近道。 日次で取っているのは [apps/k8up/etcd-snapshot.yaml](../apps/k8up/etcd-snapshot.yaml)(restic の `--host etcd`)。取り出しは: ```shell diff --git a/recovery/README.md b/recovery/README.md index 63424f5..7409a3a 100644 --- a/recovery/README.md +++ b/recovery/README.md @@ -115,16 +115,15 @@ kubectl get gatewayclass # cilium が Accepted(無いと Gateway が `Waiting ## 5. 残りの bootstrap/ が入るのを見る(ArgoCD) 手ですることは無い。ArgoCD の Application `bootstrap`([../bootstrap/argocd/repos.yaml](../bootstrap/argocd/repos.yaml))が -`ClusterIssuer`・Gateway・auth・`InfisicalSecret` を当て、最後に Gateway の Service の nodePort を 80/443 に揃える -([../bootstrap/gateway/nodeport.yaml](../bootstrap/gateway/nodeport.yaml) の PostSync の Job)。 +`ClusterIssuer`・Gateway・auth・`InfisicalSecret` を当てる。 ```shell kubectl -n argocd get application bootstrap # InfisicalSecret の CRD が来るまでは何度か落ちて retry で戻る -kubectl -n kube-system get svc cilium-gateway-doany # 80:80 / 443:443。違えば ArgoCD で bootstrap を Sync し直す kubectl get certificate -A # Gateway に証明書が付く ``` -ここが 80/443 でないと公開経路が全部死ぬ([../bootstrap/cilium/values.yaml](../bootstrap/cilium/values.yaml) の `nodePort`)。 +Gateway の Service の nodePort はランダムな値のままでよい(2026-10-18 まではここで 80/443 に揃えていた。 +[../bootstrap/cilium/values.yaml](../bootstrap/cilium/values.yaml) の `nodePort`)。 Argo CD はこの間に `apps/` と各リポジトリの `deploy/` を同期しはじめる。**中身はまだ空**なので、 Infisical の Secret を待つアプリと、Forgejo から来る非公開アプリ(`repos-forgejo` が `ErrorOccurred`)は上がらない。想定どおり。 diff --git a/talos/patches/apiserver.yaml b/talos/patches/apiserver.yaml index eff9d82..aacff19 100644 --- a/talos/patches/apiserver.yaml +++ b/talos/patches/apiserver.yaml @@ -13,12 +13,7 @@ kind: KubeAPIServerConfig # Talos はマシンのアドレス・ホスト名・Kubernetes の名前は自動で入れるが、公開名は入らない。 certExtraSANs: - ks.doany.io -extraArgs: - # Gateway の Service は nodePort 80/443 を持っていて、そこが公開経路そのもの - # (Cilium の L7LB リダイレクト。bootstrap/cilium/values.yaml の `nodePort` に実測あり)。 - # 既定の 30000-32767 のままだと 80/443 を Service に入れられず、復元後の - # 当て直しが `provided port is not in the valid range` で弾かれる。 - service-node-port-range: 80-32767 +# `service-node-port-range` は既定に戻した(2026-10-18。bootstrap/cilium/values.yaml の `nodePort`)。 --- apiVersion: v1alpha1 kind: KubeAuthenticationConfig From b399b72567441d57280ca4a9f84e2a7c6d173f00 Mon Sep 17 00:00:00 2001 From: Ruk Doe Date: Sun, 18 Oct 2026 12:21:16 +0900 Subject: [PATCH 2/2] =?UTF-8?q?=E5=8F=82=E7=85=A7=E5=88=87=E3=82=8C?= =?UTF-8?q?=E3=82=92=E7=9B=B4=E3=81=99(k3s=20=E3=81=AE=E9=A0=83=E3=81=AE?= =?UTF-8?q?=E5=89=AF=E4=BD=9C=E7=94=A8=E3=81=AE=E8=AA=AC=E6=98=8E)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01JbmWyUB9jcBcYCnNyiGBSV --- apps/forgejo/node-access.yaml | 2 +- talos/patches/cluster.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/forgejo/node-access.yaml b/apps/forgejo/node-access.yaml index 18995d8..fe8bfd5 100644 --- a/apps/forgejo/node-access.yaml +++ b/apps/forgejo/node-access.yaml @@ -1,6 +1,6 @@ # ノード自身(containerd)が fj.doany.io に届くための口。 # -# **ホストからノードの 80/443 には繋がらない**(bootstrap/cilium/values.yaml の nodePort の説明の 3)。 +# **ホストからノードの 80/443 には繋がらなかった**(k3s の頃。bootstrap/cilium/values.yaml の `nodePort`。今も要るかは未確認)。 # 公開経路は Gateway なので、containerd が fj.doany.io のイメージを取ろうとすると # 接続がタイムアウトする(2026-09-15 に shadai で踏んだ)。レジストリはトークンの取得先(realm)も # `https://fj.doany.io/v2/token` を返すので、IP だけ差し替えるミラーでは足りない。 diff --git a/talos/patches/cluster.yaml b/talos/patches/cluster.yaml index cf83528..2917968 100644 --- a/talos/patches/cluster.yaml +++ b/talos/patches/cluster.yaml @@ -96,7 +96,7 @@ nameservers: - address: 1.0.0.1 --- # ノード(containerd)から fj.doany.io のイメージを取るため、名前を中継の ClusterIP に向ける。 -# ノード自身からは Gateway の 443 に繋がらない(bootstrap/cilium/values.yaml の nodePort の 3)。 +# ノード自身からは Gateway の 443 に繋がらなかった(k3s の頃。bootstrap/cilium/values.yaml の `nodePort`。今も要るかは未確認)。 # 中継は apps/forgejo/node-access.yaml(2026-09-15) apiVersion: v1alpha1 kind: StaticHostConfig