Kubernetes HA集群搭建指南

K8s HA集群的运行主要由k8s基本组件、etcd集群和docker运行环境组成的,其中etcd集群可以理解为k8s集群的数据库,它主要作用是服务发现、全局配置、以及保存一些路由相关的信息。为了保证k8s集群的高可用性,我们需要保证etcd存储数据的可靠性,所以在这个我们在搭建k8s集群的过程中搭建了一个etcd HA集群。我们搭建k8s HA集群的次序如下所示:

  • 1、搭建etcd集群
  • 2、启动k8s master组件
  • 3、启动k8s node组件

搭建ETCD集群

etcd集群搭建的方式比较多,可以搭建一个全新的集群,也可以从已有的etcd中,将数据迁移到一个新集群上。

创建一个全新的集群

创建一个全新的集群过程比较简单,这里我们采取的是在三台机器上搭建了一个etcd集群。假如说etcd的运行路径为/home/work/etcd,我们需要手动创建一个data-dir文件夹用于保存etcd中的数据以及集群的一些属性信息,然后在每台机器上启动start-etcd.sh脚本:

1
2
3
4
5
6
#!/bin/bash

HOSTNAME="http://sandbox-1"
CLUSTER="etcd0=http://sandbox-3:2380,etcd1=http://sandbox-2:2380,etcd2=http://sandbox-1:2380"

./bin/etcd --name etcd2 --data-dir /home/work/etcd/data-dir --advertise-client-urls "$HOSTNAME:2379","$HOSTNAME:4001" --listen-client-urls http://0.0.0.0:2379,http://0.0.0.0:4001 --initial-advertise-peer-urls "$HOSTNAME:2380" --listen-peer-urls http://0.0.0.0:2380 --initial-cluster-token etcd-cluster-1 --initial-cluster $CLUSTER --initial-cluster-state new >> ./log/etcd.log 2>&1

这里需要注意的是,在每台机器上启动时,需要将etcd name和hostname改下。具体搭建过程和测试过程,参考:
Etcd集群搭建过程

数据迁移到一个新的etcd集群

这种方式下我们可以通过已有的etcd data-dir中的数据创建一个新的etcd集群,这个etcd集群会包含旧etcd的数据信息,这样就实现了etcd的数据迁移。当然也可以使用一个空的data-dir文件目录创建一个全新的集群。

大体思路:先以–force-cluster的方式创建一个仅含有一个节点的etcd集群,然后通过添加成员的方式,扩展成一个etcd集群。具体做法参考:Etcd数据迁移

创建Etcd集群中可能会遇到集群创建失败或者etcdctl无法连接本机端口等问题,请参考 常见问题列表(Q&A):1

启动master的组件

master组件主要包括:kube-apiserver、kube-controller-manager、kube-scheduler和flanneld,下面是k8s的master组件的启动过程:

Kube-apiserver

Kube-apiserver它作为集群资源对象的唯一操作入口,其他的所有组件必须通过它提供的API来操作资源数据,它的启动方式如下所示:

1
../bin/kube-apiserver --logtostderr=true --v=4 --etcd_servers=http://sandbox-1:2379,http://sandbox-2:2379,http://sandbox-3:2379 --address=0.0.0.0 --allow_privileged=false --service-cluster-ip-range=10.254.0.0/16 --service-node-port-range=8000-40000 >> ./log/kube-apiserver.log 2>&1

这里我们必须为它指定etcd集群的地址,可以指定 --advertise-address 为本机的IP地址对外提供API服务,指定 --service-cluster-ip-range 限制集群服务的分配ip的范围,指定 --service-node-port-range 限制集群服务端口分配的范围等。

启动API Server的过程中可能遇到外部无法访问本机API Server服务或者绑定IP失败等问题,请参考常见问题列表(Q&A):2、3

Kube-controller-manager

它是集群内部的管理控制中心,其主要目的是实现k8s集群的故障检测和恢复的自动化工作,比如根据RC的定义完成Pod的复制或删除。它的启动如下所示:

1
../bin/kube-controller-manager --logtostderr=true --v=4 --master=http://127.0.0.1:8080 --node-monitor-grace-period=10s --pod-eviction-timeout=10s --leader-elect=true >> ./log/kube-controller-manager.log 2>&1

kube-scheduler

集群中的调度器,负责Pod在集群节点上的调度分配。其启动方式如下所示:

1
../bin/kube-scheduler --logtostderr=true --v=2 --master=http://127.0.0.1:8080 --leader-elect=true >> ./log/kube-scheduler.log 2>&1

flanneld

这里的flanneld以对外提供服务的方式运行的,它的主要作用包括为k8s集群设置一个网段,保证每个节点的网段不发生冲突,同时充当集群的路由功能等。

1
2
etcdctl set /coreos.com/network/config '{"Network": "10.1.0.0/16"}'
../bin/flanneld-server -v 16 -listen="0.0.0.0:8888" >> ./log/flanneld-server.log 2>&1

这里我们首先通过etcdctl set操作,设置了整个集群网络的IP范围,然后监听本地的8888端口,与flannel的客户端通信,协商每个flannel客户端节点的网络IP范围。

Flanneld启动过程中可能会发生启动失败,日志出现HTTP状态码500等,请参见 常见问题列表(Q&A):5

启动k8s node组件

k8s的node组件主要包括docker、flanneld、kubelet、kube-proxy,下面是k8s node组件的启动过程:

flanneld

这个flanneld与master上运行的flanneld有些不同,这里的flanneld充当的是客户端的角色,它向master上的flanneld协商一个IP网段用于本地的Pod使用,并把这些信息记录在etcd中,一定程度上充当了路由的作用。它的启动选项如下所示:

1
2
ETCD_CLUSTER="http://sandbox-1:2379,http://sandbox-2:2379,http://sandbox-3:2379"
../bin/flanneld -etcd-endpoints=$ETCD_CLUSTER -remote=sandbox-1:8888 >> ./log/flannel.log 2>&1

我们这里需要为flanneld指定etcd集群的地址以及master上运行的flanneld地址。

Docker

目前我们的k8s版本是v1.3.3,它支持docker和rkt两种容器类型,在我们的生产环境中主要使用的是docker。Docker的运行环境搭建脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/bin/bash

