CakePHP3 を CircleCI でテストする #cakephp

概要

CakePHP3 を CircleCI でテストするようにするまとめ。 Database を使ったテストも出来るようにしています。

TL;DR

最終的な yml はこんな感じです。

version: 2
jobs:
  build:
    docker:
      - image: circleci/php:7.1.32-fpm-node-browsers
        environment:
          DATABASE_URL: "mysql://root:password@127.0.0.1/my_app?encoding=utf8mb4&timezone=UTC&cacheMetadata=true"
          DATABASE_TEST_URL: "mysql://root:password@127.0.0.1/test_myapp?encoding=utf8mb4&timezone=UTC&cacheMetadata=true"

      - image: circleci/mysql:5.7-ram
        environment:
          MYSQL_DATABASE: my_app
          MYSQL_ROOT_PASSWORD: password

    steps:
      - checkout

      - run: sudo apt update && sudo apt install libicu-dev libpng-dev
      - run: sudo docker-php-ext-install pdo pdo_mysql

      # pecl で入れたいものがあれば
      # - run: sudo pecl install grpc
      # - run: sudo docker-php-ext-enable grpc

      # composer cache
      - restore_cache:
          keys:
            - composer-v1-{{ checksum "composer.lock" }}
            - composer-v1-

      - run: composer install -n --prefer-dist

      - save_cache:
          key: composer-v1-{{ checksum "composer.lock" }}
          paths:
            - vendor

      # database setup
      - run:
          name: Waiting for MySQL to be ready
          command: dockerize -wait tcp://localhost:3306 -timeout 1m
      - run:
          name: Migrate
          command: |
            bin/cake migrations migrate
      - run:
          name: Create database for test
          command: mysql -h 127.0.0.1 -u root -ppassword --execute "CREATE DATABASE IF NOT EXISTS test_myapp CHARACTER SET utf8"

      # test
      - run:
          name: run tests with phpunit
          command: ./vendor/bin/phpunit --log-junit tests/_output/result.xml
      - store_test_results:
          path: tests/_output
      - store_artifacts:
          path: tests/_output

解説

Cake 用の php イメージ構築

- image: circleci/php:7.1.32-fpm-node-browsers
  environment:
    DATABASE_URL: "mysql://root:password@127.0.0.1/my_app?encoding=utf8mb4&timezone=UTC&cacheMetadata=true"
    DATABASE_TEST_URL: "mysql://root:password@127.0.0.1/test_myapp?encoding=utf8mb4&timezone=UTC&cacheMetadata=true"

circleci の php イメージを使っています。

環境変数は Cake 側でテストする際に利用するために設定しています。

- run: sudo apt-get update && sudo apt-get install -y libicu-dev libpng-dev default-mysql-client
- run: sudo docker-php-ext-install pdo pdo_mysql

必要なパッケージや拡張をインストールします。

cake や php 本体のバージョンによって必要なものが変わってくるかもしれません。

mysql クライアントはテスト用 DB を作成するために入れています(後述)。

この辺は都度やると時間もかかるので、カスタムイメージ用意したほうがいいかもしれません。

composer install やキャッシュはいつものやつって感じですね。

テスト用の DB 構築

- image: circleci/mysql:5.7-ram
  environment:
    MYSQL_DATABASE: my_app
    MYSQL_ROOT_PASSWORD: password

CircleCI 公式の MySQL を使用しています。

-ram 付はメモリ内で実行されるため、パフォーマンスが高いです。

特に理由がなければ、こちらを使うとよさそうです。

- run:
    name: Waiting for MySQL to be ready
    command: dockerize -wait tcp://localhost:3306 -timeout 1m

DB の起動を dockerize で待ちます。

CircleCI のイメージであれば、dockerize は別途インストールしなくても入っていると思います。

- run:
    name: Migrate
    command: |
      bin/cake migrations migrate
- run:
    name: Create database for test
    command: mysql -h 127.0.0.1 -u root -ppassword --execute "CREATE DATABASE IF NOT EXISTS test_myapp CHARACTER SET utf8"

Migrate をした後に、テスト用の DB を別途作成しています。

CakePHP の場合、 DB を使ったテストでは Fixture を使うことになると思います。

その際、テーブル情報のインポート (public $import = ['table' => 'articles'] みたいなの) を使うことが想定されるため、別で作成するようにしています。

あとはテストを実行して、その結果を保存しているだけです。

参考

MySQL で日付の疑似表を作る #mysql

概要

MySQL で日付の疑似表を作るクエリ。

集計分析等で、データがない日付も一覧としては出したいようなケースでの利用を想定。

TL;DR

色々試してみたけど、以下に落ち着いた。

SELECT
    @seq := 0          AS seq
  , DATE('2020-01-01') AS ymd
FROM
    dual
UNION ALL
SELECT
    @seq := @seq + 1                          AS seq
  , DATE_ADD('2019-12-31', INTERVAL @seq DAY) AS calendar
FROM
    information_schema.COLUMNS
