Kubernetes mínimo viable

A tradución do artigo preparouse na véspera do comezo do curso "Prácticas e ferramentas de DevOps".

Kubernetes mínimo viable

Se estás lendo isto, probablemente escoitaches algo sobre Kubernetes (e se non, como acabaches aquí?) Pero que é exactamente Kubernetes? Isto “Orquestración de contedores de calidade industrial”? Ou "Sistema operativo nativo na nube"? Que significa isto?

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:

Kubernetes mínimo viable

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:

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

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.

$ 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

Que pasa se só corremos kubelet?

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

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:

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

Despois, noutra xanela de terminal/tmux/o que sexa, crearemos un 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 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):

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 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:

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

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

(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):

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

Garda isto como kubeconfig.yaml, mata o proceso kubelet e reinicie cos parámetros necesarios:

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

(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:

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

Aquí temos un erro bastante interesante:

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

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

(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.

Obtén máis información sobre o curso no webinar gratuíto.

Le máis:

Fonte: www.habr.com

Engadir un comentario