解析 | OpenShift源码简析之pod网络配置(一)

openshift底层是通过kubelet来管理pod,kubelet通过CNI插件来配置pod网络.openshift node节点在启动的时会在一个goroutine中启动kubelet, 由kubelet来负责pod的管理工作。

本文主要从源码的角度入手,简单分析在openshift环境下kubelet是如何通过调用openshift sdn插件来配置pod网络。

我们先看一张pod网络配置的流程图,如下:

接下来根据流程图对各部分代码进行分析:

创建POD

当kubelet接受到pod创建请求时,会调用底层的docker来创建pod。调用入口位于pkg/kubelet/kuberuntime/kuberuntime_manager.go#L643,代码如下:

1podSandboxID, msg, err = m.createPodSandbox(pod, podContainerChanges.Attempt)

如上所示,kubelet是通过调用createPodSandbox这个方法来创建pod,该方法的定义位于pkg/kubelet/kuberuntime/kuberuntime_sandbox.go#L35内容如下:

 1// createPodSandbox creates a pod sandbox and returns (podSandBoxID, message, error).
 2func (m *kubeGenericRuntimeManager) createPodSandbox(pod *v1.Pod, attempt uint32) (string, string, error) {
 3    podSandboxConfig, err := m.generatePodSandboxConfig(pod, attempt)
 4    if err != nil {
 5        message := fmt.Sprintf("GeneratePodSandboxConfig for pod %q failed: %v", format.Pod(pod), err)
 6        glog.Error(message)
 7        return "", message, err
 8    }
 9
10    // Create pod logs directory
11    err = m.osInterface.MkdirAll(podSandboxConfig.LogDirectory, 0755)
12    if err != nil {
13        message := fmt.Sprintf("Create pod log directory for pod %q failed: %v", format.Pod(pod), err)
14        glog.Errorf(message)
15        return "", message, err
16    }
17
18    podSandBoxID, err := m.runtimeService.RunPodSandbox(podSandboxConfig)
19    if err != nil {
20        message := fmt.Sprintf("CreatePodSandbox for pod %q failed: %v", format.Pod(pod), err)
21        glog.Error(message)
22        return "", message, err
23    }
24
25    return podSandBoxID, ""nil
26}

该方法首先会调用generatePodSandboxConfig来生成pod sandbox配置文件,然后调用MkdirAll方法来创建pod的日志目录,最后调用RunPodSandbox来完成具体的pod创建工作。

RunPodSandbox方法位于pkg/kubelet/dockershim/docker_sandbox.go#L79, 内容如下:

 1// RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure
 2// the sandbox is in ready state.
 3// For docker, PodSandbox is implemented by a container holding the network
 4// namespace for the pod.
 5// Note: docker doesn't use LogDirectory (yet).
 6func (ds *dockerService) RunPodSandbox(ctx context.Context, r *runtimeapi.RunPodSandboxRequest) (*runtimeapi.RunPodSandboxResponse, error) {
 7    config := r.GetConfig()
 8
 9    // Step 1: Pull the image for the sandbox.
10    image := defaultSandboxImage
11    podSandboxImage := ds.podSandboxImage
12    if len(podSandboxImage) != 0 {
13        image = podSandboxImage
14    }
15
16    // NOTE: To use a custom sandbox image in a private repository, users need to configure the nodes with credentials properly.
17    // see: http://kubernetes.io/docs/user-guide/images/#configuring-nodes-to-authenticate-to-a-private-repository
18    // Only pull sandbox image when it's not present - v1.PullIfNotPresent.
19    if err := ensureSandboxImageExists(ds.client, image); err != nil {
20        return nil, err
21    }
22
23    // Step 2: Create the sandbox container.
24    createConfig, err := ds.makeSandboxDockerConfig(config, image)
25    if err != nil {
26        return nil, fmt.Errorf("failed to make sandbox docker config for pod %q: %v", config.Metadata.Name, err)
27    }
28    createResp, err := ds.client.CreateContainer(*createConfig)
29    if err != nil {
30        createResp, err = recoverFromCreationConflictIfNeeded(ds.client, *createConfig, err)
31    }
32
33    if err != nil || createResp == nil {
34        return nil, fmt.Errorf("failed to create a sandbox for pod %q: %v", config.Metadata.Name, err)
35    }
36    resp := &runtimeapi.RunPodSandboxResponse{PodSandboxId: createResp.ID}
37
38    ds.setNetworkReady(createResp.ID, false)
39    defer func(e *error) {
40        // Set networking ready depending on the error return of
41        // the parent function
42        if *e == nil {
43            ds.setNetworkReady(createResp.ID, true)
44        }
45    }(&err)
46
47    // Step 3: Create Sandbox Checkpoint.
48    if err = ds.checkpointHandler.CreateCheckpoint(createResp.ID, constructPodSandboxCheckpoint(config)); err != nil {
49        return nil, err
50    }
51
52    // Step 4: Start the sandbox container.
53    // Assume kubelet's garbage collector would remove the sandbox later, if
54    // startContainer failed.
55    err = ds.client.StartContainer(createResp.ID)
56    if err != nil {
57        return nil, fmt.Errorf("failed to start sandbox container for pod %q: %v", config.Metadata.Name, err)
58    }
59
60    // Rewrite resolv.conf file generated by docker.
61    // NOTE: cluster dns settings aren't passed anymore to docker api in all cases,
62    // not only for pods with host network: the resolver conf will be overwritten
63    // after sandbox creation to override docker's behaviour. This resolv.conf
64    // file is shared by all containers of the same pod, and needs to be modified
65    // only once per pod.
66    if dnsConfig := config.GetDnsConfig(); dnsConfig != nil {
67        containerInfo, err := ds.client.InspectContainer(createResp.ID)
68        if err != nil {
69            return nil, fmt.Errorf("failed to inspect sandbox container for pod %q: %v", config.Metadata.Name, err)
70        }
71
72        if err := rewriteResolvFile(containerInfo.ResolvConfPath, dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options); err != nil {
73            return nil, fmt.Errorf("rewrite resolv.conf failed for pod %q: %v", config.Metadata.Name, err)
74        }
75    }
76
77    // Do not invoke network plugins if in hostNetwork mode.
78    if config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtimeapi.NamespaceMode_NODE {
79        return resp, nil
80    }
81
82    // Step 5: Setup networking for the sandbox.
83    // All pod networking is setup by a CNI plugin discovered at startup time.
84    // This plugin assigns the pod ip, sets up routes inside the sandbox,
85    // creates interfaces etc. In theory, its jurisdiction ends with pod
86    // sandbox networking, but it might insert iptables rules or open ports
87    // on the host as well, to satisfy parts of the pod spec that aren't
88    // recognized by the CNI standard yet.
89    cID := kubecontainer.BuildContainerID(runtimeName, createResp.ID)
90    err = ds.network.SetUpPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID, config.Annotations)
91    if err != nil {
92        // TODO(random-liu): Do we need to teardown network here?
93        if err := ds.client.StopContainer(createResp.ID, defaultSandboxGracePeriod); err != nil {
94            glog.Warningf("Failed to stop sandbox container %q for pod %q: %v", createResp.ID, config.Metadata.Name, err)
95        }
96    }
97    return resp, err
98}

