AmazonLinuxでselenium + chromedriver + headlessするメモ

※2018/09/04追記:今はもっと簡単に導入できるので当記事みたいな苦労は不要。

※ここではselenium-server-standaloneを使ってるけど直近のselenium-webdriverで非remoteの場合はselenium-server-standaloneなしで動かした方がよさそう。

これまでAmazonLinuxでseleniumをheadlessモード(Xvfbを使った昔ながらのやり方ではなくchromefirefoxに機能として備わったheadlessモード)で動かす際、chromefirefoxも導入がややこしかったが、AmazonLinux2はCentOS7チックになったためgoogle-chromeリポジトリを設定してyumするだけで入るようになった。

とりあえず備忘録として無印AmazonLinuxとAmazonLinux2での導入手順のメモを残しておく。
以下注記。

  • Dockerfileで記す。
  • dockerでのみ必要でAMIから生成した場合は不要な記述もある。
  • Amazon Linux 2のタイムゾーン設定は未調査なのでこのままだとダメかも。
  • 無印Amazon Linuxで使っていたintoli提供のchromeRPMは2017/12/29現在なくなってるかも。
  • PHP7でアプリを作ってる想定。
  • MITM ProxyはPython2.7で動かす想定。

無印Amazon Linux (2017.09以前)の場合

FROM amazonlinux:2017.09

# タイムゾーン
RUN echo 'ZONE="Asia/Tokyo"' > /etc/sysconfig/clock && \
    echo 'UTC=false' >> /etc/sysconfig/clock && \
    ln -snf /usr/share/zoneinfo/Japan /etc/localtime

# remi
RUN yum -y install http://rpms.famillecollet.com/enterprise/remi-release-6.rpm

# 日本語の画面キャプチャをするためにIPAフォント、AWS CLIのレスポンス加工したいときのためにjq
RUN yum -y install jq ipa-gothic-fonts ipa-mincho-fonts

# アプリケーション用のユーザとグループとログ出力場所
RUN yum -y install shadow-utils && \
    groupadd -g 800 appuser && \
    useradd -u 800 -g appuser -s /sbin/nologin appuser && \
    mkdir /var/log/appuser && \
    chown appuser:appuser /var/log/appuser

# PHP7
RUN yum -y install https://mirror.webtatic.com/yum/el6/latest.rpm && \
    yum -y install php70w php70w-cli php70w-common php70w-devel php70w-fpm php70w-gd php70w-mbstring php70w-mcrypt php70w-mysqlnd php70w-opcache php70w-pdo php70w-pear php70w-xml php70w-pecl-redis php70w-pecl-imagick php70w-intl && \
    yum -y install re2c gcc gcc-c++ libuuid-devel

# composer
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# selenium-server-standalone用Java
RUN yum -y install java-1.8.0-openjdk

# selenium系
# docker imageにunzipなかったので入れておく
RUN yum -y install unzip
# chrome driver
RUN curl -o /tmp/chromedriver.zip https://chromedriver.storage.googleapis.com/2.33/chromedriver_linux64.zip && unzip /tmp/chromedriver.zip chromedriver -d /usr/local/bin/
# chrome(intoli提供のRPM。2017/12/29現在なくなってる?)
RUN yum -y install https://intoli.com/blog/installing-google-chrome-on-centos/google-chrome-stable-60.0.3112.113-1.x86_64.rpm
# selenium server
RUN curl -o /usr/local/lib/selenium-server-standalone-3.6.0.jar http://selenium-release.storage.googleapis.com/3.6/selenium-server-standalone-3.6.0.jar
# chrome libs (ConsoleKitとpolkitは相互依存してるので単一コマンドで入れる必要あり)
RUN yum -y install http://ftp.riken.jp/Linux/centos/6/os/x86_64/Packages/ORBit2-2.14.17-6.el6_8.x86_64.rpm && \
    yum -y install http://ftp.riken.jp/Linux/centos/6/os/x86_64/Packages/ConsoleKit-libs-0.4.1-6.el6.x86_64.rpm && \
    yum -y install http://ftp.riken.jp/Linux/centos/6/os/x86_64/Packages/eggdbus-0.6-3.el6.x86_64.rpm && \
    yum -y install http://ftp.riken.jp/Linux/centos/6/os/x86_64/Packages/ConsoleKit-0.4.1-6.el6.x86_64.rpm http://ftp.riken.jp/Linux/centos/6/os/x86_64/Packages/polkit-0.96-11.el6.x86_64.rpm && \
    yum -y install http://ftp.riken.jp/Linux/centos/6/os/x86_64/Packages/GConf2-2.28.0-7.el6.x86_64.rpm

# MITM Proxy
# chromedriverのバグで相手サーバのオレオレ証明書の検証をスキップできないのでMITM Proxyを相手サーバの検証をスキップするモードで動かし、chromedriverにはMITM Proxyの証明書を信頼させる形で回避。
# Chrome M65で新オプション導入されるのでそれまでMITM Proxyでしのぐ。
# https://bugs.chromium.org/p/chromium/issues/detail?id=721739
# 最新のMITM Proxyはpython3用なので2.7系なら0.18.2を明示して導入。
# docker imageにpipなかったので入れておく
RUN yum -y install python27-devel python27-pip
RUN yum -y install libxml2-devel libxslt-devel openssl-devel && \
    pip install "mitmproxy==0.18.2"

# php.iniや自分のアプリの起動、MITM Proxyの起動スクリプトはここでコピー
COPY etc/php.d/php.ini /etc/php.d/
COPY etc/init.d/selenium /etc/init.d/
COPY etc/init.d/mitmproxy /etc/init.d/
RUN chmod 755 /etc/init.d/selenium && \
    chmod 755 /etc/init.d/mitmproxy

# サービスの定期実行
RUN echo '*/10 * * * * /usr/bin/php -c /etc/php.ini /opt/apps/myapp/start.php >> /var/log/appuser/output.log 2>&1' >> /var/spool/cron/appuser && \
    chkconfig crond on && \
    chkconfig selenium on && \
    chkconfig mitmproxy on

Amazon Linux 2 (2017.12以降)の場合

FROM amazonlinux:2017.12

# タイムゾーン(無印からコピーしてきて未検証のままなので効いてないかも)
RUN echo 'ZONE="Asia/Tokyo"' > /etc/sysconfig/clock && \
    echo 'UTC=false' >> /etc/sysconfig/clock && \
    ln -snf /usr/share/zoneinfo/Japan /etc/localtime

# remi(無印だとepelが入ってたが2だと入ってないっぽい)
RUN yum -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
RUN yum -y install http://rpms.famillecollet.com/enterprise/remi-release-7.rpm

# 日本語の画面キャプチャをするためにIPAフォント、AWS CLIのレスポンス加工したいときのためにjq
RUN yum -y install jq ipa-gothic-fonts ipa-mincho-fonts

# アプリケーション用のユーザとグループとログ出力場所
RUN yum -y install shadow-utils && \
    groupadd -g 800 appuser && \
    useradd -u 800 -g appuser -s /sbin/nologin appuser && \
    mkdir /var/log/appuser && \
    chown appuser:appuser /var/log/appuser

# PHP7
RUN yum -y install php70 php70-php-cli php70-php-common php70-php-devel php70-php-fpm php70-php-gd php70-php-mbstring php70-php-mcrypt php70-php-mysqlnd php70-php-opcache php70-php-pdo php70-php-pear php70-php-xml php70-php-pecl-redis php70-php-pecl-imagick php70-php-intl && \
    yum -y install re2c gcc gcc-c++ libuuid-devel

# composer
# デフォルトでphp70にパスが通ってないためenableスクリプトを実行してphpへのパスを通す
RUN source /opt/remi/php70/enable && \
    curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# selenium-server-standalone用Java
RUN yum -y install java-1.8.0-openjdk

# selenium系
# docker imageにunzipなかったので入れておく
RUN yum -y install unzip
# chrome driver
RUN curl -o /tmp/chromedriver.zip https://chromedriver.storage.googleapis.com/2.34/chromedriver_linux64.zip && unzip /tmp/chromedriver.zip chromedriver -d /usr/local/bin/
# chrome(本当はファイルコピーがよいが手抜きでそのまま書いてしまった)
RUN echo '[google-chrome]' > /etc/yum.repos.d/google-chrome.repo && \
    echo 'name=google-chrome' >> /etc/yum.repos.d/google-chrome.repo && \
    echo 'baseurl=http://dl.google.com/linux/chrome/rpm/stable/$basearch' >> /etc/yum.repos.d/google-chrome.repo && \
    echo 'enabled=1' >> /etc/yum.repos.d/google-chrome.repo && \
    echo 'gpgcheck=1' >> /etc/yum.repos.d/google-chrome.repo && \
    echo 'gpgkey=https://dl-ssl.google.com/linux/linux_signing_key.pub' >> /etc/yum.repos.d/google-chrome.repo
RUN yum -y install google-chrome-stable
# selenium server
RUN curl -o /usr/local/lib/selenium-server-standalone-3.6.0.jar http://selenium-release.storage.googleapis.com/3.6/selenium-server-standalone-3.6.0.jar


# MITM Proxy
# chromedriverのバグで相手サーバのオレオレ証明書の検証をスキップできないのでMITM Proxyを相手サーバの検証をスキップするモードで動かし、chromedriverにはMITM Proxyの証明書を信頼させる形で回避。
# Chrome M65で新オプション導入されるのでそれまでMITM Proxyでしのぐ。
# https://bugs.chromium.org/p/chromium/issues/detail?id=721739
# 最新のMITM Proxyはpython3用なので2.7系なら0.18.2を明示して導入。
# docker imageにpipなかったので入れておく
RUN yum -y install python-devel python2-pip
RUN yum -y install libxml2-devel libxslt-devel openssl-devel && \
    pip install "mitmproxy==0.18.2"

# php.iniや自分のアプリの起動、MITM Proxyの起動スクリプトはここでコピー
# Amazon Linux 2はCentOS7同様systemdだが手抜きで無印と同じinit.dでやってる
COPY etc/php.d/php.ini /etc/opt/remi/php70/php.ini
COPY etc/init.d/selenium /etc/init.d/
COPY etc/init.d/mitmproxy /etc/init.d/
RUN chmod 755 /etc/init.d/selenium && \
    chmod 755 /etc/init.d/mitmproxy

# サービスの定期実行
# デフォルトでphp70にパスが通ってないためenableスクリプトを実行してphpへのパスを通す
# Amazon Linux 2はCentOS7同様systemdだが手抜きで無印と同じchkconfigでやってる
RUN echo '*/10 * * * * source /opt/remi/php70/enable && php /opt/apps/myapp/start.php >> /var/log/appuser/output.log 2>&1' >> /var/spool/cron/appuser && \
    chkconfig crond on && \
    chkconfig selenium on && \
    chkconfig mitmproxy on

MITM Proxyのスクリプト

MITM Proxyは起動時に自身のキーペアを生成し公開鍵は「mitmproxy-ca-cert.pem」として出力する。
これを「/usr/share/pki/ca-trust-source/anchors」に格納して「update-ca-trust」コマンドを実行することでOS内で「mitmproxy-ca-cert.pem」が信頼できる証明書として登録される。
なお、MITM Proxyのキーペアは起動時に異なる内容で生成されるため、MITM Proxyのインストール時に「/usr/share/pki/ca-trust-source/anchors」にコピーしてもダメ。必ず起動の度にOSにそれを信頼させる必要がある。

start_mitmproxy.sh
#!/bin/bash

# mitmproxy only for chromedriver bug.

export PATH=$PATH:/usr/local/bin

count=`ps -ef | grep [m]itmdump | wc -l`
if [ $count -ne 0 ]; then
  echo "mitmproxy (mitmdump) is already started. Nothing to do."
  exit 0
fi

nohup mitmdump -p 28080 --insecure > /dev/null 2>&1 &

# mitmproxy generate mitmproxy-ca-cert.pem in FIRST START TIME (not a time of installed).
sleep 3
cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/share/pki/ca-trust-source/anchors
update-ca-trust enable
update-ca-trust extract
stop_mitmproxy.sh
#!/bin/bash

# mitmproxy only for chromedriver bug.

ps -ef | grep [m]itmdump |  awk '{ print "kill -9", $2 }' | sh

Selenium Serverのスクリプト

PHPの任意のアプリmyappはユーザappuserのcronで定期実行される想定(複数の並行実行あり)。
selenium-server-standaloneとMITM Proxyは立ち上げっぱなし。
selenium-server-standaloneを落とす際にmyappがひとつも動いていないことを確認する処理と落ちている間はmyapp起動cronを止める処理を入れている(myapp自身の中にも/tmp/stop-selenium-server有無のチェックあり)。

start_selenium_server.sh
#!/bin/bash

export PATH=$PATH:/usr/local/bin

# it is heavy logs
# -Dwebdriver.chrome.logfile=${CHROME_LOG}
# -Dwebdriver.chrome.verboseLogging=true
CHROME_LOG=/var/log/appuser/chromedriver.log-wor

SELENIUM_LOG=/var/log/appuser/selenium.log-wor
SELENIUM_SERVER_JAR=/usr/local/lib/selenium-server-standalone-3.6.0.jar

count=`ps -ef | grep ${SELENIUM_SERVER_JAR} | grep -v grep | wc -l`
if [ $count -ne 0 ]; then
  echo "Selenium Server is already started. Nothing to do."
  exit 0
fi

nohup java -Dwebdriver.chrome.logfile=${CHROME_LOG} -Dselenium.LOGGER=${SELENIUM_LOG} -Dselenium.LOGGER.level=WARNING -jar ${SELENIUM_SERVER_JAR} -enablePassThrough false > /dev/null 2>&1 &

echo "Remove the stop Selenium Server flag file."
rm -rf /tmp/stop-selenium-server

if [ -e /var/spool/cron/appuser ]; then
  echo "There is new appuser cron file. Remove old it."
  rm -rf /var/spool/cron/.appuser
else
  echo "Enable appuser cron."
  mv -i /var/spool/cron/{.,}appuser
fi
stop_selenium_server.sh
#!/bin/bash

echo "Disable appuser cron."
if [ -e /var/spool/cron/appuser ]; then
  mv -i /var/spool/cron/{,.}appuser
fi

echo "Create the stop Selenium Server flag file."
touch /tmp/stop-selenium-server

sleep 2

while :
do
  count=`ps -ef | grep php | grep [M]yapp | grep -v "/bin/sh -c " | wc -l`
  if [ $count = 0 ]; then
    break
  fi
  echo "There are processes of myapp count[${count}]. Wait..."
  sleep 5
done
echo "There is no process of myapp. OK."

ps -ef | grep [s]elenium-server-standalone |  awk '{ print "kill -9", $2 }' | sh