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
i przejść do konsoli Asteriska:
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
Mateusz M.

Ostatnie wpisy

Podsumowanie: luty i marzec 2024

Ostatnio tygodnie były tak bardzo wypełnione, że nie udało mi się napisać nawet krótkiego podsumowanie. Więc dziś zbiorczo podsumuję luty… Read More

2 tygodnie ago

Podsumowanie: styczeń 2024

Zapraszam na krótkie podsumowanie miesiąca. Książki W styczniu przeczytałem "Homo Deus: Historia jutra". Książka łudząco podoba do wcześniejszej książki tego… Read More

3 miesiące ago

Podsumowanie roku 2023

Cześć! Zapraszam na podsumowanie roku 2023. Książki Zacznijmy od książek. W tym roku cel 35 książek nie został osiągnięty. Niemniej… Read More

4 miesiące ago

Podsumowanie: grudzień 2023

Zapraszam na krótkie podsumowanie miesiąca. Książki W grudniu skończyłem czytać Mein Kampf. Nudna książka. Ciekawsze fragmenty można by było streścić… Read More

4 miesiące ago

Praca zdalna – co z nią dalej?

Cześć, ostatnio w Internecie pojawiło się dużo artykułów, które nie były przychylne pracy zdalnej. Z drugiej strony większość komentarzy pod… Read More

4 miesiące ago

Podsumowanie: listopad 2023

Zapraszam na krótkie podsumowanie miesiąca. Książki W listopadzie dokończyłem cykl "Z mgły zrodzony" Sandersona. Tylko "Stop prawa" mi nie do… Read More

5 miesięcy ago