Repository: kubermatic/machine-controller
Pull request #811 (2020-08-17 16:16:27)

Always ensure to have machine-delete-finalizer
**What this PR does / why we need it**:
After creating a new provider instance we always want to have the
machine-delete-finalizer. Possible provider specific finalizers can't be
deleted without it.

We have noted this especially for azure clusters, where the machine-controller doesn't try to run deleteCloudProvider instance because the finalizer `machine-delete-finalizer` is missing.

**Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>` format, will close the issue(s) when PR gets merged)*:
Fixes #

**Special notes for your reviewer**:

**Optional Release Note**:
<!--
Write your release note:
1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string "action required".
2. If no release note is required, just write "NONE".
-->
```release-note
NONE
```


kubermatic-bot: @phiphi282: Adding the "do-not-merge/release-note-label-needed" label because no release-note block was detected, please follow our [release note process](https://git.k8s.io/community/contributors/guide/release-notes.md) to remove it.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>
kubermatic-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubermatic/machine-controller/pull/811#" title="Author self-approved">phiphi282</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **xrstf**
You can assign the PR to them by writing `/assign @xrstf` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubermatic%2Fmachine-controller).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubermatic/machine-controller/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["xrstf"]} -->
kubermatic-bot: Hi @phiphi282. Thanks for your PR.

I'm waiting for a [kubermatic](https://github.com/orgs/kubermatic/people) member to verify that this patch is reasonable to test. If it is, they should reply with `/ok-to-test` on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should [join the org](https://github.com/orgs/kubermatic/people) to skip this step.

Once the patch is verified, the new status will be reflected by the `ok-to-test` label.

I understand the commands that are listed [here](https://go.k8s.io/bot-commands?repo=kubermatic%2Fmachine-controller).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.
</details>

phiphi282: /assign @xrstf

Patch:
diff --git a/OWNERS_ALIASES b/OWNERS_ALIASES
index 52defe09..dae8cdf1 100644
--- a/OWNERS_ALIASES
+++ b/OWNERS_ALIASES
@@ -3,7 +3,6 @@
 aliases:
   machine-controller-maintainers:
     - irozzo-1A
-    - kdomanski
     - kron4eg
     - moadqassem
     - moelsayed
diff --git a/cmd/machine-controller/main.go b/cmd/machine-controller/main.go
index 1614441d..d0a03ddc 100644
--- a/cmd/machine-controller/main.go
+++ b/cmd/machine-controller/main.go
@@ -25,6 +25,7 @@ import (
 	"net/http"
 	"net/http/pprof"
 	"os"
+	"strconv"
 	"strings"
 	"time"
 
@@ -76,13 +77,14 @@ var (
 	skipEvictionAfter                time.Duration
 	nodeCSRApprover                  bool
 
-	nodeHTTPProxy          string
-	nodeNoProxy            string
-	nodeInsecureRegistries string
-	nodeRegistryMirrors    string
-	nodePauseImage         string
-	nodeHyperkubeImage     string
-	nodeKubeletRepository  string
+	nodeHTTPProxy           string
+	nodeNoProxy             string
+	nodeInsecureRegistries  string
+	nodeRegistryMirrors     string
+	nodePauseImage          string
+	nodeHyperkubeImage      string
+	nodeKubeletRepository   string
+	nodeKubeletFeatureGates string
 )
 
 const (
@@ -168,6 +170,7 @@ func main() {
 	flag.StringVar(&nodePauseImage, "node-pause-image", "", "Image for the pause container including tag. If not set, the kubelet default will be used: https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/")
 	flag.StringVar(&nodeHyperkubeImage, "node-hyperkube-image", "k8s.gcr.io/hyperkube-amd64", "Image for the hyperkube container excluding tag. Only has effect on CoreOS Container Linux and Flatcar Linux, and for kubernetes < 1.18.")
 	flag.StringVar(&nodeKubeletRepository, "node-kubelet-repository", "quay.io/poseidon/kubelet", "Repository for the kubelet container. Only has effect on Flatcar Linux, and for kubernetes >= 1.18.")
+	flag.StringVar(&nodeKubeletFeatureGates, "node-kubelet-feature-gates", "RotateKubeletServerCertificate=true", "Feature gates to set on the kubelet. Default: RotateKubeletServerCertificate=true")
 	flag.BoolVar(&nodeCSRApprover, "node-csr-approver", false, "Enable NodeCSRApprover controller to automatically approve node serving certificate requests.")
 
 	flag.Parse()
@@ -179,6 +182,11 @@ func main() {
 		klog.Fatalf("invalid cluster dns specified: %v", err)
 	}
 
+	kubeletFeatureGates, err := parseKubeletFeatureGates(nodeKubeletFeatureGates)
+	if err != nil {
+		klog.Fatalf("invalid kubelet feature gates specified: %v", err)
+	}
+
 	var parsedJoinClusterTimeout *time.Duration
 	if joinClusterTimeout != "" {
 		parsedJoinClusterTimeoutLiteral, err := time.ParseDuration(joinClusterTimeout)
@@ -258,12 +266,13 @@ func main() {
 		skipEvictionAfter:     skipEvictionAfter,
 		nodeCSRApprover:       nodeCSRApprover,
 		node: machinecontroller.NodeSettings{
-			ClusterDNSIPs:     clusterDNSIPs,
-			HTTPProxy:         nodeHTTPProxy,
-			NoProxy:           nodeNoProxy,
-			HyperkubeImage:    nodeHyperkubeImage,
-			KubeletRepository: nodeKubeletRepository,
-			PauseImage:        nodePauseImage,
+			ClusterDNSIPs:       clusterDNSIPs,
+			HTTPProxy:           nodeHTTPProxy,
+			NoProxy:             nodeNoProxy,
+			HyperkubeImage:      nodeHyperkubeImage,
+			KubeletRepository:   nodeKubeletRepository,
+			KubeletFeatureGates: kubeletFeatureGates,
+			PauseImage:          nodePauseImage,
 		},
 	}
 	if parsedJoinClusterTimeout != nil {
@@ -544,3 +553,26 @@ func parseClusterDNSIPs(s string) ([]net.IP, error) {
 	}
 	return ips, nil
 }
+
+func parseKubeletFeatureGates(s string) (map[string]bool, error) {
+	featureGates := map[string]bool{}
+	sFeatureGates := strings.Split(s, ",")
+
+	for _, featureGate := range sFeatureGates {
+		sFeatureGate := strings.Split(featureGate, "=")
+		if len(sFeatureGate) != 2 {
+			return nil, fmt.Errorf("invalid kubelet feature gate: %q", featureGate)
+		}
+		featureGateEnabled, err := strconv.ParseBool(sFeatureGate[1])
+		if err != nil {
+			return nil, fmt.Errorf("failed to parse kubelet feature gate: %q", featureGate)
+		}
+
+		featureGates[sFeatureGate[0]] = featureGateEnabled
+	}
+	if len(featureGates) == 0 {
+		featureGates["RotateKubeletServerCertificate"] = true
+	}
+
+	return featureGates, nil
+}
diff --git a/examples/hetzner-machinedeployment.yaml b/examples/hetzner-machinedeployment.yaml
index 576d30fe..69eb3639 100644
--- a/examples/hetzner-machinedeployment.yaml
+++ b/examples/hetzner-machinedeployment.yaml
@@ -47,6 +47,7 @@ spec:
             # Optional
             datacenter: ""
             location: "fsn1"
+            image: "ubuntu-20.04"
             # Optional: network IDs or names
             networks:
               - "<< YOUR_NETWORK >>"
diff --git a/pkg/apis/plugin/plugin.go b/pkg/apis/plugin/plugin.go
index 62365daf..14ddba54 100644
--- a/pkg/apis/plugin/plugin.go
+++ b/pkg/apis/plugin/plugin.go
@@ -53,6 +53,7 @@ type UserDataRequest struct {
 	PauseImage            string
 	HyperkubeImage        string
 	KubeletRepository     string
+	KubeletFeatureGates   map[string]bool
 }
 
 // UserDataResponse contains the responded user data.
diff --git a/pkg/cloudprovider/provider/hetzner/provider.go b/pkg/cloudprovider/provider/hetzner/provider.go
index 1623328c..154674a9 100644
--- a/pkg/cloudprovider/provider/hetzner/provider.go
+++ b/pkg/cloudprovider/provider/hetzner/provider.go
@@ -58,6 +58,7 @@ type Config struct {
 	Token      string
 	ServerType string
 	Datacenter string
+	Image      string
 	Location   string
 	Networks   []string
 	Labels     map[string]string
@@ -97,18 +98,27 @@ func (p *provider) getConfig(s v1alpha1.ProviderSpec) (*Config, *providerconfigt
 	if err != nil {
 		return nil, nil, fmt.Errorf("failed to get the value of \"token\" field, error = %v", err)
 	}
+
 	c.ServerType, err = p.configVarResolver.GetConfigVarStringValue(rawConfig.ServerType)
 	if err != nil {
 		return nil, nil, err
 	}
+
 	c.Datacenter, err = p.configVarResolver.GetConfigVarStringValue(rawConfig.Datacenter)
 	if err != nil {
 		return nil, nil, err
 	}
+
+	c.Image, err = p.configVarResolver.GetConfigVarStringValue(rawConfig.Image)
+	if err != nil {
+		return nil, nil, err
+	}
+
 	c.Location, err = p.configVarResolver.GetConfigVarStringValue(rawConfig.Location)
 	if err != nil {
 		return nil, nil, err
 	}
+
 	for _, network := range rawConfig.Networks {
 		networkValue, err := p.configVarResolver.GetConfigVarStringValue(network)
 		if err != nil {
@@ -116,6 +126,7 @@ func (p *provider) getConfig(s v1alpha1.ProviderSpec) (*Config, *providerconfigt
 		}
 		c.Networks = append(c.Networks, networkValue)
 	}
+
 	c.Labels = rawConfig.Labels
 	return &c, &pconfig, err
 }
@@ -154,6 +165,12 @@ func (p *provider) Validate(spec v1alpha1.MachineSpec) error {
 		}
 	}
 
+	if c.Image != "" {
+		if _, _, err = client.Image.Get(ctx, c.Image); err != nil {
+			return fmt.Errorf("failed to get image: %v", err)
+		}
+	}
+
 	if len(c.Networks) != 0 {
 		for _, network := range c.Networks {
 			if _, _, err = client.Network.Get(ctx, network); err != nil {
@@ -181,17 +198,21 @@ func (p *provider) Create(machine *v1alpha1.Machine, _ *cloudprovidertypes.Provi
 	ctx := context.TODO()
 	client := getClient(c.Token)
 
-	imageName, err := getNameForOS(pc.OperatingSystem)
-	if err != nil {
-		return nil, cloudprovidererrors.TerminalError{
-			Reason:  common.InvalidConfigurationMachineError,
-			Message: fmt.Sprintf("Invalid operating system specified %q, details = %v", pc.OperatingSystem, err),
+	if c.Image == "" {
+		imageName, err := getNameForOS(pc.OperatingSystem)
+		if err != nil {
+			return nil, cloudprovidererrors.TerminalError{
+				Reason:  common.InvalidConfigurationMachineError,
+				Message: fmt.Sprintf("Invalid operating system specified %q, details = %v", pc.OperatingSystem, err),
+			}
 		}
+		c.Image = imageName
 	}
 
 	if c.Labels == nil {
 		c.Labels = map[string]string{}
 	}
+
 	c.Labels[machineUIDLabelKey] = string(machine.UID)
 	serverCreateOpts := hcloud.ServerCreateOpts{
 		Name:     machine.Spec.Name,
@@ -224,7 +245,7 @@ func (p *provider) Create(machine *v1alpha1.Machine, _ *cloudprovidertypes.Provi
 		}
 	}
 
-	serverCreateOpts.Image, _, err = client.Image.Get(ctx, imageName)
+	serverCreateOpts.Image, _, err = client.Image.Get(ctx, c.Image)
 	if err != nil {
 		return nil, hzErrorToTerminalError(err, "failed to get image")
 	}
diff --git a/pkg/cloudprovider/provider/hetzner/types/types.go b/pkg/cloudprovider/provider/hetzner/types/types.go
index 5bffa4a2..b467b6c5 100644
--- a/pkg/cloudprovider/provider/hetzner/types/types.go
+++ b/pkg/cloudprovider/provider/hetzner/types/types.go
@@ -24,6 +24,7 @@ type RawConfig struct {
 	Token      providerconfigtypes.ConfigVarString   `json:"token,omitempty"`
 	ServerType providerconfigtypes.ConfigVarString   `json:"serverType"`
 	Datacenter providerconfigtypes.ConfigVarString   `json:"datacenter"`
+	Image      providerconfigtypes.ConfigVarString   `json:"image"`
 	Location   providerconfigtypes.ConfigVarString   `json:"location"`
 	Networks   []providerconfigtypes.ConfigVarString `json:"networks"`
 	Labels     map[string]string                     `json:"labels,omitempty"`
diff --git a/pkg/cloudprovider/provider/packet/provider.go b/pkg/cloudprovider/provider/packet/provider.go
index 75f6e023..e24d9779 100644
--- a/pkg/cloudprovider/provider/packet/provider.go
+++ b/pkg/cloudprovider/provider/packet/provider.go
@@ -429,6 +429,8 @@ func getNameForOS(os providerconfigtypes.OperatingSystem) (string, error) {
 		return "centos_7", nil
 	case providerconfigtypes.OperatingSystemCoreos:
 		return "coreos_stable", nil
+	case providerconfigtypes.OperatingSystemFlatcar:
+		return "flatcar_stable", nil
 	}
 	return "", providerconfigtypes.ErrOSNotSupported
 }
diff --git a/pkg/controller/machine/machine.go b/pkg/controller/machine/machine.go
index 8eba5d56..12e03a77 100644
--- a/pkg/controller/machine/machine.go
+++ b/pkg/controller/machine/machine.go
@@ -128,6 +128,9 @@ type NodeSettings struct {
 	HyperkubeImage string
 	// The kubelet repository to use. Currently only Flatcar Linux uses it.
 	KubeletRepository string
+	// Translates to feature gates on the kubelet.
+	// Default: RotateKubeletServerCertificate=true
+	KubeletFeatureGates map[string]bool
 }
 
 type KubeconfigProvider interface {
@@ -327,16 +330,6 @@ func (r *Reconciler) createProviderInstance(prov cloudprovidertypes.Provider, ma
 	}
 	instance, err := prov.Create(machine, r.providerData, userdata)
 	if err != nil {
-		// Remove the finalizer if creation failed. We always add it initially to be 100% sure
-		// its there if an instance was created
-		if updateMachineError := r.updateMachine(machine, func(m *clusterv1alpha1.Machine) {
-			if s := sets.NewString(m.Finalizers...); s.Has(FinalizerDeleteInstance) {
-				s.Delete(FinalizerDeleteInstance)
-			}
-		}); updateMachineError != nil {
-			return nil, fmt.Errorf("failed to remove %q finalizer with err=%q after instance creation failed with err=%q",
-				FinalizerDeleteInstance, updateMachineError, err)
-		}
 		return nil, err
 	}
 	return instance, nil
@@ -608,19 +601,49 @@ func (r *Reconciler) deleteCloudProviderInstance(prov cloudprovidertypes.Provide
 }
 
 func (r *Reconciler) deleteNodeForMachine(machine *clusterv1alpha1.Machine) error {
-	selector, err := labels.Parse(NodeOwnerLabelName + "=" + string(machine.UID))
-	if err != nil {
-		return fmt.Errorf("failed to parse label selector: %v", err)
-	}
-	listOpts := &ctrlruntimeclient.ListOptions{LabelSelector: selector}
-	nodes := &corev1.NodeList{}
-	if err := r.client.List(r.ctx, nodes, listOpts); err != nil {
-		return fmt.Errorf("failed to list nodes: %v", err)
-	}
+	// If there's NodeRef on the Machine object, remove the Node by using the
+	// value of the NodeRef. If there's no NodeRef, try to find the Node by
+	// listing nodes using the NodeOwner label selector.
+	if machine.Status.NodeRef != nil {
+		objKey := ctrlruntimeclient.ObjectKey{Name: machine.Status.NodeRef.Name}
+		node := &corev1.Node{}
+		nodeFound := true
+		if err := r.client.Get(r.ctx, objKey, node); err != nil {
+			if !kerrors.IsNotFound(err) {
+				return fmt.Errorf("failed to get node %s: %v", machine.Status.NodeRef.Name, err)
+			}
+			nodeFound = false
+			klog.V(2).Infof("node %q does not longer exist for machine %q", machine.Status.NodeRef.Name, machine.Spec.Name)
+		}
 
-	for _, node := range nodes.Items {
-		if err := r.client.Delete(r.ctx, &node); err != nil {
-			return err
+		if nodeFound {
+			if err := r.client.Delete(r.ctx, node); err != nil {
+				if !kerrors.IsNotFound(err) {
+					return err
+				}
+				klog.V(2).Infof("node %q does not longer exist for machine %q", machine.Status.NodeRef.Name, machine.Spec.Name)
+			}
+		}
+	} else {
+		selector, err := labels.Parse(NodeOwnerLabelName + "=" + string(machine.UID))
+		if err != nil {
+			return fmt.Errorf("failed to parse label selector: %v", err)
+		}
+		listOpts := &ctrlruntimeclient.ListOptions{LabelSelector: selector}
+		nodes := &corev1.NodeList{}
+		if err := r.client.List(r.ctx, nodes, listOpts); err != nil {
+			return fmt.Errorf("failed to list nodes: %v", err)
+		}
+		if len(nodes.Items) == 0 {
+			// We just want log that we didn't found the node. We don't want to
+			// return here, as we want to remove finalizers at the end.
+			klog.V(3).Infof("No node found for the machine %s", machine.Spec.Name)
+		}
+
+		for _, node := range nodes.Items {
+			if err := r.client.Delete(r.ctx, &node); err != nil {
+				return err
+			}
 		}
 	}
 
@@ -669,6 +692,7 @@ func (r *Reconciler) ensureInstanceExistsForMachine(
 				PauseImage:            r.nodeSettings.PauseImage,
 				HyperkubeImage:        r.nodeSettings.HyperkubeImage,
 				KubeletRepository:     r.nodeSettings.KubeletRepository,
+				KubeletFeatureGates:   r.nodeSettings.KubeletFeatureGates,
 				NoProxy:               r.nodeSettings.NoProxy,
 				HTTPProxy:             r.nodeSettings.HTTPProxy,
 			}
@@ -888,8 +912,31 @@ func (r *Reconciler) getNode(instance instance.Instance, provider providerconfig
 				return node.DeepCopy(), true, nil
 			}
 		}
+		// If we were unable to find Node by ProviderID, fallback to IP address matching.
+		// This usually happens if there's no CCM deployed in the cluster.
+		//
+		// This mechanism is not always reliable, as providers reuse the IP addresses after
+		// some time.
+		//
+		// If we rollout a Machine, it can happen that a new instance has the same
+		// IP addresses as the instance that has just been deleted. If machine-controller
+		// processes the new Machine before removing the old Machine and the corresponding
+		// Node object, machine-controller could update the NodeOwner label on the old Node
+		// object to point to the Machine that just got created, as IP addresses would match.
+		// This causes machine-controller to fail to delete the old Node object, which could
+		// then cause cluster stability issues in some cases.
 		for _, nodeAddress := range node.Status.Addresses {
 			for instanceAddress := range instance.Addresses() {
+				// We observed that the issue described above happens often on Hetzner.
+				// As we know that the Node and the instance name will always be same
+				// on Hetzner, we can use it as an additional check to prevent this
+				// issue.
+				// TODO: We should do this for other providers, but there are providers where
+				// the node and the instance names will not match, so it requires further
+				// investigation (e.g. AWS).
+				if provider == providerconfigtypes.CloudProviderHetzner && node.Name != instance.Name() {
+					continue
+				}
 				if nodeAddress.Address == instanceAddress {
 					return node.DeepCopy(), true, nil
 				}
diff --git a/pkg/userdata/centos/provider.go b/pkg/userdata/centos/provider.go
index 36a67e6a..5b7ef6a0 100644
--- a/pkg/userdata/centos/provider.go
+++ b/pkg/userdata/centos/provider.go
@@ -48,6 +48,11 @@ func (p Provider) UserData(req plugin.UserDataRequest) (string, error) {
 		return "", fmt.Errorf("invalid kubelet version: '%v'", err)
 	}
 
+	dockerVersion, err := userdatahelper.DockerVersionYum(kubeletVersion)
+	if err != nil {
+		return "", fmt.Errorf("invalid docker version: %v", err)
+	}
+
 	pconfig, err := providerconfigtypes.GetConfig(req.MachineSpec.ProviderSpec)
 	if err != nil {
 		return "", fmt.Errorf("failed to get provider config: %v", err)
@@ -86,6 +91,7 @@ func (p Provider) UserData(req plugin.UserDataRequest) (string, error) {
 		ProviderSpec     *providerconfigtypes.Config
 		OSConfig         *Config
 		KubeletVersion   string
+		DockerVersion    string
 		ServerAddr       string
 		Kubeconfig       string
 		KubernetesCACert string
@@ -95,6 +101,7 @@ func (p Provider) UserData(req plugin.UserDataRequest) (string, error) {
 		ProviderSpec:     pconfig,
 		OSConfig:         centosConfig,
 		KubeletVersion:   kubeletVersion.String(),
+		DockerVersion:    dockerVersion,
 		ServerAddr:       serverAddr,
 		Kubeconfig:       kubeconfigString,
 		KubernetesCACert: kubernetesCACert,
@@ -188,12 +195,13 @@ write_files:
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
 {{- /*	Due to DNF modules we have to do this on docker-ce repo
 		More info at: https://bugzilla.redhat.com/show_bug.cgi?id=1756473 */}}
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
 {{- /* We need to explicitly specify docker-ce and docker-ce-cli to the same version.
 	See: https://github.com/docker/cli/issues/2533 */}}
 
-    DOCKER_VERSION='18.09.9-3.el7'
+    DOCKER_VERSION='{{ .DockerVersion }}'
     yum install -y docker-ce-${DOCKER_VERSION} \
       docker-ce-cli-${DOCKER_VERSION} \
       ebtables \
@@ -254,7 +262,7 @@ write_files:
 
 - path: "/etc/kubernetes/kubelet.conf"
   content: |
-{{ kubeletConfiguration "cluster.local" .DNSIPs | indent 4 }}
+{{ kubeletConfiguration "cluster.local" .DNSIPs .KubeletFeatureGates | indent 4 }}
 
 - path: "/etc/kubernetes/pki/ca.crt"
   content: |
diff --git a/pkg/userdata/coreos/provider.go b/pkg/userdata/coreos/provider.go
index aa3e202d..43eb53bc 100644
--- a/pkg/userdata/coreos/provider.go
+++ b/pkg/userdata/coreos/provider.go
@@ -296,7 +296,7 @@ storage:
       mode: 0644
       contents:
         inline: |
-{{ kubeletConfiguration "cluster.local" .DNSIPs | indent 10 }}
+{{ kubeletConfiguration "cluster.local" .DNSIPs .KubeletFeatureGates | indent 10 }}
 
     - path: /opt/load-kernel-modules.sh
       filesystem: root
diff --git a/pkg/userdata/flatcar/provider.go b/pkg/userdata/flatcar/provider.go
index 0d991670..37b0a8b7 100644
--- a/pkg/userdata/flatcar/provider.go
+++ b/pkg/userdata/flatcar/provider.go
@@ -304,7 +304,7 @@ storage:
       mode: 0644
       contents:
         inline: |
-{{ kubeletConfiguration "cluster.local" .DNSIPs | indent 10 }}
+{{ kubeletConfiguration "cluster.local" .DNSIPs .KubeletFeatureGates | indent 10 }}
 
     - path: /opt/load-kernel-modules.sh
       filesystem: root
@@ -395,6 +395,24 @@ storage:
           PasswordAuthentication no
           ChallengeResponseAuthentication no
 
+{{- if not .FlatcarConfig.DisableAutoUpdate }}
+    - path: "/etc/polkit-1/rules.d/60-noreboot_norestart.rules"
+      filesystem: root
+      mode: 0644
+      contents:
+        inline: |
+          polkit.addRule(function(action, subject) {
+              if (action.id == "org.freedesktop.login1.reboot" ||
+                  action.id == "org.freedesktop.login1.reboot-multiple-sessions") {
+                  if (subject.user == "core") {
+                      return polkit.Result.YES;
+                  } else {
+                      return polkit.Result.AUTH_ADMIN;
+                  }
+              }
+          });
+{{- end }}
+
     - path: /etc/docker/daemon.json
       filesystem: root
       mode: 0644
diff --git a/pkg/userdata/helper/download_binaries_script.go b/pkg/userdata/helper/download_binaries_script.go
index c2befe1e..ceb0ae2c 100644
--- a/pkg/userdata/helper/download_binaries_script.go
+++ b/pkg/userdata/helper/download_binaries_script.go
@@ -99,7 +99,7 @@ mkdir -p /opt/cni/bin
 
 {{- /* # cni */}}
 if [ ! -f /opt/cni/bin/loopback ]; then
-    curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz | tar -xvzC /opt/cni/bin -f -
+    curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.7/cni-plugins-linux-amd64-v0.8.7.tgz | tar -xvzC /opt/cni/bin -f -
 fi
 
 {{- if .DownloadKubelet }}
@@ -125,7 +125,7 @@ func SafeDownloadBinariesScript(kubeVersion string) (string, error) {
 		return "", fmt.Errorf("failed to parse download-binaries template: %v", err)
 	}
 
-	const CNIVersion = "v0.8.6"
+	const CNIVersion = "v0.8.7"
 
 	// force v in case if it's not there
 	if !strings.HasPrefix(kubeVersion, "v") {
diff --git a/pkg/userdata/helper/helper.go b/pkg/userdata/helper/helper.go
index 399c2b12..d84dbba6 100644
--- a/pkg/userdata/helper/helper.go
+++ b/pkg/userdata/helper/helper.go
@@ -21,6 +21,8 @@ import (
 	"fmt"
 	"strings"
 
+	"github.com/Masterminds/semver"
+
 	"k8s.io/client-go/tools/clientcmd"
 	clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
 )
@@ -132,6 +134,38 @@ func DockerConfig(insecureRegistries, registryMirrors []string) (string, error)
 	return string(b), err
 }
 
+// DockerVersionApt returns Docker version to be installed on instances using apt (Ubuntu).
+func DockerVersionApt(kubernetesVersion *semver.Version) (string, error) {
+	if kubernetesVersion == nil {
+		return "", fmt.Errorf("invalid kubernetes version")
+	}
+
+	lessThen117, _ := semver.NewConstraint("< 1.17")
+
+	if lessThen117.Check(kubernetesVersion) {
+		return "5:18.09.9~3-0~ubuntu-bionic", nil
+	}
+
+	// return default
+	return "5:19.03.12~3-0~ubuntu-bionic", nil
+}
+
+// DockerVersionYum returns Docker version to be installed on instances using yum (CentOS/RHEL).
+func DockerVersionYum(kubernetesVersion *semver.Version) (string, error) {
+	if kubernetesVersion == nil {
+		return "", fmt.Errorf("invalid kubernetes version")
+	}
+
+	lessThen117, _ := semver.NewConstraint("< 1.17")
+
+	if lessThen117.Check(kubernetesVersion) {
+		return "18.09.9-3.el7", nil
+	}
+
+	// return default
+	return "19.03.12-3.el7", nil
+}
+
 func ProxyEnvironment(proxy, noProxy string) string {
 	return fmt.Sprintf(`HTTP_PROXY=%s
 http_proxy=%s
diff --git a/pkg/userdata/helper/kubelet.go b/pkg/userdata/helper/kubelet.go
index 89f4055d..779760c7 100644
--- a/pkg/userdata/helper/kubelet.go
+++ b/pkg/userdata/helper/kubelet.go
@@ -137,7 +137,7 @@ func KubeletSystemdUnit(kubeletVersion, cloudProvider, hostname string, dnsIPs [
 }
 
 // kubeletConfiguration returns marshaled kubelet.config.k8s.io/v1beta1 KubeletConfiguration
-func kubeletConfiguration(clusterDomain string, clusterDNS []net.IP) (string, error) {
+func kubeletConfiguration(clusterDomain string, clusterDNS []net.IP, featureGates map[string]bool) (string, error) {
 	clusterDNSstr := make([]string, 0, len(clusterDNS))
 	for _, ip := range clusterDNS {
 		clusterDNSstr = append(clusterDNSstr, ip.String())
@@ -165,7 +165,7 @@ func kubeletConfiguration(clusterDomain string, clusterDNS []net.IP) (string, er
 		CgroupDriver:          "systemd",
 		ClusterDNS:            clusterDNSstr,
 		ClusterDomain:         clusterDomain,
-		FeatureGates:          map[string]bool{"RotateKubeletServerCertificate": true},
+		FeatureGates:          featureGates,
 		ProtectKernelDefaults: true,
 		ReadOnlyPort:          0,
 		RotateCertificates:    true,
diff --git a/pkg/userdata/rhel/provider.go b/pkg/userdata/rhel/provider.go
index b0ba746f..b64755b5 100644
--- a/pkg/userdata/rhel/provider.go
+++ b/pkg/userdata/rhel/provider.go
@@ -48,6 +48,11 @@ func (p Provider) UserData(req plugin.UserDataRequest) (string, error) {
 		return "", fmt.Errorf("invalid kubelet version: %v", err)
 	}
 
+	dockerVersion, err := userdatahelper.DockerVersionYum(kubeletVersion)
+	if err != nil {
+		return "", fmt.Errorf("invalid docker version: %v", err)
+	}
+
 	pconfig, err := providerconfigtypes.GetConfig(req.MachineSpec.ProviderSpec)
 	if err != nil {
 		return "", fmt.Errorf("failed to get provider config: %v", err)
@@ -86,6 +91,7 @@ func (p Provider) UserData(req plugin.UserDataRequest) (string, error) {
 		ProviderSpec     *providerconfigtypes.Config
 		OSConfig         *Config
 		KubeletVersion   string
+		DockerVersion    string
 		ServerAddr       string
 		Kubeconfig       string
 		KubernetesCACert string
@@ -95,6 +101,7 @@ func (p Provider) UserData(req plugin.UserDataRequest) (string, error) {
 		ProviderSpec:     pconfig,
 		OSConfig:         rhelConfig,
 		KubeletVersion:   kubeletVersion.String(),
+		DockerVersion:    dockerVersion,
 		ServerAddr:       serverAddr,
 		Kubeconfig:       kubeconfigString,
 		KubernetesCACert: kubernetesCACert,
@@ -188,9 +195,10 @@ write_files:
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
 {{- /*	Due to DNF modules we have to do this on docker-ce repo
 		More info at: https://bugzilla.redhat.com/show_bug.cgi?id=1756473 */}}
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
-    DOCKER_VERSION='18.09.9-3.el7'
+    DOCKER_VERSION='{{ .DockerVersion }}'
     yum install -y docker-ce-${DOCKER_VERSION} \
       docker-ce-cli-${DOCKER_VERSION} \
       ebtables \
@@ -252,7 +260,7 @@ write_files:
 
 - path: "/etc/kubernetes/kubelet.conf"
   content: |
-{{ kubeletConfiguration "cluster.local" .DNSIPs | indent 4 }}
+{{ kubeletConfiguration "cluster.local" .DNSIPs .KubeletFeatureGates | indent 4 }}
 
 - path: "/etc/kubernetes/pki/ca.crt"
   content: |
diff --git a/pkg/userdata/sles/provider.go b/pkg/userdata/sles/provider.go
index 34fa5b11..9e7fc533 100644
--- a/pkg/userdata/sles/provider.go
+++ b/pkg/userdata/sles/provider.go
@@ -241,7 +241,7 @@ write_files:
 
 - path: "/etc/kubernetes/kubelet.conf"
   content: |
-{{ kubeletConfiguration "cluster.local" .DNSIPs | indent 4 }}
+{{ kubeletConfiguration "cluster.local" .DNSIPs .KubeletFeatureGates | indent 4 }}
 
 - path: "/etc/profile.d/opt-bin-path.sh"
   permissions: "0644"
diff --git a/pkg/userdata/ubuntu/provider.go b/pkg/userdata/ubuntu/provider.go
index 4186b1ad..9fcc6ca8 100644
--- a/pkg/userdata/ubuntu/provider.go
+++ b/pkg/userdata/ubuntu/provider.go
@@ -49,6 +49,11 @@ func (p Provider) UserData(req plugin.UserDataRequest) (string, error) {
 		return "", fmt.Errorf("invalid kubelet version: %v", err)
 	}
 
+	dockerVersion, err := userdatahelper.DockerVersionApt(kubeletVersion)
+	if err != nil {
+		return "", fmt.Errorf("invalid docker version: %v", err)
+	}
+
 	pconfig, err := providerconfigtypes.GetConfig(req.MachineSpec.ProviderSpec)
 	if err != nil {
 		return "", fmt.Errorf("failed to get providerSpec: %v", err)
@@ -88,6 +93,7 @@ func (p Provider) UserData(req plugin.UserDataRequest) (string, error) {
 		OSConfig         *Config
 		ServerAddr       string
 		KubeletVersion   string
+		DockerVersion    string
 		Kubeconfig       string
 		KubernetesCACert string
 		NodeIPScript     string
@@ -97,6 +103,7 @@ func (p Provider) UserData(req plugin.UserDataRequest) (string, error) {
 		OSConfig:         ubuntuConfig,
 		ServerAddr:       serverAddr,
 		KubeletVersion:   kubeletVersion.String(),
+		DockerVersion:    dockerVersion,
 		Kubeconfig:       kubeconfigString,
 		KubernetesCACert: kubernetesCACert,
 		NodeIPScript:     userdatahelper.SetupNodeIPEnvScript(),
@@ -251,7 +258,7 @@ write_files:
 {{- /* We need to explicitly specify docker-ce and docker-ce-cli to the same version.
 	See: https://github.com/docker/cli/issues/2533 */}}
 
-    DOCKER_VERSION='5:18.09.9~3-0~ubuntu-bionic'
+    DOCKER_VERSION='{{ .DockerVersion }}'
     DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y \
       curl \
       ca-certificates \
@@ -359,7 +366,7 @@ write_files:
 
 - path: "/etc/kubernetes/kubelet.conf"
   content: |
-{{ kubeletConfiguration "cluster.local" .DNSIPs | indent 4 }}
+{{ kubeletConfiguration "cluster.local" .DNSIPs .KubeletFeatureGates | indent 4 }}
 
 - path: /etc/docker/daemon.json
   permissions: "0644"


Test patch:
diff --git a/pkg/controller/machine/machine_test.go b/pkg/controller/machine/machine_test.go
index 42661ba0..799f8d98 100644
--- a/pkg/controller/machine/machine_test.go
+++ b/pkg/controller/machine/machine_test.go
@@ -26,6 +26,7 @@ import (
 
 	clusterv1alpha1 "github.com/kubermatic/machine-controller/pkg/apis/cluster/v1alpha1"
 	"github.com/kubermatic/machine-controller/pkg/cloudprovider/instance"
+	cloudprovidertypes "github.com/kubermatic/machine-controller/pkg/cloudprovider/types"
 	providerconfigtypes "github.com/kubermatic/machine-controller/pkg/providerconfig/types"
 
 	corev1 "k8s.io/api/core/v1"
@@ -36,6 +37,7 @@ import (
 	"k8s.io/client-go/kubernetes/scheme"
 	"k8s.io/client-go/tools/record"
 	"k8s.io/klog"
+	ctrlruntimeclient "sigs.k8s.io/controller-runtime/pkg/client"
 	ctrlruntimefake "sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
@@ -99,7 +101,8 @@ func TestController_GetNode(t *testing.T) {
 	node1 := getTestNode("1", "aws")
 	node2 := getTestNode("2", "openstack")
 	node3 := getTestNode("3", "")
-	nodeList := []*corev1.Node{&node1, &node2, &node3}
+	node4 := getTestNode("4", "hetzner")
+	nodeList := []*corev1.Node{&node1, &node2, &node3, &node4}
 
 	tests := []struct {
 		name     string
@@ -149,6 +152,38 @@ func TestController_GetNode(t *testing.T) {
 			err:      nil,
 			instance: &fakeInstance{id: "3", addresses: map[string]corev1.NodeAddressType{"172.16.1.3": corev1.NodeInternalIP}},
 		},
+		{
+			name:     "hetzner node found by internal ip",
+			provider: "hetzner",
+			resNode:  &node3,
+			exists:   true,
+			err:      nil,
+			instance: &fakeInstance{id: "3", name: "node3", addresses: map[string]corev1.NodeAddressType{"192.168.1.3": corev1.NodeInternalIP}},
+		},
+		{
+			name:     "hetzner node found by external ip",
+			provider: "hetzner",
+			resNode:  &node3,
+			exists:   true,
+			err:      nil,
+			instance: &fakeInstance{id: "3", name: "node3", addresses: map[string]corev1.NodeAddressType{"172.16.1.3": corev1.NodeExternalIP}},
+		},
+		{
+			name:     "hetzner node not found - node and instance names mismatch",
+			provider: "hetzner",
+			resNode:  nil,
+			exists:   false,
+			err:      nil,
+			instance: &fakeInstance{id: "3", name: "instance3", addresses: map[string]corev1.NodeAddressType{"192.168.1.3": corev1.NodeInternalIP}},
+		},
+		{
+			name:     "hetzner node found by provider id",
+			provider: "hetzner",
+			resNode:  &node4,
+			exists:   true,
+			err:      nil,
+			instance: &fakeInstance{id: "4", addresses: map[string]corev1.NodeAddressType{"": ""}},
+		},
 	}
 
 	for _, test := range tests {
@@ -427,3 +462,156 @@ func TestControllerShouldEvict(t *testing.T) {
 		})
 	}
 }
+
+func TestControllerDeleteNodeForMachine(t *testing.T) {
+	machineUID := types.UID("test-1")
+
+	tests := []struct {
+		name             string
+		machine          *clusterv1alpha1.Machine
+		nodes            []runtime.Object
+		err              error
+		shouldDeleteNode string
+	}{
+		{
+			name: "delete node by nodeRef",
+			machine: &clusterv1alpha1.Machine{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:       "machine-1",
+					Finalizers: []string{"machine-node-delete-finalizer"},
+				},
+				Status: clusterv1alpha1.MachineStatus{
+					NodeRef: &corev1.ObjectReference{Name: "node-1"},
+				},
+			},
+			nodes: []runtime.Object{&corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "node-0",
+				}}, &corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "node-1",
+				}},
+			},
+			err:              nil,
+			shouldDeleteNode: "node-1",
+		},
+		{
+			name: "delete node by NodeOwner label",
+			machine: &clusterv1alpha1.Machine{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:       "machine-1",
+					Finalizers: []string{"machine-node-delete-finalizer"},
+					UID:        machineUID,
+				},
+				Status: clusterv1alpha1.MachineStatus{},
+			},
+			nodes: []runtime.Object{
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node-0",
+						Labels: map[string]string{
+							NodeOwnerLabelName: string(machineUID),
+						},
+					},
+				},
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node-1",
+					},
+				},
+			},
+			err:              nil,
+			shouldDeleteNode: "node-0",
+		},
+		{
+			name: "no node should be deleted",
+			machine: &clusterv1alpha1.Machine{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:       "machine-1",
+					Finalizers: []string{"machine-node-delete-finalizer"},
+					UID:        machineUID,
+				},
+				Status: clusterv1alpha1.MachineStatus{},
+			},
+			nodes: []runtime.Object{
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node-0",
+					},
+				},
+				&corev1.Node{
+					ObjectMeta: metav1.ObjectMeta{
+						Name: "node-1",
+					},
+				},
+			},
+			err:              nil,
+			shouldDeleteNode: "",
+		},
+		{
+			name: "node referred by nodeRef doesn't exist",
+			machine: &clusterv1alpha1.Machine{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:       "machine-1",
+					Finalizers: []string{"machine-node-delete-finalizer"},
+				},
+				Status: clusterv1alpha1.MachineStatus{
+					NodeRef: &corev1.ObjectReference{Name: "node-1"},
+				},
+			},
+			nodes: []runtime.Object{&corev1.Node{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: "node-0",
+				}},
+			},
+			err:              nil,
+			shouldDeleteNode: "",
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			objects := []runtime.Object{test.machine}
+			objects = append(objects, test.nodes...)
+
+			client := ctrlruntimefake.NewFakeClient(objects...)
+
+			ctx := context.TODO()
+			providerData := &cloudprovidertypes.ProviderData{
+				Ctx:    ctx,
+				Update: cloudprovidertypes.GetMachineUpdater(ctx, client),
+				Client: client,
+			}
+
+			reconciler := &Reconciler{
+				client:       client,
+				recorder:     &record.FakeRecorder{},
+				providerData: providerData,
+			}
+
+			err := reconciler.deleteNodeForMachine(test.machine)
+			if diff := deep.Equal(err, test.err); diff != nil {
+				t.Errorf("expected to get %v instead got: %v", test.err, err)
+			}
+			if err != nil {
+				return
+			}
+
+			if test.shouldDeleteNode != "" {
+				err = client.Get(ctx, types.NamespacedName{Name: test.shouldDeleteNode}, &corev1.Node{})
+				if !kerrors.IsNotFound(err) {
+					t.Errorf("expected node %q to be deleted, but got: %v", test.shouldDeleteNode, err)
+				}
+			} else {
+				nodes := &corev1.NodeList{}
+				err = client.List(ctx, nodes, &ctrlruntimeclient.ListOptions{})
+				if err != nil {
+					t.Errorf("error listing nodes: %v", err)
+				}
+				if len(test.nodes) != len(nodes.Items) {
+					t.Errorf("expected %d nodes, but got %d", len(test.nodes), len(nodes.Items))
+				}
+			}
+		})
+	}
+}
diff --git a/pkg/userdata/centos/provider_test.go b/pkg/userdata/centos/provider_test.go
index 52e3ba3f..639e35b4 100644
--- a/pkg/userdata/centos/provider_test.go
+++ b/pkg/userdata/centos/provider_test.go
@@ -194,6 +194,10 @@ func TestUserDataGeneration(t *testing.T) {
 	}
 	provider := Provider{}
 
