
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
kubelet
deve 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
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!'" 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!
kubelet
leia 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 busybox
com 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/etcd
no 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-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 da API será iniciado. A verificação com a ajuda curl
mostra 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 --kubeconfig
ativa o modo de servidor API, a ausência --kubeconfig
ativa 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 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 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,
kubectl
deve 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
automountServiceAccountToken
para 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
mink8s
pelo 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.