Kubernetes 是一款开源的项目,管理 Linux 容器集群,并可将集群作为一个单一的系统来对待。其可跨多主机来管理和运行 Docker 容器、提供容器的定位、服务发现以及复制控制。它由 Google 发起,现在得到了如微软、红帽、IBM 和Docker 等众多厂商的支持。
Google 使用容器技术有着超过十年的历史,每周要启动超过2 亿台容器。通过Kubernetes,Google 分享了他们关于容器的专业经验,即创建大规模运行容器的开放平台。
一旦用户开始使用Docker 容器,那么问题就来了,一、如何跨多个Docker 主机扩展和启动容器,且能够在主机间平衡这些容器。二、还需要高度抽象出API 来定义如何从逻辑上组织容器、定义容器池、负载均衡以及关联性。该项目就是为了解决这两个问题而生的。
Kubernetes 仍然处于早期阶段,这也就意味着会有很多新的变化进入到该项目,目前还仅有比较简单的实例,仍需要加入新的功能,但是它正在一步一步地平稳发展,也得到了很多大公司的支持,前途无量。
Kubernetes 概念
Kubernetes 的架构被定义为由一个 master 服务器和多个 minons 服务器组成。命令行工具连接到 master 服务器的 API 端点,其可以管理和编排所有的 minons 服务器,Docker 容器接收来自 master 服务器的指令并运行容器。
- Master:Kubernetes API 服务所在,多 Master 的配置仍在开发中。
- Minons:每个具有 Kubelet 服务的 Docker 主机,Kubelet 服务用于接收来自 Master 的指令,且管理运行容器的主机。
- Pod :定义了一组绑在一起的容器,可以部署在同一 Minons 中,例如一个数据库或者是 web 服务器。
- Replication controller :定义了需要运行多少个 Pod 或者容器。跨多个 minons 来调度容器。
- Service:定义了由容器所发布的可被发现的服务/端口,以及外部代理通信。服务会映射端口到外部可访问的端口,而所映射的端口是跨多个 minons 的 Pod 内运行的容器的端口。
- kubecfg:命令行客户端,连接到 master 来管理 Kubernetes。
(点击图片可放大显示)
Kubernetes 由状态所定义,而不是进程。当你定义了一个 pod 时,Kubernetes 会设法确保它会一直运行。如果其中的某个容器挂掉了,Kubernetes 会设法再启动一个新的容器。如果一个复制控制器定义了 3 份复制,Kubernetes 会设法一直运行这 3 份,根据需要来启动和停止容器。
本文中所用的例子应用是 Jenkins 持续集成服务,Jenkins 是典型的通过主从服务来建立分布式的工作任务的例子。Jenkins 由 jenkins swarm 插件来配置,运行一个 jenkins 主服务和多个 jenkins 从服务,所有的 Jenkins 服务都以 Docker 容器的方式跨多个主机运行。swarm 从服务在启动时连接到 Jenkins 主服务,然后就可以运行 Jenkins 任务了。例子中使用的配置文件可以从Github 上下载到,而该Docker 镜像可以从 casnchez/jenkins-swarm 获取,对于 Jenkins 主服务来说,可以通过 swarm 插件来扩展官方 Jenkins 镜像。对于 jenkins 从服务来说,可以从 csanchez/jenkins-swarm-slave 获取,它只是在 JVM 容器中运行了 jenkins 从服务而已。
创建 Kubernetes 集群
Kubernetes 提供了多个操作系统和云/虚拟化提供商下创建集群的脚本,有 Vagrant(用于本地测试)、Google Compute Engine、Azure、Rackspace 等。
本文所实践的例子就是运行在 Vagrant 之上的本地集群,使用 Fedora 作为操作系统,遵照的是官方入门指南,测试的 Kubernetes 版本是 0.5.4。取代了默认的 3 个 minion(Docker 主机),而是使用了 2 个 minion,两台主机就足以展示 Kubernetes 的能力了,三台有点浪费。
当你下载了 Kubernetes ,然后将至解压后,即可在本地的目录下运行这些示例了。初学者创建集群仅需要一个命令,即./cluster/kube-up.sh。
$ export KUBERNETES_PROVIDER=vagrant $ export KUBERNETES_NUM_MINIONS=2 $ ./cluster/kube-up.sh
获取示例配置文件:
$ git clone https://github.com/carlossg/kubernetes-jenkins.git
集群创建的快慢取决于机器的性能和内部的带宽。但是其最终完成不能有任何的错误,而且它仅需要运行一次。
命令行工具
和 Kubernetes 交互的命令行工具叫做 kubecfg,此脚本位于 cluster/kubecfg.sh。
为了检验我们刚才创建的两个 minons 已经启动并运行了,只需运行 kubecfg list minions 命令即可,它的结果会显示 Vagrant 的两台虚拟机。
$ ./cluster/kubecfg.sh list minions Minion identifier ---------- 10.245.2.2 10.245.2.3
Pod
在 Kubernetes 的术语中,Jenkins 主服务被定义为一个 pod 。在一个 pod 中可以指定多个容器,这些容器均部署在同一 Docker 主机中,在一个 pod 中的容器的优势在于可以共享资源,例如存储卷,而且使用相同的网络命名空间和IP 地址。默认的卷是空的目录,类型为emptyDir,它的生存时间就是pod 的生命周期,而并非是指定的容器,所以如果一个容器失效了,但是其持久性的存储仍然在。另外一个卷的类型是hostDir,它是将主机的一个目录挂载到容器中。
在这个具体的Jenkins 示例中,我们所创建的pod 是两个容器,分别是Jenkins 主服务和MySQL,前者作为实例,后者作为数据库。然而我们只需要关心jenkins 主服务容器即可。
为了创建一个Jenkins pod,我们运行定义了Jenkins 容器pod 的kubecfg,使用Docker 镜像csanchez/jenkins-swarm,为了能够访问Jenkins 的Web 界面和从服务的API,我们将主机的端口8080 和50000 映射到容器,以及将/var/jenkins_home 挂载为卷。读者可从 Github 下载到示例代码。
Jenkins Web 界面的 pod(pod.json)的定义如下:
{ "id": "jenkins", "kind": "Pod", "apiVersion": "v1beta1", "desiredState": { "manifest": { "version": "v1beta1", "id": "jenkins", "containers": [ { "name": "jenkins", "image": "csanchez/jenkins-swarm:1.565.3.3", "ports": [ { "containerPort": 8080, "hostPort": 8080 }, { "containerPort": 50000, "hostPort": 50000 } ], "volumeMounts": [ { "name": "jenkins-data", "mountPath": "/var/jenkins_home" } ] } ], "volumes": [ { "name": "jenkins-data", "source": { "emptyDir": {} } } ] } }, "labels": { "name": "jenkins" } }
然后使用下面命令来创建:
$ ./cluster/kubecfg.sh -c kubernetes-jenkins/pod.json create pods Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- jenkins csanchez/jenkins-swarm:1.565.3.3 <unassigned> name=jenkins Pending</unassigned>
这需要等待一段时间,具体时间的长短要视你的网络而定,因为它会从 Docker Hub 上下载 Docker 镜像到 minion,我们可以查看它的状态,以及在那个 minion 中启动了。
$ ./cluster/kubecfg.sh list pods Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- jenkins csanchez/jenkins-swarm:1.565.3.3 10.0.29.247/10.0.29.247 name=jenkins Running
如果我们使用 SSH 登录到 minion 中,此 minion 即是 pod 被分配到的 minion-1 或 minion-2,我们就可以看到 Docker 按照所定义的那样启动起来了。其中还包括了用于 Kubernetes 内部管理的容器(kubernetes/pause 和 google/cadvisor)。
$ vagrant ssh minion-2 -c "docker ps" CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7f6825a80c8a google/cadvisor:0.6.2 "/usr/bin/cadvisor" 3 minutes ago Up 3 minutes k8s_cadvisor.b0dae998_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0.default.file_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0_28df406a 5c02249c0b3c csanchez/jenkins-swarm:1.565.3.3 "/usr/local/bin/jenk 3 minutes ago Up 3 minutes k8s_jenkins.f87be3b0_jenkins.default.etcd_901e8027-759b-11e4-bfd0-0800279696e1_bf8db75a ce51fda15f55 kubernetes/pause:go "/pause" 10 minutes ago Up 10 minutes k8s_net.dbcb7509_0d38f5b2-759c-11e4-bfd0-0800279696e1.default.etcd_0d38fa52-759c-11e4-bfd0-0800279696e1_e4e3a40f e6f00165d7d3 kubernetes/pause:go "/pause" 13 minutes ago Up 13 minutes 0.0.0.0:8080->8080/tcp, 0.0.0.0:50000->50000/tcp k8s_net.9eb4a781_jenkins.default.etcd_901e8027-759b-11e4-bfd0-0800279696e1_7bd4d24e 7129fa5dccab kubernetes/pause:go "/pause" 13 minutes ago Up 13 minutes 0.0.0.0:4194->8080/tcp k8s_net.a0f18f6e_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0.default.file_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0_659a7a52
还有,我们一旦拿到了容器的 ID,就可以通过如 vagrant ssh minion-1 -c "docker logs cec3eab3f4d3"这样的命令来查看容器的日志了。
我们也可以访问 Jenkins 的 Web 界面,至于要访问的 URL 是 http://10.245.2.2:8080/ 还是 http://10.0.29.247:8080/,要看用到了那个 minion。
服务发现
Kubernetes 支持定义服务,一种为容器使用发现和代理请求到合适的 pod 的方法。下面示例使用了 service-http.json 文件来创建一个服务,其将 id 为 jenkins 指向了贴有标签为 name=jenkins 的 pod。而标签是在如上面 pod 的定义中所声明的。且将端口 8888 重定向到容器的 8080 。
{ "id": "jenkins", "kind": "Service", "apiVersion": "v1beta1", "port": 8888, "containerPort": 8080, "selector": { "name": "jenkins" } }
使用 kubecfg 来创建服务:
$ ./cluster/kubecfg.sh -c kubernetes-jenkins/service-http.json create services Name Labels Selector IP Port ---------- ---------- ---------- ---------- ---------- jenkins name=jenkins 10.0.29.247 8888
每个服务都会被分配一个唯一的 IP 地址,且此 IP 地址会伴随服务的整个生命周期。如果我们有多个 pod 匹配服务的定义,服务就会在所有它们之上提供负载均衡。
服务的另外一个特性是可以为由 Kubernetes 来运行的容器设置一些环境变量,使其可以连接到该服务容器,这和运行已链接的 Docker 容器有些类似,它可以帮助我们从若干从服务中找到 Jenkins 主服务。
JENKINS_PORT='tcp://10.0.29.247:8888' JENKINS_PORT_8080_TCP='tcp://10.0.29.247:8888' JENKINS_PORT_8080_TCP_ADDR='10.0.29.247' JENKINS_PORT_8080_TCP_PORT='8888' JENKINS_PORT_8080_TCP_PROTO='tcp' JENKINS_SERVICE_PORT='8888' SERVICE_HOST='10.0.29.247'
在此示例中,我们还需要打开端口 50000,Jenkins swarm 插件需要用这个端口。我们创建另外一个 service-slave.json 文件,这样 Kubernetes 就可以重定向端口到 Jenkins 服务容器了。
{ "id": "jenkins-slave", "kind": "Service", "apiVersion": "v1beta1", "port": 50000, "containerPort": 50000, "selector": { "name": "jenkins" } }
再次使用 kubecfg 来创建:
$ ./cluster/kubecfg.sh -c kubernetes-jenkins/service-slave.json create services Name Labels Selector IP Port ---------- ---------- ---------- ---------- ---------- jenkins-slave name=jenkins 10.0.86.28 50000
列出所有可用的已经定义的服务,包括一些 Kubernetes 内部的服务:
$ ./cluster/kubecfg.sh list services Name Labels Selector IP Port ---------- ---------- ---------- ---------- ---------- kubernetes-ro component=apiserver,provider=kubernetes 10.0.22.155 80 kubernetes component=apiserver,provider=kubernetes 10.0.72.49 443 jenkins name=jenkins 10.0.29.247 8888 jenkins-slave name=jenkins 10.0.86.28 50000
复制控制器
复制控制器允许在多个 minion 中运行多个 pod。Jenkins 的从服务以此方式来运行,可以确保一直有一个运行 Jenkins 任务的从服务池。
创建 replication.json,所定义的内容如下:
{ "id": "jenkins-slave", "apiVersion": "v1beta1", "kind": "ReplicationController", "desiredState": { "replicas": 1, "replicaSelector": { "name": "jenkins-slave" }, "podTemplate": { "desiredState": { "manifest": { "version": "v1beta1", "id": "jenkins-slave", "containers": [ { "name": "jenkins-slave", "image": "csanchez/jenkins-swarm-slave:1.21", "command": [ "sh", "-c", "/usr/local/bin/jenkins-slave.sh -master http://$JENKINS_SERVICE_HOST:$JENKINS_SERVICE_PORT -tunnel $JENKINS_SLAVE_SERVICE_HOST:$JENKINS_SLAVE_SERVICE_PORT -username jenkins -password jenkins -executors 1" ] } ] } }, "labels": { "name": "jenkins-slave" } } }, "labels": { "name": "jenkins-slave" } }
podTemplate 一节允许和 pod 的定义进行一样的配置。在此示例中,我们打算让 Jenkins 从服务自动地连接到 Jenkins 主服务,而不是利用 Jenkins 主服务的多播发现。要实现此想法,我们需要执行 jenkins-slave.sh 命令,指定参数 -master,从而实现在 Kubernetes 中让从服务连接到主服务。注意,上述配置文件中,我们使用了 Kubernetes 所提供的为 Jenkins 服务定义的环境变量(JENKINS_SERVICE_HOST 和 JENKINS_SERVICE_PORT)。此方法中镜像的命令覆盖了容器的配置,为的是利用已经下载好的镜像。这也同样可以在 pod 的定义中去实现。
使用 kubecfg 来创建副本:
$ ./cluster/kubecfg.sh -c kubernetes-jenkins/replication.json create replicationControllers Name Image(s) Selector Replicas ---------- ---------- ---------- ---------- jenkins-slave csanchez/jenkins-swarm-slave:1.21 name=jenkins-slave 1
现在执行 pod 列表,可以看到新的 pod 已经创建,其数量是由我们刚刚所定义的复制控制器所决定的。
$ ./cluster/kubecfg.sh list pods Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Running 07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Pending
第一次运行的 jenkins-swarm-slave 镜像是 minion 从 Docker 仓库下载下来的,但是一段时间之后(这取决于你的互联网连接),Jenkins 从服务会自动连接到 Jenkins 主服务。登录到 Jenkins 从服务所在的服务器,docker ps 会显示出运行中的容器和 Docker 的日志,这能够帮助你来调试容器启动中出现的问题。
$ vagrant ssh minion-1 -c "docker ps" CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 870665d50f68 csanchez/jenkins-swarm-slave:1.21 "/usr/local/bin/jenk About a minute ago Up About a minute k8s_jenkins-slave.74f1dda1_07651754-4f88-11e4-b01e-0800279696e1.default.etcd_11cac207-759f-11e4-bfd0-0800279696e1_9495d10e cc44aa8743f0 kubernetes/pause:go "/pause" About a minute ago Up About a minute k8s_net.dbcb7509_07651754-4f88-11e4-b01e-0800279696e1.default.etcd_11cac207-759f-11e4-bfd0-0800279696e1_4bf086ee edff0e535a84 google/cadvisor:0.6.2 "/usr/bin/cadvisor" 27 minutes ago Up 27 minutes k8s_cadvisor.b0dae998_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0.default.file_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0_588941b0 b7e23a7b68d0 kubernetes/pause:go "/pause" 27 minutes ago Up 27 minutes 0.0.0.0:4194->8080/tcp k8s_net.a0f18f6e_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0.default.file_cadvisormanifes12uqn2ohido76855gdecd9roadm7l0_57a2b4de
复制控制器可以自动的调整任何想要的副本数量:
$ ./cluster/kubecfg.sh resize jenkins-slave 2
再次列出 pod 的话,可以看到每个副本是在哪里运行的。
$ ./cluster/kubecfg.sh list pods Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- 07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Running a22e0d59-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.3/10.245.2.3 name=jenkins-slave Pending jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Running
调度
目前默认的调度是随机的,但是基于资源的调度很快就将实现了。在写作本文的时候,基于内存和 CPU 利用率的调度还有一些没有修复的问题。还有整合 Apache Mesos 的调度也在紧锣密鼓的进行中。Apache Mesos 是一款分布式系统的框架,提供了资源管理的 API,和跨整个数据中心或云环境的调度。
自愈
使用 Kubernetes 的一大好处就是其能够自动管理和修复容器。
如果运行 Jenkins 服务的容器由于某些原因宕机了,比如说进程崩溃了,那么 Kubernetes 就会得到通知,并会在几秒钟之后创建一个新的容器。
$ vagrant ssh minion-2 -c 'docker kill `docker ps | grep csanchez/jenkins-swarm: | sed -e "s/ .*//"`' 51ba3687f4ee $ ./cluster/kubecfg.sh list pods Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Failed 07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Running a22e0d59-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.3/10.245.2.3 name=jenkins-slave Running
稍后再执行 list pod 命令,一般不会超过 1 分钟,会看到如下内容:
Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Running 07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Running a22e0d59-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.3/10.245.2.3 name=jenkins-slave Running
将 Jeknkis 的数据目录以卷的形式运行,我们保证了在容器宕机之后仍然能够保留数据,所以不会丢失任何 Jenkins 的任务或者是已经创建好的数据。而且因为 Kubernetes 在每个 minion 都代理了服务,Jenkins 从服务会自动连接到 Jenkins 主服务,而根本无须关心它是在哪里运行的!而且 Jenkins 从服务容器宕掉,系统也会自动创建新的容器,服务发现会将之自动加入到 Jenkins 服务池中。
如果发生了更加严重的情况,比如 minion 挂掉了,Kubernetes 目前还没能提供将现有容器重新调度到其它仍在运行的 minion 中的能力,它仅仅会显示某个 pod 失效,如下所示:
$ vagrant halt minion-2 ==> minion-2: Attempting graceful shutdown of VM... $ ./cluster/kubecfg.sh list pods Name Image(s) Host Labels Status ---------- ---------- ---------- ---------- ---------- jenkins csanchez/jenkins-swarm:1.565.3.3 10.245.2.3/10.245.2.3 name=jenkins Failed 07651754-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.2/10.245.2.2 name=jenkins-slave Running a22e0d59-4f88-11e4-b01e-0800279696e1 csanchez/jenkins-swarm-slave:1.21 10.245.2.3/10.245.2.3 name=jenkins-slave Failed
清理
kubecfg 还提供了一些命令来停止和删除复制控制器、pod、以及服务等。
要停止复制控制器,设置复制数为 0,所有 Jenkins 从服务的容器都会被终止:
$ ./cluster/kubecfg.sh stop jenkins-slave
删除它:
$ ./cluster/kubecfg.sh rm jenkins-slave
删除 Jenkins 服务 Pod,Jenkins 主服务的容器会被终止:
$ ./cluster/kubecfg.sh delete pods/jenkins
删除服务:
$ ./cluster/kubecfg.sh delete services/jenkins $ ./cluster/kubecfg.sh delete services/jenkins-slave
总结
Kubernetes 还是一个尚处于早期的项目,但是极有希望来管理 Docker 的跨服务器部署以及简化执行长时间运行和分布式的 Docker 容器。通过抽象基础设施概念,定义状态而不是进程,它提供了集群的简单定义,包括脱离管理的自我修复能力。简而言之,Kubernetes 让 Docker 的管理更加的轻松。
关于作者
Carlos Sanchez在自动化、软件开发质量、QA 以及运维等方面有超过 10 年的经验,范围涉及从构建工具、持续集成到部署自动化,DevOps 最佳实践,再到持续交付。他曾经为财富 500 强的公司实施过解决方案,在多家美国的创业公司工作过,最近任职的公司叫做 MaestroDev,这也是他一手创建的公司。Carlos 曾经在世界各地的各种技术会议上作演讲,包括 JavaOne、EclipseCON、ApacheCON、JavaZone、Fosdem 和 PuppetConf。他非常积极的参与开源,是 Apache 软件基金会的成员,同时也是其它一些开源团体的成员,为很多个开源项目做过贡献,比如 Apache Maven、Fog 和 Puppet。
查看英文原文: Scaling Docker with Kubernetes
感谢夏雪对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群)。
评论