投稿者「kanazawa」のアーカイブ

PHP5.6化に向けたCircleCIのアップデート

kanazawa|2019年03月04日
CI

SREチームの金澤です。
CakePHP2.8移行が終了し、次のステップとしてPHP5.6化を進めています。

今回はPHP5.6化に向けて行ったCircleCI周りのアップデートについてお話させていただきます。

※実際のソースは公開したPHP、CakePHPバージョンアップのリポジトリで参照できるようにしています。

導入背景

以前の記事「PHPバージョンアップに向けて現状のソースの品質を担保・向上していく」で

  1. コーディング規約の遵守
  2. syntaxの先取り修正
  3. 複雑度悪化への歯止め
  4. UTの継続実施

の4つを継続的に実施するためにCircleCIを本格的に導入しました。

今回は、PHP5.6バージョンアップに向けて

  1. コーディング規約の遵守
  2. UTの継続実施

のアップデートを行いました。

現在のCircleCIの設定

現在はCircleCIをパスしないとソースをマージできないようにしています。
それに合わせて極端に厳しい設定はしないよう変更しています。
例えば、複雑度やphpmdで指摘されるような内容は、すぐに修正することは難しいため、CIでは回さないようにしています。

差分実行

CircleCIでチェックするソースコードは、原則、修正したもののみを対象にしています。
修正したソースコードを抽出するスクリプトが以下になります。

#!/bin/bash

set -e

branch=${1}

declare -a tmps=(
    /tmp/diff.log
)

for file in ${tmps}; do
    if [ -f "${file}" ]; then
        rm -rf ${file}
    fi
done

echo "branch: ${branch}"

set +e
git diff -p --name-only --reverse --format="" origin/master...origin/${branch} | sort | uniq > /tmp/diff.log
set -e

echo "--------------------------------------------------"
echo "diff files:"
echo "`cat /tmp/diff.log`"

exit 0

以下のコマンドで、派生元のMasterブランチと現在のブランチのソース差分を比較します。

git diff -p --name-only --reverse --format="" origin/master...origin/${branch} | sort | uniq > /tmp/diff.log

「…」で派生元Masterブランチとの比較となります。
※「..」だと現在のMasterブランチとの比較になってしまい、時間が経つほど余計な差分が出るようになります。

抽出した結果を/tmp/diff.logに保存しておき、後のチェックで利用します。

コーディング規約の遵守

PHP5.6に向けて設定更新しました。
PHP-CS-Fixerを以下のように設定しました。

<?php
$finder = PhpCsFixer\Finder::create()
    ->notName('.php_cs')
    ->notName('*.html')
    ->notName('*.md')
    ->notName('*.rb')
    ->notName('*.sh')
    ->notName('*.xml')
    ->exclude('tmp')
    ->in(__DIR__);

return PhpCsFixer\Config::create()
    ->setRules(array(
        '@PSR2' => true,
        '@PHP56Migration' => true,
//        'cast_spaces' => array('space' => 'none'), // PHP CS Fixer 2.2.20 では設定不可
//        'combine_consecutive_issets' => true, // PHP CS Fixer 2.2.20 では設定不可
        'combine_consecutive_unsets' => true,
//        'ereg_to_preg' => true, // PHP 5.6対応後に設定
        'no_empty_comment' => true,
        'no_empty_phpdoc' => true,
        'no_whitespace_before_comma_in_array' => true,
        'whitespace_after_comma_in_array' => true,
        'braces' => array()
    ))
    ->setUsingCache(false)
    ->setFinder($finder);

@PSR2と@PHP56Migrationを設定し、PHP5.6に対応するようにしました。
コメントアウトしている箇所については、PHP5.6バージョンアップ後に設定する予定です。
(PHP5.3ではPHP CS Fixerは 2.1までしか対応していない)

CircleCIでこのPHP CS Fixerを実行し、NGだった場合にSlackに以下のように通知します。

修正はPHP-CS-Fixerのコマンドで1発でできるようになっています。
このタイミングで、ショートタグ<?からロングタグ<?phpへの変換も行われます。

CakePHP2.8チェック

これは、CakePHP2.8移行時に作成したもので移行作業時に重宝しました。
CakePHP2.8移行が完了した現在も稼働中で、Cake1.3の書式で書いた箇所を通知してくれます。

※具体的なソースはこちらになります(Ruby製)

UTの継続実施

今回一番やりたかったことです。

CakePHP2.8移行完了の際に、旧CakePHP1.3のUTを思い切って全部捨てました。
旧CakePHP1.3のUTが以下の問題を抱えていたためです。

  1. CakePHP1.3のテストがSimpleTest製(CakePHP2.8はPHPUnit)
  2. テストに非常に時間がかかる(全実行に23時間)
  3. メンテナンスしきれていない(すべてのテストが正常終了していない)

そのため、今回のCircleCI化に向けてCakePHP2.8製のUTのみを残しました。
※旧CakePHP1.3のUTは別リポジトリに退避し、サルベージしながらCakePHP2.8用に復元させていく予定です。

UTの実施のためには、開発で利用している以下のDockerコンテナをCircleCIで起動する必要があります。

  1. Appコンテナ(PHP5.3)
  2. MySQLコンテナ(データ入り)

この2つのコンテナを起動するために、CircleCIで以下の設定を行っています。
(Docker ExecuterでAWS ECRからコンテナをダウンロード)

    docker:
      - image: xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/lancers_app:latest
        aws_auth:
          aws_access_key_id: $AWS_ACCESS_KEY_ID
          aws_secret_access_key: $AWS_SECRET_ACCESS_KEY
        environment:
          TZ: /usr/share/zoneinfo/Asia/Tokyo
          DB_HOST: 127.0.0.1
      - image: xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/mysql:latest
        aws_auth:
          aws_access_key_id: $AWS_ACCESS_KEY_ID
          aws_secret_access_key: $AWS_SECRET_ACCESS_KEY
        environment:
          TZ: /usr/share/zoneinfo/Asia/Tokyo

UTが失敗したときも同様に通知します。

CIの実行時間について

今回の変更に伴い、今まで1分以内で終わっていたCIが、5分近くかかるようになりました。
そのうち2分半がDockerのセットアップに費やされています。
今後、コンテナ容量の削減や、CircleCI上にキャッシュする等、工夫していきたいところです。

UTは、今のところ全テストを実行しており、30秒程度で終わっていますが、今後旧CakePHP1.3のテストの復元や、新テストの追加が進むと、さらに実行時間が増加されることが予想されるので、差分実行(修正に関連するテストのみの実行)等の工夫が必要になってくると思います。

最後に

まだまだ改良の余地はあるものの、PHP-CS-FixerやPHPUnitを本格的にCircleCIで回すことができたのは大きな前進だと思います。
今後、PHP5.6やPHP7にコンテナ変更した際にもCIを回しながらチェックしていきたいと思います。

CakePHP1.3→2.8移行が完了しました

kanazawa|2019年02月06日
CakePHP

SREチームの金澤です。

1年以上かけて取り組んできた、CakePHP1.3→2.8バージョンアップが完了しましたので報告いたします。

ランサーズ社のCakePHPの取り組み

ランサーズは現在11年目ですが、永らくバージョンアップをしておらず、PHP 5.3 + CakePHP1.3の環境で稼働していました。

2017/2に全社的にバージョンアップを決断し、その後の取り組みをまとめたものが以下になります。

また、毎週木曜日にCakePHP 3.xクックブック & ソースコードリーディングを開催し、社外からの参加者も交えて40回近く開催することができました。

2017年は開発部全体でのPHP活動が盛り上がっていましたが、
2018年は地道にCakePHP2.8対応を継続した年になりました。

バージョンアップ後の後片付け

ランサーズのCakePHP1.3→2.8移行は、普段の開発を止めず、コントローラー単位で徐々に移行していくアプローチをとりました。

CakePHP1.3→2.8移行は、2017/11から本格的に着手しました。

  • 2018.12.16 PHPカンファレンス2018に登壇しました
    で登壇したときは、コントローラー移行率が99%でした。

    年末には100%に達していたのですが、その後、CakePHP1.3を削除する作業が残っていました。
    工数的にはそれほど大変ではないものの、慎重に作業したため、約1か月かけての作業となりました。

    • 各種ディレクトリの移動
      • tmp
      • logs
      • vendor
      • composer
      • webroot
    • CakePHP1.3側のソースを削除
      • Nginxを変更(CakePHP2.8のindex.phpを直接呼び出す)
      • composer autoloadの参照をCakePHP2.8に移動
      • リリースシステムの設定変更

    レスポンス、サーバー負荷の改善

    CakePHPを2にバージョンアップするとパフォーマンスが改善することを聞いていましたが、
    ピーク時のサーバー台数が2台ほど減りました(ピーク時6~8台→4~5台)

    また、移行完了当日に、サーバーレスポンスの過去最速を記録し、初めて200msを切ることができました。

    App::usesによる遅延ローディング等の効果が出ていると推測しています。
    ※移行中に多少のパフォーマンスチューニングも行ったのでその効果もあるかと思います。

    バージョンアップ資料の共有

    ランサーズのソース(60万行、200コントローラー)を移行した過程で得た、バージョンアップのノウハウを
    GitHubで公開しました。
    https://github.com/LancersDevTeam/PHP_versionup

    今更感はありますが、今後、移行する方の参考になりましたら幸いです。

    今後の予定

    ランサーズのPHP、CakePHPバージョンアップは、以下のフェーズに分けて取り組んでいます。

    Apache + mod_php + PHP 5.3 + CakePHP 1.3
    ↓(第1フェーズ)
    Nginx + PHP-FPM + PHP 5.3 + CakePHP 1.3
    ↓(第2フェーズ)
    Nginx + PHP-FPM + PHP 5.3 + CakePHP 2.8
    ↓(第3フェーズ)
    Nginx + PHP-FPM + PHP 5.6 + CakePHP 2.8
    ↓(第4フェーズ)
    Nginx + PHP-FPM + PHP 5.6 + CakePHP 2.10
    ↓(第5フェーズ)
    Nginx + PHP-FPM + PHP 7.x + CakePHP 2.10

    今回、第2フェーズを終えることができました。

    続きまして、第3フェーズである、PHP5.6化に着手いたします。
    しかしながら、PHP5.6も2018/12でサポートが終了してしまいましたので、なるべく早くPHP7にバージョンアップしたいところです。

    CakePHPのバージョンアップはコントローラー単位で1年以上かけて取り組みましたが、
    第3フェーズ以降は、もっと早く完了させたいと考えています。

  • PHPカンファレンス2018に登壇しました

    kanazawa|2018年12月16日
    CakePHP

    SREチームの金澤です。
    Lancers(ランサーズ) Advent Calendar 2018 16日目の記事になります。

    2018/12/15(土)に、PHPカンファレンス2018に登壇させていただきました。

    登壇内容

    ランサーズのCakePHP1.3 → Cake2.8移行

    Track 6 3F特別会議室 / 13:00 ~ 13:25

    約1年かけて進めてきた、CakePHP1.3 → Cake2.8移行の詳細についてお話させていただきました。

    この日までに移行を完了させて望みたかったところでしたが、あと一歩間に合いませんでした。
    ※現在の進捗は99%です。移行が完了しましたら改めてお知らせしたいと思います。

    今回の発表で、移行手順のパターン化したものを表にまとめました。
    この表は、社内で共有されているGitHub Wikiをまとめたものですが、今後この情報をパブリックリポジトリとして公開していきたいと思います。

    今後の展望としては、CakePHP1.3 → CakePHP2.8移行が完了したら、

    PHP 5.3 → 5.6
    CakePHP2.8 → 2.10
    PHP 5.6 → 7.2

    とバージョンアップを進めていく予定です。

    、過去ランサーズの開発やバージョンアップに携わってきた方々も来てくださいました。

     

    これからも総力戦でバージョンアップを進めていきたいと思います。


    お知らせ

    来月、2019年1月26日(土) に行われるPHPカンファレンス仙台にも登壇させていただきます。

    https://phpcon-sendai.net/2019/
    Track C 10:25-10:55
    AWSでWordPressのスケールアウト

    この記事でもお話していました、WordPressのスケールアウトについて、最新情報とセキュリティ対策を交えてお話する予定です。
    地元開催なので楽しみにしています。

    WordPressのプラグインを改造して本家に取り込んでもらおうと思った話

    kanazawa|2018年12月07日
    Aurora

    SREチームの金澤です。
    Lancers(ランサーズ) Advent Calendar 2018 7日目の記事になります。

    昨日は、odrum428さんの「slackで動くピアボーナス機能を実装した話」でした。

    今年は、Lancers以外のAdvent Calendarも参加させていただいてます。(以下)

    5日同様、PHP Advent Calendar 7日目との掛け持ちにしようと思っていたのですが、それができないことがわかり、新しいネタをひねりだすことに。。

    ということで、WordPressネタを書かせていただきました。

    WordPressのYARPPプラグイン

    ランサーズでは、10個以上のサービスをWordPressで運用しています。
    そのうち、以下のサービスについては、Yet Another Related Posts Plugin(YARPP)を利用しています。

    ランサーズエリアパートナープログラム
    さすらいワーク

    YARPPは、投稿した記事のキーワードを解析し、記事を表示する時に関連記事やキーワードを表示することができるプラグインです。

    例えば、以下のページの右側に表示される情報は、YARPPプラグインが生成しています。
    https://lohai.jp/teshio/

     

    MySQLの全文検索機能

    このプラグインは、MySQLの全文検索機能を使っています。
    この全文検索機能は日本語の形態素解析まではサポートしておらず、英語のような単純なスペース区切りの文章が対象になりますが、YARPPプラグインはこれを利用して解析を行っています。

    MySQLのストレージエンジン

    MySQLは、大きく2つのストレージエンジンをサポートしています。
    MyISAMとInnoDBです。

    MyISAMは、古くからサポートされているストレージエンジンで、高速な処理が可能ですが、トランザクションをサポートしていません。
    InnoDBは、現在主流のストレージエンジンで、トランザクションをサポートしています。

    そして、MySQLの全文検索機能は永らくMyISAMしかサポートしていませんでした。

    同様に、YARPPプラグインもMyISAMしか対応していませんでした。

    ※InnoDBの場合、管理画面上で動作しない旨の警告が表示されます。

    WordPressのサーバー拡張

    ランサーズのWordPressサーバーは、最初はAWSのEC2を1台で運用していました。
    しかし、事業拡張に伴い、様々なサービスをWordPressで構築することになり、アクセス数も増えてきたため、徐々にサーバーリソースが足りなくなり、AWSのマネージドサービスと連携したサーバー拡張を行いました。

    ※この時の詳細な内容はエンジニアブログの以下にまとめています。
    AWSでWordPressのスケールアウト

     

    その過程で、EC2内にインストールしていたMySQLをRDSに引っ越すことになりました。

     

    しかし、RDSのMySQLはInnoDBしかサポートしていません。
    (MyISAMが使えないわけではないですが、サポート対象外になります。)

    RDSに移行するのであれば、DBの全テーブルをInnoDBに全て移行しておきたいところですが、ここでYARPPプラグインがネックになりました。

    MySQL InnoDBの全文検索サポート

    しかし、MySQLもバージョン5.6.4からInnoDBの全文検索機能(InnoDB FTS)がサポートされるようになりました。
    MyISAMの全文検索同様、SQLのMATCH~AGAINST構文で利用することができ、互換性があります。

    YARPPは世界的にそこそこ有名なプラグインなので、InnoDBへの対応が期待されていました。

    Title and body indexes with InnoDB
    https://wordpress.org/support/topic/title-and-body-indexes-with-innodb/

    Support for InnoDB with full-text index
    https://wordpress.org/support/topic/support-for-with-full-text-index/?replies=4

    しかしながら、対応される様子はありませんでした。

    YARPPプラグインのInnoDB対応

    RDSへの移行を進めるため、YARPPプラグインに手をいれてInnoDB対応をすることにしました。

    この時の詳細は、以下の記事に記載しています。
    http://qiita.com/yKanazawa/items/70686b13b17e7bd2e9e6

    プラグインのマージを依頼

    YARPPプラグインのInnoDB対応に需要があることは分かっていたので、この修正をプラグインの作者にマージしてもらおうと思いました。

    GitHubであれば、

    – 本家からFork
    – 修正してPull Request
    – レビューしてもらい、OKならマージして取り込み

    のようなフローになるので、そのイメージでWorpressコミュニティ周りを調査し始めました。

    その結果、WordPressは未だに、SVNで管理していることが分かってきました。
    http://core.svn.wordpress.org/

    そして、WordPressプラグインはTracで管理されているようでした。
    https://plugins.trac.wordpress.org/

    YARPPのフォーラムを見つけたので、見よう見まねでマージを依頼してみました。
    https://plugins.trac.wordpress.org/ticket/2655

    ソースの差分はGitHub上に提示するというドロくささ。
    https://github.com/yKanazawa/yet-another-related-posts-plugin/pull/1/files

    その結果

    めでたくマージされ、YARPPプラグインはInnoDB対応の最新版になりました!

    と言いたいところでしたが、結局、なしのつぶてで、マージされることはありませんでした。

    そして、2018/10/23にYARPPプラグインは見事に廃止となりました。
    https://wordpress.org/plugins/yet-another-related-posts-plugin/

    もはや、WordPressのサイトからこのプラグインをダウンロードすることはできなくなりました。
    とはいえ、今も現役で利用しているので、こっそりとメンテナンスは続けていきたいと思います。

    もはや本家もないし、GitHubのマージボタンに手を伸ばし、自分のMasterに取り込み完了。
    https://github.com/yKanazawa/yet-another-related-posts-plugin

    ※追記:2019/5に復活したようです。

    YARPP – Yet Another Related Posts Plugin

    お知らせ

    2019年1月26日(土) に行われるPHPカンファレンス仙台に登壇します。

    https://phpcon-sendai.net/2019/
    Track C 10:25-10:55
    AWSでWordPressのスケールアウト

    この記事でもお話していました、WordPressのスケールアウトについて、最新情報とセキュリティ対策を交えてお話する予定です。
    地元開催なので楽しみにしています。

    ランサーズ版SQLチューニングポリシー

    kanazawa|2018年12月05日
    MySQL

    SREチームの金澤です。
    Lancers(ランサーズ) Advent Calendar 2018 5日目の記事になります。

    昨日は、inamuuさんのElasticCloudからAmazon Elasticsearch Serviceへの移行して良かったこと大変だったことでした。

    今年は、Lancers以外のAdvent Calendarも参加させていただいてます。(以下)

    本日の記事は、CakePHP Advent Calendar 5日目との掛け持ちにしようと思っていたのですが、それができないことが今更ながらわかりました。なんという失態。。

    なので、お蔵入りしてした社内用ドキュメント(SQLチューニングポリシー)を公開することにしました。(なぜそうなるのかの考察をもっとしたいものではありましたが。。。)

    なお、この記事は

    と関連する記事でもあります。


    イントロダクション

    このドキュメントは、アプリエンジニアが新機能を実装する際の資料として用意したものです。

    過去、新規機能実装時に、新規テーブルに適切なインデックスが付与されていなかったり、不用意に負荷のかかるSQLを発行していたことが多かったため、実装前の参考資料として書いていました。

    ※ドキュメント作成時のMySQLのバージョンは5.6です。

    本番DBでの事前検証

    EXPLAIN文での確認

    実装時に、負荷のかかりそうなSQLを発行する場合は、本番DBのレプリカで事前に実行計画を確認しておいてください。

    mysql> EXPLAIN SELECT …;
    

    EXPLAIN文については、以下のページに詳しい解説があります。

    漢(オトコ)のコンピュータ道:MySQLのEXPLAINを徹底解説!!

    レスポンス確認

    同様に、実際のレスポンスタイムを本番DBのレプリカで事前に確認しておいてください。

    SQLをチューニングして、EXPLAIN上は改善していても、実際のレスポンスは改善しないパターンもあります。その場合は、レスポンスタイムの早いほうを選択してください。


    SQL発行時の注意点

    日付型カラムでのインデックス範囲検索は1ヶ月以内が目安

    1ヶ月以上広範囲になると、インデックスが採用されずフルスキャンがかかる可能性が高くなります。

    ※データ量や統計情報に依存します。開発環境で適切にインデックスが採用されていても本番環境で同じ動作をするとは限らないので、事前に本番DBのレプリカでも検証しておいてください。

    ※例:proposalsテーブルのcreated範囲検索にインデックスが採用されない例

    mysql> EXPLAIN SELECT `Proposal`.`id`, `Proposal`.`string` FROM `proposals` AS `Proposal` WHERE Proposal.created > '2014-11-01 00:00:00';
    +----+-------------+----------+-------+------------------------+------------------------+---------+------+--------+-----------------------+
    | id | select_type | table    | type  | possible_keys          | key                    | key_len | ref  | rows   | Extra                 |
    +----+-------------+----------+-------+------------------------+------------------------+---------+------+--------+-----------------------+
    | 1  |      SIMPLE | Proposal | range | proposal_index_created | proposal_index_created |       6 | NULL | 221634 | Using index condition |
    +----+-------------+----------+-------+------------------------+------------------------+---------+------+--------+-----------------------+
    1 row in set (0.01 sec)
    

    FORCE INDEX(USE INDEX)を使うときの注意点

    MySQLがどうしても適切なインデックスを選択してくれない場合、FORCE INDEX(USE INDEX)を使うことで、強制的にインデックスを選択させる方法があります。

    ただし、FORCE INDEX(USE INDEX)を使うと、将来的に以下のようなデメリットが生じるので、あくまでも一時的な手段にしておいてください。

    • インデックスチューニングをしても、FORCE INDEXを指定しているクエリは改善されない
    • FORCE INDEXに指定したインデックスを削除するとエラーを引き起こす可能性がある
      • FORCE INDEX(PRIMARY)は大丈夫
      • FORCE INDEX(インデックス名)のインデックス名が削除されるとエラーになる

    理想的なのは、FORCE INDEXを使わず、MySQLが自然に適切なインデックスを選択できるようなインデックス構成やSQLにしておくことです。

    上記の注意点を理解した上で、一時的なパフォーマンス対策の手段として利用してください。


    テーブル作成時のインデックス付与の指針

    やみくもにインデックスを付与しない

    インデックスを付与すると、データ更新時の負荷が増えます。
    マスターDBに負荷をかけることになりますので計画的に付与する必要があります。
    特に更新頻度の多いテーブルは注意が必要です。

    パフォーマンスが悪いからと、やみくもにインデックスを付与してしまうと、MySQLの実行計画が狂い、他のSQLにも悪影響を与える可能性があります。

    createdでソートする目的でインデックスを付与しない

    PRIMARY KEYであるidカラムでも同様のソートが可能です。

    ※抽出目的でcreatedに付与する場合は、ソートもcreatedで統一します。

    カーディナリティの低いカラムにはインデックスを付与しない

    例えば、各テーブルのdeletedカラムは、カーディナリティが2しかない(0か1の値しか持たない)ため、インデックスの付与はほとんど効果がありません。

    むしろ、MySQLが誤ってこのインデックスを採用してしまい、パフォーマンスが落ちることもあります。

    ※ただし、0より1の割合が圧倒的に少数であるカラムに対し、かつ1での検索が大半を占める場合などは、カーディナリティが低くても有効に機能しますのでその条件を満たす場合は付与できます。

    複合インデックスの採用条件

    MySQLでは、原則1テーブルにつき1つしかインデックスを使わないため、複数カラムにインデックスを効かせるためには複合インデックスを付与する必要があります。

    しかしながら、MySQLの場合は複合インデックスを作成すると、既存のSQLの実行計画が変更され、場合によっては既存SQLのパフォーマンスが悪化する可能性があります。

    パフォーマンス検証スクリプト(※独自に用意した、サービス全画面のSQLを実行するスクリプト)を実行し、大幅にパフォーマンスが悪化するSQLがないことを確認しておきましょう。

    また、複合インデックスは更新負荷が大きくなりますので、採用頻度と合わせて慎重に検討が必要です。

    複合インデックスを採用する場合、先頭カラムの単一インデックスは削除する

    複合インデックスの先頭カラムは単一インデックスとしても作用するので、複合インデックスを付与する場合は、先頭カラムの単一インデックスが存在していたら削除しておくことで、インデックス量が減り、マスターDBの更新負荷が軽減されます。

    ただし、単一インデックスのときよりもパフォーマンスが落ちる場合もあるので、事前検証が必要です。

    複合インデックスは、カーディナリティの高いカラム順に並べる

    複合インデックスを付与する場合は、できるだけカーディナリティの高い順に並べてください。
    発行するSQLによるので、どんなときも適用できるルールではありませんが、極端にカーディナリティの低いカラムを先頭にすると効果が出にくくなります。 (MySQLが優先的に採用してしまい、効果が落ちることもある)

    以下、categorizationsテーブルの(よくない)例

    mysql> SHOW INDEX FROM categorizations;
    +-----------------+------------+------------------------------------------------------------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | Table           | Non_unique | Key_name                                          | Seq_in_index | Column_name         | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
    +-----------------+------------+------------------------------------------------------------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    | categorizations |          0 | PRIMARY                                           |            1 | id                  | A         |     1295306 |     NULL |   NULL |      |      BTREE |         |               |
    | categorizations |          1 | category_id                                       |            1 | category_id         | A         |         164 |     NULL |   NULL |      |      BTREE |         |               |
    | categorizations |          1 | categorization_type                               |            1 | categorization_type | A         |           6 |     NULL |   NULL | YES  |      BTREE |         |               |
    | categorizations |          1 | categorization_id                                 |            1 | categorization_id   | A         |     1295306 |     NULL |   NULL | YES  |      BTREE |         |               |
    | categorizations |          1 | category_id_categorization_type_categorization_id |            1 | category_id         | A         |         154 |     NULL |   NULL |      |      BTREE |         |               |
    | categorizations |          1 | category_id_categorization_type_categorization_id |            2 | categorization_type | A         |         398 |     NULL |   NULL | YES  |      BTREE |         |               |
    | categorizations |          1 | category_id_categorization_type_categorization_id |            3 | categorization_id   | A         |     1295306 |     NULL |   NULL | YES  |      BTREE |         |               |
    +-----------------+------------+---------------------------------------------------+--------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
    7 rows in set (0.01 sec)
    

    お知らせ

    12/15(土)にPHPカンファレンスに登壇いたします。
    http://phpcon.php.gr.jp/2018/

    13:00 ~ 13:25 Track 6 3F特別会議室
    ランサーズのCakePHP1.3→Cake2.8移行

    ランサーズでは、約1年かけて、CakePHP1.3をCakePHP2.8への移行を進めており、現在の進捗は99%です。(今年中には完了させたい)

    カンファレンスでは、移行で得たノウハウを詳細にお話したいと思います。
    明日は、odrum428さんの「slackで動くピアボーナス機能を実装した話」になります。

    ランサーズ開発合宿2018 バージョンアップチーム

    kanazawa|2018年08月30日
    CakePHP

    SREチームの金澤です。

    ランサーズ開発合宿のバージョンアップチームの成果についてお話させていただきます。

    ランサーズのバージョンアッププロジェクト

    現在、ランサーズのバージョンアップはSREチームが主導で行っています。

    バージョンアップは、以下のフェーズにわけて進行中です。

    1. CakePHP 1.3 → CakePHP 2.8
    2. PHP 5.3 → PHP 5.6
    3. CakePHP 2.8 → CakePHP 2.x最新
    4. PHP 5.6 → PHP 7 最新

    そして、現在、1を進めている最中です。
    コントローラー単位で移行を進めており、2018/6時点での進捗は70%でしたが、残る30%については、難易度が高いコントローラー、バッチ、管理画面などが含まれていました。

    2018年度のSREチーム

    2018/4から4人体制でSREチームが発足しました。
    ※その詳細は、「SREチームの発足」に書かせていただきました。

    そして、バージョンアッププロジェクトをSREチームで担当することになりました。

    現在、SREチームは

    SRE
    CRE
    バージョンアップ

    の3部門を抱えていて、それぞれがお互いの領域を跨ってカバーしている体制になっています。

    ※CREチームについては、また別途機会がありましたらお話ししたいと思います。

    2018年度のバージョンアップ体制

    ランサーズのCakePHP 1.3 → 2.8 バージョンアッププロジェクトは、当初3人でスタートしましたが、現在は、エンジニア全員での総力体制で進めています。

    ※この経緯については「PHPカンファレンス福岡2018に登壇しました」に書かせていただきました。

    バージョンアップを専任で行っているメンバーは現在1人しかいないのですが、これまでの蓄積からある程度手順がパターン化できており、そのノウハウをGithub Wikiにまとめ、エンジニア全員で担当できる体制を整えています。

    合宿の方針

    総力体制とはいえ、普段の業務でバージョンアップ作業に充てられるのは、SREチームのエンジニアで30%くらい、その他のエンジニアは10%くらいという感覚です。

    開発合宿は、普段の業務ではできないことを集中して行うことができる場です。
    この機会に一気にバージョンアップを進めたいと思っていました。

    今までの合宿では、ほぼ全員が、普段できないようなクリエイティブなテーマで取り組んでいました。
    それはそれで楽しいのですが、今年の合宿は過去最大の人数で行うこともあり、確実にアウトプットを出したいと考えていました。

    合宿は1泊2日で行われますが、実質的な作業時間は6時間+αくらいしかありません。
    限られた時間で、できる限りのアウトプットを出すため、以下の方針で進めることにしました。

    バッチのバージョンアップにリソースを集中

    バッチのバージョンアップは、比較的パターンが単純で、検証もしやすいため、今回の合宿ではバッチのバージョンアップに全リソースを集中することにしました。

    事前にアサイン

    事前に、どのバッチを誰が担当するかを決めておきました。
    バッチのバージョンアップは、ソース修正自体はそれほど難しいものではないのですが、動作確認を適切にできるかがポイントになります。
    バッチを作った人が内容を一番理解しているので、基本的にはバッチを作った人に優先的にバージョンアップをアサインするようにしました。

    フライング

    合宿は時間が限られているので、何も準備せずに行くと、開発環境のセットアップだけで終わってしまうことにもなりかねません。
    各担当者には、事前に1つ以上のバッチのバージョンアップを消化してもらい、一通りのフローを理解してから合宿に臨みました。

    現地でリアルタイムリリース

    原則、リリース作業は社内でしかできないようにしていますが、今回は合宿時でリリースまでしてしまうことで、確実にアウトプットを確定するようにしました。

    そのために、熱海に「槍」を持ってきました。

    ランサーズでは、リリース時に「槍」を持つことでリリースロックを実現しています。
    古典的な方法ですが、カナリア確認時に別の人が重複リリースしてトラブルになることを防止しています。

    現地到着後、リリースできる環境をSREチームが10分で構築しました。

     

    結果

    バッチだけで20リリースできました!

    リリースすると、全社チャットにリリース通知が来るので、休日にも関わらず開発部が猛烈な勢いで進めていることをアピールでき、合宿がどれほど効果が高いイベントかをアピールできたかと思います。

    バージョンアップの進捗は、72% → 80%になりました。
    1年がかりで進めているプロジェクトが、わずか1日で8%進んだことになります。

    この合宿をあと3回やりたい気分です。

    おまけ

    夕食後の1コマ。

    ストIIとぷよぷよは世代関係なく遊べますね。

    SREチームの発足

    kanazawa|2018年06月25日
    DevOps

    SREチームの金澤です。

    2018年度より、SREチームを発足しました。
    その経緯をお話ししたいと思います。

    インフラエンジニアとして

    私は、2013年11月にランサーズに入社しました。
    ランサーズ5年目にして、サービスが本格的に伸び始めた時期で、アプリエンジニアが運用を兼務するには荷が重くなり始めており、専任のインフラエンジニアが必要でした。

    入社後、4年半にわたり、サービスの安定化や負荷対策、最新の技術やサービスへの追従など、様々な施策を実行してきました。

    スタートアップのインフラエンジニアに求められる仕事は多岐にわたります。
    時には、サービスのソースコードに手を入れることも度々ありました。
    また、この時期のランサーズは、開発環境の支援や、社内インフラの整備もできる人材が不足しており、そのような業務もインフラエンジニアである私が担っていました。

    インフラエンジニア採用の難しさ

    2017年度のインフラチームは、新卒、インターン含めて4人でしたが、来年度の体制変更に伴い、再び1人になることがほぼ確定していました。

    そのため、経験豊富なインフラエンジニアを採用することが急務になりました。

    しかしながら、スタートアップにマッチするインフラエンジニアは採用が非常に難しいです。
    今までも採用活動を継続的に行ってきましたが、なかなか採用までに至らず、苦戦していました。

    スタートアップでは、特定の分野に精通するだけでなく、複数の分野の業務をこなす必要があります。
    今思えば、インフラエンジニアという枠組みで採用を試みた結果、書類選考の過程で多くのミスマッチが生じていたと思います。

    スタートアップとSRE

    ここ数年で、SRE(Site Reliability Engineering)という言葉が浸透してきました。
    Googleが提唱した言葉で、オライリーから書籍が出版されて、日本でもより認知度が広まってきたと思います。


    私自身、SREという言葉は最近認知したのですが、この書籍を読み、スタートアップ企業に必要なエッセンスがたくさん含まれていると感じました。

    そして、ランサーズのインフラチームが担ってきた

    • 新サービスのローンチ支援
    • リリースシステムの構築
    • 定型作業の自動化
    • 開発環境の構築
    • 分析基盤の運用
    • セキュリティの強化

    などは、ランサーズが成長する過程で積み重ねてきた施策であり、SREそのものでした。

    SRE(Site Reliability Engineer)の採用

    ランサーズに必要なエンジニア像として、SREはとても腑に落ちるものであることに気づき、今後はSRE(Site Reliability Engineer)というキーワードで採用を行おうと決意しました。

    これまでの集大成として、2月にTECH PLAYでSREをテーマにしたイベントを行わせていただきました。

    その時のスライドがこちらになります。

    また、採用方法もエージェントに頼らず、ダイレクトリクルーティングによる採用に切り替えました。
    エンジニアが自ら採用活動を行うため、エンジニアの負担が大きくなるのですが、採用の初期段階からエンジニアが参画するため、ミスマッチが大幅に減り、精度の高い採用ができるようになったと思います。

    SREチームの発足

    その結果、2018年度より新規に優秀なSREメンバーが3名加わり、正式にSREチームが発足しました。
    当初、1人採用できればと思っていたので、大変嬉しく思います。

    今年度のSREチームは、PHPバージョンアップやCRE(Customer Reliability Engineering)チームとの連携も含め、幅広いミッションが与えられましたが、未来につながる施策も含めて、今後進めていきたいと思います。

    ※次回は、SREチームの新メンバーにブログを書いてもらいます。

    お知らせ

    7/3に行われるSRE Lounge #4に登壇させていただくことになりました。
    https://sre-lounge.connpass.com/event/91566/

    おかげさまで満員御礼となりました。

    皆さんに少しでも有益な情報が提供できるように頑張りたいと思います。

    PHPカンファレンス福岡2018に登壇しました

    kanazawa|2018年06月18日
    CakePHP

    SREチームの金澤です。

    2018/06/16(土)に、PHPカンファレンス福岡に登壇させていただきました。

    昨年に続き、2度目の登壇となりました。
    この度は、採択していただきありがとうございました。

    登壇内容

    CakePHP 1.3 + PHP 5.3 → CakePHP 3 + PHP 7 バージョンアップ報告

    Fusicホール/16:15~16:45(30分)

    PHPバージョンアップ全般に関しては金澤が発表し、CakePHPのバージョンアップについてはアプリエンジニアの小林が発表しました。

    昨年、バージョンアップすることを決定し、エンジニアブログPHPカンファレンス福岡で社外に公表し、外部のPHP有識者の方々から助言をいただきながら進めてきました。

    昨年の発表時は、まだ前準備のフェーズで、実際のバージョンアップに取り掛かったのは、2017/7頃からでした。
    SREチームでは、PHP5.3 → 5.6 のバージョンアップから提案しましたが、アプリチームと協議した結果、CakePHP1.3 → 2.8から始めることにしました。

    CakePHP1.3 → CakePHP2.8の移行作業は、共存させながらコントローラー単位で徐々に行っております。
    普段の開発を止めずに同時並行でバージョンアップを行うためにこの方法を採用しました。

    このあたりの経緯は、昨年のPHPカンファレンス東京でも発表させていただきました。

    バージョンアップ開始当初は、専任の担当が3人で進めていましたが、2018/4から正式にSREチームを発足し、バージョンアッププロジェクト全体を担当しています。
    現在は、手の空いたエンジニアにスポットでバージョンアップを依頼し、片手間ながらほぼ総力体制で取り掛かっています。
    ※今までに行ってきたバージョンアップの手順がほぼ確立されていたので、Github Wikiに手順を集約することで、難しいコントローラーでなければ、新卒エンジニアでも手伝える環境になっています。

    2018/6時点で、CakePHP1.3 → CakePHP2.8の移行進捗は約70%です。
    今後の展望としては、CakePHP1.3 → CakePHP2.8移行が完了したら、

    PHP 5.3 → 5.6
    CakePHP2.8 → 2.10
    PHP 5.6 → 7.2

    とバージョンアップを進めていく予定です。

    登壇後、今年もたくさんの方にアドバイスをいただきました。
    大変感謝しております。

    年単位のプロジェクトでなので、時に会社の事情に左右されることもありますが、今後もプロジェクトを停滞させずに進めていきたいと思います。

    CloudFront

    EC2オリジンのCloudFrontで静的ファイルをキャッシュした話

    kanazawa|2017年12月11日
    AWS

    ランサーズ Advent Calendar 2017 11日目の記事です。

    インフラエンジニアの金澤です。

    CloudFrontでサムネイルをキャッシュした話に続きまして、静的ファイルをキャッシュした手順も記録として残しておきたいと思います。

    導入に至った経緯

    Appサーバーの負荷とレスポンス遅延

    当時は、サーバー費用をそこまでかけられないフェーズで、なるべく抑えた運用を心掛けていました。

    AppサーバーのEC2は、CPU使用率が80%~90%になってもサービスが落ちたりすることはありませんが、50%を超えるとサーバーレスポンスに影響が出始めます。

    レスポンスを悪化させないためには、CPU使用率はなるべく50%以下に抑えたいところです。

    静的ファイルのサーバー分離

    静的コンテンツへのリクエスト数は、動的コンテンツの10倍以上あります。

    静的ファイルのアクセスをAppサーバーから分離させればAppサーバーの接続数を大幅に削減でき、リソースを動的コンテンツに集中させることができます。

    Webサイトホスティング設定をしたS3に分離する方法もありましたが、リリースフローが複雑になります。

    CloudFrontにキャッシュさせれば、リリースフローも変更する必要がなく、エッジローケーションからの配信になるのでブラウザレスポンスの改善も期待できます。

    CloudFrontの費用

    img.lancers.jpにサムネイルをキャッシュしたときの月額費用は約月4万円(当時)でした。

    サムネイルのアクセスと静的コンテンツのアクセス数は大体同じくらいでした。

    CloudFrontが値下げされた時期でもあり、Appサーバー1台分よりも費用が抑えられそうな見積りができ、CloudFrontにキャッシュさせる案を採用することにしました。

    導入手順

    CloudFront用のドメインを定義

    静的ファイル用に以下のドメインを定義します。

    • static.lancers.jp
      • CloudFrontのAレコードAlias
    • static-origin.lancers.jp
      • WWWサーバーで設定するドメイン

    www.lancers.jpドメインでアクセスしていた静的ファイルを、以下のようにstatic.lancers.jpドメインでアクセスさせるようにします。

    https://www.lancers.jp/touch_icon.png

    https://static.lancers.jp/touch_icon.png

    static.lancers.jpドメインでアクセスすると、CloudFrontを経由してキャッシュされ、2回目以降はCloudFrontのエッジロケーションから配信されます。

    WWWサーバーの設定

    static-origin.lancers.jp用のホスト設定を定義します。
    Nginxの場合は以下のようになります。(動的コンテンツのphpはアクセスできないように設定)

    server {
        listen 80;
        server_name static-origin.lancers.jp;
        root /var/www/lancers/app/webroot;
     
        location ~ \.(css|gif|ico|jpeg|jpg|js|otf|pdf|png|svg|swf|ttf|woff|woff2|zip)$ {
            expires max;
        }
     
        location ~ \.php$ {
            deny all;
        }
     
        location ~ /\. {
            deny all;
        }
    }
    

    CloudFrontのディストリビューションを作成

    static-origin.lancers.jpをオリジンとしたCloudFrontのディストリビューションを作成します。
    static.lancers.jpのAレコードAliasにCloudFrontのドメインを関連付けます。

    静的ファイルのアクセスをstatic.lancers.jpに変更

    www.lancers.jp でアクセスされていた静的ファイルをstatic.lnacers.jpに変更します。
    静的ファイル読み込み箇所のソースを以下のように修正します。

    <img src="/touch_icon.png">
    ↓
    <img src="<?= Configure::read('static.url') ?>/touch_icon.png">
    

    この対応は1300箇所以上あり、地道な作業になりました。
    ただし、一度に全て変更する必要はないため、徐々に対応していきました。

    導入後の問題とその解決方法

    クエリストリングのハッシュ化

    CloudFrontのキャッシュ更新は、img.lancers.jpのときと同様、クエリストリングを付与し、静的ファイル更新時にクエリストリングも更新させる必要があります。

    しかし、この更新作業が開発者、特にデザイナーの方々の実装負荷を上げてしまうことになりました。

    img.lancers.jpのときは、更新日時をDBから取得していましたが、静的ファイルの場合は?v1.4のようなバージョン表記にしたため、都度更新する必要があります。

    そこで、アプリエンジニアの方に作っていただいたのが、git mergeのタイミングでハッシュ値を生成させる仕組みです。

    具体的には、.git/hooks/post-merge でcommit_hash.phpというファイルを生成します。

    commit_hash.php には、以下の様なハッシュ値の定義が出力されます。

    Configure::write('static_file_version', '78152cf38634670a1a631275f1288f34a9d71e90'));
    

    このハッシュ値をクエリストリングにすることで、リリース毎にクエリストリングが切り替わる仕組みです。
    https://static.lancers.jp/touch_icon.png?v=78152cf38634670a1a631275f1288f34a9d71e90

    リリースの順番

    静的ファイルにCloudFrontを適用後、リリース時にハッシュ値を更新したにも拘わらず、キャッシュが更新されない事象が度々発生していました。

    全てのソースを転送し終わる前にcommit_hash.phpを転送してしまうと、静的画像が更新される前にクエリストリングのハッシュ値が更新されてしまい、古い静的画像が再びキャッシュされてしまうためです。

    リリース時は、最新のソースを全て転送し、最後にcommit_hash.phpを転送する必要があります。

    また、Appサーバーが複数ある場合は、各Appサーバーへのリリースの時間差で同様の問題が発生することもあります。
    commit_hash.phpの転送は、全サーバーへ極力同じタイミングで行う必要があります。

    ※シンボリックリンクを切り替えるリリース方式の場合も同様に、切り替えを最後に、そして極力同時に行う必要があります。

    CORS設定

    例えば、WEBフォントのwoffファイルは、サイズが大きい為、特にCloudFrontにキャッシュさせたいファイルですが、当初、woffファイルをstatc.lancers.jpで参照させるとCORSのエラーが出力されていました。

    この問題を解決させるためには、static.lancers.jp側にwww.lancers.jpからの利用を許可させる必要があります。

    具体的には、以下のようにAccess-Control-Allow-Originヘッダを付与する必要があります。

    Access-Control-Allow-Origin: https://www.lancers.jp

    Nginx側で以下のように設定しました。

    server {
        listen 80;
        server_name static-origin.lancers.jp;
        root /var/www/lancers/app/webroot;
     
        set $cors "";
        if ($http_origin ~* https://www\.lancers\.jp$) {
            set $cors "true";
        }
     
        location ~ \.(css|gif|ico|jpeg|jpg|js|otf|pdf|png|svg|swf|ttf|woff|woff2|zip)$ {
            if ($cors = "true") {
                add_header Access-Control-Allow-Origin "$http_origin";
            }
            expires max;
        }
    …
    

    最後に

    静的ファイルのアクセス負荷対策として、WebサーバーとAppサーバーを分離させる方法がよく紹介されていますが、CloudFrontを導入すれば、Webサーバー側の役目をほぼ担ってくれます。

    大きなサーバー構成の変更が必要なく、ブラウザレスポンスの改善も期待できますので、CloudFrontにキャッシュさせるという方法も選択肢の一つになるかと思います。

    CloudFront

    EC2オリジンのCloudFrontでサムネイルをキャッシュした話

    kanazawa|2017年12月04日
    Apache

    ランサーズ Advent Calendar 2017 4日目の記事です。

    インフラエンジニアの金澤です。
    少し古いネタになりますが、CloudFrontでサムネイルをキャッシュした手順を記録として残しておきたいと思います。

    サムネイルの生成処理について

    ランサーズは、2012年5月にAWSに移行しました。
    ランサーズでは、プロフィール画像や提案画像をサムネイル処理しています。
    例えば、ランサーズのコンペの閲覧一覧で閲覧できるロゴ等の提案画像は、圧縮、縮小されたサムネイル画像です。
    (オリジナル画像はS3にあり、仕事を依頼したクライアントしか見ることができません)

    これらのサムネイル画像は、オリジナル画像をImageMagickで圧縮、縮小して表示します。
    この処理は大きな負荷がかかるため、一度作成したサムネイルはNFSに保管しキャッシュしていました。

    サムネイル生成とキャッシュ

    ランサーズでは、サムネイル画像をimg.lancers.jpというドメインで管理し、URLでアクセスされたタイミングでサムネイルを生成し、NFSに保管しています。
    Apacheで、以下のように設定すると、

    <VirtualHost *:80>
        ServerName img.lancers.jp
        DocumentRoot /var/www/lancers/app/tmp/thumbs
        <IfModule mod_rewrite.c>
            RewriteEngine On
            RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
            RewriteRule ^/([a-z]+)/([0-9a-f]+)/([0-9a-f]+)/([0-9a-f]+)_([0-9]+)_([0-9]+)([_]*)([0-9]*).jpg$ http://www.lancers.jp/file/$1/$5/$6/$8 [R]
        </IfModule>
            <Directory /var/www/lancers/app/tmp/thumbs>
                Order allow,deny
                Allow from all
            </Directory>
    </VirtualHost>
    

    以下のように動作します。

    • NFSにサムネイルがキャッシュされていない場合
      • www.lancers.jpにリダイレクト
      • PHP経由でサムネイルを生成して返す
      • 生成したサムネイルをNFSにキャッシュ

    • NFSにサムネイルがキャッシュされている場合
      • キャッシュのサムネイルをそのまま返す

    NFSのストレージ逼迫問題

    サービスが成長していくにつれ、サムネイルの数も増え、NFSのストレージ容量が逼迫してきました。
    当時のEC2は、ストレージ容量を拡張するには、一度AMIを取得してから再作成しなければなりませんでした。
    NFSは常時稼働しているので、メンテナンスを入れる必要がありましたが、AMIの取得に3時間以上かかり、メンテナンス時間内に終わるか見積りが難しく、キャッシュ期間を短くすることで対応していました。
    また、当時のEC2は1TBまでしかストレージを拡張できなかったので、拡張できてもいつかは限界が来ることが見えていました。

    キャッシュをNFSからCloudFrontへ

    ストレージ逼迫問題を解決する手段として、サムネイルもS3に格納する方法がまず思い浮かびます。
    ですが、ストレージをS3にするとサムネイル存在チェックの遅延時間が長くなるため断念したそうです(当時の担当者談)
    そこで、サムネイルをCloudFrontにキャッシュさせ、NFSを撤廃することを試みました。
    img.lancers.jpを、ELBからCloudFrontのアドレスに変更し、ELBにはimg-origin.lancers.jpというアドレスを対応させます。

    • CloudFrontにサムネイルがキャッシュされていない場合
      • PHP経由でサムネイルを生成して返す
      • 生成したサムネイルはCloudFrontにキャッシュされる

    • CloudFrontにサムネイルがキャッシュされている場合
      • CloudFrontのキャッシュが返される
        • ELB、Appサーバーには到達しない


    Apacheの設定を以下のように変更します。
    ※/var/www/lancers/app/tmp/thumbs はNFSから各EC2サーバーのディレクトリに設定変更し、サムネイル生成時の一時保管場所としてのみ利用します。

    <VirtualHost *:80>
        ServerName img-origin.lancers.jp
        DocumentRoot /var/www/lancers/app/tmp/thumbs
        <IfModule mod_rewrite.c>
            RewriteEngine On
            RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
            RewriteRule ^/([a-z]+)/([0-9a-f]+)/([0-9a-f]+)/([0-9a-f]+)_([0-9]+)_([0-9]+)([_]*)([0-9]*).jpg$ http://www.lancers.jp/file/$1/$5/$6/$8 [R]
        </IfModule>
        <Directory /var/www/lancers/app/tmp/thumbs>
            Order allow,deny
            Allow from all
        </Directory>
    </VirtualHost>
    

    ところが、この設定だとCloudFrontにはキャッシュされません。
    img.lancers.jp→www.lancers.jpにリダイレクトされてしまうため、サムネイルの生成過程でCloudFrontを経由しなくなるためです。
    img.lancers.jpドメインを維持したままサムネイルを生成させるため、リダイレクトではなく自分自身へプロキシさせる形にしました。

    このときのApacheの設定は以下のようになります。

    <VirtualHost *:80>
        ServerName img-origin.lancers.jp
        DocumentRoot /var/www/lancers/app/tmp/thumbs
        <IfModule mod_rewrite.c>
            RewriteEngine On
            RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
            RewriteRule ^/([a-z]+)/([0-9a-f]+)/([0-9a-f]+)/([0-9a-f]+)_([0-9]+)_([0-9]+)([_]*)([0-9]*).jpg$ http://127.0.0.1/file/$1/$5/$6/$8 [P]
        </IfModule>
        <Directory /var/www/lancers/app/tmp/thumbs>
            Order allow,deny
            Allow from all
        </Directory>
    </VirtualHost>
    

    ※Nginx化したときは、以下のように設定しました。

    server {
        listen 80;
        server_name img-origin.lancers.jp;
     
        root /var/www/lancers/app/tmp/thumbs;
     
        location ~ ^/([a-z]+)/([0-9a-f]+)/([0-9a-f]+)/([0-9a-f]+)_([0-9]+)_([0-9]+)([_]*)([0-9]*).jpg$ {
            limit_req zone=one burst=100;
            proxy_set_header X-Forwarded-Server $host;
            proxy_pass http://127.0.0.1/file/$1/$5/$6/$8;
        }
     
        location ~ \.php$ {
            deny all;
        }
     
        location ~ /\. {
            deny all;
        }
    }
    

    CloudFrontのTIPS

    証明書のアップロード

    AWSの証明書の場合

    CloudFrontで、AWSのCertificate Managerで作成したSSL証明書を利用したい場合は、「バージニア北部」のリージョンで作成する必要があります。

    その他の証明書の場合

    ELBとCertificate ManagerCloudFrontで共通の証明書を使いたい場合、マネジメントコンソールではなく、AWS CLIで登録する必要があります。
    ※詳細はQiitaに書かせていただきました。https://qiita.com/yKanazawa/items/f4362a21ead88888bffd

    キャッシュの更新

    一度CloudFrontにサムネイルがキャッシュされると、サムネイル画像が更新されても同じURLでは画像が切り替わりません。
    そこで、URLにクエリ文字列を付与して対応します。
    参考:クエリ文字列パラメータに基づいてキャッシュするように CloudFront を設定する
    CloudFrontのBehavior設定で、以下のように設定しておくと、クエリ文字列が変更されたときに、再度オリジンのEC2に読み込みにいきます。

    ランサーズでは、サムネイル画像の更新時に更新日時をクエリ文字列に付与して対応しています。
    https://img.lancers.jp/userprofile/6/c/6ccc3…e7515_225170_130.jpg?20171130092919

    キャッシュの確認

    アクセスした画像が、CloudFrontのキャッシュが返したものなのか、オリジンのEC2が返したものなのかを確認したい場合は、Response HeadersのX-Cacheヘッダを確認します。

    • CloudFrontのキャッシュから返された場合
      • X-Cache: Hit From CloudFront
    • オリジンのEC2から返された場合
      • X-Cache: Miss From CloudFront

    Chromeの場合は、デベロッパーツールの「Network」タブで確認できます。

    アクセスログの取得

    CloudFrontを経由すると、WebサーバーのアクセスログのRefererがすべて”Amazon CloudFront”になってしまいます。
    (リモートIPアドレスも、CloudFrontのエッジロケーションのものになります)

    10.0.x.xx [10/Jun/2014:17:32:30 +0900] 6.124.41.xx.xxx - - [10/Jun/2014:17:32:30 +0900] "GET / HTTP/1.1" 200 24154 "-" "Amazon CloudFront" xxx ms
    10.0.x.xx [10/Jun/2014:17:31:39 +0900] 60 66.249.xx.xxx - - [10/Jun/2014:17:31:39 +0900] "GET /proposal/search/award HTTP/1.1" 200 12331 "-" "Amazon CloudFront" xxx ms
    10.0.x.xx [10/Jun/2014:17:32:09 +0900] 60 66.249.xx.xxx - - [10/Jun/2014:17:32:09 +0900] "GET /proposal/search/award HTTP/1.1" 200 12331 "-" "Amazon CloudFront" xxx ms
    

    Refererを確認するときは、CloudFrontのアクセスログをS3から取得する必要があります。

    アクセスログの設定

    CloudFrontのGeneral設定で、以下のように設定すれば

    S3バケットにログが格納されます。

    アクセスログフォーマット

    以下のフォーマットで保存されています。

    #Version: 1.0
    #Fields: date time x-edge-location sc-bytes c-ip cs-method cs(Host) cs-uri-stem sc-status cs(Referer) cs(User-Agent) cs-uri-query cs(Cookie) x-edge-result-type x-edge-request-id x-host-header cs-protocol cs-bytes time-taken
    2014-10-01  19:50:21    MIA50   417 66.249.71.8 GET xxxxxxxxxxxxx.cloudfront.net    /proposal/f/e/fec...ec2_3989074_150.jpg  307 -   Googlebot-Image/1.0 20140929163745?timestamp=1410048000048  -   Miss    dxn...maQ==    img.lancers.jp  http    307 0.400
    2014-10-01  19:50:22    MIA50   417 66.249.71.8 GET xxxxxxxxxxxxx.cloudfront.net    /proposal/b/5/b5b...abd_3915699_150.jpg  307 -   Googlebot-Image/1.0 20140912094937?timestamp=1410048000041  -   Miss    aRH...mlw==    img.lancers.jp  http    307 0.372
    2014-10-01  19:50:25    MIA50   417 66.249.71.8 GET xxxxxxxxxxxxx.cloudfront.net    /proposal/0/2/029...b70_3981251_150.jpg  307 -   Googlebot-Image/1.0 20140926205158?timestamp=1410048000036  -   Miss    _MS...gRg==    img.lancers.jp  http    307 0.342
    2014-10-01  19:50:28    MIA50   417 66.249.71.8 GET xxxxxxxxxxxxx.cloudfront.net    /proposal/d/d/dd6...245_3966683_150.jpg  307 -   Googlebot-Image/1.0 20140923170904?timestamp=1410048000045  -   Miss    MLd...kJw==    img.lancers.jp  http    307 0.368
    

    アクセスログ解析

    CloudFrontのアクセスログは、10~20分単位で圧縮保存されるので、ファイル数が膨大になります。
    AWS CLIで一括ダウンロードして解析を行います

    2014-10-02のログ全てダウンロード

    aws s3 sync s3://ログバケット名/cloudfront/ ./ --exclude "*" --include "*2014-10-02*"
    

    2014-10-02のログから、リモートIPアドレスが「xxx.xxx.xxx.xxx」であるものを抽出する

    gunzip -c *2014-10-02* | grep xxx.xxx.xxx.xxx
    

    CloudFront導入効果

    • NFSのストレージ逼迫問題の解決
      • NFSにキャッシュする必要がなくなったため、NFSは必要なくなりました
    • ブラウザレスポンスの改善
      • 約0.5s程改善されました
    • AppサーバーのCPU負荷改善
      • AppサーバーのCPU負荷が約10%程改善しました

    最後に

    CloudFrontの事例は、S3との連携が主に紹介されていますが、S3以外をオリジンとして利用することも可能です。
    今回は、EC2で生成したサムネイルをCloudFrontにキャッシュする事例を紹介させていただきました。
    ランサーズでは、サムネイル以外の静的画像にもCloudFrontを利用していますので、その事例も後日紹介させていただければ幸いです。

    ランサーズのNginx+PHP-FPM化

    kanazawa|2017年09月05日
    Apache

    インフラエンジニアの金澤です。
    以前ブログでお話しました、ランサーズのNginx+PHP-FPM化が一段落したのでまとめました。

    移行の目的

    以前は、Apache 2.2 + mod_php の構成で動作していました。
    以前のブログでもお話ししましたように、PHP 5.3からバージョンアップする過程で、今までのhttpd.confをそのまま使うことができなくなったため、この機会に設定を一から見直し、Nginx + PHP-FPM 構成に移行しました。

    移行時に行ったこと

    Nginxのインストール

    ランサーズは、Amazon Linuxで稼働しています。
    yumパッケージのNginxには欲しい機能が含まれていなかったため、ソースからインストールしました。
    configure時に以下のオプションでコンパイルすることで、yumパッケージでインストールした場合とほぼ同じディレクトリ構成になります。

    ./configure \
    --user=nginx \
    --group=nginx \
    --prefix=/etc/nginx \
    --sbin-path=/usr/sbin/nginx \
    --conf-path=/etc/nginx/nginx.conf \
    --error-log-path=/var/log/nginx/error.log \
    --pid-path=/var/run/nginx.pid \
    --lock-path=/var/run/nginx.lock \
    --http-log-path=/var/log/nginx/access.log \
    --with-http_addition_module \
    --with-http_gzip_static_module \
    --with-http_gunzip_module \
    --with-http_realip_module \
    --with-http_v2_module \
    --with-http_ssl_module \
    --with-http_stub_status_module
    

    PHP-FPMとの連携

    PHP-FPMとはunix socketで連携します。

    server unix:/var/run/php-fpm/php-fpm.sock;
    

    ELB対策

    ELBのSSL Termination機能を利用しているため、https通信も、EC2内部ではhttpで処理しています。
    https経由のアクセスであったかどうかを判定するために、X-Forwarded-Proto環境変数を利用しています。

    set $elb_https off;
    if ($http_x_forwarded_proto = https) {
        set $elb_https on;
    }
    
    location ~ \.php {
    ...
        fastcgi_param HTTPS $elb_https;
    ...
    }
    

    そして、アクセスログがELBのプライベートアドレスにならないように、以下の設定を行いました。

    set_real_ip_from 10.0.0.0/8;
    real_ip_header X-Forwarded-For;
    

    プロキシ設定

    http://www.lancers.jp/magazine/ 等、いくつかのURLをWordPressサーバーにプロキシしています。
    Apache(httpd)の場合は、rewriteやProxyPassで手軽にプロキシ設定できましたが、Nginxの場合は、resolverにDNSのIPアドレスを設定する必要があります。
    AWSの場合、10.0.0.0/16 のVPC上のresolverは10.0.0.2になります。

    location ~ ^/magazine/(.*)$ {
        resolver 10.0.0.2;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        set $endpoint "magazine.lancers.jp";
        proxy_pass http://$endpoint/magazine/$1?$args;
    }
    

    開発環境ではhostsを利用しているので、dnsmasqをインストールし、hostsをDNSサーバーとして稼働させています。
    このときのresolverのIPアドレスは 127.0.0.1 になります。

    Apache機能依存関数の置き換え

    Apache(httpd)機能依存のPHP関数を置き換える必要がありました。

    apache_note関数

    アクセスログに、ログインユーザーidを記録するために利用していました。

    apache_note('userId', $this->loginUser['id']);
    

    Apache → Nginx移行時にエラーにならないように、暫定的に以下のようにソースを修正しました。

    if (strpos($_SERVER['SERVER_SOFTWARE'], 'pache') !== false) {
        apache_note('userId', $this->loginUser['id']);
    } else { // nginxの場合はResponse Headerを利用
        header('X-User-ID: ' . $this->loginUser['id']);
    }
    

    getallheaders関数

    このブログの記事を参考にさせていただきました。
    app/config/bootstrap.php に以下のように実装しました。
    Nginxに移行した場合に、こちらで実装したgetallheaders関数が実行されるようになります。

    if (!function_exists('getallheaders')) {
        function getallheaders()
        {
            $headers = array();
            foreach ($_SERVER as $name => $value) {
                if (substr($name, 0, 5) == 'HTTP_') {
                    $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
                }
            }
            return $headers;
        }
    }
    

    チューニング設定

    ※AWSのEC2 c4.2xlarge(8コア、15GB)インスタンスで稼働させた場合の設置値です。

    Apache + mod_phpの設定

    Apache + mod_phpの時代は、試行錯誤しながら、最終的に以下の値で運用していました。

    StartServers        100
    MinSpareServers     100
    MaxSpareServers     120
    ServerLimit         350
    MaxClients          350
    MaxRequestsPerChild  30
    

    MaxRequestsPerChildのデフォルトは10000ですが、かなり低く設定しています。
    低く設定すると、すぐにプロセスがなくなるため、forkの量が増え、CPU負荷が増加します。
    高く設定すると、プロセスのforkが少なくなりますが、アクセスの度にプロセスのメモリ使用量が増加していきます。
    CPU能力の高いc4系のインスタンスを利用しているため、メモリ容量と比較してCPUに余裕があります。
    CakePHP等のフレームワークを使ったサービスの場合、GCによるメモリ解放があまり期待できないので、こまめにプロセスを切ることでメモリを確保していました。

    Nginx + PHP-FPMの設定

    Nginxはイベント駆動のため、基本的にはCPU数分だけプロセスを稼働する運用になります。
    インスタンスタイプの変更時に動的にプロセス数が変更されるように、worker_processesをautoに設定しました。

    worker_processes auto;
    

    ※結果、現在、以下の10プロセスが稼働しています。

    ID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
     814 nginx     20   0 73920 6952 3148 S  0.0  0.0   0:00.98 nginx
     815 nginx     20   0 73920 6964 3148 S  0.0  0.0   0:01.78 nginx
     816 nginx     20   0 73920 6704 3148 S  0.0  0.0   0:02.59 nginx
     817 nginx     20   0 74168 6832 3148 S  0.0  0.0   0:05.45 nginx
     819 nginx     20   0 75604 8380 3148 S  0.0  0.1   0:15.86 nginx
     820 nginx     20   0 75948 8856 3148 S  0.0  0.1   1:04.30 nginx
     822 nginx     20   0 78564  11m 3148 S  0.0  0.1   5:00.41 nginx
     823 nginx     20   0 77844  10m 3148 S  0.0  0.1   4:53.30 nginx
     824 nginx     20   0 72920 5056 2448 S  0.0  0.0   0:00.20 nginx
    2017 root      20   0 72920 5928 3416 S  0.0  0.0   0:04.44 nginx
    

    PHP-FPMのプロセス数は100に設定しました。
    ランサーズの場合、PHP-FPM のメモリ消費量は、1プロセスあたり最大で約100MBほど消費していました。
    100プロセスの場合、PHP-FPMのプロセスだけで約10GBのメモリ消費となります。
    pm_max_requests は、ApacheのMaxRequestsPerChild にあたる設定です。
    Apacheのときと同様に30に設定しました。

    pm = dynamic
    pm.max_children = 100
    pm.start_servers = 100
    pm.min_spare_servers = 100
    pm.max_spare_servers = 100
    pm.max_requests = 30
    

    ※すべての設定を100に統一したので、今後はpm = static にしても良いかもしれません。

    移行結果

    ※AWSのEC2 c4.2xlarge(8コア、15GB)インスタンスで稼働させた結果です。

    リソース消費量

    移行前(apache + mod_php)

    CPUは8コア(800%)中、200%手前でほぼ安定。

    メモリ15GBをほぼリミットまで利用していました。

    移行後(Nginx + PHP-FPM)

    CPU使用率は、100%~300%の間で変動するようになりました。

    PHP-FPM 100プロセスの設定で、EC2のメモリ15GBをほぼリミットまで利用。
    ※その後、slabキャッシュを定期的に削除するようにしたところ、5GB程度の利用に収まっています。

    サーバーレスポンス

    結果、ほとんど変化はありませんでした。

    Apache(httpd)は、mod_phpを利用するため、preforkで動作していました。
    静的ファイルもpreforkのApache(httpd)プロセスで処理していたため、イベント駆動のnginxのほうがパフォーマンスは向上しそうですが、前段でCDN(CloudFront)を挟んでキャッシュしているため、EC2への静的ファイルへのアクセス頻度が低く、差が出にくいと考えています。

    mod_phpとPHP-FPMは、ランサーズにおいては、レスポンス上はほとんど差がでませんでした。
    ただし、今回はパフォーマンス向上ではなく、バージョンアップの前準備が目的でしたので、極端にパフォーマンスが劣化しなければOKという方針で取り組んでいます。
    また、現在PHP-FPM 5.3で稼働していますが、今後、PHP-FPM 5.6にバージョンアップしたらどうなるかも計測していきたいと思います。

    PHP、CakePHPのバージョンアップ状況

    以前のブログで、以下のフェーズに分けてバージョンアップを行うと話していました。

    Apache + mod_php + PHP 5.3 + CakePHP 1.3
    ↓(第1フェーズ)
    Nginx + PHP-FPM + PHP 5.3 + CakePHP 1.3
    ↓(第2フェーズ)
    Nginx + PHP-FPM + PHP 5.6 + CakePHP 1.3
    ↓(第3フェーズ)
    Nginx + PHP-FPM + PHP 5.6 + CakePHP 2
    ↓(第4フェーズ)
    Nginx + PHP-FPM + PHP 5.6 + CakePHP 3
    ↓(第5フェーズ)
    Nginx + PHP-FPM + PHP 7.x + CakePHP 3

    その後、計画の詳細を詰め、現在は以下のフェーズに分けて進めています。
    ※インフラ側では、PHP 5.6のバージョンアップ検証を進めていましたが、その前にCakePHPを2.8までバージョンアップする方向で進めることになりました。

    Apache + mod_php + PHP 5.3 + CakePHP 1.3
    ↓(第1フェーズ)
    Nginx + PHP-FPM + PHP 5.3 + CakePHP 1.3
    ↓(第2フェーズ)
    Nginx + PHP-FPM + PHP 5.3 + CakePHP 2.8
    ↓(第3フェーズ)
    Nginx + PHP-FPM + PHP 5.6 + CakePHP 2.8
    ↓(第4フェーズ)
    Nginx + PHP-FPM + PHP 5.6 + CakePHP 2.9
    ↓(第5フェーズ)
    Nginx + PHP-FPM + PHP 7.x + CakePHP 2.9
    ↓(第6フェーズ)
    Nginx + PHP-FPM + PHP 7.x + CakePHP 3
    ↓(第7フェーズ)
    Nginx + PHP-FPM + PHP 7.x + CakePHP 4

    そして現在、第2フェーズの、CakePHP 1.3 → 2.8 バージョンアップ作業を進めています。

    PHPカンファレンスに弊社エンジニアが登壇します

    10/8 に開催される、PHPカンファレンス2017に弊社エンジニアの秋山が登壇します。
    https://joind.in/event/japan-php-conference-2017/session23-lancers
    上でお話しました、バージョンアップ状況の詳細についてもお話しする予定です。

    PHPカンファレンス福岡 2017に登壇しました

    kanazawa|2017年06月12日
    AWS

    インフラエンジニアの金澤です。

    2017/06/10(土)に、PHPカンファレンス福岡に登壇させていただきました。

    今回、30分枠とLT枠(5分)の2枠を採択していただきました。

    登壇内容

    CakePHP 1.3 + PHP 5.3 → CakePHP 3 + PHP 7 移行を決めた話

    Dホール/16:15〜16:45 (30分)

    事前に、概要をエンジニアブログで公表させていただいていたのですが、思いのほか反響がありました。
    (特に、CakePHP 1.3 → 3 への移行に関しては賛否両論あり、参考にさせていただきました)

    バージョンアップは以下のフェーズに分けて計画しています。

    Apache + mod_php + PHP 5.3 + CakePHP 1.3
    ↓(第1フェーズ)
    Nginx + PHP-FPM + PHP 5.3 + CakePHP 1.3
    ↓(第2フェーズ)
    Nginx + PHP-FPM + PHP 5.6 + CakePHP 1.3
    ↓(第3フェーズ)
    Nginx + PHP-FPM + PHP 5.6 + CakePHP 2
    ↓(第4フェーズ)
    Nginx + PHP-FPM + PHP 5.6 + CakePHP 3
    ↓(第5フェーズ)
    Nginx + PHP-FPM + PHP 7.x + CakePHP 3

    現在、第2フェーズの最中です。

    PHP 5.6のバージョンアップまでは、できそうな手ごたえを感じていますが、問題はCakePHPのバージョンアップです。
    ここをどのように行うか、今も議論中で、会場の皆さんにもアドバイスをいただきたい旨を話しました。

    最後、時間が少し余ったため、その場で質疑応答の時間としていただき、CakePHPのバージョンアップ経験者の方々から様々なアドバイスをいただきました。

    • CakePHP 2 → CakePHP 3の移行スクリプトはそれほど期待できない
    • UTがSimpleTestからPHP Unitに変更されるのをどう対応するか
    • 経験者の方々からの移行工数の目安

    など、これからぶつかるであろう壁について、予め確認することができて良かったです。

    ※ブログの反響を受け、詳細な質問に答えられるように、PHPエンジニアの上野にも参加してもらいました。上野自身もかなり収穫があったようです。

    WHERE 1 = 1

    Fusicホール/17:00〜17:45 ライトニングトーク(各5分)

    質疑応答(Ask the Speaker)でも盛り上がっていたのですが、LTは2番目の発表だったため、切り上げて急いでFusicホールに向かうことに。

    間髪入れず、次の発表となり、緊張はしなかったのですが、時間がなくてあっという間に5分が過ぎてしまいました。
    銅鑼が鳴り焦って帰って来たら、マイクを持って帰ってしまう失態(笑
    LTもリハーサルしておくべきでした。またどこかで再チャレンジしたいです。
    でも、なんとか伝えたいとこまでは伝えられたと思います。

    反響としては、「面白かった」ではなく、「恐ろしい話だった」というもの多かったと思います。
    会場の方々も、CakePHPでコーディングしている方々は多く、やはり他人事ではないのだと感じました。

    こちらはスライドはアップできないのですが、
    一番伝えたいことは、CakePHPの以下のバグを放置しないことです。
    CakePHP generated UPDATE query with WHERE 1 = 1

    このバグは2014/7にFixされていますので、
    該当バージョンの方は速やかにアップデートしておくことをお勧めします。
    https://github.com/cakephp/cakephp/pull/3959

    ※CakePHP 1.3側も、1.3.21 でFixされています。
    https://github.com/cakephp/cakephp/commit/41082b1

    懇親会

    まず、LTの話題で盛り上がりました。
    開発環境で起こったことがあるという話も聞き、表には出てこなくても、やっぱり同じ経験をしている方もいるのだと思いました。

    そして、CakePHPのバージョンアップについて、引き続き熱い議論が繰り広げられました。
    今後のバージョンアップ方法について、考えている方法をいくつか提案しましたが、その良し悪しについても極めて論理的にアドバイスを頂きました。
    自分のサービスであるかのようにアドバイスしていただいた、Fusicさんや有識者の方々に深く感謝いたします。

    特に、CakePHP 2 → CakePHP 3 へのバージョンアップは、フレームワークを別なものに変更するくらいに考えたほうがよく、このタイミングで、別なフレームワークに移行する方も多いとのことでした。

    ※この話を聞き、バージョンアップ計画としては、PHP 5.6 + CakePHP 2 までできたら、(CakePHP 3ではなく)先にPHP7.x化を着手した方が良さそうだなーという感触です。

    福岡の参加者で、ランサーズを使っている方が多かったのも意外な驚きでした。
    フリーランサーや学生さんが、ランサーズを知っていて、私たちのセッションを楽しみにしていただいてたことを聞き、嬉しかったです。

    最後に

    今回、登壇者という形で参加させていただきましたが、参加者としても非常に勉強になり、刺激を受けたイベントでした。特に、今回、PHPのバージョンアップをテーマにしたセッションが他にもあり、参考にさせていただいた内容も多かったです。

    CakePHP 2 → 3 への移行がツラいということは身に染みて分かりましたが、それでもチャレンジしたいという気持ちも残っていますし、アドバイスいただいた方々はじめ、ランサーズがCakePHP 3までできるのか、興味深く見守ってもらえると感じました。

    まずは、PHP 5.6 + CakePHP 2 まで、確実にバージョンアップしたいと思います。
    次の日に、CakePHP 1.3 → 2の移行スクリプトを検証を開始してしまうくらい、テンション上がってしまいました。

    お知らせ

    CakePHP 3.4 クックブック & ソースコードリーディング

    CakePHPの最新版を勉強しておく目的で、3月よりソースコードリーディング会を始めました。

    2017/6/22(木)に第3回目を行います。

    第3回 CakePHP 3.4 クックブック & ソースコードリーディング

    社外からの参加者も募集しています。興味のある方は是非ご参加ください!

    エンジニア募集中!

    ランサーズでは、バージョンアップのプロジェクトに協力していただけるエンジニアを募集しています。

    PHP/CakePHP大規模なバージョンアップを推進するエンジニアを募集!

    本プロジェクトに興味を持った方と、是非一緒に推進したいです!

    ※もちろん、プロダクト成長させていくエンジニアも募集しています!

    詳しくは↓から

    PHP、CakePHPバージョンアップの決断

    kanazawa|2017年05月26日
    Apache

    インフラエンジニアの金澤です。

    この度、ランサーズ稼働環境(PHP + CakePHP)のバージョンアップを決断しました。
    まずは私から、その経緯と計画についてお話いたします。

    バージョンアップ決断の理由

    ランサーズは、2008年にサービスを開始しました。
    現在、PHP 5.3.29 + CakePHP 1.3.6 で稼働しており、長らくバージョンアップせずに開発を続けてきましたが、今回、PHP 7 + CakePHP 3までバージョンアップすることを決断しました。
    その理由は、主に以下になります。

    旧バージョンのサポート終了

    PHP 5.3もCakePHP 1.3も、サポートが終了しています。
    今後、特にセキュリティに関わるバグが報告された場合、今のバージョンを続ける限り、自分たちで対応する必要があります。
    (実際、独自に手を入れている箇所がいくつか存在します)

    ライブラリバージョンアップへの追従

    サードパーティが提供するPHPライブラリも、PHP5.3をサポートしなくなってきています。
    例えば、以下のライブラリです。

    最新ライブラリを利用するためには、要求するPHPバージョンまで追従する必要がありました。

    開発生産性向上とエンジニア採用

    開発エンジニアからも、新しいPHPの関数が使えないことを嘆く声が出ていました。
    PHPの古いバージョンで好んで開発したいエンジニアはほとんどいないでしょう。
    最新のPHP、CakePHPの提供するライブラリを利用できれば、開発生産性の向上が期待できます。
    また、常に最新の技術を採用し続けることで、エンジニアにとっても魅力的な開発環境を提供したいと思いました。

    バージョンアップ計画

    今回、バージョンアップの幅がかなり大きくなるので、条件を整理しました。

    • PHP 5.4にするには、Apacheのバージョンアップが必要(後述)
    • PHP 5.4→5.5→5.6については、大きな構成変更は生じない
    • CakePHP 3にするには、PHP 5.6以上が必要
    • PHP 7ではCakePHP 1.3はまともに動かない

    上記の条件を踏まえ、バージョンアップは、以下のフェーズに分け、段階的に行うことにしました。

    • Apache + mod_php → Nginx + PHP-FPM移行
    • PHP 5.3 → PHP 5.6移行
    • CakePHP 1.3 → CakePHP 3移行
    • PHP 5.6 → PHP 7移行

    Apache + mod_php → Nginx + PHP-FPM移行

    現在のランサーズは、Amazon Linux + Apache(httpd)2.2 + mod_php で稼働しています。
    ※以前はCentOS6で稼働していたのですが、昨年Amazon Linuxに移行しました。
    Amazon LinuxのyumでPHPをインストールすると、Apache(httpd)もセットでインストールされます。
    PHPとApache(httpd)のバージョン組み合わせは以下の通りです。

    php httpd
    5.3.29 2.2.31
    5.4.45 2.4.25
    5.6.30 2.4.25

    つまり、yumでPHPを5.3からバージョンアップすると、Apache(httpd)も2.4にバージョンアップされます。
    Apache(httpd)2.2と2.4では設定ファイルの構成が大幅に変更されていますので、単純に2.2の設定ファイルをそのまま使うというわけにはいきません。
    移行するなら、設定項目を1から見直す必要があります。

    設定を1から見直すのであれば、Apache + mod_php以外の選択肢も候補に入ります。
    今回は、パフォーマンスの向上が期待できるNginx + PHP-FPMを採用しました。

    PHP 5.3 → PHP 5.6移行

    Nginx + PHP-FPMの移行が終わったら、PHPを5.6までバージョンアップします。
    PHP 5.6までバージョンアップするには、以下の条件を満たすようにソースを修正する必要があります。

    当初、PHP 5.4 → PHP 5.5 → PHP 5.6と段階的にバージョンアップする計画でしたが、PHP5.4では関連ライブラリのインストールが難しく、断念しました。
    PHP 5.6へ一気にバージョンアップする計画は、一度に修正する量は多くなりますが、総合的に考えてこちらの方が速く移行できると判断しました。

    CakePHP 1.3 → CakePHP 3移行

    ここが一番の難関になります。
    CakePHP 1.3 → 2移行は大きな壁で、稼働中のサービスをどのように移行するのか、課題が山積みです。
    しかしながら、CakePHP 2 → 3移行は、それと比較すれば、それほど難しくないと見積もっています。
    移行状況や以降方法次第で計画が変更される可能性はありますが、現在はCakePHP 1.3 → CakePHP 3に一気に移行する計画を立てています。

    CakePHP 1.3 → 3移行は、実績がほとんどないので、大きなチャレンジになります。

    2017/5/30追記:

    すみません。雑に書きすぎました。
    CakePHP2→3の方が大変ではないかという意見を多く頂きましたので補足させてください。

    開発エンジニアからも、CakePHP2→3のほうが大変だという意見が出ています。
    (特に、CakePHP2→3でモデル周りが全部変更されている点など)
    その一方で、古株のエンジニアは、CakePHP本体にも修正を入れている点を懸念しております。
    (過去のセキュリティアップデートを自前で組み込んだり、独自の機能のためにcoreソースを書き換えている箇所が結構存在します)

    CakePHP2に移行するには、

    • UTを完全整備(過去の試みはことごとく失敗)
    • ランサーズ独自の修正を完全に撤廃(かなり大変)
    • UTをチェックしながらCakePHP1.3→2へ移行

    というステップが必要になります。

    逆に、ここまでできれば、体制がかなり整います。
    CakePHP2→3はランサーズ独自の修正がない状態で取り掛かれますし、他社での移行事例や意見もそのまま参考にできます。

    とはいえ、CakePHP2→3の移行は、一般的にはCakePHP1→2よりも大変な作業になります。
    (「難しくない」という表現が適切ではありませんでした)
    同じ苦労を2度するのであれば、一度にやる方法も検討しても良いのではないかという意見もあります。
    (CakePHP1.3のbootstrap.phpのuriのパス指定で3.4.xに切り替える方法で移行した方もいるとのこと)

    何れにせよ、ここはまだ明確な結論が出ていません。
    今後も議論しながら、現状は、今できる作業を着実に進めている状態です。
    ※ここら辺、知見のある方に是非ご意見いただきたいです。

    PHP 5.6 → PHP 7移行

    CakePHP 3に移行できれば、PHP 7に移行できます。
    PHP 7は実装が新しくなり、5.6まで維持していた互換性を排除している部分も多いので、この移行作業は1フェーズとし、じっくり行う予定です。
    ※PHP 7.1も検討していましたが、現状、Amazon LinuxのyumリポジトリではPHP 7.0までしかサポートしていないため、現状は7.0までアップデートする計画にしています。

    現在の状況

    Nginx + PHP-FPM化

    ほぼ検証が完了し、近日中に移行予定です。
    移行が完了しましたら、その結果をブログに書きたいと思います。

    PHP 5.3 → 5.6化

    Nginx版でPHP5.6をインストールした開発環境用Dockerコンテナを構築中です。
    ランサーズではDockerで開発環境を構築しています。

    PHP 5.6のコンテナを構築し、PHP5.3版とは別にDockerレジストリにアップしておきます。
    開発エンジニアは、利用するコンテナをPHP5.3、PHP5.6に自由に切り替えることができるので、この仕組みで互換性を検証していく予定です。

    UTテストジョブの整備

    現状のUTテストジョブのうち、正常に終了しないものや、非常に時間がかかるものがあります。
    そのため、CIを完全に回しきれていない状態です。
    バージョンアップによるエラーやバグを速やかに検知できるように、全て正常終了し、かつCIを現実時間内に回せるレベルまで整備中です。

    エンジニア合宿

    5/27~5/28にエンジニア合宿を行います。
    UTのテストジョブ整備を中心に、プロジェクトを推進する予定です。

    PHPカンファレンス登壇

    6/10(金)PHPカンファレンス福岡 で登壇予定です。
    http://phpcon.fukuoka.jp/2017/
    今回ブログに書かせていただいた内容を、さらに詳しく発表したいと思います。

    エンジニア募集中!

    ランサーズでは、このバージョンアップのプロジェクトに協力していただけるエンジニアを募集しています。

    PHP/CakePHP大規模なバージョンアップを推進するエンジニアを募集!

    本プロジェクトに興味を持った方と、是非一緒に推進したいです!

    いよいよ、プロジェクトが本格的に始動します。

    AWSでWordPressのスケールアウト

    kanazawa|2017年03月06日
    Aurora

    インフラエンジニアの金澤です。
    今回は、AWS上のWordPressサーバーをスケールアウトするために行った手順について紹介いたします。

    ランサーズで運用しているWordPressサービス

    ランサーズでは10以上のWordPressサービスをAWSで運用しています。
    ランサーズと比べてアクセス数が多くなく、負荷も低いのでこれらのサービスを1台のEC2で運用してきましたが、リソースが不足してきたため、複数台にスケールアウトできるように準備し始めました。

    スケールアウトするために解決すべき問題

    WordPressのEC2をスケールアウトするには、以下の問題を解決する必要があります。

    MySQL

    EC2のローカル上にMySQLがインストールされた状態では、複数台構成にできません。
    MySQLを別サーバーに移動する必要があります。

    uploadsディレクトリ

    wp-content/uploadsディレクトリには、記事投稿時にアップロードされた写真等のメディアデータが格納されます。
    EC2上にこのディレクトリがある状態では複数台構成にできません。
    uploadsディレクトリを別サーバーに移動する必要があります。

    pluginsディレクトリ

    wp-content/pluginsディレクトリには、WordPressのプラグインが格納されています。
    複数台構成にすると、管理画面からプラグインを追加しても、1台のサーバーにしか反映されません。
    全サーバーにプラグインが配布されるような仕組みを作る必要があります。

    ログ

    複数台構成にすると、各サーバーにログが分散され、解析が困難になります。
    アクセスログを解析したい場合は、別途ログを集約する仕組みを構築することになります。

    スケールアウトするために行なった準備

    以下、上記の問題を解決するために行った手順について紹介いたします。

    スケールアウト前

    元々は、1台のEC2にグローバルIPを付与して運用していました。
    https通信については、必要なドメインのみSSL証明書を申請していました。

    Git化

    ランサーズでは、WordPressのプロジェクトは、全てGitHubで管理しています。

    wp-content/plugins ディレクトリはGit化し、プラグインの追加もGitHubに反映しています。
    プラグインの追加は本番サーバーの管理画面では行わず、開発環境で追加し、検証が完了したものをgit pullでリリースするフローにしています。
    ※wp-config.phpやwp-content/uploadsディレクトリはGit管理から外しています。

    ELBを追加

    スケールアウトの第一歩として、まずELB経由で通信する構成に変更します。
    グローバルIPアドレスは必要なくなるため、EC2はプライベートVPCに移動させます。

    このタイミングで、ELBの以下の機能を利用するように設定しました。

    AWSのSSL証明書設定(https化)

    AWSの無料SSL証明書が利用できるようになったため、サービスをhttps化しました。

    SSL Termination

    ELBのSSL Termination機能を利用することで、EC2内ではhttpのままで通信できます。
    ELBアクセス時にhttp、httpsのどちらかでアクセスされたかを判定するために、X-Forwarded-Proto環境変数を利用するため、wp-config.php に以下の記述を追加します。
    (wp-settings.phpの読み込み前に記述)

    if (!empty( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' ) {
        $_SERVER['HTTPS']='on';
    }
     
    /** Sets up WordPress vars and included files. */
    require_once(ABSPATH . 'wp-settings.php');
    

    MySQLをRDSに移行

    RDS for MySQLを作成し、EC2内のMySQLをRDSに移行します。

    MyISAMのテーブルが含まれているとRDSの機能が制限されますので、特に理由がなければInnoDBに変換しておいたほうが良いでしょう。

    uploadsディレクトリをS3に移行

    wp-content/uploadsディレクトリのコンテンツはS3に格納します。

    S3に格納するために必要なプラグインは以下の2つになります。

    Amazon Web Servicesプラグイン

    プラグインを追加後、wp-config.phpに以下の設定を追加します。

    define( 'DBI_AWS_ACCESS_KEY_ID', 'XXXXXXXXXXXXXXXXXXXX' );
    define( 'DBI_AWS_SECRET_ACCESS_KEY', 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' );
    

    WP Offload S3 Liteプラグイン

    Webサイトホスティングを有効にしたS3バケットを作成し、プラグインを追加、設定します。
    既存のuploadsディレクトリのコンテンツは、S3にアップしておく必要があります。
    aws s3コマンドでS3にコピーします。

    $ aws s3 sync ./wordpressディレクトリ名/wp-content/uploads s3://S3バケット名/wordpressディレクトリ名/wp-content/uploads
    

    DB側のupload情報も修正が必要になります。
    ここが一番面倒な作業になります。
    このブログの記事を参考にさせていただきました。

    wp_postmetaテーブルにデータ追加

    INSERT文をSQLで生成し、出力されたINSERT文を適用してデータを追加します。

    SELECT
      CONCAT(
        'INSERT INTO wp_postmeta(post_id, meta_key, meta_value) VALUES (',
        post_id,
        ', \'amazonS3_info\', \'a:3:{s:6:\"bucket\";s:13:\"S3バケット名\";s:3:\"key\";s:',
        length(meta_value) + 29,
        ':\"wordpress/wp-content/uploads/',
        meta_value,
        '\";s:6:\"region\";s:14:\"ap-northeast-1\";}\');'
      ) AS record
    FROM
      wordpress.wp_postmeta
    WHERE
      meta_key = '_wp_attached_file'
    ;
    

    ※length(meta_value) + 29の29という数字はuploadsディレクトリまでのパスの文字数です。
    例えば、wordpress/wp-content/uploads/ というパスであれば、パスの文字数が29になるので、29を加算します。

    データ追加が上手く行けば、管理画面の「メディア」メニューにアップロードしたデータのURLが、S3WebサイトホスティングのURLになります。

    wp_postテーブルのURL修正

    wp_postテーブルや、その他、アップロード画像をURL参照しているテーブルのURLも、S3のURLに変更します。
    この作業はバックアップも兼ねて、mysqldumpでテーブルデータをエクスポートしてから、エディタでURLを変更後、インポートし直すのが良いと思います。

    リリースシステムとの連携

    ここまで準備すれば、EC2をスケールアウト可能になります。
    複数台構成のEC2にリリースを行うために、リリースシステムからリリースするフローに変更しました。
    ランサーズには、専用のリリースシステムがあります。
    このリリースシステムに、WordPress系プロジェクトのリリース機能を追加します。
    リリースシステム上でgit pullして、ansible経由でWordPress全サーバーに同期します。
    WordPressサーバー側はGit管理する必要がなくなるため、このタイミングで.gitディレクトリを削除します。

    今後の課題

    ログの集約

    fluentd + S3プラグインでログをS3に集約します。
    ログサーバーにも転送し、全WordPressサーバーのアクセスログは、ログサーバーで参照するようにします。

    DBのスケールアウト

    HyperDBプラグインを導入すると、参照系と更新系を分離できるようになります。
    RDSのリードレプリカを作成し、参照系はHAProxy等で分散させるようにします。

    最後に

    実は、WordPressのEC2はまだ1台のままです。(笑
    スケールアウトの準備をした過程で、リソースがELB、EC2、RDS、S3に分散された結果、もう少しだけ耐えられそうな見込みになったためです。
    もう少しアクセスが増えたら、EC2をスケールアウトし始めることになると思います。
    今回は、その準備ができましたというお話でした。

    開発環境のDocker化 その後

    kanazawa|2016年12月19日
    DevOps

    インフラエンジニアの金澤です。

    開発環境をDocker化してから1年経ちましたので、その後のアップデートについて書きたいと思います。

    前提

    ランサーズでは、Dockerを開発を以下の目的で導入しました

    • PCリソース(HDD、メモリ)を削減
      • コンテナ単位でサーバーを構成し、VMを1つに統一
    • 本番環境のサーバー構成と極力互換性を保つ
      • 開発環境との差異による障害発生を未然に防ぐ

    そのため、Dockerのベストプラクティスではないことも行っております。
    例えば、以下のようなことも行っております。

    • 1コンテナに複数サービスを稼働
      • sshdも稼働
    • サービスをフォアグラウンドで稼働
      • /etc/init.dのスクリプトで稼働
    • 軽量化よりも利便性を優先
      • 便利なパッケージはインストール

    Amazon Linuxコンテナ

    2016年11月に、Amazon LinuxのコンテナイメージがDocker Hubから提供されました。

    今まで、本番AWSのEC2ではAmazon Linuxを利用していたのに対し、開発環境のコンテナはCentOS6を利用していました。
    パッケージバージョンの細かい差異が問題になることがありましたが、開発用コンテナもAmazon Linuxで構築することができるようになり、その問題がなくなりました。
    また、ランサーズでは、本番EC2と開発用コンテナに対し同じAnsibleのplaybookで構築していましたが、OS間の差異を吸収する処理がほとんどなくなりました。

    DockerfileのFROMに以下のように記述することでAmazon Linuxのコンテナイメージを利用することができます。

    FROM amazonlinux:2016.09
    

    ただし、EC2のAmazon Linuxと完全に同じではないので注意する必要があります。

    /etc/sysconfig/networkがない

    これが原因で、開発環境のDockerコンテナ構築時にAnsibleのPlaybookが失敗することがあります。
    また、ランサーズのDocker環境では/etc/init.d/の起動スクリプトでサービスを起動していますが、ここで/etc/sysconfig/networkを参照していることがあり、起動に失敗することがあります。
    そのため、Dockerfileで構築時に/etc/sysconfig/networkを配置する等の対策をしています。

    viがインストールされていない

    EC2のAmazon Linuxにインストールされていたパッケージで、Amazon Linuxコンテナにインストールされていないものがたくさんあります。
    viもその1つです。コンテナにviは必ずしも必要ありませんが、CentOS6コンテナにはviがインストールされていたので、それを利用していた方のために、viをインストールしています。

    docker-compose対応

    Docker導入段階でもdocker-composeは検討しておりましたが、コンテナ単位での細かい制御をしたいため、シェルスクリプトで運用していました。
    その中で、コンテナ起動後にipコマンドで固定IPを付与する処理も行ってましたが、最近のDockerでは固定IPで運用できるようになりましたので、それに合わせてdocker-composeも導入し始めました。

    docker-compose.ymlで、以下のように172.21.0.0/16のネットワークを新規に構築します。

    networks:
      lancers:
        driver: bridge
        ipam:
          driver: default
          config:
          - subnet: 172.21.0.0/16
            gateway: 172.21.0.1
    

    Appコンテナでは以下のように設定します。
    ipv4_addressで固定IPアドレスを設定し、hosts設定はextra_hostsに記述しています。
    コンテナのビルドは個人では行わず、構築済のコンテナをpullしています。
    registryコンテナ経由で5000番ポートで取得しています。

    services:
      app:
        image: localhost:5000/app:latest
        hostname: app
        networks:
          lancers:
            ipv4_address: 172.21.6.11
        extra_hosts:
           - "dev.lancers.jp:172.21.50.11"
           - "dev-img.lancers.jp:172.21.50.11"
    …
        container_name: app-6-11
        volumes:
          - ~/www:/var/www
    

    registryコンテナの起動は以下の通り。
    構築したコンテナは東京リージョンのS3に格納しているため、AWSのアクセスキーを指定して起動します。

    services:
      registry:
        image: registry:2.2
        container_name: registry
        ports:
          - 5000:5000
        environment:
          REGISTRY_STORAGE_S3_ACCESSKEY: XXXXXXXX
          REGISTRY_STORAGE_S3_SECRETKEY: xxxxxxxx
          REGISTRY_STORAGE_S3_BUCKET: docker-registory-lancers
          REGISTRY_STORAGE_S3_REGION: ap-northeast-1
          REGISTRY_STORAGE_S3_ROOTDIRECTORY: /
          REGISTRY_STORAGE: s3
    

    registryコンテナは、コンテナをpull、updateするときのみに利用するため、registry.ymlファイルとして個別に定義しています。
    registryコンテナを利用するときのみ、以下のコマンドで起動します。

    docker-compose -f registry.yml up -d
    

    ランサーズのDocker環境では、AWSのELBに相当するelbコンテナを用意しています。
    elbコンテナに80番、443番のポートフォワーディングを設定しており、ランサーズのAppコンテナを始め、各種サービスのコンテナはすべてelbコンテナ経由でアクセスしています。

    services:
      elb:
        image: localhost:5000/elb:latest
        hostname: elb
        networks:
          lancers:
            ipv4_address: 172.21.50.11
        extra_hosts:
           - "dev.lancers.jp:172.21.6.11"
           - "dev-img.lancers.jp:172.21.6.11"
    …
           - "dev-engineer.blog.lancers.jp:172.21.4.51"
    …
        container_name: elb-50-11
        ports:
          - 80:80
          - 443:443
    

    以上、概要を説明させていただきましたが、docker-composeについては、複数サービスへの対応や、記述の最適化等でもっと工夫できる余地がありますので、また機会がありましたら別途書かせていただきたいと思います。

    Docker for Mac(Windows)対応

    VirtualBoxを利用しない、ハイパーバイザー型のDocker for Mac(Windows)がリリースされましたので、こちらも利用を開始しました。

    運用面での変更点

    IPアドレス

    今までのDocker ToolboxでのDocker Machineと運用面で大きく変わるのは、DockerのIPアドレスです。
    Docker Toolboxのときは、デフォルトで作成されるVirtualBox VMのIPアドレスは、192.168.99.100になります。
    ローカルPC上のhostsにこのIPアドレスに対して、dev.lancers.jp等のドメインを記述していましたが、Docker for Mac(Windows)の場合は、このIPが127.0.0.1になります。

    ディレクトリ共有

    ソースをローカルPCで修正するために、Docker Mount + VirturlBox共有フォルダでソースを共有していましたが、Docker for Mac(Windows)では、VitrualBox共有フォルダがなくなりました。
    Docker Toolboxのときは、VirtualBoxの共有フォルダ設定で、個人PCの作業ディレクトリの違いを吸収できたのですが、Docker for Mac(windows)ではこれがなくなるため、docker run時のマウント設定で調整する必要があります。

    コンテナへのログイン

    ランサーズのDocker環境ではsshdを稼働させており、docker-toolboxでは

    docker-machine ssh xxxxx
    

    として、docker-machineのVMにログイン後、sshコマンドで各種コンテナに一般ユーザーでログインしていました。
    Docker for Mac(windows)では、docker-machine sshのログインプロセスがなく、ローカルPCから直接docker exec等でrootでログインするプロセスになります。

    Kitematicを利用するとGUIから手軽にdocker execができますので、これを利用するのも良いと思います。
    Kitematic

    ※ちなみに、Windows版KitematicでEXECを実行すると、Power Shellが起動します。私は、WindowsでDockerを操作するときはCygwin(Cygterm)を利用していたのですが、この環境だとdocker execが上手く動きません。そのため、Kitematicを利用していますが、ここでターミナルを選べないのが現状ちょっと辛いところです。

    ※sshを利用する方法として、どれかのコンテナの22番をポートマッピング設定し、それを踏み台にするという手は残っています。

    Docker for Mac(Windows)のメリット

    PC起動時のDocker設定が必要ない

    PC起動時にDocker for Mac(Windows)を自動起動する設定にしておけば、事前準備が必要なくなります。
    つまり、PCを起動する度に、以下のコマンドを打たなくても良くなります。

    <

    div class=”code panel pdl conf-macro output-block” data-hasbody=”true” data-macro-name=”code”>

    <

    div class=”codeContent panelContent pdl”>

    <

    div id=”highlighter_272017″ class=”syntaxhighlighter sh-midnight nogutter php”>

    docker-machine start xxxxx
    eval "$(docker-machine env xxxxx)"
    

    Docker for Mac(Windows)のデメリット

    複数のDocker VMの起動

    VitualBoxを利用していたときは、例えば、192.168.99.100、192.168.99.101と2つのVMを作成し、それぞれのVM上でDockerコンテナを稼働することが手軽にできましたが、それができなくなります。

    OS対応

    ハイパーバイザーをサポートしたOSでないと動作しません。
    Docker for MacはMacOS 10.10.3(Yosemite)以降の対応となります。
    Docker for WindowsはWindows10以降の対応となります。

    Linuxデスクトップ対応

    Linuxデスクトップを利用するエンジニアも増えたため、Linux環境にも対応しました。

    Linux環境でも、dockerとdocker-composeをインストールすれば、Mac、Windowsとほぼ同じように使えます。
    違う点は、デフォルトでローカルPCからDockerコンテナのIPアドレスで直接コンテナにアクセスすることができることです。

    Docker for Mac(Windows)のときと同様、VirtualBoxはないので、作業ディレクトリをDockerコンテナと共有する場合は、Docker Mountで動的に共有ディレクトリを調整する必要があります。

    アップデートの移行状況

    現在は、社内のアーリーアダプター中心に、順次アップデートを適用して頂いています。
    現状の環境でも特に支障なく開発ができていますので、アップデートできる方からゆるやかに移行が進んでいる段階です。
    今後も、互換性を保ちながら移行を進めていきたいと思います。