Każdy miał chyba taki moment w swojej karierze tudzież prywatnych działaniach, gdy entuzjastycznie zaczynał nowy projekt. Zaczynanie nowego projektu to często bardzo fajna i przyjemna sprawa. Szczególnie gdy chce nam się wszystko przygotowywać i konfigurować. Ale jeżeli akurat zależy nam na jak najszybszym przetestowaniu nowego pomysłu, takie przygotowania mogą być frustrujące i wyczerpujące. I tutaj pojawia się całkiem ciekawa technologia zwana Docker.
Docker to oprogramowanie, które pozwala tworzyć i uruchamiać kontenery, czyli takie wirtualne maszyny z osobnym systemem operacyjnym. Ale nie są to pełne maszyny wirtualne uruchamiane na wirtualnym sprzęcie. Są one uruchamiane na poziomie jądra systemu operacyjnego. Atutem takich kontenerów jest to, że nie generują one dużego narzutu zasobów — jak to często bywa przy pełnej wirtualizacji.
Powyższy akapit to jednak w dużej mierze nieistotne detale. To, co nas interesuje, to możliwość wykorzystania Dockera jako bazy do błyskawicznego przygotowania naszego środowiska pracy. I to nie byle jakiego, ale dokładnie takiego, jakie jest używane w serwisie Kaggle. No i, żeby było ciekawiej, zintegrujemy je z IDE VS Code. Od pewnego czasu jestem dużym fanem tego IDE i sądzę, że będzie to niezła gratka, nie tylko dla początkujących.
Docker
Pierwszy składnik, który będzie nam potrzebny to sam Docker. Nie będę tutaj rozpisywał jak dokładnie zainstalować Dockera na konkretnym systemie. Jeżeli tak jak ja korzystasz z dystrybucji Linuksa, to odpowiednie instrukcje znajdziesz tutaj. W ten sposób zainstalujesz Docker Engine, który jest darmowy i open source. Jest to sam „silnik”, którego używa się za pomocą linii komend. Co ciekawe, w wielu dystrybucjach Docker jest już dostępny. Jest to jednak prawie zawsze dość stara wersja, która, mimo iż może działać bezproblemowo, niekoniecznie jest wspierana i maksymalnie wydajna. Polecam więc instalację najnowszej stabilnej wersji wprost z oficjalnych repozytoriów.
Dla systemów Windows i macOS twórcy Dockera przygotowali aplikację graficzną, która oprócz wspomnianego powyżej Docker Engine zawiera też interfejs do sprawdzania statusu uruchomionych kontenerów oraz łatwe zarządzanie nimi. Oficjalna instrukcja instalacji znajduje się tutaj. Sam nie korzystałem z tego rozwiązania, ale zdaje się ono być przydatne przy rozwiązywaniu problemów, szczególnie dla początkujących. Uwaga: licencja tego narzędzia (Docker Desktop) wymusza wykupienie płatnej subskrypcji jeżeli firma, w której jest ono używane, ma więcej niż 250 pracowników lub więcej niż 10 M USD rocznego przychodu.
VS Code
Docker sam w sobie jest bardzo przydatnym i kompletnym narzędziem developerskim. Na dobrą sprawę, nie trzeba nic więcej, bo można „ręcznie” połączyć się z uruchomionymi kontenerami i skorzystać z narzędzi, które są tam dostępne. Albo sobie je doinstalować. Nie jest to jednak najbardziej wygodne i edukacyjne podejście dla osób początkujących. Po prostu może to być zbyt przytłaczające. I tu z pomocą przyjdzie nam VS Code. VS Code oprócz tego, że jest nowoczesnym i darmowym środowiskiem programistycznym, posiada całą gamę dodatków. I jednym z takich dodatków jest Remote Development, który pozwala na łatwe manewry związane z kontenerami właśnie.
Potrzebne nam więc VS Code. Pobrać je można z oficjalnej strony Microsoftu. Środowisko to jest darmowe i zbudowane z komponentów open source. Nie jest całościowo na licencji open source. Jest jednakże darmowe, również do użytku komercyjnego więc nie musimy się obawiać nalotu prawników. Po instalacji potrzebujemy jeszcze doinstalować wspomniany powyżej dodatek umożliwiający bezbolesną komunikację z Dockerem. W głównym oknie VS Code naciskamy kombinację przycisków Ctrl + p. W pasku, który się pojawi wklejamy ext install ms-vscode-remote.vscode-remote-extensionpack. VS Code połączy się wtedy z oficjalnym repozytorium i pobierze wskazany dodatek.
Kaggle
Teraz czeka nas najlepsza zabawa. Żeby bez problemowo móc przerzucać kod z komputera do środowisk Kaggle i odwrotnie, najlepiej byłoby mieć taką samą strukturę katalogów. Można to zrobić odpowiednio, podpinając katalogi z dysku do katalogów w kontenerze. Ale ja jakoś szczególnie nie przepadam za tym pomysłem, bo często gdzieś się pomylę i ogólnie mi to trochę zaciemnia to rozwiązanie. Moje podejście to natomiast zbudowanie sobie lokalnego drzewa katalogów, które wygląda jak drzewo katalogów Kaggle. Przyjmijmy, że chciałem pracować nad danymi z konkursu PetFinder.my — Pawpularity Contest. Tworzę więc sobie następującą strukturę:
kaggle ├── .devcontainer.json ├── input │ └── petfinder-pawpularity-score │ ├── sample_submission.csv │ ├── test │ │ ├── 4128bae22183829d2b5fea10effdb0c3.jpg │ │ ├── 43a2262d7738e3d420d453815151079e.jpg │ │ ├── 4e429cead1848a298432a0acad014c9d.jpg │ │ ├── 80bc3ccafcc51b66303c2c263aa38486.jpg │ │ ├── 8f49844c382931444e68dffbe20228f4.jpg │ │ ├── b03f7041962238a7c9d6537e22f9b017.jpg │ │ ├── c978013571258ed6d4637f6e8cc9d6a3.jpg │ │ └── e0de453c1bffc20c22b072b34b54e50f.jpg │ ├── test.csv │ ├── train [9912 entries exceeds filelimit, not opening dir] │ └── train.csv └── working └── petfinder-pawpularity-score.ipynb
Zaczynam od katalogu kaggle. Katalog ten nazwałem tak sobie po prostu, żeby wiedzieć, co w nim jest. Można go sobie nazwać dowolnie. Następnie mamy dwa kluczowe katalogi input i working. Te katalogi chcielibyśmy, żeby tak się nazywały, bo tak się nazywają w środowiskach kaggle. W katalogu input rozpakowałem sobie paczkę z danymi pobranymi z kaggle. W ichnim systemie dane te leżą w dokładnie takim samym miejscu i wszystko w nich jest takie samo. Możemy więc sięgać do katalogów test i train oraz do plików korzystając z takich samych ścieżek. Ale no właśnie, skoro ścieżki chcemy mieć takie same, to musimy startować z kodem z tego samego miejsca. I rozwiązujemy to za pomocą katalogu working. Notebook, który uruchamiamy przez środowisko na stronie kaggle znajduje się właśnie w takim katalogu. Jeżeli więc utworzymy sobie lokalnie plik notebooka (w moim przypadku nazwałem go sobie tak, jak mi pasowało, czyli nazwą konkursu) w tym katalogu, to używając względnych ścieżek, będziemy sobie mogli wczytywać dane dokładnie tak samo w obydwu przypadkach. Uważne oko, zauważy, że został nam jeden plik, którego jeszcze nie omówiłem. Jest to .devcontainer.json i właśnie on jest kwintesencją tego artykułu.
.devcontainer.json
W tym pliku znajdziemy opis, jak to wszystko jest ze sobą spięte. W naszym przypadku plik ten wygląda tak:
{ "image": "gcr.io/kaggle-images/python", "extensions": [ "ms-python.vscode-pylance", "ms-toolsai.jupyter" ], "containerEnv": { "LOCAL": "True" }, }
Jest to najmniejsza wersja, jaka według mnie ma sens i jednocześnie pozwala bez problemowo pracować tak jak na stronie Kaggle. Mamy tutaj trzy elementy. Pierwszy od góry to image. Definiujemy tutaj jaki obraz Docker ma być uruchomiony. Naszym celem jest to, czego używa Kaggle. VS Code pobierze więc ten obraz z repozytorium. Zawartość tego kontenera można swobodnie podejrzeć tutaj.
Później, VS Code umieszcza część swoich bebechów w uruchomionym kontenerze. Ale że jest to minimalna wersja potrzebna do uruchomienia, powinniśmy od razu doinstalować sobie potrzebne dodatki. Dodatki te definiujemy w kluczu extensions. Ja wybrałem jeden ogólny od Pythona i jeden od obsługi notebooków Jupyter.
Ostatnim elementem jest ustawienie zmiennej środowiskowej przez containerEnv. Za pomocą takich zmiennych możemy przekazywać m.in. do Pythona informacje o środowisku, w którym został uruchomiony. Ja stworzyłem tutaj zmienną LOCAL, której przypisałem True. Poniżej pokażę, jak ją użyłem.
Praktyka
Czas na praktykę. Musisz pobrać i zainstalować na swoim komputerze Docker i VS Code. Jak już je masz, to musisz stworzyć na dysku odpowiednią strukturę katalogów i plików. Aby to ułatwić, stworzyłem repozytorium, które to zawiera. Znajdziesz je tutaj.
Teraz wystarczy uruchomić VS Code i wskazać katalog, który pobrałeś z repozytorium albo przygotowałeś ręcznie. VS Code wykryje plik .devcontainer.json i zapyta się, czy uruchomić ten katalog wewnątrz kontenera. Jeżeli się zgodzimy, to będziemy musieli chwilę poczekać. Za pierwszym razem, VS Code pobierze obraz Kaggle, który trochę waży. Na dzień pisania tego artykułu jest to 18.4 GB. Waży to tyle, bo mamy tam całą masę bibliotek, które często robią praktycznie to samo. A jest tak, bo obraz ten powstał z myślą, żeby zadowolić wszystkich zainteresowanych uczeniem maszynowym w języku Python. Gdy już się oswoisz z Dockerem, dobrym pomysłem będzie zbudowanie swojego obrazu na bazie jakiegoś innego małego. Np. taki python3-slim, czyli minimalny Linux z najnowszym Pythonem to 122 MB. Można go wziąć jako bazę i dorzucić swoje ulubione biblioteki jedna za drugą.
Gdy już się wszystko pobierze i uruchomi, możemy i uruchomić przykładowy plik notebooka. Zostaniemy zapytani o wybranie kernela. Należy zwrócić uwagę, że obojętnie jakich środowisk wirtualnych nie mamy w systemie bazowym, pojawi się tutaj lista tylko kerneli ze środowisk wewnątrz kontenera. Dokładnie o to nam chodziło. Wybieramy środowisko base, z dystrybucji conda i już możemy działać pełnią możliwości.
W domu i w Kaggle
Pozostała jeszcze zmienna LOCAL do wyjaśnienia. W notebooku pobieram jej zawartość (w komórce In [8]) do zmiennej local i interpretuję czy jest True, czy False. Jeżeli uruchomię ten notebook w środowisku kaggle, to zmiennej LOCAL tam nie będzie, więc local będzie False więc n = 10. Na moim komputerze w Dockerze ta zmienna jest, wiec local będzie True i n = 1. W ten sposób mogę sterować np. operacjami, które na Kaggle będą mogły korzystać z GPU, a na moim laptopie tylko z CPU.
Podsumowanie
Docker to bardzo wygodne narzędzie programistyczne. Pozwala bez zbędnych ceregieli dzielić się nie tylko kodem, ale i całymi systemami operacyjnymi, w których wszystko jest przygotowane dokładnie, tak jak autor sobie zaplanował. Co więcej, takie kontenery można ze sobą łączyć za pomocą docker-compose i w ten sposób budować sieci, w których różne systemy gadają ze sobą. Oczywiście, jeżeli autorzy dockerów są niechlujni albo chcą złapać wszystkie sroki za ogon, to zamiast lekkich kontenerków, dostajemy kobyłę jak od Kaggle. Jeżeli jednak mamy dostępny plik Dockerfile to możemy podejrzeć, jak to tam zostało zrobione i samemu sobie zbudować coś bardziej odpowiedniego.
W artykule tym opisałem integrację VS Code z Dockerem. Okazuje się jednak, że jeżeli jakieś IDE pretenduje do bycia współczesnym, też mniej lub bardziej pozwala kodować w Dockerze. Polecam więc zdecydowanie sprawdzić i zorganizować sobie coś takiego, bo tak według mnie będzie wyglądać przyszłość programowania.