Report Additional POD IPs
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
		| @@ -87,8 +87,9 @@ type CniConfig struct { | ||||
| 	NetworkPluginMaxConfNum int `toml:"max_conf_num" json:"maxConfNum"` | ||||
| 	// NetworkPluginConfTemplate is the file path of golang template used to generate | ||||
| 	// cni config. | ||||
| 	// When it is set, containerd will get cidr from kubelet to replace {{.PodCIDR}} in | ||||
| 	// the template, and write the config into NetworkPluginConfDir. | ||||
| 	// When it is set, containerd will get cidr(s) from kubelet to replace {{.PodCIDR}}, | ||||
| 	// {{.PodCIDRRanges}} or {{.Routes}} in the template, and write the config into | ||||
| 	// NetworkPluginConfDir. | ||||
| 	// Ideally the cni config should be placed by system admin or cni daemon like calico, | ||||
| 	// weaveworks etc. However, there are still users using kubenet | ||||
| 	// (https://kubernetes.io/docs/concepts/cluster-administration/network-plugins/#kubenet) | ||||
|   | ||||
| @@ -137,14 +137,14 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox | ||||
| 		// In this case however caching the IP will add a subtle performance enhancement by avoiding | ||||
| 		// calls to network namespace of the pod to query the IP of the veth interface on every | ||||
| 		// SandboxStatus request. | ||||
| 		sandbox.IP, sandbox.CNIResult, err = c.setupPod(ctx, id, sandbox.NetNSPath, config) | ||||
| 		sandbox.IP, sandbox.AdditionalIPs, sandbox.CNIResult, err = c.setupPodNetwork(ctx, id, sandbox.NetNSPath, config) | ||||
| 		if err != nil { | ||||
| 			return nil, errors.Wrapf(err, "failed to setup network for sandbox %q", id) | ||||
| 		} | ||||
| 		defer func() { | ||||
| 			if retErr != nil { | ||||
| 				// Teardown network if an error is returned. | ||||
| 				if err := c.teardownPod(ctx, id, sandbox.NetNSPath, config); err != nil { | ||||
| 				if err := c.teardownPodNetwork(ctx, id, sandbox.NetNSPath, config); err != nil { | ||||
| 					log.G(ctx).WithError(err).Errorf("Failed to destroy network for sandbox %q", id) | ||||
| 				} | ||||
| 			} | ||||
| @@ -302,31 +302,32 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox | ||||
| 	return &runtime.RunPodSandboxResponse{PodSandboxId: id}, nil | ||||
| } | ||||
|  | ||||
| // setupPod setups up the network for a pod | ||||
| func (c *criService) setupPod(ctx context.Context, id string, path string, config *runtime.PodSandboxConfig) (string, *cni.CNIResult, error) { | ||||
| // setupPodNetwork setups up the network for a pod | ||||
| func (c *criService) setupPodNetwork(ctx context.Context, id string, path string, config *runtime.PodSandboxConfig) (string, []string, *cni.CNIResult, error) { | ||||
| 	if c.netPlugin == nil { | ||||
| 		return "", nil, errors.New("cni config not initialized") | ||||
| 		return "", nil, nil, errors.New("cni config not initialized") | ||||
| 	} | ||||
|  | ||||
| 	opts, err := cniNamespaceOpts(id, config) | ||||
| 	if err != nil { | ||||
| 		return "", nil, errors.Wrap(err, "get cni namespace options") | ||||
| 		return "", nil, nil, errors.Wrap(err, "get cni namespace options") | ||||
| 	} | ||||
|  | ||||
| 	result, err := c.netPlugin.Setup(ctx, id, path, opts...) | ||||
| 	if err != nil { | ||||
| 		return "", nil, err | ||||
| 		return "", nil, nil, err | ||||
| 	} | ||||
| 	logDebugCNIResult(ctx, id, result) | ||||
| 	// Check if the default interface has IP config | ||||
| 	if configs, ok := result.Interfaces[defaultIfName]; ok && len(configs.IPConfigs) > 0 { | ||||
| 		return selectPodIP(configs.IPConfigs), result, nil | ||||
| 		ip, additionalIPs := selectPodIPs(configs.IPConfigs) | ||||
| 		return ip, additionalIPs, result, nil | ||||
| 	} | ||||
| 	// If it comes here then the result was invalid so destroy the pod network and return error | ||||
| 	if err := c.teardownPod(ctx, id, path, config); err != nil { | ||||
| 	if err := c.teardownPodNetwork(ctx, id, path, config); err != nil { | ||||
| 		log.G(ctx).WithError(err).Errorf("Failed to destroy network for sandbox %q", id) | ||||
| 	} | ||||
| 	return "", result, errors.Errorf("failed to find network info for sandbox %q", id) | ||||
| 	return "", nil, result, errors.Errorf("failed to find network info for sandbox %q", id) | ||||
| } | ||||
|  | ||||
| // cniNamespaceOpts get CNI namespace options from sandbox config. | ||||
| @@ -426,14 +427,28 @@ func toCNIDNS(dns *runtime.DNSConfig) *cni.DNS { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // selectPodIP select an ip from the ip list. It prefers ipv4 more than ipv6. | ||||
| func selectPodIP(ipConfigs []*cni.IPConfig) string { | ||||
| // selectPodIPs select an ip from the ip list. It prefers ipv4 more than ipv6 | ||||
| // and returns the additional ips | ||||
| // TODO(random-liu): Revisit the ip order in the ipv6 beta stage. (cri#1278) | ||||
| func selectPodIPs(ipConfigs []*cni.IPConfig) (string, []string) { | ||||
| 	var ( | ||||
| 		additionalIPs []string | ||||
| 		ip            string | ||||
| 	) | ||||
| 	for _, c := range ipConfigs { | ||||
| 		if c.IP.To4() != nil { | ||||
| 			return c.IP.String() | ||||
| 		if c.IP.To4() != nil && ip == "" { | ||||
| 			ip = c.IP.String() | ||||
| 		} else { | ||||
| 			additionalIPs = append(additionalIPs, c.IP.String()) | ||||
| 		} | ||||
| 	} | ||||
| 	return ipConfigs[0].IP.String() | ||||
| 	if ip != "" { | ||||
| 		return ip, additionalIPs | ||||
| 	} | ||||
| 	if len(ipConfigs) == 1 { | ||||
| 		return additionalIPs[0], nil | ||||
| 	} | ||||
| 	return additionalIPs[0], additionalIPs[1:] | ||||
| } | ||||
|  | ||||
| // untrustedWorkload returns true if the sandbox contains untrusted workload. | ||||
|   | ||||
| @@ -246,16 +246,29 @@ func TestToCNIPortMappings(t *testing.T) { | ||||
|  | ||||
| func TestSelectPodIP(t *testing.T) { | ||||
| 	for desc, test := range map[string]struct { | ||||
| 		ips      []string | ||||
| 		expected string | ||||
| 		ips                   []string | ||||
| 		expectedIP            string | ||||
| 		expectedAdditionalIPs []string | ||||
| 	}{ | ||||
| 		"ipv4 should be picked even if ipv6 comes first": { | ||||
| 			ips:      []string{"2001:db8:85a3::8a2e:370:7334", "192.168.17.43"}, | ||||
| 			expected: "192.168.17.43", | ||||
| 			ips:                   []string{"2001:db8:85a3::8a2e:370:7334", "192.168.17.43"}, | ||||
| 			expectedIP:            "192.168.17.43", | ||||
| 			expectedAdditionalIPs: []string{"2001:db8:85a3::8a2e:370:7334"}, | ||||
| 		}, | ||||
| 		"ipv4 should be picked when there is only ipv4": { | ||||
| 			ips:                   []string{"192.168.17.43"}, | ||||
| 			expectedIP:            "192.168.17.43", | ||||
| 			expectedAdditionalIPs: nil, | ||||
| 		}, | ||||
| 		"ipv6 should be picked when there is no ipv4": { | ||||
| 			ips:      []string{"2001:db8:85a3::8a2e:370:7334"}, | ||||
| 			expected: "2001:db8:85a3::8a2e:370:7334", | ||||
| 			ips:                   []string{"2001:db8:85a3::8a2e:370:7334"}, | ||||
| 			expectedIP:            "2001:db8:85a3::8a2e:370:7334", | ||||
| 			expectedAdditionalIPs: nil, | ||||
| 		}, | ||||
| 		"the first ipv4 should be picked when there are multiple ipv4": { // unlikely to happen | ||||
| 			ips:                   []string{"2001:db8:85a3::8a2e:370:7334", "192.168.17.43", "2001:db8:85a3::8a2e:370:7335", "192.168.17.45"}, | ||||
| 			expectedIP:            "192.168.17.43", | ||||
| 			expectedAdditionalIPs: []string{"2001:db8:85a3::8a2e:370:7334", "2001:db8:85a3::8a2e:370:7335", "192.168.17.45"}, | ||||
| 		}, | ||||
| 	} { | ||||
| 		t.Logf("TestCase %q", desc) | ||||
| @@ -265,7 +278,9 @@ func TestSelectPodIP(t *testing.T) { | ||||
| 				IP: net.ParseIP(ip), | ||||
| 			}) | ||||
| 		} | ||||
| 		assert.Equal(t, test.expected, selectPodIP(ipConfigs)) | ||||
| 		ip, additionalIPs := selectPodIPs(ipConfigs) | ||||
| 		assert.Equal(t, test.expectedIP, ip) | ||||
| 		assert.Equal(t, test.expectedAdditionalIPs, additionalIPs) | ||||
| 	} | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -38,11 +38,11 @@ func (c *criService) PodSandboxStatus(ctx context.Context, r *runtime.PodSandbox | ||||
| 		return nil, errors.Wrap(err, "an error occurred when try to find sandbox") | ||||
| 	} | ||||
|  | ||||
| 	ip, err := c.getIP(sandbox) | ||||
| 	ip, additionalIPs, err := c.getIPs(sandbox) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.Wrap(err, "failed to get sandbox ip") | ||||
| 	} | ||||
| 	status := toCRISandboxStatus(sandbox.Metadata, sandbox.Status.Get(), ip) | ||||
| 	status := toCRISandboxStatus(sandbox.Metadata, sandbox.Status.Get(), ip, additionalIPs) | ||||
| 	if status.GetCreatedAt() == 0 { | ||||
| 		// CRI doesn't allow CreatedAt == 0. | ||||
| 		info, err := sandbox.Container.Info(ctx) | ||||
| @@ -67,39 +67,46 @@ func (c *criService) PodSandboxStatus(ctx context.Context, r *runtime.PodSandbox | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| func (c *criService) getIP(sandbox sandboxstore.Sandbox) (string, error) { | ||||
| func (c *criService) getIPs(sandbox sandboxstore.Sandbox) (string, []string, error) { | ||||
| 	config := sandbox.Config | ||||
|  | ||||
| 	if goruntime.GOOS != "windows" && | ||||
| 		config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtime.NamespaceMode_NODE { | ||||
| 		// For sandboxes using the node network we are not | ||||
| 		// responsible for reporting the IP. | ||||
| 		return "", nil | ||||
| 		return "", nil, nil | ||||
| 	} | ||||
|  | ||||
| 	if closed, err := sandbox.NetNS.Closed(); err != nil { | ||||
| 		return "", errors.Wrap(err, "check network namespace closed") | ||||
| 		return "", nil, errors.Wrap(err, "check network namespace closed") | ||||
| 	} else if closed { | ||||
| 		return "", nil | ||||
| 		return "", nil, nil | ||||
| 	} | ||||
|  | ||||
| 	return sandbox.IP, nil | ||||
| 	return sandbox.IP, sandbox.AdditionalIPs, nil | ||||
| } | ||||
|  | ||||
| // toCRISandboxStatus converts sandbox metadata into CRI pod sandbox status. | ||||
| func toCRISandboxStatus(meta sandboxstore.Metadata, status sandboxstore.Status, ip string) *runtime.PodSandboxStatus { | ||||
| func toCRISandboxStatus(meta sandboxstore.Metadata, status sandboxstore.Status, ip string, additionalIPs []string) *runtime.PodSandboxStatus { | ||||
| 	// Set sandbox state to NOTREADY by default. | ||||
| 	state := runtime.PodSandboxState_SANDBOX_NOTREADY | ||||
| 	if status.State == sandboxstore.StateReady { | ||||
| 		state = runtime.PodSandboxState_SANDBOX_READY | ||||
| 	} | ||||
| 	nsOpts := meta.Config.GetLinux().GetSecurityContext().GetNamespaceOptions() | ||||
| 	var ips []*runtime.PodIP | ||||
| 	for _, additionalIP := range additionalIPs { | ||||
| 		ips = append(ips, &runtime.PodIP{Ip: additionalIP}) | ||||
| 	} | ||||
| 	return &runtime.PodSandboxStatus{ | ||||
| 		Id:        meta.ID, | ||||
| 		Metadata:  meta.Config.GetMetadata(), | ||||
| 		State:     state, | ||||
| 		CreatedAt: status.CreatedAt.UnixNano(), | ||||
| 		Network:   &runtime.PodSandboxNetworkStatus{Ip: ip}, | ||||
| 		Network: &runtime.PodSandboxNetworkStatus{ | ||||
| 			Ip:            ip, | ||||
| 			AdditionalIps: ips, | ||||
| 		}, | ||||
| 		Linux: &runtime.LinuxPodSandboxStatus{ | ||||
| 			Namespaces: &runtime.Namespace{ | ||||
| 				Options: &runtime.NamespaceOption{ | ||||
|   | ||||
| @@ -31,6 +31,7 @@ func TestPodSandboxStatus(t *testing.T) { | ||||
| 		id = "test-id" | ||||
| 		ip = "10.10.10.10" | ||||
| 	) | ||||
| 	additionalIPs := []string{"8.8.8.8", "2001:db8:85a3::8a2e:370:7334"} | ||||
| 	createdAt := time.Now() | ||||
| 	config := &runtime.PodSandboxConfig{ | ||||
| 		Metadata: &runtime.PodSandboxMetadata{ | ||||
| @@ -62,7 +63,17 @@ func TestPodSandboxStatus(t *testing.T) { | ||||
| 		Id:        id, | ||||
| 		Metadata:  config.GetMetadata(), | ||||
| 		CreatedAt: createdAt.UnixNano(), | ||||
| 		Network:   &runtime.PodSandboxNetworkStatus{Ip: ip}, | ||||
| 		Network: &runtime.PodSandboxNetworkStatus{ | ||||
| 			Ip: ip, | ||||
| 			AdditionalIps: []*runtime.PodIP{ | ||||
| 				{ | ||||
| 					Ip: additionalIPs[0], | ||||
| 				}, | ||||
| 				{ | ||||
| 					Ip: additionalIPs[1], | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		Linux: &runtime.LinuxPodSandboxStatus{ | ||||
| 			Namespaces: &runtime.Namespace{ | ||||
| 				Options: &runtime.NamespaceOption{ | ||||
| @@ -99,7 +110,7 @@ func TestPodSandboxStatus(t *testing.T) { | ||||
| 			State:     test.state, | ||||
| 		} | ||||
| 		expected.State = test.expectedState | ||||
| 		got := toCRISandboxStatus(metadata, status, ip) | ||||
| 		got := toCRISandboxStatus(metadata, status, ip, additionalIPs) | ||||
| 		assert.Equal(t, expected, got) | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -79,7 +79,7 @@ func (c *criService) StopPodSandbox(ctx context.Context, r *runtime.StopPodSandb | ||||
| 		} else if closed { | ||||
| 			netNSPath = "" | ||||
| 		} | ||||
| 		if err := c.teardownPod(ctx, id, netNSPath, sandbox.Config); err != nil { | ||||
| 		if err := c.teardownPodNetwork(ctx, id, netNSPath, sandbox.Config); err != nil { | ||||
| 			return nil, errors.Wrapf(err, "failed to destroy network for sandbox %q", id) | ||||
| 		} | ||||
| 		if err = sandbox.NetNS.Remove(); err != nil { | ||||
| @@ -155,8 +155,8 @@ func (c *criService) waitSandboxStop(ctx context.Context, sandbox sandboxstore.S | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // teardownPod removes the network from the pod | ||||
| func (c *criService) teardownPod(ctx context.Context, id string, path string, config *runtime.PodSandboxConfig) error { | ||||
| // teardownPodNetwork removes the network from the pod | ||||
| func (c *criService) teardownPodNetwork(ctx context.Context, id string, path string, config *runtime.PodSandboxConfig) error { | ||||
| 	if c.netPlugin == nil { | ||||
| 		return errors.New("cni config not initialized") | ||||
| 	} | ||||
|   | ||||
| @@ -17,8 +17,10 @@ limitations under the License. | ||||
| package server | ||||
|  | ||||
| import ( | ||||
| 	"net" | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"text/template" | ||||
|  | ||||
| 	"github.com/containerd/containerd/log" | ||||
| @@ -32,17 +34,36 @@ import ( | ||||
| type cniConfigTemplate struct { | ||||
| 	// PodCIDR is the cidr for pods on the node. | ||||
| 	PodCIDR string | ||||
| 	// PodCIDRRanges is the cidr ranges for pods on the node. | ||||
| 	PodCIDRRanges []string | ||||
| 	// Routes is a list of routes configured. | ||||
| 	Routes []string | ||||
| } | ||||
|  | ||||
| // cniConfigFileName is the name of cni config file generated by containerd. | ||||
| const cniConfigFileName = "10-containerd-net.conflist" | ||||
| const ( | ||||
| 	// cniConfigFileName is the name of cni config file generated by containerd. | ||||
| 	cniConfigFileName = "10-containerd-net.conflist" | ||||
| 	// zeroCIDRv6 is the null route for IPv6. | ||||
| 	zeroCIDRv6 = "::/0" | ||||
| 	// zeroCIDRv4 is the null route for IPv4. | ||||
| 	zeroCIDRv4 = "0.0.0.0/0" | ||||
| ) | ||||
|  | ||||
| // UpdateRuntimeConfig updates the runtime config. Currently only handles podCIDR updates. | ||||
| func (c *criService) UpdateRuntimeConfig(ctx context.Context, r *runtime.UpdateRuntimeConfigRequest) (*runtime.UpdateRuntimeConfigResponse, error) { | ||||
| 	podCIDR := r.GetRuntimeConfig().GetNetworkConfig().GetPodCidr() | ||||
| 	if podCIDR == "" { | ||||
| 	podCIDRs := r.GetRuntimeConfig().GetNetworkConfig().GetPodCidr() | ||||
| 	if podCIDRs == "" { | ||||
| 		return &runtime.UpdateRuntimeConfigResponse{}, nil | ||||
| 	} | ||||
| 	cidrs := strings.Split(podCIDRs, ",") | ||||
| 	for i := range cidrs { | ||||
| 		cidrs[i] = strings.TrimSpace(cidrs[i]) | ||||
| 	} | ||||
| 	routes, err := getRoutes(cidrs) | ||||
| 	if err != nil { | ||||
| 		return nil, errors.Wrap(err, "get routes") | ||||
| 	} | ||||
|  | ||||
| 	confTemplate := c.config.NetworkPluginConfTemplate | ||||
| 	if confTemplate == "" { | ||||
| 		log.G(ctx).Info("No cni config template is specified, wait for other system components to drop the config.") | ||||
| @@ -70,8 +91,38 @@ func (c *criService) UpdateRuntimeConfig(ctx context.Context, r *runtime.UpdateR | ||||
| 		return nil, errors.Wrapf(err, "failed to open cni config file %q", confFile) | ||||
| 	} | ||||
| 	defer f.Close() | ||||
| 	if err := t.Execute(f, cniConfigTemplate{PodCIDR: podCIDR}); err != nil { | ||||
| 	if err := t.Execute(f, cniConfigTemplate{ | ||||
| 		PodCIDR:       cidrs[0], | ||||
| 		PodCIDRRanges: cidrs, | ||||
| 		Routes:        routes, | ||||
| 	}); err != nil { | ||||
| 		return nil, errors.Wrapf(err, "failed to generate cni config file %q", confFile) | ||||
| 	} | ||||
| 	return &runtime.UpdateRuntimeConfigResponse{}, nil | ||||
| } | ||||
|  | ||||
| // getRoutes generates required routes for the passed in cidrs. | ||||
| func getRoutes(cidrs []string) ([]string, error) { | ||||
| 	var ( | ||||
| 		routes       []string | ||||
| 		hasV4, hasV6 bool | ||||
| 	) | ||||
| 	for _, c := range cidrs { | ||||
| 		_, cidr, err := net.ParseCIDR(c) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		if cidr.IP.To4() != nil { | ||||
| 			hasV4 = true | ||||
| 		} else { | ||||
| 			hasV6 = true | ||||
| 		} | ||||
| 	} | ||||
| 	if hasV4 { | ||||
| 		routes = append(routes, zeroCIDRv4) | ||||
| 	} | ||||
| 	if hasV6 { | ||||
| 		routes = append(routes, zeroCIDRv6) | ||||
| 	} | ||||
| 	return routes, nil | ||||
| } | ||||
|   | ||||
| @@ -45,14 +45,13 @@ func TestUpdateRuntimeConfig(t *testing.T) { | ||||
| 		"ipam": { | ||||
| 			"type": "host-local", | ||||
| 			"subnet": "{{.PodCIDR}}", | ||||
| 			"routes": [ | ||||
| 			{"dst": "0.0.0.0/0"} | ||||
| 			] | ||||
| 			"ranges": [{{range $i, $range := .PodCIDRRanges}}{{if $i}}, {{end}}[{"subnet": "{{$range}}"}]{{end}}], | ||||
| 			"routes": [{{range $i, $route := .Routes}}{{if $i}}, {{end}}{"dst": "{{$route}}"}{{end}}] | ||||
| 		} | ||||
| 	}, | ||||
| 	] | ||||
| }` | ||||
| 		testCIDR = "10.0.0.0/24" | ||||
| 		testCIDR = "10.0.0.0/24, 2001:4860:4860::8888/32" | ||||
| 		expected = ` | ||||
| { | ||||
| 	"name": "test-pod-network", | ||||
| @@ -64,9 +63,8 @@ func TestUpdateRuntimeConfig(t *testing.T) { | ||||
| 		"ipam": { | ||||
| 			"type": "host-local", | ||||
| 			"subnet": "10.0.0.0/24", | ||||
| 			"routes": [ | ||||
| 			{"dst": "0.0.0.0/0"} | ||||
| 			] | ||||
| 			"ranges": [[{"subnet": "10.0.0.0/24"}], [{"subnet": "2001:4860:4860::8888/32"}]], | ||||
| 			"routes": [{"dst": "0.0.0.0/0"}, {"dst": "::/0"}] | ||||
| 		} | ||||
| 	}, | ||||
| 	] | ||||
|   | ||||
| @@ -55,6 +55,8 @@ type Metadata struct { | ||||
| 	NetNSPath string | ||||
| 	// IP of Pod if it is attached to non host network | ||||
| 	IP string | ||||
| 	// AdditionalIPs of the Pod if it is attached to non host network | ||||
| 	AdditionalIPs []string | ||||
| 	// RuntimeHandler is the runtime handler name of the pod. | ||||
| 	RuntimeHandler string | ||||
| 	// CNIresult resulting configuration for attached network namespace interfaces | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Antonio Ojea
					Antonio Ojea