replication controller设计解读
Kubernetes中第二个重要的概念就是replication controller,它决定了一个pod有多少同时运行的副本,并保证这些副本的期望状态与当前状态一致。所以,如果创建了一个pod,并且在希望该pod是持续运行的应用时[即仅适用于重启策略(RestartPolicy)为Always的pod],一般都推荐同时给pod创建一个replication controller,让这个controller一直守护pod,直到pod被删除。replication controller在设计上依然体现出了“旁路控制”的思想,在Kubernetes中并没有像Cloud Foundry那样设置一个专门的健康检查组件,而是为每个pod“外挂”了一个控制器进程,从而避免了健康检查组件成为性能瓶颈;即使这个控制器进程失效,容器依然可以正常运行,pod和容器无需知道这个控制器,也不会把这个控制器作为依赖(容器运行依赖于健康检查组件,并随之形成的三角依赖关系曾是Cloud Foundry v2中的一个重大问题,也是Cloud Foundry v3重点改进的内容,有兴趣的读者可以自行了解)。
可以看到,在上述过程中,pod的状态可以说是replication controller进行上述控制的唯一依据。所以,有必要先了解一下Kubernetes中的pod的状态和转移过程。
- pod的状态转换在Kubernetes中,pod的状态值(podStatus)的数量和定义是系统严格保留和规定的,如表所示。
而上述pod的状态转换与pod的重启策略则是紧密相关的,它的转移过程如下所示。pod处于Running状态,总共包含1个容器,容器退出,这时发生的操作如下。
❏ 若容器正常退出,向系统输出信息为completion(完成)的事件对象,否则输出failure事件;
❏ 若RestartPolicy是Always,重启退出的容器,pod仍处于Running状态;
❏ 若RestartPolicy是OnFailure,若容器正常退出,pod变成Succeeded状态,若异常退出则进行重启并处于Running状态;
❏ 若RestartPolicy是Never,容器正常退出pod变成Succeeded状态,否则变为Failed状态。
pod处于Running状态,总共包含2个容器,其中1个容器异常退出,这时发生的操作如下。
❏ 向系统输出信息为failure(错误)的事件对象(event);
❏ 若RestartPolicy是Always,重启异常退出的容器,pod仍处于Running状态;
❏ 若RestartPolicy是OnFailure,重启异常退出的容器,pod仍处于Running状态;
❏ 若RestartPolicy是Never, pod变成Failed状态。
pod处于Running状态,总共包含2个容器,容器1已经异常退出,并且系统已经按照上述第二种情况完成处理,当容器2也退出时(不论是否异常退出),这时发生的操作如下。
❏ 向系统输出信息为failure(错误)的事件对象(event);
❏ 若RestartPolicy是Always,重启退出的容器,pod仍处于Running状态;
❏ 若RestartPolicy是OnFailure,重启退出的容器,pod仍处于Running状态;
❏ 若RestartPolicy是Never, pod变成Failed状态。
pod处于Running状态,容器内存溢出,这时发生的操作如下。
❏ pod内容器异常退出;
❏ 向系统输出信息为failure(错误)的事件对象(event);
❏ 若RestartPolicy是Always,重启退出的容器,pod仍处于Running状态;
❏ 若RestartPolicy是OnFailure,重启退出的容器,pod仍处于Running状态;
❏ 若RestartPolicy是Never, pod变成Failed状态。
pod处于Running状态,此时pod所在的主机磁盘发生故障,这时发生的操作如下。
❏ pod内所有容器都被杀死;
❏ 向系统输出信息为failure(错误)的事件对象(event);
❏ pod变成Failed状态;
❏ 如果pod被一个replication controller控制,则将在其他工作节点上重新创建一个新的pod。
pod处于Running状态,它的工作节点与集群断开。
❏ node controller等待一个超时时间;
❏ node controller标记该工作节点上所有pod均处于failed状态;
❏ 被replication controller控制的pod将在其他工作节点上重新被创建运行。
但是,对于当前replication controller实现方法来说,它能够识别的pod重启策略只有Always一种,这是因为当前replication controller的设计目标是保证本身状态为Running且容器一切正常的pod的数量永远跟预设的数目一致。任何时候一个pod中的容器退出或者整个pod变成Failed, replication controller都会固执地重启这个容器或者pod,并使之变成Running状态。即使单独将一个pod的重启策略设置为Never或者OnFailure,后续这个pod关联起来的replication controller也都会忽略这个设置而使用Always策略。如果用户希望在Kubernetes中运行“一次性”任务,比如Map-Reduce的Job或者类似的batch task,可以定义一个Job对象,这将在后续章节中进行介绍。
- replication controller的描述文件
前面已经介绍过,replication controller是Kubernetes为解决“如何构造完全同质的pod副本”问题而引入的资源对象。与pod对象类似,Kubernetes使用一份JSON格式的资源配置文件来定义一个replication controller对象。replication controller的资源配置文件主要由3个方面组成:一个用于创建pod的pod模板(pod template)、一个期望副本数和一个用于选择被控制的pod集合的label selector。replication controller将会不断地监测控制的pod集合的数量并与期望的副本数量进行比较,根据实际情况进行创建和删除pod的操作。一份定义replication controller的资源配置文件示例如下所示,该replication controller实例化了两个运行nginx的pod。
apiVersion: v1
kind: ReplicationController
metadata:
name: nginx-controller
spec:
# 两个副本
replicas: 2
# 这个replica controller管理包含如下标签的pod
selector:
name: nginx
template:
metadata:
# 重要!下面的labels属性即被创建的pod的labels,必须与上面的replicaSelector一样
labels:
name: nginx
spec:
containers:
- name: nginx
image: k8stest/nginx:test
ports:
- containerPort: 80
从上述描述文件中不难看到,replication controller通过使用预定义的pod模板来创建pod,一旦pod创建成功,对模板的任何更改都不会对已经在运行的pod有任何直接的影响。我们推荐replication controller只负责选择指定的pod然后保证这个pod的数量和状态正确,而调整这些已经在运行的pod的CPU、MEM参数等操作应该直接更新pod本身而不是更新replication controller。这依然是旁路控制和解耦的思想。此外,前面已经介绍过,replication controller只能与重启策略为Always的pod进行协作,如果pod模板指定了其他重启策略,那么在创建的时候会提示不支持该策略,比如spec.template.spec.restartPolicy: Unsupported value:“Never”: supported values:Always)。
replication controller对pod的数量和健康状况的监控则是通过副本选择器(replica selector, label selector的一种)来实现的。replica selector定义了replication controller和它所控制的pod之间一种松耦合的关系。这与pod刚好相反,pod与属于它的Docker容器之间体现一种更强的耦合关系。这是一个非常有用的特性,因为可以通过修改pod的labels将一个pod从replication controller的控制集中移除。比如可以将出现了故障的pod从工作集群中移除,然后针对这个pod进行debug、数据恢复等操作。与此同时,replication controller则会自动重启一个新的pod来替换被移除的那个pod。需要注意的是,删除一个replication controller不会影响它所创建的pod,如果想删除一个replication controller所控制的pod,需要将该replication controller的副本数(replicas)字段置为0,这样所有的pod都会被自动删除。
最后,只要满足replica selector的pod都会受到该replication controller的影响,并不仅限于在创建时嵌套在replication controller manifest内的pod。因此,在使用时,用户需要保证任意一个pod只对应一个replication controller。这是因为replication controller没有相应的查重机制,如果一个pod对应了多个replication controller,那么这些replication controller之间会产生冲突。未来,Kubernetes不准备给replication controller赋予更多的职责。Kubernetes的设计者信奉“小而优”的设计哲学,replication controller只要准确、高效地做好以下两点工作就足够了。值得注意的是,Kubernetes现在还引入了一个ReplicaSet的概念,它可以认为是replication controller的扩展,将在后续小节中进行详细介绍。
未来,Kubernetes不准备给replication controller赋予更多的职责。Kubernetes的设计者信奉“小而优”的设计哲学,replication controller只要准确、高效地做好以下两点工作就足够了。值得注意的是,Kubernetes现在还引入了一个ReplicaSet的概念,它可以认为是replication controller的扩展,将在后续小节中进行详细介绍。
❏ 维护它所控制的pod的数量。如果需要调整pod的数量,则通过修改它的副本数(replicas)字段来实现。
❏ 在pod模板中定义replication controller所控制的pod的labels,使用replica selector匹配pod集,实现对pod的管理。
replication controller并不负责譬如调度pod、检查pod是否与指定的pod模板匹配等工作,因为这会阻塞replication controller的弹性伸缩和其他自动化操作的进程。
- replication controller的典型场景
❏ 重调度。前文提到,不管使用者想运行1个还是1000个pod副本,replication controller都能保证指定数目的pod正常运行。一旦当前的宿主机节点异常崩溃或pod运行终止,Kubernetes就会进行相应pod重调度。
❏ 弹性伸缩。不论是通过手动还是自动弹性伸缩控制代理来修改副本数(replicas)字段,replication controller都能轻松实现pod数量的弹性伸缩。
❏ 滚动更新(灰度发布)。replication controller被设计成通过逐个替换pod的方式来进行副本增删操作,这使得容器的滚动更新会非常简单。
■ 假设服务集群中已经有一个旧的replication controller负责管理旧版本容器的数量,现在需要启动一个新的replication controller,将其初始副本数设置成1,这个replication controller负责管理新版本容器的数量。
■ 逐步将新的replication controller的副本数+1,将旧的replication controller副本数-1,直到旧的replication controller的副本数减为0,然后将旧的replication controller删除。这样就完成了一个replication controller对应的所有pod的更新。
❏ 应用多版本release追踪。在生产环境中一个已经发布的应用程序同时在线多个release版本是一个很普遍的现象。通过replica selector机制,我们能很方便地实现对一个应用的多版本release进行管理。假设需要有10个labels均为tier=frontend, environment=prod的pod,现在希望占用其中一个pod用于测试新功能,可以进行如下操作。
■ 首先,创建一个replication controller,并设置其pod副本数为9,其replia selector为tier=frontend, environment=prod, track=stable。
■ 然后,再创建一个replication controller,并设置其pod副本数为1(作为测试pod),其replia selector为tier=frontend,environment=prod, track=canary。
- replication controller的使用示例
前文已经介绍过replication controller的各项定义和设计思路,下面使用以下描述文件来创建一个replication controller。
$ cat redis-controller.json
{
"apiVersion": "v1",
"kind": "ReplicationController",
"metadata": {
"name": "redis-controller",
"labels": {
"name": "redis"
}
},
"spec": {
"replicas": 1,
"selector": {
"name": "redis"
},
"template": {
"metadata": {
"labels": {
"name": "redis"
}
},
"spec": {
"containers": [{
"name": "redis",
"image": "k8stest/redis:test",
"imagePullPolicy": "IfNotPresent",
"ports": [{
"containerPort": 6379,
"hostPort": 6380
}]
}]
}
}
}
}
该资源配置文件的kind字段表明定义的是一个replication controller对象,pod的副本数为1, .spec.selector字段定义了一个label selector,表明该replication controller控制所有labels为{“name”: “redis”}的pod。.spec.template对应嵌套的pod模板,表明该pod中有一个名为redis的容器。当然,.spec. template.metadata.labels字段也是必需的,且必须与replicaSelector字段的值匹配。需要注意的是,replication controller一般也会有一组自己的labels属性,在该例子中即最下面一个"labels":{“name”: “redis”},这是一个可选项。
注意 replication controller资源文件中两个labels字段的区别:一个是属于replication controller管理的pod的,一个是属于replication controller自身的,前者是必填项,后者是可选项。
根据上述资源配置文件使用kubectl create命令创建一个replication controller对象。