在Kubernetes集群中部署Heapster

背景

公司的容器云平台需要新增应用的自动扩缩容功能,以便能够更加智能化的对应用进行管理。

Kubernetes官方提供了HPA(Horizontal Pod Autoscaling)资源对象。要让我们部署的应用做到自动水平的(水平指的是增减Pod副本数量)进行扩缩容,我们只需要在Kubernetes集群中创建HPA资源对象,然后让该资源对象关联某一需要进行自动扩缩容的应用即可。

HPA默认的是以Pod平均CPU利用率作为度量指标,也就是说,当应用Pod的平均CPU利用率高于设定的阈值时,应用就会增加Pod的数量。CPU利用率的计算公式是:Pod当前CPU的使用量除以它的Pod Request(这个值是在部署deployment时自己设定的)值。而平均CPU利用率指的就是所有Pod的CPU利用率的算术平均值。

Pod平均CPU利用率的计算需要知道每个Pod的CPU使用量,目前是通过查询Heapster扩展组件来得到这个值,所以需要安装部署Heapster。

接下来我就将我们在Kubernetes集群中部署Heapster的过程记录下来,也会描述我们在部署过程中遇到的问题以及解决的方法,希望能够帮助到也准备在Kubernetes集群中部署Heapster的朋友。

Heapster成功部署之后,我们使用了性能测试工具对http应用做了压力测试,以观察HPA进行自动扩缩容时的实际效果。

部署过程

1

首先,我们在github中搜索Heapster,会找到Kubernetes中的Heapster库:
这里写图片描述
将这个库clone到集群的master节点中。

在Heapster目录下运行命令kubectl create -f deploy/kube-config/standalone/Heapster-controller.yaml

理论上创建完成后会启动三个资源对象,deployment、service、serviceaccount,此时Heapster应该就能够为HPA提供CPU的使用量了。

此时为验证Heapster是否可用,在集群中部署一个HPA资源对象,关联某个应用,并设定阈值为90:
这里写图片描述
查看这个HPA时我们可以看到,CURRENT的CPU利用率为,也就是说,HPA没能从Heapster中取得CPU的使用量。

于是,我们用kubectl get pod --namespace=kube-system命令查看Heapster的pod的运行情况,发现是拉取镜像失败的缘故。

打开部署Heapster的yaml文件如下:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: Heapster
  namespace: kube-system
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: Heapster
  namespace: kube-system
spec:
  replicas: 1
  template:
    metadata:
      labels:
        task: monitoring
        k8s-app: Heapster
    spec:
      serviceAccountName: Heapster
      containers:
      - name: Heapster
        image: gcr.io/google_containers/Heapster-amd64:v1.4.0
        imagePullPolicy: IfNotPresent
        command:
        - /Heapster
        - --source=kubernetes:https://kubernetes.default
---
apiVersion: v1
kind: Service
metadata:
  labels:
    task: monitoring
    # For use as a Cluster add-on (https://github.com/kubernetes/kubernetes/tree/master/cluster/addons)
    # If you are NOT using this as an addon, you should comment out this line.
    kubernetes.io/cluster-service: 'true'
    kubernetes.io/name: Heapster
  name: Heapster
  namespace: kube-system
spec:
  ports:
  - port: 80
    targetPort: 8082
  selector:
    k8s-app: Heapster

可以看到23行,镜像是要从谷歌的gcr.io镜像仓库中拉取的,由于我们的集群服务器不能连通外网,所以镜像当然就拉取失败了。

2

此时,我们使用之前使用过的自己仓库中的Heapster镜像来替代此yaml文件中使用的镜像。

清除刚才部署失败的资源对象后再次在Heapster目录下使用命令:kubectl create -f deploy/kube-config/standalone/Heapster-controller.yaml

这个时候发现对应的资源对象都创建成功了,而且pod也成功运行了。

此时看HPA的情况如下:
这里写图片描述
此时问题没有得到解决,虽然Heapster的pod运行起来了,但是HPA还是没能取到CPU使用量的值。

现在的情况来看,此时还不能确定问题是在于Heapster安装没成功还是HPA没能从Heapster中取得值。

所以,接下来就是要确定一下Heapster是否安装成功了。

这个时候,我们想到去Heapster中的docs目录下看看有没有什么debug的方法,也就是进入Heapster目录下的docs中的debugging。

用curl命令对URL/api/v1/model/debug/allkeys做取值,得到的结果如下:
这里写图片描述
然后再用kubectl describe hpa yce --namespace=yce命令来查看HPA资源对象的详情
这里写图片描述
Message中显示,从Heapster获取指标失败。

此时结论就得出了,Heapster已经成功安装,不过集群无法从Heapster中获取到监控数据。

好了,接下来我们决定再给Heapster换一个镜像试试,有可能是我们的镜像版本问题导致与Kubernetes集群不能协同工作。

在docker hub中搜索到了版本为v1.2.0的Heapster镜像,替换为此镜像后再次创建资源对象。

3

创建成功后,惊喜出现了
这里写图片描述
可以看到,HPA已经可以从Heapster中取到值了。然后用kubectl top node命令查看节点的指标,发现也可以取到值了。
这里写图片描述

对HPA关联的应用做压力测试

理论上Heapster已经安装成功。为了验证HPA是否可用,我们写了一个简单的http程序,名为helloworldhey。该程序就是做了一个自增整型变量1000次的循环。

将该程序打包成镜像,部署对应的deployment和service资源对象,然后在集群外部通过暴露的节点端口用性能测试工具ab对其进行压力测试。可以看到结果如下:
这里写图片描述
可以看到,我这里设置的阈值为50%,也就是说,在平均CPU使用率超过50%时,HPA会对pod进行自动的扩容,也即是增加pod的数量,使增加后的pod总数量可以让平均CPU使用率低于50%。

可以看到此时的CPU使用率已经为110%,所以,理论上HPA应该自动的拉起2个pod来分担CPU的使用量。看看pod的目前情况,结果如下:
这里写图片描述
可以看到此时共有三个pod,与预期的结果相同。

不过,可能有人会有疑问,为什么最初创建的pod会重启多次?原因就在于Kubernetes在部署deployment时,每个pod都设置有requests与limits值,当该pod的CPU(或内存)使用量超过该limits值时,kubelet就会重启这个pod,由于在压力测试的开始时刻只有此一个pod,所以CPU使用量肯定是超过了这个limit值于是pod就被重启了。

至此,heapter就被部署成功且可以正常使用了。

Kubernetes网络原理

概述

Kubernetes的搭建与使用少不了网络基础设施的搭建工作, 本文简述了Kubernetes所需要的网络基础环境,Docker的网络实现以及Kubernetes的网络实现。最后,简单提了一下可以实现这些网络基础的网络开源组件Flannel。希望可以通过这些简单的语言描述,让初学者能够初步了解Kubernetes的网络原理。

Kubernetes网络模型

Kubernetes网络模型设计的基础原则:每个Pod都有一个独立的IP地址,这样Pod之间可以直接进行通信,无论它们是否在同一个Node上。而且用户就不需要考虑将容器的端口映射到节点的物理端口上了。

一个Pod分配一个独立的IP,一个Pod内部的所有容器共享一个Linux网络堆栈,包括它们的IP地址、网络设备、配置等都是共享的。所以容器之间是可以通过localhost来连接对方端口的。从这个层面来看,Pod内部容器之间的隔离性相比于不同Pod的容器有所降低,但也只是它们不用相同的端口而已。Pod中的容器有点类似于VM中的进程。

Kubernetes网络模型的必要基础:
1.集群中所有的容器和容器之间可以不通过NAT进行通信
2.节点可以不通过NAT与任一容器进行通信
3.容器的地址和别人看到的地址是一样的

就是说,并不是节点安装了Kubernetes与Docker就可以工作了。还必须满足以上的网络模型才行。而原生的Docker并不能很好的支持这些要求。所以需要一些网络组件来满足这些基础网络要求。

