Содержание

О проекте

Стандартная система веб-разработчика представляет из себя его личную машину с установленным пакетом, типа Denver или appserv, который включает в себя apache+php+mysql, несколько папок с копиями репозиториев для проектов, которое позволяем ему просматривать и тестировать скрипты на своей локальной машине. В этом есть много плюсов, например, если это на ноутбуке, то можно носить с собой и дома работать, но есть и минусы, к примеру, если надо показать кому то свои наработки, что надо приглашать непосредственно к своему рабочему месту, или просить человека настраивать файл hosts, что бы работали виртуальные хосты.

Возникла идея, создать какой нибудь сервер с виртуальными сервисами, для работы с которым на рабочую машину веб-разработчика не надо было бы ничего ставить.

В конечном итоге, было принято решение в качестве ОС использовать FreeBSD, samba, систему jail.

Как это работает

Каждому пользователю выдается jail со своим собственным IP, доступом по ssh, mysql сервером, apache22 настроенным под массовый виртуальный хостинг, с возможность указания дополнительных виртуальных хостов, которые не подходят под шаблон стандартного виртуального хоста, расшареной папкой через samba, куда просто путем добавления каталога и скриптов автоматически появляется поддомен третьего уровня.

Для упрощения, будем использовать вариант с тремя разработчиками.

Настройка сети

Добавляем необходимое нам количество IP адресов на активный интерфейс, по одному на каждый джейл.

ifconfig em0 add alias 10.8.0.200/32
ifconfig em0 add alias 10.8.0.201/32
ifconfig em0 add alias 10.8.0.202/32
ifconfig em0 add alias 10.8.0.203/32

Записываем изменения в файле /etc/rc.conf

ifconfig_em0_alias0="inet 10.8.0.200/32"
ifconfig_em0_alias1="inet 10.8.0.201/32"
ifconfig_em0_alias2="inet 10.8.0.202/32"
ifconfig_em0_alias3="inet 10.8.0.203/32"

Замечу, что адрес сервера 10.8.0.5, но мы так же создадим один дополнительный джейл и повесим его на адрес 10.8.0.200, это будет главный, управляющий джейл. Как, для чего, и зачем - пояснения будут даны позже.

Настройка работы с джейлами

Для настройки джейлов будем использовать свежеустановленный сервер с FreeBSD. Кто то использует vim или vi, кто то nano, мне удобно пользоваться встроенным редактором в mc.

Поставим порт управления джейлами

[root@dev /]# cd /usr/ports/sysutils/ezjail
[root@dev /]# make install clean

Добавляем автозапуск джейла, указав в файле /etc/rc.conf:

ezjail_enable="YES"

У меня на сервере самый большой размер имеет раздел /home. Поэтому, берем файл примеров настройки ezjail и копируем его в конфигурационный файл

[root@dev /]# cd /usr/local/etc
[root@dev /]# cp ezjail.conf.sample ezjail.conf

В нем берем строку

ezjail_jaildir=/usr/jails

И приводим к виду

ezjail_jaildir=/home/jails

Сохраняем файл и устанавливаем мир для джейлов, для этого используем команду

[root@dev /]# ezjail-admin create

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

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

Суть управляющего джейла в чем: каждый разработчик получает в свое пользование полноценную машину с рутовыми правами. Надо оно ему, или не надо, это уже зависит от конкретного разработчика. Соответственно, мы не хотим, что бы он смог там что нибудь поломать, т.е. права у него будут полные, а сломать не может. Все это работает как. Согласно документации и тому как делаются джейлы посредством использования утилиты ezjail-admin, при установки мира для джейлов создается костяк окружения, т.е. каталоги /bin /boot /lib /libexec /rescue /sbin /sys, которые являются симлинками на каталог в корне джейла /basejail/bin /basejail/boot /basejail/lib /basejail/libexec /basejail/rescue /basejail/sbin /basejail/sys соответственно, которые в свою очередь ссылаются на каталог /home/jails/basejail. Каталог /basejail монтируется в джейл посредством mount_nullfs в режиме только для чтения, что означает, что пользователь не сможет ничего сделать с системными файлами, ни удалить, не заменить, ни отредактировать. Соответственно, так же примерно монтируются каталоги внутри каталога /usr, за исключением каталога /usr/local, куда пользователь уже может писать и ставить различные порты.

