GitLab Shell Runner. Lansare competitivă a serviciilor testate folosind Docker Compose

GitLab Shell Runner. Lansare competitivă a serviciilor testate folosind Docker Compose

Acest articol va fi de interes atât pentru testeri, cât și pentru dezvoltatori, dar este destinat în principal specialiștilor în automatizare care se confruntă cu problema instalării GitLab CI/CD pentru testarea integrării în condiții de resurse insuficiente de infrastructură și/sau absența unei orchestrații container. platformă. Vă voi spune cum să configurați implementarea mediilor de testare folosind docker compose pe un singur runner shell GitLab și astfel încât, atunci când implementați mai multe medii, serviciile lansate să nu interfereze între ele.


Conținut

Предпосылки

  1. În practica mea, sa întâmplat adesea ca testarea integrării să fie „tratată” pe proiecte. Și adesea prima și cea mai semnificativă problemă este conducta CI, în care testarea integrării în curs de dezvoltare serviciul(e) este realizat într-un mediu de dezvoltare/etapă. Acest lucru a cauzat destul de multe probleme:

    • Din cauza defectelor unui anumit serviciu în timpul testării integrării, circuitul de testare poate fi deteriorat de date întrerupte. Au existat cazuri când trimiterea unei solicitări cu un format JSON rupt a blocat serviciul, ceea ce a făcut standul complet inoperabil.
    • Încetinirea circuitului de testare pe măsură ce datele de testare cresc. Cred că nu are sens să descriem un exemplu cu curățarea/rularea înapoi a bazei de date. În practica mea, nu am întâlnit un proiect în care această procedură a decurs fără probleme.
    • Risc de întrerupere a funcționalității circuitului de testare la testarea setărilor generale ale sistemului. De exemplu, utilizator/grup/parolă/politica aplicației.
    • Datele de testare din testele automate fac viața dificilă pentru testeri manuali.

    Unii vor spune că autotestele bune ar trebui să curețe datele după sine. Am argumente impotriva:

    • Standurile dinamice sunt foarte convenabile de utilizat.
    • Nu orice obiect poate fi eliminat din sistem prin intermediul API-ului. De exemplu, un apel de ștergere a unui obiect nu a fost implementat deoarece contrazice logica de afaceri.
    • Când se creează un obiect prin intermediul API-ului, se poate crea o cantitate mare de metadate, ceea ce este problematic de șters.
    • Dacă testele au dependențe între ele, atunci procesul de curățare a datelor după rularea testelor se transformă într-o durere de cap.
    • Apeluri suplimentare (și, în opinia mea, nejustificate) către API.
    • Și argumentul principal: când datele de testare încep să fie șterse direct din baza de date. Acesta se transformă într-un adevărat circ PK/FK! Auzim de la dezvoltatori: „Tocmai am adăugat/am eliminat/am redenumit un semn, de ce au fost prinși 100500 de teste de integrare?”

    După părerea mea, cea mai optimă soluție este un mediu dinamic.

  2. Mulți oameni folosesc docker-compose pentru a rula un mediu de testare, dar puțini oameni folosesc docker-compose atunci când efectuează teste de integrare în CI/CD. Și aici nu iau în calcul kubernetes, swarm și alte platforme de orchestrare a containerelor. Nu toate companiile le au. Ar fi bine dacă docker-compose.yml ar fi universal.
  3. Chiar dacă avem propriul nostru QA runner, cum ne putem asigura că serviciile lansate prin docker-compose nu interferează unele cu altele?
  4. Cum se colectează jurnalele serviciilor testate?
  5. Cum să curățați alergătorul?

Am propriul meu GitLab runner pentru proiectele mele și am întâlnit aceste întrebări în timpul dezvoltării Client Java pentru TestRail. Mai exact, atunci când rulează teste de integrare. Mai jos vom rezolva aceste probleme folosind exemple din acest proiect.

La conținut

GitLab Shell Runner

Pentru un alergător, recomand o mașină virtuală Linux cu 4 vCPU, 4 GB RAM, 50 GB HDD.
Există o mulțime de informații despre configurarea gitlab-runner pe Internet, așa că pe scurt:

  • Conectați-vă la mașină prin SSH
  • Daca ai mai putin de 8 GB RAM, atunci recomand face schimb de 10 GBastfel încât criminalul OOM să nu vină și să ne ucidă sarcinile din cauza lipsei de memorie RAM. Acest lucru se poate întâmpla atunci când mai mult de 5 sarcini sunt lansate simultan. Sarcinile vor progresa mai lent, dar constant.

    Exemplu cu OOM killer

    Dacă vedeți în jurnalele de activități bash: line 82: 26474 Killed, apoi executați doar pe alergător 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

    Și dacă imaginea arată cam așa, atunci fie adăugați swap, fie adăugați RAM.

  • Set gitlab-runner, docher, Docker-scriere, face.
  • Adăugarea unui utilizator gitlab-runner la grup docker
    sudo groupadd docker
    sudo usermod -aG docker gitlab-runner
  • Inregistreaza-te gitlab-runner.
  • Deschis pentru editare /etc/gitlab-runner/config.toml si adauga

    concurrent=20
    [[runners]]
      request_concurrency = 10

    Acest lucru vă va permite să rulați sarcini paralele pe un singur alergător. Citeşte mai mult aici.
    Dacă aveți o mașină mai puternică, de exemplu 8 vCPU, 16 GB RAM, atunci aceste numere pot fi făcute de cel puțin 2 ori mai mari. Dar totul depinde de ce anume va fi lansat pe acest alergător și în ce cantitate.

