目录
前言
1、kubernetes架构图
2、kubernetes基本概念和术语
2.1 Master
2.2 Node
2.3 Pod
2.4 Label(标签)
2.5 Replication Controller(副本控制器,RC)
2.6 Service(服务)
2.6.1 概念
2.6.2 kubernetes的服务发现机制
2.6.3 外部系统访问Service的问题
2.7 Namespace(命名空间)
2.8 Annotation(注解)
3、 Kubernetes工作流程
kubernetes是一个完备的分布式系统支撑平台,大部分概念如Node、Pod、Replication Controller、Service等都可以看作是一种“服务”,几乎所有的资源对象都可以通过kubernetes提供的kubectl工具(或API编程调用)执行增、删、改、查操作并将其保存在etcd中持久化存储。从这个角度看kubernetes其实就是一个高度自动化的资源控制系统,它通过跟踪对比etcd库里保存的“资源期望状态”与当前环境中的“实际资源状态”的差异来实现自动控制和自动纠错的高级功能。
更加通俗的展示:
在集群管理方面,kubernetes将集群中的机器划分为一个Master节点和一群工作节点(Node)。
其中,在Master节点上运行着集群管理相关的一组进程kube-apiserver、kube-controller-manager、kube-scheduler,这些进程实现了整个集群的资源管理、Pod调度、弹性伸缩、安全控制、系统监控和纠错等管理功能,并且都是全自动完成的。
Node作为集群中的工作节点,运行真正的应用程序,在Node节点上kubernetes管理的最小运行单元是Pod。Node上运行着kubernetes的kubelet、kube-proxy服务进程,这些服务进程负责Pod的创建、启动、监控、重启、销毁,以及实现软件模式的负载均衡器。
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。除了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上面展示了Node的如下关键信息:
Node的基本信息:名称、标签、创建时间等;Node的当前运行状态:Node启动后会运行一系列的自检工作,在一切正常时设置Node为Ready状态,该状态表示Node处于健康状态,Master将可以在其上调度新的任务了(如启动Pod);Node的主机地址与主机名;Node上的资源数量:描述Node可用的系统资源,包括CPU、内存数量、最大可调度Pod数量等。主机信息当前运行的Pod列表概要信息;已分配的资源使用概要信息,如资源申请的最低、最大允许使用量占系统总量的百分比;Node相关的Event信息。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要求底层网络支持集群内任意两个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的时候,可以对外暴露管理端口与服务端口这两个Endpoint。
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!=productionname not in (php-frontend),env!=productionLabel 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系统中最核心的应用模型,使得被管理对象能够被精细地分组管理,同时实现了整个集群的高可用性。
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=3Scaling的执行结果如下图所示:
注意:删除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的滚动升级功能。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来查看
任何分布式系统都会涉及“服务发现”这个基础问题,kubernetes采用了直观朴素的思路去解决这个问题。
每个kubernetes中的Service都有一个唯一的Cluster IP以及唯一的名字,而名字是开发者自己定义的,部署的时候也没必要改变,所有完全可以固定在配置中。接下来的问题就是如何通过Service的名字找到对应的Cluster IP?
最早的时候kubernetes采用了Linux环境变量的方式解决这个问题,即每个Service生成一些对应的LInux环境变量(ENV),并在每个Pod的容器启动时,自动注入这些环境变量。
kubernetes通过Add-On增值包的方式引入了DNS系统,把服务名作为DNS域名,程序就可以直接使用服务名来建立通信连接了,目前kubernetes的大部分应用都已经采用了DNS这些新兴的服务发现机制。
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的所有问题,比如负载均衡问题。
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
Annotation与Label类似,也是以key-value键值对的形式进行定义。不同的是Label具有严格的命名规则,它定义的时kubernetes对象的元数据(Metadata),并且勇于Label Selector,而Annotation则是用户任意定义的“附加”信息,以便于外部工具进行查找,而kubernetes的模块自身会通过Annotation的方式标记资源对象的一些特殊信息。
通常来说,用Annotation来记录的信息如下:
build信息、release信息、Docker镜像信息等,例如时间戳、release id号、PR号、镜像hash值、docker registry地址等日志库、监控库、分析库等资源库的地址信息程序调试工具信息团队的联系信息