lunes, 5 de marzo de 2018

Docker (11) : Recapitulación

1. Introducción


Seguimos las esplicaciones de Eric Smalling.

Vemos los conceptos de Docker:

  • Imagen: Es la base de un contenedor. Representa a una aplicación entera.
  • Container: La unidad estandar donde reside el servicio de la aplicación y se ejecuta
  • Engine: Crea, envía y ejecuta los contenedores ya sea en un servidor físico o virtual o en un datacenter o en un proveedor de servicios cloud.
  • Servicio de registro (Docker Hub o Docker Trusted Registry): Servicio de almacenamiento y distribución de imágenes ( que está localizado en cloud o en un servidor)

2. Imagen

  • Es un sistema lógico de ficheros unificado del contenedor que se ejecuta. Agrupa diferentes primitivas de sistema en ramas "branches" (directorios, sistemas de ficheros, subvolumenes, snapshots)
  • Cada rama "branch" representa una capa "layer" de la imagen.
  • Las imágenes se puede construir y quitar como se quiera de manera monolítica como las máquinas virtuales tradicionales
  • Cuando se crea un contenedor, una capa reescribible (modificable) se añade a la parte superior del sistema de ficheros, que es la que guardará los cambios y trabajos hechos sobre el contenedor


3. Contenedores & Copy on Write

  • Eficiencia: Para segundas instanciaciones de contenedores. Cada copia de contenedor puede ocupar < 1Mb de espacio. Pero también guardan adicionalmente el trabajo que se le ha hecho en cada uno en particular en la capa reescribible pero la imagen es inmutable.
  • Los contenedores parecen ser una copia de la imagen original, pero en realidad es solo un link a la imagen original compartida. Por tanto una imagen de 500 MB que tenga 2 contenedores en marcha, solamente habrán 500 MB de la imagen + unos pocos megas por cada contendor. O sea que no habrá 3 x 500 MB.

4. Persistencia

  • Volume: Es un un directorio que existe fuera del sistema de ficheros del contenedor, y por tanto no se borra cuando destruimos el contenedor. Es un link.
  • el "volume" permite compartir y persistir datos entre contenedores. Es como un NFS.
  • Se puede crear mediante un Dockerfile o via CLI (Command Line Interface)

5. Capas de imágenes:

Veamos el dockerfile que nos proponen:


#https://www.youtube.com/watch?v=LJNKYW6Qls0
#Sample Java WebbApp Dockerfile -unoptimized (330 MB)

#our base OS image (it is the first line taken into account)
FROM ubuntu:16.04

#update yum repos
RUN apt-get update -y

#Install JDK
RUN apt-get install -y openjdk-8-jre-headless curl

#Install Tomcat
RUN curl http://mirror.olnevhost.nev/pub/apache/tomcat/tomcat-8/v8.5.20/bin/apache-tomcat-8.5.20.tar.gz -0
RUN tar xzvf /apache-tomcat-8.5.20.tar.gz

#copy webapp from our local file directory (context)
# some resources cannot be downloaded from internet. Our source code
# should be copied from our machines.
COPY target/*.war /apache-tomcat-8.5.20/webapps/

#cmd tells what do we want top happen when running the container:
# start tomcat
CMD /apache-tomcat-8.5.20/bin/catalina.sh run

Las capas que tenemos (en orden de creación son)
  1. Kernel de docker
  2. Ubuntu Linux 16.04
  3. Update apt catalogs
  4. Install JDK and curl
  5. Download Tomcat
  6. Install tomcat
  7. Copy webapp
  8. Start tomcat

6. "Build" la imagen (construir-compilar):

El comando es:

docker build -f dockerfile -t app:1 . 

Siendo:
  • docker: Instrucción general de los comando de docker
  • buid: Parámetro que indica que queremos contruir una imágen
  • -f dockerfile: Nombre del fichero docker (si se omite se supone que Dockerfile)
  • -t app:1 : Nombre de la imagen y tag opcional (después de":")
  • "." : Path donde "build" el "context" y el Dockerfile

7. Ejecutar la imagen= crear contenedor:

El comando es:

docker run --rm -ti -p 8080:8080 app:1 

Siendo:
  • docker: Instrucción general de los comando de docker
  • run: Parámetro que indica que queremos ejecutar una imágen en un contenedor 
  • --rm: Borra el contenedor si existe previamente.
  • -t: Corre en una tty (consola i/o)
  • -i: Corre en modo interactivo (puede ir junto con la opción "t" 
  • -p 8080:8080: Redirigimos todo el tráfico del host del puerto 8080 al port 8080 del contenedor. Solo para aplicaciones web
  • app:1 : Nombre de la imagen y tag opcional (después de":")


8. Optimizar el tamaño de la imagen

Esta imagen se puede optimizar de varias maneras:
  1. Combinar todos los RUN posibles para crear menos capas (colocando && \ al final de cada sentencia )
  2. Borrar los ficheros de instalación
  3. Utilizar una imagen de SO base más pequeño como Alpine con java instalado (openjdk:8-alpine), PERO, añadiéndole las utilidades que nos van a hacer falta como "curl"
  4. Utilizar una imágen de SO mas pequeña (optimizada) pero que tenga la mayoría de nuestros programas instalados como java, Tomcat, curl (tomcat:8.5-alpine). Al final queda un tamaño de alrededor de 113 MB.
  5. Contrapartida: Las imágenes más pequeñas tienen mas vulnerabilidades.

9. Desplegar la aplicación en Clusters

Tenemos que distinguir entre services, stacks y swarms.

  • Swarm: Un grupo de hosts conectados y corriendo como cluster. Contiene uno o varios managers y también uno o variosworkers. Puede sobrevivir al corte de servicio (luz, red) de un managers (si tiene varios)
  • Service: Es un "wrapper" o envoltorio de una aplicación que suministra una determinada función (tomcat, base de datos, o cada una de las 3 capas de una 3-Tier app etc). Es decir un runtime equivale a un container y es representado por un service. Los services tienen unos metadatos que permite a un swarn su orquestación o planificación. Estos metaddatos pueden ser : requerimientos de memoria, conexiónes a redes lógicas etc.
  • Stack: Si una imagen se definia con un Dockerfile, la stack se define mediante el docker-compose . Se definen los servicioss y como hablan entre si(por ejemplo los front-ends y los back-ends (tomcat, servidores de BBDD, etc) y se definen las CPU o PCI (Payment Card Industry compliance o normas para proteger a los dueños de tarjetas de crédito en una transacción financiera) y como están enlazados y también como se pueden escalar (es decir si queremos asignar mas servicios de esa misma clase -réplicas- a medida que crecemos en arquitectura)

10. Ejemplo de fichero de stack (docker-compose.yml)

Tenemos un despliegue de una aplicación simple J2EE que consta de 2 containers:
  1. Front end: Basado en React
  2. Back end: Basado en Java


version: "3.1"

services:
  movieplex7:
    image: eric-dtr.dckr.org/admin/javaone-movies:1.0
    ports:
      - "8080:8080"
    networks
      - www


  react-client:
    image: eric-dtr.dckr.org/admin/javaone-react:1.0
    ports:
      - "80:3000"
    networks:
      - www

networks:
  www:


Veamols algunas órdenes

# Lista los nodos (managers y workers)
docker node ls 

#Despliega un stack a partir de un fichero de servicios (docker-compose.yml) y lo llama j1
docker stack deploy -c dockercompose.yml j1 

En este comando se ha creado la "network" j1_www y 2 servicios: j1_movieplex7 y j1_react-client

#Listamos los servicios (deben aparecer los 2 arriba indicados)
docker service ls

#Replicamos el segundo servicio 5 veces (escalamos)
docker service scale j1_react-client=5

11. Docker "health check"(Verificar la salud de los servicios)

Se puede definir que si algún servicio va mal, que se saque del pool de servicios. Veamos algunos ejemplos:

HEALTHCHECK CMD curl --fail http://localhost || exit 1
Verifica que se haga un curl a nuestro localhost y sale si no vuelve con un "200".

HEALTHCHECK  --interval=12c --timeout=12s --start-period=30s \
  CMD node /healthcheck.js
Cada 12 segundos y un tiempo de comienzo (warm up) de 30 segundos la primera vez, ejecuta este comando node.js para comprobar que todo va bien.

Si no se verifica el HEALTHCHECK, se para el servicio (se queda en estado muerto). Se puede programar para eliminar del todo este servicio y ejecutar uno nuevo si es el caso.


12. JVM memory. Problemático

Java 9 no controla muy bien las restricciones de memoria o de CPU a las que está sometido un container. Se puede restringir la memoria, CPU, etc a un contenedor.

En java se debe declarar el tamaño de la JVM Heap con los argumentos "-Xmx":

  • Por omisión J2SE 5.0+ usa hasta el 25% de la RAM del Host o 1GB (la menor de las 2)
  • Los limites de memoria del contenedor (realizados via cgroups) son ignorados, ya que el cgroups está previsto para Java 9
  • De todas formas es de buenas prácticas especifica rl cgroups
Utilizar las reservas de CPU y memoria y limites para evitar demasiadas suscriociones en los hosts:
  • --menory
  • --memory-reservation
  • --cpus
  • etc
Si se limita la CPU, asegurarse de actualizar el GC(garbage collector) Thread limiter en la JVM
  • -XX_ParallelGCThreads


13. Logging. Problemático

1. No crear/descargar los logs en las capas de lectura/escritura de los containers.(RW Layers), ya que estas capas son lentas al tener pocos recursos, y además hay que hacer un exec o cp hacia fuera del contenedor para ver el log.

2. Opción n.1: Enviar los logs a stdout que serán visibles con el comando "docker logs" o a traves de la consola web Docker UCP

3. Opción n.2: Enviar los logs al VOLUME. Algunos utilizan un VOLUME NAS/SAN centralizado. Es lo mas utilizado. Es un volumen no dockerizado.

4. Opción n.3: Utilizar los drivers de log que tiene Docker. (none, json-file, syslog,...) Veamos algunos:

  • syslog y splunk: Se utilizan si los datos de log son muy sensibles y se deben transportar a traves de sistemas SSL/TSL.
  • journald: Es el sistema habitual de los administradores de Linux. 
  • awslogs y gcplogs: para entornos cloud en un solo proveedor cloud.
  • gelf y fluent: para application log drivers pensado para almacenar logs en BBDD NoSQL



14. Troubleshooting (Solución de problemas)

1. JVM herramientas de comandos en línea via docker exec:
  • GC Stats: jstat --gcutil 
  • Heap dumos/histograms: jmap 
2. Mostrar (exponer) los puertos JMX de jconsole u otras utilidades

3. Comprobaciones inteligentes de Health Check: Comprobar el puerto 8080 y otros puertos

4. Comprobar herramientas de monitorización de terceras partes que sean "container aware"(esten al tanto de los contenedores), como los usos de licencias en nuevos servidores

5. Comandos específicos de docker como docker stats o ctop.

15. Configuraciones multi-entorno

Se pueden tener distintas configuraciones entre entornos como producciópn y desarrollo etc:

1. Los artefactos Build son imagenes docker, no ficheros war o similares

2. Construir imágenes en CI (integración contínua), guardar en un registry, y desplegar en cualquier parte

3.Los patrones a tener en cuenta para tratar configuraciones diferentes son:

  • Separar los ficheros YML para diferentes stacks (entornos)
  • Secretos (credenciales) de docker (no es buena prácitca)
  • Configuracion de aplicaciones a traves de montaje de VOLUMES
  • Herramientas de configuración de terceras partes como Consul o Vault


Los secretos de docker están encriotados en el swarm. Están expuestos solo alos los nodos que ejecutan los servicios que necesitan. Los secretos sulen estar en contenedores RAM, de manera que se desvanecen cuando el contenedor desaparece. La información entre nodos es via TSL. Pueden haber diferentes valores de secretos según el entorno usando "tags". Desde la UCP se pueden gestionar que secretos y donde estan disponibles.





No hay comentarios :

Publicar un comentario