- PVSM.RU - https://www.pvsm.ru -
Все чаще к нам обращаются клиенты с просьбой обеспечить доступ в Kubernetes-кластер для возможности обращения к сервисам внутри кластера: чтобы можно было напрямую подключиться к какой-то базе данных или сервису, для связи локального приложения с приложениями внутри кластера…
Например, возникает потребность подключиться со своей локальной машины к сервису memcached.staging.svc.cluster.local
. Мы предоставляем такую возможность с помощью VPN внутри кластера, к которому подключается клиент. Для этого анонсируем подсети pod'ов, сервисов и push'им кластерные DNS клиенту. Таким образом, когда клиент пытается подключиться к сервису memcached.staging.svc.cluster.local
, запрос уходит в DNS кластера и в ответ получает адрес данного сервиса из сервисной сети кластера или адрес pod'а.
K8s-кластеры мы настраиваем с помощью kubeadm, где по умолчанию сервисная подсеть — 192.168.0.0/16
, а сеть pod'ов — 10.244.0.0/16
. Обычно всё хорошо работает, но есть пара моментов:
192.168.*.*
часто используется в офисных сетях клиентов, а еще чаще — в домашних сетях разработчиков. И тогда у нас получаются конфликты: домашние роутеры работают в этой подсети и VPN push'ит эти подсети из кластера клиенту.Мы уже довольно давно приняли практику использования различных подсетей для сервисов и pod'ов в рамках одного проекта — в общем, чтобы все кластеры были с разными сетями. Однако есть большое количество кластеров в работе, которые не хотелось бы перекатывать с нуля, так как в них запущены многие сервисы, stateful-приложения и т.п.
И тогда мы задались вопросом: как бы поменять подсеть в существующем кластере?
Наиболее распространенная практика — пересоздать все сервисы с типом ClusterIP. Как вариант, могут посоветовать [1] и такое:
The following process has a problem: after everything configured, the pods come up with the old IP as a DNS nameserver in /etc/resolv.conf.
Since I still did not find the solution, i had to reset the entire cluster with kubeadm reset and init it again.
Но не всем это подходит… Вот более детальные вводные для нашего случая:
192.168.0.0/16
, заменить её на 172.24.0.0/16
.И так уж совпало, что нам давно было интересно посмотреть, что и как в Kubernetes хранится в etcd, что вообще с этим можно сделать… Вот и подумали: «Почему бы просто не обновить данные в etcd, заменив старые IP-адреса (подсеть) на новые?»
Поискав готовые инструменты для работы с данными в etcd, мы не нашли ничего полностью решающего поставленную задачу. (Кстати, если вы знаете о любых утилитах для работы с данными напрямую в etcd — будем признательны за ссылки.) Однако хорошей отправной точкой стала etcdhelper от OpenShift [2] (спасибо его авторам!).
Эта утилита умеет подключаться к etcd с помощью сертификатов и читать оттуда данные с помощью команд ls
, get
, dump
.
Следующая мысль закономерна: «Что мешает дописать эту утилиту, добавив возможность записи данных в etcd?»
Она воплотилась в модифицированную версию etcdhelper с двумя новыми функциями changeServiceCIDR
и changePodCIDR
. На её код можно посмотреть здесь [3].
Что делают новые функции? Алгоритм changeServiceCIDR
:
Функция changePodCIDR
по сути аналогична changeServiceCIDR
— только вместо редактирования спецификации сервисов мы делаем это для узла и меняем .spec.PodCIDR
на новую подсеть.
План по реализации поставленной задачи — очень простой, но подразумевает даунтайм на момент пересоздания всех pod'ов в кластере. После описания основных шагов мы также поделимся мыслями, как в теории можно минимизировать этот простой.
Подготовительные действия:
/etc/kubernetes
.Краткий план действий по смене serviceCIDR:
Далее представлена полная последовательность действий в деталях.
1. Устанавливаем etcd-client для дампа данных:
apt install etcd-client
2. Собираем etcdhelper:
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 control plane. В файлах /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’а (в том числе), их необходимо перевыпустить:
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
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-адрес мастер узла
rm /etc/kubernetes/pki/apiserver.{key,crt}
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
docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
admin.conf
:
kubeadm alpha certs renew admin.conf
./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 со старой подсети на новую. Далее в статье написано о возможных вариантах минимизировать простой.
kube-system
:
kubectl -n kube-system edit cm kubelet-config-1.16
— здесь заменим clusterDNS
на новый IP-адрес сервиса kube-dns: kubectl -n kube-system get svc kube-dns
.
kubectl -n kube-system edit cm kubeadm-config
— исправим data.ClusterConfiguration.networking.serviceSubnet
на новую подсеть.
kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(S+)s+(S+).*/kubectl --namespace 1 delete pod 2/e'
Мысли, как можно минимизировать даунтайм:
kube-dns-tmp
и новым адресом 172.24.0.10
.if
в etcdhelper, который не будет модифицировать сервис kube-dns.ClusterDNS
на новый, при этом старый сервис продолжит работать одновременно с новым.kube-dns-tmp
и поменять serviceSubnetCIDR
для сервиса kube-dns.
Этот план позволит минимизировать даунтайм до ~минуты — на время удаления сервиса kube-dns-tmp
и замены подсети для сервиса kube-dns
.
Заодно мы решили посмотреть, как модифицировать podNetwork с помощью получившегося etcdhelper'а. Последовательность действий получается следующей:
kube-system
;Теперь подробнее об этих действиях:
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. Модифицируем манифест controller-manager'а:
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. Заменим podCIDR, внеся правки напрямую в 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-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 можно произвести и проще (например, так [4]). Но ведь нам хотелось научиться работать с etcd напрямую, потому что существуют случаи, когда правка объектов Kubernetes в etcd — единственный возможный вариант. (Например, нельзя просто так без простоя изменить у Service поле spec.clusterIP
.)
В статье рассмотрена возможность работы с данными в etcd напрямую, т.е. в обход Kubernetes API. Иногда такой подход позволяет делать «хитрые штуки». Приведенные в тексте операции мы тестировали на реальных K8s-кластерах. Однако их статус готовности к широкому применению — PoC (proof of concept). Поэтому, если вы хотите использовать модифицированную версию утилиты etcdhelper на своих кластерах, делайте это на свой страх и риск.
Читайте также в нашем блоге:
Автор: vitaliy-sn
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/sistemnoe-administrirovanie/353232
Ссылки в тексте:
[1] могут посоветовать: https://www.devops.buzz/public/kubeadm/change-servicesubnet-cidr
[2] etcdhelper от OpenShift: https://github.com/openshift/origin/tree/master/tools/etcdhelper
[3] здесь: https://github.com/flant/examples/blob/master/2020/04-etcdhelper
[4] так: https://serverfault.com/a/977401
[5] etcd 3.4.3: исследование надёжности и безопасности хранилища: https://habr.com/ru/company/flant/blog/487534/
[6] Calico для сети в Kubernetes: знакомство и немного из опыта: https://habr.com/ru/company/flant/blog/485716/
[7] 6 занимательных системных багов при эксплуатации Kubernetes [и их решение]: https://habr.com/ru/company/flant/blog/443458/
[8] Визуальное руководство по диагностике неисправностей в Kubernetes: https://habr.com/ru/company/flant/blog/484954/
[9] Источник: https://habr.com/ru/post/501956/?utm_source=habrahabr&utm_medium=rss&utm_campaign=501956
Нажмите здесь для печати.