[关闭]
@dujun 2015-03-27T15:54:40.000000Z 字数 10983 阅读 3472

10.3.2.2 replication controller

Kubernetes写书


Replication Controller

什么是replication controller

replication controller(副本控制器)保证了一定数量的pod副本在任何时候都是处于正常运行状态的。如果pod副本数量太多了,replication controller就会销毁一些pod,如果pod副本数量太少了,replication controller则会创建并启动更多的pod,直到pod的数量达到预设的副本数。即使你的应用程序只用到一个pod,我们也建议你使用replication controller,因为replication controller会根据pod的健康状况自动替换pod,这样就能最大限度地降低譬如运行pod的节点异常崩溃等意外状况造成的损失(replication controller会在其他工作节点(如果集群中可用的工作节点不止一个的话)上重新创建并启动一个新的pod),提高系统的容灾能力。replication controller由Kubernetes controller manager进行管理。Kubernetes目标以后能够加入更多能够处理其它类型工作的控制器,包括:构建、测试和批处理等控制器,而这些控制器将统一交由controller manager管理。

pod的生命周期文档指出,当且仅当pod的重启策略RestartPolicy = Always时,replication controller才会对该pod进行正常的管理操作(譬如:创建、销毁、重启等),而当pod的重启策略是其他类型(onFailureNever)时,replication controller就会拒绝实例化该pod。可能有读者会认为replication controller管理pod的这个前提条件有些古板和不近人情,因为即使pod的容器重启策略RestartPolicyonFailureNever,在pod发生故障的时候重启pod这个要求也是合情合理的,但是在我们撰写这本书的时候,Kubernetes replication controller的实现规则就是这样的。在进一步讨论replication controller重启pod的规则之前,我们先来了解下pod的状态值和生命周期。

pod的状态值(PodStatus)的数量和定义是系统严格保留和规定的,下表简单地罗列了pod的各状态值及其含义。

状态值 含义
pending pod的创建请求已经被系统接受,但pod内还有一个或多个容器未启动。距离pod创建成功还需要一些时间,这些时间可能包括:下载Docker镜像的网络传输时间和pod的调度时间等。
running pod已经被绑定到工作节点上而且pod内所有容器均已被运行过了,最重要的是至少有一个容器还处于运行状态或正在启动的过程中。
succeeded pod内所有容器均正常退出。
failed pod内所有容器均已退出且至少有一个容器因为发生错误而退出。

表x pod的各状态值及其含义

接下来我们将讨论pod的生命周期。一般情况下,pod被成功创建后不会自动消失直到有人销毁他们,这个“人”可能是用户也有可能是replication controller。唯一的例外是当pod处于succeededfailed状态的时间过长(超时参数由系统设定)时,该pod会被系统自动回收。如果一个工作节点发生故障或从集群中断开,那么Node Controller(同样由controller manager管理)就会对这些工作节点应用例如超时等策略并将集群丢失的那个工作节点上的所有pod的状态标记为failed

最后,举几个例子来说明pod状态与pod重启策略(RestartPolicy)的关系。

通过上面分析可以发现,pod运行异常终止的原因很大程度上与工作节点密切相关(譬如工作节点异常下线等),而replication controller重启pod的实现机制又是在其他工作节点上重新创建运行pod内的所有容器,如果pod的重启策略RestartPolicy != Always,就会与以上过程发生冲突,而replication controller要求被控制的pod的重启策略RestartPolicy必须是Always的原因就在于此。事实上,pod默认的重启策略RestartPolicy就是Always,这意味着被replication controller控制的pod对其内部容器使用的是“积极”的重启策略。所以,用户在自定义pod的重启策略时一定要“三思而后行”,一定要搞清楚replication controller重新创建pod的前提条件。这里还需要特别注意的是,pod的重启策略应用的对象是Docker容器而非pod本身,例如重启策略RestartPolicy = Always意味着当pod容器退出时,就对容器进行重启操作而不是让replication controller重启pod。

一个replication controller进程不会无缘无故自己终止运行,但是我们也不能期望它能够像service进程(关于service,后面章节会有详细介绍)那样能够一直运行。一般情况下,replication controller与service协同使用,一个service可能包含多个由若干个replication controller控制的pod,在service进程的生命周期里,将伴随着多个replication controller的创建和销毁。

