対象: HP ProLiant DL360 Gen9(UEFI、iLO は 10.0.0.3、Xeon E5-2696 v4、NIC は BCM5719 ×4、ディスクは SAS の SSD 447 GB)。 検証用に Hyper-V でも同じ ISO を使う。VM ブートドリルは 2026-09-06 に v1.14.0 でやった。 いま使う版は talos/versions.yaml が正(2026-10-13 時点で v1.15.0-alpha.1)。下の URL の版もそれに合わせる。
Talos は「素の ISO」ではなく、必要な system extension を焼き込んだ ISO を https://factory.talos.dev で作る。 組み合わせは schematic という YAML で表し、その内容のハッシュが schematic ID になる(同じ YAML なら誰が作っても同じ ID)。
この箱に要る extension は intel-ucode と dvb-pt3(BCM5719 の tg3、bonding、AHCI は Talos のカーネルに入っている)。
dvb-pt3 は PT3 チューナーのドライバで、v1.15.0-alpha.1 からある(decisions.md「PT3 チューナー」)。
PT3 のために本番を alpha に乗せている。
カーネル引数もここに入れる。 v1.14 では machine.install が UnattendedInstallConfig と衝突して machine config 側に
書けないため、IOMMU の引数は schematic に持たせる。
# schematic.yaml (本番用)
customization:
extraKernelArgs:
- intel_iommu=on # 元は PT3 を KubeVirt に渡すため。dvb-pt3 が入ったいまは要らないが、パススルーの道として残す
- iommu=pt
systemExtensions:
officialExtensions:
- siderolabs/dvb-pt3
- siderolabs/intel-ucodecurl -X POST --data-binary @schematic.yaml https://factory.talos.dev/schematics
# => {"id":"2d61dd07b20062062ea671b4d01873506103b67c0f7a4c3fb6cf4ee85585dcb8"}実際に発行した ID は 3 つある。
| 用途 | schematic ID |
|---|---|
| 本番(dvb-pt3 + intel-ucode + IOMMU の引数。2026-10-13 から) | 96371c8f956f7f5f9d09925bb49fe096c0cad05c5cf5bdb464790982c0b067ca |
| 旧本番(intel-ucode + IOMMU の引数。PT3 なし) | 32820716ca2384dc3cefbb672e6be929c67636e93e556d7740c312efb6538302 |
| 検証用(intel-ucode のみ) | 2d61dd07b20062062ea671b4d01873506103b67c0f7a4c3fb6cf4ee85585dcb8 |
Web UI(factory.talos.dev → Bare-metal Machine → amd64 → version → extensions)でも同じ ID が出る。
| 用途 | URL |
|---|---|
| ISO(通常、Secure Boot オフ) | https://factory.talos.dev/image/96371c8f956f7f5f9d09925bb49fe096c0cad05c5cf5bdb464790982c0b067ca/v1.15.0-alpha.1/metal-amd64.iso |
| ISO(Secure Boot 用、Sidero の鍵で署名。UEFI に鍵登録が要るので今回は使わない) | 同じ URL で metal-amd64-secureboot.iso |
--install-image と talosctl upgrade --image |
factory.talos.dev/installer/96371c8f956f7f5f9d09925bb49fe096c0cad05c5cf5bdb464790982c0b067ca:v1.15.0-alpha.1 |
プレリリース(alpha / beta)も同じ形の URL で取れる。 版のところを差し替えるだけ。Web UI では既定で隠れているので、 pre-release を表示するチェックを入れる。2026-10-13 に v1.15.0-alpha.1 が出た当日、 上の本番 schematic で ISO(約 409 MB)と installer が 200 を返すのを確かめた。 ノード 1 台の本番を alpha に乗せるのは荒いが、PT3 のドライバがこの版からしか無いので割り切っている。 1.15.0 の正式版が出たら Renovate の PR で乗り換える。
ISO は約 400 MB。extension を足したくなったら schematic を作り直して ID を差し替え、talosctl upgrade で当てる(ISO を焼き直す必要はない)。
本番は USB でやる(2026-09-09 の判断。iLO の Virtual Media は USB を作らなくて済むが、 挙動が怪しいことがあったため)。ISO はハイブリッドなので、そのまま書けばよい。
# Linux から(USB が /dev/sdX として見えている前提)
sudo dd if=metal-amd64.iso of=/dev/sdX bs=4M status=progress oflag=syncWindows なら Rufus で ISO を選び、書き込みモードを聞かれたら DD イメージモードを選ぶ。balenaEtcher でもよい。 WSL からは USB が見えないので(usbipd で渡す必要がある)Windows 側でやる方が早い。
起動のしかた:
- Secure Boot を Disabled に(POST 中に F9 → RBSU → Server Security → Secure Boot Settings)。 通常 ISO は署名されていない。Boot Mode は UEFI のまま。
- USB を挿して電源を入れ、POST 中に **F11(Boot Menu)**でその USB を選ぶ。
- インストールが終わって再起動したら USB を抜く。 挿したままだとブート順によっては また maintenance mode で上がる。
コンソールは iLO の Remote Console(HTML5)でよい ── 実機の前に立たなくても ブートメニューも maintenance mode の IP も見える。USB だけ物理で挿す。
iLO の Virtual Media を使う場合: Remote Console → Virtual Drives → Image File CD-ROM/DVD で 手元の ISO を選び(上の factory の URL を直接貼ってもよい)、Power → Reset して F11 で "iLO Virtual USB 3 : iLO Virtual CD-ROM" を選ぶ。
Hyper-V(検証用): 第 2 世代 VM、Secure Boot オフ、メモリ 4 GB 以上、CPU 2 以上、ディスク 40 GB 以上、 DVD ドライブに ISO を接続、ネットワークは外部スイッチ(LAN の DHCP で IP が付く)。第 1 世代でも動くが UEFI にならないので実機と条件が変わる。
ISO から起動すると Talos は maintenance mode で立ち上がり、コンソールに IP が出る。まだ何もインストールされていない。
ここから先は手元の PC の talosctl で行う(ホストには一切ログインしない)。手順は
../talos/README.md(render.sh → apply-config → bootstrap → kubeconfig)、
当日の順番は migration-day.md。
バックアップからの復元なら bootstrap に --recover-from=etcd.snapshot を付ける(下の「etcd スナップショットからの復旧」)。
- PT3(Earthsoft、
earth_pt3)が Talos のカーネルに入っていない(CONFIG_DVB_PT3 is not set、extension も無い)。 denpa の tuner-agent は/dev/dvbと/dev/bus(B-CAS リーダー)を hostDevices で掴むので、今のままだと Talos 上では動かない。 算段は decisions.md「PT3 チューナー」。 - Secure Boot を切る(上記)。
- iLO の IP(10.0.0.3)と管理者パスワードを手元に。ISO 起動も再インストールも全部ここからできる。
2026-09-06 に QEMU/KVM で v1.14.0 を実際に起動して確かめたこと。1.13 以前の書き方は通らない。
talosctl gen config は v1alpha1 の 1 枚ではなく、KubeNetworkConfig / KubeletConfig /
UnattendedInstallConfig / ResolverConfig / KubeNodeConfig … という型付きドキュメントの束を吐く。
同じ項目を v1alpha1 側にも書くと apply が弾かれる(... is already set in v1alpha1 config)。
| やりたいこと | 1.14 での書き場所 |
|---|---|
| pod / service の CIDR | KubeNetworkConfig の podSubnets / serviceSubnets |
| インストール先ディスク | UnattendedInstallConfig の provisioning.diskSelector.match(CEL 式。例 disk.dev_path == "/dev/sda") |
| カーネル引数 | machine.install.extraKernelArgs は使えない(同じく衝突)。Image Factory の schematic の customization.extraKernelArgs に入れる |
| host DNS | ResolverConfig。既定で有効なので普通は書かなくてよい |
kubelet の extraMounts |
KubeletConfig には無い。KubeletConfig を $patch: delete してから v1alpha1 の machine.kubelet に書く |
| control-plane への scheduling 許可 | v1alpha1 の allowSchedulingOnControlPlanes は弾かれる。KubeNodeConfig の taints を消す必要があるが、strategic merge の空マップ(taints: {})では消えなかった。talhelper 経由か、起動後に kubectl taint nodes --all node-role.kubernetes.io/control-plane- |
fd43::/64 は service subnets: invalid subnet: fd43::/64 is too large, it must be at least /108 で弾かれる。
k3s はこれを許していたので、移行時に fd43::/108 へ変更が要る(ClusterIP が振り直しになる)。
pod 側の fd42::/64 はそのままでよい。検証では 10.43.0.10 と fd43::a の両方が付いた。
KubeAdmissionControlConfig に enforce: baseline(例外は kube-system のみ)が入っている。k3s には無かった制限で、
hostPath / privileged / hostNetwork を使う workload は namespace にラベルが要る。
kubectl label ns <ns> pod-security.kubernetes.io/enforce=privileged実際に local-path-provisioner はこれで詰まった(ヘルパー Pod が hostPath を使うため PVC が Pending のまま)。
ラベルを付けたら PVC が Bound になり、Pod から書いた内容がディスクに残ることまで確認した。
baseline は hostPort も弾く。 ここを見落としやすい。走っている Pod を全部数えて(2026-09-07、09-08 に数え直し)、
ラベルが要る namespace は次のとおり。どれも namespace.yaml に enforce: privileged が入っていて、実機でも確認した:
| namespace | baseline に通らない理由 |
|---|---|
kube-system |
Cilium(privileged・hostNetwork・hostPath・SYS_ADMIN/NET_ADMIN・hostPort)。Talos の側で例外にしてある(下の設定) |
local-path-storage |
ヘルパー Pod の hostPath。talos/manifests/local-path.yaml がラベルを付ける |
netbird |
hostPort 3478(内蔵 STUN。UDP なのでゲートウェイを通せない)・51822、NET_ADMIN 等、hostPath |
denpa |
privileged・hostPath(/dev/dvb・/dev/bus・/dev/dri)。ラベルは denpa リポジトリの deploy/namespace.yaml にあり、gitops だけを grep すると抜けて見える |
adguardhome |
hostPort 53 / 853 |
matrix |
hostPort 8443(LiveKit。2026-10-06 に Mattermost Calls から同じ番号を引き継いだ) |
3proxy |
hostPort 3129(TLS 終端サイドカー) |
cloudflare-ddns |
hostNetwork |
forgejo |
Runner の docker(dind)が privileged |
(当時は erpnext の CAP_CHOWN(baseline が足せる capability は NET_BIND_SERVICE だけ)と mattermost の hostPort 8443 もあった。どちらも 2026-10-06 に撤去)
残りの namespace は baseline のままでよい。argocd / cert-manager / infisical はラベル無しで上がった(ドリル 4 回目)。 洗い出しは Pod の spec を直接数えて出す:
kubectl get pods -A -o json | jq -r '.items[] | . as $p | [$p.metadata.namespace] +
(if $p.spec.hostNetwork then ["hostNetwork"] else [] end) +
($p.spec.containers[] | (.securityContext.privileged // false | if . then ["privileged"] else [] end) +
((.securityContext.capabilities.add // []) | map("cap:"+.)) +
((.ports // []) | map(select(.hostPort)) | map("hostPort:"+(.hostPort|tostring)))) +
(($p.spec.volumes // []) | map(select(.hostPath)) | map("hostPath")) | @tsv' | sort -ukube-system の例外は Talos の設定:
kind: KubeAdmissionControlConfig
name: PodSecurity
configuration:
defaults: {enforce: baseline, audit: restricted, warn: restricted}
exemptions:
namespaces: [kube-system]- ISO(Image Factory の schematic
2d61dd07…)から UEFI で起動、maintenance mode の API はポート 50000 apply-config→ 自動でディスクへインストール → 再起動 →bootstrap→ kubeconfig 取得cluster.inlineManifestsは期待どおり適用された(仕込んだ Namespace がラベルごと存在した)。bootstrap/をここに載せる案(decisions.md)は成立する- flannel でデュアルスタック、CoreDNS / kube-proxy / scheduler すべて Running
Talos 期のバックアップは etcd(k8s オブジェクト)と PV データで別経路になる。両方を試した。
talosctl etcd snapshot ./etcd.snapshot # 1.8 MB / 394 keys (検証時)復旧は、同じ secrets.yaml で machine config を作り直すのが肝(PKI が一致しないと復旧できない)。
# まっさらなディスクに ISO から入れ直したあと
talosctl apply-config --insecure -n <IP> -f controlplane.yaml
# 再起動を待って
talosctl bootstrap --recover-from=./etcd.snapshot2026-09-06 に空のディスクから実際に通した結果:
- Node が同じ名前・同じ作成時刻で Ready に戻り、Namespace・Deployment・PVC/PV の紐付け・ PSA のラベルまでスナップショット時点の状態が復元された
- PV の中身は戻らない。
/var/local-path-provisioner(当時のパス。いまは/var/mnt/local-path)は空のままだった。 etcd は「PVC がこの PV に紐づいている」という事実しか持っていない - したがって Talos 期の復元は etcd スナップショット → PV データを restic(k8up)から戻す の 2 段になる。 順序は etcd が先(PV オブジェクトが無いと戻す先が決まらない)
bootstrap --recover-from は etcd サービスが上がるまで bootstrap is not available yet を返すので、
数分待って再試行する。
どちらで戻すか(2026-10-05 に揃えた)。 主経路は git から建て直すほう
(docs/migration-day.md の 3 以降。ドリルで通してあり、スナップショットの鮮度に左右されない)。
etcd スナップショットは近道で、git に無い状態(Application の同期状態、発行済みの証明書、
API サーバが割り当てた nodePort など)まで一度に戻したいときに使う。日次で取っているのは
../talos/after-migration/etcd-snapshot.yaml
(移行後は apps/k8up/)で、restic の --host etcd。取り出しは:
restic snapshots --host etcd
restic dump <ID> /work/etcd.snapshot > etcd.snapshot本番サーバの上で回す。ホストのネットワークには触らない。 ドリル 4 回ぶんの つまずきをここにまとめてある(各回で何が分かったかは下の記録)。
「bond は user-mode ネットワークでは試せない」というのは誤りだった。-netdev hubport で
QEMU の中だけに L2 セグメントを作れるので、ホストに tap も bridge も作らずに 2 本の NIC を
同じセグメントに挿せる。SLIRP(-netdev user)は ipv6=on で RA を送ってくるので、
RA 由来のデフォルトルートもここで試せる。
# hub 1 = bond のメンバー 2 本 + SLIRP(IPv6 あり)、hub 2 = eno4 相当の別 LAN
sudo qemu-system-x86_64 -machine q35,accel=kvm -cpu host -smp 4 -m 12288 \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive if=pflash,format=raw,file=OVMF_VARS.fd \
-drive file=disk.raw,if=virtio,format=raw -boot order=c \
-netdev 'user,id=n0,ipv4=on,net=10.0.0.0/24,host=10.0.0.1,dhcpstart=10.0.0.15,ipv6=on,ipv6-net=240f:6d:842b:1::/64,ipv6-host=240f:6d:842b:1::1,hostfwd=tcp:127.0.0.1:50201-10.0.0.15:50000,hostfwd=tcp:127.0.0.1:50211-10.0.0.2:50000,hostfwd=tcp:127.0.0.1:50221-10.0.0.2:6443' \
-netdev hubport,id=hp0,hubid=1,netdev=n0 \
-netdev hubport,id=hp1,hubid=1 -device virtio-net-pci,netdev=hp1,mac=52:54:00:ee:00:01 \
-netdev hubport,id=hp2,hubid=1 -device virtio-net-pci,netdev=hp2,mac=52:54:00:ee:00:02 \
-netdev 'user,id=n1,ipv4=on,ipv6=off,net=10.10.0.0/24,host=10.10.0.1' \
-netdev hubport,id=hp3,hubid=2,netdev=n1 \
-netdev hubport,id=hp4,hubid=2 -device virtio-net-pci,netdev=hp4,mac=52:54:00:ee:00:05 \
-display none -serial file:serial.log -monitor unix:monitor.sock,server,nowaitKVM を使うので sudo で起動する。 patches/*.yaml からの読み替えは
インタフェース名とディスクだけ(eno1/eno2/eno4 → enp0s2/enp0s3/enp0s4、
/dev/sda → /dev/vda)。balance-alb・miimon: 100・静的 IPv6・
v6 のデフォルトルートを書かないことはそのまま。ボリュームの大きさだけディスクに合わせて縮める。
- ディスクは 140 GB 取る(sparse なので実消費は 4 GB ちょい)。 小さくすると
volumes.yamlの値を縮めることになり、maxSize: 64GiBが効くかを試したことにならない - ISO ではなく
metal-amd64.raw.zstを焼く。 ISO 経由だと「ISO で起動 →apply-config→ 再起動 → ISO 側がインストール → 再起動 → ディスク」という段取りに なり、外す時機を間違えると PXE ブートに落ちる。raw なら最初から maintenance mode で上がる。 実機は USB で ISO(上の「メディアに載せる」)── ここだけ実機と違う NETDEV WATCHDOG: transmit queue 0 timed outは QEMU 側のミス。 bond の 2 本目を 何も繋がっていないハブに挿していた。QEMU 自身がwarning: hub 3 is not connected to host networkと言うので、qemu.logを必ず読む。 ハブが正しければ NIC 3 本でも問題ない ── 2 回目の記録に「NIC は 1 本にする」と 書いていたが、原因はハブの挿し間違いのほうだった- インタフェース名は起動ごとに変わる。 maintenance mode で
talosctl get links --insecureを見てから patch を読み替えること - コンソールが要るときは monitor の
screendump。-display noneでもscreendump /path/foo.ppmで画面が撮れる。kubelet の版ズレはこれで見つけた (シリアルには Talos のダッシュボードが流れてこない)。socat/ncが無ければ python のsocket.AF_UNIXでmonitor.sockに直接書けばよい - 前の VM を殺し忘れると
hostfwdのポートを握ったままで、新しい VM が黙って死ぬ。pgrep -af qemu-system-x86_64で確認する。pkill -fのパターンはqemu-system-[x]86_64のように書く ── そうしないと自分の ssh セッションごと殺す(実際に踏んだ) ipv6=onを付けるとipv4=onを明示しないとIPv4 disabled but netmask/host/dns providedで起動しない- v1.14 の
talosctlは--nodes/--endpointsがグローバルフラグではない。talosctl --insecure -n … get linksはunknown commandになる。talosctl get links --insecure -n … -e …の順で書く - maintenance mode は
-e 127.0.0.1:50201 -n 127.0.0.1:50201。設定投入後は apid 経由なので-nはノードの実アドレス(-n 10.0.0.2 -e 127.0.0.1:50211)。-n 127.0.0.1:50211はinvalid targetになる talosctl apply-config -m rebootは無い(auto/no-reboot/staged/try)talosctl upgrade-k8sは k8s の API に直接繋ぎに行くので、SLIRP のときは--endpoint https://127.0.0.1:<hostfwd>を渡す。kubeconfigの書き換えだけでは足りない- ハブの構成は monitor の
info networkで確認できる
5 回目のあとに 7 本マージしたので、machine config に効くぶんを確かめ直した
(Entra を 2 つめの issuer にした bootstrap/apiserver/ と talos/patches/apiserver.yaml)。
今回はディスクを 140 GB にした。 3〜5 回目は VM のディスクが小さくて
talos/patches/volumes.yaml の値を縮めて焼いていたので、本番と同じ
maxSize: 64GiB / minSize: 50GiB のまま試したのはこれが初めて。
確かめたこと:
| apiserver が Entra の issuer を載せて起動する | --authentication-config=/system/config/kubernetes/kube-apiserver/authentication-config.yaml が渡り、ファイルに issuer が 2 本(github + login.microsoftonline.com)。anonymous の livez/readyz/healthz も残っている |
| groups の CEL | entra:admin / entra:viewer が両方描かれている |
| RBAC が inlineManifest から入る | entra-admin(→ cluster-admin)・entra-viewer(→ view)・entra-viewer-extra(→ readonly-extra)と ClusterRole readonly-extra |
EPHEMERAL |
ちょうど 64.0 GiB。maxSize が効く |
u-local-path |
72.0 GiB(残りに grow)、ready |
| control-plane の taint | 無し |
| Pod | 25 個が Running / Completed。そのあと ArgoCD が Application 16 本を復元しはじめる |
所要: apply-config → apid 復帰 75 秒 → bootstrap → ノード Ready 200 秒 → bootstrap 層が全部 Running まで 120 秒。
headlamp は secret "headlamp-oidc" not found で止まる。これは正常。 Infisical の DB が
空なので InfisicalSecret を使うアプリは Secret をもらえない。
migration-day.md の 7 が「先に Infisical の DB だけ戻す」と言っているとおりで、
当日は 8 の手順で 1 本だけ戻してから待つ。portainer の namespace が作られないことも確認した。
4 回目は ArgoCD の extraObjects を空にして app-of-apps を外していた。5 回目は
外さずに、本物のリポジトリ群を同期させた。 見たかったのは
「まっさらなクラスタで ArgoCD が apps/ を再構築しきるか」の一点。
移行当日に何も上がらない不具合が出た。 詳細は下。
復元リハーサル(Ubuntu の VM)では中で iptables を叩いていたが、 Talos の VM には入れない。 ホスト側で落とすと本番の通信まで巻き込む。
QEMU を専用ユーザーで動かして -m owner --uid-owner で落とす。
sudo useradd -r -M -s /usr/sbin/nologin -G kvm qemudrill
for ip in <api.cloudflare.com 6 つ> <acme> <本番の公開 IP> <R2 2 つ>; do
sudo iptables -I OUTPUT 1 -m owner --uid-owner qemudrill -d $ip -j REJECT
done
for p in 587 465 25; do # SMTP は REJECT。DROP だと Infisical が起動しない
sudo iptables -I OUTPUT 1 -m owner --uid-owner qemudrill -p tcp --dport $p -j REJECT --reject-with tcp-reset
done
sudo ip6tables -I OUTPUT 1 -m owner --uid-owner qemudrill -d 2000::/3 -j REJECT止めたのは cloudflare-ddns(本番の DNS を書き換える)・netbird(本番に繋ぐ)・ k8up(本番の restic リポジトリに書く)・ACME・SMTP。 イメージの取得(ghcr.io / docker.io など)は通るので同期は進む。
sudo -u qemudrillは通らない(sudoers が root 宛てだけ NOPASSWD)。sudo runuser -u qemudrill -- <script>を使う- 起動は script にする。
nohup … > logのリダイレクトは呼び出し側の権限で 開かれるので、qemudrill が所有するディレクトリには書けない /var/tmp/drillを qemudrill 所有にするとkubeconfigを書き出せない。 出力先は別の場所にする
gitops OutOfSync Missing
one or more synchronization tasks are not valid: failed to discover server
resources for group version secrets.infisical.com/v1alpha1: the server could
not find the requested resource (retried 5 times)
InfisicalSecret の CRD がまだ無い。 ArgoCD は同期の前に dry-run するので、
CRD が引けないとその Application が丸ごと落ちる ── そして
CRD を入れる当の Application(apps/infisical-operator/)が同じ同期の中にいるので
抜けられない。
k8up の Schedule で 2026-09-07 に踏んだのと同じ形なのに、
InfisicalSecret には SkipDryRunOnMissingResource=true が付いていなかった。
復元リハーサルでは出ない ── あちらは etcd から戻すので CRD が最初からある。
まっさらから組む Talos の移行でだけ出る。
直したら通った(同じドリルの中で確認):
CRD infisical 系 0 → 7
Pod のある namespace 5 → 18
erpnext 15 / netbird 5 / cloudflare-ddns 4 / mattermost 2 / adguardhome 2 /
infisical-operator / infisical-push-bridge / k8up / headlamp / 3proxy …
CRD が入っても、先に 5 回失敗していた Application は
(retried 5 times) のまま止まったままだった。refresh=hard の注釈では動かず、
明示的に sync を投げたら Synced / Healthy になった。
移行当日は、gitops の同期が通ったあとに、止まっているアプリを 1 回ずつ叩く。 migration-day.md の 7 に入れた。
3 回目までは Cilium・local-path・metrics-server だけだった。4 回目は
ArgoCD・cert-manager・infisical と SOPS 済みの Secret を入れた machine configを
そのまま焼いて、apply-config 1 回でどこまで立ち上がるかを見た。
立ち上がった:
main Ready control-plane v1.36.2
argocd argocd-application-controller-0 / applicationset / notifications /
redis / repo-server / server すべて Running
cert-manager cert-manager / cainjector / webhook Running(startupapicheck は Completed)
infisical infisical / postgresql-0 / redis-master-0 Running、/api/status が 200
kube-system cilium / envoy / operator / coredns ×2 / hubble ×2 / metrics-server
local-path-storage local-path-provisioner
EPHEMERAL partition ready /dev/vda5 13 GB
u-local-path partition ready /dev/vda6 11 GB
ドリル専用の細工は 1 つだけ ── ArgoCD の extraObjects を空にして
app-of-apps を外した。本物のリポジトリを同期しに行くと cloudflare-ddns が
DNS を書き換え、netbird が本番に繋ぎに行く(「副作用を出さないための遮断」と
同じ理由)。それ以外は本番と同じ値。
infisical の Deployment と Service が default namespace に落ちた。
$ kubectl get deploy -A | grep infisical
default deployment.apps/infisical 0/1 0 0 4m15s ← ここ
infisical (無い)
helm template -n infisical は .Release.Namespace を渡すが、
chart 側がそれを metadata.namespace に書かないことがある(bitnami の subchart は
書いていた。infisical 本体のテンプレートだけが書いていなかった)。helm や kubectl なら
-n で決まるが、Talos のマニフェスト適用には既定の namespace が無い。
infisical-secrets は infisical namespace にあるので、default に落ちた Pod は
永久に起動しない。 移行当日は Infisical が上がらず、そこから全アプリの秘密が
配られないので、何も動かないところだった。
直し方は talos/add-namespace.awk ── helm の描き出しを
通して、metadata.namespace が無い名前空間付きリソースに補う。クラスタスコープのもの
(Namespace / ClusterRole / CRD など)には足さない(足すと API が弾く)。
全部の chart の描き出しに通してある。
直した machine config を apply-config してから talosctl upgrade-k8s を流すと、
infisical namespace に Deployment が作られた(default の古いものは残る。
まっさらな導入なら存在しない)。../talos/README.md「上げ方 /
当て直し方」に書いた手順が、実際にそのとおり効くことの裏付け。
- infisical はメール無しでも上がる。 起動時に
Testing smtp connectionで 止まって見えるが、infisical-smtp(Infisical operator が作る Secret)が 無くても最終的に1/1 Runningになり/api/statusが 200 を返す。 鶏卵にはならないことの確認になった - 起動直後に
CreateContainerConfigError: failed to sync secret cacheが数回出るが、 optional な Secret の同期待ちで、放っておくと直る
1・2 回目は talos/patches/ だけを起動した。3 回目は talos/render.sh が
描いた machine config を丸ごと焼いて、Cilium・local-path・metrics-server まで上がるところを見た。
移行当日に踏むはずだった穴が 3 つ出た。
コンソールに出ていたのはこれ:
KUBERNETES v1.37.0 KUBELET x Unhealthy
patches/cluster.yaml が KubeletConfig をドキュメントごと消していて、
--kubernetes-version が入れていた kubelet のイメージも一緒に消えていた。
生成物には kubelet のイメージが 1 行も無く、Talos の既定で上がる。
== --kubernetes-version v1.36.2 あり: kubelet:v1.36.2 / kube-apiserver:v1.36.2
== 無し(既定): kubelet:v1.37.0 / kube-apiserver:v1.37.0
kubelet が API サーバより新しいのは Kubernetes のサポート外。 CI は
kube-apiserver:<版> しか見ておらず、「kubelet が無い」ことに気づけなかったので、
needle を足した(#114)。その後 user volume を入れて KubeletConfig を消す必要が
なくなったので、いまはこの穴自体が塞がっている。needle は再発の見張りとして残してある。
KubeNodeConfig は既定で node-role.kubernetes.io/control-plane: NoSchedule を入れる。
ノードが 1 台のこのクラスタでは、toleration を持たない Pod が全部 Pending になる
(k3s は単一ノードを control-plane 兼 worker として扱うので、この問題が無かった)。
質が悪いのは、ノードが Ready に見えること。 cilium と CoreDNS は toleration を
持っているので上がる:
kube-system cilium-l4xvm 1/1 Running
kube-system coredns-6cb54fb45c-9k5sl 1/1 Running
kube-system metrics-server-5ffb6b8554-drtss 0/1 Pending
local-path-storage local-path-provisioner-75f8fcb898-j9hqb 0/1 Pending
Warning FailedScheduling default-scheduler no nodes available to schedule pods
taints はマップなので taints: {} では消えない。中に $patch: replace を書くと
キーとして残ってしまい name "$patch" is invalid で validate が落ちる。
KubeletConfig / HostnameConfig と同じくドキュメントごと消して labels だけ
書き戻すのが唯一の手(#115)。
EPHEMERAL partition ready /dev/vda5 24 GB ← 26 GB のディスクほぼ全部
ETCD directory ready ← その中
CRI directory ready ← その中
etcd もコンテナイメージも PV データも同じ 1 枚。local-path は PVC の容量を強制しないので、 1 個の PVC が暴走すると etcd ごと道連れになる。ボリュームの設定は「まだ確保されていない ときにしか効かない」ので、直せるのは入れ直すときだけ。
VolumeConfig(EPHEMERAL に maxSize)と UserVolumeConfig を足して割り直した。
システムボリュームはユーザーボリュームより先に確保されるので、EPHEMERAL に上限を
書き忘れると取り分が残らない(siderolabs/talos#12713)。
- user volume のマウント先は kubelet コンテナに自動で通る。
extraMountsのハックが 要らなくなり、上の 1 の原因ごと消えた UserVolumeConfigはmetadata.nameではなくトップレベルのname。名前は 1〜34 文字の英数字とハイフンで、ディスク上のラベルはu-<name>になる
Cilium のデータパスは k3s 期と同じ形で上がる。
KubeProxyReplacement: True [bond0 10.0.0.2 240f:6d:842b:1::2 (Direct Routing), enp0s4 10.10.0.4]
Cilium: Ok 1.20.1
Routing: Network: Tunnel [vxlan] Host: BPF
Masquerading: BPF [bond0, enp0s4] 10.42.0.0/24 fd42::/64 [IPv4: Enabled, IPv6: Enabled]
cgroup.hostRoot と KubePrism(localhost:7445)の上書き(talos/cilium-values.yaml)が
効いていることの裏付けでもある。
service-node-port-range: 80-32767 は実際に効く。 生成物を見るだけでなく、
クラスタで nodePort 80 の Service を作れることを確かめた
(Gateway の公開経路がここに乗っている。talos/patches/apiserver.yaml)。
$ kubectl create svc nodeport np --tcp=80:80 --node-port=80
np NodePort 10.43.251.48 <none> 80:80/TCP
dual-stack も出る。 fd43::/108 に変えた影響を確かめた:
$ kubectl -n kube-system get svc kube-dns -o jsonpath='{.spec.clusterIPs}'
["10.43.0.10","fd43::a"] ipFamilyPolicy: RequireDualStack
talosctl upgrade-k8s を通せば更新も削除もされる。 Sidero のドキュメントには
Talos only creates missing resources from inline manifests — it never deletes or updates them.
とある一方で「更新するには machine config を直して talosctl upgrade-k8s」とも書いてあり、
どちらが効くのか読んでも分からなかったので、QEMU で確かめた。前者は Talos 自身の
起動時の適用の話で、upgrade-k8s は別の経路だった。
プローブ用の ConfigMap を inlineManifests に載せ、値を BEFORE → AFTER に書き換えて試した。
| 操作 | 値の更新 | config から消したとき |
|---|---|---|
talosctl apply-config |
されない(BEFORE のまま) |
消えない |
talosctl upgrade-k8s --to <いまと同じ版> |
される(AFTER になる) |
オブジェクトも消える |
upgrade-k8s の出力は差分つきで、SSA とインベントリで完全な reconcile をしている。
< configured ConfigMap/default/drill-probe
- value: BEFORE
+ value: AFTER
< deleted ConfigMap/default/drill-probe # config から消したあと
フラグにも表れている ── --manifests-inventory-policy(既定 AdoptIfNoInventory)、
--manifests-no-prune、--manifests-force。prune は既定で有効。
inlineManifests + upgrade-k8s は、machine config だけで回せる本物の経路。
「初回だけ入れて以後は触れない」ではない。この実測を根拠に、Talos 期は
bootstrap/ をまるごと inlineManifests に載せることにした
(decisions.md「machine config と Cilium の chart をどう連動させるか」)。
当時の懸念(値が machine config に埋まってレビューできない)は、
render.sh が bootstrap/ の値から描く形にして消えている ── 読むのは
bootstrap/cilium/values.yaml のままで、machine config はその派生物。
1 回目はネットワークだけを見た。2 回目は API サーバの設定(patches/apiserver.yaml)を足して
bootstrap まで通し、コントロールプレーンが上がるところまで確かめた。
確かめられたこと:
KubeAuthenticationConfigを入れてもコントロールプレーンは上がる。configurationは既定を置き換えるのでanonymousを書き忘れるとヘルスチェックが 匿名で通らなくなる ── と書いていたが、明示して書けば正しく上がることを実地で確認した。kube-apiserver/kube-controller-manager/kube-schedulerの 3 つともCONTAINER_RUNNINGcertExtraSANsは効く。 6443 に繋いで証明書を見るとDNS:ks.doany.ioが入っている- ネットワークは 1 回目と同じ(bond0 に
10.0.0.2/24と静的 IPv6::2、enp0s4に10.10.0.4/24)
見つかった問題: versions.yaml の Kubernetes 版が使われていなかった。
生成物: kube-apiserver:v1.37.0 ← Talos v1.14.0 の既定
versions.yaml: kubernetes v1.36.2 ← 宣言しているだけ
talosctl gen config は versions.yaml を読まないので、--kubernetes-version で渡さないと
Talos の既定になる。移行当日に意図せず 1.36 → 1.37 の飛び級をするところだった。
README の手順と talos-validate のワークフローに --kubernetes-version を足し、
生成物に宣言どおりの版が入っているかも CI で見るようにした。
VM 側の読み替え(NIC 名は起動ごとに変わる、qemu は sudo)は上の「VM の組み方」。
talosctl validate が通るだけで一度も起動していなかった talos/patches/cluster.yaml /
main.yaml を、本番サーバ上の QEMU で実際に起動して確かめた。ホストのネットワークには一切触っていない。
talosctl read /proc/net/bonding/bond0 -n 10.0.0.2 -e 127.0.0.1:50011Bonding Mode: adaptive load balancing # = balance-alb
Currently Active Slave: enp0s3
MII Status: up
MII Polling Interval (ms): 100 # = miimon
Slave Interface: enp0s3 / MII Status: up / Link Failure Count: 0
Slave Interface: enp0s4 / MII Status: up / Link Failure Count: 0
monitor から片方の carrier を落とす(set_link virtio-net-pci.0 off)と 8 秒以内に
MII Status: down / Link Failure Count: 1 になり、Currently Active Slave が enp0s4 に移って
talosctl の応答は途切れなかった。balance-alb らしく メンバー間で MAC が入れ替わる
(get links で enp0s3 が …aa:00:02、enp0s4 が …aa:00:01 になる)のも見えた。
なお QEMU の set_link … on ではゲスト側の carrier が戻らないので、復旧方向は確かめられていない。
静的アドレスと SLAAC は素直に並ぶ。
bond0/10.0.0.2/24
bond0/240f:6d:842b:1::2/64 # patches に書いた静的アドレス
bond0/240f:6d:842b:1:9f48:ba23:fcbb:1cd0/64 # SLAAC
bond0/fe80::8628:e278:4977:732/64
問題は既定経路のほうで、Kubernetes が上がった瞬間に IPv6 の default route が消えた。
| 時点 | net.ipv6.conf.bond0.forwarding |
accept_ra |
protocol: ra の default route |
|---|---|---|---|
| maintenance mode | 0 | 1(既定) | あり |
| クラスタ起動後 | 1 | 1(既定) | 無い |
| クラスタ起動後 | 1 | 2 | あり |
accept_ra=1 は「forwarding が有効なら RA を無視する」という意味で、flannel / kube-proxy が
net.ipv6.conf.all.forwarding=1 にした時点でカーネルが RA 由来の経路を落とす。
Ubuntu 期にこれが問題にならなかったのは NetworkManager が userspace で RA を処理していたからで、
実機は現在 bond0/accept_ra = 0 のまま default via fe80::… proto ra metric 300 が載っている。
Talos には RA を代行するものが無いので、カーネルに任せる = accept_ra: "2" が必須。
patches/cluster.yaml に足した:
net.ipv6.conf.bond0.accept_ra: "2"入れたあとの確認(クラスタが Ready になってから見ること):
talosctl get routes -o yaml -n 10.0.0.2 -e … | grep -B10 'protocol: ra'
# id: bond0/inet6/fe80::2//1024
# dst: ""
# gateway: fe80::2
# outLinkName: bond0
# protocol: ra同時に見つかった。net.ipv6.conf.bond0.addr_gen_mode: "2"(stable-privacy)は
stable_secret が未設定だとカーネルが EINVAL を返すので、一度も適用されない。
talosctl get addresses は EUI-64 のまま(240f:6d:842b:1:5054:ff:feaa:1)で、
KernelParamSpecController が数秒おきにこれを吐き続ける:
ERROR controller failed {"controller": "runtime.KernelParamSpecController",
"error": "write /proc/sys/net/ipv6/conf/bond0/addr_gen_mode: invalid argument"}
"3"(random)に変えたら通った。 書き込みが成功し、link-local も SLAAC も MAC 由来でなくなる
(fe80::8628:e278:4977:732 / 240f:6d:842b:1:9f48:ba23:fcbb:1cd0)。
実機の NetworkManager も同じ見た目(240f:6d:842b:1:a096:1754:8738:2ede)なので状態としては揃う。
違いは 3 は再起動ごとに IID が変わること。ブート間で固定したければ stable_secret を足して 2 に戻す
(cloudflare-ddns の local.iface.stable:bond0 が ::2 ではなく SLAAC 側を拾っていた場合はそれが要る)。
/proc/modules にも /lib/modules/…/kernel/ にも wireguard は出てこない。=y で組み込まれているからで、
talosctl read /sys/module/wireguard/version -n 10.0.0.2 -e … # => 1.0.0実際に privileged + hostNetwork の Pod(namespace に pod-security.kubernetes.io/enforce=privileged)から
wg-quick up wg0 を通した(ip link add dev wg0 type wireguard が通り、UDP 51820 を v4 / v6 で LISTEN)。
ホスト側の talosctl get links にも wg0 … KIND wireguard が現れる。
Ubuntu で必要だった AppArmor プロファイルの無効化は Talos には存在しない(そもそも LSM が SELinux)。
UnattendedInstallConfigの CEL diskSelector で実際にインストールされた(EPHEMERALが/dev/vda4)- schematic のカーネル引数が載る:
/proc/cmdlineにintel_iommu=on iommu=pt patches/main.yamlのvfio_pci/vfio_iommu_type1が/proc/modulesに出るnet.ipv6.conf.eno4.disable_ipv6: "1"は効く(相当インタフェースに v6 アドレスが一切付かない)bond0は Talos が作ったあとに sysctl が適用される(存在しないパスなら ENOENT のはずが EINVAL だった)
読み替えたぶんと、SLIRP が本物でないぶんは未検証のまま:
- インタフェース名は
enp0s3/enp0s4/enp0s5。実機はeno1/eno2/eno4(tg3)。 名前が違えばmachine.sysctlsのキーもinterfacesも当たらないので、実機ではtalosctl get linksで名前を確認してから流すこと - ディスクは
/dev/vdaで試した。実機は/dev/sda(talosctl get disksで確定させる) - 相手が SLIRPなので、balance-alb の ARP ネゴシエーションを本物のスイッチ相手に試したことにはならない。 ハブなので送ったフレームが相方の NIC にも返ってくるが、それで bond が壊れることは無かった
- RA も SLIRP のもの(
fe80::2、プレフィックス240f:6d:842b:1::/64は手で合わせた)。 ISP のルータの RA(MTU オプション、RDNSS、valid/preferred lifetime)は別物 - carrier の復旧方向、PT3 本体(当時は vfio パススルーの予定。いまは dvb-pt3 で
/dev/dvbが出るか)
Talos 1.14.0 の既定は Flannel 0.28.9 + kube-proxy(1.13 から Flannel も NetworkPolicy に対応。L3/L4 まで)。
このクラスタは Cilium に替える(decisions.md「ルーティングの選定」。CNI の交換は構築時にやる ──
稼働中の差し替えは全 Pod 再起動が前提で、後からだとコストが一桁変わる)。Talos 側の止め方は
../talos/patches/cni.yaml(v1alpha1 の cni.name: none はもう書けない)、
Cilium 側の Talos 固有の値は ../talos/cilium-values.yaml。
- LoadBalancer の実体は無い(k3s の ServiceLB 相当が無い)。hostPort と Gateway の hostNetwork で済ませた (decisions.md「LoadBalancer をどう置き換えるか」)
- クライアント IP: 単一ノードなので
externalTrafficPolicy: Clusterのままで保たれる(decisions.md「クライアント IP」) NetworkDefaultActionConfig: blockを使うなら、NetworkRuleConfigで要るポートを明示的に開ける
| レイヤー | 決めること | 変更手段 |
|---|---|---|
| イメージ | カーネル引数、system extension(GPU ドライバ等) | Image Factory で schematic を作って talosctl upgrade |
| machine config | CNI、kube-proxy、ディスク、ネットワーク、ファイアウォール | talosctl apply-config |
| Kubernetes | ワークロード、Envoy Gateway、Cilium 本体 | kubectl / Helm / GitOps |
SSH もシェルもパッケージマネージャも無く gRPC API だけなので、障害時は「直さず作り直す」。
machine config は必ず git に置く(唯一の真実になる)。デバッグは talosctl dmesg / logs / read、1.13 以降は debug コンテナ。