HOME/kuberntes/

Kubernetes基本概念和术语

Article Outline
TOC
Collection Outline

Kubernetes基本概念和术语

kubernetes中的大部分概念(Node、Replication Controller、Service)等都可以看作是资源对象,这些资源对象都可以通过API编程调度(kubectl工具就是封装了这些调度接口),在调度完成后,将资源的状态保存在etcd中持久化存储。

Master

在kubernetes集群中,Master指的就是集群的控制节点,这个节点主要负责集群的管理和控制。在实际的使用中,我们对于集群的操作都是发送到Master节点,由它负责具体的执行过程。其中Master节点运行着以下相关进程:

  • Kubernetes API Server(kube-apiserver):该进程提供HTTP Rest的接口,集群中对资源的操作都是通过它来实现的,是整个集群的入口;
  • Kubernetes Controller Manager(kube-controller-manager):集群中资源对象的控制中心;
  • Kubernetes Scheduler(kube-scheduler):集群中负责资源调度的进程;
  • etcd Server:该进程并不是必选项,一般而言我们默认使用etcd服务来保存资源对象的数据。

Node

Kubernetes集群中,其他节点统称为Node节点。Master和Node可以是物理主机也可以是虚拟主机。在Kubernetes集群中,Node节点是负责工作负载 的主体。在Node节点上,一般会运行着以下进程:

  • kubelet:负责Pod对应的容器的创建、启动、停止等工作,与Master协作,实现集群管理的功能;
  • kube-proxy:该进程主要负责通信及负载均衡机制;
  • Container Engine:容器引擎,这里可以选择Docker或者kubernetes支持的容器引擎,一般我们选择Docker Engine。

Node节点可以动态的增加到kubernetes集群中,也就是说,如果我们为一台主机安装完成了Node节点所需要的服务并且正常开启,那么这一台节点可以随时添加到正在运行的kuberntes集群中。 Node加入到集群中后,节点上的kubelet进程会定时的将Node节点的信息上报到Master节点中。Master节点根据每个Node节点上报的信息,实现均衡的资源调度策略,一旦某个Node节点在规定时间内没有上报信息,则Master节点判断该Node节点出现异常,然后将该Node节点标记为不可用,然后触发工作负载迁移的流程。 在Master节点上,我们可以使用kubectl get nodes命令来查看集群中的节点数量及状态,使用kubectl describe node <node_name>来查看指定节点的详细信息。

Pod

Pod是kubernetes集群中一个基本的概念,kubernetes集群能调度的最小单元就是Pod。但是,在一个Pod中可以包含一个或多个容器,其中有一个pause的容器比较特殊,这个容器是kuberntes平台的一部分。除了pause容器外,每个Pod一般还包含一个或多个用户的业务容器。

在kuberntes中,Pod包含了一组容器单元,这一组容器往往在业务上联系紧密。如果单单使用Docker的话,我们需要编写一个compose文件,将这些容器联系起来,但是,在使用了kuberntes进行编排后,我们就可以将这些容器放在一个Pod中,用户的业务容器可以共享pause容器的IP、挂载的Volume等,这样就解决了业务容器之间通信及文件共享的问题。另外,对于在同一个Pod的业务容器而言,我们不需要监控每个容器的运行状态,kubernetes只需要监控pause容器的状态,以它的状态代表这个Pod中容器的状态。

在实际的使用中,kubernetes为每个Pod都分配了一个唯一的IP地址,称为PodIP,每个Pod中的容器都共享这个PodIP,通过虚拟二层网络技术(Flannel、Openvswitch),同一kubernetes集群中,Pod之间可以跨越主机进行通信。

Pod分类

实际上Pod细分下来还可以分为:普通的Pod和静态Pod两种类型。静态Pod比较特殊,它并不存放在集群的etcd存储里,而是放在某个具体的Node上的一个具体文件中,并且只能在这个Node上运行。静态Pod被节点上的kubelet进程直接管理,kubelet通过kubernetes API服务为每个静态pod创建镜像,这些pod对于API服务是可见的,但是不受API服务控制。
如果确实需要在集群中使用静态pod的话,我们应该考虑DaemonSet的方式。
Pod可以设置资源限制,用以限制Pod对Node节点上计算资源的使用。在kubernetes中,对于计算资源的限制需要设置两个参数:Requests和Limits,其中Requests表示资源的最小申请量,Limits表示Pod可以使用该资源最大的量,如果Pod申请的资源超过这个限制,那么kubernetes会杀掉该Pod进程并且重启。

Label

在Kubernetes中,Label(标签)是附加在资源对象上的一个键-值(key=value)对,key和value都由用户定义。Label与我们日常描述的“标签”大体上相同。给资源对象附加上Label有助于用户进行资源分配、调度、配置、部署等管理工作。
一个资源对象可以添加任意数量的Label,同样,一个Label也可以附加在任意数量的资源对象上。通过这种方式,可以实现资源的多维度管理工作。给资源对象附加Label可以在资源对象定义时确定,也可以在资源对象创建完成后再动态的添加上去。
在给资源对象添加完Label后,我们可以使用Label Selector查询及选择具有某些Label的资源对象,然后再进行更多的操作。目前API支持两种selector表达式:基于等式(equality-based)和基于集合(set-based)。

基于等式的Label Selector
name = mysql:匹配所有具有Label为name=mysql的资源对象
environment != development:匹配所有Label中不具有environment=development的资源对象

基于集合的Label Selector
name in (redis-master, redis-salve):匹配所有Label包含name=redis-master和name=redis-slave的资源对象
tier notin (frontend, backend):匹配所有Label中不包含tier=frontend和tier=backend的资源对象

Label Selector还可以通过组合实现更复杂的查询,每个查询条件使用“,”进行分割,代表的是逻辑与(&&)的关系。比如:

name=mysql,env!=development:代表Label中包含name=mysql,不包含env=development的资源对象

Label Selector的作用主要体现在以下几个方面:

  • kube-controller进程通过资源对象RC上定义的Label Selector来筛选要监控的pod副本数量;
  • kube-proxy进程通过Service的Label Selector来选择对应的pod,自动建立起每个Service到对应pod的请求转发路由;
  • 通过对某些node定义Label,通过kube-scheduler定向的将pod调度到某些node上。 通过使用Label和Label Selector,可以更方便的实现对资源对象的精细化管理,以及实现集群的高可用性。

Replication Controller(RC)

对于Replication Controller的官方解释是,Replication Controller确保一次运行指定数量的pod,确保一个pod或一组同类的pod总是可用的。我们可以理解为,Replication Controller对pod的运行进行了定义,一旦pod不符合这个定义,就进行相应的调整。

那么我们在定义一个Replication Controller的时候,需要定义哪些内容呢?一般而言,我们需要包含以下信息:

  • Pod的副本数量(replicas);
  • 用于选择目标pod的Label Selector;
  • 创建新pod使用的模板(template)。

以下是一个Replication Controller的定义文件:

apiVersion: v1
kind: ReplicationController
metadata:
    name: nginx
spec:
    replicas: 3
    selector:
        app: nginx
    template:
        metadata:
            name: nginx
            labels:
                app: nginx
... ...

上述文件中,kind声明了该配置文件是一个ReplicationController,文件中包含了之前提到的内容,replicas定义了pod的数量,selector定义了Label Selector,template中定义了创建新pod使用的模板。

当定义的RC提交到kubernetes集群以后,Master节点上的Controller Manager会定期的在集群中巡检pod,如果发现pod数量与RC中定义的数量不符合时,就会删除多余的pod或者创建新的pod以保证集群中pod的数量与RC中定义的数量一致。这就实现了集群的高可用性。如果可能的话,即使创建单个pod,我们也尽可能的使用Replication Controller的方式。当然,在运行时,我们也可以通过命令修改RC的replicas参数,动态的调整pod的数量来实现缩放(scaling),这可以通过kubectl scale命令来实现,如:

kubectl scale rc nginx --replicas=5

使用上述命令对pod进行缩放后,可以在集群中查看pod数量是否与修改后的数量一致。

对于RC中定义的pod,在删除的时候,首先将RC中的replicas数量设置为0,然后更新该RC,也可以通过kubectl提供的stopdelete命令来删除RC和RC中定义的pod。

滚动升级

由于RC的存在,当我们在切换集群中某类pod中的Docker镜像的时候,不是一次性完成镜像的更换,而是逐步更换镜像,保持集群中总的pod数量是不变的,当所有的pod中的镜像完成更换后,升级完成。这种方式称为滚动升级(Rolling Update)。

Replica Set

在新版本的kubernetes中,Replication Controller升级为一个新的概念----ReplicaSet,相比于Replication Controller而言,ReplicateSet增加了基于集合的Label Selector(Set-based selector),RC只支持基于等式的Label Selector(equality-based selector)。

我们来看一个官方的ReplicaSet的例子:

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: frontend
  labels:
    app: guestbook
    tier: frontend
spec:
  # modify replicas according to your case
  replicas: 3
  selector:
    matchLabels:
      tier: frontend
    matchExpressions:
      - {key: tier, operator: In, values: [frontend]}
  template:
    metadata:
      labels:
        tier: frontend
    spec:
      containers:
      - name: php-redis
        image: gcr.io/google_samples/gb-frontend:v3

其中matchExpressions下的条件{key: tier, operator: In, values: [frontend]}就是定义的匹配条件及matchLabel用于查找详细信息的操作。

对于RS(Replica Set)来说,kubectl中可用于RC的命令大部分都适用,比如我们可以使用kubectl get rs来查看RS的信息。

但是,实际上我们很少会单独使用Replica Set,其主要是被Deployment这个资源对象使用,Deployment管理Replica Set并为pod提供更新及其他功能。也就是说,除非需要自定义编排,我们没有必要直接操作Replica Set,而是直接使用Deployment。

Deployment

实际上,Deployment是一个高级的、升级版的Replication Controller。相对于RC来说,Deployment可以方便的直到当前pod的部署进度。

Deployment的典型使用场景有以下几个方面:

  • 创建一个Deployment对象来生成对应的Replica Set并完成pod副本的创建过程;
  • 更新Deployment以创建新的Pod(如镜像升级);
  • 回滚到之前的Deployment版本;
  • 扩展Deployment以应对更多负载;
  • 清除不在需要的Replica Sets;
  • 挂起或恢复一个Deployment;
  • 检查Deployment的状态以确定部署是否完成;

Deployment的定义与Replica Set的定义很相似,区别仅限于API声明及kind类型:

apiVersion: apps/v1beta1 # for versions before 1.6.0 use extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80

完成一个Deployment的编写后,使用kubectl create -f <deployment-file>.yaml来创建一个Deploymnet,使用kubectl get deployments命令来查看Deployment的信息。,如果想查看更信息的扩展信息,可以使用kubectl describe deployments来查看输出结果。

Service

kubernetes的Service定义了一种抽象对象,即逻辑上的一组Pod和一种可以访问它们的策略——微服务。这一组Pod能够被Service访问到,通常是通过Label Selector实现的。

对于Serivce来说,对接的一组Pod中,每个Pod在kubernetes中都会被分配一个独立的IP地址,并且每个Pod都有单独的Endpoint对外提供服务。因此,Service对外提供服务的时候,是需要在这组Pod组成的集群中添加一个负载均衡器,将这一组的Pod的Endpoint列表加入到负载均衡器的端口转发列表中。这样以来,外部客户端通过访问负载均衡器的对外IP地址和端口就可以访问Service,最后外部客户端的请求具体转发到哪一个Pod中则是由负载均衡器的算法分配的。
在kubernetes中,运行在每个Node上的kube-proxy就是一个负载均衡器,它主要负责将对Service的请求转发到后端的Pod实例上,并在内部实现服务的负载均衡和会话保持机制。需要注意的是,Service不是共用一个负载均衡器的IP地址,而是每个Service分配了全局唯一的虚拟IP地址,称为ClusterIP。这样一来,每个Service就变成了具备唯一IP地址的端点,服务调用就变成了基本的TCP通信问题。
在前面的内容中,我们知道Pod的Endpoint会随着Pod的销毁和重新创建而发生改变。但是,Service一旦创建,kubernetes就自动的分配一个可用的ClusterIP,在整个Service的生命周期内,它的ClusterIP不发生变化,那么我们只要将Service的Name与Service的ClusterIP做一个DNS域名映射,服务发现的问题也就相应的被解决了。
在实际应用中,很多服务其实是有多端口的,比如:MySQL、redis等。Service支持多个Endpoint,如果存在多个Endpoint的时候,分别对Endpoint命名进行区分,对于多端口命名的操作,就牵扯服务发现这部分的内容,后续会做详细介绍。

外部系统访问Service的问题

在目前介绍的概念中,我们了解到kubernetes中已经存在三种不同的IP,分别是Node IPPod IPCluster IP。它们的作用分别是:

  • Node IP是集群中每个节点的物理网卡IP地址,这是一个真是存在的物理网络,所有在这个网络中的服务器都可以通过这个网络进行通信,不管它是不是在集群中。这也说明集群之外的节点访问集群内的某个节点或者TCP/IP服务的时候,必须通过Node IP进行通信;
    • Pod IP是每个Pod的IP地址,这是通过docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络。因此在集群中,不同的Pod进行通信的时候,就是通过Pod IP所在的虚拟二层网络进行通信的,真实的TCP/IP流量则是通过Node IP所在的物理网卡流出的;
    • Cluster IP是一个虚拟的IP,它仅仅作用于Service对象,并有kubernetes管理和分配IP地址(来源于Cluster IP池),使用ping命令不能连接到Cluster IP,因为没有网络对象进行响应,最后,Cluster IP只能结合Service Port组成一个具体的通信端口,单独的Cluster IP不具备TCP/IP通信的基础。

从上面可以看出,Cluster IP属于kubernetes内部的地址,无法在集群外部直接使用这个地址。为了能够让外部访问Service,可以采用NodePort的方式。具体可以在Service的配置中做如下定义:

apiVersion: v1
kind: Service
metadata:
  name: tomcat-service
spec:
  type: NodePort
  ports:
   - ports: 8080
     nodePort: 30001
  selector:
    tier: frontend

上述配置信息中,spec.type表明指定tomcat-service使用NodePort,spec.ports.nodePort定义了其使用的端口为30001,这样以来,在浏览器中通过http://<nodePort>:30001就可以访问tomcat了。
NodePort的实现实质上是kubernetes集群为Node上需要外部访问的Service开启一个对应的TCP监听端口。

负载均衡

前面介绍的内容解决了集群外部访问Service的主要问题,但是还有负载均衡问题没有得到处理。如下所示: loadbalancer.png 我们设置负载均衡独立于kubernetes集群之外,外部请求的流量会首先经过负载均衡器,然后由负载均衡器分配带集群的Node上,实现这个功能可以通过加设硬件负载均衡器或通过HAproxy或nginx的方式来配置流量转发。