intra-mart WebPlatform 7.2 の Docker を作成する

この CookBook では、intra-mart WebPlatform 7.2 の Docker の作成手順について紹介しています。

レシピ

  1. ベースイメージの作成
  2. intra-mart WebPlatform 7.2 の Docker イメージを作成します
  3. 実行します

1. ベースイメージの作成

CentOS 5.11 を使用します。

Dockerfile
FROM centos:centos5.11

EXPOSE 22

ENV DEBIAN_FRONTEND noninteractive

# CentOS-Base.repo
COPY CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo

# yum
RUN yum clean all && yum -y update

# locale
RUN yum reinstall -y glibc-common
RUN localedef -i ja_JP -f UTF-8 ja_JP.utf8
RUN touch /etc/sysconfig/i18n
RUN echo 'LANG="ja_JP.UTF-8"' >> /etc/sysconfig/i18n
ENV LANG ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja

# timezone
RUN yum install -y tzdata
RUN echo 'ZONE="Asia/Tokyo"' > /etc/sysconfig/clock
RUN echo 'UTC=false' >> /etc/sysconfig/clock
RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

# tools
RUN yum groupinstall -y 'Development Tools'
RUN yum install -y wget curl vim emacs tar unzip mlocate perl ssh openssh-server openssl-devel

# root passwd
RUN bash -c 'echo "root:password" | chpasswd'

# ssh
RUN sed -i -e "s/#PasswordAuthentication yes/PasswordAuthentication yes/g" /etc/ssh/sshd_config
RUN sed -i -e "s/#PermitRootLogin yes/PermitRootLogin yes/g" /etc/ssh/sshd_config
RUN sed -i -e "s/UsePAM yes/UsePAM no/g" /etc/ssh/sshd_config

RUN updatedb

CMD /etc/init.d/sshd restart && /bin/bash
CentOS-Base.repo
# CentOS-Base.repo
#
# The mirror system uses the connecting IP address of the client and the
# update status of each mirror to pick mirrors that are updated to and
# geographically close to the client.  You should use this for CentOS updates
# unless you are manually picking other mirrors.
#
# If the mirrorlist= does not work for you, as a fall back you can try the
# remarked out baseurl= line instead.
#
#

[base]
name=CentOS-$releasever - Base
#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os
baseurl=http://vault.centos.org/5.11/os/x86_64/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5

#released updates
[updates]
name=CentOS-$releasever - Updates
#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=updates
baseurl=http://vault.centos.org/5.11/updates/x86_64/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5

#additional packages that may be useful
[extras]
name=CentOS-$releasever - Extras
#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=extras
baseurl=http://vault.centos.org/5.11/extras/x86_64/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5

#additional packages that extend functionality of existing packages
[centosplus]
name=CentOS-$releasever - Plus
#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=centosplus
baseurl=http://mirror.centos.org/centos/5.11/centosplus/x86_64/
gpgcheck=1
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5

#contrib - packages by Centos Users
[contrib]
name=CentOS-$releasever - Contrib
#mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=contrib
baseurl=http://mirror.centos.org/centos/5.11/contrib/x86_64/
gpgcheck=1
enabled=0
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5

[libselinux]
name=CentOS-5 - libselinux
baseurl=http://vault.centos.org/5.11/centosplus/x86_64/
gpgcheck=1
enabled=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-5
includepkgs=libselinux*

ロケールを ja_JP, タイムゾーンを Asia/Tokyo に変更し、root/password で ssh 接続ができるように設定します。
また、そのままでは yum が利用できないため、リポジトリの参照先を http://vault.centos.org に変更しています(CentOS-Base.repo)。
参考:Yum update error on CentOS 5.6

mycentos:5.11 というタグでビルドします。

docker build -t mycentos:5.11 .

2. intra-mart WebPlatform の Docker イメージを作成します

先ほど作成したベースイメージを利用します。
jdk-6u45-linux-x64.bin を事前にダウンロードしておきます。
entrykit_0.4.0_Linux_x86_64.tgz を事前にダウンロードしておきます。
iWP 7.2 の setup.jar も事前に準備しておきます。
また、事前に java -jar setup.jar -s iwp7.2.0.install を実行して保存したサイレントインストール用のファイル(iwp7.2.0.install)を準備しておきます。
サイレントインストールについては intra-mart WebPlatform をサイレントインストールする方法を参照してください。
さらに、以下のように data-source.xml, http.xml を事前に用意しておきます。

