null

glassfish + jail = love

Озадачился тут я переносом одного (догадайтесь какого) сайта, использующего glassfish, с временного сервера на постоянный. И естественным образом, возникло желание немного поотделять его от других задач и собственно основной системы, а, так как на сервере используется FreeBSD, выбор естественным образом пал на jail-ы, существующие в FreeBSD со времён 4-ой версии оной.

Замечательный glassfish для своей работы не просто требует Java, но и запускает несколько процессов, запуская новые виртуальные машины java с различными параметрами, из-за чего в jail необходимо иметь полную копию JRE и всех необходимых библиотек. Учитывая то, что в основной системе Java как таковая в общем-то не нужна, но может оказаться полезной возможность клонирования jail или его переноса на другу систему, было решено выделить под glassfish отдельную систему на ZFS и jail, имеющий полную копию системы и локально установленную из портов Java со всеми её зависимостями.

Ну, как говорится, в добрый путь.

Раз уж используем jail с полной копией системы, то почему бы и не воспользоваться штатным /etc/rc.d/jail, берём пример из /etc/defaults/rc.conf, копируем его в своий /etc/rc.conf с изменением имени jail и необходимых параметров.

jail_enable="NO"
jail_list="bbb glassfish"
jail_set_hostname_allow="NO"

jail_glassfish_rootdir="/opt/jail/glassfish"
jail_glassfish_hostname="www.tune-it.ru"
jail_glassfish_interface="nfe0"
jail_glassfish_ip="192.168.xxx.xxx"
jail_glassfish_exec_start="/bin/sh /etc/rc"
jail_glassfish_exec_stop="/bin/sh /etc/rc.shutdown"
jail_glassfish_devfs_enable="YES"
jail_glassfish_devfs_ruleset="devfsrules_jail"
jail_glassfish_flags="-l -U root"
jail_glassfish_mount_enable="YES"
jail_glassfish_fstab="/usr/local/etc/jail/glassfish.fstab"

Запускаем сам jail и идём собирать jdk. Так как в соседнем jail уже используется openjdk6 из портов, собираем его:

/etc/rc.d/jail start glassfish
jexec 2 su -
cd /usr/ports/java/openjdk6
make install clean

(на самом деле всё конечно не так. И для jexec у меня псевдоним написан, и собирал я через portmaster, и начинал сборку со сборки svn, потому что после этого извлекал из svn файлы настроек портов, ну да сейчас разговор не об этом)

Теперь надо поставить glassfish. Заходим на официальный сайт и обнаруживаем, что дистрибутив программы на Java по каким-то причинам разбит на несколько, для каждой платформы свой. Так как в прямом виде дистрибутив для FreeBSD там отсутствует, берём первый попавшийся. Мне первопопался дистрибутив для linux. Его и ставим:

cd /export
java -Xmx256m -jar /path/to/dist/glassfish-installer-v2.1.1-b31g-linux.jar
cd /export/glassfish
chmod +x lib/ant/bin/ant
lib/ant/bin/ant -f setup.xml

После чего создаём домен:

/export/glassfish/bin/asadmin create-domain --user admin \
  --domaindir /export/www/domains --adminport 8008 --instanceport 8000 \
  --profile cluster tune-it

Пока всё хорошо. Запускаем домен:

/export/glassfish/bin/asadmin start-domain --user admin --verbose=true \
  --passwordfile /export/www/domains/tune-it/config/password.txt \
  --domaindir /export/www/domains tune-it

Домен вроде бы запустился, но через некоторое время после запуска получаем некий Java exception:

[#|2011-01-19T10:05:47.106+0000|WARNING|sun-appserver2.1|
        javax.enterprise.system.stream.err|_ThreadID=17;
        _ThreadName=httpWorkerThread-8008-0;
        _RequestID=ee8063ca-bd4b-4034-bb7d-a45685941b07;|
        com.sun.enterprise.registration.RegistrationException:
        /export/glassfish/lib/registration/servicetag-registry.xml (Permission denied)
        at com.sun.enterprise.registration.RepositoryManager.writeToFile(RepositoryManager.java:482)
        at com.sun.enterprise.registration.RepositoryManager.updateRuntimeValues(RepositoryManager.java:304)
        at com.sun.enterprise.registration.SysnetRegistrationService.getServiceTags(SysnetRegistrationService.java:199)
        at com.sun.enterprise.registration.SysnetRegistrationService.getServiceTags(SysnetRegistrationService.java:204)
        at com.sun.enterprise.tools.admingui.sysnet.RegisterHandlers.getProductInstanceURN(RegisterHandlers.java:514)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:616)
        at com.sun.jsftemplating.layout.descriptors.handler.Handler.invoke(Handler.java:422)
        at com.sun.jsftemplating.layout.descriptors.LayoutElementBase.dispatchHandlers(LayoutElementBase.java:424)
        at com.sun.jsftemplating.layout.descriptors.LayoutElementBase.dispatchHandlers(LayoutElementBase.java:398)
        at com.sun.jsftemplating.layout.descriptors.LayoutComponent.beforeCreate(LayoutComponent.java:336)
        at com.sun.jsftemplating.layout.descriptors.LayoutComponent.getChild(LayoutComponent.java:275)
        at com.sun.jsftemplating.layout.LayoutViewHandler.buildUIComponentTree(LayoutViewHandler.java:538)
        at com.sun.jsftemplating.layout.LayoutViewHandler.createView(LayoutViewHandler.java:237)
        at com.sun.faces.lifecycle.RestoreViewPhase.execute(RestoreViewPhase.java:208)
        at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:100)
        at com.sun.faces.lifecycle.RestoreViewPhase.doPhase(RestoreViewPhase.java:102)
        at com.sun.faces.lifecycle.LifecycleImpl.execute(LifecycleImpl.java:118)
        at com.sun.faces.extensions.avatar.lifecycle.PartialTraversalLifecycle.execute(PartialTraversalLifecycle.java:80)
        at javax.faces.webapp.FacesServlet.service(FacesServlet.java:265)
        at com.sun.enterprise.tools.admingui.servlet.DelayedInitFacesServlet.service(DelayedInitFacesServlet.java:89)
        at org.apache.catalina.core.ApplicationFilterChain.servletService(ApplicationFilterChain.java:427)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:333)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214)
        at com.sun.webui.jsf.util.UploadFilter.doFilter(UploadFilter.java:240)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:246)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:214)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:313)
        at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:287)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:218)
        at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:648)
        at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:593)
        at com.sun.enterprise.web.WebPipeline.invoke(WebPipeline.java:94)
        at com.sun.enterprise.web.PESessionLockingStandardPipeline.invoke(PESessionLockingStandardPipeline.java:98)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:222)
        at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:648)
        at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:593)
        at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:587)
        at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1093)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:166)
        at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:648)
        at org.apache.catalina.core.StandardPipeline.doInvoke(StandardPipeline.java:593)
        at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:587)
        at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:1093)
        at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:291)
        at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.invokeAdapter(DefaultProcessorTask.java:666)
        at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.doProcess(DefaultProcessorTask.java:597)
        at com.sun.enterprise.web.connector.grizzly.DefaultProcessorTask.process(DefaultProcessorTask.java:872)
        at com.sun.enterprise.web.connector.grizzly.DefaultReadTask.executeProcessorTask(DefaultReadTask.java:341)
        at com.sun.enterprise.web.connector.grizzly.DefaultReadTask.doTask(DefaultReadTask.java:263)
        at com.sun.enterprise.web.connector.grizzly.DefaultReadTask.doTask(DefaultReadTask.java:214)
        at com.sun.enterprise.web.connector.grizzly.TaskBase.run(TaskBase.java:264)
        at com.sun.enterprise.web.connector.grizzly.WorkerThreadImpl.run(WorkerThreadImpl.java:117)
Caused by: java.io.FileNotFoundException: /export/glassfish/lib/registration/servicetag-registry.xml (Permission denied)
        at java.io.FileOutputStream.open(Native Method)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:209)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:160)
        at com.sun.enterprise.registration.RepositoryManager.writeToFile(RepositoryManager.java:479)
        ... 54 more
|#]

[#|2011-01-19T10:05:47.109+0000|INFO|sun-appserver2.1|
        javax.enterprise.system.stream.out|
        _ThreadID=17;_ThreadName=httpWorkerThread-8008-0;|
        !!!!!! No service Tag|#]

При внимательном взгляде на сообщения видим, что ему не удалось обновить ServiceTag-и, так как у этого пользователя нет доступа на запись в каталог glassfish, что в общем-то нормально. Еще домен почему-то не освободил терминал, в котором был запущен, но в остальном домен, кажется, запустился и даже позволяет на себя зайти по http. Теперь создаём и запускаем node agent:

/export/glassfish/bin/asadmin create-node-agent --user admin --port 8008 \
  --agentdir /export/www/nodeagents \
  --passwordfile /export/www/domains/tune-it/config/password.txt na_www
/export/glassfish/bin/asadmin start-node-agent --user admin --verbose=true \
  --passwordfile /export/www/domains/tune-it/config/password.txt \
  --agentdir /export/www/nodeagents/ na_www

На первый вгляд вроде нормально запустилось. По крайней мере видим сообщене на домене:

[#|2011-01-19T10:52:56.781+0000|INFO|sun-appserver2.1|
        javax.ee.enterprise.system.tools.synchronization|
        _ThreadID=18;_ThreadName=RMI TCP Connection(36)-192.168.46.8;
        Wed Jan 19 10:52:56 GMT 2011;na_www;|
        SYNC005: Received synchronization request at time [
        Wed Jan 19 10:52:56 GMT 2011] from [na_www].|#]

Из чего можно сделать, что домен получил синхронизационный запрос от агента и всё хорошо. По крайней мере никаких других сообщений об ошибках не обнаружилось. Но при попытке нажать на закладку node agents домена, в качестве статуса созданного агента видим пустую строку, а сам агент разоряется на информативнейшее ругательство:

[#|2011-01-19T11:09:49.395+0000|INFO|sun-appserver2.1|
        javax.ee.enterprise.system.nodeagent|_ThreadID=11;
        _ThreadName=RMI TCP Connection(15)-192.168.46.8;|
        NAGT1503:Node Agent could not get status for agent na_www.
com.sun.enterprise.admin.servermgmt.InstanceException: Unauthorized access
        at com.sun.enterprise.admin.servermgmt.pe.PEInstancesManager.isRestartNeeded(PEInstancesManager.java:567)
        at com.sun.enterprise.admin.servermgmt.RuntimeStatus.getRuntimeStatus(RuntimeStatus.java:85)
        at com.sun.enterprise.ee.nodeagent.NodeAgent.getRuntimeStatus(NodeAgent.java:362)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:616)
        at com.sun.jmx.mbeanserver.StandardMBeanIntrospector.invokeM2(StandardMBeanIntrospector.java:111)
        at com.sun.jmx.mbeanserver.StandardMBeanIntrospector.invokeM2(StandardMBeanIntrospector.java:45)
        at com.sun.jmx.mbeanserver.MBeanIntrospector.invokeM(MBeanIntrospector.java:226)
        at com.sun.jmx.mbeanserver.PerInterface.noSuchMethod(PerInterface.java:211)
        at com.sun.jmx.mbeanserver.PerInterface.invoke(PerInterface.java:112)
        at com.sun.jmx.mbeanserver.MBeanSupport.invoke(MBeanSupport.java:251)
        at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:857)
        at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:795)
        at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1450)
        at javax.management.remote.rmi.RMIConnectionImpl.access$200(RMIConnectionImpl.java:90)
        at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1285)
        at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1383)
        at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:807)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:616)
        at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)
        at sun.rmi.transport.Transport$1.run(Transport.java:177)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Transport.java:173)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:553)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:808)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:667)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
        at java.lang.Thread.run(Thread.java:636)
Caused by: java.lang.SecurityException: Unauthorized access
        at com.sun.enterprise.admin.server.core.channel.AdminChannelServer.isRestartNeeded(AdminChannelServer.java:173)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:616)
        at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)
        at sun.rmi.transport.Transport$1.run(Transport.java:177)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.Transport.serviceCall(Transport.java:173)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:553)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:808)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:667)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
        at java.lang.Thread.run(Thread.java:636)
        at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:273)
        at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:251)
        at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:160)
        at com.sun.enterprise.admin.server.core.channel.AdminChannelServer_Stub.isRestartNeeded(Unknown Source)
        at com.sun.enterprise.admin.server.core.channel.RMIClient.isRestartNeeded(RMIClient.java:362)
        at com.sun.enterprise.admin.servermgmt.pe.PEInstancesManager.isRestartNeeded(PEInstancesManager.java:563)
        ... 33 more
|#]

Вот тут-то и начинается всё самое интересное. Попытки нагуглить решение навели только на рекомендации переустановить glassfish. Ага. Свежепоставленный. Переустановить. Ну переустановили. А толку? Ситуация повторилась с точностью до.

Потом вспоминаем, что в соседнем jail-е при настройке великого и ужастного bbb человек долго вычищал из конфигов упоминания о localhost. На всякий случай в /etc/hosts в строчку, описывающую хост, добавляем еще и localhost, и ищем в каталоге www все упоминания localhost. Их оказалось в domains/tune-it/config/domain.xml и nodeagents/na_www/agent/config/domain.xml, заменил на FQDN. Перезапускаю домен и агента - ситуация не меняется абсолютно.

При этом на старой машине этот же glassfish замечатально работает, правда там используется java/jdk6, который не обновлялся уже очень давно, из-за чего и стал ставить openjdk. Хорошо, сносим openjdk, собираем java/jdk6. Пересоздаём домены и агентов - безрезультатно. Перепроверяю соответствующие domain.xml - ситуация не меняется. При этом grep -r localhost упорно настаивает на том, что это слово явно встречается еще и в каких-то .class файлах.

Ладно. Как говориться - уговорили, отдаю в jail не только "внешний" IP, но и 127.0.0.1:

jail_tmp_ip="192.168.46.8,127.0.0.1"

Полностью повторяем все пляски с обеими версиями JDK, исправлениями /etc/hosts и domain.xml. И что бы Вы думали? Правильно - абсолютный нуль толку. Хорошо. давайте перетянем уже готовый домен с агентом с временной машины и проверим на нём. Сказано - сделано. Перетащили. После соответствующих исправлений в конфигурационных файлах запускаем домен - всё хорошо, запускаем агента - в какой-то момент ругается на невозможность запустить виртуальную машину. Вроде такого раньше не было.

После несколькочасовых плясок с бубном выясняется, что добрые разработчики glassfish в imq/bin/imqbrokerd  по умолчанию ставят опцию -Xss128k, а потом путём каких-то хитрых манипуляций в случае запуска на 64-х битной JVM переопределяют его в -Xss256. Только эти хитрые манипуляции в моём случае почему-то не срабатывают, посему лечим проблему прямой заменой в самом файле. Изменили. Только вот легче всё равно не стало - статус агента пуст, агент ругается на неавторизованные запросы.

Ладно. Исходная система, которая временная и с которой мигрируем, работает в 32-х битном режиме, тогда как новая - в 64-х. Может быть таки glassfish просто не способен работать на 64-х битной java? Пересобираем мир в 32-х битном режиме, инсталлируем его в jail, собираем там 32-х битный JDK, переставляем glassfish, пересоздаём домен и агента, пробуем и openjdk и sun-овский jdk. Время потеряно, результата - нет.

Раз результата нет, попытаемся зайти с другой стороны. Начинаем смотреть в вывод netstat -an на старой и новой системах. На старой обнаруживается некоторое количество соединений на localhost, упоминания которого встречались в .class файлах, а на новой - таких нет. Возникает смутная догадка. Запускаем в jail-е sshd на localhost:

/usr/sbin/sshd -o Port=2222 -o ListenAddress=127.0.0.1

И проверяем результат:

# netstat -an | grep 2222
tcp4       0      0 192.168.xxx.xxx.2222      *.*                    LISTEN

При этом telnet localhost 2222 замечательно отрабатывает.

Оказывается, в jail адрес 127.0.0.1 является не совсем честным, и попытки сделать на него bind() приводят к тому, что фактически сокет привязывается к "внешнему" адресу, попытки соединения при этом на 127.0.0.1 успешно проходят, но в качестве адреса источника и назначения используется "внешний" адрес. А коварный агент glassfish-а увидев в качестве адреса соединения адрес отличный от 127.0.0.1 считает его неавторизованным.

Что же делать как же быть? В FreeBSD можно запустить jail с указанием ip4=inherit, и в этом случае в jail становятся доступны все IP адрес исходной системы, но штатный /etc/rc.d/jail таким функционалом не обладает. Что ж, достаём уже свой скрипт, используемый мной при запуске некоторых других приложений в jail, и запускаем jail уже через него.

Достаём обратно 64-х битный jail с openjdk, запускаем glassfish. Ура! Всё работает! Победа.

Коротко о себе:

Работаю в компании Tune-IT и тьютором кафедры Вычислительной техники в СПбГУИТМО.

Очень люблю команду cat, core solaris и IPv6.