Steeds vaker wenden klanten zich tot ons met het verzoek om toegang tot de Kubernetes-cluster, zodat ze interne services kunnen benaderen: direct verbinding kunnen maken met een database of service, voor de verbinding van een lokale applicatie met applicaties binnen de cluster…

Bijvoorbeeld, er is behoefte om vanaf je lokale machine verbinding te maken met de service memcached.staging.svc.cluster.local. Wij bieden deze mogelijkheid middels een VPN binnen de cluster, waar de klant verbinding mee maakt. Hiervoor adverteren we de subnetten van pods, services en pushen we de cluster DNS naar de klant. Zo, wanneer de klant probeert verbinding te maken met de service memcached.staging.svc.cluster.local, wordt het verzoek gestuurd naar de cluster DNS en ontvangt het in ruil de adres van deze service uit het service-netwerk van de cluster of het adres van de pod.
K8s-clusters configureren we met kubeadm, waarbij de standaard service subnet is 192.168.0.0/16, en het pod-netwerk is 10.244.0.0/16. Normaal gesproken werkt alles goed, maar er zijn een paar punten:
- Het subnet
192.168.*.*wordt vaak gebruikt in de netwerken van klanten, en nog vaker - in de netwerken van ontwikkelaars thuis. En dan krijgen we conflicten: thuisrouters werken in dit subnet en VPN push't deze subnetten vanuit de cluster naar de klant. - We hebben verschillende clusters (productie-, stage- en/of verschillende dev-clusters). Dan zullen in al deze clusters standaard dezelfde subnets voor pods en services zijn, wat grote complicaties creëert voor het gelijktijdig werken met services in meerdere clusters.
We hebben al geruime tijd de praktijk aangenomen om verschillende subnetten voor services en pods te gebruiken binnen een project - over het algemeen zodat alle clusters verschillende netwerken hebben. Er zijn echter een groot aantal clusters in gebruik die we niet vanaf nul willen opnieuw opzetten, aangezien hierin veel services, stateful-applicaties, enz. draaien.
En toen stelden we ons de vraag: hoe kunnen we het subnet in een bestaande cluster wijzigen?
Zoektocht naar oplossingen
De meest voorkomende praktijk is om de all services met het type ClusterIP opnieuw aan te maken. Als alternatief, en zoiets:
Het volgende proces heeft een probleem: nadat alles is geconfigureerd, komen de pods met het oude IP als DNS-nameserver in /etc/resolv.conf.
Aangezien ik de oplossing nog steeds niet heb gevonden, moest ik de hele cluster resetten met kubeadm reset en het opnieuw initialiseren.
Maar niet iedereen vindt dit geschikt… Hier zijn meer details voor onze situatie:
- Flannel wordt gebruikt;
- Er zijn clusters zowel in de cloud als op hardware;
- We zouden graag een herdeploiement van alle services in het cluster willen vermijden.
- Er is behoefte om alles met een minimum aan problemen te doen.
- De versie van Kubernetes is 1.16.6 (de verdere stappen zijn eigenlijk vergelijkbaar voor andere versies).
- De belangrijkste taak is om in het cluster, dat is uitgerold met kubeadm met een service subnet,
192.168.0.0/16, deze te vervangen door172.24.0.0/16.
En het toeval wil dat we al een tijd geïnteresseerd zijn in wat en hoe Kubernetes in etcd opslaat, en wat we daarmee kunnen doen... Dus dachten we: "Waarom zouden we gewoon de gegevens in etcd niet bijwerken, door de oude IP-adressen (subnet) te vervangen door de nieuwe??»
Na het zoeken naar kant-en-klare hulpmiddelen voor het werken met gegevens in etcd, vonden we niets dat het probleem volledig oploste. (Trouwens, als je kent van enige tools voor het direct werken met gegevens in etcd - we zouden dankbaar zijn voor de links.) Een goede uitgangspunt werd echter (dank aan de auteurs!).
Dit hulpmiddel kan verbinding maken met etcd met behulp van certificaten en gegevens daaruit lezen met behulp van commando's. ls, krijgen, dump.
We breiden etcdhelper uit.
De volgende gedachte is logisch: "Wat houdt ons tegen om deze tool uit te breiden en de mogelijkheid toe te voegen om gegevens in etcd te schrijven?"
Dit resulteerde in een gemodificeerde versie van etcdhelper met twee nieuwe functies. changeServiceCIDR en changePodCIDR. De code kan worden bekeken .
Wat doen de nieuwe functies? Het algoritme changeServiceCIDR:
- maakt een deserializer aan;
- compileert een reguliere expressie om de CIDR te vervangen;
- loopt door alle services met het type ClusterIP in het cluster:
- decodeert de waarde uit etcd naar een Go-object;
- vervangt de eerste twee bytes van het adres met behulp van de reguliere expressie;
- wijst het service IP-adres toe uit het nieuwe subnet;
- maakt een serializer aan, zet het Go-object om naar protobuf, en schrijft de nieuwe gegevens in etcd.
Functie changePodCIDR is in wezen vergelijkbaar changeServiceCIDR — alleen in plaats van het specificeren van services te bewerken, doen we dit voor de knooppunt en veranderen .spec.PodCIDR in het nieuwe subnet.
Praktijk
Verandering van serviceCIDR
Het plan voor de uitvoering van de taak is heel eenvoudig, maar impliceert downtime op het moment van het opnieuw creëren van alle pods in het cluster. Na de beschrijving van de belangrijkste stappen zullen we ook gedachten delen over hoe deze downtime in theorie kan worden geminimaliseerd.
Voorbereidende acties:
- installatie van benodigde software en het samenstellen van de gepatchte etcdhelper;
- back-up van etcd en
/etc/kubernetes.
Kort plan van aanpak voor het wijzigen van serviceCIDR:
- wijziging van de manifesten van de apiserver en controller-manager;
- heruitgave van certificaten;
- wijziging van de ClusterIP-services in etcd;
- herstart alle pods in het cluster.
Hieronder wordt de volledige volgorde van acties in detail weergegeven.
1. Installeer de etcd-client voor de gegevensdump:
apt install etcd-client2. Verzamel etcdhelper:
- Installeer 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 - Opslaan als
etcdhelper.go, laad afhankelijkheden, bouw: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. Maak een back-up van 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. Wijzig het service-netwerk in de Kubernetes control plane manifesten. In de bestanden /etc/kubernetes/manifests/kube-apiserver.yaml en /etc/kubernetes/manifests/kube-controller-manager.yaml wijzig de parameter --service-cluster-ip-range naar het nieuwe netwerk: 172.24.0.0/16 in plaats van 192.168.0.0/16.
5. Aangezien we het service-netwerk wijzigen waarop kubeadm certificaten voor de apiserver uitgeeft (inclusief dit), moeten ze worden heruitgegeven:
- Laten we kijken naar welke domeinen en IP-adressen het huidige certificaat is uitgegeven:
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 - Laten we een minimale configuratie voor kubeadm voorbereiden:
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-adres van de master node - Verwijder de oude crt en key, want zonder deze wordt het nieuwe certificaat niet uitgegeven:
rm /etc/kubernetes/pki/apiserver.{key,crt} - Heruitgeven van certificaten voor de API-server:
kubeadm init phase certs apiserver --config=kubeadm-config.yaml - Controleer of het certificaat is uitgegeven voor het nieuwe netwerk:
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 - Na het heruitgeven van het certificaat van de API-server, herstarten we zijn container:
docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart - Genereer de configuratie opnieuw voor
admin.conf:kubeadm alpha certs renew admin.conf - Bewerk de gegevens in 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/16Let op! Op dit moment stopt de domeinnaamresolutie in de cluster, omdat in de reeds bestaande pod's het oude adres van CoreDNS (kube-dns) is ingesteld, terwijl kube-proxy de iptables-regels heeft veranderd van het oude subnet naar het nieuwe. Verder in het artikel worden mogelijkheden beschreven om stilstand te minimaliseren.
/etc/resolv.confLaten we de ConfigMaps in de namespace aanpassen. - kubectl -n kube-system edit cm kubelet-config-1.16
kube-system:— hier vervangen weclusterDNS
door het nieuwe IP-adres van de kube-dns service:kubectl -n kube-system get svc kube-dnskubectl -n kube-system edit cm kubeadm-config.— we passendata.ClusterConfiguration.networking.serviceSubnet
aan, omdat het adres van kube-dns is veranderd, moeten we de kubelet-config op alle knooppunten bijwerken:in het nieuwe subnet. - kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
Het enige dat overblijft, is alle pod's in de cluster opnieuw opstarten: - kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(S+)s+(S+).*//kubectl --namespace 1 delete pod 2/e'
Stilstand minimaliseren
Denk na over manieren om de downtime te minimaliseren:
Na wijzigingen aan de manifesten van het control plane, een nieuwe kube-dns service aanmaken, bijvoorbeeld met de naam
- kube-dns-tmp
en een nieuw adres.Maak172.24.0.10. - in etcdhelper, dat de kube-dns service niet zal wijzigen.
alsVervang in alle kubelets het adres - ClusterDNS
door het nieuwe, terwijl de oude service tegelijkertijd blijft werken.Wacht tot de pod's met applicaties opnieuw zijn uitgerold, of vanzelf door natuurlijke oorzaken, of op een afgesproken tijd. - Verwijder de service
- en wijzig
en een nieuw adres.serviceSubnetCIDRvoor de kube-dns service.Dit plan stelt ons in staat om de downtime te minimaliseren tot ~één minuut — de tijd die nodig is voor het verwijderen van de service
en het vervangen van het subnet voor de service. en een nieuw adres. Wijziging van podNetwork kube-dns.
Onderwijl hebben we ook gekeken naar hoe we podNetwork kunnen wijzigen met behulp van de resulterende etcdhelper. De reeks acties is als volgt:
we passen de configuraties aan in
- we corrigeren het kube-controller-manager manifest;
kube-system; - we passen podCIDR direct in etcd aan;
- we herstarten alle knooppunten van de cluster.
- Nu meer over deze acties:
1. We modificeren de ConfigMaps in de namespace
— we corrigeren kube-system:
— we passen data.ClusterConfiguration.networking.podSubnet naar het nieuwe subnet kubectl -n kube-system edit cm kube-proxy 10.55.0.0/16.
data.config.conf.clusterCIDR: 10.55.0.0/16 data.ClusterConfiguration.networking.podSubnet 2. We modifieren het controller-manager manifest:.
vim /etc/kubernetes/manifests/kube-controller-manager.yaml
--cluster-cidr=10.55.0.0/16 data.ClusterConfiguration.networking.podSubnet 3. We kijken naar de huidige waarden.
.spec.podCIDR .spec.podCIDRs, .InternalIP, .status.addresses, voor alle knooppunten in de cluster: kubectl get no -o json | jq '[.items[] | {"name": .metadata.name, "podCIDR": .spec.podCIDR, "podCIDRs": .spec.podCIDRs, "InternalIP": (.status.addresses[] | select(.type == "InternalIP") | .address)}]'
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. We will change podCIDR by making adjustments directly in 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/165. We will check that podCIDR has indeed changed:
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. We will sequentially restart all nodes in the cluster.
7. If at least one node retains the old podCIDR, kube-controller-manager will not be able to start, and pods in the cluster will not be scheduled.
In fact, changing the podCIDR can be done more simply (for example, ). But we wanted to learn how to work with etcd directly, as there are cases where editing Kubernetes objects in etcd is — het enige a viable option. (For instance, you can't just change the field for Service spec.clusterIP.)
Conclusie
This article discusses the possibility of working with data in etcd directly, i.e., bypassing the Kubernetes API. Sometimes this approach allows for ‘clever tricks’. The operations described in the text have been tested on real K8s clusters. However, their readiness status for widespread use is — PoC (proof of concept). Therefore, if you wish to use a modified version of the etcdhelper utility on your clusters, do so at your own risk.
P.S.
Lees ook op onze blog:
- «»;
- «»;
- «»;
- «».
Bron: habr.com