Dockerfile
FROM mycentos:5.11

EXPOSE 22 8080 9009

ENV DEBIAN_FRONTEND noninteractive

# yum
RUN yum -y update && yum clean all
RUN yum -y upgrade && yum -y update && yum clean all

# JDK
COPY jdk-6u45-linux-x64.bin /jdk-6u45-linux-x64.bin
COPY setup_jdk.sh /setup_jdk.sh
RUN chmod u+x /setup_jdk.sh
RUN /setup_jdk.sh
RUN rm -f /setup_jdk.sh
ENV JAVA_HOME /usr/local/java/jdk
ENV PATH /usr/local/java/jdk/bin:$PATH
RUN echo 'JAVA_HOME=/usr/local/java/jdk' >> /root/.bashrc
RUN echo 'PATH=$PATH:/usr/local/java/jdk/bin' >> /root/.bashrc
RUN rm -f /jdk-6u45-linux-x64.bin

# iwp
COPY iwp7.2.0.install /iwp7.2.0.install
COPY setup.jar /setup.jar
COPY setup_iwp.sh /setup_iwp.sh
RUN chmod u+x /setup_iwp.sh
RUN /setup_iwp.sh
RUN rm -f /setup_iwp.sh
COPY data-source.xml /usr/local/imart/conf/data-source.xml
COPY http.xml.tmpl /usr/local/imart/conf/http.xml.tmpl

# JDBC Driver
RUN curl -L -C - -O https://jdbc.postgresql.org/download/postgresql-8.4-703.jdbc4.jar
RUN mv /postgresql-8.4-703.jdbc4.jar /usr/local/imart/lib/postgresql-8.4-703.jdbc4.jar

# entrykit
COPY entrykit_0.4.0_Linux_x86_64.tgz /entrykit_0.4.0_Linux_x86_64.tgz
RUN tar zxvf /entrykit_0.4.0_Linux_x86_64.tgz
RUN mv /entrykit /usr/local/bin/
RUN entrykit --symlink
RUN rm -f /entrykit_0.4.0_Linux_x86_64.tgz

COPY run.sh /run.sh
RUN chmod +x /run.sh

RUN updatedb

ENTRYPOINT [ "render", "/usr/local/imart/conf/http.xml", "--" ]
CMD [ "/run.sh" ]

細かいセットアップはシェルスクリプトに逃がし、Dockerfile からはそれを実行するようにします。
これにより、Dockerfile をシンプルにでき、かつイメージ容量の削減にもつながります。

http.xml ファイルに PostgreSQL への接続情報を環境変数で受け取れるようにします。
そのための処理を entrykit の render 機能で実現しています。
http.xml の元となるファイルを /usr/local/imart/conf/http.xml.tmpl に COPY コマンドでコピーし、ENTRYPOINT にて render しています。
こうすることで、docker run 時に entrykit の render を動かし、/usr/local/imart/conf/http.xml.tmpl を元に /usr/local/imart/conf/http.xml を生成します。

setup_jdk.sh
#!/bin/sh

chmod u+x /jdk-6u45-linux-x64.bin
/jdk-6u45-linux-x64.bin
rm -f /jdk-6u45-linux-x64.bin

mkdir -p /usr/local/java
mv jdk1.6.0_45 /usr/local/java/jdk1.6.0_45
ln -s /usr/local/java/jdk1.6.0_45 /usr/local/java/jdk

Java 1.6.0_45 のセットアップスクリプトです。

setup_iwp.sh
#!/bin/sh

java -jar /setup.jar -f /iwp7.2.0.install
sed -i -e "s/<option>/<option>-Xdebug -Xrunjdwp:transport=dt_socket,address=9009,server=y,suspend=n /g" /usr/local/imart/conf/imart.xml

iWP 7.2.0 のセットアップスクリプトです。
サイレントインストールファイル(iwp7.2.0.install)を利用してサイレントインストールを行います。
また、Java のリモートデバッグをポート 9009 で開放するためのオプションを追加で設定しています。

iwp7.2.0.install
1
3
y
n
y
/usr/local/java/jdk
/usr/local/imart
1
1
4
4
127.0.0.1
127.0.0.1
8080
8080
8080
49152
APP:127.0.0.1:8080
128
2048
y
y

事前に java -jar setup.jar -s iwp7.2.0.install にてサイレントインストール用のファイルを作成します。
IP アドレスは docker run 時に決まるため、サイレントインストール時には 127.0.0.1 を指定します。
docker run 時に実行する run.sh ファイルにて、設定ファイル中の 127.0.0.1 を実際のコンテナの IP アドレスに置き換える処理を入れています。

run.sh
#!/bin/bash

/etc/init.d/sshd restart

IP=$(ip addr show eth0 | perl -n -e 'if (m/inet ([\d\.]+)/g) { print $1 }')

sed -i -e "s/127\.0\.0\.1/${IP}/g" /usr/local/imart/conf/http.xml
sed -i -e "s/127\.0\.0\.1/${IP}/g" /usr/local/imart/conf/imart.xml

touch /usr/local/imart/bin/alone.log
java -cp /usr/local/imart/bin/imart.jar -Xms16m -Xmx128m jp.co.intra_mart.bin.server.ServerController -lonely >> /usr/local/imart/bin/alone.log 2>&1 < /dev/null &
tail -F /usr/local/imart/bin/alone.log

docker run 時に実行するスクリプトです。
sshd と iWP を起動しています。
また、http.xml と imart.xml にインストール時に指定した IP アドレス 127.0.0.1 を、Docker コンテナ実行時の IP アドレスに置換しています。

myiwp というタグでビルドします。

docker build -t myiwp:7.2.0 .
http.xml.tmpl
<!--
   - Resin 3.1 configuration file.
  -->
