{"record":{"id":"1d270fb52f659b82","repo":"kubernetes/kops","slug":"k8sclient-not-set","errorCode":null,"errorMessage":"K8sClient not set","messagePattern":"K8sClient not set","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/instancegroups/instancegroups.go","lineNumber":672,"sourceCode":"\t\tklog.Infof(\"Stopping instance %q, node %q, in group %q (this may take a while).\", id, nodeName, u.CloudInstanceGroup.HumanName)\n\t} else {\n\t\tklog.Infof(\"Stopping instance %q, in group %q (this may take a while).\", id, u.CloudInstanceGroup.HumanName)\n\t}\n\n\tif err := c.Cloud.DeleteInstance(u); err != nil {\n\t\tif nodeName != \"\" {\n\t\t\treturn fmt.Errorf(\"error deleting instance %q, node %q: %v\", id, nodeName, err)\n\t\t}\n\t\treturn fmt.Errorf(\"error deleting instance %q: %v\", id, err)\n\t}\n\n\treturn nil\n}\n\n// drainNode drains a K8s node.\nfunc (c *RollingUpdateCluster) drainNode(ctx context.Context, u *cloudinstances.CloudInstance) error {\n\tif c.K8sClient == nil {\n\t\treturn fmt.Errorf(\"K8sClient not set\")\n\t}\n\n\tif u.Node == nil {\n\t\treturn fmt.Errorf(\"node not set\")\n\t}\n\n\tif u.Node.Name == \"\" {\n\t\treturn fmt.Errorf(\"node name not set\")\n\t}\n\n\thelper := &drain.Helper{\n\t\tCtx:                 ctx,\n\t\tClient:              c.K8sClient,\n\t\tForce:               true,\n\t\tGracePeriodSeconds:  -1,\n\t\tIgnoreAllDaemonSets: true,\n\t\tOut:                 os.Stdout,\n\t\tErrOut:              os.Stderr,","sourceCodeStart":654,"sourceCodeEnd":690,"githubUrl":"https://github.com/kubernetes/kops/blob/4c8573c808a73d578c5eadc86d410646ea0b0d73/pkg/instancegroups/instancegroups.go#L654-L690","documentation":"drainNode requires a Kubernetes client to evict pods from the node. This guard error means RollingUpdateCluster was constructed without a K8sClient, so draining is impossible. It propagates into drainTerminateAndWait and (with FailOnDrainError) fails the roll.","triggerScenarios":"Rolling update invoked without building a kubeconfig-backed clientset — e.g. --cloud-only flows or programmatic use that leaves K8sClient nil while still attempting to drain a registered node.","commonSituations":"Programmatic/library use of RollingUpdateCluster forgetting to set K8sClient; kops run in an environment where no kubeconfig can be resolved; intentionally cloud-only operations hitting the drain path.","solutions":["Ensure K8sClient is set when constructing RollingUpdateCluster (clientset from a valid kubeconfig)","If no cluster access is intended, use --cloud-only so drain is skipped","Verify kubeconfig availability/validity for the target cluster"],"exampleFix":"// before\nru := &instancegroups.RollingUpdateCluster{Cloud: cloud, ...} // K8sClient nil\n// after\nk8sClient, err := kubernetes.NewForConfig(config)\nru := &instancegroups.RollingUpdateCluster{Cloud: cloud, K8sClient: k8sClient, ...}","handlingStrategy":"validation","validationCode":"if c.K8sClient == nil {\n    clientset, err := kubernetes.NewForConfig(restConfig)\n    if err != nil { return err }\n    c.K8sClient = clientset\n}","typeGuard":"func drainable(c *RollingUpdateCluster) bool { return c != nil && c.K8sClient != nil }","tryCatchPattern":"if err := c.drainNode(ctx, u); err != nil {\n    if err.Error() == \"K8sClient not set\" {\n        return fmt.Errorf(\"cannot drain: configure a Kubernetes clientset or use --cloud-only\")\n    }\n    return err\n}","preventionTips":["Always populate K8sClient when constructing RollingUpdateCluster","Use --cloud-only explicitly when no cluster API access is intended","Validate kubeconfig resolution before starting a roll"],"tags":["kubernetes","client","drain","configuration"],"backgroundTag":"missing-k8s-client","analyzedSha":"4c8573c808a73d578c5eadc86d410646ea0b0d73","analyzedAt":"2026-09-05T04:13:19.212Z","contentChangedAt":"2026-09-05T04:13:19.212Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}