page_adsence

2016年7月4日月曜日

IntelliJを使ってGo言語の開発環境を整えてみる for Windows

会社のPCは現在Windowsなので、Windows上でGo言語の開発環境を作ってみた。
OS:Windows7
IntelliJ:15.0.6
今回行う事は大きく2つです。
1. Windows上へのGoのインストール
2. IntelliJ側のプラグインのインストールと設定
では早速Goのインストールから始めようと思います。

Go SDKのインストール

  1. 下記のサイトからGoのインストーラー「go1.6.2.windows-amd64.msi」をダウンロードしてきます。
    https://golang.org/dl/
  2. インストーラーを起動し、インストールを開始します。
    インストーラーはデフォルトだとインストール先がC:\Goになっているので、
    別のディレクトリに入れたい場合は、任意のディレクトリを指定して下さい。
  3. インストールが完了したら、コマンドプロンプトを起動し、goのバージョンを確認します。
    > go version
    バージョンの確認が出来たらプロンプトは閉じても問題ありません。
    ※この時点でPathやGOROOT等の環境変数は自動で設定されています。
これでGoのインストール作業は完了です。
では引き続きIntelliJ側の設定を行っていきます。

IntelliJのPluginインストール

  1. IntelliJを起動します。
  2. 「Welcome to IntelliJ IDEA」の画面右下の「configure」→「Plugins」をクリック
  3. 画面下側の「Browse repositories…」をクリック。
  4. 画面上部の検索窓に「GO」と入力し、「GO」と書いてあるものをインストールします。
  5. プラグインのインストールが完了すると、再起動を求められるので、IntelliJを再起動します。

  6. 再起動したら、「Welcome to IntelliJ IDEA」画面で「Create New Project」をクリック。
  7. 画面左側のリストにGoがあるので、GoをクリックしてNextをクリック
  8. Procjet SDKの画面で「Configure...」をクリック
  9. Goをインストールしたディレクトリを指定し、OKを押下、Project SDKにGoと出てれば、Nextボタンを押下する。
    (Go言語をインストール時に何も変更指定なければ、C:\Goにインストールされているので、そのままOKを押せば問題ない)

  10. Procjet NameとProject locationを任意の場所にしてFinishをクリック
  11. 開いたプロジェクトでGo Fileを追加して、Hello World的なのを書いてみる。
    package main
    
    import "fmt"
    
    func main(){
     fmt.Printf("Hello World!")
    }
  12. 書いたら実行(メニューバーのRun -> Run ‘go run XXXXX.go’)
  13. コンソールにHello World!と出れば成功!
古い情報とかだとjarファイル落としてこないといけないとか書かれていますが、
基本的にはGoをインストールとIntelliJの画面内の操作で完結するようです。

おまけ

最初はIntelliJのバージョンが15.0.4のものを使っていたのですが、
なぜかGo SDKのディレクトリを指定しても、下記の様なエラーが出てしまって使えませんでした。
「The Selected directory is not a valid home for Go SDK」
IntelliJのバージョンと、Pluginのバージョンが合っていないのかとも思ったのですが、
PluginのREADMEを確認する限りでは問題ありませんでした。
go-lang-idea-plugin
取り敢えずIntelliJの最新バージョンが出てたのでアップデートしてみたら使えるようにはなったのですが、
これで根本原因が解決出来たのかは不明です・・・。

2016年6月29日水曜日

Nginxのstable版とmainline版に関して

開発環境でNginxの1.9を利用していたのですが、インフラ屋さんに1.10と1.11が出てますけど、どっちにしますかと言われたので調べてみた。
yumでインストールしたNginxのバージョンが1.9だったので、何も考えずに1.9を使っていたのですが、どうやらNginxはナンバリング方法がちょっと特殊っぽい。

Nginxのバージョン1.10と1.11のリリースノート

図を見てもらうだけで大体理解出来ると思いますが、
現在Nginxでは「mainline版」と「stable版」の2つをリリースしている。

mainline版(1.11が最新)

新機能の追加、仕様変更、バグ修正等が行われる。

stable版(1.10が最新)

mainline版からフォークされたもので、mainline版で行った重要なバグ修正のみをstable版にマージするようになっている。

といった形になっています。
記事を軽く読んだ限りですと、バグ修正もmainlineに先に取り込まれるので、mainlineを使った方が安定するようです。
なのでインフラ屋さんにはmainline版である1.11を使ってもらうようにお願いしました。
取り敢えずNginx使おうと思って始めたので、バージョンをあまり気にしていなかったのですが、きちんと確認するべきですね・・・。

2016年6月15日水曜日

docker-composeのextra_hosts

docker-composeのリファレンスを読んでいたら、extra_hostsなるオプションがあった。
自分が初めてDocker使った時にあったのかどうかは調べてないのですが、こんな便利なオプションを見逃していたなんて・・・。
dockerコンテナは基本的に起動時にIPを動的に割り当てているため、毎回コンテナ上のhostsファイルが書き換わってしまい、独自の設定を入れる方法がなかった。
力技でやる場合は、ENTRYPOINTとかのシェルスクリプト内でhosts書き換えたりする程度しか思いついていなかった。

早速使ってみた。
※ローカルにphpやmysqlという名前のイメージがある状態で行っています。
適当なディレクトリにdocker-compose.ymlを作って、下記の内容を記述します。
$ vi docker-compose.yml
mysql:
  image: mysql
  container_name: mysql
  hostname: mysqldb
  ports:
    - 3306:3306

php:
  image: php
  container_name: php
  hostname: php
  extra_hosts:
    - test1.com:111.111.111.111
    - test2.com:222.222.222.222
  links:
    - mysql
この状態で
$ docker-compose up -d
してから、コンテナ内に入る。
$ docker exec -it php bash
で、hostsファイルを確認する。
# view /etc/hosts
172.17.2.103    php
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.2.95       mysql mysqldb
172.17.2.95       mysql_1 mysqldb mysql
111.111.111.111   test1.com
222.222.222.222   test2.com
こんな感じで、記載される様になります。
便利!

※但し、大量のhostsを記載する場合はdocker-compose.ymlのファイル内が長くなってしまうので、
その辺はある程度長くなってしまったら力技の方がいいのかもしれないです。
extra_hostsもファイル指定出来ればいいんですけどね。

2016年5月25日水曜日

PHPのバージョンアップに伴うPHP関連の調査メモ

PHPを5.2系から5.6系にバージョンアップするにあたって、諸々調査してみました。
  • php.ini関連(主にAPC- > APCu + OPCacheへの移行に関して)
  • php-fpmの設定値に関して
  • mysqlndの利用可・不可

php.ini関連(主にAPC- > APCu + OPCacheへの移行に関して)

APCはPHP 5.5以降では利用できなくなってしまったため、APCu + OPCacheに乗り換える必要があります。
乗り換えるにあったって、諸々調査したのでその結果を残しておこうと思います。

APC(Alternative PHP Cache)

まずAPCとは「Alternative PHP Cache (APC) は、PHP の実行コードをキャッシュする仕組みで、 フリーかつオープンに使用できます。PHP の中間コードのキャッシュ・最適化を行うための、フリーでオープンかつ堅牢なフレームワークを提供するということを目標としています。」(PHPの公式から抜粋)
と書かれていますが、実際は中間コードのキャッシュだけではなく、インメモリ型のKVSとして利用することも可能です。
※但しネットワーク越しのアクセスは不可

APCu

APCu is APC stripped of opcode caching.
→ APCuはAPCのオペコードキャッシュを除いたものです。

The first APCu codebase was versioned 4.0.0, it was forked from the head of the APC master branch at the time.
→ APCuの最初のコードはバージョン4.0.0で、APCのマスターブランチをフォークして作成されています。

PHP 7 support is available as of APCu 5.0.0.
→ PHP 7はAPCu5.0.0が利用出来る様になっています。

APCu can provide a compatibility mode, such that it can provide a drop in replacement for the applicable parts of APC.
→ APCuは互換性モードを提供しており、APCの一部の機能の下位互換を持っています。

公式から抜粋して訳してみたのですが、合っている保証はありません。
要するにインメモリ型のKVSの機能のみを提供するもので、APCからフォークされて出来ているみたいです。

OPCache(Zend OPCache)

OPCacheは「OPcache はコンパイル済みのバイトコードを共有メモリに保存し、PHP がリクエストのたびにスクリプトを読み込み、パースする手間を省くことでパフォーマンスを向上させます。」
と記載されている通りの機能です。
1点問題になるのは、OPCacheはシンボリックリンクを解決した状態でキャッシュされるため、
シンボリックリンクを変更しても、キャッシュは更新されないという問題がある。

http://kohkimakimoto.hatenablog.com/entry/2014/09/13/154342

また、opcache_resetもCLIから実行するとCLI側のオペコードキャッシュしか消えないようです。
そしてWebから実行されたopcache_resetはWeb側もCLI側も消えるという・・・。
この辺は自分でも検証してみようと思います。
結果は後日追記する予定です。

http://tech.gmo-media.jp/post/136590171029/php-53-to-php-56

php-fpm(FastCGI Process Manager)の設定値に関して

「PHP の FastCGI 実装のひとつで、 主に高負荷のサイトで有用な追加機能を用意しています。」 各サーバのスペックに応じてチューニングする必要のある部分は下記の所になります。(下記はデフォルト値です)
項目名設定値
pm.max_children50
pm.start_servers5
pm.min_spare_servers5
pm.max_spare_servers35
pm.max_requestsコメントアウト

この辺の値を変更する必要があるのですが、一律で変更出来るものではないので、
各自負荷テストを行った結果を反映させる必要があります。

mysqlndの利用可・不可

