图片 2

4入门教程,滚动升级

1.前言

Kubernetes Rolling Upgrade

一、前言

在kubernetes中,Pod是最基础的调度单位,多个pod
可以组成一个集合,这个集合向外提供服务。这时候,我们需要以下两种情形需要关注:

背景

Kubernetes
是一个很好的容器应用集群管理工具,尤其是采用ReplicaSet这种自动维护应用生命周期事件的对象后,将容器应用管理的技巧发挥得淋漓尽致。在容器应用管理的诸多特性中,有一个特性是最能体现Kubernetes强大的集群应用管理能力的,那就是滚动升级。

滚动升级的精髓在于升级过程中依然能够保持服务的连续性,使外界对于升级的过程是无感知的。整个过程中会有三个状态,全部旧实例,新旧实例皆有,全部新实例。旧实例个数逐渐减少,新实例个数逐渐增加,最终达到旧实例个数为0,新实例个数达到理想的目标值。

  Kubernetes
是Google开源的容器集群管理系统,基于Docker构建一个容器的调度服务,提供资源调度、均衡容灾、服务注册、动态扩缩容等功能套件,目前CentOS
yum源上最新版本为1.4。本文介绍如何基于CentOS
7.3构建Kubernetes平台,在正式介绍之前,大家有必要先理解Kubernetes几个核心概念及其承担的功能。以下为Kubernetes的架构设计图:

1)集合中的Pod可能会由于某种原因Fail,这时候需要某种机制能够创建新的Pod以确保有足够数量的Pod在运行。

kubernetes 滚动升级

Kubernetes
中采用ReplicaSet(简称RS)来管理Pod实例。如果当前集群中的Pod实例数少于目标值,RS
会拉起新的Pod,反之,则根据策略删除多余的Pod。Deployment正是利用了这样的特性,通过控制两个RS里面的Pod,从而实现升级。
滚动升级是一种平滑过渡式的升级,在升级过程中,服务仍然可用。这是kubernetes作为应用服务化管理的关键一步。服务无处不在,并且按需使用。这是云计算的初衷,对于PaaS平台来说,应用抽象成服务,遍布整个集群,为应用提供随时随地可用的服务是PaaS的终极使命。
1.ReplicaSet
关于RS的概念大家都很清楚了,我们来看看在k8s源码中的RS。

type ReplicaSetController struct {
    kubeClient clientset.Interface
    podControl controller.PodControlInterface

    // internalPodInformer is used to hold a personal informer.  If we're using
    // a normal shared informer, then the informer will be started for us.  If
    // we have a personal informer, we must start it ourselves.   If you start
    // the controller using NewReplicationManager(passing SharedInformer), this
    // will be null
    internalPodInformer framework.SharedIndexInformer

    // A ReplicaSet is temporarily suspended after creating/deleting these many replicas.
    // It resumes normal action after observing the watch events for them.
    burstReplicas int
    // To allow injection of syncReplicaSet for testing.
    syncHandler func(rsKey string) error

    // A TTLCache of pod creates/deletes each rc expects to see.
    expectations *controller.UIDTrackingControllerExpectations

    // A store of ReplicaSets, populated by the rsController
    rsStore cache.StoreToReplicaSetLister
    // Watches changes to all ReplicaSets
    rsController *framework.Controller
    // A store of pods, populated by the podController
    podStore cache.StoreToPodLister
    // Watches changes to all pods
    podController framework.ControllerInterface
    // podStoreSynced returns true if the pod store has been synced at least once.
    // Added as a member to the struct to allow injection for testing.
    podStoreSynced func() bool

    lookupCache *controller.MatchingCache

    // Controllers that need to be synced
    queue *workqueue.Type

    // garbageCollectorEnabled denotes if the garbage collector is enabled. RC
    // manager behaves differently if GC is enabled.
    garbageCollectorEnabled bool
}

这个结构体位于pkg/controllers/replicaset,这里我们可以看出,RS最主要的几个对象,一个是针对Pod的操作对象-podControl.看到这个名字就知道,这个对象是控制RS下面的Pod的生命周期的,我们看看这个PodControl所包含的方法。

// PodControlInterface is an interface that knows how to add or delete pods
// created as an interface to allow testing.
type PodControlInterface interface {
    // CreatePods creates new pods according to the spec.
    CreatePods(namespace string, template *api.PodTemplateSpec, object runtime.Object) error
    // CreatePodsOnNode creates a new pod accorting to the spec on the specified node.
    CreatePodsOnNode(nodeName, namespace string, template *api.PodTemplateSpec, object runtime.Object) error
    // CreatePodsWithControllerRef creates new pods according to the spec, and sets object as the pod's controller.
    CreatePodsWithControllerRef(namespace string, template *api.PodTemplateSpec, object runtime.Object, controllerRef *api.OwnerReference) error
    // DeletePod deletes the pod identified by podID.
    DeletePod(namespace string, podID string, object runtime.Object) error
    // PatchPod patches the pod.
    PatchPod(namespace, name string, data []byte) error
}

