Report Additional POD IPs
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
parent
9d60f9c56e
commit
fcd6bf318b
@ -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.
|
||||
|
@ -247,15 +247,28 @@ func TestToCNIPortMappings(t *testing.T) {
|
||||
func TestSelectPodIP(t *testing.T) {
|
||||
for desc, test := range map[string]struct {
|
||||
ips []string
|
||||
expected 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",
|
||||
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",
|
||||
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
|
||||
}
|
||||
|
||||
const (
|
||||
// cniConfigFileName is the name of cni config file generated by containerd.
|
||||
const cniConfigFileName = "10-containerd-net.conflist"
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user