摘要:

hypervisor;cgroup;不可变基础设施;Headless Service;systemd;Init Containe;Sidecar;声明式 API; CNI;Calico;OwnerReference;32位网络掩码;operator = CRD + webhook + controller;

——云绑定应用程序是指在构建应用程序时使用云提供的服务和资源。通过将更多应用程序逻辑和管理责任卸载到云服务,开发人员可以专注于业务逻辑

——是否直连路由的判断是用pc自己的子网掩码去和目的ip相与,和自身网络地址相同,就是同一网段,不同就不是。若不是直连路由,就检查是否网关配置,有的话,同样检查网关ip是否在同一网段。

——IPIP overlay:在某些情况下,你可能无法访问网关以修改其设置,或者它可能不支持 BGP 对等连接。在这种情况下,你可以使用 IPIP overlay 来封装流向目标集群的流量。overlay 网络允许网络设备在底层网络(称为底层)上相互通信,而底层网络不需要了解连接到 overlay 网络的设备。

——ClusterIP 在 K8s 中只是一个虚拟的 IP(它并不是任何真实存在的网络设备地址)。


容器是一种轻量级的虚拟化技术,因为它跟虚拟机比起来,它少了一层 hypervisor 层。

Container Runtime Interface实现了运行时和 Kubernetes 的解耦,在 containerd 中可以配置多个容器运行时,containerd 中的 cri-plugin (它是一个实现了 CRI 的插件)就实现了 CRI,cri-plugin 在 containerd 的配置文件中查询 runc 、runv对应的 Handler,kata, gVisor 这样的容器运行时只需要对接 contaienrd 就可以了。containerd的配置文件默认放在/etc/containerd/config.toml 这个位置下。

对于容器来说,最重要的是怎么保证这个进程所用到的资源是被隔离和被限制住的,在 Linux 内核上面是由 cgroup 和 namespace 这两个技术来保证的。

namespace 是用来做资源隔离的,在 Linux 内核上有七种 namespace,docker 中用到了前六种。第七种 cgroup namespace 在 docker 本身并没有用到,但是在 runC 实现中实现了 cgroup namespace。

etcd 是一个分布式的、可靠的 key-value 存储系统,它用于存储分布式系统中的关键数据。 etcd 是支持指定前缀 watch,通过这种方式,就可以实现 etcd 的数据同步:

因为 Kubernetes 将自身所用的状态设备存储在 etcd 中,其状态数据的存储的复杂性将 etcd 给 cover 掉之后,Kubernetes 系统自身不需要再树立复杂的状态流转,因此自身的系统设立架构也得到了大幅的简化。

通常情况下分布式系统和 Master 都是有状态逻辑的,无法允许多个 Master 同时运行(典型的是分布式存储服务)。可以通过 etcd 来实现选主,将其中的一个 Master 选主成 Leader,负责控制整个集群中所有 Slave 的状态。被选主的 Leader 可以将自己的 IP 注册到 etcd 中,使得 Slave 节点能够及时获取到当前组件的地址,从而使得系统按照之前单个 Master 节点的方式继续工作。

AirNet全系统参数分发:通过 etcd 去实现一个分布式的信号量,并且它支持能够自动地剔除掉故障节点。在进程执行过程中,如果进程的运行周期比较长,我们可以将进程运行过程中的一些状态数据存储到 etcd,从而使得当进程故障之后且需要恢复到其他地方时,能够从 etcd 中去恢复一些执行状态,而不需要重新去完成整个的计算逻辑,以此来加速整个任务的执行效率。

 K8s对于新的node节点的心跳数据上传(在分布式系统中需要去检测一个节点是否存活的时候,就需要租约机制),或需要检测分布式系统中一个进程是否存活,那么就会在这个分布式进程中去访问 etcd 并且创建一个租约lease,同时在该进程中去调用 KeepAlive 的方法,与 etcd 保持一个租约不断的续约。租约在进程挂掉的一段时间就会被 etcd 自动清理掉。所以可以通过这个机制来判定节点是否存活。

