Compose: Ejecutar un comando después de ejecutar

Creado en 5 ago. 2015  ·  131Comentarios  ·  Fuente: docker/compose

Hola,

Será muy útil tener algo como "onrun" en el YAML para poder ejecutar comandos después de la ejecución. Similar a https://github.com/docker/docker/issues/8860

mongodb:
    image: mongo:3.0.2
    hostname: myhostname
    domainname: domain.lan
    volumes:
        - /data/mongodb:/data
    ports:
        - "27017:27017" 
    onrun:
        - mongodump --host db2dump.domain.lan --port 27017 --out /data/mongodb/dumps/latest
        - mongorestore -d database /data/mongodb/dumps/latest/database

Después del inicio de mongodb, volcará db2dump.domain.lan y lo restaurará.

Cuando detenga y luego inicie el contenedor, la parte de ejecución no se ejecutará para preservar la idempotencia.

EDITAR 15 de junio de 2020

Cinco años después, Compose quiere "estandarizar" las especificaciones,
consulte https://github.com/compose-spec/compose-spec/issues/84

Comentario más útil

Entonces, para administrar mi ventana acoplable, me sugiere que use un Script o un Makefile. Entonces, ¿por qué se creó la composición?
? Podemos gestionar, escalar, etc. contenedores con script || dockerfile?

Ok, tomo este ejemplo, es lo que usé para implementar mi entorno de prueba de aplicaciones en el proceso de CI.

rabbitmq:
    image: rabbitmq:3.5.1-management
    environment:
        RABBITMQ_NODENAME: rabbit
    hostname: rabbitmq
    domainname: domain.lan
    volumes:
        - /data/rabbitmq/db:/var/lib/rabbitmq
    ports:
        - "5672:5672" 
        - "15672:15672"
        - "25672:25672"
        - "4369:4369"

mongodb:
    image: mongo:3.0.2
    hostname: mongo
    domainname: domain.lan
    volumes:
        - /data/mongodb:/data
    ports:
        - "27017:27017" 

appmaster:
    image: appmaster
    hostname: master
    domainname: domain.lan
    environment:
        ...
    ports:
        - "80:80" 
        - "8080:8080"
    links:
        - mongodb
        - rabbitmq

celery:
    image: celery
    hostname: celery
    domainname: domain.lan
    environment:
        ...
    links:
        - rabbitmq

Una vez que se inicia el contenedor, debo aprovisionar mongodb, administrar la cola y la cuenta en rabbitmq

Lo que estoy haciendo hoy es un guión con:

#!/bin/bash
PROJECT=appmaster
docker-compose -f appmaster.yml -p appmaster up -d
docker exec appmaster_rabbitmq_1 rabbitmqctl add_user user password
docker exec appmaster_rabbitmq_1 rabbitmqctl add_vhost rabbitmq.domain.lan
docker exec appmaster_rabbitmq_1 rabbitmqctl set_permissions -p rabbitmq.domain.lan password ".*" ".*" ".*"
docker exec appmaster_mongodb_1 mongodump --host mongo-prd.domain.lan --port 27017 --out /data/mongodb/dumps/latest
docker exec appmaster_mongodb_1 mongorestore -d database /data/mongodb/dumps/latest/database

Con la instrucción onrun puedo hacer directamente docker-compose -f appmaster.yml -p appmaster up -d
y el archivo yml se vuelve más legible

rabbitmq:
    ...
    onrun:
        - rabbitmqctl add_user user password
        - rabbitmqctl add_vhost rabbitmq.domain.lan
        - rabbitmqctl set_permissions -p rabbitmq.domain.lan password ".*" ".*" ".*"

mongodb:
    ...
    onrun:
        - mongodump --host mongo-prd.domain.lan --port 27017 --out /data/mongodb/dumps/latest
        - mongorestore -d database /data/mongodb/dumps/latest/database

Todos 131 comentarios

Creo que estos deberían ser pasos en el Dockerfile

FROM mongo:3.0.2
ADD data/mongodb/dumps/latest /data/mongodb/dumps/latest
RUN mongorestore -d database /data/mongodb/dumps/latest/database

De esa manera también lo almacena en caché cuando reconstruye.

Gracias @dnephin.
Por supuesto, puedo hacer un Dockerfile y usarlo en la construcción en lugar de imágenes, o puedo usar docker exec.
MongoDB es solo un ejemplo, puede tener este ejemplo con mysql y creación de cuenta, o con rabbitmq y creación de cola, etc.

onrun permitirá flexibilidad en la orquestación de composición, redactar leerá la lista de ejecución y hará docker exec en cada elemento.

El punto es que poner comandos en docker exec en docker-compose.yml es innecesario cuando puede hacerlo en el Dockerfile o en el script de inicio del contenedor, los cuales también harán que su contenedor sea más útil cuando _no_ que se ejecuta con Compose.

Alternativamente, inicie su aplicación con un script de shell o Makefile que ejecute los comandos docker y docker-compose apropiados.

No vale la pena agregar la funcionalidad a Compose a menos que agregue un valor significativo en comparación con cualquiera de esos, y no creo que lo sea para los casos de uso que ha citado.

Entonces, para administrar mi ventana acoplable, me sugiere que use un Script o un Makefile. Entonces, ¿por qué se creó la composición?
? Podemos gestionar, escalar, etc. contenedores con script || dockerfile?

Ok, tomo este ejemplo, es lo que usé para implementar mi entorno de prueba de aplicaciones en el proceso de CI.

rabbitmq:
    image: rabbitmq:3.5.1-management
    environment:
        RABBITMQ_NODENAME: rabbit
    hostname: rabbitmq
    domainname: domain.lan
    volumes:
        - /data/rabbitmq/db:/var/lib/rabbitmq
    ports:
        - "5672:5672" 
        - "15672:15672"
        - "25672:25672"
        - "4369:4369"

mongodb:
    image: mongo:3.0.2
    hostname: mongo
    domainname: domain.lan
    volumes:
        - /data/mongodb:/data
    ports:
        - "27017:27017" 

appmaster:
    image: appmaster
    hostname: master
    domainname: domain.lan
    environment:
        ...
    ports:
        - "80:80" 
        - "8080:8080"
    links:
        - mongodb
        - rabbitmq

celery:
    image: celery
    hostname: celery
    domainname: domain.lan
    environment:
        ...
    links:
        - rabbitmq

Una vez que se inicia el contenedor, debo aprovisionar mongodb, administrar la cola y la cuenta en rabbitmq

Lo que estoy haciendo hoy es un guión con:

#!/bin/bash
PROJECT=appmaster
docker-compose -f appmaster.yml -p appmaster up -d
docker exec appmaster_rabbitmq_1 rabbitmqctl add_user user password
docker exec appmaster_rabbitmq_1 rabbitmqctl add_vhost rabbitmq.domain.lan
docker exec appmaster_rabbitmq_1 rabbitmqctl set_permissions -p rabbitmq.domain.lan password ".*" ".*" ".*"
docker exec appmaster_mongodb_1 mongodump --host mongo-prd.domain.lan --port 27017 --out /data/mongodb/dumps/latest
docker exec appmaster_mongodb_1 mongorestore -d database /data/mongodb/dumps/latest/database

Con la instrucción onrun puedo hacer directamente docker-compose -f appmaster.yml -p appmaster up -d
y el archivo yml se vuelve más legible

rabbitmq:
    ...
    onrun:
        - rabbitmqctl add_user user password
        - rabbitmqctl add_vhost rabbitmq.domain.lan
        - rabbitmqctl set_permissions -p rabbitmq.domain.lan password ".*" ".*" ".*"

mongodb:
    ...
    onrun:
        - mongodump --host mongo-prd.domain.lan --port 27017 --out /data/mongodb/dumps/latest
        - mongorestore -d database /data/mongodb/dumps/latest/database

Esto sería bastante útil y resuelve un caso de uso.

: +1:

Hará que el uso de docker-compose más viable para pruebas cerradas como parte de una canalización de CD

: +1:

Este es un duplicado de # 877, # 1341, # 468 (y algunos otros).

Creo que la forma correcta de respaldar esto es # 1510 y permitir que las herramientas externas realicen operaciones cuando llegue al evento que desee.

Cerrando como duplicado

Esto sería muy útil. No entiendo el argumento de "oh, podrías hacer esto con un script bash". Por supuesto que podríamos hacerlo con un script bash. También podría hacer todo lo que hace Docker-compose con un script bash. Pero el punto es que hay un solo archivo YAML que controla su entorno de prueba y se puede hacer girar con un simple comando docker-compose up .

No es competencia de Compose hacer todo lo que pueda hacerse con un script de shell o Makefile; tenemos que trazar una línea en algún lugar para lograr un equilibrio entre la utilidad y evitar la hinchazón.

Además, una propiedad importante del archivo Compose es que es bastante portátil entre máquinas, incluso Mac, Linux y Windows. Si permitimos que las personas pongan comandos de shell arbitrarios en el archivo Compose, se volverán mucho menos portátiles.

@aanand Para ser justos, poder ejecutar un docker exec no implica automáticamente incompatibilidad x-plat.

Disculpas: interpreté mal este problema como si se tratara de ejecutar comandos en la máquina host. Aún así, mi primer punto se mantiene.

Entiendo tu punto @aanand. No me parece fuera de alcance, ya que docker-compose hace muchas de las mismas cosas que el motor regular docker ya hace, como command , expose , ports , build , etc. Agregar la funcionalidad exec agregaría más poder a docker-compose para convertirlo en una verdadera ventanilla única para la configuración entornos de desarrollo.

