【摘要】Kubernetes是Google开源的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有两种组件:master和slave。Master是控制部分,上面运行apiserver,controller,scheduler三个组件。Slave是实际提供资源的部分,上面运行kubelet,proxy两个组件,可以有多个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的几个术语:pod、label、service,以及两个重要的yaml文件。下面逐一介绍。
3.1
Node:
先用node这个词表示系统运行的物理结点,node可以是linux机器,也可以是IaaS上的一台虚拟机。
3.2
Pod:
Pod是kubernetes引入的概念,是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中的各种对象Pod,service,RC等都有各自的label。这样在其他的对象中的label
selector中就可以指定label来确定操作哪些对象。可能看了这个还是不明白,等下看到例子就懂了。
3.4
Service:
一个service是一组提供相同服务的Pod的对外访问接口。Service是个虚拟的东西,他依靠Pod为其提供支持,一个service需要一组提供相同服务的Pod。Service通过label标签来确定选择哪些Pod作为他的支持。
Service有两种:集群内service和对外暴露service。集群内service会在kubernetes的cluster
IP Range池中分配一个IP地址(查上面--service-cluster-ip-range这个参数看一下),这个ip地址在集群内的Pod都可以访问,但是对外不可见。每次启动一个service时,kubernetes都在这个ip-range里面随机的为service分配ip地址。
对外保留的Service需要明确 type:NodePort,这样系统就会在Node上面打开一个主机上的真实端口,客户就能够在集群外访问这个service
了。
Service、Pod、label之间的关系是这样的:
1.1
RC(replication controller).yaml
简单的说,RC文件定义了docker集群中希望启动多少个、什么样的Pod。Kubernetes的controller通过RC文件完成Pod的创建、监控、启停等。
1.2
Service.yaml
Service文件定义了服务的名称、类型、端口、寻找哪些pod支持此服务。
下面通过例子讲这两个文件。
1.3
举例:
下面通过这个例子看一下Node,Pod,Service,Label各自是什么以及他们之间的关系。假设有两个需求非常专业:一个是将字符串小写改为大写upper,一个是将字符串反序reversi(我们假设这两个东西很不容易做,我将他们做成了独立的服务)。现在,我想做一个应用,将一个字符串变大小并反序,那么需要依次使用upper和reversi两个服务。
最下面的stringservice使用上面的upper-service
和reversi-service,完成这个业务。
1.1.1
两个文件
1.1.1.1
以upper-service为例,我们先写一个upper-rc.yaml
apiVersion: v1 kind: ReplicationController metadata: name : upper ///// labels: name : upper spec: replicas: 2 selector: name : upper template: // Pod的定义 metadata: labels: name : upper/// Pod的标签。两个红色要一致。 spec: containers: - 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 spec: //详细描述 ports: - port : 8010 // targetPort: 8010 selector: name : upper |
之后,运行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> upper |
可以看到有两个service在运行,其中一个是kubernetes自己的,服务的IP地址是123.123.123.1,另一个则是刚刚启动的upper,IP地址为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 ports: - port : 30001 targetPort: 8000 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-service的pod容器里面就会有一个UPPER_SERVICE_HOST和UPPER_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端口号来访问这些服务。