Przejdź do treści

Część 2: Uruchamianie prawdziwych pipeline'ów

Tłumaczenie wspomagane przez AI - dowiedz się więcej i zasugeruj ulepszenia

W Części 1 tego kursu (Podstawowe operacje) zaczęliśmy od przykładowego workflow'u, który miał tylko minimalne funkcje, aby utrzymać niską złożoność kodu. Na przykład 1-hello.nf używał parametru wiersza poleceń (--input) do przekazywania pojedynczej wartości na raz.

Jednak większość rzeczywistych pipeline'ów wykorzystuje bardziej zaawansowane funkcje, aby umożliwić efektywną obróbkę dużych ilości danych na skalę i stosowanie wielu kroków transformacji połączonych czasami złożoną logiką.

W tej części szkolenia demonstrujemy kluczowe funkcje rzeczywistych pipeline'ów, wypróbowując rozszerzone wersje oryginalnego pipeline'u Hello World.

1. Przetwarzanie danych wejściowych z pliku

W rzeczywistym pipeline'ie zazwyczaj chcemy przetwarzać wiele punktów danych (lub serii danych) zawartych w jednym lub więcej plikach wejściowych. I gdziekolwiek to możliwe, chcemy uruchamiać przetwarzanie niezależnych danych równolegle, aby skrócić czas oczekiwania na analizę.

Aby zademonstrować, jak Nextflow to robi, przygotowaliśmy plik CSV o nazwie greetings.csv, który zawiera kilka powitań wejściowych, naśladując rodzaj danych kolumnowych, które możesz chcieć przetwarzać w prawdziwej analizie danych. Zauważ, że liczby nie mają znaczenia, są tam tylko w celach ilustracyjnych.

data/greetings.csv
1
2
3
Hello,English,123
Bonjour,French,456
Holà,Spanish,789

Napisaliśmy również ulepszoną wersję oryginalnego workflow'u, teraz o nazwie 2a-inputs.nf, która odczyta plik CSV, wyodrębni powitania i zapisze każde z nich do oddzielnego pliku.

sayHello*-output.txtHelloBonjourHolàHello,English,123 Bonjour,French,456Hola,Spanish,789greetings.csvHello-output.txtBonjour-output.txtHolà-output.txt

Uruchommy najpierw workflow, a potem przyjrzymy się odpowiedniemu kodowi Nextflow.

1.1. Uruchom workflow

Uruchom następujące polecenie w terminalu.

nextflow run 2a-inputs.nf --input data/greetings.csv
Wyjście polecenia
N E X T F L O W   ~  version 25.10.2

Launching `2a-inputs.nf` [mighty_sammet] DSL2 - revision: 29fb5352b3

executor >  local (3)
[8e/0eb066] sayHello (2) [100%] 3 of 3 ✔

Ekscytująco, wydaje się to wskazywać, że wykonano '3 of 3' wywołań dla procesu, co jest zachęcające, ponieważ w dostarczonym pliku CSV były trzy wiersze danych. To sugeruje, że proces sayHello() został wywołany trzy razy, raz dla każdego wiersza wejściowego.

1.2. Znajdź opublikowane wyjścia w katalogu results

Przyjrzyjmy się katalogowi 'results', aby zobaczyć, czy nasz workflow nadal zapisuje kopię naszych wyjść tam.

Zawartość katalogu
1
2
3
4
5
6
7
results
├── 1-hello
|   └── output.txt
└── 2a-inputs
    ├── Bonjour-output.txt
    ├── Hello-output.txt
    └── Holà-output.txt

Tak! Widzimy nowy katalog o nazwie 2a-inputs z trzema plikami wyjściowymi o różnych nazwach, co jest wygodne.

Możesz otworzyć każdy z nich, aby upewnić się, że zawierają odpowiedni ciąg powitania.

Zawartość plików
results/2a-inputs/Hello-output.txt
Hello
results/2a-inputs/Bonjour-output.txt
Bonjour
results/2a-inputs/Holà-output.txt
Holà

To potwierdza, że każde powitanie w pliku wejściowym zostało odpowiednio przetworzone.

1.3. Znajdź oryginalne wyjścia i logi

Być może zauważyłeś, że powyższe wyjście konsoli odnosiło się tylko do jednego katalogu zadania. Czy to oznacza, że wszystkie trzy wywołania sayHello() zostały wykonane w tym jednym katalogu zadania?

1.3.1. Zbadaj katalog zadania podany w terminalu

Zajrzyjmy do tego katalogu zadania 8e/0eb066.

Zawartość katalogu
8e/0eb066
work/8e/0eb066071cdb4123906b7b4ea8b047/
└── Bonjour-output.txt

Znajdujemy tylko wyjście odpowiadające jednemu z powitań (oraz pliki pomocnicze, jeśli włączymy wyświetlanie ukrytych plików).

Więc co się dzieje?

Domyślnie system logowania ANSI zapisuje informacje o statusie dla wszystkich wywołań tego samego procesu w tej samej linii. W rezultacie pokazał nam tylko jedną z trzech ścieżek katalogów zadań (8e/0eb066) w wyjściu konsoli. Są dwie inne, które tam nie są wymienione.

1.3.2. Spraw, aby terminal pokazywał więcej szczegółów

Możemy zmodyfikować zachowanie logowania, aby zobaczyć pełną listę wywołań procesu, dodając -ansi-log false do polecenia w następujący sposób:

nextflow run 2a-inputs.nf --input data/greetings.csv -ansi-log false
Wyjście polecenia
1
2
3
4
5
N E X T F L O W  ~  version 25.10.2
Launching `2a-inputs.nf` [pedantic_hamilton] DSL2 - revision: 6bbc42e49f
[ab/1a8ece] Submitted process > sayHello (1)
[0d/2cae24] Submitted process > sayHello (2)
[b5/0df1d6] Submitted process > sayHello (3)

Tym razem widzimy wszystkie trzy uruchomienia procesu i ich powiązane podkatalogi robocze wymienione w wyjściu. Wyłączenie logowania ANSI również uniemożliwiło Nextflow używanie kolorów w wyjściu terminala.

Zauważ, że sposób raportowania statusu jest nieco inny między dwoma trybami logowania. W trybie skondensowanym Nextflow raportuje, czy wywołania zostały pomyślnie ukończone, czy nie. W tym rozszerzonym trybie raportuje tylko, że zostały przesłane.

To potwierdza, że proces sayHello() jest wywoływany trzy razy i dla każdego wywołania tworzony jest oddzielny katalog zadania.

Jeśli zajrzymy do każdego z katalogów zadań wymienionych tam, możemy zweryfikować, że każdy odpowiada jednemu z powitań.

Zawartość katalogu
ab/1a8ece
work/ab/1a8ece307e53f03fce689dde904b64/
└── Hello-output.txt
0d/2cae24
work/0d/2cae2481a53593bc607077c80c9466/
└── Bonjour-output.txt
b5/0df1d6
work/b5/0df1d642353269909c2ce23fc2a8fa/
└── Holà-output.txt

To potwierdza, że każde wywołanie procesu jest wykonywane w izolacji od wszystkich innych. Ma to wiele zalet, w tym unikanie kolizji, jeśli proces produkuje jakieś pliki pośrednie o nieunikatowych nazwach.

Wskazówka

Dla złożonego workflow'u lub dużej liczby danych wejściowych wyświetlanie pełnej listy do terminala może być nieco przytłaczające, więc ludzie normalnie nie używają -ansi-log false w rutynowym użyciu.

1.4. Zbadaj kod workflow

Więc ta wersja workflow'u jest w stanie odczytać plik CSV z danymi wejściowymi, przetwarzać dane wejściowe osobno i nazywać wyjścia unikalnie.

Przyjrzyjmy się, co to umożliwia w kodzie workflow'u.

Pełny plik kodu
2a-inputs.nf
#!/usr/bin/env nextflow

/*
* Użyj echo do wypisania 'Hello World!' do pliku
*/
process sayHello {

    input:
    val greeting

    output:
    path "${greeting}-output.txt"

    script:
    """
    echo '${greeting}' > '${greeting}-output.txt'
    """
}

/*
* Pipeline parameters
*/
params {
    input: Path
}

workflow {

    main:
    // utwórz kanał dla danych wejściowych z pliku CSV
    greeting_ch = channel.fromPath(params.input)
                        .splitCsv()
                        .map { line -> line[0] }
    // wyemituj pozdrowienie
    sayHello(greeting_ch)

    publish:
    first_output = sayHello.out
}

output {
    first_output {
        path '2a-inputs'
        mode 'copy'
    }
}

Ponownie, nie musisz zapamiętywać składni kodu, ale dobrze jest nauczyć się rozpoznawać kluczowe komponenty workflow'u, które zapewniają ważną funkcjonalność.

1.4.1. Ładowanie danych wejściowych z CSV

To jest najciekawsza część: jak przeszliśmy od pobierania pojedynczej wartości z wiersza poleceń do wczytywania pliku CSV, parsowania go i obsługi zawartych w nim pojedynczych powitań?

W Nextflow robimy to za pomocą kanału. Jest to konstrukcja zaprojektowana do efektywnego zarządzania danymi wejściowymi i przekazywania ich z jednego kroku do drugiego w wieloetapowych workflow'ach, zapewniając jednocześnie wbudowaną równoległość i wiele dodatkowych korzyści.

Rozłóżmy to na czynniki.

2a-inputs.nf
    main:
    // utwórz kanał dla danych wejściowych z pliku CSV
    greeting_ch = channel.fromPath(params.input)
                        .splitCsv()
                        .map { line -> line[0] }
    // wyemituj pozdrowienie
    sayHello(greeting_ch)

Ten kod tworzy kanał o nazwie greeting_ch, który odczytuje plik CSV, parsuje go i wyodrębnia pierwszą kolumnę z każdego wiersza. Wynikiem jest kanał zawierający Hello, Bonjour i Holà.

Jak to działa?

Oto co ta linia oznacza prostym językiem:

  • channel.fromPath to fabryka kanału, która tworzy kanał ze ścieżki(ek) pliku
  • (params.input) określa, że ścieżka pliku jest podana przez --input w wierszu poleceń

Innymi słowy, ta linia mówi Nextflow: weź ścieżkę pliku podaną z --input i przygotuj się do traktowania jej zawartości jako danych wejściowych.

Następnie kolejne dwie linie stosują operatory, które wykonują faktyczne parsowanie pliku i ładowanie danych do odpowiedniej struktury danych:

  • .splitCsv() mówi Nextflow, aby sparsował plik CSV na tablicę reprezentującą wiersze i kolumny
  • .map { line -> line[0] } mówi Nextflow, aby wziął tylko element z pierwszej kolumny z każdego wiersza

Więc w praktyce, zaczynając od następującego pliku CSV:

greetings.csv
1
2
3
Hello,English,123
Bonjour,French,456
Holà,Spanish,789

Przekształciliśmy to w tablicę, która wygląda tak:

Zawartość tablicy
[[Hello,English,123],[Bonjour,French,456],[Holà,Spanish,789]]

A następnie wzięliśmy pierwszy element z każdego z trzech wierszy i załadowaliśmy je do kanału Nextflow, który teraz zawiera: Hello, Bonjour i Holà.

Jeśli chcesz zrozumieć kanały i operatory dogłębnie, w tym jak je samodzielnie pisać, zobacz Hello Nextflow Część 2: Hello Channels.

1.4.2. Wywołaj process na każdym powitaniu

Następnie, w ostatniej linii bloku main: workflow'u, przekazujemy załadowany kanał greeting_ch jako dane wejściowe do procesu sayHello().

2a-inputs.nf
    main:
    // utwórz kanał dla danych wejściowych z pliku CSV
    greeting_ch = channel.fromPath(params.input)
                        .splitCsv()
                        .map { line -> line[0] }
    // wyemituj pozdrowienie
    sayHello(greeting_ch)

To mówi Nextflow, aby uruchomił proces indywidualnie na każdym elemencie w kanale, tzn. na każdym powitaniu. A ponieważ Nextflow jest tak inteligentny, uruchomi te wywołania procesu równolegle, jeśli to możliwe, w zależności od dostępnej infrastruktury obliczeniowej.

W ten sposób można osiągnąć efektywne i skalowalne przetwarzanie dużej ilości danych (wielu próbek lub punktów danych, cokolwiek jest Twoją jednostką badawczą) przy stosunkowo niewielkiej ilości kodu.

1.4.3. Jak nazywane są wyjścia

Na koniec warto szybko spojrzeć na kod procesu, aby zobaczyć, jak uzyskujemy unikalne nazwy plików wyjściowych.

2a-inputs.nf
process sayHello {

    input:
    val greeting

    output:
    path "${greeting}-output.txt"

    script:
    """
    echo '${greeting}' > '${greeting}-output.txt'
    """
}

Widzisz, że w porównaniu z wersją tego procesu w 1-hello.nf, deklaracja wyjścia i odpowiednia część polecenia zmieniły się, aby uwzględnić wartość powitania w nazwie pliku wyjściowego.

To jest jeden ze sposobów na upewnienie się, że nazwy plików wyjściowych nie będą kolidować, gdy zostaną opublikowane do wspólnego katalogu results.

I to jest jedyna zmiana, którą musieliśmy wprowadzić wewnątrz deklaracji procesu!

Podsumowanie

Rozumiesz na podstawowym poziomie, jak kanały i operatory umożliwiają nam efektywne przetwarzanie wielu danych wejściowych.

Co dalej?

Odkryj, jak konstruowane są wieloetapowe workflow'y i jak działają.


2. Uruchamianie wieloetapowych workflow'ów

Większość rzeczywistych workflow'ów obejmuje więcej niż jeden krok. Opierając się na tym, czego właśnie nauczyliśmy się o kanałach, przyjrzyjmy się, jak Nextflow używa kanałów i operatorów do łączenia procesów w wieloetapowym workflow'ie.

W tym celu dostarczamy przykładowy workflow, który łączy trzy oddzielne kroki i demonstruje:

  1. Przepływ danych z jednego procesu do następnego
  2. Zbieranie wyjść z wielu wywołań procesu do jednego wywołania procesu

Konkretnie, stworzyliśmy rozszerzoną wersję workflow'u o nazwie 2b-multistep.nf, która przyjmuje każde powitanie wejściowe, konwertuje je na wielkie litery, a następnie zbiera wszystkie powitania wielkimi literami do jednego pliku wyjściowego.

sayHello*-output.txtconvertToUpperUPPER-*collectGreetingsCOLLECTED-output.txtHELLOBONJOURHOLàHello,English,123 Bonjour,French,456Holà,Spanish,789greetings.csvHELLOBONJOURHOLàUPPER-Hello-output.txtUPPER-Bonjour-output.txtUPPER-Holà-output.txt

Jak poprzednio, najpierw uruchomimy workflow, a potem przyjrzymy się kodowi, aby zobaczyć, co jest nowe.

2.1. Uruchom workflow

Uruchom następujące polecenie w terminalu:

nextflow run 2b-multistep.nf --input data/greetings.csv
Wyjście polecenia
1
2
3
4
5
6
7
N E X T F L O W   ~  version 25.10.2

Launching `2b-multistep.nf` [soggy_franklin] DSL2 - revision: bc8e1b2726

[d6/cdf466] sayHello (1)       | 3 of 3 ✔
[99/79394f] convertToUpper (2) | 3 of 3 ✔
[1e/83586c] collectGreetings   | 1 of 1 ✔

Widzisz, że zgodnie z obietnicą, wiele kroków zostało uruchomionych jako część workflow; pierwsze dwa (sayHello i convertToUpper) były prawdopodobnie uruchomione na każdym indywidualnym powitaniu, a trzeci (collectGreetings) został uruchomiony tylko raz, na wyjściach wszystkich trzech wywołań convertToUpper.

2.2. Znajdź wyjścia

Zweryfikujmy, że to faktycznie się stało, patrząc na katalog results.

Zawartość katalogu
results
├── 1-hello
|   └── output.txt
├── 2a-inputs
|   ├── Bonjour-output.txt
|   ├── Hello-output.txt
|   └── Holà-output.txt
└── 2b-multistep
    ├── COLLECTED-batch-output.txt
    ├── batch-report.txt
    └── intermediates
        ├── Bonjour-output.txt
        ├── Hello-output.txt
        ├── Holà-output.txt
        ├── UPPER-Bonjour-output.txt
        ├── UPPER-Hello-output.txt
        └── UPPER-Holà-output.txt

Jak widzisz, mamy nowy katalog o nazwie 2b-multistep i zawiera znacznie więcej plików niż wcześniej. Niektóre pliki zostały zgrupowane w podkatalogu o nazwie intermediates, podczas gdy dwa pliki znajdują się na najwyższym poziomie.

Te dwa to końcowe wyniki wieloetapowego workflow. Poświęć chwilę, aby spojrzeć na nazwy plików i sprawdzić ich zawartość, aby potwierdzić, że są takie, jakich oczekujesz.

Zawartość plików
results/2b-multistep/COLLECTED-batch-output.txt
HELLO
BONJOUR
HOLà
results/2b-multistep/batch-report.txt
There were 3 greetings in this batch.

Pierwszy zawiera nasze trzy powitania, wielkimi literami i zebrane z powrotem do jednego pliku, zgodnie z obietnicą. Drugi to plik raportu, który podsumowuje pewne informacje o uruchomieniu.

2.3. Zbadaj kod

Przyjrzyjmy się kodowi i zidentyfikujmy kluczowe wzorce dla wieloetapowych workflow.

Pełny plik kodu
2b-multistep.nf
#!/usr/bin/env nextflow

/*
* Użyj echo do wypisania 'Hello World!' do pliku
*/
process sayHello {

    input:
    val greeting

    output:
    path "${greeting}-output.txt"

    script:
    """
    echo '${greeting}' > '${greeting}-output.txt'
    """
}

/*
* Użyj narzędzia zamiany tekstu do przekształcenia pozdrowienia na wielkie litery
*/
process convertToUpper {

    input:
    path input_file

    output:
    path "UPPER-${input_file}"

    script:
    """
    cat '${input_file}' | tr '[a-z]' '[A-Z]' > 'UPPER-${input_file}'
    """
}

/*
* Zbierz pozdrowienia pisane wielkimi literami do jednego pliku wyjściowego
*/
process collectGreetings {

    input:
    path input_files
    val batch_name

    output:
    path "COLLECTED-${batch_name}-output.txt", emit: outfile
    path "${batch_name}-report.txt", emit: report

    script:
    count_greetings = input_files.size()
    """
    cat ${input_files} > 'COLLECTED-${batch_name}-output.txt'
    echo 'There were ${count_greetings} greetings in this batch.' > '${batch_name}-report.txt'
    """
}

/*
* Pipeline parameters
*/
params {
    input: Path
    batch: String = 'batch'
}

workflow {

    main:
    // utwórz kanał dla danych wejściowych z pliku CSV
    greeting_ch = channel.fromPath(params.input)
                        .splitCsv()
                        .map { line -> line[0] }
    // wyemituj pozdrowienie
    sayHello(greeting_ch)
    // przekształć pozdrowienie na wielkie litery
    convertToUpper(sayHello.out)
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect(), params.batch)

    publish:
    first_output = sayHello.out
    uppercased = convertToUpper.out
    collected = collectGreetings.out.outfile
    batch_report = collectGreetings.out.report
}

output {
    first_output {
        path '2b-multistep/intermediates'
        mode 'copy'
    }
    uppercased {
        path '2b-multistep/intermediates'
        mode 'copy'
    }
    collected {
        path '2b-multistep'
        mode 'copy'
    }
    batch_report {
        path '2b-multistep'
        mode 'copy'
    }
}

Dużo się tam dzieje, ale najbardziej oczywistą różnicą w porównaniu z poprzednią wersją workflow'u jest to, że teraz jest wiele definicji procesów i odpowiednio kilka wywołań procesów w bloku workflow'u.

Przyjrzyjmy się bliżej i zobaczmy, czy możemy zidentyfikować najciekawsze elementy.

2.3.1. Wizualizacja struktury workflow'u

Jeśli używasz VSCode z rozszerzeniem Nextflow, możesz uzyskać pomocny diagram pokazujący, jak procesy są połączone, klikając mały link DAG preview wyświetlany tuż nad blokiem workflow w dowolnym skrypcie Nextflow.

publish

params

first_output

input

sayHello

convertToUpper

collectGreetings

batch

uppercased

collected

batch_report

To daje ładny przegląd tego, jak procesy są połączone i co produkują.

Widzisz, że oprócz oryginalnego procesu sayHello, mamy teraz również convertToUpper i collectGreetings, które odpowiadają nazwom procesów, które widzieliśmy w wyjściu konsoli. Dwie nowe definicje procesów są zbudowane w ten sam sposób co proces sayHello, z wyjątkiem tego, że collectGreetings przyjmuje dodatkowy parametr wejściowy o nazwie batch i produkuje dwa wyjścia.

Nie będziemy wchodzić w szczegóły kodu dla każdego z nich, ale jeśli jesteś ciekawy, możesz sprawdzić szczegóły w Części 2 Hello Nextflow.

Na razie przyjrzyjmy się, jak procesy są ze sobą połączone.

2.3.2. Jak procesy są połączone

Naprawdę interesującą rzeczą do przyjrzenia się jest to, jak wywołania procesów są połączone ze sobą w bloku main: workflow'u.

2b-multistep.nf
    main:
    // utwórz kanał dla danych wejściowych z pliku CSV
    greeting_ch = channel.fromPath(params.input)
                        .splitCsv()
                        .map { line -> line[0] }
    // wyemituj pozdrowienie
    sayHello(greeting_ch)
    // przekształć pozdrowienie na wielkie litery
    convertToUpper(sayHello.out)
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect(), params.batch)

Widzisz, że pierwsze wywołanie procesu, sayHello(greeting_ch), jest niezmienione. Następnie kolejne wywołanie procesu, do convertToUpper, odnosi się do wyjścia sayHello jako sayHello.out.

Wzorzec jest prosty: processName.out odnosi się do kanału wyjściowego procesu, który może być przekazany bezpośrednio do następnego procesu. W ten sposób przekazujemy dane z jednego kroku do następnego w Nextflow.

2.3.3. Proces może przyjmować wiele danych wejściowych

Trzecie wywołanie procesu, do collectGreetings, jest nieco inne.

2b-multistep.nf
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect(), params.batch)

Widzisz, że to wywołanie otrzymuje dwa dane wejściowe, convertToUpper.out.collect() i params.batch. Ignorując na razie część .collect(), możemy to uogólnić jako collectGreetings(input1, input2).

To odpowiada dwóm deklaracjom wejściowym w module procesu:

2b-multistep.nf
process collectGreetings {

    input:
    path input_files
    val batch_name

Gdy Nextflow parsuje to, przypisze pierwsze dane wejściowe w wywołaniu do path input_files, a drugie do val batch_name.

Więc teraz wiesz, że proces może przyjmować wiele danych wejściowych i jak wygląda wywołanie w bloku workflow'u.

Teraz przyjrzyjmy się bliżej temu pierwszemu wejściu, convertToUpper.out.collect().

2.3.4. Co robi collect() w wywołaniu collectGreetings

Aby przekazać wyjście sayHello do convertToUpper, po prostu odwołaliśmy się do kanału wyjściowego sayHello jako sayHello.out. Ale dla następnego kroku widzimy odniesienie do convertToUpper.out.collect().

Co to jest ten fragment collect() i co robi?

To oczywiście operator. Tak jak operatory splitCsv i map, które napotkaliśmy wcześniej. Tym razem operator nazywa się collect i jest stosowany do kanału wyjściowego produkowanego przez convertToUpper.

Operator collect służy do agregowania wyjść z wielu wywołań tego samego procesu i pakowania ich w pojedynczy element kanału.

W kontekście tego workflow'u pobiera trzy powitania wielkimi literami z convertToUpper.out --które są trzema oddzielnymi elementami i normalnie byłyby obsługiwane w oddzielnych wykonaniach przez następny proces-- i łączy je w jeden element.

Bardziej praktycznie: gdybyśmy nie zastosowali collect() do wyjścia convertToUpper() przed przekazaniem go do collectGreetings(), Nextflow po prostu uruchomiłby collectGreetings() niezależnie na każdym powitaniu, co nie osiągnęłoby naszego celu.

UPPER-Hello-output.txtUPPER-Bonjour-output.txtUPPER-Holà-output.txtcollectGreetingscollectGreetingscollectGreetingsCOLLECTED-output.txtCOLLECTED-output.txtCOLLECTED-output.txtHELLOBONJOURHOLàWITHOUT THE collect() OPERATOR

W przeciwieństwie do tego, użycie collect() pozwala nam wziąć wszystkie oddzielne powitania wielkimi literami wyprodukowane przez drugi krok workflow'u i przekazać je wszystkie razem do jednego wywołania w trzecim kroku pipeline'u.

collectGreetingsCOLLECTED-output.txtHELLOBONJOURHOLàUPPER-Hello-output.txtUPPER-Bonjour-output.txtUPPER-Holà-output.txtWITH THE collect() OPERATOR

W ten sposób otrzymujemy wszystkie powitania z powrotem do tego samego pliku.

Jest wiele innych operatorów dostępnych do stosowania transformacji na zawartości kanału między wywołaniami procesów.

To daje twórcom pipeline'ów dużo elastyczności w dostosowywaniu logiki przepływu ich pipeline'u. Wadą jest to, że czasami może to utrudnić rozszyfrowanie tego, co pipeline robi.

2.3.5. Parametr wejściowy może mieć wartość domyślną

Być może zauważyłeś, że collectGreetings przyjmuje drugie wejście, params.batch:

2b-multistep.nf
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect(), params.batch)

To przekazuje parametr CLI o nazwie --batch do workflow. Jednak gdy uruchomiliśmy workflow wcześniej, nie określiliśmy parametru --batch.

Co się dzieje? Przyjrzyj się blokowi params:

2b-multistep.nf
params {
    input: Path
    batch: String = 'batch'
}

W workflow'ie jest skonfigurowana wartość domyślna, więc nie musimy jej podawać. Ale jeśli podamy jedną w wierszu poleceń, wartość, którą określimy, zostanie użyta zamiast domyślnej.

Spróbuj:

nextflow run 2b-multistep.nf --input data/greetings.csv --batch test
Wyjście polecenia
1
2
3
4
5
6
7
N E X T F L O W   ~  version 25.10.2

Launching `2b-multistep.nf` [soggy_franklin] DSL2 - revision: bc8e1b2726

[a5/cdff26] sayHello (1)       | 3 of 3 ✔
[c5/78794f] convertToUpper (2) | 3 of 3 ✔
[d3/b4d86c] collectGreetings   | 1 of 1 ✔

Powinieneś zobaczyć nowe końcowe wyjścia nazwane Twoją własną nazwą batch.

Zawartość katalogu
results
├── 1-hello
|   └── output.txt
├── 2a-inputs
|   ├── Bonjour-output.txt
|   ├── Hello-output.txt
|   └── Holà-output.txt
└── 2b-multistep
    ├── COLLECTED-batch-output.txt
    ├── COLLECTED-test-output.txt
    ├── batch-report.txt
    ├── test-report.txt
    └── intermediates
        ├── Bonjour-output.txt
        ├── Hello-output.txt
        ├── Holà-output.txt
        ├── UPPER-Bonjour-output.txt
        ├── UPPER-Hello-output.txt
        └── UPPER-Holà-output.txt

To jest aspekt konfiguracji danych wejściowych, który omówimy bardziej szczegółowo w Części 3, ale na razie ważne jest, aby wiedzieć, że parametry wejściowe mogą mieć wartości domyślne.

2.3.6. Proces może produkować wiele wyjść

W definicji procesu collectGreetings widzimy następujące deklaracje wyjść:

2b-multistep.nf
    output:
    path "COLLECTED-${batch_name}-output.txt", emit: outfile
    path "${batch_name}-report.txt", emit: report

Które są następnie przywoływane przez nazwę podaną z emit: w bloku publish::

2b-multistep.nf
    publish:
    first_output = sayHello.out
    uppercased = convertToUpper.out
    collected = collectGreetings.out.outfile
    batch_report = collectGreetings.out.report

To ułatwia przekazywanie konkretnych wyjść indywidualnie do innych procesów w workflow'ie, w połączeniu z różnymi operatorami.

2.3.7. Opublikowane wyjścia można organizować

W bloku output użyliśmy własnych ścieżek do grupowania wyników pośrednich, aby ułatwić wybranie tylko końcowych wyjść workflow'u.

2b-multistep.nf
output {
    first_output {
        path '2b-multistep/intermediates'
        mode 'copy'
    }
    uppercased {
        path '2b-multistep/intermediates'
        mode 'copy'
    }
    collected {
        path '2b-multistep'
        mode 'copy'
    }
    batch_report {
        path '2b-multistep'
        mode 'copy'
    }
}

Istnieją bardziej zaawansowane sposoby organizowania opublikowanych wyjść; omówimy kilka w części o konfiguracji.

Chcesz dowiedzieć się więcej o budowaniu workflow'ów?

Szczegółowe omówienie budowania wieloetapowych workflow'ów znajdziesz w Hello Nextflow Część 3: Hello Workflow.

Podsumowanie

Rozumiesz na podstawowym poziomie, jak wieloetapowe workflow'y są konstruowane przy użyciu kanałów i operatorów oraz jak działają. Widziałeś również, że procesy mogą przyjmować wiele danych wejściowych i produkować wiele wyjść, oraz że mogą być publikowane w uporządkowany sposób.

Co dalej?

Dowiedz się, jak pipeline'y Nextflow mogą być modułowe, aby promować ponowne wykorzystanie kodu i łatwość konserwacji.


3. Uruchamianie zmodularyzowanych pipeline'ów

Jak dotąd wszystkie workflow'y, które oglądaliśmy, składały się z jednego pojedynczego pliku workflow'u zawierającego cały odpowiedni kod.

Jednak rzeczywiste pipeline'y zazwyczaj korzystają z modularyzacji, co oznacza, że kod jest podzielony na różne pliki. To może uczynić ich rozwój i konserwację bardziej efektywnymi i zrównoważonymi.

Tutaj zademonstrujemy najpopularniejszą formę modułowości w Nextflow, czyli użycie modułów.

W Nextflow moduł to pojedyncza definicja procesu zamknięta w samodzielnym pliku. Aby go użyć w workflow'ie, wystarczy dodać jednoliniową instrukcję importu do głównego pliku; następnie można zintegrować funkcjonalność w normalny sposób. To umożliwia ponowne wykorzystanie definicji w wielu pipeline'ach bez tworzenia wielu kopii.

Do tej pory uruchamialiśmy workflow'y, które miały wszystkie swoje procesy zawarte w monolitycznym pliku kodu. Teraz zobaczymy, jak to wygląda, gdy procesy są przechowywane w indywidualnych modułach.

Oczywiście znowu przygotowaliśmy odpowiedni workflow do celów demonstracyjnych, o nazwie 2c-modules.nf, wraz z zestawem modułów znajdujących się w katalogu modules/.

2c-modules.nfsayHello.nfconvertToUpper.nfcollectGreetings.nfincludemodules/sayHelloconvertToUppercollectGreetings
Zawartość katalogu
modules/
├── collectGreetings.nf
├── convertToUpper.nf
├── cowpy.nf
└── sayHello.nf

Widzisz, że są cztery pliki Nextflow, każdy nazwany po jednym z procesów. Na razie możesz zignorować plik cowpy.nf; zajmiemy się nim później.

3.1. Zbadaj kod

Tym razem najpierw przyjrzymy się kodowi. Zacznij od otwarcia pliku workflow 2c-modules.nf.

Pełny plik kodu
2c-modules.nf
#!/usr/bin/env nextflow

// Dołącz moduły
include { sayHello } from './modules/sayHello.nf'
include { convertToUpper } from './modules/convertToUpper.nf'
include { collectGreetings } from './modules/collectGreetings.nf'

/*
* Pipeline parameters
*/
params {
    input: Path
    batch: String = 'batch'
}

workflow {

    main:
    // utwórz kanał dla danych wejściowych z pliku CSV
    greeting_ch = channel.fromPath(params.input)
                        .splitCsv()
                        .map { line -> line[0] }
    // wyemituj pozdrowienie
    sayHello(greeting_ch)
    // przekształć pozdrowienie na wielkie litery
    convertToUpper(sayHello.out)
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect(), params.batch)

    publish:
    first_output = sayHello.out
    uppercased = convertToUpper.out
    collected = collectGreetings.out.outfile
    batch_report = collectGreetings.out.report
}

output {
    first_output {
        path '2c-modules/intermediates'
        mode 'copy'
    }
    uppercased {
        path '2c-modules/intermediates'
        mode 'copy'
    }
    collected {
        path '2c-modules'
        mode 'copy'
    }
    batch_report {
        path '2c-modules'
        mode 'copy'
    }
}

Widzisz, że logika workflow'u jest dokładnie taka sama jak w poprzedniej wersji workflow'u. Jednak kod procesu zniknął z pliku workflow'u, a zamiast tego są instrukcje include wskazujące na oddzielne pliki w katalogu modules.

hello-modules.nf
3
4
5
6
// Dołącz moduły
include { sayHello } from './modules/sayHello.nf'
include { convertToUpper } from './modules/convertToUpper.nf'
include { collectGreetings } from './modules/collectGreetings.nf'

Otwórz jeden z tych plików, a znajdziesz kod dla odpowiedniego procesu.

Pełny plik kodu
modules/sayHello.nf
#!/usr/bin/env nextflow

/*
* Użyj echo do wypisania 'Hello World!' do pliku
*/
process sayHello {

    input:
    val greeting

    output:
    path "${greeting}-output.txt"

    script:
    """
    echo '${greeting}' > '${greeting}-output.txt'
    """
}

Jak widzisz, kod procesu nie zmienił się; został po prostu skopiowany do indywidualnego pliku modułu zamiast być w głównym pliku workflow'u. To samo dotyczy pozostałych dwóch procesów.

Zobaczmy więc, jak wygląda uruchomienie tej nowej wersji.

3.2. Uruchom workflow

Uruchom to polecenie w terminalu, z flagą -resume:

nextflow run 2c-modules.nf --input data/greetings.csv -resume
Wyjście polecenia
N E X T F L O W   ~  version 25.10.2

Launching `2c-modules.nf` [soggy_franklin] DSL2 - revision: bc8e1b2726

[j6/cdfa66] sayHello (1)       | 3 of 3, cached: ✔
[95/79484f] convertToUpper (2) | 3 of 3, cached: ✔
[5e/4358gc] collectGreetings   | 1 of 1, cached: ✔

Zauważysz, że wykonania procesu wszystkie pomyślnie użyły pamięci podręcznej, co oznacza, że Nextflow rozpoznał, że już wykonał żądaną pracę, mimo że kod został podzielony, a główny plik workflow'u został przemianowany.

Nic z tego nie ma znaczenia dla Nextflow; liczy się skrypt zadania, który jest generowany po zebraniu i ocenie całego kodu.

Wskazówka

Możliwe jest również zamknięcie sekcji workflow'u jako 'subworkflow', który można zaimportować do większego pipeline'u, ale to wykracza poza zakres tego kursu.

Więcej o rozwijaniu komponowalnych workflow'ów możesz dowiedzieć się w Side Quest o Workflows of Workflows.

Podsumowanie

Wiesz, jak procesy mogą być przechowywane w samodzielnych modułach, aby promować ponowne wykorzystanie kodu i poprawić łatwość konserwacji.

Co dalej?

Naucz się używać kontenerów do zarządzania zależnościami oprogramowania.


4. Używanie konteneryzowanego oprogramowania

Jak dotąd workflow'y, których używaliśmy jako przykładów, musiały tylko uruchamiać bardzo podstawowe operacje przetwarzania tekstu przy użyciu narzędzi UNIX dostępnych w naszym środowisku.

Jednak rzeczywiste pipeline'y zazwyczaj wymagają specjalistycznych narzędzi i pakietów, które nie są domyślnie zawarte w większości środowisk. Zazwyczaj musiałbyś zainstalować te narzędzia, zarządzać ich zależnościami i rozwiązywać wszelkie konflikty.

To wszystko jest bardzo żmudne i denerwujące. Znacznie lepszym sposobem rozwiązania tego problemu jest użycie kontenerów.

Kontener to lekka, samodzielna, wykonywalna jednostka oprogramowania utworzona z obrazu kontenera, która zawiera wszystko, co potrzebne do uruchomienia aplikacji, w tym kod, biblioteki systemowe i ustawienia.

Wskazówka

Uczymy tego przy użyciu technologii Docker, ale Nextflow obsługuje również kilka innych technologii kontenerowych.

4.1. Użyj kontenera bezpośrednio

Najpierw spróbujmy bezpośrednio wejść w interakcję z kontenerem. To pomoże utrwalić zrozumienie tego, czym są kontenery, zanim zaczniemy ich używać w Nextflow.

4.1.1. Pobierz obraz kontenera

Aby użyć kontenera, zazwyczaj pobierasz lub "ściągasz" obraz kontenera z rejestru kontenerów, a następnie uruchamiasz obraz kontenera, aby utworzyć instancję kontenera.

Ogólna składnia jest następująca:

Składnia
docker pull '<container>'
  • docker pull to instrukcja dla systemu kontenerowego, aby pobrać obraz kontenera z repozytorium.
  • '<container>' to adres URI obrazu kontenera.

Jako przykład pobierzmy obraz kontenera zawierający cowpy, pythonową implementację narzędzia o nazwie cowsay, które generuje grafikę ASCII do wyświetlania dowolnych tekstowych danych wejściowych w zabawny sposób.

Istnieją różne repozytoria, w których można znaleźć opublikowane kontenery. Użyliśmy usługi Seqera Containers, aby wygenerować ten obraz kontenera Docker z pakietu Conda cowpy: 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273'.

Uruchom pełne polecenie pull:

docker pull 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273'
Wyjście polecenia
Unable to find image 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273' locally
131d6a1b707a8e65: 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
622dd7f15040: Pull complete
895fb5d0f4df: Pull complete
Digest: sha256:fa50498b32534d83e0a89bb21fec0c47cc03933ac95c6b6587df82aaa9d68db3
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

To mówi systemowi, aby pobrał określony obraz. Po zakończeniu pobierania masz lokalną kopię obrazu kontenera.

4.1.2. Uruchom kontener

Kontenery można uruchamiać jako jednorazowe polecenie. Można ich również używać interaktywnie, co daje Ci wiersz poleceń wewnątrz kontenera i pozwala eksperymentować.

Ogólna składnia jest następująca:

Składnia
docker run --rm '<container>' [polecenie narzędzia]
  • docker run --rm '<container>' to instrukcja dla systemu kontenerowego, aby uruchomić instancję kontenera z obrazu kontenera i wykonać w niej polecenie.
  • --rm mówi systemowi, aby wyłączył instancję kontenera po zakończeniu polecenia.

W pełni złożone polecenie wykonania kontenera wygląda tak:

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

Uruchom to polecenie, a powinieneś zobaczyć, że Twój znak zachęty zmienia się na coś takiego jak (base) root@b645838b3314:/tmp#, co wskazuje, że jesteś teraz wewnątrz kontenera.

Możesz to zweryfikować, uruchamiając ls, aby wylistować zawartość katalogu:

ls /
Wyjście polecenia
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

Widzisz, że system plików wewnątrz kontenera jest inny niż system plików na Twoim systemie hosta.

Wskazówka

Gdy uruchamiasz kontener, jest on domyślnie odizolowany od systemu hosta. To oznacza, że kontener nie może uzyskać dostępu do żadnych plików w systemie hosta, chyba że wyraźnie na to pozwolisz, określając, że chcesz zamontować wolumin jako część polecenia docker run używając następującej składni:

Składnia
-v <ścieżka_zewnętrzna>:<ścieżka_wewnętrzna>

To skutecznie ustanawia tunel przez ścianę kontenera, którego możesz użyć, aby uzyskać dostęp do tej części Twojego systemu plików.

Jest to omówione bardziej szczegółowo w Części 5 Hello Nextflow.

4.1.3. Uruchom narzędzie cowpy

Z wnętrza kontenera możesz uruchomić polecenie cowpy bezpośrednio.

cowpy "Hello Containers"
Wyjście polecenia
______________________________________________________
< Hello Containers >
------------------------------------------------------
    \   ^__^
      \  (oo)\_______
        (__)\       )\/\
          ||----w |
          ||     ||

To produkuje grafikę ASCII domyślnej postaci krowy (lub 'cowacter') z dymkiem mowy zawierającym określony przez nas tekst.

Teraz, gdy przetestowałeś podstawowe użycie, możesz spróbować podać mu jakieś parametry. Na przykład dokumentacja narzędzia mówi, że możemy ustawić postać za pomocą -c.

cowpy "Hello Containers" -c tux
Wyjście polecenia
__________________
< Hello Containers >
------------------
  \
    \
        .--.
      |o_o |
      |:_/ |
      //   \ \
    (|     | )
    /'\_   _/`\
    \___)=(___/

Tym razem wyjście grafiki ASCII pokazuje pingwina Linuxa, Tux, ponieważ określiliśmy parametr -c tux.

Ponieważ jesteś wewnątrz kontenera, możesz uruchamiać polecenie cowpy tyle razy, ile chcesz, zmieniając parametry wejściowe, bez martwienia się o instalację jakichkolwiek bibliotek w samym systemie.

Inne dostępne postacie

Użyj flagi '-c', aby wybrać inną postać, w tym:

beavis, cheese, daemon, dragonandcow, ghostbusters, kitty, moose, milk, stegosaurus, turkey, turtle, tux

Możesz się tym pobawić. Gdy skończysz, wyjdź z kontenera używając polecenia exit:

exit

Znajdziesz się z powrotem w normalnej powłoce.

4.2. Użyj kontenera w workflow

Gdy uruchamiamy pipeline, chcemy wskazać Nextflow, jakiego obrazu użyć w każdym kroku. Co ważne, oczekujemy też, że automatycznie obsłuży całą tę pracę: pobierze obraz, uruchomi instancję, wykona polecenie i usunie ją po zakończeniu.

Dobra wiadomość: to dokładnie to, co Nextflow zrobi za nas. Musimy tylko określić kontener dla każdego procesu.

Aby zademonstrować, jak to działa, stworzyliśmy kolejną wersję naszego workflow'u, która uruchamia cowpy na pliku zebranych powitań wyprodukowanych w trzecim kroku.

COLLECTED-output.txtHELLOBONJOURHOLàcowPycowpy-COLLECTED-output.txt ________/ HOLà \| HELLO |\ BONJOUR / -------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||

To powinno wyprodukować plik zawierający grafikę ASCII z trzema powitaniami w dymku mowy.

4.2.1. Zbadaj kod

Workflow jest bardzo podobny do poprzedniego, plus dodatkowy krok do uruchomienia cowpy.

Pełny plik kodu
2d-container.nf
#!/usr/bin/env nextflow

// Dołącz moduły
include { sayHello } from './modules/sayHello.nf'
include { convertToUpper } from './modules/convertToUpper.nf'
include { collectGreetings } from './modules/collectGreetings.nf'
include { cowpy } from './modules/cowpy.nf'

/*
* Pipeline parameters
*/
params {
    input: Path
    batch: String = 'batch'
    character: String
}

workflow {

    main:
    // utwórz kanał dla danych wejściowych z pliku CSV
    greeting_ch = channel.fromPath(params.input)
                        .splitCsv()
                        .map { line -> line[0] }
    // wyemituj pozdrowienie
    sayHello(greeting_ch)
    // przekształć pozdrowienie na wielkie litery
    convertToUpper(sayHello.out)
    // zbierz wszystkie pozdrowienia do jednego pliku
    collectGreetings(convertToUpper.out.collect(), params.batch)
    // wygeneruj grafikę ASCII powitań za pomocą cowpy
    cowpy(collectGreetings.out.outfile, params.character)

    publish:
    first_output = sayHello.out
    uppercased = convertToUpper.out
    collected = collectGreetings.out.outfile
    batch_report = collectGreetings.out.report
    cowpy_art = cowpy.out
}

output {
    first_output {
        path '2d-container/intermediates'
        mode 'copy'
    }
    uppercased {
        path '2d-container/intermediates'
        mode 'copy'
    }
    collected {
        path '2d-container/intermediates'
        mode 'copy'
    }
    batch_report {
        path '2d-container'
        mode 'copy'
    }
    cowpy_art {
        path '2d-container'
        mode 'copy'
    }
}

Widzisz, że ten workflow importuje proces cowpy z pliku modułu i wywołuje go na wyjściu wywołania collectGreetings(), plus parametr wejściowy o nazwie params.character.

2d-container.nf
// wygeneruj grafikę ASCII za pomocą cowpy
cowpy(collectGreetings.out, params.character)

Proces cowpy, który opakowuje polecenie cowpy do generowania grafiki ASCII, jest zdefiniowany w module cowpy.nf.

Pełny plik kodu
modules/cowpy.nf
#!/usr/bin/env nextflow

// Wygeneruj grafikę ASCII za pomocą cowpy (https://github.com/jeffbuttars/cowpy)
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}
    """
}

Proces cowpy wymaga dwóch danych wejściowych: ścieżki do pliku wejściowego zawierającego tekst do umieszczenia w dymku mowy (input_file) oraz wartości zmiennej character.

Co ważne, zawiera również linię container 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273', która wskazuje na URI kontenera, którego użyliśmy wcześniej.

4.2.2. Sprawdź, czy Docker jest włączony w konfiguracji

Trochę wyprzedzimy Część 3 tego kursu szkoleniowego, wprowadzając plik konfiguracyjny nextflow.config, który jest jednym z głównych sposobów, jakie Nextflow oferuje do konfigurowania wykonywania workflow'u. Gdy plik o nazwie nextflow.config jest obecny w bieżącym katalogu, Nextflow automatycznie go załaduje i zastosuje zawartą w nim konfigurację.

W tym celu dołączyliśmy plik nextflow.config z pojedynczą linią kodu, która włącza Docker.

nextflow.config
docker.enabled = true

Ta konfiguracja mówi Nextflow, aby używał Docker dla każdego procesu, który określa kompatybilny kontener.

Wskazówka

Technicznie możliwe jest włączenie wykonywania Docker z wiersza poleceń, na podstawie pojedynczego uruchomienia, używając parametru -with-docker <container>. Jednak to pozwala nam określić tylko jeden kontener dla całego workflow'u, podczas gdy podejście, które właśnie pokazaliśmy, pozwala nam określić inny kontener dla każdego procesu. To drugie jest znacznie lepsze dla modułowości, konserwacji kodu i odtwarzalności.

4.2.3. Uruchom workflow

Podsumowując, oto co zamierzamy uruchomić:

sayHello*-output.txtconvertToUpperUPPER-*collectGreetingsCOLLECTED-output.txtHELLOBONJOURHOLàHello,English,123 Bonjour,French,456Holà,Spanish,789greetings.csvHELLOBONJOURHOLàUPPER-Hello-output.txtUPPER-Bonjour-output.txtUPPER-Holà-output.txtcowPycowpy-COLLECTED-output.txt ________/ HOLà \| HELLO |\ BONJOUR / -------- \ ^__^ \ (oo)\_______ (__)\ )\/\ ||----w | || ||

Myślisz, że zadziała?

Uruchommy workflow z flagą -resume i określmy, że chcemy, aby postacią był indyk.

nextflow run 2d-container.nf --input data/greetings.csv --character turkey -resume
Wyjście polecenia
N E X T F L O W   ~  version 25.10.2

Launching `2d-container.nf` [elegant_brattain] DSL2 - revision: 028a841db1

executor >  local (1)
[95/fa0bac] sayHello (3)       | 3 of 3, cached: 3 ✔
[92/32533f] convertToUpper (3) | 3 of 3, cached: 3 ✔
[aa/e697a2] collectGreetings   | 1 of 1, cached: 1 ✔
[7f/caf718] cowpy              | 1 of 1 ✔

Pierwsze trzy kroki użyły pamięci podręcznej, ponieważ już je wcześniej uruchomiliśmy, ale proces cowpy jest nowy, więc faktycznie zostaje uruchomiony.

Możesz znaleźć wyjście kroku cowpy w katalogu results.

Zawartość pliku
results/2d-container/cowpy-COLLECTED-batch-output.txt
_________
/ HOLà    \
| HELLO   |
\ BONJOUR /
---------
  \                                  ,+*^^*+___+++_
  \                           ,*^^^^              )
    \                       _+*                     ^**+_
    \                    +^       _ _++*+_+++_,         )
              _+^^*+_    (     ,+*^ ^          \+_        )
            {       )  (    ,(    ,_+--+--,      ^)      ^\
            { (\@)    } f   ,(  ,+-^ __*_*_  ^^\_   ^\       )
          {:;-/    (_+*-+^^^^^+*+*<_ _++_)_    )    )      /
          ( /  (    (        ,___    ^*+_+* )   <    <      \
          U _/     )    *--<  ) ^\-----++__)   )    )       )
            (      )  _(^)^^))  )  )\^^^^^))^*+/    /       /
          (      /  (_))_^)) )  )  ))^^^^^))^^^)__/     +^^
        (     ,/    (^))^))  )  ) ))^^^^^^^))^^)       _)
          *+__+*       (_))^)  ) ) ))^^^^^^))^^^^^)____*^
          \             \_)^)_)) ))^^^^^^^^^^))^^^^)
          (_             ^\__^^^^^^^^^^^^))^^^^^^^)
            ^\___            ^\__^^^^^^))^^^^^^^^)\\
                  ^^^^^\uuu/^^\uuu/^^^^\^\^\^\^\^\^\^\
                    ___) >____) >___   ^\_\_\_\_\_\_\)
                    ^^^//\\_^^//\\_^       ^(\_\_\_\)
                      ^^^ ^^ ^^^ ^

Widzisz, że postać mówi wszystkie powitania, ponieważ uruchomiła się na pliku zebranych powitań wielkimi literami.

Co ważniejsze, mogliśmy to uruchomić jako część naszego pipeline bez konieczności prawidłowej instalacji cowpy i wszystkich jego zależności. I teraz możemy udostępnić pipeline współpracownikom i sprawić, że uruchomią go na swojej infrastrukturze bez konieczności instalowania czegokolwiek, poza Docker lub jedną z jego alternatyw (taką jak Singularity/Apptainer) jak wspomniano powyżej.

4.2.4. Sprawdź, jak Nextflow uruchomił konteneryzowane zadanie

Na zakończenie tej sekcji przyjrzyjmy się podkatalogowi roboczemu dla jednego z wywołań process cowpy, aby uzyskać nieco więcej wglądu w to, jak Nextflow pracuje z kontenerami pod maską.

Sprawdź wyjście z polecenia nextflow run, aby znaleźć ścieżkę do podkatalogu roboczego dla process cowpy. Patrząc na to, co otrzymaliśmy dla uruchomienia pokazanego powyżej, linia dziennika konsoli dla process cowpy zaczyna się od [7f/caf718]. To odpowiada następującej skróconej ścieżce katalogu: work/7f/caf718.

W tym katalogu znajdziesz plik .command.run, który zawiera wszystkie polecenia, które Nextflow uruchomił w Twoim imieniu w trakcie wykonywania pipeline.

Zawartość pliku
work/7f/caf71890cce1667c094d880f4b6dcc/.command.run
#!/bin/bash
### ---
### name: 'cowpy'
### container: 'community.wave.seqera.io/library/cowpy:1.1.5--3db457ae1977a273'
### outputs:
### - 'cowpy-COLLECTED-batch-output.txt'
### ...

Są tam polecenia docker, które Nextflow składa na podstawie URI obrazu kontenera podanego w definicji process, a także innych ustawień, które możemy określić za pomocą konfiguracji. Domyślnie Nextflow montuje katalog roboczy wewnątrz kontenera, co sprawia, że wszystkie pliki wejściowe i wyjściowe są dostępne bez dodatkowych czynności z naszej strony. Całkiem sprytne!

Podsumowanie

Wiesz, jak określić kontener dla process i jak Nextflow obsługuje go pod maską, aby uczynić Twój kod przenośnym i odtwarzalnym.

Co dalej?

W następnej części kursu dowiesz się, jak dostosować konfigurację workflow do swoich preferencji i środowiska obliczeniowego.