Saltar a contenido

Parte 5: Hello Containers

Traducción asistida por IA - más información y sugerencias

Vea la lista de reproducción completa en el canal de YouTube de Nextflow.

📗 La transcripción del video está disponible aquí.

En las Partes 1-4 de este curso de capacitación, aprendió a usar los bloques de construcción básicos de Nextflow para ensamblar un workflow simple capaz de procesar texto, paralelizar la ejecución si había múltiples entradas y recopilar los resultados para su posterior procesamiento.

Sin embargo, estaba limitado a las herramientas básicas de UNIX disponibles en su entorno. Las tareas del mundo real a menudo requieren varias herramientas y paquetes que no están incluidos de forma predeterminada. Típicamente, necesitaría instalar estas herramientas, gestionar sus dependencias y resolver cualquier conflicto.

Todo eso es muy tedioso y molesto, así que vamos a mostrarle cómo usar contenedores para resolver este problema de manera mucho más conveniente.

Un contenedor es una unidad de software ligera, independiente y ejecutable creada a partir de una imagen de contenedor que incluye todo lo necesario para ejecutar una aplicación, incluyendo código, bibliotecas del sistema y configuraciones. Como puede imaginar, eso va a ser muy útil para hacer que sus pipelines sean más reproducibles.

Tenga en cuenta que enseñaremos esto usando Docker, pero recuerde que Nextflow también admite varias otras tecnologías de contenedores.

Cómo comenzar desde esta sección

Esta sección del curso asume que ha completado las Partes 1-4 del curso Hello Nextflow y tiene un pipeline completo y funcional.

Si está comenzando el curso desde este punto, necesitará copiar el directorio modules desde las soluciones:

cp -r solutions/4-hello-modules/modules .

0. Calentamiento: Ejecutar hello-containers.nf

Vamos a usar el script de workflow hello-containers.nf como punto de partida. Es equivalente al script producido al trabajar en la Parte 4 de este curso de capacitación, excepto que hemos cambiado los destinos de salida:

hello-containers.nf
output {
    first_output {
        path 'hello_containers'
        mode 'copy'
    }
    uppercased {
        path 'hello_containers'
        mode 'copy'
    }
    collected {
        path 'hello_containers'
        mode 'copy'
    }
    batch_report {
        path 'hello_containers'
        mode 'copy'
    }
}

Solo para asegurarnos de que todo funciona, ejecute el script una vez antes de hacer cualquier cambio:

nextflow run hello-containers.nf
Salida del comando
 N E X T F L O W   ~  version 25.10.2

Launching `hello-containers.nf` [nice_escher] DSL2 - revision: d5dfdc9872

executor > local (7)
[5a/ec1fa1] sayHello (2) [100%] 3 of 3 ✔
[30/32b5b8] convertToUpper (3) [100%] 3 of 3 ✔
[d3/be01bc] collectGreetings [100%] 1 of 1 ✔

Como anteriormente, encontrará los archivos de salida en el directorio especificado en el bloque output (results/hello_containers/).

Contenido del directorio
results/hello_containers/
├── Bonjour-output.txt
├── COLLECTED-batch-output.txt
├── Hello-output.txt
├── Holà-output.txt
├── batch-report.txt
├── UPPER-Bonjour-output.txt
├── UPPER-Hello-output.txt
└── UPPER-Holà-output.txt

Si eso funcionó para usted, está listo para aprender cómo usar contenedores.


1. Usar un contenedor 'manualmente'

Lo que queremos hacer es agregar un paso a nuestro workflow que usará un contenedor para la ejecución.

Sin embargo, primero vamos a repasar algunos conceptos y operaciones básicas para solidificar su comprensión de qué son los contenedores antes de comenzar a usarlos en Nextflow.

1.1. Descargar la imagen del contenedor

Para usar un contenedor, generalmente descarga o extrae una imagen de contenedor de un registro de contenedores, y luego ejecuta la imagen del contenedor para crear una instancia de contenedor.

La sintaxis general es la siguiente:

Syntax
docker pull '<container>'

La parte docker pull es la instrucción al sistema de contenedores para extraer una imagen de contenedor de un repositorio.

La parte '<container>' es la dirección URI de la imagen del contenedor.

Como ejemplo, extraigamos una imagen de contenedor que contiene cowpy, una implementación en Python de una herramienta llamada cowsay que genera arte ASCII para mostrar entradas de texto arbitrarias de una manera divertida.

Example
 ________________________
< Are we having fun yet? >
 ------------------------
    \                                  ___-------___
     \                             _-~~             ~~-_
      \                         _-~                    /~-_
             /^\__/^\         /~  \                   /    \
           /|  O|| O|        /      \_______________/        \
          | |___||__|      /       /                \          \
          |          \    /      /                    \          \
          |   (_______) /______/                        \_________ \
          |         / /         \                      /            \
           \         \^\\         \                  /               \     /
             \         ||           \______________/      _-_       //\__//
               \       ||------_-~~-_ ------------- \ --/~   ~\    || __/
                 ~-----||====/~     |==================|       |/~~~~~
                  (_(__/  ./     /                    \_\      \.
                         (_(___/                         \_____)_)

Hay varios repositorios donde puede encontrar contenedores publicados. Usamos el servicio Seqera Containers para generar esta imagen de contenedor Docker a partir del paquete Conda cowpy: 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273'.

Ejecute el comando pull completo:

docker pull 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273'
Salida del comando
1.1.5--3db457ae1977a273: Pulling from library/cowpy
dafa2b0c44d2: Pull complete
dec6b097362e: Pull complete
f88da01cff0b: Pull complete
4f4fb700ef54: Pull complete
92dc97a3ef36: Pull complete
403f74b0f85e: Pull complete
10b8c00c10a5: Pull complete
17dc7ea432cc: Pull complete
bb36d6c3110d: Pull complete
0ea1a16bbe82: Pull complete
030a47592a0a: Pull complete
c23bdb422167: Pull complete
e1686ff32a11: Pull complete
Digest: sha256:1ebc0043e8cafa61203bf42d29fd05bd14e7b4298e5e8cf986504c15f5aa4160
Status: Downloaded newer image for community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273
community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273

Si nunca ha descargado la imagen antes, esto puede tardar un minuto en completarse. Una vez que esté listo, tiene una copia local de la imagen del contenedor.

1.2. Usar el contenedor para ejecutar cowpy como un comando único

Una forma muy común en que las personas usan contenedores es ejecutarlos directamente, es decir, de forma no interactiva. Esto es excelente para ejecutar comandos únicos.

La sintaxis general es la siguiente:

Syntax
docker run --rm '<container>' [tool command]

La parte docker run --rm '<container>' es la instrucción al sistema de contenedores para iniciar una instancia de contenedor a partir de una imagen de contenedor y ejecutar un comando en ella. La bandera --rm le dice al sistema que apague la instancia del contenedor después de que el comando se haya completado.

La sintaxis [comando de herramienta] depende de la herramienta que esté usando y de cómo esté configurado el contenedor. Comencemos simplemente con cowpy.

Completamente ensamblado, el comando de ejecución del contenedor se ve así; adelante, ejecútelo.

docker run --rm 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' cowpy
Salida del comando
______________________________________________________
< Cowacter, eyes:default, tongue:False, thoughts:False >
------------------------------------------------------
    \   ^__^
      \  (oo)\_______
        (__)\       )\/\
          ||----w |
          ||     ||

El sistema inició el contenedor, ejecutó el comando cowpy con sus parámetros, envió la salida a la consola y finalmente, apagó la instancia del contenedor.

1.3. Usar el contenedor para ejecutar cowpy interactivamente

También puede ejecutar un contenedor de forma interactiva, lo que le da un prompt de shell dentro del contenedor y le permite jugar con el comando.

1.3.1. Iniciar el contenedor

Para ejecutar de forma interactiva, simplemente agregamos -it al comando docker run. Opcionalmente, podemos especificar el shell que queremos usar dentro del contenedor agregando p. ej. /bin/bash al comando.

docker run --rm -it 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' /bin/bash

Note que su prompt cambia a algo como (base) root@b645838b3314:/tmp#, lo que indica que ahora está dentro del contenedor.

Puede verificar esto ejecutando ls / para listar el contenido del directorio desde la raíz del sistema de archivos:

ls /
Salida del comando
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

Usamos ls aquí en lugar de tree porque la utilidad tree no está disponible en este contenedor. Puede ver que el sistema de archivos dentro del contenedor es diferente del sistema de archivos en su sistema host.

Una limitación de lo que acabamos de hacer es que el contenedor está completamente aislado del sistema host de forma predeterminada. Esto significa que el contenedor no puede acceder a ningún archivo en el sistema host a menos que usted lo permita explícitamente.

Le mostraremos cómo hacer eso en un minuto.

1.3.2. Ejecutar el(los) comando(s) de herramienta deseado(s)

Ahora que está dentro del contenedor, puede ejecutar el comando cowpy directamente y darle algunos parámetros. Por ejemplo, la documentación de la herramienta dice que podemos cambiar el personaje ('cowacter') con -c.

cowpy "Hello Containers" -c tux
Salida del comando
__________________
< Hello Containers >
------------------
  \
    \
        .--.
      |o_o |
      |:_/ |
      //   \ \
    (|     | )
    /'\_   _/`\
    \___)=(___/

Ahora la salida muestra al pingüino de Linux, Tux, en lugar de la vaca predeterminada, porque especificamos el parámetro -c tux.

Debido a que está dentro del contenedor, puede ejecutar el comando cowpy tantas veces como desee, variando los parámetros de entrada, sin tener que preocuparse por los comandos de Docker.

Consejo

Use la bandera '-c' para elegir un personaje diferente, incluyendo: beavis, cheese, daemon, dragonandcow, ghostbusters, kitty, moose, milk, stegosaurus, turkey, turtle, tux

Esto es genial. Lo que sería aún más genial es si pudiéramos alimentar nuestro greetings.csv como entrada en esto. Pero como no tenemos acceso al sistema de archivos, no podemos.

Arreglemos eso.

1.3.3. Salir del contenedor

Para salir del contenedor, puede escribir exit en el prompt o usar el atajo de teclado Ctrl+D.

exit

Su prompt ahora debería volver a lo que era antes de iniciar el contenedor.

1.3.4. Montar datos en el contenedor

Como se señaló anteriormente, el contenedor está aislado del sistema host de forma predeterminada.

Para permitir que el contenedor acceda al sistema de archivos del host, puede montar un volumen desde el sistema host en el contenedor usando la siguiente sintaxis:

Syntax
-v <outside_path>:<inside_path>

En nuestro caso, <ruta_externa> será el directorio de trabajo actual, por lo que podemos usar simplemente un punto (.), y <ruta_interna> es solo un alias que inventamos; llamémoslo /my_project (la ruta interna debe ser absoluta).

Para montar un volumen, reemplazamos las rutas y agregamos el argumento de montaje de volumen al comando docker run de la siguiente manera:

docker run --rm -it -v .:/my_project 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' /bin/bash

Esto monta el directorio de trabajo actual como un volumen que será accesible bajo /my_project dentro del contenedor.

Puede verificar que funciona listando el contenido de /my_project:

ls /my_project
Salida del comando
data               hello-config.nf      hello-modules.nf   hello-world.nf  nextflow.config  solutions         work
hello-channels.nf  hello-containers.nf  hello-workflow.nf  modules         results          test-params.json

Ahora puede ver el contenido del directorio de trabajo desde dentro del contenedor, incluyendo el archivo greetings.csv bajo data/.

Esto efectivamente estableció un túnel a través de la pared del contenedor que puede usar para acceder a esa parte de su sistema de archivos.

1.3.5. Usar los datos montados

Ahora que hemos montado el directorio de trabajo en el contenedor, podemos usar el comando cowpy para mostrar el contenido del archivo greetings.csv.

Para hacer esto, usaremos cat /my_project/data/greetings.csv | para canalizar el contenido del archivo CSV al comando cowpy.

cat /my_project/data/greetings.csv | cowpy -c turkey
Salida del comando
data/greetings.csv
 ____________________
/ Hello,English,123  \
| Bonjour,French,456 |
\ Holà,Spanish,789   /
--------------------
  \                                  ,+*^^*+___+++_
  \                           ,*^^^^              )
    \                       _+*                     ^**+_
    \                    +^       _ _++*+_+++_,         )
              _+^^*+_    (     ,+*^ ^          \+_        )
            {       )  (    ,(    ,_+--+--,      ^)      ^\
            { (\@)    } f   ,(  ,+-^ __*_*_  ^^\_   ^\       )
          {:;-/    (_+*-+^^^^^+*+*<_ _++_)_    )    )      /
          ( /  (    (        ,___    ^*+_+* )   <    <      \
          U _/     )    *--<  ) ^\-----++__)   )    )       )
            (      )  _(^)^^))  )  )\^^^^^))^*+/    /       /
          (      /  (_))_^)) )  )  ))^^^^^))^^^)__/     +^^
        (     ,/    (^))^))  )  ) ))^^^^^^^))^^)       _)
          *+__+*       (_))^)  ) ) ))^^^^^^))^^^^^)____*^
          \             \_)^)_)) ))^^^^^^^^^^))^^^^)
          (_             ^\__^^^^^^^^^^^^))^^^^^^^)
            ^\___            ^\__^^^^^^))^^^^^^^^)\\
                  ^^^^^\uuu/^^\uuu/^^^^\^\^\^\^\^\^\^\
                    ___) >____) >___   ^\_\_\_\_\_\_\)
                    ^^^//\\_^^//\\_^       ^(\_\_\_\)
                      ^^^ ^^ ^^^ ^

¡Esto produce el arte ASCII deseado de un pavo recitando nuestros saludos de ejemplo! Excepto que aquí el pavo está repitiendo las filas completas en lugar de solo los saludos. ¡Ya sabemos que nuestro workflow de Nextflow hará un mejor trabajo!

Siéntase libre de jugar con este comando. Cuando haya terminado, salga del contenedor como anteriormente:

exit

Se encontrará de vuelta en su shell normal.

Conclusión

Sabe cómo extraer un contenedor y ejecutarlo ya sea como un comando único o de forma interactiva. También sabe cómo hacer que sus datos sean accesibles desde dentro de su contenedor, lo que le permite probar cualquier herramienta que le interese con datos reales sin tener que instalar ningún software en su sistema.

¿Qué sigue?

Aprenda cómo usar contenedores para la ejecución de procesos de Nextflow.


2. Usar contenedores en Nextflow

Nextflow tiene soporte integrado para ejecutar procesos dentro de contenedores para permitirle ejecutar herramientas que no tiene instaladas en su entorno de cómputo. Esto significa que puede usar cualquier imagen de contenedor que desee para ejecutar sus procesos, y Nextflow se encargará de extraer la imagen, montar los datos y ejecutar el proceso dentro de ella.

Para demostrar esto, vamos a agregar un paso de cowpy al pipeline que hemos estado desarrollando, después del paso collectGreetings.

collectGreetingsCOLLECTED-trio-output.txtHELLOBONJOURHOLà--batch triotrio-report.txtThere were 3 greetingsin this batch.UPPER-*UPPER-*UPPER-*_________/ HOLà \| HELLO |\ BONJOUR /--------- \ ,+*^^*+___+++_ \ ,*^^^^ ) \ _+* ^**+_ \ +^ _ _++*+_+++_, ) _+^^*+_ ( ,+*^ ^ \+_ ) { ) ( ,( ,_+--+--, ^) ^\ { (\@) } f ,( ,+-^ __*_*_ ^^\_ ^\ ) {:;-/ (_+*-+^^^^^+*+*<_ _++_)_ ) ) / ( / ( ( ,___ ^*+_+* ) < < \ U _/ ) *--< ) ^\-----++__) ) ) ) ( ) _(^)^^)) ) )\^^^^^))^*+/ / / ( / (_))_^)) ) ) ))^^^^^))^^^)__/ +^^ ( ,/ (^))^)) ) ) ))^^^^^^^))^^) _) *+__+* (_))^) ) ) ))^^^^^^))^^^^^)____*^ \ \_)^)_)) ))^^^^^^^^^^))^^^^) (_ ^\__^^^^^^^^^^^^))^^^^^^^) ^\___ ^\__^^^^^^))^^^^^^^^)\\ ^^^^^\uuu/^^\uuu/^^^^\^\^\^\^\^\^\^\ ___) >____) >___ ^\_\_\_\_\_\_\) ^^^//\\_^^//\\_^ ^(\_\_\_\) ^^^ ^^ ^^^ ^cowPycowpy-COLLECTED-trio-output.txt

2.1. Escribir un módulo cowpy

Primero, creemos el módulo del proceso cowpy.

2.1.1. Crear un archivo stub para el nuevo módulo

Cree un archivo vacío para el módulo llamado cowpy.nf.

touch modules/cowpy.nf

Esto nos da un lugar para poner el código del proceso.

2.1.2. Copiar el código del proceso cowpy en el archivo del módulo

Podemos modelar nuestro proceso cowpy en los otros procesos que hemos escrito anteriormente.

modules/cowpy.nf
#!/usr/bin/env nextflow

// Generar arte ASCII con cowpy
process cowpy {

    input:
    path input_file
    val character

    output:
    path "cowpy-${input_file}"

    script:
    """
    cat ${input_file} | cowpy -c "${character}" > cowpy-${input_file}
    """

}

El proceso espera un input_file que contiene los saludos así como un valor character.

La salida será un nuevo archivo de texto que contiene el arte ASCII generado por la herramienta cowpy.

2.2. Agregar cowpy al workflow

Ahora necesitamos importar el módulo y llamar al proceso.

2.2.1. Importar el proceso cowpy en hello-containers.nf

Inserte la declaración de importación arriba del bloque workflow y complétela apropiadamente.

hello-containers.nf
3
4
5
6
7
// Incluir módulos
include { sayHello } from './modules/sayHello.nf'
include { convertToUpper } from './modules/convertToUpper.nf'
include { collectGreetings } from './modules/collectGreetings.nf'
include { cowpy } from './modules/cowpy.nf'
hello-containers.nf
3
4
5
6
// Incluir módulos
include { sayHello } from './modules/sayHello.nf'
include { convertToUpper } from './modules/convertToUpper.nf'
include { collectGreetings } from './modules/collectGreetings.nf'

Ahora el módulo cowpy está disponible para usar en el workflow.

2.2.2. Agregar una llamada al proceso cowpy en el workflow

Conectemos el proceso cowpy() a la salida del proceso collectGreetings(), que como recordará produce dos salidas:

  • collectGreetings.out.outfile contiene el archivo de salida <--lo que queremos
  • collectGreetings.out.report contiene el archivo de reporte con el conteo de saludos por lote

En el bloque workflow, haga el siguiente cambio de código:

hello-containers.nf
    main:
    // crear un canal para entradas desde un archivo CSV
    greeting_ch = channel.fromPath(params.input)
                        .splitCsv()
                        .map { line -> line[0] }
    // emitir un saludo
    sayHello(greeting_ch)
    // convertir el saludo a mayúsculas
    convertToUpper(sayHello.out)
    // recopilar todos los saludos en un archivo
    collectGreetings(convertToUpper.out.collect(), params.batch)
    // generar arte ASCII de los saludos con cowpy
    cowpy(collectGreetings.out.outfile, params.character)
hello-containers.nf
    main:
    // crear un canal para entradas desde un archivo CSV
    greeting_ch = channel.fromPath(params.input)
                        .splitCsv()
                        .map { line -> line[0] }
    // emitir un saludo
    sayHello(greeting_ch)
    // convertir el saludo a mayúsculas
    convertToUpper(sayHello.out)
    // recopilar todos los saludos en un archivo
    collectGreetings(convertToUpper.out.collect(), params.batch)

Note que declaramos un nuevo parámetro CLI, params.character, para especificar qué personaje queremos que diga los saludos.

2.2.3. Agregar el parámetro character al bloque params

Esto es técnicamente opcional pero es la práctica recomendada y es una oportunidad para establecer un valor predeterminado para el personaje mientras lo hacemos.

hello-containers.nf
/*
* Parámetros del pipeline
*/
params {
    input: Path = 'data/greetings.csv'
    batch: String = 'batch'
    character: String = 'turkey'
}
hello-containers.nf
/*
* Parámetros del pipeline
*/
params {
    input: Path = 'data/greetings.csv'
    batch: String = 'batch'
}

Ahora podemos ser perezosos y omitir escribir el parámetro de personaje en nuestras líneas de comando.

2.2.4. Actualizar las salidas del workflow

Necesitamos actualizar las salidas del workflow para publicar la salida del proceso cowpy.

2.2.4.1. Actualizar la sección publish:

En el bloque workflow, haga el siguiente cambio de código:

hello-containers.nf
    publish:
    first_output = sayHello.out
    uppercased = convertToUpper.out
    collected = collectGreetings.out.outfile
    batch_report = collectGreetings.out.report
    cowpy_art = cowpy.out
hello-containers.nf
    publish:
    first_output = sayHello.out
    uppercased = convertToUpper.out
    collected = collectGreetings.out.outfile
    batch_report = collectGreetings.out.report

El proceso cowpy solo produce una salida, por lo que podemos referirnos a ella de la manera habitual agregando .out.

Pero por ahora, terminemos de actualizar las salidas a nivel de workflow.

2.2.4.2. Actualizar el bloque output

Necesitamos agregar la salida final cowpy_art al bloque output. Mientras lo hacemos, también editemos los destinos de publicación ya que ahora nuestro pipeline está completo y sabemos qué salidas realmente nos importan.

En el bloque output, haga los siguientes cambios de código:

hello-containers.nf
output {
    first_output {
        path 'hello_containers/intermediates'
        mode 'copy'
    }
    uppercased {
        path 'hello_containers/intermediates'
        mode 'copy'
    }
    collected {
        path 'hello_containers/intermediates'
        mode 'copy'
    }
    batch_report {
        path 'hello_containers'
        mode 'copy'
    }
    cowpy_art {
        path 'hello_containers'
        mode 'copy'
    }
}
hello-containers.nf
output {
    first_output {
        path 'hello_containers'
        mode 'copy'
    }
    uppercased {
        path 'hello_containers'
        mode 'copy'
    }
    collected {
        path 'hello_containers'
        mode 'copy'
    }
    batch_report {
        path 'hello_containers'
        mode 'copy'
    }
}

Ahora las salidas publicadas estarán un poco más organizadas.

2.2.5. Ejecutar el workflow

Solo para recapitular, esto es lo que estamos buscando:

sayHello*-output.txtconvertToUpperUPPER-*collectGreetingsCOLLECTED-trio-output.txtHELLOBONJOURHOLàHello, English, 123 Bonjour, French, 456Holà, Spanish, 789greetings.csvHELLOBONJOURHOLàUPPER-Hello-output.txtUPPER-Bonjour-output.txtUPPER-Holà-output.txtcowPycowpy-COLLECTED-trio-output.txt--batch triotrio-report.txtThere were 3 greetingsin this batch.--input_________/ HOLà \| HELLO |\ BONJOUR /--------- \ ,+*^^*+___+++_ \ ,*^^^^ ) \ _+* ^**+_ \ +^ _ _++*+_+++_, ) _+^^*+_ ( ,+*^ ^ \+_ ) { ) ( ,( ,_+--+--, ^) ^\ { (\@) } f ,( ,+-^ __*_*_ ^^\_ ^\ ) {:;-/ (_+*-+^^^^^+*+*<_ _++_)_ ) ) / ( / ( ( ,___ ^*+_+* ) < < \ U _/ ) *--< ) ^\-----++__) ) ) ) ( ) _(^)^^)) ) )\^^^^^))^*+/ / / ( / (_))_^)) ) ) ))^^^^^))^^^)__/ +^^ ( ,/ (^))^)) ) ) ))^^^^^^^))^^) _) *+__+* (_))^) ) ) ))^^^^^^))^^^^^)____*^ \ \_)^)_)) ))^^^^^^^^^^))^^^^) (_ ^\__^^^^^^^^^^^^))^^^^^^^) ^\___ ^\__^^^^^^))^^^^^^^^)\\ ^^^^^\uuu/^^\uuu/^^^^\^\^\^\^\^\^\^\ ___) >____) >___ ^\_\_\_\_\_\_\) ^^^//\\_^^//\\_^ ^(\_\_\_\) ^^^ ^^ ^^^ ^

¿Cree que va a funcionar?

Eliminemos las salidas publicadas anteriores para tener un estado limpio, y ejecutemos el workflow con la bandera -resume.

rm -r hello_containers/
nextflow run hello-containers.nf -resume
Salida del comando (editada para claridad)
 N E X T F L O W   ~  version 25.10.2

Launching `hello-containers.nf` [lonely_woese] DSL2 - revision: abf1dccf7f

executor >  local (1)
[c9/f5c686] sayHello (3)       [100%] 3 of 3, cached: 3 ✔
[ef/3135a8] convertToUpper (3) [100%] 3 of 3, cached: 3 ✔
[7f/f435e3] collectGreetings   [100%] 1 of 1, cached: 1 ✔
[9b/02e776] cowpy              [  0%] 0 of 1 ✘
ERROR ~ Error executing process > 'cowpy'

Caused by:
  Process `cowpy` terminated with an error exit status (127)


Command executed:

  cat COLLECTED-batch-output.txt | cowpy -c "turkey" > cowpy-COLLECTED-batch-output.txt

Command exit status:
  127

Command output:
  (empty)

Command error:
  .command.sh: line 2: cowpy: command not found

Work dir:
  /workspaces/training/hello-nextflow/work/9b/02e7761db848f82db3c3e59ff3a9b6

Tip: when you have fixed the problem you can continue the execution adding the option `-resume` to the run command line

-- Check '.nextflow.log' file for details
ERROR ~ Cannot access first() element from an empty List

-- Check '.nextflow.log' file for details

¡Oh no, hay un error! El código de error dado por error exit status (127) significa que el ejecutable que solicitamos no se encontró.

Eso tiene sentido, ya que estamos llamando a la herramienta cowpy pero aún no hemos especificado un contenedor (ups).

2.3. Usar un contenedor para ejecutar el proceso cowpy

Necesitamos especificar un contenedor y decirle a Nextflow que lo use para el proceso cowpy().

2.3.1. Especificar un contenedor para cowpy

Podemos usar la misma imagen que estábamos usando directamente en la primera sección de este tutorial.

Edite el módulo cowpy.nf para agregar la directiva container a la definición del proceso de la siguiente manera:

modules/cowpy.nf
process cowpy {

    container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273'

    input:
    path input_file
    val character

    output:
    path "cowpy-${input_file}"

    script:
    """
    cat ${input_file} | cowpy -c "${character}" > cowpy-${input_file}
    """
}
modules/cowpy.nf
process cowpy {

    input:
    path input_file
    val character

    output:
    path "cowpy-${input_file}"

    script:
    """
    cat ${input_file} | cowpy -c "${character}" > cowpy-${input_file}
    """
}

Esto le dice a Nextflow que si el uso de Docker está habilitado, debe usar la imagen de contenedor especificada aquí para ejecutar el proceso.

2.3.2. Habilitar el uso de Docker a través del archivo nextflow.config

Note que dijimos 'si el uso de Docker está habilitado'. De forma predeterminada, no lo está, por lo que necesitamos decirle a Nextflow que está permitido usar Docker. Para ese fin, vamos a anticipar ligeramente el tema de la siguiente y última parte de este curso (Parte 6), que cubre la configuración.

Una de las principales formas que Nextflow ofrece para configurar la ejecución del workflow es usar un archivo nextflow.config. Cuando tal archivo está presente en el directorio actual, Nextflow lo cargará automáticamente y aplicará cualquier configuración que contenga.

Proporcionamos un archivo nextflow.config con una sola línea de código que deshabilita explícitamente Docker: docker.enabled = false.

Ahora, cambiemos eso a true para habilitar Docker:

nextflow.config
docker.enabled = true
nextflow.config
docker.enabled = false

Consejo

Es posible habilitar la ejecución de Docker desde la línea de comandos, por ejecución, usando el parámetro -with-docker <container>. Sin embargo, eso solo nos permite especificar un contenedor para todo el workflow, mientras que el enfoque que acabamos de mostrarle nos permite especificar un contenedor diferente por proceso. Esto es mejor para la modularidad, el mantenimiento del código y la reproducibilidad.

2.3.3. Ejecutar el workflow con Docker habilitado

Ejecute el workflow con la bandera -resume:

nextflow run hello-containers.nf -resume
Salida del comando
 N E X T F L O W   ~  version 25.10.2

Launching `hello-containers.nf` [drunk_perlman] DSL2 - revision: abf1dccf7f

executor >  local (1)
[c9/f5c686] sayHello (3)       [100%] 3 of 3, cached: 3 ✔
[ef/3135a8] convertToUpper (3) [100%] 3 of 3, cached: 3 ✔
[7f/f435e3] collectGreetings   [100%] 1 of 1, cached: 1 ✔
[98/656c6c] cowpy              [100%] 1 of 1 ✔

¡Esta vez sí funciona! Como de costumbre, puede encontrar las salidas del workflow en el directorio de resultados correspondiente, aunque esta vez están un poco más ordenadas, con solo el reporte y la salida final en el nivel superior, y todos los archivos intermedios guardados en un subdirectorio.

Contenido del directorio
results/hello_containers/
├── cowpy-COLLECTED-batch-output.txt
├── intermediates
│   ├── Bonjour-output.txt
│   ├── COLLECTED-batch-output.txt
│   ├── Hello-output.txt
│   ├── Holà-output.txt
│   ├── UPPER-Bonjour-output.txt
│   ├── UPPER-Hello-output.txt
│   └── UPPER-Holà-output.txt
└── batch-report.txt

La salida final de arte ASCII está en el directorio results/hello_containers/, bajo el nombre cowpy-COLLECTED-batch-output.txt.

Contenido del archivo
results/hello_containers/cowpy-COLLECTED-batch-output.txt
_________
/ HOLà    \
| HELLO   |
\ BONJOUR /
---------
  \                                  ,+*^^*+___+++_
  \                           ,*^^^^              )
    \                       _+*                     ^**+_
    \                    +^       _ _++*+_+++_,         )
              _+^^*+_    (     ,+*^ ^          \+_        )
            {       )  (    ,(    ,_+--+--,      ^)      ^\
            { (\@)    } f   ,(  ,+-^ __*_*_  ^^\_   ^\       )
          {:;-/    (_+*-+^^^^^+*+*<_ _++_)_    )    )      /
          ( /  (    (        ,___    ^*+_+* )   <    <      \
          U _/     )    *--<  ) ^\-----++__)   )    )       )
            (      )  _(^)^^))  )  )\^^^^^))^*+/    /       /
          (      /  (_))_^)) )  )  ))^^^^^))^^^)__/     +^^
        (     ,/    (^))^))  )  ) ))^^^^^^^))^^)       _)
          *+__+*       (_))^)  ) ) ))^^^^^^))^^^^^)____*^
          \             \_)^)_)) ))^^^^^^^^^^))^^^^)
          (_             ^\__^^^^^^^^^^^^))^^^^^^^)
            ^\___            ^\__^^^^^^))^^^^^^^^)\\
                  ^^^^^\uuu/^^\uuu/^^^^\^\^\^\^\^\^\^\
                    ___) >____) >___   ^\_\_\_\_\_\_\)
                    ^^^//\\_^^//\\_^       ^(\_\_\_\)
                      ^^^ ^^ ^^^ ^

Y ahí está, nuestro hermoso pavo diciendo los saludos como se deseaba.

2.3.4. Inspeccionar cómo Nextflow lanzó la tarea en contenedor

Como coda final de esta sección, echemos un vistazo al subdirectorio de trabajo para una de las llamadas al proceso cowpy para obtener un poco más de información sobre cómo funciona Nextflow con contenedores bajo el capó.

Verifique la salida de su comando nextflow run para encontrar la ruta al subdirectorio de trabajo para el proceso cowpy. Mirando lo que obtuvimos para la ejecución mostrada arriba, la línea de registro de consola para el proceso cowpy comienza con [98/656c6c]. Eso corresponde a la siguiente ruta de directorio truncada: work/98/656c6c.

En ese directorio, encontrará el archivo .command.run que contiene todos los comandos que Nextflow ejecutó en su nombre en el curso de ejecutar el pipeline.

Contenido del archivo
work/98/656c6c90cce1667c094d880f4b6dcc/.command.run
#!/bin/bash
### ---
### name: 'cowpy'
### container: 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273'
### outputs:
### - 'cowpy-COLLECTED-batch-output.txt'
### ...
set -e
set -u
NXF_DEBUG=${NXF_DEBUG:=0}; [[ $NXF_DEBUG > 1 ]] && set -x
NXF_ENTRY=${1:-nxf_main}


nxf_sleep() {
  sleep $1 2>/dev/null || sleep 1;
}

nxf_date() {
    local ts=$(date +%s%3N);
    if [[ ${#ts} == 10 ]]; then echo ${ts}000
    elif [[ $ts == *%3N ]]; then echo ${ts/\%3N/000}
    elif [[ $ts == *3N ]]; then echo ${ts/3N/000}
    elif [[ ${#ts} == 13 ]]; then echo $ts
    else echo "Unexpected timestamp value: $ts"; exit 1
    fi
}

nxf_env() {
    echo '============= task environment ============='
    env | sort | sed "s/\(.*\)AWS\(.*\)=\(.\{6\}\).*/\1AWS\2=\3xxxxxxxxxxxxx/"
    echo '============= task output =================='
}

nxf_kill() {
    declare -a children
    while read P PP;do
        children[$PP]+=" $P"
    done < <(ps -e -o pid= -o ppid=)

    kill_all() {
        [[ $1 != $$ ]] && kill $1 2>/dev/null || true
        for i in ${children[$1]:=}; do kill_all $i; done
    }

    kill_all $1
}

nxf_mktemp() {
    local base=${1:-/tmp}
    mkdir -p "$base"
    if [[ $(uname) = Darwin ]]; then mktemp -d $base/nxf.XXXXXXXXXX
    else TMPDIR="$base" mktemp -d -t nxf.XXXXXXXXXX
    fi
}

nxf_fs_copy() {
  local source=$1
  local target=$2
  local basedir=$(dirname $1)
  mkdir -p $target/$basedir
  cp -fRL $source $target/$basedir
}

nxf_fs_move() {
  local source=$1
  local target=$2
  local basedir=$(dirname $1)
  mkdir -p $target/$basedir
  mv -f $source $target/$basedir
}

nxf_fs_rsync() {
  rsync -rRl $1 $2
}

nxf_fs_rclone() {
  rclone copyto $1 $2/$1
}

nxf_fs_fcp() {
  fcp $1 $2/$1
}

on_exit() {
    local last_err=$?
    local exit_status=${nxf_main_ret:=0}
    [[ ${exit_status} -eq 0 && ${nxf_unstage_ret:=0} -ne 0 ]] && exit_status=${nxf_unstage_ret:=0}
    [[ ${exit_status} -eq 0 && ${last_err} -ne 0 ]] && exit_status=${last_err}
    printf -- $exit_status > /workspaces/training/hello-nextflow/work/98/656c6c90cce1667c094d880f4b6dcc/.exitcode
    set +u
    docker rm $NXF_BOXID &>/dev/null || true
    exit $exit_status
}

on_term() {
    set +e
    docker stop $NXF_BOXID
}

nxf_launch() {
    docker run -i --cpu-shares 1024 -e "NXF_TASK_WORKDIR" -v /workspaces/training/hello-nextflow/work:/workspaces/training/hello-nextflow/work -w "$NXF_TASK_WORKDIR" --name $NXF_BOXID community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273 /bin/bash -ue /workspaces/training/hello-nextflow/work/98/656c6c90cce1667c094d880f4b6dcc/.command.sh
}

nxf_stage() {
    true
    # stage input files
    rm -f COLLECTED-batch-output.txt
    ln -s /workspaces/training/hello-nextflow/work/7f/f435e3f2cf95979b5f3d7647ae6696/COLLECTED-batch-output.txt COLLECTED-batch-output.txt
}

nxf_unstage_outputs() {
    true
}

nxf_unstage_controls() {
    true
}

nxf_unstage() {
    if [[ ${nxf_main_ret:=0} == 0 ]]; then
        (set -e -o pipefail; (nxf_unstage_outputs | tee -a .command.out) 3>&1 1>&2 2>&3 | tee -a .command.err)
        nxf_unstage_ret=$?
    fi
    nxf_unstage_controls
}

nxf_main() {
    trap on_exit EXIT
    trap on_term TERM INT USR2
    trap '' USR1

    [[ "${NXF_CHDIR:-}" ]] && cd "$NXF_CHDIR"
    export NXF_BOXID="nxf-$(dd bs=18 count=1 if=/dev/urandom 2>/dev/null | base64 | tr +/ 0A | tr -d '\r\n')"
    NXF_SCRATCH=''
    [[ $NXF_DEBUG > 0 ]] && nxf_env
    touch /workspaces/training/hello-nextflow/work/98/656c6c90cce1667c094d880f4b6dcc/.command.begin
    set +u
    set -u
    [[ $NXF_SCRATCH ]] && cd $NXF_SCRATCH
    export NXF_TASK_WORKDIR="$PWD"
    nxf_stage

    set +e
    (set -o pipefail; (nxf_launch | tee .command.out) 3>&1 1>&2 2>&3 | tee .command.err) &
    pid=$!
    wait $pid || nxf_main_ret=$?
    nxf_unstage
}

$NXF_ENTRY

Si busca nxf_launch en este archivo, debería ver algo como esto:

nxf_launch() {
    docker run -i --cpu-shares 1024 -e "NXF_TASK_WORKDIR" -v /workspaces/training/hello-nextflow/work:/workspaces/training/hello-nextflow/work -w "$NXF_TASK_WORKDIR" --name $NXF_BOXID community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273 /bin/bash -ue /workspaces/training/hello-nextflow/work/98/656c6c90cce1667c094d880f4b6dcc/.command.sh
}

Como puede ver, Nextflow está usando el comando docker run para lanzar la llamada al proceso. También monta el subdirectorio de trabajo correspondiente en el contenedor, establece el directorio de trabajo dentro del contenedor en consecuencia y ejecuta nuestro script bash con plantilla en el archivo .command.sh.

¿Todo el trabajo duro que tuvimos que hacer manualmente en la primera sección? ¡Nextflow lo hace por nosotros detrás de escena!

 _______________________
< Hurray for robots...! >
 -----------------------
                                   ,-----.
                                   |     |
                                ,--|     |-.
                         __,----|  |     | |
                       ,;::     |  `_____' |
                       `._______|    i^i   |
                                `----| |---'| .
                           ,-------._| |== ||//
                           |       |_|P`.  /'/
                           `-------' 'Y Y/'/'
                                     .==\ /_\
   ^__^                             /   /'|  `i
   (oo)\_______                   /'   /  |   |
   (__)\       )\/\             /'    /   |   `i
       ||----w |           ___,;`----'.___L_,-'`\__
       ||     ||          i_____;----\.____i""\____\

Conclusión

Sabe cómo usar contenedores en Nextflow para ejecutar procesos.

¿Qué sigue?

¡Tome un descanso!

Cuando esté listo, continúe con Parte 6: Hello Config para aprender cómo configurar la ejecución de su pipeline para que se ajuste a su infraestructura, así como gestionar la configuración de entradas y parámetros.

¡Es la última parte, y luego habrá terminado con este curso!


Cuestionario

#

¿Qué es un contenedor?

#

¿Cuál es la diferencia entre una imagen de contenedor y una instancia de contenedor?

#

¿Qué hace la bandera -v en un comando docker run?

#

¿Por qué necesita montar volúmenes al usar contenedores?

#

¿Cómo se especifica un contenedor para un proceso de Nextflow?

#

¿Qué configuración de nextflow.config habilita Docker para su workflow?

#

¿Qué maneja automáticamente Nextflow al ejecutar un proceso en un contenedor? (Seleccione todas las que apliquen)