Но мы пойдем немного дальше. В текущей статье, разработчиков 3, но что делать, если их будет больше? Это по первых, они могут ставить любой порт. Порт можно поставить нормально, можно криво, не хочется потом разбираться, почему так произошло. Так же, они будут иметь право удалять порты, зависимости и прочее. Не говоря уже о том, что при обновлении, придется заходить в каждый джейл и обновлять порты.

Установка первого джейла

Рассмотрим очевидное решение, которое мне показалось разумным.

Для начала создадим управляющий джейл.

[root@dev /]# ezjail-admin create hq.local 10.8.0.200

Лежать он будет по пути /home/jails/hq.local Создаем каталог и сопутствующие подкаталоги

[root@dev /]# mkdir /home/jails/baselocaljail/{bin,include,info,lib,libdata,libexec,man,sbin,share,var,www}

В нашем случае, он будет монтироваться в джейле в каталог /usr/local

Все точки монтирования определяются файлами /etc/fstab.JAILNAME

В нашем случае, это будет /etc/fstab.hq_local

Его содержимое по умолчанию

/home/jails/basejail /home/jails/hq.local/basejail nullfs ro 0 0

Для нашего случая, что бы разработчики не могли ничего делать с установленными портами, кроме как непосредственно работать с ними, необходимо добавить в этот файл еще одну точку монтирования

/home/jails/baselocaljail /home/jails/hq.local/baselocaljail nullfs rw 0 0

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

/home/jails/baselocaljail /home/jails/hq.local/baselocaljail nullfs ro 0 0

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

[root@dev /]# rm /home/jails/hq.local/usr/ports ; mkdir /home/jails/hq.local/usr/ports

Так же прописываем это в /etc/fstab.hq_local

/usr/ports /home/jails/hq.local/usr/ports nullfs rw 0 0

Запускаем непосредственно джейл

[root@dev /]# /usr/local/etc/rc.d/ezjail start

И смотрим в списках джейлов, какой он имеет JID.

[root@dev /]# jls
   JID  IP Address      Hostname                      Path
     1  10.8.0.200      hq.local                      /home/jails/hq.local

Заходим в него

[root@dev /]# jexec 1 tcsh
hq#

Все, мы в джейле.

Настройка управляющего джейла

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

#hq cd /usr/local/
#hq ln -s /baselocaljail/{bin,include,info,lib,libdata,libexec,man,sbin,share,var,www} .

Т.е. фактически, все основные каталоги, кроме etc будут ссылаться на каталог, который мы примонтировали для джейлов. Во всех джейлах содержимое будет одинаково.

Для установки портов я использую локальную копию портов и его же локальный кеш distfiles, что бы не качать одни и те же порты, что уже и так есть у меня, когда я настраивал основную систему. Для этого надо подправить один файл, /etc/make.conf, закоментировать строчку DISTDIR

#DISTDIR   /var/ports/distfiles

И, соответственно, поле этого ставим те порты, которые нам нужны.

Джейлы разработчиков

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

Подводные камни

Масса.

Во первых, это php. Как известно, php использует файл /usr/local/etc/php/extenstions.ini для предопределения, какие экстеншены мы будем использовать. При установке какого либо порта для php или для его удаления, необходимо обновлять файл в остальных джейлах и рестартить везде apache. Можно просто обойтись копированием, но я сделал немного по другому.

В каталоге /baselocaljail у меня есть каталог var, куда я засунул файл extensions.ini. Соответственно, при этом, при обновлении этого файла в управляющем джейле, он будет везде одинаков.

#hq mkdir /usr/local/etc/php
#hq touch /baselocaljail/var/extensions.ini
#hq cd /usr/local/etc/php && ln -s /baselocaljail/var/extensions.ini .

