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,
[root@minion1 ~]# ovs-vsctl add-br obr0
接下来建立 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 <<EOF >/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. 参考资料
- https://n40lab.wordpress.com/2014/09/04/openvswitch-2-3-0-lts-and-centos-7/
- https://github.com/GoogleCloudPlatform/kubernetes/tree/master/examples/guestbook
感谢郭蕾对本文的策划和审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。
评论