写点什么

CentOS 7 实战 Kubernetes 部署

2014 年 12 月 03 日

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. 参考资料

  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

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

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ )或者腾讯微博( @InfoQ )关注我们,并与我们的编辑和其他读者朋友交流。

2014 年 12 月 03 日 06:5444578

评论

发布
暂无评论
发现更多内容

go每日一库 [cmd]

happlyfox

golang 4月日更

u盘偷猎系统源代码

赫鲁小夫

4月日更

优雅编程:JavaScript代码优化常见的3个小技巧

devpoint

map reduce 空值运算符 filter 扩展运算符

强化学习—DQN:不讲前世,就论今生

打工人!

深度学习 强化学习 深度强化学习 图解源码分析 DQN

零基础学Tableau系列 | 04—标靶图、甘特图、瀑布图

不温卜火

数据可视化 数据清洗 4月日更

第一课作业

杰语

配置中的动态代码

顿晓

配置化开发 Function 4月日更 动态函数

Redis数据结构zset详解:范围查找

程序员架构进阶

redis 源码分析 Zset 28天写作 4月日更

【架构实战营】第一模块作业

hiqian

架构实战营第一模块课程总结

Veek

架构实战营

架构实战营-M01H

BlazeLuLu

架构实战营

模块一,学习总结

俞立夫

架构实战营

【架构实战营】第一模块总结

hiqian

架构实战营

8x Flow 业务建模法(二):再看什么是业务逻辑

胡皓

领域驱动设计 DDD 业务建模 8xFlow 业务逻辑

Java 多线程

anuyyy

Java 多线程 Runnable 4月日更

架构实战营 - 模块一作业

凯迪

架构实战营

容器的生命周期状态变化

耳东

容器 4月日更

当你的内心归于平静,美好便会悄然而至

小天同学

自我思考 个人感悟 个人总结 4月日更

算法训练营 - 学习笔记 - 第一周

心在飞

[架构实战营][0期]模块1作业

张民

架构实战营

Linux awk命令

一个大红包

4月日更

像智能手机一样造车,可能吗?

脑极体

架构师训练营大作业一

潘涛

架构师训练营 4 期

架构实战营第一模块命题作业

Veek

架构实战营

架构实战营-模块1-作业

莫问

架构实战营

模块一作业

Presley

架构师训练营大作业二

潘涛

架构师训练营 4 期

Redis 数据倾斜和集群内通信开销

escray

redis 极客时间 学习笔记 3月日更 Redis 核心技术与实战

聪明人的训练(四)

Changing Lin

4月日更

Git命令大全,Git基本了解

Chalk

git 学习 4月日更

广告投放预算低?千人成本低才是真的省!

󠀛Ferry

七日更 4月日更

CentOS 7实战Kubernetes部署-InfoQ