Docker dla amatora danych

Docker dla “amatora” danych – cz. 2 Przekazywanie parametrów do kontenera na przykładzie skryptu Python

Wstęp

Kontenery to temat na pewno nie nowy, natomiast w ostatnich latach bijący chyba rekordy popularności. Podczas tej rosnącej fali ja również postanowiłem sprawdzić, z czym to się je i w czym Docker może pomóc mi. W niniejszym serii postów chciałbym podzielić się z własnymi doświadczeniami, notatkami, fragmentami kodów oraz pokazać, w jaki sposób można wykorzystać Docker’a. Będzie to Docker z perspektywy osoby, która pracuje z danymi. Począwszy od ich gromadzenia, po ich przechowywanie, aż po ich przetwarzanie. Na pewno nie zabraknie samego Docker’a, SQL Server’a i Python’a przedstawionych w jak najbardziej praktyczny sposób. Zapraszam!

Przekazywanie parametrów do kontenera

Jednym z najbardziej powszechnych scenariuszy wydaje się być przekazywanie parametrów do kontenera. W przypadku Dockera nie jest to duży problem i odbywa się zupełnie tak samo jak w przypadku uruchamiania każdej innej aplikacji z linii poleceń. Jako przykład posłuży aplikacji z poprzedniego postu, do której został dodany fragment odpowiedzialny za pobranie jednego parametru (argumentu) i przypisanie go do zmiennej. W dalszej części zmienna ta posłuży do wyświetlenia odpowiedniej ilości postów.

import sys
import requests
from bs4 import BeautifulSoup

response = requests.get('https://pl.seequality.net/feed/')

post_counter = int(sys.argv[1])

if response.status_code == 200:
    feed = BeautifulSoup(response.content, features="lxml")
    for item in feed.find_all("item")[0:post_counter]:
        print ("{0} : {1}".format(item.pubdate.get_text(), item.title.get_text()))
else:
    print ('An error has occurred.')

Dockerfile pozostaje niezmieniony.

# Use an official Python runtime as a parent image
FROM python:latest

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Define entry point
ENTRYPOINT ["python", "app.py"]

Lista niestandardowych bibliotek również nie ulegnie zmianie.

requests
beautifulsoup4
lxml

W następnym kroku możemy przystąpić do zbudowania obrazu. Komenda wygląda tak samo jak poprzednio. Parametry nie są bowiem zależne od obrazu, natomiast od kontenera, czyli od konkretnego uruchomienia skryptu lub aplikacji.

docker build --tag='get-seequality-posts-args' .

Zmiana występuje zatem w komendzie uruchamiającej kontener. W tym miejscu możemy przekazać jeden lub więcej parametrów do kontenera.

docker run get-seequality-posts-args 1
docker run get-seequality-posts-args 3

Powyższy przykład pokazuje, że przekazywanie parametrów jest możliwe oraz proste. Aplikacja zwróciła różne wyniki zgodnie z przekazanym parametrem. Należy jedynie pamiętać, że parametry przekazywane są do aplikacji podczas uruchamiania kontenera, czyli de facto w chwili uruchamiania kolejnej instancji aplikacji. Obraz kontenera pozostaje natomiast “uniwersalnym” obrazem.

Oczywiście istniałaby również możliwość skorzystania z dodatkowych bibliotek jak argparse w samym skrypcie, użycia parametru tekstowego lub większej ilości parametrów.

Przykładowo:

docker run nazwa_obrazu "wartość parametru tekstowego"
docker run nazwa_obrazu "wartość parametru 1" "wartość parametru 2" 3

Domyślne wartości parametrów

Podczas próby uruchomienia kontenera z tego samego obrazu bez argumentu kontener zwróci błąd ze względu na brak argumentu.

docker run get-seequality-posts-args

Oczywiście osobną kwestią jest prawidłowa obsługa takiego wyjątku w samej aplikacji, natomiast inną kwestią jest możliwość zdefiniowania domyślnych wartości parametrów w samym pliku DockerFile. W tym przykładzie jedyna zmiana następuje właśnie w tym pliku (zarówno kod aplikacji jak i lista zależności się nie zmienia).

Nowy plik DockerFIle wygląda następująco:

# Use an official Python runtime as a parent image
FROM python:latest

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Define entry point
ENTRYPOINT ["python", "app.py"]

# Define default paramer value
CMD ["5"]
docker build --tag='get-seequality-posts-args2' .

Obecnie podczas uruchomienia skryptu oraz podania parametru skrypt działa dokładnie tak samo jak poprzednio, natomiast w chwili, gdy parametr nie zostanie podany, zostanie wykorzystany domyślny parametr zdefiniowany w pliku DocerFile.

docker run get-seequality-posts-args2 1
docker run get-seequality-posts-args2 3
docker run get-seequality-posts-args2

Jak zostało zauważone w realnym przykładzie takie wyjątki powinny być obsłużone również w kodzie skryptu lub aplikacji, natomiast warto pamiętać o możliwości definiowania domyślnych wartości parametrów w tym miejscu.

Argparse

Powyższy przykład prezentuje tylko schemat działania i przekazywanie parametrów nie ogranicza się wyłącznie do tego co zostało pokazane, natomiast będzie działać również w innych scenariuszach. Przykładowo można zapewnić wsparcie dla “argparse”. Kod aplikacji został poszerzony o wykorzystanie właśnie tej biblioteki i obsługę parametrów z jej wykorzystaniem.

import sys
import requests
from bs4 import BeautifulSoup
import argparse

response = requests.get('https://pl.seequality.net/feed/')

parser = argparse.ArgumentParser(description='Simple script to get last N posts from seequality.net')
parser.add_argument('-pc','--post_counter', type=int, help='Post counter - number of posts to get', required=True)
args = parser.parse_args()

post_counter = args.post_counter

if response.status_code == 200:
    feed = BeautifulSoup(response.content, features="lxml")
    for item in feed.find_all("item")[0:post_counter]:
        print ("{0} : {1}".format(item.pubdate.get_text(), item.title.get_text()))
else:
    print ('An error has occurred.')

Nastepnie podobnie jak w poprzednim przykładzie istnieje możliwość zdefiniowania domyślnych parametrów w pliku DocerFile.

# Use an official Python runtime as a parent image
FROM python:latest

# Set the working directory to /app
WORKDIR /app

# Copy the current directory contents into the container at /app
COPY . /app

# Install any needed packages specified in requirements.txt
RUN pip install --trusted-host pypi.python.org -r requirements.txt

# Make port 80 available to the world outside this container
EXPOSE 80

# Define environment variable
ENV NAME World

# Define entry point
ENTRYPOINT ["python", "app.py"]

# Define default parameter values
CMD ["--post_counter=5"]

Komenda do budowy pakietu nie ulega zmianie.

Następnie podczas uruchomienia kontenera:

Podczas uruchomienia istnieje również możliwość wykorzystania “argparse”. Docker zapewnia zatem łatwą obsługę parametrów i szerokie wsparcie w tym obszarze. W tym poście zostało to pokazane na przykładzie języka Python, natomiast podobnie będzie to działać dla innych aplikacji.

Zakończenie

Kontenery i Docker dla “amatora danych” mogą okazać się bardzo przydatne, nawet do zastosować w domu (nieprodukcyjnych). Osobiście aplikacje piszę najpierw na komputerze (zwykle język Python), następnie w chwili gdy już są przetestowane oraz sprawnie działają przygotowuję obraz w Dockerze dla takiej aplikacji i następnie uruchamiam aplikację w kontenerze w NAS albo na VPS, który można stosunkowo tanio znaleźć w sieci. Według mnie ułatwia to pracę osoby, która pracuje (hobbystycznie, ale nie tylko) z danymi pomijając już kwestię bardzo szerokiego wykorzystania Dockera w zastosowaniach czysto projektowych i cyklu rozwijania różnego rodzaju aplikacji. Oczywiście jest to tylko wstęp do tej tematyki, a kolejne tematy zostaną poruszone już wkrótce.

Uwaga

Nie jestem z całą pewnością ekspertem w zakresie Dockera oraz Pythona, a wpis ten traktuję bardziej jako zapis moich notatek i prób, które być może przydadzą się również Tobie. W przypadku jakichkolwiek błędów i usprawnień bardzo zapraszam do komentowania lub kontaktu.

Slawomir Drzymala
Follow me on

Leave a Reply