使用 Kubernetes 来管理 Docker 扩展

阅读数:10857 2015 年 10 月 26 日

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

调度

目前默认的调度是随机的,但是基于资源的调度很快就将实现了。在写作本文的时候,基于内存和 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 读者交流群)。

收藏

评论

微博

发表评论

注册/登录 InfoQ 发表评论