之前在《Kubernetes初体验》一文中已经简单介绍了Kubernetes的架构和一些基本概念,最近接着学习了一下,觉得Kubernetes的这些资源(或者称为对象)对于理解和学习Kubernetes非常重要,而且因为比较多,所以决定写篇博客来做一下总结,加深记忆。当然,本文不会对每个资源做深入描述,因为每个资源要介绍的比较清楚都需要一篇长篇大论了。后面我会分篇详细介绍每一个资源,当然最好的了解方式还是去看Kubernetes官方对于这些资源的描述:https://kubernetes.io/docs/reference。本文的目标有两个:

  1. 对Kubernetes的架构做一个简单的介绍。
  2. 对Kubernetes的大部分资源极其关系做一个简单的介绍。

文章尽可能简明、简短。Just keep it simple and focus。

Kubernetes架构

先引用一下官方的架构图:

请输入图片描述

对于本文来说,我觉得这张图有点复杂了,但是我又懒得自己画了,就用这张吧。Kubernetes是一个集群,和传统的集群相似,它也是有一个主节点和若干个工作节点组成。在Kubernetes中,主节点称之为Master节点,就是上图中左边的大框;工作节点称之为Node(原来称为Minion,一个意思)。下面我们分别介绍Master节点和Node节点。

Master

Master节点上面主要由四个模块组成:APIServer、scheduler、controller manager、etcd。

  1. APIServer。APIServer的功能如其名,负责对外提供RESTful的Kubernetes API服务,它是系统管理指令的统一入口,任何对资源进行增删改查的操作都要交给APIServer处理后再提交给etcd。如架构图中所示,kubectl(Kubernetes提供的客户端工具,该工具内部就是对Kubernetes API的调用)是直接和APIServer交互的。
  2. schedule。scheduler的职责很明确,就是负责调度pod(Kubernetes中最小的调度单元,后面马上就会介绍)到合适的Node上。如果把scheduler看成一个黑匣子,那么它的输入是pod和由多个Node组成的列表,输出是Pod和一个Node的绑定(bind),即将这个pod部署到这个Node上。虽然scheduler的职责很简单,但我们知道调度系统的智能程度对于整个集群是非常重要的。Kubernetes目前提供了调度算法,但是同样也保留了接口,用户可以根据自己的需求定义自己的调度算法。
  3. controller manager。如果说APIServer做的是“前台”的工作的话,那controller manager就是负责“后台”的。后面我们马上会介绍到资源,每个资源一般都对应有一个控制器,而controller manager就是负责管理这些控制器的。还是举个例子来说明吧:比如我们通过APIServer创建一个pod,当这个pod创建成功后,APIServer的任务就算完成了。而后面保证Pod的状态始终和我们预期的一样的重任就由controller manager去保证了。
  4. etcd。etcd是一个高可用的键值存储系统,Kubernetes使用它来存储各个资源的状态,从而实现了Restful的API。

至此,Kubernetes Master就简单介绍完了。当然,每个模块内部的实现都很复杂,而且功能也比较复杂,我现在也只是比较浅的了解了一下。如果后续了解的比较清楚了,再做总结分享。

Node

真正干活的来了。每个Node节点主要由三个模块组成:kubelet、kube-proxy、runtime。先从简单的说吧。

  1. runtime。runtime指的是容器运行环境,目前Kubernetes支持docker和rkt两种容器,一般都指的是docker,毕竟docker现在是最主流的容器。
  2. kube-proxy。该模块实现了Kubernetes中的服务发现和反向代理功能。反向代理方面:kube-proxy支持TCP和UDP连接转发,默认基于Round Robin算法将客户端流量转发到与service对应的一组后端pod。服务发现方面,kube-proxy使用etcd的watch机制,监控集群中service和endpoint对象数据的动态变化,并且维护一个service到endpoint的映射关系,从而保证了后端pod的IP变化不会对访问者造成影响。另外kube-proxy还支持session affinity。(这里涉及到了service的概念,可以先跳过,等了解了service之后再来看。)
  3. kubelet。Kubelet是Master在每个Node节点上面的agent,是Node节点上面最重要的模块,它负责维护和管理该Node上面的所有容器,但是如果容器不是通过Kubernetes创建的,它并不会管理。本质上,它负责使Pod得运行状态与期望的状态一致。

至此,Kubernetes的Master和Node就简单介绍完了。下面我们来看Kubernetes中的各种资源/对象。

Kubernetes资源/对象

当然上面已经介绍的Node也算Kubernetes的资源,这里就不再赘述了。

Pod

Pod是Kubernetes里面抽象出来的一个概念,它具有如下特点:

  • Pod是能够被创建、调度和管理的最小单元;
  • 每个Pod都有一个独立的IP;
  • 一个Pod由一个或多个容器构成;
  • 一个Pod内的容器共享Pod的所有资源,这些资源主要包括:共享存储(以Volumes的形式)、共享网络、共享端口等。
  • 集群内的Pod之间不论是否在同一个Node上都可以任意访问,这一般是通过一个二层网络来实现的。

Kubernetes虽然也是一个容器编排系统,但不同于其他系统,它的最小操作单元不是单个容器,而是Pod。这个特性给Kubernetes带来了很多优势,比如最显而易见的是同一个Pod内的容器可以非常方便的互相访问(通过localhost就可以访问)和共享数据。使用Pod时我们需要注意两点:

  1. 虽然Pod内可以有多个容器,但一般我们只将有亲密关系的容器放在一个Pod内,比如这些容器需要相互访问、共享数据等。举个最典型的例子,比如我们有一个系统,前端是tomcat作为web,后端是存储数据的数据库mysql,那么将这两个容器部署在一个Pod内就非常合理了,因为他们通过localhost就可以访问彼此。
  2. 虽然每个Pod都有独立的IP,但是不推荐前台通过IP去访问Pod,因为Pod一旦销毁重建,IP就会变化。如果我们的Pod提供了对外的Web服务,那么我们可以通过Kubernetes提供的service去访问,后面会介绍到。

下面是一个Pod的描述文件nginx-pod.yaml:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
  labels:
    app: nginx
spec:
  containers:
    - image: registry.hnaresearch.com/library/nginx:latest
      name:  nginx
      ports:
      - containerPort: 80

apiVersion表示API的版本,kind表示我们要创建的资源的类型。metadata是Pod的一些元数据描述。spec描述了我们期望该Pod内运行的容器。通过kubectl create -f nginx-pod.yaml就可以创建一个Pod,这个Pod里面只有一个nginx容器。

➜  kubectl create -f nginx-pod.yaml
pod "nginx-pod" created
➜  kubectl get pod
NAME        READY     STATUS    RESTARTS   AGE
nginx-pod   1/1       Running   0          1h

这里我们只是为了示例,其实实际应用中我们很少会去直接创建一个Pod,因为这样创建的Pod就像个没人管的孩子,挂了的话也不会有人去重新建立一个新的来顶替它。Kubernetes提供了很多创建Pod的方式,下面我们接着介绍。

Replication Controller

Replication Controller简称RC,一般翻译为副本控制器,这里的副本指的是Pod。如它的名字所言RC的作用就是保证任意时刻集群中都有期望个数的Pod副本在正常运行。我们通过一个简单的RC描述文件(mysql-rc.yaml)来介绍它:

apiVersion: v1
kind: ReplicationController
metadata:
  name: mysql
spec:
  replicas: 1
  selector:
    app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: registry.hnaresearch.com/library/mysql:5.6
        ports:
        - containerPort: 3306
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "123456"

上面这个文件描述了一个RC,名字叫mysql,最上面的spec描述了我们期望有1个副本,这些副本都是按照下面的template去创建的。如果某一时刻副本数比replicas描述的少,就按照template去创建新的,如果多了,就干掉几个。而下面的spec描述了这个Pod内运行的容器。

➜  kubectl create -f mysql-rc.yaml
replicationcontroller "mysql" created
➜  kubectl get rc
NAME      DESIRED   CURRENT   READY     AGE
mysql     1         1         1         7s
➜  kubectl get pod
NAME          READY     STATUS    RESTARTS   AGE
mysql-1l717   1/1       Running   0          27s
nginx-pod     1/1       Running   0          1h

然后我们进行一些删除操作:

➜  kubectl delete pod nginx-pod
pod "nginx-pod" deleted
➜  kubectl get pod
NAME          READY     STATUS    RESTARTS   AGE
mysql-1l717   1/1       Running   0          5m
➜  kubectl delete pod mysql-1l717
pod "mysql-1l717" deleted
➜  kubectl get pod
NAME          READY     STATUS    RESTARTS   AGE
mysql-2vl9k   1/1       Running   0          4s

我们先删掉之前通过Pod描述文件创建的nginx-pod,按照预期它被删除了,并没有重建。然后我们删掉mysql-1l717,发现又出来一个新的mysql-2vl9k。这是因为mysql这个是通过RC创建的,除非删除它的RC,否则任意时刻该RC都会保证有预期个Pod副本在运行。

那么,RC是怎么和Pod产生关联的呢?上面的selector是什么含义?OK,我们来介绍下一个对象。

Labels和Selector

Label是附属在Kubernetes对象上的键值对,它可以在创建的时候指定,也可以随时增删改。一个对象上面可以有任意多个Labels。它往往对于用户是有意义的,对系统是没有特殊含义的。我个人理解你可以简单把他当做Git上面的tag。这里我们只介绍一下它和Selector配合使用时的场景。我们从上面Pod和RC的描述文件中可以看到,每个Pod都有一个Labels,而RC的Selector部分也有一个定义了一个labels。RC会认为凡是和它Selector部分定义的labels相同的Pod都是它预期的副本。比如凡是labels为app=mysql的Pod都是刚才定义的RC的副本。

所以就有一个注意点,我们不要单独去创建Pod,更不要单独去创建符合某个RC的Selector的Pod,那样RC会认为是它自己创建的这个Pod而导致与预期Pod数不一致而干掉某些Pod。当然Labels还有很多用途,Selector除了等值外也有一些其他判读规则,这里不细述。

Service

终于轮到Service出场了,之前我们已经多次提到它了。Service是Kubernetes里面抽象出来的一层,它定义了由多个Pods组成的逻辑组(logical set),可以对组内的Pod做一些事情:

  • 对外暴露流量
  • 做负载均衡(load balancing)
  • 服务发现(service-discovery)。

前面我们说了如果我们想将Pod内容器提供的服务暴露出去,就要使用Service。因为Service除了上面的特性外,还有一个集群内唯一的私有IP和对外的端口,用于接收流量。如果我们想将一个Service暴露到集群外,即集群外也可以访问的话,有两种方法:

  • LoadBalancer - 提供一个公网的IP
  • NodePort - 使用NAT将Service的端口暴露出去。

为什么不能通过Pod的IP,而要通过Service呢?因为在Kubernetes中,Pod是可能随时死掉被重建的,所以说其IP是不可靠的。但是Service一旦创建,其IP就会一直固定直到这个Service消亡。其实我们能够看到,Kubernetes中一个Service就相当于一个微服务。这里我们就不细述Service的创建方法以及如何使用LB以及NodePort了。

Replica Sets和Deployment

Replica Sets被称为下一代的Replication Controller,它被设计出来的目的是替代RC并提供更多的功能。就目前看,ReplicaSet和RC的唯一区别是对于Labels和Selector的支持。RC只支持等值的方式,而ReplicaSet还支持集合的选择方式(In,Not In)。另外,ReplicaSet很少像RC一样单独使用(当然,它可以单独使用),一般都是配合Deployment一起使用。

Deployment也是Kubernetes新增加的一种资源,从它的名字就可以看出它主要是为部署而设计的,之前的文章中已经有具体的例子了。想像一下我们利用RC创建了一些Pod,但现在我们想要更新Pod内容器使用的镜像或者想更改副本的个数等。这些我们无法通过修改已有的RC去做,只能删除旧的,创建新的。但这样Pod内的容器就会停止,也即业务就会中断,这在生产环境中往往是不可接受的。但有了Deployment以后,这些问题就都可以解决了。通过Deployment我们可以动态的控制副本个数、ReplicaSet和Pod的状态、滚动升级等。Deployment的强大真的需要很长的一篇文章来介绍,后续的博客再介绍吧。

HPA

HPA全称Horizontal Pod Autoscaling,即Pod的水平自动扩展,我觉得这个简直就是Kubernetes的黑科技。因为它可以根据当前系统的负载来自动水平扩容,如果系统负载超过预定值,就开始增加Pod的个数,如果低于某个值,就自动减少Pod的个数。因为被以前的系统扩容缩容深深的折磨过,所以我觉得这个功能是多么的强大。当然,目前Kubernetes的HPA只能根据CPU和内存去度量系统的负载,而且目前还依赖heapster去收集CPU的使用情况,所以功能还不是很强大,但是其完善也只是时间的问题了。让我们期待吧。

Namespace

Kubernetes提供了Namespace来从逻辑上支持多租户的功能,默认有两个Namespace:

  • default。用户默认的namespace。
  • kube-system。系统创建的对象都在这个namespace下。

当然我们可以自己创建新的namespace。

Daemon Set

有时我们可能有这样的需求,需要在所有Pod上面(包括将来新创建的)都运行某个容器,比如用于监控、日志收集等。那我们就可以使用DaemonSet,它可以保证所有容器上面都运行一份我们指定的容器的实例。而且,通过Labels和Selector,我们可以实现只在某些Pod上面部署,非常的灵活。

但是现在也有一些局限,比如如果我们无法更改已经部署的Daemon Set。如果需要更改,只能删除重建。当然,更改的功能也已经在开发中了。

其他

当然,Kubernetes还有很多其他的资源/对象,比如执行一次任务的Job,存储相关的Volume等,这些我觉得都没法简单的说清楚其功能。后续再介绍。

写到这里,感觉本文已经和开始的说的简明、简短有些渐行渐远了...Oops