Nossa experiência com dados no cluster etcd Kubernetes diretamente (sem API K8s)

Cada vez mais, os clientes nos pedem para fornecer acesso ao cluster Kubernetes para poder acessar serviços dentro do cluster: para poder conectar-se diretamente a algum banco de dados ou serviço, para conectar uma aplicação local com aplicações dentro do cluster...

Nossa experiência com dados no cluster etcd Kubernetes diretamente (sem API K8s)

Por exemplo, é necessário conectar-se da sua máquina local a um serviço memcached.staging.svc.cluster.local. Fornecemos esse recurso usando uma VPN dentro do cluster ao qual o cliente se conecta. Para fazer isso, anunciamos sub-redes de pods, serviços e enviamos DNS do cluster para o cliente. Assim, quando um cliente tenta se conectar ao serviço memcached.staging.svc.cluster.local, a solicitação vai para o DNS do cluster e em resposta recebe o endereço desse serviço da rede de serviços do cluster ou do endereço do pod.

Configuramos clusters K8s usando kubeadm, onde a sub-rede de serviço padrão é 192.168.0.0/16, e a rede de pods é 10.244.0.0/16. Geralmente tudo funciona bem, mas há alguns pontos:

  • Sub-rede 192.168.*.* frequentemente usado em redes de escritórios de clientes e ainda mais frequentemente em redes domésticas de desenvolvedores. E então temos conflitos: os roteadores domésticos funcionam nesta sub-rede e a VPN envia essas sub-redes do cluster para o cliente.
  • Temos vários clusters (produção, estágio e/ou vários clusters de desenvolvimento). Então, por padrão, todos eles terão as mesmas sub-redes para pods e serviços, o que cria grandes dificuldades para o trabalho simultâneo com serviços em diversos clusters.

Há muito tempo adotamos a prática de usar diferentes sub-redes para serviços e pods dentro do mesmo projeto – em geral, para que todos os clusters tenham redes diferentes. No entanto, há um grande número de clusters em operação que eu não gostaria de transferir do zero, pois eles executam muitos serviços, aplicações com estado, etc.

E então nos perguntamos: como mudar a sub-rede em um cluster existente?

Procure por soluções

A prática mais comum é recriar todos serviços com tipo ClusterIP. Como opção, pode aconselhar e tal:

O seguinte processo apresenta um problema: depois de tudo configurado, os pods aparecem com o IP antigo como servidor de nomes DNS em /etc/resolv.conf.
Como ainda não encontrei a solução, tive que redefinir todo o cluster com kubeadm reset e iniciá-lo novamente.

Mas isso não é adequado para todos... Aqui estão introduções mais detalhadas para o nosso caso:

  • Flanela é usada;
  • Existem clusters tanto nas nuvens quanto no hardware;
  • Gostaria de evitar a reimplantação de todos os serviços no cluster;
  • É necessário geralmente fazer tudo com um número mínimo de problemas;
  • A versão do Kubernetes é 1.16.6 (no entanto, as etapas adicionais serão semelhantes para outras versões);
  • A principal tarefa é garantir que em um cluster implantado usando kubeadm com uma sub-rede de serviço 192.168.0.0/16, substitua-o por 172.24.0.0/16.

E aconteceu que há muito estávamos interessados ​​​​em ver o que e como o Kubernetes é armazenado no etcd, o que pode ser feito com isso... Então pensamos: “Por que não apenas atualizar os dados no etcd, substituindo os endereços IP antigos (sub-rede) por novos?? »

Depois de procurar ferramentas prontas para trabalhar com dados no etcd, não encontramos nada que resolvesse completamente o problema. (A propósito, se você conhece algum utilitário para trabalhar com dados diretamente no etcd, agradeceríamos os links.) Contudo, um bom ponto de partida é etcdhelper do OpenShift (graças aos seus autores!).

Este utilitário pode se conectar ao etcd usando certificados e ler dados de lá usando comandos ls, get, dump.

Adicionar etcdhelper

O próximo pensamento é lógico: “O que impede você de adicionar este utilitário adicionando a capacidade de gravar dados no etcd?”

Tornou-se uma versão modificada do etcdhelper com duas novas funções changeServiceCIDR и changePodCIDR. nela você pode ver o código aqui.

O que os novos recursos fazem? Algoritmo changeServiceCIDR:

  • crie um desserializador;
  • compilar uma expressão regular para substituir o CIDR;
  • passamos por todos os serviços com o tipo ClusterIP no cluster:
    • decodifique o valor do etcd em um objeto Go;
    • usando uma expressão regular substituímos os dois primeiros bytes do endereço;
    • atribua ao serviço um endereço IP da nova sub-rede;
    • crie um serializador, converta o objeto Go em protobuf, grave novos dados no etcd.

Função changePodCIDR essencialmente semelhante changeServiceCIDR - só que em vez de editar a especificação do serviço, fazemos isso para o nó e alteramos .spec.PodCIDR para uma nova sub-rede.

Prática

Alterar serviço CIDR

O plano de implementação da tarefa é muito simples, mas envolve tempo de inatividade no momento da recriação de todos os pods do cluster. Depois de descrever as principais etapas, também compartilharemos ideias sobre como, em teoria, esse tempo de inatividade pode ser minimizado.

Etapas preparatórias:

  • instalar o software necessário e montar o etcdhelper corrigido;
  • backup etcd e /etc/kubernetes.

Breve plano de ação para alterar o serviceCIDR:

  • alterar os manifestos do apiserver e do controller-manager;
  • reemissão de certificados;
  • alterando os serviços ClusterIP no etcd;
  • reinicialização de todos os pods no cluster.

A seguir está uma sequência completa de ações em detalhes.

1. Instale o etcd-client para despejo de dados:

apt install etcd-client

2. Construa o etcdhelper:

  • Instale o 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
  • Nós economizamos para nós mesmos etcdhelper.go, baixe dependências, colete:
    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. Faça um backup 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. Altere a sub-rede de serviço nos manifestos do plano de controle do Kubernetes. Em arquivos /etc/kubernetes/manifests/kube-apiserver.yaml и /etc/kubernetes/manifests/kube-controller-manager.yaml alterar o parâmetro --service-cluster-ip-range para uma nova sub-rede: 172.24.0.0/16 ao invés de 192.168.0.0/16.

5. Como estamos alterando a sub-rede de serviço para a qual o kubeadm emite certificados para apiserver (inclusive), eles precisam ser reemitidos:

  1. Vamos ver para quais domínios e endereços IP o certificado atual foi emitido:
    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. Vamos preparar uma configuração mínima para o 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. Vamos deletar o crt e a chave antigos, pois sem isso o novo certificado não será emitido:
    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Vamos reemitir certificados para o servidor API:
    kubeadm init phase certs apiserver --config=kubeadm-config.yaml
  5. Vamos verificar se o certificado foi emitido para a nova sub-rede:
    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. Após reemitir o certificado do servidor API, reinicie seu contêiner:
    docker ps | grep k8s_kube-apiserver | awk '{print $1}' | xargs docker restart
  7. Vamos regenerar a configuração para admin.conf:
    kubeadm alpha certs renew admin.conf
  8. Vamos editar os dados no 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 

    Atenção! Neste momento, a resolução do domínio para de funcionar no cluster, pois nos pods existentes /etc/resolv.conf o endereço CoreDNS antigo (kube-dns) é registrado e o kube-proxy altera as regras de iptables da sub-rede antiga para a nova. Mais adiante no artigo está escrito sobre possíveis opções para minimizar o tempo de inatividade.

  9. Vamos consertar ConfigMap no namespace kube-system:
    kubectl -n kube-system edit cm kubelet-config-1.16

    - substitua aqui clusterDNS para o novo endereço IP do serviço kube-dns: kubectl -n kube-system get svc kube-dns.

    kubectl -n kube-system edit cm kubeadm-config

    - vamos consertar data.ClusterConfiguration.networking.serviceSubnet para uma nova sub-rede.

  10. Como o endereço kube-dns mudou, é necessário atualizar a configuração do kubelet em todos os nós:
    kubeadm upgrade node phase kubelet-config && systemctl restart kubelet
  11. Tudo o que resta é reiniciar todos os pods no cluster:
    kubectl get pods --no-headers=true --all-namespaces |sed -r 's/(S+)s+(S+).*/kubectl --namespace 1 delete pod 2/e'

Minimize o tempo de inatividade

Reflexões sobre como minimizar o tempo de inatividade:

  1. Após alterar os manifestos do plano de controle, crie um novo serviço kube-dns, por exemplo, com o nome kube-dns-tmp e novo endereço 172.24.0.10.
  2. Fazer if no etcdhelper, que não modificará o serviço kube-dns.
  3. Substitua o endereço em todos os kubelets ClusterDNS para um novo, enquanto o serviço antigo continuará funcionando simultaneamente com o novo.
  4. Aguarde até que os pods com aplicativos sejam transferidos sozinhos por motivos naturais ou em um horário combinado.
  5. Excluir serviço kube-dns-tmp e mudar serviceSubnetCIDR para o serviço kube-dns.

Este plano permitirá que você minimize o tempo de inatividade para aproximadamente um minuto - durante a remoção do serviço kube-dns-tmp e alterando a sub-rede do serviço kube-dns.

PodNetwork de modificação

Ao mesmo tempo, decidimos ver como modificar o podNetwork usando o etcdhelper resultante. A sequência de ações é a seguinte:

  • corrigindo configurações em kube-system;
  • corrigindo o manifesto kube-controller-manager;
  • altere o podCIDR diretamente no etcd;
  • reinicialize todos os nós do cluster.

Agora mais sobre essas ações:

1. Modifique ConfigMap no namespace kube-system:

kubectl -n kube-system edit cm kubeadm-config

- corrigindo data.ClusterConfiguration.networking.podSubnet para uma nova sub-rede 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

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

2. Modifique o manifesto do gerenciador do controlador:

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

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

3. Veja os valores atuais .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addresses para todos os nós do cluster:

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. Substitua podCIDR fazendo alterações diretamente no 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. Vamos verificar se o podCIDR realmente mudou:

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. Vamos reinicializar todos os nós do cluster, um por um.

7. Se você deixar pelo menos um nó podCIDR antigo, o kube-controller-manager não poderá ser iniciado e os pods no cluster não serão agendados.

Na verdade, alterar o podCIDR pode ser feito de forma ainda mais simples (por exemplo, assim). Mas queríamos aprender como trabalhar diretamente com o etcd, porque há casos de edição de objetos Kubernetes no etcd - apenas variante possível. (Por exemplo, você não pode simplesmente alterar o campo Serviço sem tempo de inatividade spec.clusterIP.)

Total

O artigo discute a possibilidade de trabalhar diretamente com dados no etcd, ou seja, ignorando a API Kubernetes. Às vezes, essa abordagem permite que você faça “coisas complicadas”. Testamos as operações fornecidas no texto em clusters K8s reais. No entanto, o seu estado de prontidão para utilização generalizada é PoC (prova de conceito). Portanto, se desejar usar uma versão modificada do utilitário etcdhelper em seus clusters, faça-o por sua própria conta e risco.

PS

Leia também em nosso blog:

Fonte: habr.com

Adicionar um comentário