بنقرة واحدة
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.