Kubernetes viables mínimos

La traducción del artículo se preparó antes del inicio del curso "Prácticas y herramientas de DevOps" .










Si estás leyendo esto, probablemente hayas escuchado algo sobre Kubernetes (y si no, ¿cómo llegaste aquí?) Pero, ¿qué es exactamente Kubernetes? ¿Es esto “Orquestación de contenedores de grado industrial” ? ¿O "sistema operativo nativo de la nube" ? ¿Qué significa esto de todos modos?



Para ser honesto, no estoy 100% seguro. Pero creo que es interesante profundizar en los aspectos internos y ver qué sucede realmente en Kubernetes bajo sus muchas capas de abstracción. Entonces, solo por diversión, veamos cómo se ve realmente un “clúster de Kubernetes” mínimo. (Esto será mucho más fácil que Kubernetes The Hard Way ).



Supongo que tiene conocimientos básicos de Kubernetes, Linux y contenedores. Todo de lo que vamos a hablar aquí es solo para investigación / estudio, ¡no ejecute nada de esto en producción!



Visión de conjunto



Kubernetes contiene muchos componentes. Según wikipedia , la arquitectura se ve así:







hay al menos ocho componentes que se muestran aquí, pero ignoraremos la mayoría de ellos. Quiero decir que lo más pequeño que razonablemente se puede llamar Kubernetes tiene tres componentes principales:



  • kubelet
  • kube-apiserver (que depende de etcd - su base de datos)
  • tiempo de ejecución del contenedor (en este caso Docker)


Veamos qué dice la documentación sobre cada uno de ellos ( ruso , inglés ). Primero, el kubelet :



un agente que se ejecuta en todos los nodos del clúster. Él se asegura de que los contenedores estén funcionando en la vaina.



Suena bastante simple. ¿Qué pasa con los contenedores en tiempo de ejecución (tiempo de ejecución del contenedor)?



El tiempo de ejecución del contenedor es un programa diseñado para ejecutar contenedores.



Muy informativo. Pero si está familiarizado con Docker, debe tener un conocimiento básico de lo que hace. (Los detalles de la separación de preocupaciones entre el tiempo de ejecución del contenedor y el kubelet son bastante sutiles y no entraré en ellos aquí). ¿



Y la API del servidor ?



Servidor de API: un componente del panel de Kubernetes que representa la API de Kubernetes. El servidor API es la interfaz del panel de Kubernetes.



Cualquiera que haya hecho algo con Kubernetes ha tenido que interactuar con la API directamente o mediante kubectl. Este es el corazón de lo que hace que Kubernetes Kubernetes, el cerebro que convierte las montañas de YAML que todos conocemos y amamos (?) En una infraestructura funcional. Parece obvio que la API debería estar presente en nuestra configuración mínima.



Condiciones previas



  • Una máquina virtual o física de Linux rooteada (estoy usando Ubuntu 18.04 en una máquina virtual).
  • ¡Y es todo!


Instalación aburrida



Docker debe estar instalado en la máquina que usaremos. (No voy a entrar en detalles sobre cómo funcionan Docker y los contenedores; hay excelentes artículos por ahí si está interesado ). Instalemoslo con apt:



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


Después de eso, necesitamos obtener los binarios de Kubernetes. De hecho, para el lanzamiento inicial de nuestro "cluster", solo necesitamos kubelet, ya que podemos usarlo para lanzar otros componentes del servidor kubelet. Para interactuar con nuestro clúster después de que esté en funcionamiento, también 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


¿Qué pasa si acabamos de lanzar kubelet?



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


kubeletdebería ejecutarse como root. Es bastante lógico, ya que necesita administrar todo el nodo. Echemos un vistazo a sus parámetros:



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


¡Vaya, hay tantas opciones! Afortunadamente, solo necesitamos un par de ellos. Este es uno de los parámetros que nos interesan:



--pod-manifest-path string


La ruta al directorio que contiene los archivos de los pods estáticos o la ruta al archivo que describe los pods estáticos. Los archivos que comienzan con puntos se ignoran. (DESPRECADO: este parámetro debe establecerse en el archivo de configuración que se pasa a Kubelet a través de la opción --config. Para obtener más información, consulte kubernetes.io/docs/tasks/administer-cluster/kubelet-config-file ).



Este parámetro nos permite ejecutar estática Pods : pods que no se administran a través de la API de Kubernetes. Las cápsulas estáticas rara vez se usan, pero son muy convenientes para generar rápidamente un grupo, que es exactamente lo que necesitamos. Ignoraremos esta fuerte advertencia (de nuevo, ¡no ejecute esto en producción!) Y veremos si podemos hacerlo.



Primero, crearemos un directorio para pods estáticos y ejecutaremos kubelet:



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


Luego, en otra terminal / ventana tmux / en otro lugar, crearemos un manifiesto 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


kubeletempieza a escribir algunas advertencias y parece que no pasa nada. ¡Pero este no es el caso! Echemos un vistazo a 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!


kubeletleyó el manifiesto del pod e indicó a Docker que ejecutara un par de contenedores según nuestra especificación. (Si tiene curiosidad sobre el contenedor de "pausa", este es el pirateo de Kubernetes; consulte este blog para obtener más detalles ). Kubelet lanzará nuestro contenedor busyboxcon el comando especificado y lo reiniciará indefinidamente hasta que se elimine el pod estático.



Felicítese. ¡Acabamos de encontrar una de las formas más complicadas de enviar texto a la terminal!



Ejecutar etcd



Nuestro objetivo final es ejecutar la API de Kubernetes, pero para eso primero debemos ejecutar etcd . Comencemos un clúster etcd mínimo colocando su configuración en el directorio de pods (por ejemplo 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


Si alguna vez ha trabajado con Kubernetes, entonces estos archivos YAML deberían resultarle familiares. Solo hay dos cosas que vale la pena señalar aquí:



montamos la carpeta de host /var/lib/etcden el pod para que los datos etcd se guarden después de un reinicio (si no se hace esto, el estado del clúster se borrará cada vez que se reinicie el pod, lo que sería malo incluso para una instalación mínima de Kubernetes).



Hemos instalado hostNetwork: true. Esta opción, como era de esperar, configura etcd para usar la red de host en lugar de la red interna del pod (esto facilitará que el servidor de API encuentre el clúster etcd).



Una simple comprobación muestra que etcd se está ejecutando en localhost y está guardando los datos en el 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


Lanzamiento del servidor API



Iniciar el servidor API de Kubernetes es aún más fácil. El único parámetro que debe pasarse --etcd-servershace lo que 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 archivo YAML en el directorio podsy se iniciará el servidor API. La verificación con la ayuda curlmuestra que la API de Kubernetes está escuchando en el puerto 8080 con acceso completamente abierto, ¡no se requiere 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": []
}


(Nuevamente, ¡no ejecute esto en producción! Me sorprendió un poco que la configuración predeterminada sea tan insegura. Pero supongo que esto es para facilitar el desarrollo y las pruebas).



Y, gratamente sorprendido, kubectl funciona de inmediato sin ningún extra. ajustes!



$ ./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 si profundizas un poco más, parece que algo va mal:



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


¡Las cápsulas estáticas que creamos se han ido! De hecho, nuestro nodo kubelet no aparece en absoluto:



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


¿Qué pasa? Si recuerdas, hace unos párrafos comenzamos kubelet con un conjunto extremadamente simple de parámetros de línea de comando, por lo que kubelet no sabe cómo contactar al servidor API y notificarlo de su estado. Después de examinar la documentación, encontramos la bandera correspondiente:



--kubeconfig string



La ruta al archivo kubeconfig, que indica cómo conectarse al servidor API. La presencia --kubeconfighabilita el modo de servidor API, la ausencia --kubeconfighabilita el modo fuera de línea.



Todo este tiempo, sin saberlo, estuvimos ejecutando kubelet en "modo offline". (Si fuéramos pedantes, podríamos considerar el modo autónomo de kubelet como "Kubernetes mínimo viable", pero eso sería muy aburrido). Para que la configuración "real" funcione, necesitamos pasar el archivo kubeconfig al kubelet para que sepa cómo comunicarse con el servidor API. Afortunadamente, esto es bastante sencillo (ya que no tenemos ningún problema con la autenticación o los 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


Guarde esto como kubeconfig.yaml, finalice el proceso kubelety reinicie con los parámetros requeridos:



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


(Por cierto, si intenta acceder a la API con curl cuando el kubelet está inactivo, descubrirá que todavía funciona. Kubelet no es el "padre" de sus pods, como Docker, es más como un "demonio de control". Los contenedores administrados por el kubelet se ejecutarán hasta que el kubelet los detenga).



Después de unos minutos, kubectldebería mostrarnos los pods y nodos, 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 a felicitarnos de verdad esta vez (sé que ya felicité): ¡tenemos un "clúster" de Kubernetes mínimo que funciona con una API completamente funcional!



Correr bajo



Ahora veamos de qué es capaz la API. Comencemos con el pod nginx:



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


Aquí obtenemos un error 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 cuán horriblemente incompleto es nuestro entorno de Kubernetes: no tenemos cuentas de servicio. Intentemos de nuevo creando manualmente una cuenta de servicio y veamos qué sucede:



$ 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


Incluso cuando creamos la cuenta de servicio manualmente, no se genera ningún token de autenticación. A medida que continuamos experimentando con nuestro “clúster” minimalista, descubriremos que faltarán la mayoría de las cosas útiles que suelen suceder automáticamente. El servidor de la API de Kubernetes es bastante minimalista, y la mayoría de los ajustes automáticos pesados ​​se realizan en varios controladores y trabajos en segundo plano que aún no se están ejecutando.



Podemos solucionar este problema configurando una opción automountServiceAccountTokenpara la cuenta de servicio (ya que no tendremos que usarla de todos modos):



$ 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, apareció debajo! Pero, de hecho, no se iniciará, porque no tenemos un programador (programador), otro componente importante de Kubernetes. Una vez más, podemos ver que la API de Kubernetes es sorprendentemente estúpida: cuando crea un pod en la API, lo registra, pero no intenta averiguar en qué nodo ejecutarlo.



En realidad, no necesita un programador para ejecutar un pod. Puede agregar manualmente el nodo al manifiesto en el parámetro nodeName:



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


(Reemplace mink8scon el nombre de host). Después de eliminar y aplicar, vemos que nginx se ha iniciado y está escuchando en una dirección IP interna:



$ ./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 que la red entre pods esté funcionando correctamente, podemos ejecutar curl desde otro 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>


Es muy divertido profundizar en este entorno y ver qué funciona y qué no. Encontré que ConfigMap y Secret funcionan como se esperaba, pero el servicio y la implementación no.



¡Éxito!



Esta publicación se está volviendo grande, así que voy a anunciar la victoria y declarar que esta es una configuración viable para llamar "Kubernetes". Para resumir: cuatro binarios, cinco parámetros de línea de comando y "solo" 45 líneas de YAML (no muchas según los estándares Kubernetes) y tenemos muchas cosas funcionando:



  • Los pods se administran mediante la API de Kubernetes normal (con algunos trucos)
  • Puede cargar y administrar imágenes de contenedores públicos
  • Los pods se mantienen activos y se reinician automáticamente
  • La conexión en red entre pods dentro de un solo nodo funciona bastante bien
  • ConfigMap, el montaje secreto y más simple de repositorios funciona como se esperaba


Pero la mayoría de las cosas que hacen que Kubernetes sea realmente útil aún faltan, por ejemplo:



  • Planificador de vainas
  • Autorización de autenticación
  • Múltiples nodos
  • Red de servicio
  • DNS interno agrupado
  • Controladores para cuentas de servicio, implementaciones, integraciones de proveedores en la nube y la mayoría de los demás beneficios que trae Kubernetes


Entonces, ¿qué obtuvimos realmente? La API de Kubernetes, que funciona por sí sola, es en realidad solo una plataforma de automatización de contenedores . No hace mucho, funciona para los diversos controladores y operadores que utilizan la API, pero proporciona un marco coherente para la automatización.



Obtenga más información sobre el curso en un seminario web gratuito.






Lee mas:






All Articles