Kubernetes mínimo viável

A tradução do artigo foi preparada em antecipação ao início do curso "DevOps Practices and Tools" .










Se você está lendo isso, provavelmente já ouviu algo sobre o Kubernetes (e se não, como veio parar aqui?) Mas o que exatamente é o Kubernetes? Isso é “Orquestração de contêiner de grau industrial” ? Ou "Sistema operacional nativo da nuvem" ? O que isso significa afinal?



Para ser honesto, não tenho 100% de certeza. Mas eu acho que é interessante cavar nos detalhes internos e ver o que realmente acontece no Kubernetes sob suas muitas camadas de abstração. Então, apenas por diversão, vamos ver como é realmente um "cluster Kubernetes" mínimo. (Isso será muito mais fácil do que o Kubernetes da maneira mais difícil .)



Presumo que você tenha um conhecimento básico de Kubernetes, Linux e contêineres. Tudo o que vamos falar aqui é apenas para pesquisa / estudo, não execute nada disso na produção!



Visão geral



O Kubernetes contém muitos componentes. De acordo com a wikipedia , a arquitetura se parece com isto:







Existem pelo menos oito componentes mostrados aqui, mas iremos ignorar a maioria deles. Quero afirmar que a menor coisa que pode ser razoavelmente chamada de Kubernetes tem três componentes principais:



  • kubelet
  • kube-apiserver (que depende do etcd - seu banco de dados)
  • tempo de execução do contêiner (neste caso, Docker)


Vamos ver o que a documentação diz sobre cada um deles ( russo , inglês ). Primeiro, o kubelet :



um agente em execução em cada nó do cluster. Ele verifica se os contêineres estão sendo executados no pod.



Parece bastante simples. E sobre os contêineres de tempo de execução (tempo de execução do contêiner)?



O tempo de execução do contêiner é um programa projetado para executar contêineres.



Muito informativo. Mas se você está familiarizado com o Docker, deve ter um conhecimento básico do que ele faz. (Os detalhes da separação de interesses entre o tempo de execução do contêiner e o kubelet são bastante sutis e não vou entrar nisso aqui.)



E a API do servidor ?



Servidor da API - um componente do painel do Kubernetes que representa a API do Kubernetes. O servidor da API é o front-end do painel do Kubernetes.



Qualquer pessoa que já fez algo com o Kubernetes teve que interagir com a API diretamente ou via kubectl. Este é o coração do que torna o Kubernetes Kubernetes - o cérebro que transforma as montanhas de YAML que todos nós conhecemos e amamos (?) Em uma infraestrutura funcional. Parece óbvio que a API deve estar presente em nossa configuração mínima.



Condições prévias



  • Máquina virtual ou física com Linux enraizado (estou usando o Ubuntu 18.04 em uma máquina virtual).
  • E é tudo!


Instalação chata



O Docker precisa ser instalado na máquina que usaremos. (Não vou entrar em detalhes sobre como o Docker e os contêineres funcionam; existem ótimos artigos por aí, se você estiver interessado ). Vamos apenas instalá-lo com apt:



$ sudo apt install docker.io
$ sudo systemctl start docker


Depois disso, precisamos obter os binários do Kubernetes. Na verdade, para o lançamento inicial do nosso "cluster", só precisamos kubelet, uma vez que podemos usar para lançar outros componentes do servidor kubelet. Para interagir com nosso cluster depois que ele estiver instalado e funcionando, também usaremos kubectl.



$ curl -L https://dl.k8s.io/v1.18.5/kubernetes-server-linux-amd64.tar.gz > server.tar.gz
$ tar xzvf server.tar.gz
$ cp kubernetes/server/bin/kubelet .
$ cp kubernetes/server/bin/kubectl .
$ ./kubelet --version
Kubernetes v1.18.5


O que acontece se apenas lançarmos kubelet?



$ ./kubelet
F0609 04:03:29.105194    4583 server.go:254] mkdir /var/lib/kubelet: permission denied


kubeletdeve ser executado como root. É bastante lógico, pois ele precisa gerenciar todo o nó. Vamos dar uma olhada em seus parâmetros:



$ ./kubelet -h
<  ,   >
$ ./kubelet -h | wc -l
284


Uau, são tantas opções! Felizmente, precisamos apenas de alguns deles. Aqui está um dos parâmetros em que estamos interessados:



--pod-manifest-path string


O caminho para o diretório que contém os arquivos dos pods estáticos ou o caminho para o arquivo que descreve os pods estáticos. Arquivos que começam com pontos são ignorados. (DESCONTINUADO: este parâmetro deve ser definido no arquivo de configuração passado ao Kubelet por meio da opção --config. Para obter mais informações, consulte kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)



Este parâmetro nos permite executar estático Pods - pods que não são gerenciados por meio da API Kubernetes. Os pods estáticos raramente são usados, mas são muito convenientes para aumentar rapidamente um cluster, que é exatamente o que precisamos. Iremos ignorar este aviso alto (novamente, não execute isso na produção!) E ver se podemos funcionar abaixo.



Primeiro, criaremos um diretório para pods estáticos e executaremos kubelet:



$ mkdir pods
$ sudo ./kubelet --pod-manifest-path=pods


Então, em outra janela de terminal / tmux / em outro lugar, criaremos um manifesto de pod:



$ cat <<EOF > pods/hello.yaml
apiVersion: v1
kind: Pod
metadata:
  name: hello
spec:
  containers:
  - image: busybox
    name: hello
    command: ["echo", "hello world!"]
EOF


kubeletcomeça a escrever alguns avisos e parece que nada está acontecendo. Mas não é assim! Vamos dar uma olhada no Docker:



$ sudo docker ps -a
CONTAINER ID        IMAGE                  COMMAND                 CREATED             STATUS                      PORTS               NAMES
8c8a35e26663        busybox                "echo 'hello world!'"   36 seconds ago      Exited (0) 36 seconds ago                       k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
68f670c3c85f        k8s.gcr.io/pause:3.2   "/pause"                2 minutes ago       Up 2 minutes                                    k8s_POD_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_0
$ sudo docker logs k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
hello world!


kubeletleia o manifesto do pod e instruiu o Docker a executar alguns contêineres de acordo com nossas especificações. (Se você estiver curioso sobre o contêiner “pausar”, isso é invasão do Kubernetes - consulte este blog para obter detalhes .) O Kubelet iniciará nosso contêiner busyboxcom o comando especificado e o reiniciará indefinidamente até que o pod estático seja removido.



Parabenize-se. Acabamos de apresentar uma das maneiras mais complicadas de enviar texto para o terminal!



Execute etcd



Nosso objetivo final é executar a API Kubernetes, mas, para isso, primeiro precisamos executar o etcd . Vamos iniciar um cluster etcd mínimo colocando suas configurações no diretório pods (por exemplo pods/etcd.yaml):



apiVersion: v1
kind: Pod
metadata:
  name: etcd
  namespace: kube-system
spec:
  containers:
  - name: etcd
    command:
    - etcd
    - --data-dir=/var/lib/etcd
    image: k8s.gcr.io/etcd:3.4.3-0
    volumeMounts:
    - mountPath: /var/lib/etcd
      name: etcd-data
  hostNetwork: true
  volumes:
  - hostPath:
      path: /var/lib/etcd
      type: DirectoryOrCreate
    name: etcd-data


Se você já trabalhou com o Kubernetes, esses arquivos YAML devem ser familiares para você. Há apenas duas coisas dignas de nota aqui: Montamos a



pasta do host /var/lib/etcdno pod para que os dados etcd sejam salvos após uma reinicialização (se isso não for feito, o estado do cluster será apagado toda vez que o pod for reiniciado, o que seria ruim mesmo para uma instalação mínima do Kubernetes).



Nós instalamos hostNetwork: true. Esta opção, sem surpresa, configura o etcd para usar a rede do host em vez da rede interna do pod (isso tornará mais fácil para o servidor da API encontrar o cluster do etcd).



Uma verificação simples mostra que o etcd está realmente em execução no host local e salvando os dados no disco:



$ curl localhost:2379/version
{"etcdserver":"3.4.3","etcdcluster":"3.4.0"}
$ sudo tree /var/lib/etcd/
/var/lib/etcd/
└── member
    ├── snap
    │   └── db
    └── wal
        ├── 0.tmp
        └── 0000000000000000-0000000000000000.wal


Iniciando o servidor API



Iniciar o servidor da API Kubernetes é ainda mais fácil. O único parâmetro que precisa ser passado --etcd-serversfaz o que você espera:



apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - name: kube-apiserver
    command:
    - kube-apiserver
    - --etcd-servers=http://127.0.0.1:2379
    image: k8s.gcr.io/kube-apiserver:v1.18.5
  hostNetwork: true


Coloque este arquivo YAML no diretório podse o servidor da API será iniciado. A verificação com a ajuda curlmostra que a API Kubernetes está escutando na porta 8080 com acesso totalmente aberto - nenhuma autenticação necessária!



$ curl localhost:8080/healthz
ok
$ curl localhost:8080/api/v1/pods
{
  "kind": "PodList",
  "apiVersion": "v1",
  "metadata": {
    "selfLink": "/api/v1/pods",
    "resourceVersion": "59"
  },
  "items": []
}


(Mais uma vez, não execute isso na produção! Fiquei um pouco surpreso que a configuração padrão seja tão insegura. Mas acho que isso é para facilitar o desenvolvimento e os testes.)



E, felizmente, o kubectl funciona fora da caixa, sem quaisquer extras. configurações!



$ ./kubectl version
Client Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:47:41Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"18", GitVersion:"v1.18.5", GitCommit:"e6503f8d8f769ace2f338794c914a96fc335df0f", GitTreeState:"clean", BuildDate:"2020-06-26T03:39:24Z", GoVersion:"go1.13.9", Compiler:"gc", Platform:"linux/amd64"}
$ ./kubectl get pod
No resources found in default namespace.


Problema



Mas se você cavar um pouco mais fundo, parece que algo está errado:



$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.


Os pods estáticos que criamos sumiram! Na verdade, nosso nó kubelet não aparece:



$ ./kubectl get nodes
No resources found in default namespace.


Qual é o problema? Se você se lembra, alguns parágrafos atrás, começamos o kubelet com um conjunto extremamente simples de parâmetros de linha de comando, então o kubelet não sabe como entrar em contato com o servidor de API e notificá-lo de seu estado. Depois de examinar a documentação, encontramos o sinalizador correspondente:



--kubeconfig string



O caminho para o arquivo kubeconfig, que indica como se conectar ao servidor API. A presença --kubeconfigativa o modo de servidor API, a ausência --kubeconfigativa o modo offline.



Todo esse tempo, sem saber, estávamos executando o kubelet em "modo offline". (Se fôssemos pedantes, poderíamos considerar o modo autônomo do kubelet como "Kubernetes mínimo viável", mas isso seria muito chato). Para que a configuração "real" funcione, precisamos passar o arquivo kubeconfig ao kubelet para que ele saiba como se comunicar com o servidor API. Felizmente, isso é bastante simples (já que não temos problemas com autenticação ou certificados):



apiVersion: v1
kind: Config
clusters:
- cluster:
    server: http://127.0.0.1:8080
  name: mink8s
contexts:
- context:
    cluster: mink8s
  name: mink8s
current-context: mink8s


Salve como kubeconfig.yaml, elimine o processo kubelete reinicie com os parâmetros necessários:



$ sudo ./kubelet --pod-manifest-path=pods --kubeconfig=kubeconfig.yaml


(A propósito, se você tentar acessar a API com curl quando o kubelet estiver inativo, verá que ele ainda funciona! O Kubelet não é o "pai" de seus pods, como o Docker, é mais como um "daemon de controle". Os contêineres gerenciados pelo kubelet serão executados até que o kubelet os interrompa.)



Após alguns minutos, kubectldeve nos mostrar os pods e nós, como esperamos:



$ ./kubectl get pods -A
NAMESPACE     NAME                    READY   STATUS             RESTARTS   AGE
default       hello-mink8s            0/1     CrashLoopBackOff   261        21h
kube-system   etcd-mink8s             1/1     Running            0          21h
kube-system   kube-apiserver-mink8s   1/1     Running            0          21h
$ ./kubectl get nodes -owide
NAME     STATUS   ROLES    AGE   VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION       CONTAINER-RUNTIME
mink8s   Ready    <none>   21h   v1.18.5   10.70.10.228   <none>        Ubuntu 18.04.4 LTS   4.15.0-109-generic   docker://19.3.6


Vamos realmente nos parabenizar desta vez (eu sei que já parabenizei) - temos um "cluster" mínimo do Kubernetes funcionando com uma API totalmente funcional!



Correr sob



Agora vamos ver do que a API é capaz. Vamos começar com o pod nginx:



apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx


Aqui temos um erro muito interessante:



$ ./kubectl apply -f nginx.yaml
Error from server (Forbidden): error when creating "nginx.yaml": pods "nginx" is
forbidden: error looking up service account default/default: serviceaccount
"default" not found
$ ./kubectl get serviceaccounts
No resources found in default namespace.


Aqui vemos como nosso ambiente Kubernetes é terrivelmente incompleto - não temos contas de serviço. Vamos tentar novamente criando manualmente uma conta de serviço e ver o que acontece:



$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
EOS
serviceaccount/default created
$ ./kubectl apply -f nginx.yaml
Error from server (ServerTimeout): error when creating "nginx.yaml": No API
token found for service account "default", retry after the token is
automatically created and added to the service account


Mesmo quando criamos a conta de serviço manualmente, nenhum token de autenticação é gerado. À medida que continuamos a fazer experiências com nosso “cluster” minimalista, descobriremos que a maioria das coisas úteis que geralmente acontecem automaticamente desaparecerão. O servidor da API Kubernetes é bastante minimalista, com a maioria dos ajustes automáticos pesados ​​acontecendo em vários controladores e trabalhos em segundo plano que ainda não estão em execução.



Podemos contornar esse problema definindo uma opção automountServiceAccountTokenpara a conta de serviço (já que não teremos que usá-la de qualquer maneira):



$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
  name: default
  namespace: default
automountServiceAccountToken: false
EOS
serviceaccount/default configured
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods
NAME    READY   STATUS    RESTARTS   AGE
nginx   0/1     Pending   0          13m


Finalmente, apareceu abaixo! Mas, na verdade, ele não iniciará, porque não temos um planejador (planejador) - outro componente importante do Kubernetes. Mais uma vez, podemos ver que a API do Kubernetes é surpreendentemente estúpida - quando você cria um pod na API, ele o registra, mas não tenta descobrir em qual nó executá-lo.



Na verdade, você não precisa de um agendador para executar o pod. Você pode adicionar manualmente o nó ao manifesto no parâmetro nodeName:



apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
  nodeName: mink8s


(Substitua mink8spelo nome do host.) Após excluir e aplicar, vemos que o nginx foi iniciado e está escutando em um endereço IP interno:



$ ./kubectl delete pod nginx
pod "nginx" deleted
$ ./kubectl apply -f nginx.yaml
pod/nginx created
$ ./kubectl get pods -owide
NAME    READY   STATUS    RESTARTS   AGE   IP           NODE     NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>


Para verificar se a rede entre os pods está funcionando corretamente, podemos executar o curl de outro pod:



$ cat <<EOS | ./kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: curl
spec:
  containers:
  - image: curlimages/curl
    name: curl
    command: ["curl", "172.17.0.2"]
  nodeName: mink8s
EOS
pod/curl created
$ ./kubectl logs curl | head -6
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>


É muito divertido mergulhar neste ambiente e ver o que funciona e o que não funciona. Descobri que o ConfigMap e o Secret funcionam conforme o esperado, mas o Service e Deployment não.



Sucesso!



Esta postagem está ficando grande, então vou anunciar a vitória e declarar que esta é uma configuração viável para chamar "Kubernetes". Para resumir: quatro binários, cinco parâmetros de linha de comando e "apenas" 45 linhas de YAML (não muitos pelos padrões Kubernetes) e temos muitas coisas funcionando:



  • Os pods são gerenciados usando a API Kubernetes regular (com alguns hacks)
  • Você pode fazer upload e gerenciar imagens de contêineres públicos
  • Os pods permanecem ativos e reiniciam automaticamente
  • A rede entre pods em um único nó funciona muito bem
  • ConfigMap, Secret e montagem mais simples de repositórios funcionam conforme o esperado


Mas a maioria das coisas que tornam o Kubernetes realmente útil ainda estão faltando, por exemplo:



  • Planejador de pod
  • Autenticação / Autorização
  • Nodos múltiplos
  • Rede de serviço
  • DNS interno agrupado
  • Controladores para contas de serviço, implantações, integrações de provedor de nuvem e a maioria das outras vantagens que o Kubernetes traz


Então, o que realmente conseguimos? A API Kubernetes, trabalhando por conta própria, é realmente apenas uma plataforma de automação de contêiner . Não faz muito - funciona para os vários controladores e operadores que usam a API - mas fornece uma estrutura consistente para automação.



Saiba mais sobre o curso em um webinar gratuito.






Consulte Mais informação:






All Articles