qpstudyの懇親会でLTしてきた!

『qpstudy 2013.07 懇親会 〜ドキッ!エンジニアだらけのビアバッシュ!〜』でLTしてきました。
今回はこういう流れで、急いでLT資料作って懇親会だけ参加しました。


LTは『自分もいつのまにかいい歳になって、将来のこととか考えないとなぁと思い立って、みんなの意見を聞いてみよう』ってお話でした。


こういう簡単なアンケートページを作って、リアルタイムにみんなの意見を集めました。


アンケートに使ったプログラムはこんな感じです。
表示用のインタフェースはダルいので作らず、ログに出してtail -fしてました。開発系の勉強会だったらフルボッコですね。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
<title>qpstudy LT アンケート</title>
</head>
<body>
<h1>qpstudy LT アンケート</h1>
<?php
$questions = array(
//  '質問1',
//  'テスト',
//  'テスト2'
//  1
//  '転職回数は何回?',
//  '主な理由は?'
//  2
//  '今の年俸はいくらくらい?',
//  '勤続年数、ポジションなど'
//  3
//  '3次元の嫁/彼女はいる?',
//  'どこで見つけた?'
//  4
  '目標にしている人',
  '面白そうな会社'
);

$answers = $_GET['answers'];
if($answers){
  echo "あざーす!<br />\n";
  echo '<a href="'.$_SERVER['PHP_SELF'].'">戻る</a>';

  //オシャレにLTSV形式でログ出力
  $result = array();
  for($i=0;$i < count($questions);$i++){
    $result[] = $questions[$i] .":". $answers[$i];
  }
  file_put_contents("/tmp/qp.log", implode("\t", $result) . "\n", FILE_APPEND);
}else{
  echo '<form action="'.$_SERVER['PHP_SELF'].'" method="GET">' ."\n";
  foreach($questions as $question){
    echo "<label>". $question .' : <input type="text" name="answers[]" /></label><br />' ."\n";
  }
  echo '<input type="submit" value="投票" />' ."\n";
  echo "</form>\n";
}
?>
</body>
</html>

ログはこんな感じでした

転職回数は何回?:0	主な理由は?:
転職回数は何回?:0	主な理由は?:
転職回数は何回?:2	主な理由は?:職場環境、収入
転職回数は何回?:0	主な理由は?:なし
転職回数は何回?:1	主な理由は?:社内制度に絶望したっ
転職回数は何回?:3	主な理由は?:やることなくなった
転職回数は何回?:3	主な理由は?:飽きる
転職回数は何回?:2	主な理由は?:病気退職
転職回数は何回?:1	主な理由は?:社長と合わなくて
転職回数は何回?:2	主な理由は?:無し
転職回数は何回?:4	主な理由は?:新しいことがやりたくなった
転職回数は何回?:0回	主な理由は?:0だから、ない!
転職回数は何回?:1	主な理由は?:何となく
転職回数は何回?:0	主な理由は?:やったこと無いです
転職回数は何回?:0回っす。	主な理由は?:踏ん切りなくて、、、
転職回数は何回?:0	主な理由は?:一年目なので
転職回数は何回?:2	主な理由は?:つぶれそうって勘
転職回数は何回?:0回	主な理由は?:そろそろしたい
転職回数は何回?:1	主な理由は?:ドキュメント作成屋になったので
転職回数は何回?:0	主な理由は?:無し
転職回数は何回?:3?	主な理由は?:事業廃止に近い
転職回数は何回?:test	主な理由は?:つぶれそうって勘
転職回数は何回?:2	主な理由は?:疲れた
転職回数は何回?:2	主な理由は?:仕事が面白くない
転職回数は何回?:5	主な理由は?:あきたから
転職回数は何回?:1	主な理由は?:業績
転職回数は何回?:2	主な理由は?:ブラック
転職回数は何回?:0	主な理由は?:やってみたい
転職回数は何回?:1	主な理由は?:引越し
転職回数は何回?:0回	主な理由は?:社内転職は1回しました!
転職回数は何回?:0回っす。	主な理由は?:踏ん切りなくて、、、
転職回数は何回?:1	主な理由は?:将来性
転職回数は何回?:7	主な理由は?:マネー
3次元の嫁/彼女はいる?:めけだ	どこで見つけた?:qp
3次元の嫁/彼女はいる?:いないです	どこで見つけた?:教えてほしいです
3次元の嫁/彼女はいる?:いる	どこで見つけた?:
3次元の嫁/彼女はいる?:いない	どこで見つけた?:どこに落ちてるんですか?
3次元の嫁/彼女はいる?:いますー。	どこで見つけた?:ファミレス
3次元の嫁/彼女はいる?:いる	どこで見つけた?:共通の趣味
3次元の嫁/彼女はいる?:いない	どこで見つけた?:いない
3次元の嫁/彼女はいる?:いる	どこで見つけた?:
3次元の嫁/彼女はいる?:いまーす!	どこで見つけた?:GOこん!
3次元の嫁/彼女はいる?:いない	どこで見つけた?:いないと言っている!
3次元の嫁/彼女はいる?:年齢が危ないが一応	どこで見つけた?:Twitter経由
3次元の嫁/彼女はいる?:いない	どこで見つけた?:紹介はしました
3次元の嫁/彼女はいる?:いる	どこで見つけた?:
3次元の嫁/彼女はいる?:いない	どこで見つけた?:
3次元の嫁/彼女はいる?:いますん	どこで見つけた?:1しゃめの研修
3次元の嫁/彼女はいる?:いなくなった	どこで見つけた?:教えて
3次元の嫁/彼女はいる?:いる	どこで見つけた?:勉強会
3次元の嫁/彼女はいる?:いない	どこで見つけた?:見つけられない
3次元の嫁/彼女はいる?:いない!!!!!!!!	どこで見つけた?:
3次元の嫁/彼女はいる?:いない	どこで見つけた?:
3次元の嫁/彼女はいる?:前に居た	どこで見つけた?:会社
3次元の嫁/彼女はいる?:元嫁はいた	どこで見つけた?:勉強会
3次元の嫁/彼女はいる?:いる	どこで見つけた?:大学時代
3次元の嫁/彼女はいる?:いる	どこで見つけた?:忘れた
3次元の嫁/彼女はいる?:いない	どこで見つけた?:紹介はしました
3次元の嫁/彼女はいる?:いる	どこで見つけた?:勉強会
3次元の嫁/彼女はいる?:いない	どこで見つけた?:前カノはスポーツジム
3次元の嫁/彼女はいる?:いますー。	どこで見つけた?:ファミレス
目標にしている人:銀河	面白そうな会社:銀河さん
目標にしている人:特に思いつきません	面白そうな会社:さくら
目標にしている人:不明	面白そうな会社:不明
目標にしている人:いないです	面白そうな会社:ガールフレンド(仮)のとこ
目標にしている人:せちろーさん	面白そうな会社:銀河さん
目標にしている人:かぜぶろさん	面白そうな会社:NHN
目標にしている人:もりすさん	面白そうな会社:えぬえいちえぬ
目標にしている人:リーダー	面白そうな会社:シャッツキステ
目標にしている人:スタジオなんか屁	面白そうな会社:マーベラス
目標にしている人:たごもりさん	面白そうな会社:line
目標にしている人:h.shinonome	面白そうな会社:
目標にしている人:ひろせさん	面白そうな会社:DeNA, CyberAgent, AmazonDataServices
目標にしている人:	面白そうな会社:特許事務所
目標にしている人:いる	面白そうな会社:
目標にしている人:います	面白そうな会社:AWS
目標にしている人:みけだ	面白そうな会社:
目標にしている人:MSの西脇さん	面白そうな会社:うーん
目標にしている人:みけだ	面白そうな会社:
目標にしている人:めけだ	面白そうな会社:
目標にしている人:めけだ	面白そうな会社:てけだ
目標にしている人:みけださん	面白そうな会社:
目標にしている人:	面白そうな会社:お金くれるところ
目標にしている人:#ヤマン	面白そうな会社:AWS


いろいろ参考になりました。みなさんご意見ありがとうございました!

Nagiosでカスタム・オブジェクト変数を使って、サーバごとに監視の閾値を変える

Nagios+NRPE環境でサーバごとに監視の閾値変えたいなーと思ったんですが、nrpe.cfgをサーバごとに変えるのはちょっとダルい。
本体側の設定でなんとかできないかなと思って調べてると、こういうのがありました。
カスタム・オブジェクト変数
nagios.cfg内で自由に変数を定義できるみたいです。使ってみましょう!

サンプル設定

NRPE側の設定がこんな感じになってるとして、

command[check_disk]=/usr/lib64/nagios/plugins/check_disk -w $ARG1$ -c $ARG2$ -p $ARG3$

サーバ側はこんな感じ

;テンプレート側でデフォルト値を定義する
define host{
  name                   linux-server
...
  _DISK_WARNING  20
  _DISK_CRITICAL 10
  }

;デフォルトでいいサーバはそのまま
define host{
  use         linux-server
  host_name   test01
  hostgroups  linux
  address     192.168.1.61
  }

;デフォルトから変更したいサーバだけ変数を上書き
define host{
  use         linux-server
  host_name   test02
  hostgroups  linux
  address     192.168.1.62
  _DISK_WARNING  99
  _DISK_CRITICAL 98
  }


define command{
  command_name    check_nrpe_arg
  command_line    $USER1$/check_nrpe -t 20 -H $HOSTADDRESS$ -c $ARG1$ -a $ARG2$  
  }

;監視引数で定義したカスタムオブジェクト変数を使う
define service{
  use                  generic-service
  hostgroup_name       linux
  service_description  Disk Usage
;  check_command        check_nrpe_arg!check_disk!20% 10% /
  check_command        check_nrpe_arg!check_disk!$_HOSTDISK_WARNING$% $_HOSTDISK_CRITICAL$% /
  }


デバッグログとか見てるととりあえずできてるっぽい。

まとめ

みんなこういう場合どうやってるのかな?もっとオシャレな方法あるんだろうか・・・

サーバのリソース使用状況レポートを作る

数百台のサーバに対して

  • CPU
  • メモリ
  • HDD

の使用状況をサクッとチェックしたいなーと思ったのですが、さすがにmuninのグラフで見るのはダルすぎる。
というわけで日次でこういうページを作ってチェックするようにしました。

上記の情報が数字でダーっと並んでて、ついでに簡単に色付けとか、muninへのリンク張りとか、各項目でのソート機能付けたりとかをやってます。
CPUとメモリの使用率は前日の平均、ディスク使用率はバッチ実行時の値です。


最初はmuninのRRDファイルから作ろうかと思ったのですが(gist)、この程度の情報ならsysstatやdfの結果から作るほうが簡単なので、sshで集めてくることにしました。

とりあえずHTMLに出力してますが、CSVで出したりDBに突っ込んだりすれば各種調査に便利ですよ!

ソースコード

Ruby1.9版です

#!/usr/local/bin/ruby
require 'net/ssh'
require 'json'
require 'erb'
require 'pp'

server_list_file = "/home/mikeda/check_servers/sv.list" # このファイルに調査対象のホストを記載
erb_template_file = "/home/mikeda/check_servers/resource.html.erb"

def summarize_cpu(sar_cpu)
  _, _, user, nice, system, iowait, steal, idle = sar_cpu.split(/\s+/)
  {
    user:   user.to_f,
    nice:   nice.to_f,
    system: system.to_f,
    iowait: iowait.to_f,
    steal:  steal.to_f,
    idle:   idle.to_f
  }
end

def summarize_memory(sar_memory)
  _, kbmemfree, kbmemused, memused, kbbuffers, kbcached, kbcommit, commit = sar_memory.split(/\s+/)
  mem = {
    kbmemfree: kbmemfree.to_i,
    kbmemused: kbmemused.to_i,
    memused:   memused.to_i,
    kbbuffers: kbbuffers.to_i,
    kbcached:  kbcached.to_i,
    kbcommit:  kbcommit.to_i,
    commit:    commit.to_f
  }
  total = ( mem[:kbmemfree] + mem[:kbmemused] ) / 1024
  used = ( mem[:kbmemused] - mem[:kbbuffers] - mem[:kbcached] ) / 1024
  {
    total: total,
    used:  used,
    usage: (100 * used.to_f / total).to_i
  }
end

def summarize_disk(df)
  disk = {}
  df.split("\n").each do |d|
    filesystem, size, used, avail, usage, mount = d.split(/\s+/)
    disk[mount] = {
      filesystem: filesystem,
      size:       size.to_i,
      used:       used.to_i,
      avail:      avail.to_i,
      usage:      usage.chop.to_i
    }
  end

  # /varが個別にマウントされていればその情報、それ以外は/の情報を返す
  disk['/var'] || disk['/']
end


servers = open(server_list_file).readlines.map(&:chomp)
resources = {}
YESTERDAY_SAR = "/var/log/sa/sa#{(Date.today - 1).strftime("%d")}"
servers.each do |host|
  begin
    Net::SSH.start(host, 'mikeda') do |ssh|
      resources[host] = {}

      sar_cpu = ssh.exec!("LANG=C sar -f #{YESTERDAY_SAR} | tail -1")
      resources[host][:cpu] = summarize_cpu(sar_cpu)

      sar_memory = ssh.exec!("LANG=C sar -r -f #{YESTERDAY_SAR} | tail -1")
      resources[host][:memory] = summarize_memory(sar_memory)

      df = ssh.exec!("LANG=C df -lmP | sed 1d")
      resources[host][:disk] = summarize_disk(df)
    end
  rescue => e
    STDERR.puts "#{host}: #{e.message}"
  end
end


### HTML出力用
def color(value, warn, critical)
  if value > critical
    'red'
  elsif value > warn
    'yellow'
  else
    ''
  end
end
def color_cpu_usr(usage); color(usage, 30, 60) end
def color_cpu_sys(usage); color(usage, 5,  10) end
def color_cpu_io(usage);  color(usage, 5,  10) end
def color_memory(usage);  color(usage, 60, 80) end
def color_disk(usage);    color(usage, 60, 80) end

puts ERB.new(File.read(erb_template_file)).result


### JSON出力用
#puts JSON.pretty_generate(resources)

とりあえずコイツをcronで毎日実行してます

7 7 * * * /home/mikeda/check_servers/check_server_resource.rb > /var/www/html/files/check_servers/resource/resource_`date +\%Y\%m\%d`.html
ERBテンプレート

各項目でソートできるようにコレ使ってます

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>サーバリソースチェック</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<script type="text/javascript" src="/files/js/joequery-Stupid-Table-Plugin/stupidtable.min.js"></script>
<script type="text/javascript">
$(function(){
        $("table").stupidtable();
});
</script>
</style>
</head>
<body>
<table border="1">
<thead>
<tr>
<th data-sort="string">Host</th>
<th data-sort="float">user</th>
<th data-sort="float">system</th>
<th data-sort="float">io</th>
<th data-sort="float">idle</th>
<th data-sort="int">メモリ使用率</th>
<th data-sort="int">ディスク使用率</th>
</tr>
</thead>
<% resources.each do |host, r| %>
<tr>
  <td><a href="http://munin.mikeda.jp/server/<%= host %>/index.html#system"><%= host %></a></td>
  <td align="right" bgcolor="<%= color_cpu_usr(r[:cpu][:user]) %>"><%= r[:cpu][:user] %></td>
  <td align="right" bgcolor="<%= color_cpu_sys(r[:cpu][:system]) %>"><%= r[:cpu][:system] %></td>
  <td align="right" bgcolor="<%= color_cpu_io(r[:cpu][:iowait]) %>"><%= r[:cpu][:iowait] %></td>
  <td align="right" ><%= r[:cpu][:idle] %></td>
  <td align="right" bgcolor="<%= color_memory(r[:memory][:usage]) %>"><%= r[:memory][:usage] %>%</td>
  <td align="right" bgcolor="<%= color_disk(r[:disk][:usage]) %>"><%= r[:disk][:usage] %>%</td>
</tr>
<% end %>
</table>
</body>
</html>

なんでもかんでもmuninやZabbixのグラフで見ようとするのは非効率的ですよ!

ログの差分出力スクリプト

昔Perlで書いたやつRuby1.9版をとりあえず作りました

ログファイルを指定すると、前回実行からの差分を出力してくれます

$ echo 111 > test.log
$ ./logtail.rb test.log
111

$ echo 222 >> test.log
$ echo 333 >> test.log
$ ./logtail.rb test.log
222
333


ソースコード

#!/usr/local/bin/ruby

# ログの差分出力スクリプト
# 使用例
#   前回実行からの追記分を出力する
#   $ logtail.rb access_log
#
#   ログのローテートに対応させる場合はオプションでローテートファイルを指定する
#   $ logtail.rb access_log -r access_log.1
#
#   デフォルトでは/tmp/<ログファイル名>.logtail.offsetに前回出力情報が保存される。
#   日付つきファイルなどファイル名が変わる場合、
#   同じファイルを複数バッチから処理する場合はこのファイルパスを個別に指定すること
#   $ logtail.rb access_log.20130602 -r access_log.20130601 -o /tmp/test_access_log.logtail.offset
#
# TODO:オフセットファイルの作成、書き込み権限のチェック
# TODO:ファイル名が同じだとデフォルトのオフセットファイル名が重複する。フルパスのハッシュ値とかつけたほうがいいかな

require 'optparse'
require 'pp'

options = {}
OptionParser.new do |opt|
  opt.on('-r', '--rotated_log=VAL') {|v| options[:rotated_log] = v}
  opt.on('-o', '--offset_file=VAL') {|v| options[:offset_file] = v}
  opt.parse!
end

log = ARGV.shift
exit 1 unless log


class LogTail
  def initialize(log, options)
    @log = log
    @rotated_log = options[:rotated_log]
    @offset_file = options[:offset_file] || "/tmp/#{File.basename(log)}.logtail.offset"
    @offset = load_offset
  end

  def load_offset
    return nil unless File.exist? @offset_file

    line = File.read(@offset_file)
    ino, pos = line.split(',')
    { ino: ino.to_i, pos: pos.to_i}
  end

  def save_offset(ino, pos)
    File.write(@offset_file, "#{ino},#{pos}")
  end
  
  def tail_print(log, pos)
    file = File.open(log)
    file.seek(pos)
    file.each{|line| puts line}
    end_pos = file.pos
    file.close
  
    return end_pos
  end
  
  def tail_rotated_log
    return unless @offset && @rotated_log && File.exists?(@rotated_log)

    stat = File.stat(@rotated_log)
    if @offset[:ino] == stat.ino && @offset[:pos] < stat.size
      end_pos = tail_print(@rotated_log, @offset[:pos])
      save_offset(stat.ino, end_pos)
    end
  end

  def tail_log
    return unless @log && File.exists?(@log)

    stat = File.stat(@log)
    if @offset && @offset[:ino] == stat.ino
      if @offset[:pos] < stat.size
        start_pos = @offset[:pos]
      elsif @offset[:pos] > stat.size
        save_offset(stat.ino, stat.size)
        return
      else
        return
      end
    else
      start_pos = 0
    end
    end_pos = tail_print(@log, start_pos)
    save_offset(stat.ino, end_pos)
  end

  def execute 
    tail_rotated_log
    tail_log
  end
end
  
LogTail.new(log, options).execute


muninやnagiosとかと連携して、各種ログの監視、集計結果のグラフ化などに使えます。
例えばこういうふうにApacheの平均レスポンスタイムをmuninでグラフ化したい場合、


httpd.confのLogFormatにレスポンスタイムを追加して、

  # %Dを追加
  LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %D" combined

 # cronologやrotatelogsで日付ファイルに出力してる場合
  CustomLog "|/usr/sbin/cronolog -S /var/log/httpd/access_log.latest /var/log/httpd/access_log.%Y%m%d" combined


こういうmuninプラグインを作ればOKです

#!/bin/bash

today=$(date +%Y%m%d)
yesterday=$(date +%Y%m%d -d "1 days ago")
log=/var/log/httpd/access_log.$today
rotated_log=/var/log/httpd/access_log.$yesterday
offset_file=/tmp/munin_app_response_time.logtail.offset

if [ "$1" = "config" ]; then
   echo 'graph_title response time'
   echo 'graph_args --base 1000 -l 0'
   echo 'graph_vlabel msec'
   echo 'graph_scale no'
   echo 'graph_category app'
   echo 'avg.label avg'
   exit 0
fi

# TODO:muninをがしばらく停止すると次回実行時に大きな値が出てしまうので、オフセットファイルのタイムスタンプをチェックする

if [ -e "$offset_file" ];then
  avg_msec=$(/usr/local/bin/logtail.rb $log -r $rotated_log -o $offset_file | perl -alne '$c++;$s+=$F[-1];END{print int(($s/$c)/1000)}')
else
  # 初回は集計せずに捨てる
  /usr/local/bin/logtail.rb $log -r $rotated_log -o $offset_file > /dev/null
  avg_msec='N'
fi
echo "avg.value $avg_msec"


仕組みや実装上、あまり厳密な集計には向かないですがこういうグラフ作成とかログ監視には充分かな

CLIでNagiosのアラートを停止する

先日、こんなことをつぶやいたところ、

@hirose31さんから親切なアドバイスをいただきました。

使ってみるとなかなか便利だったので紹介しておきます!

Nagiosの外部コマンド機能について

名前付きパイプにコマンドを書き込むことで、Nagiosに命令を送る機能があるようです。
『外部コマンド』
コマンドのリストや書式、設定については上記リンクで確認して下さい。


EPELからインストールしたNagiosだとデフォルトで有効化されていました。

# nagios.cfg
check_external_commands=1
command_check_interval=-1
command_file=/var/spool/nagios/cmd/nagios.cmd
external_command_buffer_slots=4096


というわけで、特定ホストのサービス監視のアラート停止は以下のコマンドでできます。

[root@nagios01 ~]# echo "[$(date +%s)] DISABLE_HOST_SVC_NOTIFICATIONS;test01" > /var/spool/nagios/cmd/nagios.cmd

バッチリ通知が止まりました!!!

もどしはENABLE_HOST_SVC_NOTIFICATIONS、ホスト監視も停止する場合はDISABLE_HOST_NOTIFICATIONSを合わせて使います。
簡単ですね!!!

せっかくなので複数ホストのアラートを完全に無効化するスクリプトを作ってみました

ソースコード

#!/bin/bash

action=$1;shift
hosts=$@
command_file="/var/spool/nagios/cmd/nagios.cmd"

if [ "$action" = start ];then
  commands="ENABLE_HOST_NOTIFICATIONS ENABLE_HOST_SVC_NOTIFICATIONS"
elif [ "$action" = stop ];then
  commands="DISABLE_HOST_NOTIFICATIONS DISABLE_HOST_SVC_NOTIFICATIONS"
else
  exit
fi

now=$(date +%s)
for host in $hosts;do
  for command in $commands;do
    echo "[$now] $command;$host"
  done
done > $command_file

※エラー処理を追加したものをここにアップしました


使い方はこんな感じです

[root@nagios01 ~]# ./nagios_alert.sh stop test01 test02
[root@nagios01 ~]# ./nagios_alert.sh start test01 test02


(゚∀゚)キタコレ!!


これで作業がちょっと楽になりそうです。
@hirose31さん、ありがとうございました!

『qpstudy3周年記念LT大会』でLTしてきた!

『qpstudy3周年記念LT大会 〜新人さん、業界にようこそ!〜 with ビール』に参加&LTしてきました!


14時から始まって簡単なメインセッションの後、20人くらいのLTが19時近く?まで!
いやー、さすがに飲み疲れましたw


自分のLT資料です


今回のやりたかったのはこのへんでした

  • なにか新人さんが参考になる話をする
  • ライブオペレーション

ライブオペレーションについては、開発系の勉強会の『ライブコーディング』と同じ感じで、

  • みんなでだれかのオペレーションを見て
  • それについていろいろ議論する

と楽しいかなーと。
初挑戦だったので、テキパキ行かずに何やってるかうまく伝えられなかったですが、なかなか楽しかったのでまたやりたいと思います!


あとはライブオペレーションでやったことについて、ザッと解説しておきます!

ライブオペレーションの手順書

お題は趣味構築中のサーバ管理Railsアプリです。
サーバ、ミドルウェア、アプリの構築、外部公開まで一通りのオペレーションをやりました。


さっき通しでやってみたら全部で10分くらいでした。
※発表の時は、時間のかかるとこは先に完了済みサーバを準備しておいて切り替える、QP3分クッキング方式を使いました。

VM作成

シェルスクリプト叩くだけです(細かい仕組みは後で補足解説します)

ssh vm-host02
sudo su -
sh vm_create.sh qpstudy01 192.168.1.151

インストーラが起動してOSがインストールされます

ローカルDNSの登録、OSの初期設定、chef実行準備もついでに完了しちゃいます

OS設定、ミドルウェアのセットアップ

サーバが起動したらchef-soloを実行してミドルウェアをセットアップします

ssh qpstudy01
sudo su - sysadmin
vim ~/chef/nodes/qpstudy01.json
sudo /home/sysadmin/.rbenv/shims/chef-solo -c ~/chef/solo/solo.rb -j ~/chef/nodes/qpstudy01.json

今回のnode定義JSONはこんな感じ

{
  "run_list": [
    "role[default]",
    "recipe[rbenv::mikeda]",
    "recipe[mysql::server]"
  ]
}

このへんが実行されます。

  • 共通設定(OSの初期設定、管理/監視ツールのインストールなど)
  • ruby(rbenv)のセットアップ
  • MySQLのセットアップと起動

※問題なければjsonファイルをgithubにpushしちゃいますが、そのへんの運用はknife-solo的な中央管理に見直し検討中です。


自宅環境で使っているcookbookはgithubで公開してるので、細かいところはそっちを見て下さい。
https://github.com/mikeda/chef

アプリの構築と起動

githubからアプリをcloneしてセットアップします。

ssh qpstudy01
git clone https://github.com/mikeda/sabakan
cd sabakan

# 依存gemのインストール
bundle install

# DBのセットアップ
bundle exec rake db:create
bundle exec rake db:migrate

# サーバ起動
# 今回はwebrickで起動します。(ちゃんと外部公開する時はunicornとかpassengerを使いましょう)
bundle exec rails server
外部DNSドメイン登録

Route53使ってるので、AWSのManagementConsoleから登録します

外部公開用リバースプロキシの設定

登録したドメインへのアクセスを、構築したサーバに転送するように設定します。

ssh proxy01
sudo su -
vim /etc/nginx/conf.d/qpstudy.conf
/etc/init.d/nginx configtest
/etc/init.d/nginx reload

今回のコンフィグはこんな感じです

server {
    server_name  qpstudy.mikeda.jp;
    access_log  /var/log/nginx/qpstudy.mikeda.jp_access.log  main;

    location / {
        proxy_pass   http://192.168.1.151:3000;

        proxy_set_header        Host                $host;
        proxy_set_header        X-Real-IP           $remote_addr;
        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;
    }
}


これで完了!外部のブラウザからアクセスできるようになります。

簡単ですねー
(本番ではプロキシ設定ミスって502エラーでしたw)

VM作成部分の補足解説

VM作成スクリプトの中身はこんな感じです

#!/bin/bash

HOSTNAME=$1
IP=$2
LOCATION="http://192.168.1.110/mrepo/centos6-x86_64/disc1/"
TYPE=centos6_kvm
VCPUS=1
RAM=1024
DISK=5

IMG=/data/vm/${HOSTNAME}.img

if [ -f "$IMG" ];then
  echo already exists
  exit 1
fi

### PowerDNSにレコード登録
mysql -umikeda -pmikedapass -hdns01 pdns -e "INSERT INTO records (domain_id, name, content, type, ttl, prio) VALUES (1, '${HOSTNAME}.mikeda.jp', '${IP}', 'A', 60, NULL);"

### VMイメージファイル作成
dd if=/dev/zero of=$IMG bs=1MiB count=`expr $DISK \* 1024`

### VMインストール
virt-install  --connect qemu:///system \
  --name $HOSTNAME \
  --ram $RAM \
  --vcpus=$VCPUS \
  --disk path=$IMG \
  --os-type=linux \
  --os-variant=virtio26 \
  --accelerate \
  --nographics \
  --location="${LOCATION}" \
  --extra-args="ks=http://192.168.1.110/ks/gen/ks.php?cfg=${TYPE}.cfg+${HOSTNAME}+${IP} console=tty0 console=ttyS0,115200n8"

内部DNSサーバ(MySQLバックエンドのPowerDNS)にホストを登録するのも合わせてやっています。


virt-installで指定するkickstartファイルは動的に生成しています。
このPHPの中身はこうなってます。

<?php
list($cfg, $hostname, $ip) = explode(" ", $_GET["cfg"]);

$ks_config = file_get_contents($cfg);
echo preg_replace(
  "/^network.*/m",
  "network --device eth0 --bootproto static "
    . "--ip $ip --netmask 255.255.255.0 --gateway 192.168.1.1 "
    . "--nameserver 192.168.1.103 "
    . "--hostname $hostname",
  $ks_config
);

ベースのkickstartファイルのnetwork行を書き換えてるだけですね。
URLを叩くと、

curl 'http://192.168.1.110/ks/gen/ks.php?cfg=centos6_kvm.cfg+qpstudy01+192.168.1.151'

こういうkickstartファイルが帰ってきます

install
text
url --url=http://192.168.1.110/mrepo/centos6-x86_64/disc1/
lang ja_JP.UTF-8
keyboard jp106
network --device eth0 --bootproto static --ip 192.168.1.151 --netmask 255.255.255.0 --gateway 192.168.1.1 --nameserver 192.168.1.103 --hostname qpstudy01
rootpw  --iscrypted XXXXXXXXXXXXXXXXXX
firewall --service=ssh
authconfig --enableshadow --passalgo=sha512
selinux --disabled
timezone Asia/Tokyo
reboot

zerombr
bootloader --location=mbr --driveorder=vda --append="console=ttyS0,115200n8"
clearpart --all --initlabel --drives=vda
part /boot --fstype ext4 --size=100 --ondisk=vda
part swap --size=1024
part / --fstype ext4 --size=1 --grow

%packages
@base
@core
@debugging
@development
@japanese-support
@performance
@server-policy
sgpio
systemtap-client
telnet
openssl-devel
readline-devel
zlib-devel
nc

%pre
#/bin/sh
/usr/bin/wget http://192.168.1.110/setup/os_setup_centos6.sh -O /tmp/os_setup_centos6.sh

%post
%include /tmp/os_setup_centos6.sh


kickstartの%postで%includeしているスクリプトで、最低限のOS設定とchefのセットアップを実施しています。

#!/bin/sh

### ユーザ追加
useradd -u 999 sysadmin
useradd -u 1000 mikeda
sed -i 's|^mikeda:.*$|mikeda:$6$BovFTzjb$Eoa5XXXXXXXwK5e1:15627:0:99999:7:::|' /etc/shadow

