В предыдущей статье я объяснил, как использовать Google Cloud Functions для создания системы брендированного веб-сайта. Сегодня давайте посмотрим, как использовать движок Kubernetes для проведения сквозных тестов.
Интеграция сборки и развертывания вашего веб-сайта в программное обеспечение CI / CD довольно часто вызывает некоторые проблемы.
В нашем случае, в Travix, мы работаем с GoCD для управления нашим CI / CD. Для управления нашей инфраструктурой (CI / CD / front-end production) мы полагаемся на Google Cloud Platform, а точнее на GKE.
Travix - это OTA (Интернет-туристическое агентство), которое управляет до 40 веб-сайтами по всему миру под брендами Cheaptickets, Vayama, Budgetair, Vliegwinkel и Flugladen.
GoCD работает с системой конвейеров. В конвейере может быть один или несколько этапов, в рамках которых может быть одно или несколько заданий.
Чтобы выполнить конвейер, его этапы выполняются агентом. Агент - это работник с заданным набором функций. Каждый этап развертывается в собственном рабочем пространстве. Он выполняет свою работу до конца, после чего завершается. На одном агенте может выполняться множество этапов.
Когда количество конвейеров увеличивается, также увеличивается сложность ваших тестов и время, необходимое для их выполнения. Вы также можете развернуть дополнительную инфраструктуру для своих тестов на своем сервере. Например, для ваших сквозных тестов вы можете развернуть свой собственный Selenium Grid. Однако это делает вашу архитектуру немного более сложной в обслуживании.
1. Контекст
Здесь мы поговорим о интерфейсных e2e-тестах. Допустим, у нас есть 50 фирменных веб-сайтов с общим исходным кодом, у которых есть собственный набор настроек / функций, которые делают их немного уникальными.
Подобно тому, что я описал ранее, если вы хотите добавить несколько тестов в свой поток выпуска, это может стать довольно огромной инфраструктурой, довольно сложной и довольно болезненной в использовании и обслуживании.
Наши тесты e2e написаны на NodeJS с использованием фреймворка webdriverio. Мы используем Babel для небольшого синтаксиса (хипстеры JavaScript). Проект хранится в репозитории Git. Основными пользователями тестов e2e являются наши фронтенд-разработчики. Они разрабатывают пользовательский интерфейс, а затем создают объекты страницы для написания сценариев для тестов.
Каждый раз, когда мы создаем один из 50 веб-сайтов, мы запускаем тесты e2e для этого веб-сайта с использованием Chrome и Firefox. Пока они не пройдут, сайт не будет запущен в рабочую среду. Это означает, что каждый раз, когда я хочу выпустить все веб-сайты, я должен дважды клонировать свой репозиторий, выполняя 2 npm install
для каждого сайта. Я так провожу свои тесты e2e в 2 * 50 раз.
100-кратный запуск вашего этапа может привести к некоторым проблемам с вашими агентами (потребление памяти из-за использования среды выполнения Babel, ~ 1 ГБ ОЗУ, доступность ваших агентов). Говоря о времени, 1 этап занимает примерно от 4 до 5 минут. Поэтому нам нужно подождать почти 100 * 4,5 минут, прежде чем мы сможем выпустить все сайты.
Как сократить время, необходимое для запуска наших тестов e2e? Как сделать их более дружественными к памяти? Как снизить сложность нашей инфраструктуры?
Теперь, когда мы представили ситуацию и записали поднятые ею вопросы, давайте на них ответим.
Мы могли бы сократить время выполнения не за счет увеличения количества агентов или их доступной памяти, а за счет использования агента для простого создания задания в нашем кластере Kubernetes.
Задание Kubernetes - это под (один или несколько контейнеров), выполняющий конкретную задачу, ограниченную по времени (в отличие от микросервиса или сервера, которые постоянно работают). Задание выполняется до тех пор, пока оно не завершится в результате сбоя или успеха.
Здесь мы хотим, чтобы одна работа выполняла тесты e2e, предоставляя браузер, когда это необходимо, и сохраняла отчеты из нашей платформы в некотором постоянном хранилище.
2. Давайте поместим вещи в контейнеры
Итак, сначала мы собираемся поместить наши тесты в контейнер. Для этого давайте сначала опишем, как мы выполняем сквозное тестирование. У нас есть линтер для тестирования исходного кода, затем у нас есть компиляция и, наконец, мы можем запускать наши скрипты с NodeJS.
Вот файл Docker, в котором используются многоступенчатые сборки для создания контейнера, и файл JavaScript, тестирующий «наш сайт» с его package.json
:
Чтобы создать образ, убедитесь, что вы используете Docker 17.09+ (я использую Docker 17.12.0-ce-mac45, канал Edge). Клонируйте Gist в своей рабочей области, cd в каталог, запустите docker-machine
, создайте исходную среду и создайте образ:
$> git clone https://gist.github.com/1e4518f09a4d28b32d78bcce1411c6e3.git gist $> cd gist $> docker-machine start $> eval $(docker-machine env) $> docker build -t my-repository-name/e2etest .
Вот он, ваш контейнер построен. Чтобы протестировать наши скрипты в Chrome, давайте смонтируем образ, содержащий браузер Chrome. Для этого мы будем использовать официальный автономный образ Chrome от SeleniumHQ.
Запустите эту команду в своем терминале:
$> docker run -d -p 4444:4444 -v /dev/shm:/dev/shm selenium/standalone-chrome
Мы запускаем образ, выполняющий Chrome как демон. Вы можете получить доступ к браузеру через порт 4444, используя IP-адрес вашего Docker-компьютера (выполните docker-machine ip
в своем терминале, чтобы увидеть IP-адрес).
Теперь давайте запустим наши тесты на нашей машине:
$> docker run -e BROWSER=chrome -e SELENIUM_HOST=$(docker-machine ip) -e SELENIUM_PORT=4444 -t my-repository-name/e2etest Title is: WebdriverIO at DuckDuckGo
Как видите, когда мы запускаем контейнер в неинтерактивном режиме, он выполняет наши сквозные тесты.
Давайте посмотрим, как управлять отчетами. Мы добавляем в наши сценарии e2e вызов метода saveScreenshot
Webdriver.io, чтобы создать снимок экрана из браузера страницы, которую мы тестируем. Мы также выводим некоторые подробности в каталог report
.
Теперь ваш сценарий должен выглядеть так:
Теперь, когда мы выводим журналы и создаем снимок экрана в каталоге report
, давайте подключим том к образу Docker для хранения файлов.
Мы будем использовать крошечный скрипт с использованием fs.watch
. Каждый раз, когда файл изменяется, сценарий помещает его в Google Cloud Storage Bucket. Таким образом, у нас есть постоянное хранилище для наших отчетов.
Вот небольшой скрипт, который просматривает файлы и загружает их в корзину со своим файлом Docker и его package.json
.
Клонируйте Gist в своем рабочем пространстве, создайте контейнер.
$> git clone https://gist.github.com/3efff749b08f0b95ca0fa907a7c56597.git gist-catcher $> cd gist-catcher $> docker build -t my-repository-name/catcher .
Когда это будет сделано, создайте каталог report
в той же папке.
Теперь давайте запустим контейнер в терминале и воспользуемся другим терминалом для запуска тестов e2e.
Вы должны увидеть что-то эквивалентное:
Терминал 1
$> docker run -e BROWSER=chrome -e SELENIUM_HOST=$(docker-machine ip) -e SELENIUM_PORT=4444 -v /path/to/workspace/report-catcher/report:/var/app/report -t my-repository-name/e2etest Title is: WebdriverIO at DuckDuckGo
Терминал 2
$> docker run -v $PWD/report:/var/app/report -t my-repository-name/catcher chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change screenshot.jpg change screenshot.jpg change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change chrome.2018-01-15T23-34-04.1.log change
Контейнеры catcher
и e2etest
связаны с помощью тома $PWD/report
.
3. Развертывание наших контейнеров в Kubernetes
Теперь, когда у нас все запущено на моем компьютере, давайте развернем контейнеры в кластере Kubernetes.
Предположим, что у вас уже есть кластер Kubernetes. Кроме того, мы предполагаем, как и мы, что вы работаете с облачной платформой Google и имеете достаточные учетные данные для создания учетной записи службы приложений, которая может вести запись в сегменте облачного хранилища Google. На следующих шагах потребуется также доступ к репозиторию Docker для отправки образов.
Для этого сделайте следующее:
$> docker push my-repository-name/e2etest $> docker push my-repository-name/catcher
Начнем манифест Kubernetes.
Сначала мы создаем заданное пространство имен для запуска наших заданий и объявляем основной контейнер нашего задания.
apiVersion: v1 kind: Namespace metadata: name: my-end-to-end-tests --- apiVersion: batch/v1 kind: Job metadata: name: e2e-chrome-mysite-1 namespace: my-end-to-end-tests spec: parallelism: 1 completions: 1 backoffLimit: 3 activeDeadlineSeconds: 20 template: metadata: labels: app: "e2e-chrome-mysite-1" run: "1" version: "1" spec: restartPolicy: Never containers: - name: chrome image: my-repository-name/container-e2e env: - name: "TARGET_URL" value: "http://www.mywebsite.com" - name: "BROWSER" value: "chrome" - name: "SELENIUM_HOST" value: "127.0.0.1" - name: "SELENIUM_PORT" value: "4444"
Мы (пытаемся) ограничить нашу работу запуском только один раз в кластере, установив parallelism
на 1 и completion
на 1. Я имею в виду try
, потому что может случиться так, что модуль будет запущен дважды. Мы также ограничиваем количество backoffLimit
и activeDeadlineSeconds
, чтобы избежать бесконечного цикла при попытке запустить модуль.
SELENIUM_HOST
установлен на 127.0.0.1
. Автономный браузер будет выполнять ту же работу, поэтому все контейнеры используют один и тот же IP-адрес.
Мы должны создать секрет, необходимый для доступа к API GCP. Он нужен для нашего контейнера sidecar, который отправляет отчеты в Google Cloud Storage Bucket. Как и то, что мы делали локально, мы также разделяем том с предыдущим контейнером.
После объявления пространства имен добавьте секретное объявление:
--- apiVersion: v1 kind: Secret metadata: name: e2e-chrome-mysite-1-secrets namespace: my-end-to-end-tests labels: app: e2e-chrome-mysite-1 type: Opaque data: gcloud.json: "my-base64-encoded-file" ---
Добавьте следующее в конце предыдущего объявления контейнера:
volumeMounts: - name: reports mountPath: /var/app/reports
Объявим наш новый контейнер:
- name: catcher image: my-repository-name/catcher env: - name: "VOLUME_PATH" value: "/var/app/report" - name: "GCLOUD_APPLICATION_CREDIENTIALS" value: "/home/.config/gcloud/auth.json" - name: "GCLOUD_BUCKET_NAME" value: "my-bucket" - name: "GCLOUD_PROJECT_NAME" value: "my-project" volumeMounts: - name: reports mountPath: /var/app/reports - name: secrets mountPath: /home/.config/gcloud
А теперь объявляем свои объемы:
volumes: # extended memory for the browser - name: extended-mem hostPath: path: /dev/shm # shared volume where we write the report from the main container. - name: reports emptyDir: {} # secret volume containing the app credentials for the side container pushing to GCP - name: secrets secret: secretName: e2e-chrome-mysite-1-secrets
Как вы уже заметили, я добавил еще один том /dev/shm
. Он используется для расширения памяти последнего необходимого нам контейнера sidecar - браузера.
Браузер может появиться откуда угодно, как только ваш кластер сможет вытащить изображение.
Подобно тому, что я написал ранее, мы будем использовать стандартный образ SeleniumHQ Docker для автономного Chrome.
Добавьте эти строки после объявления контейнера-улавливателя:
- name: chrome-browser image: selenium/standalone-chrome:3.8.1-erbium ports: - containerPort: 4444 env: - name: SCREEN_WIDTH value: "1680" - name: SCREEN_HEIGHT value: "1050" volumeMounts: - mountPath: /dev/shm name: extended-mem
Мы предоставляем контейнеру объем /dev/shm
, чтобы уменьшить вероятность сбоя браузера.
Вы можете найти полный манифест здесь.
Мы готовы развернуть свою работу. Давайте запустим в терминале следующие команды:
$> kubectl apply -f ./kubernetes.yaml
Если вы запустите kubectl get ns
, вы увидите, что ваше пространство имен было правильно создано.
Давайте теперь посмотрим, запустился ли модуль:
$> kubectl get po -n my-end-to-end-tests -w
Вы должны увидеть статус своей работы.
Чтобы просмотреть журналы, выполните следующую команду:
$> kubectl logs e2e-chrome-mysite-1-rand1 chrome -n my-end-to-end-tests
Вы можете увидеть журналы, поступающие из каждого контейнера, просто поиграйте со вторым параметром kubectl logs
, значение может быть chrome
, chrome-browser
или catcher
.
Когда ваш основной контейнер завершится, вы можете узнать его статус и код выхода, используя следующий маленький скрипт bash:
Чтобы пойти дальше, вы можете автоматизировать все шаги, которые мы прошли для запуска задания и его завершения в вашем CI.
Здесь мы сделали все вручную (запуск задания, завершение задания). Однако сегодня вы можете найти некоторые инструменты для управления и мониторинга ваших заданий Kubernetes, такие как Azure Brigade или Apache Airflow.
Кроме того, поскольку мы отправляем наши отчеты в корзину Google Cloud Storage, вы можете легко реализовать их анализ с помощью функции Google Cloud. Исходя из этого, вы можете создать собственную систему оповещений с помощью Slack.
Но, несмотря на все это, вы должны выбрать, что вы хотите делать :)
Наслаждаться !