Mateusz Mazurek – programista z pasją

Python, architektura, ciekawostki ze świata IT

Inżynieria oprogramowania Linux Programowanie Utrzymanie oprogramowania

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 asterisk13
docker 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.

Pobierz całość wpisu.

Dzięki za wizytę,
Mateusz Mazurek

A może wolisz nowości na mail?

Subskrybuj
Powiadom o
guest

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.

1 Komentarz
Inline Feedbacks
View all comments