kubernetes应用部署原理

Kubernetes应用部署模型解析(原理篇)
html


十多年来Google一直在生产环境中使用容器运行业务,负责管理其容器集群的系统就是Kubernetes的前身Borg。其实如今不少工做在Kubernetes项目上的Google开发者先前就在Borg这个项目上工做。多数Kubernetes的应用部署模型的思想都起源于Borg,了解这些模型是掌握Kubernetes的关键。Kubernetes的API版本目前是v1,本文以代码0.18.2版为基础来介绍它的应用部署模型,最后咱们用一个简单的用例来讲明部署过程。在部署结束后,阐述了它是如何用Iptables规则来实现各类类型Service的。


Kubernetes架构
前端

Kubernetes集群包括 Kubernetes代理 (agents )Kubernetes服务 (master node)两种角色,代理角色的组件包括 Kube-proxy Kubelet,它们同时部署在一个节点上,这个节点也就是代理节点。服务角色的组件包括 kube-apiserverkube-schedulerkube-controller-manager ,它们能够任意布属,它们能够部署在同一个节点上,也能够部署在不一样的节点上(目前版本好像不行)。 Kubernetes集群依赖的第三方组件目前有 etcddocker两个。前者提供状态存储,两者用来管理容器。集群还可使用分布式存储给容器提供存储空间。下图显示了目前系统的组成部分:


Kubernetes代理节点Kubelet和Kube-proxy运行在代理节点上。他们监听服务节点的信息来启动容器和实现Kubernetes网络和其它业务模型,好比Service、Pod等。固然每一个代理节点都运行Docker。Docker负责下载容器镜像和运行容器。

Kubelet
node

Kubelet 组件管理 Pods 和它们的容器,镜像和卷等信息。


Kube-ProxyKube-proxy是一个简单的网络代理和负载均衡器。它具体实现Service模型,每一个Service都会在全部的Kube-proxy节点上体现。根据Serviceselector所覆盖的Pods, Kube-proxy会对这些Pods作负载均衡来服务于Service的访问者。

Kubernetes服务节点Kubernetes服务组件造成了Kubernetes的控制平面,目前他们运行在单一节点上,可是未来会分开来部署,以支持高可用性。

etcd全部的持久性状态都保存在etcd中。Etcd同时支持watch,这样组件很容易获得系统状态的变化,从而快速响应和协调工做。

Kubernetes API Server这个组件提供对API的支持,响应REST操做,验证API模型和更新etcd中的相应对象。

Scheduler经过访问Kubernetes中/binding API, Scheduler负责Pods在各个节点上的分配。Scheduler是插件式的,Kubernetes未来能够支持用户自定义的scheduler。

Kubernetes Controller Manager ServerController Manager Server负责全部其它的功能,好比endpoints控制器负责Endpoints对象的建立,更新。node控制器负责节点的发现,管理和监控。未来可能会把这些控制器拆分而且提供插件式的实现。

Kubernetes模型Kubernetes的伟大之处就在于它的应用部署模型,主要包括Pod、Replication controller、Label和Service。

PodKubernetes的最小部署单元是Pod而不是容器。做为First class API公民,Pods能被建立,调度和管理。简单地来讲,像一个豌豆荚中的豌豆同样,一个Pod中的应用容器同享同一个上下文:

nginx

  • PID 名字空间。可是在docker中不支持
  • 网络名字空间,在同一Pod中的多个容器访问同一个IP和端口空间。
  • IPC名字空间,同一个Pod中的应用可以使用SystemV IPC和POSIX消息队列进行通讯。
  • UTS名字空间,同一个Pod中的应用共享一个主机名。
  • Pod中的各个容器应用还能够访问Pod级别定义的共享卷。


从生命周期来讲,Pod应该是短暂的而不是长久的应用。 Pods被调度到节点,保持在这个节点上直到被销毁。当节点死亡时,分配到这个节点的Pods将会被删掉。未来可能会实现Pod的迁移特性。在实际使用时,咱们通常不直接建立Pods, 咱们经过replication controller来负责Pods的建立,复制,监控和销毁。一个Pod能够包括多个容器,他们直接每每相互协做完成一个应用功能。


Replication controller复制控制器确保Pod的必定数量的份数(replica)在运行。若是超过这个数量,控制器会杀死一些,若是少了,控制器会启动一些。控制器也会在节点失效、维护的时候来保证这个数量。因此强烈建议即便咱们的份数是1,也要使用复制控制器,而不是直接建立Pod。
git

在生命周期上讲,复制控制器本身不会终止,可是跨度不会比Service强。Service可以横跨多个复制控制器管理的Pods。并且在一个Service的生命周期内,复制控制器能被删除和建立。Service和客户端程序是不知道复制控制器的存在的。
复制控制器建立的Pods应该是能够互相替换的和语义上相同的,这个对无状态服务特别合适。
Pod是临时性的对象,被建立和销毁,并且不会恢复。复制器动态地建立和销毁Pod。虽然Pod会分配到IP地址,可是这个IP地址都不是持久的。这样就产生了一个疑问:外部如何消费Pod提供的服务呢?


ServiceService定义了一个Pod的逻辑集合和访问这个集合的策略。集合是经过定义Service时提供的Label选择器完成的。举个例子,咱们假定有3个Pod的备份来完成一个图像处理的后端。这些后端备份逻辑上是相同的,前端不关心哪一个后端在给它提供服务。虽然组成这个后端的实际Pod可能变化,前端客户端不会意识到这个变化,也不会跟踪后端。Service就是用来实现这种分离的抽象。
github

对于Service,咱们还能够定义Endpoint,Endpoint把Service和Pod动态地链接起来。


Service Cluster IP和 kuber proxy每一个代理节点都运行了一个kube-proxy进程。这个进程从服务进程那边拿到Service和Endpoint对象的变化。 对每个Service, 它在本地打开一个端口。 到这个端口的任意链接都会代理到后端Pod集合中的一个Pod IP和端口。在建立了服务后,服务Endpoint模型会体现后端Pod的 IP和端口列表,kube-proxy就是从这个endpoint维护的列表中选择服务后端的。另外Service对象的sessionAffinity属性也会帮助kube-proxy来选择哪一个具体的后端。缺省状况下,后端Pod的选择是随机的。能够设置service.spec.sessionAffinity 成"ClientIP"来指定同一个ClientIP的流量代理到同一个后端。在实现上,kube-proxy会用IPtables规则把访问Service的Cluster IP和端口的流量重定向到这个本地端口。下面的部分会讲什么是service的Cluster IP。
web

注意:在0.18之前的版本中Cluster IP叫PortalNet IP。


内部使用者的服务发现Kubernetes在一个集群内建立的对象或者在代理集群节点上发出访问的客户端咱们称之为内部使用者。要把服务暴露给内部使用者,Kubernetes支持两种方式:环境变量和DNS。

环境变量当kubelet在某个节点上启动一个Pod时,它会给这个Pod的容器为当前运行的Service设置一系列环境变量,这样Pod就能够访问这些Service了。通常地状况是{SVCNAME}_SERVICE_HOSTh和{SVCNAME}_SERVICE_PORT变量, 其中{SVCNAME}是Service名字变成大写,中划线变成下划线。好比Service "Redis-master",它的端口是 TCP  6379,分配到的Cluster IP地址是 10.0.0.11,kubelet可能会产生下面的变量给新建立的Pod容器:
docker

REDIS_MASTER_SERVICE_HOST= 10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR= 10.0.0.11
注意,只有在某个Service后建立的Pod才会有这个Service的环境变量。


DNS一个可选的Kubernetes附件(强烈建议用户使用)是DNS服务。它跟踪集群中Service对象,为每一个Service对象建立DNS记录。这样全部的Pod就能够经过DNS访问服务了。
ubuntu