<resin xmlns="http://caucho.com/ns/resin"
       xmlns:resin="http://caucho.com/ns/resin/core">

  <!-- for jaxb -->
  <system-property javax.xml.bind.JAXBContext="com.sun.xml.bind.v2.ContextFactory"/>

  <!-- for StAX -->
  <system-property javax.xml.stream.XMLEventFactory="com.ctc.wstx.stax.WstxEventFactory"/>
  <system-property javax.xml.stream.XMLInputFactory="com.ctc.wstx.stax.WstxInputFactory"/>
  <system-property javax.xml.stream.XMLOutputFactory="com.ctc.wstx.stax.WstxOutputFactory"/>

  <!--
     - Logging configuration for the JDK logging API.
    -->
  <log name="">
    <handler type="jp.co.intra_mart.common.platform.log.handler.JDKLoggingOverIntramartLoggerHandler"/>
  </log>

  <!--
     - 'info' for production
     - 'fine' or 'finer' for development and troubleshooting
    -->
  <logger name="com.caucho" level="info"/>
  <logger name="com.caucho.sql.spy" level="fine"/>

  <logger name="com.caucho.java" level="config"/>
  <logger name="com.caucho.loader" level="config"/>

  <!--
     - For production sites, change dependency-check-interval to something
     - like 600s, so it only checks for updates every 10 minutes.
    -->
  <dependency-check-interval>2s</dependency-check-interval>

  <!--
     - You can change the compiler to "javac", "eclipse" or "internal".
    -->
  <javac compiler="javac" args="-g -source 1.5"/>

  <cluster id="app-tier">
    <!-- sets the content root for the cluster, relative to server.root -->
    <root-directory>.</root-directory>

    <server-default>

      <!--
         - Configures the minimum free memory allowed before Resin
         - will force a restart.
        -->
      <memory-free-min>1M</memory-free-min>

      <!-- Maximum number of threads. -->
      <thread-max>256</thread-max>

      <!-- Configures the socket timeout -->
      <socket-timeout>65s</socket-timeout>

      <!-- Configures the keepalive -->
      <keepalive-max>128</keepalive-max>
      <keepalive-timeout>15s</keepalive-timeout>

    </server-default>

    <!-- define the servers in the cluster -->
    <server id="APP:127.0.0.1:8080" address="127.0.0.1">
      <cluster-port port="6800" secure="false"/>

      <http port="8080" secure="false"/>

    </server>

    <!--
       - Defaults applied to each web-app.
      -->
    <web-app-default>
      <prologue>
        <!--
           - Enable EL expressions in Servlet and Filter init-param
          -->
        <allow-servlet-el>true</allow-servlet-el>
      </prologue>

      <!--
         - Sets timeout values for cacheable pages, e.g. static pages.
        -->
      <cache-mapping url-pattern="/" expires="5s"/>
      <cache-mapping url-pattern="*.gif" expires="60s"/>
      <cache-mapping url-pattern="*.jpg" expires="60s"/>
      <cache-mapping url-pattern="*.png" expires="60s"/>

      <!--
         - for security, disable session URLs by default.
        -->
      <session-config>
        <session-timeout>10</session-timeout>
        <session-max>4096</session-max>
        <enable-cookies>true</enable-cookies>
        <enable-url-rewriting>true</enable-url-rewriting>
        <reuse-session-id>false</reuse-session-id>
        <cookie-secure>true</cookie-secure>
      </session-config>

      <!--
         - For security, set the HttpOnly flag in cookies.
        -->
      <cookie-http-only>true</cookie-http-only>

      <!--
         - Some JSP packages have incorrect .tld files.  It's possible to
         - set validate-taglib-schema to false to work around these packages.
        -->
      <jsp>
        <validate-taglib-schema>true</validate-taglib-schema>
        <el-ignored>false</el-ignored>
        <fast-jstl>true</fast-jstl>
        <fast-jsf>false</fast-jsf>
        <ignore-el-exception>true</ignore-el-exception>
        <is-xml>false</is-xml>
        <precompile>true</precompile>
        <recompile-on-error>false</recompile-on-error>
        <require-source>false</require-source>
        <session>true</session>
        <velocity-enabled>false</velocity-enabled>
      </jsp>
    </web-app-default>

    <!-- includes the app-default for default web-app behavior -->
    <resin:import path="${resin.home}/conf/app-default.xml"/>

    <!--
       - Sample database pool configuration
       -
       - The JDBC name is java:comp/env/jdbc/test
       -
         - For Oracle
         <database>
           <jndi-name>jdbc/oracle</jndi-name>
           <driver>
             <type>oracle.jdbc.pool.OracleConnectionPoolDataSource</type>
             <url>jdbc:oracle:thin:@localhost:1521:dbname</url>
             <user>username</user>
             <password>password</password>
            </driver>
            <prepared-statement-cache-size>8</prepared-statement-cache-size>
            <max-connections>20</max-connections>
            <max-idle-time>30s</max-idle-time>
          </database>

         - For SQL Server
         <database>
           <jndi-name>jdbc/sqlserver</jndi-name>
           <driver>
             <type>com.microsoft.sqlserver.jdbc.SQLServerDriver</type>
             <url>jdbc:sqlserver://localhost:1433;DatabaseName=dbname</url>
             <user>username</user>
             <password>password</password>
             <init-param>
               <param-name>SelectMethod</param-name>
               <param-value>cursor</param-value>
             </init-param>
           </driver>
           <prepared-statement-cache-size>8</prepared-statement-cache-size>
           <max-connections>20</max-connections>
           <max-idle-time>30s</max-idle-time>
         </database>

         - For DB2
         <database>
           <jndi-name>jdbc/db2</jndi-name>
           <driver>
             <type>com.ibm.db2.jcc.DB2Driver</type>
             <url>jdbc:db2://localhost:50000/dbname</url>
             <user>username</user>
             <password>password</password>
           </driver>
           <prepared-statement-cache-size>8</prepared-statement-cache-size>
           <max-connections>20</max-connections>
           <max-idle-time>30s</max-idle-time>
         </database>

         - For PostgreSQL
         <database>
           <jndi-name>jdbc/postgres</jndi-name>
           <driver>
             <type>org.postgresql.Driver</type>
             <url>jdbc:postgresql://localhost:5432/dbname</url>
             <user>username</user>
             <password>password</password>
           </driver>
           <prepared-statement-cache-size>8</prepared-statement-cache-size>
           <max-connections>20</max-connections>
           <max-idle-time>30s</max-idle-time>
         </database>
    -->

    <database>
      <jndi-name>jdbc/postgres</jndi-name>
      <driver>
        <type>org.postgresql.Driver</type>
        <url>{{ var "PG_URL" | default "jdbc:postgresql://localhost:5432/dbname" }}</url>
        <user>{{ var "PG_USER" | default "username" }}</user>
        <password>{{ var "PG_PASSWORD" | default "password" }}</password>
      </driver>
      <prepared-statement-cache-size>8</prepared-statement-cache-size>
      <max-connections>20</max-connections>
      <max-idle-time>30s</max-idle-time>
    </database>

    <!--
       - Configures the persistent store for single-server or clustered
       - in Resin professional.
      -->
    <!--
    <resin:if test="${resin.professional}">
      <persistent-store type="jdbc">
        <init>
          <data-source>jdbc/sessionDB</data-source>
        </init>
      </persistent-store>
    </resin:if>
    -->

    <!--
       - Default host configuration applied to all virtual hosts.
      -->
    <host-default>
      <!--
         - With another web server, like Apache, this can be commented out
         - because the web server will log this information.
        -->
      <access-log path="log/access.log"
            format='%h %l %u %t "%r" %s %b "%{Referer}i" "%{User-Agent}i"'
            rollover-period="1W"/>

      <!-- creates the webapps directory for .war expansion -->
      <web-app-deploy path="webapps"/>

      <!-- creates the deploy directory for .ear expansion -->
      <ear-deploy path="deploy">
        <ear-default>
          <ejb-server>
            <config-directory>WEB-INF</config-directory>
          </ejb-server>
        </ear-default>
      </ear-deploy>

    </host-default>

    <!-- configures the default host, matching any host name -->
    <host id="" root-directory=".">
      <!--
         - configures an explicit root web-app matching the
         - webapp's ROOT
        -->
      <web-app id="/" root-directory="webapps/ROOT"/>

      <web-app id="/imart" root-directory="${resin.home}/doc/imart" redeploy-mode="manual">
        <!--
        <session-config>
          <use-persistent-store>true</use-persistent-store>
          <always-save-session>true</always-save-session>
          <save-mode>after-request</save-mode>
        </session-config>
        -->
      </web-app>

    </host>
  </cluster>
</resin>

事前に iWP 7.2.0 をインストールしそのインストール先のフォルダから http.xml を http.xml.tmpl としてコピーします。
さらに、199行~210行目に PostgreSQL で接続するためのデータベース接続先を定義します(この CookBook の Docker では PostgreSQL のみ対応することとします)。

行数 環境変数 説明
203 PG_URL PostgreSQL データベースへの接続先 URL の情報を、PG_URL 環境変数として受け取るようにしています。
204 PG_USER PostgreSQL データベースへの接続先ユーザの情報を、PG_USER 環境変数として受け取るようにしています。
205 PG_PASSWORD PostgreSQL データベースへの接続先ユーザのパスワードの情報を、PG_PASSWORD 環境変数として受け取るようにしています。

docker run 時に -e オプションにて PG_URL, PG_USER, PG_PASSWORD 環境変数を指定してもらう作りとしています。
そのため、http.xml ファイルの199行~210行目を追加し、環境変数に応じて設定値を変更する処理を行っています。
設定ファイル中にこのように記載することで entrykit の render 実行時に環境変数の値に置き換えることができます。

data-source.xml
<?xml version="1.0" encoding="UTF-8"?>
<data-source>

    <system-data-source>
        <connect-id>default</connect-id>
        <resource-ref-name>java:comp/env/jdbc/postgres</resource-ref-name>
    </system-data-source>
    <group-data-source>
        <login-group-id>default</login-group-id>
        <resource-ref-name>java:comp/env/jdbc/postgres</resource-ref-name>
    </group-data-source>

</data-source>

java:comp/env/jdbc/postgres を設定済みの data-source.xml を用意します。

3. 実行します

docker run -it \
    -p 8080:8080 \
    -p 9009:9009 \
    -p 2222:22 \
    -e PG_URL=jdbc:postgresql://mypostgresql.example.com:5432/iwp \
    -e PG_USER=imart \
    -e PG_PASSWORD=imart \
    myiwp:7.2.0

この CookBook のコンテナは、PostgreSQL のみ対応しています。
PostgreSQL を別途用意し、docker run 時の PG_URL, PG_USER, PG_PASSWORD 環境変数にその接続情報を渡します。
上記の例では、mypostgresql.example.com:5432 の DB=iwp, user=imart, password=imart を利用します。

また、コンテナは root/password で ssh 接続ができるようになっています。
あくまで検証用の Docker としてこのように設定しているため、セキュリティにはご注意ください。

このように、Docker イメージ化するだけで、検証用の iWP をすぐに準備できます。
是非ご活用ください。