Skip to content

Commit 6bb07b3

Browse files
authored
Merge pull request #2037 from r2d4/kubeadm-feature-gates
[kubeadm] Pass features gates to components
2 parents 6a53c0c + 967913b commit 6bb07b3

File tree

5 files changed

+300
-18
lines changed

5 files changed

+300
-18
lines changed

pkg/minikube/bootstrapper/kubeadm/kubeadm.go

+27-2
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,14 @@ func NewKubeletConfig(k8s bootstrapper.KubernetesConfig) (string, error) {
191191

192192
extraFlags := convertToFlags(extraOpts)
193193
b := bytes.Buffer{}
194-
if err := kubeletSystemdTemplate.Execute(&b, map[string]string{"ExtraOptions": extraFlags}); err != nil {
194+
opts := struct {
195+
ExtraOptions string
196+
FeatureGates string
197+
}{
198+
ExtraOptions: extraFlags,
199+
FeatureGates: k8s.FeatureGates,
200+
}
201+
if err := kubeletSystemdTemplate.Execute(&b, opts); err != nil {
195202
return "", err
196203
}
197204

@@ -262,17 +269,35 @@ sudo systemctl start kubelet
262269
return nil
263270
}
264271

272+
func parseFeatureGates(featureGates string) (map[string]string, error) {
273+
if featureGates == "" {
274+
return nil, nil
275+
}
276+
fgMap := map[string]string{}
277+
fg := strings.Split(featureGates, ",")
278+
for _, f := range fg {
279+
kv := strings.SplitN(f, "=", 2)
280+
if len(kv) != 2 {
281+
return nil, fmt.Errorf("Invalid feature gate format: %s", f)
282+
}
283+
fgMap[kv[0]] = kv[1]
284+
}
285+
286+
return fgMap, nil
287+
}
288+
265289
func generateConfig(k8s bootstrapper.KubernetesConfig) (string, error) {
266290
version, err := ParseKubernetesVersion(k8s.KubernetesVersion)
267291
if err != nil {
268292
return "", errors.Wrap(err, "parsing kubernetes version")
269293
}
270294

271295
// generates a map of component to extra args for apiserver, controller-manager, and scheduler
272-
extraComponentConfig, err := NewComponentExtraArgs(k8s.ExtraOptions, version)
296+
extraComponentConfig, err := NewComponentExtraArgs(k8s.ExtraOptions, version, k8s.FeatureGates)
273297
if err != nil {
274298
return "", errors.Wrap(err, "generating extra component config for kubeadm")
275299
}
300+
276301
opts := struct {
277302
CertDir string
278303
ServiceCIDR string

pkg/minikube/bootstrapper/kubeadm/kubeadm_test.go

+155-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ limitations under the License.
1717
package kubeadm
1818

1919
import (
20+
"reflect"
2021
"testing"
2122

2223
"k8s.io/minikube/pkg/minikube/bootstrapper"
@@ -88,11 +89,111 @@ etcd:
8889
dataDir: /data
8990
nodeName: extra-args-minikube
9091
apiServerExtraArgs:
91-
fail-no-swap: true
92+
fail-no-swap: "true"
9293
controllerManagerExtraArgs:
93-
kube-api-burst: 32
94+
kube-api-burst: "32"
9495
schedulerExtraArgs:
95-
scheduler-name: mini-scheduler
96+
scheduler-name: "mini-scheduler"
97+
`,
98+
},
99+
{
100+
description: "two extra args for one component",
101+
cfg: bootstrapper.KubernetesConfig{
102+
NodeIP: "192.168.1.101",
103+
KubernetesVersion: "v1.8.0-alpha.0",
104+
NodeName: "extra-args-minikube",
105+
ExtraOptions: util.ExtraOptionSlice{
106+
util.ExtraOption{
107+
Component: Apiserver,
108+
Key: "fail-no-swap",
109+
Value: "true",
110+
},
111+
util.ExtraOption{
112+
Component: Apiserver,
113+
Key: "kube-api-burst",
114+
Value: "32",
115+
},
116+
},
117+
},
118+
expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
119+
kind: MasterConfiguration
120+
api:
121+
advertiseAddress: 192.168.1.101
122+
bindPort: 8443
123+
kubernetesVersion: v1.8.0-alpha.0
124+
certificatesDir: /var/lib/localkube/certs/
125+
networking:
126+
serviceSubnet: 10.0.0.0/24
127+
etcd:
128+
dataDir: /data
129+
nodeName: extra-args-minikube
130+
apiServerExtraArgs:
131+
fail-no-swap: "true"
132+
kube-api-burst: "32"
133+
`,
134+
},
135+
{
136+
description: "enable feature gates",
137+
cfg: bootstrapper.KubernetesConfig{
138+
NodeIP: "192.168.1.101",
139+
KubernetesVersion: "v1.8.0-alpha.0",
140+
NodeName: "extra-args-minikube",
141+
FeatureGates: "HugePages=true,OtherFeature=false",
142+
},
143+
expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
144+
kind: MasterConfiguration
145+
api:
146+
advertiseAddress: 192.168.1.101
147+
bindPort: 8443
148+
kubernetesVersion: v1.8.0-alpha.0
149+
certificatesDir: /var/lib/localkube/certs/
150+
networking:
151+
serviceSubnet: 10.0.0.0/24
152+
etcd:
153+
dataDir: /data
154+
nodeName: extra-args-minikube
155+
apiServerExtraArgs:
156+
feature-gates: "HugePages=true,OtherFeature=false"
157+
controllerManagerExtraArgs:
158+
feature-gates: "HugePages=true,OtherFeature=false"
159+
schedulerExtraArgs:
160+
feature-gates: "HugePages=true,OtherFeature=false"
161+
`,
162+
},
163+
{
164+
description: "enable feature gates and extra config",
165+
cfg: bootstrapper.KubernetesConfig{
166+
NodeIP: "192.168.1.101",
167+
KubernetesVersion: "v1.8.0-alpha.0",
168+
NodeName: "extra-args-minikube",
169+
FeatureGates: "HugePages=true,OtherFeature=false",
170+
ExtraOptions: util.ExtraOptionSlice{
171+
util.ExtraOption{
172+
Component: Apiserver,
173+
Key: "fail-no-swap",
174+
Value: "true",
175+
},
176+
},
177+
},
178+
expectedCfg: `apiVersion: kubeadm.k8s.io/v1alpha1
179+
kind: MasterConfiguration
180+
api:
181+
advertiseAddress: 192.168.1.101
182+
bindPort: 8443
183+
kubernetesVersion: v1.8.0-alpha.0
184+
certificatesDir: /var/lib/localkube/certs/
185+
networking:
186+
serviceSubnet: 10.0.0.0/24
187+
etcd:
188+
dataDir: /data
189+
nodeName: extra-args-minikube
190+
apiServerExtraArgs:
191+
fail-no-swap: "true"
192+
feature-gates: "HugePages=true,OtherFeature=false"
193+
controllerManagerExtraArgs:
194+
feature-gates: "HugePages=true,OtherFeature=false"
195+
schedulerExtraArgs:
196+
feature-gates: "HugePages=true,OtherFeature=false"
96197
`,
97198
},
98199
{
@@ -132,3 +233,54 @@ schedulerExtraArgs:
132233
})
133234
}
134235
}
236+
237+
func TestParseFeatureGates(t *testing.T) {
238+
tests := []struct {
239+
description string
240+
fg string
241+
expected map[string]string
242+
shouldErr bool
243+
}{
244+
{
245+
description: "no feature gates",
246+
},
247+
{
248+
description: "one feature gate",
249+
fg: "AppArmor=true",
250+
expected: map[string]string{
251+
"AppArmor": "true",
252+
},
253+
},
254+
{
255+
description: "two feature gates",
256+
fg: "AppArmor=true,HugePages=true",
257+
expected: map[string]string{
258+
"AppArmor": "true",
259+
"HugePages": "true",
260+
},
261+
},
262+
{
263+
description: "missing value pair",
264+
fg: "AppArmor=true,HugePages",
265+
shouldErr: true,
266+
},
267+
}
268+
269+
for _, test := range tests {
270+
t.Run(test.description, func(t *testing.T) {
271+
actual, err := parseFeatureGates(test.fg)
272+
t.Logf("%+v", actual)
273+
if err == nil && test.shouldErr {
274+
t.Errorf("Expected error but got none: fg: %v", actual)
275+
return
276+
}
277+
if err != nil && !test.shouldErr {
278+
t.Errorf("Unexpected error: %s", err)
279+
return
280+
}
281+
if !reflect.DeepEqual(actual, test.expected) {
282+
t.Errorf("Actual not equal expected: Actual: %v Expected: %v", actual, test.expected)
283+
}
284+
})
285+
}
286+
}

pkg/minikube/bootstrapper/kubeadm/templates.go

+31-6
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,15 @@ limitations under the License.
1616

1717
package kubeadm
1818

19-
import "html/template"
19+
import (
20+
"fmt"
21+
"sort"
22+
"text/template"
23+
)
2024

21-
var kubeadmConfigTemplate = template.Must(template.New("kubeadmConfigTemplate").Parse(`apiVersion: kubeadm.k8s.io/v1alpha1
25+
var kubeadmConfigTemplate = template.Must(template.New("kubeadmConfigTemplate").Funcs(template.FuncMap{
26+
"printMapInOrder": printMapInOrder,
27+
}).Parse(`apiVersion: kubeadm.k8s.io/v1alpha1
2228
kind: MasterConfiguration
2329
api:
2430
advertiseAddress: {{.AdvertiseAddress}}
@@ -30,9 +36,9 @@ networking:
3036
etcd:
3137
dataDir: {{.EtcdDataDir}}
3238
nodeName: {{.NodeName}}
33-
{{range .ExtraArgs}}{{.Component}}:{{range $key, $value := .Options}}
34-
{{$key}}: {{$value}}
35-
{{end}}{{end}}`))
39+
{{range .ExtraArgs}}{{.Component}}:{{range $i, $val := printMapInOrder .Options ": " }}
40+
{{$val}}{{end}}
41+
{{end}}`))
3642

3743
var kubeletSystemdTemplate = template.Must(template.New("kubeletSystemdTemplate").Parse(`
3844
[Service]
@@ -42,7 +48,7 @@ Environment="KUBELET_DNS_ARGS=--cluster-dns=10.0.0.10 --cluster-domain=cluster.l
4248
Environment="KUBELET_CADVISOR_ARGS=--cadvisor-port=0"
4349
Environment="KUBELET_CGROUP_ARGS=--cgroup-driver=cgroupfs"
4450
ExecStart=
45-
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_SYSTEM_PODS_ARGS $KUBELET_DNS_ARGS $KUBELET_CADVISOR_ARGS $KUBELET_CGROUP_ARGS {{.ExtraOptions}}
51+
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_SYSTEM_PODS_ARGS $KUBELET_DNS_ARGS $KUBELET_CADVISOR_ARGS $KUBELET_CGROUP_ARGS {{.ExtraOptions}} {{if .FeatureGates}}--feature-gates={{.FeatureGates}}{{end}}
4652
`))
4753

4854
const kubeletService = `
@@ -68,3 +74,22 @@ sudo /usr/bin/kubeadm alpha phase etcd local --config {{.KubeadmConfigFile}}
6874
`))
6975

7076
var kubeadmInitTemplate = template.Must(template.New("kubeadmInitTemplate").Parse("sudo /usr/bin/kubeadm init --config {{.KubeadmConfigFile}} --skip-preflight-checks"))
77+
78+
// printMapInOrder sorts the keys and prints the map in order, combining key
79+
// value pairs with the separator character
80+
//
81+
// Note: this is not necessary, but makes testing easy
82+
func printMapInOrder(m map[string]string, sep string) []string {
83+
if m == nil {
84+
return nil
85+
}
86+
keys := []string{}
87+
for k := range m {
88+
keys = append(keys, k)
89+
}
90+
sort.Strings(keys)
91+
for i, k := range keys {
92+
keys[i] = fmt.Sprintf("%s%s\"%s\"", k, sep, m[k])
93+
}
94+
return keys
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
Copyright 2016 The Kubernetes Authors All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package kubeadm
18+
19+
import (
20+
"reflect"
21+
"testing"
22+
)
23+
24+
func TestPrintMapInOrder(t *testing.T) {
25+
tests := []struct {
26+
description string
27+
m map[string]string
28+
sep string
29+
expected []string
30+
}{
31+
{
32+
description: "single kv",
33+
sep: ": ",
34+
m: map[string]string{
35+
"a": "1",
36+
},
37+
expected: []string{`a: "1"`},
38+
},
39+
{
40+
description: "two kv",
41+
sep: "=",
42+
m: map[string]string{
43+
"b": "2",
44+
"a": "1",
45+
},
46+
expected: []string{`a="1"`, `b="2"`},
47+
},
48+
{
49+
description: "no kv",
50+
sep: ",",
51+
},
52+
}
53+
54+
for _, test := range tests {
55+
t.Run(test.description, func(t *testing.T) {
56+
actual := printMapInOrder(test.m, test.sep)
57+
if !reflect.DeepEqual(actual, test.expected) {
58+
t.Errorf("Actual: %v, Expected: %v", actual, test.expected)
59+
}
60+
})
61+
}
62+
}

0 commit comments

Comments
 (0)