好比说咱们在Kubernetes 名字空间"my-ns"中有个叫my-service的服务,DNS服务会建立一条"my-service.my-ns"的DNS记录。同在这个命名空间的Pod就能够经过"my-service"来获得这个Service分配到的Cluster IP,在其它命名空间的Pod则能够用全限定名"my-service.my-ns"来得到这个Service的地址。


Pod IP and Service Cluster IPPod IP 地址是实际存在于某个网卡(能够是虚拟设备)上的,但Service Cluster IP就不同了,没有网络设备为这个地址负责。它是由kube-proxy使用Iptables规则从新定向到其本地端口,再均衡到后端Pod的。咱们前面说的Service环境变量和DNS都使用Service的Cluster IP和端口。
后端

就拿上面咱们提到的图像处理程序为例。当咱们的Service被建立时,Kubernetes给它分配一个地址10.0.0.1。这个地址从咱们启动API的service-cluster-ip-range参数(旧版本为portal_net参数)指定的地址池中分配,好比--service-cluster-ip-range=10.0.0.0/16。假设这个Service的端口是1234。集群内的全部kube-proxy都会注意到这个Service。当proxy发现一个新的service后,它会在本地节点打开一个任意端口,建相应的iptables规则,重定向服务的IP和port到这个新建的端口,开始接受到达这个服务的链接。
当一个客户端访问这个service时,这些iptable规则就开始起做用,客户端的流量被重定向到kube-proxy为这个service打开的端口上,kube-proxy随机选择一个后端pod来服务客户。这个流程以下图所示:

根据Kubernetes的网络模型,使用Service Cluster IP和Port访问Service的客户端能够坐落在任意代理节点上。外部要访问Service,咱们就须要给Service外部访问IP。


外部访问ServiceService对象在Cluster IP range池中分配到的IP只能在内部访问,若是服务做为一个应用程序内部的层次,仍是很合适的。若是这个Service做为前端服务,准备为集群外的客户提供业务,咱们就须要给这个服务提供公共IP了。

外部访问者是访问集群代理节点的访问者。为这些访问者提供服务,咱们能够在定义Service时指定其spec.publicIPs,通常状况下publicIP 是代理节点的物理IP地址。和先前的Cluster IP range上分配到的虚拟的IP同样,kube-proxy一样会为这些publicIP提供Iptables 重定向规则,把流量转发到后端的Pod上。有了publicIP,咱们就可使用load balancer等经常使用的互联网技术来组织外部对服务的访问了。
spec.publicIPs在新的版本中标记为过期了,代替它的是spec.type=NodePort,这个类型的service,系统会给它在集群的各个代理节点上分配一个节点级别的端口,能访问到代理节点的客户端都能访问这个端口,从而访问到服务。


Label和Label selectorLabel标签在Kubernetes模型中占着很是重要的做用。Label表现为key/value对,附加到Kubernetes管理的对象上,典型的就是Pods。它们定义了这些对象的识别属性,用来组织和选择这些对象。Label能够在对象建立时附加在对象上,也能够对象存在时经过API管理对象的Label。

在定义了对象的Label后,其它模型能够用Label 选择器(selector)来定义其做用的对象。
Label选择器有两种,分别是 Equality-basedSet-based
好比以下 Equality-based选择器样例:


environment = productiontier != frontendenvironment = production,tier != frontend

对于上面的选择器,第一条匹配L abel具备 environment key且等于 production的对象,第二条匹配具备 tier key,可是值不等于 frontend的对象。因为 kubernetes使用 AND逻辑,第三条匹配 production但不是 frontend的对象。
Set-based选择器样例:


environment in (production, qa)tier notin (frontend, backend)partition

第一条选择具备 environment key,并且值是 production或者 qalabel附加的对象。第二条选择具备 tier key,可是其值不是 frontendbackend。第三条选则具备 partition key的对象,不对 value进行校验。
replication controller复制控制器和 Service都用 labellabel selctor来动态地配备做用对象。复制控制器在定义的时候就指定了其要建立 PodLabel和本身要匹配这个 PodselectorAPI服务器应该校验这个定义。咱们能够动态地修改 replication controller建立的 PodLabel用于调式,数据恢复等。一旦某个 Pod 因为 Label 改变replication controller移出来后, replication controller会立刻启动一个新的 Pod来确保复制池子中的份数。对于 ServiceLabel selector能够用来选择一个 Service的后端 Pods


一个简单的应用

讲了这么多的原理和概念,本章咱们就部署一个简单应用来感觉一下Kubernetes的部署模型。

部署Kubernetes集群

kubernetes github站点上有数十种针对各类环境的部署文档,本文选择基于 ubuntu的集群部署方案。在没有使用本地 docker镜像的状况下,在部署过程当中须要确保可以访问站点gcr.io。
基于 Ubuntu的集群部署方案文档写得比较详细,按照它的步骤几乎不会出错。在进行真正的部署以前,必定要确保:


  • 全部的节点安装了docker version 1.2+ 和 bridge-utils
  • 若是没有本地的docker registry, 要确保节点能访问互联网gcr.io
  • 确保管理节点可以ssh 访问全部节点。好比ssh gongysh@192.168.0.201 ls


这里咱们集群将采用下图显示的结构。咱们将在管理节点上运行集群管理命令。咱们将有一个服务和代理混合的节点,还有两个纯的代理节点。


首先咱们要下载kubernetes的代码到管理节点上:


$ git clone https://github.com/GoogleCloudPlatform/kubernetes.git

而后进行本地构建:

cd kubernetes./build/run.sh hack/build-do.sh

修改config-default.sh定义集群,本文使用的几个关键配置以下 :

gongysh@fedora20:~/git/kubernetes/cluster/ubuntu$ cat config-default.sh#!/bin/bash# Define all your cluster nodes, MASTER node comes first"# And separated with blank space like <user_1@ip_1> <user_2@ip_2> <user_3@ip_3>export nodes="gongysh@192.168.0.201 gongysh@192.168.0.202 gongysh@192.168.0.203"# Define all your nodes role: a(master) or i(minion) or ai(both master and minion), must be the order sameexport roles=("ai" "i" "i")# Define minion numbersexport NUM_MINIONS=${NUM_MINIONS:-3}# define the IP range used for service portal.# according to rfc 1918 ref: https://tools.ietf.org/html/rfc1918 choose a private ip range here.export SERVICE_CLUSTER_IP_RANGE=192.168.3.0/24# define the IP range used for flannel overlay network, should not conflict with above SERVICE_CLUSTER_IP_RANGE rangeexport FLANNEL_NET=172.16.0.0/16....

最后运行集群构建命令:


$ cd cluster$ KUBERNETES_PROVIDER=ubuntu ./kube-up.sh

当你看到:

Kubernetes cluster is running.  The master is running at:   http://192.168.0.201 ... calling validate-cluster Found 3 nodes.      1        NAME            LABELS    STATUS      2        192.168.0.201   <none>    Ready      3        192.168.0.202   <none>    Ready      4        192.168.0.203   <none>    Ready Validate output: Cluster validation succeeded Done, listing cluster services: Kubernetes master is running at http://192.168.0.201:8080

代表集群构建成功。


部署nginx应用
咱们如下面的图来安装一个简单的静态内容的nginx应用:


首先,咱们用复制器启动一个2个备份的nginx Pod。而后在前面挂Service,一个service只能被集群内部访问,一个能被集群外的节点访问。下面全部的命令都是在管理节点上运行的。

部署nginx pod 和复制器

以下表所示:


$ cat nginx-rc.yaml apiVersion: v1 kind: ReplicationController metadata:   name: nginx-controller spec:   replicas: 2   selector:     name: nginx   template:     metadata:       labels:         name: nginx     spec:       containers:         - name: nginx           image: nginx           ports:             - containerPort: 80

