【摘要】KubernetesGoogle开源的Docker容器集群管理系统,为容器化的应用提供资源调度部署运行服务发现扩容缩容等整一套功能。本文介绍了kubernetes的重要概念,并通过实例的示例解释了如何应用kubernetes管理docker集群。

因操作系统不同、应用场景不同kubernetes的使用方法有不同,本文只介绍其中一种笔者实践过的切实可行的方法,旨在使读者快速了解Kubernetes,对其有直观的感受。本文以单机版举例,下面的例子都在同一台物理结点上执行,多结点的情况需要解决网络问题,要引入flannel,我们在另一篇中介绍。

1   启动

前置条件:已经安装好docker

前面的安装完成之后,下面我们启动kubernetes

2.1 首先启动ETCD

shell下敲(加sudo):将“10.43.86.110”换成你的linux机器ip

etcd  --initial-advertise-peer-urls http://10.43.86.110:7001  --advertise-client-urls http://10.43.86.110:4001 --listen-peer-urls  http://0.0.0.0:7001 --listen-client-urls http://0.0.0.0:4001

Etcd启动之后大概是这样:

etcd  --initial-advertise-peer-urls http://10.43.86.110:7001  --advertise-client-urls http://10.43.86.110:4001 --listen-peer-urls http://0.0.0.0:7001  --listen-client-urls http://0.0.0.0:4001

2016-01-16 13:13:10.155368 I  | etcdmain: etcd Version: 2.2.1

2016-01-16 13:13:10.156616 I  | etcdmain: Git SHA: 75f8282

2016-01-16 13:13:10.157180 I  | etcdmain: Go Version: go1.5.1

2016-01-16 13:13:10.158041 I  | etcdmain: Go OS/Arch: linux/amd64

2016-01-16 13:13:10.158220 I  | etcdmain: setting maximum number of CPUs to 1, total number of available  CPUs is 1

2016-01-16 13:13:10.158820 W  | etcdmain: no data-dir provided, using default data-dir ./default.etcd

2016-01-16 13:13:10.177120 N  | etcdmain: the server is already initialized as member before, starting as  etcd member...

2016-01-16 13:13:10.177572 I  | etcdmain: listening for peers on http://0.0.0.0:7001

2016-01-16 13:13:10.187725 I  | etcdmain: listening for client requests on http://0.0.0.0:4001

2016-01-16 13:13:11.749759 I  | etcdserver: recovered store from snapshot at index 760076

2016-01-16 13:13:11.763795 I  | etcdserver: name = default

2016-01-16 13:13:11.786005 I  | etcdserver: data dir = default.etcd

2016-01-16 13:13:11.790356 I  | etcdserver: member dir = default.etcd/member

2016-01-16 13:13:11.790411 I  | etcdserver: heartbeat = 100ms

2016-01-16 13:13:11.790424 I  | etcdserver: election = 1000ms

2016-01-16 13:13:11.790436 I  | etcdserver: snapshot count = 10000

2016-01-16 13:13:11.791149 I  | etcdserver: advertise client URLs = http://10.43.86.110:4001

2016-01-16 13:13:11.791360 I  | etcdserver: loaded cluster information from store: <nil>

2016-01-16 13:13:14.483279 I  | etcdserver: restarting member ce2a822cea30bfca in cluster 7e27652122e8b2ae  at commit index 769230

2016-01-16 13:13:14.530205 I  | raft: ce2a822cea30bfca became follower at term 13

2016-01-16 13:13:14.561409 I  | raft: newRaft ce2a822cea30bfca [peers: [ce2a822cea30bfca], term: 13,  commit: 769230, applied: 760076, lastindex: 769230, lastterm: 13]

2016-01-16 13:13:14.632877 I  | etcdserver: starting server... [version: 2.2.1, cluster version: 2.2]

2016-01-16 13:13:18.150987 I  | raft: ce2a822cea30bfca is starting a new election at term 13

2016-01-16 13:13:18.211244 I  | raft: ce2a822cea30bfca became candidate at term 14

2016-01-16 13:13:18.221462 I  | raft: ce2a822cea30bfca received vote from ce2a822cea30bfca at term 14

2016-01-16 13:13:18.292336 I  | raft: ce2a822cea30bfca became leader at term 14

2016-01-16 13:13:18.318144 I  | raft: raft.node: ce2a822cea30bfca elected leader ce2a822cea30bfca at term  14

