使用 kubeadm 安装 k8s 集群
本次实践所使用的集群的组件:
- cilium —— 一个 CNI 组件。网络连通性的功能很普通,默认使用 vxlan 实现,也可以自行配置 bgp,甚至可以利用 CNI 的链把通网的功能交给 calico 等其他 CNI 来实现。其亮点在与基于 ebpf 实现的网络策略和链路跟踪功能,同时也可以使用 ebpf 实现 k8s 的 service vip 功能,从而替换 kube-proxy。cilium 使用的 epbf 依赖 新版 linux 内核 。
由于 linux 内核 nf_conntrack 功能的问题,导致 iptables 和 ipvs 对 service 的 vip 进行 nat 时,将并发的、且源 ip、源端口、目标 ip、目标端口相同的两个 udp 协议呀请求识别为一个“连接”,于是在目标返回第一个响应时,认为这个“连接”已经结束,从而抛弃掉第二个响应。导致请求方等待响应超时重试的问题。
由于这个问题在新版本的内核中也没有完全修复,因此决定抛弃 kube-proxy,改为完全实现了 kube-proxy 所有功能的 CNI 组件——cilium。
RKE 虽然支持自定义 CNI 组件,但并不支持跳过 kube-proxy 的安装。而且我们在项目中的其他需求,也因为 RKE 对集群的傻瓜式管理造成了自定义集群的困难。因此,我们决定抛弃 RKE,使用 GKE。
- 由于 cilium 的 ebpf 功能依赖新版本的 linux 内核。官方推荐内核 5.3+,因此我们直接使用 elrepo 上最新版的 ml 内核。
操作系统、节点准备、Docker 环境、nexus3 仓库配置与 《rancher 高可用 + 离线部署》 中类似,细节略有不同,本实践不再详细说明。
节点规划
192.168.1.146 etcd, controlplane
192.168.1.147 etcd, controlplane
192.168.1.148 etcd, controlplane
192.168.1.149 worker
192.168.1.150 worker
192.168.1.151 worker
controlplane
节点也被成为 master
。上面运行着以下重要部件:
-
etcd
分布式数据库。k8s 集群所有状态和数据均保存在这里。与 zookeeper 的原理类似,3 个节点即可容许 1 个节点失效。etcd 只与 apiserver 进行交互。每个 apiserver 直接访问与它同在一台机器上的 etcd 即可。 -
kube-apiserver
统一 api 接口。k8s 的所有组件均与 apiserver 交互。 -
kube-scheduler
调度器。决定如何分配资源的组件。 -
kube-controller
控制器。决定如何实现资源的组件。
外部负载均衡器
在集群中,etcd 本身组成高可用集群后,每个 etcd 实例只被所在节点上的 apiserver 访问。因此不需要负载均衡器。scheduler 调度器和 controller 不会被主动访问,因此也不需要负载均衡器。只有 apiserver 作为用户和各个组件与集群交互的入口,需要一个负载均衡器来保证 apiserver 的高可用。
在集群内的 default 命名空间下,有一个名为 kubernetes
的 service 服务即为集群内 apiserver 的负载均衡器。而针对 apiserver 在集群外的访问,需要一个外部的负载均衡器。apiserver 虽然是基于 http 的 json api,但是鉴权使用的是 tls 双向证书。因此,你需要一个 4 层的负载均衡器,为 apiserver 进行高可用配置。无论你使用 haproxy、lvs 还是 F5、nginx stream,你都只需要将一个负载均衡器的统一入口,即 域名/ip + 端口 转发至所有 apiserver 的端口即可。
没有外部负载均衡器时的变通
本实践中,我们将会使 apiserver 监听 6444 端口,同时指定 apiserver 的统一入口为 127.0.0.1:6443。在集群所有节点上手动启动一个 nginx 容器,将 127.0.0.1:6443 转发至所有 apiserver 的 6444 端口。
将如下所示的 nginx 配置保存为:/opt/kube-api-proxy.conf
error_log stderr notice;
worker_processes auto;
events {
multi_accept on;
use epoll;
worker_connections 1024;
}
stream {
upstream kube_apiserver {
server 192.168.1.146:6444;
server 192.168.1.147:6444;
server 192.168.1.148:6444;
}
server {
listen 6443;
proxy_pass kube_apiserver;
proxy_timeout 30;
proxy_connect_timeout 2s;
}
}
运行一个始终启动的 nginx 容器:
docker run -d --restart=always --network=host \
--name nginx-proxy \
-v /opt/kube-api-proxy.conf:/etc/nginx/nginx.conf \
nginx:1.17.10-alpine
这样在集群所有节点上启动这个 nginx 容器后,我们就可以愉快的开始折腾 kubeadm 了。
安装 controlplane
我们规划了三台节点作为 controlane。我们只用在一台节点上设置好集群的配置文件,安装好这台节点后,其他节点依据节点角色直接加入集群即可。
初始化第一个 controlplane
首先我们为集群生成默认配置:
kubeadm config print init-defaults > kubeadm.init.yaml
接着修改以下字段:
apiVersion: kubeadm.k8s.io/v1beta2
kind: InitConfiguration
bootstrapTokens:
- groups:
token: foobarfoobarfoobar # 修改加入集群所使用的 token
localAPIEndpoint:
advertiseAddress: 192.168.1.146 # 这个节点上的 apiserver 监听的 ip
bindPort: 6444 # apiserver 监听的端口
nodeRegistration:
name: 192.168.1.146 # 这个节点的名字
---
kind: ClusterConfiguration
clusterName: originsteam # 集群名字
etcd:
local:
dataDir: /opt/etcd # etcd 使用的数据目录。etcd 对磁盘 io 要求较高。
imageRepository: google_containers # docker 镜像的路径。本实践使用的 nexus3 代理了 https://registry.aliyuncs.com 仓库。
controlPlaneEndpoint: "127.0.0.1:6443" # apiserver 的外部负载均衡入口
networking:
dnsDomain: cluster.local # 集群内部 dns 使用的域
serviceSubnet: 10.43.0.0/16 # 集群 server 使用的 cicr
podSubnet: 10.44.0.0/16 # 集群 pod 使用的 cicr
之后我们就可以启动第一个节点了:
kubeadm init \
--config kubeadm.init.yaml \
--upload-certs \
--skip-phases=addon/kube-proxy
由于我们需要使用 cilium 替换 kube-proxy,因此我们使用 --skip-phases=addon/kube-proxy
跳过了 kube-proxy 的安装。
如果安装失败,你可以使用 kubeadm revert
命令重置这台机器上 kubeadm 的操作,然后重新安装。
安装成功后,命令会输出一大堆内容,告诉你其他节点加入集群的方法。我们只需要记住里面的两个参数,并替换到下面的命令中:
-
--discovery-token-ca-cert-hash sha256:fobarfoobarfoobarbar
集群 ca 指纹 -
--certificate-key barfoobarfoobarfoofoo
新增 controplane 节点需要的私钥指纹
token
是你在 kubeadm.init.yaml
里面设置的。
安装其他 controlplane 节点
在其他 controlplane 节点上运行,注意替换命令中的参数:
kubeadm join \
127.0.0.1:6443 \
--token foobarfoobarfoobar \
--discovery-token-ca-cert-hash sha256:fobarfoobarfoobarbar \
--control-plane \
--certificate-key barfoobarfoobarfoofoo \
--apiserver-advertise-address 192.168.1.148 \ # 替换为节点 ip
--node-name 192.168.1.148 \ # 替换为节点 ip
--apiserver-bind-port 6444
如果安装失败,你可以使用 kubeadm revert
命令重置这台机器上 kubeadm 的操作,然后重新安装。
安装 CNI 组件
在线安装:
helm repo add cilium https://helm.cilium.io/
helm install cilium cilium/cilium --version 1.7.3 \
--namespace kube-system \
--set global.kubeProxyReplacement=strict \
--set global.registry=cilium \
--set global.k8sServiceHost=127.0.0.1 \
--set global.k8sServicePort=6443
离线安装
可在有 helm 的机器上渲染好 yaml:
helm fetch cilium/cilium --version 1.7.3
helm template cilium ./cilium-1.7.3.tgz \
--output-dir ./ \
--namespace kube-system \
--set global.kubeProxyReplacement=strict \
--set global.registry=cilium \
--set global.k8sServiceHost=127.0.0.1 \
--set global.k8sServicePort=6443
然后将渲染好的 cilium 目录打包至集群安装:
kubectl apply -R -f cilium
加入 worker 节点
kubeadm join \
127.0.0.1:6443 \
--token foobarfoobarfoobar \
--discovery-token-ca-cert-hash sha256:fobarfoobarfoobarbar \
--node-name 192.168.1.149
如果安装失败,你可以使用 kubeadm revert
命令重置这台机器上 kubeadm 的操作,然后重新安装。
metrics-server
可在有 helm 的机器上渲染好 yaml:
helm fetch stable/metrics-server
helm template metrics-server ./metrics-server-2.11.1.tgz \
--output-dir . \
--namespace kube-system \
--set image.repository=google_containers/metrics-server-amd64 \
--set args='{"--kubelet-insecure-tls"}'
然后将渲染好的 cilium 目录打包至集群安装:
kubectl apply -R -f metrics-server
ingress-nginx
可在有 helm 的机器上渲染好 yaml:
helm fetch ingress-nginx/ingress-nginx
helm template ingress-nginx ./ingress-nginx-2.1.0.tgz \
--output-dir . \
--namespace ingress-nginx \
--set controller.image.repository=kubernetes-ingress-controller/nginx-ingress-controller \
--set controller.dnsPolicy=ClusterFirstWithHostNet \
--set controller.hostNetwork=true \
--set controller.kind=DaemonSet \
--set controller.service.enabled=false \
--set controller.reportNodeInternalIp=true \
--set controller.admissionWebhooks.enabled=false
然后将渲染好的 cilium 目录打包至集群安装:
kubectl create namespace ingress-nginx
kubectl apply -n ingress-nginx -R -f ingress-nginx
hubble
Network, Service & Security Observability for Kubernetes
hubble 是 cilium 的 web ui,用于观测 cilium 对集群的流量监测。
hubble 目前没有 helm repo,所以直接下载 git 仓库后,在 install/kubernetes
下渲染。
helm template hubble-ui hubble \
--output-dir ../ \
--namespace kube-system \
--set metrics.enabled="{dns,drop,tcp,flow,port-distribution,icmp,http}" \
--set ui.enabled=true \
--set image.repository=cilium/hubble \
--set ui.image.repository=cilium/hubble-ui \
# 如果不想手动配置 ingress,可以添加以下配置
# --set ingress.enabled=true \
# --set ingress.hosts="{hubble.foobar.com}" \
渲染完成后即可将 install/hubble
目录打包至集群安装:
kubectl apply -R -f .
图形化管理
在初始化第一个 controplane 节点后,你就可以直接使用 Lens 这个本地图形化客户端、k8s IDE 来观察和管理集群了。当然,你也可以安装 rancher 来基于 web GUI 来管理,你可以参考 《rancher 高可用 + 离线部署》的部分内容来为 GKE 集群安装 rancher。
离线安装的镜像准备
外部负载均衡器
来自 dockerhub
的 nginx:1.17.10-alpine
GKE
配置好 kubeadm.init.yaml
后,使用以下命令即可获取所需的镜像:
kubeadm config images list --config kubeadm.init.yaml
来自 registry.aliyuncs.com
仓库的
google_containers/kube-apiserver:v1.18.0
google_containers/kube-controller-manager:v1.18.0
google_containers/kube-scheduler:v1.18.0
google_containers/kube-proxy:v1.18.0
google_containers/pause:3.2
google_containers/etcd:3.4.3-0
google_containers/coredns:1.6.7
cilium
可以在渲染好的 yaml 中找到,来自 dockerhub
的
cilium/cilium:v1.7.3
cilium/operator:v1.7.3
metrics-server
可以在渲染好的 yaml 中找到,来自 registry.aliyuncs.com
仓库的
google_containers/metrics-server-amd64:v0.3.6
ingress-nginx
可以在渲染好的 yaml 中找到,来自 quay.io
仓库的
kubernetes-ingress-controller/nginx-ingress-controller:0.32.0
hubble
可以在渲染好的 yaml 中找到,来自 quay.io
仓库的
cilium/hubble:v0.5.0
cilium/hubble-ui:latest
备份
集群需要备份的内容有:
- 第一个 controplane 节点上的
/etc/kubernetes/pki
证书目录 - etcd 快照
- 其它持久化数据,比如持久卷。
一般生产环境中,第三点由专门的存储服务设施完成,和集群分离,且带有快照功能。因此,本实践仅讨论前两点的恢复。
证书和私钥
pki
目录中保存着整个集群使用到的所有证书和私钥,除用作根 ca 的证书是 10 年有效期外,其它证书均为 1 年有效期。因此,证书目录的备份不用太频繁,可以选择每个月备份一次即可。
由于 kubeadm 签署的证书,每个 controplane 节点均不完全一样,具体区别在证书使用者的 ip 上。我们只需要选择一个 controlplane 节点,备份上面的 /etc/kubernetes/pki
目录即可。当然,恢复时也需要在这个节点上进行——至少是同样ip的节点上。
etcd
etcd 可以选择每天备份,甚至每小时备份。无论你使用脚本、crontab、或者使用 k8s 自己的定时任务都可以。
etcdctl \
--cert /etc/kubernetes/pki/etcd/peer.crt \
--key /etc/kubernetes/pki/etcd/peer.key \
--cacert /etc/kubernetes/pki/etcd/ca.crt \
--endpoints https://192.198.1.146:2379 \
snapshot save etcd-snapshot-save.db
将 pki
目录和 etcd-snapshot-save.db
文件打包压缩备份即可。
恢复
在所有 controlplane
节点上运以下命令以重置节点环境:
kubeadm reset -f
最好 reboot 一次,这样节点的 iptables、ipvs、网络接口等状态会被清除。
然后选择一台节点进行
恢复备份的 pki
证书到 /etc/kubernetes/pki
cp -r pki /etc/kubernetes/
可以运行以下命令看看证书里面的 ip 和节点 ip 是否对应:
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep kubernetes.default.svc
为 etcd 节点生成数据文件
etcdctl snapshot restore etcd-snapshot-save.db \
--name=192.168.1.146 \
--initial-cluster=192.168.1.146=https://192.168.1.146:2380 \
--initial-advertise-peer-urls=https://192.168.1.146:2380
将生成的数据文件放置在 etcd 数据目录中:
cp -r 192.168.1.146.etcd/member/ /opt/etcd/
准备好安装集群时的 kubeadm.init.yaml
,使用 kubeadm
初始化集群:
kubeadm init \
--config kubeadm.init.yaml \
--upload-certs \
--skip-phases=addon/kube-proxy\
--ignore-preflight-errors=DirAvailable--opt-etcd
这里加入了 --ignore-preflight-errors=DirAvailable--opt-etcd
参数使 kubeadm
忽略了 etcd 数据目录非空的错误,因此 kubeadm
初始化的“新”集群就会依然使用老集群的数据。同时,由于已经提前放置好了 pki
目录,证书不会重新生成,而直接使用了老的证书。
和安装新集群一样,我们只需要记住命令运行结束后输出的两个参数:
-
--discovery-token-ca-cert-hash sha256:fobarfoobarfoobarbar
集群 ca 指纹,和旧集群一样 -
--certificate-key barfoobarfoobarfoofoo
新增 controplane 节点需要的私钥指纹。新的,要记
token
是你在 kubeadm.init.yaml
里面设置的。和旧集群一样。
接着我们将剩余的 controplane 节点加入集群。你需要先删除刚刚恢复的集群中的其它 controplane 节点,然后再运行命令加入集群,注意替换命令中的参数:
kubeadm join \
127.0.0.1:6443 \
--token foobarfoobarfoobar \
--discovery-token-ca-cert-hash sha256:fobarfoobarfoobarbar \
--control-plane \
--certificate-key barfoobarfoobarfoofoo \
--apiserver-advertise-address 192.168.1.148 \ # 替换为节点 ip
--node-name 192.168.1.148 \ # 替换为节点 ip
--apiserver-bind-port 6444
将其它 controlplane 节点安装完毕后,集群恢复完成。