Tworzenie aplikacji nie jest proste i wie to każdy programista. Najtrudniejsze jest jednak rozpoczęcie swojej przygody, więc tym razem Przemek chciałby pomóc w tej kwestii, czyli rozpoczęcia pracy z Pythonem i Dockerem podczas pracy na systemie Windows.
Wielu deweloperów ze względu na wygodę decyduję się na pracę w środowisku najbliższym aplikacji, na którym będzie wystawiona na produkcję. Pozwala to między innymi na uniknąć niepożądanych problemów integracji międzyplatformowej oraz redukuje to koszt złożonych i drogich narzędzi deweloperskich, które oferują do jakiegoś stopnia integrację wieloplatformową. Ci, którzy nie dysponowali dużym budżetem i specjalistycznym wsparciem technicznym jeszcze do niedawna z góry byli skazani na różne formy obejścia, np. stawiając paralelnie dwa systemy operacyjne spięte pod jedną boot’owalną partycją efi, bądź instalacją maszyn wirtualnych z użyciem Virtualbox’a czy VMWare’a. Z inicjatywą na rozwiązanie potrzeb mniejszych deweloperów wyszedł Microsoft, który po raz pierwszy wprowadził wbudowany w system Win10 wsparcie dla obsługi systemów Linux’owych. WSL (Windows Subsystem for Linux) w pierwszej generacji nie był pełnym systemem i opierał się częściowo o zasoby systemowe Windows’a. Nie posiadał własnego jądra, stąd też nie mógł korzystać w pełni z własnych wywołań systemowych i bezpośrednio korzystać z zasobów sprzętowych. Z tego względu takie narzędzia deweloperskie jak konteneryzacja z użyciem Dockera nie było jeszcze możliwe do zrealizowania. Ale wraz z wejściem w życie najnowszej stabilnej wersji Win10 2004 w maju bieżącego roku deweloperzy otrzymali stabilne niezawodne narzędzie jakim jest WSL2. Microsoft w swojej dokumentacji WSL and WSL2 Comparision przedstawia podstawowe różnice jakie wprowadzono w aktualizacji. Te zmiany umożliwiły pełne wsparcie dla konteneryzacji Dockera i nie tylko. WSL2 nie jest uzależniony już od systemu plików Windows’a oraz umożliwia udostępnienie serwera X, co dla mniej wtajemniczonych oznacza: Tak jest możliwe uruchomienie aplikacji z interfejsem graficznym pod WSL. Więcej o tym i jak to zrobić można przeczytać tutaj: Running WSL GUI Apps on Windows 10
WSL na start – Instalacja najnowszej aktualizacji Windows’a maj 2020
W pierwszej kolejności wypadałoby sprawdzić jaką aktualnie mamy wersję systemu. Najprościej jest to zrobić wpisując w pasek zadań winver
Jeżeli wersja jest wyższa niż 1903 i build wyższy niż 18362 nie ma pilnej potrzeby aktualizacji do nowszej wersji w celu instalacji WSL2, ale zalecana jest mimo wszystko aktualizacja do najnowszej stabilnej wersji.
Klasycznie można zaktualizować Windowsa korzystająć z opcji Windows Update w ustawieniach systemu Win10. Jak należy to zrobić wytłumaczone jest w dokumentacji od Microsoftu pod linkiem Pobieranie aktualizacji systemu Windows 10 z maja 2020 r.. Jeżeli z jakichś przyczyn najnowsza aktualizacja się nie wyświetli, można spróbować zaktualizować system ręcznie korzystając z Asystenta aktualizacji. Możliwe jest też aktualizacja Windows’a do wersji preview, która nie jest oficjalnie wydana i żeby z niej skorzystać należy zasubskrybować opcję Windows Insider Program:
Więcej o tym jak to zrobić można przeczytać pod linkiem Getting started with the Windows Insider Program, ale nie jest to zalecane, ze względu na bardzo zawodne funkcjonowanie WSL2.
WSL – Instalacja i aktualizacja do WSL2
Po tym jak upewniliśmy się, że system spełnia wymogi do instalacji WSL2 możemy przejść do głównego etapu stawiana środowiska. Bardzo pomocna będzie w tym szczegółowa dokumentacja Microsoftu: Install Windows Subsystem for Linux . W pierwszej kolejności trzeba uruchomić Windows’owy shell systemowy PowerShell z uprawnieniami administratora.
Następnie wprowadzamy polecenie w shellu w celu umożliwienia instalacji WSL:
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
Następnie wprowadzamy polecenie w shellu w celu umożliwienia aktualizacji do WSL2:
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
Pobieramy i instalujemy aktualizację do WSL2 link
Na koniec w shellu poniższym poleceniem ustawiamy dla WSL domyślną wersję 2:
wsl --set-default-version 2
Powyższe operacje mogą zajać trochę czasu, a system będzie wymagał ponownego uruchomienia w celu wprowadzenia zmian. Po pomyślnym przejściu tego etapu możemy w końcu zainstalować wybraną przez nas dystrybucję Linux’a. Należy uruchomić aplikację Microsoft Store i wpisać dystrybucję, którą chcemy zainstalować. Do wyboru mamy:
- Ubuntu 16.04 LTS
- Ubuntu 18.04 LTS
- Ubuntu 20.04 LTS
- openSUSE Leap 15.1
- SUSE Linux Enterprise Server 12 SP5
- SUSE Linux Enterprise Server 15 SP1
- Kali Linux
- Debian GNU/Linux
- Fedora Remix for WSL
- Pengwin
- Pengwin Enterprise
- Alpine WSL
Dobrym wyborem na start będzie Ubuntu 20.04 LTS, ze względu na najbardziej powszechne użycie i najlepsze wsparcie. Tu w celach prezentacyjnych pokażę OpenSUSE, ale nie ma większej różnicy w instalacji różnych dystrybucji pod WSL2.
Po pobraniu wpisujemy w pasku zadań naszą dystrubucję i uruchamiamy, należy odczekać chwilę i voilà mamy zainstalowaną dystrybucję linuksa pełną gębą.
Takimi rzeczami jak konfiguracja sieci i inne podstawowe rzeczy są z góry załatwione. Utworzony jest most sieciowy, z naszą dystrybucją, dzięki czemu mamy pełną łączność ze światem z poziomu dystrybucji. Możemy to sprawdzić wpisując w PowerShella ipconfig
, a w WSL ip a show dev eth0
:
Instalacja Docker Desktop
Kolejnym krokiem będzie instalacja zaplecza Dockera, dzięki któremu będzie możliwe korzystanie z wirtualizacji WSL2 do zarządzania kontenerami pod Windows’em. link
Po zainstalowaniu Docker-Desktop i wejściu w ustawienia, możemy zobaczyć, że demon Docker’a będzie korzystał z wirtualizacji WSL2.
O developmencie z użyciem Docker’a i WSL2 można przeczytać tutaj: Docker Desktop WSL 2 backend
Teraz należałoby doinstalować również podstawowe narzędzia potrzebne do zarządzania kontenerami Docker’a. Należy uruchomić dystrybucję i uruchomić polecenie w shellu:
$ sudo apt -y install docker docker-compose
Instalacja Visual Studio Code i konfiguracja do pracy z WSL
Obecnie najlepszym środowiskiem deweloperskim, które spina wszystkie powyższe technologie jest Visual Studio Code od Microsoft’u.
By otrzymać wsparcie VS Code’a dla WSL2 należy zainstalować odpowiedni plugin. Najprościej jest to zrobić wciskając kombinację klawiszy Ctrl+Shift+x i wpisać „WSL”. Pierwsza pozycja Remote – WSL, instalujemy wtyczkę i możemy się cieszyć pełnym wsparciem dla WSL2.
Utworzenie bazowego projektu Django i jego deployment z użyciem Docker’a
W pierwszej kolejności należałoby utworzyć folder na naszą pierwszą aplikację.
Na ten moment folder z naszą aplikacją jest otwarty spod systemu Windows’a, aby otworzyć folder pod WSL2 należy wcisnąć zielony przełącznik w lewym dolnym rogu okna. Następnie wybieramy opcję Remote-WSL: Reopen Folder in WSL, a następnie naszą zainstalowaną dystrybucję.
Kolejno uruchamiamy terminal klikając prawym na pasku po lewej i wybieramy opcję Open in Integrated Terminal. Instalujemy podstawowe narzędzie, by móc przejść do dalszej pracy. W pierwszej kolejności pylint i pipenv. Pylint pomaga w pisaniu kodu w Pythonie i podpowiada składnię, a pipenv pozwoli nam na wprowadzenie wirtualnego środowiska w celu odizolowania narzędzi deweloperskich tylko na potrzebę tego projektu. Należy wykonać poniższe polecenie:
$ sudo apt -y install pylint pipenv
Na potrzebę naszej aplikacji django potrzebny będzie oddzielny katalog, który uprości nam później deployment.
$ mkdir src; cd src
Następnie uruchamiamy wirtualne środowisko wpisując polecenie:
$ pipenv shell .
Instalujemy przy aktywowanym środowisku podstawowe narzędzia: django i silnik bazodanowy do obsługi PostgreSQL psycopg2
$ pipenv install django psycopg2-binary
Tworzymy teraz nasz przykładowy projekt z aplikacją.
$ django-admin startproject example
Później wchodzimy do katalogu z projektem i tworzymy przykładową aplikację
$ django-admin startapp example_app
Kolejno potrzebujemy teraz skonfigurować nasz projekt korzystał z bazy danych Postgres’a i by dane do naszej bazy danych były pobierane jako zmienne środowiskowe. Otwieramy plik example/example/settings.py
i na samym początku zaciągamy moduł os, który będzie potrzebny do pobrania zmiennych ze środowiska.
""" Django settings for example project. Generated by 'django-admin startproject' using Django 3.1.1. For more information on this file, see https://docs.djangoproject.com/en/3.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.1/ref/settings/ """ from pathlib import Path import os # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent ...
Dodajemy naszą aplikację do zainstalowanych:
# Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'example_app' ] ...
I modyfikujemy sekcję DATABASES:
# Database # https://docs.djangoproject.com/en/3.1/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql_psycopg2', 'NAME': os.getenv('POSTGRES_DB'), 'USER': os.getenv('POSTGRES_USER'), 'PASSWORD': os.getenv('POSTGRES_PASSWORD'), 'HOST': os.getenv('POSTGRES_HOST'), 'PORT': '', } } ...
Następnie w katalogu głównym całego projektu tworzymy plik .env i uzupełniamy go adekwatnymi zmiennymi:
Kolejno potrzebujemy, aby nasza aplikacja była uruchamiana w kontenerze, stąd należy utworzyć plik Dockerfile, aplikacja opiera się cała na pythonie, więc zaciągniemy gotowy obraz python:3.8-slim.
FROM python:3.8-slim
Kolejno na potrzebę kontenera tworzymy domyślnego użytkownika z odpowiednimi ograniczonymi uprawnieniami, spod którego aplikacja będzie uruchamiana.
ENV ENVIRONMENT docker ENV NAME example ENV HOME /home/$NAME ENV APP /home/$NAME/app RUN useradd --create-home --home-dir $HOME -u 1000 $NAME \ && mkdir -p $HOME \ && chown -R $NAME:$NAME $HOME
Aby aplikacja w kontenerze działała prawidłowo potrzebuje dostępu do podstawowych narzędzi, bo mogła uruchomić aplikację:
RUN ["pip", "install", "pipenv"]
Wskazujemy, gdzie skopiować pliki naszej aplikacji do katalogu roboczego w kontenerze. Aby to zrobić prawidłowo należy wpierw zmienić użytkownika example, a następnie skopiować pliki z przekazaniem uprawnień dla naszego użytkownika.
USER $NAME COPY --chown=$NAME ./src $APP WORKDIR $APP
Na koniec instalujemy zależności dla naszego projektu i uruchamiamy skrypt inicjalizujący i uruchamiający naszą aplikację.
RUN ["pipenv", "sync"] CMD ["/bin/bash", "entrypoint.sh"]
Całość pliku kontenera powinna wyglądać mniej więcej tak:
FROM python:3.8-slim ENV ENVIRONMENT docker ENV NAME example ENV HOME /home/$NAME ENV APP /home/$NAME/app RUN useradd --create-home --home-dir $HOME -u 1000 $NAME \ && mkdir -p $HOME \ && chown -R $NAME:$NAME $HOME RUN ["pip", "install", "pipenv"] USER $NAME COPY --chown=$NAME ./src $APP WORKDIR $APP RUN ["pipenv", "sync"] CMD ["/bin/bash", "entrypoint.sh"]
Teraz potrzebujemy napisać skrypt, który jak wyżej zostało wspomniane zainicjalizuje i uruchomi aplikację django. Potrzebujemy zmigrować modele bazodanowe i odpalić aplikację:
#!/bin/bash cd example pipenv run python manage.py migrate pipenv run python manage.py runserver 0.0.0.0:8000
Ostatnie co zostało do zrealizowanie to dostarczenie bazy danych dla naszej aplikacji, tu z pomocą przyjdzie nam docker-compose, który pozwala na wyrażenie jakie zależności zachodzą pomiędzy poszczególnym kontenerami. Upraszczając opis tych zależności, nasza aplikacja w swoim kontenerze nie posiada bazy danych, stąd potrzebujemy utworzyć kolejny kontener, w którym będzie uruchomiana instancja bazy PostgreSQL. Całość opisujemy w pliku docker-compose.yml i umieszczamy go w głównym katalogu projektu.
# version: '3.4' version: '3' services: example_app: env_file: - .env # wskazanie ścieżki do pliku ze zmiennymi środowiskowymi restart: unless-stopped # warunek restartu kontenera, gdyby coś poszło nie tak build: . # ścieżka do obrazu image: example # nazwa obrazu volumes: # wskazanie katalogów, które mają być zachowane nawet po tym jeśli kontener zakończy pracę - ./src:/home/jgportal/app ports: # porty które mają być wystawione (tutaj port dla naszej aplikacji django) - "8000:8000" depends_on: - example_db_host # wskazanie kontenera, który ma wpierw być uruchomiony jako kontener zależny (nasza baza danych) networks: - example # wskazanie do jakiej sieci należy kontener example_db_host: env_file: - .env restart: unless-stopped image: postgres:12-alpine volumes: - 'postgres_data:/var/lib/postgresql/data/' # baza danych musi zachowywać dane nawet jeśli ją zatrzymamy i uruchomimy ponownie ports: - "5432:5432" networks: - example # baza danych jest w tej samej sieci co nasza aplikacja networks: example: volumes: postgres_data: src:
Nareszcie na tym etapie pracy możemy sprawdzić owoce naszej pracy, by uruchomić całą usługę w głównym katalogu wpisujemy dwa polecenia:
$ docker-compose build $ docker-compose up
Jeżeli wszystko poszło zgodnie z planem, powinniśmy otrzymać podobny rezultat:
Dowiedz się jeszcze o inny przydatnych rzeczach o Dockerze tutaj!