Nuestra experiencia con datos en etcd Kubernetes cluster directamente (sin API K8s)

Cada vez más, los clientes nos piden que les demos acceso al cluster de Kubernetes para poder acceder a servicios dentro del cluster: para poder conectarnos directamente a alguna base de datos o servicio, para conectar una aplicación local con aplicaciones dentro del cluster…

Nuestra experiencia con datos en etcd Kubernetes cluster directamente (sin API K8s)

Por ejemplo, es necesario conectarse desde su máquina local a un servicio memcached.staging.svc.cluster.local. Proporcionamos esta capacidad utilizando una VPN dentro del clúster al que se conecta el cliente. Para hacer esto, anunciamos subredes de pods, servicios y enviamos DNS del clúster al cliente. Así, cuando un cliente intenta conectarse al servicio memcached.staging.svc.cluster.local, la solicitud va al DNS del clúster y en respuesta recibe la dirección de este servicio de la red de servicios del clúster o la dirección del pod.

Configuramos clústeres K8 usando kubeadm, donde la subred de servicio predeterminada es 192.168.0.0/16, y la red de pods es 10.244.0.0/16. Normalmente todo funciona bien, pero hay un par de puntos:

  • Subred 192.168.*.* Se utiliza a menudo en redes de oficinas de clientes y, aún más a menudo, en redes domésticas de desarrolladores. Y luego tenemos conflictos: los enrutadores domésticos funcionan en esta subred y la VPN envía estas subredes desde el clúster al cliente.
  • Contamos con varios clusters (producción, stage y/o varios clusters de desarrollo). Luego, de forma predeterminada, todos tendrán las mismas subredes para pods y servicios, lo que crea grandes dificultades para trabajar simultáneamente con servicios en varios clústeres.

Hace mucho tiempo que adoptamos la práctica de utilizar diferentes subredes para servicios y pods dentro del mismo proyecto, en general, para que todos los clústeres tengan redes diferentes. Sin embargo, hay una gran cantidad de clústeres en funcionamiento que no me gustaría renovar desde cero, ya que ejecutan muchos servicios, aplicaciones con estado, etc.

Y entonces nos preguntamos: ¿cómo cambiar la subred en un cluster existente?

Buscar soluciones

La práctica más común es recrear todos servicios con tipo ClusterIP. Como una opción, puede aconsejar y tal:

El siguiente proceso tiene un problema: después de configurar todo, los pods muestran la IP anterior como servidor de nombres DNS en /etc/resolv.conf.
Como todavía no encontré la solución, tuve que restablecer todo el clúster con kubeadm reset e iniciarlo nuevamente.

Pero esto no es adecuado para todos... Aquí hay introducciones más detalladas para nuestro caso:

  • Se utiliza franela;
  • Hay clusters tanto en las nubes como en el hardware;
  • Me gustaría evitar volver a implementar todos los servicios en el clúster;
  • En general, es necesario hacer todo con un número mínimo de problemas;
  • La versión de Kubernetes es 1.16.6 (sin embargo, los pasos posteriores serán similares para otras versiones);
  • La tarea principal es garantizar que en un clúster implementado usando kubeadm con una subred de servicio 192.168.0.0/16, reemplácelo con 172.24.0.0/16.

Y resultó que hacía tiempo que estábamos interesados ​​en ver qué y cómo se almacena en Kubernetes en etcd, qué se puede hacer con ello... Así que pensamos: “¿Por qué no simplemente actualizar los datos en etcd, reemplazando las direcciones IP antiguas (subred) por otras nuevas?? "

Después de buscar herramientas listas para usar para trabajar con datos en etcd, no encontramos nada que resolviera completamente el problema. (Por cierto, si conoce alguna utilidad para trabajar con datos directamente en etcd, agradeceríamos los enlaces). Sin embargo, un buen punto de partida es etcdhelper de OpenShift (¡gracias a sus autores!).

Esta utilidad puede conectarse a etcd mediante certificados y leer datos desde allí mediante comandos ls, get, dump.

Agregar etcdhelper

El siguiente pensamiento es lógico: "¿Qué le impide agregar esta utilidad agregando la capacidad de escribir datos en etcd?"

Se convirtió en una versión modificada de etcdhelper con dos nuevas funciones. changeServiceCIDR и changePodCIDR. sobre su puedes ver el código aquí.

¿Qué hacen las nuevas funciones? Algoritmo changeServiceCIDR:

  • crear un deserializador;
  • compilar una expresión regular para reemplazar CIDR;
  • Revisamos todos los servicios con el tipo ClusterIP en el cluster:
    • decodificar el valor de etcd en un objeto Go;
    • usando una expresión regular reemplazamos los dos primeros bytes de la dirección;
    • asignar al servicio una dirección IP de la nueva subred;
    • cree un serializador, convierta el objeto Go en protobuf, escriba nuevos datos en etcd.

Función changePodCIDR esencialmente similar changeServiceCIDR - solo que en lugar de editar la especificación del servicio, lo hacemos para el nodo y cambiamos .spec.PodCIDR a una nueva subred.

Práctica

Cambiar servicio CIDR

El plan para implementar la tarea es muy simple, pero implica un tiempo de inactividad al momento de recrear todos los pods en el clúster. Después de describir los pasos principales, también compartiremos ideas sobre cómo, en teoría, se puede minimizar este tiempo de inactividad.

Pasos preparatorios:

  • instalar el software necesario y ensamblar el etcdhelper parcheado;
  • copia de seguridad, etc. y /etc/kubernetes.