这里我们可以看到,RS可以完全控制Pod.这里有两个watch,rsController和podController,他们分别负责watch
ETCD中RS和Pod的变化。这里一个重要的对象不得不提,那就是syncHandler,这个是所有Controller都有的对象。每一个控制器通过Watch来监视ETCD中的变化,使用sync的方式来同步这些对象的状态,注意这个Handler只是一个委托,实际真正的Handler在创建控制器的时候指定。这种模式不仅仅适用于RS,其他控制器亦如此。
下面的逻辑更加清晰地说明了watch的逻辑。

rsc.rsStore.Store, rsc.rsController = framework.NewInformer(
        &cache.ListWatch{
            ListFunc: func(options api.ListOptions) (runtime.Object, error) {
                return rsc.kubeClient.Extensions().ReplicaSets(api.NamespaceAll).List(options)
            },
            WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
                return rsc.kubeClient.Extensions().ReplicaSets(api.NamespaceAll).Watch(options)
            },
        },
        &extensions.ReplicaSet{},
        // TODO: Can we have much longer period here?
        FullControllerResyncPeriod,
        framework.ResourceEventHandlerFuncs{
            AddFunc:    rsc.enqueueReplicaSet,
            UpdateFunc: rsc.updateRS,
            // This will enter the sync loop and no-op, because the replica set has been deleted from the store.
            // Note that deleting a replica set immediately after scaling it to 0 will not work. The recommended
            // way of achieving this is by performing a `stop` operation on the replica set.
            DeleteFunc: rsc.enqueueReplicaSet,
        },
    )

每次Watch到ETCD中的对象的变化,采取相应的措施,具体来说就是放入队列,更新或者取出队列。对于Pod来说,也有相应的处理。

podInformer.AddEventHandler(framework.ResourceEventHandlerFuncs{
        AddFunc: rsc.addPod,
        // This invokes the ReplicaSet for every pod change, eg: host assignment. Though this might seem like
        // overkill the most frequent pod update is status, and the associated ReplicaSet will only list from
        // local storage, so it should be ok.
        UpdateFunc: rsc.updatePod,
        DeleteFunc: rsc.deletePod,
    })

RS基本的内容就这些,在RS的上层是Deployment,这个对象也是一个控制器。

// DeploymentController is responsible for synchronizing Deployment objects stored
// in the system with actual running replica sets and pods.
type DeploymentController struct {
    client        clientset.Interface
    eventRecorder record.EventRecorder

    // To allow injection of syncDeployment for testing.
    syncHandler func(dKey string) error

    // A store of deployments, populated by the dController
    dStore cache.StoreToDeploymentLister
    // Watches changes to all deployments
    dController *framework.Controller
    // A store of ReplicaSets, populated by the rsController
    rsStore cache.StoreToReplicaSetLister
    // Watches changes to all ReplicaSets
    rsController *framework.Controller
    // A store of pods, populated by the podController
    podStore cache.StoreToPodLister
    // Watches changes to all pods
    podController *framework.Controller

    // dStoreSynced returns true if the Deployment store has been synced at least once.
    // Added as a member to the struct to allow injection for testing.
    dStoreSynced func() bool
    // rsStoreSynced returns true if the ReplicaSet store has been synced at least once.
    // Added as a member to the struct to allow injection for testing.
    rsStoreSynced func() bool
    // podStoreSynced returns true if the pod store has been synced at least once.
    // Added as a member to the struct to allow injection for testing.
    podStoreSynced func() bool

    // Deployments that need to be synced
    queue workqueue.RateLimitingInterface
}

对于DeploymentController来说,需要监听Deployment,RS和Pod。从Controller的创建过程中可以看出来。

dc.dStore.Store, dc.dController = framework.NewInformer(
        &cache.ListWatch{
            ListFunc: func(options api.ListOptions) (runtime.Object, error) {
                return dc.client.Extensions().Deployments(api.NamespaceAll).List(options)
            },
            WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
                return dc.client.Extensions().Deployments(api.NamespaceAll).Watch(options)
            },
        },
        &extensions.Deployment{},
        FullDeploymentResyncPeriod,
        framework.ResourceEventHandlerFuncs{
            AddFunc:    dc.addDeploymentNotification,
            UpdateFunc: dc.updateDeploymentNotification,
            // This will enter the sync loop and no-op, because the deployment has been deleted from the store.
            DeleteFunc: dc.deleteDeploymentNotification,
        },
    )

    dc.rsStore.Store, dc.rsController = framework.NewInformer(
        &cache.ListWatch{
            ListFunc: func(options api.ListOptions) (runtime.Object, error) {
                return dc.client.Extensions().ReplicaSets(api.NamespaceAll).List(options)
            },
            WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
                return dc.client.Extensions().ReplicaSets(api.NamespaceAll).Watch(options)
            },
        },
        &extensions.ReplicaSet{},
        resyncPeriod(),
        framework.ResourceEventHandlerFuncs{
            AddFunc:    dc.addReplicaSet,
            UpdateFunc: dc.updateReplicaSet,
            DeleteFunc: dc.deleteReplicaSet,
        },
    )

    dc.podStore.Indexer, dc.podController = framework.NewIndexerInformer(
        &cache.ListWatch{
            ListFunc: func(options api.ListOptions) (runtime.Object, error) {
                return dc.client.Core().Pods(api.NamespaceAll).List(options)
            },
            WatchFunc: func(options api.ListOptions) (watch.Interface, error) {
                return dc.client.Core().Pods(api.NamespaceAll).Watch(options)
            },
        },
        &api.Pod{},
        resyncPeriod(),
        framework.ResourceEventHandlerFuncs{
            AddFunc:    dc.addPod,
            UpdateFunc: dc.updatePod,
            DeleteFunc: dc.deletePod,
        },
        cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
    )

    dc.syncHandler = dc.syncDeployment
    dc.dStoreSynced = dc.dController.HasSynced
    dc.rsStoreSynced = dc.rsController.HasSynced
    dc.podStoreSynced = dc.podController.HasSynced

这里最核心的就是syncDeployment,因为这里面有rollingUpdate和rollback的实现。在这里如果watch到某个Deployment对象的RollbackTo.Revision部位nil,则执行rollingbach。这个Revision是版本号,注意虽然是回滚,但k8s内部记录的版本号永远是增长的。
有人会好奇,rollback是怎么做到的,其实原理很简单,k8s记录了各个版本的PodTemplate,把旧的PodTemplate覆盖新的Template即可。
对于K8S来说,升级有两种方式,一种是重新构建,一种是滚动升级。

switch d.Spec.Strategy.Type {
    case extensions.RecreateDeploymentStrategyType:
        return dc.rolloutRecreate(d)
    case extensions.RollingUpdateDeploymentStrategyType:
        return dc.rolloutRolling(d)
}

这个rolloutRolling里面包含了所有的秘密,这里我们可以看到。

func (dc *DeploymentController) rolloutRolling(deployment *extensions.Deployment) error {
    newRS, oldRSs, err := dc.getAllReplicaSetsAndSyncRevision(deployment, true)
    if err != nil {
        return err
    }
    allRSs := append(oldRSs, newRS)

    // Scale up, if we can.
    scaledUp, err := dc.reconcileNewReplicaSet(allRSs, newRS, deployment)
    if err != nil {
        return err
    }
    if scaledUp {
        // Update DeploymentStatus
        return dc.updateDeploymentStatus(allRSs, newRS, deployment)
    }

    // Scale down, if we can.
    scaledDown, err := dc.reconcileOldReplicaSets(allRSs, controller.FilterActiveReplicaSets(oldRSs), newRS, deployment)
    if err != nil {
        return err
    }
    if scaledDown {
        // Update DeploymentStatus
        return dc.updateDeploymentStatus(allRSs, newRS, deployment)
    }

    dc.cleanupDeployment(oldRSs, deployment)

    // Sync deployment status
    return dc.syncDeploymentStatus(allRSs, newRS, deployment)
}

这里做了如下几件事:

  1. 查找新的RS和旧的RS,并计算出新的Revision(这是Revision的最大值);
  2. 对新的RS进行扩容操作;
  3. 对旧的RS进行缩容操作;
  4. 完成之后,删掉旧的RS;
  5. 通过Deployment状态到etcd;

至此,我们知道了滚动升级在kubernetes中的原理。其实在传统的负载均衡应用中,滚动升级的做法很类似,但是在容器环境中,我们有RS,通过这种方法更为便捷。

Docker中部署Kubernetes
http://www.linuxidc.com/Linux/2016-07/133020.htm

Kubernetes集群部署 
http://www.linuxidc.com/Linux/2015-12/125770.htm

OpenStack, Kubernetes, Mesos 谁主沉浮 
http://www.linuxidc.com/Linux/2015-09/122696.htm

Kubernetes集群搭建过程中遇到的问题及解决 
http://www.linuxidc.com/Linux/2015-12/125735.htm

