S%cc%81rodowisko deweloperskie wykorzystujac vagranta i dockera

Przygotowanie środowiska pracy dla osoby dołączającej do projektu programistycznego wiąże się z zainstalowaniem i konfiguracją wielu narzędzi niezbędnych do tworzenia oprogramowania. W przypadku javowca jest to z całą pewnością instalacja Java Development Kit, a także zintegrowanego środowiska programistycznego IDE np. Eclipse. Do tego dochodzą narzędzia pomocnicze takie jak Maven, Git, a także serwer aplikacji i baza danych. Czas potrzebny do instalacji tych narzędzi, a następnie uruchomienia projektu może wynieść (w zależności od stopnia skomplikowania projektu) od kilku godzin do nawet kilku dni. Czy można to zautomatyzować?

Korzyści z tego wynikające byłyby ewidentne:

  • szybsze wprowadzenie do projektu
  • spójne środowisko u wszystkich developerów
  • łatwa zmiana konfiguracji i zainstalowanych narzędzi

Owszem, istnieją narzędzia do automatyzacji tego procesu - to Vagrant i Docker. Są uniwersalne i pozwalają rozwiązać również wiele innych problemów, ale tu skupimy się tylko na przygotowaniu środowiska deweloperskiego.

Vagrant

Vagrant jest narzędziem umożliwiającym tworzenie maszyn wirtualnych na podstawie pliku konfiguracyjnego. Taki VM może zostać uruchomiony na środowisku VirtualBox, VMWare, a nawet w chmurze (np. poprzez Amazon EC2). Warto przy tym wspomnieć, że taka konfiguracja będzie mogła zostać uruchomiona niezależnie od systemu operacyjnego na którym pracuje obecnie deweloper. Vagrant obsługuje najbardziej popularne środowiska pracy takie jak Windows, Linux czy OS X.

Docker

Docker jest narzędziem podobnym do Vagrant, jednak w rzeczywistości oba te narzędzia mogą tworzyć komplementarną całość. Docker daje nam możliwość tworzenia tzw. kontenerów. Kontener pracuje w oparciu o dany system operacyjny i jest izolowany od innych procesów uruchomionych w danej chwili w natywnym systemie operacyjnym.

Na pierwszy rzut oka mogłoby się zatem wydawać, że kontener jest synonimem maszyny wirtualnej. To jednak nie jest prawdą. Maszyna wirtualna emuluje wiele aspektów maszyny fizycznej tzn. zarówno sam system operacyjny jak i cały hardware potrzebny do jego uruchomienia. To powoduje, że wymagania dotyczące zasobów procesora i pamięci operacyjnej są zazwyczaj w przypadku maszyny wirtualnej dość wysokie. Kontener zaś mimo, iż stanowi odrębną całość to korzysta z zasobów udostępnianych w ramach natywnego systemu operacyjnego. Dzięki temu jego wymagania dotyczące zasobów są o wiele niższe niż w przypadku maszyny wirtualnej.

Kontener ma również tę zaletę, iż jest przenaszalny tzn. łatwo można sobie wyobrazić użycie tego samego kontenera w środowisku deweloperskim, następnie testowym, a docelowo w systemie produkcyjnym. Niesie to ze sobą również kolejną zaletę tj. spójność konfiguracji kontenerów na wszystkich środowiskach uruchomieniowych.

Kto chociaż raz brał udział w większym przedsięwzięciu programistycznym doskonale zdaję sobie sprawę, iż różnice pomiędzy środowiskiem programistycznym, a testowym i produkcyjnym potrafią doprowadzić do bardzo trudnych w zdiagnozowaniu i naprawie błędów.

 

Co zrobimy?

W tym artykule przedstawię proces przygotowania maszyny deweloperskiej w oparciu o Xubuntu-Desktop. Posłuży ona do rozwoju aplikacji opartej na JEE. Użyję Javę 8 i serwer Wildfly (jako kontener Dockera). Zakładam, że IDE będzie Eclipse z pluginem JBoss Tools, dzięki któremu znacznie uprości się procedura deploymentu. Aplikację, której źródła są na Githubie można zbudować przy pomocy Mavena.

Komponenty maszyny deweloperskiej.

Architektura komponentów maszyny deweloperskiej.

 

Cały proces podzielimy na kilka kroków:

  1. Instalacja Vagranta i VirtualBoxa
  2. Konfiguracja maszyny wirtualnej - Vagrantfile
  3. Konfiguracja systemu operacyjnego i narzędzi
  4. Konfiguracja Dockera
  5. Klonowanie kodów źródłowych z serwera GitHub
  6. Pierwsze uruchomienie maszyny
  7. Uruchomienie przykładowego projektu

 

Instalacja Vagranta i VirtualBoxa

System na którym uruchomiona zostanie maszyna wirtualna ze środowiskiem deweloperskim musi posiadać zainstalowane dwa narzędzia tj. Vagrant i VirtualBox. Tak jak wspomniałem, VirtualBox nie jest jedynym wspieranym rozwiązaniem, ale jest popularny i bezpłatny. Dlatego go wybrałem.

Zarówno Vagrant jak i VirtualBox dostępne są dla systemów Windows, Linux oraz OS X. Ściągnijmy je z:

https://www.vagrantup.com/downloads.html

https://www.virtualbox.org/wiki/Downloads

Vagrant domyślnie posiada CLI (command line interface). Aby przekonać się czy narzędzie to zostało poprawnie zainstalowane wystarczy wydać polecenie:

> vagrant --version

Vagrant 1.9.3

 

W przypadku VirtualBoxa mamy do dyspozycji graficzny interfejs użytkownika. 

Konfiguracja  maszyny wirtualnej - Vagrantfile

Plik `Vagrantfile` opisuje konfigurację maszyny. Składnia oparta jest na języku Ruby, jednak jest ona na tyle prosta, że nie trzeba znać tego języka. Najpierw wylistuję pełną konfigurację, a potem wyjaśnię dlaczego tak wygląda.

 

Zacznijmy od góry:

VAGRANT_API_VERSION  = "2"

Vagrant.configure(VAGRANT_API_VERSION) do |config|

  //właściwa konfiguracja

end

Używamy drugiej, bieżącej wersji API Vagranta. Określamy ją wprost, by uniknąć problemów z kompatybilnością.

 

config.vm.box = "bento/ubuntu-16.10"

Określa obraz bazowy. W tym wypadku to serwerowa wersja Ubuntu bez żadnego środowiska graficznego, które doinstalujemy później.

 

config.vm.provider "virtualbox" do |vb|
  vb.name = "Developer machine"
  vb.cpus = 4
  vb.memory = 8192
  vb.customize ["modifyvm", :id, "--vram", "128"]
  vb.customize ["modifyvm", :id, "--accelerate3d", "on"]  
  vb.customize ["modifyvm", :id, "--clipboard", "bidirectional"]  
  vb.customize ["storageattach", :id, "--storagectl", "SATA Controller", "--port", "0", 
     "--device", "0", "--nonrotational", "on"]
  vb.gui = true
end

To konfiguracja sprzętowa maszyny. W zależności od zasobów i innych programów uruchamianych na maszynie wirtualnej konfiguracja może się zmieniać, ale prosto można ją modyfikować. `Provider` określa nazwę narzędzia, które posłuży do uruchomienia maszyny wirtualnej. W tym przypadku będzie to VirtualBox. Kolejne elementy specyfikują:

name -  nazwę maszyny.
cpus – liczbę rdzeni procesora, które mogą być wykorzystane przez maszynę.
memory – liczbę megabajtów pamięci RAM udostępnioną dla maszyny.
vram – liczbę megabajtów pamięci RAM dostępną dla karty graficznej.
clipboard – dwukierunkowy schowek umożliwiający kopiowanie tekstu z/do maszyny wirtualnej za pomocą Ctrl+C, Ctrl+V.
storageattach – ten parametr wskazuje, że stosowany dysk jest dyskiem SSD co może przyspieszyć działanie maszyny wirtualnej.
gui – w przypadku, gdy ten parametr przyjmie wartość true to VirtualBox spróbuje uruchomić maszynę w trybie graficznym z wykorzystaniem środowiska graficznego (oczywiście musi ono zostać zainstalowane w systemie operacyjnym jeśli nie jest dostępne domyślnie w obrazie z którego korzystamy).

 

config.vm.provision "file",
  source: "eclipse.desktop",
  destination: "/home/vagrant/Desktop/eclipse.desktop"

config.vm.provision "file",
  source: "Dockerfile4Wildfly",
  destination: "/home/vagrant/Dockerfile4Wildfly"

config.vm.provision "file",
  source: "docker-wildfly.service",
  destination: "/home/vagrant/docker-wildfly.service"

Opcja konfiguracyjna provision pozwala skopiować pliki z dysku lokalnego do wskazanej lokalizacji na tworzonej maszynie wirtualnej. Za pomocą tej opcji można również wywołać dowolny skrypt/program na maszynie wirtualnej np. instalujący niezbędne oprogramowanie i inicjalizujący właściwą konfigurację zainstalowanego systemu.

 

Powyżej 3 opcje provision typu „file”, które kopiują odpowiednio pliki eclipse.desktop, Docker4Wildfly oraz docker-wildfly.service we wskazane lokalizacje na maszynie wirtualnej. Pliki te są niezbędne do dalszej konfiguracji maszyny wirtualnej.

config.vm.provision "shell", path: "init.sh"

Drugi typ opcji provision to „shell”. Stosuje się go w przypadku potrzeby wywołania kilku poleceń shella zainstalowanego systemu operacyjnego.

Podajmy opcję path, która wskazuje na skrypt `init.sh`. Skrypt ten opisany jest w kolejnym rozdziale.

 

Konfiguracja systemu operacyjnego i narzędzi

Konfiguracja maszyny wirtualnej to nie tylko instalacja niezbędnego oprogramowania, ale także dodanie do systemu operacyjnego komponentów, które umożliwią efektywną i sprawną pracę dewelopera. To właśnie init.sh (ze względu na długość tego pliku tylko go linkuję, sama struktura jest dość prosta):

 

Jest tu kilka sekcji. W pierwszej: 

apt-get update -y
apt-get install -y xubuntu-desktop
apt-get install -y ubuntu-session
apt-get install -y virtualbox-guest-dkms virtualbox-guest-utils virtualbox-guest-x11 linux-headers-$(uname -r)

To instalacja środowiska graficznego a także tzw. GuestAdditions do VirtualBoxa, które zapewnią wspólny schowek czy dostowanie rozdzielczości. Flaga –y  w powyższych poleceniach wymusza instalację w trybie nieinteraktywnym.

 

Następnie instalujemy JDK:

add-apt-repository -y ppa:webupd8team/java
echo oracle-java8-installer shared/accepted-oracle-license-v1-1 select true | /usr/bin/debconf-set-selections
apt-get install -y oracle-java8-installer

Pierwsze polecenie powoduje dodanie do systemu danych repozytorium skąd pobrana zostanie wersja instalacyjna JDK. Drugie polecenie służy do automatycznego zatwierdzenia licencji, co jest konieczne w przypadku instalacji Javy pochodzącej od Oracle.

 

Pora na Eclipse, ściągamy i rozpakowujemy:

wget -q http://ftp-stud.fht-esslingen.de/pub/Mirrors/eclipse/technology/ epp/downloads/release/neon/3/eclipse-jee-neon-3-linux-gtk-x86_64.tar.gz
tar -xvf /home/vagrant/eclipse-jee-neon-3-linux-gtk-x86_64.tar.gz--directory /opt

 

W Vagrantfile dodaliśmy eclipse.desktop. Jest to link do aplikacji Eclipse, który umieszczony na pulpicie, co pozwala na łatwe uruchomienie aplikacji. Trzeba nadać mu teraz prawa do wykonywania.

chmod +x /home/vagrant/Desktop/eclipse.desktop

 

Instalujemy JBoss Tools, o którym wspominałem na początku:

/opt/eclipse/eclipse -clean -purgeHistory -application org.eclipse.equinox.p2.director -noSplash -repository http://download.jboss.org/jbosstools/neon/stable/updates/ -installIUs org.jboss.ide.eclipse.as.feature.feature.group

 

Czas na instalację innych komponentów.

Instalacja Mavena

apt-get install -y maven

 

Instalacja Dockera

curl -fsSL https://get.docker.com/ | sh

 

Dodanie użytkownika vagrant do grupy docker, co umożliwi wykonywanie poleceń Dockera bez uprawnień administracyjnych.

gpasswd -a vagrant docker

 

Przygotowanie katalogu dla deploymentu aplikacji. Katalog ten będzie współdzielony pomiędzy maszyną wirtualną, a kontenerem Wildfly. Dzięki temu archiwa ear/war umieszczane w tym folderze będą automatycznie deploy’owane na serwer aplikacji Wildfly. Dodatkowo za pomocą polecenia chown jako właściciel folderu ustalony został użytkownik vagrant.

mkdir /home/vagrant/deployments
chown vagrant:vagrant deployments

 

Instalacja prostego edytora tekstu – Notepadqq. Przyda się do edycji plików konfiguracyjnych.

apt-get install -y notepadqq

 

Konfiguracja Dockera

Odbywa się ona w dwóch krokach

1. Budowa kontenera

Najpierw musimy zbudować kontener Wildfly - z pliku Dockerfile4Wildfly i oznaczyć go tagiem wildfly:

docker build --tag=wildfly --file=Dockerfile4Wildfly .

Polecenie FROM pozwala określić obraz bazowy na podstawie którego zostanie utworzony kontener. Jest to gotowy obraz z oficjalnego repo na DockerHub.

Za pomocą komendy USER określimy nazwę użytkownika, który będzie uruchamiał pozostałe polecenia.

Polecenie RUN pozwala uruchomić dowolną komendę jednokrotnie w trakcie budowania kontenera. Wykorzystamy tę możliwość do utworzenia użytkownika administracyjnego serwera Wildfly, który będzie miał m.in. dostęp do konsoli administracyjnej serwera.

Polecenie CMD jest podobne do RUN jednak różni się tym, iż uruchamia komendę przy każdym starcie kontenera. W tym przypadku uruchomimy Wildfly.

2. Automatyczne uruchamianie kontenera Wildfly przy starcie maszyny wirtualnej.

By to zapewnić stworzymy serwis systemowy (zdefiniowany w docker-wildfly.service), który będzie uruchamiany przy każdym starcie systemu.

Sekcja Unit określa opis serwisu oraz wymagania dotyczące zależności od innych serwisów i kolejności wywołania serwisów. W tym przypadku jest oczywiste, że do uruchomienia kontenera potrzebujemy aktywnej usługi Docker.

[Unit]
Description=Wildfly container
Requires=docker.service
After=docker.service

W kolejnej sekcji tzn. Service określamy polecenie wykonywane w ramach uruchomienia serwisu (ExecStart) oraz opcję automatycznego restartu usługi (Restart)  np. w wypadku kiedy usługa nie uruchomi się poprawnie lub nastąpi błąd w trakcie jej działania. W naszym przypadku uruchamiamy kontener wildfly, dodatkowo parametryzując go poprzez flagi:

–p – co powoduje przekierowanie portów tzn. porty 8080 i 9990 kontenera będą dostępne na maszynie wirtualnej.

–v – to stworzenie tzw. woluminu czyli lokalizacji współdzielonej pomiędzy kontenerem i maszyną wirtualną. W opcji tej podajemy dwie lokalizacje rozdzielone dwukropkiem. Pierwsza lokalizacja wskazuje na folder na maszynie wirtualnej, druga to folder w kontenerze. Dzięki temu archiwa umieszczane w katalogu /home/vagrant/deployments będą automatycznie umieszczane w katalogu /opt/jboss/wildfly/standalone, w następstwie czego nastąpi ich deployment na serwer Wildfly.

[Service]
Restart=always
ExecStart=/usr/bin/docker run -p 8080:8080 -p 9990:9990 -v /home/vagrant/deployments:/opt/jboss/wildfly/standalone /deployments/:rw wildfly

 

Plik z definicją serwisu jest kopiowany w czasie uruchamiania skryptu init.sh. Zaraz potem jest on dodawany jako automatycznie uruchamiający się przy starcie.

cp /home/vagrant/docker-wildfly.service /etc/systemd/system/docker-wildfly.service
systemctl enable docker-wildfly.service

 

Klonowanie kodów źródłowych z serwera GitHub

Chcielibyśmy, aby po uruchomieniu maszyny wirtualnej po raz pierwszy kod projektu został automatycznie zaciągnięty z GitHuba do określonego folderu. Możemy to zrealizować w następujący sposób (nadal w pliku init.sh):

export PROJECT_FOLDER="javaee7-simple-sample"
export GIT_PROJECT_URL="https://github.com/javaee-samples/javaee7-simple-sample"

git clone $GIT_PROJECT_URL $PROJECT_FOLDER
chown -R vagrant:vagrant $PROJECT_FOLDER

Ważna jest komenda chown, która zmienia właściciela folderu projektowego na użytkownika vagrant (jest to konieczne, gdyż domyślnie polecenia w Vagrant wykonywane są przez roota).

 

Pierwsze uruchomienie maszyny

Uruchomienie maszyny wirtualnej dokonujemy w lokalizacji gdzie umieszczony jest plik Vagrantfile za pomocą polecenia vagrant up. Spowoduje to pobranie odpowiedniego obrazu bazowego oraz wykonanie wszystkich innych czynności zdefiniowanych w pliku Vagrantfile.

Terminal po uruchomieniu vagrant up
Tak wygląda terminal po uruchomieniu komendy vagrant up

Uwaga! Pierwsze uruchomienie ze względu na konieczność pobrania obrazu bazowego oraz instalacji wszystkich narzędzi może trwać nawet kilkadziesiąt minut.

Po załadowaniu obrazu oraz instalacji i konfiguracji wszystkich narzędzi uruchomione zostanie okno programu VirtualBox. Zamiast spodziewanego środowiska graficznego pojawi się na nim terminal tekstowy systemu Ubuntu. Jest to związane z tym, iż środowisko graficzne zostało zainstalowane dopiero w trakcie uruchamiania maszyny wirtualnej.

Aby wyeliminować ten problem należy zatrzymać maszynę wirtualną poleceniem vagrant halt, a następnie ponownie wydać polecenie vagrant up. Wówczas powinniśmy zobaczyć ekran logowania. W przypadku problemów możemy uruchomić skrypty instalacyjne ponownie wydając komendę vagrant up --provision. Obraz użyty do stworzenia tej maszyny wirtualnej posiada domyślnie użytkownika vagrant z hasłem vagrant.

 

Uruchomienie przykładowego projektu

W celu uruchomienia przykładowego projektu (Java Enterprise Edition 7 Simple Sample) musimy jeszcze wykonać trzy kroki:

  • Zaimportować projekt do Eclipse
  • Dodać serwer Wildfly w zakładce Servers w Eclipse
  • Zdeployować aplikację na serwer Wildfly

Import projektu do Eclipse

1. Wybieramy opcję File/Import project, a następnie Existing Maven Projects

2. Określamy lokalizację projektu tj. wybieramy folder /home/vagrant/javaee7-simple-sample

 

Konfiguracja serwera Wildfly w Eclipse

1. Na zakładce Servers klikamy link „No servers are available. Click this link to create a new server”. Spowoduje to pojawienie się okna jak na rys. nr 9. Należy wybrać tutaj serwer aplikacji Wildfly w wersji 10.x. Inne parametry pozostawiamy bez zmian.

2. W kolejnym kroku określamy parametry dostępu do serwera, tu przyda się poniższy zrzut ekranu:

Konfiguracja serwera Wildfly

 

Ponieważ deployment będzie odbywał się poprzez konsolę administracyjną Wildfly to postępujemy w następujący sposób:

a) określamy serwer jako zdalny (Remote)

b) specyfikujemy, że kontrola serwera odbywać będzie się poprzez konsolę administracyjną (Management Operations)

c) zaznaczamy opcję, iż cykl życia serwera zarządzany jest poprzez zewnętrzne narzędzie (Server lifecycle is externally managed)

d) odznaczamy opcję przyporządkowania runtime’u do serwera (Assign a runtime to the server)

3. W kolejnym kroku pozostawiamy wszystkie domyślne opcje bez zmian.

4. Ostatni element konfiguracji to zmiana hasła do konsoli administracyjnej serwera Wildfly. Hasło zostało ustalone na secret# w ustawieniach, które wprowadzaliśmy w konfiguracji Dockera. Tu również przyda się obrazek poglądowy:

Zmiana hasła do konsoli administracyjnej Wildfly

 

D​eployment aplikacji na serwer Wildfly

1. Klikamy prawym na projekcie i wybieramy opcję Run As -> Run on Server. Pojawi się wówczas okno pokazane niżej:

2.  Uruchomienie aplikacji skutkuję automatycznym deploymentem i otwarciem domyślnej przeglądarki z uruchomioną aplikacją. Prezentuje się ona bardzo okazale:

Uruchomiona aplikacja

 

Podsumowanie

Powyższy przykład pokazał, że narzędzia Vagrant i Docker świetnie nadają się do stworzenia środowiska deweloperskiego. Przykładowa konfiguracja dotyczyła środowiska dla programisty Java Enterprise Edition jednak można przygotować podobną konfigurację dla innej technologii.

Wszystkie pliki potrzebne do uruchomienia maszyny deweloperskiej zostały umieszczone w publicznym repozytorium dostępnym na serwerze GitHub pod poniższym adresem:

https://github.com/wpieprzyca/vagrant-docker-example.git

Autor: Wojciech Pieprzyca, Java Team Leader, FIS-SST