E de ajuns.

La conținut

Se pregătește docker-compose.yml

Sarcina principală este un docker-compose.yml universal, pe care dezvoltatorii/testerii îl pot folosi atât local, cât și în conducta CI.

În primul rând, creăm nume de servicii unice pentru CI. Una dintre variabilele unice din GitLab CI este variabila CI_JOB_ID. Dacă specificați container_name cu sens "service-${CI_JOB_ID:-local}", apoi în cazul:

  • dacă CI_JOB_ID nedefinit în variabilele de mediu,
    atunci numele serviciului va fi service-local
  • dacă CI_JOB_ID definit în variabilele de mediu (de exemplu 123),
    atunci numele serviciului va fi service-123

În al doilea rând, creăm o rețea comună pentru serviciile lansate. Acest lucru ne oferă izolarea la nivel de rețea atunci când rulăm mai multe medii de testare.

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

De fapt, acesta este primul pas către succes =)

Exemplu de docker-compose.yml meu cu comentarii

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

Exemplu de rulare locală

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

Dar nu totul este atât de simplu cu lansarea în CI.

La conținut

Pregătirea Makefile

Folosesc Makefile pentru că este foarte convenabil atât pentru managementul mediului local, cât și în CI. Mai multe comentarii online

# У меня в проектах все вспомогательные вещи лежат в директории `.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

Control

face 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

faceți docker-log-uri

$ 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 Runner. Lansare competitivă a serviciilor testate folosind Docker Compose

La conținut

Se pregătește .gitlab-ci.yml

Efectuarea de teste de integrare

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

Ca rezultat al rulării unei astfel de sarcini, directorul jurnalelor din artefacte va conține jurnalele de service și de testare. Ceea ce este foarte convenabil în caz de erori. Fiecare test în paralel își scrie propriul jurnal, dar despre asta voi vorbi separat.

GitLab Shell Runner. Lansare competitivă a serviciilor testate folosind Docker Compose

La conținut

Curățarea alergătorului

Sarcina va fi lansată numai conform unui program.

stages:
- clean
- build
- test

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

Apoi, accesați proiectul nostru GitLab -> CI/CD -> Programe -> Program nou și adăugați un program nou

GitLab Shell Runner. Lansare competitivă a serviciilor testate folosind Docker Compose

La conținut

Rezultat

Lansarea a 4 sarcini în GitLab CI
GitLab Shell Runner. Lansare competitivă a serviciilor testate folosind Docker Compose

În jurnalele ultimei sarcini cu teste de integrare vedem containere din diferite sarcini

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

Jurnal mai detaliat

$ 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

Toate sarcinile au fost finalizate cu succes

Artefactele sarcinilor conțin jurnalele de service și de testare
GitLab Shell Runner. Lansare competitivă a serviciilor testate folosind Docker Compose

GitLab Shell Runner. Lansare competitivă a serviciilor testate folosind Docker Compose

Totul pare a fi frumos, dar există o nuanță. Conducta poate fi anulată forțat în timpul testelor de integrare, caz în care rularea containerelor nu va fi oprită. Din când în când trebuie să curățați alergătorul. Din păcate, sarcina de îmbunătățire a GitLab CE este încă în stare Operatii Deschise

Dar am adăugat lansarea unei sarcini conform unui program și nimeni nu ne interzice să o rulăm manual.
Accesați proiectul nostru -> CI/CD -> Programe și rulați sarcina Clean runner

GitLab Shell Runner. Lansare competitivă a serviciilor testate folosind Docker Compose

Total:

  • Avem un shell runner.
  • Nu există conflicte între sarcini și mediu.
  • Executăm sarcini cu teste de integrare în paralel.
  • Puteți rula teste de integrare fie local, fie într-un container.
  • Jurnalele de service și de testare sunt colectate și atașate sarcinii de conductă.
  • Este posibil să curățați runnerul de imaginile vechi Docker.

Timpul de configurare este de aproximativ 2 ore.
Asta e tot, de fapt. Voi fi bucuros să primesc feedback.

La conținut

Sursa: www.habr.com

Adauga un comentariu