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.