自动装箱:
基于容器对应用运行环境的资源配置要求自动部署应用容器
自我修复
当容器失败时,会对容器进行重启
当所部署的 Node 节点有问题时,会对容器进行重新部署和重新调度
当容器未通过监控检查时,会关闭此容器直到容器正常运行时,才会对外提供服务
水平扩展
通过简单的命令、用户 UI 界面或基于 CPU 等资源使用情况,对应用容器进行扩缩容
服务发现
用户不需使用额外的服务发现机制,就能够基于 Kubernetes 自身能力实现服务发现和负载均衡
滚动更新
可以根据应用的变化,对应用容器运行的应用,进行一次性或批量式更新
版本回退
可以根据应用部署情况,对应用容器运行的应用,进行历史版本即时回退
密钥和配置管理
在不需要重新构建镜像的情况下,可以部署和更新密钥和应用配置,类似热部署。
存储编排
自动实现存储系统挂载及应用,特别对有状态应用实现数据持久化非常重要
存储系统可以来自于本地目录、网络存储(NFS、Gluster、Ceph 等)、公共云存储服务
批处理
提供一次性任务,定时任务;满足批量数据处理和分析的场景
再引用刘超大神的一个总结.可以看到k8s对目前的微服务设计简直量身定做
1.etcd, 底层存储
2.api-server. 请求入口. 用户可以请求apiserver,对资源进行增删改查. 是唯一连接etcd的组件.对scheduler/kubelet/proxy都提供了watch/list接口.
3.kube-scheduler. 调度器,监听apiserver中的数据(etcd),将服务调度到合适的node.
4.controller-manager 资源的监控器.例如deploymentController,ServiceController等等.可以确保你的资源维持在你想要的状态.例如你的 DeploymentController会确保一直有3个pod运行.
4.kubelet. 接受master的指令(例如创建pod),上报本机服务的状态.
5.kube-proxy 网络组件,保证网络通讯.
为什么需要pod.因为业务进程间一般不会是孤零零的,很多时候要共享同一个网络栈,存储等.但是容器是单进程模型.容器的1号进程就是应用进程本身.
因此出现了pod,pod本质上是一组共享了某些资源的容器。是一个逻辑概念.Kubernetes 真正处理的,还是宿主机操作系统上 Linux 容器的 Namespace 和 Cgroups,而并不存在一个所谓的 Pod 的边界或者隔离环境。具体的说:Pod 里的所有容器,共享的是同一个 Network Namespace,并且可以声明共享同一个 Volume
$ docker run --net=B --volumes-from=B --name=A image-A ...
貌似这样也可以,但是问题是,需要先有b再有a才行.
所以,在 Kubernetes 项目里,Pod 的实现需要使用一个中间容器,这个容器叫作 Infra 容器。在这个 Pod 中,Infra 容器永远都是第一个被创建的容器,而其他用户定义的容器,则通过 Join Network Namespace 的方式,与 Infra 容器关联在一起.
Pod的状态:
Pending。这个状态意味着,Pod 的 YAML 文件已经提交给了 Kubernetes,API 对象已经被创建并保存在 Etcd 当中。但是,这个 Pod 里有些容器因为某种原因而不能被顺利创建。比如,调度不成功。
Running。这个状态下,Pod 已经调度成功,跟一个具体的节点绑定。它包含的容器都已经创建成功,并且至少有一个正在运行中。因此,当一个pod中包含多个容器.只有全部
Succeeded。这个状态意味着,Pod 里的所有容器都正常运行完毕,并且已经退出了。这种情况在运行一次性任务时最为常见。
Failed。这个状态下,Pod 里至少有一个容器以不正常的状态(非 0 的返回码)退出。这个状态的出现,意味着你得想办法 Debug 这个容器的应用,比如查看 Pod 的 Events 和日志。
Unknown。这是一个异常状态,意味着 Pod 的状态不能持续地被 kubelet 汇报给 kube-apiserver,这很有可能是主从节点(Master 和 Kubelet)间的通信出现了问题。
livenessProbe
怎么判断容器是否存活,如果你什么都不配置,那么就是按照容器的判断了,镜像拉下来,进程起来了,还在,就说明是存活的.但是有时候不严谨,例如hang住了.
因此:
配置 livenessProbe,是为pod里的容器设置的!!. 针对的是容器.pod只是个逻辑概念.当你的pod里存在多个容器,那么就可以针对每个容器,设置不同的健康检查策略.
restartPolicy
Always:在任何情况下,只要容器不在运行状态,就自动重启容器(默认)
OnFailure: 只在容器异常时才自动重启容器.
Never: 从来不重启容器。
注意:这里是针对容器.而不是pod.
pod状态,pod中容器的状态,restartPolicy的关系?
1.如果是单容器的pod,设置restartPolicy=true,那么只要异常就会重启,因此一直running;如果restartPolicy=never,那么就是failed
2.如果是多容器的pod,例如有3个容器,2个运行,1个异常,那么pod的状态是running,ready一栏显示 2/3. 即使你设置了restartPolicy=Never.除非你所有的容器都挂掉了.
readinessProbe
readinessProbe 检查结果的成功与否,决定的这个 Pod 是不是能被通过 Service 的方式访问到,而并不影响 Pod 的生命周期.例如一个web程序启动后.需要去数据库读取数据到内存,初始化之后才能接受请求. 那如果我们配置在liveness的话,如果初始化时间一长,就会重启..因为已经异常了.或者如果程序hang住了,此时应该不接受流量.
2.Deployment 和 ReplicaSet
rs: yaml文件由 pod数目+pod定义. rsController会保证running的pod始终保持在设置的数量上.通过rs实现了水平扩展/收缩.
deployment:yaml文件由 pod数目+pod定义.deployment实现了水平扩展/收缩和滚动发布.
从上面看,rs已经实现了pod的水平扩展.而deployment是rs的父集.k8s中,deployment直接操作的也是rs.而非pod.
怎么实现水平扩展?deployment对象直接操作rs对象,通过修改对应的pod数量就可以实现.这个很简单.
怎么实现滚动更新?
控制两个rs对象.其中有几个参数可以控制滚动的快慢以及速率.
DESIRED:在任何时间窗口内,只有指定比例的 Pod 处于离线状态,默认25%
EmptyDir Volume:
EmptyDir 是 Kubernetes 中最简单的卷了,当我们为一个 Pod 设置一个 EmptyDir 类型的卷时,其实就是在当前 Pod 对应的目录创建了一个空的文件夹,这个文件夹会随着 Pod 的删除而删除。
Projected Volume:
投射数据卷,也就是把数据投射到 容器内.
1.secret
例如你想把数据库密码存放起来,然后pod启动的时候,再设置到容器里.分为两个步骤.
a.编写secret对象
b.pod中引入相应的secret数据
注意:如果etcd中的值发生变化,容器中的值会变化吗?
会的.那么谁去监听和更新呢?node里的proxy负责的是网络.那么肯定就是kubelet了.
2.configMap
和secret类似,只不过没有进行加密
3.DownloadApi
把当前pod的一些信息投射到容器,当然是启动前就能确定的.
4.ServiceAccountToken
特殊的secret,apiServer访问时,需要一些用户校验的
Kubernetes 其实在每个 Pod 创建的时候,自动在它的 spec.volumes 部分添加上了默认 ServiceAccountToken 的定义,然后自动给每个容器加上了对应的 volumeMounts 字段。这个过程对于用户来说是完全透明的。这样,一旦 Pod 创建完成,容器里的应用就可以直接从这个默认 ServiceAccountToken 的挂载目录里访问到授权信息和文件。这个容器内的路径在 Kubernetes 里是固定的,即:/var/run/secrets/kubernetes.io/serviceaccoun.一般在开发插件时使用.
pv和pvc
为什么需要pv和pvc. 开发人员其实只关注我需要多少存储空间,具体这些存储空间存在本机还是云上,存在a目录还是b目录.是什么底层存储引擎.不太关注.
这些让运维去搞就行了.因此pv和pvc也是为了把功能解耦,职责解耦.
注意:
pv和pvc是一一对应的
为什么需要service?
1.pod的ip是不固定的 2.pod之间需要负载.
service本质是什么?
是一组pod的抽象.例如pod上都打了service=a,那么可以定义一个service,选取规则是 service=a.
那么service怎么访问呢?
clusterIp(通过一个vip,内部访问内部)
有点丑的.主要步骤
a.创建service对象,api-server会分配一个vip 10.0.1.175
b.kube-proxy监听到etcd的变化,更改iptables配置,大概就是,请求某个vip,会分发到3个节点,且概率相同.
//请求10.0.1.175,目标端口是80的包,要经过KUBE-SVC-NWV5X2332I4OT4T3这个规则
-A KUBE-SERVICES -d 10.0.1.175/32 -p tcp -m comment --comment "default/hostnames: cluster IP" -m tcp --dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3
//KUBE-SVC-NWV5X2332I4OT4T3共有三条规则
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-WNBA2IHDGP2BOBGZ
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-X3P2623AGDH6CDF3
-A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -j KUBE-SEP-57KPRZ3JQVENLNBR//第一个规则,
-A KUBE-SEP-57KPRZ3JQVENLNBR -s 10.244.3.6/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-57KPRZ3JQVENLNBR -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.3.6:9376-A KUBE-SEP-WNBA2IHDGP2BOBGZ -s 10.244.1.7/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-WNBA2IHDGP2BOBGZ -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.1.7:9376-A KUBE-SEP-X3P2623AGDH6CDF3 -s 10.244.2.3/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
-A KUBE-SEP-X3P2623AGDH6CDF3 -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 10.244.2.3:9376
注意这里要进行DNAT的转换.也就是要访问10.0.1.175时,会将目的地址改为pod地址.
这样导致的一个后果就是.每台机器上的kube-proxy都要维护很多pod的iptables规则.例如有新的service增加了,那么又要维护一套vip--->pod的规则.而写规则是kube-proxy写的(也就是再用户态).
频繁的刷规则,会大量占用该宿主机的 CPU 资源,甚至会让宿主机“卡”在这个过程中
那么怎么优化呢?
使用IPVS.ipvs其实原理也是操作netfilter,然后修改目标地址为pod地址.只不过 1.内部数据结构是用的hash 2.工作在内核态
c.当k8s内部机器.访问vip时,根据iptables配置,会路由到某一个pod上. 注意,只有在集群内部访问时,才可以.
(从外部访问内部)
nodeport(端口)
(38 | 从外界连通Service与Service调试“三板斧”-极客时间)
clusterIp有一个缺点,就是只能在机器内部访问,例如你在一台外部机器上,想访问服务,就访问不了了.
nodePort为每个Node映射了一个端口,对外暴露
1.创建service时,指定type为nodePort.指定node对外暴露的端口和映射的pod的端口.同时,也会创建一个vip.
2.kube-proxy监听到service后,添加iptables规则. node.port--->vip--->podIp
loadbalance(适用于公有云)
1.创建service,配置type=loadbalance,此时k8s会调用公有云的一个接口(CloudProvider),并且把被代理的 Pod 的 IP 地址配置给负载均衡服务做后端
刚刚说了,service想要暴露给外部,可以创建loadBalance方式.but,每个service都创建一个loadbalance吗?这个可是云服务给你提供的,太浪费了.我们希望有一个全局的负载均衡器,当访问/user/a时,可以调用到svc_user的服务,当访问/login时,可以调用到svc_login的服务.
这种全局的、为了代理不同后端 Service 而设置的负载均衡服务,就是 Kubernetes 里的 Ingress 服务。可以理解为 service的service.ingress就是对反向代理的抽象.
一个ingress对象
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: cafe-ingress
spec:
tls:
- hosts:
- cafe.example.com
secretName: cafe-secret
rules:
- host: cafe.example.com
http:
paths:
- path: /tea
backend:
serviceName: tea-svc
servicePort: 80
- path: /coffee
backend:
serviceName: coffee-svc
servicePort: 80
上面的含义,host为cafe.example.com的请求,当path为/tea时,请求转到 tea-svc这个service,当请求/coffee时,转发到coffee-svc,是不是像nginx??事实上,一个ingress对象就是对nginx配置的一个描述.
当你创建了ingress对象,怎么把这个配置转化为能力呢?当然是controller,还记得上面说过,controller就是对pod,deployment等资源进行控制的.我们把nginx-controller运行起来,这个 IngressController 会根据你定义的 Ingress 对象,提供对应的代理能力.
此处注意两个概念,ingress:配置对象 ingressController:代理能力实现,通常是一个pod.
问题:
ingress部署在公有云,那用这种方式比较合适.用Deployment部署ingress-controller,创建一个type为LoadBalancer的service关联这组pod
该方式一般用于宿主机是相对固定的环境ip地址不变的场景。NodePort方式暴露ingress虽然简单方便,但是NodePort多了一层NAT,在请求量级很大时可能对性能会有一定影响。
每个节点都会有一个pod.
该方式整个请求链路最简单,性能相对NodePort模式更好。缺点是由于直接利用宿主机节点的网络和端口,一个node只能部署一个ingress-controller pod。比较适合大并发的生产环境使用
参考
参考[k8s]从docker容器网络到flannel_责任全在mg的博客-CSDN博客
k8s ingress原理及ingress-nginx部署测试 - SegmentFault 思否
Kubernetes 入门&进阶实战 - 知乎