CentOS 7实战Kubernetes部署

作者 杨章显

1. 前言

上一节我们阐述了Kubernetes的系统架构,让大家对Kubernetes有一定的初步了解,但是就如何使用Kubernetes, 也许大家还不知如何下手。本文作者将带领大家如何在本地部署、配置Kubernetes集群网络环境以及通过实例演示跨机器服务间的通信,主要包括如下内容:

  • 部署环境介绍
  • Kubernetes集群逻辑架构
  • 部署Open vSwitch、Kubernetes、Etcd组件
  • 演示Kubernetes管理容器

2. 部署环境

  • VMware Workstation:10.0.3
  • VMware Workstation网络模式:NAT
  • 操作系统信息:CentOS 7 64位
  • Open vSwitch版本信息:2.3.0
  • Kubernetes版本信息:0.5.2
  • Etcd版本信息:0.4.6
  • Docker版本信息:1.3.1
  • 服务器信息:
    | Role      | Hostname   | IP Address  |
    |:---------:|:----------:|:----------: |
    |APIServer  |kubernetes  |192.168.230.3|
    |Minion     | minion1    |192.168.230.4|
    |Minion     | minion2    |192.168.230.5|

3. Kubernetes集群逻辑架构

在详细介绍部署Kubernetes集群前,先给大家展示下集群的逻辑架构。从下图可知,整个系统分为两部分,第一部分是Kubernetes APIServer,是整个系统的核心,承担集群中所有容器的管理工作;第二部分是minion,运行Container Daemon,是所有容器栖息之地,同时在minion上运行Open vSwitch程序,通过GRE Tunnel负责minion之间Pod的网络通信工作。

4. 部署Open vSwitch、Kubernetes、Etcd组件

4.1 安装Open vSwitch及配置GRE

为了解决跨minion之间Pod的通信问题,我们在每个minion上安装Open vSwtich,并使用GRE或者VxLAN使得跨机器之间Pod能相互通信,本文使用GRE,而VxLAN通常用在需要隔离的大规模网络中。对于Open vSwitch的具体安装步骤,可参考这篇博客,我们在这里就不再详细介绍安装步骤了。安装完Open vSwitch后,接下来便建立minion1和minion2之间的隧道。首先在minion1和minion2上建立OVS Bridge,

接下来建立gre,并将新建的gre0添加到obr0,在minion1上执行如下命令,

[root@minion1 ~]# ovs-vsctl add-port obr0 gre0 -- set Interface gre0 type=gre options:remote_ip=192.168.230.5

在minion2上执行,

[root@minion2 ~]# ovs-vsctl add-port obr0 gre0 -- set Interface gre0 type=gre options:remote_ip=192.168.230.4

至此,minion1和minion2之间的隧道已经建立。然后我们在minion1和minion2上创建Linux网桥kbr0替代Docker默认的docker0(我们假设minion1和minion2都已安装Docker),设置minion1的kbr0的地址为172.17.1.1/24, minion2的kbr0的地址为172.17.2.1/24,并添加obr0为kbr0的接口,以下命令在minion1和minion2上执行。

[root@minion1 ~]# brctl addbr kbr0               //创建linux bridge
[root@minion1 ~]# brctl addif kbr0 obr0          //添加obr0为kbr0的接口
[root@minion1 ~]# ip link set dev docker0 down   //设置docker0为down状态
[root@minion1 ~]# ip link del dev docker0        //删除docker0

为了使新建的kbr0在每次系统重启后任然有效,我们在/etc/sysconfig/network-scripts/目录下新建minion1的ifcfg-kbr0如下:

DEVICE=kbr0
ONBOOT=yes
BOOTPROTO=static
IPADDR=172.17.1.1
NETMASK=255.255.255.0
GATEWAY=172.17.1.0
USERCTL=no
TYPE=Bridge
IPV6INIT=no