+	kubeletFeatureGates := map[string]bool{
+		"RotateKubeletServerCertificate": true,
+	}
+
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			emtpyProviderSpec := clusterv1alpha1.ProviderSpec{
@@ -227,6 +231,7 @@ func TestUserDataGeneration(t *testing.T) {
 				InsecureRegistries:    test.insecureRegistries,
 				RegistryMirrors:       test.registryMirrors,
 				PauseImage:            test.pauseImage,
+				KubeletFeatureGates:   kubeletFeatureGates,
 			}
 			s, err := provider.UserData(req)
 			if err != nil {
diff --git a/pkg/userdata/centos/testdata/kubelet-v1.15-aws.yaml b/pkg/userdata/centos/testdata/kubelet-v1.15-aws.yaml
index 3750c79b..b400b035 100644
--- a/pkg/userdata/centos/testdata/kubelet-v1.15-aws.yaml
+++ b/pkg/userdata/centos/testdata/kubelet-v1.15-aws.yaml
@@ -69,6 +69,7 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
     DOCKER_VERSION='18.09.9-3.el7'
@@ -90,7 +91,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/centos/testdata/kubelet-v1.16-aws.yaml b/pkg/userdata/centos/testdata/kubelet-v1.16-aws.yaml
index 7072aca7..4d11556d 100644
--- a/pkg/userdata/centos/testdata/kubelet-v1.16-aws.yaml
+++ b/pkg/userdata/centos/testdata/kubelet-v1.16-aws.yaml
@@ -69,6 +69,7 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
     DOCKER_VERSION='18.09.9-3.el7'
@@ -90,7 +91,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/centos/testdata/kubelet-v1.17-aws-external.yaml b/pkg/userdata/centos/testdata/kubelet-v1.17-aws-external.yaml
index a8496e83..e9d7f35f 100644
--- a/pkg/userdata/centos/testdata/kubelet-v1.17-aws-external.yaml
+++ b/pkg/userdata/centos/testdata/kubelet-v1.17-aws-external.yaml
@@ -69,9 +69,10 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
-    DOCKER_VERSION='18.09.9-3.el7'
+    DOCKER_VERSION='19.03.12-3.el7'
     yum install -y docker-ce-${DOCKER_VERSION} \
       docker-ce-cli-${DOCKER_VERSION} \
       ebtables \
@@ -90,7 +91,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/centos/testdata/kubelet-v1.17-aws.yaml b/pkg/userdata/centos/testdata/kubelet-v1.17-aws.yaml
index e6c6c39d..c9ef5921 100644
--- a/pkg/userdata/centos/testdata/kubelet-v1.17-aws.yaml
+++ b/pkg/userdata/centos/testdata/kubelet-v1.17-aws.yaml
@@ -69,9 +69,10 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
-    DOCKER_VERSION='18.09.9-3.el7'
+    DOCKER_VERSION='19.03.12-3.el7'
     yum install -y docker-ce-${DOCKER_VERSION} \
       docker-ce-cli-${DOCKER_VERSION} \
       ebtables \
@@ -90,7 +91,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/centos/testdata/kubelet-v1.17-vsphere-mirrors.yaml b/pkg/userdata/centos/testdata/kubelet-v1.17-vsphere-mirrors.yaml
index f31df455..5c83eeb5 100644
--- a/pkg/userdata/centos/testdata/kubelet-v1.17-vsphere-mirrors.yaml
+++ b/pkg/userdata/centos/testdata/kubelet-v1.17-vsphere-mirrors.yaml
@@ -81,9 +81,10 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
-    DOCKER_VERSION='18.09.9-3.el7'
+    DOCKER_VERSION='19.03.12-3.el7'
     yum install -y docker-ce-${DOCKER_VERSION} \
       docker-ce-cli-${DOCKER_VERSION} \
       ebtables \
@@ -103,7 +104,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/centos/testdata/kubelet-v1.17-vsphere-proxy.yaml b/pkg/userdata/centos/testdata/kubelet-v1.17-vsphere-proxy.yaml
index 821b5bfd..dacab955 100644
--- a/pkg/userdata/centos/testdata/kubelet-v1.17-vsphere-proxy.yaml
+++ b/pkg/userdata/centos/testdata/kubelet-v1.17-vsphere-proxy.yaml
@@ -81,9 +81,10 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
-    DOCKER_VERSION='18.09.9-3.el7'
+    DOCKER_VERSION='19.03.12-3.el7'
     yum install -y docker-ce-${DOCKER_VERSION} \
       docker-ce-cli-${DOCKER_VERSION} \
       ebtables \
@@ -103,7 +104,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/centos/testdata/kubelet-v1.17-vsphere.yaml b/pkg/userdata/centos/testdata/kubelet-v1.17-vsphere.yaml
index 58cd02b5..f3a6e708 100644
--- a/pkg/userdata/centos/testdata/kubelet-v1.17-vsphere.yaml
+++ b/pkg/userdata/centos/testdata/kubelet-v1.17-vsphere.yaml
@@ -73,9 +73,10 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
-    DOCKER_VERSION='18.09.9-3.el7'
+    DOCKER_VERSION='19.03.12-3.el7'
     yum install -y docker-ce-${DOCKER_VERSION} \
       docker-ce-cli-${DOCKER_VERSION} \
       ebtables \
@@ -95,7 +96,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/coreos/provider_test.go b/pkg/userdata/coreos/provider_test.go
index 668a142b..e28e624d 100644
--- a/pkg/userdata/coreos/provider_test.go
+++ b/pkg/userdata/coreos/provider_test.go
@@ -437,6 +437,10 @@ func TestUserDataGeneration(t *testing.T) {
 		},
 	}
 
+	kubeletFeatureGates := map[string]bool{
+		"RotateKubeletServerCertificate": true,
+	}
+
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			rProviderSpec := test.providerSpec
@@ -477,6 +481,7 @@ func TestUserDataGeneration(t *testing.T) {
 				RegistryMirrors:       test.registryMirrors,
 				PauseImage:            test.pauseImage,
 				HyperkubeImage:        test.hyperkubeImage,
+				KubeletFeatureGates:   kubeletFeatureGates,
 			}
 
 			s, err := provider.UserData(req)
diff --git a/pkg/userdata/coreos/testdata/auto-update-openstack-kubelet-v-version-prefix.yaml b/pkg/userdata/coreos/testdata/auto-update-openstack-kubelet-v-version-prefix.yaml
index 873752e3..d6a7cd2b 100644
--- a/pkg/userdata/coreos/testdata/auto-update-openstack-kubelet-v-version-prefix.yaml
+++ b/pkg/userdata/coreos/testdata/auto-update-openstack-kubelet-v-version-prefix.yaml
@@ -400,7 +400,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/coreos/testdata/v1.15.0-vsphere.yaml b/pkg/userdata/coreos/testdata/v1.15.0-vsphere.yaml
index 9a0eafd2..c045454c 100644
--- a/pkg/userdata/coreos/testdata/v1.15.0-vsphere.yaml
+++ b/pkg/userdata/coreos/testdata/v1.15.0-vsphere.yaml
@@ -397,7 +397,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/coreos/testdata/v1.16.2-vsphere-static-ipconfig.yaml b/pkg/userdata/coreos/testdata/v1.16.2-vsphere-static-ipconfig.yaml
index aa77d9e1..07d9794d 100644
--- a/pkg/userdata/coreos/testdata/v1.16.2-vsphere-static-ipconfig.yaml
+++ b/pkg/userdata/coreos/testdata/v1.16.2-vsphere-static-ipconfig.yaml
@@ -419,7 +419,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/coreos/testdata/v1.17.0-vsphere-mirrors.yaml b/pkg/userdata/coreos/testdata/v1.17.0-vsphere-mirrors.yaml
index 4f0709cb..234441b7 100644
--- a/pkg/userdata/coreos/testdata/v1.17.0-vsphere-mirrors.yaml
+++ b/pkg/userdata/coreos/testdata/v1.17.0-vsphere-mirrors.yaml
@@ -438,7 +438,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/coreos/testdata/v1.17.0-vsphere-overwrite-cloudconfig.yaml b/pkg/userdata/coreos/testdata/v1.17.0-vsphere-overwrite-cloudconfig.yaml
index 7f774375..546c4d9f 100644
--- a/pkg/userdata/coreos/testdata/v1.17.0-vsphere-overwrite-cloudconfig.yaml
+++ b/pkg/userdata/coreos/testdata/v1.17.0-vsphere-overwrite-cloudconfig.yaml
@@ -422,7 +422,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/coreos/testdata/v1.17.0-vsphere-proxy.yaml b/pkg/userdata/coreos/testdata/v1.17.0-vsphere-proxy.yaml
index e277de5f..42634572 100644
--- a/pkg/userdata/coreos/testdata/v1.17.0-vsphere-proxy.yaml
+++ b/pkg/userdata/coreos/testdata/v1.17.0-vsphere-proxy.yaml
@@ -438,7 +438,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/coreos/testdata/v1.17.0.yaml b/pkg/userdata/coreos/testdata/v1.17.0.yaml
index 6bf602e1..92f7d4fa 100644
--- a/pkg/userdata/coreos/testdata/v1.17.0.yaml
+++ b/pkg/userdata/coreos/testdata/v1.17.0.yaml
@@ -420,7 +420,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/coreos/testdata/v1.17.2-disable-auto-update-aws-external.yaml b/pkg/userdata/coreos/testdata/v1.17.2-disable-auto-update-aws-external.yaml
index d4a77960..17753eef 100644
--- a/pkg/userdata/coreos/testdata/v1.17.2-disable-auto-update-aws-external.yaml
+++ b/pkg/userdata/coreos/testdata/v1.17.2-disable-auto-update-aws-external.yaml
@@ -397,7 +397,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/coreos/testdata/v1.17.2-disable-auto-update-aws.yaml b/pkg/userdata/coreos/testdata/v1.17.2-disable-auto-update-aws.yaml
index 289704fb..8922209a 100644
--- a/pkg/userdata/coreos/testdata/v1.17.2-disable-auto-update-aws.yaml
+++ b/pkg/userdata/coreos/testdata/v1.17.2-disable-auto-update-aws.yaml
@@ -398,7 +398,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/coreos/testdata/v1.17.2-disable-locksmith-aws.yaml b/pkg/userdata/coreos/testdata/v1.17.2-disable-locksmith-aws.yaml
index 45b09873..a5658cbb 100644
--- a/pkg/userdata/coreos/testdata/v1.17.2-disable-locksmith-aws.yaml
+++ b/pkg/userdata/coreos/testdata/v1.17.2-disable-locksmith-aws.yaml
@@ -396,7 +396,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/coreos/testdata/v1.17.2-disable-update-engine-aws.yaml b/pkg/userdata/coreos/testdata/v1.17.2-disable-update-engine-aws.yaml
index 6ef93e35..01ce88ad 100644
--- a/pkg/userdata/coreos/testdata/v1.17.2-disable-update-engine-aws.yaml
+++ b/pkg/userdata/coreos/testdata/v1.17.2-disable-update-engine-aws.yaml
@@ -396,7 +396,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/coreos/testdata/v1.17.3-auto-update-openstack-multiple-dns.yaml b/pkg/userdata/coreos/testdata/v1.17.3-auto-update-openstack-multiple-dns.yaml
index ac9061f5..d58a3839 100644
--- a/pkg/userdata/coreos/testdata/v1.17.3-auto-update-openstack-multiple-dns.yaml
+++ b/pkg/userdata/coreos/testdata/v1.17.3-auto-update-openstack-multiple-dns.yaml
@@ -402,7 +402,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/flatcar/provider_test.go b/pkg/userdata/flatcar/provider_test.go
index e4c5d1c7..b8625898 100644
--- a/pkg/userdata/flatcar/provider_test.go
+++ b/pkg/userdata/flatcar/provider_test.go
@@ -81,6 +81,10 @@ kPe6XoSbiLm/kxk32T0=
 			},
 		},
 	}
