- build kubeadm for windows with the patch in this gist
- the patch applies to kubeadm v1.19.x and master
- call "kubeadm reset ..." with the new binary
- remove any workarounds such as the one with symlink
- call "kubeadm join ..." with the new binary
- if "join" passes and if the file "/var/lib/kubelet/config.yaml" contains paths with windows drive letters (e.g. C:) it means this patch works.
Last active
November 14, 2020 23:20
-
-
Save neolit123/1b7375c8a956155a2ca1cdd901336bce to your computer and use it in GitHub Desktop.
test-mutate-kubeletconfig-windows
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/cmd/kubeadm/app/apis/kubeadm/types.go b/cmd/kubeadm/app/apis/kubeadm/types.go | |
index 1759b765513..87557c2b754 100644 | |
--- a/cmd/kubeadm/app/apis/kubeadm/types.go | |
+++ b/cmd/kubeadm/app/apis/kubeadm/types.go | |
@@ -451,6 +451,9 @@ type ComponentConfig interface { | |
// SetUserSupplied sets the state of the component config "user supplied" flag to, either true, or false. | |
SetUserSupplied(userSupplied bool) | |
+ | |
+ // Mutate allows applying pre-defined modifications to the config before it's marshaled. | |
+ Mutate() error | |
} | |
// ComponentConfigMap is a map between a group name (as in GVK group) and a ComponentConfig | |
diff --git a/cmd/kubeadm/app/componentconfigs/fakeconfig_test.go b/cmd/kubeadm/app/componentconfigs/fakeconfig_test.go | |
index 4db2625f3ec..2e6cdad0527 100644 | |
--- a/cmd/kubeadm/app/componentconfigs/fakeconfig_test.go | |
+++ b/cmd/kubeadm/app/componentconfigs/fakeconfig_test.go | |
@@ -101,6 +101,10 @@ func (cc *clusterConfig) Default(_ *kubeadmapi.ClusterConfiguration, _ *kubeadma | |
cc.config.KubernetesVersion = "bar" | |
} | |
+func (cc *clusterConfig) Mutate() error { | |
+ return nil | |
+} | |
+ | |
// fakeKnown replaces temporarily during the execution of each test here known (in configset.go) | |
var fakeKnown = []*handler{ | |
&clusterConfigHandler, | |
diff --git a/cmd/kubeadm/app/componentconfigs/kubelet.go b/cmd/kubeadm/app/componentconfigs/kubelet.go | |
index ae13e8822e8..d03e45ae06c 100644 | |
--- a/cmd/kubeadm/app/componentconfigs/kubelet.go | |
+++ b/cmd/kubeadm/app/componentconfigs/kubelet.go | |
@@ -17,8 +17,12 @@ limitations under the License. | |
package componentconfigs | |
import ( | |
+ "os" | |
"path/filepath" | |
+ "runtime" | |
+ "strings" | |
+ "github.com/pkg/errors" | |
"k8s.io/apimachinery/pkg/util/version" | |
clientset "k8s.io/client-go/kubernetes" | |
"k8s.io/klog/v2" | |
@@ -223,6 +227,63 @@ func (kc *kubeletConfig) Default(cfg *kubeadmapi.ClusterConfiguration, _ *kubead | |
} | |
} | |
+// Mutate modifies absolute path fields in the KubeletConfiguration to be Windows compatible absolute paths. | |
+func (kc *kubeletConfig) Mutate() error { | |
+ if runtime.GOOS != "windows" { | |
+ return nil | |
+ } | |
+ | |
+ // When "kubeadm join" downloads the KubeletConfiguration from the cluster on Windows | |
+ // nodes, it would contain absolute paths that may lack drive letters, since the config | |
+ // could have been generated on a Linux control-plane node. On Windows the | |
+ // Golang path.IsAbs() function returns false unless the path contains a drive letter. | |
+ // This trips client-go and the kubelet, creating problems on Windows nodes. | |
+ // Fixing it in client-go or the kubelet is a breaking change to existing Windows | |
+ // users that rely on relative paths: | |
+ // https://github.com/kubernetes/kubernetes/pull/77710#issuecomment-491989621 | |
+ // | |
+ // Thus, a workaround here is to adapt the KubeletConfiguration paths for Windows. | |
+ // Note this is currently bound to KubeletConfiguration v1beta1. | |
+ klog.V(2).Infoln("[componentconfig] Adapting the paths in the KubeletConfiguration for Windows...") | |
+ | |
+ // Get the drive from where the kubeadm binary was called. | |
+ exe, err := os.Executable() | |
+ if err != nil { | |
+ return errors.Wrap(err, "could not obtain information about the kubeadm executable") | |
+ } | |
+ drive := filepath.VolumeName(filepath.Dir(exe)) | |
+ klog.V(2).Infof("[componentconfig] Assuming Windows drive %q", drive) | |
+ | |
+ // Mutate the paths in the config. | |
+ mutatePathsOnWindows(&kc.config, drive) | |
+ return nil | |
+} | |
+ | |
+func mutatePathsOnWindows(cfg *kubeletconfig.KubeletConfiguration, drive string) { | |
+ mutateStringField := func(name string, field *string, setEmpty bool) { | |
+ klog.V(2).Infof("[componentconfig] Adapting path for field %q with current value %q", name, *field) | |
+ // Set empty is a special case. | |
+ if setEmpty { | |
+ *field = "" | |
+ return | |
+ } | |
+ // path.IsAbs() is not reliable here in the Windows runtime, so check if the | |
+ // path starts with "/" instead. This means the path originated from a Unix node and | |
+ // is an absolute path. | |
+ if !strings.HasPrefix(*field, "/") { | |
+ return | |
+ } | |
+ // Prepend the drive letter to the path and update the field. | |
+ *field = filepath.Join(drive, *field) | |
+ return | |
+ } | |
+ | |
+ // Mutate the fields we care about. | |
+ mutateStringField("resolvConf", &cfg.ResolverConfig, true) // make sure this is disabled on Windows | |
+ mutateStringField("staticPodPath", &cfg.StaticPodPath, false) | |
+ mutateStringField("authentication.x509.clientCAFile", &cfg.Authentication.X509.ClientCAFile, false) | |
+} | |
+ | |
// isServiceActive checks whether the given service exists and is running | |
func isServiceActive(name string) (bool, error) { | |
initSystem, err := initsystem.GetInitSystem() | |
diff --git a/cmd/kubeadm/app/componentconfigs/kubelet_test.go b/cmd/kubeadm/app/componentconfigs/kubelet_test.go | |
index bf5d7ce8e6c..b7280f62230 100644 | |
--- a/cmd/kubeadm/app/componentconfigs/kubelet_test.go | |
+++ b/cmd/kubeadm/app/componentconfigs/kubelet_test.go | |
@@ -327,3 +327,66 @@ func TestKubeletFromCluster(t *testing.T) { | |
return kubeletHandler.FromCluster(client, testClusterCfg()) | |
}) | |
} | |
+ | |
+func TestMutatePathsOnWindows(t *testing.T) { | |
+ const drive = "C:" | |
+ | |
+ tests := []struct { | |
+ name string | |
+ cfg *kubeletconfig.KubeletConfiguration | |
+ expected *kubeletconfig.KubeletConfiguration | |
+ }{ | |
+ { | |
+ name: "valid: all fields are absolute paths", | |
+ cfg: &kubeletconfig.KubeletConfiguration{ | |
+ ResolverConfig: "/foo/resolver", | |
+ StaticPodPath: "/foo/staticpods", | |
+ Authentication: kubeletconfig.KubeletAuthentication{ | |
+ X509: kubeletconfig.KubeletX509Authentication{ | |
+ ClientCAFile: "/foo/ca.crt", | |
+ }, | |
+ }, | |
+ }, | |
+ expected: &kubeletconfig.KubeletConfiguration{ | |
+ ResolverConfig: "", | |
+ StaticPodPath: filepath.Join(drive, "/foo/staticpods"), | |
+ Authentication: kubeletconfig.KubeletAuthentication{ | |
+ X509: kubeletconfig.KubeletX509Authentication{ | |
+ ClientCAFile: filepath.Join(drive, "/foo/ca.crt"), | |
+ }, | |
+ }, | |
+ }, | |
+ }, | |
+ { | |
+ name: "valid: some fields are not absolute paths", | |
+ cfg: &kubeletconfig.KubeletConfiguration{ | |
+ ResolverConfig: "/foo/resolver", | |
+ StaticPodPath: "./foo/staticpods", // not an absolute Unix path | |
+ Authentication: kubeletconfig.KubeletAuthentication{ | |
+ X509: kubeletconfig.KubeletX509Authentication{ | |
+ ClientCAFile: "/foo/ca.crt", | |
+ }, | |
+ }, | |
+ }, | |
+ expected: &kubeletconfig.KubeletConfiguration{ | |
+ ResolverConfig: "", | |
+ StaticPodPath: "./foo/staticpods", | |
+ Authentication: kubeletconfig.KubeletAuthentication{ | |
+ X509: kubeletconfig.KubeletX509Authentication{ | |
+ ClientCAFile: filepath.Join(drive, "/foo/ca.crt"), | |
+ }, | |
+ }, | |
+ }, | |
+ }, | |
+ } | |
+ | |
+ for _, test := range tests { | |
+ t.Run(test.name, func(t *testing.T) { | |
+ mutatePathsOnWindows(test.cfg, drive) | |
+ if !reflect.DeepEqual(test.cfg, test.expected) { | |
+ t.Errorf("Missmatch between expected and got:\nExpected:\n%+v\n---\nGot:\n%+v", | |
+ test.expected, test.cfg) | |
+ } | |
+ }) | |
+ } | |
+} | |
diff --git a/cmd/kubeadm/app/componentconfigs/kubeproxy.go b/cmd/kubeadm/app/componentconfigs/kubeproxy.go | |
index c53beccdce5..a0b15d3220d 100644 | |
--- a/cmd/kubeadm/app/componentconfigs/kubeproxy.go | |
+++ b/cmd/kubeadm/app/componentconfigs/kubeproxy.go | |
@@ -118,3 +118,8 @@ func (kp *kubeProxyConfig) Default(cfg *kubeadmapi.ClusterConfiguration, localAP | |
kp.config.FeatureGates[features.IPv6DualStack] = enabled | |
} | |
} | |
+ | |
+// Mutate is NOP for the kube-proxy config | |
+func (kp *kubeProxyConfig) Mutate() error { | |
+ return nil | |
+} | |
diff --git a/cmd/kubeadm/app/phases/kubelet/config.go b/cmd/kubeadm/app/phases/kubelet/config.go | |
index a0a07612901..c16d8b11630 100644 | |
--- a/cmd/kubeadm/app/phases/kubelet/config.go | |
+++ b/cmd/kubeadm/app/phases/kubelet/config.go | |
@@ -43,6 +43,9 @@ func WriteConfigToDisk(cfg *kubeadmapi.ClusterConfiguration, kubeletDir string) | |
return errors.New("no kubelet component config found") | |
} | |
+ if err := kubeletCfg.Mutate(); err != nil { | |
+ return err | |
+ } | |
kubeletBytes, err := kubeletCfg.Marshal() | |
if err != nil { | |
return err |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment