lunes, 30 de julio de 2018

Laboratorio - Integración Contínua (IV)

Cuarto paso en la creación de nuestro laboratorio CI. Recuerdo los pasos:

  1. Instalación de Ubuntu en VMWare Player en Windows 7
  2. Instalación de Jenkins en Ubuntu
  3. Instalación GitLab en Ubuntu
  4. Instalación de Docker y Minikube en Ubuntu
  5. Desarrollo de un microservicio en Eclipse Windows (1) y (2)
    1. Angular 6 frontend
    2. SpringBoot 2.x backend
    3. Mongo Replicaset (3 replicas)
  6. Despliegue CI en Minikube utilizando la infraestructura configurada
    1. Pipeline - Jenkinsfile
    2. GitLab webhook
    3. Dockerfile
    4. Deploy y Service yaml
    5. Secret
    6. Ingress

Vamos pues con el cuarto paso, la instalación de Docker en nuestro Ubuntu. Por extensión dejamos la instalación de Minikube para la siguiente entrada.



Instalación Docker
Como siempre, en la medida de lo posible seguimos la documentación oficial, en este caso la de DockerAccedemos a Ubuntu y abrimos un terminal. 

Lo haremos a través del gestor de paquetes "apt". Para ello, primero configuramos el repositorio de donde descargaremos los paquetes necesarios, y a continuación realizamos la instalación propiamente dicha.


Preparación
Update previo
    sudo apt-get update

Instalación de paquetes para acceso al repo vía ssl
    sudo apt-get install \
        apt-transport-https \
        ca-certificates \
        curl \
        software-properties-common

Añadimos la clave GPG (GNU Privacy Guard - Software de encriptación) oficial de Docker para acceso vía SSL
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

Validamos
    sudo apt-key fingerprint 0EBFCD88

    nos deberá aparecer una salida como la siguiente

        pub   rsa4096 2017-02-22 [SCEA]
                 9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
        uid           [desconocida] Docker Release (CE deb) <docker@docker.com>
        sub   rsa4096 2017-02-22 [S]

Añadimos el repositorio estable de docker
    sudo add-apt-repository \
       "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
       $(lsb_release -cs) \
       stable"

Ahora ya podemos proceder a la instalación propiamente dicha.


Instalación
Actualizamos
    sudo apt-get update

Instalamos la última versión de Docker
    sudo apt-get install docker-ce

Verificamos. Primero lanzamos algunos comandos de testeo
    sudo docker images

    La salida debe ser vacía (sólo la cabecera)

Ejecutamos una imagen de prueba de la instalación con
   sudo docker run hello-world

Al no existir dicha imagen local, lo que hará Docker es descargarla del "Docker Hub" (también "Docker Store"). Es el repositprio público de imágenes. Para los conocedores de java el docker hub viene a ser como el "maven central" para librerías.


Una vez descargada la imagen, se lanza (run) dicha imagen, generando un contenedor (imagen en ejecución), dejándonos un log "Hello from Docker".

Nos dice también cómo podemos ejecutar y entrar en un contenedor ubuntu (docker run -it ubuntu bash), etc...

En nuestro caso, la imagen descargada es sólo de prueba y únicamente contiene el log que aparece en la captura anterior. El contenedor que genera dicho log se para de forma inmediata, por lo que no podemos acceder a su interior. Podemos comprobarlo con 
    sudo docker ps -a  (nos muestra todos los contenedores, tanto los activos como los inactivos)

    veremos en la columna "STATUS" algo como "Exited (0) X minutes ago"
    si ejecutamos "sudo docker ps" (sólo muestra los contenedores activos) veremos una salida vacía.

Si ejecutamos de nuevo "sudo docker images" veremos la imagen descargada.

Las imágenes se puede "tagear" de forma que su nombre sea 
    NOMBRE:TAG

Si no se especifica TAG se sobreentiende que es el tag "latest". Por ello, la imagen descargada de ejemplo si hacemos "sudo docker images" vemos el tag "latest"


Para eliminar un contenedor activo primero hemos de pararlo
    sudo docker stop ID / NAME  (ID de imagen o nombre)

y a continuación lo eliminamos
    sudo docker rm ID / NAME

Para ver la configuración de una imagen
    sudo docker inspect ID / NAME 

Para eliminar una imagen (no ha de haber ningún contenedor activo o inactivo que la referencie)
    sudo docker rmi ID / NAME

Si miramos a nivel de procesos veremos que todo funciona al estar activo un daemon, el "dockerd"
    ps -ef | grep docker




Post-instalación
Como veis, continuamente hemos de ejecutar los comandos docker con sudoLa causa de tener que usarlo continuamente es que dicho daemon está asociado a un socket Unix, propiedad del usuario root (ver captura anterior).

Si ejectuamos cualquier comando docker sin sudo aparecerá el fatídico error
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.38/containers/json: dial unix /var/run/docker.sock: connect: permission denied

Al final se hace muy farragoso anteponer siempre sudo, por lo que, para evitarlo, seguiremos el proceso de post-instalación indicado en la página oficial de docker.

Validamos que exista el grupo docker mediante
    grep docker /etc/group docker

    En mi instalación aparece como 
    docker:x:994:

Entonces ejecutamos 
    sudo usermod -aG docker $USER
    
    ó

    sudo usermod -aG docker $(whoami)
    
    lo cual añade al usuario conectado al grupo docker*. Lo podemos verificar con el comando "id $USER" (o "id $whoami". Veremos que aparece dentro de "grupos=" el grupo docker, en mi caso "....,994(docker)"

Refrescamos la configuración de grupos
    newgrp docker

Si ejecutamos ahora 
    docker images ó docker ps 

    veremos la misma salida que cuando utilizábamos "sudo".

Si necesitamos parar, reinciar, etc... el daemon lo haremos como con cualquier otro servicio
    sudo service docker status/start/stop/restart

o

    sudo systemctl status/start/stop/restart docker



docker-compose
Vamos a instalar docker-compose ya que es posible que lo necesitemos para instalar de forma conjunta imágenes que trabajan de forma "solidaria" como puede ser ELK, o bien imágenes custom como una app springboot que necesite de postgres o mongo como backend. Etc...

Es decir, para nuestro laboratorio propiamente dicho no necesitaremos ejecutar docker-compose, pero si que es posible que nuestras aplicaciones requieran de otros contenedores activos, levantados mediante docker-compose.

En esta caso no utilizaremos "apt" sino que lo descargaremos a una ruta determinada
    sudo curl -L https://github.com/docker/compose/releases/download/1.22.0/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose

lo cual instalará como un comando más en la ruta "/usr/local/bin". Se instala sin permiso de ejecución, por lo que se los damos:
    sudo chmod +x /usr/local/bin/docker-compose

Si ejecutamos ahora "docker-compose --version" veremos la versión instalada, que en estos momentos es
    docker-compose version 1.22.0, build f46880fe




Prueba completa

Veamos de forma más elaborada como funciona el mundo Docker de imágenes y contenedores. Vamos a descargar, ejecutar y modificar una imagen de nginx (servidor web).

Lo primero que hemos de hacer es acceder al docker hub para ver qué imágenes nginx hay disponibles. A ser posible usaremos la oficial. Accedemos a "https://store.docker.com" y usamos el buscador introduciendo "nginx". De las dos que aparecen seleccionamos la oficial


En la página de detalle se nos indican diferentes formas de arrancar nuestra imagen. Haremos dos pruebas, la primera con la configuración "por defecto" y otra un tanto más elaborada (con docker-compose y Dockerfile), modificando la página por defecto.


Con "docker run"
Lanzamos 
    docker run --name mynginx -p 90:80 -d nginx

Le damos nombre (--name) ya que luego es más sencillo referenciarla sin necesidad de conocer su ID.
El atributo "-d" indica "dettached", de forma que se libere la consola tras su lanzamiento.
El atributo "-p 90:80" indica que mapeamos el port interno 80 (el de arranque de nginx) del contenedor con el puerto 90 local del ubuntu.

Tras unos segundos de descarga (es la primera vez y por tanto ha de descargar la imagen) ya la tenemos activa. Lo revisamos con "docker ps". Ahí tenemos nuestro contenedor activo:



Accedemos vía web http://localhost:90


Podemos ver la imagen descargada con "docker images", o inspeccionarla con "docker inspect nginx"

Podemos acceder al interior del contendor para echar un vistazo. Está desaconsejado hacer modificaciones sobre un contenedor "vivo". Todo lo que se quiera hacer ha de ser vía Dockerfile, ya que lo que se haga en vivo se perderá. Un contenedor es algo de usar y tirar. No tiene vida más allá de su ejecución.

Lo que si es conveniente es, si no está documentado, acceder al contenedor, ver rutas necesarias, etc... para luego generar nuestro Dockerfile y que sea a través de él que se haga cualquier tipo de customización. Esto es lo que vamos a hacer, y la información que saquemos la utilizaremos para la prueba siguiente.

Accedemos con 
    docker exec -it mynginx bash  (abrimos sesión interactiva "-i" con un pseudo tty "-t" y con interprete "bash")

Nos deberá aparecer el prompt del contenedor
    root@213dc1f94923:/#

La mayoría de comandos no están instalados, por ejemplo si lanzamos ifconfig, ps, curl, .... veremos que nos aparece un "bash: xxx: comand not found". Si para nuestra inspección necesitamos instalar algún comando lo podremos hacer con "apt" o el gestor de paquetes del sistema (yum, etc...).

Podemos revisar que tipo de linux de nuestro contenedor con "cat /etc/issue". En nuestro caso, la imagen nginx devuelve
    Debian GNU/Linux 9 \n \l

Es decir, la imagen nginx se generó en su origin sobre un Debian 9 mínimo.

Pero vamos a lo que nos interesa. Según documentación, el document root de nginx se encuentra en "/usr/share/nginx/html". Vemos que efectivamente, allí encontramos el "index.html".

Probemos a añadir una página nueva con
    echo "Otro index" > /usr/share/nginx/html/otroindex.html

Accedemos a "http://localhost:90/otroindex.html"


O modificamos el index.html

    echo "mi index" > /usr/share/nginx/html/index.html

y ahí lo tenemos:


Con esto es suficiente, salimos con "exit" y a continuación paramos el contenedor con "docker stop mynginx" y lo eliminamos con "docker rm mynginx"

Que quede claro, si ahora lanzamos de nuevo la imagen con "docker run......" todo esto habrá desaparecido y volveremos a tener el index.html por defecto.



Con "docker-compose"
Vale, con la información extraída de la primera prueba vamos a generar un Dockerfile que sobreescriba el index por defecto y añada un segundo index.

Creamos un directorio de trabajo. 
    vamos a la home del usuario
    cd

    creamos el directorio "Desarrollo" y accedemos a él
    mkdir Desarrollo && cd $_

    creamos el directorio "docker" y accedemos a él
    mkdir docker && cd $_

    creamos un directorio que contendrá los html's, no accedemos al directorio creado
    mkdir custom_html

Creamos el index nuevo
    echo "mi index custom" > custom_html/index.html

Creamos un segundo index
    echo "mi segundo index custom" > custom_html/otroindex.html

Creamos nuestro Dockerfile con "vi Dockerfile", el cual contendrá la siguiente información:
    FROM nginx
    MAINTAINER Gines Collado - https://java-ms-cloud.blogspot.com 
    RUN mv /usr/share/nginx/html/index.html /usr/share/nginx/html/index_back.html
    COPY custom_html /usr/share/nginx/html

    FROM: la imagen fuente
    MAINTAINER: información sobre el autor del Dockerfile
    RUN: ejecución de un comando
    COPY: copia de ficheros

La configuración del docker-compose.yml

    version: '3'
    services:
      nginx:
        build: .
        container_name: mynginx
        ports:
          - "90:80" 

Con build indicamos dónde ha de buscar el Dockerfile, en este caso lo tenemos en la misma ruta que el docker-compose.yml. Damos nombre al contenedor y declaramos el mapeo de puertos, 90 local de ubuntu, 80 interior al contenedor.

Esta es la estructura de directorios y ficheros:






Lanzamos desde el directorio donde se encuentra el docker-compose.yml 
    docker-compose up -d --build

  nos devuelve algo como lo siguiente:

    es decir, se descarga la imagen base, ejecuta los pasos del Dockerfile y nos indica que h finalizado con
"Successfully tagged nginx_nginx:latest". Luego veremos qué significa (ver Extras).

Verificamos el index custom:





Y el segundo index custom


Verificamos, sin acceder al contenedor, los ficheros custom que están en la ruta correcta y que se ha generado un backup del index anterior.

    docker exec mynginx ls -lrt /usr/share/nginx/html

    userblog@ubuntu:~/Desarrollo/docker/nginx$ docker exec mynginx ls -lrt /usr/share/nginx/html
    total 16
    -rw-r--r-- 1 root root 612 Jul 24 13:02 index_back.html
    -rw-r--r-- 1 root root 537 Jul 24 13:02 50x.html
    -rw-r--r-- 1 root root  16 Jul 30 09:06 index.html
    -rw-r--r-- 1 root root  24 Jul 30 09:06 otroindex.html


Finalmente paramos el contenedor nginx. Podemos hacerlo según lo descrito más arriba (docker stop...) o bien, ya que lo hemos lanzado con docker-compose, con
    docker-compose down

Siempre desde el mismo directorio de trabajo que hemos generado para las pruebas. Con este último comando se para y elimina el contenedor arrancado.



Extras
Tras la última prueba vayamos a ver las imágenes. Deberíamos tener sólo una de nginx, la que hemos descargado y que nos ha servido de base para la introducción de nuestros index custom.

Sorpresa, hay dos imágenes nginx
    userblog@ubuntu:~/Desarrollo/docker/nginx$ docker images
    REPOSITORY          TAG               IMAGE ID                CREATED                SIZE
    nginx_nginx              latest              497203bdc980        About an hour ago   109MB
    nginx                         latest              c82521676580        5 days ago               109MB

¿Qué ha pasado? Bien, al modificar la imagen (MV y COPY del Dockerfile), se ha generado una nueva, "nginx_nginx" (revisar la salida de la ejecución en el apartado anterior). Esta nueva imagen contiene la imagen base y capas adicionales, con las modificaciones.

Docker procede por capas, de forma que no repite capas de las imágenes base, sino que las "reutiliza". 

Veamos las capas o layers de cada imagen. Si es cierto lo que decimos, deberíamos ver dentro de las capas de la nueva imagen "nginx_nginx" todas las de la imagen base "nginx" y alguna más con la parte custom.

Lanzamos
    docker image inspect nginx -f '{{.RootFS.Layers}}'

Obtenemos una salida como la siguiente:
[sha256:cdb3f9544e4c61d45da1ea44f7d92386639a052c620d1550376f22f5b46981af sha256:a8c4aeeaa0451a16218376ce6ec0e55094128baeb0dbe122f1b25c3fa81a5a5b sha256:08d25fa0442e3ea585b87bc6e9d41a1aa51624c83aec7fbafc1636f22eecf36f]


Y ahora lanzamos lo mismo pero sobre la nueva imagen
    docker image inspect nginx_nginx -f '{{.RootFS.Layers}}'

Obtenemos esta otra salida:
[sha256:cdb3f9544e4c61d45da1ea44f7d92386639a052c620d1550376f22f5b46981af sha256:a8c4aeeaa0451a16218376ce6ec0e55094128baeb0dbe122f1b25c3fa81a5a5b sha256:08d25fa0442e3ea585b87bc6e9d41a1aa51624c83aec7fbafc1636f22eecf36f sha256:310e76377bb19a2ca0d22e0d5fc9af094065024870339ec152e085a5c96f41e4 sha256:e333c6d0c4a212ab2ec89e9c6544723aab4d727f9b081d4317378304f334a901]

Efectivamente, en verde vemos las capas "repetidas", es decir, las capas de la imagen base que persisten en la nueva. Además, vemos dos nuevas capas en la nueva. 

Si queremos tener una imagen visual de las diferencias podemos lanzar

    docker history nginx
y
    docker history nginx_nginx

O bien compararlas con

    diff <(docker history nginx) <(docker history nginx_nginx)

Lo cual arroja la siguiente salida:
  userblog@ubuntu:~/Desarrollo/docker/nginx$ diff <(docker history nginx) <(docker history nginx_nginx)
  1a2,4
  > 4e826b21879e     15 minutes ago    /bin/sh -c #(nop) COPY dir:2d04cfb7dded61311…    40B
  > 69545e040185     15 minutes ago    /bin/sh -c mv /usr/share/nginx/html/index.ht…           612B
  > 2189bca3821a     15 minutes ago    /bin/sh -c #(nop)  MAINTAINER Gines Collado …     0B

donde se muestra precisamente el contenido de nuestro Dockerfile, que ha servido para crear la imagen nueva a partir de la base. Se observa el tamaño de cada uno de los diferentes elementos "nuevos".



Final
De momento esto es todo. En la siguiente entrada instalaremos Minikube, cerrando la creación del entorno, quedando pendiente ya sólo su uso para el despliegue de aplicaciones.




Anterior


No hay comentarios:

Publicar un comentario