+
+	kubeletFeatureGates = map[string]bool{
+		"RotateKubeletServerCertificate": true,
+	}
 )
 
 // fakeCloudConfigProvider simulates cloud config provider for test.
@@ -223,6 +227,7 @@ func TestUserDataGeneration(t *testing.T) {
 				PauseImage:            test.pauseImage,
 				HyperkubeImage:        test.hyperkubeImage,
 				KubeletRepository:     test.kubeletImage,
+				KubeletFeatureGates:   kubeletFeatureGates,
 			}
 
 			s, err := provider.UserData(req)
diff --git a/pkg/userdata/flatcar/testdata/v1.17.0.yaml b/pkg/userdata/flatcar/testdata/v1.17.0.yaml
index 20722b71..aea27233 100644
--- a/pkg/userdata/flatcar/testdata/v1.17.0.yaml
+++ b/pkg/userdata/flatcar/testdata/v1.17.0.yaml
@@ -427,7 +427,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/flatcar/testdata/v1.18.0.yaml b/pkg/userdata/flatcar/testdata/v1.18.0.yaml
index 3ef20d04..a33772bc 100644
--- a/pkg/userdata/flatcar/testdata/v1.18.0.yaml
+++ b/pkg/userdata/flatcar/testdata/v1.18.0.yaml
@@ -427,7 +427,7 @@ storage:
           cni_bin_dir=/opt/cni/bin
           mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
           arch=${HOST_ARCH:-amd64}