replication controller是如何工作的

假设现在你已经拥有了一些多容器、包含标签(labels)的pod并打算利用这些pod构建一个应用。你可能会尝试自己逐一创建、启动这一串的pod,这没问题,但是如果你真这样做了,一大堆的运维问题将接踵而至,例如:你如何实现pod数量的弹性伸缩以及你如何保证所有pod都是同质的?replication controller就是Kubernetes为回答上面的问题而引入的资源对象。与pod对象类似,Kubernetes使用一份JSON格式的资源配置文件来定义一个replication controller对象。replication controller将会不断地监测控制的pod集合的数量并与期望的副本数量进行比较,根据实际情况进行创建和删除pod的操作。一份定义replication controller的资源配置文件示例如下所示,该replication controller实例化了两个运行nginx的pod。

id: nginx-controller
apiVersion: v1beta1
kind: ReplicationController
desiredState:
  replicas: 2
  # replicaSelector用于标识replica controller管理的pod集
  replicaSelector:
    name: nginx
  # podTemplate定义了要创建的pod的模板
  podTemplate:
    desiredState:
      manifest:
        version: v1beta1
        id: nginx
        containers:
          - name: nginx
            image: dockerfile/nginx
            ports:
              - containerPort: 80
    # 重要!下面的labels属性即pod的labels,且必须与上面的replicaSelector相匹配
    #apiserver要求强制执行此限制
    labels:
      name: nginx

replication controller通过使用预定义的pod模板来创建pod而不是直接指定对所有pod副本的当前期望状态。pod与pod模板的关系可以用饼干和饼干切割器来比喻,一旦饼干被切割,就与饼干切割器没有任何联系了。同样,一旦pod创建成功,便与pod模板没有任何关联,而后续的对模板的任何操作都不会对已创建pod有任何直接的影响。类似地,可以直接更新通过replication controller创建的pod而无需再经过replication controller。replication controller创建pod副本的方式与pod创建docker容器的方式刚好相反,pod是通过指定属于该pod的所有容器的当前期望状态来创建docker容器。

由replication controller通过pod模板定义的pod在创建之初的状态和配置是完全一样的(尽管随着时间的推移,不同pod的配置和状态可能会有所不同),由replication controller创建的pod的这种特性非常适合用于构建无状态的服务,即系统的水平扩展可以通过复制实例来实现的那些服务。当然,replication controllers同样也可以用于构建有状态的服务(例如:基于选主算法和工作线程池的应用程序),但是这些应用程序需要额外使用动态任务分配机制,例如:etcd锁模块或者RabbitMQ工作队列等。而对pod的自定义操作,例如计算资源(cpu、内存等)的垂直伸缩,则应该由其他replication controller而不是创建该pod的那个replication controller来执行。

replication controller对pod的数量和健康状况的监控是通过副本选择器(replica selector,label selector的一种)来实现的。前面已经介绍了,Kubernetes中每个资源对象都有一组labels属性,通过label selector与labels的匹配实现了一种简单的过滤/选择资源对象的机制。replication controller的replica selector正是利用label selector机制来识别和选择其管理的pod集的,replication controller会保证每个由它创建的pod都有与它的replica selector相匹配的labels。

replica selector定义了replication controller和它所控制的pod之间一种松耦合的关系,这与pod正好相反,pod与属于它的docker容器之间是一种更强的耦合关系。通过修改pod的labels可以将pod从replication controller的控制集中移除,利用这种技术就能够将pod从服务(service)集群中移除,然后针对这个pod进行debug、数据恢复等操作。而replication controller则会自动重启一个新的pod来替换被移除的那个pod(假设副本的数量不变)。需要注意的是,删除一个replication controller不会影响它所创建的pod,如果想删除一个replication controller所控制的pod,需要将该replication controller的副本数(replicas)字段置为0,这样所有的pod都会被自动删除。

最后,在使用replication controller时,用户需要保证任意一个pod只对应一个replication controller,即不同的replication controller的replica selector指向的pod集合没有交集,因为replication controller自身没有相应的查重机制。

replication controller的职责

replication controller的职责简单归纳起来就两点:

