二、Kubernetes和其网络实现
2.1 kubernetes 基础
单主机容器不能满足性能、高可靠、高可用的需求:
资源调度和分配难度较大
性能受主机资源的限制
不支持高可用
Docker诞生之初,只是在一个主机上去部署容器,而现在面临分布式、多主机、高可用的场景架构需求,出现了容器的编排工具:
Docker的 Docker Swarm
Apache Mesos
Google kubernetes
通过容器编排工具,可以在集群中统一管理容器,提供基于容器的大型的,弹性的,分布式应用服务。
(1) K8s 架构
类似于任何云计算,K8S也有自己的管理平面,两类节点: master node 与 worker node
(2) Master Node
由四部分组成
etcd: 可持久化,高可用的K-V数据库,用来存放K8S数据。本身是一个独立的项目,一个可以持久化的数据库。
kube-apiserver:整个K8S集群的管理入口,向上提供REST API接口,用户可以使用这个接口,管理整个集群,可水平扩展,如果想提升性能,扩展多个APIServer实例就可以。常用的cubectl命令也是将请求转换成REST发送给kube-apiserver。
kube-scheduler: 为新创建的容器选择Worder Node(所有应用都应用在WorkerNode上), 用来做部署。选WorkerNode。
kube-controller-manager: K8S资源管理,每个资源都可以对应一个controller,由controller检测和管理的。每个controlle是独立的进程,这样每个controller才不会相互影响。K8S是由go语言开发的,它的高并发性特点,使的它可以同时调用CPU的多个核,controller是可以并发的,所以多个controller是由kube-controller-manager来统一运行协同工作的。为K8S资源运行controller用来检测并管理这些资源,确保K8S管理的容器运行在正常运行状态。
(3) K8S Worker Node
kubelet:管理Worker Node 上的容器,从MasterNode获取相关信息,来启动容器,并确保运行状态
kube-proxy:配置Worker Node上的容器网络
Container Runtime : 实际运行容器的程序,支持多种容器,K8S支持多种Container Runtime,其中包括Docker, rkt, runc等等。一般用Docker。
(4) kubectl
K8S的命令行工具,配置容器,可以创建、更新、删除容器。
常见K8S流程:
*通过kubectl 下发创建容器的命令
*位于Master Node 的kube-apiserver收到请求(rest api)
*调用kube-scheduler。位于Master Node的kube-scheduler选择一个或多个Worker Node 用来部署容器(根据当前资源情况或者是策略)
*kube-scheduler 将选择的Worker Node 通知给kube-apiserver, kube-apiserver 再转发给相应Worker Node上的kubelet
*位于Worker Node的kubelet进程,收到来自Master Node 的请求(kube-apiserver) ,调用自身的container runtime, 例如Docker, 创建容器
*创建完容器后,位于Master Node的kube-controller-manager 不停的检测容器的状态,并作出调整:如果发现某个Worker Node下线了,kube-controller-manager会从其他的Worker Node中重新启动容器
2.2 K8S Pod 网络介绍*
Pod 是K8S的最小单元,包括一个或多个位于同一主机的容器,以及它们共享的存储,网络与运行信息
对于Docker来说,Container,或者容器,是应用程序运行的逻辑主机。
一般来说一个进程,运行在一个Container里面,对于K8S来说呢,Pod是应用程序运行的逻辑主机,一般的,一组进程运行在一个Pod里面。
为什么要以Pod为管理单元呢,这是与现在的应用程序有关,现在的应用程序并不是由一个进程组成,而是有可能由多个进程组成,协同工作,一起构成一个应用。但是为了进程间高效协同,所有要放在同一主机上,IP可达,这样增加了管理复杂度,如何解决:
用不同的Docker Container 运行不同的进程,但是通过一个管理平台来确保这些Container运行在一个主机上,并通过这个管理平台使这些Container共享资源(网络协议栈,这样的话Container之间访问就不用通过IP地址,而是通过类似127.0.0.1这样的localhost IP来实现),这就是K8S的方法。
*更好检测
*解耦软件依赖
*模块化通用程序
(1)pod 内所有的容器公用一个网络空间,公用一个虚拟网卡,公用一个IP地址;网路共享,其他隔离
(2)pod内的容器可以通过localhost访问彼此
(3)pod 这样的网络模型增加了管理难度,为了便于管理网络,每个pod启用一个专门的pause容器来管理网络,因为是一个资源占用的容器,所以不启动,始终是pause状态,不占用系统资源,pause容器是pod的核心,它为pod内的容器提供了虚拟的网络环境。
2.3 Kubenet
K8S支持的network-plugin
* None , k8s 放弃了对网路的管理
*cni ,支持定制的网络插件, 例如flannel , Calico
*kubenet, kubernetes 原生的网络实现方案
(1)kubenet 是kubelete 进程中的一部分
创建Linuxbridge cbr0
通过kubelet 的参数来分配pod的IP地址
(2)kubenet 在pod 内与pod 间通信
同在同一个主机里docker 中不同container通信一样:通过linux bridge 来做转发
(3) 跨Worker Node 间的Pod间通信
通过路由器和交换机
2.4 kubernetes 安装
在已经安装好Docker的基础上,安装Kubernetes
(1)更新get ,以及可以通过https进行传输,添加源是阿里云apt-key
sudo apt-get update && sudo apt-get install -y apt-transport-https curl
(2) 添加Kubernetes源
sudo bash -c 'cat<<EOF > /etc/apt/sources.list.d/kubernetes.list
(3)更新包信息
(4)安装kubernetes 核心组件
apt-get install -y kubelet kubeadm kubectl kubernetes-cni
kubelet:kubelet是Kubernets最重要的组件,它要运行在集群上所有的机器,用来执行类似于开启pods和容器。
kubectl:在集群运行时,控制容器的部件。只需要将其运行在主节点上,但是它将对所有节点都有效。
2.5 创建第一个Pod
2.6 YAML简介
是一种语言格式,用来描述系统配置以及相应资源,与JSON完全兼容,是可以转换为json文件的,由Map和List两类资源组成。
(1) YAML Map
以上示例是由YAML定义的MAP,它由两个键值对,如第一个的name是apiVersion,value是v1;第二个name是kind,value是pod。
"---"是MAP之间的分隔符,是用来区分不同MAP的。这样可以在一个YAML文件里来定义多个MAP。
第三个键值对的name是matedate,value是另外一个map,另一个键值对:name是 name, value是test-pod。
(2) YAML List
如上,name是args,value是“sleep”,"1000","message","Bring back Firefly!" 共同构成list。
2.7 常见调试命令
(1) kubectl describe <resource type> <resource name>
显示资源的详细信息
(2)kubectl get events
显示Kubenetes系统中最近的一些事件。查看K8S运行状态,是否正常。
(3) kubectl get <resource type> <resource name> -o yaml
以YAML格式来详细的输出资源信息。
(4) kubectl logs -f <pod name> <container name>
持续输出容器中主要进程的log, 类似于docker logs。
(5)kubectl exec -it <pod name> --container <container name> -- <command>
在容器中执行命令,类似于docker exec
(6)kubectl run -it --rm --restart=Never busybox --image=busybox sh
启动一个Pod, 并直接登录到pod中,常用来调试网络。
node 与 pod 的关系
(7)kubectl attach <pod name> -c <container name> - it
加入到一个现有的容器console中。
2.8Deployment 与 DaemonSet
2.8.1 Deployment
回顾一下Docker与K8S的最小管理单元,Docker里的最小管理单元是Container容器,Docker的命令是直接控制到容器。
在K8S里Pod是原子单元,一个Pod可以包括多个进程,代表一个应用单元。
K8S也支持直接去管理Pod,但是K8S并不建议用户直接去管理Pod, Pod虽然可以确保所有的容器是运行在WorkerNode上,但是当我们在部署一个弹性的,可扩展的应用时,需要同时部署多个相同的Pod,这些Pod最好在不同的WorkerNode上,并且在不同的时间,Pod的数量可以去动态的增减,如在业务高峰期Pod可以动态增加,业务淡季可以删除。所以需要多Pod进行管理,如果容器上的软件进行升级,还需要对不同Pod上的容器进行更新。
这些管理工作,可以交由用户去做,用户可以制作脚本,或是开发中间层,去控制。
但是K8S的设计哲学是,假定用户不是专家,K8S不向用户去暴露这些复杂的,易出错的操作。
因此,K8S提供了Deployment对象,推荐用户使用Deployment 去管理Pod, Deployment在Pod基础之上,利用了ReplicaSet管理Pod。
之前,创建Pod是直接创建,然后K8S在整个集群的WorkerNode里,去选择一个WorkerNode来创建Pod。现在,创建Pod,是先创建一个Deployment,在Deployment里定义ReplicaSet,创建一组相同的Pod,K8S从WorkerNode里走相同数量的WorkerNode来部署这组Pod。
2.8.2 DaemenSet
DaemenSet 是用来管理Pod的复制。
DaemenSet类似于Deployment, 是用来管理Pod的,DaemenSet与Deployment的区别是:Deployment是用户来制定要创建多少个Pod,DaemenSet它是用来确保每个或是部分的WorkerNode来运行同一个Pod,它们都是用来管理同一个Pod的多个复制,DaemenSet确保每一个WorkerNode都有Pod在运行,如果用户向K8S增加一个Node, DaemenSet会增加一个指定的Pod,如果用户向K8S删除Node, 则相应的Pod也被删除。
Deployment是用来管理无状态的Pod,如Web Server,这些Pod可以动态增减。
DaemonSet用来确保每个Node都运行一个Pod, 并且这个Pod会在其他正常Pod前启动。常用来部署管理程序。
如果我们现在要在K8S上来部署一套OpenStack,我们可以把neturon, nova进程等放在DaemonSet上,这样每增加一个新节点时,K8S会为这些新节点的OpenStack管理进程给拉起来。
2.9创建一个Deployment
2.10 Service
1.为什么需要Service
Pod 是K8S管理的原子单元,Pod的不稳定情况,尤其是网络的不稳定情况如何解决。
(1)如当一个Worknode 下线后,其内部的Pod,就会被调度到其他的Workernode
(2)当修改Deployment的ReplicaSets时,Pod会被创建或删除
Pod的删除或创建会影响其IP地址的变更,这样会对应用带来影响。
因此,需要一种机制来屏蔽IP地址的变化。通用的解决方法:反向代理,或LoadBalancer,来屏蔽IP地址的变化。
2. LoadBalancer
(1)LB的IP地址是固定的
(2) 当应用程序的实例变化了,Pod重建了导致IP地址变化,LB维护更新后的IP列表就可以
(3)LB收到请求之后转发到后端应用程序的实例上。
3. Service
Service 是LB的一种实现。
(1)对应与LB提供服务的IP地址是Service 的Cluster IP
(2)Service 后端管理的是一系列的Pod
(3)Service 如何知道哪些Pod是它的后端(成员)?通过Pod里的label selector来找到对应的Pod
4. Service V.S. Deployment
Deployment侧重的是部署。Deployment指示Etcd中的数据,在Worknode上没有实体,Worknode上只有Pod。Deployment只是在Masternode的Etcd中的数据。
Service是对一组Pod提供应用的逻辑抽象,提供访问入口以及访问策略。Service 需要多Worknode做相应的配置和修改。Service是在Worknode上的配置
5. LabelSelector
(1)Label
Label是挂载在K8S实体--Pod 上的键值对,用来标识对象。K8S可以借助Label来进行有选择的Pod上应用部署
(2)LabelSelector
LabelSelector是K8S提供的用来查询带有特定Label的的API
对于Service 和 Deployment,我们都需要定义LabelSelector。
6.Service 类型
Service是LB的一种实现。Service为一组Pod提供了访问入口。Service分为四类。
(1)Cluster IP
是默认的Service类型, Service带一个私网IP地址,K8S集群内的其他Pod通过这个私网IP地址来访问Service逻辑抽象出来的一组Pod。
(2)NodePort
在Service对应的Pod所在的所有Worknode上,为一组Pod提供访问入口。因为Worknode的IP地址不再是私网地址,因此这组Pod现在可以被K8S集群以外的客户端访问。
(3)LoadBalancer
创建一个外部LoadBalancer, 并且给这个LB分配一个固定的外网地址。通过这个外网地址为一组Pod提供访问入口。
(4)ExternalName
通过DNS提供服务。
2.11创建一个Service
创建一个类型未Cluster IP 的Service
Cluster IP 的Service为K8S集群内的Pod访问Service提供网络连接,Pod的地址是私网地址,是可以通过指定Kubenet的参数,或者是kube-control-manager的参数来分配一个CIDR给Pod。Cluster IP 同样也是一个私网地址,为K8S集群内的Pod访问Service提供连接,这个地址是通过KubeApi的参数来制订的。
通过yaml文件来创建Service:
通过Yaml来创建
访问测试:
wget -o - X.X.X.X
2.12 Cluster IP类型Service
Pod 在任何模式下,都有一个netdev设备(网卡设备)与之对应。Service对应的IP是Cluster IP,但是没有一个设备与之对应。在Pod里或是在WorkNode里也没有关于Service的明细路由。K8S如何为Service提供网络?
Cluster IP 类型的Service是为K8S里面的Pod提供LB服务。
Pod是通过veth对连接在Linux Bridge上的。
在一个K8S集群里Pod的网络是项目联通的,不管Pod是否在同一个WorkNode上。
Kube-proxy是运行在WorkNode上的一个进程,它在Service里的作用就是拦截发往Service的Cluster IP的数据,并修改目的IP地址,它有三种实现:
(1)基于用户空间转发(目前基本不用,性能差)
(2)基于内核的netfilter(iptales),是linux内核的一个LB模块,就是在Linux内核来实现Service,K8S1.9后支持,是Linux内核中基于规则即match-action的包处理引擎,它类似于Openflow,netfilter是一个内核模块,它对应的配置工具是iptables,(是目前主流方案)在iptables中有一个nat表,具体包处理流程:
DNAT处理类型: 随机; 基于源IP。
(3)基于ipvs
2.13 Cluster IP iptables分析
2.14 Endpoint
Endpoint是与Service密切相关的概念,Service与真正提供服务的Pod是两个独立的对象,这个在创建Service时的YAML文件里的Service与Deployment是两个独立的YAML map 就已经说明。
Service是通过label fuleter来找到对应的Pod,但是Pod是不稳定的,它随时会被删除、修改、或者增加新的Pod,所以为了使确保Service能够快速、准确的送到有效的Pod,在K8S里会在Kubecontroller manager针对Service有一个叫Endpoint controller的对象,它负责:
(1)监听Service和对应Pod的变化;
(2)监听到Service被删除,会同步删除与该Service同名的endpoint对象;
(3)监听到新的Service创建,会根据新建Service信息获取Pod列表,创建对应的endpoint对象;
(4)监听到Service被更新,会根据更新后的Service信息获取Pod列表,更新对应的endpoint对象;
(5)监听Pod事件(创建、删除),会更新对应的Service的endpoint对象,将pod IP记录到endpoint中。
所以对于每个Worknode,监听的不是Service对应Pod的变化,而是监听endpoint的变化,就可以知道对应Pod的信息,
2.15 基于Client IP 的 Service
2.16 向外网发布ClusterIP类型的Service
方法一:添加路由,将类型为Cluster IP服务的目的地址 IP加载在物理路由器上,指向对应的WorkNode,这样外部可以访问WorkNode, 在WorkNode上的kube-proxy会用iptalbes来修改目的IP和目的端口:将目的IP由Service 的IP地址更换为Pod的IP地址,将端口也转换为Server Pod所服务的端口。
实际做的时候,可以在路由器上配置多条等价路由对应多个WorkNode来提高冗余度。
一个Service只用一个Cluster IP地址。
问题:添加路由的方法扩展性不好,在大规模集群中无法实现。
方法二:External IP,为类型为Cluster IP类型的Service指定一个External IP; K8S 增加iptalbes规则匹配External IP 和 Service对应的端口号。匹配之后,还是走到K8S Service对应的iptalbes子链。
2.17 NodePort类型的Service
NodePort类型色Service是可以在WorkNode 的IP地址上直接暴露Service。
当创建一个NodePort类型的Service时,K8S会为此Service分配一个30000-32767之间的端口号。
在每个WorkerNode 上,都可以通过<IP>:<端口号>来访问Service。它也是通过Kubeproxy,使用iptables来实现。
比较特殊的是NodePort会做一个Source IP的变换。
NodePort向外发布服务与Cluster IP向外发布不同,不用在Pod之间的路由器上添加路由,因为它是直接在WorkNode上的Pod上暴露出来的,其次它可以在多个WorkNode上来暴露同一Service。
但是,也存在一些问题:
(1)端口的有限,面临扩展性问题。
(2)虽然在WorkNode上可以有多个Pod用NodePort对外暴露服务,但是当Client来访问时,必须制定一个WorkNode,也就是只能通过某一个WorkNode来访问Service,当WorkNode出现故障后,需要用户自己切换到另外一个WorkNode上来访问Service,虽然NodePort 类型的服务有多个入口,但是需要用户自己去做切换。
2.18 LoadBalancer类型Service 和Ingress
K8S中第三中类型的Service--LoadBalancer 类型Service
(1)LoadBalancer
在NodePort类型的Service中,只能从一个Worknode来访问Service,故障需要人为切换,因此不太适合于生产环境。
这样,提供服务的IP(也就是Pod的IP)会发生变化,解决办法:就是在Pod上再添加Service。
NodePort对外服务的IP是WorkNode的IP,但是这个IP地址也不确定的,是可能变化的,解决方案:在WorkNode上再添加LB实例,屏蔽具体的IP地址变化,此处的LB提供了一个公网的IP地址供用户访问,同时又有多个WorkNode作为后端,当用户访问公网IP时,LB将请求发往LB之后的多个WorkNode,再在WorkNode上再通过IPtables规则,再做一次负载分担,将用户的请求发送到相应的Pod上。所以这样的话,就形成了两层LB:一层是实际的面向用户的LB,它在Worker Node之上做了一次封装;第二层是一个基于Iptables的一个分布式LB。
K8S思想是向用户屏蔽复杂操作,创建LB和公网IP地址等提供以上服务的Service就叫LoadBalancer。
(2)Ingress
是独立的K8S资源,完成http和https的路由。是一个七层的路由。
可以通过一个IP地址对应到多个域名。
通过不同的path分发到不同的service。
需要一个单独的Ingress controller来实现Ingress。(不同与Endpoint Controller 和Deploment Controller这些Controller都是作为Kubecontroller的一部分运行在一个进程里面),但是Ingress controller并不是Kubecontroller里的一部分,需要一个单独的Ingress Controller来实现。Ingress Controller是一个第三方提供的,用来实现七层LB的Controller。Ingress 不是一种Service类型,只是对于LB Service的一种补充,并且也是与LB一样,也是需要有其他云平台配合才能实现。
通常IP地址资源是昂贵的,通过Ingress一种七层的负载均衡可以在一个IP地址基础之上,根据不同的hostname,不同的url,对应不同的后端Service,这样一个IP地址就可以对应多个后端的K8S Service。
2.19 K8S DNS
(1)DNS 基础
A: 域名与IPv4地址的对应
AAAA: 域名与IPv6地址的对应
PTR: 域名与IP地址的对应,区别是A或者AAAA是通过域名获得IP地址,RTP是通过IP地址来获得域名
CNAME: 记录一个域名与另一个域名的对应关系
NS: 为当前Zone指定一个DNS server
SOA: 包含Zone的一些信息,例如首要的DNS server, 管理员邮箱地址, 更新时间等
(2)K8S DNS
K8S 提供一个内部的DNS Server, 所有带有Cluster IP的Service, 都会在这个内部DNS Server中添加一条ClusterIP到Service name对应的A类记录。
在Pod内部, K8S会像Docker一样修改/etc/resolv.conf文件,将DNS server指向K8S内部的DNS Server。
所以,在K8S集群内,可以通过Service name 直接访问Service 。
ClusterIP、NodePort、LoadBalancer 三种Service 都有自己的ClusterIP, 因此都可以通过Service name 直接访问。
(3)K8S DNS的具体实现
早期是由kubeadmin来实现的,但是1.12版本后,kubedns就替代了kubeadmin,成为默认的DNS实现。
kubedns在K8S里是通过一个Pod来实现的,这个Pod内有三个容器:
kubedns:监听K8S的API,监听有关Service与Endpoint的变化;监听到变化后会去更新dnsmasq的配置。
dnsmasq:是用来提供DNS Server的功能,是一个轻量级DNS Server的实现。
execheathz: 为以上两个容器提供健康检查。
1.12版本之后默认的DNS是CoreNDS , 是CNCF管理的一个项目,在一个容器中实现K8S资源的监测和DNS服务的提供。可以在一个容器内实现DNS的监测和DNS的服务。
K8S是通过一个多Pod的Deployment来管理这些容器,这样可以确保总是有确定数量的CoreNDSPod在运行。
K8S还为多个Pod提供一个ClusterIP类型的Service。这样DNS请求被负载分担到多个Pod上。
DNS的数量可以通过REPLICA的属性来定义。
2.20 ExternalName 类型Service
第四中类型的Service。不依赖之间的三种Service。提供域名映射的方法。
在服务中,不光IP地址可能会发生变化,域名也会发生变化。通常域名是写在应用程序里的,可维护性弱,如果没有其他措施,域名的修改就要启动修改应用程序,并且重启应用,这样代价较大。如果有一种机制可以屏蔽域名的变化。固定域名-->跳转域名。就是将Service name 与Service里服务于外部的一个域名做一个对应。如果我们要访问这个Service 的话,K8S DNS会返回一个CNAME记录,将Service name转到另一个域名。
ExternalName Service的Yaml文件:
2.21 HostPort 与 HostNetwork
(1)HostPort
HostPort类似与DockerPulish Port的功能:将Docker的一个容器的Port映射到主机的一个Port上。HostPort是将K8S的一个Pod上提供服务的Port映射到Pod所在的WorkNode上的一个Port上。
实现方式:Kubenet 通过iptables配置新增子链(chain)
(2)HostNetwork
K8S的HostNetwork与Docker的HostNetwork类似,它将K8S里的Pod直接连接到WorkNode的主机网络空间,就是将Pod与WorkNode的主机网络连通,此时与Docker类似,Pod也没有自己的Network namespaces,因它使用的就是主机的Network namespaces,实际上是放弃了K8S的网络管理,直接将K8S里的容器连接在了主机的网络空间里。
HostPort和HostNetwork都有一个问题:Pod本身是不稳定的,可能迁移到其他的WorkNode,IP地址会发生变化。还有就是Pod服务所映射的端口与主机端口可能发生冲突,需要人为避免冲突。
==================================================================