Csináld magad stressztesztelés

Csináld magad stressztesztelés - teljesen rugalmas és realisztikus.

Korábban írtunk a stressztesztelésről, ahol a Blazemeter-t mutattuk be, ahol megtanulhattad, hogyan crasheld le az oldaladat anélkül, hogy aggódnál az infrastruktúra miatt. Szóval miért is fáradtam azzal, hogy megírjam ezt a bejegyzést a csináld magad megközelítésről? Van egy komplex frontend alkalmazásunk, ahol szinte lehetetlen lenne hűen szimulálni az összes hálózati tevékenységet hosszú időn keresztül. Böngésző-alapú tesztelési keretrendszert akartunk használni, nevezetesen WebdriverI/O-t néhány egyedi Node.js csomaggal a Blazemeter-en, és gyorsabbnak bizonyult elkezdeni az infrastruktúra kezelését és teljes kontrollt szerezni a környezet felett. Mi lett végül? Egy publikus felhőszolgáltatót használva (esetünkben a Linode-ot), programozott módon elindítottuk a szükséges számú gépet ideiglenesen, felkészítettük őket a megfelelő stack-kel, és a WebdriverI/O teszt végrehajtásra került. Az Ansible, Linode CLI és WebdriverIO segítségével az egész folyamat megismételhető és skálázható, lássuk hogyan!

Infrastruktúra fázis

Bármely tisztességes felhőszolgáltatónak van interfésze a felhőgépek kódból történő provizionálásához és kezeléséhez. Erre építve, ha tetszőleges számú számítógépre van szükséged a teszt indításához, megkaphatod 1-2 órára (100 végpont egy kávé áráért, hogy hangzik?).

Sok lehetőség van virtuális gépek dinamikus és programozott létrehozására stressztesztelés céljából. Az Ansible dinamikus inventory-t kínál, azonban a választott felhőszolgáltatónk nem volt benne az Ansible legújabb stabil verziójában (2.7) ennek a bejegyzésnek az írásakor. Valamint az alábbi megoldás az infrastruktúra fázist függetlenné teszi, bármilyen provizionálás (akár tisztán shell szkriptek) lehetséges minimális adaptációval.

Kövessük a lépéseket a Linode CLI telepítési útmutatójában. A kulcs az, hogy legyen a konfigurációs fájl a ~/.linode-cli-nél a hitelesítő adatokkal és gép alapértelmezésekkel. Ezután létrehozhatsz egy gépet egyetlen sorral:

linode-cli linodes create --image "linode/ubuntu18.04" --region eu-central --authorized_keys "$(cat ~/.ssh/id_rsa.pub)"  --root_pass "$(date +%s | sha256sum | base64 | head -c 32 ; echo)" --group "stress-test"

A megadott publikus kulccsal jelszó nélküli bejelentkezés lesz lehetséges. Azonban ez messze nem elég a provizionálás előtt. A bootolás időbe telik, az SSH szerver nem elérhető azonnal, és a mi speciális helyzetünk az, hogy a stresszteszt után azonnal el szeretnénk dobni a példányokat, a teszt végrehajtással együtt a költségek minimalizálása érdekében.

A gép bootolására várás egy kicsit hosszabb kódrészlet, a CSV output robusztusan elemezhető:

## Várakozás a bootra, hogy SSH-zhassunk.
while linode-cli linodes list --group=stress-test --text --delimiter ";" --format 'status' --no-headers | grep -v running
do
  sleep 2
done

Azonban az SSH kapcsolat valószínűleg még nem lehetséges, várjuk meg, amíg a port megnyílik:

for IP in $(linode-cli linodes list --group=stress-test --text --delimiter ";" --format 'ipv4' --no-headers);
do
  while ! nc -z $IP 22 < /dev/null > /dev/null 2>&1; do
    sleep 1
  done
done

Észreveheted, hogy ez átfed a gép bootolás várakozással. Az egyetlen előny az, hogy a kettő szétválasztása kifinomultabb hibakezelést és jelentést tesz lehetővé.

Ezután a csoportunkban lévő összes gép törlése triviális:

for ID in $(linode-cli linodes list --group=stress-test --text --delimiter ";" --format 'id' --no-headers);
do
  linode-cli linodes delete "$ID"
done

Tehát miután mindent egy szkriptbe csomagoltunk, és egy Ansible meghívást is beletettünk a közepébe, ez lesz a stress-test.sh:

#!/bin/bash

LINODE_GROUP="stress-test"
NUMBER_OF_VISITORS="$1"

NUM_RE='^[0-9]+$'
if ! [[ $NUMBER_OF_VISITORS =~ $NUM_RE ]] ; then
  echo "error: Nem szám: $NUMBER_OF_VISITORS" >&2; exit 1
fi

if (( $NUMBER_OF_VISITORS > 100 )); then
  echo "warning: Biztos, hogy $NUMBER_OF_VISITORS linode-ot akarsz létrehozni?" >&2; exit 1
fi

echo "Az inventory fájl visszaállítása."
cat /dev/null > hosts

echo "A szükséges linode-ok létrehozása, az inventory fájl feltöltése."
for i in $(seq $NUMBER_OF_VISITORS);
do
  linode-cli linodes create --image "linode/ubuntu18.04" --region eu-central --authorized_keys "$(cat ~/.ssh/id_rsa.pub)" --root_pass "$(date +%s | sha256sum | base64 | head -c 32 ; echo)" --group "$LINODE_GROUP" --text --delimiter ";"
done

## Várakozás a bootra.
while linode-cli linodes list --group="$LINODE_GROUP" --text --delimiter ";" --format 'status' --no-headers | grep -v running
do
  sleep 2
done

## Várakozás az SSH portra.
for IP in $(linode-cli linodes list --group="$LINODE_GROUP" --text --delimiter ";" --format 'ipv4' --no-headers);
do
  while ! nc -z $IP 22 < /dev/null > /dev/null 2>&1; do
    sleep 1
  done
  ### Az IP összegyűjtése az Ansible hosts fájlhoz.
  echo "$IP" >> hosts
done
echo "Az SSH szerverek elérhetővé váltak"

echo "A playbook végrehajtása"
ansible-playbook -e 'ansible_python_interpreter=/usr/bin/python3' -T 300 -i hosts main.yml

echo "A létrehozott linode-ok takarítása."
for ID in $(linode-cli linodes list --group="$LINODE_GROUP" --text --delimiter ";" --format 'id' --no-headers);
do
  linode-cli linodes delete "$ID"
done

Provizionálási fázis

Ahogy korábban írtam, az Ansible csak egy lehetőség, bár népszerű lehetőség gépek provizionálására. Egy ilyen teszthez még egy csomó shell parancs is elegendő lenne a stack beállításához. Azonban miután valaki megkóstolja a deklaratív infrastrukturális munkát, ez lesz az első választás.

Ha ez az első tapasztalatod az Ansible-lel, nézd meg a hivatalos dokumentációt. Dióhéjban, egyszerűen YAML-ben deklaráljuk, hogyan nézzen ki a gép(ek), és milyen csomagokkal rendelkezzen.

Véleményem szerint egy ilyen egyszerű playbook, mint az alábbi, olvasható és érthető önmagában, előzetes tudás nélkül is. Tehát a main.yml a következő:

- name: WDIO-alapú stresszteszt
  hosts: all
  remote_user: root

  tasks:
    - name: Apt csomagok frissítése és upgrade-elése
      become: true
      apt:
        upgrade: yes
        update_cache: yes
        cache_valid_time: 86400

    - name: WDIO és Chrome függőségek
      package:
        name: "{{ item }}"
        state: present
      with_items:
         - unzip
         - nodejs
         - npm
         - libxss1
         - libappindicator1
         - libindicator7
         - openjdk-8-jre

    - name: Chrome letöltése
      get_url:
        url: "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb"
        dest: "/tmp/chrome.deb"

    - name: Chrome telepítése
      shell: "apt install -y /tmp/chrome.deb"

    - name: Chromedriver beszerzése
      get_url:
        url: "https://chromedriver.storage.googleapis.com/73.0.3683.20/chromedriver_linux64.zip"
        dest: "/tmp/chromedriver.zip"

    - name: Chromedriver kicsomagolása
      unarchive:
        remote_src: yes
        src: "/tmp/chromedriver.zip"
        dest: "/tmp"

    - name: Chromedriver indítása
      shell: "nohup /tmp/chromedriver &"

    - name: A WDIO teszt forráskódjának szinkronizálása
      copy:
        src: "wdio"
        dest: "/root/"

    - name: WDIO telepítése
      shell: "cd /root/wdio && npm install"

    - name: Kezdési dátum
      debug:
        var=ansible_date_time.iso8601

    - name: Végrehajtás
      shell: 'cd /root/wdio && ./node_modules/.bin/wdio wdio.conf.js --spec specs/stream.js'

    - name: Befejezési dátum
      debug:
        var=ansible_date_time.iso8601

Telepítjük a Chrome függőségeit, magát a Chrome-ot, a WDIO-t, majd végrehajthatjuk a tesztet. Erre az egyszerű esetre ez elég. Ahogy korábban utaltam rá:

ansible-playbook -e 'ansible_python_interpreter=/usr/bin/python3' -T 300 -i hosts main.yml

Mi az előnye a shell szkriptekkel szemben? Erre a konkrét felhasználási esetre főleg az, hogy az Ansible biztosítja, hogy minden párhuzamosan történhessen, és elegendő hibakezelésünk és jelentésünk legyen.

Teszt fázis

Szeretjük a teszteket. A starter kit-ünkben vannak WebdriverIO tesztek (sok más típusú teszt mellett), így ezt választottuk a teljes stack stresszteszteléséhez. Ha ismered a JavaScript-et vagy a Node.js-t, a tesztkód könnyen érthető lesz:

const assert = require('assert');

describe('podcasts', () => {
    it('should be streamable', () => {
        browser.url('/');
        $('.contact .btn').click();

        browser.url('/team');
        const menu = $('.header.menu .fa-bars');
        menu.waitForDisplayed();
        menu.click();
        $('a=Jobs').click();
        menu.waitForDisplayed();
        menu.click();
        $('a=Podcast').click();
        $('#mep_0 .mejs__controls').waitForDisplayed();
        $('#mep_0 .mejs__play button').click();
        $('span=00:05').waitForDisplayed();
    });
});

Ez a spec fájlunk, ami a lényeg, a konfigurációval együtt.

Megtehettük volna egy csomó kéréssel jMeter-ben vagy Gatling-ban? Majdnem. A hab a tortán az, ahol a podcast streamelését stresszteszteljük. Szimulálunk egy felhasználót, aki 10 másodpercig hallgatja a podcastot. Bármely frontend-nehéz alkalmazáshoz a realisztikus stressztesztelés igazi böngészőt igényel, a WDIO pontosan ezt biztosítja számunkra.

A WebdriverIO teszt végrehajtás - headless mód kikapcsolva
A WebdriverIO teszt végrehajtás - headless mód kikapcsolva

Teszt végrehajtási fázis

Miután a shell szkriptet végrehajthatóvá tettük (chmod 750 stress-test.sh), képesek vagyunk végrehajtani a tesztet akár:

  • egy látogatóval egy virtuális gépről: ./stress-test.sh 1
  • 100 látogatóval 100 virtuális gépről mindegyikhez: ./stress-test.sh 100

ugyanolyan egyszerűséggel. Azonban nagyon nagyszabású teszteknél gondolnod kell néhány szűk keresztmetszetre, például a tesztelési oldal adatközpontjának kapacitására. Értelmesebb lehet véletlenszerűen választani egy adatközpontot minden tesztelő géphez.

A teszt végrehajtás két fő részből áll: a környezet bootstrap-elése és maga a teszt végrehajtása. Ha a környezet bootstrap-elése túl nagy százalékot vesz igénybe, az egyik stratégia egy Docker image előkészítése, és ahelyett, hogy újra és újra létrehoznánk a környezetet, csak használjuk az image-et. Ebben az esetben érdemes konténer-specifikus hosting megoldást keresni önálló virtuális gép helyett.

Szeretnéd most kipróbálni? Csak csinálj egy git clone https://github.com/Gizra/diy-stress-test.git-et!

Eredmény elemzés

Egy ilyen elosztott DIY tesztnél az eredmények elemzése kihívást jelenthet. Például hogyan mérnéd a kérés/másodpercet egy adott böngésző-alapú teszthez, mint a WebdriverI/O?

Esetünkben az elemzés a másik oldalon történik. Szinte minden hosting megoldás, amivel találkozunk, támogatja a New Relic-et, ami sokat segíthet egy ilyen elemzésben. A tesztünk DIY volt, de az eredménykezelést kiszerveztük. A hab a tortán az, hogy segít a szűk keresztmetszetek felkutatásában is, így hasonló megoldás alkalmazható a te hosting platformodra is.

Azonban mi van, ha össze szeretnéd gyűjteni az eredményeket egy ilyen elosztott teszt végrehajtás után?

Anélkül, hogy részletekbe mennénk, tanulmányozhatod az Ansible fetch modulját, így összegyűjthetsz egy eredménylogot az összes teszt szerverről és helyben egy központi helyen tarthatod.

Következtetés

Nagyszerű tapasztalat volt, hogy miután némi nehézségbe ütköztünk egy hosztolt stresszteszt platformmal; végül képesek voltunk újraalkotni egy megoldást a nulláról sokkal több fejlesztési idő nélkül. Ha az alkalmazásodnak is speciális, szokatlan eszközökre van szüksége a stresszteszteléshez, fontold meg ezt a megközelítést. Az összes választott komponens, mint a Linode, WebdriverIO vagy Ansible könnyen lecserélhető a kedvenc megoldásodra. Földrajzilag elosztott stressztesztelés, teljesen realisztikus weboldal látogatók nehéz frontend logikával, alacsony költségű stressztesztelés - úgy tűnik, most már le vagy fedve!

Áron Novák

Áron Novák

WDIO Ansible Automatizált tesztelés Stressztesztelés Devops Drupal-planet