Si eres desarrollador y te vas manteniendo al día seguro que alguna vez habrás oído hablar de Kubernetes o k8s que es su diminutivo, y entiendo que si estás aquí es porque aún te suena a chino.
Pues bien lo primero que te voy a recomendar, es si tampoco estás familiarizado con Docker te empapes un poco de él, ya que Kubernetes necesita de él para funcionar y habrá conceptos que quizás no acabes de entender de no ser así. Un punto de partida podría ser el artículo que escribimos hace algún tiempo de como dockerizar tu aplicación Go, y sino tienes muchos recursos en la red para ello.
Además estaréis pensando, ¿pero este blog no era sobre Go? así es, pero nos hemos tomado un vacío legal, ya que Kubernetes está escrito en Go, y no sólo eso sino que es open source.
¿Para que sirve Kubernetes?
Primero que nada hay que decir, para los más frikis de la casa, que Kubernetes viene del griego que significa, timonel o piloto, además es la raíz de gobernador y de cibernética. Una vez aclarado esto que seguro que os mantenía en vilo, podemos ver qué es Kubernetes a nivel técnico que es lo que nos toca.
Kubernetes nos ofrece una serie de herramientas que nos permiten automatizar la distribución de aplicaciones a un cluster de servidores, además nos asegura que la gestión del hardware será la más eficiente. Por si eso fuera poco, Kubernetes se sitúa de tal forma que podamos tener múltiples servidores y para nosotros sea como si sólo trabajáramos con uno solo.
Para todo esto Kubernetes nos expone una API Restful que es la que utilizaremos para desplegar nuestras aplicaciones de manera sencilla, no tenemos que pensar ya en que parte del clúster desplegar nuestra aplicación sino que Kubernetes se ocupará por nosotros.
Como el uso de su API, puede resultar algo engorroso, Kubernetes tiene a bien ofrecernos un cliente con el que encapsula todas las llamadas a su API de una manera muy sencilla, y este es kubectl que es con la herramienta que estaremos trabajando durante todo el artículo.
Preparando nuestro entorno
Ahora que entendemos un poco mejor que es Kubernetes, pasaremos a preparar nuestro entorno, para ello como ya hemos dicho vamos a necesitar instalar el cliente kubectl.
Esto es tan sencillo como seguir las indicaciones que nos ofrece la propia documentación oficial al respecto: Instalar y Configurar kubectl.
Una vez tengamos instalado kubectl, lo siguiente que necesitaremos es un clúster de Kubernetes, tranquilos, no salgáis aún a por la billetera, no es necesario contratar ningún clúster de Kubernetes en la nube, al menos no por ahora, ya que haremos nuestros ejercicios en local.
Y seguro que estaréis pensando, ahora necesitaré un reactor nuclear para poder correr mi clúster de Kubernetes en local, bueno pues siento decirte que no, nosotros para este artículo utilizaremos minikube.
Minikube, utiliza la virtualización para montar un clúster de servidores en nuestro entorno, además es una de las herramientas oficiales que ofrece Kubernetes, y además también nos ofrecen todas las instrucciones de instalación en nuestro idioma.
Ya lo tenemos todo para empezar a jugar.
Nuestra aplicación
Nosotros hemos querido crear una aplicación para este ejercicio, podríamos haber utilizado perfectamente nuestra famosa gopherapi, pero queríamos hacer algo más llamativo, por ello hemos creado giphyneitor, una simple web-app, donde utilizamos la api de Giphy para pintar un gif random cada vez que accedemos a la página.
El código de dicha aplicación por si os interesa lo tenéis disponible en nuestro repositorio, dicho repositorio también contiene todo el ejercicio de Kubernetes que veremos en el artículo.
Para poder utilizar nuestra aplicación deberéis usar la imagen de docker que también os proporcionamos, friendsofgo/giphyneitor, pero obviamente podrías usar otras imagenes de docker.
Recursos de Kubernetes
Para poder hacer funcionar nuestra aplicación en Kubernetes necesitaremos de recursos para ello, dentro de Kubernetes nos podemos encontrar diversos recursos, que se dividen en tipos de objetos, no los veremos todos sino los que podríamos llegar a necesitar para desplegar una aplicación como giphyneitor.
Para crear nuestros recursos utilizaremos como no la herramienta kubectl la cual nos ofrece todo lo necesario para trabajar con ellos.
Para ello utilizaremos los generators, que no es más que un comando, kubectl create {tipo de objeto}, además si añadimos el flag --dry-run -o yaml podremos ver como será el objeto que creara Kubernetes pero sin crearlo, o en el caso de los pods, kubectl run.
Antes de empezar crearemos un namespace, un namespace se usa para si tenemos distintos equipos o proyectos poder agrupar el conjunto de recursos bajo un contexto descriptivo.
Pods
Quizás si te digo pod, tu cabeza empieza a trabajar y a recordar conversaciones sueltas con otros desarrolladores, y es que en Kubernetes siempre utilizaremos Pods para ejecutar nuestras aplicaciones.
Los Pods contienen una colección de uno o más contenedores y/o volúmenes de disco. Los pods son mortales, es decir si destruimos un pod esté no volverá a levantarse, además todos ellos nacen utilizando la misma network, además todos ellos nacen utilizando la misma network, de esta manera nuestro Pod expondrá una sola dirección de IP para nuestros contenedores.
Para crear nuestro Pod necesitaremos ejecutar lo siguiente:
$ kubectl run giphyneitor --image=friendsofgo/giphyneitor --env=GIPHY_API_KEY=your_api_key --port=8080 --restart=Never
Esto nos creará un Pod con la siguiente configuración:
apiVersion: v1
kind: Pod
metadata:
labels:
run: giphyneitor
name: giphyneitor
spec:
containers:
- env:
- name: GIPHY_API_KEY
value: your_api_key
image: friendsofgo/giphyneitor
name: giphyneitor
ports:
- containerPort: 8080
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Never
También podríamos crear nuestro pod, a partir de un fichero, que sería igual que la configuración que veis arriba, para poder obtener dicha configuración y no crear el Pod en ese mismo instante, usaremos los flag --dry-run -o yaml como comentamos anteriormente.
El flav --env lo utilizamos en nuestro caso ya que nuestra aplicación requiere de una variable de entorno llamada GIPHY_API_KEY para almacenar la key de la api de Giphy.
$ kubectl run giphyneitor --image=friendsofgo/giphyneitor --env=GIPHY_API_KEY=your_api_key --port=8080 --restart=Never --dry-run -o yaml
Con eso podríamos guardar el output en un fichero yaml, por ejemplo giphyneitor-pod.yaml y ejecutar el comando:
$ kubectl apply -f giphyneitory-pod.yaml
Ahora que ya tenemos creado nuestro pod:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
giphyneitor 1/1 Running 0 5s
Podríamos acceder a nuestra aplicación, debería ser tan sencillo como obtener la IP del Pod y acceder ¿no?, pero ¿cómo podemos conocer la ip de nuestro pod?
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
giphyneitor 1/1 Running 0 4m34s 172.17.0.4 minikube <none> <none>
Ya lo tenemos todo ¿no?, pues navegamos a http://172.17.0.4:8080
Muchos posiblemente ya sabréis porque ha pasado esto, pero otros puede que tengáis la cara de la mujer del gif, para estos últimos os debo una explicación.
La IP que tiene nuestro Pod es privada y no puede accederse desde fuera del clúster, sólo otros pods podrían verlas.
¿Entonces? yo quiero ver como funciona mi aplicación desde Kubernetes para eso he venido, es lo que estáis pensando ¿no? Que no cunda el pánico.
Si recordáis en docker debemos de realizar un mapeo de puertos para poder conectar nuestro container a un puerto de nuestra máquina ¿verdad? Pues Kubernetes no iba a ser menos:
$ kubectl port-forward giphyneitor 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Realmente podríamos terminar aquí, pero como hemos dicho los Pods son mortales, así que si por algún motivo hubiera que reiniciar cambiar el nodo, borraramos por error el Pod o cualquier otro problema que pudiera surgir, nuestro Pod no volvería a la vida hasta que lo volvieramos a levantar, y si encima no nos hemos guardado su configuración tocaría volver a ejecutar el kubectl run nuevamente.
Seguro que algún listillo, estará pensando, eh he visto que en el comando has puesto el flag, --restart=Never, pon Always y listo ¿no?, pues ¿por qué no lo probamos a ver que sucede?
$ kubectl run giphyneitor --image=friendsofgo/giphyneitor --env=GIPHY_API_KEY=your_api_key --port=8080 --restart=Always --dry-run -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
run: giphyneitor
name: giphyneitor
spec:
replicas: 1
selector:
matchLabels:
run: giphyneitor
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
run: giphyneitor
spec:
containers:
- env:
- name: GIPHY_API_KEY
value: your_api_key
image: friendsofgo/giphyneitor
name: giphyneitor
ports:
- containerPort: 8080
resources: {}
status: {}
¿Os habéis fijado que ha sucedido?
apiVersion: apps/v1
kind: Deployment
Pues si, el comando ha dejado de crearnos un Pod para crearnos un Deployment
Deployment
Como hemos dicho hasta ahora nuestro Pod puede ser creado pero una vez es borrado por el motivo que sea no volverá a levantarse, esto nos complica mucho el mantenimiento, por ello surgen los Deployments.
Antes de entrar en materia con los Deployments creo que sería bueno que entendierais como funcionan los estados en Kubernetes, todos los recursos que ejecutamos en Kubernetes son creados mediante el llamado bucle de reconciliación, esto que puede parecer un palabro muy complicado, no es más que pensar que cuando creamos un recurso, le vamos a decir que esté en un estado determinado, de esta forma Kubernetes puede comprobar el estado anterior, y el estado nuevo que estamos pidiendo y asegurarse de que dicho recurso acabe en el estado deseado.
Por ejemplo en el caso de creación del Pod que vimos antes, el estado previo era que no había Pod y el que estamos enviando al ejecutar el comando kubectl run es que tiene que existir un Pod con las características deseadas.
Así pues nuestro Deployment entre otras cosas que vamos a ver, lo que se encargará es de siempre asegurar que el estado de nuestro Pod sea el deseado, mediante el bucle de reconciliación, de ese modo aunque nuestro Pod sea borrado manualmente, el Deployment lo volverá a crear.
Así pues como hemos visto si queremos crear nuestro deployment, ejecutaremos:
$ kubectl run giphyneitor --image=friendsofgo/giphyneitor --env=GIPHY_API_KEY=your_api_key --port=8080 --restart=Always
Si ahora ejecutamos el comando kubectl get pods veremos la primera diferencia, al utilizar un Deployment.
NAME READY STATUS RESTARTS AGE
giphyneitor-6bb7fdd7d5-qlh7q 1/1 Running 0 5s
Como vemos, el nombre del Pod ya no es simplemente giphyneitor sino que le ha concatenado un hash, esto lo hará porque cada vez que realicemos una modificación en nuestro deployment y lo despleguemos, levantará un nuevo Pod con el estado deseado y eliminará el anterior.
NAME READY STATUS RESTARTS AGE
giphyneitor-6bb7fdd7d5-96cj4 0/1 ContainerCreating 0 2s
giphyneitor-6bb7fdd7d5-qlh7q 0/1 Terminating 0 2m22s
Igual que antes podríamos realizar un port-forward contra nuestro Pod y ver que nuestra aplicación está funcionando correctamente.
ConfigMaps
Hasta ahora hemos estado guardando los datos de configuración dentro de nuestro Pod o Deployment, pero no siempre queremos que dichos datos permanezcan a la misma altura que nuestra aplicación, para ello Kubernetes nos ofrece los ConfigMaps.
Los ConfigMaps nos permiten almacenar valores de configuración como hemos mencionado, mediante clave/valor.
Así pues podríamos usar un ConfigMap en nuestra aplicación para guardar el valor de la key de la api de Giphy.
Para crear un ConfigMap utilizaremos el comando kubectl create, veamos como.
$kubctl create cm giphyneitor-config --from-literal=GIPHY_API_KEY=your_api_key --dry-run -o yaml
Dicho comando nos creara un nuevo ConfigMap llamado gipyneitor-config con la siguiente configuración:
apiVersion: v1
data:
GIPHY_API_KEY: your_api_key
kind: ConfigMap
metadata:
name: giphyneitor-config
En este caso igual que el anterior, podríamos crear nuestro fichero yaml con la configuración del ConfigMap y ejecutar kubectl apply -f {nombre del yaml} y nos crearía dicho ConfigMap.
Ahora nos queda indicar a nuestro Pod o Deployment que comience a leer ese ConfigMap para cargar nuestros valores de configuración.
Para ello podemos coger nuestro yaml anteriormente creado para el Deployment y cambiar la sección de env por lo siguiente:
envFrom:
- configMapRef:
name: giphyneitor-config
Obteniendo un fichero general tal que así:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
run: giphyneitor
name: giphyneitor
spec:
replicas: 1
selector:
matchLabels:
run: giphyneitor
strategy: {}
template:
metadata:
labels:
run: giphyneitor
spec:
containers:
- envFrom:
- configMapRef:
name: giphyneitor-config
image: friendsofgo/giphyneitor
name: giphyneitor
ports:
- containerPort: 8080
resources: {}
Lo mismo haríamos si quisiéramos ejecutarlo sobre un Pod. De esta manera ahora hemos abstraído la parte de configuración de valores de nuestro Deployment.
Conclusión
En este artículo hemos visto como partiendo desde una aplicación Go con su imagen creada en docker hemos podido configurar nuestro propio clúster de Kubernetes en local así como hemos aprendido algunos de sus muchos recursos. Esto solo han sido unas pinceladas para que os vayáis introduciendo a este increíble mundo de Kubernetes.
Si queréis profundizar aún más, además de la documentación oficial os recomendamos muy fuerte, el curso que impartió Jose Armesto aka Fiunchinho en Codely TV.
Recordamos también que todos los ejemplos los tenéis en nuestro github en el repositorio, Play with Kubernetes, y cualquier duda o sugerencia como siempre podéis dejarla en los comentarios o en nuestro Twitter, @FriendsOfGoTech.