kubernetes/kops · error

cannot apply changes to ForwardingRule

Error message

cannot apply changes to ForwardingRule: %v

What it means

kOps GCE forwarding rules are effectively immutable: after applying label changes, any remaining diff between expected and actual state causes this error instead of attempting in-place modification. It tells you the cluster spec demands a field change (IP, target, network, ports, etc.) that the GCE API cannot apply to an existing rule.

Solutions

  1. Identify the changed fields printed in the error and revert the spec change, or accept LB recreation: `kops update cluster` will not do it in place — delete and recreate the rule (often via `kops delete instancegroup`/changing loadBalancerType and re-creating)
  2. For API load balancers, changing the loadBalancerType typically requires recreating the load balancer resource; plan for downtime
  3. Diff current vs desired cluster spec (`kops get -oyaml`) to see which LB field changed
  4. Upgrade path note: some changes are handled by kOps internally by recreating tasks — ensure RuleIPAddress/observed state is refreshed before update

Example fix

// before: editing fields in place on an existing rule (unsupported)
//   changes: {PortRange: "8080-8080"} -> error
// after: delete and re-apply the load balancer so the rule is recreated
kops delete cluster --name ... (or recreate the LB object), then
kops update cluster --yes
Defensive patterns

Strategy: validation

Validate before calling

// before updating an existing rule, check only labels differ (labels are the only mutable field)
func onlyLabelsChanged(changes *ForwardingRule) bool {
    c := *changes
    c.Labels = nil
    return reflect.DeepEqual(&c, &ForwardingRule{})
}
// if false, plan deletion+recreation of the rule instead of an update

Try / catch

if !reflect.DeepEqual(changes, &ForwardingRule{}) {
    return fmt.Errorf("cannot apply changes to ForwardingRule: %v (recreate the rule instead of updating)", changes)
}

Prevention

When it happens

Trigger: RenderGCE with a != nil where, after clearing changes.Labels, reflect.DeepEqual(changes, &ForwardingRule{}) is false — i.e. changes to Name/IPAddress/PortRange/Target/Network/etc. remain.

Common situations: Changing loadBalancerType, port ranges, target pool, backend service, or static IP on an existing GCE load balancer; upgrading kOps and cluster spec fields shifting; editing the cluster spec LB settings directly.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/51f2c731d890b6db. Report an issue: GitHub.

Appendix: source

Thrown at upup/pkg/fi/cloudup/gcetasks/forwardingrule.go:277

		if changes.Labels != nil {
			req := compute.RegionSetLabelsRequest{
				LabelFingerprint: a.labelFingerprint,
				Labels:           e.Labels,
			}
			op, err := t.Cloud.Compute().ForwardingRules().SetLabels(ctx, t.Cloud.Project(), t.Cloud.Region(), o.Name, &req)
			if err != nil {
				return fmt.Errorf("setting ForwardingRule labels: %w", err)
			}

			if err := t.Cloud.WaitForOp(op); err != nil {
				return fmt.Errorf("setting ForwardRule labels: %w", err)
			}

			changes.Labels = nil
		}

		if !reflect.DeepEqual(changes, &ForwardingRule{}) {
			return fmt.Errorf("cannot apply changes to ForwardingRule: %v", changes)
		}
	}

	return nil
}

type terraformForwardingRule struct {
	Name                string                   `cty:"name"`
	PortRange           *string                  `cty:"port_range"`
	Ports               []string                 `cty:"ports"`
	Target              *terraformWriter.Literal `cty:"target"`
	IPAddress           *terraformWriter.Literal `cty:"ip_address"`
	IPProtocol          string                   `cty:"ip_protocol"`
	LoadBalancingScheme *string                  `cty:"load_balancing_scheme"`
	Network             *terraformWriter.Literal `cty:"network"`
	Subnetwork          *terraformWriter.Literal `cty:"subnetwork"`
	BackendService      *terraformWriter.Literal `cty:"backend_service"`
	Labels              map[string]string        `cty:"labels"`

View on GitHub (pinned to 4c8573c808)