Marcin Lewandowski
Marcin Lewandowski
Programista PHP ( Symfony ), blogger, trener oraz miłośnik kawy. Na co dzień pracuję z Symfony, RabbitMQ, ElasticSearch, Node.js, Redis, Docker, MySQL.

Zarządzanie wieloma maszynami w Vagrantfile

Zarządzanie wieloma maszynami w Vagrantfile

Vagrantfile jest konfiguracją wirtualnej maszyny, która ma zostać zbudowana przez Vagranta. Jednak do tej pory mieliśmy do czynienia tylko z jedną wirtualną maszyną, a możliwe jest skonfigurowanie wielu maszyn w ramach jednego pliku Vagrantfile.

Jest to o tyle wygodne, że umożliwia nam postawienie np. serwera www niezależnie od bazy danych czy też innych usług, które później możemy bardzo łatwo wymieniać.

Równoważenie obciążenia (load balancing)

Dla naszych potrzeb postawimy 3 serwery www (Nginx) z czego jeden będzie tylko odpowiedzialny za równoważenie obciążenia pomiędzy dwoma pozostałymi.

Serwer równoważący obciążenie

Serwer ten nie będzie niczym wyszukanym, zastosujemy tutaj najprostsze rozwiązanie jakie daje nam Nginx. Definiujemy listę serwerów pomiędzy, które ma być rozdzielany ruch. Definicja ta powinna znaleźć się w pliku konfiguracyjnym server /etc/nginx/sites-enabled/default

upstream myapp {
   server 192.168.10.101;
   server 192.168.10.102;
}

Następnie dodajemy w definicji serwera server w proxy_pass wpis z naszą nazwą myapp. Całość pliku będzie wtedy wyglądała następująco:

upstream myapp {
    server 192.168.10.101;
    server 192.168.10.102;
}
 
server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;
     
    root /usr/share/nginx/html;
    index index.html index.htm;
     
    server_name localhost;
     
    location / {
        proxy_pass //myapp;
    }
}

W ten oto sposób Nginx będzie przekierowywał ruch raz na jedną raz na drugą maszynę. Zapisujemy go pod nazwą nginx-load-balancing w katalogu vagrant_files i będziemy kopiować do wirtualnej maszyny jako nowo obowiązujący plik konfiguracyjny.

Czas na tej podstawie przygotować plik Vagrantfile, tworzący odpowiednią wirtualną maszynę.

Vagrant.configure("2") do |config|
 
  config.vm.define "lb" do |lb|
    lb.vm.box = "ubuntu/xenial64"
    lb.vm.network "private_network", ip: "192.168.10.100"
     
    lb.vm.provision "shell", inline: <<-SHELL 
      sudo apt-get update
      sudo apt-get install -y nginx
      sudo service nginx stop
      sudo cp /vagrant/vagrant_files/nginx-load-balancing /etc/nginx/sites-enabled/default
      sudo service nginx start
      sudo echo "Load Balancing" >> /var/www/html/index.html
    SHELL
  end
end

Jeśli czytaliście moje poprzednie wpisy dotyczące Vagrant-a, to jedyna nowa rzecz to config.vm.define. Jest to zapis pozwalający na zdefiniowanie wielu maszyn wirtualnych w ramach jednego pliku Vagrantfile.

config.vm.define "lb" do |lb|

Konstrukcja jest banalna, zaczynamy od odwołania się do nazwy naszej konfiguracji, czyli w tym przypadku config. Następnie stała vm.define oraz podajemy własną nazwę pod którą będziemy definiować wirtualną maszynę. W moim przypadku "lb" i przypisujemy ją do nazwy |lb|. Pozostała część już powinna być wam znana 😉

Uruchamiamy tworzenie maszyny poleceniem vagrant up i po wejściu na stronę zobaczymy taki komunikat:

Wynika to z faktu nie podsiadania żadnego serwera www, który obsłużył by nasz ruch.

Serwer www

Skoro mamy już maszynę, która balansuje nasz ruch to stwórzmy serwery obsługujące ten ruch. Będziemy bazować na tym co dodaliśmy w poprzedniej maszynie, gdyż jedyna różnica w ich przypadku to strona która będzie wyświetlana.

Zaczynamy od przygotowania pliku dla Nginx-a, który będzie wyświetlał tylko prostą statyczną stronę z nazwą maszyny wirtualnej. Dzięki temu możliwe będzie obserwowanie przełączania ruchu pomiędzy maszynami.

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;
 
    root /usr/share/nginx/html;
    index index.html index.htm;
 
    server_name localhost;
 
    location / {
        try_files $uri $uri/ =404;
    }
}

Zapiszemy tę konfigurację w pliku nginx-web w katalogu vagrant_files. Teraz dodamy nową definicję serwera www do pliku Vagrantfile.

config.vm.define "web1" do |web1|
 
  web1.vm.box = "ubuntu/xenial64"
  web1.vm.network "private_network", ip: "192.168.10.101"
 
  web1.vm.provision "shell", inline: <<-SHELL 
     
    sudo apt-get update
    sudo apt-get install -y nginx
    sudo service nginx stop
    sudo cp /vagrant/vagrant_files/nginx-web /etc/nginx/sites-enabled/default
    sudo service nginx start
    sudo echo "WEB 1" >> /var/www/html/index.html
 
  SHELL
 
end

Niszczymy poprzednio zdefiniowaną maszynę poleceniem vagrant destroy -f, a następnie uruchamiamy tworzenie maszyn zdefiniowanych w zmodyfikowanym pliku Vagrantfile poleceniem vagrant up. Po stworzeniu maszyn i przejściu na stronę pod adresem load balancer-a http://192.168.10.100 powinniśmy zobaczyć stronę:

Ostatni krok to stworzenie jeszcze jednej wirtualnej maszyny wyświetlającej stronę www. Kopiujemy poprzednią definicję zmieniając jedynie nazwę definicji, adres IP oraz wyświetlany plik html.

config.vm.define "web2" do |web2|
 
  web2.vm.box = "ubuntu/xenial64"
  web2.vm.network "private_network", ip: "192.168.10.102"
 
  web2.vm.provision "shell", inline: <<-SHELL 
 
    sudo apt-get update
    sudo apt-get install -y nginx
    sudo service nginx stop
    sudo cp /vagrant/vagrant_files/nginx-web /etc/nginx/sites-enabled/default
    sudo service nginx start
    sudo echo "WEB 2" >> /var/www/html/index.html
 
  SHELL
 
end

I ponownie niszczymy maszynę poleceniem vagrant destroy -f i ją odbudowujemy vagrant up. Po chwili, gdy wszystkie wirtualne maszyny zostaną zbudowane wchodzimy na load balancer dostępny pod adresem http://192.168.10.100. Teraz każde odświeżenie strony powinno powodować wyświetlenie nazwy innej wirtualnej maszyny która obsłużyła ruch.

W ten oto prosty sposób otrzymaliśmy prosty system równoważący obciążenie. Nic nie szkodzi na przeszkodzie, aby dodać dodatkowy serwer, który odciąży dwa pozostałe.

Optymalizacja skryptów

Definicje serwerów www zawierają elementy powtarzalne, takim elementem jest instalacja oprogramowania na wirtualnych maszynach. Zobaczmy jak możemy to zoptymalizować.

Zacznijmy od przeniesienia kodu do pliku install_web.sh w katalogu vagrant_files. Będzie on wyglądał następująco:

sudo apt-get update
sudo apt-get install -y nginx
sudo service nginx stop
sudo cp /vagrant/vagrant_files/nginx-web /etc/nginx/sites-enabled/default
sudo service nginx start
sudo echo "WEB 1" &gt;&gt; /var/www/html/index.html

Jednaj jeden element nie jest tutaj wspólny, mianowicie w tworzonych plikach html wpisana jest inna nazwa serwera. To nic bowiem możemy przekazywać parametry do plików definiowanych w provision typu shell. Oto zmieniony wpis dla pierwszego serwera:

web1.vm.provision  :shell do |shell| 
  shell.args = "WEB1" 
  shell.path = "vagrant_files/install_web.sh" 
end

Teraz zamiast zapisu w linii, mamy blok konfiguracji o nazwie shell. W bloku tym została ustawiona lista parametrów (args) przekazywanych do pliku, gdzie przekazywany jest tylko jeden argument o treści WEB1. Z naszej nazwy zniknęła spacja, gdyż spacją rozdzielane są poszczególne argumenty.

Kolejny parametr to path czyli ścieżka do pliku, który ma zostać uruchomiony w powłoce i do którego przekazać argumenty.

W pliku odbieramy argumenty, które są dostępne pod znakiem dolara i numerem argumentu. Dla naszej definicji argument będzie dostępny pod $1. W związku z czym zmieniamy jego definicję na:

sudo apt-get update
sudo apt-get install -y nginx
sudo service nginx stop
sudo cp /vagrant/vagrant_files/nginx-web /etc/nginx/sites-enabled/default
sudo service nginx start
sudo echo $1 >> /var/www/html/index.html

Po czym nanosimy analogiczną zmianę w konfiguracji drugiego serwera www.

web2.vm.provision  :shell do |shell| 
  shell.args = "WEB2" 
  shell.path = "vagrant_files/install_web.sh" 
end

Zapisujemy zmiany, niszczymy maszyny vagrant destroy -f i odbudowujemy je na nowo vagrant up. Po odbudowie maszyn nie powinniśmy zobaczyć żadnej różnicy, oczywiście poza krótszym plikiem Vagrantfile 😉

Podsumowanie

Przedstawione rozwiązanie pokazuje jedynie jak możemy w prosty sposób definiować wiele maszyn, oraz jak na tej bazie prosto wykonać load balancing wykorzystując Nginx-a.

Wiadomą sprawą jest że kod aplikacji na maszynach powinien być identyczny, tutaj zaś jego zróżnicowanie miało jedynie na celu pokazać działanie rozwiązania.

Dla leniwych pełna konfiguracja dostępna jest na GitHub-ie 😉