kubernetes各版本离线安装包

CNI接口很简单,特别一些新手一定要克服恐惧心里,和我一探究竟,本文结合原理与实践,认真读下来一定会对原理理解非常透彻。

环境介绍

我们安装kubernetes时先不安装CNI. 如果使用了sealyun离线包 那么修改下 kube/conf/master.sh

只留如下内容即可:

[root@helix105 shell]# cat master.sh
kubeadm init --config ../conf/kubeadm.yaml
mkdir ~/.kube
cp /etc/kubernetes/admin.conf ~/.kube/config

kubectl taint nodes --all node-role.kubernetes.io/master-

清空CNI相关目录:

rm -rf /opt/cni/bin/*
rm -rf /etc/cni/net.d/*

启动kubernetes, 如果已经装过那么kubeadm reset一下:

cd kube/shell && sh init.sh && sh master.sh

此时你的节点是notready的,你的coredns也没有办法分配到地址:

[root@helix105 shell]# kubectl get pod -n kube-system -o wide
NAME                                            READY   STATUS    RESTARTS   AGE   IP              NODE                    NOMINATED NODE   READINESS GATES
coredns-5c98db65d4-5fh6c                        0/1     Pending   0          54s   <none>          <none>                  <none>           <none>
coredns-5c98db65d4-dbwmq                        0/1     Pending   0          54s   <none>          <none>                  <none>           <none>
kube-controller-manager-helix105.hfa.chenqian   1/1     Running   0          19s   172.16.60.105   helix105.hfa.chenqian   <none>           <none>
kube-proxy-k74ld                                1/1     Running   0          54s   172.16.60.105   helix105.hfa.chenqian   <none>           <none>
kube-scheduler-helix105.hfa.chenqian            1/1     Running   0          14s   172.16.60.105   helix105.hfa.chenqian   <none>           <none>
[root@helix105 shell]# kubectl get node
NAME                    STATUS     ROLES    AGE   VERSION
helix105.hfa.chenqian   NotReady   master   86s   v1.15.0

安装CNI

创建CNI配置文件

$ mkdir -p /etc/cni/net.d
$ cat >/etc/cni/net.d/10-mynet.conf <<EOF
{
    "cniVersion": "0.2.0",
    "name": "mynet",
    "type": "bridge",
    "bridge": "cni0",
    "isGateway": true,
    "ipMasq": true,
    "ipam": {
        "type": "host-local",
        "subnet": "10.22.0.0/16",
        "routes": [
            { "dst": "0.0.0.0/0" }
        ]
    }
}
EOF
$ cat >/etc/cni/net.d/99-loopback.conf <<EOF
{
    "cniVersion": "0.2.0",
    "name": "lo",
    "type": "loopback"
}
EOF

这里两个配置一个是给容器塞一个网卡挂在网桥上的,另外一个配置负责撸(本地回环)。。

配置完后会发现节点ready:

[root@helix105 shell]# kubectl get node
NAME                    STATUS   ROLES    AGE   VERSION
helix105.hfa.chenqian   Ready    master   15m   v1.15.0

但是coredns会一直处于ContainerCreating状态,是因为bin文件还没有:

failed to find plugin "bridge" in path [/opt/cni/bin]

plugins里实现了很多的CNI,如我们上面配置的bridge.

$ cd $GOPATH/src/github.com/containernetworking/plugins
$ ./build_linux.sh
$ cp bin/* /opt/cni/bin
$ ls bin/
bandwidth  dhcp      flannel      host-local  loopback  portmap  sbr     tuning
bridge     firewall  host-device  ipvlan      macvlan   ptp      static  vlan

这里有很多二进制,我们学习的话不需要关注所有的,就看ptp(就简单的创建了设备对)或者bridge

再看coredns已经能分配到地址了:

[root@helix105 plugins]# kubectl get pod -n kube-system -o wide
NAME                                            READY   STATUS    RESTARTS   AGE     IP              NODE                    NOMINATED NODE   READINESS GATES
coredns-5c98db65d4-5fh6c                        1/1     Running   0          3h10m   10.22.0.8       helix105.hfa.chenqian   <none>           <none>
coredns-5c98db65d4-dbwmq                        1/1     Running   0          3h10m   10.22.0.9

看一下网桥,cni0上挂了两个设备,与我们上面的cni配置里配置的一样,type字段指定用哪个bin文件,bridge字段指定网桥名:

[root@helix105 plugins]# brctl show
bridge name bridge id       STP enabled interfaces
cni0        8000.8ef6ac49c2f7   no      veth1b28b06f
                                        veth1c093940

原理

为了更好理解kubelet干嘛了,我们可以找一个脚本来解释 script 这个脚本也可以用来测试你的CNI:

为了易读,我删除一些不重要的东西,原版脚本可以在连接中去拿

# 先创建一个容器,这里只为了拿到一个net namespace
contid=$(docker run -d --net=none golang:1.12.7 /bin/sleep 10000000)
pid=$(docker inspect -f '{{ .State.Pid }}' $contid)
netnspath=/proc/$pid/ns/net # 这个我们需要

kubelet启动pod时也是创建好容器就有了pod的network namespaces,再去把ns传给cni 让cni去配置

./exec-plugins.sh add $contid $netnspath # 传入两个参数给下一个脚本,containerid和net namespace路径

docker run --net=container:$contid $@
NETCONFPATH=${NETCONFPATH-/etc/cni/net.d}

i=0
# 获取容器id和网络ns
contid=$2
netns=$3

# 这里设置了几个环境变量,CNI命令行工具就可以获取到这些参数
export CNI_COMMAND=$(echo $1 | tr '[:lower:]' '[:upper:]')
export PATH=$CNI_PATH:$PATH # 这个指定CNI bin文件的路径
export CNI_CONTAINERID=$contid
export CNI_NETNS=$netns

for netconf in $(echo $NETCONFPATH/10-mynet.conf | sort); do
        name=$(jq -r '.name' <$netconf)
        plugin=$(jq -r '.type' <$netconf) # CNI配置文件的type字段对应二进制程序名
        export CNI_IFNAME=$(printf eth%d $i) # 容器内网卡名

        # 这里执行了命令行工具
        res=$($plugin <$netconf) # 这里把CNI的配置文件通过标准输入也传给CNI命令行工具
        if [ $? -ne 0 ]; then
                # 把结果输出到标准输出,这样kubelet就可以拿到容器地址等一些信息
                errmsg=$(echo $res | jq -r '.msg')
                if [ -z "$errmsg" ]; then
                        errmsg=$res
                fi

                echo "${name} : error executing $CNI_COMMAND: $errmsg"
                exit 1
        let "i=i+1"
done

总结一下:

         CNI配置文件
         容器ID
         网络ns
kubelet -------------->  CNI command
   ^                        |
   |                        |
   +------------------------+
       结果标准输出

bridge CNI实现

既然这么简单,那么就可以去看看实现了:

bridge CNI代码

//cmdAdd 负责创建网络
func cmdAdd(args *skel.CmdArgs) error 

//入参数都已经写到这里面了,前面的参数从环境变量读取的,CNI配置从stdin读取的
type CmdArgs struct {
    ContainerID string
    Netns       string
    IfName      string
    Args        string
    Path        string
    StdinData   []byte
}

所以CNI配置文件除了name type这些特定字段,你自己也可以加自己的一些字段.然后自己去解析

然后啥事都得靠自己了

//这里创建了设备对,并挂载到cni0王桥上
hostInterface, containerInterface, err := setupVeth(netns, br, args.IfName, n.MTU, n.HairpinMode, n.Vlan)

具体怎么挂的就是调用了netlink 这个库,sealos在做内核负载时同样用了该库。

err := netns.Do(func(hostNS ns.NetNS) error { //创建设备对
    hostVeth, containerVeth, err := ip.SetupVeth(ifName, mtu, hostNS)
    ...
    //配置容器内的网卡名mac地址等
    contIface.Name = containerVeth.Name
    contIface.Mac = containerVeth.HardwareAddr.String()
    contIface.Sandbox = netns.Path()
    hostIface.Name = hostVeth.Name
    return nil
})
...

// 根据index找到宿主机设备对名
hostVeth, err := netlink.LinkByName(hostIface.Name)
...
hostIface.Mac = hostVeth.Attrs().HardwareAddr.String()

// 把宿主机端设备对挂给网桥
if err := netlink.LinkSetMaster(hostVeth, br); err != nil {}

// 设置hairpin mode
if err = netlink.LinkSetHairpin(hostVeth, hairpinMode); err != nil {
}

// 设置vlanid
if vlanID != 0 {
    err = netlink.BridgeVlanAdd(hostVeth, uint16(vlanID), true, true, false, true)
}

return hostIface, contIface, nil

最后把结果返回:

type Result struct {
    CNIVersion string         `json:"cniVersion,omitempty"`
    Interfaces []*Interface   `json:"interfaces,omitempty"`
    IPs        []*IPConfig    `json:"ips,omitempty"`
    Routes     []*types.Route `json:"routes,omitempty"`
    DNS        types.DNS      `json:"dns,omitempty"`
}

// 这样kubelet就收到返回信息了
func (r *Result) PrintTo(writer io.Writer) error {
    data, err := json.MarshalIndent(r, "", "    ")
    if err != nil {
        return err
    }
    _, err = writer.Write(data)
    return err
}

如:

{
  "cniVersion": "0.4.0",
  "interfaces": [                                            (this key omitted by IPAM plugins)
      {
          "name": "<name>",
          "mac": "<MAC address>",                            (required if L2 addresses are meaningful)
          "sandbox": "<netns path or hypervisor identifier>" (required for container/hypervisor interfaces, empty/omitted for host interfaces)
      }
  ],
  "ips": [
      {
          "version": "<4-or-6>",
          "address": "<ip-and-prefix-in-CIDR>",
          "gateway": "<ip-address-of-the-gateway>",          (optional)
          "interface": <numeric index into 'interfaces' list>
      },
      ...
  ],
  "routes": [                                                (optional)
      {
          "dst": "<ip-and-prefix-in-cidr>",
          "gw": "<ip-of-next-hop>"                           (optional)
      },
      ...
  ],
  "dns": {                                                   (optional)
    "nameservers": <list-of-nameservers>                     (optional)
    "domain": <name-of-local-domain>                         (optional)
    "search": <list-of-additional-search-domains>            (optional)
    "options": <list-of-options>                             (optional)
  }
}

总结

CNI接口层面是非常简单的,所以更多的就是在CNI本身的实现了,懂了上文这些就可以自己去实现一个CNI了,是不是很酷,也会让大家更熟悉网络以更从容的姿态排查网络问题了。

扫码关注sealyun



探讨可加QQ群:98488045

彻底理解kubernetes CNI的更多相关文章

  1. [转帖]Kubernetes CNI网络最强对比:Flannel、Calico、Canal和Weave

    Kubernetes CNI网络最强对比:Flannel.Calico.Canal和Weave https://blog.csdn.net/RancherLabs/article/details/88 ...

  2. 理解Kubernetes(1):手工搭建Kubernetes测试环境

    系列文章: 1. 手工搭建环境 1. 基础环境准备 准备 3个Ubuntu节点,操作系统版本为 16.04,并做好以下配置: 系统升级 设置 /etc/hosts 文件,保持一致 设置从 0 节点上无 ...

  3. 理解Kubernetes(2): 应用的各种访问方式

    理解Kubernetes系列文章: 手工搭建环境 应用的各种访问方式 1. 通过 Pod 的 IP 地址访问应用 1.1 Pod 的IP地址 每个Pod 都会被分配一个IP地址,比如下面这儿pod的I ...

  4. 后端技术杂谈11:十分钟理解Kubernetes核心概念

    本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 本文转自 https://github.com/h2pl/Java-Tutorial 喜欢的 ...

  5. Kubernetes CNI网络插件

    CNI 容器网络接口,就是在网络解决方案由网络插件提供,这些插件配置容器网络则通过CNI定义的接口来完成,也就是CNI定义的是容器运行环境与网络插件之间的接口规范.这个接口只关心容器的网络连接,在创建 ...

  6. 十分钟带你理解Kubernetes核心概念

    什么是Kubernetes? Kubernetes(k8s)是自动化容器操作的开源平台,这些操作包括部署,调度和节点集群间扩展.如果你曾经用过Docker容器技术部署容器,那么可以将Docker看成K ...

  7. 一个简单的例子理解Kubernetes的三种IP地址类型

    很多Kubernetes的初学者对Kubernetes里面三种不同的IP地址和工作机制理解得不是很清楚. 本文我们通过一个最简单的例子来学习. 用如下命令行创建一个基于nginx的deployment ...

  8. 深入理解 Kubernetes 资源限制:CPU

    原文地址:https://www.yangcs.net/posts/understanding-resource-limits-in-kubernetes-cpu-time/ 在关于 Kubernet ...

  9. 深入理解Kubernetes资源限制:内存

    写在前面 当我开始大范围使用Kubernetes的时候,我开始考虑一个我做实验时没有遇到的问题:当集群里的节点没有足够资源的时候,Pod会卡在Pending状态.你是没有办法给节点增加CPU或者内存的 ...

随机推荐

  1. Spark学习之路(七)—— 基于ZooKeeper搭建Spark高可用集群

    一.集群规划 这里搭建一个3节点的Spark集群,其中三台主机上均部署Worker服务.同时为了保证高可用,除了在hadoop001上部署主Master服务外,还在hadoop002和hadoop00 ...

  2. vuex分模块

    Vuex速学篇:(4)把我们的业务按模块分类 原创 2016年11月29日 10:45:38 8504 文档:http://vuex.vuejs.org/zh-cn/modules.html 这个mo ...

  3. mysql报错(Not unique table/alias)

    Not unique table/alias 错误编号:1066 问题分析: SQL 语句中出现了非唯一的表或别名. 解决方法: 1.请检查出现问题位置的 SQL 语句中是否使用了相同的表名,或是定义 ...

  4. Effective Java第三版(一) ——用静态工厂代替构造器

    此文做为<Effective Java>系列的第一篇,所以有必要大概说下此书的特点,当然很多人可能都看过,毕竟是有着Java四大名著之一的大名在外,不过总会有萌新不了解,例如我!<E ...

  5. Java学习笔记之---类和对象

    Java学习笔记之---类和对象 (一)类 类是一个模板,它描述一类对象的行为和状态  例如:动物类是一个类,动物们都有属性:颜色,动物们都有行为:吃饭 public class Dog { Stri ...

  6. 19.linux文件属性

    1.linux文件属性 ls -lih i查看文件inode,h查看文件大小 文件总共10个属性 inode索引节点编号(唯一的) 文件类型和权限,第一个字符为类型,后面字符为权限 硬链接的数量 文件 ...

  7. Java实现LRU算法

    一.LRU算法简介 LRU(Least Recently Used)最近最久未使用算法 常见应用场景:内存管理中的页面置换算法.缓存淘汰中的淘汰策略等 二.实现理论 底层结构:双向链表 + HashM ...

  8. Tomcat配置解析

    Tomcat文件配置 tomcat解压后目录 bin:可执行文件(startup.bat shutdown.bat) conf:配置文件(server.xml) lib:tomcat依赖的jar文件 ...

  9. mybatis基础配置

    我这个写的比较简略,是自己短时间记录的可能只适合自己看,新手或者不懂的建议看看下面大神这篇: https://www.cnblogs.com/homejim/p/9613205.html 1.MyBa ...

  10. Socket网络编程系列教程序

    C语言的用途相当多,可以用在数据结构.数据库.网络.嵌入式等方面,历经40多年不衰,真是厉害!最近一直想从某一应用方面写一个系列教程,好好地把某一方面讲深讲透.         正好博主对网络方面的编 ...