GitLab Shell 運行程式。 使用 Docker Compose 競爭性地啟動經過測試的服務

GitLab Shell 運行程式。 使用 Docker Compose 競爭性地啟動經過測試的服務

測試人員和開發人員都會對本文感興趣,但主要針對自動化專家,他們面臨著在基礎設施資源不足和/或缺乏容器編排的情況下設置 GitLab CI/CD 進行整合測試的問題平台。 我將告訴您如何在單一 GitLab shell 運行器上使用 docker compose 設定測試環境的部署,以便在部署多個環境時,啟動的服務不會相互幹擾。


Содержание

先決條件

  1. 在我的實踐中,經常發生整合測試被「對待」在專案上的情況。 通常第一個也是最重要的問題是 CI 管道,其中整合測試 正在開發中 服務在開發/階段環境中執行。 這導致了很多問題:

    • 由於整合測試過程中特定業務有缺陷,測試電路可能會因資料損壞而損壞。 在某些情況下,發送帶有損壞的 JSON 格式的請求會導致服務崩潰,從而導致展台完全無法運作。
    • 隨著測試資料的增加,測試電路速度減慢。 我認為描述清理/回滾資料庫的範例是沒有意義的。 在我的實踐中,我還沒有遇到這個過程順利的專案。
    • 測試一般系統設定時有破壞測試電路功能的風險。 例如,使用者/群組/密碼/應用程式原則。
    • 來自自動化測試的測試數據讓手動測試人員的生活變得困難。

    有人會說,好的自動測試應該要自行清理資料。 我有反對的理由:

    • 動態支架使用起來非常方便。
    • 並非每個物件都可以透過 API 從系統中刪除。 例如,刪除物件的呼叫未實現,因為它與業務邏輯相矛盾。
    • 當透過API建立物件時,會建立大量的元數據,這些元資料很難刪除。
    • 如果測試之間存在依賴關係,那麼執行測試後清理資料的過程就會變得令人頭痛。
    • 對 API 的額外呼叫(在我看來,這是不合理的)。
    • 主要論點:何時開始直接從資料庫清除測試資料。 這變成了真正的PK/FK馬戲團! 我們聽到開發人員說:“我剛剛添加/刪除/重命名了一個標誌,為什麼 100500 個整合測試被捕獲?”

    在我看來,最好的解決方案是動態環境。

  2. 很多人使用docker-compose來運行測試環境,但是很少人在CI/CD中進行整合測試時使用docker-compose。 這裡我沒有考慮 kubernetes、swarm 和其他容器編排平台。 並非每個公司都有它們。 如果 docker-compose.yml 是通用的那就太好了。
  3. 即使我們有自己的 QA 運行器,我們如何確保透過 docker-compose 啟動的服務不會互相干擾?
  4. 如何收集被測試服務的日誌?
  5. 如何清潔轉輪?

我的專案有自己的 GitLab 運行程序,在開發過程中遇到了這些問題 Java客戶端測試軌。 更準確地說,是在執行整合測試時。 下面我們將使用該專案中的範例來解決這些問題。

返回內容

GitLab Shell 運行器

對於跑步者,我推薦具有 4 個 vCPU、4 GB RAM、50 GB HDD 的 Linux 虛擬機器。
網路上有很多關於設定gitlab-runner的資料,簡單介紹一下:

  • 透過 SSH 登入機器
  • 如果您的 RAM 小於 8 GB,那麼我建議 使交換空間為 10 GB這樣 OOM 殺手就不會因為記憶體不足而終止我們的任務。 當同時啟動超過 5 個任務時,可能會發生這種情況。 任務將進展緩慢但穩定。

    OOM 殺手的範例

    如果您在任務日誌中看到 bash: line 82: 26474 Killed,然後在跑步者上執行 sudo dmesg | grep 26474

    [26474]  1002 26474  1061935   123806     339        0             0 java
    Out of memory: Kill process 26474 (java) score 127 or sacrifice child
    Killed process 26474 (java) total-vm:4247740kB, anon-rss:495224kB, file-rss:0kB, shmem-rss:0kB

    如果圖片看起來像這樣,那麼要么添加交換空間,要么添加 RAM。

  • 安裝 gitlab-runner, 搬運工人, 泊塢窗,撰寫, 製作。
  • 新增用戶 gitlab-runner 到小組 docker
    sudo groupadd docker
    sudo usermod -aG docker gitlab-runner
  • 登記 gitlab-runner。
  • 開啟進行編輯 /etc/gitlab-runner/config.toml 並添加

    concurrent=20
    [[runners]]
      request_concurrency = 10

    這將允許您在一個跑步者上運行並行任務。 閱讀更多 這裡.
    如果您有更強大的機器,例如 8 個 vCPU、16 GB RAM,那麼這些數字至少可以增加 2 倍。 但這一切都取決於該跑者究竟會發射什麼以及發射的數量。

這就夠了。

返回內容

準備 docker-compose.yml

主要任務是一個通用的 docker-compose.yml,開發人員/測試人員可以在本地和 CI 管道中使用它。

首先,我們為 CI 制定獨特的服務名稱。 GitLab CI 中獨特的變數之一是變數 CI_JOB_ID。 如果您指定 container_name 有意義 "service-${CI_JOB_ID:-local}",那麼在這種情況下:

  • 如果 CI_JOB_ID 未在環境變數中定義,
    那麼服務名稱將是 service-local
  • 如果 CI_JOB_ID 在環境變數中定義(例如 123),
    那麼服務名稱將是 service-123

其次,我們為已推出的服務建立一個通用網路。 這為我們在運行多個測試環境時提供了網路級隔離。

networks:
  default:
    external:
      name: service-network-${CI_JOB_ID:-local}

事實上,這是成功的第一步 =)

我的 docker-compose.yml 帶有註解的範例

version: "3"

# Для корректной работы web (php) и fmt нужно, 
# чтобы контейнеры имели общий исполняемый контент.
# В нашем случае, это директория /var/www/testrail
volumes:
  static-content:

# Изолируем окружение на сетевом уровне
networks:
  default:
    external:
      name: testrail-network-${CI_JOB_ID:-local}

services:
  db:
    image: mysql:5.7.22
    # Каждый container_name содержит ${CI_JOB_ID:-local}
    container_name: "testrail-mysql-${CI_JOB_ID:-local}"
    environment:
      MYSQL_HOST: db
      MYSQL_DATABASE: mydb
      MYSQL_ROOT_PASSWORD: 1234
      SKIP_GRANT_TABLES: 1
      SKIP_NETWORKING: 1
      SERVICE_TAGS: dev
      SERVICE_NAME: mysql
    networks:
    - default

  migration:
    image: registry.gitlab.com/touchbit/image/testrail/migration:latest
    container_name: "testrail-migration-${CI_JOB_ID:-local}"
    links:
    - db
    depends_on:
    - db
    networks:
    - default

  fpm:
    image: registry.gitlab.com/touchbit/image/testrail/fpm:latest
    container_name: "testrail-fpm-${CI_JOB_ID:-local}"
    volumes:
    - static-content:/var/www/testrail
    links:
    - db
    networks:
    - default

  web:
    image: registry.gitlab.com/touchbit/image/testrail/web:latest
    container_name: "testrail-web-${CI_JOB_ID:-local}"
    # Если переменные TR_HTTP_PORT или TR_HTTPS_PORTS не определены,
    # то сервис поднимается на 80 и 443 порту соответственно.
    ports:
      - ${TR_HTTP_PORT:-80}:80
      - ${TR_HTTPS_PORT:-443}:443
    volumes:
      - static-content:/var/www/testrail
    links:
      - db
      - fpm
    networks:
      - default

本地運行範例

docker-compose -f docker-compose.yml up -d
Starting   testrail-mysql-local     ... done
Starting   testrail-migration-local ... done
Starting   testrail-fpm-local       ... done
Recreating testrail-web-local       ... done

但在 CI 中啟動並不是一切都這麼簡單。

返回內容

準備 Makefile

我使用Makefile是因為它對於本機環境管理和CI都非常方便。 更多網路評論

# У меня в проектах все вспомогательные вещи лежат в директории `.indirect`,
# в том числе и `docker-compose.yml`

# Использовать bash с опцией pipefail 
# pipefail - фейлит выполнение пайпа, если команда выполнилась с ошибкой
SHELL=/bin/bash -o pipefail

# Останавливаем контейнеры и удаляем сеть
docker-kill:
    docker-compose -f $${CI_JOB_ID:-.indirect}/docker-compose.yml kill
    docker network rm network-$${CI_JOB_ID:-testrail} || true

# Предварительно выполняем docker-kill 
docker-up: docker-kill
    # Создаем сеть для окружения 
    docker network create network-$${CI_JOB_ID:-testrail}
    # Забираем последние образы из docker-registry
    docker-compose -f $${CI_JOB_ID:-.indirect}/docker-compose.yml pull
    # Запускаем окружение
    # force-recreate - принудительное пересоздание контейнеров
    # renew-anon-volumes - не использовать volumes предыдущих контейнеров
    docker-compose -f $${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
    # Ну и, на всякий случай, вывести что там у нас в принципе запущено на машинке
    docker ps

# Коллектим логи сервисов
docker-logs:
    mkdir ./logs || true
    docker logs testrail-web-$${CI_JOB_ID:-local}       >& logs/testrail-web.log
    docker logs testrail-fpm-$${CI_JOB_ID:-local}       >& logs/testrail-fpm.log
    docker logs testrail-migration-$${CI_JOB_ID:-local} >& logs/testrail-migration.log
    docker logs testrail-mysql-$${CI_JOB_ID:-local}     >& logs/testrail-mysql.log

# Очистка раннера
docker-clean:
    @echo Останавливаем все testrail-контейнеры
    docker kill $$(docker ps --filter=name=testrail -q) || true
    @echo Очистка докер контейнеров
    docker rm -f $$(docker ps -a -f --filter=name=testrail status=exited -q) || true
    @echo Очистка dangling образов
    docker rmi -f $$(docker images -f "dangling=true" -q) || true
    @echo Очистка testrail образов
    docker rmi -f $$(docker images --filter=reference='registry.gitlab.com/touchbit/image/testrail/*' -q) || true
    @echo Очистка всех неиспользуемых volume
    docker volume rm -f $$(docker volume ls -q) || true
    @echo Очистка всех testrail сетей
    docker network rm $(docker network ls --filter=name=testrail -q) || true
    docker ps

檢查

進行docker-up

$ make docker-up 
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
Killing testrail-web-local   ... done
Killing testrail-fpm-local   ... done
Killing testrail-mysql-local ... done
docker network rm network-${CI_JOB_ID:-testrail} || true
network-testrail
docker network create network-${CI_JOB_ID:-testrail}
d2ec063324081c8bbc1b08fd92242c2ea59d70cf4025fab8efcbc5c6360f083f
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Pulling db        ... done
Pulling migration ... done
Pulling fpm       ... done
Pulling web       ... done
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Recreating testrail-mysql-local ... done
Recreating testrail-fpm-local       ... done
Recreating testrail-migration-local ... done
Recreating testrail-web-local       ... done
docker ps
CONTAINER ID  PORTS                                     NAMES
a845d3cb0e5a  0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp  testrail-web-local
19d8ef001398  9000/tcp                                  testrail-fpm-local
e28840a2369c  3306/tcp, 33060/tcp                       testrail-migration-local
0e7900c23f37  3306/tcp                                  testrail-mysql-local

製作 docker 日誌

$ make docker-logs
mkdir ./logs || true
mkdir: cannot create directory ‘./logs’: File exists
docker logs testrail-web-${CI_JOB_ID:-local}       >& logs/testrail-web.log
docker logs testrail-fpm-${CI_JOB_ID:-local}       >& logs/testrail-fpm.log
docker logs testrail-migration-${CI_JOB_ID:-local} >& logs/testrail-migration.log
docker logs testrail-mysql-${CI_JOB_ID:-local}     >& logs/testrail-mysql.log

GitLab Shell 運行程式。 使用 Docker Compose 競爭性地啟動經過測試的服務

返回內容

準備.gitlab-ci.yml

運行整合測試

Integration:
  stage: test
  tags:
    - my-shell-runner
  before_script:
    # Аутентифицируемся в registry
    - docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
    # Генерируем псевдоуникальные TR_HTTP_PORT и TR_HTTPS_PORT
    - export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
    - export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
    # создаем директорию с идентификатором задачи
    - mkdir ${CI_JOB_ID}
    # копируем в созданную директорию наш docker-compose.yml
    # чтобы контекст был разный для каждой задачи
    - cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
  script:
    # поднимаем наше окружение
    - make docker-up
    # запускаем тесты исполняемым jar (у меня так)
    - java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
    # или в контейнере
    - docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
  after_script:
    # собираем логи
    - make docker-logs
    # останавливаем окружение
    - make docker-kill
  artifacts:
    # сохраняем логи
    when: always
    paths:
      - logs
    expire_in: 30 days

運行此類任務的結果是,工件中的日誌目錄將包含服務和測試日誌。 這在出現錯誤時非常方便。 每個並行測試都會寫入自己的日誌,但我將單獨討論這一點。

GitLab Shell 運行程式。 使用 Docker Compose 競爭性地啟動經過測試的服務

返回內容

清潔轉輪

該任務將僅根據時間表啟動。

stages:
- clean
- build
- test

Clean runner:
  stage: clean
  only:
    - schedules
  tags:
    - my-shell-runner
  script:
    - make docker-clean

接下來,前往我們的 GitLab 專案 -> CI/CD -> Schedules -> New Schedule 並新增新的時間表

GitLab Shell 運行程式。 使用 Docker Compose 競爭性地啟動經過測試的服務

返回內容

導致

在 GitLab CI 啟動 4 個任務
GitLab Shell 運行程式。 使用 Docker Compose 競爭性地啟動經過測試的服務

在最後一個整合測試任務的日誌中,我們看到來自不同任務的容器

CONTAINER ID  NAMES
c6b76f9135ed  testrail-web-204645172
01d303262d8e  testrail-fpm-204645172
2cdab1edbf6a  testrail-migration-204645172
826aaf7c0a29  testrail-mysql-204645172
6dbb3fae0322  testrail-web-204645084
3540f8d448ce  testrail-fpm-204645084
70fea72aa10d  testrail-mysql-204645084
d8aa24b2892d  testrail-web-204644881
6d4ccd910fad  testrail-fpm-204644881
685d8023a3ec  testrail-mysql-204644881
1cdfc692003a  testrail-web-204644793
6f26dfb2683e  testrail-fpm-204644793
029e16b26201  testrail-mysql-204644793
c10443222ac6  testrail-web-204567103
04339229397e  testrail-fpm-204567103
6ae0accab28d  testrail-mysql-204567103
b66b60d79e43  testrail-web-204553690
033b1f46afa9  testrail-fpm-204553690
a8879c5ef941  testrail-mysql-204553690
069954ba6010  testrail-web-204553539
ed6b17d911a5  testrail-fpm-204553539
1a1eed057ea0  testrail-mysql-204553539

更詳細的日誌

$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /home/gitlab-runner/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
$ export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
$ export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
$ mkdir ${CI_JOB_ID}
$ cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
docker network rm testrail-network-${CI_JOB_ID:-local} || true
Error: No such network: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Pulling web       ... done
Pulling fpm       ... done
Pulling migration ... done
Pulling db        ... done
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Creating volume "204645172_static-content" with default driver
Creating testrail-mysql-204645172 ... 
Creating testrail-mysql-204645172 ... done
Creating testrail-migration-204645172 ... done
Creating testrail-fpm-204645172       ... done
Creating testrail-web-204645172       ... done
docker ps
CONTAINER ID        IMAGE                                                          COMMAND                  CREATED              STATUS              PORTS                                           NAMES
c6b76f9135ed        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   13 seconds ago       Up 1 second         0.0.0.0:51148->80/tcp, 0.0.0.0:25426->443/tcp   testrail-web-204645172
01d303262d8e        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   16 seconds ago       Up 13 seconds       9000/tcp                                        testrail-fpm-204645172
2cdab1edbf6a        registry.gitlab.com/touchbit/image/testrail/migration:latest   "docker-entrypoint.s…"   16 seconds ago       Up 13 seconds       3306/tcp, 33060/tcp                             testrail-migration-204645172
826aaf7c0a29        mysql:5.7.22                                                   "docker-entrypoint.s…"   18 seconds ago       Up 16 seconds       3306/tcp                                        testrail-mysql-204645172
6dbb3fae0322        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   36 seconds ago       Up 22 seconds       0.0.0.0:44202->80/tcp, 0.0.0.0:20151->443/tcp   testrail-web-204645084
3540f8d448ce        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   38 seconds ago       Up 35 seconds       9000/tcp                                        testrail-fpm-204645084
70fea72aa10d        mysql:5.7.22                                                   "docker-entrypoint.s…"   40 seconds ago       Up 37 seconds       3306/tcp                                        testrail-mysql-204645084
d8aa24b2892d        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   About a minute ago   Up 53 seconds       0.0.0.0:31103->80/tcp, 0.0.0.0:43872->443/tcp   testrail-web-204644881
6d4ccd910fad        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   About a minute ago   Up About a minute   9000/tcp                                        testrail-fpm-204644881
685d8023a3ec        mysql:5.7.22                                                   "docker-entrypoint.s…"   About a minute ago   Up About a minute   3306/tcp                                        testrail-mysql-204644881
1cdfc692003a        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   About a minute ago   Up About a minute   0.0.0.0:44752->80/tcp, 0.0.0.0:23540->443/tcp   testrail-web-204644793
6f26dfb2683e        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   About a minute ago   Up About a minute   9000/tcp                                        testrail-fpm-204644793
029e16b26201        mysql:5.7.22                                                   "docker-entrypoint.s…"   About a minute ago   Up About a minute   3306/tcp                                        testrail-mysql-204644793
c10443222ac6        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   5 hours ago          Up 5 hours          0.0.0.0:57123->80/tcp, 0.0.0.0:31657->443/tcp   testrail-web-204567103
04339229397e        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   5 hours ago          Up 5 hours          9000/tcp                                        testrail-fpm-204567103
6ae0accab28d        mysql:5.7.22                                                   "docker-entrypoint.s…"   5 hours ago          Up 5 hours          3306/tcp                                        testrail-mysql-204567103
b66b60d79e43        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   5 hours ago          Up 5 hours          0.0.0.0:56321->80/tcp, 0.0.0.0:58749->443/tcp   testrail-web-204553690
033b1f46afa9        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   5 hours ago          Up 5 hours          9000/tcp                                        testrail-fpm-204553690
a8879c5ef941        mysql:5.7.22                                                   "docker-entrypoint.s…"   5 hours ago          Up 5 hours          3306/tcp                                        testrail-mysql-204553690
069954ba6010        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon of…"   5 hours ago          Up 5 hours          0.0.0.0:32869->80/tcp, 0.0.0.0:16066->443/tcp   testrail-web-204553539
ed6b17d911a5        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoi…"   5 hours ago          Up 5 hours          9000/tcp                                        testrail-fpm-204553539
1a1eed057ea0        mysql:5.7.22                                                   "docker-entrypoint.s…"   5 hours ago          Up 5 hours          3306/tcp                                        testrail-mysql-204553539

所有任務均順利完成

任務工件包含服務和測試日誌
GitLab Shell 運行程式。 使用 Docker Compose 競爭性地啟動經過測試的服務

GitLab Shell 運行程式。 使用 Docker Compose 競爭性地啟動經過測試的服務

一切看起來都很美好,但也有細微差別。 在整合測試運行時可以強制取消管道,在這種情況下運行的容器不會停止。 有時您需要清潔流道。 不幸的是,GitLab CE 的改進任務仍處於狀態 已提交

但是我們添加了根據時間表啟動任務,並且沒有人禁止我們手動運行它。
前往我們的專案 -> CI/CD -> Schedules 並運行任務 Clean runner

GitLab Shell 運行程式。 使用 Docker Compose 競爭性地啟動經過測試的服務

合計:

  • 我們有一名砲彈跑者。
  • 任務和環境之間不存在衝突。
  • 我們並行運行具有整合測試的任務。
  • 您可以在本機或容器中執行整合測試。
  • 收集服務和測試日誌並將其附加到管道任務。
  • 可以從舊的 Docker 映像中清除運行器。

設定時間約 2 小時。
事實上,僅此而已。 我會很高興收到回饋。

返回內容

來源: www.habr.com

添加評論