オープンソースこねこね

Webプログラミングなどについてあれこれ。

TravisCIでSSHクライアントのテストをする。

AltaxというPHPで並列SSHするデプロイツールを作っているのですが、 これのSSH接続部分のテストは当然SSHサーバが必要で、そのへんをTravisでどーやるのかわからず、ずっと悩んでいました。

で、調べていたら、

https://github.com/libgit2/libgit2/pull/1774

が検索に引っかかって、これを見ながらやってみたらうまくできたので、メモしておきます。

やり方

以下のようなシェルスクリプトを用意します。

setup_ssh_server.sh

#!/bin/sh 

sudo apt-get update -qq
sudo apt-get install -qq libssh2-1-dev openssh-client openssh-server

sudo start ssh

ssh-keygen -t rsa -f ~/.ssh/id_rsa -N "" -q
cat ~/.ssh/id_rsa.pub >>~/.ssh/authorized_keys
ssh-keyscan -t rsa localhost >>~/.ssh/known_hosts

export SSH_PRIVATE_KEY="$HOME/.ssh/id_rsa"
export SSH_PUBLIC_KEY="$HOME/.ssh/id_rsa.pub"

apt-getでSSHサーバ入れて起動して、ssh-keygenで鍵を作っているわけです。 あとはこれを.travis.ymlでbefore_scriptとかに設定してテスト前に実行するようにすれば、 SSHサーバがローカルホストで起動するので、SSH接続のテストができるようになります。

.travis.yml

language: php

php:
  - 5.3
  - 5.4
  - 5.5

before_script:
  - sh setup_ssh_server.sh

script:
  - php vendor/bin/phpunit

ヽ(=´▽`=)ノ

JenkinsをCentOSにインストールしたときのメモ

f:id:kohkimakimoto:20131120194039p:plain

Githubで公開しているプロジェクトのCIにはTravisが使えて便利なのですが、 仕事で開発しているWebアプリとかは公開するわけには行かなかったりするので、Travisは使えません。

そんなわけで、仕事でもCIの環境が欲しくなったので、今更ながらjenkinsをインストールしてみましたので、メモを残しておきます。

目的

PHPのWebアプリのユニットテスト

環境

ローカルのVirtualBox上に構成したCentOS6。

Jenkinsのインストール

Javaが必要なので先にyumとかで入れておきます

$ yum install java-1.7.0-openjdk

http://pkg.jenkins-ci.org/redhat/

redhat,centos用のRPMがあるのでそれをダウンロードしてインストールします。

$ rpm -ivh jenkins-1.540-1.1.noarch.rpm

実際はchefのレシピを用意してインストールしているので以下のようなrecipeを書いて実行しています。

jenkins/attributes/default.rb

default['jenkins']['rpm'] = "jenkins-1.540-1.1.noarch.rpm"
default['jenkins']['rpm_url'] = "http://pkg.jenkins-ci.org/redhat/jenkins-1.540-1.1.noarch.rpm"

jenkins/recipes/default.rb

remote_file '/tmp/' + node['jenkins']['rpm'] do
  source node['jenkins']['rpm_url']
  owner "root"
  group "root"
  mode "0755"
  not_if "test -e " + '/tmp/' + node['jenkins']['rpm']
end

package "jenkins" do
  action :install
  source '/tmp/' + node['jenkins']['rpm']
  provider Chef::Provider::Package::Rpm
  not_if "rpm -q jenkins"
end

service "jenkins" do
  action [:enable, :start]
end

インストールすると

/usr/lib/jenkins/jenkins.war

にjenkins本体のwarが

/var/lib/jenkins/

にデータディレクトリが作成されます。

WebUIにアクセス

http://xx.xx.xx.xx:8080/

仮想サーバの8080ポートにブラウザでアクセスするとjenkinsの画面が表示されます。

設定

ローカル環境なので、セキュリティとか気にしない。というわけで特に環境設定なし。デフォルトのまま。

ジョブ

ジョブはjenkinsに実行させたい処理を定義します。今回はリポジトリからPHPアプリのソースをとってきて、PHPUnitでテストを実行させたいと思います。まず、

  • 左メニューの[新規ジョブ作成]→[ジョブ名]に適当な名前をつけて[フリースタイル・プロジェクトのビルド]を選択して[OK]。

その後、最低限の設定だけします。

  • [ソースコード管理]は使っているVCSを選択します。お仕事ではsvnを使っているので[Subversion]。[リポジトリURL]を入力します。

  • [ビルド・トリガ]は[SCMをポーリング]を選択。スケジュールには1分ごとにチェックさせたいので以下のようにしました。

      */1 * * * *
    
  • [ビルド]の箇所では[ビルド手順の追加]→[シェルの実行]を選んで、svnからソースを落としてきたあとに行う実際のタスクを書きます。私の場合は以下のようになりました。

      /usr/local/bin/composer install --no-interaction
      php vendor/bin/phpunit -c phpunit-ci.xml.dist
    

なんでかJenkinsからの実行だと/usr/local/bin/にパスが通っていないので、グローバルインストールしてあるcomposerをフルパスで実行してます。 composer installのあとにphpunitを実行していますが、jenkins用のテストの設定(カバレッジをとるなど、後述します)を使いたいので-cオプションで専用のphpunitの設定ファイルを指定しています。

これで[保存]をクリックして完了。トップに戻って、時計アイコンをクリックして、試しに実行してみます。

カバレッジ

テストがうまくできたら、カバレッジもとりたくなりました。PHPカバレッジをとるには、Clover PHP pluginを使います。 左メニューの[Jenkinsの管理]→[プラグインの管理]から

  • Jenkins Clover PHP plugin

をインストール。Jenkinsを再起動。

ジョブの設定画面に行くと[ビルド後の処理の追加]に[Clover PHP カバレッジレポートを集計]という項目が増えているので、これをクリックして処理を追加します。

  • Clover XMLパス:reports/coverage/coverage.xml
  • Clover HTMLレポート ディレクトリ:reports/coverage

のように設定します。その後phpunit実行時に指定する設定ファイルphpunit-ci.xml.distに、以下のようなカバレッジを出力する設定を追加します。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true" berbose="true" bootstrap="test/bootstrap.php">

  ...いろいろ設定

  <logging>
    <log type="coverage-clover" target="reports/coverage/coverage.xml"/>
    <log type="coverage-html"   target="reports/coverage" charset="UTF-8" yui="true" highlight="true" lowUpperBound="35" highLowerBound="70"/>
  </logging>
</phpunit>

以上でOK。テストとともにカバレッジを出力してくれるようになります。

終わりに

というわけで開発作業中、ローカルPCに開発環境用とCI用の2つのVMを常時起動させて作業するようになりました。 なんでもできることはローカルで、というのが最近の作業スタイルになっています。 基本一人で作業している身なので、サーバが外にある必要がないんですよね、あんまし。

あと、Jenkinsのジョブの設定とかもWebからやらないでchefでやりたいのですが、このへんは今後調べようかと思っています。

Composerのドキュメントを日本語訳してみてる話

先週末からComposerのドキュメントを個人的に日本語訳してみてます。

英語むずいいぃぃぃ~~~~~!

はい。英語が苦手なんです。キライなんです。使えるようになりたくはあるのだけど、本腰入れる気合もなくて、なんというかだらだら勉強してる。 とりあえず、通勤時間に電車で単語帳見たり、iPhoneに英語教材のオーディオブック入れて流し聞いたりしてます。 で、そんな英語学習のひとつとして、ちょっとプログラミングに関連させてドキュメントの和訳をやってみてます。

正直、続くかわからんけど。

ひとまず、IntroductionとBasic Usageのとこだけ和訳してみたので、そのことをブログに書いてみました。

なお、一応補足しておくと、ComposerはPHPの依存管理ツールです。RubyのBundlerみたいなやつですね。

Grunt

ところで英語の話とは別に、最近買ったWebDBPressにGruntの話があって、 これをちょっと使ってみたいのもあって、HTMLのビルド周りをGruntで自動化してみました。

GruntはJavaScriptのタスクランナーとのこと。要はmakeのJavaScript版みたいな感じだと思う。 特徴的なのがプラグインでタスクを簡単に追加、拡張できて、Webフロントエンド開発に便利なタスクが最初からいっぱい用意されていること。 今回使ったのは以下のプラグイン

  • grunt-contrib-watch: 作業中のファイル更新を監視して、タスクを自動実行させる。
  • grunt-contrib-connect: 作業中の確認用HTTPサーバ。livereloadサーバも立ててブラウザの自動再読み込みもできる。
  • grunt-contrib-cssmin: CSSのミニファイ。
  • grunt-markdown: MarkdownをHTMLに変換。
  • grunt-contrib-copy: ファイルのコピー。
  • grunt-contrib-clean: ファイルの削除。
  • grunt-contrib-uglify: JavaScriptのミニファイ。

ちょっとした静的サイトジェネレータのようなものが構築できてしまって、大変便利なのでした。( ´∀`)

