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

Cada vez más a menudo, los clientes nos solicitan el acceso al clúster de Kubernetes para tener la posibilidad de acceder a los servicios dentro del clúster: para poder conectarse directamente a alguna base de datos o servicio, para conectar la aplicación local con aplicaciones dentro del clúster ...



Por ejemplo, es necesario conectarse desde su máquina local hasta el servicio memcached.staging.svc.cluster.local. Brindamos esta oportunidad con una VPN dentro del clúster al que se conecta el cliente. Para hacer esto, estamos anunciando las subredes de pods, servicios y DNS de clúster de inserción al cliente. Por lo tanto, cuando el 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 desde la red del servicio del clúster o la dirección del pod.

Configuramos los clústeres K8s usando kubeadm, donde está la subred de servicio predeterminada 192.168.0.0/16y la red de pod 10.244.0.0/16. Por lo general, todo funciona bien, pero hay un par de puntos:

  • La subred se 192.168.*.*usa a menudo en redes de clientes de oficina, 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 empuja estas subredes desde el clúster al cliente.
  • Tenemos varios grupos (producción, etapa y / o varios grupos de desarrollo). Luego, en todos ellos, por defecto, habrá las mismas subredes para pods y servicios, lo que crea grandes dificultades para trabajar con servicios en varios clústeres simultáneamente.

Desde hace bastante tiempo, hemos adoptado la práctica de usar diferentes subredes para servicios y pods dentro del mismo proyecto, en general, de modo que todos los clústeres estén con redes diferentes. Sin embargo, hay una gran cantidad de clústeres en funcionamiento que no quisiera rodar desde cero, ya que ejecutan muchos servicios, aplicaciones con estado, etc.

Y luego nos preguntamos: ¿cómo cambiaría la subred en un clúster existente?

Búsqueda de decisiones.


La práctica más común es recrear todos los servicios con el tipo ClusterIP. Alternativamente, también pueden aconsejar esto:

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

Pero esto no se adapta a todos ... Aquí hay notas introductorias más detalladas para nuestro caso:

  • Utilizado por franela;
  • Hay racimos tanto en las nubes como en el hierro;
  • Me gustaría evitar la implementación repetida de todos los servicios en el clúster;
  • Es necesario hacer todo con un mínimo de problemas;
  • La versión de Kubernetes es 1.16.6 (sin embargo, otras acciones serán similares para otras versiones);
  • La tarea principal es 192.168.0.0/16reemplazarlo con un clúster implementado usando kubeadm con una subred de servicio 172.24.0.0/16.

Y sucedió que durante mucho tiempo fue interesante para nosotros ver qué y cómo en Kubernetes está almacenado en etcd, qué se puede hacer con él ... Entonces pensamos: " ¿Por qué no simplemente actualizar los datos en etcd reemplazando las antiguas direcciones IP (subred)? a los nuevos ?

Al buscar herramientas listas para trabajar con datos en etcd, no encontramos nada que resuelva completamente la tarea. (Por cierto, si conoce alguna utilidad para trabajar con datos directamente en etcd, le agradeceremos los enlaces). Sin embargo, OpenShift , etcdhelper se convirtió en un buen punto de partida (¡gracias a sus autores!) .

Esta utilidad es capaz de conectarse a ETCD el uso de certificados y leer los datos utilizando los comandos ls, get, dump.

Añadir etcdhelper


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

Se ha traducido a una versión modificada de etcdhelper con dos novedades changeServiceCIDRy características changePodCIDR. Puedes ver su código aquí .

¿Qué hacen las nuevas funciones? algoritmo changeServiceCIDR:

  • crear un deserializador;
  • compila una expresión regular para reemplazar CIDR;
  • Pasamos por todos los servicios con el tipo ClusterIP en el clúster:

    • decodifica el valor de etcd al objeto Go;
    • usando una expresión regular, reemplace los primeros dos bytes de la dirección;
    • asignar al servicio una dirección IP desde una nueva subred;
    • cree un serializador, convierta el objeto Go en protobuf, escriba nuevos datos en etcd.