-          CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+          CNI_VERSION="${CNI_VERSION:-v0.8.7}"
           cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
           cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
           curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/helper/testdata/download_binaries_v1.10.0.golden b/pkg/userdata/helper/testdata/download_binaries_v1.10.0.golden
index 438e56c4..c545d612 100644
--- a/pkg/userdata/helper/testdata/download_binaries_v1.10.0.golden
+++ b/pkg/userdata/helper/testdata/download_binaries_v1.10.0.golden
@@ -4,7 +4,7 @@ mkdir -p /etc/kubernetes/manifests
 mkdir -p /etc/cni/net.d
 mkdir -p /opt/cni/bin
 if [ ! -f /opt/cni/bin/loopback ]; then
-    curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz | tar -xvzC /opt/cni/bin -f -
+    curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.7/cni-plugins-linux-amd64-v0.8.7.tgz | tar -xvzC /opt/cni/bin -f -
 fi
 if [ ! -f /opt/bin/kubelet ]; then
     curl -Lfo /opt/bin/kubelet https://storage.googleapis.com/kubernetes-release/release/v1.10.0/bin/linux/amd64/kubelet
diff --git a/pkg/userdata/helper/testdata/download_binaries_v1.11.0-rc.2.golden b/pkg/userdata/helper/testdata/download_binaries_v1.11.0-rc.2.golden
index df95c1cb..0ff34fe7 100644
--- a/pkg/userdata/helper/testdata/download_binaries_v1.11.0-rc.2.golden
+++ b/pkg/userdata/helper/testdata/download_binaries_v1.11.0-rc.2.golden
@@ -4,7 +4,7 @@ mkdir -p /etc/kubernetes/manifests
 mkdir -p /etc/cni/net.d
 mkdir -p /opt/cni/bin
 if [ ! -f /opt/cni/bin/loopback ]; then
