我们直接在 etcd Kubernetes 集群中处理数据的经验(没有 K8s API)

越来越多的客户要求我们提供对 Kubernetes 集群的访问,以便能够访问集群内的服务:能够直接连接到某些数据库或服务,将本地应用程序与集群内的应用程序连接......

我们直接在 etcd Kubernetes 集群中处理数据的经验(没有 K8s API)

例如,需要从本地计算机连接到服务 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 帮助程序 来自 OpenShift (感谢其作者!).

该实用程序可以使用证书连接到 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(含)颁发证书的服务子网,因此需要重新颁发证书:

  1. 让我们看看当前证书已颁发给哪些域和 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
  2. 让我们为 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-адрес мастер узла
  3. 让我们删除旧的 crt 和密钥,因为没有这个,将不会颁发新证书:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. 让我们为 API 服务器重新颁发证书:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. 让我们检查是否为新子网颁发了证书:
    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
  6. 重新颁发API服务器证书后,重新启动其容器:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. 让我们重新生成配置 admin.conf:
    kubeadm alpha certs renew admin.conf
  8. 让我们编辑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 规则从旧子网更改为新子网。 本文进一步介绍了最大限度减少停机时间的可能选项。

  9. 让我们修复命名空间中的 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 到一个新的子网。

  10. 由于 kube-dns 地址已更改,因此需要更新所有节点上的 kubelet 配置:
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. 剩下的就是重新启动集群中的所有 pod:
    kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(S+)s+(S+).*/kubectl --namespace 1 delete pod 2/e'

最大限度地减少停机时间

关于如何最大限度地减少停机时间的想法:

  1. 更改控制平面清单后,创建一个新的 kube-dns 服务,例如,名称为 kube-dns-tmp 和新地址 172.24.0.10.
  2. 使 if 在etcdhelper中,它不会修改kube-dns服务。
  3. 替换所有kubelet中的地址 ClusterDNS 到新服务,而旧服务将继续与新服务同时工作。
  4. 等待带有应用程序的 Pod 因自然原因自行滚动或在约定的时间滚动。
  5. 删除服务 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 可以更简单(例如, 所以)。 但我们想学习如何直接使用 etcd,因为在 etcd 中编辑 Kubernetes 对象时存在一些情况 - 唯一的 可能的变体。 (例如,您不能在不停机的情况下只更改服务字段 spec.clusterIP.)

本文讨论了直接使用 etcd 中的数据的可能性,即绕过 Kubernetes API。 有时这种方法可以让你做“棘手的事情”。 我们在真实的 K8s 集群上测试了文中给出的操作。 然而,它们广泛使用的准备状态是 PoC(概念验证)。 因此,如果您想在集群上使用 etcdhelper 实用程序的修改版本,请自行承担风险。

PS

另请阅读我们的博客:

来源: habr.com

添加评论