目录
2.5 Replication Controller(副本控制器,RC)
前言
kubernetes中的大部分概念如Node、Pod、Replication Controller、Service等都可以看作是一种“服务”,几乎所有的资源对象都可以通过kubernetes提供的kubectl工具(或API编程调用)执行增、删、改、查操作并将其保存在etcd中持久化存储。从这个角度看kubernetes其实就是一个高度自动化的资源控制系统,它通过跟踪对比etcd库里保存的“资源期望状态”与当前环境中的“实际资源状态”的差异来实现自动控制和自动纠错的高级功能。
1、kubernetes架构图
更加通俗的展示:
2、kubernetes基本概念和术语
2.1 Master
K8S中的Master指的是集群控制节点,每个k8s集群里都需要有一个master节点来负责整个集群的管理和控制,基本上k8s所有的控制命令都是发给它,由它来负责具体的执行过程,我们后面所有执行的命令基本都是在Master节点上运行的。
Master节点通常会占据一个独立的X86服务器/一个虚拟机,主要是因为它太重要了,它是整个集群的“首脑”,如果它宕机或者不能用,那么所有控制命令都将失效。
Master节点上运行着以下一组关键进程:
- kube-apiserver:提供了HTTP Rest接口的关键服务进程,是k8s中所有资源的增、删、改、查等操作的唯一入口,也是集群控制的入口进程,任何对资源的增删该查都要交给APIServer处理后再交给etcd,如上图,kubectl(kubernetes提供的客户端工具,该工具内部是对kubernetes API的调用)是直接和APIServer交互的。
- kube-controller-manager:如果APIServer做的是前台的工作的话,那么controller manager就是负责后台的。每一个资源都对应一个控制器。而control manager就是负责管理这些控制器的,比如我们通过APIServer创建了一个Pod,当这个Pod创建成功后,APIServer的任务就算完成了。它作为k8s里所有资源对象的自动化控制中心,可理解为资源对象的“大总管”。
- kube-schedule:负责资源调度进程,相当于“调度室”。kube-schedule负责调度Pod到合适的Node上,如果把scheduler看成一个黑匣子,那么它的输入是pod和由多个Node组成的列表,输出是Pod和一个Node的绑定。 kubernetes目前提供了调度算法,同样也保留了接口。用户根据自己的需求定义自己的调度算法。
- etcd:etcd是一个高可用的键值存储系统,kubernetes使用它来存储各个资源的状态数据,从而实现了Restful的API。
2.2 Node
除了Master,kubernetes集群中的其他机器被称为Node节点,在较早版本中称之为Minion。
与Master一样,Node节点可以是一台物理机,也可是虚拟机。Node节点才是kubernetes集群中的工作负载节点,每个Node都会被Master分配一个工作负载(Docker容器),当Node节点宕机时,其工作负载会被Master自动转移至其它节点上去。
Node节点上运行着以下一组关键进程:
- kubelet:负责Pod对应的容器创建、启停、资源监控等任务。同时与Master节点密切协作,实现集群管理的基本功能。
- kube-proxy:实现kubernetes Service的通信与负载均衡机制的重要组件
- Docker Engine(docker):Docker引擎,负责本机的容器创建和管理工作。
Node节点可以在运行期间动态增加到kubernetes集群中,前提是这个节点已经正确安装、配置和启动了上述关键进程,默认情况下kubelet会向Master注册自己,这也是kubernetes推荐的Node管理方式。一旦Node被纳入集群管理范围,kubelet进程就会定时向Master节点汇报自身的情报,如操作系统、Docker版本、机器的CPU和内存情况,以及之前有哪些Pod在运行等,这样Master可以获知每个Node的资源使用情况,并实现高效负载均衡的资源调度策略,而某个Node超过指定时间不上报信息时,会被Master判定为“失联”,Node的状态被标记为不可用(Not Ready),随后Master会触发“工作负载大转移”的自动流程。
查看集群Node数量:
#kubectl get nodes |
通过#kubectl describe node <node_name>查看某个Node详细信息:
#kubectl describe node kubernetes-node1 |
2.3 Pod
Pod是k8s的最重要也是最基本的概念,它是kubernetes集群中运行部署应用或服务的最小单元,可以支持多容器。它的设计理念是支持多个容器在一个Pod中共享网络地址和文件系统,可以通过进程间通信和文件共享这些简单高效的方式组合完成服务。
如图是Pod的组成示意图,我们看到每个Pod都有一个特殊的被称为“根容器”的Pause容器。Pause容器对应的镜像属于kubernetes平台的一部分,除了Pause容器,每个Pod还包含一个或多个紧密相关的用户业务容器。
为什么k8s会设计出一个全新的Pod的概念并且Pod有这样特殊的组成结构?
- 原因1:一组容器作为一个单元情况下,难以对“整体”简单进行判断及有效地进行行动。
当一个容器死亡时,此时算整体死亡吗?还是算个体死亡?是N/M的死亡率么?引入业务无关并且不易死亡的Pause容器作为Pod的根容器,以它的状态代表整个容器组的状态,简单、巧妙解决这个难题。
- 原因2:Pod里面,多个业务容器之间如何解决彼此的通信和文件共享问题?
Pod里面多个容器共享Pause容器的IP,共享Pause容器挂接的Volume,这样既简化了密切关联的业务容器之间通信问题,也解决了彼此之间的文件共享问题。
- k8s为每个Pod都分配了唯一的IP地址,称之为Pod IP。一个Pod里面的多个容器共享Pod IP地址。k8s要求底层网络支持集群r内任意两个Pod之间的TCP/IP直接通信,通常采用虚拟二层网络技术来实现。牢记一点:在kubernetes里,一个Pod里的容器与另外主机上的Pod容器能够直接通信。
Pod的两种类型:普通的Pod、静态Pod(static Pod)
静态Pod并不存放在etcd存储内,而是存放在某个具体额Node上的一个具体文件中,并且只在此Node上启动运行。
普通Pod一旦被创建,就会被放入到etcd存储中,随后会被kubernetes进程实例化成一组相关的Docker容器并启动起来。
在默认情况下,当Pod里某个容器停止时,kubernetes会自动检测到这个问题并且重新启动这个Pod(重启Pod里的所有容器),如果Pod所在的Node宕机,则会将这个Node上的所有Pod重新调度到其他Node节点上。Pod、容器与Node的关系如下:
kubernetes里的所有资源对象都可采用yaml或JSON格式的文件来定义描述:
apiVersion: v1
kind: Pod
metadata:
name: myweb
labels:
name: myweb
spec:
containers:
- name: myweb
image: kubeguide/tomcat-app:v1
ports:
- containerPort: 8080
evn:
- name: MYSQL_SERVER_HOST
value: 'mysql'
- name: MYSQL_SERVER_HOST
value: '3306'
解释一下上边各个属性含义:
kind:为Pod表名这是一个Pod的定义
metadata:
name:Pod的名字
labels:资源对象的标签,这里声明myweb拥有一个name=myweb的标签
spec:声明容器组的定义规则
定义了容器的名称、对应镜像名称,该容器注入了环境变量(env关键字),并且在8080端口(containerPort,容器的端口号)上启动容器进程。
Pod的IP+容器端口(containerPort)组成了一个新的概念-------Endpoint,它表示此 Pod里的一个服务进程的对外通信地址。一个Pod也存在多个Endpoint的情况,当我们把Tomcat定义为一个Pod的时候,可以对外暴露管理端口与服务端口这两个Emdpoint。
2.4 Label(标签)
Label是k8s系统的另外一个核心概念。一个Label是一个key=value的键值对,其中key与value由用户自己指定。Label通常在资源对象定义时确定,也可以在对象创建后动态添加或删除。
我们可通过给指定的资源对象绑定一个或多个不同的Label来实现多维度的资源分组管理功能,便于灵活、方便地进行资源分配、调度、配置、部署等管理工作。
一些常用的Label实例如下:
- 版本标签:“release”:"stable",“release”:"canary"....
- 环境标签:“environment”:"dev",“environment”:"qa",“environment”:"production",
- 架构标签:"tier":"frontend","tier":"backend","tier":"middleware"
- 分区标签:"position":"customerA","position":"customerB".....
- 质量管控标签:"track":"daily","track":"weekly"
Label相当于我们熟悉的“标签”,给某个资源对象定义一个Label,就相当于给它打了一个标签,随后可通过Label Selector(标签选择器)查询和筛选拥有某些Label的资源对象,k8s通过这种方式实现了类似SQL的简单又通用的对象查询机制。
Label Selector可以类比为SQL语句的where查询条件,如name=redis-slave这个Label Selector作用于Pod时,可以类比为select * from pod where pod's name ='redis-slave'这样的语句。当前有两种Label Selector的表达式:【1】基于等式;【2】基于集合。
前者采用“等式类”的表达式匹配标签。具体栗子:
- name=redis-slave:匹配所有具有标签name=redis-slave的资源对象;
- env!=production:匹配所有不具有env!=production的资源对象
后者使用集合操作的表达式匹配标签,举个栗子:
- name in (redis-master,redis-slave):匹配所有具有标签name=redis-master或者name=redis-slave的资源对象;
- name not in (php-frontend):匹配所有不具有标签name=php-frontented的资源对象;
可以通过多个Label Selector表达式的组合实现复杂的条件选择,多个表达式之间用“,”进行分隔即可,几个条件之间是“AND”的关系,即同时满足多个条件,栗子:
- name=redis-slave,env!=production
- name not in (php-frontend),env!=production
Label Selector在kubernetes中的重要使用场景如下几处:
- kube-controller进程通过资源对象RC上定义的Label Selector来筛选要监控的Pod副本的数量,从而实现Pod副本的数量始终符合预期设定的全自动控制流程。
- kube-proxy进程通过Service的Label Selector来选择对应的Pod,自动建立起每个Service到对应Pod的请求转发路由表,从而实现Service的智能负载均衡机制。
- 通过对某些Node定义特定的Label,并且在Pod定义文件中使用NodeSelector这种标签调度策略,kube-scheduler进程可以实现Pod“定向调度”的特性。
假设为Pod定义了3个Label:release、env、role,不同的Pod定义了不同的Label值,如下图所示,如果我们设置了“role=frontend”的Label Selector,则会选取到Node1和Node2上的Pod。
而设置“rolease=beta”的Label Selector,则会选取到Node2和Node3上的Pod。
小结:使用Label可以给对象创建多组标签,Label和Label Selector共同构成了kubernetes系统中最核心的应用模型,使得被管理对象能够被精细地分组管理,同时实现了整个集群的高可用性。
2.5 Replication Controller(副本控制器,RC)
RC是kubernetes集群中最早的保证Pod高可用的API对象,通过监控运行中的Pod来保证集群中运行指定数目的Pod副本。它其实定义了一个期望的场景,即声明某种Pod的副本数量在任意时刻都符合某个预期值,所以RC的定义包括如下几个部分:
- Pod期望的副本数量(replicas);
- 用于筛选目标Pod的Label Selector;
- 当Pod的副本数量小于预期数量的时候,用于创建新Pod的Pod模板(template);
- 当Pod的副本多于预期数量时,RC就会杀死多余的Pod副本。
一个完整的RC定义的例子,即确保用于tier=frontend标签的这个Pod在整个kubernetes集群中始终只有一个副本:
apiVersion: v1
kind: ReplicationController
metadata:
name: frontend
spec:
replicas: 1 #预期Pod节点副本数量
selector: #标签选择器
tier: frontend
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
containers:
- name: tomcat-demo
image: tomcat
imagePullPolicy: IfNotPresent
evn:
- name: GET_HOSTS_FROM
value: dns
ports:
- containerPort: 80
- 我们定义一个RC兵提交到kubernetes集群中后,Master节点上的Controller Manager组件就得到通知,定期巡检系统中存活的目标Pod,并确保目标Pod实例的数量刚好等于此RC的期望数量,如果有过多的Pod副本在运行,系统就会停掉一下Pod,否则系统就会再自动创建一些Pod。通过RC,kubernetes实现了用户应用集群的高可用性,并且大大减少了系统管理员的手工运维工作。
我们以3个node节点的集群为例,说明kubernetes如何通过RC来实现Pod副本数量自动控制的机制。加入我们的RC里定义redis-slave这个Pod需要保持3个副本,系统将可能在其中的两个Node上创建Pod。下图将描述在两个Node上创建redis-slave Pod的情形。
加入Node2上的Pod2意外终止,根据RC定义的replicas数量2,kubernetes将会自动创建并启动一个新的Pod,以保证整个集群中始终有两个redis-slave Pod在运行。
如下图所示,系统可能选择Node1或者Node3来创建一个新的Pod:
此外,在运行时,我们可以通过修改RC的副本数量,来实现Pod的动态缩放(Scaling)功能。
这可以通过执行kubectl scale命令来一键完成:
# kubectl scale rc redis-slave --replicas=3 |
Scaling的执行结果如下图所示:
注意:删除RC并不会影响通过该RC已创建好的Pod。为了删除所有Pod,可以设置replicas的值为0,然后更新该RC。另外,kubectl提供了stop和delete命令来一次性删除RC和RC控制的全部Pod。
RC实现kubernetes应用升级:
传统升级方式:通过Build一个新的Docker镜像,并用新的镜像版本替代旧版本达到升级目标。
平滑升级方式:当前系统中10个对应的旧版本的Pod,可以让旧版本的Pod每次停止一个,同时创建一个新版本的Pod,在整个升级过程中,此消彼长,而运行的Pod数量始终是10个,当所有的Pod都已是新版本时,升级过程完成。通过RC的机制,kubernetes很容易就实现了高级实用的特性,即“滚动升级(Rolling Update)”
在kubernetes 1.2版本后,RC升级为一个新的概念--------Replica Set,即下一代RC。
它与当前RC的区别:Replica Set支持基于集合的Label Selector(Set-based selector),而RC只支持基于等式的Label Selector(equality-based selector)。
RC的一些特性与作用:
- 通过定义一个RC实现Pod的创建过程及副本数量的自动控制;
- RC里包含完整的Pod定义模板;
- RC通过Label Selector机制实现对Pod副本的自动控制;
- 通过改变RC中的Pod副本数量,可以实现Pod的扩容或缩容功能;
- 通过改变RC中Pod模板中的镜像模板,可以实现Pod的滚动升级功能。
2.6 Service(服务)
2.6.1 概念
kubernetes中的每个service就是我们常提起的微服务架构中的一个“微服务”,先前说的Pod、RC等资源对象就是为kubernetes service做“嫁衣”的。如图显示了Pod、RC与service的逻辑关系:
上图,kubernetes的service定义了一个服务的服务入口地址,前端应用(Pod)通过这个入口地址访问其背后的一组由Pod副本组成的集群实例,Service与其后端Pod副本集群之间则是通过Label Selector来实现“无缝对接”。而RC的作用实际上就是保证Service的服务能力和质量始终处于预期的标准。
Service不是共用一个负载均衡器的IP地址,而是每个Service分配了一个全局唯一的虚拟IP地址,即Cluster IP,这样每个服务就变成了具备唯一IP地址的“通信节点”,服务调用就变成了最基础的TCP网络通信问题。
我们知道Pod的Endpoint地址会随着Pod的销毁和重新创建而发生改变,因为新Pod的IP地址与之前旧Pod的不同。而Service一旦创建,kubernetes就会自动为它分配一个可用的Cluster IP,而在Service的整个生命周期内,它的Cluster IP不会发生改变。关于地址改变问题:只要用Service的name与Cluster IP地址做一个DNS域名映射即可完美解决。关于Endpoint列表,我们可以通过#kubectl get endpoints来查看
2.6.2 kubernetes的服务发现机制
任何分布式系统都会涉及“服务发现”这个基础问题,kubernetes采用了直观朴素的思路去解决这个问题。
每个kubernetes中的Service都有一个唯一的Cluster IP以及唯一的名字,而名字是开发者自己定义的,部署的时候也没必要改变,所有完全可以固定在配置中。接下来的问题就是如何通过Service的名字找到对应的Cluster IP?
最早的时候kubernetes采用了Linux环境变量的方式解决这个问题,即每个Service生成一些对应的LInux环境变量(ENV),并在每个Pod的容器启动时,自动注入这些环境变量。
kubernetes通过Add-On增值包的方式引入了DNS系统,把服务名作为DNS域名,程序就可以直接使用服务名来建立通信连接了,目前kubernetes的大部分应用都已经采用了DNS这些新兴的服务发现机制。
2.6.3 外部系统访问Service的问题
kubernetes中存在“三种IP”,分别如下:
- Node IP:Node节点的IP地址
- Pod IP:Pod的IP地址
- Cluster IP:Service的IP地址
首先,Node IP是kubernetes集群中每个节点的物理网卡的IP地址,真实存在的物理网络,所有属于这个网络的服务器之间都能通过这个网络直接通信,不管它们中是否有部分节点不属于这个kubernetes集群。这也表明了kubernetes集群之外的节点访问kubernetes集群之内的某个节点或者TCP/IP服务的时候,必须要通过NodeIP进行通信。
其次,Pod IP是每个Pod的IP地址,它是Docker Engine根据docker()网桥的IP地址段进行分配的,常为虚拟的二层网络。kubernetes要求位于不同Node上的Pod能够彼此直接通信,所有kubernetes里一个Pod里的容器访问另外一个Pod里的容器,就是通过Pod IP所在的虚拟二层网络进行通信的,而真实的TCP/IP流量则是通过Node IP的物理网卡流出的。
最后,关于Service的Cluster IP,它也是一个虚拟的IP,但是更像是一个“伪造”的IP网络,原因如下:
- Cluster IP仅仅作用于kubernetes Service这个对象,并由kubernetes管理和分配IP地址(来源于Cluster IP地址池)
- Cluster IP无法被ping,应为没有一个“实体网络对象”来响应
- Cluster IP只能结合Service Port组成一个具体的通信端口,单独的Cluster IP不具备TCP/IP通信的基础,并且它们属于kubernetes集群这样一个封闭的空间,集群之外的节点如果要访问这个通信端口,则需要做一些额外的工作
- 在kubernetes集群之内,Node IP网、Pod IP网与Cluster IP网之间的通信,采用kubernetes的特殊路由规则。
由此可知,Service的Cluster IP属于kubernetes集群内部的地址,无法在集群外部直接使用这个地址。比如web端的服务模块,我们可采用 NodePort来解决,在其Service文件中定义nodeport端口,我们在浏览器里服务http://<nodeport IP>:nodeport/
NodePort的实现方式是在kubernetes集群里的每个Node上为需要外部访问的Service开启一个对应的TCP监听端口,外部系统只需任意一个Node的IP地址+r具体的NodePort端口号即可访问此服务。在任意Node上运行netstat命令,可看大所有NodePort端口被监听:#netstat -tlp | grep 8080
当然,NodePort并没有完全解决外部访问Service的所有问题,比如负载均衡问题。
2.7 Namespace(命名空间)
namespace用于实现多租户的资源隔离。它通过将集群内部的资源对象“分配”到不同namespace中,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。
kubernetes集群在启动后,会创建一个名为“default”的Namespace,通过kubectl可以查看到:#kubectl get namespaces
如果不特别指明Namespace,则用户创建的Pod、RC、Service都将被系统创建到这个默认的名为default的Namespace中。Namespace的定义很简单,如下所示yaml定义了名为development的Namespace。
apiVersion: v1
kind: Namespace
metadata:
name: development
2.8 Annotation(注解)
Annotation与Label类似,也是以key-value键值对的形式进行定义。不同的是Label具有严格的命名规则,它定义的时kubernetes对象的元数据(Metadata),并且勇于Label Selector,而Annotation则是用户任意定义的“附加”信息,以便于外部工具进行查找,而kubernetes的模块自身会通过Annotation的方式标记资源对象的一些特殊信息。
通常来说,用Annotation来记录的信息如下:
- build信息、release信息、Docker镜像信息等,例如时间戳、release id号、PR号、镜像hash值、docker registry地址等
- 日志库、监控库、分析库等资源库的地址信息
- 程序调试工具信息
- 团队的联系信息
3、 Kubernetes工作流程
- 【1】创建一个包含应用程序的Develpment的yml文件,然后通过kubectl客户端工具发送给ApiServer;
- 【2】ApiServer接收到客户端的请求并将资源内容存储到数据库etcd中
- 【3】Controller组件(包含scheduler、replication、endpoint)监控资源变化并作出反应
- 【4】ReplicaSet检查数据库变化,创建期望数量的Pod实例
- 【5】Scheduler再次检查数据变化,发现尚未被分配到具体执行节点(Node)的Pod,然后根据一组相关规则将Pod分配到可以运行它们的节点(Node)上,并更新数据库,记录Pod分配情况。
- 【6】kubelet监控数据库变化,管理后续Pod的生命周期,发现被分配到它所在的Node节点上运行的那些Pod。如果找到新的Pod,则会在该节点上运行这个新的Pod,也就是告诉Doeck Engine运行这个Pod
- 【7】kuberproxy运行在集群各个主机上,管理网络通信,如服务发现、负载均衡。例如当有数据发送到主机时,将其路由到正确的pod或容器。对于从主机上发出的数据,它可以基于请求地址发现远程服务器,并将数据正确路由,在某些情况下会使用轮训调度算法(Round-robin)将请求发送到集群中的多个实例。
注意:本文归作者所有,未经作者允许,不得转载