mysqlnd「MySQL Native Driver」とは「MySQL Client Library」に変わる新たなライブラリです。
mysqlndにしかない機能も多数あるので、移行出来るものならしたいものですが、そうはいかない世の中でした。
php5.5以降からmysqlndがデフォルトで利用される様になったのですが、mysqlndで16バイト長のパスワードが設定されているMySQL Serverに接続しにいくと、エラーが出てしまい接続出来ないという現象が発生します。
現状稼働しているMySQLに接続しにいかないといけないとかがあると、一手間必要になってしまいます。
どういった事が必要になるかというと、mysqlndでは41バイト長のパスワードハッシュでないと接続出来ないので、
バージョンの古いMySQLのバージョンだと、16バイト長のパスワードハッシュになっているので、それを41バイトハッシュに書き換えてやる必要があります。
影響範囲の洗い出しが簡単で、DB停止とかが出来るのであれば、変更可能だとは思いますが、そうでない場合は簡単には踏み切れないですね・・・。
PHP5.2(libmysql)とPHP5.6(mysqlnd)で、それぞれ16バイトと41バイトのパスワードが設定されている時にどういった挙動になるかを表にしてみました。
PHP5.2.17(libmysql)PHP5.6.18(mysqlnd)
MySQL5.1 [16byte Password]OKNG(mysqlnd cannot connect)
MySQL5.6 [16byte Password]OKNG(パスワード不一致)
MySQL5.1 [41byte Password]OKOK
MySQL5.6 [41byte Password]OKOK

基本的にはパスワードさえ41バイト長のものに変えてしまえばログイン出来るのですが、
注意点としては、MySQL4.0以前のクライアントが残っていると41バイト長のパスワードは受け付けられないので、ログインできなくなります。(下記の様なエラーが出るみたいです)
> mysql -h localhost -u root
Client does not support authentication protocol requested
by server; consider upgrading MySQL client
とはいえ、MySQL5.7では16バイト長のパスワード(OLD_PASSWORD)のサポートがなくなるみたいですので、早めに移行するに越したことはないですね。
既存のデータベースに引っ張られない様なシステムの構築する場合は、積極的に使っていきたいと思います。

現状利用しているphpのモジュール一覧を取得する。

phpのモジュール一覧を確認しようと思ったのですが、オプション忘れたので調べた。

言わずと知れたphpinfoのコマンド版
php -i
今回知りたかったロードしているモジュール一覧
php -m

phpの公式にコメント付きで書いてあるので、詳細はそちらで。
http://php.net/manual/ja/features.commandline.options.php

2016年4月2日土曜日

Dockerを利用してSeleniumのテスト環境を作る&WebDriverを使ってみる。

お手軽にSeleniumのテスト環境を作成する。

前提として
・CentOS7
・Docker1.8
・docker-compose1.5.2
・PHP
・composer
が導入済みの状態。

docker-compose.ymlには以下のように記載する。
selenium_chromeやselenium_firefoxで指定しているポート番号は任意で決めてもらってOK。
selenium_hub:
  image: docker.io/selenium/hub
  container_name: selenium-hub
  ports:
    - 4444:4444

selenium_chrome:
  image: docker.io/selenium/node-chrome-debug
  container_name: selenium-chrome
  ports:
    - 49154:5900
  links:
    - selenium_hub:hub

selenium_firefox:
  image: docker.io/selenium/node-firefox-debug
  container_name: selenium-firefox
  ports:
    - 49155:5900
  links:
    - selenium_hub:hub

コンテナを起動させる。
$ docker-compose up -d
こんな感じの状態になっていればOK
$ docker-compose ps
      Name                 Command           State            Ports
----------------------------------------------------------------------------
selenium-chrome      /opt/bin/entry_point.sh   Up      0.0.0.0:49154->5900/tcp
selenium-firefox       /opt/bin/entry_point.sh   Up      0.0.0.0:49155->5900/tcp
selenium-hub            /opt/bin/entry_point.sh   Up      0.0.0.0:4444->4444/tcp

で、composer.jsonは下記の様に記載して保存。
$ vi composer.json
{
    "require": {
        "phpunit/phpunit": "5.1.*",
        "phpunit/phpunit-selenium": "dev-master",
        "facebook/webdriver":"dev-master"
    }
}
composerコマンドで、上記のパッケージをインストールする。
$ composer install
とりあえずFirefoxを立ち上げて、自分のブログのページを表示させてみるスクリプトを書く。
$ vi test.php
<?php
require_once 'vendor/autoload.php';

$host   = 'http://VMのIP:4444/wd/hub';
$driver = RemoteWebDriver::create($host, DesiredCapabilities::firefox());
$driver->get('http://gyagya1111.blogspot.jp/');
で、実行する。
$ php test.php
VNCクライアントとかを使って、VMのIP:49155にアクセスすると、動いているのが見える。

2016年3月31日木曜日

composerに関して

案件でcomposer使っていて、軽い障害になりかけた時に記憶が曖昧で自信がなかったのでメモしておく。
composer install
composer.lockファイルを元にvendor配下にソースをインストールする。
composer.lockファイルが存在していない場合は、composer.jsonを元に最新バージョンをインストールする。
composer update
composer.jsonを元に最新のソースをインストールする。
バージョンアップが発生したものに関しては、composer.lockファイルの中身が更新される。
引数でパッケージ名を指定してやると、特定のパッケージのみ更新される。

2016年2月26日金曜日

Dockerfile作成時のTIPS

基本的にはコンテナの中で一通り作業をして、それをDockerfileに記述するというスタイルで作っているのですが、
一発でDockerfileが完成することなんて無いので、なるべく効率よく作れるように下記の事を心掛けています。

1.ステップは細かく分ける。

 最終的なDockerfileはステップ数をなるべく減らす必要があるが、
 Dockerfileを作成している時にはなるべく作業単位を細かくした方が効率がいい。
 例えば、インストールに時間の掛かる処理の後ろで、処理の軽い内容をやるとする。
 長い時間掛けてインストールが終わったのに、処理の軽い方で失敗した時には、
 インストールに掛かった時間がまるごと無駄になってしまう。
 時間の掛かる処理ほど細かく分けた方が、Dockerfileを書く時には効率よく書ける。

2.キャッシュをうまく使う。

 例えばDockerfileの最初でyumなりaptなりでインストール作業を行っていたとして、
 途中別の何かをインストールする際に、追加で入れないといけないミドルウェアが出てきた場合、
 最初にインストールしているところに追記するのではなく、必要になる処理の直前で追加してやる。
 こうすることにより、途中まで作成されたキャッシュイメージを無駄にせずに済む。

2016年2月24日水曜日

Hubotをインストール

以前nodebrewを使ってnode.jsをインストールしたので、今回はhubotのインストールを行う。
$ npm install -g yo generator-hubot coffee-script
すごい色々出てくるので略
インストール出来たか確認する。
$ npm list -g generator-hubot yo coffee-script
/home/vagrant/.nodebrew/node/v5.7.0/lib
├── coffee-script@1.10.0
├── generator-hubot@0.3.1
└── yo@1.6.0
こんな感じの一覧が出てくればインストールは完了。

hubotを配置するためのディレクトリを作成。
mkdir mybot && cd mybot
自分用のhubotを作成する。
幾つか質問されるが、書いてある内容はこんな感じ。
$ yo hubot
? ==========================================================================
We're constantly looking for ways to make yo better!
May we anonymously report usage statistics to improve the tool over time?
More info: https://github.com/yeoman/insight & http://yeoman.io
========================================================================== 
→ yoの品質向上のために、使用統計や時間を匿名で報告するかもしれませんがいいですか?
Yes or No

? Owner (User )
→ 所有者は誰ですか?
自分の名前とかメールアドレスを書く

? Bot name (sanbot)
→ 作成するボットの名前
作成したディレクトリ名がデフォルトで入る様になっているので、変更する場合だけ入力する。

? Description (A simple helpful robot for your Company)
→ 概要説明
特にないので、空のままでEnter

? Bot adapter (campfire)
→ 作成したボットをどこで使うか
今回はslackを使うので、slackと入力する。
以上の項目を入力すると、ボットが作成される。
以下は出力されたログ。
$ yo hubot
? ==========================================================================
We're constantly looking for ways to make yo better!
May we anonymously report usage statistics to improve the tool over time?
More info: https://github.com/yeoman/insight & http://yeoman.io
========================================================================== Yes
                     _____________________________
                    /                             \
   //\              |      Extracting input for    |
  ////\    _____    |   self-replication process   |
 //////\  /_____\   \                             /
 ======= |[^_/\_]|   /----------------------------
  |   | _|___@@__|__
  +===+/  ///     \_\
   | |_\ /// HUBOT/\\
   |___/\//      /  \\
         \      /   +---+
          \____/    |   |
           | //|    +===+
            \//      |xx|

? Owner XXXXXXXXXXXXXX@XXXXXXXX.com
? Bot name test-bot
? Description A simple helpful robot for your Company
? Bot adapter (campfire) slackgot back false
? Bot adapter slack
   create bin/hubot
   create bin/hubot.cmd
   create Procfile
   create README.md
   create external-scripts.json
   create hubot-scripts.json
   create .gitignore
   create package.json
   create scripts/example.coffee
   create .editorconfig
                     _____________________________
 _____              /                             \
 \    \             |   Self-replication process   |
 |    |    _____    |          complete...         |
 |__\\|   /_____\   \     Good luck with that.    /
   |//+  |[^_/\_]|   /----------------------------
  |   | _|___@@__|__
  +===+/  ///     \_\
   | |_\ /// HUBOT/\\
   |___/\//      /  \\
         \      /   +---+
          \____/    |   |
           | //|    +===+
            \//      |xx|


色々出てくるので略
最後の方にツリー構造みたいなのが出てくる。
エラーが出来なければインストール完了。 botを起動してみる。
$ bin/hubot -a shell -n sanbot
   ~ 略 ~
