① k8s 基本使用(上)
本文将介绍 k8s 中的一些最基本的命令,并辅以解释一些基本概念来方便理解,也就是说,本文是一篇偏向实用性而非学术性的文章,如果你想提前了解一下 k8s 相关的知识的话,可以通过以下链接进行学习:
k8s 是经典的一对多模型,有一个主要的管理节点 master 和许多的工作节点 slaver 。当然,k8s 也可以配置多个管理节点,拥有两个以上的管理节点被称为 高可用 。k8s 包括了许多的组件,每个组件都是单运行在一个 docker 容器中,然后通过自己规划的虚拟网络相互访问。你可以通过 kubectl get pod -n kube-system 查看所有节点上的组件容器。
在管理节点中会比工作节点运行更多的 k8s 组件,我们就是靠着这些多出来的组件来对工作节点发号施令。他们都叫什么这里就不详细提了。反正对于”基本使用“来说,这些名字并不重要。
要想理解一个东西就要先明白它的内在理念。通俗点就是,k8s 做了什么?为了提供更加可靠的服务,就要增加服务器的数量,减少每个服务器的体量来平摊负载,而越来越多的虚拟机就会带来越来越高的运维成本。如何让少量的运维人员就可以管理数量众多的服务器及其上的服务呢?这就是 k8s 做的工作。
k8s 把数量众多的服务器重新抽象为一个统一的资源池 ,对于运维人员来说,他们面前没有服务器1、服务器2的概念,而是一个统一的资源池,增加新的服务器对运维人员来说,只是增加自资源池的可用量。不仅如此,k8s 把所有能用的东西都抽象成了资源的概念,从而提供了一套更统一,更简洁的管理方式。
接下来,我会把每个基本命令当做一节来进行介绍,并辅以介绍一些基本概念。本文介绍的命令涵盖了增删改查四方面,可参加下面表格,因为篇幅较长,我们将 create 及之后的不那么常用的命令放在下一篇文章 k8s 基本使用(下) 里讲:
接下来进入正题,首先来了解一下 k8s 中最最最常用的命令 kubectl get ,要记住,k8s 把所有的东西都抽象成了资源,而 kubectl get 就是用来查看这些资源的。最常见的资源就是 pod 。
不仅我们自己的服务是要包装成 pod 的,就连 k8s 自己也是运行在一堆 pod 上。接下来就让我们查看一下 k8s 的 pod :
-n 参数指定了要查看哪个命名空间下的 pod 。 k8s 所有的 pod 都被放置在 kube-system 命名空间下。
执行了 kubectl get pod -n kube-system 命令后,你就可以看到如下内容:
其中每一行就是一个资源,这里我们看到的资源是 pod 。你看到的 pod 数量可能和我的不一致,因为这个列表里包含了 k8s 在所有节点上运行的 pod ,你加入的节点越多,那么显示的 pod 也就越多。我们来一列一列的看:
kubectl get 可以列出 k8s 中所有资源
这里只介绍了如何用 kubectl 获取 pod 的列表。但是不要把 get 和 pod 绑定在一起,pod 只是 k8s 中的一种服务,你不仅可以 get pod ,还可以 get svc ( 查看服务 )、 get rs ( 查看副本控制器 )、 get deploy ( 查看部署 )等等等等,虽然说 kubectl get pod 是最常用的一个,但是如果想查看某个资源而又不知道命令是什么, kbuectl get <资源名> 就对了。
如果你想看更多的信息,就可以指定 -o wide 参数,如下:
加上这个参数之后就可以看到资源的所在 ip 和所在节点 node 了。
记得加上 -n
-n 可以说是 kubectl get 命令使用最频繁的参数了,在正式使用中,我们永远不会把资源发布在默认命名空间。所以,永远不要忘记在 get 命令后面加上 -n 。
kubectl get 命令可以列出 k8s 中的资源,而 kubectl get pod 是非常常用的查看 pod 的命令。而 -n 参数则可以指定 pod 所在的命名空间。
kubectl describe 命令可以用来查看某一资源的具体信息,他同样可以查看所有资源的详情, 不过最常用的还是查看 pod 的详情 。他也同样可以使用 -n 参数指定资源所在的命名空间。
举个例子,我们可以用下面命令来查看刚才 pod 列表中的某个 pod,注意不要忘记把 pod 名称修改成自己的:
然后你就可以看到很多的信息,咱们分开说,首先是基本属性,你可以在详细信息的开头找到它:
基本属性
其中几个比较常用的,例如 Node 、 labels 和 Controlled By 。通过 Node 你可以快速定位到 pod 所处的机器,从而检查该机器是否出现问题或宕机等。通过 labels 你可以检索到该 pod 的大致用途及定位。而通过 Controlled By ,你可以知道该 pod 是由那种 k8s 资源创建的,然后就可以使用 kubectl get <资源名> 来继续查找问题。例如上文 DaemonSet/kube-flannel-ds-amd64 ,就可以通过 kubectl get DaemonSet -n kube-system 来获取上一节资源的信息。
内部镜像信息
在中间部分你可以找到像下面一样的 Containers 段落。该段落详细的描述了 pod 中每个 docker 容器的信息,常用的比如 Image 字段,当 pod 出现 ImagePullBackOff 错误的时候就可以查看该字段确认拉取的什么镜像。其他的字段名都很通俗,直接翻译即可。
事件
在 describe 查看详情的时候,最常用的信息获取处就是这个 Event 段落了,你可以在介绍内容的末尾找到它,如下:
是的,如果你看到上面这样,没有任何 Events 的话,就说明该 pod 一切正常。当 pod 的状态不是 Running 时,这里一定会有或多或少的问题,长得像下面一样,然后你就可以通过其中的信息分析 pod 出现问题的详细原因了:
kubectl describe <资源名> <实例名> 可以查看一个资源的详细信息,最常用的还是比如 kubectl describe pod <pod名> -n <命名空间> 来获取一个 pod 的基本信息。如果出现问题的话,可以在获取到的信息的末尾看到 Event 段落,其中记录着导致 pod 故障的原因。
如果你想查看一个 pod 的具体日志,就可以通过 kubectl logs <pod名> 来查看。注意,这个只能查看 pod 的日志。通过添加 -f 参数可以持续查看日志。例如,查看 kube-system 命名空间中某个 flannel pod 的日志,注意修改 pod 名称:
然后就可以看到如下输出:
如果你发现某个 pod 的服务有问题,但是状态还是显示 Running ,就可以使用 kubectl logs 来查看其详细日志。
在本篇文章里,我们了解了 k8s 的宗旨和一些基本概念,并知道了最为常用的 get 、 descibe 及 logs 命令,知道了这三条命令之后就几乎可以从 k8s 中获取所有常用信息了。接下来的 k8s 基本使用(下) 里,我们会更深一步,来了解 k8s 中如何创建、修改及删除资源。
② 计算机网络-k8s网络
K8S网络模型设计:扁平的可连通的网络
K8S的网络是一个极其复杂的网络,如果想要用两个简单的词来描述K8S网络,那么我觉得扁平和可连通是K8S网络最大的特点(不懂隔离性)。
何为连通呢?
二层网络的连通:如果能够直接通过MAC帧直接通信的网络便是二层连通的网络,LAN就是这种网络
比如无限WIFI网络,比如以太网
三层网络的连通:如果能够通过IP报直接通信的网络便是三层连通的网络,便是三层连通
三层网络的连通分为两个部分,第一个部分是三层网络中的每一个LAN都是二层连通的,其次需要存在能够连通的路由来保证;这里可以简单回顾下三层网络通信的流程
通过路由表确定目标ip是否在链路上
如果在链路上,通过arp协议获取对应主机的mac地址,发送mac帧到链路上;
如果不在同一个链路上,通过本地路由表发送mac帧给下一跳,然后下一跳解析mac帧,分析ip报,继续路由直到最终跳到目标网络再次通过mac帧发送到目标主机或者到达ttl消失。
假如其中任何一个步骤不满足或者出问题,三层网络就无法连通
何为扁平呢?
就是希望可以在pod内直接通过IP进行互相通信而不需要在pod内部使用vpn之类的东西来连接其他pod(基础架构化),具体的可以看下k8s对网络的设计与要求。
k8s在设计其网络时,就希望网络对运行在其中的pod是透明的,因此提出了以下的一些要求与原则
k8s组网要求
所有的Pods之间可以在不使用 NAT网络地址转换 的情况下相互通信
所有的Nodes之间可以在不使用NAT网络地址转换的情况下相互通信
每个Pod自己看到的自己的ip和其他Pod看到的一致
k8s网络模型设计原则
每个Pod都拥有一个独立的 IP地址,而且 假定所有 Pod 都在一个可以直接连通的、扁平的网络空间中 。
不管它们是否运行在同 一 个 Node (宿主机)中,都要求它们可以直接通过对方的 IP 进行访问。
设计这个原则的原因 是,用户不需要额外考虑如何建立 Pod 之间的连接,也不需要考虑将容器端口映射到主机端口等问题。
而要想深入了解K8S的网络,就不得不去了解Linux操作系统中的网络以及计算机网络协议栈和一些网络技术
其中关于计算机网络协议栈道部分上次分享已经分享过了,所以本次的主题更多是Linux操作系统的网络以及一些网络技术
Linux操作系统中的网络
首先,我们来看下基本的linux网络,如下图所示
一个APP生成socket数据,然后经过网络协议栈包装IP报文,然后封装成MAC帧,在经过网络协议栈的过程中,会存在netfilters对数据进行一定的处理,同时也会存在路由的过程,
如果在同一个物理链路内,将直接通过ARP协议获取目标IP地址的MAC地址最终发送出去;
如果不在同一个物理链路则通过路由表确定下一跳的MAC地址,封装成MAC帧发送到目标地址。
在这个过程中,会根据路由表选择对应的端口,如果是lo端口,则会将帧原封不动的返回计算机网络协议栈,然后回到监听对应端口的SOCKET里。
如果是以太网端口则走以太网端口,如果是蓝牙或者无线网端口同理。
iptables与netfilters
iptables是一个用户空间的应用程序,通过该程序可以修改一些配置文件,这些文件定义了防火墙的一些行为,netfilters是操作系统内核的一部分,netfilters里有5个回调钩子会触发iptables里的规则;iptables只是Linux防火墙的管理工具而已,位于/sbin/iptables。真正实现防火墙功能的是
netfilter,它是Linux内核中实现包过滤的内部结构。
这里不具体讲述其实现的原理,仅仅列出netfilters的一些功能:
1)filter表——三个链:INPUT、FORWARD、OUTPUT
作用:过滤数据包 内核模块:iptables_filter.
2)Nat表——三个链:PREROUTING、POSTROUTING、OUTPUT
作用:用于网络地址转换(IP、端口) 内核模块:iptable_nat
3)Mangle表——五个链:PREROUTING、POSTROUTING、INPUT、OUTPUT、FORWARD
作用:修改数据包的服务类型、TTL、并且可以配置路由实现QOS内核模块:iptable_mangle(别看这个表这么麻烦,咱们设置策略时几乎都不会用到它)
4)Raw表——两个链:OUTPUT、PREROUTING
作用:决定数据包是否被状态跟踪机制处理 内核模块:iptable_raw
虚拟网络设备 tap/tun
TUN 和 TAP 设备是 Linux 内核虚拟网络设备,纯软件实现。TUN(TUNnel)设备模拟网络层设备,处理三层报文如 IP
报文。TAP 设备模拟链路层设备,处理二层报文,比如以太网帧。TUN 用于路由,而 TAP 用于创建网桥。OS 向连接到 TUN/TAP
设备的用户空间程序发送报文;用户空间程序可像往物理口发送报文那样向 TUN/TAP 口发送报文,在这种情况下,TUN/TAP
设备发送(或注入)报文到 OS 协议栈,就像报文是从物理口收到一样。
虚拟网络设备 veth-pairs
虚拟以太网电缆。使用双向有名管道实现。常用于不同 namespace 之间的通信,即 namespace 数据穿越或容器数据穿越。
虚拟网络设备 bridge
bridge是linux自带的虚拟交换机(网桥),其可以连接多个以太网设备,拥有智能处理MAC帧的能力,流向交换机的MAC帧将智能的被传输到相应的二层链路
网络命名空间
在 Linux 中,网络名字空间可以被认为是隔离的拥有单独网络栈(网卡、路由转发表、iptables)的环境。网络名字空间经常用来隔离网络设备和服务,只有拥有同样网络名字空间的设备,才能看到彼此。
从逻辑上说,网络命名空间是网络栈的副本,有自己的网络设备、路由选择表、邻接表、Netfilter表、网络套接字、网络procfs条目、网络sysfs条目和其他网络资源。
从系统的角度来看,当通过clone()系统调用创建新进程时,传递标志CLONE_NEWNET将在新进程中创建一个全新的网络命名空间。
从用户的角度来看,我们只需使用工具ip(package is iproute2)来创建一个新的持久网络命名空间。
从系统实现来说,就是原本一个数据结构是static公共的,后来变成进程私有的,在PCB里存在一个命名空间的结构,命名空间里有着网络命名空间,网络命名空间拥有着所有跟网络相关的配置数据
默认空的网络命名空间可能只有一个未启动的lo回环网卡。
两个网络命名空间可以通过以太网揽直接连着两个网络命名空间的网卡,也可以通过以太网网桥连接。
通过以太网网桥或者以太网揽连接的两个网络命名空间只能说是在二层连通的,如果希望在三层连通,还需要给每个网络命名空间配置相应的路由表规则以及分配IP地址。
如何使用虚拟网络设备联通网络命名空间
SingleHost容器网络
none模式
本质上就是创建一个网络命名空间,里面没有路由表,也没有通过veths-pair连接任何链路,外部无法访问这个容器,容器也无法访问外部
host模式
本质上就是使用宿主机的默认网络命名空间
container模式
本质上就是将当前容器部署在另一个容器所在的网络命名空间,这样发给本地的报文最终通过回环网卡回到了本机,这是同一个网络命名空间可以互通的原因
bridge模式
桥接模式就是在这些网络命名空间通过veth-pairs连接到同一个虚拟交换机上(二层连通),同时在对应的命名空间配置对应的路由表规则,但是从图片中可以看到交换机另一端连的上网络协议栈。
也就是那些MAC帧都会被宿主机接收,但是宿主机接收并不一定会处理,比如并没有开启ip转发功能(工作于路由器模式还是主机模式),那么不是本地ip的报文都会被丢弃;或者说netfilters拒绝处理
这些奇怪的报文。
理论上,这些容器发送给其他容器的mac报文是会被虚拟交换机智能转发到对应的容器的,这是同一主机不同容器互相连通的原因
假如宿主机配备了相应的路由规则和防火墙规则,那么容器的报文说能够通过路由最终转发出去的,这也是容器访问互联网的原理
但是这种模式是没法运用在多主机的情况下,因为宿主机不知道其他宿主机里的虚拟网络的路由,当相关ip报到达宿主机时,这些ip报将会被宿主机交给默认路由(下一跳:路由器)
最终路由器会把相关的ip报丢失或者到达ttl最终丢失
MultiHost容器网络
路由方案
回顾docker的单机网络模型,我们发现多主机不能通行的原因就在于你只能给当前主机配置路由规则和防火墙规则,而其他主机并不知道这些ip在你的虚拟网络中,假如能够将这些路由信息同步到其他
宿主机,那么网络便会打通。比较直接的想法就是给每台宿主机配置路由规则。而路由规则要求下一跳必须在当前网络,所以假如宿主机是二层互联的,那么通过给这些宿主机同步这些路由规则便能够
实现一个扁平的连通的网络。
其中布置在每一台宿主机可以通过k8s的daemonSet实现,而这种数据的管理可以交给etcd来实现。
这类方案便是基于路由,基于这个方案的实现有基于静态路由的flannel的host-gateway,以及基于动态路由的calico(使用边际路由协议以及一堆深奥的名词的实现)。
下面来看看Flannel的host-gateway原理(每一台宿主机都相当于本机容器网络的路由器):
通过路由方案构建的网络,宿主机也能访问这些虚拟网络里的Pod
询问基德大佬得知国际化sit环境的k8s网络接口实现就是Flannel的Host-gateway,而我们的办公网络和集群网络之间的路由是搭建好的,所以我们应该可以直接通过podId访问pod里的服务
下面是sit环境的两个服务
跟踪路由发现符合猜想
其中10.1.9.56和10.1.1.24就是宿主机的ip,这些宿主机在一个LAN里,这些宿主机相当于虚拟网络中的路由器;
猜测我们办公网和qunhe集群在一个VLAN里(二层可达)
隧道方案
隧道方案比较典型的就是UDP和XVLAN,两者都是使用Overlay网络(覆盖网络,所谓的大二层技术);其实用隧道技术最多的是VPN应用
其中UDP是XVLAN的替代品(早期Linux没有支持XVLAN协议,通过tun/tap技术将流量引到用户空间然后解包生成包再发,因为发生在用户空间而且多次导致性能较差,所以一般不推荐,除非你的linux版本比较低没法用xvlan)
下面就简单介绍下XVLAN技术的大概原理,下图是XVLAN的报文格式,可以发现就是在高层协议的报文里塞了二层报文
其中XVLAN头里有一个关键的字段,VNID这是个24位的字段,每个虚拟的网络主机都有一个自身的VNID作为标识,理论上支持2的24次方个虚拟网络。
在docker的桥接网络里,是使用docker0网桥,在Flannel的xvlan方案里则是使用cni0作为网桥(和docker0没啥区别),主要的不同是cni网桥后面连接的是flannel.1这个网络设备,应该是一个虚拟网卡
这个网卡将原始报文包装成XVLAN报文(linux高版本支持xvlan报文)
这时需要的信息有 源nodeId,目标nodeId,源vnid,源macId,目标macId,源podId,目标podId
其中目标nodeId,目标macId这两个信息是不存在的;因此需要有个方式根据目标podId获取目标nodeId以及目标macId
因此需要记录如何根据目标podId获取目标macId以及目标nodeId即可
这些数据是可以托管在某个地方的,Flannel就是将这些信息记录在etcd上
在每个node上的flannel.1网络设备通过etcd来通过对方的podId获取nodeId和macId
这样最终报文就变成了一个源ip是源nodeIp,目标ip是目标nodeIp的IP报文了(两台宿主机三层可达)
原本经过虚拟网桥是直接连接网络协议栈,但在xvlan模式下,则改为连接一个flannel1,在flannel1中将对原始报文封装成overlay报文转发
udp模式类似,只是udp转发报文说通过tap连通到用户空间,用户空间对报文进行处理然后发送(因为多次内核态用户态切换且数据问题,性能较差,仅在不支持xvlan的低版本linux中使用)
当然xvlan是一个技术,上面只是简单介绍最简单的形式
参考:
开发内功修炼之网络篇: https://mp.weixin.qq.com/mp/appmsgalbum?__biz=MjM5Njg5NDgwNA==&action=getalbum&album_id=1532487451997454337&scene=173&from_msgid=2247485270&from_itemidx=1&count=3&nolastread=1#wechat_redirect
K8S知识图谱: https://zhaohuabing.com/post/2020-02-22-k8s-mindmap/
VXLAN协议原理简介: https://cizixs.com/2017/09/25/vxlan-protocol-introction/
③ k8s网络原理-ipvs
一、背景知识
本文主要介绍k8s网络中service 的两种模式(clusterIp、nodeport),数据是如何通过ipvs&iptables流转的。在学习上述知识的同时,还需要了解一下ipset、conntrack的相关知识。 往期回顾文章
1.1、ipset
ipset是什么?ipset其实是iptables的扩展,可以定义一些列地址的集合。拿黑名单来举例,我想让黑名单里面的ip拒绝访问网站(黑名单有很多个),按照传统iptables做法,需要在filter表添加很多规则匹配时一条一条匹配效率很低(严重影响性能),而有了ipset,则只用添加一条规则即可,使用hash结构效率很高。
而使用ipset命令如下
当然,ipset还支持 hash:ip,hash:ip,port,ip等多种hash key的组成,具体可以通过 ipset -h 查看。接下来说明一下 -m set 后面 src 和 dst 两个的含义。src 指来源,dst 指目标,此规则的意思是来自192.178.113.100 ip 访问本机8410端口的流量给DROP掉。
ipset使用hash结构,比iptables的链表遍历效率要高很多。ipset还有很多更加高级的玩法,本文就不在阐述了。
1.2、ipvs
lvs是什么?全称是Linux Virtual Server,是由章文嵩博士主导的开源负载均衡项目,目前已经集成到linux内核中。lvs提供了丰富的负载均衡能力,接收到用户请求后根据具体的负载均衡算法在内核态把请求转发到后端的某个server上,也就是说lvs不需要监听具体的端口。接下来我们看一下lvs的一些基本概念。
ipvs的原理如下。ipvs工作在iptables 的 input链上,VIP一般定义在DS节点上的一个虚拟ip,拿nat模式举例如下。
① : 当请求数据包到DS上最先经过iptables 的PREROUTING链,判断目标ip (VIP) 是本机的ip,于是把请求转发到INPUT链上。
② : 因为lvs工作在INPUT链上,数据到达INPUT链上后lvs会将用户请求和定义的后端服务做对比,如果是请求的后端服务,则使用某种负载均衡算法找到一个后端RIP,修改数据包的目的ip和端口为某个RIP的(DNAT转换)。
③ : 此时数据到达POSTROUTING链(不会做SNAT),数据包的源ip 为CIP,目的ip为RIP,数据包发往RIP上。
lvs提供了三种包转发模式,如下所示
由于k8s使用的是NAT模式,接下来看下 NAT模式下的数据包流向 。如下图所示
①:请求数据包到达DS,数据包经过PREROUTING链,此时ip 包 src ip为CIP,dst ip 为VIP
②:由于请求的VIP是DS上的虚拟ip,数据包发往INPUT链。
③:数据包到INPUT链上后,ipvs发现数据包请求是定义的集群服务,于是使用定义好的负载均衡算法找到一个具体的RS节点,做DNAT,修改数据包dst ip为RIP,数据包到达POSTROUTING链,发送给RS。
④:RS收到数据包后对比dst ip 发现是自己,接收数据包做处理,处理完成后ip 数据包 src ip 为RIP,dst ip 为CIP,把数据包发给DS。
⑤:DS 接收到RS的响应包,修改src ip 为自身的VIP,dst ip 为CIP,把数据包发送给client端。
三种模式对比&优缺点
接下来在简单聊一下ipvs的负载均衡策略,简单介绍下面四种。
上面介绍完了ipvs内核态的基本原理,接下来介绍一下如何使用 ipvsadm 用户态命令来操作ipvs。说明:此次试验是在四个虚拟机上,ipvs的模式使用的nat模式,RS的网关没有指向DS的ip(没办法做到)在DS节点上手动创建SNAT命令,下文有详细介绍。创建一个vip,在ip为192.168.113.101上
为vip添加RS
添加完成RS后,查看ipvs规则,如下图所示
client端的ip地址为192.168.113.102,client端要想直接访问vip的话,需要在client端添加静态路由,添加命令如下
添加完命令后,在client端curl 10.10.0.1:8410 发现不通,此时去某个RS上抓包如下
上图抓包显示,client 直接访问的vip,而数据包的目的ip 变为了rs的ip,因此可以看出ipvs做了DNAT转换。因为做了DNAT,RS发送响应数据直接发给client,client收到RS的数据包。client给vip发的包却收到了RS的响应包(client 想我从来没有给RS发过数据),因此client端会把此数据包丢弃。
因为ipvs没有做SNAT,接下来在DS上添加iptables规则自己实现SNAT的功能,添加完SNAT后, RS就看不到真实的CIP了 。
此时还是不通,查找资料后发现ipvs 的 conntrack 没有开,手动打开,后续文章介绍conntrack是什么,设置完成后可以愉快的访问了。
总结:通过ipvs提供的DNAT功能和负载均衡功能,很容易实现外部用户访问内网的需求。但是还要考虑高可用层面,比如主DS宕机VIP要漂移到备DS上,后端RS重启或宕机,ipvs负载均衡列表中要及时把有问题的RS剔除,这样才能真正的实现高可用。
1.3、conntrack
大家在家上网时用到的都是192.168.x.x的ip地址,这是私网ip地址。那么大家是如何能够成功的访问外网的呢?答案是路由器帮我们做了SNAT的功能,使我们发出的数据包的src ip变为路由器的公网ip,这样数据包就能在互联网上愉快的转发了。从而实现了对内网的保护。
那么问题来了,既然做了SNAT转换,那响应数据包回来以后路由器怎么知道转到哪台PC上呢?路由器可能链接了很多PC,不可能都给每一个PC转发吧。。。答案就是conntrack实现的。
接下来我拿上面ipvs的例子举例,我们手动实现了在DS上SNAT转换,在client上curl vip:8410,这时候查看DS上和client上的conntrack表如下
先从client上的连接跟踪分析起:主要看 src、dst、sport、dport这几个字段。
client发送数据包
client端发出数据包的src ip 为192.168.113.102,dst ip 为10.10.0.1 (VIP), sport 为35562这个端口,dport为8410(VIP 定义端口)。
client端接收响应数据包
期望src ip 为vip(10.10.0.1),dst ip 为CIP(192.168.113.102),sport为8410,dport为35562
DS接收数据包
DS接收到src ip 为CIP(192.168.113.102),dst ip 为vip(10.10.0.1),sport为35562,dport为8410的数据包
DS接收响应数据包
由于在DS侧做了DNAT转换,根据负载均衡策略找到了一个RS(RIP 192.168.113.99),同时也做了SNAT转换(判断是否是VIP和端口),转换为DS的DIP。所以当DS收到src ip 为192.168.113.99(RIP),dst ip 为192.168.113.101(DIP),sport为8080,dport为35562,会根据连接跟踪表找到这个包是192.168.113.102这个client发过来的,因此把数据包在转发给192.168.113.102:35562 上。
conntrack各个字段的含义
总结:
本文只是简单的说明了一下conntrack,并没有具体说明数据流经netfilter时何时创建记录,数据存储的数据结构啥样,底层比较复杂,感兴趣的大佬可以自行研究~
二、k8s网络通信
介绍完了ipset、ipvs、conntrack,接下来进入正题,看一下ipvs模式下k8s的网络通信。kube-proxy 的主要作用是watch apiserver,当监听到pod 或service变化时,修改本地的iptables规则或ipvs规则。
2.1、clusterIp模式
clusterIp模式为一个集群内部可访问的ip,集群外部没办法访问这个ip,试验环境如下:
创建完deployment和service后,查看一下service的ip如下。
接下来看下宿主机网卡、ipvs规则、ipset规则有什么变化
查看iptables 的nat表和filter表,看一下k8s创建了哪些规则以及经过哪些链
接下来分析一下curl 10.108.113.237 数据是如何走的,只讨论在nat表和filter表的流向,因为在mangle和raw都没有规则。
1、nat表PREROUTING链
①:数据首先进入PREROUTING链,所有请求都会进入KUBE-SERVICES链。
②:进入KUBE-SERVICES后,查看对应在此链上的规则,发现请求的目的ip和port在KUBE-CLUSTER-IP 对应的ipset里面(上面已有展示),匹配上了则跳往KUBE-MARK-MASQ链。
③:数据流向KUBE-MARK-MASQ链,主要做了mark 打标记的功能,iptables命令如下
④:之后走向KUBE-NODE-PORT链,因为没有定义nodepode 类型的service,此处先略过。 2、filter表的INPUT链
⑤:首先进入INPUT链,所有数据转向KUBE-FIREWALL链。
⑥:进入KUBE-FIREWALL链,如果发现数据包打了0x8000/0x8000,DROP掉。因为ipvs工作在INPUT链上,做完DNAT之后直接转发到POSTROUTING链上。
3、nat表POSTROUTING链
⑦:进入POSTROUTING链,所有数据转向KUBE-POSTROUTING链
⑧:进入KUBE-POSTROUTING链,对有0x4000/0x4000标记的数据包做SNAT转换,因为ipvs只有DNAT功能。
4、数据转发给flannel网卡,进行转发
⑨:flannel 根据具体的backend模式,对数据做封包等操作,然后发出去。flannel的网络模式比较复杂,之后会专门文章进行说明。
2.2、nodeport模式
要想把集群内部的服务可以让集群外部访问,可以使用nodeport模式在物理机上开一个端口,这样外部就能访问集群内部的服务了。说明:还是使用上面创建的deployment。
查看创建service的信息,发现也创建了集群内部的一个ip。
iptables规则如下
接下来看下ipset规则有什么变化,发现KUBE-NODE-PORT-TCP下的一个成员是刚才我们指定的那个nodePort的值。
接下来看一下iptables规则,nat表和filter表
1、nat表PREROUTING链
①:数据首先进入PREROUTING链,所有请求都会进入KUBE-SERVICES链。
②:ip和port匹配不上KUBE-CLUSTER-IP 的ipset,判断是访问的本地地址,进入KUBE-NODE-PORT链。
③:进入KUBE-NODE-PORT链后,判断访问端口在 KUBE-NODE-PORT-TCP ipset规则中,因此进入KUBE-MARK-MASQ链。
④:进入KUBE-MARK-MASQ链,对数据做mark标记
后续流程跟clusterIp一样,此处就不在阐述。
2.3、dns相关
k8s中的dns默认使用的是coredns,通过以下命令查看。k8s中定义的service是有域名的,访问域名要通过dns解析,此时coredns就发挥它的作用了。
上面的试验时我们创建了一个my-service 的nodePort的service,此时查看一下此域名对应的ip,如下图所示,域名解析出来的ip与service对应的ip相同,大功告成。
参考:
以上相关内容介绍了k8s service ipvs的相关实现,如有错误欢迎指出~
④ k8s基本使用入门-使用Pod
定义了一个 nginx.yaml 的文件:
其中定义了版本,类型,名称,镜像,端口等等。
现在来启动它:
简单查看一下状态:
可以看到命名为 nginx 的 pod 起来了,1/1 表示只有一个
查看详细状态:
可以观察到 pod 的 ip(172.30.64.2),以及所被分配到的节点(kube-node01)
进入到 pod 方式以下方法:
这样以来就直接进入到 pod 对应的容器里边来了,如果这个 pod 里边含有多个容器,那么默认是进入到第一个容器当中。如果想要进入到其他容器,那么看下命令帮助信息:
现在看到这个 pod 在集群中的 ip 是172.30.64.2,那么我们访问一下
发现在集群当中访问都是可以的,但是我们这个 nginx 部署之后,是想要让外部人员能够访问的,那么这个时候就要用的 kubectl 的port-forward了。
先看看介绍:
我们可以通过这个指令将 pod 里边的端口映射到主机上来,方法如下
将 pod 里边的 80 端口映射到主机的 8080 端口,那么现在去访问一下看看:
发现已经可以了。但是刚刚那个映射的时候,可以看到映射输出在前台不会退出,而一旦退出之后,则这个映射又消失了,怎样才能恒久的映射呢,这个后边再说
⑤ K8s中pod的数据通讯
k8s的网络通讯方式
k8s的网络模型假定是所有Pod都在一个可以直接联通的扁平的网路空间,我们需要自己实现这个网络假设,将不同节点上的Docker容器之间的互相访问先打通,然后运行K8s。
同一个Pod内的多个容器: 同一个pod共享一个网络命名空间,共享同一个linux协议栈
同一个节点各pod之间的通讯: 通过docker0网桥直接发送数据,不通过flannel
pod与service之间的通讯: 各节点的iptables规则。
pod到外网: pod向外网发送请求,查找路由表转发数据包到宿主机的网卡,宿主机网卡完成路由选择后,iptables执行Masquerade,把源IP更改为着网卡的IP,然后向外网发送请求。
flannel的功能是让集群中的不同节点主机创建的Docker容器具有集群唯一的虚拟IP地址,能够在这些Ip地址之间建立
一个覆盖网络(OverlayNetwork),通过这个覆盖网络,将数据包原封不动传递到目标容器内部。
不同节点的pod之间的访问:
首先,在node服务器上会安装flanneld的守护进程,这个进程会监听一个端口,这个端口是用于接收和转发数据包的端口,flanneld进程启动以后,会开启一个网桥叫flannel0,flannel0会抓取docker0转发的数据包,docker0会分配自己的IP到对应的pod上,如果是同一台主机中的不同pod的话,他们之间的通讯走的是docker0网桥。如果是不同节点之间的pod进行通讯,数据包源地址写自己的ip,目标地址写目标地址的ip,然后将数据发送到docker0网桥也就是网关,此时flannel0有自己钩子函数,将数据从docker0抓取,flannel0是flanneld开启端口,数据到了flanneld之后会将数据报文进行一个封装,然后将数据转发到目标节点,然后到了目标节点之后会将数据进行一次解封,到达目标节点的flanneld,然后数据会进行二次解封,flanneld会通过自己的端口将数据发送到docker0网桥,然后在对数据进行转发,从而发送到相应的目标pod。
这里的etcd与flannel的关系:
存储管理flannel可分配的IP地址段资源
监控etcd中每个pod的实际地址,并在内存中建立维护pod节点路由表。
flannel启动以后会向etcd中插入需要分配的网段以及这些网段分别分配在哪个节点上。
⑥ 简述Kubernetes网络策略
为实现细粒度的容器间网络访问隔离策略,Kubernetes引入Network Policy。
Network Policy的主要功能是对Pod间的网络通信进行限制和准入控制,设置允许访问或禁止访问的客户端Pod列表。Network Policy定义网络策略,配合策略控制器(Policy Controller)进行策略的实现。我推荐你去看看时速云,他们是一家全栈云原生技术服务提供商,提供云原生应用及数据平台产品,其中涵盖容器云PaaS、DevOps、微服务治理、服务网格、API网关等。大家可以去体验一下。 如果我的回答能够对您有帮助的话,求给大大的赞。
⑦ K8s创建Pod的不同姿势,常用策略讲解Demo
钱比你想象的重要得多,超过20岁了就别整天活在梦里了,对于平凡的你来讲,钱就是你的尊严。
Pod 环境:Ansible+K8s集群
Ansible 环境准备
容器集群环境准备
kubectl explain --help
查看pod的语法结构
新建一个命名空间用于创建
kubectl config set-context context1 --namespace=liruilong-pod-create
查看当前集群信息
查看命名空间
设置刚才新建的命名空间为当前命名空间
kubectl run podcommon --image=nginx --image-pull-policy=IfNotPresent --labels="name=liruilong" --env="name=liruilong"
kubectl get pods -o wide
kubectl delete pod pod-demo --force
每个Pod都有一个pause镜像
kubectl run pod-demo --image=nginx --image-pull-policy=IfNotPresent --dry-run=client -o yaml >pod-demo.yaml
yaml文件的获取方法:-o yaml
yaml文件创建pod
pod-demo.yaml
yaml文件创建pod
删除pod:delete pod
创建pod时指定运行命令。替换镜像中CMD的命令
kubectl delete -f comm-pod.yaml 删除pod
批量创建pod
通过 sed 更改 pod名字的方式:sed ‘s/demo/demo1/’ demo.yaml | kubectl apply -f -
容器共享pod的网络空间的。即使用同一个IP地址:pod IP
一个pod内创建多个容器
comm-pod.yaml 文件编写
创建 多容器pod
--image-pull-policy
restartPolicy –单个容器正常退出
k8s中每个资源对象都有标签
查看标签
指定标签过滤
⑧ 简述Kubernetes网络模型
Kubernetes网络模型中每个Pod都拥有一个独立的IP地址,并假定所有Pod都在一个可以直接连通的、扁平的网络空间中。所以不管它们是否运行在同一个Node(宿主机)中,都要求它们可以直接通过对方的IP进行访问。设计这个原则的原因是,用户不需要额外考虑如何建立Pod之间的连接,也不需要考虑如何将容器端口映射到主机端口等问题。
同时为每个Pod都设置一个IP地址的模型使得同一个Pod内的不同容器会共享同一个网络命名空间,也就是同一个Linux网络协议栈。这就意味着同一个Pod内的容器可以通过localhost来连接对方的端口。
在Kubernetes的集群里,IP是以Pod为单位进行分配的。一个Pod内部的所有容器共享一个网络堆栈(相当于一个网络命名空间,它们的IP地址、网络设备、配置等都是共享的)。
我推荐你去看看时速云,他们是一家全栈云原生技术服务提供商,提供云原生应用及数据平台产品,其中涵盖容器云PaaS、DevOps、微服务治理、服务网格、API网关等。大家可以去体验一下。 如果我的回答能够对您有帮助的话,求给大大的赞。
⑨ Pod的特性
Pod是k8s系统中可以创建和管理的最小单元,是资源对象模型中由用户创建或部署的最小资源对象模型,也是在k8s上运行容器化应用的资源对象,其他的资源对象都是用来支撑或者扩展Pod对象功能的,比如控制器对象是用来管控Pod对象的,Service或者Ingress资源对象是用来暴露Pod引用对象的,PersistentVolume资源对象是用来为Pod提供存储等等,k8s不会直接处理容器,而是Pod,Pod是由一个或者多个container组成的。
每个Pod都是运行应用的单个实例,如果需要水平扩展应用(例如,运行多个实例),则应该使用多个Pods,每个实例一个Pod。在Kubernetes中,这样通常称为Replication。Replication的Pod通常由Controller创建和管理。
Pod可以被理解成一群可以共享网络、存储和计算资源的容器化服务的集合。再打个形象的比喻,在同一个Pod里的几个Docker服务/程序,好像被部署在同一台机器上,可以通过localhost互相访问,并且可以共用Pod里的存储资源(这里是指Docker可以挂载Pod内的数据卷,数据卷的概念,后文会详细讲述,暂时理解为“需要手动mount的磁盘”)。笔者总结Pod如下图,可以看到:同一个Pod之间的Container可以通过localhost互相访问,并且可以挂载Pod内所有的数据卷;但是不同的Pod之间的Container不能用localhost访问,也不能挂载其他Pod的数据卷。
1.1、为什么需要pod
我们先谈谈为什么k8s会使用pod这个最小单元,而不是使用docker的容器,k8s既然使用了pod,当然有它的理由。
1、更利于扩展
k8s不仅仅支持Docker容器,也支持rkt甚至用户自定义容器,为什么会有这么多不同的容器呢,因为容器并不是真正的虚拟机,docker的一些概念和误区总结,此外,Kubernetes不依赖于底层某一种具体的规则去实现容器技术,而是通过CRI这个抽象层操作容器,这样就会需要pod这样一个东西,pod内部再管理多个业务上紧密相关的用户业务容器,就会更有利用业务扩展pod而不是扩展容器。
2、更容易定义一组容器的状态
如果我们没有使用pod,而是直接使用一组容器去跑一个业务呢,那么当其中一个或者若干个容器出现问题呢,我们如何去定义这一组容器的状态呢,通过pod这个概念,这个问题就可以很好的解决,一组业务容器跑在一个k8s的pod中,这个pod中会有一个pause容器,这个容器与其他的业务容器都没有关系,以这个pause容器的状态来代表这个pod的状态.
3、利于容器间文件共享,以及通信。
pod里的多个业务容器共享pause容器的ip和存储卷Volume,pod中的其他容器共享pause容器的ip地址和存储,这样就做到了文件共享和互信。
1.2 Pod 特性:
1 资源共享:IP和Volume
一个Pod里的多个容器可以共享存储和网络IP,可以看作一个逻辑的主机。共享的如 namespace,cgroups或者其他的隔离资源。
多个容器共享同一个network namespace,由此在一个Pod里的多个容器共享Pod的IP和端口namespace,所以一个Pod内的多个容器之间可以通过localhost来进行通信,所需要注意的是不同容器要注意不要有端口冲突即可。不同的Pod有不同的IP,不同Pod内的多个容器之前通信,不可以使用IPC(如果没有特殊指定的话)通信,通常情况下使用Pod的IP进行通信。
k8s要求底层网络支持集群内任意两个pod直接的TCP/IP直接通信,这通常才有虚拟二层网络技术来实现,例如Flannel,Openswitch等。
一个Pod里的多个容器可以共享存储卷,这个存储卷会被定义为Pod的一部分,并且可以挂载到该Pod里的所有容器的文件系统上。
2 生命周期短暂
Pod属于生命周期比较短暂的组件,比如,当Pod所在节点发生故障,那么该节点上的Pod会被调度到其他节点,但需要注意的是,被重新调度的Pod是一个全新的Pod,跟之前的Pod没有半毛钱关系。
3 平坦的网络
K8s集群中的所有Pod都在同一个共享网络地址空间中,也就是说每个Pod都可以通过其他Pod的IP地址来实现访问。
1.3 Pod使用和管理
1、核心原则是:将多个应用分散到多个Pod中 原因:基于资源的合理应用;扩缩容,不同应用应该有不同的扩缩容策略等。
结论:单Pod单容器应用,除非特殊原因。
你很少会直接在kubernetes中创建单个Pod。因为Pod的生命周期是短暂的,用后即焚的实体。当Pod被创建后(不论是由你直接创建还是被其他Controller),都会被Kubernetes调度到集群的Node上。直到Pod的进程终止、被删掉、因为缺少资源而被驱逐、或者Node故障之前这个Pod都会一直保持在那个Node上。
Pod不会自愈。如果Pod运行的Node故障,或者是调度器本身故障,这个Pod就会被删除。同样的,如果Pod所在Node缺少资源或者Pod处于维护状态,Pod也会被驱逐。Kubernetes使用更高级的称为Controller的抽象层,来管理Pod实例。虽然可以直接使用Pod,但是在Kubernetes中通常是使用Controller来管理Pod的。
1.4、Pod和Controller
Controller可以创建和管理多个Pod,提供副本管理、滚动升级和集群级别的自愈能力。例如,如果一个Node故障,Controller就能自动将该节点上的Pod调度到其他健康的Node上。
包含一个或者多个Pod的Controller示例:
Deployment
StatefulSet
DaemonSet
通常,Controller会用你提供的Pod Template来创建相应的Pod。
在用户定义范围内,如果pod增多,则ReplicationController会终止额外的pod,如果减少,RC会创建新的pod,始终保持在定义范围。例如,RC会在Pod维护(例如内核升级)后在节点上重新创建新Pod。
对Pod的定义可以通过Yaml或Json格式的配置文件来完成。关于Yaml或Json中都能写哪些参数,参考官网 http://kubernetes.io/docs/user-guide/pods/multi-container/
Pod的yaml整体文件内容及功能注解如下:
我们来看一个段实际的例子
在使用docker时,我们可以使用docker run命令创建并启动一个容器,而在Kubernetes系统中对长时间运行的容器要求是:其主程序需要一直在前台运行。如果我们创建的docker镜像的启动命令是后台执行程序,例如Linux脚本:
nohup ./startup.sh &
则kubelet创建包含这个容器的pod后运行完该命令,即认为Pod执行结束,之后根据RC中定义的pod的replicas副本数量生产一个新的pod,而一旦创建出新的pod,将在执行完命令后陷入无限循环的过程中,这就是Kubernetes需要我们创建的docker镜像以一个前台命令作为启动命令的原因。
对于无法改造为前台执行的应用,也可以使用开源工具supervisor辅助进行前台运行的功能。
Pod可以由一个或多个容器组合而成
场景1:单个应用多个容器
spring boot web:
kubectl create -f springboot-deployment.yml
kubectl get pods -o wide
加入 –o wide参数 查看额外信息:包括node和ip
pod处于pending的原因:通过 kubectl describe pods springbootweb 进一步查找问题。
可以看到pod的镜像信息写错了:
先删除pod,然后再创建: kubectl delete pod springbootweb
由于创建的端口号是9081,可以直接访问:curl 10.0.86.2:9081
# curl 10.0.86.2:9081
Hello world
场景2:Pod不同应用多个容器组合而成
例如:两个容器应用的前端frontend和redis为紧耦合的关系,应该组合成一个整体对外提供服务,则应该将这两个打包为一个pod.
配置文件frontend-localredis-pod.yaml如下:
属于一个Pod的多个容器应用之间相互访问只需要通过localhost就可以通信,这一组容器被绑定在一个环境中。
使用kubectl create创建该Pod后,get Pod信息可以看到如下图:
#kubectl get gods
可以看到READY信息为2/2,表示Pod中的两个容器都成功运行了.
2.3 集群外部访问Pod
上面的例子,在k8s集群的安装有kube-proxy的node节点上,可以直接通过curl 10.0.86.2:9081 访问集群的pod。但在集群外的客户端系统无法通过Pod的IP地址或者Service的虚拟IP地址和虚拟端口号访问到它们。为了让外部客户端可以访问这些服务,可以将Pod或Service的端口号映射到宿主机,以使得客户端应用能够通过物理机访问容器应用。
1、将容器应用的端口号映射到物理机
(2)通过设置Pod级别的hostNetwork-true,该Pod中所有容器的端口号都将被直接映射到物理机上。设置hostNetwork-true时需要注意,在容器的ports定义部分如果不指定hostPort,则默认hostPort等于containerPort,如果指定了hostPort,则hostPort必须等于containerPort的值。
静态pod是由kubelet进行管理的仅存在于特定Node的Pod上,他们不能通过API Server进行管理,无法与ReplicationController、Deployment或者DaemonSet进行关联,并且kubelet无法对他们进行健康检查。静态Pod总是由kubelet进行创建,并且总是在kubelet所在的Node上运行。
创建静态Pod有两种方式:配置文件或者HTTP方式
1)配置文件方式
首先,需要设置kubelet的启动参数"--config",指定kubelet需要监控的配置文件所在的目录,kubelet会定期扫描该目录,并根据目录中的 .yaml或 .json文件进行创建操作
假设配置目录为/etc/kubelet.d/配置启动参数:--config=/etc/kubelet.d/,然后重启kubelet服务后,再宿主机受用docker ps或者在Kubernetes Master上都可以看到指定的容器在列表中
由于静态pod无法通过API Server直接管理,所以在master节点尝试删除该pod,会将其变为pending状态,也不会被删除
#kubetctl delete pod static-web-node1
要删除该pod的操作只能在其所在的Node上操作,将其定义的.yaml文件从/etc/kubelet.d/目录下删除
#rm -f /etc/kubelet.d/static-web.yaml
#docker ps
Volume类型包括:emtyDir、hostPath、gcePersistentDisk、awsElasticBlockStore、gitRepo、secret、nfs、scsi、glusterfs、persistentVolumeClaim、rbd、flexVolume、cinder、cephfs、flocker、downwardAPI、fc、azureFile、configMap、vsphereVolume等等,可以定义多个Volume,每个Volume的name保持唯一。在同一个pod中的多个容器能够共享pod级别的存储卷Volume。Volume可以定义为各种类型,多个容器各自进行挂载操作,讲一个Volume挂载为容器内需要的目录。
如下图:
如上图中的Pod中包含两个容器:tomcat和busybox,在pod级别设置Volume “app-logs”,用于tomcat想其中写日志文件,busybox读日志文件。
配置文件如下:
busybox容器可以通过kubectl logs查看输出内容
#kubectl logs volume-pod -c busybox
tomcat容器生成的日志文件可以登录容器查看
#kubectl exec -ti volume-pod -c tomcat -- ls /usr/local/tomcat/logs
应用部署的一个最佳实践是将应用所需的配置信息于程序进行分离,这样可以使得应用程序被更好的复用,通过不用配置文件也能实现更灵活的功能。将应用打包为容器镜像后,可以通过环境变量或外挂文件的方式在创建容器时进行配置注入。ConfigMap是Kubernetes v1.2版本开始提供的一种统一集群配置管理方案。
6.1 ConfigMap:容器应用的配置管理
容器使用ConfigMap的典型用法如下:
ConfigMap以一个或多个key:value的形式保存在Kubernetes系统中共应用使用,既可以用于表示一个变量的值,也可以表示一个完整的配置文件内容。
通过yuaml配置文件或者直接使用kubelet create configmap 命令的方式来创建ConfigMap
6.2 ConfigMap的创建
举个小例子cm-appvars.yaml来描述将几个应用所需的变量定义为ConfigMap的用法:
# vim cm-appvars.yaml
执行kubectl create命令创建该ConfigMap
#kubectl create -f cm-appvars.yaml
查看建立好的ConfigMap:
#kubectl get configmap
kubectl describe configmap cm-appvars
kubectl get configmap cm-appvars -o yaml
另:创建一个cm-appconfigfile.yaml描述将两个配置文件server.xml和logging.properties定义为configmap的用法,设置key为配置文件的别名,value则是配置文件的文本内容:
在pod "cm-test-app"定义中,将configmap "cm-appconfigfile"中的内容以文件形式mount到容器内部configfiles目录中。
Pod配置文件cm-test-app.yaml内容如下:
创建该Pod:
#kubectl create -f cm-test-app.yaml
Pod "cm-test-app"created
登录容器查看configfiles目录下的server.xml和logging.properties文件,他们的内容就是configmap “cm-appconfigfile”中定义的两个key的内容
#kubectl exec -ti cm-test-app -- bash
root@cm-rest-app:/# cat /configfiles/server.xml
root@cm-rest-app:/# cat /configfiles/ logging.properties
6.3使用ConfigMap的条件限制
使用configmap的限制条件如下:
Pod在整个生命周期过程中被定义为各种状态,熟悉Pod的各种状态有助于理解如何设置Pod的调度策略、重启策略
Pod的状态包含以下几种,如图:
Pod的重启策略(RestartPolicy)应用于Pod内所有的容器,并且仅在Pod所处的Node上由kubelet进行判断和重启操作。当某哥容器异常退出或者健康检查石柏师,kubelet将根据RestartPolicy的设置进行相应的操作
Pod的重启策略包括Always、OnFailure及Nerver,默认值为Always。
kubelet重启失效容器的时间间隔以sync-frequency乘以2n来计算,例如1、2、4、8倍等,最长延时5分钟,并且成功重启后的10分钟后重置该事件。
Pod的重启策略和控制方式息息相关,当前可用于管理Pod的控制器宝库ReplicationController、Job、DaemonSet及直接通过kubelet管理(静态Pod),每种控制器对Pod的重启策略要求如下:
RC和DaemonSet:必须设置为Always,需要保证该容器持续运行
Job:OnFailure或Nerver,确保容器执行完成后不再重启
kubelet:在Pod失效时重启他,不论RestartPolicy设置什么值,并且也不会对Pod进行健康检查
对Pod的健康检查可以通过两类探针来检查:LivenessProbe和ReadinessProbe
LivenessProbe探针:用于判断容器是否存活(running状态),如果LivenessProbe探针探测到容器不健康,则kubelet杀掉该容器,并根据容器的重启策略做响应处理
ReadinessProbe探针:用于判断容器是否启动完成(ready状态),可以接受请求。如果ReadinessProbe探针探测失败,则Pod的状态被修改。Endpoint Controller将从service的Endpoint中删除包含该容器所在的Pod的Endpoint。
kubelet定制执行LivenessProbe探针来诊断容器的健康状况。LivenessProbe有三种事项方式。
1)ExecAction:在容器内部执行一个命令,如果该命令的返回值为0,则表示容器健康。例:
(2)TCPSocketAction:通过容器ip地址和端口号执行TCP检查,如果能够建立tcp连接表明容器健康。例:
3)HTTPGetAction:通过容器Ip地址、端口号及路径调用http get方法,如果响应的状态吗大于200且小于400,则认为容器健康。例:
对于每种探针方式,都需要设置initialDelaySeconds和timeoutSeconds两个参数,它们含义如下:
initialDelaySeconds:启动容器后首次监控检查的等待时间,单位秒
timeouSeconds:健康检查发送请求后等待响应的超时时间,单位秒。当发生超时就被认为容器无法提供服务无,该容器将被重启
九.玩转Pod调度
在Kubernetes系统中,Pod在大部分场景下都只是容器的载体而已,通常需要通过RC、Deployment、DaemonSet、Job等对象来完成Pod的调度和自动控制功能。
9.1 RC、Deployment:全自动调度
RC的主要功能之一就是自动部署容器应用的多份副本,以及持续监控副本的数量,在集群内始终维护用户指定的副本数量。
在调度策略上,除了使用系统内置的调度算法选择合适的Node进行调度,也可以在Pod的定义中使用NodeName、NodeSelector或NodeAffinity来指定满足条件的Node进行调度。
1)NodeName
Pod.spec.nodeName用于强制约束将Pod调度到指定的Node节点上,这里说是“调度”,但其实指定了nodeName的Pod会直接跳过Scheler的调度逻辑,直接写入PodList列表,该匹配规则是强制匹配。
2)NodeSelector:定向调度
Kubernetes Master上的scheler服务(kube-Scheler进程)负责实现Pod的调度,整个过程通过一系列复杂的算法,最终为每个Pod计算出一个最佳的目标节点,通常我们无法知道Pod最终会被调度到哪个节点上。实际情况中,我们需要将Pod调度到我们指定的节点上,可以通过Node的标签和pod的nodeSelector属性相匹配来达到目的。
Pod.spec.nodeSelector是通过kubernetes的label-selector机制进行节点选择,由scheler调度策略MatchNodeSelector进行label匹配,调度pod到目标节点,该匹配规则是强制约束。
启用节点选择器的步骤为:
kubectl label nodes <node-name> <label-key>=<label-value>
例: #kubectllabel nodes k8s-node-1 zonenorth
运行kubectl create -f命令创建Pod,scheler就会将该Pod调度到拥有zone=north标签的Node上。 如果多个Node拥有该标签,则会根据调度算法在该组Node上选一个可用的进行Pod调度。
需要注意的是:如果集群中没有拥有该标签的Node,则这个Pod也无法被成功调度。
3)NodeAffinity:亲和性调度
该调度策略是将来替换NodeSelector的新一代调度策略。由于NodeSelector通过Node的Label进行精确匹配,所有NodeAffinity增加了In、NotIn、Exists、DoesNotexist、Gt、Lt等操作符来选择Node。调度侧露更加灵活。
9.2 DaemonSet:特定场景调度
DaemonSet用于管理集群中每个Node上仅运行一份Pod的副本实例,如图:
这种用法适合一些有下列需求的应用:
在实际生产环境中,我们经常遇到某个服务需要扩容的场景,也有可能因为资源精确需要缩减资源而需要减少服务实例数量,此时我们可以Kubernetes中RC提供scale机制来完成这些工作。
以redis-slave RC为例,已定义的最初副本数量为2,通过kubectl scale命令可以将Pod副本数量
#kubectl scale rc redis-slave --replicas=3
ReplicationController"redis-slave" scaled
#kubectl get pods
除了可以手工通过kubectl scale命令完成Pod的扩容和缩容操作以外,新版本新增加了Horizontal Podautoscaler(HPA)的控制器,用于实现基于CPU使用路进行启动Pod扩容缩容的功能。该控制器基于Mastger的kube-controller-manager服务启动参数 --horizontal-pod-autoscler-sync-period定义的时长(默认30秒),周期性监控目标Pod的Cpu使用率并在满足条件时对ReplicationController或Deployment中的Pod副本数量进行调整,以符合用户定义的平均Pod Cpu使用率,Pod Cpu使用率来源于heapster组件,所以需预先安装好heapster。
当集群中的某个服务需要升级时,我们需要停止目前与该服务相关的所有Pod,然后重新拉取镜像并启动。如果集群规模较大,因服务全部停止后升级的方式将导致长时间的服务不可用。由此,Kubernetes提供了rolling-update(滚动升级)功能来解决该问题。
滚动升级通过执行kubectl rolling-update命令一键完成,该命令创建一个新的RC,然后自动控制旧版本的Pod数量逐渐减少到0,同时新的RC中的Pod副本数量从0逐步增加到目标值,最终实现Pod的升级。需要注意的是,系统要求新的RC需要与旧的RC在相同的Namespace内,即不能把别人的资产转到到自家名下。
例:将redis-master从1.0版本升级到2.0:
需要注意的点:
运行kubectl rolling-update来完成Pod的滚动升级:
#kubectl rolling-update redis-master -f redis-master-controller-v2.yaml
另一种方法就是不使用配置文件,直接用kubectl rolling-update加上--image参数指定新版镜像名来完成Pod的滚动升级
#kubectl rolling-update redis-master --image=redis-master:2.0
与使用配置文件的方式不同的是,执行的结果是旧的RC被删除,新的RC仍然使用就的RC的名字。
如果在更新过程总发现配置有误,则用户可以中断更新操作,并通过执行kubectl rolling-update-rollback完成Pod版本的回滚。