同样在minion2上新建ifcfg-kbr0,只需修改ipaddr为172.17.2.1和gateway为172.17.2.0即可,然后执行systemctl restart network重启系统网络服务,你能在minion1和minion2上发现kbr0都设置了相应的IP地址。为了验证我们创建的隧道是否能通信,我们在minion1和minion2上相互ping对方kbr0的IP地址,从下面的结果发现是不通的,经查找这是因为在minion1和minion2上缺少访问172.17.1.1和172.17.2.1的路由,因此我们需要添加路由保证彼此之间能通信。

[root@minion1 network-scripts]# ping 172.17.2.1
PING 172.17.2.1 (172.17.2.1) 56(84) bytes of data.
^C
--- 172.17.2.1 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1000ms

[root@minion2 ~]#  ping 172.17.1.1
PING 172.17.1.1 (172.17.1.1) 56(84) bytes of data.
^C
--- 172.17.1.1 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1000ms

由于通过ip route add添加的路由会在下次系统重启后失效,为此我们在/etc/sysconfig/network-scripts目录下新建一个文件route-eth0存储路由,这里需要注意的是route-eth0和ifcfg-eth0的黑体部分必须保持一致,否则不能工作,这样添加的路由在下次重启后不会失效。为了保证两台minion的kbr0能相互通信,我们在minion1的route-eth0里添加路由172.17.2.0/24 via 192.168.230.5 dev eno16777736,eno16777736是minion1的网卡,同样在minion2的route-eth0里添加路由172.17.1.0/24 via 192.168.230.4 dev eno16777736。重启网络服务后再次验证,彼此kbr0的地址可以ping通,如:

[root@minion2 network-scripts]# ping 172.17.1.1
PING 172.17.1.1 (172.17.1.1) 56(84) bytes of data.
64 bytes from 172.17.1.1: icmp_seq=1 ttl=64 time=2.49 ms
64 bytes from 172.17.1.1: icmp_seq=2 ttl=64 time=0.512 ms
^C
--- 172.17.1.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 0.512/1.505/2.498/0.993 ms

到现在我们已经建立了两minion之间的隧道,而且能正确的工作。下面我们将介绍如何安装Kubernetes APIServer及kubelet、proxy等服务。

4.2 安装Kubernetes APIServer

在安装APIServer之前,我们先下载Kubernetes及Etcd,做一些准备工作。在kubernetes上的具体操作如下:

[root@kubernetes ~]# mkdir /tmp/kubernetes
[root@kubernetes ~]# cd /tmp/kubernetes/
[root@kubernetes kubernetes]# wget https://github.com/GoogleCloudPlatform/kubernetes/releases/download/v0.5.2/kubernetes.tar.gz
[root@kubernetes kubernetes]# wget https://github.com/coreos/etcd/releases/download/v0.4.6/etcd-v0.4.6-linux-amd64.tar.gz

然后解压下载的kubernetes和etcd包,并在kubernetes、minion1、minion2上创建目录/opt/kubernetes/bin,

[root@kubernetes kubernetes]# mkdir -p /opt/kubernetes/bin
[root@kubernetes kubernetes]# tar xf kubernetes.tar.gz
[root@kubernetes kubernetes]# tar xf etcd-v0.4.6-linux-amd64.tar.gz
[root@kubernetes kubernetes]# cd ~/kubernetes/server
[root@kubernetes server]# tar xf kubernetes-server-linux-amd64.tar.gz
[root@kubernetes kubernetes]# /tmp/kubernetes/kubernetes/server/kubernetes/server/bin

复制kube-apiserver,kube-controller-manager,kube-scheduler,kubecfg到kubernetes的/opt/kubernetes/bin目录下,而kubelet,kube-proxy则复制到minion1和minion2的/opt/kubernetes/bin,并确保都是可执行的。

