ubuntu20.04+dockerでphp-8.1+laravel8+seleniumしてみる

docker

前提

久々にseleniumを触る機会があったので、備忘録としてのメモ。前の記録と違ってseleniumはpipで入れているので、起動とか必要がなくなったバージョン。なるべく面倒は省いていかないと!

ベースの環境はWindow11+WSL2+Ubuntu20.4なので、異なる部分は読み替えてください。基本Linuxベースなので、パスぐらいだとは思います。

laravel8の準備

まず作業ディレクトリを作ります。ホームディレクトリの下にmyprojectというディレクトリを作ってます。

$ mkdir /home/ubuntu/myproject
$ cd /home/ubuntu/myproject

laravel8をインストール。プロジェクト名は「selenium-app」。

$ curl -s https://laravel.build/selenium-app | bash

docker環境の準備

必要になるディレクトリの作成

docker用ディレクトリを作成して、その下の必要ディレクトリも作成。

$ cd selenium-app
$ mkdir docker
$ cd docker
$ mkdir -p nginx/conf

最終的な構成は以下になる予定。

celenium-app/docker
├── app
├── Dockerfile
├── php-fpm.d
│   ├── zz-www.conf
│   └── php.ini
├── docker-compose.yml
└── nginx
    ├── Dockerfile
    └── conf
        └── default.conf

設定ファイルの作成

各種設定ファイルを作成していきます。

php設定ファイルの作成(php.ini)

php.iniを作成します。特に注意点はなし。

$ vim app/php.ini
zend.exception_ignore_args = off
expose_php = on
max_execution_time = 30
max_input_vars = 1000
upload_max_filesize = 64M
post_max_size = 128M
memory_limit = 256M
error_reporting = E_ALL
display_errors = on
display_startup_errors = on
log_errors = on
error_log = /dev/stderr
default_charset = UTF-8

[Date]
date.timezone = ${TZ}

[mysqlnd]
mysqlnd.collect_memory_statistics = on

[Assertion]
zend.assertions = 1

[mbstring]
mbstring.language = Japanese

php-fpmの設定ファイル作成(zz-www.conf)

php-fpmをsocket接続で利用する場合(今回の例のような場合)、php-fpmの設定ファイルはabc順でいう「zz-docker.conf」の名前よりも後ろに来るような名前にする必要があります。

これを書いている2022/10時点でも、「zz-docker.conf」よりも名称順で上に来る名前を付けると、「zz-docker.conf」の設定ファイルに上書きされます。なので、今回は設定ファイルの名称を「zz-www.conf」としています。

仕様なのかバグなのかは分かりませんが、上書きするならもっと若い名前にしてほしいものです・・・

$ vim app/php-fpm.d/zz-www.conf
[www]
user = www-data
group = www-data
listen = /var/run/php-fpm/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0666

nginx設定ファイルの作成

nginxの設定ファイルを作成します。例はphp-fpmがソケット接続になっている点に注意。host名については「selenium-app.localhost」にしています。

$ vim nginx/conf/default.conf
access_log /dev/stdout main;
error_log /dev/stderr warn;

server {
    listen 80;
    root /var/www/public;
    server_name selenium-app.localhost;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;
    client_max_body_size 20m;
    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}

アプリケーション側のDockerfileの作成

アプリケーション側(php実行側)のDockerfileを作成します。この例では、大体以下のような構成にしています。一応この設定ファイルにしてからchromeとchrome-driverのバージョン差異が起きたことはないですが、もし起きた場合には最新の方のバージョンを古い方に合わせて上げる必要があります。

  • php-fpmのバージョンは「8.1.8」
  • chromeとchrome-driverは最新版
  • seleniumはpipインストールを利用
$ vim app/Dockerfile
FROM php:8.1.8-fpm-buster
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]

# timezone environment
ENV TZ=Asia/Tokyo \
  # locale
  LANG=en_US.UTF-8 \
  LANGUAGE=en_US:en \
  LC_ALL=en_US.UTF-8 \
  # composer environment
  COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer \
  COMPOSER_NO_INTERACTION=1

COPY --from=composer:2.1 /usr/bin/composer /usr/bin/composer

RUN apt-get update && \
  apt-get -y install git wget vim  zip curl unzip libicu-dev libonig-dev libzip-dev locales libpq-dev libfreetype6-dev libjpeg62-turbo-dev libpng-dev libjpeg-dev libcurl4-gnutls-dev gnupg2 procps default-jre grep fonts-takao fonts-ipafont fonts-ipaexfont && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  locale-gen en_US.UTF-8 && \
  localedef -f UTF-8 -i en_US en_US.UTF-8 && \
  mkdir /var/run/php-fpm && \
  mkdir /var/log/php && \
  docker-php-ext-install intl pdo_mysql zip bcmath mbstring && \
  docker-php-ext-configure gd --with-freetype --with-jpeg && \
  docker-php-ext-install -j$(nproc) gd && \
  docker-php-ext-install curl && \
  composer config -g process-timeout 3600 && \
  composer config -g repos.packagist composer https://repo.packagist.org

# chrome
RUN wget https://dl.google.com/linux/linux_signing_key.pub
RUN apt-key add linux_signing_key.pub
RUN echo 'deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main' | tee /etc/apt/sources.list.d/google-chrome.list
RUN apt update
RUN apt -y install google-chrome-stable
RUN google-chrome --version

# chrome-driver
RUN wget -q -O /tmp/chromedriver.zip http://chromedriver.storage.googleapis.com/`curl -sS chromedriver.storage.googleapis.com/LATEST_RELEASE`/chromedriver_linux64.zip
RUN unzip /tmp/chromedriver.zip chromedriver -d /usr/local/bin/
RUN chromedriver --version
RUN rm /tmp/chromedriver.zip

# selenium
RUN apt install -y python3-pip
RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install cryptography
RUN python3 -m pip install selenium

COPY ./php-fpm.d/zz-www.conf /usr/local/etc/php-fpm.d/zz-www.conf
COPY ./php.ini /usr/local/etc/php/php.ini

WORKDIR /var/www

# CMD sh -c "composer update && composer install"

Webサーバ側のDockerfileの作成

Webサーバ側(nginx)のDockerfileを作成します。

$ vim nginx/Dockerfile
FROM node:latest as node
FROM nginx:alpine
SHELL ["/bin/ash", "-oeux", "pipefail", "-c"]

ENV TZ=JST

RUN apk update && \
  apk add --update --no-cache --virtual=.build-dependencies g++

# node command
COPY --from=node /usr/local/bin /usr/local/bin
# npm command
COPY --from=node /usr/local/lib /usr/local/lib
# yarn command
COPY --from=node /opt /opt
# nginx config file
COPY ./conf/default.conf /etc/nginx/conf.d/default.conf

WORKDIR /var/www

docker-compose.ymlの作成

最後にdocker-compose.ymlを作成します。拡張子の間違いに注意。

$ vi docker-compose.yml
version: '3'

volumes:
  selenium-app-php-fpm-socket:
services:
  nginx:
    container_name: selenium-app-nginx
    build:
      context: ./nginx
    volumes:
      - selenium-app-php-fpm-socket:/var/run/php-fpm
      - ../../selenium-app:/var/www
    depends_on:
      - app
    networks:
      - selenium-app-network
      - local-network
    environment:
      VIRTUAL_HOST: "selenium-app.localhost"

  app:
    container_name: selenium-app
    build:
      context: ./app
    volumes:
      - selenium-app-php-fpm-socket:/var/run/php-fpm
      - ../../selenium-app:/var/www
    networks:
      - selenium-app-network
      - local-network

networks:
  selenium-app-network:
    name: selenium-app-network
  local-network:
    external: true
    name: local-network

ビルド・起動

ビルドします。(初めはno-cacheは必要ないが、エラー落ち等の再実行用)そこそこ時間がかかるので気長に待ちます。自分の環境では3分ぐらいでした。

$ docker-compose build --no-cache

起動します。

$ docker-compose up -d

コンテナの一覧を取得して、起動していることを確認します。

$ docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED          STATUS          PORTS                                      NAMES
6d33fdf12671   selenium-app-nginx     "/docker-entrypoint.…"   24 seconds ago   Up 22 seconds   80/tcp                                     selenium-app-nginx
e93f13bd2926   selenium-app-app       "docker-php-entrypoi…"   24 seconds ago   Up 23 seconds   9000/tcp                                   selenium-app

dockerコンテナ内へ接続

上で確認したコンテナへ接続します。細かくは書かないですが、コンテナIDの初め3,4桁打てば識別されます。

$ docker exec -it e93f /bin/bash

php-webdriverのインストール

laravelにパッケージを追加します。facebook-webdriverはもう名前として古いので、php-webdriverを使いましょう。

# composer require php-webdriver/webdriver
(dockerコンテナ内で)

サンプルプログラムの作成

まずテンプレートとなるコマンドプログラムを生成します。

# php artisan make:command SeleniumSampleBatch
(dockerコンテナ内で)

中身を書き換えます。

# vi app/Console/Commands/SeleniumSampleBatch.php
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Log;
use Facebook\WebDriver\Chrome\ChromeOptions;
use Facebook\WebDriver\Chrome\ChromeDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;
use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\WebDriverExpectedCondition;
use Facebook\WebDriver\WebDriverBy;
use Facebook\WebDriver\Interactions\WebDriverActions;
use Facebook\WebDriver\WebDriverDimension;

class SeleniumSampleBatch extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'command:selenium-sample-batch';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Selenium Sample Batch';

    /**
     * Create a new command instance.
     *
     * @return void
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Execute the console command.
     *
     * @return int
     */
    public function handle()
    {
        $driverPath = realpath("/usr/local/bin/chromedriver");
        putenv("webdriver.chrome.driver=" . $driverPath);

        // chrome option
        $options = new ChromeOptions();
        $options->addArguments([
            'disable-infobars',
            '--headless',
            '--no-sandbox',
            'window-size=1920,1080',
        ]);

        $capabilitites = DesiredCapabilities::chrome();
        $capabilitites->setCapability(ChromeOptions::CAPABILITY, $options);
        $driver = ChromeDriver::start($capabilitites);

        // googleのページ取得
        $driver->get('https://www.google.co.jp/');
        $driver->wait(3)->until(
            WebDriverExpectedCondition::presenceOfElementLocated(WebDriverBy::name('q'))
        );

        // フォームに文字列を入力して検索実行
        $element = $driver->findElement(WebDriverBy::name('q'))
            ->sendKeys('php 8.1')
            ->submit();

        $driver->wait(3)->until(WebDriverExpectedCondition::titleContains('php 8.1'));

        // キャプチャ保存
        $file = storage_path() . "/sample.png";
        $driver->takeScreenshot($file);

        $driver->quit();
	return Command::SUCCESS;
    }
}

動作確認をします。

# php artisan  command:selenium-sample-batch
(dockerコンテナ内で)

実行後に、storage/sample.pngができていれば動作はOKです。

おまけ Webサービスの動作確認

ブラウザから「http://selenium-app.localhost/」へ接続すると、laravelのいつものページが見れます。何か出た場合は、「/var/www/storage/*」に権限が足りない場合があるので、777等にしておきましょう。

余談

あまり変わったことはやっていないけど、たまにやると結構忘れているから困ります・・・

コメント

タイトルとURLをコピーしました