Далее. При установке какого нибудь дополнительного порта, необходимо в каждом джейле запускать /etc/rc.d/ldcondig

Например, для этого можно написать простенький скрипт:

#!/bin/csh

jls | grep user | awk '{ system( "jexec "$1" /etc/rc.d/ldconfig restart");}'

То же самое относиться к апачу

#!/bin/csh

jls | grep user | awk '{ system( "jexec "$1" /usr/local/etc/rc.d/apache22 restart");}'

Ну, и конечно, некоторые программы кладут скрипты запуска, а так же файлы конфигураций по пути /usr/local/etc, их уже надо отслеживать руками.

Настройка bind

Вся эта система работает в офисе, компьютеры которого используют локальный DNS-сервер, где прописаны сервера разработчиков для теста. Создаем, к примеру, в офисе зону local. И домены второго уровня, для джейлов разработчиков, например user001.local user002.local user003.local.

Для начала, создаем саму зону .local

У меня на сервере настройки и зоны лежат в каталоге /etc/namedb

Берем стандартный кофиг named.conf и добавляем туда строчку:

include "/etc/namedb/named.zones.conf";

Его содержимое:

zone "local" {
      type master;
      file "/etc/namedb/master/local.zone";
};
zone "user001.local" {
      type master;
      file "/etc/namedb/master/user001.local.zone";
};
zone "user002.local" {
      type master;
      file "/etc/namedb/master/user002.local.zone";
};
zone "user003.local" {
      type master;
      file "/etc/namedb/master/user003.local.zone";
};

И, соответственно, сами зоны:

/etc/namedb/master/user001.local.zone:

$ORIGIN .
$TTL 86400      ; 1 day
user001.local            IN SOA  ns.user001.local. root.ns.user001.local. (
                              2010040806  ; serial
                              7200       ; refresh (2 hours)
                              3600       ; retry (1 hour)
                              1209600    ; expire (2 weeks)
                              86400      ; minimum (1 day)
                              )
                      NS      ns.user001.local.
$ORIGIN user001.local.
ns                      A       10.8.0.1
@                       A       10.8.0.201
*                       A       10.8.0.201

/etc/namedb/master/user002.local.zone:

$ORIGIN .
$TTL 86400      ; 1 day
user002.local            IN SOA  ns.user002.local. root.ns.user002.local. (
                              2010040806  ; serial
                              7200       ; refresh (2 hours)
                              3600       ; retry (1 hour)
                              1209600    ; expire (2 weeks)
                              86400      ; minimum (1 day)
                              )
                      NS      ns.user002.local.
$ORIGIN user002.local.
ns                      A       10.8.0.1
@                       A       10.8.0.202
*                       A       10.8.0.202

/etc/namedb/master/user001.local.zone:

$ORIGIN .
$TTL 86400      ; 1 day
user003.local            IN SOA  ns.user003.local. root.ns.user003.local. (
                              2010040806  ; serial
                              7200       ; refresh (2 hours)
                              3600       ; retry (1 hour)
                              1209600    ; expire (2 weeks)
                              86400      ; minimum (1 day)
                              )
                      NS      ns.user003.local.
$ORIGIN user003.local.
ns                      A       10.8.0.1
@                       A       10.8.0.203
*                       A       10.8.0.203

Содержимое файла /etc/namedb/master/local.zone

$ORIGIN .
$TTL 86400      ; 1 day
local            IN SOA  ns.local. root.ns.local. (
                                2010040807  ; serial
                                7200       ; refresh (2 hours)
                                3600       ; retry (1 hour)
                                1209600    ; expire (2 weeks)
                                86400      ; minimum (1 day)
                                )
                        NS      ns.local.
$ORIGIN local.
localhost               A       127.0.0.1
ns                      A       10.8.0.1
mail                    A       10.8.0.1
gw                      A       10.8.0.1
dev                     A       10.8.0.5
hq                      A       10.8.0.200
svn                     A       10.8.0.230