未来,Kubernetes也不准备给replication controller赋予更多的职责。Kubernetes的设计者信奉“小而优”的设计哲学,replication controller只要准确、高效地做好以上两点工作就足够了。replication controller并不负责譬如调度pod,检查pod是否与指定的pod模板匹配等工作,因为这会阻塞replication controller的弹性伸缩和其它自动化操作的进程。

注:
在我们撰写这本书的时候,replication controller只是基于运行的pod数量来对pod进行控制,即维护一个account计数器,新加入一个pod就account++,而一个pod运行结束则account--。为了更好地控制pod,Kubernetes未来计划将处于“准备就绪”状态(新增这么一个pod的状态值)的pod和其他更多的信息也考虑在内,以优化pod的替换策略。另外,为客户端提供必要的系统信息以支持自定义的pod替换和弹性伸缩策略也在Kubernetes的roadmap中。

replication controller的常用模式

正如上面所提到的,不管你是想运行1个还是1000个pod,replication controller都能保证指定数目的pod正常运行,即使发生节点异常崩溃或pod运行终止等状况。

不论是通过手动修改还是通过自动弹性伸缩控制代理来修改副本数(replicas)字段,replication controller都能轻松实现pod的数量的弹性伸缩。

Replication controller被设计成通过逐个替换pod的方式来促进服务集群(service)的迭代更新(rolling update)。迭代更新的步骤如下所示:

1,从服务集群中任取一个旧的replication controller,并启动一个新的replication controller,将其初始副本数设置成1。
2,逐步将新的replication controller的副本数+1,将旧的replication controller副本数-1,直到旧的replication controller的副本数减为0,然后将旧的replication controller删除。这样就完成了一个replication controller对应的所有pod的更新。
3,重复步骤1和2,直到服务集群中所有旧的replication controller都完成更新。

通过上述方式,即使迭代更新过程中发生不可预料的错误,pod集合的更新也都在可控范围内。理想情况下,迭代更新控制器需要将系统可用的应用实例数量考虑在内,并在实际生产环境中保证在任何时刻都有足够数量的可用pod,这将在未来版本的Kubernetes中得到实现。

在上面对应用迭代更新的讨论中,我们发现一个应用在rolling update时,可能存在多个版本的release。事实上,在生产环境中一个已经发布的应用程序存在多个release版本是一个很正常的现象。通过replication的replica selector机制,我们能很方便地实现对一个应用的多版本release进行追踪。假设一个Kubernetes服务对象(service)包含多个pod,这些pod的labels均为tier=frontend, environment=prod,pod的数量为10个。如果你希望拿出其中一个pod用于测试新功能,你可以这样做:
首先,创建一个replication controller,并设置其pod副本数为9,其replia selector设置为tier=frontend, environment=prod, track=stable
然后,再创建一个eplication controller,并并设置其pod副本数为1(就是那个测试pod),其replia selector设置为tier=frontend, environment=prod, track=canary
这样,service就同时覆盖了测试版和稳定版的pod,实现了应用程序多版本release的追踪。

创建replication controller

与创建pod的方法类似,kubectl创建replication controller需要提供一份资源配置文件来描述该replication controller,如下所示。

$ cat redis-controller.json 
{  
"id": "redisController",  
"apiVersion": "v1beta1",  
"kind": "ReplicationController",  
"desiredState": {  
  "replicas": 1,  
  "replicaSelector": {"name": "redis"},
  "podTemplate": {  
    "desiredState": {  
       "manifest": {  
         "version": "v1beta1",  
         "id": "redisController",  
         "containers": [{  
           "name": "redis",  
           "image": "10.10.103.215:5000/redis",  
           "imagePullPolicy": "PullIfNotPresent",  
           "ports": [{  
               "containerPort": 6379,  
               "hostPort": 6380 
           }]  
         }]  
       }  
     },  
     "labels": {"name": "redis"}  
    }},  
"labels": {"name": "redis"}  
}

该资源配置文件的kind字段表明定义的是一个replication controller对象,desiredState:replicas字段表明pod的副本数为1,desiredState:replicaSelector字段定义了一个label selector,表明该replication controller控制所有labels为{"name": "redis"}的pod。desiredState:podTemplate字段定义了一个pod的模板,该pod中有一个名为redis的容器。desiredState:podTemplate:labels字段的重要性已经在上面的replication controller资源文件示例中有说明,该字段是必须的,且必须与replicaSelector字段的值匹配,因为pod的labels属性是replication controller识别和选择pod的唯一依据。出于管理的方便和概念的一致性,replication controller一般也会有一组和它们管理的pod成员相同的labels属性,在这个例子中即:"labels": {"name": "redis"},replication controller资源文件的labels字段是可选项,即使不写也不会有任何影响。

注:请注意replication controller资源文件中两个labels字段的区别:一个是属于replication controller管理的pod的,一个是属于replication controller自身的,且前者是必须的,而后者是可选项。

根据上述资源配置文件使用kubectl create命令创建一个replication controller对象。

$ kubectl create -f redis-controller.json
redisController

使用kubectl get命令查看replication controller的基本信息。

$ kubectl get replicationController
CONTROLLER          CONTAINER(S)        IMAGE(S)                   SELECTOR            REPLICAS
redisController     redis               10.10.103.215:5000/redis   name=redis          1

replication controller的资源文件结构相对比较简单,这一点也能从replication controller的数据结构定义看出来。

type ReplicationController struct {
    TypeMeta     `json:",inline"`
    DesiredState ReplicationControllerState `json:"desiredState,omitempty"`
    CurrentState ReplicationControllerState `json:"currentState,omitempty"`
    Labels       map[string]string          `json:"labels,omitempty"`
}

TypeMetaLabels属性分别代表Kubernetes资源对象共有的元数据和标签,这在前文对pod的解析中有详细介绍,这里不再赘述。DesiredState属性对应于资源文件中desiredState字段的内容,包括三部分:Replicas(期望副本数)、ReplicaSelector(用于选择被控制的pod集合的副本选择器)和PodTemplate(用于创建pod的pod模板),因此资源文件中需要用户输入的也就是以上三部分,而这三个部分中有解析必要的也只有PodTemplatePodTemplate数据结构包含的三个属性,分别是:DesiredState(pod的期望状态,该属性已经在上文pod配置文件的解析中有详细介绍)、NodeSelector(用于为pod选择minion的label selector,该属性也已经在上文中被解析过)和Labels(pod的labels,必须与ReplicaSelector属性匹配)。综上所述,只要掌握了pod资源文件的语法,自定义replication controller的资源文件也就变得非常简单了。

replication controller自动重启pod

首先使用kubectl get pods命令查看系统中的pod信息。

$ kubectl get pods
POD                                    CONTAINER(S)        IMAGE(S)                   HOST                LABELS              STATUS
50b01271-9738-11e4-98cb-005056b43972   redis               10.10.103.215:5000/redis   127.0.0.1/          name=redis          Running

注意到刚刚创建的replication controller的label selectorname=redis,因此该replication controller控制的是labels为name=redis的pod,即id为50b01271-9738-11e4-98cb-005056b43972的那个pod。我们先故意删除该pod,测试replication controller是否会自动创建并启动一个新的pod替换它。使用kubectl delete命令删除由replication controller控制的那个pod。

$ kubectl delete pod 50b01271-9738-11e4-98cb-005056b43972
I0112 11:09:56.310955   27656 restclient.go:141] Waiting for completion of operation 31309
50b01271-9738-11e4-98cb-005056b43972

当我们再次查看系统的pod信息时,我们发现原先的那个pod已经不见了,取而代之的是一个新的pod,如下所示。

$ kubectl get pods
POD                                    CONTAINER(S)        IMAGE(S)                   HOST                LABELS              STATUS
7d9ba70f-9a08-11e4-98cb-005056b43972   redis               10.10.103.215:5000/redis   127.0.0.1/          name=redis          Running

以上输出表明,我们先成功地删除了pod 50b01271-9738-11e4-98cb-005056b43972,当我们再次查看系统中pod的信息时,发现已经有一个新的pod 7d9ba70f-9a08-11e4-98cb-005056b43972,其label为name=redis,自动替换了原先被删除的那个。

最后,删除一个replication controller并不影响其所控制的pod,这也进一步说明了replication controller与pod的那种"饼干与饼干切割器"的关系。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注