24 KiB
Inżynieria uczenia maszynowego
27 marca 2024
4. Konteneryzacja
Plan na dziś
- Konteneryzacja i Docker
- Integracja Docker-Jenkins
1. Konteneryzacja
- Konteneryzacja to metoda tworzenia lekkich, odizolowanych środowisk uruchomieniowych
- Różnica między konteneryzacją a maszynami wirtualnymi:
- Wirtualizacja na poziomie systemu operacyjnego, a nie sprzętu
- Konteneryzacja jest lżejsza: kontener używa tego samego jądra (_kernel) co system hosta - mniejszy narzut
- Konteneryzacja zapewnia gorszy stopień izolacji od hosta
- Dzięki dostępnym narzędziom i infrastrukturze kontenery są łatwiejsze w utrzymywaniu i bardziej przenośne
- Kontenery ułatwiają rozwój, uruchamianie i dostarczanie aplikacji.
- Konteneryzacja to nie tylko Docker:
- chroot
- Solaris Containers
- LXC (Linux Containers)
- OpenVZ
- Windows containers
1.1 Docker
- Najpopularniejszy obecnie system konteneryzacji
- Działa w m.in. na systemach Linux, Windows i Mac OS
- Udostępnia narzędzia i serwisy ułatwiające korzystanie, zarządzanie i dzielenie się kontenerami
- Docker umożliwia stworzenie paczki zawierającej sam program jak i środowisko, w którym ma być on uruchomiony
- Dokumentacja: https://docs.docker.com/get-started/
1.1 Terminologia
- _Container (kontener) - instancja obrazu. Może być uruchomiona, zatrzymana. Stan kontenera można zapisać tworząc nowy obraz. Uruchomienie kontenera zazwyczaj zajmuje mało czasu.
- _Image (obraz) - niezmienny (readonly) przepis na stworzenie kontenera. Obrazy można ściągnąć lub udostępnić za pośrednictem rejestru. Budowanie obrazu trwa zazwyczaj długo.
- _Dockerfile - plik tekstowy zawierający przepis na zbudowanie obrazu
- _Docker registry - rodzaj repozytorium, przechowującego obrazy dockera
- _Docker Hub - publiczne Docker registry, z którego każdy może pobrać potrzebne mu obrazy oraz publikować swoje
1.2 Jak zacząć?
- Na komputerach w laboratoriach: https://laboratoria.wmi.amu.edu.pl/problemy/docker
- Lokalnie: instalacja
- Możliwa zarówno pod Linux, Windows i MacOS.
- Kontenery linuxowe można uruchamiać również pod Windowsem - za pomocą Docker Desktop lub WSL 2 (Windows Subsystem for Linux). Pod spodem oba używają maszyny wirtualnej (Hyper-V) z Linuxem, w której uruchamiane są kontenery (tak to działa w teorii, a tak to można uruchomić)
1.3 Podstawowe polecenia
docker help [polecenie]
- wyświetla listę dostępnych poleceń dockera lub opis podanego polecenia. Rozbudowana dokumentacja poleceń: https://docs.docker.com/engine/reference/commandline/docker/docker run
- uruchamia istniejący obraz tworząc kontener. Przykładowe wywołania:docker run -i -t ubuntu
Uruchamia kontener z obrazu "ubuntu", alokuje terminal (-t
) i ustawia tryb "interaktywny" (-i
), dzięki czemu dostajemy terminal "wenątrz" kontenera i możemy wywoływać w nim poleceniadocker run -p 8080:8080 jenkins/jenkins:lts
Uruchomi kontener z Jenkinsem, w wersji "LTS". Jeśli obraz Jenkins nie był wcześniej zbudowany/pobrany na lokalną maszynę, automatycznie zostanie pobrany z Docker Hub. Port 8080 konenera zostanie powiązany z portem 8080 hosta. Dzięki temu będziemy mogli w przeglądarce dostać się do Jenkinsa pod adresem http://localhost:8080
docker build [OPTIONS] PATH | URL | -
- buduje obraz na podstawie pliku Dockerfile i kontekstu (plików dostępnych podczas budowania). Przykład:docker build -t tzietkiewicz/helloworld:1.0 .
buduje obraz przekazując bieżący katalog (.
) jako kontekst i korzystając z pliku Dockerfile znajdującego się tamże. Obraz zostanie otagowany (-t
) tagiemtzietkiewicz/helloworld
z wersją1.0
docker images
- listuje dostępne lokalnie obrazydocker ps
- listuje uruchomione/zatrzymane kontenerydocker stop CONTAINER_ID
- zatrzymuje kontener. Uruchomione w nim procesy zostają wyłączone (SIGTERM
->SIGKILL
)docker start CONTAINER_ID
- uruchamia ponownie kontenerdocker system df
- pokazuje miejsce zajęte przez obrazy, kontenery i woluminydocker system prune
- usuwa nieużywane obrazy i kontenery, zazwyczaj zwalniając sporo miejsca na dyskudocker exec
- uruchamia polecenie wewnątrz działającego kontenera
1.4 Interakcja/komunikacja z kontenerem
- Poprzez terminal
- Poprzez
docker exec
- wywołanie polecenia w działającym kontenerze - Poprzez port zmapowany przez flagę
-p
(np. REST API albo interfejs w przegląrce) - Przez system plików:
- bind mount: podmontowanie katalogów hosta w kontenerze poprzez flagę
-v
; dokumentacja tu i tam - volumes: https://docs.docker.com/storage/volumes/
- bind mount: podmontowanie katalogów hosta w kontenerze poprzez flagę
Zadanie 1 [na zajęciach]
- Zainstaluj Docker (lub skorzystaj z https://laboratoria.wmi.amu.edu.pl/problemy/docker)
- Uruchom obraz
ubuntu
w trybie interaktywnym z terminalem (docker run -ti ubuntu
)
Poniższe punkty wywołujemy wewnątrz kontenera (w otwartym właśnie terminalu):
- Jaka wersja Ubuntu działa w kontenerze? (polecenie
cat /etc/issue
) - Jaki użytkownik wywołuje polecenia? (polecenie
whoami
) - Jaki jest bieżący katalog? (polecenie
pwd
) - Jaki procesor jest widoczny w środku kontenera? Czy jest to procesor Twojej maszyny? (
lscpu
) - Ile wolnego miejsca na dysku jest widoczne wewnątrz kontenera (
df -h
)? Czy jest to zgodne z tym, co pokazuje maszyna hosta? - Sprawdź, ile dostępnej pamięci RAM widać w konenerze (
free -h
). Czy zgadza się to z wynikiem na maszynie hosta? - Spróbuj uruchomić jedno z popularnych narzędzi Linuksowych, dostępnych na maszynie hosta (np.
vim
,less
,htop
). Czy udało się je uruchomić? - Zainstaluj w kontenerze jedno z brakujących narzędzi, np:
apt update; apt install htop
- Uruchom następujące polecenie:
while true; do date > time.log; sleep 1; done
będzie ono co sekundę zapisywać bieżący timestamp w pliku time.log
Poniższe polecenia wykonujemy na zewnątrz kontenera (otwórz w tym celu nową konsolę):
- Sprawdź ID uruchomionego kontenera (
docker ps
) i zatrzymaj go (docker stop
) - Sprawdź co się stało w oknie z konsolą kontenera
- Uruchom kontener jeszcze raz (
docker start
). Gdzie jest nasza konsola?!! - Żeby otrzymać konsolę, wykonaj jedno z poniższych:
- zatrzymaj kontener i uruchom jeszcze raz z flagą
-i
- uruchom polecenie "bash" w kontenerze za pomocą polecenia
docker exec -ti CONTAINER_ID bash
- pozwala ono uruchomić dowolne polecenie w działającym kontenerze.
- zatrzymaj kontener i uruchom jeszcze raz z flagą
- Czy polecenie
while true; do date > time.log; sleep 1; done
wciąż działa? Sprawdź ostatni timestamp zapisany do plikutime.log
1.4 Dockerfile
- Dokumentacja: https://docs.docker.com/engine/reference/builder/
- Dockerfile składa się z serii poleceń.
- Polecenia dockera są pisane WIELKIMI LITERAMI.
- Wywołanie każdego polecenia tworzy nową warstwę (_layer).
- Jeśli zbudowaliśmy obraz, a potem zmieniliśmy jedno z poleceń lub dodaliśmy nowe, to przebudowane zostaną tylko warstwy od zmienionej w dół (oszczędność czasu i zasobów).
Przykładowy Dockerfile:
# Nasz obraz będzie dziedziczył z obrazu Ubuntu w wersji latest
FROM ubuntu:latest
# Instalujemy niezbędne zależności. Zwróć uwagę na flagę "-y" (assume yes)
RUN apt update && apt install -y figlet
# Stwórzmy w kontenerze (jeśli nie istnieje) katalog /app i przejdźmy do niego (wszystkie kolejne polecenia RUN, CMD, ENTRYPOINT, COPY i ADD będą w nim wykonywane)
WORKDIR /app
# Skopiujmy nasz skrypt do katalogu /app w kontenerze
COPY ./figlet-loop.sh ./
# Domyślne polecenie, które zostanie uruchomione w kontenerze po jego starcie
CMD ./figlet-loop.sh
Zawartość pliku figlet-loop.sh
:
#!/bin/bash
while read line; do
figlet "$line"
done
Budujemy obraz:
docker build -t figlet-loop .
Sending build context to Docker daemon 3.072kB
Step 1/5 : FROM ubuntu:latest
---> 94e814e2efa8
Step 2/5 : RUN apt update && apt install -y figlet
---> Running in ba8b14deeeca
Get:1 http://archive.ubuntu.com/ubuntu bionic InRelease [242 kB]
[...]
Get:18 http://security.ubuntu.com/ubuntu bionic-security/universe amd64 Packages [1402 kB]
Fetched 22.3 MB in 3s (8343 kB/s)
Reading package lists...
Building dependency tree...
Reading state information...
[...]
Reading package lists...
Building dependency tree...
Reading state information...
The following NEW packages will be installed:
figlet
0 upgraded, 1 newly installed, 0 to remove and 50 not upgraded.
Need to get 133 kB of archives.
After this operation, 752 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu bionic/universe amd64 figlet amd64 2.2.5-3 [133 kB]
debconf: delaying package configuration, since apt-utils is not installed
Fetched 133 kB in 0s (605 kB/s)
Selecting previously unselected package figlet.
(Reading database ... 4039 files and directories currently installed.)
Preparing to unpack .../figlet_2.2.5-3_amd64.deb ...
Unpacking figlet (2.2.5-3) ...
Setting up figlet (2.2.5-3) ...
update-alternatives: using /usr/bin/figlet-figlet to provide /usr/bin/figlet (figlet) in auto mode
update-alternatives: warning: skip creation of /usr/share/man/man6/figlet.6.gz because associated file /usr/share/man/man6/figlet-figlet.6.gz (of link group figlet) doesn't exist
Removing intermediate container ba8b14deeeca
---> 30470dc0bd47
Step 3/5 : WORKDIR /app
---> Running in 47ca74217790
Removing intermediate container 47ca74217790
---> 0f352dfc965d
Step 4/5 : COPY ./figlet-loop.sh ./
---> 450ba60dc50d
Step 5/5 : CMD ./figlet-loop.sh
---> Running in 38f83a71b1a9
Removing intermediate container 38f83a71b1a9
---> c6d81a065621
Successfully built figlet-loop:latest
Uruchamiamy:
docker run -ti figlet-loop
Hello World!
_ _ _ _ __ __ _ _ _
| | | | ___| | | ___ \ \ / /__ _ __| | __| | |
| |_| |/ _ \ | |/ _ \ \ \ /\ / / _ \| '__| |/ _` | |
| _ | __/ | | (_) | \ V V / (_) | | | | (_| |_|
|_| |_|\___|_|_|\___/ \_/\_/ \___/|_| |_|\__,_(_)
Zamiast domyślnego polecenia (figlet-loop.sh
) uruchommy bash, żeby sprawdzić, co jest w naszym kontenerze:
docker run -ti figlet-loop:latest bash
root@a44c68ce835e:/app# pwd
/app
root@a44c68ce835e:/app# ls -l
total 4
-rwxrwxr-x 1 root root 53 Mar 28 22:33 figlet-loop.sh
root@a44c68ce835e:/app#
Zadanie 2. [10 pkt]
Napisz prosty Dockerfile, który zdefinuje środowisko potrzebne do wywołania poleceń stworzonych na zajęciach "2. Dane". Przypuszczalnie wystarczy, że zainstalujesz zależności takie jak kaggle czy pandas, np:
pip install --user kaggle pip install --user pandas
Niech zdefiniowany przez Ciebie obraz dziedziczy z jakiegoś popoularneg obrazu, np.
ubuntu
Umieść Dockerfile w głównym katalogu repozytorium ze skryptami stworzonymi na zajęciach 2.Zbuduj obraz korzystając z polecenia
docker build .
Na końcu otrzymasz id obrazu, który powstałUruchom kontener z terminalem
docker run -ti IMAGE_ID
podając ID obrazu, które otrzymałeś w poleceniu 2.Spróbuj uruchomić jeden z twoich skryptów w kontenerze. Jeśli brakuje jakiś zależności: możesz spróbować zainstalować je interaktywnie w konsoli. W ten sposób będziesz wiedział jakie polecenia dodać do Dockerfile.
Plik Dockerfile dodaj do repozytorium
Utwórz bezpłatne konto na https://hub.docker.com/
Po zalogowaniu stwórz nowe repozytorium (https://hub.docker.com/repository/create), możesz nazwać je np.
ium
W ustawieniach docker hub wygeneruj "Access token" (https://hub.docker.com/settings/security)
Uruchom
docker login --username TWÓJ_DOCKER_ID
i podaj stworzony w poprzednim kroku "Access token"Dodaj tag do obrazu stworzonego 2 punkcie 3. Dzięki temu będzie można go opublikować:
docker tag IMAGE_ID TWÓJ_DOCKER_ID/ium:NUMER_WERSJI
. Mogliśmy otagować obraz na etapie jego budowania (flaga-t
), ale wtedy nie znaliśmy jeszcze nazwy użytkownika i repozytorium z Docker Hub.Teraz możesz wypchnąć swój otagowany obraz do repozytorium:
docker push TWÓJ_DOCKER_ID/ium:NUMER_WERSJI
Twój obraz powinien być widoczny na Docker Hub. Inni mogą go teraz znaleźć na Docker Hub, np.:
https://hub.docker.com/r/tzietkiewicz/ium
oraz uruchomić wywołując:docker run TWÓJ_DOCKER_ID/ium:NUMER_WERSJI
, np.:
docker run tzietkiewicz/ium
Link do obrazu w Docker Hub wklej do arkusza ze zbiorami danych link
2. Integracja Docker-Jenkins
2.1 Jenkins
Jenkins może działać na wielu systemach operacyjnych:
- Linux
- Windows
- MacOS
2.1 Jenkins - Terminologia (https://www.jenkins.io/doc/book/glossary/):
- Controller: The central, coordinating process which stores configuration, loads plugins, and renders the various user interfaces for Jenkins
- Agent: An agent is typically a machine, or container, which connects to a Jenkins controller and executes tasks when directed by the controller
- Master: A deprecated term, synonymous with Controller.
- Node: A machine which is part of the Jenkins environment and capable of executing Pipelines or Projects. Both the Controller and Agents are considered to be Nodes.
- Executor: A slot for execution of work defined by a Pipeline or Project on a Node. A Node may have zero or more Executors configured which corresponds to how many concurrent Projects or Pipelines are able to execute on that Node.
Główna instancja Jenkinsa (tzw. _Controller aka. Master) może mieć podłączonych kilka węzłów ("nodes") typu "slave".
- _Controller jest odpowiedzialny za interakcję z użytkownikiem, rozdzielanie zadań dla między agentów
- Zadania ("builds") są wykonywane na jednym z "Agentów". Node, na którym jest uruchomiony "Controller" może działać również jako "Agent"
- Każdy węzeł może działać pod kontrolą innego systemu operacyjnego. Dzięki temu możemy wykonywać zadania (albo ich części) w różnych środowiskach
- Do definiowana gdzie może być wykonana dana część pipeline, służy sekcja agent / node. Poniższy kod wywoła sie tylko na węźle/węzłach "myAgent":
node("myAgent") { stage("One"){ echo 'hello' } }
2.2 Docker w Jenkins
- Jenkins posiada wygodną intergrację z Dockerem
- Umożliwia ona uruchamianie kroków (steps) wewnątrz kontera
- Obrazy mogą być automatycznie pobrane albo zbudowane na podstawie Dockerfile
- Więcej informacji: https://www.jenkins.io/doc/book/pipeline/docker/
2.2 Przykłady pipeline
Używanie gotowego obrazu:
- Scrippted pipeline:
node { docker.image('ubuntu:latest').inside { stage('Test') { sh 'cat /etc/issue' } } }
- Declarative:
pipeline { agent { docker { image 'ubuntu:latest' } } stages { stage('Test') { steps { sh 'cat /etc/issue' } } } }
2.2 Przykłady pipeline
Budowanie obrazu z Dockerfile:
Scrippted pipeline:
node { checkout scm //Pierwszy argument to tag, który zostania nadany zbudowanemu obrazowi //Jeśli chcemy użyć Dockerfile z innej ścieżki niż ./Dockerfile, możemy ją podać jako drugi argument def testImage = docker.build("test-image", "./dockerfiles/test") //Wszystkie polecenia poniżej wykonają się w kontenerze, z podmontowanym Workspace Jenkinsa testImage.inside { sh 'make test' } }
Declarative:
pipeline { agent { dockerfile true } stages { stage('Test') { steps { sh 'cat /etc/issue' } } } }
Zadanie 3. [5 pkt]
Zmodyfikuj stworzony na poprzednich (3.) zajęciach Jenkinsfile opisujący pipeline "s123456-create-dataset" tak, żeby wywoływać w nim skrypty stworzone na zajęciach 2., w środku kontenera stworzonego w zadaniu 2.
Skorzystaj z mechanizmu tworzącego kontener bezpośrednio na Jenkinsie wprost z pliku Dockerfile.Zmodyfikuj stworzony na poprzednich (3.) zajęciach Jenkinsfile opisujący pipeline "s123456-dataset-stats" tak, żeby wywoływać w nim skrypty stworzone na zajęciach 2., w środku kontenera stworzonego w zadaniu 2.
Skorzystaj z mechanizmu wykorzystującego gotowy obraz (ściągany z Docker Hub)