La función changePodCIDRes esencialmente la misma changeServiceCIDR: solo que en lugar de editar la especificación del servicio, la hacemos para el nodo y la cambiamos .spec.PodCIDRa una nueva subred.

Práctica


Cambiar servicioCIDR


El plan para la implementación de la tarea es muy simple, pero implica tiempo de inactividad en el momento de la recreación de todos los pods en el clúster. Después de describir los pasos principales, también compartiremos nuestros pensamientos sobre cómo, en teoría, este simple puede ser minimizado.

Acciones preparatorias:

  • instalando el software necesario y ensamblando el parche etcdhelper;
  • ETCD copia de seguridad y /etc/kubernetes.

Plan de acción breve para cambiar el servicio CIDR:

  • Modificación de los manifiestos apiserver y controller-manager
  • reemisión de certificados;
  • Los servicios de ClusterIP cambian en etcd;
  • reinicie todos los pods en un 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. Recopilamos etcdhelper:

  • Ponemos 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
  • Nos salvamos etcdhelper.go, cargamos las dependencias, recogemos:

    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. Hacer copia de seguridad, 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 la subred de servicio en los manifiestos del plano de control de Kubernetes. En archivos /etc/kubernetes/manifests/kube-apiserver.yamly /etc/kubernetes/manifests/kube-controller-manager.yamlcambie el parámetro --service-cluster-ip-rangea una nueva subred: en su 172.24.0.0/16lugar 192.168.0.0/16.

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

  1. Veamos qué dominios y direcciones IP se emite 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. Prepare la 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 la clave y la clave anterior, porque sin esto no se emitirá un nuevo certificado:

    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Vuelva a emitir certificados para el servidor 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. Queda por reiniciar todos los pods en el clúster:

    kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(\S+)\s+(\S+).*/kubectl --namespace \1 delete pod \2/e'

Tiempo de inactividad minimizado


Reflexiones sobre cómo minimizar el tiempo de inactividad:

  1. Después de cambiar las manifestaciones del plano de control, cree un nuevo servicio kube-dns, por ejemplo, con un nombre kube-dns-tmpy una nueva dirección 172.24.0.10.
  2. Make ifin etcdhelper, que no modificará el servicio kube-dns.
  3. Reemplace la dirección en todos los kubelets ClusterDNSpor una nueva, mientras que el servicio antiguo continuará funcionando simultáneamente con el nuevo.
  4. Espere a que las vainas con aplicaciones se desplieguen solas por razones naturales o en un momento acordado.
  5. Elimine el servicio kube-dns-tmpy cámbielo serviceSubnetCIDRpor el servicio kube-dns.

Este plan minimizará el tiempo de inactividad hasta ~ un minuto, por el tiempo que se elimina kube-dns-tmpel servicio y se reemplaza la subred para el servicio kube-dns.

Modificación podNetwork


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

  • arreglamos configuraciones en kube-system;
  • arreglamos el manifiesto de kube-controller-manager;
  • cambiamos 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

- arreglado data.ClusterConfiguration.networking.podSubneten una nueva subred 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

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

2. Modifique el manifiesto del controlador-administrador:

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

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

3. Observe los valores actuales .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addressespara 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 haciendo cambios directamente a 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. Compruebe 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. A su vez, reiniciaremos todos los nodos del clúster.

7. Si al menos un nodo abandona el antiguo podCIDR , entonces kube-controller-manager no podrá iniciarse, y los pods en el clúster no se planificarán.

De hecho, cambiar el podCIDR puede hacerse más fácil (por ejemplo, así ). Pero queríamos aprender a trabajar con etcd directamente, porque hay casos en los que editar objetos Kubernetes en etcd es la única opción posible. (Por ejemplo, no puede simplemente cambiar el campo de un Servicio sin tiempo de inactividad spec.clusterIP).

Total


El artículo considera la posibilidad de trabajar con datos en etcd directamente, es decir sin pasar por la API de Kubernetes. A veces, este enfoque le permite hacer "cosas difíciles". Las operaciones descritas en el texto se probaron en clústeres K8 reales. Sin embargo, su estado de preparación para el 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 propio riesgo y riesgo.

PD


Lea también en nuestro blog:


All Articles