Kubernetes minimamente viável

A tradução do artigo foi preparada em antecipação ao início do curso «Práticas e Ferramentas DevOps».

Kubernetes minimamente viável

Se você está lendo isso, provavelmente já ouviu falar do Kubernetes (e se não, como você chegou aqui?). Mas o que realmente é o Kubernetes? É “Orquestração de contêineres em nível industrial”? Или “Sistema Operacional Nativo da Nuvem”? Что вообще это значит?

Sinceramente, não tenho 100% de certeza. Mas acho interessante mergulhar nas entrelinhas e ver o que realmente acontece no Kubernetes por trás de suas muitas camadas de abstração. Então, por curiosidade, vamos dar uma olhada em como realmente é o mínimo “cluster Kubernetes”. (Isto será muito mais simples do que Kubernetes The Hard Way.)

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

Visão geral

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

Kubernetes minimamente viável

Aqui estão pelo menos oito componentes mostrados, mas a maioria deles iremos ignorar. Quero afirmar que a menor coisa que pode ser razoavelmente chamada de Kubernetes consiste em três componentes principais:

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

Vamos ver o que cada um deles diz na documentação (rus., ingl…). Primeiro kubelet:

Um agente que roda em cada nó no cluster. Ele garante que os contêineres sejam executados em um pod.

Parece bastante simples. E quanto ao ambiente de execução de contêiner (container runtime)?

O ambiente de execução de contêiner é um programa destinado a executar contêineres.

Muito informativo. Mas se você está familiarizado com o Docker, deve ter uma ideia geral do que ele faz. (Os detalhes da separação de responsabilidades entre o ambiente de execução de contêineres e o kubelet são na verdade bem sutis e aqui não vou me aprofundar neles.)

eu posso usar API-server?

O servidor API é um componente da interface de controle do Kubernetes, que representa a API do Kubernetes. O servidor API é a parte cliente da interface de controle do Kubernetes.

Qualquer um que já tenha feito alguma coisa com Kubernetes teve que interagir com a API, seja diretamente ou através do kubectl. Este é o coração do que faz o Kubernetes ser o Kubernetes — o cérebro que transforma montanhas de YAML, que todos nós conhecemos e amamos (?), em infraestrutura funcional. Parece óbvio que a API deve estar presente em nossa configuração mínima.

Condições preliminares

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

Instalação chata

No sistema que vamos usar, é necessário instalar o Docker. (Não vou explicar em detalhes como o Docker e os containers funcionam; se você estiver interessado, existem artigos incríveis). 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 iniciar nosso “cluster”, precisamos apenas de kubelet, pois para executar os outros componentes do servidor poderemos usar kubelet. Para interagir com nosso cluster uma vez que ele esteja 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 simplesmente executarmos kubelet?

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

kubelet deve ser executado como root. Faz sentido, já que ele precisa gerenciar todo o nó. Vamos dar uma olhada nas suas opções:

$ ./kubelet -h

$ ./kubelet -h | wc -l
284

Uau, quanta opção! Felizmente, só precisaremos de algumas delas. Aqui está um dos parâmetros que nos interessa:

--pod-manifest-path string

Caminho para o diretório contendo arquivos para pods estáticos, ou o caminho para um arquivo com a descrição dos pods estáticos. Arquivos que começam com pontos são ignorados. (OBSOLETE: este parâmetro deve ser configurado no arquivo de configuração passado para o Kubelet através da opção --config. Para mais informações, veja kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)

Este parâmetro nos permite executar pods estáticos — pods que não são gerenciados através da API do Kubernetes. Pods estáticos são usados raramente, mas são muito convenientes para rapidamente levantar um cluster, e é exatamente isso que precisamos. Vamos ignorar esse aviso intenso (novamente, não execute isso em produção!) e ver se conseguimos iniciar um pod.

Primeiro, vamos criar um diretório para os pods estáticos e iniciar kubelet:

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

Então, em outro terminal/janela tmux/em outro lugar, criaremos o manifesto do pod:

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