[root@kubernetes amd64]# cp kube-apiserver kube-controller-manager kubecfg kube-scheduler /opt/kubernetes/bin
[root@kubernetes amd64]# scp kube-proxy kubelet root@192.168.230.4:/opt/kubernetes/bin
[root@kubernetes amd64]# scp kube-proxy kubelet root@192.168.230.5:/opt/kubernetes/bin

为了简单我们只部署一台etcd服务器,如果需要部署etcd的集群,请参考官方文档,在本文中将其跟Kubernetes APIServer部署同一台机器上,而且将etcd放置在/opt/kubernetes/bin下,etcdctl跟ectd同一目录。

[root@kubernetes kubernetes]# cd /tmp/kubernetes/etcd-v0.4.6-linux-amd64
[root@kubernetes etcd-v0.4.6-linux-amd64]# cp etcd etcdctl /opt/kubernetes/bin

需注意的是kubernetes和minion上/opt/kubernetes/bin目录下的文件都必须是可执行的。到目前,我们准备工作已经差不多,现在开始给apiserver,controller-manager,scheduler,etcd配置unit文件。首先我们用如下脚本etcd.sh配置etcd的unit文件,

#!/bin/sh

ETCD_PEER_ADDR=192.168.230.3:7001
ETCD_ADDR=192.168.230.3:4001
ETCD_DATA_DIR=/var/lib/etcd
ETCD_NAME=kubernetes

! test -d $ETCD_DATA_DIR && mkdir -p $ETCD_DATA_DIR
cat </usr/lib/systemd/system/etcd.service
[Unit]
Description=Etcd Server

[Service]
ExecStart=/opt/kubernetes/bin/etcd \\
    -peer-addr=$ETCD_PEER_ADDR \\
    -addr=$ETCD_ADDR \\
    -data-dir=$ETCD_DATA_DIR \\
    -name=$ETCD_NAME \\
    -bind-addr=0.0.0.0

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable etcd
systemctl start etcd

对剩下的apiserver,controller-manager,scheduler的unit文件配置的脚本,可以在github 上GetStartingKubernetes找到,在此就不一一列举。运行相应的脚本后,在APIServer上etcd, apiserver, controller-manager, scheduler服务就能正常运行。

4.3 安装Kubernetes Kubelet及Proxy

根据Kubernetes的设计架构,需要在minion上部署docker, kubelet, kube-proxy,在4.2节部署APIServer时,我们已经将kubelet和kube-proxy已经分发到两minion上,所以只需配置docker,kubelet,proxy的unit文件,然后启动服务就即可,具体配置见GetStartingKubernetes

5. 演示Kubernetes管理容器

为了方便,我们使用Kubernetes提供的例子Guestbook来演示Kubernetes管理跨机器运行的容器,下面我们根据Guestbook的步骤创建容器及服务。在下面的过程中如果是第一次操作,可能会有一定的等待时间,状态处于pending,这是因为第一次下载images需要一段时间。

5.1 创建redis-master Pod和redis-master服务

