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

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

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

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


解析 | OpenShift源码简析之pod网络配置(一)_第1张图片
image

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

创建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    } 910    // Create pod logs directory11    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, err16    }1718    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, err23    }2425    return podSandBoxID, "", nil26}

该方法首先会调用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 := defaultSandboxImage11    podSandboxImage := ds.podSandboxImage12    if len(podSandboxImage) != 0 {13        image = podSandboxImage14    }1516    // 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-repository18    // Only pull sandbox image when it's not present - v1.PullIfNotPresent.19    if err := ensureSandboxImageExists(ds.client, image); err != nil {20        return nil, err21    }2223    // 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    }3233    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}3738    ds.setNetworkReady(createResp.ID, false)39    defer func(e *error) {40        // Set networking ready depending on the error return of41        // the parent function42        if *e == nil {43            ds.setNetworkReady(createResp.ID, true)44        }45    }(&err)4647    // Step 3: Create Sandbox Checkpoint.48    if err = ds.checkpointHandler.CreateCheckpoint(createResp.ID, constructPodSandboxCheckpoint(config)); err != nil {49        return nil, err50    }5152    // Step 4: Start the sandbox container.53    // Assume kubelet's garbage collector would remove the sandbox later, if54    // 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    }5960    // 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 overwritten63    // after sandbox creation to override docker's behaviour. This resolv.conf64    // file is shared by all containers of the same pod, and needs to be modified65    // 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        }7172        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    }7677    // Do not invoke network plugins if in hostNetwork mode.78    if config.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtimeapi.NamespaceMode_NODE {79        return resp, nil80    }8182    // 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 pod86    // sandbox networking, but it might insert iptables rules or open ports87    // on the host as well, to satisfy parts of the pod spec that aren't88    // 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, err98}

在上面代码的第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    } 910    repoToPull, _, _, err := parsers.ParseImageName(image)11    if err != nil {12        return err13    }1415    keyring := credentialprovider.NewDockerKeyring()16    creds, withCredentials := keyring.Lookup(repoToPull)17    if !withCredentials {18        glog.V(3).Infof("Pulling image %q without credentials", image)1920        err := client.PullImage(image, dockertypes.AuthConfig{}, dockertypes.ImagePullOptions{})21        if err != nil {22            return fmt.Errorf("failed pulling image %q: %v", image, err)23        }2425        return nil26    }2728    var pullErrs []error29    for _, currentCreds := range creds {30        authConfig := credentialprovider.LazyProvide(currentCreds)31        err := client.PullImage(image, authConfig, dockertypes.ImagePullOptions{})32        // If there was no error, return success33        if err == nil {34            return nil35        }3637        pullErrs = append(pullErrs, err)38    }3940    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, ctxErr12    }13    if err != nil {14        return nil, err15    }16    return &createResp, nil17}

该方法在第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 ctxErr7    }8    return err9}

从上面的代码第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    }1112    return nil13}

该方法主要逻辑是第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    } 910    // Windows doesn't have loNetwork. It comes only with Linux11    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 err15        }16    }1718    _, 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 err22    }2324    return err25}

该方法先调用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.defaultNetwork5}

该方法返回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    }1213    sort.Strings(files)14    for _, confFile := range files {15        var confList *libcni.NetworkConfigList16        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                continue21            }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                continue27            }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                continue33            }3435            confList, err = libcni.ConfListFromConf(conf)36            if err != nil {37                glog.Warningf("Error converting CNI config file %s to list: %v", confFile, err)38                continue39            }40        }41        if len(confList.Plugins) == 0 {42            glog.Warningf("CNI config list %s has no networks, skipping", confFile)43            continue44        }45        confType := confList.Plugins[0].Network.Type4647        // 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, nil54    }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          string3    NetworkConfig *libcni.NetworkConfigList4    CNIConfig     libcni.CNI5}

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, err14    }1516    return res, nil17}

该方法首先调用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 passed10    // to the plugin11    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, err12        }1314        prevResult, err = invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args("ADD", rt))15        if err != nil {16            return nil, err17        }18    }1920    return prevResult, nil21}

该方法首先调用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(32, 32), 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            }103104            // Add a route to service network via SDN105            _, 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        }117118        return nil119    })120    if err != nil {121        return err122    }123124    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的处理流程。

你可能感兴趣的:(解析 | OpenShift源码简析之pod网络配置(一))