Perl CGI on Docker and Testing it on CircleCI

## はじめに
自分は長らく仕事で、PerlのWebアプリケーションのおもりをしています。
ですが、そのアプリケーションにはテストがなくて、何をするにもドキドキです…。
そもそも作り始められた当時はそのような文化はメジャーではなかったのです。

時間が経ち、世の中には便利なものがたくさん登場してきました。
今であれば、そのアプリケーションに自動テストを動かす環境を整備することもできなくはないのではないか？
そう考えました。

シンプルなPerl CGIのアプリケーションをDocker上で動かし、
CircleCIを使って、E2Eテストを動かすようにしてみます。

動かすまでが目的なので、そんなに凝ったことはしません。

![PSX_20191123_234153.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14380/082357f1-9b74-2aa3-3846-cdbc8c1e1903.jpeg)

## ソース
この記事で使っているソースは↓です。
https://github.com/ken1flan/perl_test_on_circleci

## Perl CGIのサンプルアプリケーション
サンプルのCGIは、データベースを使った簡単なものにしました。
/cgi-bin/index.cgi にアクセスすると、「ただいまのラッキーにゃんこは〜です☆」と表示します。
〜の部分にはデータベースCatsDBのCatColorsに保存されているレコードからランダムにひとつ取得したものを表示します。

![スクリーンショット 2019-11-20 11.39.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14380/6f528a71-a67c-88e6-3d98-d68f38e3cd67.png)

### 構成
CGIはapache httpdのmod_cgidによって動いています。
また、データベースはMariaDBを使っています。

## Dockerに載せる
Dockerfileに必要なパッケージを入れたり、設定をしたりする手順を書き、それをを元にマシンのイメージを作成します。