Docker的网络基础

Docker本身的技术依赖于近年来Linux内核虚拟化技术的发展,所以Docker对Linux内核有很强的依赖性。这接下来讲述一下Docker使用到的与Linux网络有关的主要技术。

网络的命名空间

网络命名空间代表的是一个独立的协议栈,所以它们之间是相互隔离的。Docker利用了网络的命名空间特性,实现了不同容器之间网络的隔离。

Veth设备对

引入Veth设备对的目的是让两个网络命名空间进行通信。在Docker内部,Veth设备对也是联系容器到外面的重要设备,离开它是不行的。

网桥

网桥是二层交换机(数据链路层),转发的依据是MAC地址。
在Linux的内部网络栈里也实现了网桥设备,与实际的网桥设备作用相似。是Linux内部各种网络设备之间相互转发数据的二层设备。但Linux网桥与实际的交换机又有不同之处,因为它不仅可能会转发或丢弃,还可能被送到网络协议栈的上层(网络层),从而被自己(网络协议栈)所消化。

Iptables/Netfilter

iptables-save命令可以查看Iptables中的内容
这里写图片描述
我们可以看到service的IP转发到Pod的IP

路由

Linux系统包含一个完整的路由功能。当IP层进行数据的发送或转发时,会使用路由表来决定发往哪里。

Docker的网络实现

标准的Docker支持四种网络模式:host、container、none、bridge。
在Kubernetes管理模式下,通常只会使用bridge模式。如图:
这里写图片描述
这里写图片描述
这里写图片描述
使用ip route show table local type local可以查看本地设备的地址,也显示了docker0网桥的IP地址
这样,在节点内部容器之间可以相互通信,在节点外部则不行。必须将容器的端口映射到主机的端口上才行,docker在跨主机通信时会面临很多的问题。

Kubernetes的网络实现

Kubernetes网络设计主要解决以下场景:
1.容器之间的通信
2.Pod之间的通信
3.Pod到Service之间的通信
4.集群外部与内部组件之间的通信

容器之间的通信

同一个Pod下的容器共享同一个网络命名空间,共享同一个Linux协议栈。所以它们就像在同一台机器上一样。可以相互之间直接通信。可以用localhost访问彼此的端口。

如果容器2运行的是mysql容器,那么容器1通过localhost:3306就直接能够访问运行在容器2上的Mysql了。当然,既然像是在同一台机器上的不同进程,那么它们之间也就是可以通过IPC(进程间通信)进行通信(如消息队列或管道)。
这里写图片描述

Pod之间的通信

同一Node内的Pod之间的通信

这里写图片描述
同一个Node内的不同Pod都是通过Veth连接在同一个Docker0网桥上的。所以它们之间是可以直接进行通信的。而且,非本地数据的网络数据都是默认发送到Docker0网桥然后由它进行中转的。
Docker0网桥会路由到节点上的所有Pod的,这个可以通过看Kubernetes任一节点上的路由表了解
这里写图片描述
这个就表示docker0的IP地址为10.0.82.1,而该节点上的Pod都是10.0.82网段的。

不同Node上的Pod之间的通信

不同Node上的Pod与Pod之间进行通信,肯定是要通过节点的物理网卡的。而且不同的Pod肯定是不能有相同的私有IP的。另外,我们还必须要有Node IP找到该Pod的IP才成。
对于这些要求,Flannel就可以做得到。
这里写图片描述

开源的网络组件

Kubernetes是谷歌的开源项目,它假定了所有的Pod都是在一个可以直接连通的扁平的网络空间中,因为它建立在GCE上,而GCE已经实现了这些要求。

但是对于一个私有云来说,要想用Kubernetes就必须自己来实现这个网络假设。有些开源软件可以做到这些。

Flannel

Flannel实现了两点来保证Kubernetes的底层网络:
1.保证集群中每个Pod的IP不冲突
2.建立了一个覆盖网络,保证数据可以在不同节点之间的Pod传递(通过Pod节点路由表可以找到Pod与节点的对应关系)
这里写图片描述