在Ubuntu下部署Kubernetes集群 
http://www.linuxidc.com/Linux/2016-09/135018.htm

Kubernetes
的详细介绍
:请点这里
Kubernetes
的下载地址
:请点这里

本文永久更新链接地址:http://www.linuxidc.com/Linux/2016-10/136041.htm

图片 1

图片 2

2)Pod
的个数由访问请求决定。即当前实例个数不足以满足访问请求时,需要增加实例个数,反之,需要通过某种策略减少实例数。

  1. Pods

如果人工来实时监控实例的运行状态,手动启动新的pod以替代fail的pod,监控实例的负载情况,手动创建或者删除pod,这个工作繁琐且工作量大,好在kubernetes已经有相应的机制来应对这种变化。

  在Kubernetes系统中,调度的最小颗粒不是单纯的容器,而是抽象成一个Pod,Pod是一个可以被创建、销毁、调度、管理的最小的部署单元。比如一个或一组容器。

2.概要

  1. Replication Controllers

声明:
这里的介绍主要基于kubernetes官网的内容,您可以选择 kubernetes
官网 阅读更加详细内容。

  Replication
Controller是Kubernetes系统中最有用的功能,实现复制多个Pod副本,往往一个应用需要多个Pod来支撑,并且可以保证其复制的副本数,即使副本所调度分配的主宿机出现异常,通过Replication
Controller可以保证在其它主宿机启用同等数量的Pod。Replication
Controller可以通过repcon模板来创建多个Pod副本,同样也可以直接复制已存在Pod,需要通过Label
selector来关联。

1)关于RelicationController
和 RelicaSet

  1. Services

简单来说,这两者的主要作用都是确保有指定的数量的Pod实例在运行,区别在于后者是前者的升级版。他们都会检测Pod的个数,一旦某个pod
fail,则启动新的Pod,当然如果数量过多(fail的pod复活),则需要删除某些实例。

  Services是Kubernetes最外围的单元,通过虚拟一个访问IP及服务端口,可以访问我们定义好的Pod资源,目前的版本是通过iptables的nat转发来实现,转发的目标端口为Kube_proxy生成的随机端口,目前只提供GOOGLE云上的访问调度,如GCE。如果与我们自建的平台进行整合?请关注下篇《kubernetes与HECD架构的整合》文章。

这里目前有两种常见的使用场景。第一,通过一个RC(或RS)部署一个Pod,这种情形下,一个Pod
fail,RS
会主动创建新的Pod来替换旧的Pod,反之,会删除多余的Pod,这也是一种高可用的方案。第二种,通过RS部署多个Pod,这种情形下,一旦某个Pod
fail
掉,RS同样会创建新的Pod来弥补,以确保总是有相同数目的pod在提供服务,不至于由于pod
fail,应用的服务水平下降。

  1. Labels

下面是一个RC的yaml定义文件:

  Labels是用于区分Pod、Service、Replication
Controller的key/value键值对,仅使用在Pod、Service、 Replication
Controller之间的关系识别,但对这些单元本身进行操作时得使用name标签。

 

  1. Proxy
apiVersion: v1
kind: ReplicationController
metadata:
  name: nginx
spec:
  replicas: 3
  selector:
    app: nginx
  template:
    metadata:
      name: nginx
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80

  Proxy不但解决了同一主宿机相同服务端口冲突的问题,还提供了Service转发服务端口对外提供服务的能力,Proxy后端使用了随机、轮循负载均衡算法。

这里的replicas即需要维持的Pod个数,值得一提的是,这个个数是可以改变的,只需要修改这个文件,然后执行替换操作即可,然后RS就会按照新的数量维护Pod个数。

  1. Deployment

 

  Kubernetes Deployment提供了官方的用于更新Pod和Replica
Set(下一代的Replication Controller)的方法Kubernetes
Deployment提供了官方的用于更新Pod和Replica Set(下一代的Replication
Controller)的方法,您可以在Deployment对象中只描述您所期望的理想状态(预期的运行状态),Deployment控制器为您将现在的实际状态转换成您期望的状态,例如,您想将所有的webapp:v1.0.9升级成webapp:v1.1.0,您只需创建一个Deployment,Kubernetes会按照Deployment自动进行升级。现在,您可以通过Deployment来创建新的资源(pod,rs,rc),替换已经存在的资源等。

这里的template即Pod的定义,这个与创建单个pod的定义是一致的,事实上,这个配置会传递下去,通过这个配置创建pod。

  Deployment集成了上线部署、滚动升级、创建副本、暂停上线任务,恢复上线任务,回滚到以前某一版本(成功/稳定)的Deployment等功能,在某种程度上,Deployment可以帮我们实现无人值守的上线,大大降低我们的上线过程的复杂沟通、操作风险