19 sie

Konfigurowanie integracyjnego testowania środowiska z Docker oraz Docker Compose na Ubuntu 16.04

Testowanie środowiska z Docker oraz Docker Compose na Ubuntu 16

Ciągła integracja (CI) odnosi się do praktyki, kiedy deweloperzy integrują kod tak często, na ile jest to możliwe, a każda próba jest wcześniej testowana przed i po wprowadzeniu do wspólnego repozytorium przez zautomatyzowaną kompilację.

CI przyśpiesza proces rozwoju i minimalizuje ryzyko wystąpienia problemów krytycznych, niemniej jednak skonfigurowanie nie jest takie proste; zautomatyzowane kompilacje działają w innym środowisku, gdzie instalacja zależności uruchomieniowych oraz konfiguracja usług zewnętrznych może być inna niż w Twoim środowisku lokalnym i deweloperskim.

Nowy artykuł o Instalacji Docker-CE na Ubuntu 20.04

Docker jest platformą bazującą się na kontenerach mających na celu niwelowanie problemów środowiska normalizacji tak, aby wdrażanie aplikacji też można było normalizować. Docker pozwala programistom na symulowanie środowisk produkcyjnych na komputerach lokalnych

Dzięki Docker programiści mogą symulować środowiska produkcyjne na komputerach lokalnych poprzez uruchomienie komponentów aplikacji w lokalnych kontenerach. Kontenery te są łatwe do zautomatyzowania przy użyciu Docker Compose, niezależnie od zastosowania i bazowego systemu operacyjnego.

W niniejszym artykule wykorzystamy Docker Compose w celu zademonstrowania automatyzacji przepływu czynności CI.

Stworzymy Zdockeryzowaną aplikację „Hello world” typu Python oraz testowy skrypt Bash. Aplikacja Python będzie wymagać uruchomienia dwóch kontenerów: jeden dla aplikacji jako takiej, a drugi dla kontenera Redis w celu przechowywania, co jest wymagane z uwagi na zależność od aplikacji.

Następnie testowy skrypt zostanie Zdokeryzowany we własnym kontenerze i całe testowane środowisko zostanie przeniesione do pliku docker-compose.test.yml. W taki oto sposób przekonamy się, że każdy uruchamiany test działa na świeże i jednolite środowisko aplikacji.

Takie podejście pokazuje, jak można zbudować identyczne, świeże środowisko testowe dla aplikacji, włącznie z jego zależnościami, za każdym testowaniem.

Takim oto sposobem możemy zautomatyzować przepływy czynności CI niezależnie od testowanej aplikacji oraz podstawowej infrastruktury.

Wymagania

Będziesz potrzebował:

  • Serwera Ubuntu 16.04
  • Użytkownika innego, niż root z uprawnieniami sudo.
  • Znajomości Docker oraz Docker Compose.
  • Testowania, testowania i jeszcze raz (automatycznego) testowania! Powinieneś już docenić zalety testowania i stosowania CI w swojej pracy.

Instalowanie Docker

Jeżeli Docker nie jest jeszcze dostępny na serwerze, najprostszym sposobem jest pobranie i wykonanie oficjalnego skryptu instalacyjnego Docker wymagającego hasła sudo.
wget -qO- https://get.docker.com/ | sh

W celu ułatwienia pracy z Docker, dodaj użytkownika do grupy docker poprzez następujące polecenie:
sudo usermod -aG docker $(whoami)

Wyloguj się i zaloguj na swoim serwerze, aby aktywować grupę docker dla Twojego użytkownika.

Instalowanie Docker Compose

Docker Compose to narzędzie open-source przeznaczone do definiowania i uruchamiania aplikacji wielu kontenerowych przy użyciu deklaratywnego podejścia. Aby zainstalować Docker Compose, wykonaj następujące polecenia:
sudo apt-get update
sudo apt-get -y install python-pip
sudo pip install docker-compose

Przekonaj się, że docker-compose zostało poprawnie zainstalowane poprzez:
docker-compose --version

Powinieneś zobaczyć coś w tym rodzaju:

Odpowiedź
docker-compose version 1.6.2, build 4d72027

W odpowiedzi dostaniesz wersję docker-compose, która została zainstalowana.

Stworzenie Aplikacji „Hello World” w Pythonie

Za chwilę stworzymy prostą aplikację na Pythonie. Będzie to przykładowa aplikacja, którą będziesz mógł przetestować w tej konfiguracji.

