SHOYAN BLOG

I am a pragmatic programmer.

NginxのCookbookでハマった

Nginxのcookbookでversionを指定しているのに、指定したバージョンでインストールされない問題でハマった。

https://supermarket.chef.io/cookbooks/nginx

結果としてわかったこと

パッケージでインストールする場合はバージョンが指定できない。
バージョンを指定したい場合はソースでインストールする必要がある。

Nginx cookbookはパッケージでのインストールかソースでのインストールのいずれかを選ぶことができる。
デフォルトはパッケージである。
ソースでインストールしたい場合は、node['nginx']['install_method’]='source' とする必要がある。
install_methodを指定したときにはじめてversionの指定が可能となる。

立ちはだかる様々な問題

versionが反映されない

attributes/default.rbにversionを定義してみたが、指定されたバージョンがインストールされない問題にハマった。
デフォルトである、1.4.4がインストールされている。

解決方法

rolesのoverride_attributesとして指定すると指定されたバージョンがインストールされた。
基本的にvendor cookbookの値の定義はroleのoverride_attributeで指定したほうがいいのかもしれない。
とはいえ、パラメーターによっては上書きされる場合もあってそのあたりの挙動がよくわかっていない。

Checksumを指定しないといけない

sourceからインストールする場合、checksumを設定する必要がある。
checksumは以下のようにして作成できる。

1
shasum -a 256 nginx-x.x.x.tar.gz

もしくは

1
curl http://nginx.org/download/nginx-x.x.x.tar.gz | shasum -a 256

checksumは node['nginx']['source']['checksum’] に指定する。

ChefのAttributeの優先順位について

マニュアルによると、以下のようになっている(しかし、versionをoverrideで定義しても上書きできなかった)。

  • A default attribute located in a cookbook attribute file
  • A default attribute located in a recipe
  • A default attribute located in an environment
  • A default attribute located in role
  • A force_default attribute located in a cookbook attribute file
  • A force_default attribute located in a recipe
  • A normal attribute located in a cookbook attribute file
  • A normal attribute located in a recipe
  • An override attribute located in a cookbook attribute file
  • An override attribute located in a recipe
  • An override attribute located in a role
  • An override attribute located in an environment
  • A force_override attribute located in a cookbook attribute file
  • A force_override attribute located in a recipe
  • An automatic attribute identified by Ohai at the start of the chef-client run

https://docs.chef.io/attributes.html#attribute-precedence

Attributeの使い分けについて

以下のようにルール決めをしているという記事もあった。

Attributeファイル=>文言変更
recipeファイル=>テスト的に変えたいものがある時のみ。本番では消す。
Environmentファイル=>development(開発),alpha(実験用)、staging(テスト)、production(本番)でのIPやOSバージョンの違い。同じ構成が複数台ある時もここで区別。
Role=>ミドルウェアのバージョン違い

http://dev.classmethod.jp/server-side/chef/attribute-overrides-pattern/

参考リンク

グラフを描画するHighChartsで平均気温のグラフを描画する

グラフを描画するHighChartsを紹介します。
HighChartsはJavaScriptのグラフ描画ライブラリです。
HighChartsを使えば簡単にグラフの描画ができます。

デモページ

福岡市の平均気温をグラフで表示してみました。

http://codepen.io/shoyan/details/jrOjWK/

サンプルを作ってみる

簡単なサンプルを作ってみましょう。
私がサンプルとして利用していたCODEPENを利用すると簡単に作成できるのでオススメです。

HighChartsを使うには、jQueryとhighCharts.jsが必要です。

1
2
<script src="http://code.highcharts.com/highcharts.js"></script>
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>

index.htmlのbody部は以下を定義します。
div要素にグラフが描画されます。

index.html

1
<div id="container" style="width:100%; height:400px;"></div>

JavaScript tag <script> </script> に書くか、外部ファイルに定義してください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$(function () {
    $('#container').highcharts({
        chart: {
            type: 'bar'
        },
        title: {
            text: 'Fruit Consumption'
        },
        xAxis: {
            categories: ['Apples', 'Bananas', 'Oranges']
        },
        yAxis: {
            title: {
                text: 'Fruit eaten'
            }
        },
        series: [{
            name: 'Jane',
            data: [1, 0, 4]
        }, {
            name: 'John',
            data: [5, 7, 3]
        }]
    });
});

あとは、ブラウザでアクセスすればグラフが描画されます。

構文

基本的な構文は以下です。

1
$("グラフを描画するhtml要素").highcharts(option)

HighChartsの基本的なパラメーター(option)

HighChartsの基本的なパラメーター(option)を紹介します。

  • TITLE: チャートのタイトル
  • SERIES: 描画するデータの値
  • TOOLTIP: チャートにマウスオーバーしたときに表示されるツールチップの設定
  • LEGEND: SERIESの説明
  • AXES: 縦軸と横軸の説明を設定します。

グラフの種類について

typeにグラフの種類を設定できます。

  • 棒グラフ: column
  • 折れ線グラフ: line
  • 円グラフ: pie
  • 帯グラフ: bar
  • ヒストグラム: column
  • 散布図: scatter
  • 箱ひげ図: boxplot
  • 三角グラフ: pyramid

様々なグラフのデモページが用意されており、参考になります。

http://www.highcharts.com/demo

Dockerのコンテナでyum Installが失敗する

Dockerのコンテナでyum installが失敗する。
以下のようなエラーがでていた。

1
Insufficient space in download directory /var/cache/yum/x86_64/6/updates/packages

容量が不足しているらしい。

ディスク容量を確認したところ、100%になっていた。

1
2
3
4
5
6
7
bash-4.2# df
Filesystem     1K-blocks     Used Available Use% Mounted on
none            19049892 18226752         0 100% /
tmpfs             509992        0    509992   0% /dev
tmpfs             509992        0    509992   0% /sys/fs/cgroup
/dev/sda1       19049892 18226752         0 100% /etc/hosts
shm                65536        0     65536   0% /dev/shm

不要なコンテナが溜まっていて、そのせいでディスク容量を圧迫していたようだ。

コンテナを消す方法

  • docker ps -a でコンテナの一覧が表示される
  • docker rm container_id で消す

コンテナを消すと、ディスク容量に空きができてyum installできるようになった。

こちらは類似案件。

Sinatraのロギング機構について調べてみた

Sinatraのloggerヘルパーを使ったところ、なぜか標準エラーの出力先にログが吐かれており、標準出力の出力先にはログが吐かれない。
標準出力先にログを吐くものだと思っていたのだが、自分が想定していた挙動と違うので調べてみた。

まずは、Sinatraのloggerヘルパーのソースコードを確認してみる。

1
2
3
def logger
  request.logger
end

https://github.com/sinatra/sinatra/blob/939ce04c1b77d24dd78285ba0836768ad57aff6c/lib/sinatra/base.rb#L327

request.loggerを返している。
レシーバであるrequestは rack::requestなので、rack::request#loggerは何を返しているかを確認する。

1
def logger; get_header(RACK_LOGGER) end

https://github.com/rack/rack/blob/master/lib/rack/request.rb#L136

get_headerは@envから引数に与えられた値を返すだけのメソッド。

1
2
3
def get_header(name)
  @env[name]
end

RACK_LOGGERは以下のように定義されている。

1
RACK_LOGGER = 'rack.logger'.freeze

https://github.com/rack/rack/blob/9073125f71afd615091f575d74ec468a0b1b79bf/lib/rack.rb#L64

ここまでで、loggerヘルパーはenv['rack.logger’]を取得していることがわかった。

では、rack.loggerには何が設定されているのかという疑問が湧いてくる。
rackには3つのロガーがある。

  • CommonLogger
  • Logger
  • NullLogger

このうち、LoggerとNullLoggerがRACK_LOGGERにセットしていた。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Rack::Logger
require 'logger'

module Rack
  # Sets up rack.logger to write to rack.errors stream
  class Logger
    def initialize(app, level = ::Logger::INFO)
      @app, @level = app, level
    end

    def call(env)
      logger = ::Logger.new(env[RACK_ERRORS])
      logger.level = @level

      env[RACK_LOGGER] = logger
      @app.call(env)
    end
  end
end

# Rack::NullLogger
module Rack
  class NullLogger
    def initialize(app)
      @app = app
    end

    def call(env)
      env[RACK_LOGGER] = self
      @app.call(env)
    end
    ...........

通常、Rack::Loggerが使われる。
Rack::LoggerはRubyのloggerライブラリのラッパーで、log deviceにenv[RACK_ERRORS]をセットしている。
env[RACK_ERRORS]が何かを調べたところ、基本的には$stderrがセットされるようだ。

(例)webrickの場合は、$stderrがセットされている。

1
2
3
4
5
6
7
8
9
10
11
12
env.update(
  RACK_VERSION => Rack::VERSION,
  RACK_INPUT => rack_input,
  RACK_ERRORS => $stderr,
  RACK_MULTITHREAD => true,
  RACK_MULTIPROCESS => false,
  RACK_RUNONCE => false,
  RACK_URL_SCHEME => ["yes", "on", "1"].include?(env[HTTPS]) ? "https" : "http",
  RACK_IS_HIJACK => true,
  RACK_HIJACK => lambda { raise NotImplementedError, "only partial hijack is supported."},
  RACK_HIJACK_IO => nil
)

https://github.com/rack/rack/blob/95172a60fe5c2a3850163fc75e0981fe440c064e/lib/rack/handler/webrick.rb#L68

ということで、結果的にSinatraのloggerは標準エラーに出力されることになる。

アプリケーションログを任意のファイルに出力するには

任意のファイルにログを出力したい場合は、自前でloggerを定義してやればよい。

1
2
3
4
5
6
7
8
def logger
  return @logger unless @logger.nil?
  file = File.new("#{settings.root}/log/#{settings.environment}.log", 'a+')
  file.sync = true
  @logger = ::Logger.new(file)
end

logger.info "Hello"

参考リンク

Guardでrspecのテストを自動化する

自動化ツールのGuardの紹介をします。
Guardはファイルを監視して、ファイルになんらかの変更がされたら、指定した処理を実行するツールです。
この機能を使ってファイルが変更されたらテストを自動で実行させたり、シンタックスチェックをすることができます。
今回はrspecで自動的にテストを実行する方法を紹介します。

まずはGuardのinstallをします。
Gemfileに定義してインストールします。

Gemfile

1
2
3
4
group :development do
  gem 'guard'
  gem 'guard-rspec', require: false
end

bundle installして、Guardfileを作成します。
GuardfileはGuardの設定を定義するファイルです。

1
2
$ bundle install
$ bundle exec guard init rspec

guard init rspecを実行するとrspecの設定が書かれたGuardfileが作成されます。
Railsを想定した設定が書かれていますので、Railsの場合はそのままでOKです。

ファイルが実行されたらrspecを実行する

ファイルが実行されたらrspecを実行するようにしましょう。
別でウィンドウを開いてguardを実行します。

1
$ bundle exec guard

ファイルを変更するとそのファイルのテストが実行され、テスト結果が表示されます。

Guard-rspecのDSL

Guardの設定はGuardのDSLを用いて設定します。

1
2
3
4
5
6
7
# rspecのグループを定義し、監視しているファイルに変更があった場合は"bundle exec rspec""を実行する。
guard :rspec, cmd: "bundle exec rspec" do
  # Guard::RspecのDSLのインスタンスを作成
  dsl = Guard::RSpec::Dsl.new(self)
  rspec = dsl.rspec
  watch(rspec.spec_helper) { rspec.spec_dir }
end

watchで監視するファイルを設定します。
watchの引数は以下です。

1
watch(監視するファイル) { コマンドに渡される引数 }

ここででてきた、rspec.spec_helperrspec.spec_dir はどんな値を返すのでしょうか。
pryで覗いてみましょう。

1
2
3
4
5
6
7
8
$ pry
> require 'guard/rspec/dsl'
> dsl = Guard::RSpec::Dsl.new(self)
> rspec = dsl.rspec
> rspec.spec_helper
=> "spec/spec_helper.rb"
> rspec.spec_dir
=> "spec"

ソースを少し見てみましょう。

1
2
3
4
5
6
7
8
9
def rspec
  @rspec ||= OpenStruct.new(to_s: "spec").tap do |rspec|
    rspec.spec_dir = "spec"
    rspec.spec = -(m) { Dsl.detect_spec_file_for(rspec, m) }
    rspec.spec_helper = "#{rspec.spec_dir}/spec_helper.rb"
    rspec.spec_files = %r{^#{rspec.spec_dir}/.+_spec\.rb$}
    rspec.spec_support = %r{^#{rspec.spec_dir}/support/(.+)\.rb$}
  end
end

https://github.com/guard/guard-rspec/blob/master/lib/guard/rspec/dsl.rb#L28

実装にはOpenStructが使われています。
OpenStructとは要素を動的に追加・削除できる手軽な構造体を提供するクラスです
http://docs.ruby-lang.org/ja/2.1.0/class/OpenStruct.html

要素を追加するためにtapメソッドを使っています。
OpenStructとtapメソッドをうまく使っていますね。

実は、というか当たり前なのですがOpenStructなので値の上書きも簡単にできてしまいます。

1
2
3
4
5
6
> rspec.spec_helper
=> "spec/spec_helper.rb"
> rspec.spec_helper = 'hoge'
=> "hoge"
> rspec.spec_helper
=> "hoge"

Guardのカスタマイズ

Guardの様々なプラグインが開発されています。
プラグインは以下のページで参照できます。
https://github.com/guard/guard/wiki/Guard-Plugins

ちなみに、今回使ったguard-rspecもGuardのプラグインです。
他にも様々なプラグインが用意されています。

関連記事