Merge branch 'i18n'

This commit is contained in:
Publio Estupiñán 2021-05-30 21:38:55 -05:00
commit 6eca886f68
15 changed files with 1640 additions and 3 deletions

2
.gitignore vendored
View File

@ -1 +1,3 @@
node_modules
.idea/

View File

@ -3,6 +3,7 @@
FROM python:alpine AS base
WORKDIR /app
COPY requirements.txt .
RUN apk add --no-cache g++
RUN pip install -r requirements.txt
# Run tests to validate app

2
docs/index.es.md Normal file
View File

@ -0,0 +1,2 @@
redirect: /tutorial/

View File

@ -0,0 +1,279 @@
## Escaneo de Seguridad
Cuando haya creado una imagen, es una buena práctica escanearla en busca de vulnerabilidades de seguridad utilizando el
comando `docker scan`.
Docker se ha asociado con [Snyk](http://snyk.io) para proporcionar el servicio de análisis de vulnerabilidades.
Por ejemplo, para escanear la imagen `getting-started` que creó anteriormente en el tutorial, simplemente escriba
```bash
docker scan getting-started
```
El análisis utiliza una base de datos de vulnerabilidades que se actualiza constantemente, por lo que el resultado que
ve variará a medida que se descubran nuevas vulnerabilidades, pero podría verse así:
```plaintext
✗ Low severity vulnerability found in freetype/freetype
Description: CVE-2020-15999
Info: https://snyk.io/vuln/SNYK-ALPINE310-FREETYPE-1019641
Introduced through: freetype/freetype@2.10.0-r0, gd/libgd@2.2.5-r2
From: freetype/freetype@2.10.0-r0
From: gd/libgd@2.2.5-r2 > freetype/freetype@2.10.0-r0
Fixed in: 2.10.0-r1
✗ Medium severity vulnerability found in libxml2/libxml2
Description: Out-of-bounds Read
Info: https://snyk.io/vuln/SNYK-ALPINE310-LIBXML2-674791
Introduced through: libxml2/libxml2@2.9.9-r3, libxslt/libxslt@1.1.33-r3, nginx-module-xslt/nginx-module-xslt@1.17.9-r1
From: libxml2/libxml2@2.9.9-r3
From: libxslt/libxslt@1.1.33-r3 > libxml2/libxml2@2.9.9-r3
From: nginx-module-xslt/nginx-module-xslt@1.17.9-r1 > libxml2/libxml2@2.9.9-r3
Fixed in: 2.9.9-r4
```
El resultado enumera el tipo de vulnerabilidad, una URL para obtener más información y, lo que es más importante, qué
versión de la biblioteca relevante corrige la vulnerabilidad.
Hay varias otras opciones, sobre las que puede leer en [documentación docker scan](https://docs.docker.com/engine/scan/).
Además de escanear su imagen recién construida en la línea de comando, también puede
[configurar Docker Hub](https://docs.docker.com/docker-hub/vulnerability-scanning/)
para escanear todas las imágenes recién enviadas automáticamente, y luego puede ver los resultados tanto en Docker Hub
como en Docker Desktop.
![Escaneo de vulnerabilidades del hub](hvs.png){: style=width:75% }
{: .text-center }
## Capas de imagen
¿Sabías que puedes mirar lo que compone una imagen? Usando el comando `docker image history`,
puede ver el comando que se usó para crear cada capa dentro de una imagen.
1. Utilice el comando `docker image history` para ver las capas en la imagen de `getting-started` que creó
anteriormente en el tutorial.
```bash
docker image history getting-started
```
Debería obtener un resultado que se parezca a esto (los identificadores de fechas pueden ser diferentes).
```plaintext
IMAGE CREATED CREATED BY SIZE COMMENT
a78a40cbf866 18 seconds ago /bin/sh -c #(nop) CMD ["node" "src/index.j… 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 en la imagen. La pantalla aquí muestra la base en la parte inferior con
la capa más nueva en la parte superior. Con esto, también puede ver rápidamente el tamaño de cada capa, lo que ayuda
a diagnosticar imágenes grandes.
1. Notarás que varias de las líneas están truncadas. Si agrega el indicador `--no-trunc`, obtendrá la salida completa
(sí ... es curioso cómo usa una bandera truncada para obtener una salida no truncada, ¿eh?)
```bash
docker image history --no-trunc getting-started
```
## Almacenamiento en caché de capas
Ahora que ha visto las capas en acción, hay una lección importante que aprender para ayudar a reducir los build
times de las imágenes de su contenedor.
> Una vez que cambia una capa, todas las capas posteriores también deben volver a crearse
Veamos el Dockerfile que estábamos usando una vez más ...
```dockerfile
FROM node:12-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
```
Volviendo a la salida del historial de la imagen, vemos que cada comando en el Dockerfile se convierte en una nueva capa
en la imagen. Quizás recuerde que cuando hicimos un cambio en la imagen, las dependencias yarn tuvieron que reinstalarse.
¿Hay alguna forma de solucionar este problema? No tiene mucho sentido distribuir las mismas dependencias cada vez que
construimos, ¿verdad?
Para solucionar esto, necesitamos reestructurar nuestro Dockerfile para ayudar a soportar el almacenamiento en caché de
las dependencias. Para aplicaciones Node-based, esas dependencias se definen en el archivo `package.json`. Entonces,
¿qué pasa si copiamos solo ese archivo primero, instalamos las dependencias y, luego, copiamos todo lo demás? Luego,
solo recreamos las dependencias yarn si hubo un cambio en el `package.json`. ¿Tener sentido?
1. Actualice el Dockerfile para copiar en el `package.json` primero, instale las dependencias y luego copie todo lo
demás en.
```dockerfile hl_lines="3 4 5"
FROM node:12-alpine
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --production
COPY . .
CMD ["node", "src/index.js"]
```
1. Cree un archivo llamado `.dockerignore` en la misma carpeta que Dockerfile con el siguiente contenido.
```ignore
node_modules
```
Los archivos `.dockerignore` son una manera fácil de copiar selectivamente solo archivos relevantes de imagen.
Puede leer más sobre esto
[aquí](https://docs.docker.com/engine/reference/builder/#dockerignore-file).
En este caso, la carpeta `node_modules` debe omitirse en el segundo paso `COPY` porque de lo contrario,
posiblemente sobrescribirá los archivos que fueron creados por el comando en el paso `RUN`.
Para obtener más detalles sobre por qué se recomienda esto para las aplicaciones Node.js y otras prácticas
recomendadas, echa un vistazo a su guía en
[Dockerizar una aplicación web Node.js](https://nodejs.org/en/docs/guides/nodejs-docker-webapp/).
1. Cree una nueva imagen usando `docker build`.
```bash
docker build -t getting-started .
```
Debería ver un resultado como este ...
```plaintext
Sending build context to Docker daemon 219.1kB
Step 1/6 : FROM node:12-alpine
---> b0dc3a5e5e9e
Step 2/6 : WORKDIR /app
---> Using cache
---> 9577ae713121
Step 3/6 : COPY package.json 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", "src/index.js"]
---> Running in 49999f68df8f
Removing intermediate container 49999f68df8f
---> e709c03bc597
Successfully built e709c03bc597
Successfully tagged getting-started:latest
```
Verá que se reconstruyeron todas las capas. Perfectamente bien desde que cambiamos bastante el Dockerfile.
1. Ahora, haga un cambio en el archivo `src/static/index.html` (como cambiar el `<title>` para decir "The Awesome Todo App").
1. Cree la imagen de Docker ahora usando `docker build -t Getting started .` nuevamente. Esta vez, su salida debería
verse un poco diferente.
```plaintext hl_lines="5 8 11"
Sending build context to Docker daemon 219.1kB
Step 1/6 : FROM node:12-alpine
---> b0dc3a5e5e9e
Step 2/6 : WORKDIR /app
---> Using cache
---> 9577ae713121
Step 3/6 : COPY package.json 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", "src/index.js"]
---> Running in 2be75662c150
Removing intermediate container 2be75662c150
---> 458e5c6f080c
Successfully built 458e5c6f080c
Successfully tagged getting-started:latest
```
Antes que nada, ¡Deberías notar que la construcción fue MUCHO más rápida! Y verá que todos los pasos 1-4 tienen
"Using cache". Entonces, ¡hurra! Estamos usando la caché de compilación. Pushing y pulling esta imagen y las
actualizaciones también será mucho más rápido. ¡Hurra!
## Construcciones Multi-Stage
Si bien no vamos a profundizar demasiado en este tutorial, las compilaciones multi-stage son una herramienta
increíblemente poderosa para ayudar a usar multiple stages para crear una imagen. Tienen varias ventajas:
- Separe las dependencias en build-time de las dependencias en runtime
- Reduzca el tamaño general de la imagen enviando _solo_ lo que su aplicación necesita para ejecutarse
### Ejemplo Maven/Tomcat
Al crear aplicaciones basadas en Java, se necesita un JDK para compilar el código fuente en código de bytes Java.
Sin embargo, ese JDK no es necesario en producción. Además, es posible que esté utilizando herramientas como Maven o
Gradle para ayudar a crear la aplicación. Esos tampoco son necesarios en nuestra imagen final. Las compilaciones de
varias etapas ayudan.
```dockerfile
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, usamos una etapa (llamada `build`) para realizar la compilación real de Java usando Maven. En la segunda
etapa (comenzando en `FROM tomcat`), copiamos archivos de la etapa de `build`. La imagen final es solo la última etapa
que se está creando (que se puede anular usando el indicador `--target`).
### Ejemplo React
Al crear aplicaciones React, necesitamos un entorno Node para compilar el código JS (normalmente JSX), SASS stylesheets,
y más en HTML, JS, y CSS estático. Si no estamos haciendo renderizado del lado del servidor, ni siquiera necesitamos
un entorno Node para nuestra compilación de producción. ¿Por qué no enviar los recursos estáticos en un contenedor nginx
estático?
```dockerfile
FROM node:12 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
```
Aquí, estamos usando una imagen `node: 12` para realizar la compilación (maximizando el almacenamiento en caché de la
capa) y luego copiando la salida en un contenedor nginx. Genial, ¿eh?
## Resumen
Al comprender un poco cómo se estructuran las imágenes, podemos crear imágenes más rápido y enviar menos cambios.
El escaneo de imágenes nos da la confianza de que los contenedores que estamos ejecutando y distribuyendo son seguros.
Las compilaciones Multi-stage también nos ayudan a reducir el tamaño general de la imagen y a aumentar la seguridad del
contenedor final al separar las dependencias en build-time de las dependencias en runtime.

75
docs/tutorial/index.es.md Normal file
View File

@ -0,0 +1,75 @@
---
next_page: app.md
---
## El comando que acabas de ejecutar
¡Felicidades! ¡Ha iniciado el contenedor para este tutorial!
Primero expliquemos el comando que acaba de ejecutar. En caso de que lo hayas olvidado
aquí está el comando:
```cli
docker run -d -p 80:80 docker/getting-started
```
Notará que se utilizan algunas banderas. Aquí hay más información sobre ellas:
- `-d` - ejecuta el contenedor en modo separado (en segundo plano)
- `-p 80:80` - mapea el puerto 80 del host al puerto 80 en el contenedor
- `docker/getting-started` - la imagen a usar
!!! info "Consejo profesional"
Puede combinar banderas de un solo carácter para acortar el comando completo.
Como ejemplo, el comando anterior podría escribirse como:
```
docker run -dp 80:80 docker/getting-started
```
## El panel de Docker
Antes de ir demasiado lejos, queremos resaltar el panel de Docker, que le brinda
una vista rápida de los contenedores que se ejecutan en su máquina. Le brinda acceso
rápido a los registros del contenedor, le permite obtener un shell dentro del
contenedor y le permite administrar fácilmente el ciclo de vida del contenedor (detener,
eliminar, etc.).
Para acceder al panel, siga las instrucciones para
[Mac](https://docs.docker.com/docker-for-mac/dashboard/) o
[Windows](https://docs.docker.com/docker-for-windows/dashboard/). Si abre el panel ahora,
verá este tutorial en ejecución. El nombre del contenedor (`jolly_bouman` a continuación)
es un nombre creado al azar. Por lo tanto, lo más probable es que tenga un nombre diferente.
![Tutorial container running in Docker Dashboard](tutorial-in-dashboard.png)
## ¿Qué es un contenedor?
Ahora que ha ejecutado un contenedor, que _es_ un contenedor? En pocas palabras, un contenedor es
simplemente otro proceso en su máquina que ha sido aislado de todos los demás procesos en la máquina host.
Ese aislamiento aprovecha [kernel namespaces y cgroups](https://medium.com/@saschagrunert/demystifying-containers-part-i-kernel-space-2c53d6979504), características que han estado
en Linux durante mucho tiempo. Docker ha trabajado para hacer que estas capacidades sean accesibles y fáciles de usar.
!!! info "Creación de contenedores desde cero"
Si desea ver cómo se construyen los contenedores desde cero, Liz Rice de Aqua Security
tiene una charla fantástica en la que crea un contenedor desde cero en Go. Si bien hace
un contenedor simple, esta charla no se relaciona con las redes, el uso de imágenes para
el sistema de archivos y más. Pero ofrece una _fantástica_ inmersión en el modo en que
funcionan las cosas.
<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/8fi7uSYlOdc" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
## ¿Qué es una imagen de contenedor?
Cuando se ejecuta un contenedor, utiliza un sistema de archivos aislado. Este sistema de
archivos personalizado es proporcionado por una **imagen de contenedor**. Dado que la
imagen contiene el sistema de archivos del contenedor, debe contener todo lo necesario
para ejecutar una aplicación: todas las dependencias, configuración, scripts, binarios,
etc. La imagen también contiene otra configuración para el contenedor, como variables de
entorno, un comando predeterminado para ejecutar, y otros metadatos.
Más adelante profundizaremos en las imágenes, cubriendo temas como capas, mejores prácticas y más.
!!! info
Si está familiarizado con `chroot`, piense en un contenedor como una versión extendida de` chroot`.
El sistema de archivos simplemente proviene de la imagen. Pero, un contenedor agrega aislamiento
adicional que no está disponible cuando simplemente se usa chroot.

View File

@ -0,0 +1,266 @@
Hasta este momento, hemos estado trabajando con aplicaciones de contenedor único. Pero ahora queremos
agregar MySQL al stack de aplicaciones. A menudo surge la siguiente pregunta: "¿Dónde se ejecutará MySQL?
¿Instalarlo en el mismo contenedor o ejecutarlo por separado?" En general, **cada contenedor debe hacer
una cosa y hacerlo bien.** Algunas razones:
- Existe una gran posibilidad de que tenga que escalar las API y los front-ends de manera diferente
a las bases de datos
- Los contenedores separados le permiten versionar y actualizar versiones de forma aislada
- Si bien puede usar un contenedor para la base de datos localmente, es posible que desee usar un
servicio administrado para la base de datos en producción. Entonces no desea enviar su motor de
base de datos con su aplicación.
- La ejecución de varios procesos requerirá un administrador de procesos (el contenedor solo inicia
un proceso), lo que agrega complejidad al inicio del contenedor
Y hay más razones. Entonces, actualizaremos nuestra aplicación para que funcione así:
![Todo App connected to MySQL container](multi-app-architecture.png)
{: .text-center }
## Redes de contenedores
Recuerde que los contenedores, por defecto, se ejecutan de forma aislada y no saben nada sobre otros
procesos o contenedores en la misma máquina. Entonces, ¿cómo permitimos que un contenedor hable con
otro? La respuesta es la **creación de redes**. Ahora, no tiene que ser un ingeniero de redes (¡hurra!).
Simplemente recuerda esta regla ...
> Si dos contenedores están en la misma red, pueden comunicarse entre sí. Si no lo están, no pueden.
## Iniciando MySQL
Hay dos formas de poner un contenedor en una red: 1) Asignarlo al inicio o 2) Conectar un contenedor existente.
Por ahora, crearemos la red primero y adjuntaremos el contenedor MySQL al inicio.
1. Crea la red.
```bash
docker network create todo-app
```
1. Inicie un contenedor MySQL y conéctelo a la red. También vamos a definir algunas variables de entorno que la base de
datos utilizará para inicializar la base de datos. (consulte la sección "Environment Variables" en la [MySQL Docker Hub listing](https://hub.docker.com/_/mysql/)).
```bash
docker run -d \
--network todo-app --network-alias mysql \
-v todo-mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=todos \
mysql:5.7
```
Si está utilizando PowerShell, utilice este comando.
```powershell
docker run -d `
--network todo-app --network-alias mysql `
-v todo-mysql-data:/var/lib/mysql `
-e MYSQL_ROOT_PASSWORD=secret `
-e MYSQL_DATABASE=todos `
mysql:5.7
```
También verá que especificamos la bandera `--network-alias`. Volveremos a eso en un momento.
!!! info "Pro-tip"
Notarás que estamos usando un volume named `todo-mysql-data` aquí y lo estamos montando en `/var/lib/mysql`,
que es donde MySQL almacena sus datos. Sin emabargo, nunca ejecutamos un comando `docker volume create`.
Docker reconoce que queremos usar un volume named y crea uno automáticamente para nosotros.
1. Para confirmar que tenemos la base de datos en funcionamiento, conéctese a la base de datos y verifique que se conecte.
```bash
docker exec -it <mysql-container-id> mysql -p
```
Cuando aparezca la solicitud de contraseña, escriba **secret**. En el shell de MySQL, enumere las bases
de datos y verifique que vea la base de datos `todos`.
```cli
mysql> SHOW DATABASES;
```
Debería ver una salida que se ve así:
```plaintext
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| todos |
+--------------------+
5 rows in set (0.00 sec)
```
¡Hurra! ¡Tenemos nuestra base de datos `todos` y está lista para que la usemos!
## Conectarse a MySQL
Ahora que sabemos que MySQL está funcionando, usémoslo! ¿Pero la pregunta es cómo? Si ejecutamos
otro contenedor en la misma red, ¿cómo encontramos el contenedor (recuerde que cada contenedor
tiene su propia dirección IP)?
Para resolverlo, usaremos el [nicolaka/netshoot](https://github.com/nicolaka/netshoot) contenedor,
que se envía con _muchas_ herramientas que son útiles para solucionar problemas o depurar problemas de red.
1. Inicie un nuevo contenedor con la imagen de nicolaka/netshoot. Asegúrate de conectarlo a la misma red.
```bash
docker run -it --network todo-app nicolaka/netshoot
```
1. Dentro del contenedor, usaremos el comando `dig`, que es una útil herramienta de DNS.
Vamos a buscar la dirección IP del nombre de host `mysql`.
```bash
dig mysql
```
Y obtendrás un resultado como este ...
```text
; <<>> DiG 9.14.1 <<>> mysql
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;mysql. IN A
;; ANSWER SECTION:
mysql. 600 IN A 172.23.0.2
;; Query time: 0 msec
;; SERVER: 127.0.0.11#53(127.0.0.11)
;; WHEN: Tue Oct 01 23:47:24 UTC 2019
;; MSG SIZE rcvd: 44
```
En la "ANSWER SECTION", verá un registro `A` para` mysql` que se resuelve en `172.23.0.2`
(lo más probable es que su dirección IP tenga un valor diferente). Si bien `mysql` no es normalmente un nombre de
host válido, Docker pudo resolverlo en la dirección IP del contenedor que tenía ese alias de red (¿recuerda el
indicador` --network-alias` que usamos anteriormente?).
Lo que esto significa es que... nuestra aplicación solo necesita conectarse a un host llamado `mysql` y se
comunicará con la base de datos. ¡No hay nada más simple que eso!
## Ejecutando nuestra aplicación con MySQL
La aplicación **todo** admite la configuración de algunas variables de entorno para especificar la configuración de la
conexión MySQL. Ellos son:
- `MYSQL_HOST` - el nombre de host del servidor MySQL en ejecución
- `MYSQL_USER` - el nombre de usuario que se utilizará para la conexión
- `MYSQL_PASSWORD` - la contraseña que se utilizará para la conexión
- `MYSQL_DB` - la base de datos para usar una vez conectado
!!! warning Establecer la configuración de conexión a través de Env Vars
Si bien el uso de env vars para establecer la configuración de conexión generalmente está bien para el desarrollo,
Es **MUY DESALENTADOR** cuando se ejecutan aplicaciones en producción. Diogo Monica, el ex líder de seguridad en Docker,
[wrote a fantastic blog post](https://diogomonica.com/2017/03/27/why-you-shouldnt-use-env-variables-for-secret-data/)
explicando por qué.
Un mecanismo más seguro es utilizar el soporte secreto proporcionado por su marco de orquestación de contenedores.
En la mayoría de los casos, estos secrets se montan como archivos en el contenedor en ejecución. Verá muchas
aplicaciones (incluida la imagen MySQL y la aplicación de tareas pendientes) que también admiten vars env con un
sufijo `_FILE` para señalar un archivo que contiene la variable.
Como ejemplo, la configuración de la var `MYSQL_PASSWORD_FILE` hará que la aplicación use el contenido del archivo
referenciado como contraseña de conexión. Docker no hace nada para admitir estas variables de entorno. Su aplicación
necesitará saber para buscar la variable y obtener el contenido del archivo.
Con todo eso explicado, ¡comencemos nuestro contenedor listo para desarrolladores!
1. Especificaremos cada una de las variables de entorno anteriores, además de conectar el contenedor a nuestra red de
aplicaciones.
```bash hl_lines="3 4 5 6 7"
docker run -dp 3000:3000 \
-w /app -v "$(pwd):/app" \
--network todo-app \
-e MYSQL_HOST=mysql \
-e MYSQL_USER=root \
-e MYSQL_PASSWORD=secret \
-e MYSQL_DB=todos \
node:12-alpine \
sh -c "yarn install && yarn run dev"
```
Si está utilizando PowerShell, utilice este comando.
```powershell hl_lines="3 4 5 6 7"
docker run -dp 3000:3000 `
-w /app -v "$(pwd):/app" `
--network todo-app `
-e MYSQL_HOST=mysql `
-e MYSQL_USER=root `
-e MYSQL_PASSWORD=secret `
-e MYSQL_DB=todos `
node:12-alpine `
sh -c "yarn install && yarn run dev"
```
1. Si miramos los registros del contenedor (`docker logs <container-id>`), deberíamos ver un mensaje que indica que está
usando la base de datos mysql.
```plaintext hl_lines="7"
# Previous log messages omitted
$ nodemon src/index.js
[nodemon] 1.19.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node src/index.js`
Connected to mysql db at host mysql
Listening on port 3000
```
1. Abra la aplicación en su navegador y agregue algunos elementos a su lista de tareas pendientes.
1. Conéctese a la base de datos mysql y demuestre que los elementos se escriben en la base de datos. Recuerde, la
contraseña es **secreta**.
```bash
docker exec -it <mysql-container-id> mysql -p todos
```
Y en el shell de mysql, ejecute lo siguiente:
```plaintext
mysql> select * from todo_items;
+--------------------------------------+--------------------+-----------+
| id | name | completed |
+--------------------------------------+--------------------+-----------+
| c906ff08-60e6-44e6-8f49-ed56a0853e85 | Do amazing things! | 0 |
| 2912a79e-8486-4bc3-a4c5-460793a575ab | Be awesome! | 0 |
+--------------------------------------+--------------------+-----------+
```
Obviamente, tu tabla se verá diferente porque tiene tus artículos. ¡Pero deberías verlos almacenados allí!
Si echas un vistazo rápido al panel de Docker, verás que tenemos dos contenedores de aplicaciones ejecutándose. Pero no
hay indicios reales de que estén agrupados en una sola aplicación. ¡Veremos cómo mejorarlo en breve!
![Docker Dashboard showing two ungrouped app containers](dashboard-multi-container-app.png)
## Resumen
En este punto, tenemos una aplicación que ahora almacena sus datos en una base de datos externa que se ejecuta en un
contenedor separado. Aprendimos un poco sobre redes de contenedores y vimos cómo se puede realizar el descubrimiento de
servicios utilizando DNS.
Pero es muy probable que empiece a sentirse un poco abrumado con todo lo que necesita hacer para iniciar esta aplicación.
¡Tenemos que crear una red, iniciar contenedores, especificar todas las variables de entorno, exponer puertos y más!
Eso es mucho para recordar y ciertamente hace que las cosas sean más difíciles de transmitir a otra persona.
En la siguiente sección, hablaremos sobre Docker Compose. Con Docker Compose, podemos compartir nuestras pilas de
aplicaciones de una manera mucho más fácil y dejar que otros las giren con un solo (y simple) comando.

View File

@ -0,0 +1,114 @@
Para el resto de este tutorial, trabajaremos con un administrador de
lista de tareas simple que se ejecuta en Node.js. Si no está familiarizado
con Node.js, ¡no se preocupe! ¡No se necesita experiencia real en JavaScript!
En este punto, su equipo de desarrollo es bastante pequeño y simplemente
está creando una aplicación para probar su PMV (producto mínimo viable).
Desea mostrar cómo funciona y qué es capaz de hacer sin necesidad de pensar
en cómo funcionará para un equipo grande, múltiples desarrolladores, etc.
![Todo List Manager Screenshot](todo-list-sample.png){: style="width:50%;" }
{ .text-center }
## Obteniendo nuestra aplicación
Antes de que podamos ejecutar la aplicación, necesitamos obtener el código fuente
de la aplicación en nuestra máquina. Para proyectos reales, normalmente clonarás el
repositorio. Pero, para este tutorial, hemos creado un archivo ZIP que contiene la aplicación.
1. [Descarga el ZIP](/assets/app.zip). Abra el archivo ZIP y asegúrese de extraer el contenido.
1. Una vez extraído, usa tu editor de código favorito para abrir el proyecto. Si necesitas un
editor, puedes usar [Visual Studio Code](https://code.visualstudio.com/). Debería ver el
`package.json` y dos subdirectorios (`src` y `spec`).
![Screenshot of Visual Studio Code opened with the app loaded](ide-screenshot.png){: style="width:650px;margin-top:20px;"}
{: .text-center }
## Creación de la imagen del contenedor de la aplicación
Para construir la aplicación, necesitamos usar un `Dockerfile`. Un
dockerfile es simplemente un script de instrucciones basado en
texto que se usa para crear una imagen de contenedor. Si ha creado
Dockerfiles antes, es posible que vea algunas fallas en el Dockerfile
a continuación. ¡Pero no te preocupes! Los repasaremos.
1. Cree un archivo llamado `Dockerfile` en la misma carpeta que el archivo `package.json` con el siguiente contenido.
```dockerfile
FROM node:12-alpine
RUN apk add --no-cache python g++ make
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
```
Compruebe que el archivo `Dockerfile` no tenga una extensión de archivo como `.txt`. Algunos editores pueden agregar esta extensión de archivo automáticamente y esto daría como resultado un error en el siguiente paso.
1. Si aún no lo ha hecho, abra una terminal y vaya al directorio `app` con el `Dockerfile`. Ahora construya la imagen del contenedor usando el comando `docker build`.
```bash
docker build -t getting-started .
```
Este comando usó el Dockerfile para crear una nueva imagen de contenedor. Es posible
que haya notado que se descargaron muchas "capas". Esto se debe a que le indicamos
al constructor que queríamos comenzar desde la imagen `node: 12-alpine`. Pero, como
no teníamos eso en nuestra máquina, era necesario descargar esa imagen.
Después de descargar la imagen, copiamos en su aplicación y usamos `yarn` para instalar
las dependencias de su aplicación. La directiva `CMD` especifica el comando predeterminado
que se ejecutará al iniciar un contenedor desde esta imagen.
Finalmente, la bandera `-t` etiqueta nuestra imagen. Piense en esto simplemente como un nombre
legible por humanos para la imagen final. Dado que llamamos a la imagen `getting-started`,
podemos hacer referencia a esa imagen cuando ejecutamos un contenedor.
El `.` al final del comando `docker build` indica que Docker debe buscar el `Dockerfile` en el directorio actual.
## Iniciar un contenedor de aplicaciones
Ahora que tenemos una imagen, ¡ejecutemos la aplicación! Para hacerlo, usaremos el
comando `docker run` (¿recuerdas eso de antes?).
1. Inicie su contenedor usando el comando `docker run` y especifique el nombre de la imagen que acabamos
de crear:
```bash
docker run -dp 3000:3000 getting-started
```
¿Recuerda las banderas `-d` y `-p`? Estamos ejecutando el nuevo contenedor en modo "separado" (en
segundo plano) y creando un mapeo entre el puerto 3000 del host y el puerto 3000 del contenedor.
Sin el mapeo de puertos, no podríamos acceder a la aplicación.
1. Después de unos segundos, abra su navegador web a [http://localhost:3000](http://localhost:3000).
¡Deberías ver nuestra aplicación!
![Empty Todo List](todo-list-empty.png){: style="width:450px;margin-top:20px;"}
{: .text-center }
1. Continúe y agregue uno o dos elementos y compruebe que funcionan como espera. Puede marcar elementos
como completos y eliminar elementos. ¡Tu interfaz está almacenando elementos con éxito en el backend!
Bastante rápido y fácil, ¿eh?
En este punto, debería tener un administrador de lista de tareas en ejecución con algunos elementos,
¡todos creados por usted! Ahora, hagamos algunos cambios y aprendamos a administrar nuestros contenedores.
Si echa un vistazo rápido al panel de Docker, debería ver sus dos contenedores ejecutándose ahora
(este tutorial y su contenedor de aplicaciones recién lanzado).
![Docker Dashboard with tutorial and app containers running](dashboard-two-containers.png)
## Resumen
En esta breve sección, aprendimos los conceptos básicos sobre cómo crear una imagen de contenedor
y crear un Dockerfile. Una vez que creamos una imagen, iniciamos el contenedor y vimos la aplicación
en ejecución.
A continuación, haremos una modificación en nuestra aplicación y aprenderemos cómo actualizar nuestra
aplicación en ejecución con una nueva imagen. En el camino, aprenderemos algunos otros comandos útiles.

View File

@ -0,0 +1,166 @@
En caso de que no se haya dado cuenta, nuestra lista de tareas pendientes se borra cada vez
que lancemos el contenedor. ¿Por qué es esto? Veamos cómo está funcionando el contenedor.
## El sistema de archivos del contenedor
Cuando se ejecuta un contenedor, utiliza las distintas capas de una imagen para su sistema de archivos.
Cada contenedor también tiene su propio "espacio temporal" para crear/actualizar/eliminar archivos.
Los cambios no se verán en otro contenedor, _incluso si_ están usando la misma imagen.
### Viendo esto en la práctica
Para ver esto en acción, vamos a iniciar dos contenedores y crear un archivo en cada uno.
Lo que verá es que los archivos creados en un contenedor no están disponibles en otro.
1. Inicie un contenedor `ubuntu` que creará un archivo llamado `/data.txt` con un número
aleatorio entre 1 y 10000.
```bash
docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
```
En caso de que sienta curiosidad por el comando, estamos iniciando un shell bash e invocando
dos comandos (porque tenemos `&&`). La primera parte elige un solo número aleatorio y lo
escribe en `/data.txt`. El segundo comando es simplemente mirar un archivo para mantener el
contenedor en ejecución.
1. Validamos que podemos ver la salida ejecutando `exec` en el contenedor. Para hacerlo, abra el Panel de control y haga clic en la primera acción del contenedor que ejecuta la imagen de `ubuntu`.
![Dashboard open CLI into ubuntu container](dashboard-open-cli-ubuntu.png){: style=width:75% }
{: .text-center }
Verá una terminal que está ejecutando un shell en el contenedor de ubuntu. Ejecute el siguiente comando para ver el contenido del archivo `/data.txt`. Cierra esta terminal luego nuevamente.
```bash
cat /data.txt
```
Si prefiere la línea de comandos, puede usar el comando `docker exec` para hacer lo mismo. Necesita obtener el ID
del contenedor (use `docker ps` para obtenerlo) y obtener el contenido con el siguiente comando.
```bash
docker exec <container-id> cat /data.txt
```
¡Debería ver un número aleatorio!
1. Ahora, comencemos otro contenedor `ubuntu` (la misma imagen) y veremos que no tenemos el mismo archivo.
```bash
docker run -it ubuntu ls /
```
¡Y mira! ¡No hay ningún archivo `data.txt` allí! Eso es porque se escribió en el espacio temporal solo
para el primer contenedor.
1. Continúe y elimine el primer contenedor usando el comando `docker rm -f`.
## Volúmenes de contenedores
Con el experimento anterior, vimos que cada contenedor comienza desde la definición de la imagen cada vez
que comienza. Si bien los contenedores pueden crear, actualizar y eliminar archivos, esos cambios se pierden
cuando se elimina el contenedor y todos los cambios se aíslan en ese contenedor. Con los volúmenes, podemos
cambiar todo esto.
[Volumes](https://docs.docker.com/storage/volumes/) brindan la capacidad de conectar rutas específicas del
sistema de archivos del contenedor a la máquina host. Si se monta un directorio en el contenedor, los cambios
en ese directorio también se ven en la máquina host. Si montamos ese mismo directorio en los reinicios del
contenedor, veríamos los mismos archivos.
Hay dos tipos principales de volúmenes. Eventualmente usaremos ambos, pero comenzaremos con **volúmenes con nombre**.
## Persistiendo nuestra Todo Data
De forma predeterminada, la aplicación de tareas pendientes almacena sus datos en una
[SQLite Database](https://www.sqlite.org/index.html) en `/etc/todos/todo.db`.
Si no está familiarizado con SQLite, ¡no se preocupe! Es simplemente una base de datos relacional
en la que todos los datos se almacenan en un solo archivo. Si bien esto no es lo mejor para aplicaciones
a gran escala, funciona para demostraciones pequeñas. Hablaremos de cambiar esto a un motor de base de
datos diferente más adelante.
Dado que la base de datos es un solo archivo, si podemos conservar ese archivo en el host y ponerlo a
disposición del siguiente contenedor, debería poder continuar donde lo dejó el último. Al crear un
volumen y adjuntarlo (a menudo llamado "montar") al directorio en el que se almacenan los datos,
podemos conservar los datos. A medida que nuestro contenedor escribe en el archivo `todo.db`, se
mantendrá en el host en el volumen.
Como se mencionó, usaremos un **volumen con nombre**. Piense en un volumen con nombre como simplemente
un depósito de datos. Docker mantiene la ubicación física en el disco y solo necesita recordar el nombre
del volumen. Cada vez que utilice el volumen, Docker se asegurará de que se proporcionen los datos correctos.
1. Cree un volumen usando el comando `docker volume create`.
```bash
docker volume create todo-db
```
1. Detenga el contenedor de la aplicación **todo** una vez más desde el Dashboard (o con `docker rm -f <id>`), ya
que todavía se está ejecutando sin usar el volumen persistente.
1. Inicie el contenedor de la aplicación **todo**, pero agregue el indicador `-v` para especificar un montaje de
volumen. Usaremos el volumen nombrado y lo montaremos en `/etc/todos`, que capturará todos los archivos creados
en la ruta.
```bash
docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
```
1. Una vez que se inicia el contenedor, abra la aplicación y agregue algunos elementos a su lista de **todo**.
![Items added to todo list](items-added.png){: style="width: 55%; " }
{: .text-center }
1. Retire el contenedor de la aplicación **todo**. Utilice el Dashboard o `docker ps` para obtener el ID y luego
`docker rm -f <id>` para eliminarlo.
1. Inicie un nuevo contenedor usando el mismo comando de arriba.
1. Abra la aplicación. ¡Debería ver sus artículos todavía en su lista!
1. Continúe y retire el contenedor cuando haya terminado de revisar su lista.
¡Hurra! ¡Ahora ha aprendido a conservar los datos!
!!! info "Pro-tip"
Mientras que los named volumes y los bind mounts (de los que hablaremos en un minuto) son los dos
tipos principales de volúmenes admitidos por una instalación predeterminada del motor Docker, ¡hay
muchos complementos de controladores de volumen disponibles para admitir NFS, SFTP, NetApp y más!
Esto será especialmente importante una vez que comience a ejecutar contenedores en varios hosts en
un entorno agrupado con Swarm, Kubernetes, etc.
## Sumergiéndonos en nuestro volumen
Mucha gente pregunta con frecuencia "¿Dónde está Docker _realmente_ almacenando mis datos cuando utilizo
un named volume?" Si quieres saberlo, puedes usar el comando `docker volume inspect`.
```bash
docker volume inspect todo-db
[
{
"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 en el disco donde se almacenan los datos. Tenga en cuenta que en la mayoría
de las máquinas, deberá tener acceso de root para acceder a este directorio desde el host. ¡Pero ahí es donde está!
!!! info "Acceder a los datos de volumen directamente en Docker Desktop"
Mientras se ejecuta en Docker Desktop, los comandos de Docker se ejecutan realmente dentro de una pequeña máquina
virtual en su máquina. Si desea ver el contenido real del directorio Mountpoint, primero debe ingresar a la VM.
## Resumen
En este punto, tenemos una aplicación en funcionamiento que puede sobrevivir a los reinicios.
¡Podemos mostrárselo a nuestros inversores y esperamos que puedan captar nuestra visión!
Sin embargo, vimos anteriormente que la reconstrucción de imágenes para cada cambio lleva bastante tiempo.
Tiene que haber una mejor manera de hacer cambios, ¿verdad? Con bind mounts (que insinuamos anteriormente),
¡hay una mejor manera! ¡Echemos un vistazo a eso ahora!

View File

@ -0,0 +1,93 @@
Ahora que hemos creado una imagen, ¡compartámosla! Para compartir imágenes de Docker,
debe utilizar un Docker registry. El registry predeterminado es Docker Hub y es de
donde provienen todas las imágenes que hemos utilizado.
## Crear un repositorio
Para enviar una imagen, primero debemos crear un repositorio en Docker Hub.
1. Vaya a [Docker Hub](https://hub.docker.com) e inicie sesión si es necesario.
1. Haga clic en el botón **Create Repository**.
1. Para el nombre del repositorio, use `getting-started`. Asegúrese de que la visibilidad sea `Public`.
1. ¡Haga clic en el botón **Create**!
Si miras en el lado derecho de la página, verás una sección llamada **Docker commands**. Esto proporciona
un comando de ejemplo que deberá ejecutar para enviarlo a este repositorio.
![Docker command with push example](push-command.png){: style=width:75% }
{: .text-center }
## Empujando nuestra imagen
1. En la línea de comandos, intente ejecutar el comando push que ve en Docker Hub. Tenga en cuenta que su comando
utilizará su namespace, no "docker".
```plaintext
$ docker push docker/getting-started
The push refers to repository [docker.io/docker/getting-started]
An image does not exist locally with the tag: docker/getting-started
```
¿Por qué falló? El comando push buscaba una imagen llamada docker/getting-started, pero
no encontró una. Si ejecuta `docker image ls`, tampoco verá ninguna.
Para solucionar este problema, necesitamos "etiquetar" nuestra imagen existente que hemos
creado para darle otro nombre.
1. Inicie sesión en Docker Hub usando el comando `docker login -u YOUR-USER-NAME`.
1. Use el comando `docker tag` para darle un nuevo nombre a la imagen de `getting-started`. Asegúrese de cambiar
`YOUR-USER-NAME` por su ID de Docker.
```bash
docker tag getting-started YOUR-USER-NAME/getting-started
```
1. Ahora intente su comando push de nuevo. Si está copiando el valor de Docker Hub, puede eliminar la parte
`tagname`, ya que no agregamos una etiqueta al nombre de la imagen. Si no especifica una etiqueta, Docker
usará una etiqueta llamada `latest`.
```bash
docker push YOUR-USER-NAME/getting-started
```
## Ejecutando nuestra imagen en una nueva instancia
Ahora que nuestra imagen se ha creado y enviado a un registry, ¡intentemos ejecutar nuestra aplicación en
una instancia nueva que nunca ha visto esta imagen de contenedor! Para hacer esto, usaremos Play with Docker.
1. Abra su navegador en [Play with Docker](http://play-with-docker.com).
1. Inicie sesión con su cuenta de Docker Hub.
1. Una vez que haya iniciado sesión, haga clic en el enlace "+ ADD NEW INSTANCE" en la barra lateral izquierda. (Si no lo ve, amplíe un poco su navegador). Después de unos segundos, se abrirá una ventana de terminal en su navegador.
![Play with Docker add new instance](pwd-add-new-instance.png){: style=width:75% }
{: .text-center }
1. En la terminal, inicie su aplicación recién lanzada.
```bash
docker run -dp 3000:3000 YOUR-USER-NAME/getting-started
```
¡Debería ver que la imagen se baja y finalmente se inicia!
1. Haga clic en la insignia 3000 cuando aparezca y debería ver la aplicación con sus modificaciones. ¡Hurra!
Si la insignia 3000 no aparece, puede hacer clic en el botón "Open Port" y escribir 3000.
## Resumen
En esta sección, aprendimos cómo compartir nuestras imágenes empujándolas a un registry. Luego fuimos a una
nueva instancia y pudimos ejecutar la imagen recién enviada. Esto es bastante común en las CI pipelines, donde
el pipeline creará la imagen y la enviará a un registry y luego el entorno de producción puede usar la última
versión de la imagen.
Ahora que lo hemos resuelto, volvamos a lo que notamos al final de la última sección. Como recordatorio, notamos
que cuando reiniciamos la aplicación, perdimos todos los elementos de nuestra lista de tareas pendientes. Obviamente,
esa no es una gran experiencia de usuario, ¡así que aprendamos cómo podemos conservar los datos durante los reinicios!

View File

@ -0,0 +1,114 @@
Como una pequeña solicitud de función, el equipo de producto nos ha pedido que cambiemos
el "texto vacío" cuando no tenemos ningún elemento de la lista de tareas pendientes. Les
gustaría hacer la transición a lo siguiente:
> You have no todo items yet! Add one above!
Bastante simple, ¿verdad? Hagamos el cambio.
## Actualización de nuestro código fuente
1. En el archivo `src/static/js/app.js`, actualice la línea 56 para usar el nuevo texto vacío.
```diff
- <p className="text-center">No items yet! Add one above!</p>
+ <p className="text-center">You have no todo items yet! Add one above!</p>
```
1. Construyamos nuestra versión actualizada de la imagen, usando el mismo comando que usamos antes.
```bash
docker build -t getting-started .
```
1. Comencemos un nuevo contenedor usando el código actualizado.
```bash
docker run -dp 3000:3000 getting-started
```
**Uh oh!** Probablemente vio un error como este (las ID serán diferentes):
```bash
docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell
(bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 0.0.0.0:3000 failed: port is already allocated.
```
¿Entonces qué pasó? No podemos iniciar el nuevo contenedor porque nuestro contenedor anterior todavía se está
ejecutando. La razón por la que esto es un problema es porque ese contenedor está usando el puerto 3000 del
host y solo un proceso en la máquina (contenedores incluidos) puede escuchar un puerto específico. Para solucionar
este problema, debemos quitar el contenedor antiguo.
## Reemplazo de nuestro contenedor viejo
Para quitar un contenedor, primero debe detenerse. Una vez que se haya detenido, se puede quitar. Tenemos dos
formas de quitar el contenedor viejo. Siéntase libre de elegir el camino con el que se sienta más cómodo.
### Eliminar un contenedor usando la CLI
1. Obtenga el ID del contenedor usando el comando `docker ps`.
```bash
docker ps
```
1. Utilice el comando `docker stop` para detener el contenedor.
```bash
# Cambie <the-container-id> con el ID de docker ps
docker stop <the-container-id>
```
1. Una vez que el contenedor se ha detenido, puede eliminarlo utilizando el comando `docker rm`.
```bash
docker rm <the-container-id>
```
!!! info "Pro tip"
Puede detener y eliminar un contenedor con un solo comando agregando el indicador "force"
al comando `docker rm`. Por ejemplo: `docker rm -f <the-container-id>`
### Eliminar un contenedor con el panel de Docker
Si abre el panel de Docker, puede eliminar un contenedor con dos clics. Sin duda,
es mucho más fácil que tener que buscar el ID del contenedor y eliminarlo.
1. Con el tablero abierto, coloque el cursor sobre el contenedor de la aplicación y verá aparecer
una colección de botones de acción a la derecha.
1. Haga clic en el icono de la papelera para eliminar el contenedor.
1. Confirma la eliminación y listo.
![Docker Dashboard - removing a container](dashboard-removing-container.png)
### Iniciando nuestro contenedor de aplicaciones actualizado
1. Ahora, inicie su aplicación actualizada.
```bash
docker run -dp 3000:3000 getting-started
```
1. Actualiza tu navegador a [http://localhost:3000](http://localhost:3000) ¡y debería ver su texto de ayuda actualizado!
![Updated application with updated empty text](todo-list-updated-empty-text.png){: style="width:55%" }
{: .text-center }
## Resumen
Si bien pudimos crear una actualización, es posible que haya notado dos cosas:
- ¡Todos los elementos existentes en nuestra lista de tareas pendientes se han ido! ¡Esa no es una muy buena aplicación!
Hablaremos de eso en breve.
- Hubo _muchos_ pasos involucrados para un cambio tan pequeño. En una próxima sección, hablaremos sobre cómo ver las
actualizaciones de código sin necesidad de reconstruir e iniciar un nuevo contenedor cada vez que hagamos un cambio.
Antes de hablar de persistencia, veremos rápidamente cómo compartir estas imágenes con otros.

View File

@ -0,0 +1,115 @@
En el capítulo anterior, hablamos y usamos un **named volume** para conservar los datos en nuestra base de datos.
Los named volumes son excelentes si simplemente queremos almacenar datos, ya que no tenemos que preocuparnos por
_dónde_ se almacenan los datos.
Con **bind mounts**, controlamos el punto de montaje exacto en el host. Podemos usar esto para conservar datos,
pero a menudo se usa para proporcionar datos adicionales en contenedores. Cuando trabajamos en una aplicación,
podemos usar un bind mount para montar nuestro código fuente en el contenedor para permitirle ver los cambios
de código, responder y ver los cambios de inmediato.
Para las aplicaciones basadas en Node, [nodemon](https://npmjs.com/package/nodemon) es una gran herramienta para
observar los cambios de archivos y luego reiniciar la aplicación. Existen herramientas equivalentes en la mayoría
de los otros lenguajes y frameworks.
## Comparaciones rápidas de tipos de volumen
Los bind mounts y named volumes son los dos tipos principales de volúmenes que vienen con el motor de Docker.
Sin embargo, hay controladores de volumen adicionales disponibles para admitir otros casos de uso ([SFTP](https://github.com/vieux/docker-volume-sshfs), [Ceph](https://ceph.com/geen-categorie/getting-started-with-the-docker-rbd-volume-plugin/), [NetApp](https://netappdvp.readthedocs.io/en/stable/), [S3](https://github.com/elementar/docker-s3-volume) y más).
| | Named Volumes | Bind Mounts |
| - | ------------- | ----------- |
| Ubicación del host | Docker elige | Tú controlas |
| Ejemplo de montaje (using `-v`) | my-volume:/usr/local/data | /path/to/data:/usr/local/data |
| Llena un nuevo volumen con el contenido del contenedor. | Si | No |
| Soporta controladores de volumen | Si | No |
## Iniciar un contenedor en modo de desarrollo
Para ejecutar nuestro contenedor para admitir un flujo de trabajo de desarrollo, haremos lo siguiente:
- Monte nuestro código fuente en el contenedor
- Instale todas las dependencias, incluidas las dependencias "dev"
- Inicie nodemon para observar los cambios en el sistema de archivos
¡Hagamoslo!
1. Asegúrese de no tener ningún contenedor de `getting-started` en ejecución.
1. Ejecute el siguiente comando. Explicaremos lo que está pasando después:
```bash
docker run -dp 3000:3000 \
-w /app -v "$(pwd):/app" \
node:12-alpine \
sh -c "yarn install && yarn run dev"
```
Si está utilizando PowerShell, utilice este comando.
```powershell
docker run -dp 3000:3000 `
-w /app -v "$(pwd):/app" `
node:12-alpine `
sh -c "yarn install && yarn run dev"
```
- `-dp 3000:3000` - igual que antes. Ejecuta en modo independiente (en segundo plano) y crea un mapeo de puertos
- `-w /app` - establece el "directorio de trabajo" o el directorio actual desde el que se ejecutará el comando
- `-v "$(pwd):/app"` - enlaza la montura al directorio actual desde el host en el contenedor en el directorio `app`
- `node:12-alpine` - la imagen a usar. Tenga en cuenta que esta es la imagen base para nuestra aplicación del Dockerfile
- `sh -c "yarn install && yarn run dev"` - el comando. Estamos iniciando un shell usando `sh` (alpine no tiene` bash`)
y ejecutando `yarn install` para instalar _todas_ las dependencias y luego ejecutando `yarn run dev`. Si miramos
en el `package.json`, veremos que el script `dev` está iniciando `nodemon`.
1. Puede ver los registros usando `docker logs -f <container-id>`. Sabrá que está listo para comenzar cuando vea esto ...
```bash
docker logs -f <container-id>
$ nodemon src/index.js
[nodemon] 1.19.2
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): *.*
[nodemon] starting `node src/index.js`
Using sqlite database at /etc/todos/todo.db
Listening on port 3000
```
Cuando haya terminado de ver los registros, salga presionando `Ctrl`+`C`.
1. Ahora, hagamos un cambio en la aplicación. En el archivo `src/static/js/app.js`, cambiemos el botón "Add Item" para que
simplemente diga "Add". Este cambio estará en la línea 109.
```diff
- {submitting ? 'Adding...' : 'Add Item'}
+ {submitting ? 'Adding...' : 'Add'}
```
1. Simplemente actualice la página (o ábrala) y debería ver el cambio reflejado en el navegador casi de inmediato. Es
posible que el servidor Node demore unos segundos en reiniciarse, por lo que si obtiene un error, intente actualizar
después de unos segundos.
![Screenshot of updated label for Add button](updated-add-button.png){: style="width:75%;"}
{: .text-center }
1. No dude en realizar cualquier otro cambio que desee. Cuando haya terminado, detenga el contenedor y cree su nueva
imagen usando `docker build -t Getting-started .`.
El uso de bind mounts es _muy_ común para las configuraciones de desarrollo local. La ventaja es que la máquina de
desarrollo no necesita tener instaladas todas las herramientas y entornos de compilación. Con un solo comando
"docker run", el entorno de desarrollo se extrae y está listo para funcionar. Hablaremos de Docker Compose en un paso
futuro, ya que esto ayudará a simplificar nuestros comandos (ya tenemos muchas banderas en nuestros comandos).
## Resumen
En este punto, podemos conservar nuestra base de datos y responder rápidamente a las necesidades y demandas de nuestros
inversores y fundadores. ¡Hurra! ¿Pero adivina que? ¡Recibimos buenas noticias!
**¡Su proyecto ha sido seleccionado para un futuro desarrollo!**
Para prepararnos para producción, necesitamos migrar nuestra base de datos de trabajar en SQLite a algo que pueda
escalar un poco mejor. Para simplificar, mantendremos una base de datos relacional y cambiaremos nuestra aplicación
para usar MySQL. Pero, ¿cómo deberíamos ejecutar MySQL? ¿Cómo permitimos que los contenedores se comuniquen entre sí?
¡Hablaremos de eso a continuación!

View File

@ -0,0 +1,360 @@
[Docker Compose](https://docs.docker.com/compose/) es una herramienta que se desarrolló para ayudar a definir y compartir
aplicaciones de varios contenedores. Con Compose, podemos crear un archivo YAML para definir los servicios y con un solo
comando, podemos hacer girar todo o destruirlo todo.
La gran ventaja de usar Compose es que puede definir su pila de aplicaciones en un archivo, mantenerlo en la raíz del
repositorio de su proyecto (ahora está controlado por la versión) y permitir fácilmente que otra persona contribuya a
su proyecto. Alguien solo necesitaría clonar su repositorio e iniciar la aplicación de redacción. De hecho, es posible
que veas bastantes proyectos en GitHubGitLab haciendo exactamente esto ahora.
Entonces, ¿cómo empezamos?
## Instalación de Docker Compose
Si instaló Docker DesktopToolbox para Windows o Mac, ¡ya tiene Docker Compose! Las instancias de Play-with-Docker
también tienen Docker Compose instalado. Si está en una máquina Linux, deberá instalar Docker Compose usando
[las instrucciones aquí](https://docs.docker.com/compose/install/).
Después de la instalación, debería poder ejecutar lo siguiente y ver la información de la versión.
```bash
docker-compose version
```
## Creando nuestro Compose File
1. En la raíz del proyecto de la aplicación, cree un archivo llamado `docker-compose.yml`.
1. En el archivo de redacción, comenzaremos definiendo la versión del esquema. En la mayoría de los casos, es mejor
utilizar la última versión compatible. Puede consultar la
[Referencia a Compose file](https://docs.docker.com/compose/compose-file/) para ver las versiones actuales
del esquema y la matriz de compatibilidad.
```yaml
version: "3.7"
```
1. A continuación, definiremos la lista de servicios (o contenedores) que queremos ejecutar como parte de nuestra aplicación.
```yaml hl_lines="3"
version: "3.7"
services:
```
Y ahora, comenzaremos a migrar un servicio a la vez en el compose file.
## Definición de la App Service
Para recordar, este era el comando que estábamos usando para definir nuestro contenedor de aplicaciones.
```bash
docker run -dp 3000:3000 \
-w /app -v "$(pwd):/app" \
--network todo-app \
-e MYSQL_HOST=mysql \
-e MYSQL_USER=root \
-e MYSQL_PASSWORD=secret \
-e MYSQL_DB=todos \
node:12-alpine \
sh -c "yarn install && yarn run dev"
```
Si está utilizando PowerShell, utilice este comando.
```powershell
docker run -dp 3000:3000 `
-w /app -v "$(pwd):/app" `
--network todo-app `
-e MYSQL_HOST=mysql `
-e MYSQL_USER=root `
-e MYSQL_PASSWORD=secret `
-e MYSQL_DB=todos `
node:12-alpine `
sh -c "yarn install && yarn run dev"
```
1. Primero, definamos la entrada de servicio y la imagen del contenedor. Podemos elegir cualquier nombre para el servicio.
El nombre se convertirá automáticamente en un alias de red, que será útil a la hora de definir nuestro servicio MySQL.
```yaml hl_lines="4 5"
version: "3.7"
services:
app:
image: node:12-alpine
```
1. Normalmente, verá el comando cerca de la definición de la `image`, aunque no hay ningún requisito para realizar el pedido.
Entonces, sigamos adelante y traslademos eso a nuestro archivo.
```yaml hl_lines="6"
version: "3.7"
services:
app:
image: node:12-alpine
command: sh -c "yarn install && yarn run dev"
```
1. Migremos la parte `-p 3000: 3000` del comando definiendo los `ports` para el servicio.
Usaremos la [sintaxis corta](https://docs.docker.com/compose/compose-file/#short-syntax-1) aquí, pero también hay una
[sintaxis larga](https://docs.docker.com/compose/compose-file/#long-syntax-1) más detallada disponible también.
```yaml hl_lines="7 8"
version: "3.7"
services:
app:
image: node:12-alpine
command: sh -c "yarn install && yarn run dev"
ports:
- 3000:3000
```
1. A continuación, migraremos tanto el directorio de trabajo (`-w app`) como el mapeo de volumen (`-v "$(pwd):/app"`)
usando las definiciones `working_dir` y `volumes`. Los volúmenes también tienen una sintaxis [corta](https://docs.docker.com/compose/compose-file/#short-syntax-3) y [larga](https://docs.docker.com/compose/compose-file/#long-syntax-3).
Una ventaja de las definiciones de volumen de Docker Compose es que podemos usar rutas relativas del directorio actual.
```yaml hl_lines="9 10 11"
version: "3.7"
services:
app:
image: node:12-alpine
command: sh -c "yarn install && yarn run dev"
ports:
- 3000:3000
working_dir: /app
volumes:
- ./:/app
```
1. Finalmente, necesitamos migrar las definiciones de variables de entorno usando la clave `environment`.
```yaml hl_lines="12 13 14 15 16"
version: "3.7"
services:
app:
image: node:12-alpine
command: sh -c "yarn install && yarn run dev"
ports:
- 3000:3000
working_dir: /app
volumes:
- ./:/app
environment:
MYSQL_HOST: mysql
MYSQL_USER: root
MYSQL_PASSWORD: secret
MYSQL_DB: todos
```
### Definición del servicio MySQL
Ahora es el momento de definir el servicio MySQL. El comando que usamos para ese contenedor fue el siguiente:
```bash
docker run -d \
--network todo-app --network-alias mysql \
-v todo-mysql-data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=secret \
-e MYSQL_DATABASE=todos \
mysql:5.7
```
Si está utilizando PowerShell, utilice este comando.
```powershell
docker run -d `
--network todo-app --network-alias mysql `
-v todo-mysql-data:/var/lib/mysql `
-e MYSQL_ROOT_PASSWORD=secret `
-e MYSQL_DATABASE=todos `
mysql:5.7
```
1. Primero definiremos el nuevo servicio y lo llamaremos `mysql` para que automáticamente obtenga el alias de la red.
Continuaremos y especificaremos la imagen a usar también.
```yaml hl_lines="6 7"
version: "3.7"
services:
app:
# The app service definition
mysql:
image: mysql:5.7
```
1. A continuación, definiremos el mapeo de volumen. Cuando ejecutamos el contenedor con `docker run`, el named volume
se creó automáticamente. Sin embargo, eso no sucede cuando se ejecuta con Compose. Necesitamos definir el volumen
en la sección `volúmenes:` de nivel superior y luego especificar el punto de montaje en la configuración del servicio.
Simplemente proporcionando solo el nombre del volumen, se utilizan las opciones predeterminadas.
Sin embargo, hay [muchas más opciones disponibles](https://docs.docker.com/compose/compose-file/#volume-configuration-reference).
```yaml hl_lines="8 9 10 11 12"
version: "3.7"
services:
app:
# The app service definition
mysql:
image: mysql:5.7
volumes:
- todo-mysql-data:/var/lib/mysql
volumes:
todo-mysql-data:
```
1. Finalmente, solo necesitamos especificar las variables de entorno.
```yaml hl_lines="10 11 12"
version: "3.7"
services:
app:
# The app service definition
mysql:
image: mysql:5.7
volumes:
- todo-mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: todos
volumes:
todo-mysql-data:
```
En este punto, nuestro `docker-compose.yml` completo debería verse así:
```yaml
version: "3.7"
services:
app:
image: node:12-alpine
command: sh -c "yarn install && yarn run dev"
ports:
- 3000:3000
working_dir: /app
volumes:
- ./:/app
environment:
MYSQL_HOST: mysql
MYSQL_USER: root
MYSQL_PASSWORD: secret
MYSQL_DB: todos
mysql:
image: mysql:5.7
volumes:
- todo-mysql-data:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: secret
MYSQL_DATABASE: todos
volumes:
todo-mysql-data:
```
## Ejecutando nuestra pila de aplicaciones
Ahora que tenemos nuestro archivo `docker-compose.yml`, ¡podemos iniciarlo!
1. Asegúrese de que no se estén ejecutando primero otras copias de la appdb (`docker ps` y `docker rm -f <ids>`).
1. Inicie la pila de aplicaciones usando el comando `docker-compose up`. Agregaremos la bandera `-d` para ejecutar todo en segundo plano.
```bash
docker-compose up -d
```
Cuando ejecutamos esto, deberíamos ver un resultado como este:
```plaintext
Creating network "app_default" with the default driver
Creating volume "app_todo-mysql-data" with default driver
Creating app_app_1 ... done
Creating app_mysql_1 ... done
```
¡Notará que el volumen fue creado además de una red! De forma predeterminada, Docker Compose crea automáticamente
una red específicamente para el stack de aplicaciones (razón por la cual no definimos una en el archivo de composición).
1. Veamos los logs usando el comando `docker-compose logs -f`. Verá los logs de cada uno de los servicios intercalados
en un solo flujo. Esto es increíblemente útil cuando desea estar atento a problemas relacionados con el tiempo. La
bandera `-f` "sigue" el log, por lo que le dará una salida en vivo a medida que se genere.
Si aún no lo ha hecho, verá un resultado que se ve así ...
```plaintext
mysql_1 | 2019-10-03T03:07:16.083639Z 0 [Note] mysqld: ready for connections.
mysql_1 | Version: '5.7.27' socket: '/var/run/mysqld/mysqld.sock' port: 3306 MySQL Community Server (GPL)
app_1 | Connected to mysql db at host mysql
app_1 | Listening on port 3000
```
El nombre del servicio se muestra al principio de la línea (a menudo de color) para ayudar a distinguir los mensajes.
Si desea ver los registros de un servicio específico, puede agregar el nombre del servicio al final del comando de logs
(por ejemplo, `docker-compose logs -f app`).
!!! info "Consejo profesional: esperar la base de datos antes de iniciar la aplicación"
Cuando la aplicación se inicia, en realidad se sienta y espera a que MySQL esté listo y listo antes de intentar
conectarse. Docker no tiene ningún soporte integrado para esperar a que otro contenedor esté completamente en
funcionamiento, en ejecución y listo antes de iniciar otro contenedor. Para proyectos Node-based, puede usar la
dependencia [wait-port](https://github.com/dwmkerr/wait-port). Existen proyectos similares para marcos de otros
lenguajes.
1. En este punto, debería poder abrir su aplicación y verla en ejecución. ¡Y oye! ¡Estamos en un solo comando!
## Ver nuestro stack de aplicaciones en el Dashboard Docker
Si miramos el panel de Docker, veremos que hay un grupo llamado **app**. Este es el "nombre del proyecto" de Docker Compose
y se utiliza para agrupar los contenedores. Por defecto, el nombre del proyecto es simplemente el nombre del directorio
en el que se encontraba el `docker-compose.yml`.
![Dashboard Docker con proyecto app](dashboard-app-project-collapsed.png)
Si gira la aplicación, verá los dos contenedores que definimos en el compose file. Los nombres también son un poco más
descriptivos., ya que siguen el patrón de `<project-name>_<service-name>_<replica-number>`. Por lo tanto, es muy fácil
ver rápidamente qué contenedor es nuestra aplicación y qué contenedor es la base de datos mysql.
![Dashboard Docker con proyecto app ampliado](dashboard-app-project-expanded.png)
## Derribarlo todo
Cuando esté listo para derribarlo todo, simplemente ejecute `docker-compose down` o presione la papelera en el Docker
Dashboard para toda la aplicación. Los contenedores se detendrán y la red se eliminará.
!!! warning "Removing Volumes"
De forma predeterminada, los named volumes en su compose file NO se eliminan cuando se ejecuta `docker-compose down`.
Si desea eliminar los volúmenes, deberá agregar la marca `--volumes`.
El panel de Docker _no_ elimina volúmenes cuando elimina el stack de aplicaciones.
Una vez derribado, puede cambiar a otro proyecto, ejecutar `docker-compose up` y estar listo para contribuir a ese proyecto.
¡Realmente no hay nada más simple que eso!
## Resumen
En esta sección, aprendimos sobre Docker Compose y cómo nos ayuda a simplificar drásticamente la definición y
el uso compartido de aplicaciones multiservicio. Creamos un archivo de redacción traduciendo los comandos que
estábamos usando al formato de redacción apropiado.
En este punto, comenzamos a concluir el tutorial. Sin embargo, existen algunas mejores prácticas sobre
la creación de imágenes que queremos cubrir, ya que hay un gran problema con el Dockerfile que hemos
estado usando. Entonces, ¡echemos un vistazo!

View File

@ -0,0 +1,26 @@
Aunque hemos terminado con nuestro taller, ¡todavía hay MUCHO más que aprender sobre los contenedores!
No vamos a profundizar aquí, ¡pero aquí hay algunas otras áreas para ver a continuación!
## Orquestación de contenedores
Running containers in production is tough. No desea iniciar sesión en una máquina y simplemente ejecutar un `docker run`
o un` docker-compose up`. ¿Por qué no? Bueno, ¿qué pasa si los contenedores mueren? ¿Cómo escalas en varias máquinas? La
orquestación de contenedores resuelve este problema. Herramientas como Kubernetes, Swarm, Nomad y ECS ayudan a resolver
este problema, todas de formas ligeramente diferentes.
La idea general es que tienes "managers" que reciben el **estado esperado**. Este estado podría ser "Quiero ejecutar dos
instancias de mi aplicación web y exponer el puerto 80". Luego, los managers examinan todas las máquinas del clúster y
delegan el trabajo a los nodos "worker". Los managers observan los cambios (como la salida de un contenedor) y luego
trabajan para que el **actual state** refleje el estado esperado.
## Proyectos de la Cloud Native Computing Foundation
El CNCF es un hogar vendor-neutral para varios proyectos de código abierto, incluidos Kubernetes, Prometheus, Envoy,
Linkerd, NATS y más! Puede ver los [proyectos graduados e incubados aquí](https://www.cncf.io/projects/) y el
[Landscape CNCF aquí](https://landscape.cncf.io/). ¡Hay MUCHOS proyectos para ayudar a resolver problemas relacionados
con el monitoreo, logging, la seguridad, image registries, la mensajería y más!
Por lo tanto, si es nuevo en el landscape de contenedores y el desarrollo de aplicaciones cloud-native, ¡bienvenido!
¡Conéctese con la comunidad, haga preguntas y siga aprendiendo! ¡Estamos emocionados de tenerte!

View File

@ -33,11 +33,20 @@ plugins:
- search
- minify:
minify_html: true
- redirects:
redirect_maps:
'index.md': 'tutorial/index.md'
- i18n:
languages:
es: "Español"
en: "English"
default_language: 'en'
translate_nav:
# Customization
extra:
social:
- type: github-alt
- icon: fontawesome/brands/github-alt
link: https://github.com/docker/getting-started
# Extensions
@ -69,6 +78,7 @@ markdown_extensions:
# Page tree
nav:
# English
- Getting Started: tutorial/index.md
- Our Application: tutorial/our-application/index.md
- Updating our App: tutorial/updating-our-app/index.md
@ -79,3 +89,14 @@ nav:
- Using Docker Compose: tutorial/using-docker-compose/index.md
- Image Building Best Practices: tutorial/image-building-best-practices/index.md
- What Next?: tutorial/what-next/index.md
# Spanish
- Empezando: tutorial/index.es.md
- Nuestra Aplicación: tutorial/our-application/index.es.md
- Actualizando nuestra Aplicación: tutorial/updating-our-app/index.es.md
- Compartiendo nuestra Aplicación: tutorial/sharing-our-app/index.es.md
- Persistiendo nuestra DB: tutorial/persisting-our-data/index.es.md
- Uso de Bind Mounts: tutorial/using-bind-mounts/index.es.md
- Aplicaciones Multi-Container: tutorial/multi-container-apps/index.es.md
- Uso de Docker Compose: tutorial/using-docker-compose/index.es.md
- Mejores Prácticas para Creación de Imágen: tutorial/image-building-best-practices/index.es.md
- ¿Qué sigue?: tutorial/what-next/index.es.md

View File

@ -1,5 +1,8 @@
mkdocs==1.0.4
mkdocs-material==4.6.3
mkdocs==1.1.2
mkdocs-material==7.1.0
mkdocs-minify-plugin==0.2.3
pygments==2.7.4
pymdown-extensions==7.0
mkdocs-translations==0.1.1
mkdocs-redirects==1.0.1
mkdocs-i18n==0.3.0