TravisCIでGithubPagesを自動更新

ついでにGithubのリポジトリにソース一式Pushしたら、自動でGithubPagesをジェネレートしたサイトで更新するようにしてみました。以下が参考サイト。

参考サイトでやってることほとんどそのまんまです。 travisでgruntを実行しサイトをジェネレートして、生成物をgh-pagesブランチにコミット、Pushする。

こりゃ便利だわ~( ´∀`)。

後になって気づいたこと

getcomposer.org_doc_jpというリポジトリ名だけどgetcomposer.org_doc_jaのほうが良かったんじゃないかと。 でもtravisの設定とかやり直したくないので、このままにします。。。

あと、えいごガンバル。

PHPのArrayを少し便利に扱えるクラスを書いた。

大したコード量じゃないので毎回コピペして使ってたコードなのですが、せっかくなのでテスト書いて、TravisCIとCoverallsでテストして、Packagistに登録してみました。

https://github.com/kohkimakimoto/EArray

EArray

PHPで配列にアクセスするとき存在しないキーで値を取得しようとすると、警告がでます。

<?php
$arr = array("key_a" => "aaa");
echo $arr["key_b"];
// PHP Notice:  Undefined index: key_b ...

で、これを避けるために事前にキーの存在をチェックする処理をいれたりするんですが、いちいちこのコードを書くのが地味に面倒なのですよね。

$arr = array("key_a" => "aaa");
echo isset($arr["key_b"]) ? $arr["key_b"] : null;

これ、多次元配列だと更に面倒。

@(アットマーク)を配列の前につけてエラー出力しないように制御する方法もありますが、単に警告を無視してることになって、どうにも「その対応は違うだろ」って気持ちになってやりたくないですし。

そんなわけで、前述のEArrayというクラスを書きました。使い方は対象の配列をコンストラクタで登録してgetメソッドを使ってアクセスする。キーが存在しない場合、デフォルトでnullを戻します。

use Kohkimakimoto\EArray\EArray;

$earray = new EArray(array("foo" => "bar"));
$earray->get("foo");
$earray->get("foo2", "default");  // 第二引数でキーが存在しない時に戻す値を指定できる。

多次元配列の場合は、スラッシュ区切りの記法でスマートにアクセスできます。

$earray = new EArray(
    array(
        "foo" => array(
            "foo2" => array(
                "foo3",
                "foo4",
                ),
            "foo2-1" => "foo5",
            ),
        "bar",
        "hoge",
        )
);

$earray->get("foo/foo2-1");             # "foo5"を戻す。
$earray->get("foo.foo2-1", null, ".");  # キーにスラッシュが含まれている場合は第三引数で区切り文字を指定すればいい。

なお、ArrayAccessやIteratorインターフェースを実装しているので、普通の配列としてもオペレーションできます。 他にもREADMEにサンプルコードをいくつか書いておいたので参考にしてください。

https://github.com/kohkimakimoto/EArray/blob/master/README.md

インストール

composerを使います。composer.jsonに以下の行を追加してcomposer installすればいいと思います。

"kohkimakimoto/earray": "1.1.*"

まあ、クラスファイル一個だけなので、ちょっと使いたい人は以下のコードをコピペしてネームスペース定義を変えるとかしてもいいかもしれません。

https://raw.github.com/kohkimakimoto/EArray/master/src/Kohkimakimoto/EArray/EArray.php

ちなみにEArrayのEはExtendedの意味です。

デプロイツールAltaxをバージョン2に更新しました

Capistrano的なPHP製デプロイツールAltaxをバージョン1→2としてアップデートしました。

https://github.com/kohkimakimoto/altax

このソフトウェアはPHPで記述したタスクを、複数のリモートサーバに対して実行します。 サーバ接続には鍵認証のSSHを使用し、並列に実行します。 動作に必要なファイルは1つだけで、PHPの実行環境があれば簡単に導入できます。

Version1からの変更点

  • 実装をSymfony Componentsを使って大幅に変更、再構成。
  • pharによる実行ファイル形式の配布をサポート。
  • デフォルトの読み込み設定ファイルのパスを変更。
  • クラスベースのタスク定義をサポート。
  • ログ出力機能を追加。
  • SSH接続の実装部分をsshコマンドからphpseclibに変更。

インストール

ドキュメントはまだ更新してないので、ざっくり最初の使い方だけ記述しておきます。実行にはPHP5.3以上が必要です。またWindowsでは多分動きません。

インストールは以下のコマンドを実行するだけです。

$ curl https://raw.github.com/kohkimakimoto/altax/master/installer.sh | sudo bash -s system v2

/usr/local/bin/altaxに実行ファイルがインストールされます。 手動で好きな場所にインストールしたい場合は、以下のように直接altax.pharを持ってきて配置すればいいでしょう。

$ wget https://github.com/kohkimakimoto/altax/raw/master/altax.phar
$ chmod 755 altax.phar
$ mv altax.phar /path/to/install_dir/altax

とりあえず実行する

なにも付けずにコマンドを叩くと、バージョン情報やサブコマンド一覧が表示されます

$ altax 

設定ファイルを作成する

$ altax init

initコマンドを実行するとカレントディレクトリに以下のような構造の.altaxディレクトリを作成します。

.altax/
    config.php              # メインの設定ファイル。
    hosts.php               # 接続先ホストを記述する用のファイル
    tasks/                  # タスク定義を置くディレクトリ
        hellow.php.sample   # サンプルのタスク定義。

必須なのはconfig.phpだけで、その他のファイルは必要に応じてconfig.phpから読み込んで使用します(今回は使いません)。 config.phpはコメントアウトされているところを除くと以下のようになっています。

host('127.0.0.1', array('web', 'localhost'));

desc('This is a sample task.');
task('sample',array('roles' => 'web'), function($host, $args){

  run('echo Hellow World!');

});

host関数で接続先ホストの設定、taskで実行タスクを定義しています。このサンプルではローカルホストに接続しにいきます。 接続先ホストはパスワードなしの鍵認証でSSHできるようにしておく必要があります。

再びaltaxコマンドをサブコマンドなしで実行します。

$ altax 

Available commands:
  config   Show configurations
  help     Displays help for a command
  init     Create default configuration directory in the current directory
  list     Lists commands
  sample   This is a sample task.

Available commandsにsampleタスクが表示されます。実行してみましょう。

$ altax sample
Altax version 2.1.0
Starting altax process
  - Starting task sample
    Found 1 target hosts: 127.0.0.1
    - Running sample at 127.0.0.1
Hellow World!
    Completed task sample

以上のようにHellow World!がechoコマンドで実行されます。

詳細な使い方について

今回はここまでです。詳細は後日、ドキュメントかブログにまとめようと思っています。

メモ) Symfony Componentsが激しく便利な件

今回コマンドラインのプログラムを書き直したわけですが、その際に利用したSymfony Componentsが激しく便利でした。

あたりを使ったわけですが、 今までコマンドラインオプションをパースする処理をgetopt関数使ってシコシコ書いていたりしたのが、すんごく簡単に書けるようになります。 出力メッセージに色つけたり、ディレクトリからファイル一覧を持ってきたりとかもすんごく楽。

みんなも使ったほうがいいですヽ(^o^)丿。

メモ) だれか知ってたら教えて

SSHでコマンド実行が正常にできることのテストをtravis-ciでやるのってどうすんだと。 わからん。とりあえず、このあとCapistranoのテストコード見に行ってきますよ、と。

PHP Webアプリケーションから非同期にコマンドを実行するライブラリをつくりました。

こんなの作りました。

BackgroundProcess

Webのプロセスから重い処理を実行したいのだけど。。。

PHPCSVファイルなどから一括で大量のデータ投入を行いたいときがあります。 これをWebアプリケーションのプロセスでやろうとすると、処理時間が長いため、だいたいプロセスがタイムアウトしてエラーになってしまいます。困ったものです。

こういうとき、代わりにコマンドラインのプログラムとして実装したりするんですが、任意のタイミングで処理したい場合、コマンド実行する以上サーバにターミナルでログインできる技術者しかデータ投入作業ができなくなってしまいます。 もっとだれにでもできるように、やっぱりWebアプリケーションのインターフェースが欲しくなるのが人情です。

Webアプリから非同期にコマンドを実行する

この要件を満たす簡単な方法は、以下のようなコードでWebプロセスから外部コマンドをバックグラウンドで実行することです。

exec("php yourcommand.php > /dev/null &");

こうすることで実行コマンドは非同期にバックグラウンドで動作し、Webのプロセスはコマンドの処理内容にかかわらず、即時レスポンスを返すことができます。

ただこれだと、以下のような不満があります。

  • 外部コマンドのエラー処理が考えられていない。コマンドごとにそれぞれ内部で何らかの実装をする必要がある。
  • 実行開始したコマンドのプロセスをWebプロセス側から関与することができない。

こういうことをうまく扱うために、ジョブキューエンジンや、DBなどを使ったタスクやプロセスを管理する仕組みはすでにあると思います。 でもそこまで手間をかけたり、構成を複雑にしたくない。

ちょっとだけ発展させてみました

というわけで作ったのが、

BackgroundProcess

インストールはComposerで以下のようにcomposer.jsonを作成して

{
  "require": {
    "kohkimakimoto/background-process": "1.1.*"
  }
}

インストールコマンドを実行。

$ curl -s http://getcomposer.org/installer | php
$ php composer.phar install

使い方は、実行したいコマンドをコンストラクタに指定してオブジェクトを生成した後runメソッドを実行するだけです。

use Kohkimakimoto\BackgroundProcess\BackgroundProcess;

// Creates instance and set command string to run at the background.
$process = new BackgroundProcess("ls -l > /tmp/test.txt");
// Runs command, and it returns immediately.
$process->run();

// Get key identified the process.
$key = $process->key();

$process->key()でプロセスを識別する一意キーが取得できます。 これはあとで実行したコマンドが実行中であるかなどを確認するのに以下のように使います。

use Kohkimakimoto\BackgroundProcess\BackgroundProcess;

$manager = new BackgroundProcessManager();
$process = $manager->loadProcess($key);

// If a process specified by the key dosen't exist, loadProcess method returns null.
if (!$process) {
  echo "Not working process $key";
} else {
  $meta = $process->getMeta();
  echo $meta['created_at'];   // (ex 2013-01-01 10:00:20
  echo $meta['pid'];          // (ex 1234
}

また、コマンドがエラーになった場合デフォルトで下のパスのログファイルにエラーログ(標準エラー出力)を出力します。

/tmp/php/background_process/err.log

内部の作り

前述したように、手軽に使えるようにしたかったのでDBなどは使っていません。 BackgroundProcessはrunメソッドを実行すると、デフォルトで/tmp/php/background_process/配下にコマンドごとに、PHPファイルとJSONファイルを出力します。 PHPファイルはコンストラクタで指定したコマンドの実行を行うスクリプトで、JSONファイルはプロセスIDなどのメタデータを保持しています。 これらのファイルを利用してコマンド実行やエラー処理、プロセスIDの取得などを行っています。 コマンドが正常終了すると、これらのファイルは自動削除されます。

シーケンス図にすると以下のようになります。

20130812192611

最近のPHPWebアプリケーション開発環境

というタイトルでスライドを作りました。

http://kohkimakimoto.github.io/MySlide/slides/phpenvironment/index.html

内容はともかく、スライド作成のために利用したreveal.jsがすごくいいです。HTMLベースのかっこいいスライドを簡単に作れます。スライドの内容は標準でMarkdownで書けますし。やっぱMarkdownでしょまーくだうん。 そして、これを最近なんやかんやでちょくちょく触っていたJekyll上で動作確認し、GithubPagesでホスティングする。 これもいい感じです。

Githubリポジトリ

https://github.com/kohkimakimoto/MySlide

スライドの内容

  • VirtualBox
  • Chef
  • Composer
  • LibMigration
  • Altax
  • Ansible
  • Travis CI
  • coveralls
  • その他いろいろ