kubelet Começ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!'"   há 36 segundos      Encerrado (0) há 36 segundos                       k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
68f670c3c85f        k8s.gcr.io/pause:3.2   "pause"                há 2 minutos       Em execução há 2 minutos                                    k8s_POD_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_0
$ sudo docker logs k8s_hello_hello-mink8s_default_ab61ef0307c6e0dee2ab05dc1ff94812_4
hello world!

kubelet Lemos o manifesto do pod e dissemos ao Docker para iniciar alguns contêineres de acordo com nossa especificação. (Se você estiver interessado em saber sobre o contêiner “pause”, isso é uma manobra do Kubernetes — mais detalhes podem ser vistos em neste blog.) O Kubelet iniciará nosso contêiner busybox com o comando especificado e o reiniciará indefinidamente até que o pod estático seja removido.

Parabéns! Acabamos de inventar uma das maneiras mais complicadas de imprimir texto no terminal!

Iniciando etcd

Nosso objetivo final é iniciar a API do Kubernetes, mas para isso primeiro precisamos iniciar etcd. Vamos iniciar um cluster etcd mínimo, colocando suas configurações na pasta 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 Kubernetes, esses arquivos YAML devem ser familiares para você. Aqui vale a pena destacar apenas dois pontos:

Montamos a pasta do host /var/lib/etcd no pod para que os dados do etcd sejam mantidos após a reinicialização (se não fizer isso, o estado do cluster será apagado a cada reinicialização do pod, o que não é desejado mesmo para uma instalação mínima do Kubernetes).

Configuramos hostNetwork: true. Este parâmetro, como não poderia deixar de ser, configura o etcd para usar a rede do host em vez da rede interna do pod (o que facilitará para o servidor API encontrar o cluster etcd).

Uma simples verificação mostra que o etcd realmente está rodando no localhost e salvando 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 um servidor de API Kubernetes é ainda mais simples. O único parâmetro que deve ser passado, --etcd-servers, faz 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 pods, e o servidor API será iniciado. A verificação com curl mostra que o API Kubernetes está escutando na porta 8080 com acesso total — autenticação nã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": []
}

(Novamente, não execute isso em produção! Fiquei um pouco surpreso que a configuração padrão seja tão insegura. Mas imagino que isso seja feito para facilitar o desenvolvimento e os testes.)

E, uma grata surpresa, kubectl funciona imediatamente sem qualquer configuração adicional!

$ ./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.

O problema

Mas se você aprofundar um pouco mais, parece que algo não está certo:

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

Os pods estáticos que criamos desapareceram! Na verdade, nosso nó kubelet não é detectado:

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

Qual é o problema? Se você se lembrar, há alguns parágrafos atrás, nós iniciamos o kubelet com um conjunto extremamente simples de parâmetros de linha de comando, por isso o kubelet não sabe como se conectar ao servidor API e notificá-lo sobre seu estado. Ao examinar a documentação, encontramos a flag correspondente:

--kubeconfig string

Caminho para o arquivo kubeconfig, que especifica como se conectar ao servidor API. A presença de --kubeconfig ativa o modo do servidor API, enquanto a ausência de --kubeconfig ativa o modo autônomo.

Todo esse tempo, sem saber, tínhamos executado o kubelet em "modo autônomo". (Se fôssemos meticulosos, poderíamos considerar o modo autônomo do kubelet como um "Kubernetes minimamente viável", mas isso seria muito chato). Para que a configuração "real" funcione, precisamos fornecer 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 isso como kubeconfig.yaml, mate o processo kubelet e 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 via curl quando o kubelet não estiver funcionando, descobrirá que ele ainda está ativo! O kubelet não é o "pai" de seus pods, como o Docker, ele se assemelha mais a um "demônio controlador". Os contêineres gerenciados pelo kubelet continuarão funcionando até que o kubelet os pare.)

Daqui a alguns minutos kubectl deve nos mostrar os pods e nós, como esperávamos:

$ ./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       21h   v1.18.5   10.70.10.228           Ubuntu 18.04.4 LTS   4.15.0-109-generic   docker://19.3.6

Vamos realmente nos parabenizar desta vez (eu sei que já fiz isso) — temos um "cluster" Kubernetes mínimo funcionando com uma API funcional!

Iniciando o pod

Agora vamos ver do que a API é capaz. Comecemos com o pod nginx:

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

Aqui teremos um erro bastante 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 o quão horrivelmente incompleta está nossa infraestrutura Kubernetes — não temos contas de serviço. Vamos tentar novamente, criando uma conta de serviço manualmente, 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 manualmente uma conta de serviço, o token de autenticação não é criado. Ao continuar experimentando com nosso 'cluster' minimalista, descobrimos que muitas coisas úteis que normalmente acontecem automaticamente estarão ausentes. O servidor API do Kubernetes é bastante minimalista, a maior parte das pesadas configurações automáticas acontece em vários controladores e tarefas em segundo plano que ainda não estão sendo executadas.

Podemos contornar esse problema definindo a opção automountServiceAccountToken para a conta de serviço (já que não precisamos utilizá-la):

$ 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, o pod apareceu! Mas na verdade ele não vai iniciar, pois não temos um agendador (scheduler) — outro componente importante do Kubernetes. Novamente, vemos que a API do Kubernetes é surpreendentemente 'burra' — quando você cria um pod na API, ele o registra, mas não tenta descobrir em qual nó executá-lo.

Na verdade, para executar um pod, o agendador não é necessário. Você pode adicionar manualmente um nó no manifesto no parâmetro nodeName:

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

(Substitua mink8s pelo nome do nó.) Após delete e apply, vemos que o nginx iniciou e está ouvindo no endereço IP interno:

$ ./kubectl delete pod nginx
pod "nginx" deletado
$ ./kubectl apply -f nginx.yaml
pod/nginx criado
$ ./kubectl get pods -owide
NOME    PRONTO   STATUS    REINICIAÇÕES   IDADE   IP           NÓ     NÓ NOMINADO   PORTÕES DE PRONTIDÃO
nginx   1/1     Em Execução   0          30s   172.17.0.2   mink8s   <none>           <none>
$ curl -s 172.17.0.2 | head -4
<!DOCTYPE html>
<html>
<head>
<title>Bem-vindo ao nginx!</title>

Para garantir que a rede entre os pods está funcionando corretamente, podemos executar curl de outro pod:

$ gato &lt;&lt;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 criado
$ .\/kubectl logs curl | head -6
  % Total    % Recebido % Transferido  Velocidade Média   Tempo    Tempo     Tempo  Atual
                                 Baixar  Enviar   Total   Gasto    Restante  Velocidade
<!DOCTYPE html>
<html>
<head>
<title>Bem-vindo ao nginx!</title>

É bastante interessante explorar esse ambiente e ver o que funciona e o que não funciona. Descobri que ConfigMap e Secret funcionam como esperado, mas Service e Deployment não.

Sucesso!

Este post está crescendo, então vou declarar uma vitória e afirmar que essa é uma configuração viável que pode ser chamada de "Kubernetes". Resumindo: quatro arquivos binários, cinco parâmetros de linha de comando e "apenas" 45 linhas de YAML (não é muito pelos padrões do Kubernetes) e temos várias coisas funcionando:

  • Os pods são gerenciados por meio da API padrão do Kubernetes (com algumas adaptações)
  • É possível carregar imagens de contêiner públicas e gerenciá-las
  • Os pods permanecem ativos e reiniciam automaticamente
  • A rede entre os pods em um único nó funciona muito bem
  • ConfigMap, Secret e montagem simples de volumes funcionam corretamente

Mas a maior parte do que torna o Kubernetes realmente útil ainda está faltando, como:

  • Agendador de pods
  • Autenticação / autorização
  • Múltiplos nós
  • Rede de serviços
  • DNS interno do cluster
  • Controladores para contas de serviço, implantações, integração com provedores de nuvem e a maioria das outras 'ferramentas' que o Kubernetes oferece

Então, o que realmente temos? A API do Kubernetes, por si só, é na verdade apenas uma plataforma para automação de contêineres. Ela não faz muito – essa é a função de vários controladores e operadores que utilizam a API – mas fornece um ambiente consistente para automação.

Saiba mais sobre o curso em um webinar gratuito.

Leia mais:

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster