Para ser sincero, non estou 100% seguro. Pero creo que é interesante investigar nos aspectos internos e ver o que realmente está pasando en Kubernetes baixo as súas moitas capas de abstraccións. Entón, só por diversión, vexamos como é realmente un "clúster de Kubernetes" mínimo. (Isto será moito máis fácil que Kubernetes The Hard Way.)
Supoño que tes coñecementos básicos de Kubernetes, Linux e contenedores. Todo o que falamos aquí é só para fins de investigación/aprendizaxe, non o poñas en produción!
Comentar
Kubernetes contén moitos compoñentes. Dacordo con Wikipedia, a arquitectura ten este aspecto:
Aquí móstranse polo menos oito compoñentes, pero ignoraremos a maioría deles. Quero afirmar que o mínimo que se pode chamar razoablemente Kubernetes consta de tres compoñentes principais:
cubeta
kube-apiserver (que depende de etcd - a súa base de datos)
tempo de execución do contenedor (Docker neste caso)
Vexamos que di a documentación sobre cada un deles (rus., Inglés.). Primeiro cubeta:
Un axente en execución en cada nodo do clúster. Asegúrase de que os contedores estean funcionando na vaina.
Parece sinxelo. Que hai tempos de execución do contenedor (tempo de execución do contedor)?
Un tempo de execución de contedores é un programa deseñado para executar contedores.
Moi informativo. Pero se estás familiarizado con Docker, deberías ter unha idea xeral do que fai. (Os detalles da separación de responsabilidades entre o tempo de execución do contenedor e o kubelet son en realidade bastante sutís e non os entrarei aquí).
И Servidor API?
API Server é o compoñente do panel de control de Kubernetes que expón a API de Kubernetes. O servidor API é o lado do cliente do panel de control de Kubernetes
Calquera persoa que fixera algo con Kubernetes tivo que interactuar coa API directamente ou a través de kubectl. Este é o corazón do que fai Kubernetes Kubernetes: o cerebro que converte as montañas de YAML que todos coñecemos e amamos (?) nunha infraestrutura de traballo. Parece obvio que a API debería estar presente na nosa configuración mínima.
Condicións previas
Máquina virtual ou física Linux con acceso root (estou usando Ubuntu 18.04 nunha máquina virtual).
E é todo!
Instalación aburrida
Necesitamos instalar Docker na máquina que usaremos. (Non vou entrar en detalles sobre como funcionan Docker e os contedores; se estás interesado, hai artigos marabillosos). Simplemente instalémolo con apt:
Despois diso, necesitamos obter os binarios de Kubernetes. De feito, para o lanzamento inicial do noso "clúster" só necesitamos kubelet, xa que para executar outros compoñentes do servidor podemos empregar kubelet. Para interactuar co noso clúster despois de que estea en execución, tamén o usaremos kubectl.
kubelet debe executarse como root. Moi lóxico, xa que necesita xestionar todo o nodo. Vexamos os seus parámetros:
$ ./kubelet -h
<слишком много строк, чтобы разместить здесь>
$ ./kubelet -h | wc -l
284
Vaia, tantas opcións! Por sorte, só necesitamos un par deles. Este é un dos parámetros que nos interesan:
--pod-manifest-path string
Ruta ao directorio que contén ficheiros para pods estáticos ou ruta a un ficheiro que describe pods estáticos. Os ficheiros que comezan con puntos son ignorados. (DESACTIVADO: esta opción debe establecerse no ficheiro de configuración pasado ao Kubelet mediante a opción --config. Para obter máis información, consulte kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file .)
Esta opción permítenos executar vainas estáticas - pods que non se xestionan a través da API de Kubernetes. As vainas estáticas raramente se usan, pero son moi convenientes para crear rapidamente un clúster, e isto é exactamente o que necesitamos. Ignoraremos este gran aviso (de novo, non o executes en produción!) E veremos se podemos facer funcionar o pod.
Primeiro crearemos un directorio para pods estáticos e executaremos kubelet:
kubelet comeza a escribir algúns avisos e parece que non pasa nada. Pero iso non é certo! Vexamos 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 Lin o manifesto do pod e dei a Docker o comando para lanzar un par de contedores segundo as nosas especificacións. (Se estás a preguntar sobre o contedor de "pausa", é un hack de Kubernetes; consulta este blog.) Kubelet lanzará o noso contedor busybox co comando especificado e reinicialo indefinidamente ata que se elimine o pod estático.
Felicitate. Acabamos de crear unha das formas máis confusas de enviar texto ao terminal.
Lanzamento etcd
O noso obxectivo final é executar a API de Kubernetes, pero para facelo primeiro necesitamos executar etcd. Comecemos un clúster de etcd mínimo colocando a súa configuración no directorio pods (por exemplo, pods/etcd.yaml):
Se xa traballaches con Kubernetes, estes ficheiros YAML deberían serlle coñecidos. Aquí só hai dous puntos que vale a pena destacar:
Montamos o cartafol de host /var/lib/etcd no pod para que os datos etcd se conserven despois dun reinicio (se non se fai isto, o estado do clúster borrarase cada vez que se reinicie o pod, o que non será bo nin para unha instalación mínima de Kubernetes).
Instalamos hostNetwork: true. Esta configuración, como era de esperar, configura etcd para usar a rede host en lugar da rede interna do pod (isto facilitará ao servidor da API atopar o clúster etcd).
Unha comprobación sinxela mostra que etcd realmente se está a executar no host local e gardando datos 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
Executar un servidor API de Kubernetes é aínda máis sinxelo. O único parámetro que hai que pasar é --etcd-servers, fai o que esperas:
Coloque este ficheiro YAML no directorio pods, e iniciarase o servidor API. Comprobando con curl mostra que a API de Kubernetes escoita no porto 8080 con acceso completamente aberto, sen necesidade de autenticación.
(De novo, non executes isto en produción! Sorprendeume un pouco que a configuración predeterminada sexa tan insegura. Pero supoño que isto é para facilitar o desenvolvemento e as probas.)
E, unha agradable sorpresa, kubectl funciona fóra da caixa sen ningunha configuración 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.
problema
Pero se afondas un pouco máis, algo parece estar mal:
$ ./kubectl get pod -n kube-system
No resources found in kube-system namespace.
As vainas estáticas que creamos desapareceron! De feito, o noso nodo kubelet non se descobre en absoluto:
$ ./kubectl get nodes
No resources found in default namespace.
Que pasa? Se lembras hai uns parágrafos, iniciamos o kubelet cun conxunto moi sinxelo de parámetros de liña de comandos, polo que o kubelet non sabe como contactar co servidor da API e notificarlle o seu estado. Despois de estudar a documentación, atopamos a bandeira correspondente:
--kubeconfig string
O camiño ao ficheiro kubeconfig, que especifica como conectarse ao servidor API. Dispoñibilidade --kubeconfig activa o modo de servidor API, non --kubeconfig activa o modo sen conexión.
Durante todo este tempo, sen sabelo, estivemos executando o kubelet en "modo sen conexión". (Se fosemos pedantes, poderiamos pensar nun kubelet autónomo como "Kubernetes viables mínimos", pero iso sería moi aburrido). Para que a configuración "real" funcione, necesitamos pasar o ficheiro kubeconfig ao kubelet para que saiba como falar co servidor da API. Afortunadamente é bastante sinxelo (xa que non temos ningún problema de autenticación ou certificado):
(Por certo, se tentas acceder á API a través de curl cando o kubelet non se está a executar, verás que aínda se está a executar! Kubelet non é un "pai" dos seus pods como Docker, é máis ben un "control". daemon." Os contedores xestionados por un kubelet seguirán funcionando ata que o kubelet os deteña.)
En poucos minutos kubectl debería mostrarnos as vainas 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
Felicitemonos de verdade nesta ocasión (sei que xa nos felicitei) - temos un "clúster" mínimo de Kubernetes funcionando cunha API totalmente funcional!
Lanzamos baixo
Agora imos ver de que é capaz a API. Comecemos co pod nginx:
$ ./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.
Aquí vemos o lamentablemente incompleto que está o noso ambiente Kubernetes: non temos contas para servizos. Tenteo de novo creando manualmente unha conta de servizo e vexamos que pasa:
$ 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 cando creamos a conta de servizo manualmente, o token de autenticación non se xera. A medida que seguimos experimentando co noso "clúster" minimalista, atoparemos que faltarán a maioría das cousas útiles que adoitan ocorrer automaticamente. O servidor da API de Kubernetes é bastante minimalista, coa maior parte do traballo pesado e da configuración automática que ocorre en varios controladores e traballos en segundo plano que aínda non están en execución.
Podemos solucionar este problema configurando a opción automountServiceAccountToken para a conta de servizo (xa que non teremos que usala de todos os xeitos):
$ 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
Por fin apareceu a vaina! Pero de feito non comezará porque non temos planificador (programador) é outro compoñente importante de Kubernetes. Unha vez máis, vemos que a API de Kubernetes é sorprendentemente "tonta": cando creas un Pod na API, rexístrao, pero non intenta descubrir en que nodo executalo.
De feito, non necesitas un programador para executar un pod. Podes engadir manualmente un nodo ao manifesto no parámetro nodeName:
(Substituír mink8s ao nome do nodo.) Despois de eliminar e aplicar, vemos que nginx comezou e escoita o enderezo 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 asegurarnos de que a rede entre pods funciona correctamente, podemos executar curl desde 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>
É bastante interesante afondar neste ambiente e ver o que funciona e o que non. Descubrín que ConfigMap e Secret funcionan como se esperaba, pero o servizo e a implementación non.
Éxito!
Esta publicación vaise facendo longa, así que vou declarar a vitoria e dicir que esta é unha configuración viable que se pode chamar "Kubernetes". Para resumir: catro binarios, cinco parámetros de liña de comandos e "só" 45 liñas de YAML (non tanto para os estándares Kubernetes) e temos bastantes cousas funcionando:
Os pods xestionanse mediante a API normal de Kubernetes (con algúns trucos)
Podes cargar e xestionar imaxes de contedores públicos
Os pods permanecen activos e reinician automaticamente
A rede entre pods dentro do mesmo nodo funciona bastante ben
ConfigMap, o montaxe de almacenamento secreto e sinxelo funciona como se esperaba
Pero aínda falta moito do que fai que Kubernetes sexa realmente útil, como:
Programador de pods
Autenticación/autorización
Múltiples nodos
Rede de servizos
DNS interno en clúster
Controladores para contas de servizo, despregamentos, integración con provedores de nube e a maioría das outras vantaxes que trae Kubernetes
Entón, que conseguimos realmente? A API de Kubernetes, que se executa por si mesma, é realmente só unha plataforma para automatización de contedores. Non fai moito - é un traballo para varios controladores e operadores que usan a API - pero proporciona un ambiente coherente para a automatización.