在上面代码的第19行,首先通过调用ensureSandboxImageExists方法来拉取pod infra容器的镜像,确保在infra容器创建时镜像已经在本地。该方法的定义位于pkg/kubelet/dockershim/helpers.go#L316,内容如下:

 1func ensureSandboxImageExists(client libdocker.Interface, image string) error {
 2    _, err := client.InspectImageByRef(image)
 3    if err == nil {
 4        return nil
 5    }
 6    if !libdocker.IsImageNotFoundError(err) {
 7        return fmt.Errorf("failed to inspect sandbox image %q: %v", image, err)
 8    }
 9
10    repoToPull, _, _, err := parsers.ParseImageName(image)
11    if err != nil {
12        return err
13    }
14
15    keyring := credentialprovider.NewDockerKeyring()
16    creds, withCredentials := keyring.Lookup(repoToPull)
17    if !withCredentials {
18        glog.V(3).Infof("Pulling image %q without credentials", image)
19
20        err := client.PullImage(image, dockertypes.AuthConfig{}, dockertypes.ImagePullOptions{})
21        if err != nil {
22            return fmt.Errorf("failed pulling image %q: %v", image, err)
23        }
24
25        return nil
26    }
27
28    var pullErrs []error
29    for _, currentCreds := range creds {
30        authConfig := credentialprovider.LazyProvide(currentCreds)
31        err := client.PullImage(image, authConfig, dockertypes.ImagePullOptions{})
32        // If there was no error, return success
33        if err == nil {
34            return nil
35        }
36
37        pullErrs = append(pullErrs, err)
38    }
39
40    return utilerrors.NewAggregate(pullErrs)
41}

该方法会首先判断镜像在不在本地,如果已经存在于本地则直接返回,如果不存在则调用docker client拉取镜像,拉取镜像时还会处理认证相关的问题。

在拉取镜像成功后,在第20行调用了CreateContainer来创建infra容器,该方法的定义位于pkg/kubelet/dockershim/libdocker/kube_docker_client.go#L141,内容如下:

 1func (d *kubeDockerClient) CreateContainer(opts dockertypes.ContainerCreateConfig) (*dockercontainer.ContainerCreateCreatedBody, error) {
 2    ctx, cancel := d.getTimeoutContext()
 3    defer cancel()
 4    // we provide an explicit default shm size as to not depend on docker daemon.
 5    // TODO: evaluate exposing this as a knob in the API
 6    if opts.HostConfig != nil && opts.HostConfig.ShmSize <= 0 {
 7        opts.HostConfig.ShmSize = defaultShmSize
 8    }
 9    createResp, err := d.client.ContainerCreate(ctx, opts.Config, opts.HostConfig, opts.NetworkingConfig, opts.Name)
10    if ctxErr := contextError(ctx); ctxErr != nil {
11        return nil, ctxErr
12    }
13    if err != nil {
14        return nil, err
15    }
16    return &createResp, nil
17}

该方法在第9行实际上是调用docker client来创建容器,最终也就是调用docker的remote api来创建的容器。

在创建infra容器成功之后,在代码的第55行通过调用StartContainer来启动上一步中创建成功的容器。StartContainer的定义位于pkg/kubelet/dockershim/libdocker/kube_docker_client.go#L159,内容如下:

1func (d *kubeDockerClient) StartContainer(id string) error {
2    ctx, cancel := d.getTimeoutContext()
3    defer cancel()
4    err := d.client.ContainerStart(ctx, id, dockertypes.ContainerStartOptions{})
5    if ctxErr := contextError(ctx); ctxErr != nil {
6        return ctxErr
7    }
8    return err
9}

从上面的代码第4行可以看出,跟CreateContainer类似,这一步也是通过调用docker的api接口来完成。

至此,pod创建的工作完成,从上面的分析可以看出kubelet最终是通过调用docker的接口来完成pod的创建。

这里需要说明一点,kubelet在创建pod时是先创建一个infra容器,配置好该容器的网络,然后创建真正工作的业务容器,最后再把业务容器的网络加到infra容器的网络命名空间中,相当于业务容器共享infra容器的网络命名空间。业务容器和infra容器共同组成一个pod。

接下来,我们将分析kubelet是如何通过CNI插件来为pod配置网络。

配置pod网络

在RunPodSandbox方法的第90行,调用了network plugin的SetUpPod方法来配置pod网络。该方法位于pkg/kubelet/network/plugins.go#L406,内容如下:

 1func (pm *PluginManager) SetUpPod(podNamespace, podName string, id kubecontainer.ContainerID, annotations map[string]string) error {
 2    defer recordOperation("set_up_pod", time.Now())
 3    fullPodName := kubecontainer.BuildPodFullName(podName, podNamespace)
 4    pm.podLock(fullPodName).Lock()
 5    defer pm.podUnlock(fullPodName)
 6
 7    glog.V(3).Infof("Calling network plugin %s to set up pod %q", pm.plugin.Name(), fullPodName)
 8    if err := pm.plugin.SetUpPod(podNamespace, podName, id, annotations); err != nil {
 9        return fmt.Errorf("NetworkPlugin %s failed to set up pod %q network: %v", pm.plugin.Name(), fullPodName, err)
10    }
11
12    return nil
13}

该方法主要逻辑是第8行,调用plugin的SetUpPod方法,这里plugin是一个interface, 具体使用哪个plugin是由kubelet的启动参数–network-plugin决定的,openshift在启动kubelet时传递的参数是–netowr-plugin=cni,也就是调用cni插件的SetupPod方法。该方法的定义位于:pkg/kubelet/network/cni/cni.go#L208,内容如下:

 1func (plugin *cniNetworkPlugin) SetUpPod(namespace string, name string, id kubecontainer.ContainerID, annotations map[string]string) error {
 2    if err := plugin.checkInitialized(); err != nil {
 3        return err
 4    }
 5    netnsPath, err := plugin.host.GetNetNS(id.ID)
 6    if err != nil {
 7        return fmt.Errorf("CNI failed to retrieve network namespace path: %v", err)
 8    }
 9
10    // Windows doesn't have loNetwork. It comes only with Linux
11    if plugin.loNetwork != nil {
12        if _, err = plugin.addToNetwork(plugin.loNetwork, name, namespace, id, netnsPath); err != nil {
13            glog.Errorf("Error while adding to cni lo network: %s", err)
14            return err
15        }
16    }
17
18    _, err = plugin.addToNetwork(plugin.getDefaultNetwork(), name, namespace, id, netnsPath)
19    if err != nil {
20        glog.Errorf("Error while adding to cni network: %s", err)
21        return err
22    }
23
24    return err
25}

该方法先调用GetNetNS找到pod所在的netnamespace的路径,该值在后续配置网络时会用到,然后如果系统是linux的话,会调用addToNetwork来配置loopback设备的网络,最后调用addToNetwork来配置pod eth0接口的网络。这里需要关注一下第18行的getDefaultNetwork这个方法,该方法的源码位于pkg/kubelet/network/cni/cni.go#L177, 内容如下:

1func (plugin *cniNetworkPlugin) getDefaultNetwork() *cniNetwork {
2    plugin.RLock()
3    defer plugin.RUnlock()
4    return plugin.defaultNetwork
5}

该方法返回plugin.defaultNetwork,该值最终是调用getDefaultCNINetwork方法获取,源码位于pkg/kubelet/network/cni/cni.go#L95, 内容如下:

 1func getDefaultCNINetwork(pluginDir, binDir, vendorCNIDirPrefix string) (*cniNetwork, error) {
 2    if pluginDir == "" {
 3        pluginDir = DefaultNetDir
 4    }
 5    files, err := libcni.ConfFiles(pluginDir, []string{".conf"".conflist"".json"})
 6    switch {
 7    case err != nil:
 8        return nil, err
 9    case len(files) == 0:
10        return nil, fmt.Errorf("No networks found in %s", pluginDir)
11    }
12
13    sort.Strings(files)
14    for _, confFile := range files {
15        var confList *libcni.NetworkConfigList
16        if strings.HasSuffix(confFile, ".conflist") {
17            confList, err = libcni.ConfListFromFile(confFile)
18            if err != nil {
19                glog.Warningf("Error loading CNI config list file %s: %v", confFile, err)
20                continue
21            }
22        } else {
23            conf, err := libcni.ConfFromFile(confFile)
24            if err != nil {
25                glog.Warningf("Error loading CNI config file %s: %v", confFile, err)
26                continue
27            }
28            // Ensure the config has a "type" so we know what plugin to run.
29            // Also catches the case where somebody put a conflist into a conf file.
30            if conf.Network.Type == "" {
31                glog.Warningf("Error loading CNI config file %s: no 'type'; perhaps this is a .conflist?", confFile)
32                continue
33            }
34
35            confList, err = libcni.ConfListFromConf(conf)
36            if err != nil {
37                glog.Warningf("Error converting CNI config file %s to list: %v", confFile, err)
38                continue
39            }
40        }
41        if len(confList.Plugins) == 0 {
42            glog.Warningf("CNI config list %s has no networks, skipping", confFile)
43            continue
44        }
45        confType := confList.Plugins[0].Network.Type
46
47        // Search for vendor-specific plugins as well as default plugins in the CNI codebase.
48        vendorDir := vendorCNIDir(vendorCNIDirPrefix, confType)
49        cninet := &libcni.CNIConfig{
50            Path: []string{vendorDir, binDir},
51        }
52        network := &cniNetwork{name: confList.Name, NetworkConfig: confList, CNIConfig: cninet}
53        return network, nil
54    }
55    return nil, fmt.Errorf("No valid networks found in %s", pluginDir)
56}

