Docker, czyli trochę inna wirtualizacja na przykładzie centrali telefonicznej
Cześć,
obserwując nowinki technologiczne można było zauważyć iż jakiś czas temu miał miejsce boom na oprogramowanie które nosiło nazwę Vagrant. Ogólnie chodziło o to że w łatwy sposób mogłeś stworzyć sobie maszynę wirtualną z wybranym przez siebie systemem, łatwo nią zarządzać, dbać o aktualizacje czy tworzyć prosty provisioning. Rozwój wirtualizacji pokazał jednak iż można zrobić to lżej – nie emulować całej maszyny a tylko jakby jej środowisko uruchomieniowe – czyli system. I w oparciu o taką ideę powstał Docker.
Docker jest lekkim środowiskiem wirtualizacyjnym, stworzonym z myślą o szybkim i prostym wdrażaniu oraz publikacji zmian. Kontenery dockerowe można uruchomić wszędzie tam gdzie udało się zainstalować Dockera i we wszystkich tych miejscach te kontenery będą działały tak samo.
Kontener – to, jak wcześniej wspomniałem – środowisko uruchomieniowe dla Twoich aplikacji. Mamy do wyboru wiele obrazów takich kontenerów, które w prosty sposób pozwalają na uruchomienie serwera HTTP, bazy danych czy czegokolwiek innego. Takie „prymitywne” obrazy najczęściej służą jako postawa do pisania własnych kontenerów.
Stworzymy dziś dwa kontenery w oparciu o mój poprzedni wpis – o własnej centrali telefonicznej – ponieważ jeśli go czytałeś – to pewnie zauważyłeś że sporo pracy musi wykonać czytelnik aby uruchomić centralę na swoim komputerze. Dzięki Dockerowi uprościmy to :)
Stwórzmy więc taką hierarchię folderów, gdzie rootem jest Twój katalog domowy:
- ~/ - docker - docker_asterisk - docker_postgres
i przejdźmy do folderu docker_postgres w którym stworzymy kontener postgresa w wersji 9.2.
Chcemy teraz aby nasz kontener z postgresem miał już stworzony schemat bazy danych, odpowiedniej dla Asteriska – w spisie poprzednim korzystaliśmy z gotowej migracji i aplikacji alembic – tutaj zrobimy to samo, więc na początek dostarczmy do tego folderu plik config.ini:
# A generic, single database configuration. [alembic] # path to migration scripts script_location = config # template used to generate migration files # file_template = %%(rev)s_%%(slug)s # max length of characters to apply to the # "slug" field #truncate_slug_length = 40 # set to 'true' to run the environment during # the 'revision' command, regardless of autogenerate # revision_environment = false #sqlalchemy.url = driver://user:pass@localhost/dbname #sqlalchemy.url = postgresql://user:pass@localhost/asterisk #sqlalchemy.url = mysql://user:pass@localhost/asterisk sqlalchemy.url = postgresql://matt:test@localhost/asterisk [logger_root] level = WARN handlers = console qualname = [logger_sqlalchemy] level = WARN handlers = qualname = sqlalchemy.engine [logger_alembic] level = INFO handlers = qualname = alembic [handler_console] class = StreamHandler args = (sys.stderr,) level = NOTSET formatter = generic [formatter_generic] format = %(levelname)-5.5s [%(name)s] %(message)s datefmt = %H:%M:%S
poza tym będziemy potrzebować skryptów które zmienią nam plik pg_hba.conf oraz utworzą odpowiednich użytkowników. Zacznijmy od tego pierwszego i nazwijmy go setupConnection.sh:
sed -i '/host all all 127.0.0.1\/32 trust/a host all all 172.17.0.1\/16 md5' /var/lib/postgresql/data/pg_hba.conf
No i drugi o nazwie setup.sh:
psql -U postgres -c "CREATE USER matt WITH PASSWORD 'test';" psql -U postgres -c "CREATE DATABASE asterisk;" psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE asterisk TO matt;" cd ../asterisk-13.13.1/contrib/ast-db-manage/ alembic -c config.ini upgrade head
Aby stworzyć kontener potrzebny jest przepis jak zbudować obraz z którego później będzie można tworzyć nieograniczoną liczbę kontenerów. Przepis ten umieszczamy w pliku Dockerfile i dla naszego postgresa będzie on wyglądać tak:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | FROM postgres:9.2 ADD setupConnection.sh /docker-entrypoint-initdb.d/setupConnection.sh RUN chmod 755 /docker-entrypoint-initdb.d/setupConnection.sh ADD setup.sh /docker-entrypoint-initdb.d/setup.sh RUN chmod 755 /docker-entrypoint-initdb.d/setup.sh RUN apt-get update -y && apt-get install -y wget && apt-get install -y postgresql-server-dev-9.2 build-essential autoconf libtool pkg-config python-opengl python-imaging python-pyrex python-pyside.qtopengl idle-python2.7 qt4-dev-tools qt4-designer libgle3 python-dev RUN wget https://bootstrap.pypa.io/get-pip.py && python get-pip.py RUN pip install psycopg2 alembic RUN wget http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-13-current.tar.gz RUN tar -zxvf asterisk-13-current.tar.gz ADD config.ini / RUN cp /config.ini asterisk-13.13.1/contrib/ast-db-manage/ |
A więc o tu się dzieje… Słówko „FROM” określa z jakiego kontenera wychodzimy – tzn który rozszerzamy. Wychodzimy więc z kontenera „postgres:9.2”. Dalej poleceniem ADD kopiujemy stworzone wcześniej pliki do folderu „docker-entrypoint-initdb.d” który już znajduje się w kontenerze – był stworzony w obrazie „postgres:9.2” i jego „entrypoint” definiuje się jako „uruchom wszystkie pliki sh z folderu docker-entrypoint-initdb.d” – więc w sumie dokładnie o to nam chodzi. Entrypoint to polecenia które wykonują sie za każdym razem jak kontener startuje. Poleceniem RUN – nadajemy plikom możliwość ich wykonania. Następnie polecenie już mocno customizuje nasz kontener – instalujemy wszystkie zależności do pythona, po cztym instalujemy pipa, potem alembic’a, ściągamy asteriska, wypakowujemy go i kończąc – przenosimy nasz config.ini do folderu z migracjami. I to tyle. Aby uruchomić nasz kontener pierw musimy zbudować jego obraz, robimy to tak, będąc w folderze z Dockerfile’em postgresa:
1 | docker build -t postgres92 . |
i widzimy:
Sending build context to Docker daemon 6.144 kB Step 1 : FROM postgres:9.2 ---> 02b45836f2c8 Step 2 : ADD setupConnection.sh /docker-entrypoint-initdb.d/setupConnection.sh ---> 15ba58b40cca Removing intermediate container 9b61056d6cae Step 3 : RUN chmod 755 /docker-entrypoint-initdb.d/setupConnection.sh ---> Running in f6d0d81f634c ---> 39e76a1b1754 Removing intermediate container f6d0d81f634c Step 4 : RUN apt-get update -y && apt-get install -y wget && apt-get install -y postgresql-server-dev-9.2 build-essential autoconf libtool pkg-config python-opengl python-imaging python-pyrex python-pyside.qtopengl idle-python2.7 qt4-dev-tools qt4-designer libgle3 python-dev ---> Running in 8cce9e8fa323 ....
On wykona wszystkie nasze polecenia, czego wynikiem będzie obraz kontenera o nazwie postgres92.
Uruchommy ten kontener:
docker run -d --name postgres92 postgres92
Wykonaniem tej komendy uruchomiliśmy kontener o nazwie postgres92 na podstawie obrazu o nazwie postgres92. Zalogujmy się do psql’a:
docker exec -it postgres92 psql -U matt -d asterisk
Komenda docker exec -it nazwa_kontenera pozwala na uruchomienie binarki na tym kontenerze. A więc wykonanie tego polecenia skutkuje zalogowanie się do bazy danych:
psql (9.2.19) Type "help" for help. asterisk=>
Więc działa :) Skoro mamy już Postgresa, to czas na Asteriska.
Aby Asterisk podziałał potrzebne są nam pliki konfiguracyjne – spakujmy się do pliki config.tar.gz i przenieśmy do folder docker_asterisk. Dla leniwych plik do ściągnięcia – w formacie zip – należy go przepakować do formatu tar.gz – wybaczcie, ale mój WordPress mocno nie pozwalał bym wrzucał tar.gz’ta do wpisu. No i standardowo jak w przypadku Postgresa, Dockerfile:
1 2 3 4 5 6 7 8 9 10 11 | FROM centos:7 RUN yum upgrade -y && yum install -y epel-release dmidecode gcc-c++ ncurses-devel libxml2-devel make wget openssl-devel newt-devel kernel-devel sqlite-devel libuuid-devel gtk2-devel jansson-devel binutils-devel && yum -y install patch postgresql-devel postgresql-contrib bzip2 RUN wget http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-13-current.tar.gz && tar -zxvf asterisk-13-current.tar.gz && cd asterisk-13.13.1/ WORKDIR asterisk-13.13.1/ RUN ./configure --with-pjproject-bundled && make && make install && make samples WORKDIR / ADD config.tar.gz /config.tar.gz RUN rm -rf /etc/asterisk/* && cp config.tar.gz/* /etc/asterisk/ RUN echo "/usr/local/lib" > /etc/ld.so.conf.d/usr_local.conf && /sbin/ldconfig CMD asterisk -vvvvf |
W tym przypadku wychodzimy z Centosa 7ego. Nie ma tu nic bardzo odkrywczego – instalujemy zależności, ściągamy Asteriska, rozpakowujemy, kompilujemy, instalujemy, przenosimy nasz config.tar.gz (nie musimy go rozpakować – Docker zrobi to za nas), nadpisujemy standardowy config Asteriska, dodajemy ścieżkę do bibliotek dzielonych i uruchamiany Asteriska :) I standardowo go budujemy:
1 | docker build -t asterisk13 . |
co, również standardowo uruchamia proces budowania obrazu:
Sending build context to Docker daemon 1.927 MB Step 1 : FROM centos:7 ---> 67591570dd29 Step 2 : RUN yum upgrade -y && yum install -y epel-release dmidecode gcc-c++ ncurses-devel libxml2-devel make wget openssl-devel newt-devel kernel-devel sqlite-devel libuuid-devel gtk2-devel jansson-devel binutils-devel && yum -y install patch postgresql-devel postgresql-contrib bzip2 ---> Using cache ---> 598cd0b19227 Step 3 : RUN wget http://downloads.asterisk.org/pub/telephony/asterisk/asterisk-13-current.tar.gz && tar -zxvf asterisk-13-current.tar.gz && cd asterisk-13.13.1/ ---> Using cache ---> 23780a537960 Step 4 : WORKDIR asterisk-13.13.1/ ---> Using cache ---> df51f92b998e Step 5 : RUN ./configure --with-pjproject-bundled && make && make install && make samples ---> Using cache ---> d1ff27432767 Step 6 : WORKDIR / ---> Using cache ---> f41fd90a3902 ...
W moim przykładzie Docker skorzystał z cache’a (kawałek „Using cache”) – gdyż nim ten wpis powstał wielokrotnie budowałem ten obraz. Uruchamiany kontener:
docker run -d --name asterisk13 asterisk13docker exec -it asterisk13 asterisk -r
Co daje poprawny rezultat:
1 2 3 4 5 6 7 8 9 10 | Connected to Asterisk 13.13.1 currently running on 3dcd15318ef0 (pid = 1) [Feb 5 12:26:38] ERROR[99]: res_config_pgsql.c:167 _pgsql_exec: PostgreSQL RealTime: Failed to query 'ps_contacts@asterisk'. [Feb 5 12:26:38] ERROR[99]: res_config_pgsql.c:168 _pgsql_exec: PostgreSQL RealTime: Query Failed: SELECT * FROM ps_contacts WHERE expiration_time <= '1486297598' ORDER BY expiration_time [Feb 5 12:26:38] ERROR[99]: res_config_pgsql.c:169 _pgsql_exec: PostgreSQL RealTime: Query Failed because: (PGRES_FATAL_ERROR) [Feb 5 12:26:38] ERROR[99]: res_config_pgsql.c:1540 pgsql_reconnect: PostgreSQL RealTime: Failed to connect database asterisk on 127.0.0.1: [Feb 5 12:26:38] NOTICE[99]: res_config_pgsql.c:153 _pgsql_exec: reconnect failed 3dcd15318ef0*CLI> |
Jak widzimy nasz Asterisk rzuca błędami – nie może połączyć się z postgresem – no i ma rację. Aby kontenery mogły się widzieć można je zlinkować, połączyć w sieć lub po prostu przybindować do maszyny lokalnej. My użyjemy ostatniej możliwości. Aby to osiągnąć skorzystamy z docker compose’a – czyli oprogramowania które pozwala grupować, łączyć i zarządzać wieloma kontenerami naraz (sudo dnf install docker-compose).
A więc w folderze wyżej, tj w folderze docker stwórzmy plik docker-compose.yml z zawartością:
1 2 3 4 5 6 7 8 9 10 11 12 | version: '2' services: postgres92: container_name: postgres92 build: docker_postgres/ network_mode: "host" asterisk13: container_name: asterisk13 build: docker_asterisk/ network_mode: "host" depends_on: - postgres92 |
Gdzie deklarujemy że używamy wersji drugiej pliku compose’a i chcemy stworzyć dwie usługi, jedną na bazie obrazu postgres92 i nazwie postgres92, gdzie plik Dockerfile znajduje się w folderze docker_postgres i typie sieci – „host” – które pozwala korzystać ze stworzonych usług tak jakby były uruchomione na komputerze (rozwiązuje problemy z przekierowywaniem portów itp).
Druga usługa to nasz Asterisk i jego definicja nie różni się niczym od definicji postgres’a poza elementem „depends_on” które definiuje kolejność podnoszonych kontenerów – więc jeśli kontener asterisk13 zależy od kontenera postgres92 to Docker uruchomi postgresa jako pierwszego – co ma sens, bo jeśli byłoby inaczej – Asterisk próbowałby się połączyć do jeszcze nie uruchomionej bazy danych.
A więc zabijamy wcześniej utworzone kontenery:
docker rm postgres92 asterisk13 -f
i w folderze z utworzonym plikiem docker-compose.yml uruchamiany:
docker-compose up
Co powoduje podniesienie Asteriska jak i postgresa, zrzut z mojego Terminatora:
Na górze działający compose, na dole po prawej połączenie z postgresem a na dole po lewej Asterisk :)
Aby móc dzwonić nalezy wykonać te SQLki co w poprzednim poście:
insert into ps_aors (id, max_contacts) values (22444444, 1); insert into ps_aors (id, max_contacts) values (22555555, 1); insert into ps_auths (id, auth_type, password, username) values (22444444, 'userpass', 1, 22444444); insert into ps_auths (id, auth_type, password, username) values (22555555, 'userpass', 2, 22555555); insert into ps_endpoints (id, transport, aors, auth, context, disallow, allow, direct_media) values (22444444, 'transport-udp', '22444444', '22444444', 'testing', 'all', 'ulaw,alaw', 'no'); insert into ps_endpoints (id, transport, aors, auth, context, disallow, allow, direct_media) values (22555555, 'transport-udp', '22555555', '22555555', 'testing', 'all', 'ulaw,alaw', 'no');
Ale tym razem bez UPDATE’a o którym wspomniałem tam, gdyż teraz nie mamy NATu. W zamian należy wykonać:
update ps_endpoints set rewrite_contact='yes';
i przeładować asteriska. Co powoduje że możemy do siebie dzwonić
No i w sumie to tyle :) Docker ma potencjał, mimo iż raczej nie jest używany na produkcjach.
Kilka rzeczy o których nie było po drodze mi wspomnieć:
1. Kontenery są domyślnie bezstanowe. Co w przypadku Asteriska jest znośne, to w przypadku postgresa średnio – fajnie by było jednak między wyłączeniem a włączeniem komputera tę samą zawartość bazy. Jest to zrealizowane w obrazie Postgresa poleceniem VOLUME:
VOLUME /var/lib/postgresql/data
które montuje wskazany katalog na hoście.
2. Mimo dodania „depends_on” Asterisk i tak czasem ma problem z połączeniem z bazą przez chwilkę. Wynika to stąd że Docker nie czeka aż usługi nam się podniosą a jedynie je startuje – to jest trochę race condition – czasem Asterisk wstanie później nim baza zacznie przyjmować zapytania a czasem wcześniej. Poprawić to można poprzez skrypt „wait-for-it.sh” – https://github.com/vishnubob/wait-for-it – czyli dodać go poleceniem ADD do Dockerfile’a Asteriska i poleceniem CMD zmienić na
CMD ["./wait-for-it.sh", "localhost:5432", "--", "asterisk -vvvvf"]
Co wymusi poczekanie ze wystartowaniem Asteriska nim baza wstanie.
3. Przydatne komendy..
docker exec -it asterisk13 bash
uruchamia na kontenerze basha – więc dzięki temu możemy sobie pochodzić po filesystemie – przydatne przy debuggowaniu.
docker rm postgres92 asterisk13 -f
usuwa kontenery. W tym przypadku są to dwa kontenery – postgres92 i asterisk13.
docker rmi postgres92 -f
usuwa obraz i kontenery stworzone za jego pomocą.
docker rm $(docker ps -a -q)
Usuwa wszystkie kontenery.
docker rmi $(docker images -q)
Usuwa wszystkie obrazy.
docker inspect asterisk13
Pokazuje informacje o kontenerze.
docker logs asterisk13
Pokazuje logi z kontenera.
docker-compose up --build
Wymusze rebuild kontenerów.
docker ps -a
Pokazuje kontenery.
Mateusz Mazurek