Stwórz nowy folder dla aplikacji poprzez:
cd ~
mkdir hello_world
cd hello_world

Edytuj nowy plik app.py z nano:
nano app.py

Dodaj następującą treść:

app.py
from flask import Flask
from redis import Redis

app = Flask(__name__)
redis = Redis(host="redis")

@app.route("/")
def hello():
    visits = redis.incr('counter')
    html = "<h3>Hello World!</h3>" \
           "<b>Visits:</b> {visits}" \
           "<br/>"
    return html.format(visits=visits)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=80)

app.py jest internetową aplikacją bazującą się na Flask łączącym się z usługą danych Redis. Wiersz visits = redis.incr('counter') zwiększa liczbę odwiedzin i utrzymuję tą wartość w Redis. Finalnie, komunikat Hello World z ilością odwiedzin jest zwracany w postaci HTML.

Nasza aplikacja posiada dwie zależności, Flask i Redis, widoczne w pierwszych dwóch wierszach. Wspomniane zależności należy zdefiniować przed uruchomieniem aplikacji. Edytuj nowy plik:
nano requirements.txt

Dodaj zawartość:

requirements.txt
Flask
Redis

Dokeryzacja aplikacji “Hello World”

Docker używa pliku o nazwie Dockerfile w celu wskazania wymaganych kroków aby zbudować obraz Docker dla danej aplikacji. Wyedytuj nowy plik:
nano Dockerfile

Dodaj następującą zawartość:

Dockerfile
FROM python:2.7

WORKDIR /app

ADD requirements.txt /app/requirements.txt
RUN pip install -r requirements.txt

ADD app.py /app/app.py

EXPOSE 80

CMD ["python", "app.py"]

Przeanalizujmy znaczenie każdego wiersza:

  • FROM python:2.7: wskazuje, że obraz aplikacji “Hello World” jest zbudowany z oficjalnego obrazu Docker python:2.7
  • WORKDIR /app: ustawia roboczy katalog wewnątrz obrazu Docker do /app
  • ADD requirements.txt /app/requirements.txt: dodaje plik requirements.txt do obrazu Docker
  • RUN pip install -r requirements.txt: instaluje pip zależności aplikacji
  • ADD app.py /app/app.py: dodaje kod źródłowy aplikacji do obrazu Docker
  • EXPOSE 80: wskazuje, że naszą aplikację znajdziemy na porcie 80 (standardowy publiczny port internetowy)
  • CMD [”python”, „app.py”]: polecenie, które uruchamia naszą aplikację

Niniejszy plik Dockerfile posiada wszystkie informacje niezbędne do zbudowania głównego komponentu naszej aplikacji „Hello World”.

Zależność

Przejdźmy do bardziej zaawansowanej części przykładu. Nasza aplikacja wymaga Redis jako usługi zewnętrznej. Jest to rodzaj uzależnienia, które może być trudne do skonfigurowania w ten sam sposób za każdym razem w tradycyjnym środowisku Linux, jednak z Docker Compose możemy ustawić go w sposób powtarzalny za każdym razem.

Stwórzmy plik docker-compose.yml aby rozpocząć korzystanie z Docker Compose. Edytuj nowy plik:
nano docker-compose.yml

Dodaj następującą zawartość:

[docker-compose.yml]
web:
  build: .
  dockerfile: Dockerfile
  links:
    - redis
  ports:
    - "80:80"
redis:
  image: redis

Plik Docker Compose wskazuje jak rozpędzić lokalnie aplikację „Hello World” w dwóch kontenerach Docker.

Definiowane są dwa kontenery, web oraz redis.

  • web używa bieżącego folderu w build zawartości, a także buduje aplikację Python z właśnie stworzonego przez nas pliku Dockerfile. Jest to lokalny obraz Docker, który stworzyliśmy dla naszej aplikacji Python. Definiuje on link do kontenera redis aby mieć dostęp do IP kontenera redis. Sprawia to też, że port 80 staje się publicznie dostępny przez Internet za pośrednictwem publicznego adresu IP serwera Ubuntu.
  • redis jest wykonywany ze standardowego publicznego obrazu Docker o nazwie redis

Wdrażanie aplikacji “Hello World”

Teraz pokażemy Ci jak wdrożyć aplikację, na koniec będzie ona dostępna w sieci Internet. W celu wdrożenia procesu pracy, możesz uznać to za dev, inscenizację lub środowisko produkcyjne, ponieważ aplikację możesz wdrożyć w ten sam sposób wiele razy.

Pliki docker-compose.yml oraz Dockerfile pozwalają na automatyczne wdrożenie środowisk lokalnych poprzez:
docker-compose -f ~/hello_world/docker-compose.yml build
docker-compose -f ~/hello_world/docker-compose.yml up -d

Pierwszy wiersz buduje lokalny obraz aplikacji z pliku Dockerfile. Drugi wiersz uruchamia kontenery web oraz redis na trybie demona (-d), co jest sprecyzowane w pliku docker-compose.yml.

Sprawdź, czy kontenery aplikacji zostały stworzone:
docker ps

Powinieneś zobaczyć dwa uruchomione kontenery pod nazwą helloworld_web_1 oraz helloworld_redis_1.

Sprawdźmy czy aplikacja działa. IP kontenera helloworld_web_1 zdobędziemy poprzez:
WEB_APP_IP=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' helloworld_web_1)
echo $WEB_APP_IP

Sprawdź, czy aplikacja internetowa powraca do prawidłowej wiadomości:
curl http://${WEB_APP_IP}:80

Powinno to wyglądać mniej więcej następująco:

Odpowiedź
<h3>Hello World!</h3><b>Visits:</b> 1<br/>

Liczba odwiedzin zwiększa się za każdym razem jak trafiasz do punktu końcowego. Można również uzyskać dostęp do aplikacji „Hello World” z przeglądarki poprzez odwiedzenie publicznego adresu IP serwera Ubuntu.

Dostosowanie do własnej aplikacji

Kluczem do ustawienia własnej aplikacji jest jej umieszczenie we własnym kontenerze Docker, a także uruchomienie każdej zależności z własnego kontenera. Następnie można zdefiniować powiazania pomiędzy kontenerami Docker Compose.

Stworzenie testowego skryptu

Stwórzmy skrypt testowy dla naszej aplikacji Python. Będzie to prosty skrypt sprawdzający wydajność HTTP aplikacji. Skrypt jest przykładem testu, który możesz chcieć uruchomić jako część ciągłego procesu wdrażania integracji.

Edytuj nowy plik:
nano test.sh

Dodaj następującą zawartość:

test.sh
sleep 5
if curl web | grep -q '<b>Visits:</b> '; then
  echo "Tests passed!"
  exit 0
else
  echo "Tests failed!"
  exit 1
fi

test.sh testuje w zakresie podstawowej zawartości internetowej aplikację “Hello World”.

Wykorzystuje cURL w celu pobrania informacji na temat ilości odwiedzin i raportów, aby dowiedzieć się czy test przeszedł pomyślnie, czy nie.

Stworzenie środowiska testowego

Aby przetestować naszą aplikację musimy rozmieścić nasze środowisko testowe. Przekonajmy się, że jest to identyczne z żywym środowiskiem aplikacji, które stworzyliśmy wcześniej.

Na początek musimy Dockeryzować nasz testujący skrypt poprzez stworzenie nowego pliku Dockerfile.

Edytuj nowy plik:
nano Dockerfile.test

Dodaj następującą zawartość:

Dockerfile.test
FROM ubuntu:trusty

RUN apt-get update && apt-get install -yq curl && apt-get clean

WORKDIR /app

ADD test.sh /app/test.sh

CMD ["bash", "test.sh"]

Dockerfile.test rozszerza oficjalny obraz ubuntu:trusty w celu instalowania zależności curl, dodaje tests.sh do obrazu systemu plików i oznacza polecenie CMD, które wykonuje testowy skrypt z Bash.

Po Dockeryzacji naszych testów możemy je wykonać w sposób powtarzalny i agnostyczny.

Następny krok dotyczy podłączenia naszego testowego kontenera do aplikacji “Hello World”. Właśnie tutaj z pomocą znowu przychodzi Docker Compose.

Edytuj nowy plik:
nano docker-compose.test.yml

Dodaj następującą zawartość:

docker-compose.test.yml
sut:
  build: .
  dockerfile: Dockerfile.test
  links:
    - web
web:
  build: .
  dockerfile: Dockerfile
  links:
    - redis
redis:
  image: redis

Druga połowa pliku Docker Compose wdraża główną aplikację web i jej zależność redis tak samo, jak poprzedni plik docker-compose.yml. Ta część pliku specyfikuje kontenery web i redis. Jedyną różnicą jest to, że kontener web więcej nie wystawia portu 80, więc aplikacja nie będzie dostępna publicznie w Internecie podczas testowania. Jak widzisz, budujemy aplikację i jej zależności identycznie jak w żywym wdrażaniu.

Plik docker-compose.test.yml definiuje też kontener sut (system under tests) odpowiedzialny za wykonanie integracyjnych testów. Kontener sut precyzuje aktualny katalog jako katalog build, a także określa nasz plik Dockerfile.test. Łączy się on z kontenerem web, co powoduje, że adres IP kontenera jest dostępny dla naszego skryptu test.sh.

Dostosowanie do własnej aplikacji

Pamiętaj, że docker-compose.test.yml może zawierać dziesiątki usług zewnętrznych i wiele kontenerów testowych. Docker uruchomi wszystkie zależności z jednego hosta, ponieważ każdy kontener dzieli się podstawowym systemem operacyjnym.

Jeżeli masz do przeprowadzenia więcej testów związanych z Twoją aplikacją, możesz stworzyć dodatkowy Dockerfiles dla nich, podobnie jak opisany wcześniej plik Dockerfile.test.

Następnie możesz dodać dodatkowe kontenery poniżej kontenera sut w pliku docker-compose.test.yml, przedstawiając dodatkowe Dockerfiles.

Testowanie aplikacji “Hello World”

Wreszcie, rozszerzając ideę Docker ze środowisk lokalnych w celu testowania środowisk, mamy zautomatyzowany sposób testowania naszej aplikacji przy wykorzystaniu Docker poprzez wykonanie:
docker-compose -f ~/hello_world/docker-compose.test.yml -p ci build

To polecenie buduje lokalne obrazy potrzebne dla docker-compose.test.yml. Pamiętaj, że wykorzystujemy –f aby wskazać docker-compose.test.yml oraz -p aby zaznaczyć specyficzne imię projektu.

Teraz obróć właśnie przetestowane środowisko poprzez:
docker-compose -f ~/hello_world/docker-compose.test.yml -p ci up -d

Sprawdź odpowiedź kontenera sut wykonując:
docker logs -f ci_sut_1

Odpowiedź
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    42  100    42    0     0   3902      0 --:--:-- --:--:-- --:--:--  4200
Tests passed!

Na koniec, sprawdź kod wyjściowy kontenera sut w celu przekonania się, że testy się odbyły:
docker wait ci_sut_1

Odpowiedź
0

Po wykonaniu tego polecenia wartość $? Będzie równa 0, jeżeli testy wykonano pomyślnie. W innym przypadku testy się nie powiodły.

Miej na uwadze, że inne narzędzia CI mogą sklonować nasz kod repozytorium i wykonać te kilka poleceń w celu sprawdzenia, czy testy przechodzą wraz z najnowszymi częściami aplikacji bez zmartwień o uruchomieniach zależności lub konfiguracjach serwisów zewnętrznych.

Gratulacje! Skutecznie przeprowadziliśmy test w nowostworzonym środowisku identycznym z naszym środowiskiem produkcyjnym.

Podsumowanie

Dzięki Docker oraz Docker Compose nauczyliśmy się jak zautomatyzować budowanie aplikacji (Dockerfile), jak wdrożyć środowisko lokalne (docker-compose.yml), jak zbudować obraz testowy (Dockerfile.test) oraz jak wykonać testy (integracyjne) (docker-compose.test.yml) dla jakiejkolwiek aplikacji.

Szczególnie ważną zaletą wykorzystania pliku docker-compose.test.yml podczas testowania jest to, że proces testowania jest:

  • Zautomatyzowany: sposób wykonania przez narzędzie docker-compose.test.yml jest niezależny od testowanej aplikacji
  • Lekkość: setki serwisów zewnętrznych można wdrożyć na jednym hoście, symulując kompleksowe (integracyjne) środowiska testowe
  • Agnostycyzm: unikanie dostawcy CI lock-in pozwala na uruchomienie testów w każdej infrastrukturze oraz na każdym systemie operacyjnym podtrzymującym Docker
  • Niezmienność: testy odbywające się na Twoim komputerze przejdą do Twoich narzędzi CI

Niniejszy artykuł przedstawia przykład testowania prostej aplikacji „Hello World”.

Przyszedł czas na wykorzystanie plików aplikacji, Dockeryzację włąsnych skryptów testowych oraz stworzenie własnego docker-compose.test.yml w celu przetestowania aplikacji w świeżym i niezmiennym środowisku.