Docker – nowy sposób na wirtualizację

0

Docker jest lekkim środowiskiem wirtualizacyjnym, stworzonym z myślą o szybkim i prostym wdrażaniu oraz publikacji zmian. Istotny jest fakt, że bazuje na LXC, czyli na kontenerach linuksowych. Dzięki temu osoby, które miały z nimi styczność, łatwiej poradzą sobie z tworzeniem nowych środowisk wirtualnych.

Docker – jak LXC – jest wolnym oprogramowaniem. W związku z tym ma bogatą społeczność osób, które go rozwijają i pomagają innym w rozwiązywaniu problemów. Główną zaletą Dockera jest fakt, że stworzone za jego pomocą środowiska wirtualne będą działały i zachowywały się dokładnie tak samo, niezależnie od tego, w jakim środowisku zostaną uruchomione.

Kontenery dockerowe można uruchomić zarówno na maszynach dedykowanych, jak i w dowolnego rodzaju maszynach wirtualnych. We wszystkich tych miejscach ich zachowanie będzie niezmienne. Dzięki temu wystarczy raz zbudować środowisko dla wybranej aplikacji – potem można już tylko korzystać z tak przygotowanego szablonu. Migracja kontenerów dockerowych jest bardzo prosta.

Kolejną zaletą jest wielozadaniowość. W kontenerze można uruchomić wiele popularnych dystrybucji Linuksa, jednak muszą one być odpowiednio przygotowane. Jeśli nie mamy czasu na pracę nad własną modyfikacją, istnieje pokaźna biblioteka gotowych obrazów i szablonów. Pozwala to w kontenerach dockerowych uruchomić w prosty sposób praktycznie dowolną aplikację działającą na Linuksie.

Ponadto metoda budowania obrazów została tak przemyślana, aby były one cache’owane w trakcie budowania. Dzięki temu kolejne procesy tworzenia obrazów przebiegają bardzo szybko, gdyż mogą bazować na poprzednikach. Systemy plików poszczególnych kontenerów są oddzielne, w związku z tym mamy wrażenie pracy na zupełnie oddzielnym systemie operacyjnym.

Docker vs LXC

Po co w ogóle Docker, skoro bazuje on na LXC i praktycznie nie rozwija jego funkcjonalności? Odpowiedź jest prosta – LXC wciąż pozostaje w użyciu dla osób, które go potrzebują, tak samo, jak pełnej kontroli nad kontenerem. Docker zmienia jednak kompletnie podejście do tworzenia i utrzymania środowiska wirtualnego.

  1. Docker jest warstwą abstrakcji, która mocno upraszcza i ułatwia zarządzanie kontenerami (co w LXC wcale nie bywa proste). Przykładowo, uruchamiając kontener dockerowy na różnych środowiskach – zachowa się on zawsze tak samo. W przypadku LXC nie mamy tej pewności, ponieważ środowiska oparte na LXC mogą się znacząco różnić.
  2. System wersjonowania kontenerów dockerowych bardzo ułatwia prace zarówno programistom, jak i innym osobom zajmującym się utrzymaniem środowisk. Dotyczy to tworzenia nowych wersji, commitowania czy przywracania starych.
  3. Istnieje możliwość wielokrotnego użycia w projektach podstawowych obrazów. Wystarczy stworzyć pewien szablon, na bazie którego można opracować późniejsze gałęzie innych wersji oprogramowania.

Jak widać, Docker daje sporo elastyczności obudowanej w łatwy do użycia interfejs. Większość funkcji można zasymulować za pomocą czystego LXC, ale to kosztuje sporo pracy, którą już w przypadku Dockera wykonano za nas. A używanie Dockera – w porównaniu z LXC – dla programistów bywa dużo przyjemniejsze i obarczone mniejszym ryzykiem zawału czy nerwicy.

Dla kogo Docker i jakie ma zastosowania?
Zastosowania Dockera mogą wydawać się dość ograniczone – proste serwery WWW czy mniej skomplikowane aplikacje. Jednak w rzeczywistości pozwala stworzyć bardzo zaawansowane środowiska, składające się z wielkiej liczby kontenerów hostujących skomplikowane i ciężkie aplikacje. Co więcej – za pomocą tych kontenerów można z łatwością tworzyć środowiska deweloperskie i testowe.

Wyobraźmy sobie scenariusz, w którym zespół SCRUM pracuje nad aplikacją. Środowisko dla tej aplikacji można z łatwością zbudować w Dockerze, a następnie rozesłać między członkami zespołu – czy to w postaci pliku tar, czy też użyć prywatnego repozytorium, które to Docker wspiera. Gdy aplikacja jest gotowa, programiści wysyłają swoje zmiany do systemu kontroli wersji, a ten komunikuje się z systemem do walidowania poprawności kodu i testowania wprowadzonych zmian oraz próbuje wykonać budowę i testy aplikacji. Takim systemem może być np. Jenkins i może on w łatwy sposób po swojej stronie budować kontenery do testowania aplikacji.

Sam Jenkins również może być uruchomiony w kontenerze. Docker wspiera zagnieżdżoną konteneryzację. Takie środowiska testowe buduje się bardzo szybko, a testowanie jest łatwiejsze, ponieważ wirtualizacja nie ma tak sporego narzutu. Na koniec – publikacja nowej wersji aplikacji na środowisku produkcyjnym również przebiega błyskawicznie, gdyż Docker z repozytorium pobierze tylko te zmiany, które zostały wprowadzone w aplikacji, i dopisze je do ostatniej wersji, tworząc nowy obraz. Proces jest wyjątkowo szybki.

Programista może więc bez problemu poradzić sobie z procesami CI/CD oraz zarządzać wersjonowaniem w środowisku produkcyjnym, pozostawiając administratorom jedynie konfigurowanie i poprawę wydajności systemu hosta.

Pierwszy kontener
Zarządzanie kontenerami jest bardzo proste – interfejs CLI ma dość bogatą dokumentację, ale liczba funkcji nie jest przygniatająca. Dzięki temu łatwo o szybki start dla osób, które z Dockerem nigdy nie miały do czynienia. Poniżej opis, jak zbudować prosty kontener. Najpierw należy utworzyć plik o nazwie Dockerfile i opisać w nim, w jaki sposób kontener ma zostać zbudowany. Oto jego kod:

`FROM fedora
MAINTAINER scollier <scollier@redhat.com>

RUN yum -y update; yum clean all
RUN yum -y install nginx; yum clean all
RUN echo „daemon off;” >> /etc/nginx/nginx.conf
RUN echo „nginx on Fedora” > /usr/share/nginx/html/index.html

EXPOSE 80

CMD [ „/usr/sbin/nginx” ]`

W ten oto sposób stworzono konfigurację pierwszego kontenera, którego jedynym zadaniem jest uruchomienie serwera Nginx i serwowanie statycznej strony index.html o treści „nginx on Fedora”. Teraz należy zbudować środowisko oparte na tym Dockerfile, a następnie je uruchomić.

`$ docker build –rm -t nginx .
$ docker run -d -p 80:80 -i nginx`

W ten oto sposób utworzono i uruchomiono pierwszy w pełni funkcjonalny kontener, którego jedyną funkcją jest serwowanie statycznej strony za pomocą serwera Nginx.

Co do szczegółów dotyczących Dockera,większość akcji związanych z zarządzaniem kontenerami wykonujemy za pomocą binarki ‚docker’. Ma ona wiele parametrów, które, oczywiście, można odnaleźć w dokumentacji. Najważniejsze z nich to:

  • build – służy do tworzenia obrazu kontenera z utworzonego Dockerfile,
  • run – pozwala na uruchomienie instancji kontenera z przygotowanego obrazu,
  • attach – komenda ta umożliwia podłączenie się do uruchomionej instancji kontenera,
  • commit – utworzona instancja kontenera po wyłączeniu nie zapisze żadnych zmian w swoim wewnętrznym filesystemie – zostaną one utracone (taka jest idea); dlatego też jeśli w trakcie pracy kontenera zechcemy zapisać jego stan, to za pomocą tej komendy możemy utworzyć nowy obraz kontenera z wybranej instancji,
  • export – pozwala wyeksportować kontener do pliku tar,
  • history – istotna komenda, dzięki której możemy zobaczyć historię danej instancji kontenera,
  • images – listowanie utworzonych na serwerze obrazów,
  • import – importowanie kontenerów (np. z plików tar, tar.gz, tgz, bzip itd.),
  • ps – wyświetlanie istniejących instancji kontenerów.

Dostępnych komend jest oczywiście dużo więcej, dlatego zachęcam do przeglądu dokumentacji.

Automatyzacja

Obecnie standardem przemysłowym jest automatyzowanie procesów. W środowiskach IT mamy do tego wiele znanych narzędzi: Chef, Puppet, Cfengine czy popularny ostatnio Ansible. Ansible – jako narzędzie wśród wymienionych najlżejsze i również obarczone najłatwiejszą ścieżką nauki – – idealnie wpisuje się w świat kontenerów dockerowych. –Ponadto zawiera gotowe moduły do obsługi środowisk opartych na Dockerze, więc ilość pracy, którą trzeba wykonać – aby wdrożyć automatyzację – drastycznie maleje. Poniżej playbook ansible’owy, za pomocą którego – z utworzonego powyżej Docekrfile obrazu ‚nginx’ – można zbudować kontener:


`- hosts: web
sudo: yes
tasks:
– name: check or build image
docker_image: path=”/srv/containers/nginx” name=”nginx”
state=present`


Co kryje środek Dockerfile?

Dockerfile jest plikiem zawierającym instrukcje wymagane do utworzenia obrazu kontenera. Na powyższym przykładzie można zauważyć pięć różnych instrukcji.

  • FROM fedora – oznacza, aby z publicznego repozytorium obrazów pobrać dystrybucję Fedora, której będziemy używać jako system tego kontenera,
  • MAINTAINER – informacja na temat osoby lub grupy opiekującej się danym kontenerem,
  • RUN – uruchomienie wybranej komendy systemowej już w nowym systemie kontenera,
  • EXPOSE – prosty sposób na konfigurację nasłuchiwania na wybranych portach,
  • CMD – określa aplikację uruchomieniową, czyli tą, która ma działać na serwerze.

Ideą Dockera jest uruchamianie jednej aplikacji na kontenerze, stąd tylko ostatnie polecenie CMD zostanie wykonane. Aby uruchomić więcej niż jedną aplikację, należy w CMD użyć np. demona supervisord.

Takich poleceń wewnętrznych Dockerfile jest dużo więcej. Dzięki nim możemy definiować współdzielone z innymi kontenerami (lub hostem) katalogi i zmienne środowiskowe dla kontenera, czy przegrywać w trakcie procesu tworzenia pliki z hosta na kontener. Składnia jest elastyczna, dzięki czemu łatwo jest utworzyć środowisko dla znanych aplikacji.


Zarządzanie zasobami kontenerów

W LXC do zarządzania zasobami używamy control – groups. Podobnie ma to miejsce w Dockerze, jednak jest to tutaj trochę prostsze. Silnikiem do dystrybuowania zasobów jest ciągle cgroups. Aby ustawić limit pamięci dla uruchamianej instancji kontenera, wystarczy podać parametr uruchomieniowy:

`docker run -m=”512m”`

Możliwe jest też zarządzanie zasobami, takimi jak priorytet procesora – szczegóły w dokumentacji.

Bezpieczeństwo

Docker bazuje na kilku rozwiązaniach zapewniających bezpieczeństwo. Większość została wdrożona kilka warstw niżej pod Dockerem i odnosi się do LXC.

Pierwszym z tych rozwiązań są linuksowe przestrzenie nazw (namespaces). Zapewniają one izolację pomiędzy kontenerem a całym środowiskiem zewnętrznym (a więc i kontenerami stojącymi obok). Dzięki temu właśnie kontenery mają wrażenie, że zawierają zupełnie normalne (choć trochę ograniczone) zasoby systemowe, gdzie w rzeczywistości są to tylko specjalnie spreparowane widoki (np. na system plików czy sieć).

Kolejną furtką, która chroni kontenery, jest sposób zarządzania zasobami. Został on w całości oparty na linuksowych control – groups, a więc również przebywa na barkach LXC. Dzięki użyciu cgroups kontenerom można ograniczać zasoby, takie jak pamięć, cykle procesora, sieć, czy nawet operacje typu I/O. Te możliwości są o tyle istotne, że rezerwacja zasobów dla wybranych kontenerów pozwala im np. zapewnić wysoką stabilność i wykonanie SLA w części dotyczącej zasobów.

Co istotne, w odróżnieniu od bezpieczeństwa hipernadzorców – mitem jest, że są one bezpieczniejsze, bo tworzą dużo cięższą do przebycia warstwę abstrakcji. Czyżby? Widzieliśmy na CloudOpen sposoby na łatwe oszukanie Xena. Wiemy, że libvirt/KVM domyślnie nie włącza filtrowania L2, więc łatwo przy nieuwadze o spoofing. Prawda jest taka, że każda technologia wirtualizacji czy konteneryzacji ma słabe strony i naszym zadaniem – jako operatorów – jest zapewnienie, aby jeszcze przed wdrożeniem żadne z nich nie ujrzały światła dziennego. W przypadku LXC czy Dockera rozsądnie jest np. dodać jeszcze jakąś warstwę abstrakcji, która dodatkowo zaizoluje już mocno zapewnioną izolację kontenerów – np. za pomocą Linux Security Modules (LSM) typu SELinux czy GrSec/PAX lub AppArmor.

Największym zagrożeniem wciąż pozostaje błąd ludzki lub brak doświadczenia i wiedzy na temat uruchamianego środowiska – i tutaj najłatwiej jest szukać pomysłów na ekslpoatację systemów.

Zarządzanie siecią

Docker używa mostków sieciowych (network bridge) do zapewnienia komunikacji. Domyślnie tworzonym mostkiem jest docker0, przed którego utworzeniem Docker szuka wolnej klasy adresowej niepozostającej w konflikcie z obecnymi (na podstawie aktualnej tablicy routingu), podejmuje decyzje o jej zajęciu, a następnie się binduje do jednego z adresów IP w tej klasie (np. 172.17.12.0/24).

Dla każdej nowej instancji kontenera uruchamiany jest nowy wirtualny interfejs sieciowy, który jest bondowany do głównego mostka, a jego adres (docker0) jest wykorzystany jako brama. Ponadto docker używa iptables do zarządzania komunikacją między portami, wybranymi kontenerami itd.

W ten sposób zapewniona jest możliwość komunikacji między kontenerami i tej przychodzącej z zewnątrz. Oczywiście, istnieje też możliwość modyfikowania ustawień sieciowych (np. zmiana zakresu sieci). Podobnie w ostatniej wersji 0.11 (która jest RC) dodano tryb host networking. Dzięki temu kontener może działać w ramach sieci hosta, a nie samego Dockera. To może ułatwić konfigurację środowiska.

Repozytorium obrazów
Obrazy kontenerów można łatwo eksportować i importować (za pośrednictwem komend import/export). Można również użyć do tego centralnego repozytorium, do którego publikujemy swoją pracę. Docker zawiera publiczne repozytorium obrazów pod adresem index.docker.io – znajdziemy w nim bardzo dużą liczbę obrazów, które zostały opublikowane przez społeczność. Oczywiście, istnieje też możliwość zamówienia opcji prywatnego repozytorium.

Często jednak interesuje nas utworzenie prywatnego repozytorium w ramach własnej infrastruktury IT. Jest taka możliwość. Została ona opisana nawet na oficjalnym blogu Dockera i korzysta z projektu ‚docker-registry’, który znajduje się na GitHubie pod oficjalnym kontem firmy DotCloud – właściciela Dockera. Ponadto w internecie można znaleźć kilka metod na utworzenie prywatnych repozytoriów, np. za pomocą już gotowych… kontenerów.

Czy Docker jest stabilny?

W chwili pisania tego artykułu na blogu Dockera pojawiła się informacja, że wersja 0.11 jest oficjalnym Release Candidate dla wersji 1.0, przy okazji wprowadzając jeszcze kilka istotnych zmian. Obecnie wiele firm korzysta już z Dockera w środowiskach produkcyjnych, jednak dla tych, którzy wolą poczekać – wersja stabilna pojawi się niebawem. Minie kwartał lub dwa, zanim wszystkie choroby wieku dziecięcego wersji stabilnej zostaną zażegnane. Tyle czasu wystarczy na wdrożenie i przetestowanie nawet w sporych instytucjach.

Jak zacząć?
Jestem kontrybutorem w Fedora Project. Moje dokonania są widoczne m.in. w projekcie Fedora-Dockerfiles dostępnym na GitHubie: https://github.com/fedora-cloud/Fedora-Dockerfiles. Jest to doskonałe miejsce do rozpoczęcia prac z Dockerem, gdyż zawiera bogatą liczbę przykładów, w jaki sposób można zbudować i uruchomić proste kontenery. Przykład opisany w artykule został wzięty właśnie z tego projektu. Oficjalna dokumentacja dostępna jest na docs.docker.io.

Podstawowe technologie wirtualizacji:
Wirtualizacja pozwala osiągnąć lepszy poziom użycia zasobów sprzętowych (czyli sprzęt jest zajęty przez większą część czasu, zamiast stać bezczynnie). Są trzy technologie wirtualizacji:

Wirtualizacja pełna – w której to wirtualizowany system (gość) utrzymany jest w przekonaniu, że działa na prawdziwym, dedykowanym dla siebie sprzęcie. W ten sposób, np. można uruchomić gościa z Windowsem na hoście obsługującym Linuksa. W tym wydaniu potrzebne jest wsparcie sprzętowe wirtualizacji, gdyż pełna jej postać generuje największy narzut hipernadzorcy (czyli większe w stosunku do pozostałych technologii straty zasobów). W tym wariancie również mamy najlepszą separację między systemami gości (co teoretycznie też implikuje największy poziom bezpieczeństwa – choć to kwestia dyskusyjna, bo zależna od hipernadzorcy).
Parawirtualizacja – korzysta z hipernadzorcy poziomu 1, czyli działa bezpośrednio na poziomie sprzętu; systemy gościa pracują na poziomie o jeden wyższym niż hipernadzorca. Systemy gościa wiedzą, że są zwirtualizowane – by działać, musza mieć specjalnie zmodyfikowane jądro, tak, aby wysyłać wywołania systemowe nie do sprzętu, a do interfejsu pomiędzy hipernadzorcą a systemem gościa. Zaletami tego rozwiązania jest wyższa wydajność niż w przypadku wirtualizacji pełnej (gdyż brak tu emulacji po stronie hipernadzorcy) i w dalszym ciągu dobry stopień izolacji pomiędzy systemami gościa a hipernadzorcą. Odnośnie do bezpieczeństwa – podczas ostatniej edycji LinuxCon/CloudOpen (2013) George Dunlap z Citrixa przedstawił zagrożenia hipernadzorcy poziomu 1 – Xen (prezentacja jest dostępna na Youtube), wskazując, jak wielkim problemem bezpieczeństwa może być instalacja środowiska wirtualizacyjnego przy użyciu ustawień domyślnych.

Wirtualizacja na poziomie OS – technologia polega na separacji kolejnych środowisk wirtualnych w ramach jednego systemu sprawującego kontrolę nad nimi; ten rodzaj wirtualizacji generuje najmniejsze straty zasobów – dlatego też nazywany jest „lekką”. Takie wyodrębnione środowiska nazywamy kontenerami (lub klatkami). Z punktu widzenia użytkownika końcowego utworzona w ten sposób przestrzeń wygląda jak zupełnie wydzielony system; jednak w rzeczywistości jest to jedynie specjalnie spreparowany widok oryginalnego systemu z ograniczonym dostępem do zasobów hosta. Przykładami mogą być takie rozwiązania, jak chroot, Linux-Vserver, lmctfy, LXC, OpenVZ czy Solaris Containers.

Maciej Lasyk,
administrator systemów w Lumesse

BRAK KOMENTARZY

ZOSTAW ODPOWIEDŹ