在Docker容器技术中,通过容器,我们可以很方便的将我们的应用程序打成一个镜像,然后无论我们在哪部署应用,只要这个环境支持Docker,那么我们都可以通过Docker将我们的镜像运行起来,而不需要关心环境的问题。这一点真正做到了 "一次打包,到处运行" 的效果。正是因为有了容器技术,我们可以不再理会应用的运行环境依赖问题,这也给微服务架构的实现带来了极大的便利。
在微服务架构下,我们的应用被拆成了一个一个的微型的服务,所有这些服务联合起来组成一个完整的APP,每个服务都运行在容器中,这就可以让我们很方便的对某一功能进行扩容缩容,但是,一个APP往往有很多功能模块,这就导致我们的服务器中可能需要运行很多的容器,可能几百个甚至上千个容器,那如何管理这些容器呢?此时我们就需要一个容器编排工具,使用这个工具可以帮我们管理调度这些容器。
K8S,一个谷歌开源的容器编排管理工具应运而生。K8S全称kubernetes,其起源于谷歌内部的一个叫Borg系统,谷歌在这方面已经有十几年的使用经验,所以,在Docker技术面世后,谷歌使用自家的Golang语言重写了Borg系统,这就是K8S。
在K8S的物理架构上,分为Master节点和Node节点,Master节点负责整个集群的管理工作,Node节点负责运行具体的容器。我们可以认为Master就是整个K8S集群的大脑,Node就是具体执行大脑指令的苦力。接下来我们将介绍一下Master和Node节点的各个组成部分。
Master节点
Master节点主要由三个组件构成:API Server
、Scheduler
、Controller Manager
,接下来我们逐一介绍一下这三个组件的各自的功能。
1. API Server
API Server从字面意思来看,就是提供一组API的服务,其作用是接收处理用户请求。比如,我们需要新建一个容器,那么就可以通过向API Server发送一个请求,由API Server来完成创建容器的后续功能。
2. Scheduler
在K8S集群中,Node节点一般都会有多个,当我们需要对容器进行操作的时候,我们需要评估一下这些Node节点是否满足容器的运行条件,例如我们需要启动一个容器,这个容器需要2G内存,那么我们就需要在所有Node节点上查找出哪些节点满足我们的资源需求。这项工作就是由我们的Scheduler来帮助我们完成的,Scheduler会帮助我们选出最佳的运行容器的Node节点。
3. Controller Manager
我们都知道,K8S为我们提供了故障自愈的功能,那这是怎么实现的呢?比如我们在Node节点上运行着一些容器,如果此时容器异常退出了,那此时K8S需要为我们自动拉起一个新的容器,或者一台Node节点宕机了,那此节点上的容器就需要自动调度到其他的节点上去。那问题来了,K8S是怎么知道容器是否健康呢?答案是通过一种叫控制器的东西,控制器就是一个死循环,不停的检测其托管的容器的状态,一旦发现某个容器状态异常了,那么就自动为我们修复这个容器。
那么,问题又来了,如果这个控制器退出了呢?那此时我们的容器异常退出后,K8S就无法获取容器的健康状态了。此时,就需要我们的Controller Manager了,这个组件就是用来为我们监测所有的控制器的状态的。那么问题又来了,如果Controller Manager也退出了怎么办呢?如果Controller Manager也退出了,那就表示当前这台Master出现了异常,这是我们需要避免的问题,所以,一般在一个K8S集群中,我们往往需要多个Master节点,保持Master节点的高可用。
4. Etcd
以上就是在Master节点上的三个组件的功能,我们知道,通过API Server可以来发送请求,通过Scheduler来实现选择容器具体运行在的Node节点,通过Controller Manager来管理控制器。而且我们还要保证Master节点的高可用。那么问题又来了,集群的信息保存在哪呢?答案是Etcd中。
K8S使用Etcd作为其外部存储,所有的集群信息都会保存在Etcd集群之中,所有的Master节点都从同一个Etcd集群来获取数据,这就保证了所有的Master节点都可以无差别的管理我们的K8S集群。所以,在部署K8S集群的Master节点是,Etcd服务也是必不可少的,虽然Etcd并不是K8S的组件,但是K8S要使用其来存储数据。关于Etcd的相关内容,我们将放在部署的文章中做介绍,此处就不在赘述。大家只需要记住,Etcd服务是用来存储K8S集群信息的就可以了。
Node节点
Node节点是K8S集群中具体运行容器的服务器,在一个K8S集群中往往会有多个Node节点,通过K8S的调度,就可以把这多个Node节点组成一个巨大的资源池。在介绍Node节点之前,我们需要先更正一个问题。在上面我们介绍Master节点的时候说,我们Scheduler会帮我们选择容器运行在哪个Node节点上。实际上在K8S中,Node节点上运行的并不是容器,而是Pod,Pod是K8S使用的最小结构,Pod就相当于K8S将我们的容器进行了一层封装,在Pod中来运行一个或多个容器。
Node节点主要由三个部分组成:容器引擎,kubelet,kube-proxy,其中容器引擎并不属于K8S本身的组件,且容器引擎也可以有多种,只不过目前应用最广泛的是Docker容器引擎,本文也是基于Docker容器引擎来阐述的,所以,在后文中容器引擎就默认是Docker了。由此可知,我们所有的Node节点上都需要先部署上Docker,然后再来安装剩下的两个组件。
1. kubelet
我们介绍Master节点时说过,当我们需要对一个Pod进行操作时,Master上的Scheduler会帮我们选择最优的Node节点,之后在这个节点上来完成我们的操作。那这些具体的操作是由谁来完成的呢?答案就是kubelet组件,Node节点上的kubelet组件就是用来具体执行我们操作的。
2. kube-proxy
在讨论这个组件之前,我们需要先解决一个问题。在Docker中,我们每个容器都拥有独立的名称空间,IP地址等等,在K8S帮我们调度这些容器的时候,对于集群外的我们来说,我们是不确定这个容器被调度到了哪台Node服务器上,不管是新建容器也好,还是容器挂了之后被重新拉起也好,容器的地址都是可能会变的,那么此时我们如何来访问服务呢?此外,K8S是可以方便的为我们做横向扩容的,如果此时扩容了pod,那么我们怎么将我们的请求分发到这些新的pod上呢?
在K8S中,正是因为有如上所述的问题的存在,我们不可能给容器一个固定的IP地址,于是,为了解决这个问题,K8S在Pod之上又添加了一个逻辑的组件,称为Service,在K8S内部,我们会创建一个service资源,这个资源对应到后面的pod,用户访问我们的服务的时候,直接访问我们的service就好了,而我们的service也有自己的IP地址和名称,且这个IP地址和名称是不会变化的,这样一来,用户就不必考虑后端的pod如何变化。
在引入service资源之后,pod的变动问题得以解决,但是又出现了一个新的问题,就是在后端pod发生变化时,比如新加了一个pod,那这个pod是属于哪个服务的呢?我们的service资源和pod如何建立联系呢?此时我们就需要kube-proxy
这个组件了,这个组件就是为我们维护service资源和pod的联系的组件,当我们的pod发生变化后,我们的kube-proxy会立即将更新service
和pod
的映射关系,这就是K8S提供的服务发现的功能。
在介绍完kube-proxy
的功能后,其实我们还有一个小问题没有解决,那就是当我们后端pod发生变化的时候,pod的IP可能会发生变化,那么kube-proxy组件又是如何区分这个pod到底是属于哪个service资源的呢?答案是使用标签,K8S使用标签选择器来区分各个资源,相同标签名和标签值的pod就会被归为同一类资源,所以,kube-proxy通过标签选择器来查找同类的资源,无论pod的IP地址如何变化,其标签都是不会改变的。
3. CNI网络插件
上面我们介绍了Node上的K8S组件,我们知道在一个K8S集群中,往往有很多台Node节点,每个Node节点上都会有若干个pod,具有相同标签的pod就会加入一个service资源中来提供服务,那么在不同Node主机上的pod的IP地址会不会冲突,又能不能互相通信呢?我们已经知道,在同一台宿主机上的不同容器之间是可以相互通信的,但是在不同主机上的pod节点默认是不能通信的,为了解决这个问题,我们还需要在Node节点上部署CNI网络插件来管理我们的pod网络,这个CNI网络插件并不是K8S官方提供的,常用的CNI网络插件都是第三方提供的,最常用的有:
- flannel
- calico
CNI网络插件为我们提供了跨宿主机之间的pod网络通信功能,具体的部署和使用我们将在后续的文章中介绍。
在了解了K8S大致的组成及结构后,我们就可以得到K8S整体的架构图如下:
K8S网络架构
从上面的架构图中我们可以看到,在K8S中,每一台物理主机都有一个IP地址,每启动一个Pod都会有一个Pod地址,每创建一个服务,都会有一个Service地址。从此我们可以看出,K8S中共有3层网络,其分别是物理网络、Pod网络、Service网络,其中Service网络又称之为Cluster网络,其网络架构如下图所示:
后面会逐渐拆解K8S组件,详细介绍,敬请期待K8S
系列。