Docker + Alpine3.5 + Apache2.4 + Python2.7で、mod_pythonをソースコードからインストールしてみた

今までPython3.x & Apache2.4.xを使って、CGIをひと通り試してきました。

次は、CGIを別のものに置き換えてWebアプリケーションを作りたくなりました。

PythonのWebアプリケーションというとWSGIが思い浮かびます。ただ、CGI以降WSGI以前に登場したものを使ってみたくなりました。

調べてみると、

がありました。

 
PyApacheは、

Runs with Python1.X/2.X under Apache 1.3.X/2.0 (configurable).

[Sunday 18th. January 2004]

PyApache module

との記載があり、現在では使えそうにありませんでした。

 
mod_snakeも

I no longer have the time or motivation to work on mod_snake, so instead of letting it sit here, I have decided to kill this project.

RIP - Mod Snake

Jon Travis Last modified: Thu May 9 19:00:01 PDT 2002

RIP - Mod Snake

とあり、プロジェクトが終了していました。

 
mod_python

などの記事から終了したのかなと思いました。

ただ、その後、

にある強い想いとともに復活したようです。

今年に入ってからもGitHub上でcommitがあり、最新版であるmod_python 3.5の日本語ドキュメントも存在することからも、プロジェクトとして動いているようでした。

また、Python3対応も

The latest release is 3.5.0. Key feature of mod_python 3.5.0 is Python 3 support.

mod_python - Apache / Python Integration

とあり、一応対応できているようでした。

 
そこで今回、以下を参考に、Python2系でmod_pythonをインストールし、Hello worldしてみます。
インストール — Mod_python 3.5.0-1330047 ドキュメント

 
目次

 

環境

  • Mac OS X 10.11.6
  • Docker for Mac 17.03.1-ce-mac12
  • Alpine3.5 + Apache2.4.25 + Python 2.7.13
  • mod_python
    • GitHub上の最新版
    • 今回利用したコミットは8acf1b7

 

調査

Alpine Linuxのパッケージにはmod_pythonが無い

Docker + Alpine Linuxを使うため、Alpineのパッケージでmod_pythonを調べました。しかし、mod_pythonは無いようでした。
*python*での検索結果 | Alpine packages

 
Alpineでmod_pythonを使うには、自分でソースコードからインストールする必要がありそうでした。

 

mod_pythonコンパイル・インストールするために必要なパッケージについて

ドキュメントのインストールに記載がありました。
インストール — Mod_python 3.5.0-1330047 ドキュメント

Alpine Linuxの場合、以下のパッケージを用意すれば良さそうでした。

 

Dockerで配布しているApacheの状態について

今回はDockerで配布しているApache(2.4/alpine/Dockerfile)を使うことにします。
httpd - Docker Store

 
どんな状態のApacheなのかを調べてみます。

# Dockerコンテナを起動
$ docker container run -p 8081:80 --name httpd24 httpd:2.4.25-alpine

# 別のコンソールから、Apacheの状態を確認
$ docker container exec -it httpd24 httpd -V
Server version: Apache/2.4.25 (Unix)
Server built:   Mar  3 2017 21:57:00
Server's Module Magic Number: 20120211:67
Server loaded:  APR 1.5.2, APR-UTIL 1.5.4
Compiled using: APR 1.5.2, APR-UTIL 1.5.4
Architecture:   64-bit
Server MPM:     event
  threaded:     yes (fixed thread count)
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256
 -D HTTPD_ROOT="/usr/local/apache2"
 -D SUEXEC_BIN="/usr/local/apache2/bin/suexec"
 -D DEFAULT_PIDLOG="logs/httpd.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="conf/mime.types"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"

Server MPM: eventのため、event MPMで動いているようです。

ただ、mod_pythonに関する記事ではpreforkで動いているのを目にするのが多かったため、今回はeventからpreforkへMPMを切り替えることにします。

 
Apache2.2まではコンパイル時にMPMを指定する必要がありましたが、Apache2.4からはLoadModuleだけでMPMが切り替えられそうでした。
MPMが普通のモジュールになった | Apache2.2の設定ファイルをApache2.4に移植するためにやったことまとめ - Qiita

 
そのため、デフォルトでインストールされるモジュールを見てみたところ、

# modulesディレクトリには、mpmモジュールが存在しない
$ docker container exec -it httpd24 pwd
/usr/local/apache2

$ docker container exec -it httpd24 ls -al modules/
...
-rwxr-xr-x    1 root     root        177416 Mar  3 21:57 mod_lua.so
-rwxr-xr-x    1 root     root         23488 Mar  3 21:57 mod_macro.so
-rwxr-xr-x    1 root     root         27920 Mar  3 21:57 mod_mime.so
-rwxr-xr-x    1 root     root         37208 Mar  3 21:57 mod_mime_magic.so
-rwxr-xr-x    1 root     root         46872 Mar  3 21:57 mod_negotiation.so
...

# コンパイル時のモジュールを確認
$ docker container exec -it httpd24 httpd -l
Compiled in modules:
  core.c
  mod_so.c
  http_core.c
  event.c

# ロードされているモジュールを確認
$ docker container exec -it httpd24 httpd -M
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
Loaded Modules:
 core_module (static)
 so_module (static)
 http_module (static)
 mpm_event_module (static)
 authn_file_module (shared)
 authn_core_module (shared)
 authz_host_module (shared)
 authz_groupfile_module (shared)
 authz_user_module (shared)
 authz_core_module (shared)
 access_compat_module (shared)
 auth_basic_module (shared)
 reqtimeout_module (shared)
 filter_module (shared)
 mime_module (shared)
 log_config_module (shared)
 env_module (shared)
 headers_module (shared)
 setenvif_module (shared)
 version_module (shared)
 unixd_module (shared)
 status_module (shared)
 autoindex_module (shared)
 dir_module (shared)
 alias_module (shared)

と、デフォルトではMPM系のモジュールが無いようです。

これより、公式のDockerイメージではprefork MPMで動かすのが難しいと考えました。

 
そのため、公式のDockerfileを一部修正し、Apacheソースコードからインストールする方針とします。

修正部分は、./configureする時のオプションにおける

  • --with-mpm=preforkで、デフォルトのMPMをpreforkにする
  • --enable-mpms-shared=allで、MPM系モジュールを生成する

の2点です。

 

準備

Dockerfile

今回用意するDockerfileは

です。

#--------------------------------------------------
# Apache2.4のインストール:公式のDockerfileを流用
#--------------------------------------------------
FROM alpine:3.5

# ensure www-data user exists
RUN set -x \
  && addgroup -g 82 -S www-data \
  && adduser -u 82 -D -S -G www-data www-data
# 82 is the standard uid/gid for "www-data" in Alpine
# http://git.alpinelinux.org/cgit/aports/tree/main/apache2/apache2.pre-install?h=v3.3.2
# http://git.alpinelinux.org/cgit/aports/tree/main/lighttpd/lighttpd.pre-install?h=v3.3.2
# http://git.alpinelinux.org/cgit/aports/tree/main/nginx-initscripts/nginx-initscripts.pre-install?h=v3.3.2

ENV HTTPD_PREFIX /usr/local/apache2
ENV PATH $HTTPD_PREFIX/bin:$PATH
RUN mkdir -p "$HTTPD_PREFIX" \
  && chown www-data:www-data "$HTTPD_PREFIX"
WORKDIR $HTTPD_PREFIX

ENV HTTPD_VERSION 2.4.25
ENV HTTPD_SHA1 bd6d138c31c109297da2346c6e7b93b9283993d2

# https://issues.apache.org/jira/browse/INFRA-8753?focusedCommentId=14735394#comment-14735394
ENV HTTPD_BZ2_URL https://www.apache.org/dyn/closer.cgi?action=download&filename=httpd/httpd-$HTTPD_VERSION.tar.bz2
# not all the mirrors actually carry the .asc files :'(
ENV HTTPD_ASC_URL https://www.apache.org/dist/httpd/httpd-$HTTPD_VERSION.tar.bz2.asc

# see https://httpd.apache.org/docs/2.4/install.html#requirements
RUN set -x \
  && runDeps=' \
    apr-dev \
    apr-util-dev \
    perl \
  ' \
  && apk add --no-cache --virtual .build-deps \
    $runDeps \
    ca-certificates \
    coreutils \
    dpkg-dev dpkg \
    gcc \
    gnupg \
    libc-dev \
    # mod_session_crypto
    libressl \
    libressl-dev \
    # mod_proxy_html mod_xml2enc
    libxml2-dev \
    # mod_lua
    lua-dev \
    make \
    # mod_http2
    nghttp2-dev \
    pcre-dev \
    tar \
    # mod_deflate
    zlib-dev \
  \
  && wget -O httpd.tar.bz2 "$HTTPD_BZ2_URL" \
  && echo "$HTTPD_SHA1 *httpd.tar.bz2" | sha1sum -c - \
# see https://httpd.apache.org/download.cgi#verify
  && wget -O httpd.tar.bz2.asc "$HTTPD_ASC_URL" \
  && export GNUPGHOME="$(mktemp -d)" \
  && gpg --keyserver ha.pool.sks-keyservers.net --recv-keys A93D62ECC3C8EA12DB220EC934EA76E6791485A8 \
  && gpg --batch --verify httpd.tar.bz2.asc httpd.tar.bz2 \
  && rm -r "$GNUPGHOME" httpd.tar.bz2.asc \
  \
  && mkdir -p src \
  && tar -xf httpd.tar.bz2 -C src --strip-components=1 \
  && rm httpd.tar.bz2 \
  && cd src \
  \
  && gnuArch="$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)" \
  && ./configure \
    --build="$gnuArch" \
    --prefix="$HTTPD_PREFIX" \
    --enable-mods-shared=reallyall \
    # デフォルトはpreforkにする
    --with-mpm=prefork \
    # ただし、他のMPMもビルドする:この指定がない場合、MPMのmoduleがないので切り替えられない
    # http://tt4cs.blogspot.jp/2013/01/how-to-enable-dso-for-apache-2.4.html
    --enable-mpms-shared=all \
  && make -j "$(nproc)" \
  && make install \
  \
  && cd .. \
  && rm -r src man manual \
  \
  && sed -ri \
    -e 's!^(\s*CustomLog)\s+\S+!\1 /proc/self/fd/1!g' \
    -e 's!^(\s*ErrorLog)\s+\S+!\1 /proc/self/fd/2!g' \
    "$HTTPD_PREFIX/conf/httpd.conf" \
  \
  && runDeps="$runDeps $( \
    scanelf --needed --nobanner --recursive /usr/local \
      | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
      | sort -u \
      | xargs -r apk info --installed \
      | sort -u \
  )" \
  && apk add --virtual .httpd-rundeps $runDeps \
  && apk del .build-deps

# ローカルでchmod 755しておく
COPY httpd-foreground /usr/local/bin/

EXPOSE 80
CMD ["httpd-foreground"]


#--------------------------------------------------
# mod_pythonのインストール
#--------------------------------------------------
RUN apk --update --no-cache add python && \
    # mod_pythonで必要なパッケージを追加
    apk add --no-cache --virtual .mod_python_build_libs git && \
    apk add --no-cache --virtual .mod_python_build_libs python-dev && \
    apk add --no-cache --virtual .mod_python_build_libs apache2-dev && \
    apk add --no-cache --virtual .mod_python_build_libs flex && \
    # ./configureで必要
    apk add --no-cache --virtual .mod_python_build_libs build-base && \
    # sudo make installで必要
    apk add --no-cache --virtual .mod_python_build_libs sudo && \
    # GitHubからソースコードを持ってきてインストール
    cd /tmp && \
    mkdir mod_python && \
    cd mod_python && \
    git clone https://github.com/grisha/mod_python.git . && \
    ./configure --with-apxs=/usr/local/apache2/bin/apxs --with-python=/usr/bin/python --with-flex=/usr/bin/flex && \
    make && \
    sudo make install && \
    # 不要なパッケージやソースコードを一括削除
    apk del .mod_python_build_libs && \
    rm -r /tmp/mod_python


#--------------------------------------------------
# Apacheの設定
#--------------------------------------------------
# ローカルのhttpd.confをコピー
COPY httpd.conf /usr/local/apache2/conf/

 

httpd.conf

CGIで使ったものを流用します。

ただし、

  • CGI関係の記述を削除
  • MPMモジュールの指定を追加
    • LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
  • mod_python向けの記述を追加
    • LoadModule python_module modules/mod_python.so
  • mod_pythonのWebアプリを/usr/local/apache2/htdocs/testに配置するための記述を追加
    • <Directory "/usr/local/apache2/htdocs/test">ディレクティブ以下

を変更します。

# ServerRoot: The top of the directory tree
ServerRoot "/usr/local/apache2"

ServerName localhost
# Listen: Allows you to bind Apache to specific IP addresses and/or ports
Listen 80

# LoadModule
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule mime_module modules/mod_mime.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule env_module modules/mod_env.so
LoadModule setenvif_module modules/mod_setenvif.so
LoadModule unixd_module modules/mod_unixd.so
LoadModule status_module modules/mod_status.so

# for mod_python
LoadModule python_module modules/mod_python.so

# for running prefork MPM
LoadModule mpm_prefork_module modules/mod_mpm_prefork.so


# unixd_module settings
User daemon
Group daemon

# 'Main' server configuration

# ServerAdmin: Your address, where problems with the server should be e-mailed.
ServerAdmin you@example.com

# Deny access to the entirety of your server's filesystem.
<Directory />
    AllowOverride none
    Require all denied
</Directory>

# DocumentRoot
DocumentRoot "/usr/local/apache2/htdocs"
<Directory "/usr/local/apache2/htdocs">
    Options Indexes FollowSymLinks
    AllowOverride None
    Require all granted
</Directory>

<Directory "/usr/local/apache2/htdocs/test">
    AddHandler mod_python .py
    PythonHandler mptest
    PythonDebug On
</Directory>

# The following lines prevent .htaccess and .htpasswd files
<Files ".ht*">
    Require all denied
</Files>

# Log settings
ErrorLog /proc/self/fd/2
LogLevel warn

# log_config_module settings
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %b" common
CustomLog /proc/self/fd/1 common

# mime_module settings
TypesConfig conf/mime.types
AddType application/x-compress .Z
AddType application/x-gzip .gz .tgz

 

mod_pythonを使ったWebアプリ

チュートリアルにあったテストファイルを、ホストのpath/to/htdocs/testの中にmptest.pyとして用意します。

#!/usr/bin/python
# coding: utf-8
from mod_python import apache

def handler(req):
    req.content_type = 'text/plain'
    req.write("Hello World!")
    return apache.OK

Dockerコンテナ起動時に、このファイルをホストとDockerコンテナとで共有します。  
なお、shebangの値は、以下で調べたものを使いました。

$ docker container exec -it mod_python which python
/usr/bin/python

 

起動と動作確認

Dockerコンテナの起動

Dockerコンテナのイメージを作成し、起動します。

# Dockerコンテナイメージを作成
$ docker image build -t alpine:python27_httpd24_mod_python .

# Dockerコンテナを起動し、ホストのhtdocs以下をコンテナと共有
$ docker container run -p 8081:80 --name mod_python -v `pwd`/htdocs/:/usr/local/apache2/htdocs alpine:python27_httpd24_mod_python

 

Apacheの状況確認
# modulesディレクトリには、mpmモジュールが存在する
$ docker container exec -it mod_python pwd
/usr/local/apache2

$ docker container exec -it mod_python ls -al modules/
...
-rwxr-xr-x    1 root     root         37208 Jun  1 09:49 mod_mime_magic.so
-rwxr-xr-x    1 root     root         91720 Jun  1 09:49 mod_mpm_event.so
-rwxr-xr-x    1 root     root         48408 Jun  1 09:49 mod_mpm_prefork.so
-rwxr-xr-x    1 root     root         67720 Jun  1 09:49 mod_mpm_worker.so
...