sanbot> [Wed Feb 24 2016 07:14:55 GMT+0000 (GMT)] ERROR hubot-heroku-alive included, but missing HUBOT_HEROKU_KEEPALIVE_URL. `heroku config:set HUBOT_HEROKU_KEEPALIVE_URL=$(heroku apps:info -s  | grep web-url | cut -d= -f2)`
[Wed Feb 24 2016 07:14:55 GMT+0000 (GMT)] INFO hubot-redis-brain: Using default redis on localhost:6379
ここまで出たらEnterキーを押す。 そうすると、入力待ちの状態になるので、任意のコマンドを入力してみる。
sanbot> @sanbot ping
sanbot> PONG
ボットから返事が返って来たら完了!
終了は
sanbot> exit
で終了出来ます。

nodebrewを使ってnode.jsをインストール

最初はnvmを使っていたのですが、nodebrewの方が使いやすいらしいのでnodebrewをインストールしてみる。
CentOS7を使っている。
インストール自体はすごい簡単。
下記コマンドを実行して、パスを通すだけ。
$ curl -L git.io/nodebrew | perl - setup
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0
  0     0    0     0    0     0      0      0 --:--:--  0:00:03 --:--:--     0
100 23754  100 23754    0     0   6116      0  0:00:03  0:00:03 --:--:--  6116
Fetching nodebrew...
Installed nodebrew in $HOME/.nodebrew

========================================
Export a path to nodebrew:

export PATH=$HOME/.nodebrew/current/bin:$PATH
========================================
で、カレントユーザーの.bash_profileに追加
$ vi ~/.bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

# User specific environment and startup programs
PATH=$PATH:$HOME/.local/bin:$HOME/bin

# for nodebrew                          ← この行を追加
PATH=$PATH:$HOME/.nodebrew/current/bin ← この行を追加

export PATH
インストール可能なnode.jsのバージョンをチェック
$ nodebrew ls-remote

     ~ 略 ~

v5.0.0    v5.1.0    v5.1.1    v5.2.0    v5.3.0    v5.4.0    v5.4.1    v5.5.0
v5.6.0    v5.7.0

     ~ 略 ~
とりあえず5系の最新版をインストールする。(時間掛かる)
$ nodebrew install v5.7.0
インストール済みのnode.jsのバージョン一覧を確認する。
$ nodebrew list
v5.7.0

current: none
5.7がインストールされた事を確認出来たので、現在使用するnode.jsのバージョンを指定する。
$ nodebrew use v5.7.0
もう一回nodebrewコマンドでバージョン確認
$ nodebrew list
v5.7.0

current: v5.7.0
currentにインストールしたバージョンが出てくる様になった事を確認して、下記コマンドでnode.jsのバージョンを確認
$ node -v
v5.7.0
これでnode.jsを使える様になった。

CentOSにHeroku Toolbeltをインストールする

rubyがインストールされていないとHeroku Toolbeltは使えない。
ちなみに、CentOSの6系のリポジトリに存在しているrubyのバージョンは古く、そのrubyをインストールしてもherokuコマンドは使えない。
7系は調べてないのでわからないです・・・。
rubyのインストール方法はこちらの記事に書いたので、まだrubyがインストールされていない方は参考にして頂ければと。

Heroku Toolbeltをインストールしていく。
wget -O- https://toolbelt.heroku.com/install.sh | sh
--2015-12-29 18:33:34--  https://toolbelt.heroku.com/install.sh
toolbelt.heroku.com をDNSに問いあわせています... 54.225.72.13, 54.235.97.119, 54.235.222.159
toolbelt.heroku.com|54.225.72.13|:443 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 975 [text/plain]
`STDOUT' に保存中

 0% [                                                                                                                                           ] 0           --.-K/s              This script requires superuser access to install software.
You will be prompted for your password by sudo.
100%[==========================================================================================================================================>] 975         --.-K/s 時間 0s

2015-12-29 18:33:34 (38.1 MB/s) - stdout へ出力完了 [975/975]

Add the Heroku CLI to your PATH using:
$ echo 'PATH="/usr/local/heroku/bin:$PATH"' >> ~/.bash_profile

.bash_profileに下記の部分を追加
$ vi ~/.bash_profile
# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:$HOME/bin
PATH="$PATH:/usr/local/heroku/bin" ← この行を追加
export PATH

再読み込み
$ source ~/.bash_profile

パスが通っているか確認
$ which heroku
/usr/local/heroku/bin/heroku

以上でインストールは完了です。

2016年2月20日土曜日

dockerのコンテナからホストマシンへの通信

dockerコンテナ上からホストマシンにcurlとか投げれるかなと思って調べてみた。
ホストマシン上で、ifconfigコマンドを叩く。
$ ifconfig
docker0   Link encap:Ethernet  HWaddr 16:60:95:11:A4:9C
          inet addr:172.17.42.1  Bcast:0.0.0.0  Mask:255.255.0.0
          inet6 addr: fe80::3807:43ff:fe23:6f0a/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:834595 errors:0 dropped:0 overruns:0 frame:0
          TX packets:2501 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:26940488 (25.6 MiB)  TX bytes:647482 (632.3 KiB)
このdocker0の部分がdockerコンテナ上から見えるホストマシンのIPでした。
curlコマンドも普通に通りました。

超便利、watch iptablesコマンド

個人の開発環境の話なのですが、dockerで80番ポートを使用していたためホストマシン上で80番ポートが使えなかったので、
とりあえず8080番ポートを使おうと思ったのですが、iptalbesをオフにしたらdockerの設定が使えなくなってしまう関係でオフに出来ない状況に陥りました。
ローカルの開発環境でiptablesとかで接続出来ないと、面倒なので今までは設定オフにしちゃってたんですが、
dockerを使っている関係でそういう訳にもいかず、iptablesの設定でハマっていたのですが、同僚の人に便利なコマンドを教えてもらいました。
watch iptables -xnvL
というコマンド。
今まで使ったことないコマンドだったのですが、パケットがどの段階でどこに行っているのかが分かります。
watch iptables -xnvL
Every 2.0s: iptables -xnvL                                                                                                           Wed Feb 17 10:25:31 2016

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
     pkts        bytes          target          prot   opt in    out     source               destination
   19053  1507938   ACCEPT          all        --  *      *       0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED
            0                 0   ACCEPT          icmp   --  *      *       0.0.0.0/0            0.0.0.0/0
            0                 0   ACCEPT          all        --  lo     *       0.0.0.0/0            0.0.0.0/0
          14         2264   ACCEPT          tcp      --  *      *       0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:22
          24         1408   ACCEPT          tcp      --  *      *       0.0.0.0/0            0.0.0.0/0           tcp dpt:8080
     1012       84278   IN_LOGGING  all        --  *      *       0.0.0.0/0            0.0.0.0/0
            0                 0   REJECT          all         --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited
見るべき点はpktsとbytes部分を特に注目してみる。
iptablesは順番が結構シビアで、間違えると思うように動いてくれないのですが、このコマンドを使うと、どこで落ちているのかよく分かる。
修正前は、
REJECT      all  --  anywhere             anywhere            reject-with icmp-host-prohibited
ACCEPT     tcp  --  anywhere             anywhere            tcp dpt:webcache
となっていたのですが、自分の認識だと上に書いた設定を下に書いた設定で上書き出来るんじゃなかったかなという朧気な記憶を元に記述したのですが、案の定間違っていました・・・。
修正前の状態を見てみると、REJECTされているパケットが・・・。
watch iptables -xnvL
Every 2.0s: iptables -xnvL                                                                                                           Wed Feb 17 10:21:20 2016

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
     pkts        bytes          target          prot   opt in    out     source               destination
   19053  1507938   ACCEPT          all        --  *      *       0.0.0.0/0            0.0.0.0/0           state RELATED,ESTABLISHED
            0                 0   ACCEPT          icmp   --  *      *       0.0.0.0/0            0.0.0.0/0
            0                 0   ACCEPT          all        --  lo     *       0.0.0.0/0            0.0.0.0/0
          14         2264   ACCEPT          tcp      --  *      *       0.0.0.0/0            0.0.0.0/0           state NEW tcp dpt:22
            0       94822   REJECT          all         --  *      *       0.0.0.0/0            0.0.0.0/0           reject-with icmp-host-prohibited
            0                0   ACCEPT          tcp      --  *      *       0.0.0.0/0            0.0.0.0/0           tcp dpt:8080
            0                0   IN_LOGGING  all        --  *      *       0.0.0.0/0            0.0.0.0/0
設定の上書きではなく、条件にマッチしなかったものが順次下に行くといった挙動でした・・・。
うろ覚えで作業するものではありませんでした・・・。

composer updateでClass not found

Laravel4.2を利用している環境で、composer updateを行うと、composer updateの後続処理として、
php artisan clear-compiled
php artisan optimize

が実行されるのですが、上記の処理を実行すると、app/config/app.phpに登録している自分で作ったServiceProviderを読み込む時にClass not foundでエラーになるという状態になっていた。
$ composer update
Loading composer repositories with package information                                                                     Updating dependencies (including require-dev)

Writing lock file
Generating autoload files
> php artisan clear-compiled
PHP Fatal error:  Class 'MyAPP\Services\App\AppServiceProvider' not found in /home/user/site/myapp/vendor/laravel/framework/src/Illuminate/Foundation/ProviderRepository.php on line 157
{"error":{"type":"Symfony\\Component\\Debug\\Exception\\FatalErrorException","message":"Class 'MyAPP\\Services\\App\\AppServiceProvider' not found","file":"\/home\/user\/site\/myapp\/vendor\/laravel\/framework\/src\/Illuminate\/Foundation\/ProviderRepository.php","line":157}}Script php artisan clear-compiled handling the post-update-cmd event returned with an error



  [RuntimeException]
  Error Output: PHP Fatal error:  Class 'MyAPP\Services\App\AppServiceProvider' not found in /home/user/site/myapp/vendor/laravel/framework/src/Illuminate
  /Foundation/ProviderRepository.php on line 157



update [--prefer-source] [--prefer-dist] [--dry-run] [--dev] [--no-dev] [--lock] [--no-plugins] [--no-custom-installers] [--no-autoloader] [--no-scripts] [--no-progress] [--with-dependencies] [-v|vv|vvv|--verbose] [-o|--optimize-autoloader] [-a|--classmap-authoritative] [--ignore-platform-reqs] [--prefer-stable] [--prefer-lowest] [--] []...
一度、自分で作ったServiceProviderをコメントアウトして、php artisan dump-autoloadし、コメントアウトを外して再度php artisan dump-autoloadすると問題なく使用出来る様になる。
使用出来る様になる理由は、vendor/composer/autoload_classmap.phpに自分で作ったServiceProviderのパスが記入されるためで、
再度composer updateするとそのファイルを含めたvendor/composer配下は再生成されるため、再びClass not foundになってしまう。

原因はcomposer.jsonに書いていたパスが間違っていたというなんとも言えないミスでした。

ディレクトリ構成は、下記の通りになっています。
/vendor/vendor_name/package_name/src/MyAPP/Service/XXXX/XXXXServiceProvider.php

誤ったcomposer.jsonの記述
"autoload": {
    "psr-4": {
        "MyAPP\\": "src/"
    }
}
正しいcomposer.jsonの記述
"autoload": {
    "psr-4": {
        "MyAPP\\": "src/MyAPP/"
    }
}
このパスが間違っているせいでautoload_psr4.phpの中の処理で引っかからずに、
autoload_classmap.phpにない場合にエラーになってしまっていました。
ちなみにcomposer内のファイル読み込み処理は、下記のファイルで行われている。
PSR-4 lookupと書かれている所の$fileに読み込むファイルのパスがセットされ、ファイルの存在確認をしている。
$ vi vendor/composer/ClassLoader.php

                            〜略〜

private function findFileWithExtension($class, $ext)
{
    // PSR-4 lookup
    $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

    $first = $class[0];
    if (isset($this->prefixLengthsPsr4[$first])) {
        foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
            if (0 === strpos($class, $prefix)) {
                foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
                    if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
                        return $file;
                    }
                }
            }
        }
    }

    // PSR-4 fallback dirs
    foreach ($this->fallbackDirsPsr4 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
            return $file;
        }
    }

    // PSR-0 lookup
    if (false !== $pos = strrpos($class, '\\')) {
        // namespaced class name
        $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
            . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
    } else {
        // PEAR-like class name
        $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
    }

    if (isset($this->prefixesPsr0[$first])) {
        foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
            if (0 === strpos($class, $prefix)) {
                foreach ($dirs as $dir) {
                    if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                        return $file;
                    }
                }
            }
        }
    }

    // PSR-0 fallback dirs
    foreach ($this->fallbackDirsPsr0 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
            return $file;
        }
    }

    // PSR-0 include paths.
    if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
        return $file;
    }
}
composer.jsonファイルを修正して、composer updateコマンドを打つ。 これでエラーが出なくなりました。

2016年1月29日金曜日

Laravel4.2で入力->確認->完了の画面遷移を実装してみる。(CSRF対策もおまけで)

Laravelでフォームを作るとどんな感じになるのか試してみた。
CSRF対策と2重送信の防止をするという前提で作ってみました。

app/routes.phpを編集し、入力、確認、完了画面で実行するコントローラーとアクションを定義する。
入力画面に関してはCSRF対策用のフィルタは通さずに、確認、完了画面に関してはCSRF対策用のフィルタを通す様に記述しました。
CSRF対策用のフィルタ処理はapp/filters.phpに書いてあります。(Laravelが元々用意している機能)
$ vi app/routes.php
Route::get('/input', 'HomeController@input');
Route::group(array('before' => 'csrf'), function()
{
    Route::post('/confirm',  'HomeController@confirm');
    Route::post('/complete', 'HomeController@complete');
});

続いてアクション。
下記の3つのメソッドを追加。
$ vi app/controllers/HomeController.php
public function input()
{
    return View::make('input');
}

public function confirm()
{
    return View::make('confirm');
}

public function complete()
{
    Session::regenerateToken();

    // 登録処理

    return View::make('complete');
}
完了画面で遷移してきた段階で、Laravelが作っているCSRF対策用のトークンを再生成しています。
これは、このトークンを2重送信防止用にも使用しているため、こういった形をとっています。
CSRF対策だけならこういった対応は必要ないと思います。
結局、「Session::regenerateToken()」は_tokenという名前でトークンをセッションに保存しているので、1セッションで複数フォームを開いた場合、
先に送信した方は登録されるが、後で送信された方はトークンが一致せずにエラーになる。
こういう挙動を許すかどうかは、そのシステムの要件次第なので、使用時に判断してもらえればと思います。

最後にテンプレート。
入力画面
$ vi app/views/input.blade.php
{{ Form::open(array('url' => 'confirm')) }}
<table>
<tr>
<td>タイトル</td><td>{{ Form::text('title') }}</td>
</tr>
<tr>
<td>メッセージ</td><td>{{ Form::textarea('message') }}</td>
</tr>
<tr>
<td colspan="2">{{ Form::submit('内容を確認') }}</td>
</tr>
</table>
{{ Form::close() }}

確認画面
$ vi app/views/confirm.blade.php
{{ Form::open(array('url' => 'complete')) }}
<table>
<tr>
<td>タイトル</td><td>{{ Input::get('title') }}{{ Form::hidden('title', Input::get('title')) }}</td>
</tr>
<tr>
<td>メッセージ</td><td>{{ Input::get('message') }}{{ Form::hidden('message', Input::get('message')) }}</td>
</tr>
<tr>
<td colspan="2">{{ Form::submit('内容を登録') }}</td>
</tr>
</table>
{{ Form::close() }}
完了画面
$ vi app/views/complete.blade.php
complete!

2016年1月5日火曜日

rbenvを利用してruby2系をインストール(特定のユーザーのみ)

rbenvをインストール
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
Initialized empty Git repository in /home/vagrant/.rbenv/.git/
remote: Counting objects: 2484, done.
remote: Compressing objects: 100% (20/20), done.
remote: Total 2484 (delta 9), reused 0 (delta 0), pack-reused 2464
Receiving objects: 100% (2484/2484), 445.80 KiB | 142 KiB/s, done.
Resolving deltas: 100% (1562/1562), done.
パスを通し、ターミナル起動時に「rbenv init」する様にしておく。
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
リロード
$ source ~/.bash_profile
ruby-buildをインストール
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
Initialized empty Git repository in /home/vagrant/.rbenv/plugins/ruby-build/.git/
remote: Counting objects: 5431, done.
remote: Total 5431 (delta 0), reused 0 (delta 0), pack-reused 5431
Receiving objects: 100% (5431/5431), 1013.75 KiB | 389 KiB/s, done.
Resolving deltas: 100% (3029/3029), done.
rbenvのバージョン確認
$ rbenv
rbenv 1.0.0-14-gc388331
Usage: rbenv  []

Some useful rbenv commands are:
   commands    List all available rbenv commands
   local       Set or show the local application-specific Ruby version
   global      Set or show the global Ruby version
   shell       Set or show the shell-specific Ruby version
   rehash      Rehash rbenv shims (run this after installing executables)
   version     Show the current Ruby version and its origin
   versions    List all Ruby versions available to rbenv
   which       Display the full path to an executable
   whence      List all Ruby versions that contain the given executable

See `rbenv help ' for information on a specific command.
For full documentation, see: https://github.com/rbenv/rbenv#readme
rubyをインストールする際に必要になるパッケージをインストール
$ sudo yum install gcc make openssl-devel readline-devel
インストール可能なrubyのバージョンの一覧を確認する
$ rbenv install -l
Available versions:
  :
  略
  :
  2.2.0
  2.2.1
  2.2.2
  2.2.3
  2.2.4
  2.3.0-dev
  2.3.0-preview1
  2.3.0-preview2
  2.3.0
  2.4.0-dev
  :
  略
  :
