A nosa experiencia con datos no clúster de etcd Kubernetes directamente (sen API K8s)

Cada vez máis, os clientes pídennos que proporcionemos acceso ao clúster de Kubernetes para poder acceder aos servizos dentro do clúster: poder conectarnos directamente a algunha base de datos ou servizo, conectar unha aplicación local con aplicacións dentro do clúster...

A nosa experiencia con datos no clúster de etcd Kubernetes directamente (sen API K8s)

Por exemplo, é necesario conectarse desde a súa máquina local a un servizo memcached.staging.svc.cluster.local. Ofrecemos esta capacidade mediante unha VPN dentro do clúster ao que se conecta o cliente. Para iso, anunciamos subredes de pods, servizos e push cluster DNS ao cliente. Así, cando un cliente tenta conectarse ao servizo memcached.staging.svc.cluster.local, a solicitude diríxese ao DNS do clúster e, en resposta, recibe o enderezo deste servizo da rede de servizos do clúster ou do enderezo do pod.

Configuramos os clústeres K8s usando kubeadm, onde está a subrede de servizo predeterminada 192.168.0.0/16, e a rede de pods é 10.244.0.0/16. Normalmente todo funciona ben, pero hai un par de puntos:

  • Subrede 192.168.*.* utilízase a miúdo en redes de oficina cliente e aínda máis frecuentemente nas redes domésticas de desenvolvedores. E entón temos conflitos: os enrutadores domésticos traballan nesta subrede e a VPN empurra estas subredes do clúster ao cliente.
  • Temos varios clústeres (produción, escenario e/ou varios dev). Entón, por defecto, todos eles terán as mesmas subredes para pods e servizos, o que crea grandes dificultades para o traballo simultáneo con servizos en varios clústeres.

Hai tempo que adoptamos a práctica de usar diferentes subredes para servizos e pods dentro do mesmo proxecto, en xeral, para que todos os clústeres teñan redes diferentes. Non obstante, hai un gran número de clústeres en funcionamento que non me gustaría trasladar desde cero, xa que executan moitos servizos, aplicacións con estado, etc.

E entón preguntámonos: como cambiar a subrede nun clúster existente?

Busca de decisións

A práctica máis común é recrear todo servizos co tipo ClusterIP. Como opción, pode aconsellar e este:

O seguinte proceso ten un problema: despois de todo configurado, os pods aparecen coa IP antiga como servidor de nomes DNS en /etc/resolv.conf.
Como aínda non atopei a solución, tiven que restablecer todo o clúster con kubeadm reset e inicialo de novo.

Pero isto non é apto para todos... Aquí tes introducións máis detalladas para o noso caso:

  • Úsase franela;
  • Hai clusters tanto nas nubes como no hardware;
  • Gustaríame evitar volver implantar todos os servizos do clúster;
  • Hai que facer en xeral todo cun mínimo de problemas;
  • A versión de Kubernetes é 1.16.6 (non obstante, os pasos posteriores serán similares para outras versións);
  • A tarefa principal é garantir que nun clúster despregado usando kubeadm cunha subrede de servizo 192.168.0.0/16, substitúeo por 172.24.0.0/16.

E ocorreu que levabamos moito tempo interesados ​​en ver que e como se almacena en Kubernetes en etcd, que se pode facer con el... Así que pensamos: “Por que non actualizar os datos en etcd, substituíndo os vellos enderezos IP (subrede) por outros novos? "

Despois de buscar ferramentas preparadas para traballar con datos en etcd, non atopamos nada que resolvese completamente o problema. (Por certo, se coñeces algunha utilidade para traballar con datos directamente en etcd, agradeceríamos as ligazóns). Non obstante, un bo punto de partida é etcdhelper desde OpenShift (grazas aos seus autores!).

Esta utilidade pode conectarse a etcd usando certificados e ler datos desde alí usando comandos ls, get, dump.

Engadir etcdhelper

O seguinte pensamento é lóxico: "Que é o que che impide engadir esta utilidade engadindo a capacidade de escribir datos en etcd?"

Converteuse nunha versión modificada de etcdhelper con dúas novas funcións changeServiceCIDR и changePodCIDR. sobre ela podes ver o código aquí.

Que fan as novas funcións? Algoritmo changeServiceCIDR:

  • crear un deserializador;
  • compilar unha expresión regular para substituír CIDR;
  • pasamos por todos os servizos co tipo ClusterIP no clúster:
    • decodificar o valor de etcd nun obxecto Go;
    • usando unha expresión regular substituímos os dous primeiros bytes do enderezo;
    • asignarlle ao servizo un enderezo IP da nova subrede;
    • creamos un serializador, convertemos o obxecto Go en protobuf, escribimos novos datos en etcd.

Función changePodCIDR esencialmente similares changeServiceCIDR - só en vez de editar a especificación do servizo, facémolo para o nodo e cambiamos .spec.PodCIDR a unha nova subrede.

Práctica

Cambio de servizo CIDR

O plan para implementar a tarefa é moi sinxelo, pero implica un tempo de inactividade no momento da recreación de todos os pods do clúster. Despois de describir os pasos principais, tamén compartiremos ideas sobre como, en teoría, este tempo de inactividade se pode minimizar.

Pasos preparatorios:

  • instalar o software necesario e montar o etcdhelper parcheado;
  • copia de seguridade etcd e /etc/kubernetes.

Breve plan de acción para cambiar o servizo CIDR:

  • cambiar os manifestos apiserver e controller-manager;
  • reemisión de certificados;
  • cambiar os servizos ClusterIP en etcd;
  • reinicio de todos os pods do clúster.

A seguinte é unha secuencia completa de accións en detalle.

1. Instale etcd-client para o volcado de datos:

apt install etcd-client

2. Construír etcdhelper:

  • Instalar 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
  • Aforramos para nós etcdhelper.go, descargar dependencias, recoller:
    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. Fai unha copia de seguridade 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. Cambie a subrede do servizo nos manifestos do plano de control de Kubernetes. En arquivos /etc/kubernetes/manifests/kube-apiserver.yaml и /etc/kubernetes/manifests/kube-controller-manager.yaml cambiar o parámetro --service-cluster-ip-range a unha nova subrede: 172.24.0.0/16 en vez de 192.168.0.0/16.

5. Dado que estamos cambiando a subrede de servizo á que kubeadm emite certificados para apiserver (incluído), hai que volver emitilos:

  1. Vexamos para que dominios e enderezos IP se emitiu o certificado actual:
    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. Imos preparar unha configuración mínima para 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. Imos eliminar o crt e a chave antigas, xa que sen isto non se emitirá o novo certificado:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Volvemos a emitir certificados para o servidor API:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. Comprobamos que se emitiu o certificado para a nova subrede:
    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. Despois de volver emitir o certificado do servidor API, reinicie o seu contenedor:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Rexeneremos a configuración para admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Imos editar os datos en 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 

    Atención! Neste momento, a resolución do dominio deixa de funcionar no clúster, xa que nos pods existentes /etc/resolv.conf o antigo enderezo CoreDNS (kube-dns) está rexistrado e kube-proxy cambia as regras de iptables da subrede antiga á nova. Máis adiante no artigo está escrito sobre posibles opcións para minimizar o tempo de inactividade.

  9. Imos corrixir ConfigMap no espazo de nomes kube-system:
    kubectl -n kube-system edit cm kubelet-config-1.16

    - substituír aquí clusterDNS ao novo enderezo IP do servizo kube-dns: kubectl -n kube-system get svc kube-dns.

    kubectl -n kube-system edit cm kubeadm-config

    - arreglarémolo data.ClusterConfiguration.networking.serviceSubnet a unha nova subrede.

  10. Dado que o enderezo de kube-dns cambiou, é necesario actualizar a configuración de kubelet en todos os nós:
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Todo o que queda é reiniciar todos os pods do clúster:
    kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(S+)s+(S+).*/kubectl --namespace 1 delete pod 2/e'

Minimizar o tempo de inactividade

Pensamentos sobre como minimizar o tempo de inactividade:

  1. Despois de cambiar os manifestos do plano de control, cree un novo servizo kube-dns, por exemplo, co nome kube-dns-tmp e novo enderezo 172.24.0.10.
  2. Facer if en etcdhelper, que non modificará o servizo kube-dns.
  3. Substitúe o enderezo en todos os kubelets ClusterDNS a un novo, mentres que o servizo antigo seguirá funcionando simultaneamente co novo.
  4. Agarde ata que as vainas con aplicacións pasen por si mesmas por razóns naturais ou nunha hora acordada.
  5. Eliminar servizo kube-dns-tmp e cambiar serviceSubnetCIDR para o servizo kube-dns.

Este plan permitirache minimizar o tempo de inactividade a ~un minuto durante a duración da eliminación do servizo kube-dns-tmp e cambiar a subrede para o servizo kube-dns.

Modificación podNetwork

Ao mesmo tempo, decidimos ver como modificar podNetwork usando o etcdhelper resultante. A secuencia de accións é a seguinte:

  • corrixindo as configuracións kube-system;
  • corrixindo o manifesto kube-controller-manager;
  • cambiar podCIDR directamente en etcd;
  • reinicie todos os nodos do clúster.

Agora máis sobre estas accións:

1. Modifique o ConfigMap no espazo de nomes kube-system:

kubectl -n kube-system edit cm kubeadm-config

- corrixindo data.ClusterConfiguration.networking.podSubnet a unha nova subrede 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

- corrixindo data.config.conf.clusterCIDR: 10.55.0.0/16.

2. Modifique o manifesto do controlador-xestor:

vim /etc/kubernetes/manifests/kube-controller-manager.yaml

- corrixindo --cluster-cidr=10.55.0.0/16.

3. Observa os valores actuais .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses para todos os nodos do clúster:

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. Substitúe podCIDR facendo cambios directamente en 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. Comprobamos que podCIDR cambiou realmente:

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. Reiniciemos todos os nodos do clúster un por un.

7. Se deixas polo menos un nodo antigo podCIDR, entón kube-controller-manager non poderá iniciarse e non se programarán os pods do clúster.

De feito, cambiar podCIDR pódese facer aínda máis sinxelo (por exemplo, así). Pero queriamos aprender a traballar con etcd directamente, porque hai casos cando se editan obxectos de Kubernetes en etcd: o único variante posible. (Por exemplo, non pode simplemente cambiar o campo Servizo sen tempo de inactividade spec.clusterIP.)

Total

O artigo discute a posibilidade de traballar con datos en etcd directamente, é dicir. evitando a API de Kubernetes. Ás veces, este enfoque permíteche facer "cousas complicadas". Probamos as operacións indicadas no texto en clusters reais de K8s. Non obstante, o seu estado de preparación para o seu uso xeneralizado é PoC (proba de concepto). Polo tanto, se queres utilizar unha versión modificada da utilidade etcdhelper nos teus clústeres, faino baixo o teu propio risco.

PS

Lea tamén no noso blog:

Fonte: www.habr.com

Engadir un comentario