引言
Kubernetes(通常称为K8s,K8s是将8个字母“ubernete”替换为“8”的缩写)是一个容器集群管理系统,可以实现容器集群的自动化部署、自动扩缩容、维护等功能。
Kubernetes的核心技术概念和API对象
API对象是K8s集群中的管理操作单元。K8s集群系统每支持一项新功能,引入一项新技术,一定会新引入对应的API对象,支持对该功能的管理操作。例如管理Pod数量的的API对象是副本集Replica Set(简称RS)。
每个API对象都有3大类属性:元数据metadata、规范spec和状态status。元数据是用来标识API对象的,每个对象都至少有3个元数据:namespace,name和uid;除此以外还有各种各样的标签labels用来标识和匹配不同的对象,例如用户可以用标签env来标识区分不同的服务部署环境,分别用env=dev、env=testing、env=production来标识开发、测试、生产的不同服务。规范描述了用户期望K8s集群中的分布式系统达到的理想状态(Desired State),例如用户可以通过复制控制器Replication Controller(简称RC)设置期望的Pod副本数为3;status描述了系统实际当前达到的状态(Status),例如系统当前实际的Pod副本数为2;那么RC当前的程序逻辑就是自动启动新的Pod,争取达到副本数为3。
K8s中所有的配置都是通过API对象的spec去设置的,也就是用户通过配置系统的理想状态来改变系统,这是k8s重要设计理念之一,即所有的操作都是声明式(Declarative)的而不是命令式(Imperative)的。声明式操作在分布式系统中的好处是稳定,不怕丢操作或运行多次,例如设置副本数为3的操作运行多次也还是一个结果,而给副本数加1的操作就不是声明式的,运行多次结果就错了。
Pod
K8s有很多技术概念,同时对应很多API对象,最重要的也是最基础的是微服务Pod。
Pod是在K8s集群中运行部署应用或服务的最小单元,一个Pod上可以运行一个或多个容器,同一个Pod上的所有容器共享Pod的网络地址(即Pod上的不同容器使用localhost就能互相通信,且不同的容器使用的端口不能相同,否则会端口冲突)和文件系统。 这样这个Pod上的容器可以通过进程间通信和文件共享这种简单高效的方式组合完成服务。
Pod这种对多容器的支持是K8s最基础的设计理念。比如你运行一个操作系统发行版的软件仓库,一个Nginx容器用来发布软件,另一个容器专门用来从源仓库做同步,这两个容器的镜像不太可能是一个团队开发的,但是他们一块儿工作才能提供一个微服务;这种情况下,不同的团队各自开发构建自己的容器镜像,在部署的时候组合成一个微服务对外提供服务。
Pod是K8s集群中所有业务类型的基础,可以看作运行在K8s集群中的小机器人,不同类型的业务就需要不同类型的小机器人去执行。目前K8s中的业务主要可以分为长期伺服型(long-running)、批处理型(batch)、节点后台支撑型(node-daemon)和有状态应用型(stateful application);分别对应的小机器人控制器为Deployment、Job、DaemonSet和PetSet,这个在后面会作介绍。
Pod可以通过Kubernetes API手动管理,也可以委托给控制器来管理。
控制器
复制控制器(Replication Controller,简称RC)
RC是K8s集群中最早的保证Pod高可用的API对象。通过监控运行中的Pod来保证集群中运行指定数目的Pod副本。RC是K8s较早期的技术概念,只适用于长期伺服型的业务类型,比如控制小机器人提供高可用的Web服务。
副本集(Replica Set,简称RS)
RS是新一代RC,提供同样的高可用能力,区别主要在于RS后来居上,能支持更多种类的匹配模式。副本集对象一般不单独使用,而是作为Deployment的理想状态(Desired State)参数使用。
部署(Deployment)
Deployment表示用户对K8s集群的一次更新操作。Deployment是一个比RS应用模式更广的API对象,可以是创建一个新的服务,更新一个新的服务,也可以是滚动升级(rolling update)一个服务。滚动升级一个服务,实际是创建一个新的RS,然后逐渐将新RS中副本数增加到理想状态,将旧RS中的副本数减小到0的复合操作。这个滚动升级操作过程可以参考下图
初始状态:Deployment控制RS,RS创建了两个v1版本的Pod,现在要将这个Pod服务升级到v2版本。
- Deployment先启动另一个RS-1,RS-1先启动一个v2版本的Pod,启动完成之后,Deployment控制RS停掉一个v1版本的Pod。
- RS-1再启动一个v2版本的Pod,启动完成后,Deployment控制RS停掉剩下的另一个v1版本的Pod。
这样,滚动升级就完成了。虽然Replica Set可以独立使用,但是它不能完成滚动升级(rolling update)这种操作,所以使用Deployment来管理RS就可以完成一些复合任务。
以K8s的发展方向,未来对所有长期伺服型的的业务的管理,都会通过Deployment来管理。
任务(Job)
Job是K8s用来控制批处理型任务的API对象。批处理(batch)业务与长期伺服(long-running)业务的主要区别是批处理业务的运行有头有尾,而长期伺服业务在用户不停止的情况下永远运行。Job管理的Pod根据用户的设置把任务成功完成就自动退出了。成功完成的标志根据不同的spec.completions
策略而不同:
- 单Pod型任务有一个Pod成功就标志完成
- 定数成功型任务保证有N个任务全部成功
- 工作队列型任务根据应用确认的全局成功而标志成功。
PetSet
有状态的服务和无状态的服务
无状态(stateless)的服务
RC和RS主要是控制提供无状态服务的,其所控制的Pod的名字是随机设置的,一个Pod出故障了就被丢弃掉,在另一个地方重启一个新的Pod,就可提供服务了。这种Pod一般不挂载存储或者挂载共享存储,保存的是所有Pod共享的状态,所有的Pod是没有分别的,也即是无状态的。
有状态(stateful)的服务
有状态的服务Pod主要由PetSet控制,比如MySQL、Zookeeper这些都是有状态的服务。每个Pod挂载自己独立的存储,如果一个Pod出现故障,从其他节点启动一个同样名字的Pod,要挂载上原来Pod的存储继续以它的状态提供服务。
后台支撑服务集(DaemonSet)
DaemonSet是用来确保全部(或者一些)Node上都要运行一个Pod副本(比如日志收集、监控等)。当有Node加入集群时,就会在这个Node上新增一个Pod。当Node从集群中移除时,这个Pod也会被回收。
删除DaemonSet将会删除它创建的所有Pod。
标签和标签选择器
标签(Label)
标签是一组绑定到K8s资源对象上(如Node,Pod,Service,RC等)的key/value对,同一个对象的labels属性的key必须唯一。
一个标签可以对应多个资源,一个资源也可以有多个标签,它们是多对多的关系。
K8s可以通过标签来管理各个资源对象。因为一个资源拥有多个标签,这样就可以实现不同维度的管理。
命令行使用示例:1
2kubectl label pods pod-demo release=canary #给pod-demo增加标签
kubectl label pods pod-demo release=stable --overwrite #修改标签
配置文件方式的使用示例:1
2
3
4
5
6
7
8
9
10
11
12
13apiVersion: v1
kind: Pod
metadata:
name: label-demo
labels:
environment: production #设置environment=production的标签
app: nginx #设置app=nginx的标签
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
标签选择器(Label Selector)
给资源打上标签后,通过标签选择器(label selector),可以查询出一组有共同特征或属性的资源对象(类似于SQL中按条件查询)。
Label selector的查询条件
标签选择器的查询条件有两种:
基于等值关系
操作符:=
,==
,!=
,可以使用逗号分隔多个表达式。
命令行的使用示例:1
2
3kubectl get pods -l run=myapp
kubectl get pods -l environment=production,tier=frontend
kubectl get pods -l run!=client基于集合关系
操作符:in,notin,exists
命令行的使用示例:1
2kubectl get pods -l "run in (client,myapp,alpha)" #三个值有一个匹配上都可以
kubectl get pods -l "run notin (client,myapp,alpha)"
Service和RC可以通过标签选择器来指定其要管理的资源,比如pod。Service和RC这两种对象的label选择器在 json 或 yaml 文件中使用map定义,并且只支持相等要求。1
2
3
4# json格式
"selector": {
"component" : "redis",
}
或1
2
3# yaml格式
selector:
component: redis
新出现的管理对象如Deploment、ReplicaSet、DaemonSet和Job则可以在Label Selector中使用基于集合的筛选条件定义,例如:1
2
3
4
5
6selector:
matchLabels:
app: myweb
matchExpressions:
- {key: tier, operator: In, values: [frontend]}
- {key: environment, operator: NorIn, values: [dev]}
matchLabels用于定义一组Label,与直接写在Selector中作用相同:matchExpression用于定义一组基于集合的筛选条件,可用的条件运算符包括:In、NotIn、Exists和DoesNotExist。
如果同时设置了matchLabels和matchExpression,则两组条件为“AND”关系,即所有条件需要满足才能完成Selector的筛选。
其它概念
存储卷(Volume)
K8s集群中的存储卷跟Docker的存储卷有些类似,只不过Docker的存储卷作用范围为一个容器,而K8s的存储卷的生命周期和作用范围是一个Pod。其中包含两层含义:
- 就生命周期而言,Volume的生命周期比Pod中运行的任何容器要持久,在容器重新启动时能可以保留数据,当然,当Pod被删除不存在时,Volume也将消失。
- 每个Pod中声明的存储卷由Pod中的所有容器共享。
内部实现中,一个Volume只是一个目录,目录中可能有一些数据,pod上的容器可以访问这些数据。至于这个目录是如何产生的、支持它的介质、其中的数据内容是什么,这些都由使用的特定Volume类型来决定。
要使用Volume,pod需要指定Volume的类型和内容(spec.volumes
字段),和映射到容器的位置(spec.containers.volumeMounts
字段)。
服务(Service)
RC、RS和Deployment只是保证了支撑服务的微服务Pod的数量,但是没有解决如何访问这些服务的问题。一个Pod只是一个运行服务的实例,随时可能在一个节点上停止,在另一个节点以一个新的IP启动一个新的Pod,因此不能以确定的IP和端口号提供服务。要稳定地提供服务需要服务发现和负载均衡能力。服务发现完成的工作,是针对客户端访问的服务,找到对应的的后端服务实例。在K8s集群中,客户端需要访问的服务就是Service对象。每个Service会对应一个集群内部有效的虚拟IP,集群内部通过虚拟IP访问一个服务。
在K8s集群中微服务的负载均衡是由Kube-proxy实现的。Kube-proxy是K8s集群内部的负载均衡器。它是一个分布式代理服务器,在K8s的每个节点上都有一个;这一设计体现了它的伸缩性优势,需要访问服务的节点越多,提供负载均衡能力的Kube-proxy就越多,高可用节点也随之增多。与之相比,我们平时在服务器端做个反向代理做负载均衡,还要进一步解决反向代理的负载均衡和高可用问题。
节点(Node)
Node是Kubernetes中的工作节点,最开始被称为Minion,后来改名为Node。K8s集群中的Node也就等同于主从集群模式中的Slave节点,是所有Pod运行所在的工作主机,可以是物理机也可以是虚拟机。Node节点上的服务包括Docker、kubelet和kube-proxy。