-    curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz | tar -xvzC /opt/cni/bin -f -
+    curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.7/cni-plugins-linux-amd64-v0.8.7.tgz | tar -xvzC /opt/cni/bin -f -
 fi
 if [ ! -f /opt/bin/kubelet ]; then
     curl -Lfo /opt/bin/kubelet https://storage.googleapis.com/kubernetes-release/release/v1.11.0-rc.2/bin/linux/amd64/kubelet
diff --git a/pkg/userdata/helper/testdata/download_binaries_v1.11.0.golden b/pkg/userdata/helper/testdata/download_binaries_v1.11.0.golden
index 3a79c1c4..2c26e41f 100644
--- a/pkg/userdata/helper/testdata/download_binaries_v1.11.0.golden
+++ b/pkg/userdata/helper/testdata/download_binaries_v1.11.0.golden
@@ -4,7 +4,7 @@ mkdir -p /etc/kubernetes/manifests
 mkdir -p /etc/cni/net.d
 mkdir -p /opt/cni/bin
 if [ ! -f /opt/cni/bin/loopback ]; then
-    curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz | tar -xvzC /opt/cni/bin -f -
+    curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.7/cni-plugins-linux-amd64-v0.8.7.tgz | tar -xvzC /opt/cni/bin -f -
 fi
 if [ ! -f /opt/bin/kubelet ]; then
     curl -Lfo /opt/bin/kubelet https://storage.googleapis.com/kubernetes-release/release/v1.11.0/bin/linux/amd64/kubelet
diff --git a/pkg/userdata/helper/testdata/download_binaries_v1.11.3.golden b/pkg/userdata/helper/testdata/download_binaries_v1.11.3.golden
index 65fd6c30..ee0ac616 100644
--- a/pkg/userdata/helper/testdata/download_binaries_v1.11.3.golden
+++ b/pkg/userdata/helper/testdata/download_binaries_v1.11.3.golden
@@ -4,7 +4,7 @@ mkdir -p /etc/kubernetes/manifests
 mkdir -p /etc/cni/net.d
 mkdir -p /opt/cni/bin
 if [ ! -f /opt/cni/bin/loopback ]; then
-    curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz | tar -xvzC /opt/cni/bin -f -
+    curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.7/cni-plugins-linux-amd64-v0.8.7.tgz | tar -xvzC /opt/cni/bin -f -
 fi
 if [ ! -f /opt/bin/kubelet ]; then
     curl -Lfo /opt/bin/kubelet https://storage.googleapis.com/kubernetes-release/release/v1.11.3/bin/linux/amd64/kubelet
diff --git a/pkg/userdata/helper/testdata/download_binaries_v1.12.0.golden b/pkg/userdata/helper/testdata/download_binaries_v1.12.0.golden
index d8602524..d012152e 100644
--- a/pkg/userdata/helper/testdata/download_binaries_v1.12.0.golden
+++ b/pkg/userdata/helper/testdata/download_binaries_v1.12.0.golden
@@ -4,7 +4,7 @@ mkdir -p /etc/kubernetes/manifests
 mkdir -p /etc/cni/net.d
 mkdir -p /opt/cni/bin
 if [ ! -f /opt/cni/bin/loopback ]; then
-    curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.6/cni-plugins-linux-amd64-v0.8.6.tgz | tar -xvzC /opt/cni/bin -f -
+    curl -L https://github.com/containernetworking/plugins/releases/download/v0.8.7/cni-plugins-linux-amd64-v0.8.7.tgz | tar -xvzC /opt/cni/bin -f -
 fi
 if [ ! -f /opt/bin/kubelet ]; then
     curl -Lfo /opt/bin/kubelet https://storage.googleapis.com/kubernetes-release/release/v1.12.0/bin/linux/amd64/kubelet
diff --git a/pkg/userdata/helper/testdata/safe_download_binaries_v1.18.2.golden b/pkg/userdata/helper/testdata/safe_download_binaries_v1.18.2.golden
index 12944093..8ae21aa9 100644
--- a/pkg/userdata/helper/testdata/safe_download_binaries_v1.18.2.golden
+++ b/pkg/userdata/helper/testdata/safe_download_binaries_v1.18.2.golden
@@ -2,7 +2,7 @@ opt_bin=/opt/bin
 cni_bin_dir=/opt/cni/bin
 mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
 arch=${HOST_ARCH:-amd64}
-CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+CNI_VERSION="${CNI_VERSION:-v0.8.7}"
 cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
 cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
 curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/rhel/provider_test.go b/pkg/userdata/rhel/provider_test.go
index b56f86b5..e4724c64 100644
--- a/pkg/userdata/rhel/provider_test.go
+++ b/pkg/userdata/rhel/provider_test.go
@@ -194,6 +194,10 @@ func TestUserDataGeneration(t *testing.T) {
 	}
 	provider := Provider{}
 
+	kubeletFeatureGates := map[string]bool{
+		"RotateKubeletServerCertificate": true,
+	}
+
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
 			emtpyProviderSpec := clusterv1alpha1.ProviderSpec{
@@ -227,6 +231,7 @@ func TestUserDataGeneration(t *testing.T) {
 				InsecureRegistries:    test.insecureRegistries,
 				RegistryMirrors:       test.registryMirrors,
 				PauseImage:            test.pauseImage,
+				KubeletFeatureGates:   kubeletFeatureGates,
 			}
 			s, err := provider.UserData(req)
 			if err != nil {
diff --git a/pkg/userdata/rhel/testdata/kubelet-v1.15-aws.yaml b/pkg/userdata/rhel/testdata/kubelet-v1.15-aws.yaml
index 9e411fb6..0575d3b1 100644
--- a/pkg/userdata/rhel/testdata/kubelet-v1.15-aws.yaml
+++ b/pkg/userdata/rhel/testdata/kubelet-v1.15-aws.yaml
@@ -69,6 +69,7 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
     DOCKER_VERSION='18.09.9-3.el7'
@@ -90,7 +91,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/rhel/testdata/kubelet-v1.16-aws.yaml b/pkg/userdata/rhel/testdata/kubelet-v1.16-aws.yaml
index 9b878d62..32d6e03c 100644
--- a/pkg/userdata/rhel/testdata/kubelet-v1.16-aws.yaml
+++ b/pkg/userdata/rhel/testdata/kubelet-v1.16-aws.yaml
@@ -69,6 +69,7 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
     DOCKER_VERSION='18.09.9-3.el7'
@@ -90,7 +91,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/rhel/testdata/kubelet-v1.17-aws-external.yaml b/pkg/userdata/rhel/testdata/kubelet-v1.17-aws-external.yaml
index 3a086ba6..06237e8b 100644
--- a/pkg/userdata/rhel/testdata/kubelet-v1.17-aws-external.yaml
+++ b/pkg/userdata/rhel/testdata/kubelet-v1.17-aws-external.yaml
@@ -69,9 +69,10 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
-    DOCKER_VERSION='18.09.9-3.el7'
+    DOCKER_VERSION='19.03.12-3.el7'
     yum install -y docker-ce-${DOCKER_VERSION} \
       docker-ce-cli-${DOCKER_VERSION} \
       ebtables \
@@ -90,7 +91,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/rhel/testdata/kubelet-v1.17-aws.yaml b/pkg/userdata/rhel/testdata/kubelet-v1.17-aws.yaml
index 4b93170e..9f25d974 100644
--- a/pkg/userdata/rhel/testdata/kubelet-v1.17-aws.yaml
+++ b/pkg/userdata/rhel/testdata/kubelet-v1.17-aws.yaml
@@ -69,9 +69,10 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
-    DOCKER_VERSION='18.09.9-3.el7'
+    DOCKER_VERSION='19.03.12-3.el7'
     yum install -y docker-ce-${DOCKER_VERSION} \
       docker-ce-cli-${DOCKER_VERSION} \
       ebtables \
@@ -90,7 +91,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/rhel/testdata/kubelet-v1.17-vsphere-mirrors.yaml b/pkg/userdata/rhel/testdata/kubelet-v1.17-vsphere-mirrors.yaml
index dea5f4a1..a4678f2e 100644
--- a/pkg/userdata/rhel/testdata/kubelet-v1.17-vsphere-mirrors.yaml
+++ b/pkg/userdata/rhel/testdata/kubelet-v1.17-vsphere-mirrors.yaml
@@ -81,9 +81,10 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
-    DOCKER_VERSION='18.09.9-3.el7'
+    DOCKER_VERSION='19.03.12-3.el7'
     yum install -y docker-ce-${DOCKER_VERSION} \
       docker-ce-cli-${DOCKER_VERSION} \
       ebtables \
@@ -103,7 +104,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/rhel/testdata/kubelet-v1.17-vsphere-proxy.yaml b/pkg/userdata/rhel/testdata/kubelet-v1.17-vsphere-proxy.yaml
index ac076fe5..92a01704 100644
--- a/pkg/userdata/rhel/testdata/kubelet-v1.17-vsphere-proxy.yaml
+++ b/pkg/userdata/rhel/testdata/kubelet-v1.17-vsphere-proxy.yaml
@@ -81,9 +81,10 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
-    DOCKER_VERSION='18.09.9-3.el7'
+    DOCKER_VERSION='19.03.12-3.el7'
     yum install -y docker-ce-${DOCKER_VERSION} \
       docker-ce-cli-${DOCKER_VERSION} \
       ebtables \
@@ -103,7 +104,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/rhel/testdata/kubelet-v1.17-vsphere.yaml b/pkg/userdata/rhel/testdata/kubelet-v1.17-vsphere.yaml
index 98dcbdff..af49b033 100644
--- a/pkg/userdata/rhel/testdata/kubelet-v1.17-vsphere.yaml
+++ b/pkg/userdata/rhel/testdata/kubelet-v1.17-vsphere.yaml
@@ -73,9 +73,10 @@ write_files:
 
     yum install -y yum-utils
     yum-config-manager --add-repo=https://download.docker.com/linux/centos/docker-ce.repo
+    sed -i 's/\$releasever/7/g' /etc/yum.repos.d/docker-ce.repo
     yum-config-manager --save --setopt=docker-ce-stable.module_hotfixes=true
 
-    DOCKER_VERSION='18.09.9-3.el7'
+    DOCKER_VERSION='19.03.12-3.el7'
     yum install -y docker-ce-${DOCKER_VERSION} \
       docker-ce-cli-${DOCKER_VERSION} \
       ebtables \
@@ -95,7 +96,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/sles/provider_test.go b/pkg/userdata/sles/provider_test.go
index bc1b91d7..0292d08b 100644
--- a/pkg/userdata/sles/provider_test.go
+++ b/pkg/userdata/sles/provider_test.go
@@ -85,6 +85,10 @@ kPe6XoSbiLm/kxk32T0=
 			},
 		},
 	}
+
+	kubeletFeatureGates = map[string]bool{
+		"RotateKubeletServerCertificate": true,
+	}
 )
 
 const (
@@ -442,6 +446,7 @@ func TestUserDataGeneration(t *testing.T) {
 				InsecureRegistries:    test.insecureRegistries,
 				RegistryMirrors:       test.registryMirrors,
 				PauseImage:            test.pauseImage,
+				KubeletFeatureGates:   kubeletFeatureGates,
 			}
 			s, err := provider.UserData(req)
 			if err != nil {
diff --git a/pkg/userdata/sles/testdata/dist-upgrade-on-boot.yaml b/pkg/userdata/sles/testdata/dist-upgrade-on-boot.yaml
index 551312fd..9c16b1cd 100644
--- a/pkg/userdata/sles/testdata/dist-upgrade-on-boot.yaml
+++ b/pkg/userdata/sles/testdata/dist-upgrade-on-boot.yaml
@@ -69,7 +69,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/sles/testdata/kubelet-version-without-v-prefix.yaml b/pkg/userdata/sles/testdata/kubelet-version-without-v-prefix.yaml
index e4d7de84..a6987ede 100644
--- a/pkg/userdata/sles/testdata/kubelet-version-without-v-prefix.yaml
+++ b/pkg/userdata/sles/testdata/kubelet-version-without-v-prefix.yaml
@@ -67,7 +67,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/sles/testdata/multiple-dns-servers.yaml b/pkg/userdata/sles/testdata/multiple-dns-servers.yaml
index 79ea98c2..b31d3fcf 100644
--- a/pkg/userdata/sles/testdata/multiple-dns-servers.yaml
+++ b/pkg/userdata/sles/testdata/multiple-dns-servers.yaml
@@ -67,7 +67,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/sles/testdata/multiple-ssh-keys.yaml b/pkg/userdata/sles/testdata/multiple-ssh-keys.yaml
index ac88eff8..1992af1e 100644
--- a/pkg/userdata/sles/testdata/multiple-ssh-keys.yaml
+++ b/pkg/userdata/sles/testdata/multiple-ssh-keys.yaml
@@ -69,7 +69,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/sles/testdata/openstack-overwrite-cloud-config.yaml b/pkg/userdata/sles/testdata/openstack-overwrite-cloud-config.yaml
index dcb07e50..9ceac542 100644
--- a/pkg/userdata/sles/testdata/openstack-overwrite-cloud-config.yaml
+++ b/pkg/userdata/sles/testdata/openstack-overwrite-cloud-config.yaml
@@ -67,7 +67,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/sles/testdata/openstack.yaml b/pkg/userdata/sles/testdata/openstack.yaml
index ce87e325..561a5730 100644
--- a/pkg/userdata/sles/testdata/openstack.yaml
+++ b/pkg/userdata/sles/testdata/openstack.yaml
@@ -67,7 +67,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/sles/testdata/version-1.15.10.yaml b/pkg/userdata/sles/testdata/version-1.15.10.yaml
index b827f08b..41d10040 100644
--- a/pkg/userdata/sles/testdata/version-1.15.10.yaml
+++ b/pkg/userdata/sles/testdata/version-1.15.10.yaml
@@ -67,7 +67,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/sles/testdata/version-1.16.3.yaml b/pkg/userdata/sles/testdata/version-1.16.3.yaml
index 5952710c..6457b96f 100644
--- a/pkg/userdata/sles/testdata/version-1.16.3.yaml
+++ b/pkg/userdata/sles/testdata/version-1.16.3.yaml
@@ -67,7 +67,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/sles/testdata/version-1.17.1.yaml b/pkg/userdata/sles/testdata/version-1.17.1.yaml
index 541c37fa..25cd3986 100644
--- a/pkg/userdata/sles/testdata/version-1.17.1.yaml
+++ b/pkg/userdata/sles/testdata/version-1.17.1.yaml
@@ -67,7 +67,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/sles/testdata/vsphere-mirrors.yaml b/pkg/userdata/sles/testdata/vsphere-mirrors.yaml
index 61b6c195..b0f4b7ea 100644
--- a/pkg/userdata/sles/testdata/vsphere-mirrors.yaml
+++ b/pkg/userdata/sles/testdata/vsphere-mirrors.yaml
@@ -77,7 +77,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/sles/testdata/vsphere-proxy.yaml b/pkg/userdata/sles/testdata/vsphere-proxy.yaml
index 12498843..e677df06 100644
--- a/pkg/userdata/sles/testdata/vsphere-proxy.yaml
+++ b/pkg/userdata/sles/testdata/vsphere-proxy.yaml
@@ -77,7 +77,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/sles/testdata/vsphere.yaml b/pkg/userdata/sles/testdata/vsphere.yaml
index fa27d1c2..60211514 100644
--- a/pkg/userdata/sles/testdata/vsphere.yaml
+++ b/pkg/userdata/sles/testdata/vsphere.yaml
@@ -68,7 +68,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/ubuntu/provider_test.go b/pkg/userdata/ubuntu/provider_test.go
index 76c7f659..abf0811f 100644
--- a/pkg/userdata/ubuntu/provider_test.go
+++ b/pkg/userdata/ubuntu/provider_test.go
@@ -84,6 +84,10 @@ kPe6XoSbiLm/kxk32T0=
 			},
 		},
 	}