Breve plan de acción para cambiar el servicioCIDR:

  • cambiar los manifiestos de apiserver y controlador-administrador;
  • reemisión de certificados;
  • cambiar los servicios ClusterIP en etcd;
  • reinicio de todos los pods del clúster.

La siguiente es una secuencia completa de acciones en detalle.

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

apt install etcd-client

2. Construya 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
  • Ahorramos para nosotros mismos etcdhelper.go, descargar dependencias, recopilar:
    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. Haga una copia de seguridad, etc.:

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 la subred del servicio en los manifiestos del plano de control de Kubernetes. en archivos /etc/kubernetes/manifests/kube-apiserver.yaml и /etc/kubernetes/manifests/kube-controller-manager.yaml cambiar el parámetro --service-cluster-ip-range a una nueva subred: 172.24.0.0/16 en lugar de 192.168.0.0/16.

5. Dado que estamos cambiando la subred de servicio a la que kubeadm emite certificados para un servidor (incluido), es necesario volver a emitirlos:

  1. Veamos para qué dominios y direcciones IP se ha emitido el 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. Preparemos una 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. Eliminemos el antiguo crt y la clave, ya que sin esto no se emitirá el nuevo certificado:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Volvamos a emitir certificados para el servidor API:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. Comprobemos que el certificado fue emitido para la nueva subred:
    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. Después de volver a emitir el certificado del servidor API, reinicie su contenedor:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Regeneremos la configuración para admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Editemos los 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! En este momento, la resolución de dominio deja de funcionar en el cluster, ya que en los pods existentes /etc/resolv.conf se registra la antigua dirección CoreDNS (kube-dns) y kube-proxy cambia las reglas de iptables de la subred antigua a la nueva. Más adelante en el artículo se describen posibles opciones para minimizar el tiempo de inactividad.

  9. Arreglemos ConfigMap en el espacio de nombres kube-system:
    kubectl -n kube-system edit cm kubelet-config-1.16

    - reemplazar aquí clusterDNS a la nueva dirección IP del servicio kube-dns: kubectl -n kube-system get svc kube-dns.

    kubectl -n kube-system edit cm kubeadm-config

    - lo arreglaremos data.ClusterConfiguration.networking.serviceSubnet a una nueva subred.

  10. Dado que la dirección de kube-dns cambió, es necesario actualizar la configuración de kubelet en todos los nodos:
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Todo lo que queda es reiniciar todos los pods del clúster:
    kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(S+)s+(S+).*/kubectl --namespace 1 delete pod 2/e'

Minimizar el tiempo de inactividad

Pensamientos sobre cómo minimizar el tiempo de inactividad:

  1. Después de cambiar los manifiestos del plano de control, cree un nuevo servicio kube-dns, por ejemplo, con el nombre kube-dns-tmp y nueva dirección 172.24.0.10.
  2. hacer if en etcdhelper, que no modificará el servicio kube-dns.
  3. Reemplazar la dirección en todos los kubelets. ClusterDNS a uno nuevo, mientras que el antiguo servicio seguirá funcionando simultáneamente con el nuevo.
  4. Espere hasta que los módulos con aplicaciones se volteen solos por razones naturales o en un momento acordado.
  5. Eliminar servicio kube-dns-tmp y cambio serviceSubnetCIDR para el servicio kube-dns.

Este plan le permitirá minimizar el tiempo de inactividad a ~un minuto, mientras dure la eliminación del servicio. kube-dns-tmp y cambiar la subred para el servicio kube-dns.

Red de pod de modificación

Al mismo tiempo, decidimos ver cómo modificar podNetwork usando el etcdhelper resultante. La secuencia de acciones es la siguiente:

  • arreglando configuraciones en kube-system;
  • arreglar el manifiesto kube-controller-manager;
  • cambie podCIDR directamente en etcd;
  • reinicie todos los nodos del clúster.

Ahora más sobre estas acciones:

1. Modifique ConfigMap en el espacio de nombres kube-system:

kubectl -n kube-system edit cm kubeadm-config

- corrigiendo data.ClusterConfiguration.networking.podSubnet a una nueva subred 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

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

2. Modifique el manifiesto controlador-administrador:

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

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

3. Mira los valores actuales. .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses para todos los nodos del 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. Reemplace podCIDR realizando 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. Comprobemos que podCIDR realmente ha cambiado:

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 los nodos del clúster uno por uno.

7. Si dejas al menos un nodo vaina viejaCIDR, entonces kube-controller-manager no podrá iniciarse y los pods en el clúster no se programarán.

De hecho, cambiar podCIDR se puede hacer aún más simple (por ejemplo, tan). Pero queríamos aprender a trabajar con etcd directamente, porque hay casos al editar objetos de Kubernetes en etcd: единственный posible variante. (Por ejemplo, no puedes simplemente cambiar el campo Servicio sin tiempo de inactividad spec.clusterIP.)

Total

El artículo analiza la posibilidad de trabajar con datos en etcd directamente, es decir. sin pasar por la API de Kubernetes. A veces, este enfoque te permite hacer "cosas complicadas". Probamos las operaciones indicadas en el texto en clústeres K8 reales. Sin embargo, su estado de preparación para un uso generalizado es PoC (prueba de concepto). Por lo tanto, si desea utilizar una versión modificada de la utilidad etcdhelper en sus clústeres, hágalo bajo su propia responsabilidad.

PS

Lea también en nuestro blog:

Fuente: habr.com

Añadir un comentario