Compartir comentarios
Las respuestas se generan en base a la documentación.

Utiliza contenedores para el desarrollo con Go

Requisitos previos

Completa los pasos del módulo ejecuta tu imagen como un contenedor para aprender a administrar el ciclo de vida de tus contenedores.

Introducción

En este módulo, verás cómo ejecutar un motor de base de datos en un contenedor y conectarlo a la versión extendida de la aplicación de ejemplo. Verás algunas opciones para mantener datos persistentes y para conectar los contenedores para que se comuniquen entre sí. Finalmente, aprenderás a usar Docker Compose para administrar de manera efectiva estos entornos de desarrollo local multi-contenedor.

Base de datos local y contenedores

El motor de base de datos que vas a utilizar se llama CockroachDB. Es una base de datos SQL distribuida, moderna y nativa de la nube.

En lugar de compilar CockroachDB a partir del código fuente o usar el gestor de paquetes nativo de tu sistema operativo para instalarlo, vas a usar la imagen de Docker para CockroachDB y la ejecutarás en un contenedor.

CockroachDB es compatible con PostgreSQL en gran medida y comparte muchas convenciones con este último, particularmente los nombres predeterminados para las variables de entorno. Por lo tanto, si estás familiarizado con Postgres, no te sorprendas si ves algunos nombres de variables de entorno familiares. Los módulos de Go que funcionan con Postgres, como pgx, pq, GORM y upper/db también funcionan con CockroachDB.

Para obtener más información sobre la relación entre Go y CockroachDB, consulta la documentación de CockroachDB, aunque esto no es necesario para continuar con esta guía.

Almacenamiento

El propósito de una base de datos es tener un almacenamiento persistente de datos. Los Volúmenes son el mecanismo preferido para persistir datos generados y utilizados por los contenedores de Docker. Por lo tanto, antes de iniciar CockroachDB, crea el volumen para él.

Para crear un volumen administrado, ejecuta:

$ docker volume create roach
roach

Puedes ver la lista de todos los volúmenes administrados en tu instancia de Docker con el siguiente comando:

$ docker volume list
DRIVER    VOLUME NAME
local     roach

Redes (Networking)

La aplicación de ejemplo y el motor de base de datos se comunicarán entre sí a través de la red. Son posibles diferentes tipos de configuración de red, y vas a utilizar lo que se llama una red puente definida por el usuario (user-defined bridge network). Esto te proporcionará un servicio de resolución de DNS para que puedas hacer referencia al contenedor del motor de base de datos por su nombre de host (hostname).

El siguiente comando crea una nueva red puente llamada mynet:

$ docker network create -d bridge mynet
51344edd6430b5acd121822cacc99f8bc39be63dd125a3b3cd517b6485ab7709

Al igual que con los volúmenes administrados, existe un comando para listar todas las redes configuradas en tu instancia de Docker:

$ docker network list
NETWORK ID     NAME          DRIVER    SCOPE
0ac2b1819fa4   bridge        bridge    local
51344edd6430   mynet         bridge    local
daed20bbecce   host          host      local
6aee44f40a39   none          null      local

Tu red puente mynet se ha creado correctamente. Las otras tres redes, llamadas bridge, host y none, son las redes predeterminadas y fueron creadas por el propio Docker. Aunque no es relevante para esta guía, puedes aprender más sobre las redes de Docker en la sección de introducción a las redes de Docker.

Elige buenos nombres para volúmenes y redes

Como dice el refrán, solo hay dos cosas difíciles en las Ciencias de la Computación: la invalidación de caché y nombrar las cosas. Y los errores por uno (off-by-one errors).

Al elegir un nombre para una red o un volumen administrado, lo mejor es elegir un nombre que sea indicativo del propósito previsto. Esta guía busca la brevedad, por lo que utiliza nombres cortos y genéricos.

Inicia el motor de base de datos

Ahora que las tareas de preparación están listas, puedes ejecutar CockroachDB en un contenedor y conectarlo al volumen y la red que acabas de crear. Cuando ejecutes el siguiente comando, Docker descargará la imagen desde Docker Hub y la ejecutará localmente por ti:

$ docker run -d \
  --name roach \
  --hostname db \
  --network mynet \
  -p 26257:26257 \
  -p 8080:8080 \
  -v roach:/cockroach/cockroach-data \
  cockroachdb/cockroach:latest-v25.4 start-single-node \
  --insecure

# ... output omitted ...

Observa el uso inteligente de la etiqueta latest-v25.4 para asegurarte de que estás descargando la última versión de parche de 25.4. La diversidad de etiquetas disponibles depende del mantenedor de la imagen. Aquí, tu intención era tener la última versión corregida de CockroachDB sin alejarte demasiado de la versión de trabajo conocida a medida que pasa el tiempo. Para ver las etiquetas disponibles para la imagen de CockroachDB, puedes ir a la página de CockroachDB en Docker Hub.

Configura el motor de base de datos

Ahora que el motor de base de datos está activo, hay algunas configuraciones que hacer antes de que tu aplicación pueda comenzar a usarlo. Afortunadamente, no es mucho. Debes:

  1. Crear una base de datos vacía.
  2. Registrar una nueva cuenta de usuario en el motor de base de datos.
  3. Otorgar a ese nuevo usuario derechos de acceso a la base de datos.

Puedes hacerlo con la ayuda de la terminal SQL integrada de CockroachDB. Para iniciar la terminal SQL en el mismo contenedor donde se está ejecutando el motor de base de datos, escribe:

$ docker exec -it roach ./cockroach sql --insecure
  1. En la terminal SQL, crea la base de datos que va a utilizar la aplicación de ejemplo:

    CREATE DATABASE mydb;
  2. Registra una nueva cuenta de usuario SQL en el motor de base de datos. Utiliza el nombre de usuario totoro.

    CREATE USER totoro;
  3. Dale al nuevo usuario los permisos necesarios:

    GRANT ALL ON DATABASE mydb TO totoro;
  4. Escribe quit para salir de la terminal.

El siguiente es un ejemplo de interacción con la terminal SQL.

$ sudo docker exec -it roach ./cockroach sql --insecure
#
# Bienvenido a la terminal SQL de CockroachDB.
# Todas las sentencias deben terminar con un punto y coma.
# Para salir, escribe: \q.
#
# Server version: CockroachDB CCL v20.1.15 (x86_64-unknown-linux-gnu, built 2021/04/26 16:11:58, go1.13.9) (same version as client)
# Cluster ID: 7f43a490-ccd6-4c2a-9534-21f393ca80ce
#
# Enter \? for a brief introduction.
#
root@:26257/defaultdb> CREATE DATABASE mydb;
CREATE DATABASE

Time: 22.985478ms

root@:26257/defaultdb> CREATE USER totoro;
CREATE ROLE

Time: 13.921659ms

root@:26257/defaultdb> GRANT ALL ON DATABASE mydb TO totoro;
GRANT

Time: 14.217559ms

root@:26257/defaultdb> quit
oliver@hki:~$

Conoce la aplicación de ejemplo

Ahora que has iniciado y configurado el motor de base de datos, puedes dirigir tu atención a la aplicación.

La aplicación de ejemplo para este módulo es una versión extendida de la aplicación docker-gs-ping que has utilizado en los módulos anteriores. Tienes dos opciones:

  • Puedes actualizar tu copia local de docker-gs-ping para que coincida con la nueva versión extendida presentada en este capítulo; o
  • Puedes clonar el repositorio docker/docker-gs-ping-dev. Este último enfoque es el recomendado.

Para descargar la aplicación de ejemplo, ejecuta:

$ git clone https://github.com/docker/docker-gs-ping-dev.git
# ... output omitted ...

El archivo main.go de la aplicación ahora incluye código de inicialización de la base de datos, así como el código para implementar un nuevo requisito de negocio:

  • Una solicitud HTTP POST a /send que contenga un JSON { "value" : string } debe guardar el valor en la base de datos.

También tienes una actualización para otro requisito de negocio. El requisito era:

  • La aplicación responde con un mensaje de texto que contiene un símbolo de corazón ("<3") a las solicitudes a /.

Y ahora va a ser:

  • La aplicación responde con la cadena que contiene el recuento de mensajes almacenados en la base de datos, encerrado entre paréntesis.

    Ejemplo de salida: Hello, Docker! (7)

A continuación se muestra el listado completo del código fuente de main.go.

package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/cenkalti/backoff/v4"
	"github.com/cockroachdb/cockroach-go/v2/crdb"
	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

func main() {

	e := echo.New()

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	db, err := initStore()
	if err != nil {
		log.Fatalf("failed to initialize the store: %s", err)
	}
	defer db.Close()

	e.GET("/", func(c echo.Context) error {
		return rootHandler(db, c)
	})

	e.GET("/ping", func(c echo.Context) error {
		return c.JSON(http.StatusOK, struct{ Status string }{Status: "OK"})
	})

	e.POST("/send", func(c echo.Context) error {
		return sendHandler(db, c)
	})

	httpPort := os.Getenv("HTTP_PORT")
	if httpPort == "" {
		httpPort = "8080"
	}

	e.Logger.Fatal(e.Start(":" + httpPort))
}

type Message struct {
	Value string `json:"value"`
}

func initStore() (*sql.DB, error) {

	pgConnString := fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s sslmode=disable",
		os.Getenv("PGHOST"),
		os.Getenv("PGPORT"),
		os.Getenv("PGDATABASE"),
		os.Getenv("PGUSER"),
		os.Getenv("PGPASSWORD"),
	)

	var (
		db  *sql.DB
		err error
	)
	openDB := func() error {
		db, err = sql.Open("postgres", pgConnString)
		return err
	}

	err = backoff.Retry(openDB, backoff.NewExponentialBackOff())
	if err != nil {
		return nil, err
	}

	if _, err := db.Exec(
		"CREATE TABLE IF NOT EXISTS message (value TEXT PRIMARY KEY)"); err != nil {
		return nil, err
	}

	return db, nil
}

func rootHandler(db *sql.DB, c echo.Context) error {
	r, err := countRecords(db)
	if err != nil {
		return c.HTML(http.StatusInternalServerError, err.Error())
	}
	return c.HTML(http.StatusOK, fmt.Sprintf("Hello, Docker! (%d)\n", r))
}

func sendHandler(db *sql.DB, c echo.Context) error {

	m := &Message{}

	if err := c.Bind(m); err != nil {
		return c.JSON(http.StatusInternalServerError, err)
	}

	err := crdb.ExecuteTx(context.Background(), db, nil,
		func(tx *sql.Tx) error {
			_, err := tx.Exec(
				"INSERT INTO message (value) VALUES ($1) ON CONFLICT (value) DO UPDATE SET value = excluded.value",
				m.Value,
			)
			if err != nil {
				return c.JSON(http.StatusInternalServerError, err)
			}
			return nil
		})

	if err != nil {
		return c.JSON(http.StatusInternalServerError, err)
	}

	return c.JSON(http.StatusOK, m)
}

func countRecords(db *sql.DB) (int, error) {

	rows, err := db.Query("SELECT COUNT(*) FROM message")
	if err != nil {
		return 0, err
	}
	defer rows.Close()

	count := 0
	for rows.Next() {
		if err := rows.Scan(&count); err != nil {
			return 0, err
		}
		rows.Close()
	}

	return count, nil
}

El repositorio también incluye el Dockerfile, que es casi exactamente el mismo que el Dockerfile multi-etapa presentado en los módulos anteriores. Utiliza la imagen oficial de Docker para Go para construir la aplicación y luego construye la imagen final colocando el binario compilado en la imagen distroless, mucho más delgada.

Independientemente de si actualizaste la aplicación de ejemplo anterior o descargaste la nueva, esta nueva imagen de Docker debe construirse para reflejar los cambios en el código fuente de la aplicación.

Construye la aplicación

Puedes construir la imagen con el conocido comando build:

$ docker build --tag docker-gs-ping-roach .

Ejecuta la aplicación

Ahora, ejecuta tu contenedor. Esta vez necesitarás configurar algunas variables de entorno para que tu aplicación sepa cómo acceder a la base de datos. Por ahora, lo harás directamente en el comando docker run. Más adelante verás un método más conveniente con Docker Compose.

Note

Dado que estás ejecutando tu clúster de CockroachDB en modo inseguro (insecure), el valor de la contraseña puede ser cualquier cosa.

En producción, no lo ejecutes en modo inseguro.

$ docker run -it --rm -d \
  --network mynet \
  --name rest-server \
  -p 80:8080 \
  -e PGUSER=totoro \
  -e PGPASSWORD=myfriend \
  -e PGHOST=db \
  -e PGPORT=26257 \
  -e PGDATABASE=mydb \
  docker-gs-ping-roach

Hay algunos puntos a tener en cuenta sobre este comando:

  • Esta vez mapeas el puerto del contenedor 8080 al puerto del host 80. Por lo tanto, para las solicitudes GET puedes usar literalmente curl localhost:

    $ curl localhost
    Hello, Docker! (0)
    

    O, si lo prefieres, una URL completa funcionará igual de bien:

    $ curl http://localhost/
    Hello, Docker! (0)
    
  • El número total de mensajes almacenados es 0 por ahora. Esto está bien, porque aún no has enviado nada a tu aplicación.

  • Haces referencia al contenedor de la base de datos por su nombre de host (hostname), que es db. Es por eso que usaste --hostname db cuando iniciaste el contenedor de la base de datos.

  • La contraseña real no importa, pero debe configurarse con algún valor para evitar confundir a la aplicación de ejemplo.

  • El contenedor que acabas de ejecutar se llama rest-server. Estos nombres son útiles para administrar el ciclo de vida del contenedor:

    # No hagas esto todavía, es solo un ejemplo:
    $ docker container rm --force rest-server
    

Prueba la aplicación

En la sección anterior, ya probaste realizar consultas a tu aplicación con GET y devolvió cero para el contador de mensajes almacenados. Ahora, envíale algunos mensajes:

$ curl --request POST \
  --url http://localhost/send \
  --header 'content-type: application/json' \
  --data '{"value": "Hello, Docker!"}'

La aplicación responde con el contenido del mensaje, lo que significa que se ha guardado en la base de datos:

{ "value": "Hello, Docker!" }

Envía otro mensaje:

$ curl --request POST \
  --url http://localhost/send \
  --header 'content-type: application/json' \
  --data '{"value": "Hello, Oliver!"}'

Y de nuevo, obtienes el valor del mensaje de vuelta:

{ "value": "Hello, Oliver!" }

Ejecuta curl y mira qué dice el contador de mensajes:

$ curl localhost
Hello, Docker! (2)

En este ejemplo, enviaste dos mensajes y la base de datos los guardó. ¿O no? Detén y elimina todos tus contenedores, pero no los volúmenes, e inténtalo de nuevo.

Primero, detén los contenedores:

$ docker container stop rest-server roach
rest-server
roach

Luego, elimínalos:

$ docker container rm rest-server roach
rest-server
roach

Verifica que se hayan ido:

$ docker container list --all
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

Y arráncalos de nuevo, primero la base de datos:

$ docker run -d \
  --name roach \
  --hostname db \
  --network mynet \
  -p 26257:26257 \
  -p 8080:8080 \
  -v roach:/cockroach/cockroach-data \
  cockroachdb/cockroach:latest-v25.4 start-single-node \
  --insecure

Y el servicio a continuación:

$ docker run -it --rm -d \
  --network mynet \
  --name rest-server \
  -p 80:8080 \
  -e PGUSER=totoro \
  -e PGPASSWORD=myfriend \
  -e PGHOST=db \
  -e PGPORT=26257 \
  -e PGDATABASE=mydb \
  docker-gs-ping-roach

Por último, consulta tu servicio:

$ curl localhost
Hello, Docker! (2)

¡Genial! El recuento de registros de la base de datos es correcto, a pesar de que no solo detuviste los contenedores, sino que también los eliminaste antes de iniciar nuevas instancias. La diferencia está en el volumen administrado para CockroachDB, el cual reutilizaste. El nuevo contenedor de CockroachDB ha leído los archivos de la base de datos del disco, tal como lo haría normalmente si se ejecutara fuera del contenedor.

Finaliza todo

Recuerda que estás ejecutando CockroachDB en modo inseguro. Ahora que has construido y probado tu aplicación, es hora de apagar todo antes de continuar. Puedes listar los contenedores que estás ejecutando con el comando list (o docker ps):

$ docker container list

Ahora que conoces los IDs de los contenedores, puedes usar docker container stop y docker container rm, como se demostró en los módulos anteriores.

Detén los contenedores de CockroachDB y docker-gs-ping-roach antes de continuar.

Mayor productividad con Docker Compose

En este punto, te estarás preguntando si hay alguna manera de evitar tener que lidiar con largas listas de argumentos para el comando docker. El ejemplo de juguete que utilizaste en esta serie requiere esa variables de entorno para definir la conexión a la base de datos. Una aplicación real podría necesitar muchas, muchas más. Luego está la cuestión de las dependencias. Idealmente, quieres asegurarte de que la base de datos se inicie antes de que se ejecute tu aplicación. Y levantar la instancia de la base de datos puede requerir otro comando de Docker con muchas opciones. Pero hay una mejor manera de orquestar estos despliegues para fines de desarrollo local.

En esta sección, crearás un archivo de Docker Compose para iniciar tu aplicación docker-gs-ping-roach y el motor de base de datos CockroachDB con un solo comando.

Configura Docker Compose

En el directorio de tu aplicación, crea un nuevo archivo de texto llamado compose.yaml con el siguiente contenido.

version: "3.8"

services:
  docker-gs-ping-roach:
    depends_on:
      - roach
    build:
      context: .
    container_name: rest-server
    hostname: rest-server
    networks:
      - mynet
    ports:
      - 80:8080
    environment:
      - PGUSER=${PGUSER:-totoro}
      - PGPASSWORD=${PGPASSWORD:?database password not set}
      - PGHOST=${PGHOST:-db}
      - PGPORT=${PGPORT:-26257}
      - PGDATABASE=${PGDATABASE:-mydb}
    deploy:
      restart_policy:
        condition: on-failure
  roach:
    image: cockroachdb/cockroach:latest-v25.4
    container_name: roach
    hostname: db
    networks:
      - mynet
    ports:
      - 26257:26257
      - 8080:8080
    volumes:
      - roach:/cockroach/cockroach-data
    command: start-single-node --insecure

volumes:
  roach:

networks:
  mynet:
    driver: bridge

Esta configuración de Docker Compose es muy conveniente ya que no tienes que escribir todos los parámetros que pasas al comando docker run. Puedes hacerlo declarativamente en el archivo de Docker Compose. Las páginas de documentación de Docker Compose son bastante extensas e incluyen una referencia completa del formato del archivo Docker Compose.

El archivo .env

Docker Compose leerá automáticamente las variables de entorno de un archivo .env si está disponible. Dado que tu archivo de Compose requiere que se configure PGPASSWORD, agrega el siguiente contenido al archivo .env:

PGPASSWORD=whatever

El valor exacto no importa realmente para este ejemplo, porque ejecutas CockroachDB en modo inseguro. Asegúrate de configurar la variable con algún valor para evitar obtener un error.

Combinando archivos de Compose

El nombre de archivo compose.yaml es el nombre de archivo predeterminado que reconoce el comando docker compose si no se proporciona una bandera -f. Esto significa que puedes tener múltiples archivos de Docker Compose si tu entorno tiene tales requisitos. Además, los archivos de Docker Compose son... combinables (valga la redundancia), por lo que se pueden especificar múltiples archivos en la línea de comandos para fusionar partes de la configuración. La siguiente lista contiene solo algunos ejemplos de escenarios donde esta característica sería muy útil:

  • Usar un montaje de tipo bind (bind mount) para el código fuente para el desarrollo local, pero no al ejecutar las pruebas de CI;
  • Alternar entre usar una imagen preconstruida para el frontend para alguna aplicación API en lugar de crear un montaje de tipo bind para el código fuente;
  • Agregar servicios adicionales para pruebas de integración;
  • Y muchos más...

No vamos a cubrir ninguno de estos casos de uso avanzados aquí.

Sustitución de variables en Docker Compose

Una de las características más geniales de Docker Compose es la sustitución de variables. Puedes ver algunos ejemplos en el archivo de Compose, en la sección environment. A modo de ejemplo:

  • PGUSER=${PGUSER:-totoro} significa que, dentro del contenedor, la variable de entorno PGUSER se configurará con el mismo valor que tiene en la máquina host donde se ejecuta Docker Compose. Si no hay ninguna variable de entorno con este nombre en la máquina host, la variable dentro del contenedor toma el valor predeterminado de totoro.
  • PGPASSWORD=${PGPASSWORD:?database password not set} significa que si la variable de entorno PGPASSWORD no está configurada en el host, Docker Compose mostrará un error. Esto está bien, porque no quieres codificar de forma fija (hard-code) valores predeterminados para la contraseña. Configuras el valor de la contraseña en el archivo .env, que es local para tu máquina. Siempre es una buena idea agregar el archivo .env a tu .gitignore para evitar que los secretos se suban al control de versiones.

Existen otras formas de lidiar con valores no definidos o vacíos, tal como se documenta en la sección de sustitución de variables de la documentación de Docker.

Validando la configuración de Docker Compose

Antes de aplicar los cambios realizados en un archivo de configuración de Compose, existe la oportunidad de validar el contenido del archivo de configuración con el siguiente comando:

$ docker compose config

Cuando se ejecuta este comando, Docker Compose lee el archivo compose.yaml, lo analiza en una estructura de datos en memoria, lo valida cuando es posible y muestra la reconstrucción de ese archivo de configuración a partir de su representación interna. Si esto no es posible debido a errores, Docker imprime un mensaje de error en su lugar.

Construye y ejecuta la aplicación usando Docker Compose

Inicia tu aplicación y confirma que se está ejecutando.

$ docker compose up --build

Pasaste la bandera --build para que Docker compile tu imagen y luego la inicie.

Note

Docker Compose es una herramienta útil, pero tiene sus propias peculiaridades. Por ejemplo, no se activa ninguna reconstrucción ante una actualización en el código fuente a menos que se proporcione la bandera --build. Es un error muy común editar el código fuente propio y olvidar usar la bandera --build al ejecutar docker compose up.

Dado que tu configuración ahora se ejecuta mediante Docker Compose, se le ha asignado un nombre de proyecto, por lo que obtienes un volumen nuevo para tu instancia de CockroachDB. Esto significa que tu aplicación no podrá conectarse a la base de datos porque la base de datos no existe en este nuevo volumen. La terminal mostrará un error de autenticación para la base de datos:

rest-server             | 2021/05/10 00:54:25 failed to initialise the store: pq: password authentication failed for user totoro
roach                   | *
roach                   | * INFO: Replication was disabled for this cluster.
roach                   | * When/if adding nodes in the future, update zone configurations to increase the replication factor.
roach                   | *
roach                   | CockroachDB node starting at 2021-05-10 00:54:26.398177 +0000 UTC (took 3.0s)
roach                   | build:               CCL v20.1.15 @ 2021/04/26 16:11:58 (go1.13.9)
roach                   | webui:               http://db:8080
roach                   | sql:                 postgresql://root@db:26257?sslmode=disable
roach                   | RPC client flags:    /cockroach/cockroach <client cmd> --host=db:26257 --insecure
roach                   | logs:                /cockroach/cockroach-data/logs
roach                   | temp dir:            /cockroach/cockroach-data/cockroach-temp349434348
roach                   | external I/O path:   /cockroach/cockroach-data/extern
roach                   | store[0]:            path=/cockroach/cockroach-data
roach                   | storage engine:      rocksdb
roach                   | status:              initialized new cluster
roach                   | clusterID:           b7b1cb93-558f-4058-b77e-8a4ddb329a88
roach                   | nodeID:              1
rest-server exited with code 0
rest-server             | 2021/05/10 00:54:25 failed to initialise the store: pq: password authentication failed for user totoro
rest-server             | 2021/05/10 00:54:26 failed to initialise the store: pq: password authentication failed for user totoro
rest-server             | 2021/05/10 00:54:29 failed to initialise the store: pq: password authentication failed for user totoro
rest-server             | 2021/05/10 00:54:25 failed to initialise the store: pq: password authentication failed for user totoro
rest-server             | 2021/05/10 00:54:26 failed to initialise the store: pq: password authentication failed for user totoro
rest-server             | 2021/05/10 00:54:29 failed to initialise the store: pq: password authentication failed for user totoro
rest-server exited with code 1

Debido a la forma en que configuraste tu despliegue usando restart_policy, el contenedor que falla se reinicia cada 20 segundos. Por lo tanto, para solucionar el problema, debes iniciar sesión en el motor de la base de datos y crear el usuario. Ya lo has hecho antes en Configura el motor de la base de datos.

Esto no es un gran problema. Todo lo que tienes que hacer es conectarte a la instancia de CockroachDB y ejecutar los tres comandos SQL para crear la base de datos y el usuario, tal como se describe en Configura el motor de la base de datos.

Por lo tanto, inicia sesión en el motor de la base de datos desde otra terminal:

$ docker exec -it roach ./cockroach sql --insecure

Y ejecuta los mismos comandos que antes para crear la base de datos mydb, the user totoro, y otorgar a ese usuario los permisos necesarios. Una vez que lo hagas (y el contenedor de la aplicación de ejemplo se reinicie automáticamente), el servicio rest-service dejará de fallar y reiniciarse, y la consola se calmará.

Hubiera sido posible conectar el volumen que habías utilizado anteriormente, pero para los propósitos de este ejemplo es más problemático de lo que vale la pena, y también brindó la oportunidad de mostrar cómo introducir resiliencia en tu despliegue a través de la función restart_policy del archivo de Compose.

Probando la aplicación

Ahora, prueba tu endpoint de API. En la nueva terminal, ejecuta el siguiente comando:

$ curl http://localhost/

Deberías recibir la siguiente respuesta:

Hello, Docker! (0)

Apagado (Shutting down)

Para detener los contenedores iniciados por Docker Compose, presiona ctrl+c en la terminal donde ejecutaste docker compose up. Para eliminar esos contenedores después de que se hayan detenido, ejecuta docker compose down.

Modo desacoplado (Detached mode)

Puedes ejecutar contenedores iniciados por el comando docker compose en modo desacoplado (detached), tal como lo harías con el comando docker, usando la bandera -d.

Para iniciar la pila definida por el archivo de Compose en modo desacoplado, ejecuta:

$ docker compose up --build -d

Luego, puedes usar docker compose stop para detener los contenedores y docker compose down para eliminarlos.

Exploración adicional

Puedes ejecutar docker compose para ver qué otros comandos están disponibles.

Conclusión

Hay algunos puntos tangenciales, pero interesantes, que intencionalmente no se cubrieron en este capítulo. Para el lector más aventurero, esta sección ofrece algunas pautas para un estudio posterior.

Almacenamiento persistente

Un volumen administrado no es la única forma de proporcionar almacenamiento persistente a tu contenedor. Es muy recomendable familiarizarse con las opciones de almacenamiento disponibles y sus casos de uso, cubiertos en Administrar datos en Docker.

Clústeres de CockroachDB

Ejecutaste una sola instancia de CockroachDB, lo cual fue suficiente para este ejemplo. Pero es posible ejecutar un clúster de CockroachDB, que está compuesto por múltiples instancias de CockroachDB, cada una ejecutándose en su propio contenedor. Dado que el motor de CockroachDB está distribuido por diseño, habría requerido sorprendentemente pocos cambios en tu procedimiento ejecutar un clúster con múltiples nodos.

Dicha configuración distribuida ofrece posibilidades interesantes, como aplicar técnicas de Ingeniería del Caos (Chaos Engineering) para simular fallas en partes del clúster y evaluar la capacidad de tu aplicación para hacer frente a tales fallas.

Si estás interesado en experimentar con clústeres de CockroachDB, consulta:

Otras bases de datos

Dado que no ejecutaste un clúster de instancias de CockroachDB, te estarás preguntando si podrías haber usado un motor de base de datos no distribuido. La respuesta es 'sí', y si fueras a elegir una base de datos SQL más tradicional, como PostgreSQL, el proceso descrito en este capítulo habría sido muy similar.

Siguientes pasos

En este módulo, configuraste un entorno de desarrollo contenedorizado con tu aplicación y el motor de base de datos ejecutándose en diferentes contenedores. También escribiste un archivo de Docker Compose que conecta los dos contenedores y permite iniciar y desmantelar fácilmente el entorno de desarrollo.

En el siguiente módulo, echarás un vistazo a un enfoque posible para ejecutar pruebas funcionales en Docker.