@aanand el principal problema para muchos desarrolladores y pipelines de CI es tener datos muy cercanos al entorno de producción. Como un volcado de una base de datos. Creo este ticket hace 1 año y no se mueve nada en la ventana acoplable componer.

Por lo tanto, sugiere un Makefile o Bashcript solo para ejecutar algún ejecutivo https://github.com/docker/compose/issues/1809#issuecomment -128073224

Lo que sugiero originalmente es onrun (o una creación) que mantengan la idempotencia. Simplemente corra en el primer comienzo. Si el contenedor se detiene o pausa, el nuevo inicio no se ejecutará (o se creará)

Finalmente, en mi repositorio git tendré un archivo de composición, un archivo docker y un archivo MAKE con administración de idempotencia (puede que el archivo MAKE pueda crear un archivo de estado). ¡Genio!

Hay una gran diferencia entre command , expose , etc. y exec . El primer grupo son las opciones de contenedor, exec es un punto final de comando / api. Es una función separada, no opciones para la función de creación de contenedor.

Ya hay un par de formas de lograr esto con Compose (https://github.com/docker/compose/issues/1809#issuecomment-128059030). onrun ya existe. Es command .

Con respecto al problema específico de volcar o cargar datos desde una base de datos, esas son más tareas del tipo "flujo de trabajo" o "automatización de compilación", que generalmente se realizan en un Makefile. He estado creando un prototipo de una herramienta para exactamente esos casos de uso llamada dobi , que ejecuta todas las tareas en contenedores. También se integra muy bien con Compose. Es posible que le interese probarlo si no está satisfecho con Makefiles. Estoy trabajando en un ejemplo de un caso de uso de inicio / carga de base de datos.

@dnephin onrun no es un command simple porque simplemente extrañas la idempotencia.

Imaginemos. create en la creación del contenedor y nunca volverá a ser ejecutivo (volcado y restauración).

exec:
    create:
        - echo baby
    destroy:
        - echo keny
    start:
        - echo start
    stop:
        - echo bye

Si necesita más ejemplos:

Gracias por dobi, pero si necesitas crear una herramienta para mejorar la redacción, redactar es malo y es mejor usar una herramienta más poderosa.

pero si necesita crear una herramienta para mejorar la redacción, redactar es malo y es mejor usar una herramienta más poderosa.

Eso es como decir "si necesita aplicaciones para mejorar su sistema operativo, su sistema operativo es malo". Ninguna herramienta debería hacer todo. La filosofía de Unix es hacer una cosa y hacerlo bien . Eso es lo que estamos haciendo aquí. Compose hace su única cosa "organizar contenedores para ejecutar una aplicación". No es una herramienta de automatización de compilación.

Eso es como decir "si necesita aplicaciones para mejorar su sistema operativo, su sistema operativo es malo". Ninguna herramienta debería hacer todo. La filosofía de Unix es hacer una cosa y hacerlo bien. Eso es lo que estamos haciendo aquí.

Vaya, creo que llegamos a la mejor mala fe.

Desafortunadamente, un componente simple y reutilizable no es cómo se están desarrollando las cosas. Docker ahora está creando herramientas para lanzar servidores en la nube, sistemas para la agrupación en clústeres y una amplia gama de funciones: crear imágenes, ejecutar imágenes, cargar, descargar y, finalmente, incluso superponer redes, todo compilado en un binario monolítico que se ejecuta principalmente como raíz en su servidor. . Se eliminó el manifiesto de contenedores estándar. Deberíamos dejar de hablar de contenedores Docker y empezar a hablar de la plataforma Docker. No se está convirtiendo en el simple componente componible que habíamos imaginado.

Entonces, ¿puede garantizar que nunca veremos "docker compose" escrito en Go inside in the docker monolithic binary para mantener la filosofía de Unix? https://www.orchardup.com/blog/orchard-is-joining-docker

Para continuar hacia ese objetivo original, nos unimos a Docker. Entre otras cosas, seguiremos trabajando para hacer de Docker la mejor experiencia de desarrollo que haya visto, tanto con Fig como incorporando las mejores partes de Fig en Docker.

Entonces, en resumen, no hay forma de hacer cosas como cargar dispositivos con compose ..? Debo decir que estoy sorprendido ...
¿La forma oficial es agregar carga de accesorios a mi contenedor de producción? ¿O escribir un script de shell alrededor de mi archivo de redacción? En el último caso, también podría simplemente ejecutar 'docker run' como lo hice antes.

@discordianfish , si, de alguna manera, alguien se diera cuenta del hecho de que los ingenieros de CI / CD necesitan poder manejar los eventos del ciclo de vida y la orquestación al menos a un nivel muy básico, entonces quién sabe que docker / docker-compose realmente puede hacer su salir de las tuberías de desarrollo local y la infraestructura de prueba y encontrar un lugar en más entornos de producción. Tengo la esperanza de que quien esté trabajando en las pilas aborde estos problemas, pero no aguantaré la respiración.

Después de todo, lo que se necesita hacer en el momento de la compilación puede ser diferente de lo que se necesita en el tiempo de ejecución, y lo que se necesita en el tiempo de ejecución a menudo varía según el entorno de implementación ...

Es un trabajo un poco molesto hacer que mis scripts externos sean conscientes de si un up va a crear o iniciar contenedores ...

Y esas son cosas con las que algunos ganchos de ciclo de vida + comandos + variables de entorno podrían ayudar.

Lo ve en los marcos de gestión de servicios y otras herramientas de orquestación ... ¿por qué no en docker-compose?

Quizás le interese https://github.com/dnephin/dobi , que es una herramienta en la que he estado trabajando y que fue diseñada para esos flujos de trabajo.

@dnephin deja de enviar spam a este problema con tus herramientas. Vemos tu comentario antes y la respuesta es la misma. Makefile / bash es probablemente mejor que una enésima "ventana acoplable para mejorar mi herramienta".

Gracias por tu constructivo comentario. No me di cuenta de que ya había mencionado a dobi en este hilo hace 8 meses.

Si está satisfecho con Makefile / bash, ¡es genial! Me alegro de que se haya resuelto tu problema.

Se agregó un comentario relacionado con este tema aquí: https://github.com/docker/compose/issues/1341#issuecomment -295300246

@dnephin para este, mi comentario se puede aplicar:

Es tan triste que este problema se haya cerrado debido a cierta refractariedad a la evolución: decepcionado:

El mayor valor de tener docker compose es la estandarización

Ese es el punto. Si pudiéramos "simplemente" escribir un archivo .sh o lo que sea para hacer el trabajo sin usar Docker Compose, ¿por qué existe Docker Compose? :confuso:

Podemos entender que es un gran trabajo, como dijo @ shin-:

Desafortunadamente, es una carga demasiado pesada para apoyar en esa etapa del proyecto.

:corazón:

Pero no puedes decir "Haz un guión" lo que significa "Oye, eso es demasiado difícil, no lo lograremos".

Si es difícil hacerlo, simplemente diga "Tu idea es interesante y satisface algunas necesidades, pero es muy difícil de hacer y no tenemos recursos para hacerlo en este momento ... Tal vez podrías desarrollarla y preguntar una solicitud de extracción "o algo así: bombilla:

En el # 1341, "solo" veo una forma de escribir en docker-compose.yml comandos como nmp install que se ejecutarían antes o después de algunos eventos (como la creación de contenedores), como lo haría con docker exec <container id> npm install por ejemplo.

Caso de uso

Tengo una imagen NodeJS personalizada y quiero ejecutar npm install en el contenedor creado a partir de ella, con un docker-compose up --build .

Mi problema es: el código de la aplicación no se agrega en el contenedor, está montado en él con un volumen, definido en docker-compose.yml :

custom-node:
    build: ../my_app-node/
    tty: true
    #command: bash -c "npm install && node"
    volumes:
     - /var/www/my_app:/usr/share/nginx/html/my_app

así que no puedo ejecutar npm install en el Dockerfile porque necesita el código de la aplicación para verificar las dependencias. Describí el comportamiento aquí: http://stackoverflow.com/questions/43498098/what-is-the-order-of-events-in-docker-compose

Para ejecutar npm install , tengo que usar una solución alternativa, la declaración command :

command: bash -c "npm install && node"

que no está realmente limpio: decepcionado: y que no puedo ejecutar en versiones de Alpine (no tienen Bash instalado).

Pensé que Docker Compose proporcionaría una forma de ejecutar comandos exec en contenedores, eG:

custom-node:
    build: ../my_app-node/
    tty: true
    command: node
    volumes:
     - /var/www/my_app:/usr/share/nginx/html/my_app
    exec:
     - npm install

¡Pero no lo es, y creo que realmente falta!

Esperaba que la redacción estuviera diseñada para pruebas, pero probablemente me equivoque y esté destinada más al desarrollo local, etc. Me encontré con varios otros aspectos ásperos como contenedores huérfanos y la relación poco clara entre el nombre del proyecto, la ruta y cómo se usa para identificar la propiedad. qué sucede si tiene varios archivos de redacción en el mismo directorio, etc., etc. Así que, en general, no parece una buena opción para CI.
En su lugar, planeo reutilizar mis manifiestos k8s de producción en CI ejecutando kubelet de forma independiente. Esto también requerirá mucho pegamento, pero al menos de esta manera puedo usar las mismas declaraciones para dev, test y prod.

@ lucile-sticky puedes usar sh -c en alpine.

Parece que lo que quieres es "automatización de compilación", que no es la función de docker-compose. ¿Has mirado a dobi ?

Dos preguntas:

  • ¿Por qué no es este el papel de Docker Compose?
  • Si el punto es tener solo una herramienta para gobernarlos a todos, ¿por qué debería usar otra herramienta para completar una tarea que Docker Compose no puede hacer?

¡Esta característica es muy necesaria!

@ lucile-pegajoso

¿Por qué no es este el papel de Docker Compose?

Porque el rol de Compose está claramente definido y no incluye esas funciones.

Compose es una herramienta para definir y ejecutar aplicaciones Docker de varios contenedores. Con Compose, usa un archivo Compose para configurar los servicios de su aplicación. Luego, usando un solo comando, crea e inicia todos los servicios desde su configuración

Si el punto es tener solo una herramienta para gobernarlos a todos, ¿por qué debería usar otra herramienta para completar una tarea que Docker Compose no puede hacer?

No queremos ser la única herramienta para gobernarlos a todos. Seguimos la filosofía de UNIX y creemos en "hacer que cada programa haga bien una cosa. Para hacer un nuevo trabajo, compile de nuevo en lugar de complicar los programas antiguos añadiendo nuevas funciones".
Está bien no estar de acuerdo con esa filosofía, pero así es como desarrollamos software en Docker.

Creo este número, en agosto de 2015, cada año alguien agrega un comentario y estamos repitiendo las mismas preguntas con las mismas respuestas (y seguro que verás a @dnephin haciendo un anuncio para su herramienta).

@espinilla-

No puede separar "compilar" y "aprovisionar" en las herramientas de orquestación.

Por ejemplo, quizás conozcas a alguno de ellos:

Cuando configura un servicio, debe aprovisionarlo. Si implemento un tomcat, tengo que aprovisionarlo con una guerra, si creo una base de datos, tengo que inyectar datos, etc., sin importar cómo deba iniciarse el contenedor (deje que el mantenedor de la imagen lo administre). El propósito principal de un "aprovisionador" en el caso Compose es evitar malentendidos entre "lo que inicia mi contenedor" y "lo que lo proporciona".

Como dice tu cita en el documento de redacción "Con Compose, usas un archivo Compose para configurar los servicios de tu aplicación. Luego , usando un solo comando, creas e inicias todos los servicios desde tu configuración"

¿Filosofía Unix? Deja que me ria. Le señalo la misma respuesta que hice en este número https://github.com/docker/compose/issues/1809#issuecomment -237195021.
Veamos cómo evolucionará "moby" en la filosofía Unix.

@ shin- docker-compose no se adhiere a la Filosofía Unix por ningún tramo de la imaginación. Si docker-compose se adhiriera a la filosofía de Unix, habría comandos discretos para cada uno de build, up, rm, start, stop, etc. y cada uno tendría un stdin, stdout y stderr utilizables que se comportarían de manera consistente. dice el administrador de sistemas de Unix con más de 20 años de experiencia, incluidos System V, HP-UX, AIX, Solaris y Linux

Volvamos a la descripción general para redactar

Compose es una herramienta para definir y ejecutar aplicaciones Docker de varios contenedores. Con Compose, usa un archivo Compose para configurar los servicios de su aplicación. Luego, con un solo comando, crea e inicia todos los servicios desde su configuración.

En última instancia, docker-compose es una herramienta de orquestación para administrar un grupo de servicios basados ​​en contenedores creados a partir de imágenes de docker. Sus funciones principales son 'crear', 'iniciar', 'detener', 'escalar' y 'eliminar' servicios definidos en un archivo docker-compose.yml.

Muchos servicios requieren que se ejecuten comandos adicionales durante cada una de estas transiciones del ciclo de vida. escalar clústeres de bases de datos a menudo requiere unirse o eliminar miembros de un clúster. escalar aplicaciones web a menudo requiere notificar a un equilibrador de carga que ha agregado o eliminado un miembro. a algunos administradores de sistemas paranoicos les gusta vaciar a la fuerza los registros de sus bases de datos y crear puntos de control cuando cierran sus bases de datos.

La mayoría de las herramientas de orquestación necesitan tomar medidas sobre la transición de estado. Lo encontrará en las herramientas de AWS, las herramientas de Google, el capataz, el chef, etc. La mayoría de las cosas que viven en este espacio de orquestación tienen algún tipo de gancho de ciclo de vida.

Creo que esto está firmemente en el ámbito de docker-compose dado que es una herramienta de orquestación y es consciente de los cambios de estado. No creo que los eventos o los scripts externos se ajusten al caso de uso. No son idempotentes, es mucho más difícil lanzar un 'segundo' servicio al lado de componer para seguir los eventos. Si los ganchos se ejecutan dentro o fuera del contenedor es un detalle de implementación.

Al final del día, existe una necesidad real que están expresando los usuarios de docker-compose y @aanand , @dnephin , @ shin- parecen estar descartándola. Sería bueno ver esto incluido en una hoja de ruta.

Este tipo de funcionalidad bloquea actualmente mi adopción de Docker en mis implementaciones de producción de prueba y producción. Realmente me gustaría ver que esto se aborde de alguna manera en lugar de descartarlo.

¡Creo que esto será muy útil!

Para mí, el problema es que cuando hay un contenedor de aplicaciones A en ejecución, el servicio 'a' depende del contenedor de bases de datos B que ejecuta el servicio b. Entonces, un contenedor falla a menos que su b esté configurado.
Preferiría usar imágenes de Docker Hub en lugar de volver a escribir mis propios Dockerfiles. Pero esto significa que A falla y no se crea ningún contenedor. La única opción de lo contrario es

  1. Use B como imagen base y cree mi propio Dockerfile.
  2. Deje que A falle y configure b en el script y reinicie A.

Tengo exactamente el mismo caso de uso que @ lucile-sticky.

@lekhnath para mi caso, lo resolví editando la opción command en mi docker-compose.yml :

command: bash -c "npm install && node"

Pero es tan feo TT

@ lucile-sticky Sin embargo, debe tenerse en cuenta que esto anula cualquier comando establecido en el Dockerfile del contenedor. Trabajé alrededor de esto montando un script de shell personalizado usando volumes , haciendo que el command en mi archivo Docker Compose ejecute ese script e incluyendo en él el CMD de Dockerfile .

¿Por qué está cerrado este problema? _escribir un script bash_ o _usar esta herramienta que escribí_ no es una razón válida para cerrar este problema.

Esta es una característica muy útil e importante que se requiere en muchos casos de uso donde se usa componer.

@dnephin ¿Crees que ejecutar scripts de inicio está fuera del alcance de las implementaciones de aplicaciones basadas en contenedores? después de todo, componer se trata de "definir y ejecutar aplicaciones de varios contenedores con Docker".

Haz que alguien haya mirado a dobi si no lo has hecho, por favor hazlo aquí :)
image

Adivinando que nunca pasó nada con esto. Me encantaría ver algún tipo de funcionalidad dentro del archivo docker-compose donde podríamos escribir cuándo se debe ejecutar un comando, como el ejemplo que dio @ ahmet2mir .

Es muy triste ver que esta función no se está implementando.

Implemente esta función, por favor, necesito instalar archivos automáticamente después de docker-compose, ya que las carpetas donde se debe copiar el archivo se crean después de la inicialización de los contenedores.
Gracias

¡Es increíble que aún no se haya implementado esta función!

Esta es una forma muy pobre de @dnephin. Ha inhibido la implementación de una función tan buscada para lo que parece principalmente autopromoción, y ni siquiera está dispuesto a continuar la conversación.

Lo siento, no pude pensar en un lenguaje más suave para decirlo, la falta de esta función ha agregado una fracción a nuestro flujo de trabajo, como muchos otros desarrolladores y equipos, y usted ha sido un obstáculo para resolver este problema ...

Oh, vamos a convertirlo en unix-way entonces.
_Just_ (multiplexar entonces) canalizar docker-compose up stdin a cada contenedor ' CMD ?
Para que un archivo yaml

services:
  node:
    command: sh -

haría que esto funcione: cat provision.sh | docker-compose up
contenedores son para cosas buyendo exec, no veo un mejor uso de la entrada estándar de pasar a lo largo de comandos.

Una alternativa podría ser:

services:
  node:
    localscript: provision.sh

Aunque un poco centrado en el shell, resolvería el 99% de los casos de uso de aprovisionamiento.

Aunque hay casos de uso válidos y muchos votos a favor sobre esto ... aparentemente todavía se ha negado. Es una pena, ya que yo, como muchos otros aquí, encontraría esto extremadamente útil.

Agregar mi +1 a la gran pila de + existentes

... otro +1 aquí!

Creo que si existe tal solicitud de esta función, debería implementarse, las herramientas están aquí para ayudarnos a alcanzar nuestros objetivos y debemos moldearlas para ayudarnos a no hacernos la vida más difícil.
Entiendo la filosofía a la que alguien se adhiere, pero agregar algún tipo de "comandos de gancho" no debería ser un problema.

+1 +1

Mientras espero esta función, utilizo el siguiente script para realizar una tarea similar:

docker-start.sh

#!/usr/bin/env bash

set -e
set -x

docker-compose up -d
sleep 5

# #Fix1: Fix "iptable service restart" error

echo 'Fix "iptable service restart" error'
echo 'https://github.com/moby/moby/issues/16137#issuecomment-160505686'

for container_id in $(docker ps --filter='ancestor=reduardo7/my-image' -q)
  do
    docker exec $container_id sh -c 'iptables-save > /etc/sysconfig/iptables'
  done

# End #Fix1

echo Done

@ reduardo7 Entonces, también podría eliminar docker-compose por completo, de esa manera tendrá una dependencia menos.

@omeid , ¡tienes razón! Es una solución para realizar una tarea similar, ¡lo siento!

@ reduardo7 No hay necesidad de disculparse, lo que ha publicado probablemente será útil para algunas personas.
Solo estaba señalando que el problema original sigue en pie y no debería haberse cerrado. :)

Entiendo los soportes de

Sin embargo, si tales patrones se usan con frecuencia, ¿qué tal si presentamos una guía (o alguna prueba) para que otros puedan usarla fácilmente?

No parece haber ningún desacuerdo en cuanto a que este patrón se puede utilizar con frecuencia.

@MaybeS El único desacuerdo es que @dnephin prefiere ver promocionada su estúpida herramienta en lugar de ayudar a que docker-compose sea un mejor producto.

@omeid sí, de hecho.

ejemplo de hoy de querer una forma de componer para hacer alguna forma de onrun

version: "3.3"
services:
  gitlab:
    image: 'gitlab/gitlab-ce:latest'
    restart: always
    hostname: 'gitlab'
    environment:
      GITLAB_OMNIBUS_CONFIG: |
        # NOTE: this URL needs to be right both for users, and for the runner to be able to resolve :() - as its the repo URL that is used for the ci-job, and the pull url for users.
        external_url 'http://gitlab:9090'
        gitlab_rails['gitlab_shell_ssh_port'] = 2224
    ports:
      - '9090:9090'
      - '2224:22'
  gitlab-runner:
    image: gitlab/gitlab-runner:latest
    volumes:
    - /var/run/docker.sock:/var/run/docker.sock

y, por supuesto, el corredor no está registrado, y para hacer eso, necesitamos

  1. sacar el token de la base de datos en gitlab
  2. ejecutar registro en el contenedor del corredor

así que en lugar de definir la implementación de mi aplicación de múltiples contenedores en solo docker-compose, necesito usar algunos medios secundarios, en este caso ... ¿docs?

export GL_TOKEN=$(docker-compose exec -u gitlab-psql gitlab sh -c 'psql -h /var/opt/gitlab/postgresql/ -d gitlabhq_production -t -A -c "SELECT runners_registration_token FROM application_settings ORDER BY id DESC LIMIT 1"')
docker-compose exec gitlab-runner gitlab-runner register -n \
  --url http://gitlab:9090/ \
  --registration-token ${GL_TOKEN} \
  --executor docker \
  --description "Docker Runner" \
  --docker-image "docker:latest" \
  --docker-volumes /var/run/docker.sock:/var/run/docker.sock \
  --docker-network-mode  "network-based-on-dirname-ew_default"

mmm, es posible que pueda piratear algo, por lo que tengo otro contenedor que tiene el conector de la ventana acoplable y docker exec's

qué apostar, hay una manera ...

por ejemplo, puedo agregar:

  gitlab-initializer:
    image: docker/compose:1.18.0
    restart: "no"
    volumes:
    - /var/run/docker.sock:/var/run/docker.sock
    - ./gitlab-compose.yml:/docker-compose.yml
    entrypoint: bash
    command: -c "sleep 200 && export GL_TOKEN=$(docker-compose -p sima-austral-deployment exec -T -u gitlab-psql gitlab sh -c 'psql -h /var/opt/gitlab/postgresql/ -d gitlabhq_production -t -A -c \"SELECT runners_registration_token FROM application_settings ORDER BY id DESC LIMIT 1\"') && docker-compose exec gitlab-runner gitlab-runner register -n --url http://gitlab:9090/ --registration-token ${GL_TOKEN} --executor docker --description \"Docker Runner\" --docker-image \"docker:latest\" --docker-volumes /var/run/docker.sock:/var/run/docker.sock --docker-network-mode  \"simaaustraldeployment_default\""

a mi archivo de redacción, aunque necesito algún tipo de bucle / espera, ya que gitlab no está listo de inmediato, sleep 200 podría no ser suficiente.

así que __puedes__ hackear algún tipo de patrón como este directamente en un docker-compose.yml - pero personalmente, prefiero un soporte más limpio que este :)

@SvenDowideit onrun ya existe, es entrypoint o cmd .

El script de punto de entrada para esta imagen incluso le proporciona un gancho. $GITLAB_POST_RECONFIGURE_SCRIPT se puede establecer en la ruta de un script que se ejecutará después de que se complete toda la configuración (ver /assets/wrapper en la imagen). Establezca la variable env en la ruta de su script que hace el registro psql + y ya está todo listo.

Incluso si la imagen no proporcionó este gancho, es algo que se puede agregar con bastante facilidad extendiendo la imagen.

aunque necesito algún tipo de bucle / espera, ya que gitlab no está listo de inmediato, dormir 200 podría no ser suficiente.

Esto sería necesario incluso con la opción "exec-after-start". Dado que el script de punto de entrada en realidad proporciona un gancho, creo que probablemente no sea necesario con esa solución.

no, creo que te has perdido una parte del problema que estoy mostrando:

en mi caso, necesito acceso a ambos contenedores, no solo a uno, por lo que el punto de entrada / comando _no_ me da esto.

GL_TOKEN proviene del contenedor gitlab y luego se usa en el contenedor gitlab-runner para registrarse.

así que el truco que estoy haciendo es usar la imagen docker/compose para agregar un tercer contenedor; esto no es algo para lo que pueda modificar la configuración / punto de entrada / configuración de un contenedor, y es completamente un ejemplo (trivial) de un coordinación de contenedores múltiples que necesita más.

He estado trabajando en cosas para hacerlas un poco más mágicas, lo que básicamente significa que mi contenedor de inicialización tiene algunos ciclos de suspensión, ya que a gitlab le toma algo de tiempo iniciarse.

TBH, estoy empezando a sentir que usar un script, que se ejecuta en un contenedor de inicio que usa el archivo de composición en sí y la ventana acoplable / componer, es la forma correcta de ocultar este tipo de complejidad, para los que no son de producción, intente me fuera, y simplemente funcionará "situaciones como esta.

_ SI_ tuviera que considerar un poco de azúcar sintáctico extraño para ayudar, tal vez optaría por algo como:

gitlab-initializer:
    image: docker/compose:1.18.0
    restart: "no"
    volumes:
    - /var/run/docker.sock:/var/run/docker.sock
    - ./gitlab-compose.yml:/docker-compose.yml
    entrypoint: ['/bin/sh']
    command: ['/init-gitlab.sh']
    file:
      path: /init-gitlab.sh
      content: |
            for i in $(seq 1 10); do
                export GL_TOKEN=$(docker-compose -f gitlab-compose.yml -p sima-austral-deployment exec -T -u gitlab-psql gitlab sh -c 'psql -h /var/opt/gitlab/postgresql/ -d gitlabhq_production -t -A -c "SELECT runners_registration_token FROM application_settings ORDER BY id DESC LIMIT 1"')
                echo "$i: token($?) == $GL_TOKEN"
                ERR=$?

                if [[ "${#GL_TOKEN}" == "20" ]]; then
                    break
                fi
                sleep 10
            done
            echo "GOT IT: token($ERR) == $GL_TOKEN"

            for i in $(seq 1 10); do
                if  docker-compose -f gitlab-compose.yml  -p sima-austral-deployment exec -T gitlab-runner \
                    gitlab-runner register -n \
                    --url http://gitlab:9090/ \
                    --registration-token ${GL_TOKEN} \
                    --executor docker \
                    --description "Docker Runner" \
                    --docker-image "docker:latest" \
                    --docker-volumes '/var/run/docker.sock:/var/run/docker.sock' \
                    --docker-network-mode  "simaaustraldeployment_default" ; then
                        echo "YAY"
                        break
                fi
                sleep 10
            done

es decir, como cloud-init: http://cloudinit.readthedocs.io/en/latest/topics/examples.html#writing -out-arbitrary-files

pero cuando se trata de eso - tenemos_ una solución para coordinar cosas complicadas de múltiples contenedores desde dentro de un docker-compose-yml.

Si puede configurar un token predefinido, puede hacerlo desde un script de punto de entrada en gitlab-runner . ¿No hay forma de establecer ese límite de tiempo?

@dnephin En el momento en que mencionas el guión, estás fuera de lugar por un año luz y algo más.

onrun no es lo mismo que entrypoint o cmd .

El entrypoint / cmd es para configurar el ejecutable que se ejecutará como los contenedores init / PID 1.

La idea mencionada en este y muchos temas relacionados es sobre init scripts , que es diferente de init en el contexto del arranque, y se trata de scripts de inicio de aplicaciones, piense en la configuración de la base de datos.

@dnephin , probablemente sería más útil si se enfocara en el conjunto de problemas generales, en lugar de tratar de solucionar los problemas de un conjunto de contenedores específico.

Sin embargo, por lo que he visto, no, es un secreto generado, pero en realidad, este no es el único requisito de coordinación de contenedores múltiples en incluso este pequeño sistema de juego, es el más rápido para mí. prototipo en público.

¿Cómo es posible que hayamos podido anular entrypoint y command en un archivo de redacción desde v1 (https://docs.docker.com/compose/compose-file/compose-file -v1 / # entrypoint) y aún no tiene una directiva como onrun para ejecutar un comando cuando los contenedores están activos?

TBH, realmente no creo que onrun sea ​​plausible - Docker, o el orquestador no sabe qué significa "los contenedores están todos arriba" - en uno de mis casos, HEALTHCHECK fallará, hasta después de que yo lo haga algunas "cosas" extra donde obtengo información de un contenedor y la uso para patear algunas otras cosas en otros contenedores.

Y _si_ asimilo bien, esto significa que básicamente necesito un contenedor de operador, que contiene un código que detecta cuando algunas partes del sistema de contenedores múltiples están lo suficientemente listas para hacer parte del trabajo (enjuagar y repetir), hasta o ha completado su trabajo y sale, o tal vez incluso supervisa las cosas y las arregla.

Y esto me parece un trabajo que se resuelve mejor (en docker-compose) mediante un contenedor de docker-compose con código.

Probablemente voy a jugar con cómo convertir este operador en algo que pueda lidiar con las pilas de enjambre de Docker (debido a otras necesidades del proyecto).

No estoy del todo seguro de que haya mucho azúcar sintáctico que pueda agregarse a docker-compose, a menos que sea algo como marcar un contenedor como "este es un operador, dale habilidades mágicas".

Se ve claramente que los desarrolladores no quieren escuchar a los usuarios ... Veré alguna otra herramienta ... docker-compose es un gran problema ... No entiendo por qué no puedes entender que lo único útil que viene de docker-composer es una herramienta de compilación ... Pasé mucho tiempo buscando CÓMO puedo ejecutar el comando SIMPLE para agregar permisos dentro de un contenedor al usuario activo ...

Parece que el docker-composer simplemente NO HIZO el estado ...

Yo también quiero algo que onrun en mi archivo de redacción

__PERO__, ni los contenedores ni los componentes tienen una forma de saber qué significa onrun . Es por eso que existe el patrón de operador y por qué hice los ejemplos en https://github.com/docker/compose/issues/1809#issuecomment -362126930

__es__ posible hacer esto hoy; en esencia, agrega un servicio onrun que espera hasta que cualquier otro servicio esté realmente listo para interactuar (en el caso de gitlab, eso lleva bastante tiempo), y luego todo lo que necesites hacer para coordinar las cosas.

Si hay algo que no funciona con eso, por favor díganos y veremos si podemos encontrar algo.

Yo también quiero algo que se ejecute en mi archivo de redacción.

PERO, ni los contenedores ni los componentes tienen una forma de saber qué significa onrun.

Como lo veo, onrun por servicio, significa cuándo comienza el primer proceso de contenedor. En un mayor número de casos, el contenedor solo ejecuta un proceso de todos modos, ya que esta es la forma recomendada de ejecutar contenedores.

El problema del soporte multiplataforma se resolvió anteriormente, ya que el comando puede ser completamente independiente del sistema operativo a través de docker exec , de la misma manera que RUN no tiene por qué significar un comando de Linux en Dockerfile.
https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-docker/manage-windows-dockerfile

Todavía esperando la función onrun

También necesito estas características onrun , pensé que estaba en esta herramienta. Debido a esta característica deficiente ahora necesito mantener 2 scripts man.

Chicos, ¿qué pasa si hago un envoltorio alrededor de esta docker-compose y permito esta función onrun ? ¿Lo usarían ustedes?

@wongjiahau puede ser algo como esto? https://github.com/docker/compose/issues/1809#issuecomment -348497289

@ reduardo7 Sí, pensé en envolverlo dentro de un script llamado docker-composei , y con el docker-composei.yml que contiene el atributo onrun
Por cierto, docker-composei significa docker-compose improved .

La solución real es probablemente construir una imagen de 'Orquestador' que ejecute y administre (a través de scripts bash) las 'Imágenes de la aplicación' (posiblemente usando la ventana acoplable) internamente. De lo contrario, siempre estaremos solicitando más funciones para una herramienta que "no está destinada a hacer lo que queremos que haga".

Así que incluso deberíamos considerar Docker dentro de Docker ...

solo para agregar mi apoyo para esta característica propuesta. onrun tiene sentido, pero para ampliar la utilidad potencial y prepararla un poco para el futuro, tal vez alguien necesite mirar una arquitectura de 'un solo evento' más amplia, una de las cuales estaría en ejecución.

Dada la dirección predominante para que los contenedores sean autónomos, un servicio por contenedor, el contenedor debe ser autosuficiente en términos de su conocimiento del contexto operativo. Lo que fluye de eso, el archivo de redacción debe ser el medio para definir eso, no los scripts atornillados. Es difícil argumentar en contra de eso, a menos que seas un fanático ensimismado.

En mi caso, mis contenedores redis cargan scripts lua después de que el servidor redis se haya iniciado. En un entorno normal sin contenedor, consigo que systemd ejecute un script posterior al inicio. Simple y consistente con la arquitectura systemd. Debería existir un principio similar para la redacción dado su papel en la configuración del contexto para que se ejecuten los contenedores.

Como consejo general para los mantenedores, céntrese en principios operativos probados, no en preferencias personales.

entonces la solución (después de leer todo este hilo) es usar un script bash para hacer el trabajo ... en ese caso eliminaré docker-compose (podemos hacer todo con el cmd de docker ...)

gracias dev para escuchar a las personas que están usando tus cosas :)

Al ver la cantidad de mensajes que contienen argumentos y contraargumentos que luchan contra proposiciones simples (como tener un evento onrun ), mi primera impresión honesta es que Github Issues se ha convertido en un lugar donde los _propietarios_ (desarrolladores de proyectos) muestran sus egos e inteligencia mediante el uso de su conocimiento y argón técnico para oponerse a la contribución inteligente de los usuarios.

Por favor, hagamos que Open Source sea verdaderamente _abierto_.

¿Alguna actualización de esta función? ¿Cuál es el problema?

@ v0lume Supongo que no se molestó en leer las respuestas a lo largo de este artículo.

Todavía no parece haber una solución ... Sin embargo, me gustaría compartir una solución hacky.
Si especifica la versión "2.1" en el docker-compose.yml, puede abusar de la prueba de verificación de estado para ejecutar código adicional dentro de la imagen cuando se inicia. Aquí hay un ejemplo:

version: '2.1'
services:
    elasticsearch:
        image: docker.elastic.co/elasticsearch/elasticsearch:5.4.3
        healthcheck:
            test: |
                curl -X PUT elasticsearch:9200/scheduled_actions -H "ContentType: application/json" -d '{"settings":{"index":{"number_of_shards":'1',"number_of_replicas":'0'}}}' &&
                curl --silent --fail localhost:9200/_cat/health ||
                exit 1
            interval: 11s 
            timeout: 10s 
            retries: 3
        environment:
            - discovery.type=single-node
            - ES_JAVA_OPTS=-Xms1g -Xmx1g
            - xpack.security.enabled=false
    main:
        image: alpine
        depends_on:
            elasticsearch:
                condition: service_healthy

Si el script de prueba de salud que escribe sale con el código> = 1, podría ejecutarse varias veces.
La verificación de estado de un servicio solo se ejecutará si otro servicio depende de él y especifica la condición service_healthy como se ve en el ejemplo.

Me gusta el enfoque @ T-vK y lo he usado con éxito antes. Pero me gustaría compartir otro ... truco:

# Run Docker container here

until echo | nc --send-only 127.0.0.1 <PORT_EXPOSED_BY_DOCKER>; do
  echo "Waiting for <YOUR_DOCKER> to start..."
  sleep 1
done

# Do your docker exec stuff here

+1
Estoy totalmente de acuerdo con esto porque la función es necesaria y ya está implementada por otros orquestadores de Docker como Kubernetes. Ya tiene enlaces de ciclo de vida para contenedores y está documentado aquí .

Pero déjame contribuir con un caso de uso que no puedes resolver con Dockerfiles.

Supongamos que necesita montar un volumen en tiempo de ejecución y crear un enlace simbólico desde su contenedor al volumen sin saber previamente el nombre exacto del directorio. Tuve un caso de que el nombre del directorio era dinámico dependiendo del entorno en el que estaba implementando y lo estaba pasando como una variable.

Seguro que encontré una solución para resolver esto y hay más de una. Por otro lado, los ganchos me darían la flexibilidad y un mejor enfoque para realizar cambios dinámicamente sin la necesidad de piratear cosas y reemplazar el Dockerfile.

Me alegro de haber encontrado este problema. He estado jugando con Docker y Docker compon durante un par de años. Ahora, en serio, esperaba usarlo como una herramienta para comenzar a escalar un sistema. Lo comprobaré cada año o dos, pero según la actitud de los encargados del proyecto, simplemente lo conseguiré mediante el uso de scripts o alguna otra herramienta. Me alegro de no haber invertido mucho tiempo y haber descubierto este desde el principio.

Consejo profesional: si alguien que recién está comenzando a trasladar su flujo de trabajo a este tipo de herramienta ya necesita lo que se describe aquí, podría valer la pena volver a pensar en el "por qué" está creando esto. Sí, tienes éxito, pero es porque la gente lo utilizó en primer lugar y probablemente estabas muy abierto a darles lo que necesitaban.

Todo lo mejor.

Puedo darte lo que quieras (excepto mi novia) si se implementa esta función y seré la persona más feliz de todo el universo :)

solo para agregar mi apoyo para esta característica propuesta. onrun tiene sentido, pero para ampliar la utilidad potencial y prepararla un poco para el futuro, tal vez alguien necesite mirar una arquitectura de 'un solo evento' más amplia, una de las cuales estaría en ejecución.

Eso estaría bien.

Para agregar a esto, dado lo siguiente:

services:
    web:
        image: node:8-alpine
        depends_on:
            - db
    db:
        image: postgres:alpine
        onrun: "echo hi"

¿Sería demasiado agregar secuencias de comandos de eventos cruzados?

    web:
        events:
            db_onrun: "connectAndMigrate.sh"

En mi opinión, agregar esto a docker-compose es sencillo, no solo para usted, que está usando compose file y compose stack, sino también para otros desarrolladores de su equipo.

  • Uso de contenedores separados: todos deben saber que deben ejecutarlos.
  • Escriba Dockerfile personalizado: tenemos alrededor de 20 servicios y para cada servicio debería anular Dockerfile para ejecutar algún comando.

Necesitamos instalar y configurar mkcert , por ejemplo, en cada entorno para tener certificados confiables. No es parte de contenedor o Dockerfile ya que no es necesario en el escenario / producción. ¿Cuál es el enfoque adecuado aquí para instalar la herramienta y todos los que usan el archivo de redacción ni siquiera tienen idea de lo que está sucediendo detrás de escena?

Añadiendo otro caso de uso:

Necesitaba una instancia de wordpress. Escribí mi docker-compose.yaml. docker-compose up - ¡Vaya! Necesito configurar los permisos de archivo del directorio de complementos ... No puedo encontrar ninguna otra forma de hacerlo funcionar, debo configurar los permisos después de que el contenedor se esté ejecutando porque estoy vinculando algunos archivos del host y parece que es la única forma para arreglar los permisos fs es haciendo chown -Rf www-data.www-data /var/www/wp-content desde dentro del contenedor. ¿Escribir mi propio Dockerfile y compilar, solo para esto? Eso me parece una estupidez.

Afortunadamente para mí, el truco healthcheck proporcionado anteriormente me permitió implementar esto. Veo otras páginas en la web que hablan sobre el problema de los permisos de configuración en los volúmenes de la ventana acoplable, pero las soluciones sugeridas no funcionaron.

Me alegra ver que estos guardianes, @dnephin , @aanand , @ shin-, están recibiendo un montón de críticas por esto. Realmente dice mucho cuando una comunidad entera grita tan fuerte como sea posible, y los desarrolladores centrales simplemente se sientan, se mantienen firmes y se niegan a escuchar. Tan típico también. Vamos a contar no solo el número de pulgares hacia arriba, sino también los 34 usuarios que respondieron diciendo que esto es necesario:
01) sshishov
02) fescobar
03) sandor11
04) web-ted
05) v0lume
06) webpolis
07) Calavera
08) usuario bueno
09) wongjiahau
10) MFQ
11) yosefrow
12) bagermen
13) daqSam
14) omeid
15) dantebarba
16) willyyang
17) SharpEdgeMarshall
18) portador perdido
19) fantasma
20) rodrigorodriguescosta
21) tipo de datos
22) dextermb
23) lekhnath
24) lucile-pegajoso
25) rav84
26) dopry
27) ahmet2mir
28) montera82
29) pez discordia
30) jasonrhaas
31) fferraris
32) hipergiga
33) asoleado
34) sthulb

¿Y el número que dijo que no? Un enorme 3:
01) dnefina
02) aanand
03) brillo

Mmmm ... 34 a 3 ...

@ rm-rf-etc buena analítica ... Ya ni siquiera creo que @dnephin o @aanand estén trabajando en docker-compose. Con suerte, Docker planea desaprobar la composición en favor de las pilas y no quedará un equipo aquí del que quejarse y comenzaremos a ver el progreso del producto nuevamente.

Añadiendo otro caso de uso:

Necesitaba una instancia de wordpress. Escribí mi docker-compose.yaml. docker-compose up - ¡Vaya! Necesito configurar los permisos de archivo del directorio de complementos ... No puedo encontrar ninguna otra forma de hacerlo funcionar, debo configurar los permisos después de que el contenedor se esté ejecutando porque estoy vinculando algunos archivos del host y parece que es la única forma para arreglar los permisos fs es haciendo chown -Rf www-data.www-data /var/www/wp-content desde dentro del contenedor.

En este caso, también puede establecer la propiedad user en su archivo de composición

¿Escribir mi propio Dockerfile y compilar, solo para esto? Eso me parece una estupidez.

Parece que se ha formado una opinión sólida; pero de manera realista, no habría nada "estúpido" en escribir un Dockerfile para modificar una imagen base para que se ajuste a sus necesidades. Esa es la intención original de todas las imágenes base.

Afortunadamente para mí, el truco healthcheck proporcionado anteriormente me permitió implementar esto. Veo otras páginas en la web que hablan sobre el problema de los permisos de configuración en los volúmenes de la ventana acoplable, pero las soluciones sugeridas no funcionaron.

Me alegra ver que estos guardianes, @dnephin , @aanand , @ shin-, están recibiendo un montón de críticas por esto.

Sí, buen amigo de actitud. :RE


@ rm-rf-etc buena analítica ... Ya ni siquiera creo que @dnephin o @aanand estén trabajando en docker-compose.

Sí, han pasado algunos años, no es necesario seguir haciendo ping sobre problemas antiguos.

Con suerte, Docker planea desaprobar la composición en favor de las pilas y no quedará un equipo aquí del que quejarse y comenzaremos a ver el progreso del producto nuevamente.

🙄

@ shin- pero solo hiciste ping con esa respuesta

Recientemente me encontré con este problema nuevamente y, aunque se puede hacer como se ve en mi solución alternativa , esto solo funciona si especifica 2.1, que apesta en mi opinión.

Es simplemente alucinante para mí que la postura oficial parece ser que debes crear tus propias imágenes de la ventana acoplable para todo.
Para mí, esto es literalmente como decir "Si desea cambiar una configuración en cualquier programa, debe modificar el código fuente y volver a compilarlo".
Cada vez que agregue un nuevo servicio o desee actualizar a una versión más reciente de ... por ejemplo, la imagen de MongoDB o MySQL Docker, tendrá que crear un nuevo Dockerfile, compilarlo y potencialmente insertarlo en su registro.
Esta es una enorme pérdida de tiempo y recursos en comparación con lo que sería si pudiera cambiar image: mongo:3.0.2 a image: mongo:3.0.3 en su docker-compose.yml.
No estoy despotricando sobre los tiempos de compilación largos, estoy despotricando sobre el hecho de que tienes que preocuparte por Dockerfiles y docker build cuando todo lo que quieres es actualizar o cambiar un parámetro de un servicio que potencialmente destinado a ser utilizado como imagen base.

Y el argumento de que cada aplicación debería hacer una cosa y solo una cosa, también apesta. Ni siquiera se trata de implementar una característica completamente nueva, se trata solo de pasar otro parámetro a docker . También plantea la pregunta de por qué docker run , docker build , docker exec , docker pull etc. son todos parte de la misma aplicación. El argumento suena un poco hipócrita ahora, ¿no?

@ shin-, seguí su enlace y no veo cómo la propiedad del usuario es relevante para configurar el propietario de un directorio montado en enlace. Parece estar relacionado con los puertos.

Re: actitud: Parece que la gente está de acuerdo conmigo, así que tómelo como una fuerte retroalimentación. Lo siento si no le gusta cómo estoy expresando esto, pero realmente parece que las demandas del usuario están siendo ignoradas, entonces, ¿qué más espera?

Vine aquí con la esperanza de que la funcionalidad, como onrun: se sugiera, ya que solo llevo dos días usando compose y para mí, una herramienta como esta debería tener esta funcionalidad.

Volver a mis archivos de la ventana acoplable para actualizar cada uno con una secuencia de comandos separada para las funciones parece redundante. Simplemente quiero inyectar un token de otro contenedor en una variable de entorno donde mi dockerfile era flexible antes, ahora está estrechamente acoplado a docker-composer.yml y la solución para un propósito simple.

Maldita sea, leí todo el hilo con la esperanza de encontrar la respuesta "ok chicos, finalmente nos dimos cuenta de que esto es genial y lo implementaremos". Es triste ver que esto no avanzó.
+1 para correr!

@fabiomolinar , Hay un tipo de solución que usamos ampliamente en nuestros enjambres de producción, pero no es tan agradable como tener un evento.

Usamos el siguiente ancla

#### configure a service to run only a single instance until success
x-task: &task
  # for docker stack (not supported by compose)
  deploy:
    restart_policy:
      condition: on-failure
    replicas: 1
  # for compose (not supported by stack)
  restart: on-failure

para repetir las tareas hasta que tengan éxito. Creamos contenedores para migraciones y tareas de configuración que tienen resultados idempotentes y los ejecutamos así en nuestra composición local y en nuestras pilas.

El servicio que depende de la tarea debe fallar con cierta gracia si el trabajo de configuración no está completo. En la mayoría de los casos, siempre que esté de acuerdo con algunos errores que llegan a los usuarios finales, esto le brinda una consistencia eventual que funcionará bien en la mayoría de los entornos.

También asume que sus contenedores de servicio pueden funcionar con estados de finalización de tareas anteriores y posteriores. En casos de uso como las migraciones de bases de datos, los servicios dependientes deberían poder trabajar con esquemas previos y posteriores a la migración ... obviamente se debe pensar en la coordinación del desarrollo y la implementación, pero ese es un hecho general para cualquier persona haciendo actualizaciones continuas de servicios.

@fabiomolinar , aquí hay un ejemplo de cómo usamos este enfoque en nuestros servicios de

#### configure a service to run only a single instance until success
x-task: &task
  # for docker stack (not supported by compose)
  deploy:
    restart_policy:
      condition: on-failure
    replicas: 1
  # for compose (not supported by stack)
  restart: on-failure

#### configure a service to always restart
x-service: &service
  # for docker stack (not supported by compose)
  deploy:
    restart_policy:
      condition: any
  # for compose (not supported by stack)
  restart: always

services: 
  accounts: &accounts
    <<: *service
    image: internal/django
    ports:
      - "9000"
    networks:
      - service
    environment:
      DATABASE_URL: "postgres://postgres-master:5432/accounts"
      REDIS_URL: "hiredis://redis:6379/"

  accounts-migrate:
    <<: *accounts
    <<: *task
    command: ./manage.py migrate --noinput

Gracias por señalar eso @dopry. Pero mi caso fue algo más sencillo. Necesitaba hacer funcionar mi servidor y luego, solo después de que estuviera en funcionamiento, necesitaba hacer algunas tareas de implementación. Hoy encontré una manera de hacerlo realizando una pequeña gestión de procesos dentro de una sola línea CMD . Imagine que el servidor y los procesos de implementación se llaman server y deploy , respectivamente. Luego usé:

CMD set -m; server $ deploy && fg server

La línea de arriba activa el modo de monitor de bashes, luego inicia el proceso server en segundo plano, luego ejecuta el proceso deploy y finalmente trae el proceso server al primer plano nuevamente para evitar que Docker maten el contenedor.

Mientras discutimos esto, ¿alguien tiene algún consejo sobre cómo ejecutar un comando en el contenedor o en el host al ejecutar docker-compose up ?

Entiendo que ejecutar cualquier comando en el host comprometería las capas de seguridad, pero solo me gustaría rm un directorio antes o durante el inicio de un contenedor. El directorio es accesible tanto en el host como en el contenedor. No quiero hacer una imagen personalizada de Docker o tener un script que primero rm y luego ejecute docker-compose .

¡Gracias!

@fabiomolinar , el enfoque que propones viola algunos principios de aplicación de 12 factores . Si va a contener su infraestructura, le recomiendo que se adhiera estrictamente a ellos.

Algunos problemas que podrían surgir de su enfoque

  1. Arranque lento del contenedor.
  2. al escalar un servicio con el contenedor, la implementación se ejecutará una vez para cada instancia, lo que podría generar algunos problemas de concurrencia interesantes.
  3. más difícil de clasificar los registros de la "tarea" y el servicio para la gestión y depuración.

Encontré el enfoque que recomiendo contra la intuición al principio. Ha funcionado bien en la práctica en nuestros entornos de desarrollo local bajo docker-compose, docker swarms y clusters mesos / marathon. También se ha solucionado eficazmente la falta de "onrun".

El enfoque que he utilizado es realmente muy feo. Lo usé por un tiempo solo para hacer funcionar mi entorno de desarrollo. Pero ya lo cambié para usar scripts de punto de entrada y el comando at para ejecutar scripts después de que el servidor esté funcionando. Ahora mi contenedor se está ejecutando con el proceso correcto como PID 1 y responde a todas las señales correctamente.

Todavía necesitamos esto. No puedo encontrar una manera de cómo podría ejecutar los paquetes acumulativos de mi base de datos después de iniciar correctamente el contenedor sin hacerlo en un montón de Makefiles.

@ victor-perov crea otro contenedor para la tarea acumulada y ejecútalo como un servicio separado

Aquí hay algunos fragmentos de uno de nuestros proyectos para mostrar un servicio de tareas para ejecutar una migración de base de datos.

x-task: &task
  # run once deploy policy for tasks
  deploy:
    restart_policy: 
      condition: none
    replicas: 1

service:
  automata-auth-migrate:
    <<: *automata-auth
    <<: *task
    # without the sleep it can't lookup the host postgres.  maybe the command is ran before the network set is complete.
    command: sleep 5 && python /code/manage.py migrate --noinput

Bueno, este es el cuarto año al que se ha extendido esta discusión. Permítanme agregar mi +1 a este caso de uso de una necesidad de onrun . PD: Debería haber comprado palomitas de maíz para todo el hilo.

Yo también pensaría que onrun o equivalente (¿post-ejecución?) Es imprescindible. Agregar un script de envoltura y hacer Docker Exec en el contenedor es simplemente ... feo.

IMO docker compose fue un excelente MVP de orquestación de contenedores para convencer a las personas de que administrar contenedores puede ser fácil. Tal vez nosotros, la comunidad, deberíamos considerarlo en "modo de mantenimiento", ya que han proliferado las soluciones de orquestación listas para producción (es decir, kubernetes). Cuando tiene características avanzadas como dependencias de contenedores, combinadas con características ausentes como "ejecutar esto después de que el contenedor esté listo", parece encajar en la narrativa de que el ritmo de desarrollo simplemente se ha estancado. Como mínimo, no es obvio que esta característica _debería_ considerarse fuera de alcance.

No puede hacer todo fácilmente con Dockerfile. Digamos que desea agregar su propio script a un contenedor.
Por ejemplo, tome el contenedor mysql e intente agregar un script simple para llamar a una API en caso de algún evento.
Puedes hacerlo por:

  • Cambiando Dockerfile de mysql y agregue su propio script al contenedor antes del punto de entrada. No puede agregar CMD en Dockerfile , ya que sería un argumento para ENTRYPOINT .
  • Puede ejecutar el contenedor y luego copiar su script al contenedor en ejecución y ejecutarlo [ docker cp , docker exec ].

Por eso también creo que una característica como onrun es beneficiosa, ya que cambiar el Dockerfile no siempre es suficiente.

Volcado, ¿por qué esto está cerrado? Considere la situación, cuando está usando una imagen oficial de Docker, como Cassandra y necesita cargar el esquema después de que se inicia ... Tiene que implementar su propia solución de script bash para esto ... uf, esto es feo

@somebi parece que redactar está cerrado ...

Solo mis dos centavos: aterricé aquí porque actualmente tengo que habilitar los módulos de Apache manualmente cada vez que inicio el contenedor (SSL no está habilitado de manera predeterminada en la imagen de Docker Hub wordpress ). No es el fin del mundo, pero esperaba ejecutar un par de comandos cada vez que suba para poder subir y bajar los contenedores sin problemas sin tener que golpear.

Solo mis dos centavos: aterricé aquí porque actualmente tengo que habilitar los módulos de Apache manualmente cada vez que inicio el contenedor (SSL no está habilitado de manera predeterminada en la imagen de Docker Hub wordpress ). No es el fin del mundo, pero esperaba ejecutar un par de comandos cada vez que suba para poder subir y bajar los contenedores sin problemas sin tener que golpear.

Bueno, esto podría resolverse fácilmente si crea una nueva imagen basada en la imagen de WordPress, que tenga los módulos que necesita habilitados. Luego utilícelo en su lugar para, por ejemplo, un archivo docker:

FROM wordpress:php7.1
RUN a2enmod ssl

Otra solución sería descargar el Dockerfile de wordpress y agregarle la activación del módulo. Luego, produzca una nueva imagen para usted mismo usando Docker build. Por ejemplo, este es el Dockerfile para wordpress 5.2 con php 7.1:

wordpress dockerfile

puede habilitar más módulos en la línea 63 o ejecutar ssl genaration.

Todo este no es el caso que creo que estamos debatiendo aquí. El problema es crear ganchos dinámicos en el ciclo de vida del contenedor, como cuando comienza, termina, etc.

¡Esta sería una buena adición a docker-compose!

Respuestas como las de este hilo son la razón por la que Kubernetes se está quedando con "todo" el dinero que produce Docker (tecnología), y no es algo malo, con suerte, alguien comprará Docker (empresa) pronto y cambiará la forma en que las propuestas / solicitudes de la comunidad son bienvenidas. /analizado...

Respuestas como las de este hilo son la razón por la que Kubernetes mantiene _ "todo" _ el dinero que Docker (tecnología) está produciendo, y no es algo malo, con suerte, alguien comprará Docker (empresa) pronto y cambiará la forma en que la comunidad propone / solicita son bienvenidos / analizados ...

Escribí una crítica similar, sin ninguna declaración ofensiva (fue en la línea de _proyectos de código abierto que no son completamente de código abierto cuyos mantenedores ignoran desafiante los argumentos sin ninguna otra razón que mostrar cuánto argón técnico poseen_), obtuvo mucho apoyo y se eliminó el mensaje.

Eso muestra qué tipo de personas arrogantes están detrás de esto.

Cuando su comunidad exige algo durante 4 años y usted (Docker) cierra los ojos, muestra que no está mirando en la misma dirección que ellos: /

Y ahora Docker se rindió y se agotó.
Porque no pudieron escuchar ... perdieron.

Vergüenza, pero hola.

Es una verdadera lástima que algo así no exista. Me hubiera encantado poder crear ganchos onFailure , que podrían tener lugar cuando fallan las comprobaciones de estado.

es decir

services:
  app:
    image: myapp:latest
    hooks:
      onFailure:
        - # Call a monitoring service (from the host machine) to tell it that the service is offline.

Esto sería útil para momentos en los que la aplicación no se vincula a un puerto / socket. Kubernetes es probablemente el camino a seguir, aquí, pero este es un cambio de infraestructura bastante grande y exagerado para un entorno muy pequeño.

Editar:
Para evitar esto, terminé actualizando el punto de entrada de mi contenedor para "envolver" la funcionalidad de monitoreo. es decir

# /app/bin/run_with_monitor
#!/bin/bash
set -eE

updateMonitoringSystem() {
 # do something here... This is run from the container, though, unfortunately.
 if [[ $? -eq 1 ]]; then
  # Failed!
 else
  # All is good!
 fi
}

trap 'updateMonitoringSystem' EXIT

$@
# Dockerfile
....
CMD ["/app/bin/run_with_monitor", "./my-app"

Aún así, sería bueno hacer esto _sin_ tener que modificar la imagen.

: man_shrugging: Vine buscando esta funcionalidad básica, que tiene el competidor (Kubernetes), y en su lugar encontré un incendio en un contenedor de basura.

Es una verdadera lástima, ahora tengo que mantener imágenes de Docker separadas para probar localmente.

Feliz año nuevo: roll_eyes:

image

@LukeStonehm lo mismo aquí. Se necesitaba hacer UN comando después de que el contenedor se colocó, pero en su lugar se trató con basura caliente. Realmente no tengo ganas de administrar mis propias imágenes y archivos de la ventana acoplable cuando una imagen oficial me lleva al 90% o más del camino.

Una cantidad significativa de programas dependen de ciertos servicios para existir en el inicio. Por ejemplo, una base de datos MySQL o MongoDB.

Por lo tanto, no hay una forma sensata de usar docker-compose en estos casos.

En su lugar, se espera que los usuarios:

  • Aprenda a escribir Dockerfiles (y programación)
  • Aprenda a construir Docker images
  • Cree Dockerfiles heredando de las imágenes originales, agregando código para asegurarse de que los contenedores se esperen entre sí
  • Compruebe periódicamente las actualizaciones de seguridad de las imágenes base
  • Modifique regularmente Dockerfiles para aplicar las actualizaciones
  • Construya regularmente Docker images partir de esos Dockerfiles

Y esto apesta porque:

  • Pierde una gran cantidad de tiempo aprendiendo cosas que de otra manera ni siquiera necesitaría
  • Regularmente desperdicia recursos de hardware en construir y almacenar Docker images usted mismo o incluso al cargarlos / descargarlos (extraerlos / presionarlos)
  • Regularmente pierde tiempo escribiendo esos Dockerfiles , construyéndolos, probándolos, arreglándolos, etc.
  • Potencialmente compromete la seguridad de sus imágenes porque no sabe lo que está haciendo
  • Pierde la capacidad de ejecutar solo oficialmente verificado / firmado Docker images

Si tuviéramos una verificación de inicio, todo esto no sería necesario y simplemente podríamos cambiar image: mysql:8.0.18 a image: mysql:8.0.19 cuando queramos y ¡listo!

Siendo realistas, esto es lo que está sucediendo actualmente en el mundo real:

  • Las personas crean sus propios Dockerfiles haciendo cambios para que funcionen con docker-compose
  • Construyen sus imágenes una vez
  • Y no los parchees con regularidad
  • Los hackers se ponen felices

Y no se puede decir que se supone que docker-compose solo "hace una cosa" porque ya hace prácticamente todo. Incluir extraer y construir imágenes aún más importante, especificar dependencias usando la propiedad depends_on . Ni siquiera se trata de implementar una característica completamente nueva, se trata solo de pasar otro parámetro a docker .

@ binman-ventana acoplable @crosbymichael @dmcgowan @ebriney @ehazlett @eunomie @guillaumerose @jeanlaurent @justincormack @lorenrh @manishtomar @olegburov @routelastresort @spencerhcheng @StefanScherer @thaJeztah @tonistiigi @ulyssessouza @aiordache @ @ndeloof Chris-Crone
Por favor, reconsidere esta característica o al menos tengamos una discusión adecuada sobre esto.

La técnica task service funciona bastante bien para mí en este momento, pero tiene sus idiosincrasias. Hemos aplicado el patrón en nuestros archivos de redacción para migraciones e inicialización de aplicaciones de manera extensa. pero estoy de acuerdo en que un mejor 'depende_en' que haya esperado a una verificación de estado exitosa o una salida / finalización exitosa de la tarea haría muchas tareas más fáciles y confiables.

Esta sería una adición realmente útil.

Creo que vale la pena enfatizar que Kubernetes tiene esta funcionalidad a través del ciclo de vida postStart.

k8s! = docker-compose. Canal equivocado

Lo siento por no ser claro, pero mi punto fue: Kubernetes es compatible con esto, y debido a que Kubernetes y Docker componen tienen muchos de los mismos casos de uso / propósitos, ese sería un argumento para tenerlo en redacción. Lo siento si no estaba claro.

¡¡Buenas noticias!!

Creo que Docker nos ha escuchado (sobre este tema y algunos otros). https://www.docker.com/blog/announcing-the-compose-specification/

Intentemos trabajar en la especificación allí para satisfacer las necesidades de la comunidad. Podemos intentar hacer de esta una comunidad abierta y amigable con este reinicio.

¡¡Buenas noticias!!

Creo que Docker nos ha escuchado (sobre este tema y algunos otros). https://www.docker.com/blog/announcing-the-compose-specification/

Intentemos trabajar en la especificación allí para satisfacer las necesidades de la comunidad. Podemos intentar hacer de esta una comunidad abierta y amigable con este reinicio.

¿Alguien ha sugerido este cambio todavía? La lista de correo aún no está disponible, así que creo que el siguiente mejor lugar es aquí: https://github.com/compose-spec/compose-spec

No veo un problema que describa este problema, pero no estoy seguro de si ese es el lugar correcto ...

Editar: abrí un problema en https://github.com/compose-spec/compose-spec/issues/84.

Puede usar HEALTHCHECK para hacer otra cosa como el siguiente ejemplo:

Código

Dockerfile

FROM ubuntu

COPY healthcheck.sh /healthcheck.sh
RUN chmod a+x /healthcheck.sh

HEALTHCHECK --interval=5s CMD /healthcheck.sh

CMD bash -c 'set -x; set +e; while true; do cat /test.txt; sleep 3; done'

healthcheck.sh

#/usr/bin/env bash

set -e

FIRST_READY_STATUS_FLAG='/tmp/.FIRST_READY_STATUS_FLAG'

# Health check

echo 'Run command to validate the container status HERE'

# On success
if [ ! -f "${FIRST_READY_STATUS_FLAG}" ]; then
  # On first success...
  touch "${FIRST_READY_STATUS_FLAG}"

  # Run ON_RUN on first health check ok
  if [ ! -z "${DOCKER_ON_RUN}" ]; then
    eval "${DOCKER_ON_RUN}"
  fi
fi
  1. Ejecute el _health check_.

    • Si falla, sale del script con el código de salida 1 .

    • Si la _ verificación de estado_ está bien, la secuencia de comandos continuará.

  2. Si es la primera _ verificación de estado OK_ y si existe la variable de entorno DOCKER_ON_RUN , ejecútela.

Ejemplo

docker-compose.yml

version: "3.7"

services:
  test:
    build:
      context: .
    image: test/on-run
    environment:
      DOCKER_ON_RUN: echo x >> /test.txt

Puede usar la variable de entorno DOCKER_ON_RUN para pasar un comando personalizado para ejecutar después de la ejecución.

Resultado de ejecución

docker-compose build
docker-compose up

Salida:

Creating network "tmp_default" with the default driver
Creating tmp_test_1 ... done
Attaching to tmp_test_1
test_1  | + set +e
test_1  | + true
test_1  | + cat /test.txt
test_1  | cat: /test.txt: No such file or directory
test_1  | + sleep 3
test_1  | + true
test_1  | + cat /test.txt
test_1  | cat: /test.txt: No such file or directory
test_1  | + sleep 3
test_1  | + true
test_1  | + cat /test.txt
test_1  | x
test_1  | + sleep 3
test_1  | + true
test_1  | + cat /test.txt
test_1  | x
test_1  | + sleep 3
test_1  | + true
test_1  | + cat /test.txt
test_1  | x
test_1  | + sleep 3
  • Puede ver el error cat: /test.txt: No such file or directory hasta que la _ verificación de salud_ esté lista.
  • Solo puede ver un x dentro de /test.txt después de la ejecución .

Espero que esto pueda ayudar a alguién.

Editar 1

Si no necesita una _ verificación de estado_, puede usar el resto del script.

@ reduardo7
Gracias por tu solución.
Solo quiero agregar, en caso de que necesite ejecutar el comando uno, como para la creación de usuarios, etc., puede montar el volumen para touch "${FIRST_READY_STATUS_FLAG}"

Muchas de estas soluciones son soluciones alternativas válidas para este problema. Por ejemplo, hacer un script de punto de entrada también podría resolver esto:
ENTRYPOINT ["./entrypoint.sh"]

que incluirá una lógica más compleja antes de ejecutar el servicio o proceso real.
Sin embargo, esto todavía no es un gancho que nos permitiría inyectar lógica en el ciclo de vida del contenedor:

  • antes de crear
  • antes de empezar
  • después de comenzar
  • antes de destruir
  • incluso después de destruir
  • etc ...

Sé que no todo lo anterior es significativo, pero espero que entiendas una idea porque este es el punto.
Esto también podría incluirse en docker-compose con una directiva como:

lifecycle:
    before_start: "./beforeStartHook.sh"
    after_destroy: "./afterDestroyHook.sh"

o incluso así:

hooks:
    before_destroy: "./beforeDestroyHook.sh"
    before_create: "./fixFsRights.sh"

No puedo sobrescribir el archivo que requiere permiso de root utilizando un script de gancho o un enfoque de script de arranque, ya que iniciamos el contenedor como usuario no root

Vaya, una funcionalidad tan básica y aún no implementada.

¿Fue útil esta página
0 / 5 - 0 calificaciones