page_adsence

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コマンドを打つ。 これでエラーが出なくなりました。