ORDER BY
    seq
LIMIT 365

解説

連番を発行して、それを日付に足し込むことで実現している。

UNION ALL しているのは連番の変数宣言をするためで、 クエリが分かれてもいいなら SET @seq := 0; 文で宣言してもいい。

information_schema.COLUMNS の参照は行数を稼ぐためだけなので、 365 行よりレコードが多ければ何のテーブルでもいい。(ただし閏年は366日か)

「よなよなエールがお世話になります」を読んだ

ぷしゅ よなよなエールがお世話になります

ぷしゅ よなよなエールがお世話になります

ビールの「よなよなエール」を作っている、ヤッホーブルーイングの社長の本。

今年だったか去年だったかの RSGT でお話されていて、そのときに入手した(そして積まれていた)。

RSGT で話を聞いたときは、ヤッホーブルーイングの社風というか文化というか、そういったものは自分と合わないなという印象だった。

ただチームをチームにするためにやっている取り組みが、単なる制度ではなくて、仕組み化されているなと感じて、興味深く面白いものだったと記憶してる。

本自体は、いかにして社長になり、挫折や困難を乗り越えていったかというサクセス・ストーリーな感じ。

ドラマがあり、単に読み物としても中々面白い。

いわゆるノウハウ的なものを丁寧に解説しているわけではないけれど、ビジネスとしての戦略や組織づくりみたいなところに、多くのヒントや学びがあると思う。

僕はやっぱりヤッホーブルーイングの社風に共感しかねるところもあるなと思いつつ、それこそが個性ある組織を作り上げているって証拠なんだろう。

お酒やビールにそれほど関心が強いわけではないが、よなよなエール飲もうかな、という気持ちになった。

カスタム Docker Image を ECR で管理して、 CircleCI から利用する #AWS #Docker #CircleCI

概要

php のプロジェクトに CircleCI を導入した。

PHP の拡張(特に grpc 拡張)のインストールに時間がかかるため、独自イメージを用意してそれを使うことにした。

イメージの管理には Amazon Elastic Container Registry (ECR) を使ってみた。

ECR でリポジトリの作成

ECR でリポジトリを作成する。

とりあえず myphp みたいな名前にするが、名前空間も作れる ( grpc/php みたいに)。

他にタグの上書きを禁止したり、脆弱性診断のスキャンをする設定もある。

docs.aws.amazon.com

Dockerfile の作成

FROM circleci/php:7.1.32-fpm-stretch

# install grpc-extension
RUN set -x \
  && sudo pecl install grpc \
  && sudo docker-php-ext-enable grpc

CMD ["/bin/sh"]

CircleCI のイメージをベースに、 grpc 拡張をインストールした後、有効化している。

cloud.google.com

Dockerfile のビルド

ビルドする。

docker image build -t myphp:7.1.32-fpm-stretch .

ECR 用のタグを作成する。

docker tag myphp:7.1.32-fpm-stretch \
  ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/myphp:7.1.32-fpm-stretch

アカウント ID や リージョンは適宜置き換える。

ECR に push

ECR にログインする。

get-login がログインコマンドを返してくれるので、そのまま実行可能。

$(aws ecr get-login --region ${AWS_REGION} --no-include-email)

ECR に push する。

docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/myphp:7.1.32-fpm-stretch

list-images でイメージの一覧を確認可能。

aws ecr list-images --repository-name myphp

CircleCI からの利用

Image はこんな感じで利用できる。

AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY は CircleCI 側で環境変数に入れておく。

docker:
  - image: ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/myphp:7.1.32-fpm-grpc
    aws_auth:
      aws_access_key_id: ${AWS_ACCESS_KEY_ID}
      aws_secret_access_key: ${AWS_SECRET_ACCESS_KEY}

circleci の CLI で試すならこんな感じ

circleci build \
  -e AWS_ACCOUNT_ID=XXXX \
  -e AWS_REGION=ap-northeast-1 \
  -e AWS_ACCESS_KEY_ID=XXXX \
  -e AWS_SECRET_ACCESS_KEY=XXXX

CI であれば、読み取り用の権限だけで動く。

  • ecr:GetAuthorizationToken
  • ecr:BatchCheckLayerAvailability
  • ecr:GetDownloadUrlForLayer
  • ecr:GetRepositoryPolicy
  • ecr:DescribeRepositories
  • ecr:ListImages
  • ecr:DescribeImages
  • ecr:BatchGetImage

あたり。

docs.aws.amazon.com

補足

grpc のイメージは現状メンテナンスされていないようだった。

github.com

参考

CakePHP3 でテストの置き場所を変更する #cakephp

概要

CakePHP3 では tests/ 配下にテストコードを書くことになっている。

何らかの原因(察して)でパスを規定から変更したい場合の修正箇所に付いてまとめる。

以降 tests/PHPUnit/ に変更すると仮定して記載する。

paths.php

TESTS が定義されているので書き換える

/*
 * Path to the tests directory.
 */
