Skip to content

Commit 335a3e9

Browse files
committed
kubeadm: use correct IP family for etcd localhost
kubeadm always use the IPv4 localhost address by defaultA for etcd The probe hostname is obtained before the generation of the etcd parameters, so it can't detect the right IP familiy for the host of the probe. This causes that with IPv6 clusters doesn't work because the probe uses the IPv4 localhost address. This patchs configures the right localhost address based on the used AdvertiseAddress IP family.
1 parent 798d2fb commit 335a3e9

File tree

7 files changed

+117
-13
lines changed

7 files changed

+117
-13
lines changed

cmd/kubeadm/app/phases/controlplane/manifests.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,12 @@ func getAPIServerCommand(cfg *kubeadmapi.ClusterConfiguration, localAPIEndpoint
162162
}
163163
} else {
164164
// Default to etcd static pod on localhost
165-
defaultArguments["etcd-servers"] = fmt.Sprintf("https://127.0.0.1:%d", kubeadmconstants.EtcdListenClientPort)
165+
// localhost IP family should be the same that the AdvertiseAddress
166+
etcdLocalhostAddress := "127.0.0.1"
167+
if utilsnet.IsIPv6String(localAPIEndpoint.AdvertiseAddress) {
168+
etcdLocalhostAddress = "::1"
169+
}
170+
defaultArguments["etcd-servers"] = fmt.Sprintf("https://%s", net.JoinHostPort(etcdLocalhostAddress, strconv.Itoa(kubeadmconstants.EtcdListenClientPort)))
166171
defaultArguments["etcd-cafile"] = filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdCACertName)
167172
defaultArguments["etcd-certfile"] = filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientCertName)
168173
defaultArguments["etcd-keyfile"] = filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientKeyName)

cmd/kubeadm/app/phases/controlplane/manifests_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ func TestGetAPIServerCommand(t *testing.T) {
266266
"--requestheader-allowed-names=front-proxy-client",
267267
"--authorization-mode=Node,RBAC",
268268
"--advertise-address=2001:db8::1",
269-
fmt.Sprintf("--etcd-servers=https://127.0.0.1:%d", kubeadmconstants.EtcdListenClientPort),
269+
fmt.Sprintf("--etcd-servers=https://[::1]:%d", kubeadmconstants.EtcdListenClientPort),
270270
"--etcd-cafile=" + testCertsDir + "/etcd/ca.crt",
271271
"--etcd-certfile=" + testCertsDir + "/apiserver-etcd-client.crt",
272272
"--etcd-keyfile=" + testCertsDir + "/apiserver-etcd-client.key",
@@ -278,7 +278,7 @@ func TestGetAPIServerCommand(t *testing.T) {
278278
Networking: kubeadmapi.Networking{ServiceSubnet: "bar"},
279279
Etcd: kubeadmapi.Etcd{
280280
External: &kubeadmapi.ExternalEtcd{
281-
Endpoints: []string{"https://8.6.4.1:2379", "https://8.6.4.2:2379"},
281+
Endpoints: []string{"https://[2001:abcd:bcda::1]:2379", "https://[2001:abcd:bcda::2]:2379"},
282282
CAFile: "fuz",
283283
CertFile: "fiz",
284284
KeyFile: "faz",
@@ -311,7 +311,7 @@ func TestGetAPIServerCommand(t *testing.T) {
311311
"--requestheader-allowed-names=front-proxy-client",
312312
"--authorization-mode=Node,RBAC",
313313
"--advertise-address=2001:db8::1",
314-
"--etcd-servers=https://8.6.4.1:2379,https://8.6.4.2:2379",
314+
"--etcd-servers=https://[2001:abcd:bcda::1]:2379,https://[2001:abcd:bcda::2]:2379",
315315
"--etcd-cafile=fuz",
316316
"--etcd-certfile=fiz",
317317
"--etcd-keyfile=faz",
@@ -323,7 +323,7 @@ func TestGetAPIServerCommand(t *testing.T) {
323323
Networking: kubeadmapi.Networking{ServiceSubnet: "bar"},
324324
Etcd: kubeadmapi.Etcd{
325325
External: &kubeadmapi.ExternalEtcd{
326-
Endpoints: []string{"http://127.0.0.1:2379", "http://127.0.0.1:2380"},
326+
Endpoints: []string{"http://[::1]:2379", "http://[::1]:2380"},
327327
},
328328
},
329329
CertificatesDir: testCertsDir,
@@ -353,7 +353,7 @@ func TestGetAPIServerCommand(t *testing.T) {
353353
"--requestheader-allowed-names=front-proxy-client",
354354
"--authorization-mode=Node,RBAC",
355355
"--advertise-address=2001:db8::1",
356-
"--etcd-servers=http://127.0.0.1:2379,http://127.0.0.1:2380",
356+
"--etcd-servers=http://[::1]:2379,http://[::1]:2380",
357357
},
358358
},
359359
{

cmd/kubeadm/app/phases/etcd/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ go_library(
3535
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
3636
"//vendor/github.com/pkg/errors:go_default_library",
3737
"//vendor/k8s.io/klog:go_default_library",
38+
"//vendor/k8s.io/utils/net:go_default_library",
3839
],
3940
)
4041

cmd/kubeadm/app/phases/etcd/local.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ package etcd
1818

1919
import (
2020
"fmt"
21+
"net"
2122
"path/filepath"
23+
"strconv"
2224
"strings"
2325
"time"
2426

@@ -33,6 +35,7 @@ import (
3335
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
3436
etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
3537
staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
38+
utilsnet "k8s.io/utils/net"
3639
)
3740

3841
const (
@@ -176,7 +179,8 @@ func GetEtcdPodSpec(cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.A
176179
etcdVolumeName: staticpodutil.NewVolume(etcdVolumeName, cfg.Etcd.Local.DataDir, &pathType),
177180
certsVolumeName: staticpodutil.NewVolume(certsVolumeName, cfg.CertificatesDir+"/etcd", &pathType),
178181
}
179-
probeHostname, probePort, probeScheme := staticpodutil.GetEtcdProbeEndpoint(&cfg.Etcd)
182+
// probeHostname returns the correct localhost IP address family based on the endpoint AdvertiseAddress
183+
probeHostname, probePort, probeScheme := staticpodutil.GetEtcdProbeEndpoint(&cfg.Etcd, utilsnet.IsIPv6String(endpoint.AdvertiseAddress))
180184
return staticpodutil.ComponentPod(v1.Container{
181185
Name: kubeadmconstants.Etcd,
182186
Command: getEtcdCommand(cfg, endpoint, nodeName, initialCluster),
@@ -193,9 +197,14 @@ func GetEtcdPodSpec(cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.A
193197

194198
// getEtcdCommand builds the right etcd command from the given config object
195199
func getEtcdCommand(cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint, nodeName string, initialCluster []etcdutil.Member) []string {
200+
// localhost IP family should be the same that the AdvertiseAddress
201+
etcdLocalhostAddress := "127.0.0.1"
202+
if utilsnet.IsIPv6String(endpoint.AdvertiseAddress) {
203+
etcdLocalhostAddress = "::1"
204+
}
196205
defaultArguments := map[string]string{
197206
"name": nodeName,
198-
"listen-client-urls": fmt.Sprintf("%s,%s", etcdutil.GetClientURLByIP("127.0.0.1"), etcdutil.GetClientURL(endpoint)),
207+
"listen-client-urls": fmt.Sprintf("%s,%s", etcdutil.GetClientURLByIP(etcdLocalhostAddress), etcdutil.GetClientURL(endpoint)),
199208
"advertise-client-urls": etcdutil.GetClientURL(endpoint),
200209
"listen-peer-urls": etcdutil.GetPeerURL(endpoint),
201210
"initial-advertise-peer-urls": etcdutil.GetPeerURL(endpoint),
@@ -209,7 +218,7 @@ func getEtcdCommand(cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.A
209218
"peer-trusted-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdCACertName),
210219
"peer-client-cert-auth": "true",
211220
"snapshot-count": "10000",
212-
"listen-metrics-urls": fmt.Sprintf("http://127.0.0.1:%d", kubeadmconstants.EtcdMetricsPort),
221+
"listen-metrics-urls": fmt.Sprintf("http://%s", net.JoinHostPort(etcdLocalhostAddress, strconv.Itoa(kubeadmconstants.EtcdMetricsPort))),
213222
}
214223

215224
if len(initialCluster) == 0 {

cmd/kubeadm/app/phases/etcd/local_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,8 @@ func TestGetEtcdCommand(t *testing.T) {
266266
expected: []string{
267267
"etcd",
268268
"--name=foo",
269-
fmt.Sprintf("--listen-client-urls=https://127.0.0.1:%d,https://[2001:db8::3]:%d", kubeadmconstants.EtcdListenClientPort, kubeadmconstants.EtcdListenClientPort),
270-
fmt.Sprintf("--listen-metrics-urls=http://127.0.0.1:%d", kubeadmconstants.EtcdMetricsPort),
269+
fmt.Sprintf("--listen-client-urls=https://[::1]:%d,https://[2001:db8::3]:%d", kubeadmconstants.EtcdListenClientPort, kubeadmconstants.EtcdListenClientPort),
270+
fmt.Sprintf("--listen-metrics-urls=http://[::1]:%d", kubeadmconstants.EtcdMetricsPort),
271271
fmt.Sprintf("--advertise-client-urls=https://[2001:db8::3]:%d", kubeadmconstants.EtcdListenClientPort),
272272
fmt.Sprintf("--listen-peer-urls=https://[2001:db8::3]:%d", kubeadmconstants.EtcdListenPeerPort),
273273
fmt.Sprintf("--initial-advertise-peer-urls=https://[2001:db8::3]:%d", kubeadmconstants.EtcdListenPeerPort),

cmd/kubeadm/app/util/staticpod/utils.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,11 @@ func GetSchedulerProbeAddress(cfg *kubeadmapi.ClusterConfiguration) string {
269269
// GetEtcdProbeEndpoint takes a kubeadm Etcd configuration object and attempts to parse
270270
// the first URL in the listen-metrics-urls argument, returning an etcd probe hostname,
271271
// port and scheme
272-
func GetEtcdProbeEndpoint(cfg *kubeadmapi.Etcd) (string, int, v1.URIScheme) {
272+
func GetEtcdProbeEndpoint(cfg *kubeadmapi.Etcd, isIPv6 bool) (string, int, v1.URIScheme) {
273273
localhost := "127.0.0.1"
274+
if isIPv6 {
275+
localhost = "::1"
276+
}
274277
if cfg.Local == nil || cfg.Local.ExtraArgs == nil {
275278
return localhost, kubeadmconstants.EtcdMetricsPort, v1.URISchemeHTTP
276279
}

cmd/kubeadm/app/util/staticpod/utils_test.go

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,13 @@ func TestGetAPIServerProbeAddress(t *testing.T) {
6565
},
6666
expected: "10.10.10.10",
6767
},
68+
{
69+
desc: "filled in ipv6 AdvertiseAddress endpoint returns it",
70+
endpoint: &kubeadmapi.APIEndpoint{
71+
AdvertiseAddress: "2001:abcd:bcda::1",
72+
},
73+
expected: "2001:abcd:bcda::1",
74+
},
6875
}
6976

7077
for _, test := range tests {
@@ -103,6 +110,17 @@ func TestGetControllerManagerProbeAddress(t *testing.T) {
103110
},
104111
expected: "10.10.10.10",
105112
},
113+
{
114+
desc: "setting controller manager extra ipv6 address arg to something acknowledges it",
115+
cfg: &kubeadmapi.ClusterConfiguration{
116+
ControllerManager: kubeadmapi.ControlPlaneComponent{
117+
ExtraArgs: map[string]string{
118+
kubeControllerManagerAddressArg: "2001:abcd:bcda::1",
119+
},
120+
},
121+
},
122+
expected: "2001:abcd:bcda::1",
123+
},
106124
}
107125

108126
for _, test := range tests {
@@ -119,6 +137,7 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
119137
var tests = []struct {
120138
name string
121139
cfg *kubeadmapi.Etcd
140+
isIPv6 bool
122141
expectedHostname string
123142
expectedPort int
124143
expectedScheme v1.URIScheme
@@ -131,6 +150,7 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
131150
"listen-metrics-urls": "https://1.2.3.4:1234,https://4.3.2.1:2381"},
132151
},
133152
},
153+
isIPv6: false,
134154
expectedHostname: "1.2.3.4",
135155
expectedPort: 1234,
136156
expectedScheme: v1.URISchemeHTTPS,
@@ -143,6 +163,7 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
143163
"listen-metrics-urls": "http://1.2.3.4:1234"},
144164
},
145165
},
166+
isIPv6: false,
146167
expectedHostname: "1.2.3.4",
147168
expectedPort: 1234,
148169
expectedScheme: v1.URISchemeHTTP,
@@ -155,6 +176,7 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
155176
"listen-metrics-urls": "1.2.3.4"},
156177
},
157178
},
179+
isIPv6: false,
158180
expectedHostname: "127.0.0.1",
159181
expectedPort: kubeadmconstants.EtcdMetricsPort,
160182
expectedScheme: v1.URISchemeHTTP,
@@ -167,23 +189,87 @@ func TestGetEtcdProbeEndpoint(t *testing.T) {
167189
"listen-metrics-urls": "https://1.2.3.4"},
168190
},
169191
},
192+
isIPv6: false,
170193
expectedHostname: "1.2.3.4",
171194
expectedPort: kubeadmconstants.EtcdMetricsPort,
172195
expectedScheme: v1.URISchemeHTTPS,
173196
},
197+
{
198+
name: "etcd probe URL from two IPv6 URLs",
199+
cfg: &kubeadmapi.Etcd{
200+
Local: &kubeadmapi.LocalEtcd{
201+
ExtraArgs: map[string]string{
202+
"listen-metrics-urls": "https://[2001:abcd:bcda::1]:1234,https://[2001:abcd:bcda::2]:2381"},
203+
},
204+
},
205+
isIPv6: true,
206+
expectedHostname: "2001:abcd:bcda::1",
207+
expectedPort: 1234,
208+
expectedScheme: v1.URISchemeHTTPS,
209+
},
210+
{
211+
name: "etcd probe localhost IPv6 URL with HTTP scheme",
212+
cfg: &kubeadmapi.Etcd{
213+
Local: &kubeadmapi.LocalEtcd{
214+
ExtraArgs: map[string]string{
215+
"listen-metrics-urls": "http://[::1]:1234"},
216+
},
217+
},
218+
isIPv6: true,
219+
expectedHostname: "::1",
220+
expectedPort: 1234,
221+
expectedScheme: v1.URISchemeHTTP,
222+
},
223+
{
224+
name: "etcd probe IPv6 URL with HTTP scheme",
225+
cfg: &kubeadmapi.Etcd{
226+
Local: &kubeadmapi.LocalEtcd{
227+
ExtraArgs: map[string]string{
228+
"listen-metrics-urls": "http://[2001:abcd:bcda::1]:1234"},
229+
},
230+
},
231+
isIPv6: true,
232+
expectedHostname: "2001:abcd:bcda::1",
233+
expectedPort: 1234,
234+
expectedScheme: v1.URISchemeHTTP,
235+
},
236+
{
237+
name: "etcd probe IPv6 URL without port",
238+
cfg: &kubeadmapi.Etcd{
239+
Local: &kubeadmapi.LocalEtcd{
240+
ExtraArgs: map[string]string{
241+
"listen-metrics-urls": "https://[2001:abcd:bcda::1]"},
242+
},
243+
},
244+
isIPv6: true,
245+
expectedHostname: "2001:abcd:bcda::1",
246+
expectedPort: kubeadmconstants.EtcdMetricsPort,
247+
expectedScheme: v1.URISchemeHTTPS,
248+
},
174249
{
175250
name: "etcd probe URL from defaults",
176251
cfg: &kubeadmapi.Etcd{
177252
Local: &kubeadmapi.LocalEtcd{},
178253
},
254+
isIPv6: false,
179255
expectedHostname: "127.0.0.1",
180256
expectedPort: kubeadmconstants.EtcdMetricsPort,
181257
expectedScheme: v1.URISchemeHTTP,
182258
},
259+
{
260+
name: "etcd probe URL from defaults if IPv6",
261+
cfg: &kubeadmapi.Etcd{
262+
Local: &kubeadmapi.LocalEtcd{},
263+
},
264+
isIPv6: true,
265+
expectedHostname: "::1",
266+
expectedPort: kubeadmconstants.EtcdMetricsPort,
267+
expectedScheme: v1.URISchemeHTTP,
268+
},
183269
}
184270
for _, rt := range tests {
185271
t.Run(rt.name, func(t *testing.T) {
186-
hostname, port, scheme := GetEtcdProbeEndpoint(rt.cfg)
272+
hostname, port, scheme := GetEtcdProbeEndpoint(rt.cfg, rt.isIPv6)
187273
if hostname != rt.expectedHostname {
188274
t.Errorf("%q test case failed:\n\texpected hostname: %s\n\tgot: %s",
189275
rt.name, rt.expectedHostname, hostname)

0 commit comments

Comments
 (0)