容器CNI完全解读calico实现(三)
来源:互联网 发布:mysql引擎区别 编辑:程序博客网 时间:2024/06/16 10:02
上一篇介绍了bridge的实现,这里介绍一下calico的实现。和上一篇的结构一样的,先看add然后看del,具体添加网卡的代码如下:
func cmdAdd(args *skel.CmdArgs) error { // Unmarshal the network config, and perform validation conf := NetConf{} if err := json.Unmarshal(args.StdinData, &conf); err != nil { return fmt.Errorf("failed to load netconf: %v", err) } cniVersion := conf.CNIVersion ConfigureLogging(conf.LogLevel) workload, orchestrator, err := GetIdentifiers(args) if err != nil { return err } logger := CreateContextLogger(workload) // Allow the nodename to be overridden by the network config updateNodename(conf, logger) logger.WithFields(log.Fields{ "Orchestrator": orchestrator, "Node": nodename, }).Info("Extracted identifiers") logger.WithFields(log.Fields{"NetConfg": conf}).Info("Loaded CNI NetConf") calicoClient, err := CreateClient(conf) if err != nil { return err } // Always check if there's an existing endpoint. endpoints, err := calicoClient.WorkloadEndpoints().List(api.WorkloadEndpointMetadata{ Node: nodename, Orchestrator: orchestrator, Workload: workload}) if err != nil { return err } logger.Debugf("Retrieved endpoints: %v", endpoints) var endpoint *api.WorkloadEndpoint if len(endpoints.Items) == 1 { endpoint = &endpoints.Items[0] } fmt.Fprintf(os.Stderr, "Calico CNI checking for existing endpoint: %v\n", endpoint) // Collect the result in this variable - this is ultimately what gets "returned" by this function by printing // it to stdout. var result *current.Result // If running under Kubernetes then branch off into the kubernetes code, otherwise handle everything in this // function. if orchestrator == "k8s" { if result, err = k8s.CmdAddK8s(args, conf, nodename, calicoClient, endpoint); err != nil { return err } } else { // Default CNI behavior - use the CNI network name as the Calico profile. profileID := conf.Name if endpoint != nil { // There is an existing endpoint - no need to create another. // This occurs when adding an existing container to a new CNI network // Find the IP address from the endpoint and use that in the response. // Don't create the veth or do any networking. // Just update the profile on the endpoint. The profile will be created if needed during the // profile processing step. fmt.Fprintf(os.Stderr, "Calico CNI appending profile: %s\n", profileID) endpoint.Spec.Profiles = append(endpoint.Spec.Profiles, profileID) result, err = CreateResultFromEndpoint(endpoint) logger.WithField("result", result).Debug("Created result from endpoint") if err != nil { return err } } else { // There's no existing endpoint, so we need to do the following: // 1) Call the configured IPAM plugin to get IP address(es) // 2) Configure the Calico endpoint // 3) Create the veth, configuring it on both the host and container namespace. // 1) Run the IPAM plugin and make sure there's an IP address returned. logger.WithFields(log.Fields{"paths": os.Getenv("CNI_PATH"), "type": conf.IPAM.Type}).Debug("Looking for IPAM plugin in paths") ipamResult, err := ipam.ExecAdd(conf.IPAM.Type, args.StdinData) logger.WithField("IPAM result", ipamResult).Info("Got result from IPAM plugin") if err != nil { return err } // Convert IPAM result into current Result. // IPAM result has a bunch of fields that are optional for an IPAM plugin // but required for a CNI plugin, so this is to populate those fields. // See CNI Spec doc for more details. result, err = current.NewResultFromResult(ipamResult) if err != nil { ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return err } if len(result.IPs) == 0 { ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return goerrors.New("IPAM plugin returned missing IP config") } // Parse endpoint labels passed in by Mesos, and store in a map. labels := map[string]string{} for _, label := range conf.Args.Mesos.NetworkInfo.Labels.Labels { labels[label.Key] = label.Value } // 2) Create the endpoint object endpoint = api.NewWorkloadEndpoint() endpoint.Metadata.Name = args.IfName endpoint.Metadata.Node = nodename endpoint.Metadata.Orchestrator = orchestrator endpoint.Metadata.Workload = workload endpoint.Metadata.Labels = labels endpoint.Spec.Profiles = []string{profileID} logger.WithField("endpoint", endpoint).Debug("Populated endpoint (without nets)") if err = PopulateEndpointNets(endpoint, result); err != nil { // Cleanup IP allocation and return the error. ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return err } logger.WithField("endpoint", endpoint).Info("Populated endpoint (with nets)") fmt.Fprintf(os.Stderr, "Calico CNI using IPs: %s\n", endpoint.Spec.IPNetworks) // 3) Set up the veth hostVethName, contVethMac, err := DoNetworking(args, conf, result, logger, "") if err != nil { // Cleanup IP allocation and return the error. ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return err } logger.WithFields(log.Fields{ "HostVethName": hostVethName, "ContainerVethMac": contVethMac, }).Info("Networked namespace") mac, err := net.ParseMAC(contVethMac) if err != nil { // Cleanup IP allocation and return the error. ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return err } endpoint.Spec.MAC = &cnet.MAC{HardwareAddr: mac} endpoint.Spec.InterfaceName = hostVethName } // Write the endpoint object (either the newly created one, or the updated one with a new ProfileIDs). if _, err := calicoClient.WorkloadEndpoints().Apply(endpoint); err != nil { // Cleanup IP allocation and return the error. ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return err } logger.WithField("endpoint", endpoint).Info("Wrote endpoint to datastore") } // Handle profile creation - this is only done if there isn't a specific policy handler. if conf.Policy.PolicyType == "" { logger.Debug("Handling profiles") // Start by checking if the profile already exists. If it already exists then there is no work to do. // The CNI plugin never updates a profile. exists := true _, err = calicoClient.Profiles().Get(api.ProfileMetadata{Name: conf.Name}) if err != nil { _, ok := err.(errors.ErrorResourceDoesNotExist) if ok { exists = false } else { // Cleanup IP allocation and return the error. ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return err } } if !exists { // The profile doesn't exist so needs to be created. The rules vary depending on whether k8s is being used. // Under k8s (without full policy support) the rule is permissive and allows all traffic. // Otherwise, incoming traffic is only allowed from profiles with the same tag. fmt.Fprintf(os.Stderr, "Calico CNI creating profile: %s\n", conf.Name) var inboundRules []api.Rule if orchestrator == "k8s" { inboundRules = []api.Rule{{Action: "allow"}} } else { inboundRules = []api.Rule{{Action: "allow", Source: api.EntityRule{Tag: conf.Name}}} } profile := &api.Profile{ Metadata: api.ProfileMetadata{ Name: conf.Name, Tags: []string{conf.Name}, }, Spec: api.ProfileSpec{ EgressRules: []api.Rule{{Action: "allow"}}, IngressRules: inboundRules, }, } logger.WithField("profile", profile).Info("Creating profile") if _, err := calicoClient.Profiles().Create(profile); err != nil { // Cleanup IP allocation and return the error. ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return err } } } // Set Gateway to nil. Calico-IPAM doesn't set it, but host-local does. // We modify IPs subnet received from the IPAM plugin (host-local), // so Gateway isn't valid anymore. It is also not used anywhere by Calico. for _, ip := range result.IPs { ip.Gateway = nil } // Print result to stdout, in the format defined by the requested cniVersion. return types.PrintResult(result, cniVersion)}
创建和bridge很相似,不过这里代码有点长,一步一步解析,calico实现了标准的cni的实现,当然可以在kubernetes下面,也可以在任何cni的容器管理平台之上运行。先看k8s相关实现k8s.CmdAddK8s,
func CmdAddK8s(args *skel.CmdArgs, conf utils.NetConf, nodename string, calicoClient *calicoclient.Client, endpoint *api.WorkloadEndpoint) (*current.Result, error) { var err error var result *current.Result k8sArgs := utils.K8sArgs{} err = types.LoadArgs(args.Args, &k8sArgs) if err != nil { return nil, err } utils.ConfigureLogging(conf.LogLevel) workload, orchestrator, err := utils.GetIdentifiers(args) if err != nil { return nil, err } logger := utils.CreateContextLogger(workload) logger.WithFields(log.Fields{ "Orchestrator": orchestrator, "Node": nodename, }).Info("Extracted identifiers for CmdAddK8s") if endpoint != nil { // This happens when Docker or the node restarts. K8s calls CNI with the same parameters as before. // Do the networking (since the network namespace was destroyed and recreated). // There's an existing endpoint - no need to create another. Find the IP address from the endpoint // and use that in the response. result, err = utils.CreateResultFromEndpoint(endpoint) if err != nil { return nil, err } logger.WithField("result", result).Debug("Created result from existing endpoint") // If any labels changed whilst the container was being restarted, they will be picked up by the policy // controller so there's no need to update the labels here. } else { client, err := newK8sClient(conf, logger) if err != nil { return nil, err } logger.WithField("client", client).Debug("Created Kubernetes client") if conf.IPAM.Type == "host-local" && strings.EqualFold(conf.IPAM.Subnet, "usePodCidr") { // We've been told to use the "host-local" IPAM plugin with the Kubernetes podCidr for this node. // Replace the actual value in the args.StdinData as that's what's passed to the IPAM plugin. fmt.Fprintf(os.Stderr, "Calico CNI fetching podCidr from Kubernetes\n") var stdinData map[string]interface{} if err := json.Unmarshal(args.StdinData, &stdinData); err != nil { return nil, err } podCidr, err := getPodCidr(client, conf, nodename) if err != nil { return nil, err } logger.WithField("podCidr", podCidr).Info("Fetched podCidr") stdinData["ipam"].(map[string]interface{})["subnet"] = podCidr fmt.Fprintf(os.Stderr, "Calico CNI passing podCidr to host-local IPAM: %s\n", podCidr) args.StdinData, err = json.Marshal(stdinData) if err != nil { return nil, err } logger.WithField("stdin", string(args.StdinData)).Debug("Updated stdin data") } labels := make(map[string]string) annot := make(map[string]string) // Only attempt to fetch the labels and annotations from Kubernetes // if the policy type has been set to "k8s". This allows users to // run the plugin under Kubernetes without needing it to access the // Kubernetes API if conf.Policy.PolicyType == "k8s" { var err error labels, annot, err = getK8sLabelsAnnotations(client, k8sArgs) if err != nil { return nil, err } logger.WithField("labels", labels).Debug("Fetched K8s labels") logger.WithField("annotations", annot).Debug("Fetched K8s annotations") // Check for calico IPAM specific annotations and set them if needed. if conf.IPAM.Type == "calico-ipam" { v4pools := annot["cni.projectcalico.org/ipv4pools"] v6pools := annot["cni.projectcalico.org/ipv6pools"] if len(v4pools) != 0 || len(v6pools) != 0 { var stdinData map[string]interface{} if err := json.Unmarshal(args.StdinData, &stdinData); err != nil { return nil, err } var v4PoolSlice, v6PoolSlice []string if len(v4pools) > 0 { if err := json.Unmarshal([]byte(v4pools), &v4PoolSlice); err != nil { logger.WithField("IPv4Pool", v4pools).Error("Error parsing IPv4 IPPools") return nil, err } if _, ok := stdinData["ipam"].(map[string]interface{}); !ok { logger.Fatal("Error asserting stdinData type") os.Exit(0) } stdinData["ipam"].(map[string]interface{})["ipv4_pools"] = v4PoolSlice logger.WithField("ipv4_pools", v4pools).Debug("Setting IPv4 Pools") } if len(v6pools) > 0 { if err := json.Unmarshal([]byte(v6pools), &v6PoolSlice); err != nil { logger.WithField("IPv6Pool", v6pools).Error("Error parsing IPv6 IPPools") return nil, err } if _, ok := stdinData["ipam"].(map[string]interface{}); !ok { logger.Fatal("Error asserting stdinData type") os.Exit(0) } stdinData["ipam"].(map[string]interface{})["ipv6_pools"] = v6PoolSlice logger.WithField("ipv6_pools", v6pools).Debug("Setting IPv6 Pools") } newData, err := json.Marshal(stdinData) if err != nil { logger.WithField("stdinData", stdinData).Error("Error Marshaling data") return nil, err } args.StdinData = newData logger.WithField("stdin", string(args.StdinData)).Debug("Updated stdin data") } } } ipAddrsNoIpam := annot["cni.projectcalico.org/ipAddrsNoIpam"] ipAddrs := annot["cni.projectcalico.org/ipAddrs"] // switch based on which annotations are passed or not passed. switch { case ipAddrs == "" && ipAddrsNoIpam == "": // Call IPAM plugin if ipAddrsNoIpam or ipAddrs annotation is not present. logger.Debugf("Calling IPAM plugin %s", conf.IPAM.Type) ipamResult, err := ipam.ExecAdd(conf.IPAM.Type, args.StdinData) if err != nil { return nil, err } logger.Debugf("IPAM plugin returned: %+v", ipamResult) // Convert IPAM result into current Result. // IPAM result has a bunch of fields that are optional for an IPAM plugin // but required for a CNI plugin, so this is to populate those fields. // See CNI Spec doc for more details. result, err = current.NewResultFromResult(ipamResult) if err != nil { utils.ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return nil, err } if len(result.IPs) == 0 { utils.ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return nil, errors.New("IPAM plugin returned missing IP config") } case ipAddrs != "" && ipAddrsNoIpam != "": // Can't have both ipAddrs and ipAddrsNoIpam annotations at the same time. e := fmt.Errorf("Can't have both annotations: 'ipAddrs' and 'ipAddrsNoIpam' in use at the same time") logger.Error(e) return nil, e case ipAddrsNoIpam != "": // ipAddrsNoIpam annotation is set so bypass IPAM, and set the IPs manually. overriddenResult, err := overrideIPAMResult(ipAddrsNoIpam, logger) if err != nil { return nil, err } logger.Debugf("Bypassing IPAM to set the result to: %+v", overriddenResult) // Convert overridden IPAM result into current Result. // This method fill in all the empty fields necessory for CNI output according to spec. result, err = current.NewResultFromResult(overriddenResult) if err != nil { return nil, err } if len(result.IPs) == 0 { return nil, errors.New("Failed to build result") } case ipAddrs != "": // When ipAddrs annotation is set, we call out to the configured IPAM plugin // requesting the specific IP addresses included in the annotation. result, err = ipAddrsResult(ipAddrs, conf, args, logger) if err != nil { return nil, err } logger.Debugf("IPAM result set to: %+v", result) } // Create the endpoint object and configure it. endpoint = api.NewWorkloadEndpoint() endpoint.Metadata.Name = args.IfName endpoint.Metadata.Node = nodename endpoint.Metadata.ActiveInstanceID = args.ContainerID endpoint.Metadata.Orchestrator = orchestrator endpoint.Metadata.Workload = workload endpoint.Metadata.Labels = labels // Set the profileID according to whether Kubernetes policy is required. // If it's not, then just use the network name (which is the normal behavior) // otherwise use one based on the Kubernetes pod's Namespace. if conf.Policy.PolicyType == "k8s" { endpoint.Spec.Profiles = []string{fmt.Sprintf("k8s_ns.%s", k8sArgs.K8S_POD_NAMESPACE)} } else { endpoint.Spec.Profiles = []string{conf.Name} } // Populate the endpoint with the output from the IPAM plugin. if err = utils.PopulateEndpointNets(endpoint, result); err != nil { // Cleanup IP allocation and return the error. utils.ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return nil, err } logger.WithField("endpoint", endpoint).Info("Populated endpoint") } fmt.Fprintf(os.Stderr, "Calico CNI using IPs: %s\n", endpoint.Spec.IPNetworks) // Whether the endpoint existed or not, the veth needs (re)creating. hostVethName := k8sbackend.VethNameForWorkload(workload) _, contVethMac, err := utils.DoNetworking(args, conf, result, logger, hostVethName) if err != nil { // Cleanup IP allocation and return the error. logger.Errorf("Error setting up networking: %s", err) utils.ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return nil, err } mac, err := net.ParseMAC(contVethMac) if err != nil { // Cleanup IP allocation and return the error. logger.Errorf("Error parsing MAC (%s): %s", contVethMac, err) utils.ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return nil, err } endpoint.Spec.MAC = &cnet.MAC{HardwareAddr: mac} endpoint.Spec.InterfaceName = hostVethName logger.WithField("endpoint", endpoint).Info("Added Mac and interface name to endpoint") // Write the endpoint object (either the newly created one, or the updated one) if _, err := calicoClient.WorkloadEndpoints().Apply(endpoint); err != nil { // Cleanup IP allocation and return the error. utils.ReleaseIPAllocation(logger, conf.IPAM.Type, args.StdinData) return nil, err } logger.Info("Wrote updated endpoint to datastore") return result, nil}
1、先通过newK8sClient创建kubernetes client。判断conf.IPAM.Type ipam的网络实现,当然calico自身已经具备一个calico-ipam的网络管理实现,如果使用host-local网络配置并且配置了usePodCidr的话,通过k8s获取podCidr,这个里面需要在k8s里面设置node的podCidr。
2、判读网络策略是否使用k8s。这个里面通过k8s api获取pod的annotation,并且添加一个calico/k8s_ns,关联空间的的一个label。
3.下面进入calico-ipam的地址分配,这里先要判断k8s是否以及指定IP地址,如果没有设置,调用ipamResult, err := ipam.ExecAdd(conf.IPAM.Type, args.StdinData)分配IP;如果ipAddrs != “”说明指定IP了,就不需要ipam分配,直接告诉ipam地址被占用返回地址result, err = ipAddrsResult(ipAddrs, conf, args, logger),k8s除了设置ippool内的IP地址,还支持自己定义非IPAM管理的ip地址ipAddrsNoIpam != “”,这个就不需要ipam了。
4.创建网络卡,这个和bridge的实现是一样的,通过 utils.DoNetworking实现,这个函数里面通过netlink.LinkAdd创建一个网卡对veth,主机端一cali开通,后面11位是容器的id开头。然后就是把网卡插入容器内设置IP和路由,这个都和bridge一样,具体代码如下
if err = ip.AddDefaultRoute(gw, contVeth); err != nil { return fmt.Errorf("failed to add route %v", err) }if err = netlink.AddrAdd(contVeth, &netlink.Addr{IPNet: &addr.Address}); err != nil { return fmt.Errorf("failed to add IP addr to %q: %v", contVethName, err) }
还有就是设置主机路由了,就是设置目标IP是容器的流量都交个容器在主机端的网卡就是cali-xxxxxxxxx那个网卡。
5.然后就可以创建endpoint的了,先装配各种参数,然后通过calicoClient.WorkloadEndpoints().Apply(endpoint)创建这个endpoint。一个calico的endpoint代表一个网络点,可以简单理解为网卡的别名,就是kubernetes有了pod,但一个pod有多个endpoint一样,解耦。一个calico的endpoint包含一些metadata信息
如:
endpoint.Metadata.Name = args.IfName endpoint.Metadata.Node = nodename endpoint.Metadata.ActiveInstanceID = args.ContainerID endpoint.Metadata.Orchestrator = orchestrator endpoint.Metadata.Workload = workload endpoint.Metadata.Labels = labels
还有一些网卡信息
endpoint.Spec.MAC = &cnet.MAC{HardwareAddr: mac}endpoint.Spec.InterfaceName = hostVethName
- 容器CNI完全解读calico实现(三)
- 容器CNI完全解读bridge实现(二)
- 容器CNI完全解读(一)
- CNI:容器网络接口
- CNI:容器网络接口详解
- kubernetes容器网络接口(CNI) midonet网络插件的设计与实现
- kubernetes容器网络接口(CNI) midonet网络插件的设计与实现
- kubernetes容器网络接口(CNI) midonet网络插件的设计与实现
- Kubernetes基金会托管容器网络CNI
- 容器网络Calico进阶实践
- Universal-Image-Loader完全解析(三)---源代码解读
- Universal-Image-Loader完全解析(三)---源代码解读
- spring源码解读(1)-容器基本实现
- calico学习(1)
- 如何部署 Calico 网络?- 每天5分钟玩转 Docker 容器技术(67)
- Calico 的网络结构是什么?- 每天5分钟玩转 Docker 容器技术(68)
- Calico 的网络结构是什么?- 每天5分钟玩转 Docker 容器技术(68)
- Calico 的网络结构是什么?- 每天5分钟玩转 Docker 容器技术(68)
- Android官网内存管理概略--翻译
- JS基础 改对象中的Key名,变数组中的值为新的字典型中的Key
- Linux模式设计8-编译器检查 2011-11-25 18:53:58
- 水果消除 hnust bfs专题
- [转]SuspendLayout()用处
- 容器CNI完全解读calico实现(三)
- angularjs的$index序号
- JVM系列一:JVM内存组成及分配
- HDFS中dfs 命令
- VO(value object)
- lintcode--买卖股票的最佳时机
- LZO词典压缩器实现(关键代码)
- RT坐标系变换
- 【Cocos2d-x】图片描边的一种比较好的shader实现方法