From 8ac84b766c4501c2615a228c9caa7496fdcfc6de Mon Sep 17 00:00:00 2001 From: Ruk Doe <16yuim@gmail.com> Date: Wed, 16 Sep 2026 10:35:34 +0900 Subject: [PATCH 1/2] =?UTF-8?q?talos:=20=E5=BD=93=E3=81=A6=E3=81=9F?= =?UTF-8?q?=E3=81=82=E3=81=A8=E3=81=AE=E3=82=BA=E3=83=AC=E3=82=92=E8=A6=8B?= =?UTF-8?q?=E3=82=8B=20drift.sh?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git から作る machine config と、ノードが持っているもの (talosctl get machineconfig) を 並べて diff を出す。読むだけで当てない (適用は再起動を伴うことがあり、Talos API は mTLS だけなので CI には渡さない)。鍵・証明書は両方から落としてから比べる。 移行後にホストから手で回す、または cron に置く用 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01JgGYf5qbjDXYhtzcpLsGvv --- talos/README.md | 10 +++++++ talos/drift.sh | 71 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100755 talos/drift.sh diff --git a/talos/README.md b/talos/README.md index 23fa9c1..03d4d4a 100644 --- a/talos/README.md +++ b/talos/README.md @@ -22,6 +22,16 @@ reconcile するので、古い machine config のまま流すと**走ってい CI([talos-validate](../.github/workflows/talos-validate.yml))も同じ `render.sh` を使う。 **別の作り方をすると「CI は通るが当日は通らない」が起きる**ので、道具は 1 つにしてある。 +**当てたあとのズレは [drift.sh](drift.sh) で見る。** git から作った machine config と、 +ノードが実際に持っているもの(`talosctl get machineconfig`)を並べて diff を出す。読むだけで、 +当てはしない ── 適用は再起動を伴うことがあり、Talos API は mTLS だけなので CI には渡さない +(talos-validate と同じ「検出は自動、適用は手動」)。鍵と証明書は両方から落として比べる。 + +```shell +./talos/drift.sh # talosconfig の既定のノード +./talos/drift.sh 10.0.0.2 # ノードを指定。一致 0 / ズレ 1 +``` + **秘密は 2 つとも作ってコミット済み**(2026-09-08)── [secrets.yaml](secrets.yaml) は クラスタの CA 一式、[registries.yaml](registries.yaml) は fj.doany.io のイメージ取得の資格情報。 **`secrets.yaml` を作り直すと別のクラスタになる**ので触らない。作り方は下記。 diff --git a/talos/drift.sh b/talos/drift.sh new file mode 100755 index 0000000..b977940 --- /dev/null +++ b/talos/drift.sh @@ -0,0 +1,71 @@ +#!/bin/sh +# **動いている machine config と、このリポジトリから作る machine config を比べる。読むだけ。** +# +# Talos では k3s の頃と違い、CNI も storage も machine config の inlineManifests に載る +# (render.sh)。つまり**ズレは git とノードの間で起きる** ── `talosctl apply-config` を +# 手で打ち忘れる、実機で `talosctl patch` して git に戻し忘れる、のどちらでも。 +# +# **当てない。** machine config の適用は再起動を伴うことがあり、1 ノードでは即断になる。 +# Talos API は mTLS だけで OIDC を受けないので、CI にも渡さない +# (.github/workflows/talos-validate.yml の「検出は自動、適用は手動」と同じ線)。 +# これは**ホストから手で回す道具**で、ノードの os:admin 証明書を持っている人だけが使える。 +# +# ./talos/drift.sh # talosconfig の既定のノード +# ./talos/drift.sh 10.0.0.2 # ノードを指定 +# +# 出力は diff。**何も出なければ一致**。終了コードは一致 0 / ズレ 1(cron に置ける)。 +set -eu + +NODE=${1:-} +ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +cd "$ROOT" + +command -v talosctl >/dev/null || { echo "talosctl がない" >&2; exit 2; } +command -v yq >/dev/null || { echo "yq がない" >&2; exit 2; } + +WORK=$(mktemp -d) +trap 'rm -rf "$WORK"' EXIT INT TERM + +# 1. git から作る。**本番と同じ道具で**(render.sh は Cilium と local-path を描いてから組む) +./talos/render.sh "$WORK/rendered" >/dev/null + +# 2. ノードから取る。`get machineconfig` は資源の器に入って返るので中身(.spec)だけ出す +# shellcheck disable=SC2086 +talosctl ${NODE:+-n "$NODE"} get machineconfig v1alpha1 -o yaml \ + | yq -e '.spec' > "$WORK/live.yaml" + +# 3. 秘密と、比べても意味のないものを両方から落としてから比べる。 +# +# **鍵と証明書は落とす** — ノードのものが正で、git の側は render.sh が +# talos/secrets.yaml から毎回描くため、同じでも表現が揺れる。ここで見たいのは +# 「設定がズレていないか」であって鍵の一致ではない。 +# **version は落とす** — 資源の版はノード側にしか無い。 +strip() { + yq -P ' + del(.machine.token, .machine.ca, .machine.certSANs, + .cluster.id, .cluster.secret, .cluster.token, .cluster.ca, + .cluster.aggregatorCA, .cluster.serviceAccount, .cluster.secretboxEncryptionSecret, + .cluster.etcd.ca, .cluster.apiServer.admissionControl, + .machine.registries.config.*.auth) | + sort_keys(..) + ' "$1" +} + +strip "$WORK/rendered/controlplane.yaml" > "$WORK/a.yaml" +strip "$WORK/live.yaml" > "$WORK/b.yaml" + +if diff -u --label "git (talos/render.sh)" "$WORK/a.yaml" --label "node" "$WORK/b.yaml"; then + echo "一致(ズレなし)" + exit 0 +fi + +cat >&2 <<'MSG' + +ズレている。当てるなら(再起動が要ることがある): + + ./talos/render.sh /tmp/talos-config + talosctl apply-config -n -f /tmp/talos-config/controlplane.yaml + +逆にノード側が正なら、patches/ を直して git に戻すこと。 +MSG +exit 1 From 56a13671ca1bac2b8174fed3baa3841b94e45ff2 Mon Sep 17 00:00:00 2001 From: Ruk Doe <16yuim@gmail.com> Date: Wed, 16 Sep 2026 11:11:41 +0900 Subject: [PATCH 2/2] =?UTF-8?q?drift.sh:=20yq=20=E3=82=92=E3=82=84?= =?UTF-8?q?=E3=82=81=E3=81=A6=20python3=20+=20PyYAML=20=E3=81=A7=E8=AA=AD?= =?UTF-8?q?=E3=82=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit サーバに入っている yq は v3 (jq 構文) で、書いていた v4 の式は動かない (render.sh が awk で組んでいるのと同じ理由)。レビューの指摘 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01JgGYf5qbjDXYhtzcpLsGvv --- talos/drift.sh | 95 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 21 deletions(-) diff --git a/talos/drift.sh b/talos/drift.sh index b977940..5761321 100755 --- a/talos/drift.sh +++ b/talos/drift.sh @@ -14,6 +14,10 @@ # ./talos/drift.sh 10.0.0.2 # ノードを指定 # # 出力は diff。**何も出なければ一致**。終了コードは一致 0 / ズレ 1(cron に置ける)。 +# +# **`yq` は使わない。** サーバに入っているのは v3(jq 構文)で、v4 の式は動かない +# ── render.sh が awk で組んでいるのと同じ理由。ここは python3 + PyYAML(どちらも +# サーバにある)で読む。 set -eu NODE=${1:-} @@ -21,7 +25,8 @@ ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) cd "$ROOT" command -v talosctl >/dev/null || { echo "talosctl がない" >&2; exit 2; } -command -v yq >/dev/null || { echo "yq がない" >&2; exit 2; } +command -v python3 >/dev/null || { echo "python3 がない" >&2; exit 2; } +python3 -c 'import yaml' 2>/dev/null || { echo "python3 の PyYAML がない" >&2; exit 2; } WORK=$(mktemp -d) trap 'rm -rf "$WORK"' EXIT INT TERM @@ -29,30 +34,78 @@ trap 'rm -rf "$WORK"' EXIT INT TERM # 1. git から作る。**本番と同じ道具で**(render.sh は Cilium と local-path を描いてから組む) ./talos/render.sh "$WORK/rendered" >/dev/null -# 2. ノードから取る。`get machineconfig` は資源の器に入って返るので中身(.spec)だけ出す +# 2. ノードから取る。`get machineconfig` は資源の器に入って返るので、中身だけ取り出す # shellcheck disable=SC2086 -talosctl ${NODE:+-n "$NODE"} get machineconfig v1alpha1 -o yaml \ - | yq -e '.spec' > "$WORK/live.yaml" +talosctl ${NODE:+-n "$NODE"} get machineconfig v1alpha1 -o yaml > "$WORK/live-raw.yaml" -# 3. 秘密と、比べても意味のないものを両方から落としてから比べる。 +# 3. 秘密と、比べても意味のないものを両方から落としてから並べる。 # # **鍵と証明書は落とす** — ノードのものが正で、git の側は render.sh が -# talos/secrets.yaml から毎回描くため、同じでも表現が揺れる。ここで見たいのは -# 「設定がズレていないか」であって鍵の一致ではない。 -# **version は落とす** — 資源の版はノード側にしか無い。 -strip() { - yq -P ' - del(.machine.token, .machine.ca, .machine.certSANs, - .cluster.id, .cluster.secret, .cluster.token, .cluster.ca, - .cluster.aggregatorCA, .cluster.serviceAccount, .cluster.secretboxEncryptionSecret, - .cluster.etcd.ca, .cluster.apiServer.admissionControl, - .machine.registries.config.*.auth) | - sort_keys(..) - ' "$1" -} - -strip "$WORK/rendered/controlplane.yaml" > "$WORK/a.yaml" -strip "$WORK/live.yaml" > "$WORK/b.yaml" +# talos/secrets.yaml から毎回描く。ここで見たいのは「設定がズレていないか」であって +# 鍵の一致ではない。**資源の器(metadata・version)も落とす** — ノード側にしか無い。 +cat > "$WORK/norm.py" <<'PY' +import sys + +import yaml + +# 落とす場所。("a", "b") は a.b、"*" はその階層の全部 +DROP = [ + ("machine", "token"), + ("machine", "ca"), + ("machine", "certSANs"), + ("machine", "registries", "config", "*", "auth"), + ("cluster", "id"), + ("cluster", "secret"), + ("cluster", "token"), + ("cluster", "ca"), + ("cluster", "aggregatorCA"), + ("cluster", "serviceAccount"), + ("cluster", "secretboxEncryptionSecret"), + ("cluster", "etcd", "ca"), +] + + +def drop(node, path): + if not isinstance(node, dict): + return + head, rest = path[0], path[1:] + keys = list(node) if head == "*" else [head] + for key in keys: + if key not in node: + continue + if rest: + drop(node[key], rest) + else: + del node[key] + + +def machine_config(doc): + """`talosctl get machineconfig` の器から中身を出す。素の machine config はそのまま""" + if not isinstance(doc, dict): + return None + spec = doc.get("spec") + if spec is None: + return doc if "machine" in doc else None + # 版によって spec が YAML 文字列のことがある + return yaml.safe_load(spec) if isinstance(spec, str) else spec + + +config = None +for doc in yaml.safe_load_all(sys.stdin): + found = machine_config(doc) + if found and "machine" in found: + config = found + break +if config is None: + sys.exit("machine config を読めなかった") + +for path in DROP: + drop(config, path) +yaml.safe_dump(config, sys.stdout, sort_keys=True, allow_unicode=True, default_flow_style=False) +PY + +python3 "$WORK/norm.py" < "$WORK/rendered/controlplane.yaml" > "$WORK/a.yaml" +python3 "$WORK/norm.py" < "$WORK/live-raw.yaml" > "$WORK/b.yaml" if diff -u --label "git (talos/render.sh)" "$WORK/a.yaml" --label "node" "$WORK/b.yaml"; then echo "一致(ズレなし)"