[root@kubernetes ~]# cd /tmp/kubernetes/kubernetes/examples/guestbook
[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c redis-master.json create pods
[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c redis-master-service.json create services

完成上面的操作后,我们可以看到如下redis-master Pod被调度到192.168.230.4。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list pods
Name                                   Image(s)                   Host                Labels                                       Status
----------                             ----------                 ----------          ----------                                   ----------
redis-master                           dockerfile/redis           192.168.230.4/      name=redis-master                            Running

但除了发现redis-master的服务之外,还有两个Kubernetes系统默认的服务kubernetes-ro和kubernetes。而且我们可以看到每个服务都有一个服务IP及相应的端口,对于服务IP,是一个虚拟地址,根据apiserver的portal_net选项设置的CIDR表示的IP地址段来选取,在我们的集群中设置为10.10.10.0/24。为此每新创建一个服务,apiserver都会在这个地址段中随机选择一个IP作为该服务的IP地址,而端口是事先确定的。对redis-master服务,其服务地址为10.10.10.206,端口为6379。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list services
Name                Labels              Selector                                  IP                  Port
----------          ----------          ----------                                ----------          ----------
kubernetes-ro                           component=apiserver,provider=kubernetes   10.10.10.207        80
redis-master        name=redis-master   name=redis-master                         10.10.10.206        6379
kubernetes                              component=apiserver,provider=kubernetes   10.10.10.161        443

5.2 创建redis-slave Pod和redis-slave服务

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c redis-slave-controller.json create replicationControllers
[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c redis-slave-service.json create services

然后通过list命令可知新建的redis-slave Pod根据调度算法调度到两台minion上,服务IP为10.10.10.92,端口为6379

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list pods
Name                                   Image(s)                   Host                Labels                                       Status
----------                             ----------                 ----------          ----------                                   ----------
redis-master                           dockerfile/redis           192.168.230.4/      name=redis-master                            Running
8c0ddbda-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.5/      name=redisslave,uses=redis-master            Running
8c0e1430-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.4/      name=redisslave,uses=redis-master            Running

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list services
Name                Labels              Selector                                  IP                  Port
----------          ----------          ----------                                ----------          ----------
redisslave          name=redisslave     name=redisslave                           10.10.10.92         6379
kubernetes                              component=apiserver,provider=kubernetes   10.10.10.161        443
kubernetes-ro                           component=apiserver,provider=kubernetes   10.10.10.207        80
redis-master        name=redis-master   name=redis-master                         10.10.10.206        6379

5.3 创建Frontend Pod和Frontend服务

在创建之前修改frontend-controller.json的Replicas数量为2,这是因为我们的集群中只有2台minion,如果按照frontend-controller.json的Replicas默认值3,那会导致有2个Pod会调度到同一台minion上,产生端口冲突,有一个Pod会一直处于pending状态,不能被调度。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c frontend-controller.json create replicationControllers
[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 -c frontend-service.json create services

通过查看可知Frontend Pod也被调度到两台minion,服务IP为10.10.10.220,端口是80。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list pods
Name                                   Image(s)                   Host                Labels                                       Status
----------                             ----------                 ----------          ----------                                   ----------
redis-master                           dockerfile/redis           192.168.230.4/      name=redis-master                            Running
8c0ddbda-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.5/      name=redisslave,uses=redis-master            Running
8c0e1430-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.4/      name=redisslave,uses=redis-master            Running
a880b119-7295-11e4-8233-000c297db206   brendanburns/php-redis     192.168.230.4/      name=frontend,uses=redisslave,redis-master   Running
a881674d-7295-11e4-8233-000c297db206   brendanburns/php-redis     192.168.230.5/      name=frontend,uses=redisslave,redis-master   Running

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list services
Name                Labels              Selector                                  IP                  Port
----------          ----------          ----------                                ----------          ----------
kubernetes-ro                           component=apiserver,provider=kubernetes   10.10.10.207        80
redis-master        name=redis-master   name=redis-master                         10.10.10.206        6379
redisslave          name=redisslave     name=redisslave                           10.10.10.92         6379
frontend            name=frontend       name=frontend                             10.10.10.220        80
kubernetes                              component=apiserver,provider=kubernetes   10.10.10.161        443

除此之外,你可以删除Pod、Service及更新ReplicationController的Replicas数量等操作,如删除Frontend服务:

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 delete services/frontend
Status
----------
Success

还可以更新ReplicationController的Replicas的数量,下面是更新Replicas之前ReplicationController的信息。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list replicationControllers
Name                   Image(s)                   Selector            Replicas
----------             ----------                 ----------          ----------
redisSlaveController   brendanburns/redis-slave   name=redisslave     2
frontendController     brendanburns/php-redis     name=frontend       2

现在我们想把frontendController的Replicas更新为1,则这行如下命令,然后再通过上面的命令查看frontendController信息,发现Replicas已变为1。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 resize frontendController 1

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list replicationControllers
Name                   Image(s)                   Selector            Replicas
----------             ----------                 ----------          ----------
redisSlaveController   brendanburns/redis-slave   name=redisslave     2
frontendController     brendanburns/php-redis     name=frontend       1

5.4 演示跨机器服务通信

完成上面的操作后,我们来看当前Kubernetes集群中运行着的Pod信息。

[root@kubernetes guestbook]# kubecfg -h http://192.168.230.3:8080 list pods
Name                                   Image(s)                   Host                Labels                                       Status
----------                             ----------                 ----------          ----------                                   ----------
a881674d-7295-11e4-8233-000c297db206   brendanburns/php-redis     192.168.230.5/      name=frontend,uses=redisslave,redis-master   Running
redis-master                           dockerfile/redis           192.168.230.4/      name=redis-master                            Running
8c0ddbda-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.5/      name=redisslave,uses=redis-master            Running
8c0e1430-728c-11e4-8233-000c297db206   brendanburns/redis-slave   192.168.230.4/      name=redisslave,uses=redis-master            Running

通过上面的结果可知当前提供前端服务的PHP和提供数据存储的后端服务Redis master的Pod分别运行在192.168.230.5和192.168.230.4上,即容器运行在不同主机上,还有Redis slave也运行在两台不同的主机上,它会从Redis master同步前端写入Redis master的数据。下面我们从两方面验证Kubernetes能提供跨机器间容器的通信:

  • 在浏览器打开http://${IP_Address}:8000,IP_Address为PHP容器运行的minion的IP地址,其暴漏的端口为8000,这里IP_Address为192.168.230.5。打开浏览器会显示如下信息:
    你可以输入信息并提交,如"Hello Kubernetes"、"Container",然后Submit按钮下方会显示你输入的信息。
    由于前端PHP容器和后端Redis master容器分别在两台minion上,因此PHP在访问Redis master服务时一定得跨机器通信,可见Kubernetes的实现方式避免了用link只能在同一主机上实现容器间通信的缺陷,对于Kubernetes跨机器通信的实现方法,以后我会详细介绍。
  • 从上面的结果,可得知已经实现了跨机器的通信,现在我们从后端数据层验证不同机器容器间的通信。根据上面的输出结果发现Redis slave和Redis master分别调度到两台不同的minion上,在192.168.230.4主机上执行docker exec -ti c41711cc8971 /bin/sh,c41711cc8971是Redis master的容器ID,进入容器后通过redis-cli命令查看从浏览器输入的信息如下: *
  • 如果我们在192.168.230.5上运行的Redis slave容器里查到跟Redis master容器里相同的信息,那说明Redis master和Redis slave之间的数据同步正常工作,下面是从192.168.230.5上运行的Redis slave容器查询到的信息: *
    由此可见Redis master和Redis slave之间数据同步正常,OVS GRE隧道技术使得跨机器间容器正常通信。

6. 结论

本文主要介绍如何在本地环境部署Kubernetes集群和演示如何通过Kubernetes管理集群中运行的容器,并通过OVS管理集群不同minion的Pod之间的网络通信。接下来会对Kubernetes各个组件源码进行详细分析,阐述Kubernetes的工作原理。

7. 个人简介

杨章显,现就职于Cisco,主要从事WebEx SaaS服务运维,系统性能分析等工作。特别关注云计算,自动化运维,部署等技术,尤其是Go、OpenvSwitch、Docker及其生态圈技术,如Kubernetes、Flocker等Docker相关开源项目。Email: yangzhangxian@gmail.com

8. 参考资料

  1. https://n40lab.wordpress.com/2014/09/04/openvswitch-2-3-0-lts-and-centos-7/
  2. https://github.com/GoogleCloudPlatform/kubernetes/tree/master/examples/guestbook

感谢郭蕾对本文的策划和审校。