inicio del viaje

estamos con ganas de poner a andar una instancia de Gancio, una app federada de publicacion de eventos y, por supuesto, libre

gancio cuenta con unas instrucciones de instalacion que nos permiten levantar una instancia utilizando docker y docker-compose. sin embargo, esta forma de iniciar la instancia de gancio depende de un proceso de setup interactivo, de varios pasos.

los procesos interactivos, que requieren intervencion de una usuaria, no suelen ir muy bien con los procesos de despliegue automaticos, y requieren el uso de hacks para lidiar con ellos.

nota: en nuestro caso ejecutaremos gancio utilizando una base de datos SQLite, con lo que solo analizamos este proceso de instalacion y ejecucion de la app. parte de lo que hagamos sera util para una instalacion que utilice Postgres, aunque pensamos que igual y puedan surgir desafios adicionales en ese caso

que hace este setup interactivo?

para ver como podemos evitar ejecutar el proceso de setup interactivo, vamos a investigar que es lo que hacen estos comandos.

lso comandos en cuestion son

touch config.json db.sqlite
mkdir user_locale
docker-compose run --rm gancio gancio setup --docker --db=sqlite

el primer comando, touch, busca los archivos config.json y db.sqlite en el sistema de archivos, y si no los encuentra, los crea vacios de contenido.

el segundo comando mkdir, crea un nuevo directorio, vacio de contenido tambien.

el tercer comando, docker run ... ejecuta el docker de gancio, e inicia un proceso de setup dentro del docker.

mirando el sistema de archivos luego de ejecutar el proceso de setup interactivo, vemos que los archivos config.json y db.sqlite ,que habiamos inicialmente creado sin contenido, ahora si tienen cosas dentro.

mirando los logs de la ejecucion del proceso de setup confirmamos que da contenido al archivo de configuracion (config.json) e inicia la base de datos creando las tablas que gancio necesita para funcionar (db.sqlite).

el directorio que creamos antes no tiene ningun cambio observable.

alternativas a ejecutar el setup interactivo

pensando en alternativas a ejecutar el setup interactivo, nuestro primer objetivo es el generar el archvio config.json desde una plantilla (ej: config.json.sample).

vemos que si ejecutamos el proceso de setup interactivo, teniendo ya creado un config.json, gancio ignora nuestra configuracion, y nos vuelve a re-preguntar los datos que hemos informado en el archivo. esto no es lo que esperabamos.

volvemos a intentarlo, esta vez ignorando el paso de configuracion/setup y ejecutando docker-compose up directamente. asi evitamos el proceso de setup interactivo.

probamos diferente configuraciones y observamos las siguientes casuisticas.

  1. si existe db.sqlite, y contiene datos, gancio la utiliza
  2. si existe db.sqlite, y esta vacia, al iniciarse gancio genera las tablas necearias en la bbdd
  3. si NO existe db.sqlite, gancio no puede iniciarse y falla

con esto sabemos ahora que si queremos iniciar gancio sin ejecutar el setup interactivo necesitamos, como minimo, lo siguiente

  • un archivo de configuracion llamado config.json con los datos de la instancia
  • un archivo para la base de datos llamado db.sqlite, que puede estar vacio o tener el contenido de una instancia previa del mismo gancio
  • no es necesario crear ningun directorio, ya que gancio mismo lo crea durante la primera ejecucion, si no existe

rizando el rulo ajeno

bien, ya sabemos que para evitar el proceso de setup interactivo necesitamos un archivo de configuracion config.json con un cierto contenido, y un archivo llamado db.sqlite que puede estar vacio o contener los datos de una ejecucion previa.

ahora, estos archivos tienen que poder ser escritos por la usuaria ejecutando gancio dentro del docker. eso no es un problema si la usuaria dentro del docker es root, pero si queremos ejecutar con una usuaria no root, entonces esta nueva usuaria debe tener permisos de lectura y escritura sobre, al menos, db.sqlite e idealmente tambien sobre config.json para poder interactuar con esta configuracion desde la web de gancio.

esto si se convierte en un desafio. por fuera del docker podemos crear estos archivos facilmente con nuestra usuaria, siguiendo las instrucciones de instalacion. ahora, estos archivos no necesariamente seran accesibles por la usuaria ejecutando gancio dentro del docker. que podemos hacer al respecto?

necesitamos cambiar la propiedad de los archivos que creamos localmente a la usuaria que dentro del docker ejecutara gancio. esto o bien lo hacemos dentro del docker, o bien fuera (en el host)

si deseamos hacerlo por fuera del docker, necesitaremos ser root en el sistema que ejecuta el demonio de docker. esto no suele ser el caso, asi que descartaremos este metodo.

nos queda entonces, como unica posibilidad, la de realizar el ajuste a la propiedad de los archivos por dentro de un docker.

reina de mi burbuja

creemos que tenemos 2 factores a nuestro favor:

  • que ‘solo’ tenemos que actuar como root sobre los archivos que necesita acceder gancio
  • que dentro de un docker SI podemos ser root facilmente

volvemos a vislumbrar 2 caminos para este objetivo

  • utilizando 2 dockers diferentes, 1 para gestionar permiosos de archivos (donde seremos root), y otro para ejecutar gancio (donde seremos la usuaria que ejecuta gancio). en esta solucion, apalancamos docker-compose
  • utilizando 1 solo docker, donde podamos ser primero root y ajustar los permisos de los archivos, y luego ser la usuaria de gancio para ejecutar el servicio. en esta solucion, apalancamos el entrypoint de docker

solucion con docker-compose: dos dockers diferente

si asumimos que continuaremos utilizando el docker-compose.yaml que nos sugiere el proyecto de gancio, podemos hacerle una modificaciones para que antes de comenzar a ejecutar gancio, ejecute un docker donde sea root y cambie la propiedad de los archivos a la que sea necesaria.

para esto, agregamos al docker-compose.yaml un nuevo servicio que llamamos ego y que se ocupara de apropiarse de los archivos que gancio utilizara. el docker de ego puede ser cualquier imagen que se ejecute como root (ej. debian, alpine, ubuntu), en nuestro caso, utilizamos el mismo docker de gancio, ya que esta configurado para ejecutarse como root por defecto.

nuestro servicio ego se ocupa entonces de generar los archivos necesarios para poder iniciar gancio sin necesidad de ejecutar un proceso de setup interactivo.

    build:
      context: .
      dockerfile: Dockerfile
    restart: "no"
    image: gancio:latest
    container_name: ego 
    command: >
      sh -c "touch /opt/gancio/config.json &&
             touch /opt/gancio/db.sqlite &&
             chown -R gancio:nogroup /opt/gancio"
    volumes:
      - ./data:/opt/gancio

para forzar al servicio de gancio a esperar que ego termine sus tareas de apropiarse de las cosas, le agregamos una dependencia, tambien en el docker-compose.yaml

  gancio:
    ...
    depends_on:
      - ego
    ...

esta solucion logra el objetivo que buscamos, con el costo adicional de complejizar el docker-compose.yaml

solucion con docker: entry-point con cambio de usuaria

pensando en como simplificar la solucion de arriba, se nos ocurre que otra posibilidad es ejecutar los comandos como root para cambiar los permisos dentro del mismo docker donde luego se ejecuta gancio con un usuario especifico.

en este caso, podriamos recurir a un entrypoint que:

  • se ejecute como root
  • cree archivos necearios
  • modifique permisos
  • desescale permisos a usuario que ejecutara gancio
  • dejar docker listo para ejecutar gancio de forma regular

esto lo podemos lograr si, en lugar de iniciar el docker como la usuaria que ejecuta gancio, lo hacemos como root y durante la ejecucion del Entrypoint cambiamos a la usuaria de gancio cuando ya hayamos realizado las tareas necesarias

encontramos que el siguiente Entrypoint alcanza este objetivo, ya que luego de ejecutar las primeras acciones como root ejecuta el Command que se le pase al docker como la usuaria que ejecuta gancio

# verifica que exista un archivo de configuracion
[ -f /opt/gancio/config.json ] || { echo 'falta el archivo de configuracion config.json'; exit 1; }

# si no existe, crea la base de datos sqlite
[ -f /opt/gancio/db.sqlite ] || touch /opt/gancio/db.sqlite

# recupera permisos a nombre del usuario de gancio]
chown -R gancio:nogroup /opt/gancio

# desescala permisos y prepara ejecucion de comandos
su gancio --command="$@"

asi, todas las instrucciones que encontramos en la documentacion de gancio se ejecutaran de la misma manera en nuestro docker, solo que en lugar de ejecutar la accion como usuaria root, lo hara como la usuaria de gancio

ponerle un lazo

con esto logramos nuestro objetivo de tener una forma automatizable de desplegar gancio.

cada nuevo despliegue necesitara su propia carpeta para datos (eg. data), y dentro de ella el archivo de configuracion de la instancia (eg. data/config.json)

agregamos unos parametros tanto a la cosntruccion del docker, como a la ejecucion de gancio.

al construir el docker podemos controlar

  • rama/tag del repositorio de gancio a construir (GANCIO_VERSION)
  • uid de la usuaria que ejecutara gancio (GANCIO_UID) estos valores los debemos pasar como parte de la construccion del docker
docker-compose build --build-arg GANCIO_UID=110 --build-arg GANCIO_VERSION=master

al ejecutar gancio podemos controlar

  • la ruta donde estan almacenados los datos locales (GANCIO_DATA_PATH)
  • el puerto en el que se expondra el servicio (GANCIO_PORT) estos valores se deben editar en el archivo .env (tienes un ejemplo en .env.sample)

TODO si iniciamos una instancia con una base de datos vacia, no logramos loguearnos como admin para poder gestionarla
hemos visto que si bien se crea la bbdd, no se crea ningun usuario, y por esto no podemos loguearnos
tenemos que pensar como hacer para crear un usuario inicial que luego pueda modificarse

\o/