Тестовий сервер для команди розробників

Привіт! У даній статті я хочу поділитися досвідом розгортання тестового сервера для команди розробників. Коротко суть проблеми — є команда розробки і кілька проектів на php. Поки нас було мало і проект був по суті один, використовувався 1 тестовий сервер і щоб показати завдання замовнику — розробник «столбил» сервер на певний час. Якщо «вікон» по часу не було, то доводилось чекати. З часом зростав колектив і складність завдань, відповідно збільшувалася час перевірки та зайнятість тестового сервера, що негативно впливало на терміни виконання і премію. Тому довелося шукати рішення і воно під катом.

Ввідна

Що було:
1. Один тестовий сервер
2. Gitlab і redmine на іншому сервері
3. Бажання розібратися в проблемі
Всі сервери знаходяться в нашій локальній мережі, тестовий сервер недоступний ззовні.

Що потрібно:
1. Можливість тестувати кілька проектів/гілок одночасно
2. Розробник може зайти на сервер, до налаштувати його і при цьому не зламати нічого в інших
3. Все має бути максимально зручно і робитися по 1 кнопці бажано з gitlab (CI/CD).

Варіанти рішень

 

1. Один сервер, багато хостів

Найпростіший варіант. Використовуємо той же тестовий сервер, тільки розробнику потрібно створювати хост під кожну гілку/проект та вносити його до конфігурацію nginx/apache2.
Плюси:

  1. Швидко і всім зрозуміло
  2. Можна автоматизувати

Мінуси:

  1. Не виконується п. 2 з вимог — розробник може запустити оновлення бд і при певному збігу обставин покласти всі (Привіт Андрій!)
  2. Досить складна автоматизація з купою конфігураційних файлів

 

2. Кожному розробнику по серверу!

Виділяємо кожному з сервера і розробник сам відповідає за своє господарство.
Плюси:

  1. Розробник може повністю налаштувати сервер під свій проект

Мінуси:

  1. п. 2 вимог так і не виконується
  2. Дорого і ресурси можуть просто простоювати поки йде розробка, а не тестування
  3. Автоматизація ще складніше ніж в 1 п. з-за різних серверів

 

3. Контейнеризація — docker, kubernetes

Дана технологія все більше проникає у наше життя. Вдома я вже давно використовую для своїх проектів docker.
Docker — програмне забезпечення для автоматизації розгортання і керування додатками в середовищі віртуалізації на рівні операційної системи. Дозволяє «упаковувати» додаток з усім його оточенням і залежностями в контейнер, який може бути перенесений на будь-яку Linux-систему з підтримкою cgroups в ядрі, а також надає середовище з управління контейнерами.
Плюси:

  1. Використовується один сервер
  2. Виконуються всі пункти вимог

Мінуси:

  1. Образи і контейнери часом забирають досить багато місця, доводиться кроном чистити вже застарілі для звільнення місця.

Впровадження docker

При використанні gitlab дуже часто траплялися на очі налаштування AutoDevOps, kubernetes. Плюс бородаті дядьки на різних meetup розповідають як у них все круто працює з kubernetes. Тому було прийнято рішення спробувати розгорнути кластер на своїх потужностях, був выпрошен сервер (а тестовий чіпати не можна, там люди тестують) і понеслась!

Так як досвіду у мене з kubernetes 0, поділося все по мануали з намаганням зрозуміти, як всі ці кластери працюють. Через деякий час мені вдалося підняти кластер, але потім пішли проблеми з сертифікатами, ключами, та й взагалі з проблемою розгортання. Мені ж потрібно було рішення простіше, щоб навчити своїх колег з цим працювати (наприклад, той же відпустку не хочеться проводити сидить в скайпі і допомагає з настройкою). Тому kubernetes був залишений у спокої. Залишався сам docker і потрібно було знайти рішення для маршрутизації контейнерів. Так як їх можна було підняти на різних портах, то можна було б використовувати той же nginx для внутрішнього перенаправлення. Називається це зворотний проксі сервер.
Зворотний проксі-сервер — тип проксі-сервера, який ретранслює запити клієнтів із зовнішньої мережі на один або кілька серверів, логічно розташованих у внутрішній мережі. При цьому для клієнта це виглядає так, ніби необхідні ресурси знаходяться безпосередньо на проксі-сервері.

Читайте також  Епізод 1. Вартість Hack'а

Зворотний проксі-сервер

Щоб не винаходити велосипед, я почав шукати готові рішення. І воно знайшлося — це traefik.
Træfik — це сучасний зворотний проксі HTTP і балансувальник навантаження, який спрощує розгортання микросервисов. Træfik інтегрується з існуючими інфраструктурними компонентами ( Docker, Swarm mode, Kubernetes, Marathon, Consul, Etcd, Rancher, Amazon ECS, …) і налаштовується автоматично і динамічно. Для роботи з docker достатньо вказати його сокет і все, далі Træfik сам знаходить всі контейнери і маршрутизацію до них (докладніше в «Пакуємо програми в docker»).
Конфігурація контейнера TræfikЗапускаю його через docker-compose.yml

версія: '3'

services:
traefik:
 image: traefik:latest # The official Traefik docker image
 command: --api --docker # Enables the web UI and tells Træfik to listen to docker
ports:
 - 443:443
 - 80:80 # The HTTP port
 - 8080:8080 # The Web UI (enabled by --api)
volumes:
 - /var/run/docker.sock:/var/run/docker.sock # So that Traefik can listen to the events Docker
 - /opt/traefik/traefik.toml:/traefik.toml
 - /opt/traefik/certs/:/certs/
networks:
 - proxy
 container_name: traefik
 restart: always
networks:
proxy:
 external: true

Тут ми повідомляємо проксі, що потрібно слухати порти 80,443 і 8080 (веб морда проксі), монтуємо сокет докера, конфігураційний файл та папку з сертифікатами. Для зручності іменування тестових сайтів, ми вирішили зробити локальну доменну зону *.test. При зверненні до будь-якого сайту на неї, користувач потрапляє на наш тестовий сервер. Тому сертифікати в папці traefik самоподписаные, але він так підтримує let’s Encrypt.
Генерація сертифікатів

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout domain.key -out domain.crt

Перед стартом потрібно створити в докері мережа proxy (можете назвати по своєму).

docker network create proxy

Це буде мережа для зв’язку traefik з контейнерами php сайтів. Тому вказуємо її в параметрі networks сервісу і networks всього файлу вказавши в параметрі external: true.

Файл traefik.toml

debug = false

logLevel = "DEBUG"
defaultEntryPoints = ["https","http"] #точки входу
insecureSkipVerify = true #приймати самоподписаные сертифікати

[entryPoints]
[entryPoints.http]
 address = ":80"
[entryPoints.https]
 address = ":443"
[entryPoints.https.tls]

[docker]
endpoint = "unix:///var/run/docker.sock"
domain = "docker.localhost"
watch = true
exposedbydefault = false

Тут все досить просто — вказуємо точки входу http і https трафіку, не забудьте поставити insecureSkipVerify = true якщо сертифікати локальні. У секції entryPoints.https.tls можна не вказувати сертифікати, тоді traefik підставить свій сертифікат.
Можна запустити сервіс

docker-compose up -d

Якщо перейти за адресою site.test, то видасть помилку 404, оскільки цей домен не прив’язаний ні до якого контейнера.

Читайте також  Бензинові велосипеди або дивний пошук продуктів (e-commerce)

Пакуємо програми в docker

Тепер потрібно налаштувати контейнер з додатком, а саме:
1. вказати в мережах мережа proxy
2. додати labels з конфігурацією traefik

Нижче наведена конфігурація одного з додатків
docker-compose.yml додатки

версія: '3'
services:
app:
 build: data/docker/php #кастомний збірка сервера
 restart: always
 working_dir: /var/www/html/public
volumes:
 - ./:/var/www/html #монтування папки з сайтом
 - /home/develop/site-files/f:/var/www/html/public/f #монтування папки з завантаженнями для економії місця
links:
 - mailcatcher
 - memcached
 - mysql
labels:
 - traefik.enabled=true
 - traefik.frontend.rule=Host:TEST_DOMAIN,crm.TEST_DOMAIN,bonus.TEST_DOMAIN
 - traefik.docker.network=proxy
 - traefik.port=443
 - traefik.protocol=https
networks:
 - proxy
 - default

mailcatcher:
 image: schickling/mailcatcher:latest
 restart: always

memcached:
 image: memcached
 restart: always

mysql:
 image: mysql:5.7
 restart: always
 command: --max_allowed_packet=902505856 --sql-mode=""
environment:
 MYSQL_ROOT_PASSWORD: 12345
 MYSQL_DATABASE: site
volumes:
 - ./data/cache/mysql-db:/var/lib/mysql # збереження файлів БД на хості

phpmyadmin:
 image: phpmyadmin/phpmyadmin
 restart: always
links:
 - mysql
environment:
 MYSQL_USERNAME: root
 MYSQL_ROOT_PASSWORD: 12345
 PMA_ARBITRARY: 1
 PMA_HOST: mysql_1
labels:
 - traefik.enabled=true
 - traefik.frontend.rule=Host:pma.TEST_DOMAIN
 - traefik.docker.network=proxy
 - traefik.port=80
 - traefik.default.protocol=http
networks:
 - proxy
 - default
networks:
proxy:
 external: true

У сервісі app, у секції мережі потрібно вказати proxy і default, це означає, що він буде доступний в двох мережах, як видно з конфігурації я не пробрасываю порти назовні, все йде всередині мережі.
Далі конфігуруємо labels

 - traefik.enabled=true #включення traefik для даного сервісу
 - traefik.frontend.rule=Host:TEST_DOMAIN,crm.TEST_DOMAIN,bonus.TEST_DOMAIN #перерахування доменів для яких traefik буде перенаправляти запити сюди
 - traefik.docker.network=proxy #мережу для зв'язку
 - traefik.port=443 #порт, якщо у вас немає ssl то вкажіть 80 і нижче http
 - traefik.protocol=https #використовуваний протокол 
 #в секції phpmyadmin наведено приклад http підключення

У загальній секції networks потрібно вказати external: true
Константу TEST_DOMAIN потрібно замінити на домен, наприклад, site.test
Запускаємо додаток

docker-compose up -d

Тепер якщо зайти на домени site.test, crm.site.test, bonus.site.test можна побачити робочий сайт. А на домені pma.site.test буде phpmyadmin для зручної роботи з бд.

Налаштування GitLab

Створюємо обробник завдань, для цього запускаємо

gitlab-runner register

Вказуємо url gitlab, токен і через що буде виконуватися завдання (executors). Так як у мене тестовий і gitlab знаходяться на різних серверах, то вибираю ssh executor. Потрібно буде вказати адресу сервера та логін/пароль для підключення ssh.
Runner можна зробити прикріпленим до одному або декільком проектам. Так як у мене логіка роботи скрізь однакова, тому був створений shared runner (загальний для всіх проектів).
І останній штрих-це створити файл конфігурації CI
.gitlab-ci.yml

stages:
- build
- clear

#Конфігурація для develop
build_develop:
 stage: build #відносимо до етапу build
 tags: #якщо потрібно можна вказати теги
 - ssh-develop
 environment: #налаштування оточення, після розгортання вони виведуться Операції - Середовища проекту
 name: review/$CI_BUILD_REF_NAME #назва проекту
 url: https://site$CI_PIPELINE_ID.test #url для доступу до нього
 on_stop: clear
 when: manual
script:
 - cd ../ && cp -r $CI_PROJECT_NAME $CI_PIPELINE_ID && cd $CI_PIPELINE_ID #копіювання проекту в окрему папку
 - cp -r /home/develop/site-files/.ssh data/docker/php/.ssh #ключі ssh
 - sed -i -e "s/TEST_DOMAIN/site$CI_PIPELINE_ID.test/g" docker-compose.yml #Заміна імені домену
 - docker-compose down #на випадок ребілда
 - docker-compose up -d --build #білд образів
 - script -q -c "docker exec -it ${CI_PIPELINE_ID}_app_1 bash -c "cd ../ && php composer.phar install-prefer-dist "" #установка пакетів компосера
 - script -q -c "docker exec -it ${CI_PIPELINE_ID}_app_1 bash -c "cd ../ && php composer.phar first-install $CI_PIPELINE_ID"" #запуск скрипта первинної налаштування програми

#конфігурація для production
build_prod:
 stage: build
tags:
 - ssh-develop
environment:
 name: review/$CI_BUILD_REF_NAME
 url: https://site$CI_PIPELINE_ID.test
 on_stop: clear
 when: manual
script:
 - cd ../ && cp -r $CI_PROJECT_NAME $CI_PIPELINE_ID && cd $CI_PIPELINE_ID
 - cp -r /home/develop/site-files/.ssh data/docker/php/.ssh #ключі ssh
 - docker-compose down
 - docker-compose up -d --build
 - script -q -c "docker exec -it ${CI_PIPELINE_ID}_app_1 bash -c "cd ../ && php composer.phar install-prefer-dist --no-dev""
 - script -q -c "docker exec -it ${CI_PIPELINE_ID}_app_1 bash -c "cd ../ && php composer.phar first-install $CI_PIPELINE_ID""

clear:
 stage: clear
tags:
 - ssh-develop
environment:
 name: review/$CI_BUILD_REF_NAME
 action: stop
script:
 - cd ../ && cd $CI_PIPELINE_ID && docker-compose down && cd ../ && echo password | sudo -S rm -rf $CI_PIPELINE_ID #Зупинка контейнерів та видалення папки з проектом
 when: manual

У даній конфігурації описані 2 етапи — build і clear. Етап build має 2 варіанти виконання — build_develop і build_prod

Gitlab будує зрозумілу діаграму виконання процесу. В моєму прикладі всі процеси стартують вручну (параметр when: manual). Зроблено це для того, щоб розробник після розгортання тестового сайту, міг робити pull своїх правок у контейнер без перезбирання всього контейнера. Ще одна причина це найменування доменів — site$CI_PIPELINE_ID.test, де CI_PIPELINE_ID — номер процесу запустив збірку. Тобто віддали на перевірку сайт з доменом site123.test і щоб внести гарячі правки, одразу заливаються зміни в контейнер самим розробником.
Невелика особливість роботи ssh executor. При підключенні до сервера створюється папка виду

Читайте також  Як зробити свою гру і не здатися: Туторіал
/home/користувач/builds/хеш_гиппег/0/Группа_проекта/Название_проекта

Тому була додана рядок

cd ../ && cp -r $CI_PROJECT_NAME $CI_PIPELINE_ID && cd $CI_PIPELINE_ID

У ній ми піднімаємося на папку вище і копіюємо проект в папку з номером процесу. Так можна розгортати кілька гілок одного проекту. Але в налаштуваннях обробника потрібно поставити галку Lock to current projects, так обробник не буде намагатися розгорнути кілька гілок одночасно.
Етап clear зупиняє контейнери і видаляє папку, можуть знадобитися права root, тому використовуємо команду echo password | sudo -S rm, де password ваш пароль.

Прибирання сміття

Час від часу потрібно видаляти не використовуються контейнери, щоб не займати місце, для цього в кроні висить скрипт з таким змістом

#!/bin/bash
# видалення завершених контейнерів:
docker ps --filter status=dead --filter status=exited -aq | xargs -r docker rm -v
# видалення невикористовуваних контейнерів:
yes | docker container prune
# видалення не використовуваних образів:
yes | docker image prune
# видалення не використовуваних томів:
yes | docker volume prune

виконується раз на день.

Висновок

Дане рішення допомогло нам суттєво оптимізувати тестування і випуск нових фіч. Готовий відповісти на питання, конструктивна критика приймається.

Бонус

Для того щоб не збирати кожен раз образи з Dockerfile, можна зберігати їх локальному реєстрі докера.
Файл docker-compose.yml

registry:
 restart: always
 image: registry:2
ports:
 - 5000:5000
volumes:
 - /opt/docker-registry/data:/var/lib/registry #монтування папки для зберігання образів

В даному варіанті не використовується аутентифікація, це не безпечний спосіб (!!!), але для зберігання не критичних образів нам підходить.
Можна налаштувати gitlab для перегляду

 gitlab_rails['registry_enabled'] = true
 gitlab_rails['registry_host'] = "registry.test"
 gitlab_rails['registry_port'] = "5000"

Після цього в gitlab з’являється список образів

Степан Лютий

Обожнюю технології в сучасному світі. Хоча частенько і замислююся над тим, як далеко вони нас заведуть. Не те, щоб я прям і знаюся на ядрах, пікселях, коллайдерах і інших парсеках. Просто приходжу в захват від того, що може в творчому пориві вигадати людський розум.

You may also like...

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *