Compartir a través de


Tutorial: Conservación de datos en una aplicación contenedora mediante volúmenes en VS Code

En este tutorial, aprenderá a conservar los datos en una aplicación contenedora. Al ejecutarlo o actualizarlo, los datos siguen estando disponibles. Hay dos tipos principales de volúmenes que se usan para conservar los datos. Este tutorial se centra en los volúmenes con nombre.

También obtendrá información sobre los montajes de enlace, que controlan el punto de montaje exacto en el host. Puede usar montajes de enlace para conservar datos, pero también para agregar más datos a los contenedores. Cuando se trabaja en una aplicación, se puede usar un montaje de enlace para montar el código fuente en el contenedor y ver los cambios de código, responder y poder ver los cambios inmediatamente.

En este tutorial también se presentan las capas de imagen, el almacenamiento en caché de capas y las compilaciones de varias fases.

En este tutorial, aprenderá a:

  • Comprender los datos entre contenedores.
  • Conservar datos mediante volúmenes con nombre
  • Usar montajes de enlace.
  • Ver capa de imagen.
  • Copiar en caché las dependencias.
  • Comprender las compilaciones de varias fases.

Prerrequisitos

Este tutorial continúa con el tutorial anterior, Creación y uso compartido de una aplicación de contenedor con Visual Studio Code. Comience con ese, que incluye los requisitos previos.

Información sobre los datos entre contenedores

En esta sección, iniciará dos contenedores y creará un archivo en cada uno. Los archivos creados en un contenedor no están disponibles en otro.

  1. Inicie un contenedor de ubuntu mediante este comando:

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    

    Este comando inicia invoca dos comandos mediante el uso de &&. La primera parte elige un único número aleatorio y lo escribe en /data.txt. El segundo comando está monitoreando un archivo para mantener el contenedor en ejecución.

  2. En VS Code, en el Explorador de contenedores, haga clic con el botón derecho en el contenedor ubuntu y seleccione Asociar shell.

    Captura de pantalla que muestra la extensión Container Tools con un contenedor seleccionado y un menú contextual con Attach Shell (Asociar shell) seleccionado.

    Se abre un terminal que ejecuta un shell en el contenedor de Ubuntu.

  3. Ejecute el siguiente comando para ver el contenido del archivo /data.txt.

    cat /data.txt
    

    El terminal muestra un número entre 1 y 10000.

    Para usar la línea de comandos para ver este resultado, obtenga el identificador de contenedor mediante el comando docker ps y ejecute el siguiente comando.

    docker exec <container-id> cat /data.txt
    
  4. Inicie otro contenedor ubuntu.

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    
  5. Use este comando para examinar el contenido de la carpeta.

    docker run -it ubuntu ls /
    

    No debería haber ningún archivo data.txt, ya que se ha escrito en el espacio de desecho solo para el primer contenedor.

  6. Seleccione estos dos contenedores de Ubuntu. Haga clic con el botón derecho y seleccione Quitar. En la línea de comandos, puede quitarlos mediante el comando docker rm -f.

Conservación de los datos de tareas pendientes mediante volúmenes con nombre

De forma predeterminada, la aplicación de tareas pendientes almacena sus datos en una base de datos de SQLite en /etc/todos/todo.db. SQLite Database es una base de datos relacional que almacena los datos en un único archivo. Este enfoque funciona para proyectos pequeños.

Puede conservar el archivo en el host. Cuando lo pones a disposición del siguiente contenedor, la aplicación puede continuar desde donde se quedó. Si crea un volumen y lo adjunta a la carpeta (es decir, lo monta) en la que se almacenan los datos, puede conservar los datos. El contenedor escribe en el archivo todo.db y los datos se conservan en el host del volumen.

En esta sección, use un volumen con nombre. Docker mantiene la ubicación física del volumen en el disco. Consulte el nombre del volumen y Docker proporciona los datos adecuados.

  1. Cree un volumen mediante el comando docker volume create.

    docker volume create todo-db
    
  2. En CONTENEDORES, seleccione getting-started y haga clic con el botón derecho. Seleccione Detener para detener el contenedor de aplicaciones.

    Para detener el contenedor desde la línea de comandos, use el comando docker stop.

  3. Inicie el contenedor getting-started mediante el comando siguiente.

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    El parámetro de volumen especifica el volumen que se montará y la ubicación (/etc/todos).

  4. Actualice el explorador para volver a cargar la aplicación. Si ha cerrado la ventana del explorador, vaya a http://localhost:3000/. Agregue algunos elementos a la lista de tareas pendientes.

    Captura de pantalla que muestra la aplicación de ejemplo con varios elementos agregados a la lista.

  5. Quite el contenedor getting-started para la aplicación de tareas pendientes. Haga clic con el botón derecho en el contenedor en el Explorador de contenedores y seleccione Quitar o use los docker stop comandos y docker rm .

  6. Inicie un nuevo contenedor con el mismo comando:

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    Este comando monta la misma unidad que antes. Actualice el explorador. Los elementos que agregó siguen estando en la lista.

  7. Quite de nuevo el contenedor getting-started.

Los volúmenes con nombre y los montajes de enlace, que se tratan a continuación, son los tipos principales de volúmenes que admite una instalación predeterminada del motor de Docker.

Propiedad Volúmenes con nombre Montajes de enlace
Ubicación del host Elección de Docker Usted controla
Ejemplo de montaje (mediante -v) my-volume:/usr/local/data /path/to/data:/usr/local/data
Rellena el nuevo volumen con el contenido del contenedor. No
Admite controladores de volumen No

Hay muchos complementos de controlador de volumen disponibles para admitir NFS, SFTP, NetApp, etc. Estos complementos son especialmente importantes para ejecutar contenedores en varios hosts en un entorno agrupado, como Swarm o Kubernetes.

Si se pregunta dónde almacena realmente Docker los datos, ejecute el comando siguiente.

docker volume inspect todo-db

Observe la salida, que será similar a este resultado.

[
    {
        "CreatedAt": "2019-09-26T02:18:36Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]

El Mountpoint es la ubicación real donde se almacenan los datos. En la mayoría de los equipos, necesita acceso raíz para acceder a este directorio desde el host.

Uso de montajes de enlace

Con los montajes de enlace, puede controlar el punto de montaje exacto en el host. Este enfoque conserva los datos, pero a menudo se usa para proporcionar más datos en contenedores. Puede usar un montaje de enlace para montar el código fuente en el contenedor y ver los cambios en el código, reaccionar y consultar los cambios de inmediato.

Para ejecutar el contenedor a fin de admitir un flujo de trabajo de desarrollo, siga estos pasos:

  1. Quite los contenedores getting-started.

  2. En la carpeta app, ejecute el siguiente comando.

    docker run -dp 3000:3000 -w /app -v ${PWD}:/app node:lts-alpine sh -c "yarn install && yarn run dev"
    

    Este comando contiene los parámetros siguientes.

    • -dp 3000:3000 igual que antes. Ejecute en modo desasociado y cree una asignación de puertos.
    • -w /app: directorio de trabajo dentro del contenedor.
    • -v ${PWD}:/app": montaje de enlace del directorio actual desde el host del contenedor en el directorio /app.
    • node:lts-alpine La imagen que se va a usar. Se trata de la imagen base para la aplicación del Dockerfile.
    • sh -c "yarn install && yarn run dev" Un comando. Inicia un shell mediante sh y ejecuta yarn install para instalar todas las dependencias. Después, ejecuta yarn run dev. Si consulta el archivo package.json, el script dev inicia nodemon.
  3. Puede ver los registros mediante docker logs.

    docker logs -f <container-id>
    
    $ nodemon src/index.js
    [nodemon] 2.0.20
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching path(s): *.*
    [nodemon] watching extensions: js,mjs,json
    [nodemon] starting `node src/index.js`
    Using sqlite database at /etc/todos/todo.db
    Listening on port 3000
    

    Cuando vea la entrada final de esta lista, la aplicación se está ejecutando.

    Cuando haya terminado de ver los registros, seleccione cualquier tecla en la ventana del terminal o seleccione Ctrl+C en una ventana externa.

  4. En VS Code, abre src/static/js/app.js. Cambie el texto del botón Agregar elemento de la línea 109.

    - {submitting ? 'Adding...' : 'Add Item'}
    + {submitting ? 'Adding...' : 'Add'}
    

    Guarde sus cambios.

  5. Actualice el explorador. Deberás ver el cambio.

    Captura de pantalla que muestra la aplicación de ejemplo con el nuevo texto del botón.

  6. Quite el node:lts-alpine contenedor.

  7. En la app carpeta , ejecute el siguiente comando para quitar la node_modules carpeta creada en los pasos anteriores.

    rm -r node_modules
    

Ver capas de imagen

Puede ver las capas que componen una imagen. Ejecute el comando docker image history para ver el comando que se usó para crear cada capa dentro de una imagen.

  1. Usa docker image history para ver las capas en la imagen de introducción que creaste anteriormente en el tutorial.

    docker image history getting-started
    

    El resultado debe ser similar a este resultado.

    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "/app/src/ind…   0B                  
    f1d1808565d6        19 seconds ago      /bin/sh -c yarn install --production            85.4MB              
    a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB               
    9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B                  
    b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B                
    <missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV YARN_VERSION=1.21.1      0B                  
    <missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB   
    

    Cada una de las líneas representa una capa de la imagen. La salida muestra la base en la parte inferior con la capa más reciente en la parte superior. Con esta información, puede ver el tamaño de cada capa, lo que ayuda a diagnosticar imágenes grandes.

  2. Varias de las líneas están truncadas. Si agrega el parámetro --no-trunc, obtendrá la salida completa.

    docker image history --no-trunc getting-started
    

Dependencias de caché

Una vez que cambia una capa, también se deben volver a crear todas las capas de bajada. Aquí puede ver de nuevo el Dockerfile:

FROM node:lts-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "/app/src/index.js"]

Cada comando del Dockerfile se convierte en una nueva capa de la imagen. Para minimizar el número de capas, puede reestructurar su Dockerfile para permitir la caché de dependencias. En el caso de las aplicaciones basadas en nodos, esas dependencias se definen en el archivo package.json.

El enfoque consiste en copiar solo ese archivo en primer lugar, instalar las dependencias y, a continuación, copiar todo lo demás. El proceso solo vuelve a crear las dependencias de Yarn si se ha producido un cambio en package.json.

  1. Actualice el Dockerfile para copiar primero package.json, instale las dependencias y, después, copie todo lo demás. Este es el nuevo archivo:

    FROM node:lts-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production
    COPY . .
    CMD ["node", "/app/src/index.js"]
    
  2. Cree una nueva imagen mediante docker build.

    docker build -t getting-started .
    

    Debería ver resultados similares a los siguientes resultados:

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:lts-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Running in d53a06c9e4c2
    yarn install v1.17.3
    [1/4] Resolving packages...
    [2/4] Fetching packages...
    info fsevents@1.2.9: The platform "linux" is incompatible with this module.
    info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
    [3/4] Linking dependencies...
    [4/4] Building fresh packages...
    Done in 10.89s.
    Removing intermediate container d53a06c9e4c2
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> a239a11f68d8
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 49999f68df8f
    Removing intermediate container 49999f68df8f
    ---> e709c03bc597
    Successfully built e709c03bc597
    Successfully tagged getting-started:latest
    

    Se reconstruyeron todas las capas. Este resultado es esperado porque cambiaste el Dockerfile.

  3. Realice un cambio en el src/static/index.html. Por ejemplo, cambie el título para decir "The Awesome Todo App".

  4. Compile la imagen de Docker ahora con docker build de nuevo. Esta vez, la salida debería tener un aspecto un poco diferente.

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:lts-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> Using cache
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Using cache
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> cccde25a3d9a
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 2be75662c150
    Removing intermediate container 2be75662c150
    ---> 458e5c6f080c
    Successfully built 458e5c6f080c
    Successfully tagged getting-started:latest
    

    Dado que usa la memoria caché de compilación, debe ir mucho más rápido.

Compilaciones de varias fases

Las compilaciones de varias fases son una herramienta increíblemente eficaz que permite usar varias fases para crear una imagen. Existen varias ventajas para ellos:

  • Separar las dependencias en tiempo de compilación de las dependencias en tiempo de ejecución
  • Reducir el tamaño general de la imagen mediante el envío solo de lo que la aplicación necesita ejecutar

En esta sección se proporcionan ejemplos breves.

Ejemplo de Maven/Tomcat

Al compilar aplicaciones basadas en Java, se necesita un JDK para compilar el código fuente en código de bytes de Java. Ese JDK no es necesario en producción. Es posible que esté usando herramientas como Maven o Gradle para ayudar a compilar la aplicación. Esas herramientas tampoco son necesarias en la imagen final.

FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

En este ejemplo se usa una fase, build, para realizar la compilación de Java real mediante Maven. La segunda fase (a partir de "FROM tomcat") copia los archivos de la fase build. La imagen final es solo la última fase que se crea, que se puede invalidar mediante el parámetro --target.

Ejemplo de React

Al compilar aplicaciones de React, necesita un entorno de Node para compilar el código JavaScript, hojas de estilos de Sass y mucho más en HTML estático, JavaScript y CSS. Si no va a realizar la representación del lado servidor, ni siquiera necesita un entorno de Node para la compilación de producción.

FROM node:lts-alpine AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

En este ejemplo se usa una imagen de node:lts-alpine para realizar la compilación, que maximiza el almacenamiento en caché de capas y, a continuación, copia la salida en un contenedor de nginx.

Limpieza de recursos

Mantén todo lo que has hecho hasta ahora para continuar la serie de tutoriales.

Pasos siguientes

Ha aprendido sobre las opciones para conservar los datos de las aplicaciones de contenedor.

¿Qué quieres hacer a continuación?