该方法首先读取pluginDir也就是/etc/cni/net.d目录下的所有以”.conf”,”.conflist”或者是”.json”结尾的配置文件,然后解析配置文件,最后生成一个cniNetwork的对象,该对象包含了cni插件的名称,cni插件的配置等等。cniNetwork的定义位于pkg/kubelet/network/cni/cni.go#L58,内容如下:

1type cniNetwork struct {
2    name          string
3    NetworkConfig *libcni.NetworkConfigList
4    CNIConfig     libcni.CNI
5}

openshift node节点在启动时,会在/etc/cni/net.d目录下写入配置文件80-openshift-network.conf,内容如下:

1{
2 “cniVersion”: “0.2.0”,
3 “name”: “openshift-sdn”,
4 “type”: “openshift-sdn”
5}

所以上面的getDefaultCNINetwork的执行实际上是读取到了openshift sdn插件的相关配置。

接下来回到addToNetwork方法,该方法的定义位pkg/kubelet/network/cni/cni.go#L248, 内容如下:

 1func (plugin *cniNetworkPlugin) addToNetwork(network *cniNetwork, podName string, podNamespace string, podSandboxID kubecontainer.ContainerID, podNetnsPath string) (cnitypes.Result, error) {
 2    rt, err := plugin.buildCNIRuntimeConf(podName, podNamespace, podSandboxID, podNetnsPath)
 3    if err != nil {
 4        glog.Errorf("Error adding network when building cni runtime conf: %v", err)
 5        return nil, err
 6    }
 7
 8    netConf, cniNet := network.NetworkConfig, network.CNIConfig
 9    glog.V(4).Infof("About to add CNI network %v (type=%v)", netConf.Name, netConf.Plugins[0].Network.Type)
10    res, err := cniNet.AddNetworkList(netConf, rt)
11    if err != nil {
12        glog.Errorf("Error adding network: %v", err)
13        return nil, err
14    }
15
16    return res, nil
17}

该方法首先调用buildCNIRuntimeConf生成一个RuntimeConf对象,该对象在调用cni插件时会用到,定义位于vendor/github.com/containernetworking/cni/libcni/api.go#L26,内容如下:

 1type RuntimeConf struct {
 2    ContainerID string
 3    NetNS       string
 4    IfName      string
 5    Args        [][2]string
 6    // A dictionary of capability-specific data passed by the runtime
 7    // to plugins as top-level keys in the 'runtimeConfig' dictionary
 8    // of the plugin's stdin data.  libcni will ensure that only keys
 9    // in this map which match the capabilities of the plugin are passed
10    // to the plugin
11    CapabilityArgs map[string]interface{}
12}

ContainerID是创建的pod的ID;NetNS是pod所在的netspace的path,这个在之前有提到过;IfName在kubelet里是定义为一个常量值eth0;Args包含了一些pod相关的参数;CapabilityArgs包含了pod的portmappging的配置。

接着分析addToNetwork,该方法的第10行调用了cniNet的AddNetworkList方法,该方法的定义位于vendor/github.com/containernetworking/cni/libcni/api.go#L123,内容如下:

 1func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) {
 2    var prevResult types.Result
 3    for _, net := range list.Plugins {
 4        pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path)
 5        if err != nil {
 6            return nil, err
 7        }
 8
 9        newConf, err := buildOneConfig(list, net, prevResult, rt)
10        if err != nil {
11            return nil, err
12        }
13
14        prevResult, err = invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args("ADD", rt))
15        if err != nil {
16            return nil, err
17        }
18    }
19
20    return prevResult, nil
21}

该方法首先调用FindInPath这个方法来找到plugin的路径,FindInPath会根据CNI配置的Type在/opt/cni/bin下面找到同名的插件,然后返回插件的绝对路径。我们以openshift sdn插件的配置为例,配置的内容如下:

1{
2 “cniVersion”: “0.2.0”,
3 “name”: “openshift-sdn”,
4 “type”: “openshift-sdn”
5}

上面配置的type字段的值是openshift-sdn,也就是FindInPath会在/opt/cni/bin下查找openshift-sdn这个插件,找到后返回插件的绝对路径也就是/opt/cni/bin/opnshift-sdn。
接下来会调用ExecPluginWithResult执行刚才找到的插件,调用时传入了rutingconfig的配置,以及一个ADD参数,ADD参数表示是配置网络,CNI插件都支持ADD,DEL等参数来配置和删除网络。

上面的部分把调用openshift-sdn插件之前的流程都分析完了,下面分析openshift-sdn插件的具体调用流程。openshift-sdn插件的源码位于openshift代码库的pkg/network/sdn-cni-plugin/openshift-sdn.go文件,主要包括一下几个方法:

  • doCNI: 该方法用于向CNIServer发送请求,openshit node节点在启动时会启动一个cniServer, 用于跟cni plugin进行通信,通信的流程下面会分析。
  • CmdAdd: 用于执行ADD请求,在设置pod网络时会被调用,比如上面在调用插件时传入了ADD参数就是调用这个方法。
  • CmdDel: 用于执行DEL请求,在删除pod网络时会被调用。

在这里我们主要分析CmdAdd这个方法,该方法在上面的ExecPluginWithResult方法执行时被调用,CmdAdd方法的定义位于pkg/network/sdn-cni-plugin/openshift-sdn.go#L118,内容如下:

  1func (p *cniPlugin) CmdAdd(args *skel.CmdArgs) error {
  2    req := newCNIRequest(args)
  3    config, err := cniserver.ReadConfig(cniserver.CNIServerConfigFilePath)
  4    if err != nil {
  5        return err
  6    }
  7
  8    var hostVeth, contVeth net.Interface
  9    err = ns.WithNetNSPath(args.Netns, func(hostNS ns.NetNS) error {
 10        hostVeth, contVeth, err = ip.SetupVeth(args.IfName, int(config.MTU), hostNS)
 11        if err != nil {
 12            return fmt.Errorf("failed to create container veth: %v", err)
 13        }
 14        return nil
 15    })
 16    if err != nil {
 17        return err
 18    }
 19    result, err := p.doCNIServerAdd(req, hostVeth.Name)
 20    if err != nil {
 21        return err
 22    }
 23
 24    // current.NewResultFromResult and ipam.ConfigureIface both think that
 25    // a route with no gateway specified means to pass the default gateway
 26    // as the next hop to ip.AddRoute, but that's not what we want; we want
 27    // to pass nil as the next hop. So we need to clear the default gateway.
 28    result020, err := types020.GetResult(result)
 29    if err != nil {
 30        return fmt.Errorf("failed to convert IPAM result: %v", err)
 31    }
 32    defaultGW := result020.IP4.Gateway
 33    result020.IP4.Gateway = nil
 34
 35    result030, err := current.NewResultFromResult(result020)
 36    if err != nil || len(result030.IPs) != 1 || result030.IPs[0].Version != "4" {
 37        return fmt.Errorf("failed to convert IPAM result: %v", err)
 38    }
 39
 40    // Add a sandbox interface record which ConfigureInterface expects.
 41    // The only interface we report is the pod interface.
 42    result030.Interfaces = []*current.Interface{
 43        {
 44            Name:    args.IfName,
 45            Mac:     contVeth.HardwareAddr.String(),
 46            Sandbox: args.Netns,
 47        },
 48    }
 49    result030.IPs[0].Interface = current.Int(0)
 50
 51    err = ns.WithNetNSPath(args.Netns, func(hostNS ns.NetNS) error {
 52        // Set up eth0
 53        if err := ip.SetHWAddrByIP(args.IfName, result030.IPs[0].Address.IP, nil); err != nil {
 54            return fmt.Errorf("failed to set pod interface MAC address: %v", err)
 55        }
 56        if err := ipam.ConfigureIface(args.IfName, result030); err != nil {
 57            return fmt.Errorf("failed to configure container IPAM: %v", err)
 58        }
 59
 60        // Set up lo
 61        link, err := netlink.LinkByName("lo")
 62        if err == nil {
 63            err = netlink.LinkSetUp(link)
 64        }
 65        if err != nil {
 66            return fmt.Errorf("failed to configure container loopback: %v", err)
 67        }
 68
 69        // Set up macvlan0 (if it exists)
 70        link, err = netlink.LinkByName("macvlan0")
 71        if err == nil {
 72            err = netlink.LinkSetUp(link)
 73            if err != nil {
 74                return fmt.Errorf("failed to enable macvlan device: %v", err)
 75            }
 76
 77            // A macvlan can't reach its parent interface's IP, so we need to
 78            // add a route to that via the SDN
 79            var addrs []netlink.Addr
 80            err = hostNS.Do(func(ns.NetNS) error {
 81                parent, err := netlink.LinkByIndex(link.Attrs().ParentIndex)
 82                if err != nil {
 83                    return err
 84                }
 85                addrs, err = netlink.AddrList(parent, netlink.FAMILY_V4)
 86                return err
 87            })
 88            if err != nil {
 89                return fmt.Errorf("failed to configure macvlan device: %v", err)
 90            }
 91            for _, addr := range addrs {
 92                route := &netlink.Route{
 93                    Dst: &net.IPNet{
 94                        IP:   addr.IP,
 95                        Mask: net.CIDRMask(3232),
 96                    },
 97                    Gw: defaultGW,
 98                }
 99                if err := netlink.RouteAdd(route); err != nil {
100                    return fmt.Errorf("failed to add route to node IP: %v", err)
101                }
102            }
103
104            // Add a route to service network via SDN
105            _, serviceIPNet, err := net.ParseCIDR(config.ServiceNetworkCIDR)
106            if err != nil {
107                return fmt.Errorf("failed to parse ServiceNetworkCIDR: %v", err)
108            }
109            route := &netlink.Route{
110                Dst: serviceIPNet,
111                Gw:  defaultGW,
112            }
113            if err := netlink.RouteAdd(route); err != nil {
114                return fmt.Errorf("failed to add route to service network: %v", err)
115            }
116        }
117
118        return nil
119    })
120    if err != nil {
121        return err
122    }
123
124    return result.Print()
125}

该方法首先会调用SetupVeth(第10行)来创建一个虚拟设备对,设备对的一端连到主机上,也就是我们在主机上看到的类似veth3258a5e2这样的虚拟网卡,另一端放到容器里,也就是我们在容器里看到的eth0。

虚拟设备对的两端是相通的。然后调用doCNIServerAdd(第19行)来向cniserver请求IP,请求IP的流程后面分析,请求到IP成功之后在第53行调用SetHWAddrByIP和56行的ConfigureIface配置eth0的IP地址和mac地址。然后从61行到64行是配置loopback接口。还有一部分关于macvlan接口的配置,这里不做分析。

本节的分析到此结束,下一小节我们分析openshift-sdn获取IP时cniServer的处理流程。

K8S中文社区微信公众号

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址