# コンパイル時のモジュールを確認
$ docker container exec -it mod_python httpd -l
Compiled in modules:
  core.c
  mod_so.c
  http_core.c

# ロードされているモジュールを確認
$ docker container exec -it mod_python httpd -M
Loaded Modules:
 core_module (static)
 so_module (static)
 http_module (static)
 authz_core_module (shared)
 authz_host_module (shared)
 mime_module (shared)
 log_config_module (shared)
 env_module (shared)
 setenvif_module (shared)
 unixd_module (shared)
 status_module (shared)
 python_module (shared)
 mpm_prefork_module (shared)

# Vオプションで状況確認
$ docker container exec -it mod_python httpd -V
Server version: Apache/2.4.25 (Unix)
Server built:   Jun  1 2017 09:48:37
Server's Module Magic Number: 20120211:67
Server loaded:  APR 1.5.2, APR-UTIL 1.5.4
Compiled using: APR 1.5.2, APR-UTIL 1.5.4
Architecture:   64-bit
Server MPM:     prefork
  threaded:     no
    forked:     yes (variable process count)
Server compiled with....
 -D APR_HAS_SENDFILE
 -D APR_HAS_MMAP
 -D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
 -D APR_USE_SYSVSEM_SERIALIZE
 -D APR_USE_PTHREAD_SERIALIZE
 -D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
 -D APR_HAS_OTHER_CHILD
 -D AP_HAVE_RELIABLE_PIPED_LOGS
 -D DYNAMIC_MODULE_LIMIT=256
 -D HTTPD_ROOT="/usr/local/apache2"
 -D SUEXEC_BIN="/usr/local/apache2/bin/suexec"
 -D DEFAULT_PIDLOG="logs/httpd.pid"
 -D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
 -D DEFAULT_ERRORLOG="logs/error_log"
 -D AP_TYPES_CONFIG_FILE="conf/mime.types"
 -D SERVER_CONFIG_FILE="conf/httpd.conf"

mod_pythonがあり、prefork MPMで動作しているようです。

 

Webアプリの動作確認

curlを使って動作確認をします。

$ curl localhost:8081/test/mptest.py -D -
HTTP/1.1 200 OK
Date: Thu, 01 Jun 2017 10:38:23 GMT
Server: Apache/2.4.25 (Unix) mod_python/3.5.0-8acf1b7 Python/2.7.13
Transfer-Encoding: chunked
Content-Type: text/plain

Hello World!

動作しているようです。

 
ただ、コンテナを起動しているコンソールでは

[Thu Jun 01 10:39:29.700391 2017] [:notice] [pid 9] mod_python: (Re)importing module 'mptest'
172.17.0.1 - - [01/Jun/2017:10:39:29 +0000] "GET /test/mptest.py HTTP/1.1" 200 12
[Thu Jun 01 10:39:29.712520 2017] [:error] [pid 9] make_obcallback: could not import mod_python.apache.\n
ImportError: No module named mod_python.apache
[Thu Jun 01 10:39:29.712558 2017] [:error] [pid 9] make_obcallback: Python path being used "['/usr/lib/python27.zip', '/usr/lib/python2.7', '/usr/lib/python2.7/plat-linux2', '/usr/lib/python2.7/lib-tk', '/usr/lib/python2.7/lib-old', '/usr/lib/python2.7/lib-dynload']".
[Thu Jun 01 10:39:29.712566 2017] [:error] [pid 9] get_interpreter: no interpreter callback found.

と表示されていますが、現段階では気にしないことにします。

 
なお、DockerでApacheの再起動を普通にすると、

...
[Thu Jun 01 11:13:46.312764 2017] [core:notice] [pid 1] AH00052: child pid 129 exit signal Segmentation fault (11)
[Thu Jun 01 11:13:46.312770 2017] [core:error] [pid 1] AH00546: no record of generation 0 of exiting child 129
〜以降繰り返し〜
...

のようにSegmentation faultします。

 

ソースコード

GitHubに上げました。alpine_apache_python27_mod_pythonディレクトリの中が今回のものです。
thinkAmi-sandbox/Docker_Apache-sample