今回はとりあえず2.2.4をインストールしてみる。
$ rbenv install 2.2.4
Downloading ruby-2.2.4.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.4.tar.bz2
error: failed to download ruby-2.2.4.tar.bz2

BUILD FAILED (CentOS release 6.7 (Final) using ruby-build 20151230)
ダウンロードに失敗したのでログを確認。 ググると、時間が一致していない事が問題らしいので、サーバ側の時間を現時刻に合わせた。(ntp入れた)
tail /tmp/ruby-build.YYYYMMDDHHIISS.XXXX.log
curl: (60) Peer certificate cannot be authenticated with known CA certificates
More details here: http://curl.haxx.se/docs/sslcerts.html

curl performs SSL certificate verification by default, using a "bundle"
 of Certificate Authority (CA) public keys (CA certs). If the default
 bundle file isn't adequate, you can specify an alternate file
 using the --cacert option.
If this HTTPS server uses a certificate signed by a CA represented in
 the bundle, the certificate verification probably failed due to a
 problem with the certificate (it might be expired, or the name might
 not match the domain name in the URL).
If you'd like to turn off curl's verification of the certificate, use
 the -k (or --insecure) option.
現時刻があったことを確認して、下記コマンドを再度実行。
$ rbenv install 2.2.4
Downloading ruby-2.2.4.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.4.tar.bz2
Installing ruby-2.2.4...
Installed ruby-2.2.4 to /home/vagrant/.rbenv/versions/2.2.4
デフォルトで使用するrubyのバージョンを下記の通りにする。
$ rbenv global 2.2.4
バージョンを確認(rubyコマンドが見つからない場合は、$ source ~/.bash_profileする)
$ ruby -v
ruby 2.2.4p230 (2015-12-16 revision 53155) [x86_64-linux]
以上でインストールは完了。