function mount_cgroup() {
#cgroup fs
#set -e

# for simplicity this script provides no flexibility

# if cgroup is mounted by fstab, don't run
# don't get too smart - bail on any uncommented entry with 'cgroup' in it
if grep -v '^#' /etc/fstab | grep -q cgroup; then
echo 'cgroups mounted from fstab, not mounting /sys/fs/cgroup'
return 0
fi

# kernel provides cgroups?
if [ ! -e /proc/cgroups ]; then
return 0
fi

# if we don't even have the directory we need, something else must be wrong
if [ ! -d /sys/fs/cgroup ]; then
return 0
fi

# mount /sys/fs/cgroup if not already done
if ! mountpoint -q /sys/fs/cgroup; then
mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup
fi

cd /sys/fs/cgroup

# get/mount list of enabled cgroup controllers
for sys in $(awk '!/^#/ { if ($4 == 1) print $1 }' /proc/cgroups); do
mkdir -p $sys
if ! mountpoint -q $sys; then
if ! mount -n -t cgroup -o $sys cgroup $sys; then
rmdir $sys || true
fi
fi
done

# example /proc/cgroups:
# #subsys_name hierarchy num_cgroups enabled
# cpuset 2 3 1
# cpu 3 3 1
# cpuacct 4 3 1
# memory 5 3 0
# devices 6 3 1
# freezer 7 3 1
# blkio 8 3 1
}
mount_cgroup
mkdir Xcloud_install >/dev/null 2>&1
cd Xcloud_install
# 下载docker-1.11的可执行文件,下面下载链接不可用了,从GitHub上可下载到
wget ftp://docker-1.11.0.tgz >/dev/null 2>&1
mv /usr/bin/docker /usr/bin/docker.previous
tar xf docker-1.11.0.tgz
mv docker/* /usr/bin/
pkill docker
rm -rf /home/work/docker_Xcloud 1>/dev/null 2>&1
rm /var/run/docker.pid 1>/dev/null 2>&1
rm ../Xcloud_install –rf

然后运行docker daemon,启动选项如下所示:

1
2
3
4
5
#!/bin/bash

source /run/flannel/subnet.env
mkdir -p /home/work/docker/run 2>/dev/null
../bin/docker daemon -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375 --graph=/home/work/docker/runtime_1.11 --exec-root=/home/work/docker/run --insecure-registry=registry.Xcloud.com --bip=${FLANNEL_SUBNET} --mtu=${FLANNEL_MTU} >> ./log/docker.log 2>&1

这里需要注意的是,我们需要先将flannel子网的环境变量导入,然后再运行docker daemon,其中cgroup的挂载部分也很重要,具体过程在docker环境搭建的脚本中有所展示了。

启动Docker过程中可能会遇到docker启动失败的一些问题,请参考 常见问题列表(Q&A):4

Kubelet

负责本Node节点上的Pod的创建、修改、监控、删除等全生命周期管理,同时Kubelet定时“上报”本Node的状态信息到API Server里,它启动是否正常直接影响到整个Node节点主机是否可用,启动选项参考如下:

1
../bin/kubelet --logtostderr=true --cadvisor-port=8194 --cluster_dns=10.254.88.188 --cluster_domain=Xcloud.local --v=4 --api_servers=http://127.0.0.1:8080 --address=0.0.0.0 --allow_privileged=false --pod-infra-container-image="registry.Xcloud.com/public/pause:1.0.2" --container-runtime="docker" >> ./log/kubelet.log 2>&1

我们定了cadvisor资源收集的端口8194,然后指定了API Server的地址,值得注意的是–pod-infra-container-image指定了我们以哪个版本的pause镜像作为启动其他镜像的基础镜像,因为在每个Pod运行的起始,内部镜像的启动停止过程都是由一个Pause镜像完成的。

Kubelet的启动过程可能会遇到无法创建Pod时无法下载镜像等问题,请参考常见问题(Q&A):6

Kube-proxy

它实现了Service的代理以及软件模式的负载均衡,主要是为节点间的通信进行服务。其启动选项参考如下:

1
../bin/kube-proxy --logtostderr=true --v=4 --master=http://127.0.0.1:8080 >> ./log/kube-proxy.log 2>&1

我们指定了API Server的所在位置。

常见问题(Q&A)

1、 按第一种方式创建一个全新的集群,通过etcdctl member list发生错误,无法连接本地的etcd服务端口

可能原因:新集群目前仅启动了一个etcd实例,造成当前集群中仅有一个etcd实例存活,直接造成集群不可用。因为etcd集群保证一致性的前提要满足M>N/2(M:集群中健康的成员数 N:集群中总的成员数)
解决方法:继续在另外一台机器上启动一个etcd实例,使M>N/2,再用etcdctl member list查看集群情况,或者通过第二种方式–force-new-cluster的方式启动集群,具体做法上文有详细描述。

2、 如果启动kube-apiserver的时候,发生了No valid IP bound类似的错误

可能原因:当前节点没有默认路由条目,造成kube-apiserver不知选哪个IP作为对外提供服务的地址
解决办法:为当前主机增加一个默认路由条目。首先查看一下本机的可用IP所在的网卡名称,然后依据本机的IP,设置一个默认路由。举个例子通过ifconfig查看本机的网卡名为:xgbe0和IP:10.207.182.18

然后增加一个默认路由

1
ip route add default via 10.115.178.1 dev xgbe0

3、 启动后的API Server仅能通过本机的8080端口访问,例如 curl http://127.0.0.1:8080
可能原因:启动API Server时,默认绑定的是本机的127.0.0.1地址,也就是无法从外部使用API Server服务
解决办法:启动时添加选项–address=0.0.0.0

4、 Docker daemon启动起始就发生了错误
可能原因:没有挂载cgroup或者时没有缺少相应的可执行,比如在PATH路径中找不到docker-containerd、docker-runc等
解决办法:运行上述贴出来的Docker环境搭建脚本

5、 Master 节点上的flanneld启动失败的情况,日志出现返回状态码500的情况
可能原因:没有设置当前集群的网段,或者设置当前集群网段的操作有误
解决办法:在启动flanneld之前,先

1
etcdctl set /coreos.com/network/config '{"Network": "10.1.0.0/16"}'

然后再启动flanneld进程。如果日志信息发生返回状态码HTTP500的情况,可以尝试通过

1
etcdctl member list

查看哪台机器是etcd集群的leader,然后在这台机器上进行上述操作。

6、 Kubelet启动成功,但创建RC时,Pod的状态一直处于Pending,通过查看创建RC的事件发现kubelet pull pause镜像失败
可能原因:没有指定–pod-infra-container-image,kubelet会默认从gcr.io中拉取一个版本的pause镜像,但gcr.io中的镜像在国内的网络环境下,一般会被墙住,导致拉取失败,Pod启动不了。
解决办法:从国内某个镜像仓库中下载好google/pause镜像放到自己的私有仓库中,并指定–pod-infra-container-image选项为私有仓库的google/pause镜像