---
# System prepended metadata

title: Docker. Práctica de laboratorio
tags: [docker]

---

###### tags: `docker`
# Docker. Práctica de laboratorio

---
[TOC]

---

## Instalación de docker

[Instrucciones en la página oficial de Docker](https://docs.docker.com/install/linux/docker-ce/debian/#install-docker-engine---community-1)

Resumen para una máquina Debian de Google Compute Engine

```bash
$ sudo apt-get update
$ sudo apt-get install \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg2 \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/debian \
   $(lsb_release -cs) \
   stable"    
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io 
```

Ahora configuraremos nuestro usuario para que tenga acceso a las herramientas que hemos instalado sin tener que ser `root` haciendo:

```bash
$ sudo adduser <nombredeusuario> docker
$ exit
````

Al hacer `exit` efectivamente saldremos de la sesión, pero es necesario para que al volver a conectar se recargue la pertenencia al grupo `docker` que activo la línea anterior.

## Prueba de la instalación

Para probar la instalación podemos hacer:

```bash
docker run hello-world
```

Es interesante observar que `docker` intenta localizar de forma local la imagen `hello-world`. Como no lo la encontrará la descargará de `Dockerhub`. Tras descargarla creará un contenedor, al que asignará un hash sha512 como nombre, y que ejecutará dicha imagen. El resultado es que mostrará un mensaje informativo y finalizará la ejecución.

Para ver los contenedores que hay ejecutándose (*running*) se utiliza `docker container ls`. Para ver todos los contenedores, se utiliza `docker container ls -a`. Para parar un contenedor se utiliza `docker container stop` poniendo como parámetro el id o nombre del contenedor a parar. Después, se puede eliminar con `docker container rm` poniendo como parámetro el id o nombre del contenedor a eliminar.


## Operaciones con imágenes

Podemos solicitar un listado de las imágenes que hemos descargado de forma local haciendo:

```bash
docker image ls
```

Se puede borrar una imagen haciendo:
```bash
docker image rm <image_hash>
```
Para poder borrar una imagen, no debe aparecer en el listado de contenedores: `docker container ls -a`


## Contenedores interactivos

Podemos arrancar un contenedor y asociarle un terminal, de forma que podamos interaccionar con ellos a través de este dispositivo. Por ejemplo, podemos crear un contenedor a partir de una imagen de python y ejecutar la bash haciendo:

```bash
docker run -it python bash
```

El flag `-i` es para interactive (mantener abierto STDIN)
El flag `-t` es que hay que crear y conectar un terminal

La principal utilidad de esto es poder acceder al contenedor para inspeccionar el contenido del sistema de archivos y hacer pruebas cuando diagnosticamos un problema.

## Construcción de nuestras imágenes

Vamos a crear una imagen propia con un `Hola mundo` en Python. Para ello vamos a crear un archivo `Dockerfile` como el siguiente:

```dockerfile=
# Use an official Python runtime as a parent image
FROM python:latest

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY app.py /app

# Run app.py when the container launches
CMD ["python", "app.py"]
```

Para crear una imagen siempre partimos de otra que usaremos como base. Lo especificamos con la directiva `FROM` de la línea 2. En este caso usaremos la imagen oficial de Python disponible en Dockerhub. Las siguientes directivas fijan el directorio de trabajo `WORKDIR`, copian el archivo `app.py` que contiene nuestra aplicación y por último indica, con `CMD` que cuando el contenedor ejecute este imagen debe ejecutar `python app.py`.

El contenido de `app.py` podría ser el siguiente:

```python
ogarcia@operaciones:~/docker$ cat app.py

#  Programa: app.py
# Propósito: Ejemplo Hola mundo
#     Autor: Óscar García
#     Fecha: 29/11/2019

if __name__ == "__main__":
    print("Hola mundo desde python!")
```

Construimos la nueva imagen haciendo `docker build . -t holapython`. De esta forma se crea una nueva imagen etiquetada como `holapython` que podemos ejecutar haciendo `docker run -it holapython`

## Compartir una imagen docker

Una vez nuestra imagen está lista para ser utilizada, podemos ponerla a disposición de toda la comunidad creando una cuenta en Dockerhub y publicando en uno de sus repositorios nuestra imagen.

1) Crear una cuenta en dockerhub.com y creamos un repositorio que llamaremos `holapython`
2) Hacer `docker login` con nuestro usuario y contraseña
3) Etiquetar nuestra imagen con el nombre de nuestro usario y el repositorio que hayamos creado. Como mi nombre de usuario en Dockerhub es `opobla`, etiquetaría mi imagen así `docker tag holapython:latest opobla/holapython:latest`
4) Enviar la imagen a dockerhub haciendo `docker push opobla/holapython:latest`


Hecho esto cada uno puede descargar la imagen del compañero y ejecutarla.

:::::info 
Pregunta a tu compañero cuál es el nombre de la imagen que ha publicado y prueba a ejecutarla desde tu máquina. Como docker no la encontrará en tu máquina local, procederá a descargarla de dockerhub.
:::::

## Montamos un servidor web con docker

Para nuestro servidor web utilizaremos la imagen oficial del servidor web `nginx`. Lo podemos ejecutar directamente haciendo:

```dockerfile
docker run -d -p 80:80 nginx  
```

En este caso hay que tener en cuenta que como buen servidor web, `nginx` abre en su pila TCP el puerto 80 a la espera de conexiones. Necesitamos especificar con el flag `-p` que el puerto 80 de la máquina anfitriona (nuestra máquina de Google Compute Engine) debe conectarse al puerto 80 del contenedor. Por su parte, el flag `-d` es opcional y sirve para que el contenedor se ejecute en segundo plano. 

:::warning
En ocasiones es interesante no ejecutar el contenedor con e flag `-d` para poder observar el log de sucesos, sobre todo cuando estamos diagnosticando un error
:::

Hay que verificar que tenemos abierto el cortafuegos para el puerto 80.

Exploramos configuración de nginx desde dentro para ver que el contenido se carga desde `/usr/share/nginx/html/index.html`.

Cambiamos el contenido montando un directorio de la máquina anfitriona




## Persistencia de datos

Por defecto los archivos que se crean dentro de un contenedor se crean en la capa de lectura de dicho contenedor. Esto tiene las siguientes implicaciones:
+ La información no persiste más allá del ciclo de vida del contenedor
+ Es muy difícil extraer o compartir información de la capa de lectura (ya que es privada al contenedor)
+ El rendimiento del storage driver que gestiona este tipo de operaciones no es alto

Mecanismos para persistir datos de forma eficiente:
- Utilizando volúmenes.
- Utilizando puntos de montaje.

### Volúmenes

- Los volúmenes son elementos de almacenamiento gestionados por el propio entorno de Docker.
- Se crean de forma explícita haciendo `docker volume create` o durante el proceso de creación de un contenedor.
- Se listan con `docker volume ls`
- Se pueden borrar todos los volúmenes que no están siendo utilizados por algún contenedor haciendo `docker volume prune`
- Los volumenes pueden ser montados por más de un contenedor a la vez.

### Puntos de montaje (bind mount)

- En este caso se monta dentro del contenedor una carpeta específica de la máquina anfitrión. 

Como hemos visto anteriormente, el servidor web sirve los contenidos desde el sistema de archivo del contenedor, concretamente desde `/usr/share/nginx/html`, y más concretamente el archivo `index.html` de esa ruta. Vamos a proyectar sobre esa ruta del contenedor una carpeta de la máquina anfitriona, utilizando para ello un punto de montaje. Así, si manipulamos el archivo en la máquina anfiriona los cambios se reflejarán dentro del contenedor.

Comenzamos creando una carpeta en la máquina anfitriona haciendo `mkdir web`. Ahora lanzaremos el docker indicando que se debe montar esa carpeta `web` en la ruta interna desde donde nginx servirá los contenidos:

```bash
docker run -d -p80:80 -v $(pwd)/web:/usr/share/nginx/html nginx
```

Ahora solo nos queda crear un archivo `index.html`, dentro de la carpeta `web`, con el contenido de nuestra página web, por ejemplo:

```htmlembedded=
<html>
    <head>
        <title>Mi web dentro de un contenedor</title>
    </head>
    <body>
        <h1>
            Hola mundo web docker!
        </h1>
    </body>
</html>
```

Si recargamos el navegador web ahora veremos los cambios.

