本文包含社区提供的关于设置高可用 Kubernetes 集群的注意事项
在设置生产集群时
通常需要高可用性(即使某些控制平面或工作节点发生故障,集群仍能运行的能力)
对于工作节点,假设有足够多的节点,这是集群功能的一部分
但是,在规划和设置集群时,需要考虑控制平面节点与 etcd 实例的冗余
kubeadm
支持设置多控制平面与多 etcd 集群(见:用 kubeadm 创建高可用集群)
还有一些需要关注的点不属于 Kubernetes,因而没有包含在 Kubernetes 的项目文档中
本文提供了一些用 kubeadm
启动高可用集群的额外信息与例子
在设置拥有两个及以上的控制平面的集群时
可以通过将 API 服务器实例放在一个负载均衡器后边来实现高可用
在为新集群运行 kubeadm init
命令时,使用 --control-plane-endpoint
选项来指向该负载均衡器
负载均衡器自己也应该是高可用的,通常通过添加冗余的负载均衡器来实现
为此,设置了管理虚拟 IP 的主机集群,每个主机运行一个负载均衡器的实例
以便始终使用当前持有 vIP 的主机上的负载均衡器,而其它主机处于待机状态
在某些环境中,如在具有专用负载均衡器组件(如某些云供应商提供的)的数据中心中
可能有现成的负载均衡功能
如果没有,则可以使用用户管理的负载平衡,在这种情况下,启动集群之前需要做一些准备
下边的内容给出了一些有用的例子,当然还有许多其它配置负载均衡器的方法
keepalived 与 haproxy 结合,可以通过虚拟 IP 来实现负载均衡
这个方法已经使用了很长时间,经过了充分地测试
keepalived
服务提供了一个由可配置的健康检查管理的虚拟 IPhaproxy
服务配置为简单的基于流的负载均衡keepalived
与 haproxy
既可以以操作系统服务的形式运行
也可以以控制平面主机上的静态 pod 运行
两种情况下的服务配置是完全相同的
keepalived
的配置由两个文件组成
服务配置文件一般位于 /etc/keepalived
目录,但有些 Linux 发行版会将其放在别的地方
下边的配置能够成功用于 2.0.17 版本的 keepalived
:
! /etc/keepalived/keepalived.conf
! Configuration File for keepalived
global_defs {
router_id LVS_DEVEL
}
vrrp_script check_apiserver {
script "/etc/keepalived/check_apiserver.sh"
interval 3
weight -2
fall 10
rise 2
}
vrrp_instance VI_1 {
state ${STATE}
interface ${INTERFACE}
virtual_router_id ${ROUTER_ID}
priority ${PRIORITY}
authentication {
auth_type PASS
auth_pass ${AUTH_PASS}
}
virtual_ipaddress {
${APISERVER_VIP}
}
track_script {
check_apiserver
}
}
其中存在一些 bash
变量风格的占位符,需要根据实际需求替换:
${STATE}
MASTER
,其它所有节点配置为 BACKUP
MASTER
的节点${INTERFACE}
eth0
${ROUTER_ID}
keepalived
集群中的主机上相同${PRIORITY}
101
与 100
${AUTH_PASS}
keepalived
集群中的所有主机都有相同的值,如:42
${APISERVER_VIP}
keepalived
集群主机间进行协商的虚拟 IP上边的 keepalived
配置使用了一个健康检查脚本 /etc/keepalived/check_apiserver.sh
该脚本负责确保在持有虚拟 IP 的节点上,API 服务器处于可用状态
脚本类似:
#!/bin/sh
errorExit() {
echo "*** $*" 1>&2
exit 1
}
curl --silent --max-time 2 --insecure https://localhost:${APISERVER_DEST_PORT}/ -o /dev/null || errorExit "Error GET https://localhost:${APISERVER_DEST_PORT}/"
if ip addr | grep -q ${APISERVER_VIP}; then
curl --silent --max-time 2 --insecure https://${APISERVER_VIP}:${APISERVER_DEST_PORT}/ -o /dev/null || errorExit "Error GET https://${APISERVER_VIP}:${APISERVER_DEST_PORT}/"
fi
其中存在一些 bash
变量风格的占位符,需要根据实际需求替换:
${APISERVER_VIP}
keepalived
集群主机间进行协商的虚拟 IP${APISERVER_DEST_PORT}
haproxy
配置由 1 个文件组成
服务配置文件一般位于 /etc/haproxy
目录,但有些 Linux 发行版会将其放在别的地方
下边的配置能够成功用于 2.1.4 版本的 haproxy
:
# /etc/haproxy/haproxy.cfg
#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
log /dev/log local0
log /dev/log local1 notice
daemon
#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
mode http
log global
option httplog
option dontlognull
option http-server-close
option forwardfor except 127.0.0.0/8
option redispatch
retries 1
timeout http-request 10s
timeout queue 20s
timeout connect 5s
timeout client 20s
timeout server 20s
timeout http-keep-alive 10s
timeout check 10s
#---------------------------------------------------------------------
# apiserver frontend which proxys to the control plane nodes
#---------------------------------------------------------------------
frontend apiserver
bind *:${APISERVER_DEST_PORT}
mode tcp
option tcplog
default_backend apiserver
#---------------------------------------------------------------------
# round robin balancing for apiserver
#---------------------------------------------------------------------
backend apiserver
option httpchk GET /healthz
http-check expect status 200
mode tcp
option ssl-hello-chk
balance roundrobin
server ${HOST1_ID} ${HOST1_ADDRESS}:${APISERVER_SRC_PORT} check
# [...]
其中存在一些 bash
变量风格的占位符,需要根据实际需求替换:
${APISERVER_DEST_PORT}
${APISERVER_SRC_PORT}
${HOST1_ID}
${HOST1_ADDRESS}
server
行,被负载均衡的 API 服务器主机各对应一行可以使用不同发行版的包管理器来安装 keepalived
与 haproxy
之前的配置都已经设置好之后就可以开启服务了
在基于 RedHat 的系统上,可以使用 systemd
来启动
# systemctl enable haproxy --now
# systemctl enable keepalived --now
服务运行起来后就可以用 kubeadm init
启动 Kubernetes 集群了
如果 keepalived
与 haproxy
运行在控制平面节点上,可以将它们配置为以静态 pod 的形式运行
这需要在 /etc/kubernetes/manifests
目录创建对应的清单文件
在启动过程中,kubelet
会启动进程,这样集群就可以在开始时使用它们了
这样做可以让 keepalived
与 haproxy
也享受容器技术的好处
如可以避免受到特定环境的影响、能被 k8s 管理等
keepalived
的清单文件 /etc/kubernetes/manifests/keepalived.yaml
:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
name: keepalived
namespace: kube-system
spec:
containers:
- image: osixia/keepalived:2.0.17
name: keepalived
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
- NET_BROADCAST
- NET_RAW
volumeMounts:
- mountPath: /usr/local/etc/keepalived/keepalived.conf
name: config
- mountPath: /etc/keepalived/check_apiserver.sh
name: check
hostNetwork: true
volumes:
- hostPath:
path: /etc/keepalived/keepalived.conf
name: config
- hostPath:
path: /etc/keepalived/check_apiserver.sh
name: check
status: {}
haproxy
的清单文件 /etc/kubernetes/manifests/haproxy.yaml
:
apiVersion: v1
kind: Pod
metadata:
name: haproxy
namespace: kube-system
spec:
containers:
- image: haproxy:2.1.4
name: haproxy
livenessProbe:
failureThreshold: 8
httpGet:
host: localhost
path: /healthz
port: ${APISERVER_DEST_PORT}
scheme: HTTPS
volumeMounts:
- mountPath: /usr/local/etc/haproxy/haproxy.cfg
name: haproxyconf
readOnly: true
hostNetwork: true
volumes:
- hostPath:
path: /etc/haproxy/haproxy.cfg
type: FileOrCreate
name: haproxyconf
status: {}
其中的 bash
变量风格的占位符 ${APISERVER_DEST_PORT}
需要与前边 /etc/haproxy/haproxy.cfg
中的配置一致
服务运行起来后就可以用 kubeadm init
启动 Kubernetes 集群了
作为传统的 keepalived
与 haproxy
的一个替代方案
kube-vip
用一个服务同时实现了虚拟 IP 管理与负载均衡功能
kube-vip
可以在 2 层(用 ARP 与 leaderElection)或 3 层用 BGP 对等互连来实现
与 3.4 中的方式 2 类似,kube-vip
会作为控制平面节点上的静态 pod 运行
类似 keepalived
,协商虚拟 IP 的主机需要在同一个 IP 子网
类似 haproxy
,基于流的(stream-based)负载均衡允许 TLS 终端由其后面的 API 服务器实例处理
注意:
kube-vip
需要具备访问 API 服务器的权限
特别是在集群初始化期间(即在kubeadm init
阶段)
此时,admin.conf
是唯一可用于kube-vip
进行身份验证并与 API 服务器通信的 kubeconfig
集群启动后,建议用户签署自定义客户端 kubeconfig 并在到期时手动轮换
export VIP=192.168.0.40`
export INTERFACE=<interface>
KVVERSION=$(curl -sL https://api.github.com/repos/kube-vip/kube-vip/releases | jq -r ".[0].name")
export KVVERSION=vx.x.x
创建清单的最简单的方法是利用容器自己来创建
alias kube-vip="ctr run --rm --net-host ghcr.io/kube-vip/kube-vip:$KVVERSION vip /kube-vip"
alias kube-vip="docker run --network host --rm ghcr.io/kube-vip/kube-vip:$KVVERSION"
此配置将使用 leaderElection 创建一个清单
该清单用于启动 kube-vip
来提供控制平面与服务管理
当这个实例被选为 leader 时,会将 vip
绑定到指定的接口上
对于 type:LoadBalancer
的服务也是如此
export INTERFACE=eth0
kube-vip manifest pod \
--interface $INTERFACE \
--vip $VIP \
--controlplane \
--arp \
--leaderElection | tee /etc/kubernetes/manifests/kube-vip.yaml
清单的例子:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
name: kube-vip
namespace: kube-system
spec:
containers:
- args:
- manager
env:
- name: vip_arp
value: "true"
- name: port
value: "6443"
- name: vip_interface
value: ens192
- name: vip_cidr
value: "32"
- name: cp_enable
value: "true"
- name: cp_namespace
value: kube-system
- name: vip_ddns
value: "false"
- name: vip_leaderelection
value: "true"
- name: vip_leaseduration
value: "5"
- name: vip_renewdeadline
value: "3"
- name: vip_retryperiod
value: "1"
- name: vip_address
value: 192.168.0.40
image: ghcr.io/kube-vip/kube-vip:v0.4.0
imagePullPolicy: Always
name: kube-vip
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
- NET_RAW
- SYS_TIME
volumeMounts:
- mountPath: /etc/kubernetes/admin.conf
name: kubeconfig
hostAliases:
- hostnames:
- kubernetes
ip: 127.0.0.1
hostNetwork: true
volumes:
- hostPath:
path: /etc/kubernetes/admin.conf
name: kubeconfig
status: {}
此配置将创建一个清单
该清单用于启动 kube-vip
来提供控制平面与服务管理
与 ARP 不同,BGP 配置中的所有节点都会发布虚拟 IP 地址
注意:
将地址绑定到lo
上,是为了避免多个设备在公共接口上具有相同的地址
可以在一个逗号分隔的列表中指定所有对等点,格式为address:AS:password:multihop
export INTERFACE=lo
kube-vip manifest pod \
--interface $INTERFACE \
--vip $VIP \
--controlplane \
--bgp \
--localAS 65000 \
--bgpRouterID 192.168.0.2 \
--bgppeers 192.168.0.10:65000::false,192.168.0.11:65000::false | tee /etc/kubernetes/manifests/kube-vip.yaml
清单的例子:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
name: kube-vip
namespace: kube-system
spec:
containers:
- args:
- manager
env:
- name: vip_arp
value: "false"
- name: port
value: "6443"
- name: vip_interface
value: ens192
- name: vip_cidr
value: "32"
- name: cp_enable
value: "true"
- name: cp_namespace
value: kube-system
- name: vip_ddns
value: "false"
- name: bgp_enable
value: "true"
- name: bgp_routerid
value: 192.168.0.2
- name: bgp_as
value: "65000"
- name: bgp_peeraddress
- name: bgp_peerpass
- name: bgp_peeras
value: "65000"
- name: bgp_peers
value: 192.168.0.10:65000::false,192.168.0.11:65000::false
- name: vip_address
value: 192.168.0.40
image: ghcr.io/kube-vip/kube-vip:v0.4.0
imagePullPolicy: Always
name: kube-vip
resources: {}
securityContext:
capabilities:
add:
- NET_ADMIN
- NET_RAW
- SYS_TIME
volumeMounts:
- mountPath: /etc/kubernetes/admin.conf
name: kubeconfig
hostAliases:
- hostnames:
- kubernetes
ip: 127.0.0.1
hostNetwork: true
volumes:
- hostPath:
path: /etc/kubernetes/admin.conf
name: kubeconfig
status: {}
服务运行起来后就可以用 kubeadm init
启动 Kubernetes 集群了
以上步骤完成后就可以启动集群了,详见:用 kubeadm 创建高可用集群
在上述配置中,如果 ${APISERVER_DEST_PORT}
的值不是 6443
则需要告知 kubeadm init
将 API 服务器端口设置为对应的值
假设集群的被负载均衡的 API 服务器的端口为 8443
虚拟 IP 的 DNS 名称为 vip.mycluster.local
则需要设置 kubeadm
参数 --control-plane-endpoint
:
# kubeadm init --control-plane-endpoint vip.mycluster.local:8443 [additional arguments ...]