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

Cada vez mais, os clientes recorrem a nós com uma solicitação para fornecer acesso ao cluster Kubernetes para a possibilidade de acessar serviços dentro do cluster: para poder se conectar diretamente a algum banco de dados ou serviço, para conectar o aplicativo local a aplicativos dentro do cluster ...



Por exemplo, é necessário se conectar da sua máquina local para o serviço memcached.staging.svc.cluster.local. Fornecemos essa oportunidade com uma VPN dentro do cluster ao qual o cliente se conecta. Para fazer isso, estamos anunciando as sub-redes de pods, serviços e envia o DNS do cluster ao cliente. Assim, quando o 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ço do cluster ou do endereço do pod.

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

  • A sub-rede é 192.168.*.*freqüentemente usada 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 trabalham nessa 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). Em todos eles, por padrão, haverá as mesmas sub-redes para pods e serviços, o que cria grandes dificuldades para trabalhar com serviços em vários clusters simultaneamente.

Já há algum tempo, adotamos a prática de usar sub-redes diferentes para serviços e pods na estrutura de um projeto - em geral, para que todos os clusters estejam com redes diferentes. No entanto, há um grande número de clusters em operação que eu não gostaria de rolar do zero, pois eles executam muitos serviços, aplicativos com estado, etc.

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

Pesquisa de decisões


A prática mais comum é recriar todos os serviços com o tipo ClusterIP. Como alternativa, eles também podem aconselhar isso:

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

Mas isso não serve para todos ... Aqui estão notas introdutórias mais detalhadas para o nosso caso:

  • Usado por flanela;
  • Existem aglomerados nas nuvens e no ferro;
  • Eu gostaria de evitar a implantação repetida de todos os serviços no cluster;
  • É necessário fazer tudo com um mínimo de problemas;
  • A versão do Kubernetes é 1.16.6 (no entanto, outras ações serão semelhantes para outras versões);
  • A principal tarefa é 192.168.0.0/16substituí-lo por um cluster implantado usando o kubeadm por uma sub-rede de serviço 172.24.0.0/16.

E aconteceu que durante muito tempo foi interessante ver o que e como no Kubernetes ele é armazenado no etcd, o que pode ser feito com ele ... Então pensamos: “ Por que não atualizar os dados no etcd substituindo os antigos endereços IP (sub-rede) para os novos ? ”

Procurando ferramentas prontas para trabalhar com dados no etcd, não encontramos nada que resolva completamente a tarefa. (A propósito, se você conhece algum utilitário para trabalhar com dados diretamente no etcd, seremos gratos pelos links.) No entanto, o OpenShift etcdhelper se tornou um bom ponto de partida (graças a seus autores!) .

Este utilitário é capaz de se conectar a ETCD usando certificados e ler os dados usando os comandos ls, get, dump.

Adicionar etcdhelper


O seguinte pensamento é lógico: "O que impede a adição deste utilitário, adicionando a capacidade de gravar dados no etcd?"

Foi traduzido para uma versão modificada do etcdhelper com dois novos changeServiceCIDRe recursos changePodCIDR. Você pode ver o código dela aqui .

O que os novos recursos fazem? Algoritmo changeServiceCIDR:

  • crie um desserializador;
  • compile uma expressão regular para substituir o CIDR;
  • passamos por todos os serviços com o tipo ClusterIP no cluster:

    • decodifique o valor de etcd para o objeto Go;
    • usando uma expressão regular, substitua os dois primeiros bytes do endereço;
    • atribua ao serviço um endereço IP de uma nova sub-rede;
    • crie um serializador, converta o objeto Go em protobuf, escreva novos dados no etcd.

A função changePodCIDRé essencialmente a mesma changeServiceCIDR- somente em vez de editar a especificação de serviço, fazemos isso para o nó e alteramos .spec.PodCIDRpara uma nova sub-rede.

Prática


Alterar serviceCIDR


O plano para a implementação da tarefa é muito simples, mas envolve tempo de inatividade no momento da recriação de todos os pods no cluster. Depois de descrever as etapas principais, também compartilharemos nossos pensamentos sobre como, em teoria, essa simples pode ser minimizada.

Ações preparatórias:

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

Plano de ação curto para alterar o serviço

  • Modificando manifestos apiserver e controller-manager
  • reemissão de certificados;
  • Serviços de clusterIP mudam no etcd;
  • reinicie todos os pods em um cluster.

A seguir, é apresentada uma sequência completa de ações em detalhes.

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

apt install etcd-client

2. Nós coletamos etcdhelper:

  • Colocamos 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 nos salvamos etcdhelper.go, carregamos as dependências, coletamos:

    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 o backup do 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. Nos arquivos /etc/kubernetes/manifests/kube-apiserver.yamle /etc/kubernetes/manifests/kube-controller-manager.yamlaltere o parâmetro --service-cluster-ip-rangepara uma nova sub-rede: em 172.24.0.0/16vez disso 192.168.0.0/16.

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

  1. Vejamos quais domínios e endereços IP o certificado atual é 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. Prepare a 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 excluir o crt e a chave antigos, porque sem isso um novo certificado não será emitido:

    rm /etc/kubernetes/pki/apiserver.{key,crt}
  4. Emita novamente certificados para o 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. 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'

Tempo de inatividade minimizado


Reflexões sobre como minimizar o tempo de inatividade:

  1. Após alterar as manifestações do plano de controle, crie um novo serviço kube-dns, por exemplo, com um nome kube-dns-tmpe um novo endereço 172.24.0.10.
  2. Crie ifno etcdhelper, que não modificará o serviço kube-dns.
  3. Substitua o endereço em todos os kubelets ClusterDNSpor um novo, enquanto o serviço antigo continuará funcionando simultaneamente com o novo.
  4. Aguarde até que os pods com aplicativos rolem sozinhos por motivos naturais ou em um horário combinado.
  5. Exclua o serviço kube-dns-tmpe altere-o serviceSubnetCIDRpara o serviço kube-dns.

Esse plano minimizará o tempo de inatividade em até ~ um minuto - pelo tempo em que o serviço for removido kube-dns-tmpe a sub-rede do serviço for substituída 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:

  • nós corrigimos as configurações kube-system;
  • consertamos o manifesto do kube-controller-manager;
  • nós mudamos o podCIDR diretamente no etcd;
  • reinicie todos os nós do cluster.

Agora, mais sobre essas ações:

1. Modifique o ConfigMap no espaço para nome kube-system:

kubectl -n kube-system edit cm kubeadm-config

- corrigido data.ClusterConfiguration.networking.podSubnetem uma nova sub-rede 10.55.0.0/16.

kubectl -n kube-system edit cm kube-proxy

- nós corrigimos data.config.conf.clusterCIDR: 10.55.0.0/16.

2. Modifique o manifesto do gerente do controlador:

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

- nós corrigimos --cluster-cidr=10.55.0.0/16.

3. Olhe para os valores atuais .spec.podCIDR, .spec.podCIDRs, .InternalIP, .status.addressespara 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 o 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. Verifique 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. Por sua vez, reiniciaremos todos os nós do cluster.

7. Se pelo menos um nó sair do podCIDR antigo , o kube-controller-manager não poderá iniciar e os pods no cluster não serão planejados.

De fato, a alteração do podCIDR pode ser facilitada (por exemplo, assim ). Mas queríamos aprender a trabalhar diretamente com o etcd, porque há casos em que editar objetos do Kubernetes no etcd é a única opção possível. (Por exemplo, você não pode simplesmente alterar o campo de um serviço sem tempo de inatividade spec.clusterIP.)

Total


O artigo considera a possibilidade de trabalhar com dados no etcd diretamente, ou seja, ignorando a API do Kubernetes. Às vezes, essa abordagem permite que você faça "coisas complicadas". As operações descritas no texto foram testadas em clusters K8s reais. No entanto, seu status de prontidão para uso generalizado é PoC (prova de conceito) . Portanto, se você quiser 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 no nosso blog:


All Articles