咱们定义了一个nginx pod复制器,复制份数为2,咱们使用nginx docker镜像。
执行下面的操做建立nginx pod复制器:


$  kubectl -s http://192.168.0.201:8080 create -f nginx-rc.yaml

因为kubernetes要去gcr.io下载gcr.io/google_containers/pause镜像,而后下载nginx镜像,因此所建立的Pod须要等待一些时间才能处于running状态。


$  kubectl -s http://192.168.0.201:8080 get podsNAME                     READY     REASON    RESTARTS   AGEnginx-controller-6zr34   1/1       Running   0          48mnginx-controller-njlgt   1/1       Running   0          48m

咱们可使用describe 命令查看pod所分到的节点:


$  $ kubectl -s http://192.168.0.201:8080 describe pod nginx-controller-6zr34 2>/dev/null | grep Node:Node:                                192.168.0.203/192.168.0.203$ kubectl -s http://192.168.0.201:8080 describe pod nginx-controller-njlgt 2>/dev/null | grep Node:Node:                                192.168.0.201/192.168.0.201

从上表能够看出,这个复制器启动了两个Pod,分别运行在192.168.0.201和203代理节点主机上。

部署节点内部可访问的nginx service

Service的type有ClusterIP和NodePort之分,缺省是ClusterIP,这种类型的Service只能在集群内部访问。下表是本文用的配置文件:


$ cat nginx-service-clusterip.yaml apiVersion: v1 kind: Service metadata:   name: nginx-service-clusterip spec:   ports:     - port: 8001       targetPort: 80       protocol: TCP   selector:     name: nginx

执行下面的命令建立service:


$ kubectl -s http://192.168.0.201:8080 create -f ./nginx-service-clusterip.yaml  services/nginx-service $ kubectl -s http://192.168.0.201:8080 get serviceNAME                      LABELS                                    SELECTOR     IP(S)           PORT(S)kubernetes                component=apiserver,provider=kubernetes   <none>       192.168.3.1     443/TCPnginx-service-clusterip   <none>                                    name=nginx   192.168.3.91   8001/TCP

验证service的可访问性:
上面的输出告诉咱们这个Service的Cluster IP是192.168.3.91,端口是8001。下面咱们验证这个PortalNet IP的工做状况:


$ ssh 192.168.0.202 curl -s 192.168.3.91:8001  <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style>     body {         width: 35em;         margin: 0 auto;         font-family: Tahoma, Verdana, Arial, sans-serif;     } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p>  <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p>  <p><em>Thank you for using nginx.</em></p> </body> </html>

从前面部署复制器的部分咱们知道nginx Pod运行在201和203节点上。上面咱们特地从202代理节点上访问咱们的服务来体现Service Cluster IP在全部集群代理节点的可到达性。

部署外部可访问的nginx service

下面咱们建立NodePort类型的Service,这种类型的Service在集群外部是能够访问。下表是本文用的配置文件:


$ cat nginx-service-nodeport.yaml apiVersion: v1 kind: Service metadata:   name: nginx-service-nodeport spec:   ports:     - port: 8000      targetPort: 80       protocol: TCP   type: NodePort  selector:     name: nginx

执行下面的命令建立service:


$ kubectl -s http://192.168.0.201:8080 create -f ./nginx-service-nodeport.yaml  services/nginx-service-nodeport $ kubectl -s http://192.168.0.201:8080 get serviceNAME                      LABELS                                    SELECTOR     IP(S)          PORT(S)kubernetes                component=apiserver,provider=kubernetes   <none>       192.168.3.1    443/TCPnginx-service-clusterip   <none>                                    name=nginx   192.168.3.91   8001/TCPnginx-service-nodeport    <none>                                    name=nginx   192.168.3.84   8000/TCP

使用下面的命令得到这个service的节点级别的端口:


$ kubectl -s http://192.168.0.201:8080 describe service nginx-service-nodeport 2>/dev/null | grep NodePortType:                        NodePortNodePort:                <unnamed>        32606/TCP

