ワンクリックで
migrate-v2-to-v3
Use when migrating an existing kube-hetzner Terraform root from module v2.x to v3.x
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Use when migrating an existing kube-hetzner Terraform root from module v2.x to v3.x
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Use when users need help with kube-hetzner configuration, debugging, or questions - acts as an intelligent assistant with live repo access
Use when preparing or executing a release - verifies changelog content, updates version references, commits release prep, and, when the maintainer explicitly asks, pushes the release tag that triggers automation
Use when working on a GitHub issue - fetches issue details, analyzes codebase, implements fix following project methodology
Use when reviewing a pull request - security-focused review following repo agent guidance for breaking changes, malicious patterns, and backward compatibility
Use when triaging a GitHub issue - analyzes issue, checks for duplicates, categorizes, and drafts response
Use when a Hetzner node is unreachable, SSH fails, cloud-init seems broken, or provisioning hangs. Boots into rescue mode via hcloud CLI to inspect filesystem, logs, SSH keys, sshd config, and cloud-init state without needing SSH access to the node itself.
| name | migrate-v2-to-v3 |
| description | Use when migrating an existing kube-hetzner Terraform root from module v2.x to v3.x |
Safely migrate an existing kube-hetzner cluster from v2.x to v3.x.
This skill is for real user Terraform roots, not for brand-new clusters. It prioritizes preserving infrastructure and making Terraform validation failures actionable.
Read these before editing:
docs/v2-to-v3-migration.md - operational migration playbookdocs/v3-release-evidence.md - live v2.21 -> v3 upgrade evidenceMIGRATION.md - canonical old-to-new variable mapCHANGELOG.md - v3 upgrade notes and release contextvariables.tf - exact v3 input contract and validation ruleskube.tf.example - current v3 configuration examplescripts/v2_to_v3_migration_assistant.py - static config and plan auditdocs/selinux.md - SELinux policy, AVC evidence, and per-pool fallbackscripts/destroy.sh and scripts/cleanup.sh - teardown and orphan cleanupterraform apply unless the user explicitly asks after plan review.destroy, replace, or forces replacement in a v3 upgrade
plan.MIGRATION.md documents
that v2 defaulted k3s_channel to v1.33; v3 defaults it to stable while
automatic Kubernetes upgrades remain default-on. The user must choose before
the first v3 apply.hcloud_network, hcloud_network_subnet,
hcloud_server, hcloud_load_balancer, hcloud_primary_ip,
hcloud_placement_group, hcloud_volume, or hcloud_firewall as blockers
until explained.MIGRATION.md is the operator-facing safety
contract. Its no-destroy jq gate is canonical; do not weaken or abbreviate it.Determine:
k3s_version, set
k3s_channel = "v1.33", or consciously accept v3 stable.ssh_public_key/ssh_additional_public_keys.terraform_data.initial_readiness, including private-only, NAT, and bastion
topologies.docs/selinux.md rather than disabling
SELinux globally.From the user's Terraform root:
terraform version
terraform providers
rg -n 'module "kube-hetzner"|source\\s*=|version\\s*=' .
Find v2-only inputs:
uv run python /path/to/kube-hetzner/scripts/v2_to_v3_migration_assistant.py --root .
rg -n 'kubernetes_distribution_type|k3s_token|secrets_encryption|initial_k3s_channel|install_k3s_version|initial_rke2_channel|install_rke2_version|automatically_upgrade_k3s|sys_upgrade_controller_version|additional_k3s_environment|kubeapi_port|k3s_registries|k3s_kubelet_config|k3s_audit_policy_config|k3s_audit_log_path|k3s_audit_log_maxage|k3s_audit_log_maxbackup|k3s_audit_log_maxsize|k3s_exec_server_args|k3s_exec_agent_args|k3s_global_kubelet_args|k3s_control_plane_kubelet_args|k3s_agent_kubelet_args|k3s_autoscaler_kubelet_args|subnet_amount|placement_group_disable|block_icmp_ping_in|disable_hetzner_csi|load_balancer_disable_ipv6|load_balancer_disable_public_network|use_control_plane_lb|combine_load_balancers|control_plane_lb_type|control_plane_lb_enable_public_interface|control_plane_lb_enable_public_network|lb_hostname|robot_ccm_enabled|hetzner_ccm_use_helm|enable_hetzner_ccm_helm|cilium_loadbalancer_acceleration_mode|enable_wireguard|k8s_config_updates_use_kured_sentinel|keep_disk_agents|keep_disk_cp|use_private_bastion|disable_kube_proxy|disable_network_policy|disable_selinux|k3s_prefer_bundled_bin|placement_group_compat_idx|disable_ipv4|disable_ipv6|autoscaler_disable_ipv4|autoscaler_disable_ipv6|existing_network_id|enable_x86|enable_arm|k3s_encryption_at_rest|autoscaler_labels|autoscaler_taints|extra_kustomize_' .
The assistant is the primary first-pass report. The rg command is a manual
cross-check when a root uses unusual formatting, Terragrunt, generated HCL, or
split tfvars.
terraform state pull > "terraform-state-before-kube-hetzner-v3-$(date +%Y%m%d%H%M%S).json"
find . -maxdepth 1 -name '*.tf' -print
If editing user config directly, preserve a copy of touched .tf files.
Use MIGRATION.md for the complete map. Critical transformations:
k3s_token -> cluster_token and
kubernetes_distribution_type -> kubernetes_distribution.disable_kube_proxy -> enable_kube_proxydisable_network_policy -> enable_network_policydisable_selinux -> enable_selinuxdisable_ipv4/disable_ipv6 ->
enable_public_ipv4/enable_public_ipv6autoscaler_disable_ipv4/disable_ipv6 ->
autoscaler_enable_public_ipv4/autoscaler_enable_public_ipv6existing_network_id = ["123"] with
existing_network = { id = 123 }.enable_x86/enable_arm with enabled_architectures.network_id; control planes are primary-network only.network_id = 0; primary network is omitted/null in v3.network_subnet_mode = "per_nodepool" for normal in-place
v2 upgrades; this matches released v2 subnet resources. Use
network_subnet_mode = "shared" only for new clusters or intentional subnet
topology changes.placement_group_compat_idx with placement_group_index.extra_kustomize_* with user_kustomizations.enable_iscsid, k3s_encryption_at_rest, autoscaler_labels, and
autoscaler_taints.hetzner_ccm_use_helm / enable_hetzner_ccm_helm; v3 always
installs Hetzner CCM through the HelmChart manifest.Resolve these before the first v3 apply:
MIGRATION.md documents the silent default change:
v2 used k3s_channel = "v1.33", v3 uses k3s_channel = "stable", and
automatically_upgrade_kubernetes remains default-on. Make the user choose
exactly one:
k3s_versionk3s_channel = "v1.33" to preserve the v2 minor channelstablelatest.terraform_data.ssh_authorized_keys reconciles
/root/.ssh/authorized_keys. The default preserves unknown out-of-band keys
while revoking module-managed keys removed from ssh_public_key or
ssh_additional_public_keys. Set ssh_authorized_keys_exclusive = true only
when the user wants strict replacement with exactly the module-managed keys.terraform_data.initial_readiness SSHes to every existing
control-plane and agent node. Private-only, NAT, and bastion-only operators
must have Terraform reachability to all nodes before applying.The proven 2026-07-04/05 standard live upgrade needed only the module source
switch plus k3s_channel = "v1.33" for the first v3 plan.
terraform fmt -recursive
terraform init -upgrade
terraform validate -no-color
tmpdir="$(mktemp -d)"
rsync -a --exclude .git --exclude .terraform --exclude .terraform-tofu ./ "$tmpdir"/
(cd "$tmpdir" && tofu init -backend=false -input=false && tofu validate -no-color)
rm -rf "$tmpdir"
terraform validate checks that the module loads. v3 cross-variable migration
guards are enforced by terraform_data.validation_contract, so the saved
terraform plan is the required proof for invalid combinations and replacement
risk.
If validation fails, read the variable and validation block in variables.tf
before changing config.
terraform plan -out=v3-upgrade.tfplan
terraform show v3-upgrade.tfplan
terraform show -json v3-upgrade.tfplan > v3-upgrade-plan.json
uv run python /path/to/kube-hetzner/scripts/v2_to_v3_migration_assistant.py --root . --plan-json v3-upgrade-plan.json
If jq is available, list destructive actions:
terraform show -json v3-upgrade.tfplan \
| jq -r '.resource_changes[]? | select(.change.actions | index("delete")) | "\(.address): \(.change.actions | join(","))"'
For live v2 -> v3 migrations, run the protected-infrastructure gate. Terraform
replacements show up as action lists containing delete. The gate prints only
blocking resources; any output is a stop condition:
terraform show -json v3-upgrade.tfplan \
| jq -r '
.resource_changes[]?
| select(.type as $type | [
"hcloud_server",
"hcloud_network",
"hcloud_network_subnet",
"hcloud_load_balancer",
"hcloud_volume",
"hcloud_primary_ip",
"hcloud_placement_group",
"hcloud_firewall"
] | index($type))
| select(.change.actions | index("delete"))
| "\(.address): \(.type) \(.change.actions | join(","))"
'
After the v2 renames, the plan must show zero destroy/replace actions for
hcloud_server, hcloud_network, hcloud_network_subnet,
hcloud_load_balancer, hcloud_volume, hcloud_primary_ip,
hcloud_placement_group, and hcloud_firewall. Stop and diagnose before apply
if any of those types are listed.
Do not panic-abort a healthy first v3 plan for these expected actions:
terraform_data resources for readiness, SSH authorized-key reconcile,
validators, and destroy cleanupkube-hetzner/osexisting_network plus vswitch_id cannot have module-managed
expose_routes_to_vswitch; set it false or enable route exposure manually.multinetwork_mode = "cilium_public_overlay",
enable_experimental_cilium_public_overlay = true, and
cni_plugin = "cilium"; do not recommend it for production upgrades until the
live datapath E2E passes.node_transport_mode = "tailscale" for evaluation and separate audited
plans; do not certify production topologies from static checks alone./32 routes with Tailscale subnet-route SNAT
disabled. Single-network clusters may disable route advertisement; external
network_scope = "external" nodepools require route advertisement and Tailnet
auto-approval.network_scope = "primary" or
network_scope = "external" so same-root external Network IDs validate
during terraform plan.cilium_gateway_api_enabled,
embedded_registry_mirror, new Tailscale multinetwork shards, or external
Cloudflare Access/Tunnel routing during the same first in-place v2-to-v3
apply unless the operator explicitly accepts a blue/green/topology-change
migration. Upgrade cleanly first, then add those features in a separate
reviewed plan.docs/selinux.md: collect AVC lines, try udica-generated workload policy
first, propose upstream module policy only with reproducible AVC evidence, and
use per-pool selinux = false only as the last resort.scripts/destroy.sh from the
Terraform root. It auto-retries only the known benign ingress-LB detach race
(resource_already_detaching/422 from dual CCM and Terraform ownership) and
then prints a read-only orphan report for unlabeled primary IPs,
out-of-state autoscaled nodes, and exact managed LB names. Use
scripts/cleanup.sh only as the forceful fallback after the report shows
leftovers or state is already wrecked.min_nodes = 0; deleting them
earlier while Cluster Autoscaler is alive can recreate them.cni_plugin = "cilium"
and enable_kube_proxy = false.Return a migration report:
## kube-hetzner v2 -> v3 migration report
- Terraform root:
- Source version:
- Target version:
- Terraform/OpenTofu version:
- hcloud provider version:
- K3s channel/version intent:
- Kubernetes distribution intent:
- Addon version intent:
- SSH authorized_keys policy:
- SELinux intent and AVC evidence, if relevant:
- Inputs changed:
- Inputs removed:
- Manual state actions:
- Validation result:
- Plan result:
- Protected hcloud delete/replace gate:
- Replacements/destroys:
- Expected first-apply actions:
- Blockers:
- Recommendation:
Only run:
terraform apply v3-upgrade.tfplan
after explicit user approval and after the plan report has no unexplained destructive actions.