使用Kubernetes来管理Docker扩展

jopen 9年前

来自:http://www.infoq.com/cn/articles/scaling-docker-with-kubernetes

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来管理Docker扩展

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           name=jenkins        Pending

这需要等待一段时间,具体时间的长短要视你的网络而定,因为它会从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

使用Kubernetes来管理Docker扩展

调度

目前默认的调度是随机的,但是基于资源的调度很快就将实现了。在写作本文的时候,基于内存和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的 管理更加的轻松。

关于作者

使用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