2016-01-16  13:13:18.365497 I | etcdserver: published {Name:default  ClientURLs:[http://10.43.86.110:4001]} to cluster 7e27652122e8

 

2.2 接下来启动kubernetes

Kubernetes有两种组件:masterslaveMaster是控制部分,上面运行apiservercontrollerscheduler三个组件。Slave是实际提供资源的部分,上面运行kubeletproxy两个组件,可以有多个slave。可以把两部分都启动在同一个物理结点上,但是逻辑上要明确他们是不同的。不要问我master挂了怎么办,我还没研究这块。

下面我们按顺序启动master上的三个组件,

2.2.1  先启动apiserver。

Sudo kube-apiserver  --etcd-servers=http://10.43.86.110:4001  --service-cluster-ip-range=123.123.123.0/24 --address=0.0.0.0

解释一下这个命令,比较重要:

--etcd-servers参数是刚才启动ETCD时的--advertise-client-urls参数,表示告诉kubernetes去这个地方找ETCD

--service-cluster-ip-range=123.123.123.0/24这个参数表示kubernetes分配的service集群的地址段范围。请先记下这个地址,至于service是什么,后面还会讲到,会看到这个地址。目前我们先把kubernetes启动起来。

更多的命令参数可以敲Sudo kube-apiserver –help 研究研究。

2.2.2  接下来是controller:

sudo  kube-controller-manager --master=10.43.86.110:8080

这里的 --master=10.43.86.110:8080地址就是指apiserver的地址。如果出现有如下错误信息,没有设置cloudprovider可忽略,不影响使用。

controllermanager.go:198]Failed to start service controller: ServiceController should not be run withouta cloudprovider

 

2.2.3  然后是scheduler

sudo  kube-scheduler --master=10.43.86.110:8080

这里的 --master=10.43.86.110:8080地址就是指apiserver的地址。

 

2.2.4  上面就将master的几个组件启动了,下面启动slave的两个组件。

sudo  kubelet --api-servers=10.43.86.110:8080  --pod-infra-container-image=gcr.io/google_containers/pause

sudo  kube-proxy --master=10.43.86.110:8080

这里注意: kubelet 启动pod时要求使用一个pause镜像,要连google,由于被墙,所以请自己上网下一个镜像的包,

使用  docker load < 镜像包名称命令将镜像解压下来。可以使用docker images查看一下是否成功。看看是否有一个镜像为gcr.io/google_containers/pause

 

至此,kubernetes就启动完成了。

 

3   Kubernetes的使用

本节介绍kubernetes怎么使用,即怎么管理docker集群。要介绍这个,必须了解kubernetes的几个术语:podlabelservice,以及两个重要的yaml文件。下面逐一介绍。

3.1 Node:

先用node这个词表示系统运行的物理结点,node可以是linux机器,也可以是IaaS上的一台虚拟机。

3.2 Pod:

Podkubernetes引入的概念,是kubernetes的最基本操作单元。Pod里面包含一个或者多个容器(dockercontainer)。Pod中的多个容器应该是紧耦合的,技术上可以不是紧耦合,但是我现在看这样不会带来任何好处,只有麻烦。可以将原来opentack上面的VM同现在的Pod对等起来考虑问题,都是包含一些执行程序、提供业务,都是调度的单元,但是他们不一样。 

每个Pod拥有自己的ip地址,Pod内的容器共享网络,通过localhost就可以互相通信。这时候Pod地址、Pod内的容器地址都是一个意思。Docker 自己没有解决容器间的通信问题。现在有几个问题:

1.   Pod的地址即docker容器地址,一般docker启动时才确定,怎么让其他进程知道

2.   Pod之间怎么通信

3.   多个Pod怎么同时提供服务?

下面讲service时会解决这些问题。

3.3 Label:

label是一个核心概念,kubernetes中的各种对象PodserviceRC等都有各自的label。这样在其他的对象中的label selector中就可以指定label来确定操作哪些对象。可能看了这个还是不明白,等下看到例子就懂了。

3.4 Service:

一个service是一组提供相同服务的Pod的对外访问接口。Service是个虚拟的东西,他依靠Pod为其提供支持,一个service需要一组提供相同服务的PodService通过label标签来确定选择哪些Pod作为他的支持。

Service有两种:集群内service对外暴露service。集群内service会在kubernetescluster IP Range池中分配一个IP地址(查上面--service-cluster-ip-range这个参数看一下),这个ip地址在集群内的Pod都可以访问,但是对外不可见。每次启动一个service时,kubernetes都在这个ip-range里面随机的为service分配ip地址。

对外保留的Service需要明确 type:NodePort,这样系统就会在Node上面打开一个主机上的真实端口,客户就能够在集群外访问这个service 了。

ServicePodlabel之间的关系是这样的:

 

 

1.1 RC(replication controller).yaml

简单的说,RC文件定义了docker集群中希望启动多少个、什么样的PodKubernetescontroller通过RC文件完成Pod的创建、监控、启停等。

1.2 Service.yaml

Service文件定义了服务的名称、类型、端口、寻找哪些pod支持此服务。

下面通过例子讲这两个文件。

1.3 举例:

下面通过这个例子看一下NodePodServiceLabel各自是什么以及他们之间的关系。假设有两个需求非常专业:一个是将字符串小写改为大写upper,一个是将字符串反序reversi(我们假设这两个东西很不容易做,我将他们做成了独立的服务)。现在,我想做一个应用,将一个字符串变大小并反序,那么需要依次使用upperreversi两个服务。

最下面的stringservice使用上面的upper-service reversi-service,完成这个业务。

1.1.1  两个文件

1.1.1.1   以upper-service为例,我们先写一个upper-rc.yaml

 

apiVersion:  v1

kind:  ReplicationController  //RC文件这块就这样写

metadata:

  name : upper  / RC的名字,通过kubectl get rc可以查看当前运行的RC

  labels:

    name : upper // RC的自定义标签

spec:

  replicas:  2  //  指定启动多少个Pod

  selector:

    name : upper   // 管理拥有这个标签的Pod

  template:   // Pod的定义

    metadata:

      labels:

        name : upper/// Pod的标签。两个红色要一致。

    spec:

        containers:  // Pod内运行的容器列表

        - name: upper

          image : upperstring  /// 容器的镜像名称

          ports:

          - containerPort : 8010 //容器需要监听的端口号

 

 

之后,运行sudo kubectlcreate –f upper-rc.yaml 即可创建出两个Pod,每个里面有一个docker容器。可以通过sudo kubectl get rc sudo kubectl get pods来查看执行情况。并通过docker ps命令查看容器的启动情况。有时pod启动的有点慢,要等一会。

 

$  sudo kubectl get rc

CONTROLLER   CONTAINER(S)   IMAGE(S)      SELECTOR     REPLICAS

upper        upper           upperstring   name=upper   2

$  sudo kubectl get pods

upper-6f8ed     1/1        Running     0          3m

upper-mjl0r     1/1        Running     0          3m

可以看到启动了两个upper  Pod,名字-后面是随机的。

 

通过docker ps 查看启动的容器情况:

 

$ docker ps

CONTAINER ID        IMAGE                            COMMAND                  CREATED             STATUS              PORTS               NAMES

d08baa9df450        upperstring                      "/bin/sh -c  '/home/up"   6 minutes ago       Up 6 minutes                            k8s_upper.c0cbee37_upper-mjl0r_default_45bb014d-bea0-11e5-ac25-080027026c8b_4171e732

4496ac505776        gcr.io/google_containers/pause   "/pause"                 6 minutes ago       Up 6 minutes                             k8s_POD.e81ae784_upper-mjl0r_default_45bb014d-bea0-11e5-ac25-080027026c8b_56ba3c8f

可以看到每个Pod对应了两个容器, 一个是upper,另一个是google的pause。

 

 

1.1.1.2   下面写upper-service.yaml

 

apiVersion  : v1

kind:  Service  // Service  文件这块就这样写

metadata:

  name: upper // service 的名字

  labels:

    name: upper   //service的自定义标签

spec:   //详细描述

  ports:

  - port : 8010 // 服务监听的端口号

    targetPort: 8010 // 服务转发到后端的Pod的端口号,要和RC文件对应上

  selector:

    name : upper  // 具有这个标签的Pod,都作为本service的管理范围

 

之后,运行sudo kubectlcreate –f upper-service.yaml 启动起upper这个服务。可以通过之后,运行sudo kubectl get services 查看service的情况。

 

$ sudo kubectl get services

NAME         LABELS                                     SELECTOR     IP(S)             PORT(S)

kubernetes    component=apiserver,provider=kubernetes   <none>       123.123.123.1      443/TCP

upper        name=upper                                 name=upper   123.123.123.122   8010/TCP

 

可以看到有两个service在运行,其中一个是kubernetes自己的,服务的IP地址是123.123.123.1,另一个则是刚刚启动的upperIP地址为123.123.123.122。这个就是我们启动kubernetes时为这个kubernetes集群分配的地址段。

至此,upper这个服务就在这个docker集群中启动了。

 

那么同样的,reversi服务的RC文件和Service文件为:

 

apiVersion:  v1

kind:  ReplicationController

metadata:

  name : reversi

  labels:

    name : reversi

spec:

  replicas: 2

  selector:

    name : reversi

  template:

    metadata:

      labels:

        name : reversi

    spec:

        containers:

        - name: reversi

           image : reversistring

          ports:

           - containerPort : 8005

 

 

 

apiVersion  : v1

kind:  Service

metadata:

  name: reversi

  labels:

    name: reversi

spec:

  ports:

  - port : 8009

    targetPort: 8005

  selector:

     name : reversi   

 

 

1.1.2  对外暴露的service

最后一个string-service有点不一样,他不是集群内service,是需要对客户端暴露的。他的RC文件写法一样,但是service写法有不同。

RC.yaml:

 

apiVersion:  v1

kind:  ReplicationController

metadata:

  name : tcpserver

  labels:

    name : tcpserver

spec:

  replicas: 3

  selector:

    name : tcpserver

  template:

    metadata:

      labels:

        name : tcpserver

    spec:

        containers:

        - name: tcpserver

          image : tcpserver

          ports:

           - containerPort : 8000

 

 

Service.yaml:

 

apiVersion  : v1

kind:  Service

metadata:

  name: tcpserver

  labels:

    name: tcpserver

spec:

  type: NodePort  // service的访问方式,默认为ClusterIP,表示集群内地址。此处设置为NodePort表示使用宿主机的端口,这样可以通过宿主机的IP从外部访问此服务。即这个服务是对外可见的。

  ports:

  - port : 30001

    targetPort: 8000 // 转发到后端的Pod的端口号。

    nodePort: 30002 // 外部通过这个端口号访问此服务

  selector:

     name : tcpserver   

 

 

1.1.3  Service之间的通信

现在,三个service都启动了,并且各自指定了一定数目的Pod为自己提供服务。那么service之间怎么通信,比如string-service需要upper-service提供服务,但是upper-service的地址是启动时随机分配的,string-service如何知道往那个地址发消息?

Kubernetes通过环境变量来解决这个问题。在一个service启动后,kubernetes会在其他的容器里面增加响应的环境变量,记录service的地址。环境变量的命令为服务名_HOST”。比如upper服务启动后,在string-servicepod容器里面就会有一个UPPER_SERVICE_HOSTUPPER_SERVICE_PORT环境变量,通过这个,string-service程序就可以访问upper服务。这个非常重要,我们可以通过dockerinspect 容器ID 来查看一下容器内部的情况,就会看到如下的信息:

 

"Env":  [

            "UPPER_SERVICE_PORT=8010",

"UPPER_SERVICE_HOST=123.123.123.122",

             "TCPSERVER_PORT=tcp://123.123.123.74:30001",

             "TCPSERVER_PORT_30001_TCP_PROTO=tcp",

             "KUBERNETES_PORT_443_TCP=tcp://123.123.123.1:443",

             "KUBERNETES_PORT_443_TCP_PORT=443",

             "KUBERNETES_PORT_443_TCP_ADDR=123.123.123.1",

             "REVERSI_PORT_8009_TCP_PROTO=tcp",

             "KUBERNETES_SERVICE_HOST=123.123.123.1",

            "REVERSI_SERVICE_HOST=123.123.123.86",

             "REVERSI_PORT_8009_TCP=tcp://123.123.123.86:8009",

             "REVERSI_PORT_8009_TCP_PORT=8009",

             "KUBERNETES_SERVICE_PORT=443",

             "KUBERNETES_PORT=tcp://123.123.123.1:443",

            "UPPER_PORT_8010_TCP_ADDR=123.123.123.122",

            "REVERSI_SERVICE_PORT=8009",

             "REVERSI_PORT=tcp://123.123.123.86:8009",

             "TCPSERVER_SERVICE_HOST=123.123.123.74",

             "TCPSERVER_SERVICE_PORT=30001",

             "UPPER_PORT=tcp://123.123.123.122:8010",

             "TCPSERVER_PORT_30001_TCP_PORT=30001",

             "TCPSERVER_PORT_30001_TCP_ADDR=123.123.123.74",

             "UPPER_PORT_8010_TCP_PROTO=tcp",

             "UPPER_PORT_8010_TCP=tcp://123.123.123.122:8010",

            "UPPER_PORT_8010_TCP_PORT=8010",

             "REVERSI_PORT_8009_TCP_ADDR=123.123.123.86",

             "TCPSERVER_PORT_30001_TCP=tcp://123.123.123.74:30001",

             "KUBERNETES_PORT_443_TCP_PROTO=tcp",           

             "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

         ],

 

 

可以看到,这里的环境变量信息,同前面sudokubectl get services命令得到的信息是完全对应的。

现在,我们就可以通过Node主机的地址(公网地址)10.43.86.110 30002端口号来访问这些服务。

 

 

 

 

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