越来越多的客户要求我们提供对 Kubernetes 集群的访问,以便能够访问集群内的服务:能够直接连接到某些数据库或服务,将本地应用程序与集群内的应用程序连接......
例如,需要从本地计算机连接到服务 memcached.staging.svc.cluster.local
。 我们使用客户端连接的集群内的 VPN 来提供此功能。 为此,我们宣布 Pod、服务的子网,并将集群 DNS 推送到客户端。 因此,当客户端尝试连接到服务时 memcached.staging.svc.cluster.local
,请求发送到集群 DNS,并作为响应从集群服务网络或 pod 地址接收该服务的地址。
我们使用kubeadm配置K8s集群,其中默认服务子网为 192.168.0.0/16
,并且 Pod 的网络是 10.244.0.0/16
。 通常一切都很顺利,但有几点:
- 子网
192.168.*.*
通常用于客户端办公网络,甚至更常用于开发人员家庭网络。 然后我们就会遇到冲突:家庭路由器在该子网上工作,而 VPN 将这些子网从集群推送到客户端。 - 我们有几个集群(生产集群、阶段集群和/或几个开发集群)。 然后,默认情况下,所有 Pod 和服务都将具有相同的子网,这给多个集群中的服务同时工作带来了很大的困难。
我们很早以前就采用了在同一项目中为服务和 Pod 使用不同子网的做法 - 一般来说,以便所有集群都有不同的网络。 但是,有大量正在运行的集群,我不想从头开始滚动,因为它们运行许多服务、有状态应用程序等。
然后我们问自己:如何更改现有集群中的子网?
搜索解决方案
最常见的做法是重新创建 所有 ClusterIP 类型的服务。 作为一种选择,
以下过程有一个问题:配置完所有内容后,Pod 在 /etc/resolv.conf 中使用旧 IP 作为 DNS 名称服务器。
由于我仍然没有找到解决方案,我不得不使用 kubeadm reset 重置整个集群并再次初始化。
但这并不适合所有人...以下是我们案例的更详细介绍:
- 采用法兰绒;
- 云端和硬件上都有集群;
- 我想避免重新部署集群中的所有服务;
- 一般来说,需要以最少的问题来完成所有事情;
- Kubernetes 版本是 1.16.6(但是,其他版本的进一步步骤类似);
- 主要任务是确保在使用 kubeadm 部署且具有服务子网的集群中
192.168.0.0/16
,将其替换为172.24.0.0/16
.
碰巧的是,我们长期以来一直有兴趣了解 Kubernetes 中 etcd 中存储了什么以及如何存储,可以用它做什么......所以我们想:“为什么不直接更新 etcd 中的数据,用新的 IP 地址(子网)替换旧的 IP 地址(子网)“?
在搜索了用于处理 etcd 中数据的现成工具后,我们没有找到任何可以完全解决问题的工具。 (顺便说一句,如果您了解直接在 etcd 中处理数据的任何实用程序,我们将不胜感激这些链接。) 然而,一个好的起点是
该实用程序可以使用证书连接到 etcd 并使用命令从那里读取数据 ls
, get
, dump
.
添加etcdhelper
下一个想法是合乎逻辑的:“是什么阻止您通过添加将数据写入 etcd 的功能来添加此实用程序?”
它成为etcdhelper的修改版本,有两个新功能 changeServiceCIDR
и changePodCIDR
. 在她的 你可以看到代码
新功能有什么作用? 算法 changeServiceCIDR
:
- 创建一个解串器;
- 编译正则表达式来替换CIDR;
- 我们检查集群中具有 ClusterIP 类型的所有服务:
- 将 etcd 中的值解码为 Go 对象;
- 使用正则表达式,我们替换地址的前两个字节;
- 为该服务分配新子网的 IP 地址;
- 创建一个序列化器,将Go对象转换为protobuf,将新数据写入etcd。
功能 changePodCIDR
本质上相似 changeServiceCIDR
- 只是我们不编辑服务规范,而是为节点进行编辑并更改 .spec.PodCIDR
到一个新的子网。
实践
更改服务CIDR
执行该任务的计划非常简单,但它涉及到重新创建集群中所有 Pod 时的停机时间。 在描述主要步骤之后,我们还将分享理论上如何最大限度地减少停机时间的想法。
准备步骤:
- 安装必要的软件并组装打过补丁的 etcdhelper;
- 备份 etcd 和
/etc/kubernetes
.
更改服务CIDR的简要行动计划:
- 更改 apiserver 和controller-manager 清单;
- 重新颁发证书;
- 更改etcd中的ClusterIP服务;
- 重新启动集群中的所有 Pod。
以下是详细的完整操作序列。
1.安装etcd-client进行数据转储:
apt install etcd-client
2. 构建etcdhelper:
- 安装golang:
GOPATH=/root/golang mkdir -p $GOPATH/local curl -sSL https://dl.google.com/go/go1.14.1.linux-amd64.tar.gz | tar -xzvC $GOPATH/local echo "export GOPATH="$GOPATH"" >> ~/.bashrc echo 'export GOROOT="$GOPATH/local/go"' >> ~/.bashrc echo 'export PATH="$PATH:$GOPATH/local/go/bin"' >> ~/.bashrc
- 我们为自己存钱
etcdhelper.go
,下载依赖,收集:wget https://raw.githubusercontent.com/flant/examples/master/2020/04-etcdhelper/etcdhelper.go go get go.etcd.io/etcd/clientv3 k8s.io/kubectl/pkg/scheme k8s.io/apimachinery/pkg/runtime go build -o etcdhelper etcdhelper.go
3. 备份etcd:
backup_dir=/root/backup
mkdir ${backup_dir}
cp -rL /etc/kubernetes ${backup_dir}
ETCDCTL_API=3 etcdctl --cacert=/etc/kubernetes/pki/etcd/ca.crt --key=/etc/kubernetes/pki/etcd/server.key --cert=/etc/kubernetes/pki/etcd/server.crt --endpoints https://192.168.199.100:2379 snapshot save ${backup_dir}/etcd.snapshot
4. 更改 Kubernetes 控制平面清单中的服务子网。 在文件中 /etc/kubernetes/manifests/kube-apiserver.yaml
и /etc/kubernetes/manifests/kube-controller-manager.yaml
改变参数 --service-cluster-ip-range
到一个新的子网: 172.24.0.0/16
而不是 192.168.0.0/16
.
5. 由于我们正在更改 kubeadm 为 apiserver(含)颁发证书的服务子网,因此需要重新颁发证书:
- 让我们看看当前证书已颁发给哪些域和 IP 地址:
openssl x509 -noout -ext subjectAltName </etc/kubernetes/pki/apiserver.crt X509v3 Subject Alternative Name: DNS:dev-1-master, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, DNS:apiserver, IP Address:192.168.0.1, IP Address:10.0.0.163, IP Address:192.168.199.100
- 让我们为 kubeadm 准备一个最小的配置:
cat kubeadm-config.yaml apiVersion: kubeadm.k8s.io/v1beta1 kind: ClusterConfiguration networking: podSubnet: "10.244.0.0/16" serviceSubnet: "172.24.0.0/16" apiServer: certSANs: - "192.168.199.100" # IP-адрес мастер узла
- 让我们删除旧的 crt 和密钥,因为没有这个,将不会颁发新证书:
rm /etc/kubernetes/pki/apiserver.{key,crt}
- 让我们为 API 服务器重新颁发证书:
kubeadm init phase certs apiserver --config=kubeadm-config.yaml
- 让我们检查是否为新子网颁发了证书:
openssl x509 -noout -ext subjectAltName </etc/kubernetes/pki/apiserver.crt X509v3 Subject Alternative Name: DNS:kube-2-master, DNS:kubernetes, DNS:kubernetes.default, DNS:kubernetes.default.svc, DNS:kubernetes.default.svc.cluster.local, IP Address:172.24.0.1, IP Address:10.0.0.163, IP Address:192.168.199.100
- 重新颁发API服务器证书后,重新启动其容器:
docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
- 让我们重新生成配置
admin.conf
:kubeadm alpha certs renew admin.conf
- 让我们编辑etcd中的数据:
./etcdhelper -cacert /etc/kubernetes/pki/etcd/ca.crt -cert /etc/kubernetes/pki/etcd/server.crt -key /etc/kubernetes/pki/etcd/server.key -endpoint https://127.0.0.1:2379 change-service-cidr 172.24.0.0/16
警告! 此时,域解析在集群中停止工作,因为在现有的 pod 中
/etc/resolv.conf
旧的 CoreDNS 地址 (kube-dns) 已注册,并且 kube-proxy 将 iptables 规则从旧子网更改为新子网。 本文进一步介绍了最大限度减少停机时间的可能选项。 - 让我们修复命名空间中的 ConfigMap
kube-system
:kubectl -n kube-system edit cm kubelet-config-1.16
- 替换此处
clusterDNS
到 kube-dns 服务的新 IP 地址:kubectl -n kube-system get svc kube-dns
.kubectl -n kube-system edit cm kubeadm-config
- 我们会解决它
data.ClusterConfiguration.networking.serviceSubnet
到一个新的子网。 - 由于 kube-dns 地址已更改,因此需要更新所有节点上的 kubelet 配置:
kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
- 剩下的就是重新启动集群中的所有 pod:
kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(S+)s+(S+).*/kubectl --namespace 1 delete pod 2/e'
最大限度地减少停机时间
关于如何最大限度地减少停机时间的想法:
- 更改控制平面清单后,创建一个新的 kube-dns 服务,例如,名称为
kube-dns-tmp
和新地址172.24.0.10
. - 使
if
在etcdhelper中,它不会修改kube-dns服务。 - 替换所有kubelet中的地址
ClusterDNS
到新服务,而旧服务将继续与新服务同时工作。 - 等待带有应用程序的 Pod 因自然原因自行滚动或在约定的时间滚动。
- 删除服务
kube-dns-tmp
和改变serviceSubnetCIDR
对于 kube-dns 服务。
该计划将使您能够将服务删除期间的停机时间降至约一分钟 kube-dns-tmp
并更改服务的子网 kube-dns
.
修改pod网络
同时,我们决定研究如何使用生成的 etcdhelper 修改 podNetwork。 操作顺序如下:
- 修复配置
kube-system
; - 修复 kube-controller-manager 清单;
- 直接在etcd中更改podCIDR;
- 重新启动所有集群节点。
现在详细了解这些操作:
1.修改命名空间中的ConfigMap kube-system
:
kubectl -n kube-system edit cm kubeadm-config
- 纠正 data.ClusterConfiguration.networking.podSubnet
到一个新的子网 10.55.0.0/16
.
kubectl -n kube-system edit cm kube-proxy
- 纠正 data.config.conf.clusterCIDR: 10.55.0.0/16
.
2. 修改控制器管理器清单:
vim /etc/kubernetes/manifests/kube-controller-manager.yaml
- 纠正 --cluster-cidr=10.55.0.0/16
.
3.查看当前值 .spec.podCIDR
, .spec.podCIDRs
, .InternalIP
, .status.addresses
对于所有集群节点:
kubectl get no -o json | jq '[.items[] | {"name": .metadata.name, "podCIDR": .spec.podCIDR, "podCIDRs": .spec.podCIDRs, "InternalIP": (.status.addresses[] | select(.type == "InternalIP") | .address)}]'
[
{
"name": "kube-2-master",
"podCIDR": "10.244.0.0/24",
"podCIDRs": [
"10.244.0.0/24"
],
"InternalIP": "192.168.199.2"
},
{
"name": "kube-2-master",
"podCIDR": "10.244.0.0/24",
"podCIDRs": [
"10.244.0.0/24"
],
"InternalIP": "10.0.1.239"
},
{
"name": "kube-2-worker-01f438cf-579f9fd987-5l657",
"podCIDR": "10.244.1.0/24",
"podCIDRs": [
"10.244.1.0/24"
],
"InternalIP": "192.168.199.222"
},
{
"name": "kube-2-worker-01f438cf-579f9fd987-5l657",
"podCIDR": "10.244.1.0/24",
"podCIDRs": [
"10.244.1.0/24"
],
"InternalIP": "10.0.4.73"
}
]
4. 通过直接更改 etcd 来替换 podCIDR:
./etcdhelper -cacert /etc/kubernetes/pki/etcd/ca.crt -cert /etc/kubernetes/pki/etcd/server.crt -key /etc/kubernetes/pki/etcd/server.key -endpoint https://127.0.0.1:2379 change-pod-cidr 10.55.0.0/16
5. 让我们检查一下 podCIDR 是否真的发生了变化:
kubectl get no -o json | jq '[.items[] | {"name": .metadata.name, "podCIDR": .spec.podCIDR, "podCIDRs": .spec.podCIDRs, "InternalIP": (.status.addresses[] | select(.type == "InternalIP") | .address)}]'
[
{
"name": "kube-2-master",
"podCIDR": "10.55.0.0/24",
"podCIDRs": [
"10.55.0.0/24"
],
"InternalIP": "192.168.199.2"
},
{
"name": "kube-2-master",
"podCIDR": "10.55.0.0/24",
"podCIDRs": [
"10.55.0.0/24"
],
"InternalIP": "10.0.1.239"
},
{
"name": "kube-2-worker-01f438cf-579f9fd987-5l657",
"podCIDR": "10.55.1.0/24",
"podCIDRs": [
"10.55.1.0/24"
],
"InternalIP": "192.168.199.222"
},
{
"name": "kube-2-worker-01f438cf-579f9fd987-5l657",
"podCIDR": "10.55.1.0/24",
"podCIDRs": [
"10.55.1.0/24"
],
"InternalIP": "10.0.4.73"
}
]
6. 让我们一一重启所有集群节点。
7. 如果至少留下一个节点 旧 PodCIDR,那么 kube-controller-manager 将无法启动,集群中的 pod 将无法被调度。
事实上,更改 podCIDR 可以更简单(例如, spec.clusterIP
.)
总
本文讨论了直接使用 etcd 中的数据的可能性,即绕过 Kubernetes API。 有时这种方法可以让你做“棘手的事情”。 我们在真实的 K8s 集群上测试了文中给出的操作。 然而,它们广泛使用的准备状态是 PoC(概念验证)。 因此,如果您想在集群上使用 etcdhelper 实用程序的修改版本,请自行承担风险。
PS
另请阅读我们的博客:
- «
etcd 3.4.3:存储可靠性和安全性研究 “; - «
Calico 用于 Kubernetes 中的网络:介绍和一点经验 “; - «
Kubernetes 运行中的 6 个有趣的系统错误 [及其解决方案] “; - «
Kubernetes 故障排除可视化指南 “。
来源: habr.com