Наш досвед працы з дадзенымі ў etcd Kubernetes-кластара напрамую (без K8s API)

Усё часцей да нас звяртаюцца кліенты з просьбай забяспечыць доступ у Kubernetes-кластар для магчымасці звароту да сэрвісаў унутры кластара: каб можна было напрамую падключыцца да нейкай базы даных або сэрвісу, для сувязі лакальнага дадатку з дадаткамі ўнутры кластара…

Наш досвед працы з дадзенымі ў etcd Kubernetes-кластара напрамую (без K8s API)

Напрыклад, узнікае запатрабаванне падлучыцца са сваёй лакальнай машыны да сэрвісу. 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'іт гэтыя падсеткі з кластара кліенту.
  • У нас ёсць некалькі кластараў (кластары production, stage і/або некалькі dev-кластэраў). Тады ва ўсіх іх па змаўчанні будуць аднолькавыя падсеткі для pod'ов і сэрвісаў, што стварае вялікія складанасці для адначасовай працы з сэрвісамі ў некалькіх кластарах.

Мы ўжо даўнавата прынялі практыку выкарыстання розных падсетак для сэрвісаў і pod'ов у рамках аднаго праекту — увогуле, каб усе кластары былі з рознымі сеткамі. Аднак ёсць вялікая колькасць кластараў у працы, якія не хацелася б перакочваць з нуля, бо ў іх запушчаны шматлікія сэрвісы, stateful-прыкладанні і да т.п.

І тады мы задаліся пытаннем: як бы памяняць падсетку ў існуючым кластары?

Пошук рашэнняў

Найбольш распаўсюджаная практыка - перастварыць ўсё сэрвісы з тыпам ClusterIP. Як варыянт, могуць параіць і такое:

Гэтыя працэсы маюць пытанне: пасля таго, як configured, pods pokračuje s starým IP, a to DNS nameserver in /etc/resolv.conf.
Так, я лічу, што не выконвае патрэбу, і яна павінна аднавіць цэнтр cluster with kubeadm reset and init it again.

Але не ўсім гэта падыходзіць… Вось больш дэталёвыя ўступныя для нашага выпадку:

  • Выкарыстоўваецца Flannel;
  • Ёсць кластара як у аблоках, так і на жалезе;
  • Жадалася бы пазбегнуць паўторнага дэплою ўсіх сэрвісаў у кластары;
  • Ёсць патрэба ўвогуле зрабіць усё з мінімальнай колькасцю праблем;
  • Версія Kubernetes - 1.16.6 (зрэшты, далейшыя дзеянні будуць аналагічныя і для іншых версій);
  • Асноўная задача зводзіцца да таго, каб у кластары, разгорнутым з дапамогай kubeadm з сэрвіснай падсеткай 192.168.0.0/16, замяніць яе на 172.24.0.0/16.

І так ужо супала, што нам даўно было цікава паглядзець, што і як у Kubernetes захоўваецца ў etcd, што ўвогуле з гэтым можна зрабіць… Вось і падумалі: «Чаму б проста не абнавіць дадзеныя ў etcd, замяніўшы старыя IP-адрасы (падсетку) на новыя? »

Пашукаўшы гатовыя прылады для працы з дадзенымі ў etcd, мы не знайшлі нічога цалкам вырашальнага пастаўленую задачу. (Дарэчы, калі вы ведаеце аб любых утылітах для працы з дадзенымі напрамую ў etcd – будзем удзячныя за спасылкі.) Аднак добрай адпраўной кропкай стала etcdhelper ад OpenShift (дзякуй яго аўтарам!).

Гэтая ўтыліта ўмее падлучацца да etcd з дапамогай сертыфікатаў і чытаць адтуль дадзеныя з дапамогай каманд ls, get, dump.

Дапісваем etcdhelper

Наступная думка заканамерная: "Што замінае дапісаць гэтую ўтыліту, дадаўшы магчымасць запісу дадзеных у etcd?"

Яна ўвасобілася ў мадыфікаваную версію etcdhelper з дзвюма новымі функцыямі changeServiceCIDR и changePodCIDR. На яе код можна паглядзець тут.

Што выконваюць новыя функцыі? Алгарытм changeServiceCIDR:

  • ствараем дэсерыялізатар;
  • кампілюемы рэгулярны выраз для замены CIDR;
  • праходзім па ўсіх сэрвісах з тыпам ClusterIP у кластары:
    • дэкадуем значэнне з etcd у Go-аб'ект;
    • з дапамогай рэгулярнага выразы заменны першыя два байта адрасу;
    • прысвойваем сэрвісу IP-адрас з новай падсеткі;
    • ствараем серыялізатар, пераўтворым Go-аб'ект у protobuf, запісваем новыя дадзеныя ў etcd.

Функцыя changePodCIDR па сутнасці аналагічна changeServiceCIDR — толькі замест рэдагавання спецыфікацыі сэрвісаў мы робім гэта для вузла і мяняем .spec.PodCIDR на новую падсетку.

Практыка

Змена serviceCIDR

План па рэалізацыі пастаўленай задачы - вельмі просты, але мае на ўвазе даунтайм на момант перастварэння ўсіх pod'ов ў кластары. Пасля апісання асноўных крокаў мы таксама падзелімся думкамі, як у тэорыі можна мінімізаваць гэты просты.

Падрыхтоўчыя дзеянні:

  • усталёўка неабходнага ПА і зборка пропатченного etcdhelper;
  • бэкап etcd і /etc/kubernetes.

Кароткі план дзеянняў па змене serviceCIDR:

  • змена маніфестаў apiserver'а і controller-manager'а;
  • перавыпуск сертыфікатаў;
  • змена ClusterIP сэрвісаў у etcd;
  • рэстарт ўсіх 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 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'а (у тым ліку), іх неабходна перавыпусціць:

  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 і key, бо без гэтага новы сертыфікат не выпусціцца:
    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 на новы IP-адрас сэрвісу kube-dns: 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. Пасля змен маніфестаў control plane'а стварыць новы сэрвіс 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.

Мадыфікацыя podNetwork

Заадно мы вырашылі паглядзець, як мадыфікаваць podNetwork з дапамогай атрыманага etcdhelper'а. Паслядоўнасць дзеянняў атрымліваецца наступнай:

  • выпраўляем канфігі ў kube-system;
  • выпраўляем маніфест kube-controller-manager'а;
  • змяняны podCIDR напрамую ў etcd;
  • перазагружаем усе вузлы кластара.

Цяпер падрабязней пра гэтыя дзеянні:

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 можна зрабіць і прасцей (напрыклад, так). Але ж нам хацелася навучыцца працаваць з etcd напрамую, таму што існуюць выпадкі, калі праўка аб'ектаў Kubernetes у etcd. адзіны магчымы варыянт. (Напрыклад, нельга проста так без прастою змяніць у Service поле spec.clusterIP.)

Вынік

У артыкуле разгледжана магчымасць працы з дадзенымі ў etcd напрамую, г.зн. у абыход Kubernetes API. Часам такі падыход дазваляе рабіць "хітрыя штукі". Прыведзеныя ў тэксце аперацыі мы тэставалі на рэальных K8s-кластарах. Аднак іх статус гатоўнасці да шырокага прымянення. PoC (proof of concept). Таму, калі вы хочаце выкарыстоўваць мадыфікаваную версію ўтыліты etcdhelper на сваіх кластарах, рабіце гэта на свой страх і рызыку.

PS

Чытайце таксама ў нашым блогу:

Крыніца: habr.com

Дадаць каментар