验证service的可访问性:
上面的输出告诉咱们这个Service的节点级别端口是32606。下面咱们验证这个Service的工做状况:


$ curl 192.168.0.201:32606  <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style>     body {         width: 35em;         margin: 0 auto;         font-family: Tahoma, Verdana, Arial, sans-serif;     } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p>  <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p>  <p><em>Thank you for using nginx.</em></p> </body> </html>
代理节点上的IP tables规则解析

下面的图是IPTables中流量通过的table和chain。

能够看出,Kubernetes在nat表中插入了下面四条chain:
1.  KUBE-PORTALS-CONTAINER
这个chain主要是处理全部service对象的cluster IP和port到kube-proxy本地端口的映射。好比下面规则:


-A KUBE-PORTALS-CONTAINER -d 192.168.3.84/32 -p tcp -m comment --comment "default/nginx-service-nodeport:" -m tcp --dport 8000 -j REDIRECT --to-ports 43981

就是为nginx-service-nodeport服务的Cluster IP准备的。其中192.168.3.84/32是该服务得到的Cluster IP,端口8000是其在定义文件中指定的spec.ports.port。43981则是kube-proxy为这个service分配的本地端口。规则的意思是到192.168.3.84:8000的流量重定向到43981。
2.  KUBE-NODEPORT-CONTAINER
这条chain上则串连着类型为NodePort的service的NodePort规则。好比下面规则:

-A KUBE-NODEPORT-CONTAINER -p tcp -m comment --comment "default/nginx-service-nodeport:" -m tcp --dport 32606 -j REDIRECT --to-ports 43981

就是为nginx-service-nodeport服务的NodePort 32606准备的。意思是访问本地32606端口的流量从新定向到43981,后者是kube-proxy为这个service分配的本地端口。
3.  KUBE-PORTALS-HOST
这条chain上也关联着各个service的Cluster IP和Port的规则,好比:


-A KUBE-PORTALS-HOST -d 192.168.3.84/32 -p tcp -m comment --comment "default/nginx-service-nodeport:" -m tcp --dport 8000 -j DNAT --to-destination 192.168.0.201:43981

这条规则是和KUBE-PORTALS-CONTAINER相似的,只不过流量来自于本地进程。
4.  KUBE-NODEPORT-HOST
这条chain上则关联着类型为NodePort的service的NodePort规则。好比下面规则:

-A KUBE-NODEPORT-HOST -p tcp -m comment --comment "default/nginx-service-nodeport:" -m tcp --dport 30975 -j DNAT --to-destination 192.168.0.201:43981

这条规则是和KUBE-NODEPORT-CONTAINER相似的,只不过流量来自于本地进程。

总结

笔者认为Docker已经不是仅表明容器自己,而是一组以应用部署为中心的技术,产品和最佳实践生态系统。Kubernetes以其出身,文档的成熟度,社区的支持在这个生态系统中表现得比较突出。在部署Kubernetes时,咱们首先要理解Kubernetes的组件结构,它们有哪些角色,各个角色的做用是什么和它们之接的通讯。在应用部署时,了解Kubernetes的应用模型是很是重要的。笔者认为复制器和Service的概念是Kubernetes模型的核心,复制器和Service共同完成了应用的高可用性要求。最后本文以一个简单的nginx服务来展现了复制器和Service的使用,特别经过对Service的cluster IP和NodePort的分析,使得读者可以了解这个模型中的网络特性。
最后就是容器技术的选型,本文使用Docker做为容器,其实Kubernetes也支持CoreOS的rkt容器。kubelet的参数--container_runtime用于选择使用的容器技术。(责编/周建丁)
做者简介:龚永生,九州云架构师。多年 Linux系统开发,J2EE产品和云计算相关技术研发经验。目前活跃在OpenStack社区的各个项目上,主要技术方向是虚拟网络项目Neutron,是Neutron项目早期的主要贡献者之一。
相关文章
相关标签/搜索