mkdir /home/mikeda/.ssh
cat > /home/mikeda/.ssh/authorized_keys <<END
ssh-rsa AAAAB3NzaCXXXXXXXXXXX78rrAJbw== mikeda
END
chown -R mikeda. /home/mikeda/.ssh
chmod 700 /home/mikeda/.ssh
chmod 600 /home/mikeda/.ssh/*

### sudo設定
sed -i '/^Defaults *requiretty/s/^/#/' /etc/sudoers
echo 'mikeda ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
echo 'sysadmin ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers

### リゾルバ設定
cat <<END >/etc/resolv.conf
nameserver 192.168.1.103
nameserver 192.168.1.1
search mikeda.jp
END

### sshd設定
sed -i 's/#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
echo "sshd:192.168.1.0/255.255.255.0,127.,LOCAL" >> /etc/hosts.allow
echo "sshd:ALL" >> /etc/hosts.deny

### iptablesの停止
chkconfig ip6tables off
chkconfig iptables off

### 不必要なservice停止
chkconfig atd off
chkconfig auditd off
chkconfig autofs off
chkconfig cups off
chkconfig smartd off

### IPV6無効化。Railsがエラーになるのでいったん完全無効化してない
#cat <<END >> /etc/modprobe.d/ipv6.conf
#options ipv6 disable=1
#END
echo 'NETWORKING_IPV6=no' >> /etc/sysconfig/network

### SELinuxをdisる
sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config

### 時刻合わせ。意味ないかも。
/usr/sbin/ntpdate 210.173.160.27

### YUMレポジトリの追加
# EPEL
rpm -ivh http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

# mrepoで作成したローカルレポジトリの追加
cat > /etc/yum.repos.d/local.repo <<'END'
[freeze]
name=CentOS-$releasever - Freeze
baseurl=http://192.168.1.110/mrepo/centos6-x86_64/RPMS.freeze/
gpgcheck=0
enabled=1

[test]
name=CentOS-$releasever - Freeze
baseurl=http://192.168.1.110/mrepo/centos6-x86_64/RPMS.test/
gpgcheck=0
enabled=0
END

### 必須パッケージのインストール
yum -y install git

### chef-clientのセットアップ。RPM版に変えるかも
cat  > /tmp/install_chef-client.sh <<'END'
wget http://192.168.1.110/files/ruby/rbenv_chef_centos6.tgz -P /tmp/
tar xzf /tmp/rbenv_chef_centos6.tgz
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
git clone git://github.com/mikeda/chef.git
END
chmod 755 /tmp/install_chef-client.sh
su - sysadmin -c /tmp/install_chef-client.sh

完了すると自動でサーバが再起動されて、sshでログインできるようになってます。

[mikeda@admin02 ~]$ ssh qpstudy01
Warning: Permanently added 'qpstudy01,192.168.1.151' (RSA) to the list of known hosts.
[mikeda@qpstudy01 ~]$ 

まとめ

みんなライブオペレーションやろうよ!

rsyslogでNW機器のログ集約

NW機器のログはsyslogサーバに集約してWEBで見れるようにしてます
理由はこのへんです。

  • いちいちログインして見るのがダルい
  • 障害時にログインして確認しようにも入れないことが多い(だってNW機器ですもん)
  • 仕方ないので再起動するとログ消えちゃう

見ためはこんな感じ(サンプル

サーバ一覧

日付ごとのログ一覧

ログの中身


ずっとsyslog-ng派だったのですが、時代の流れに逆らえずrsyslogを使ってみたので手順をメモっておきます。
環境はCentOS6 & rsyslog5.8です。

手順

/etc/rsyslog.confを編集
# モジュール読み込み。UDP追加
$ModLoad imuxsock
$ModLoad imklog
$ModLoad imudp

# Apacheユーザで読めるようにパーミッション設定
$umask 0000
$DirCreateMode 0755
$FileCreateMode 0644

# ログのフォーマット、パス定義
$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
$template RemoteLog, "/var/log/remote/%hostname:::secpath-replace%/%hostname:::secpath-replace%_%$year%%$month%%$day%.log" 

# ローカルログ用のルール設定
$RuleSet local
*.info;mail.none;authpriv.none;cron.none                /var/log/messages
authpriv.*                                              /var/log/secure
mail.*                                                  -/var/log/maillog
cron.*                                                  /var/log/cron
*.emerg                                                 *
uucp,news.crit                                          /var/log/spooler
local7.*                                                /var/log/boot.log

# リモートsyslog用のルール設定
$RuleSet remote
*.* ?RemoteLog

$DefaultRuleset local

# リモートsyslogの受信設定
$AllowedSender UDP, 127.0.0.0/8, 192.168.0.0/16
$InputUDPServerBindRuleset remote
$UDPServerRun 514
rsyslog再起動
# /etc/init.d/rsyslog restart
Apache設定
# yum install httpd
# vi /etc/httpd/conf.d/remote_log.conf 
Alias /remote_log/ /var/log/remote/
<Directory /var/log/remote/>
  Order deny,allow
  Deny from all
  Allow from 127.0.0.0/8
  Allow from 192.168.0.0/16
  Options +Indexes
</Directory>
# /etc/init.d/httpd start

という感じでサクッとでけました。


rsyslog使ってみた感想

慣れの問題が大きいと思いますが、ちょっと設定がわかりづらく感じました。
・同じ事をするための書式がいろいろある
・各設定の境目とつながりがよくわからない

syslog-ngで同じことをやろうとするとこんな設定になると思います(最後の3行以外ほぼデフォルト)

options {
        sync (0);
        time_reopen (10);
        log_fifo_size (1000);
        long_hostnames (off);
        use_dns (yes);
        use_fqdn (no);
        create_dirs (yes);
        keep_hostname (no);
        stats(86400);
        dir_perm(0755);
        perm(0644);
        owner(root);
        group(root);
};

### syslogdのデフォルト設定設定を引き継ぐ
source s_sys {
        file ("/proc/kmsg" log_prefix("kernel: "));
        unix-stream ("/dev/log");
        internal();
        # udp(ip(0.0.0.0) port(514));
};

destination d_cons { file("/dev/console"); };
destination d_mesg { file("/var/log/messages"); };
destination d_auth { file("/var/log/secure"); };
destination d_mail { file("/var/log/maillog" sync(10)); };
destination d_spol { file("/var/log/spooler"); };
destination d_boot { file("/var/log/boot.log"); };
destination d_cron { file("/var/log/cron"); };
destination d_kern { file("/var/log/kern"); };
destination d_mlal { usertty("*"); };

filter f_kernel     { facility(kern); };
filter f_default    { level(info..emerg) and
                        not (facility(mail)
                        or facility(authpriv)
                        or facility(cron)); };
filter f_auth       { facility(authpriv); };
filter f_mail       { facility(mail); };
filter f_emergency  { level(emerg); };
filter f_news       { facility(uucp) or
                        (facility(news)
                        and level(crit..emerg)); };
filter f_boot   { facility(local7); };
filter f_cron   { facility(cron); };

#log { source(s_sys); filter(f_kernel); destination(d_cons); };
log { source(s_sys); filter(f_kernel); destination(d_kern); };
log { source(s_sys); filter(f_default); destination(d_mesg); };
log { source(s_sys); filter(f_auth); destination(d_auth); };
log { source(s_sys); filter(f_mail); destination(d_mail); };
log { source(s_sys); filter(f_emergency); destination(d_mlal); };
log { source(s_sys); filter(f_news); destination(d_spol); };
log { source(s_sys); filter(f_boot); destination(d_boot); };
log { source(s_sys); filter(f_cron); destination(d_cron); };


### リモートsyslog集約用設定
source s_remote { udp(ip(0.0.0.0) port(514)); };
destination d_remote { file("/var/log/remote/$HOST/$HOST.$YEAR$MONTH$DAY"); };
log { source(s_remote); destination(d_remote); };

syslog-ngの『source、destination、filterを定義→logでくっつける』書式のほうが、

  • 一貫性があって
  • 設定の境目やつながりがわかりやすい

と思うのですがどうでしょうか。