Kubernetes的主要组件概述

概述

本文试图用简单的语言描述Kubernetes主要组件的作用及其关系。这里我讲解的Kubernetes主要组件有API Server、Controller Manager、Scheduler、kubelet、kube-proxy,其中前三者运行于集群的Master节点,后两者运行于集群的Slave节点。接着描述了一下用于存储Kubernetes集群信息的Etcd,它是一个高可用、强一致性的服务发现存储仓库。最后,我抛出了一个我所遇到的一个问题。大家可以一同思考一下问题出在了哪里。

API Server

Kubernetes API Server通过kube-apiserver进程提供服务,该进程运行于Master节点上。

API Server是Kubernetes的核心组件,是各个组件通信的渠道,有如下特性:

1.集群管理的API入口

我们如果要创建一个资源对象如Deployment、Service、RC、ConfigMap等,都是要通过API Server的。

当然,我们可能是通过命令行的kubectl命令将一个yaml/json格式的文件create进行创建,还可能是通过写代码的方式使用如client-go这样的操作Kubernetes的第三方包来操作集群。总之,最终,都是通过API Server对集群进行操作的。通过API Server,我们就可以往Etcd中写入数据。Etcd中存储着集群的各种数据。
这里写图片描述

2.资源配额控制的入口

Kubernetes可以从各个层级对资源进行配额控制。如容器的CPU使用量、Pod的CPU使用量、namespace的资源数量等。这也是通过API Server进行配置的。将这些资源配额情况写入到Etcd中。

3.提供了完备的集群安全机制

Controller Manager

Replication Controller

副本控制器。用来保证Deployment或者RC中副本的数量的。

Node Controller

通过API Server监控Etcd中存储的关于节点的各类信息,这些信息是kubelet定时推给API Server的,由API Server写入到Etcd中。这些节点信息包括:节点健康状况、节点资源、节点名称、节点地址信息、操作系统版本、Docker版本、kubelet版本等。监控到节点信息若有异常情况,则会对节点进行某种操作,如节点状态变为故障状态,则删除节点与节点相关的Pod等资源的信息。
这里写图片描述

ResourceQuota Controller

将期望的资源配额信息通过API Server写入到Etcd中。然后ResourceQuota Controller会定时的统计这些信息,在系统请求资源的时候就会读取这些统计信息,如果不合法就不给分配该资源,则创建行为会报错。
这里写图片描述

Namespace Controller

用户是可以通过API Server创建新的namespace并保存在Etcd中的。Namespace Controller会定时通过API Server读取这些Namespace信息并做对应的对于Namespace的一些操作。

Endpoints Controller

负责生成和维护所有Endpoints对象的控制器。Endpoints表示了一个Service对应的所有Pod副本的访问地址。

一个Service可能对应了多个Endpoints,那么,在创建一个新的Service时Endpoints Controller就会生成对应的Endpoints。在Service被删除时,Endpoints Controller就会删除对应的Endpoints。等等。

Scheduler

Kubernetes的调度器。Scheduler监听API Server,当需要创建新的Pod时。Scheduler负责选择该Pod与哪个Node进行绑定。将此绑定信息通过API Server写入到Etcd中。

若此时与Node A进行了绑定,那么A上的Kubelet就会从API Server上监听到此事件,那么该Kubelet就会做相应的创建工作。

此调度涉及到三个对象,待调度的Pod,可用的Node,调度算法。简单的说,就是使用某种调度算法为待调度的Pod找到合适的运行此Pod的Node。
这里写图片描述
关于Kubernetes Scheduler的调度算法,我会再另写一篇文章。

Kubelet

每个Node节点上都会有一个Kubelet负责Master下发到该节点的具体任务,管理该节点上的Pod和容器。而且会在创建之初向API Server注册自身的信息,定时汇报节点的信息。它还通过cAdvisor监控容器和节点资源。

节点管理

Kubelet在创建之初就会向API Server做自注册,然后会定时报告节点的信息给API Server写入到Etcd中。默认为10秒。

Pod管理

Kubelet会监听API Server,如果发现对Pod有什么操作,它就会作出相应的动作。例如发现有Pod与本Node进行了绑定。那么Kubelet就会创建相应的Pod且调用Docker Client下载image并运行container。

容器健康检查

有三种方式对容器做健康检查:
1.在容器内部运行一个命令,如果该命令的退出状态码为0,则表明容器健康。
2.TCP检查。
3.HTTP检查。

cAdvisor资源监控

Kubelet通过cAdvisor对该节点的各类资源进行监控。如果集群需要这些监控到的资源信息,可以安装一个组件Heapster。

Heapster会进行集群级别的监控,它会通过Kubelet获取到所有节点的各种资源信息,然后通过带着关联标签的Pod分组这些信息。

如果再配合InfluxDB与Grafana,那么就成为一个完整的集群监控系统了。

Kube-proxy

负责接收并转发请求。Kube-proxy的核心功能是将到Service的访问请求转发到后台的某个具体的Pod。

无论是通过ClusterIP+Port的方式还是NodeIP+NodePort的方式访问Service,最终都会被节点的Iptables规则重定向到Kube-proxy监听服务代理端口,该代理端口实际上就是SocketServer在本地随机打开的一个端口,SocketServer是Kube-proxy为每一个服务都会创建的“服务代理对象”的一部分。

当Kube-proxy监听到Service的访问请求后,它会找到最适合的Endpoints,然后将请求转发过去。具体的路由选择依据Round Robin算法及Service的Session会话保持这两个特性。

Etcd

Etcd一种k-v存储仓库,可用于服务发现程序。在Kubernetes中就是用Etcd来存储各种k-v对象的。

所以我也认为Etcd是Kubernetes的一个重要组件。当我们无论是创建Deployment也好,还是创建Service也好,各种资源对象信息都是写在Etcd中了。

各个组件是通过API Server进行交流的,然而数据的来源是Etcd。所以维持Etcd的高可用是至关重要的。如果Etcd坏了,任何程序也无法正常运行了。

以下是我的环境中的Etcd集群
这里写图片描述
通过以下命令我们就可以看到该集群中yce这个namespace下的pod有哪些,此为目录,列出的都是键值
这里写图片描述
接下来,我们就可以查看某一个具体键值的value值
这里写图片描述
可以看到这个value值是一个Json格式的文件。

一个问题

我做了一个实验,如果从Etcd中直接改yce这个deployment的Json值,从replicas的值从1改到3,发现deployment的desired值发生了变化,但是current值并没有变化,也就是说,Pod的数量并没有因为Etc中的值的改变而改变。难道是因为Replicas Controller没有监控到Etcd中的此变化?
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
如上所示,更改了Etcd中deployment的replicas的值之后,可以用kubectl edit看到该deployment的replicas值确实发生了变化。但是deployment的current值却并无变化。

若是用kubectl edit更改deployment的replicas值就会发现deployment的current值是会发生变化的。请大家思考一下这是为什么。

总结

Kubernetes的这些组件各自分别有着重要的功能。它们之间协同工作,共同保证了Kubernetes对于容器化应用的自动管理。

其中API Server起着桥梁的作用,各个组件都要通过它进行交互。Controller Manager像是集群的大管家,管理着许多事务。Scheduler就像是一个调度亭,负责Pod的调度工作。

Kubelet则在每个节点上都有,像是一个执行者,真正创建、修改、销毁Pod的工作都是由它来具体执行。Kube-proxy像是负载均衡器,在外界需要对Pod进行访问时它作为代理进行路由工作,将具体的访问分给某一具体的Pod实例。

Etcd则是Kubernetes的数据中心,用来存储Kubernetes创建的各类资源对象信息。

这些组件缺一不可,无论少了哪一个Kubernetes都不能进行正常的工作。这里大概讲了下各组件的功能,感兴趣的可以分析Kubernetes的源码,github中就有。