Настройка среды разработки
Привет, дорогой читатель!
Сегодня я хочу поделиться своим опытом настройки персонального окружения для работы с различными PHP-based проектами. В данной статье описывается опыт ручной настройки окружения.

Данная статья написана для меня любимого с целью показать то, как можно упростить или усложнить себе жизнь. Я не хочу призывать к действию, а только делюсь тем, с чем я столкнулся и что сделал.

Автоматизацию процесса развертывания среды я напишу в одной из следующих статей.

Статья будет очень длинной с уклоном в техническую сторону. Прошу под «кат».

Предыстория


Прежде чем начать, хочу поделиться небольшой историей о том, как все начиналось.

Долгое время, процесс разработки велся в основном на одном проекте, с упором на долгосрочные перспективы (работы было много: на 1 год вперед). И так было от компании к компании. Так что мне не составляло особого труда добавить пару «конфигов» и перезагрузить сервисы на локальной машине, чтобы добавить новый проект. При смене компании я просто настраивал все с нуля подстраиваясь под проект.

В конце 2016 года мне пришлось перейти с одного проекта на другой в связи с чередой очень неприятных событий. Перейдя на новый проект столкнулся с тем, что архитектура проекта была построена так, что нужно одновременно поддерживать минимум 5 web-сервисов, работающих в связке. Так же мне все еще приходится время от времени поддерживать предыдущие проекты. Да и, не забывать об R&D проектах, Open Source проектах, “халтурке”, семье и детях.

Такое стечение обстоятельств повлияло на дальнейшую историю моей среды разработки и все что с этим связано.

Цель: гибкая среда разработки для PHP-based проектов


Мне нужно получить рабочее окружение, позволяющее вести разработку, отладку и исследования во множественных PHP-based проектах с привязкой к инфраструктуре компании.
Примечание: дальнейшее описание относится к двум проектам, которые сейчас находятся в моей зоне ответственности: Warface Hub — то, что от него осталось, и CryEngine Hub — результат молниеносной реализации, “удачной” архитектуры и “правильного” планирования.

Таким образом я получил следующее

  • Windows 10 — Host с VMWare или VitrualBox
  • Ubuntu — Guest c:
    • Samba — для работы с файлами проектов
    • Bind9 — локальный DNS сервер
    • Nginx, PHP FPM, MySQL и все остальное

  • Другие виртуальные машины

Ресурсы


В крупных компаниях очень часто используют решения от компании Microsoft для обеспечения работы внутренней связи между сотрудниками (Outlook, Lync, AD) и еще кучи «самописных» приложений. Так как лень мне не позволяет искать и настраивать клиенты под Linux или использовать Wine, то я успешно начал использовать “гибрид” двух OS и виртуализацию:

  • Windows 10 (Host OS)
  • Ubuntu 16.04 (Guest OS — VM)
  • SSD — максимально быстрый. При данной реализации очень важно иметь быстрый диск.
  • 16Gb ram — Чем больше, тем лучше. Мне приходится выделять до 12Gb для всех гостевых OS, так как приходится решать cross-browser проблемы, которые на эмуляторах не воспроизводятся.

Установка сервисов, приложений и компонентов.


На этом этапе устанавливаем все (Guest OS), что может нам понадобится для того чтобы поднять простой стек из Nginx, PHP и MySQL, а также все остальное, что может пригодится в проектах (NPM, Yarn, Composer). Не забываем про нашу среду разработки и добавляем туда Samba и Bind9.

Для меня это выглядит так
$ sudo add-apt-repository ppa:ondrej/php
$ sudo apt-get install php5.6 php7.0 php7.1 php7.1-fpm
$ sudo apt-get install php-bcmath php-curl php-dev php-fpm php-gd php-intl php-json php-mbstring php-mcrypt php-mysql php-readline php-soap php-sqlite3 php-tidy php-xml php-zip php-codecoverage php-codesniffer php-common php-geoip php-igbinary php-imagick php-memcache php-memcached php-redis php-ssh2 php-xdebug php-xhrpof
$ sudo apt-get install php5.6-bcmath php5.6-curl php5.6-dev php5.6-fpm php5.6-gd php5.6-intl php5.6-json php5.6-mbstring php5.6-mcrypt php5.6-mysql php5.6-opcache php5.6-readline php5.6-soap php5.6-sqlite3 php5.6-tidy php5.6-xml php5.6-zip
$ sudo apt-get install php7.0-bcmath php7.0-curl php7.0-dev php7.0-fpm php7.0-gd php7.0-intl php7.0-json php7.0-mbstring php7.0-mcrypt php7.0-mysql php7.0-opcache php7.0-readline php7.0-soap php7.0-sqlite3 php7.0-tidy php7.0-xml php7.0-zip
$ sudo apt-get install php7.1-opcache
 
$ sudo apt-get install memcached redis-server redis-tools nginx mysql-server-5.7 mysql-client-5.7 composer npm curl nodejs nodejs-legacy ruby-full
 
$ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
$ echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
$ sudo apt-get update && sudo apt-get install yarn

$ sudo gem update --system
$ sudo gem install compass
$ composer global require hirak/prestissimo
$ sudo npm install -g grunt-cli

$ sudo apt-get install bind9 samba

Samba-сервер


Samba-сервер Первое, к чему я потянулся — Samba-сервер, который настроил для того, чтобы моя IDE могла работать непосредственно с файловой системой гостевой OS.

Все мои проекты будут находится по пути /var/www/{project-name}.
Так же в процессе настройки гостевой ОС я принудительно настроил DHCP сервер так, чтобы у нее был статический IP и пряталось все за NAT: 172.16.126.130.

$ cat /etc/samba/smb.conf
[global]
workgroup = WORKGROUP
server string = %h server (Samba, Ubuntu)
dns proxy = no

# few changes to speed up smbd
strict allocate = Yes
read raw = Yes
write raw = Yes
strict locking = No
socket options = TCP_NODELAY IPTOS_LOWDELAY SO_RCVBUF=131072 SO_SNDBUF=131072
min receivefile size = 16384
use sendfile = true
aio read size = 16384
aio write size = 16384

max log size = 1000
syslog only = no
syslog = 0
server role = standalone server

usershare allow guests = yes

[www]
    path = /var/www
    available = yes
    valid users = oxcom
    read only = no
    browsable = yes
    public = yes
    writable = yes
    executable = yes


Чтобы IDE могла работать с этим ресурсом я монтирую эту Netbios папку как удаленный локальный диск.

Mount local drive on Windows
@echo off
@title Local UBUNTU Network Drive

net use q: \\172.16.126.130\www {user-password} /USER:oxcom

Unmount local drive
Unmount local drive
@echo off
@title Local UBUNTU Network Drive

net use q: /delete


Почему я стал использовать Samba, а не Shared Folders?
На то время, когда я этим вопросом задался получалось так, что Shared Folders являлись монтируемой директорией, а NGINX не хотел их использовать как root директорию проекта. Второй причиной стало то, что время от времени у меня Shared Folders отваливались без видимой на то причины и без возможности быстро все восстановить. А также нет возможности работать с симлинками, которые необходимы для некоторых пакетов npm и не только.

Есть ли какие-то проблемы с IDE?
Да и разные, но SSD спасает:

  • иногда индексирование проектов происходит долго
  • при быстром изменении большого количества файлов (merge, rebase, смена бранча) не сразу подхватываются изменения. Смотрю в сторону rsync.
  • Если отключить диск раньше, чем закрыть IDE, то, при закрытии проекта, IDE зависнет
  • Если отвалится сеть, то проект скорее всего нужно будет открыть еще раз

DNS-сервер


Bind9Дорогой читатель может представить насколько трудно в большой компании запросить небольшие изменения в настройках сети. И даже если вы успешно пройдете первый этап объяснения для чего вам это нужно, то второй этап (реализация) может затянуться на очень большой срок, что проще все сделать самому.

Так как я очень ленивый, то через какое-то время мне надоело добавлять в hosts файл записи вида:
172.16.126.130 project1.lo
172.16.126.130 project2.lo


Было решено, что нужен локальный DNS-сервер, который можно настроить быстрее, чем я смогу выпить бутылку немецкого крепкого пива. После чего был послан запрос в лигу лени. Результат не заставил себя долго ждать: BIND9

Настройка очень проста: описываем конфигурации прямого и обратного пула доменов, а также не забываем их подключить:

$ cat /etc/bind/db.lo
;
; BIND data file for local loopback interface
;
$TTL 604800
@ IN SOA lo. root.lo. (
0000119 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS lo.
@ IN A 172.16.126.130
@ IN AAAA ::1
* IN CNAME @

$ cat /etc/bind/db.126.16.172
;
; BIND data file for local loopback interface
;
$TTL 604800
;
; BIND reverse data file for local loopback interface
;
$TTL 604800
@ IN SOA ns.lo. root.lo. (
0000112 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
;
@ IN NS lo.

$ cat /etc/bind/named.conf.local
zone "lo" {
type master;
file "/etc/bind/db.lo";
check-names ignore;
allow-query { any; };
allow-transfer { any; };
};

zone "126.16.172.in-addr.arpa" {
type master;
file "/etc/bind/db.126.16.172";
allow-query { any; };
allow-transfer { any; };
};

$ cat /etc/bind/named.conf.options
options {
directory "/var/cache/bind";

forwarders {
8.8.8.8;
8.8.4.4;
};

recursion yes;
listen-on { any; };

auth-nxdomain no; # conform to RFC1035

listen-on-v6 { any; };
};


Сервер настроен. Теперь, для полной радости осталось решить еще несколько задач:

  • заставить Host систему использовать наш DNS-сервер для домена .lo. Поэтому меняем на Host машине параметры виртуальной сети так, чтобы первым был наш DNS-сервер, и чистим DNS-кеш.
  • заставить Guest систему тоже использовать наш DNS-сервер. Тут проще: редактируем сетевой интерфейс и прописываем туда наш DNS-сервер

Результат не заставляет себя ждать:

$ dig example.lo
; <<>> DiG 9.10.3-P4-Ubuntu <<>> example.lo
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32204
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 1, ADDITIONAL: 2

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;example.lo. IN A

;; ANSWER SECTION:
example.lo. 604800 IN CNAME lo.
lo. 604800 IN A 172.16.171.130

;; AUTHORITY SECTION:
lo. 604800 IN NS lo.

;; ADDITIONAL SECTION:
lo. 604800 IN AAAA ::1

;; Query time: 0 msec
;; SERVER: 172.16.171.130#53(172.16.171.130)
;; WHEN: Mon Jul 17 11:09:52 CEST 2017
;; MSG SIZE rcvd: 111

ping example.lo
Pinging lo [172.16.171.130] with 32 bytes of data:
Reply from 172.16.171.130: bytes=32 time<1ms TTL=64
Reply from 172.16.171.130: bytes=32 time<1ms TTL=64


Вот только «nslookup example.lo» не работает. Почему — не знаю, но любой браузер на Host машине будет нормально ходить на соответствующий сервер.

PHP-FPM и Pools


PHP Вроде все работает. Теперь необходимо приступить к настройке PHP для каждого из проектов.
Работая над последними проектами у меня получилось так, что один проект может состоять из различных сервисов или собирать в себе многие другие проекты. По-этому у меня получилась следующая структура:

  • warface.com
  • cryengine.com
  • shop.cryengine.com
  • forum.cryengine.com

В процессе работы приходится использовать API вызовы между проектами и было бы очень хорошо уметь «дебажить» это все с помощью XDebug и любимой IDE. При попытках использовать один PHP Pool я сталкивался со следующими проблемами:

  • при отладке на одном домене с общей сессией PHP-FPM блокирует исполнение остальных запросов с такой же сессий (как смог так и описал :)). Таким образом останавливаясь где-то на cryengine.com я не смогу работать с forum.cryengine.com, пока не завершится предыдущий запрос.
  • неудобно проводить отладку двух проектов одновременно с разными ключами XDebug. Да-да, я стараюсь максимально избегать изменения настроек среды из PHP

Чтобы это решить, я создал свой PHP Pool по шаблону для каждого проекта под каждую версию PHP. И подключаю их 'include=/etc/php/7.0/fpm/pool.d/*.d/*.conf'

Шаблон
$ cat /etc/php/{php-version}/fpm/pool.d/{project-name}.d/{project-subname}.conf
; Start a new pool
; the variable $pool can we used in any directive and will be replaced by the
; pool name ('www' here)
[{project-name}-{project-subname}]

user = www-data
group = www-data

listen = /run/php/php7.0-$pool-fpm.sock

listen.owner = www-data
listen.group = www-data
 
pm = dynamic
pm.max_children = 20
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 7

php_admin_value[xdebug.idekey] = key-$pool


Выглядит это примерно так:

$ ls -la /etc/php/7.0/fpm/pool.d/
cryengine.d/www.conf
cryengine.d/shop.conf
cryengine.d/forum.conf
warface.d/www.conf
www.conf
Список PHP-Sockets
php5.6-cryengine-www-fpm.sock
php5.6-cryengine-shop-fpm.sock
php5.6-cryengine-forum-fpm.sock
php5.6-warface-www-fpm.sock
php5.6-fpm.sock
php7.0-cryengine-www-fpm.sock
php7.0-cryengine-shop-fpm.sock
php7.0-cryengine-forum-fpm.sock
php7.0-warface-www-fpm.sock
php7.0-fpm.sock


Теперь каждый проект имеет свой PHP Pool и проставленным xdebug.idekey, что позволяет проводить непрерывную отладку между несколькими проектами (не забывайте настроить php.ini)

NGINX


NginxНастройка сервера довольно проста. Для каждого проекта создается отдельный файл конфигурации в 'sites-available', который описывает конфигурацию vhost. В конфигурации мы подключаем соответствующие файлы конфигурации для каждого проекта.

Основываясь на указанной выше архитектуре я реализовал вот такую структуру:

$ ls -la /etc/nginx/
cryengine.d/
- www.conf
- forum.conf
- shop.conf
- ssl.conf
warface.d/
- www.conf
- ssl.conf

sites-available/
- www.cryengine.conf
- forum.cryengine.conf
- shop.cryengine.conf
- www.warface.conf

nginx.conf

$ cat /etc/nginx/sites-available/{project-subname}.{project-name}.conf
server {
    listen 443 ssl http2;
    server_name {project-subname}.{project-name}.lo;

    root /var/www/{project-subname}.{project-name}.com/wwwroot;
    index           index.php;

    error_log  /var/www/{project-subname}.{project-name}.com/log/nginx.error.log error;
    access_log /var/www/{project-subname}.{project-name}.com/log/nginx.access.log;

    # configuration related to sub-project
    include {project-name}.d/www.conf;
    # global configuration for projects
    include {project-name}.d/ssl.conf;
}


Таким образом в /etc/nginx/{project-name}.d/ хранятся все конфигурации, которые относятся к проекту, а также сопутствующим под проектам. Каждый проект имеет свой PHP Pool пока каждую версию PHP, что позволяет быстро переключаться между версиями и только для одного проекта.

Итоги


Спасибо, что дочитали до конца. Я искренне надеюсь, что ручной труд еще не вымер, ибо разработчик должен уметь настроить среду сам и без автоматизации.
В итоге я получил гибрид между двумя ОС, который позволяет полностью использовать все внутренние сервисы компании, а также вести разработку и отладку в среде, максимально приближенной к LIVE.

Я понимаю, что это не идеальное решение, но это один из способов, как можно за короткий промежуток времени подготовить среду разработки.

Минусы:

  • Долгая индексация в IDE
  • При переустановке/переносе виртуальной машины нужно изменять конфигурацию для Bind9 и скрипт подключения диска
  • Это все не автоматизировано

Плюсы:

  • рабочую среду можно переносить с машина на машину
  • разработчик остается в Windows окружении и есть возможность использовать MS сервисы, которые установлены в компании.
  • разработчик работает с Linux окружением, которое может быть легко настроено как LIVE среда.
Поделиться с друзьями
-->

Комментарии (11)


  1. script88
    17.07.2017 15:23

    Какой смысл использовать ВМ, в то время как в самой винде есть ubuntu bash на которой можно развернуть все тоже самое?!


    1. OxCom
      17.07.2017 15:27

      Так уж случилось, что у меня еще есть Win7, в которой этого нет, но я работаю в ней с тем же подходом.
      Я сомневаюсь, что до текущего момента все компании поголовно перешли на Win10.


  1. smple
    17.07.2017 15:32

    используйте vagrant например для подобных историй.


    настройку машины можно сложить в provision


  1. OxCom
    17.07.2017 15:48

    smple
    Спасибо за совет! Планирую все это завернуть в Vagrant + Chef, и возможно что-то завернуть Docker.


    1. smple
      17.07.2017 19:10

      docker прикольная вещь и она довольно быстро стартует, по сравнению со связкой vagrant + virtualbox только там всякие забавы идут с windows и mac и есть подход в котором докер крутится внутри виртуалки.


      Поэтому все же проще vagrant + virtualbox
      У него есть минусы:


      1. Потребляет лишнюю память и cpu (в среднем 1gb ram и проц)
      2. Разворачивается не быстро (в среднем 1 минута).
      3. На больших проектах синхронизация файлов по умолчанию тормозит (обычно используют rsync или nfs что выливается в пляски с windows хорошо если лицензия корпоративная или проффесиональная)

      Но есть и огромное количество плюсов
      по поводу provision, не обязательно использовать что то типо chef, ansible или puppet, можно просто использовать bash script в простых случаях как вы описали выше.


      Вообщем если есть на это спрос могу написать конечно статью на хабр на эту тему как я разворачиваю окружение ну и конфиг соответственный.


      1. OxCom
        18.07.2017 10:14

        Мне будет очень интересно посмотреть, как это реализовали другие люди, так как от DevOps я относительно далеко, но уж очень интересно посмотреть.


        1. smple
          18.07.2017 19:40

          дело в том что я не devops а обычный разработчик, просто люблю комфортную работу и хорошо умею настрайвать всякие штуки.


        1. artarn
          20.07.2017 14:24

          я например использую docker так:

          1) в системе крутится DNS docker контейнер tonistiigi/dnsdock

          2) через docker-compose.yml описываю проект
          например

          version: '2'
          services:
             project_db:
                image: mysql:{версия}
             ....
                environment:
                   DNSDOCK_NAME: project
                   DNSDOCK_IMAGE: db
             project_php:
                build: ./php{версия}
             ....
             project_nginx:
                depends_on:
                   - project_php
                image: nginx
             ....
                environment:
                   DNSDOCK_NAME: project
                   DNSDOCK_IMAGE: site
          


          3) кастую docker-compose up -d

          получаю проект mysql + php-fpm + nginx

          в итоге на хост машине
          mysql доступен по адресу project.db.docker например для workbench
          nginx доступен по адресу http://project.site.docker

          могу остановить в любой момент, поднять несколько разных проектов с разными версиями БД и php.

          на самой хост машине mysql снес, php только версии 7.1


  1. 65536
    26.07.2017 19:33

    Про Shared Folders хочу добавить — адски тормозят на hdd. То что должно происходить 30 мс может длиться 2.5 сек, зависит от количества операций с файлами. Года полтора-два промучился, выяснилось что это из-за них, проблема известна и нерешаема. Решил уйти от этой схемы и первым делом попробовал через самбу примонтировать. PhpStorm впадает в практически бесконечную переиндексацию. Переключение ветки может всё повесить на несколько минут, поиск по всем файлам невозможен, хоть на ночь оставляй. Да и просто тормозит при навигации по проекту, выдаче подсказок и т. д. Начал искать еще варианты и нашел вот это: https://habrahabr.ru/post/139348/. Очень хорошо работает, за время перехода в браузер и нажатие ф5 на сервере уже будет всё как надо. Единственное неудобство это то, что синхронизация односторонняя и сгенерированные на сервере файлы приходится скачивать вручную.


    1. OxCom
      26.07.2017 22:34

      Интересный подход. Несколько раз смотрел в эту сторону, но нет. Мне действительно нужна двусторонняя синхронизация. Одна из причин — как ни крути, но в unix консоль более удобна и приятная в использовании чем cmd и windows решение для GIT.

      Еще я рассматривал монтирование с использованием SSHFS for Win, но PHPStorm постоянно засыпал меня уведомления, что GIT «упал» и все. Попытки решить проблему не увенчались успехом. Возможно нужно ждать обновлений.


      1. OxCom
        26.07.2017 23:06

        Решение проблемы нашел сегодня:

        неправильный права доступа
        $ find <somerepo>/.git -ls | grep 'r--r--r--'
        $ chmod 0644 {file-path}