Skip to content

Instantly share code, notes, and snippets.

@neolit123
Last active November 14, 2020 23:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save neolit123/1b7375c8a956155a2ca1cdd901336bce to your computer and use it in GitHub Desktop.
Save neolit123/1b7375c8a956155a2ca1cdd901336bce to your computer and use it in GitHub Desktop.
test-mutate-kubeletconfig-windows
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
  • 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment