fbpx

Dzisiaj wpis o tym, w jaki sposób uruchomić jednocześnie dwa procesy w jednym kontenerze. Początkowo może to brzmieć jak anti-pattern, ale pozwól, że wyjaśnię Ci dlaczego czasami warto to rozważyć – oczywiście na prawdziwym przykładzie 🙂

Kontener = Jeden proces

Ogólnie panująca zasada mówi:

Kontener powinien być zgodny z zasadą SRP i mieć pojedynczą odpowiedzialność.

W 100 % się z tym zgadzam, a nawet namawiam do tego w moim poradniku na temat tworzenia Dockerfile.

Główny proces kontenera określamy w pliku Dockerfile za pomocą instrukcji ENTRYPOINT lub CMD. O różnicach pomiędzy tymi instrukcjami pisałem w ostatnim poście.

Na tej podstawie, główny proces kontenera jest odpowiedzialny za zarządzanie pozostałymi procesami, które tworzy. Jeżeli główny proces kończy swoje działanie, automatycznie powoduje to zatrzymanie kontenera.

Chcąc uruchomić kontener składający się z wielu procesów, nie ma do tego jednoznacznego rozwiązania. Nawet jeśli przyszło Ci do głowy, że można by zdefiniować w Dockerfile dwa razy instrukcję ENTRYPOINT lub CMD – niestety, ale to nie zadziała.

Tylko ostatnia instrukcja ENTRYPOINT i/lub CMD jest brana pod uwagę,

Realny Problem

W pewnym projekcie, gdzie używamy bazy danych PostgreSQL w Dockerze do celów testowo-developerskich, potrzebowaliśmy tworzyć joby, które będą wykonywać się co określony interwał czasowy. Problem w tym, że PostgreSQL domyślnie nie posiada takiego mechanizmu, który by na to pozwalał.

Do wyboru mamy dwa pluginy: pg_cron oraz pgAgent.

Początkowo naturalnym wydawał się pg_cron, jednak po zagłębieniu się w jego specyfikację, okazało się, że wymaga on podania na sztywno w pliku konfiguracyjnym nazwy bazy danych.

Niestety nie sprostało to naszym wymaganiom, ponieważ w obecnym projekcie tworzymy dynamicznie bazy danych i na etapie konfiguracji nawet nie znamy ich nazw.

Chyba mam rozwiązanie

Szybki research na temat pgAgenta. „Tak, to zadziała” – pomyślałem.

Wystarczy znaleźć na Docker Hubie gotowy obraz Postgresa z pgAgentem i temat załatwiony. Niestety, jak to w życiu bywa – nie ma tak łatwo. Próby poszukiwania oficjalnego obrazu (lub stabilnego) zakończyły się fiaskiem. Na Githubie również pustki (lub to co znalazłem, mnie nie przekonywało)

Docker - dwa procesy w kontenerze. Jak to zrobić?

Trzeba to zrobić samemu, ale jak?

No dobra, trzeba coś wymyślić samemu. Pierwszym krokiem było rozmontowanie na części pierwsze oficjalnego Dockerfile Postgresa (open-source rulez!)

Pomijając instalację niezbędnych paczek, czy konfiguracje uprawnień, interesowało mnie, co jest głównym procesem kontenera.

FROM debian:stretch-slim

. . . 
. . .

ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 5432
CMD ["postgres"]

Plik docker-entrypoint.sh wykonuje się zawsze jako pierwszy (jeszcze przed startem głównego procesu) i odpowiada za inicjalizację i konfigurację serwera, by następnie uruchomić serwer postgresa.

Własny Dockerfile

W oficjalnym obrazie Postgresa, niestety nie ma zainstalowanego pgAgenta. Trzeba zatem zrobić to samemu 🙂

FROM postgres:10

RUN apt-get update && apt-get -y install pgagent

OK – mam już pgAgenta w obrazie, co dalej? Sama instalacja nie wystarczy. PgAgent musi zostać uruchomiony. Potrzebujemy więc uruchomić dwa procesy na raz. Jeden to pochodzący z oficjalnego obrazu skrypt docker-entrypoint.sh, drugi to pgAgent.

Rozwiązanie

Aby uruchomić jednocześnie pgAgenta oraz serwer Postgresa, potrzebujemy skryptu, który to za nas zrobi. Skrypt ten nazwałem entrypoint.sh.

W pierwszym kroku, uruchomimy pgAgenta, który będzie działać w tle.

/usr/bin/pgagent dbname=postgres user="$POSTGRES_USER"
status=$?
if [ $status -ne 0 ]; then
  echo "Failed to start first process: pg_agent: $status"
  exit $status
fi

Drugi krok, to uruchomienie Postgresa. Uruchomimy docker-entrypoint.sh (domyślny plik znajdujący się w oficjalnym obrazie) oraz pozwolimy na przekazanie dodatkowego argumentu, który zostanie określone w pliku Dockerfile, za pomocą instrukcji CMD.

/docker-entrypoint.sh "[email protected]"
status=$?
if [ $status -ne 0 ]; then
  echo "Failed to start second process: postgres engine: $status"
  exit $status
fi

Na koniec, dodajemy logikę, która w momencie gdy któryś z procesów przestanie działać, wyślę sygnał exit 1.

while sleep 60; do
  ps aux |grep pgagent |grep -q -v grep
  PROCESS_1_STATUS=$?
  ps aux |grep postgres |grep -q -v grep
  PROCESS_2_STATUS=$?
  # If the greps above find anything, they exit with 0 status
  # If they are not both 0, then something is wrong
  if [ $PROCESS_1_STATUS -ne 0 -o $PROCESS_2_STATUS -ne 0 ]; then
    echo "One of the processes has already exited."
    exit 1
  fi
done

Mamy to. Skrypt jest gotowy. Całość znajdziesz na moim Githubie.

Finalny Dockerfile

Mając plik entrypoint.sh, możemy wymusić jego uruchomienie. Poniżej znajdziesz finalny Dockerfile:

FROM postgres:10

RUN apt-get update && apt-get -y install pgagent

COPY create_extension.sh /docker-entrypoint-initdb.d
COPY entrypoint.sh /usr/local/bin/

RUN chmod +x /usr/local/bin/entrypoint.sh \
    && ln -s /usr/local/bin/entrypoint.sh /

ENTRYPOINT ["entrypoint.sh"]
CMD ["postgres"]

Pewnie zastanawiasz się, co kryje się w pliku create_extension.sh. Już tłumaczę. W tym pliku znajduje się polecenie, które wykona skrypt SQL bezpośrednio na bazie danych, a dokładniej, utworzy rozszerzenie pgagent.

psql -U "$POSTGRES_USER" -W postgres -c "CREATE EXTENSION pgagent"

Jak z tego korzystać?

Wystarczy zbudować obraz poleceniem docker build i korzystać z niego dokładnie tak samo jak w przypadku oficjalnego obrazu postgres’a.

Docker Build:

$ docker build . -t postgres-pgagent 

Docker Run:

$ docker run -d --name postgresdb -e POSTGRES_USER=postgres -e \
 POSTGRES_PASSWORD=passsword123 -p 5432:5432 \
 --restart=always postgres-pgagent

Podsumowanie

W niektórych przypadkach uruchomienie dwóch procesów kontenerze ma sens. Należy pamiętać, o odpowiednim przygotowaniu i zarządzaniu tymi procesami. Kluczowa kwestia to sytuacja w której jeden proces nagle kończy swoje działanie. Musisz odpowiedzieć sobie na pytanie:

„Co powinno się wtedy wydarzyć?”

Przykład omawiany w artykule nie jest przeznaczony do rozwiązań produkcyjnych. Jeżeli chcesz z niego skorzystać, rób to z głową!


.

Damian Naprawa

Praktykujący pasjonat konteneryzacji. Lubi dzielić się wiedzą, prowadząc warsztaty i szkolenia. Chętnie występuje również w roli prelegenta na meetupach i konferencjach. Uczestnik globalnego programu partnerskiego Docker Enablement. Pracuje z Dockerem na co dzień od kilku lat. Odpowiedzialny za tworzenie i utrzymanie systemów działających w oparciu o kontenery. Fan automatyzacji oraz podejścia "As a Code".

2 Komentarze

Kamil · 27 lutego 2020 o 7 h 54 min

Czemu ten agent nie mogl byc odpalany jako drugi contener zawierajacy agenta i laczacy sie do pierwszego z czywta baza danych?

Damian Naprawa · 27 lutego 2020 o 8 h 15 min

Dziękuje Kamil za komentarz.

Tak, co to opisałeś jest dobrą opcją. Już tłumaczę dlaczego zrobiliśmy inaczej.

Ten customowy obraz, trzymamy w naszym lokalnym Docker Registry. Osoby korzystające z tego obrazu, nie muszą się zastanawiać w jaki sposób uruchomić dwa kontenery i połączyć je ze sobą. Jak wspominałem, jest to przeznaczone do rozwiązań developerskich/testowych.

Osoba korzystające z tego odpala `docker run` i nic więcej ją nie interesuje 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *