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(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 }
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的处理流程。
登录后评论
立即登录 注册