#define('TESTS', ROOT . DS . 'tests' . DS);
define('TESTS', ROOT . DS . 'tests' . DS . 'PHPUnit' . DS);

app/paths.php at master · cakephp/app · GitHub

phpunit.xml

phpunit.xml.dist などの設定ファイルを用意している場合は書き換える。

  • phpunit タグの bootstrap 属性
    • tests/PHPUnit/bootstrap.php
  • testsuite タグの directory
    • tests/PHPUnit/TestCase/
  • etc
<phpunit
    colors="true"
    processIsolation="false"
    stopOnFailure="false"
    bootstrap="tests/PHPUnit/bootstrap.php"
    >
<!-- 中略 -->
    <testsuites>
        <testsuite name="app">
            <directory>tests/PHPUnit/TestCase/</directory>
        </testsuite>
        <!-- Add plugin test suites here. -->
    </testsuites>
<!-- 中略 -->
</phpunit>

app/phpunit.xml.dist at master · cakephp/app · GitHub

tests/bootstrap.php

  • パスを修正する
#require dirname(__DIR__) . '/vendor/autoload.php';
require dirname(__DIR__) . '/../vendor/autoload.php';
#require dirname(__DIR__) . '/config/bootstrap.php';
require dirname(__DIR__) . '/../config/bootstrap.php';

※移動しているので tests/PHPUnit/bootstrap.php になっているはず

app/bootstrap.php at master · cakephp/app · GitHub

composer.json

  • autoload-dev のパスを変更
    • ここを直さないと Fixture 等が load できない
"autoload-dev": {
    "psr-4": {
        "App\\Test\\": "tests/PHPUnit/",
        "Cake\\Test\\": "vendor/cakephp/cakephp/tests/"
    }
},

app/composer.json at master · cakephp/app · GitHub

まとめ

上記の修正を入れると、別のディレクトリでも App\Test\ な namespace でテストが書ける。

しかしこんなことはやらないほうがよい。

フレームワークを使うならできるだけ変なカスマイズせずに使おう。

Elasticsearch が応答しなくなった #AWS #Elasticsearch

概要

  • Amazon Elasticsearch Service で、Elasticsearch が突然死んでしまった
  • 復旧方法が分からないので、結局別ドメインで作り直した

原因は結局わからないままだったので備忘録です。 想定される原因や、強制的に再起動する方法があったら教えてほしいです。。

何が置きたか

ある日突然エンドポイントの応答がなくなった

502 Bad Gateway だった気もするが、記憶があやふや。 Timeout だったかもしれない。

Web アプリケーションから Elasticsearch を呼び出していたが、アプリケーションとしては 60秒のタイムアウトでレスポンスが帰っていた。

原因の調査

マネージメントコンソールから確認すると、ドメインのステータスは「アクティブ」になっており、問題無さそうに見えた。

エラーログを確認してみると下記のような感じのログが吐かれていた。

[2019-XX-XXTXX:XX:XX,XXX][WARN ][o.e.m.j.JvmGcMonitorService] [XXXXXX] [gc][XXXXXX] overhead, spent [2.1s] collecting in the last [2.1s]

似たようなログが何度か吐かれた後、静かになっている。

Cloudwatch やらでメトリクスを確認すると、上記のログが最初に吐かれる 12h ほど前から突然取得できなくなっていた。

落ちる前と比べて、何かが跳ねるなどの目立つ傾向も見受けられなかった。

また、特定のリソースが落ちる前から逼迫しているということもなかった。

※最大メモリ使用率だけは90%越えをしているタイミングはあったが、落ちる前だけに限定した現象ではなかった

対応

エラーログの内容で調べてみると、Elasticsearch 関連の情報がいくつか引っかかった。

「メモリの割当サイズを増やして再起動する」ということだったが、Elasticsearch Service を手動で再起動する方法が見つけられなかった。

そのため、インスタンスサイズの変更を試してみることにした。

しかし、ドメインのステータスが「処理中」になったまま 24h 以上変化がなかった。

下記の記事を見つけたりもしたのだが、そもそも curl が 502 になって帰ってこない。

aws.amazon.com

結局復旧は諦め、別ドメインで同じ構成のインスタンスを用意して、そちらを利用することにした。

ただの想像

  • 開発用だったということもあり、インデックスの全件更新などを高頻度で実施していた
  • サイジングなどのチューニングは行っておらず、デフォルトのまま利用していた

上記のようなことから何かしらの問題が発生したのかとは思いつつ、復旧しなかったことなど含め原因はよくわからない。

参考

brew services に慣れていない

docker で 9000番ポート使おうとしたら他が使っていると怒られた。

なんだろうと思って調べてみると php-fpm が使っていた。

$ lsof -i:9000 -P

起動したっけな、と思いながら無邪気に kill したけど再起動が走り止められない。。

自動で何か仕込んでたかな、と色々調べたところ、 brew services で動いていた。

$ brew services stop php
$ brew services list

使用頻度が低いと、パッと思い浮かばず時間を浪費してしまう。。

参考