### Perl Dockerイメージ
perl cgiを動かすコンテナのDockerイメージを作ります。
配布されている公式CentOSのイメージに、CGIを動かしていたサーバにインストールしていたパッケージを追加したり、設定ファイルをコピーしたりしています。
実際のファイルは[こちら](https://github.com/ken1flan/perl_test_on_circleci/blob/master/Dockerfile)をみてください。

#### systemd

Webサーバを通常のサーバと同様に、systemdを使って立ち上げることにします。ですが、公式イメージをそのまま使うだけでは権限が足りずにできません。[Docker-hubのCentOSのDescriptionの中程](https://hub.docker.com/_/centos?tab=description)にしたがって、設定をしていきます。

```dockerfile
ENV container docker
RUN (cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == \
systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*;
VOLUME [ "/sys/fs/cgroup" ]
```

Dockerfileの最後で起動するようにします。

```dockerfile
CMD ["/usr/sbin/init"]
```

#### httpd
yumパッケージのhttpdを入れ、systemdで扱えるように登録します。

```dockerfile
RUN yum install -y httpd httpd-devel
RUN systemctl enable httpd.service
```

[CGIのファイル](https://github.com/ken1flan/perl_test_on_circleci/blob/master/httpd/conf.d/01-cgi.conf)をコンテナにコピーします。

```dockerfile
COPY httpd/conf.d/01-cgi.conf /etc/httpd/conf.d
```

[EXPOSE](http://docs.docker.jp/engine/reference/builder.html#expose)で、80番ポートをコンテナがリッスンできるようにします。

```dockerfile
EXPOSE 80
```

#### Dockerイメージの作成

Dockerfileが出来上がったら、Dockerイメージを作成します。

```concole
$ docker build -t ken1flan/perl_test_on_circleci .
```

### MariaDB
準備するのが億劫だったので、CircleCIのパッケージを利用してしまいます。
dockerhubの公式パッケージでもよさそう。

## Docker Compose
複数のコンテナに分けているので、素のDockerだと起動が面倒なので、
開発時はdocker-composeで管理を簡単にできるようにします。
実際のファイルは[こちら](https://github.com/ken1flan/perl_test_on_circleci/blob/master/docker-compose.yml)をみてください。

### perlコンテナ

Dockerイメージは先の手順で作ったものを使います。

```yaml
  perl:
    image: ken1flan/perl_test_on_circleci:latest
```

ホストの8080番へのアクセスをコンテナの80に接続します。

```yaml
    ports:
      - "8080:80"
```

ホストのカレントディレクトリをコンテナの`/code`にマウントします。
ホストの`app`ディレクトリをコンテナの`/var/www`にマウントします。

```yaml
    volumes:
      - .:/code
      - ./app:/var/www
```

mariadbと名前で通信できるようにしたり、mariadbコンテナから起動してからperlコンテナが起動するようにします。

```yaml
    depends_on:
      - mariadb
```

systemctlが使えるようにします。

```yaml
    privileged: true
```

### mariadbコンテナ

Dockerイメージだけ指定しています。他の設定はしていません。

```yaml
  mariadb:
    image: circleci/mariadb:10.3
```

### 起動

```console
$ docker-compose up -d
WARNING: Found orphan containers (perl_test_on_circleci_db_1, perl_test_on_circleci_mysql_1) for this project. If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up.
Starting perl_test_on_circleci_mariadb_1 ... done
Starting perl_test_on_circleci_perl_1    ... done
$
```

```console
$ docker-compose ps
         Name                     Command           State          Ports
--------------------------------------------------------------------------------
perl_test_on_circleci_m   docker-entrypoint.sh      Up      3306/tcp
ariadb_1                  mysqld
perl_test_on_circleci_p   /usr/sbin/init            Up      0.0.0.0:8080->80/tcp
erl_1
$
```

### セットアップ
Dockerイメージからコンテナを立ち上げただけでは何もデータが入っていないので、入れていきます。

Perlコンテナでbashを実行し…

```concole
$ docker-compose exec perl bash
```

mysqlコマンドでデータベース作成、テーブル作成、初期データ投入を行います。

```console
# cd /code
# mysql -u root -h mariadb < createtables.sql
```

### 動作確認

下記にアクセスして無事にページが表示されればOKです。
http://localhost:8080/cgi-bin/index.cgi


## E2Eテスト
ブラウザを使ってCGIの動作をテストします。
Seleniumを使えば、クロスブラウザでも可能ですが、今回はChromeを使いました。

### Chromeとlibpngのインストール
PerlコンテナにChromeとスクリーンショットを取るために使うlibpngのパッケージを入れます。

```dockerfile
RUN yum install -y libpng libpng-devel

COPY yum.repos.d/google-chrome.repo /etc/yum.repos.d
RUN yum install -y google-chrome-stable
```

### テストスクリプト

[WWW::Mechanize::Chrome](https://metacpan.org/pod/WWW::Mechanize::Chrome)を使って、`http://127.0.0.1/cgi-bin/index.cgi`にアクセスし、表示されている文言を確認します。
実際のファイルは[こちら](https://github.com/ken1flan/perl_test_on_circleci/blob/master/test/e2e_test.pl)をみてください。


E2Eテストはブラウザを使って行いますが、`WWW::Mechanize::Chrome`を使っています。
このモジュールはSeleniumと違い、直接Chromeを操作するので、手軽でよいと思い、選択しました。

```perl
use Log::Log4perl qw(:easy);
use WWW::Mechanize::Chrome;
```

実際のページを取得する前に、起動しています。
このときに画面を使わない`headless`を指定しています。今回作ったDockerイメージはWindowマネージャのようなものが一切入れていないので、この指定がないと動きません。

```perl
my $mech = WWW::Mechanize::Chrome->new(headless=> 1);
```

起動したら、`get`にURLを指定するだけで、HTMLが取得できます。
その内容をチェックすることでテストできます。

```perl
$mech->get('http://127.0.0.1/cgi-bin/index.cgi');
my $content = $mech->content;

ok( $content =~ /ただいまのラッキーにゃんこは(茶トラ|サバトラ|キジトラ|ヒョウ柄|三毛|ブチ|サビ|シャム|黒|白|灰)です☆/ );
```

テストコードには入れませんでしたが、`content_as_png`でスクリーンショットを保存することもできます。

```perl
my $png = $mech->content_as_png();
```

## CircleCI
ここまでで、Docker上でCGIのE2Eテストができましたので、CircleCI上に持っていきたいと思います。

実際のファイルは[こちら](https://github.com/ken1flan/perl_test_on_circleci/blob/master/.circleci/config.yml)をみてください。

### Dockerイメージの指定
CircleCIの公式Dockerイメージでは当然動かないので、自分で設定したDockerイメージを使う必要があります。
CircleCIではDocker HubにあるDockerイメージであれば利用することができるので、自前のDockerイメージをDocker Hubに登録し、[それを指定](https://github.com/ken1flan/perl_test_on_circleci/blob/7e3fa77ee7007c5d20a5dd153a64d72ac9114259/.circleci/config.yml#L6)します。


```yaml
executors:
  my-executor:
    docker:
      - image: ken1flan/perl_test_on_circleci:latest
      - image: mariadb:10.3
        environment:
          MYSQL_ALLOW_EMPTY_PASSWORD: yes
```

### 作業ディレクトリの指定
プロジェクトのファイルは指定しないと、`/root/project`に配置されますが、その位置だとサーバの設定をいろいろ変える必要がでてきてしまいます。
開発環境と同じように`/code`以下にファイルを置くように[.circleci/config.yml](https://github.com/ken1flan/perl_test_on_circleci/blob/7e3fa77ee7007c5d20a5dd153a64d72ac9114259/.circleci/config.yml#L32)で設定しています。

```yaml
    working_directory: /code
```

### mariadbの起動待ち
データベースの準備が整わないうちにテストが始まると、失敗してしまいます。
ですので、MariaDBの接続用ポートを監視して、通信できることを


[Dockerfile](https://github.com/ken1flan/perl_test_on_circleci/blob/7e3fa77ee7007c5d20a5dd153a64d72ac9114259/Dockerfile#L16)中でncatをインストールしておいて…

```dockerfile
RUN yum install -y nmap-ncat
```

[.circleci/config.yml](https://github.com/ken1flan/perl_test_on_circleci/blob/7e3fa77ee7007c5d20a5dd153a64d72ac9114259/.circleci/config.yml#L36-L44)で、ncatで3306ポートを監視することで、mariadbの起動を待つようにしています。

```yaml
      - run:
         name: waiting for mariadb to be ready
         command: |
          for i in `seq 10`; do
            nc -z 127.0.0.1 3306 && echo Success && exit 0
            echo -n .
            sleep 1
          done
          echo Failed waiging for mariadb to be ready && exit 1
```

### httpdの起動

```yaml
      - run:
         name: setup httpd
         command: |
           rm -rf /var/www
           ln -s /code/app /var/www
           APP_ENV=test httpd
```

### テスト

```yaml
      - run:
         name: e2e_test
         command: APP_ENV=test carton exec perl test/e2e_test.pl
```

### できた！

![スクリーンショット 2019-11-23 12.02.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14380/135cc8ad-caa0-7ce3-ff12-a0e59299ade2.png)

## 32bitは？

古いアプリケーションだと32bitのOS上で動いていることが多いと思います。
このことについてはまだ考察が足りていません。

## 最後に
なんだかんだと、ちゃんとDocker上でPerl CGIを動かし、CircleCI上でE2Eテストもできるようになりました。
これで、古いアプリケーションのメンテナンスを頼まれても、ちゃんとテストを書きながらやれますね！