API 网关能够通过 etcd 及时感知到后端进程的地址等状态的变化,整个状态数据被 etcd 接管,那么 API 网关本身也是无状态的,它可以水平地扩展来服务更多的客户

CNI全称是 Container Network Interface,即容器网络的 API 接口。它是 K8s 中标准的一个调用网络实现的接口。Kubelet 通过这个标准的 API 来调用不同的网络插件以实现不同的网络配置方式。实现了这个接口的就是 CNI 插件,它实现了一系列的 CNI API 接口。常见的 CNI 插件包括 Calico、flannel、Terway、Weave Net 以及 Contiv。CNI 插件可以分为三种:Overlay、路由及 Underlay。K8s 通过 CNI 配置文件来决定使用什么 CNI在每个结点上配置 CNI 配置文件(/etc/cni/net.d/xxnet.conf)

[root@k8s-node02 net.d]# ll /etc/cni/net.d
总用量 8
-rw-r--r-- 1 root root  661 1月  19 15:56 10-calico.conflist
-rw------- 1 root root 3144 2月  15 03:53 calico-kubeconfig

Overlay 模式的典型特征是容器独立于主机的 IP 段,这个 IP 段进行跨主机网络通信时是通过在主机之间创建隧道的方式,将整个容器网段的包全都封装成底层的物理网络中主机之间的包。

容器设计思想_IP

该方式的好处在于它不依赖于底层网络。例如虚拟化环境,如不允如 OpenStack,网络限制较多,不允许机器之间直接通过二层协议访问,必须要带有 IP 地址这种三层的才能去做转发,限制某一个机器只能使用某些 IP 等。在这种被做了强限制的底层网络中,只能去选择 Overlay 的插件。fdb 转发表是 forwarding database 的缩写,就是把某个 Pod 的 IP 转发到某一个节点的隧道端点上去(Overlay 网络):Flannel-vxlan, Calico-ipip, Weave ;

安装calico网络时,默认安装是IPIP网络,节点上有tunl0设备,需要tunl0设备封装数据,形成隧道,启动ipip模式有个前提,它是属于4层的,因为它是基于现有的以太网的网络将原来包里的原始IP,进行一次封装,因为现有的网络已经通了,三层的路由实现不同的vlan进行通信,所以通过tunl0解包,这个tunl0类似于ipip模块,这个就跟vxlan的veth类似,这个模式跟vxlan的模式大致是一样的

为什么要VXLAN:主要原因是突破TOR交换机MAC地址表限制,TOR(Top Of Rack)交换机的一个端口虽然还是连接一个物理主机但是可能进而连接几十个甚至上百个虚拟机和相应数量的MAC地址

calico-ipip 模式和 calico-bgp 模式都有对应的局限性,对于一些主机跨子网而又无法使网络设备使用 BGP 的场景可以使用 cross-subnet 模式,实现同子网机器使用 calico-BGP 模式,跨子网机器使用 calico-ipip 模式。

[root@k8s-master01 /]# kubectl describe ippool default-ipv4-ippool
Name:         default-ipv4-ippool
Namespace:    
API Version:  crd.projectcalico.org/v1
Kind:         IPPool
Metadata:
Spec:
  Block Size:     26
  Cidr:           172.16.0.0/12
  Ipip Mode:      Always
  Nat Outgoing:   true
  Node Selector:  all()
  Vxlan Mode:     Never
[root@k8s-master01 /]# k -n kube-system edit  daemonset  calico-node
apiVersion: apps/v1
kind: DaemonSet
    spec:
      containers:
      - env:
        - name: CALICO_IPV4POOL_IPIP
          value: Always
        - name: CALICO_IPV4POOL_VXLAN
          value: Never
# ip a    //这里子网掩码26位:route add -net 172.25.244.192/26 gw  192.168.31.211
6: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 172.25.244.192/32 scope global tunl0
       valid_lft forever preferred_lft forever
root@k8s-master01 /]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
172.17.54.128   192.168.31.218  255.255.255.192 UG    0      0        0 tunl0     //tunl0 网卡走 ipip 模式
172.17.125.0    192.168.31.214  255.255.255.192 UG    0      0        0 tunl0

 路由模式中主机和容器也分属不同的网段,它与 Overlay 模式的主要区别在于它的跨主机通信是通过路由打通,无需在不同主机之间做一个隧道封包。但路由打通就需要部分依赖于底层网络,比如说要求底层网络有二层可达的一个能力;

路由模式就是依赖于 Linux 的路由协议做一个打通。这样就避免了像 vxlan 的封包方式导致的性能降低:clico-bgp(配置网络进行打通:BGP 路由创建整个集群所有节点的通道,然后将所有 Pod 的 IP 地址跟上一步创建的通道关联起来, flannel-hostgw;——使用BGP模式后,首先变化是节点上不再有tunl0设备每个host通过BGP协议获取其他host的路由信息,calico会在每个host上运行felix和bird进程;Flannel-hostgw(要求集群宿主机之间是二层连通的)使用flanneld进程来维护路由信息;calico项目使用BGP协议来自动维护整个集群的路由信息

需要集群外的资源与集群内的资源互联互通应用最初都是在虚拟机或者物理机上,容器化之后,应用无法一下就完成迁移,因此就需要传统的虚拟机或者物理机能跟容器的 IP 地址互通。为了实现这种互通,就需要两者之间有一些打通的方式或者直接位于同一层。此时可以选择 Underlay 的网络,比如 sriov 这种就是 Pod 和以前的虚拟机或者物理机在同一层。我们也可以使用 calico-bgp,此时它们虽然不在同一网段,但可以通过它去跟原有的路由器做一些 BGP 路由的一个发布,这样也可以打通虚拟机与容器。

 Underlay 模式中容器和宿主机位于同一层网络,两者拥有相同的地位。容器之间网络的打通主要依靠于底层网络。因此该模式是强依赖于底层能力的。

Underlay 意味着我们可以直接在一个物理机上插多个网卡或者是在一些网卡上做硬件虚拟化(Pod 和虚拟机或者物理机在同一层:sriov。

1.   云原生技术:

云原生为用户指定了一条低心智负担的、敏捷的、能够以可扩展、可复制的方式最大化地利用云的能力、发挥云的价值的最佳路径

云原生技术的本质是两个理论基础。

第一个理论基础是:不可变基础设施。这一点目前是通过容器镜像来实现的,其含义就是应用的基础设施应该是不可变的,是一个自包含、自描述可以完全在不同环境中迁移的东西;

第二个理论基础就是:云应用编排理论。当前的实现方式就是 Google 所提出来的“容器设计模式”。

容器技术使得应用具有了一种“自包含”的定义方式。所以,这样的应用才能以敏捷的、以可扩展可复制的方式发布在云上,发挥出云的能力。这也就是容器技术对云发挥出的革命性影响所在,所以说,容器技术正是云原生技术的核心底盘。

2.  StatefulSet 是 Kubernetes 中常见的一种 Workload,其初始目标是面向有状态应用部署,但也支持部署无状态应用

如果我不想按照序号创建和删除,那 StatefulSet 也支持其它的创建和删除的逻辑,这也就是为什么社区有些人把无状态应用也通过 StatefulSet 来管理。它的好处是它能拥有唯一的网络标识以及网络存储,同时也能通过并发的方式进行扩缩容( podMangementPolicy 字段,这个字段的可选策略为 OrderedReady 和 Parallel)。

StatefulSet.spec 中有个字段叫 podMangementPolicy 字段,这个字段的可选策略为 OrderedReady 和 Parallel,默认情况下为前者。

ServiceName:对应 Headless Service 的名字。当然如果有人不需要这个功能的时候,会给 Service 定一个不存在的 value,Controller 也不会去做校验,所以可以写一个 fake 的ServiceName——此时这个STS的域名还能用吗?: statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local。(StatefulSet+headless service会为关联的每个Pod分配一个具体的域名)。但是这里推荐每一个 Service 都要配置一个 Headless Service,不管 StatefulSet 下面的 Pod 是否需要网络标识;

StatefulSet会为关联的Pod保持一个不变的Pod Name

statefulset中Pod的hostname格式为statefulsetname-(pod序号)

通过配置一个 headless Service,使每个 Pod 有一个唯一的网络标识 (hostname);

Headless Service指定 clusterIP:None,告诉 K8s 说我不需要 clusterIP(虚拟 IP),然后 K8s 就不会分配给这个 service 一个虚拟 IP 地址,它没有虚拟 IP 地址怎么做到负载均衡以及统一的访问入口呢?pod 可以直接通过 service_name 用 DNS 的方式解析到所有后端 pod 的 IP 地址,通过 DNS 的 A 记录的方式会解析到所有后端的 Pod 的地址,客户端自己去选择一个合适的地址去访问 pod。

StatefulSet Controller 是 Owned 三个资源:ControllerRevision、Pod、PVC;注意由于拥有 OwnerReference 的资源,在管理的这个资源进行删除的默认情况下,会关联级联删除下属资源。因此默认情况下删除 StatefulSet 之后,StatefulSet 创建的 ControllerRevision 和 Pod 都会被删除,但是 PVC 因为没有写入 OwnerReference,PVC 并不会被级联删除。

3.   容器就是一个视图隔离、资源可限制、独立文件系统的进程集合。所谓“视图隔离”就是能够看到部分进程以及具有独立的主机名等;控制资源使用率则是可以对于内存大小以及 CPU 使用个数等进行限制。

容器就是一个进程集合,它将系统的其他资源隔离开来,具有自己独立的资源视图。容器就是和系统其它部分隔离开来的进程集合,这里的其他部分包括进程、网络资源以及文件系统等。

而镜像就是容器所需要的所有文件集合,其具备一次构建、到处运行的特点。一般情况下,镜像构建会在打包机或者其他的隔离环境下完成。

容器是针对于进程而言的,因此无需 Guest OS,只需要一个独立的文件系统提供其所需要文件集合即可。所有的文件隔离都是进程级别的,因此启动时间快于 VM,并且所需的磁盘空间也小于 VM。

4.   Kubernetes 是一个自动化的容器编排平台,它负责应用的部署、应用的弹性以及应用的管理,这些都是基于容器的。Kubernetes 架构是一个比较典型的二层架构和 server-client 架构。Master 作为中央的管控节点,会去与 Node 进行连接。

API Server,它本身在部署结构上是一个可以水平扩展的一个部署组件;

Controller 是一个可以进行热备的一个部署组件,它只有一个 active,它的调度器也是相应的,虽然只有一个 active,但是可以进行热备。

Kubernetes 并不会直接进行网络存储的操作,他们会靠 Storage Plugin 或者是网络的 Plugin 来进行操作。在 Kubernetes 自己的环境中,也会有 Kubernetes 的 Network,它是为了提供 Service network 来进行搭网组网的。真正完成 service 组网的组件的是 Kube-proxy,它是利用了 iptable 的能力来进行组建 Kubernetes 的 Network,就是 cluster network。

kubelet 就会去调 Container runtime 来真正去启动配置这个容器和这个容器的运行环境,去调度 Storage Plugin 来去配置存储,network Plugin 去配置网络。Pod 这个抽象也给这些容器提供了一个共享的运行环境,它们会共享同一个网络环境,这些容器可以用 localhost 来进行直接的连接。而 Pod 与 Pod 之间,是互相有 isolation 隔离的。

对一个外部用户来讲,提供了多个具体的 Pod 地址,这个用户要不停地去更新 Pod 地址,当这个 Pod 再失败重启之后,我们希望有一个抽象,把所有 Pod 的访问能力抽象成一个第三方的一个 IP 地址,实现这个的 Kubernetes 的抽象就叫 Service。

Spec是我们希望 Pod 达到的一个预期的状态;

在Spec 下面有一项叫 status,它表达了这个资源当前的状态。

——状态 status:实际上是一个自定义资源的子资源,它的好处在于,对该字段的更新并不会触发 Deployment 或 Pod 的重新部署。我们知道对于某些 Deployment 和 Pod,只要修改了某些 spec,它就会重新创建一个新的 Deployment 或者 Pod 出来。但是状态资源并不会被重新创建,它只是用来回应当前 Pod 的整个状态。

5.    Linux 容器的“单进程”模型,指的是容器的生命周期等同于 PID=1 的进程(容器应用进程)的生命周期而不是说容器里不能创建多进程。当然,一般情况下,容器应用进程并不具备进程管理能力,所以你通过 exec 或者ssh 在容器里创建的其他进程,一旦异常退出(比如 ssh 终止)是很容易变成孤儿进程的。反过来,其实可以在容器里面 run 一个 systemd,用它来管理其他所有的进程。这样会产生第二个问题:实际上没办法直接管理我的应用了,因为我的应用被 systemd 给接管了,那么这个时候应用状态的生命周期就不等于容器生命周期。

管理虚拟机= 管理基础设施; 管理容器 = 直接管理应用本身

管理容器 = 管理systemd != 直接管理应用本身

 Pod 的生命周期是等同于 Infra container 的生命周期的,与容器 A 和 B 是无关的。这也是为什么在 Kubernetes 里面,它是允许去单独更新 Pod 里的某一个镜像的,在每个 Pod 里,额外起一个 Infra container 小容器来共享整个 Pod 的  Network Namespace。一个 Pod 里面的所有容器,它们看到的网络视图是完全一样的。即:它们看到的网络设备、IP地址、Mac地址等等。

6.    容器设计模式:

通过组合两个不同角色的容器,并且按照这样一些像 Init Container 这样一种编排方式,统一的去打包这样一个应用,把它用 Pod 来去做的非常典型的一个例子。像这样的一个概念,在 Kubernetes 里面就是一个非常经典的容器设计模式,叫做:“Sidecar”。

具体应用中,先定义一个Init Container,它只做一件事情,就是把/home/cdatc/AirNet/bin(或/home/cdatc/AirNet/config)从镜像里拷贝到一个 Volume 里面,它做完这个操作就退出了,所以 Init Container 会比用户容器先启动,并且严格按照定义顺序来依次执行。然后,关键在于一个Pod 里面的多个容器,共享 Volume :现在另一个msdp容器,在启动的时候,要声明使用Volume,并且挂载在/home/cdatc/AirNet/ 目录下面。这个时候,由于前面已经运行过了一个 Init Container,已经执行完拷贝操作了,所以这个 Volume 里面已经存在了应用的/bin应用程序包和/config:第二步执行启动这个msdp容器的时候,去挂这个 Volume,一定能在里面找到前面拷贝来的 自己定义版本的应用程序/bin或配置文件/config。

所有“设计模式”的本质是:结构和重用

Sidecar:代理容器

Sidecar的用法,称作为代理容器 Proxy。 假如现在有个 Pod 需要访问一个外部系统,或者一些外部服务,但是这些外部系统是一个集群,那么这个时候如何通过一个统一的、简单的方式,用一个 IP 地址,就把这些集群都访问到?

——有一种方法就是:修改代码。因为代码里记录了这些集群的地址;另外还有一种解耦的方法,即通过 Sidecar 代理容器。简单说,单独写一个这么小的 Proxy,用来处理对接外部的服务集群,它对外暴露出来只有一个 IP 地址就可以了。所以接下来,业务容器主要访问 Proxy,然后由 Proxy 去连接这些服务集群,这里的关键在于 Pod 里面多个容器是通过 localhost 直接通信的,因为它们同属于一个 network Namespace,网络视图都一样,所以它们俩通信 localhost,并没有性能损耗。所以说代理容器除了做了解耦之外,并不会降低性能,更重要的是,像这样一个代理容器的代码就又可以被全公司重用了。

7.     控制器模 :控制型模式中最核心的就是控制循环的概念;OwnerReference 使得用户可以方便地查找一个创建资源的对象,另外,还可以用来实现级联删除的效果。

两种 API 设计方法:声明式 API 和命令式 API ;

Kubernetes 所采用的控制器模式,是由声明式 API 驱动的。确切来说,是基于对 Kubernetes 资源对象的修改来驱动的;

Kubernetes 资源之后,是关注该资源的控制器。这些控制器将异步的控制系统向设置的终态驱近

控制器是自主运行的,使得系统的自动化和无人值守成为可能;

因为 Kubernetes 的控制器和资源都是可以自定义的,因此可以方便的扩展控制器模式。特别是对于有状态应用,我们往往通过自定义资源和控制器的方式,来自动化运维操作。这个也就是后续会介绍的 operator 的场景。 operator = CRD + webhook + controller

8.    不可变基础设施(容器)的可变配置

我们不可能把一些可变的配置写到镜像里面,当这个配置需要变化的时候,可能需要我们重新编译一次镜像,这个肯定是不能接受的; ConfigMap主要是管理一些可变配置信息,比如说我们应用的一些配置文件,或者说它里面的一些环境变量,或者一些命令行参数。它的好处在于它可以让一些可变配置和容器镜像进行解耦,这样也保证了容器的可移植性

前置校验是用InitContainers ,容器启动之前的一个前置条件检验。比如说,一个容器启动之前,我可能要确认一下 DNS 服务是不是好用?又或者确认一下网络是不是连通的?那么这些其实就是一些前置的校验。

9.    在 K8s 里面,服务发现与负载均衡就是 K8s Service。

K8s Service 向上提供了外部网络以及 pod 网络的访问,即外部网络可以通过 service 去访问,pod 网络也可以通过 K8s Service 去访问。

向下,K8s 对接了另外一组 pod,即可以通过 K8s Service 的方式去负载均衡到一组 pod 上面去。

这样相当于解决了前面所说的复发性问题,或者提供了统一的访问入口去做服务发现,然后又可以给外部网络访问,解决不同的 pod 之间的访问,提供统一的访问地址。

IPVS是一款运行在Linux kernel当中的4层负载均衡器:一定要让kernel认为VIP是本地地址,这样4层的LVS才能开始干活!例如以下IP地址:192.168.31.235/32、172.25.244.192/32、10.16.174.178/32,32位网络掩码(netmask)只是为了让kernel认为该IP是本地地址,这样才能送到4层。 LVS 工作机制决定它工作在第四层,并不关心 IP 转发(3层),只有内核认为这个 IP 是自己的才会拆到 TCP 或 UDP 这一层

[root@k8s-master01 /]# ip a
4: enp4s0f0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether e4:11:5b:0c:82:ae brd ff:ff:ff:ff:ff:ff
    inet 192.168.31.211/24 brd 192.168.31.255 scope global enp4s0f0
       valid_lft forever preferred_lft forever
    inet 192.168.31.235/32 scope global enp4s0f0
       valid_lft forever preferred_lft forever
    inet6 fe80::e611:5bff:fe0c:82ae/64 scope link 
       valid_lft forever preferred_lft forever
6: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
    inet 172.25.244.192/32 scope global tunl0
       valid_lft forever preferred_lft forever
9: dummy0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 5e:85:5a:f0:a1:45 brd ff:ff:ff:ff:ff:ff
10: kube-ipvs0: <BROADCAST,NOARP> mtu 1500 qdisc noop state DOWN group default 
    link/ether be:d7:80:5e:8e:7a brd ff:ff:ff:ff:ff:ff
    inet 10.16.174.178/32 scope global kube-ipvs0
       valid_lft forever preferred_lft forever