+
+	kubeletFeatureGates = map[string]bool{
+		"RotateKubeletServerCertificate": true,
+	}
 )
 
 const (
@@ -441,6 +445,7 @@ func TestUserDataGeneration(t *testing.T) {
 				InsecureRegistries:    test.insecureRegistries,
 				RegistryMirrors:       test.registryMirrors,
 				PauseImage:            test.pauseImage,
+				KubeletFeatureGates:   kubeletFeatureGates,
 			}
 			s, err := provider.UserData(req)
 			if err != nil {
diff --git a/pkg/userdata/ubuntu/testdata/dist-upgrade-on-boot.yaml b/pkg/userdata/ubuntu/testdata/dist-upgrade-on-boot.yaml
index 948545d5..525b8e87 100644
--- a/pkg/userdata/ubuntu/testdata/dist-upgrade-on-boot.yaml
+++ b/pkg/userdata/ubuntu/testdata/dist-upgrade-on-boot.yaml
@@ -139,7 +139,7 @@ write_files:
     mv /etc/fstab.noswap /etc/fstab
     swapoff -a
 
-    DOCKER_VERSION='5:18.09.9~3-0~ubuntu-bionic'
+    DOCKER_VERSION='5:19.03.12~3-0~ubuntu-bionic'
     DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y \
       curl \
       ca-certificates \
@@ -170,7 +170,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/ubuntu/testdata/kubelet-version-without-v-prefix.yaml b/pkg/userdata/ubuntu/testdata/kubelet-version-without-v-prefix.yaml
index de1d643d..98bb3e1c 100644
--- a/pkg/userdata/ubuntu/testdata/kubelet-version-without-v-prefix.yaml
+++ b/pkg/userdata/ubuntu/testdata/kubelet-version-without-v-prefix.yaml
@@ -137,7 +137,7 @@ write_files:
     mv /etc/fstab.noswap /etc/fstab
     swapoff -a
 
-    DOCKER_VERSION='5:18.09.9~3-0~ubuntu-bionic'
+    DOCKER_VERSION='5:19.03.12~3-0~ubuntu-bionic'
     DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y \
       curl \
       ca-certificates \
@@ -168,7 +168,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/ubuntu/testdata/multiple-dns-servers.yaml b/pkg/userdata/ubuntu/testdata/multiple-dns-servers.yaml
index 780063a4..480030d8 100644
--- a/pkg/userdata/ubuntu/testdata/multiple-dns-servers.yaml
+++ b/pkg/userdata/ubuntu/testdata/multiple-dns-servers.yaml
@@ -137,7 +137,7 @@ write_files:
     mv /etc/fstab.noswap /etc/fstab
     swapoff -a
 
-    DOCKER_VERSION='5:18.09.9~3-0~ubuntu-bionic'
+    DOCKER_VERSION='5:19.03.12~3-0~ubuntu-bionic'
     DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y \
       curl \
       ca-certificates \
@@ -168,7 +168,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/ubuntu/testdata/multiple-ssh-keys.yaml b/pkg/userdata/ubuntu/testdata/multiple-ssh-keys.yaml
index 5043efe3..411b2f17 100644
--- a/pkg/userdata/ubuntu/testdata/multiple-ssh-keys.yaml
+++ b/pkg/userdata/ubuntu/testdata/multiple-ssh-keys.yaml
@@ -139,7 +139,7 @@ write_files:
     mv /etc/fstab.noswap /etc/fstab
     swapoff -a
 
-    DOCKER_VERSION='5:18.09.9~3-0~ubuntu-bionic'
+    DOCKER_VERSION='5:19.03.12~3-0~ubuntu-bionic'
     DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y \
       curl \
       ca-certificates \
@@ -170,7 +170,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/ubuntu/testdata/openstack-overwrite-cloud-config.yaml b/pkg/userdata/ubuntu/testdata/openstack-overwrite-cloud-config.yaml
index 750effa4..cac536dc 100644
--- a/pkg/userdata/ubuntu/testdata/openstack-overwrite-cloud-config.yaml
+++ b/pkg/userdata/ubuntu/testdata/openstack-overwrite-cloud-config.yaml
@@ -137,7 +137,7 @@ write_files:
     mv /etc/fstab.noswap /etc/fstab
     swapoff -a
 
-    DOCKER_VERSION='5:18.09.9~3-0~ubuntu-bionic'
+    DOCKER_VERSION='5:19.03.12~3-0~ubuntu-bionic'
     DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y \
       curl \
       ca-certificates \
@@ -168,7 +168,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/ubuntu/testdata/openstack.yaml b/pkg/userdata/ubuntu/testdata/openstack.yaml
index 646cf08a..b8350592 100644
--- a/pkg/userdata/ubuntu/testdata/openstack.yaml
+++ b/pkg/userdata/ubuntu/testdata/openstack.yaml
@@ -137,7 +137,7 @@ write_files:
     mv /etc/fstab.noswap /etc/fstab
     swapoff -a
 
-    DOCKER_VERSION='5:18.09.9~3-0~ubuntu-bionic'
+    DOCKER_VERSION='5:19.03.12~3-0~ubuntu-bionic'
     DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y \
       curl \
       ca-certificates \
@@ -168,7 +168,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/ubuntu/testdata/version-1.15.10.yaml b/pkg/userdata/ubuntu/testdata/version-1.15.10.yaml
index 51c2a890..da17c0c3 100644
--- a/pkg/userdata/ubuntu/testdata/version-1.15.10.yaml
+++ b/pkg/userdata/ubuntu/testdata/version-1.15.10.yaml
@@ -168,7 +168,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/ubuntu/testdata/version-1.16.6.yaml b/pkg/userdata/ubuntu/testdata/version-1.16.6.yaml
index 7cca6f3a..882fd6ef 100644
--- a/pkg/userdata/ubuntu/testdata/version-1.16.6.yaml
+++ b/pkg/userdata/ubuntu/testdata/version-1.16.6.yaml
@@ -168,7 +168,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/ubuntu/testdata/version-1.17.3.yaml b/pkg/userdata/ubuntu/testdata/version-1.17.3.yaml
index de1d643d..98bb3e1c 100644
--- a/pkg/userdata/ubuntu/testdata/version-1.17.3.yaml
+++ b/pkg/userdata/ubuntu/testdata/version-1.17.3.yaml
@@ -137,7 +137,7 @@ write_files:
     mv /etc/fstab.noswap /etc/fstab
     swapoff -a
 
-    DOCKER_VERSION='5:18.09.9~3-0~ubuntu-bionic'
+    DOCKER_VERSION='5:19.03.12~3-0~ubuntu-bionic'
     DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y \
       curl \
       ca-certificates \
@@ -168,7 +168,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/ubuntu/testdata/vsphere-mirrors.yaml b/pkg/userdata/ubuntu/testdata/vsphere-mirrors.yaml
index 8ea19770..195bfa95 100644
--- a/pkg/userdata/ubuntu/testdata/vsphere-mirrors.yaml
+++ b/pkg/userdata/ubuntu/testdata/vsphere-mirrors.yaml
@@ -146,7 +146,7 @@ write_files:
     mv /etc/fstab.noswap /etc/fstab
     swapoff -a
 
-    DOCKER_VERSION='5:18.09.9~3-0~ubuntu-bionic'
+    DOCKER_VERSION='5:19.03.12~3-0~ubuntu-bionic'
     DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y \
       curl \
       ca-certificates \
@@ -178,7 +178,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/ubuntu/testdata/vsphere-proxy.yaml b/pkg/userdata/ubuntu/testdata/vsphere-proxy.yaml
index 300166cb..5fcd182b 100644
--- a/pkg/userdata/ubuntu/testdata/vsphere-proxy.yaml
+++ b/pkg/userdata/ubuntu/testdata/vsphere-proxy.yaml
@@ -146,7 +146,7 @@ write_files:
     mv /etc/fstab.noswap /etc/fstab
     swapoff -a
 
-    DOCKER_VERSION='5:18.09.9~3-0~ubuntu-bionic'
+    DOCKER_VERSION='5:19.03.12~3-0~ubuntu-bionic'
     DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y \
       curl \
       ca-certificates \
@@ -178,7 +178,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/pkg/userdata/ubuntu/testdata/vsphere.yaml b/pkg/userdata/ubuntu/testdata/vsphere.yaml
index f81f96c1..021205ce 100644
--- a/pkg/userdata/ubuntu/testdata/vsphere.yaml
+++ b/pkg/userdata/ubuntu/testdata/vsphere.yaml
@@ -137,7 +137,7 @@ write_files:
     mv /etc/fstab.noswap /etc/fstab
     swapoff -a
 
-    DOCKER_VERSION='5:18.09.9~3-0~ubuntu-bionic'
+    DOCKER_VERSION='5:19.03.12~3-0~ubuntu-bionic'
     DEBIAN_FRONTEND=noninteractive apt-get -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" install -y \
       curl \
       ca-certificates \
@@ -169,7 +169,7 @@ write_files:
     cni_bin_dir=/opt/cni/bin
     mkdir -p /etc/cni/net.d /etc/kubernetes/dynamic-config-dir /etc/kubernetes/manifests "$opt_bin" "$cni_bin_dir"
     arch=${HOST_ARCH:-amd64}
-    CNI_VERSION="${CNI_VERSION:-v0.8.6}"
+    CNI_VERSION="${CNI_VERSION:-v0.8.7}"
     cni_base_url="https://github.com/containernetworking/plugins/releases/download/$CNI_VERSION"
     cni_filename="cni-plugins-linux-$arch-$CNI_VERSION.tgz"
     curl -Lfo "$cni_bin_dir/$cni_filename" "$cni_base_url/$cni_filename"
diff --git a/test/e2e/provisioning/all_e2e_test.go b/test/e2e/provisioning/all_e2e_test.go
index d4034be9..8e8f08f5 100644
--- a/test/e2e/provisioning/all_e2e_test.go
+++ b/test/e2e/provisioning/all_e2e_test.go
@@ -160,7 +160,7 @@ func TestDigitalOceanProvisioningE2E(t *testing.T) {
 		t.Fatal("unable to run the test suite, DO_E2E_TESTS_TOKEN environement varialbe cannot be empty")
 	}
 
-	selector := Not(OsSelector("sles", "rhel", "flatcar"))
+	selector := Not(OsSelector("sles", "rhel", "flatcar", "coreos"))
 	// act
 	params := []string{fmt.Sprintf("<< DIGITALOCEAN_TOKEN >>=%s", doToken)}
 	runScenarios(t, selector, params, DOManifest, fmt.Sprintf("do-%s", *testRunIdentifier))
@@ -177,7 +177,7 @@ func TestAWSProvisioningE2E(t *testing.T) {
 	if len(awsKeyID) == 0 || len(awsSecret) == 0 {
 		t.Fatal("unable to run the test suite, AWS_E2E_TESTS_KEY_ID or AWS_E2E_TESTS_SECRET environment variables cannot be empty")
 	}
-	selector := Not(OsSelector("sles"))
+	selector := Not(OsSelector("coreos", "sles"))
 	// act
 	params := []string{fmt.Sprintf("<< AWS_ACCESS_KEY_ID >>=%s", awsKeyID),
 		fmt.Sprintf("<< AWS_SECRET_ACCESS_KEY >>=%s", awsSecret),
@@ -271,7 +271,7 @@ func TestAzureProvisioningE2E(t *testing.T) {
 		t.Fatal("unable to run the test suite, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID, AZURE_CLIENT_ID and AZURE_CLIENT_SECRET environment variables cannot be empty")
 	}
 
-	selector := Not(OsSelector("sles"))
+	selector := Not(OsSelector("coreos", "sles"))
 	// act
 	params := []string{
 		fmt.Sprintf("<< AZURE_TENANT_ID >>=%s", azureTenantID),
@@ -329,7 +329,7 @@ func TestGCEProvisioningE2E(t *testing.T) {
 	}
 
 	// Act. GCE does not support CentOS.
-	selector := OsSelector("ubuntu", "coreos")
+	selector := OsSelector("ubuntu")
 	params := []string{
 		fmt.Sprintf("<< GOOGLE_SERVICE_ACCOUNT >>=%s", googleServiceAccount),
 	}
@@ -371,7 +371,7 @@ func TestPacketProvisioningE2E(t *testing.T) {
 		t.Fatal("unable to run the test suite, PACKET_PROJECT_ID environment variable cannot be empty")
 	}
 
-	selector := Not(OsSelector("sles", "rhel", "flatcar"))
+	selector := Not(OsSelector("sles", "rhel"))
 
 	// act
 	params := []string{
diff --git a/test/e2e/provisioning/testdata/machinedeployment-packet.yaml b/test/e2e/provisioning/testdata/machinedeployment-packet.yaml
index 917bf4c5..d487b98f 100644
--- a/test/e2e/provisioning/testdata/machinedeployment-packet.yaml
+++ b/test/e2e/provisioning/testdata/machinedeployment-packet.yaml
@@ -26,9 +26,9 @@ spec:
           cloudProviderSpec:
             apiKey: << PACKET_API_KEY >>
             projectID: << PACKET_PROJECT_ID >>
-            instanceType: "t1.small.x86"
+            instanceType: "c1.small.x86"
             facilities:
-            - "ewr1"
+            - "ams1"
           operatingSystem: "<< OS_NAME >>"
           operatingSystemSpec:
             distUpgradeOnBoot: false