Después de crear un cluster de un solo nodo, en esta entrada explico los pasos para publicar una aplicación en el clúster.
Objetivo: publicar una aplicación
El objetivo de esta entrada es publicar una aplicación en el clúster accesible a través de la IP del host. Es decir, a todos los efectos, el usuario accede a la aplicación sin ningún conocimiento de que se encuentra en el clúster, corriendo sobre uno o varios contenedores.
Usa ficheros YAML
Los dos objetos necesarios para publicar la aplicación, el Deployment y el Service, pueden crearse directamente o usando un fichero YAML.
El tutorial de Kubernetes usa el método online, creando los objetos directamente en la línea de comando. Sin embargo, creo que este método no debería usarse nunca en producción (porque pueden pasar cosas muy malas), por lo que vamos a usar los ficheros de configuración YAML para crear y actualizar los objetos en el clúster.
Ejemplo de proceso de aprobación
Idealmente, cualquier cambio en el estado de la aplicación en el clúster debería seguir un flujo similar al de cualquier otra modificación que afecte a un sistema de producción; es decir, el DevOp clona el repositorio con la configuración de la aplicación en su equipo, hace los cambios y los verifica. Una vez satisfecho, los sube al repositorio (pasando por el sistema de aprobación establecido, como por ejemplo, una pull request). Una vez aprobado, se incorpora el cambio al repositorio de configuración de la aplicación en producción, desde donde se aplica al entorno de producción.
Define el deployment
Como vimos en la entrada de revisión de conceptos, el primer paso para desplegar una aplicación en un clúster Kubernetes es crear un deployment.
El fichero de declaración del deployment tiene tres bloques (la estructura es común al resto de objetos):
- Cabecera
- Metadatos
- Especificaciones
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:stable-alpine
ports:
- containerPort: 80
Cabecera del deployment
apiVersion: apps/v1beta1
kind: Deployment
En primer lugar, la versión de API que usamos para definir el deployment.
En kind
, especificamos el tipo de objeto que vamos a definir.
Metadatos del deployment
metadata:
name: nginx
Datos que describen el deployment; el único necesario es el nombre del deployment.
Especificaciones del deployment
spec:
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:stable-alpine
ports:
- containerPort: 80
En el apartado de especificaciones se declaran el número de réplicas de la aplicación que queremos en ejecución. Kubernetes se encarga de mantener este número de réplicas en ejecución en todo momento, respondiendo automáticamente en caso de fallo de alguno de los pods o de los nodos del clúster.
Para poder crear nuevos pods si es necesario, debemos definir una template
(plantilla) a partir de la cual crearlos.
Como, al fin y al cabo estamos definiendo un objeto de tipo pod, tenemos de nuevo una sección de metadatos (en este caso, etiquetas para los pods del deployment) y las especificaciones para crear un pod: el nombre, la imagen base para el contenedor del pod y el puerto publicado por el contenedor.
Pueden definirse muchos otros parámetros, pero estos son los mínimos indispensables para tener un deployment funcional (el único requerido es el name
).
La imagen se descarga por defecto desde DockerHub; si quieres usar un registry alternativo, debes indicar la URL completa al recurso.
Crea el deployment
En el cluster de un solo nodo de pruebas, he subido el fichero nginx-deployment.yaml
con la definición del apartado anterior.
Para crear el deployment usamos:
$ kubectl create -f nginx-deployment.yaml
deployment "nginx" created
Otra opción es usar la opción apply
, que actualiza el deployment si ya existe.
Una vez creado, lo examinamos en detalle con describe
:
$ kubectl describe deployment nginx
Name: nginx
Namespace: default
CreationTimestamp: Sun, 16 Jul 2017 11:06:48 +0200
Labels: app=nginx
Annotations: deployment.kubernetes.io/revision=1
Selector: app=nginx
Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app=nginx
Containers:
nginx:
Image: nginx:stable-alpine
Port: 80/TCP
Environment: <none>
Mounts: <none>
Volumes: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-3287103792 (1/1 replicas created)
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
4m 4m 1 deployment-controller Normal ScalingReplicaSet Scaled up replica set nginx-3287103792 to 1
El pod creado por Kubernetes tiene una IP asignada y está escuchando en el puerto 80, pero de momento sólo es accesible desde dentro del clúster.
Puedes comprobarlo obteniendo el nombre del pod y usando describe
para obtener su IP. A continuación, mediante curl
obtén la página de bienvenida de Nginx:
$ curl http://10.32.0.4:80
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
Si intentas algo parecido desde fuera del clúster, no tendrás respuesta.
Modifica el número de réplicas
A modo de experimento, puedes modificar el número de réplicas directamente desde la línea de comandos usando:
kubectl scale deployment nginx --replicas 3
Y revisando el resultado del comando anterior:
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
nginx-3225377387-l0hwp 1/1 Running 0 3m
nginx-3225377387-ptkdt 1/1 Running 0 3m
nginx-3225377387-rrz1x 1/1 Running 0 11m
Lo correcto, de todas maneras, habría sido modificar el fichero nginx-deployment.yaml
y aplicar la nueva configuración mediante kubectl apply -f nginx-deployment.yaml
Publica la aplicación
La aplicación está disponible en el clúster, pero sólo es accesible desde dentro del clúster. De este modo no es especialmente útil (teniendo en cuenta que se trata de un servidor web).
El siguiente paso es definir un Service.
Un service define un conjunto de pods y una política de acceso.
Esto quiere decir que el servicio agrupa pods independientes en un conjunto que “trabaja en equipo” para hacer “algo” (que es lo que se llama microservice, en terminología DevOp).
En cuanto a la política de acceso, es una forma de indicar que -en la mayoría de casos- se asigna un puerto público para que se pueda acceder a la aplicación desde fuera del clúster.
Define el service
El service tiene los mismos tres bloques que el deployment:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
type: LoadBalancer
Cabecera del service
apiVersion: v1
kind: Service
En la cabecera tenemos la versión de la API. Los services existen desde la primera versión del API de Kubernetes, a diferencia de los deployments.
Recuerda de la entrada de revisión de conceptos que antes de los deployments se usaban directamente los Replication Controllers, que después de mejoraron y se convirtieron en ReplicaSets.
En cuanto al tipo de objeto, en este caso definimos un Service
.
Metadatos del service
metadata:
name: nginx
labels:
app: nginx
Tampoco hay sorpresas en cuanto a los metadatos del servicio; en este caso, además del nombre, he añadido la etiqueta app: nginx
.
Especificaciones del service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
type: LoadBalancer
Como hemos visto en la definición de servicio, éste sirve para agrupar pods. El conjunto de pods se define mediante un selector, que en este caso, se trata de la etiqueta app: nginx
.
Usar etiquetas permite organizar los pods con total flexibilidad. Por ejemplo, podrías tener diferentes frontales (Nginx para contenido estático y Apache para el resto, por ejemplo) y agruparlos todos bajo una etiqueta llamada tier: front-end
, por ejemplo, aunque cada tipo tenga, además, etiquetas específicas (app: nginx
y app: apache
).
La elección de las etiquetas es un asunto muy importante que debes tener lo mejor organizado posible para tus aplicaciones.
A continuación, en la definición del servicio, tenemos la parte de política de acceso: una sección de ports
en la que se define el protocolo y puerto (o puertos) y el tipo de “conexión”.
Finalmente, mediante type
, define el tipo de acceso desde el exterior. En el ejemplo he elegido el tipo LoadBalancer
.
Este tipo de acceso creo que no está disponible en Minikube, por lo que si usas este entorno para las pruebas, quizás debas usar
type: NodePort
.
En resumen, el servicio expone el conjunto de pods que verifican la condición expuesta en el selector
y conecta los puertos locales -de los contenedores- con puertos externos a nivel de clúster de Kubernetes.
Crea el service
El método para crear/actualizar el service es el mismo que para el deployment:
kubectl apply -f nginx-service.yaml
Una vez aplicado el service, usa describe
para inspeccionar en detalle el objeto:
$ kubectl describe service nginx
Name: nginx
Namespace: default
Labels: app=nginx
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx","namespace":"default"},"spec":{"ports":[{"port...
Selector: app=nginx
Type: LoadBalancer
IP: 10.107.44.10
Port: <unset> 80/TCP
NodePort: <unset> 31010/TCP
Endpoints: 10.32.0.3:80
Session Affinity: None
Events: <none>
Para saber con qué puerto externo se ha conectado el puerto local 80/TCP necesitamos revisar el detalle del servicio creado. Como no hemos especificado ningún targetPort
Kubernetes asigna uno libre al azar.
El comportamiento es el mismo que cuando se lanza un contenedor mediante
docker run -P
En mi caso el NodePort
asignado es el 31010/TCP. Esto significa que si acceso a la IP del clúster a través del puerto 31010, Kubernetes redirigirá la petición al puerto 80 del servicio, con lo que obtendré la página de bienvenida de Nginx.
Este es el resultado marcado como éxito para este experimento.
Resumen
En este artículo he explicado los pasos necesarios para publicar una aplicación en un clúster de Kubernetes:
- Crear un Deployment
- Crear un Service
El deployment crea los diferentes pods que componen la aplicación. El service los agrupa de manera funcional y los expone para que sean accesibles desde el exterior del clúster.