cgroupをCentOSで使う覚書

【注意】以下に書いてある具体例を行うとメモリとスワップが枯渇して操作不能になることがあるので自己責任でお願いします!どうでもいいマシンorVMで試してみてください。私はこれをVMで行い、sshで入れなくなりたいへん困りました【注意】

cgroupの使い方@CentOSの覚書です。cgroupを適用させる方法はいくつかあるんですが(たとえば起動中のプロセスに対してコマンドで適用するなど)、以下の方法がもっとも分かりやすいかと思います。

リソースポリシーとしてcgconfig.confを編集

適用先としてcgrules.confを編集

cgconfigでリソースポリシーをファイルシステムに書き込んで、プロセス監視のcgredを起動

という流れです。

とは言え少々分かりにくいので、pidを指定して走らせるとその子プロセスまで見ていって制限できるようなコマンドが欲しいですね。

具体的な作業方法 (CentOSを前提としています)

# libcgroupをインストール (入っていなければ)
$ yum install libcgroup
$ yum install libcgroup-tools

# 次のようにcgconfig.confを編集
# cpu20grp → CPU20%使えるグループ
# mem300mbgrp → メモリ300MB、スワップ200MBの合計500MB使えて、OOM Killer無効のグループ
# という意味
$ cat /etc/cgconfig.conf 
mount {
	cpuset	= /cgroup/cpuset;
	cpu	= /cgroup/cpu;
	cpuacct	= /cgroup/cpuacct;
	memory	= /cgroup/memory;
	devices	= /cgroup/devices;
	freezer	= /cgroup/freezer;
	net_cls	= /cgroup/net_cls;
	blkio	= /cgroup/blkio;
}

group cpu20grp {
	cpu {
		cpu.cfs_quota_us = 20000;
	}
}

group mem300mbgrp {
	memory {
		memory.limit_in_bytes = 300M;
		memory.memsw.limit_in_bytes = 500M;
		memory.oom_control = 1;
	}
}

# 次のようにcgrules.confを編集
# ユーザ名 コントローラ名 グループ名/
# という形式。
# 今回ユーザ名はkawaとなっている (この部分は、環境に合わせてユーザ名を書いて下さい)
# 「%」は同上の意味 (同じuserを2つ書くと無効のよう)
$ cat /etc/cgrules.conf 
kawa cpu cpu20grp/
% memory mem300mbgrp/

# cgconfigとcgredを起動
# cgconfig: cgconfig.confの内容をシステムに反映させるデーモン
# cgred: プロセスを監視して、cgrules.confに記述されたものを反映させる
$ /etc/init.d/cgconfig restart
Stopping cgconfig service:                                 [  OK  ]
Starting cgconfig service:                                 [  OK  ]

$ /etc/init.d/cgred restart
CGroup ルールエンジンデーモンを停止中...                   [  OK  ]
Starting CGroup Rules Engine Daemon:                       [  OK  ]

# 該当ユーザーで負荷のかかるプロセスを実行してみる
$ su kawa
$ yes > /dev/null

# CPU使用率を確認 → 20%程度になっている
$ top

# 制御下にあるプロセス一覧を表示・確認
$ cat /cgroup/cpu/cpu20grp/tasks
26868
26869
26982
26983
27295
27350

# 該当ユーザーでメモリを使うプロセスを実行してみる
# → 別のタブでdstat -ms とすると、450MB程度確保したところで止まる
# → それは、メモリとスワップの合計500MBを確保してしまい、cgroupの影響でそれ以上取れないため
$ gcc memeater.c
$ ./a.out

memeater.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#define MSIZE 100 // Memory size per sec. 50 means 50MB/sec
#define MB 1024*1024

int main(int argc, char *argv[])
{
  char *p;
  int malloc_size = MSIZE * MB;
  int sum = 0;

  printf("This program just eats memory.\n");
  while(1)
  {
    p = (char *)malloc(malloc_size);
	memset(p, 0, malloc_size);
	sum += MSIZE;
	printf("%5d MB\n", sum);
	sleep(1);
  }
  return 0;
}

以下追記@2015-01-15

おまけ: 特定のシェルスクリプトに対してリソース制御

運用を考えると、特定のシェルスクリプトに対して自動的にリソースが制御されると嬉しいですね。具体的に言えば、hogeというシェルスクリプトのリソース制御を/etc/cgrules.confに書いておけば、hogeを走らせた時に自動的にリソース制御してくれる、という感じです。

試しにやってみたら出来たので書いておきます。任意のスクリプトに対して制限を掛けることができるので、そこそこ便利だと思います。

# 以下のようにCPU利用率を20%に制限するグループを作成
$ vi /etc/cgconfig.conf
$ cat /etc/cgconfig.conf 
mount {
	cpuset	= /cgroup/cpuset;
	cpu	= /cgroup/cpu;
	cpuacct	= /cgroup/cpuacct;
	memory	= /cgroup/memory;
	devices	= /cgroup/devices;
	freezer	= /cgroup/freezer;
	net_cls	= /cgroup/net_cls;
	blkio	= /cgroup/blkio;
}

group cpu20grp {
	cpu {
		cpu.cfs_quota_us = 20000;
	}
}

# 以下のようにcgrules.confを編集 (意味としては、rootユーザーで実行するhogeという名前のシェルスクリプトに対して、cpu使用率20%の制限を掛ける)
$ vi /etc/cgrules.conf
$ cat /etc/cgrules.conf 
root:hoge cpu cpu20grp/

$ cgconfigとcgredを再起動して上記の設定を反映
$ /etc/init.d/cgconfig restart
Stopping cgconfig service:                                 [  OK  ]
Starting cgconfig service:                                 [  OK  ]
$ /etc/init.d/cgred restart
CGroup ルールエンジンデーモンを停止中...                   [  OK  ]
Starting CGroup Rules Engine Daemon:                       [  OK  ]

# 次のようなシェルスクリプトを作る
$ cat hoge 
#!/bin/bash

yes 'LOOP' | cat > /dev/null

# 実行権限を付加
$ chmod +x hoge

# 実行してみる
$ ./hoge

# 別のttyでtop or htopしてCPU使用率を見てみる
# → yesとcatの合計で20%程度のCPU使用率となっている
$ top

# cgredを停止してみる
$ /etc/init.d/cgred stop
CGroup ルールエンジンデーモンを停止中...                   [  OK  ]

# 再度実行してみる (リソース制御されていないので100%の使用率になるはず)
$ ./hoge

# 注意点
# プロセスを起動させてからcgredをstartした場合、そのプロセスに対しては制御が行われるが、その子プロセスに対しては制御が及ばない!
# そのため、子プロセスに対しても制御したい場合は事前にcgrules.confに書いて、cgredを再起動させてから実行する必要がある

追記@2015-02-01

Tipsを書いておきます。

☆ Debianではcgroupのmemoryがデフォルトでは無効になっているらしい
ソースはここ→ http://stackoverflow.com/questions/21337522/trying-to-use-cgroups-in-debian-wheezy-and-no-daemons

☆ $ cgconfigparser -l /etc/cgconfig.conf すると、「Loading configuration file /etc/cgconfig.conf failed Cgroup, operation not allowed」と怒られる
→ service cgconfig restart で行ったほうが良さそう。どうやら正しいコンフィグファイルでも上記のエラーメッセージが出るようだ(出典: http://sourceforge.net/p/libcg/mailman/message/27906594/にて、「If we run the cgconfigparser manually, it fails alternatively. It is a little
weird behaviour. The problem surfaces when there is some cgroup already
mounted.」とある)。service cgconfig restartして怒られなければOKと考えて良いだろう。きちんと出来ているかはless /sys/fs/cgroup/〜あたりを見てみると良い。

☆ Debian系では、memory.memsw.limit_in_bytesを設定するとservice cgconfig restart がコケる。↑のメモリの問題かもしれない。。。memory.limit_in_bytesは設定しても大丈夫だった。

http://www.usupi.org/sysad/228.html←大変参考になります。$lssubsysや、$lssubsys -a、cgconfigのmountグループに書き込んでからlssubsysすると増えるとかなるほどです。

☆ RHEL系の方がDebian系よりやれることが多いしきちんと動く傾向。PDF注意だがこことか→Control Groups(cgroups) の概要 – Fedora People

CentOS 6.4 で ufw を導入

Debian/Ubuntuのufwにすっかり慣れて、CentOS系のiptables直書きは辛くなってきたので、CentOSにufwを導入してみました。

CentOS 5.8 – ufwでファイアーウォールの設定


↑だいたいこの通りやったら出来ました。ありがたし。

作業ログ(rootで作業を想定)↓

# cd /usr/local/src/
# wget https://launchpad.net/ufw/0.33/0.33/+download/ufw-0.33.tar.gz
# tar zxvf ufw-0.33.tar.gz 
# cd ufw-0.33
# /usr/bin/python ./setup.py install
# chmod -R g-w /etc/ufw /lib/ufw /etc/default/ufw /usr/sbin/ufw
# ufw status
# ufw reset
# ufw default deny
# ufw allow 22
# ufw allow 443
# ufw enable
# ufw status

CentOS、Nginx、php-fpm、git-hookの設定

表題のものを動かしたかったわけです。割とはまったので記録しておきます。全てのコマンドはrootで行なってください。

nginxのルートディレクトリは、/var/www/nginx/としました。nginxの設定で、fastcgi_paramについて、$document_rootを使いたかったのですが使ってみるとうまくいかないので諦めて直書きです。悲しい。

portは8765としました。ドメインがexample.comとなっていますが、自身のドメインに書き換えてください。

$ yum update
$ yum install nginx php-fpm.x86_64
$ chkconfig nginx on
$ chkconfig php-fpm on
$ vi /etc/nginx/conf.d/default.conf

/etc/nginx/conf.d/default.conf
#
# The default server
#
server {
    listen       8765;
    server_name  example.com;
        #root    /var/www/nginx;
    #charset koi8-r;

    #access_log  logs/host.access.log  main;

    location / {
        #root   /usr/share/nginx/html;
                root    /var/www/nginx;
        index  index.html index.htm;
    }

    error_page  404              /404.html;
    location = /404.html {
        root   /usr/share/nginx/html;
    }

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        root           html;
        fastcgi_pass   127.0.0.1:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /var/www/nginx$fastcgi_script_name;
        include        fastcgi_params;
    }

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

/var/lib/hogeにいろいろなPHPのファイルを入れるとして(git絡みで直接/var/www/nginx/hogeには出来ない)のディレクトリのシンボリックリンクを貼ります。「/」のあるなしで変わってくるので注意です。
$ ln -s /var/lib/hoge /var/www/nginx/hoge

動かないというときは、
– nginxやphp-fpmが立ち上がっているか($netstat -natpで確認可能です)
– iptablesでnginxのポートを開けているか
– /var/log/nginx/error.log を見る
– ディレクトリやファイルのパーミションの確認
あたりをしてみてください。

加えてgit-hookの設定です。はまりどころ多数ですので記録厨になります。この通り↓やったら出来ました。

git pushで本番環境にデプロイ


なにをしたいかと言えば、
– ローカルにあるレポジトリhogeを、gitサーバー兼検証サーバー(Aサーバーとする)にgitでpush
– Aサーバー、ユーザーgituserで↑を受け取り、git-hookで/var/lib/hoge にpush
– /var/www/nginx/ に、/var/lib/hogeのシンボリックリンクを貼る
という行為です。いろいろとやってみましたがうまく行きませんでしたが↑のリンクでうまくいきました。具体的な作業ログを残しておきます。

$ su root
$ cd /var/lib
$ git clone /home/gituser/repositories/hoge.git hoge
$ chown -R gituser:gituser hoge/
$ ln -s /var/lib/hoge /var/www/nginx/hoge
$ su gituser
$ vi /home/gituser/repositories/hoge-app.git/hooks/post-receive
$ chmod 700 /home/gituser/repositories/hoge-app.git/hooks/post-receive
$ cat /home/gituser/repositories/hoge-app.git/hooks/post-receive
#!/bin/sh
cd /var/lib/hoge && git --git-dir=.git pull

CentOSやMacで “Can’t locate JSON.pm in @INC” と怒られるときの対処

CentOSでコマンドラインでPerlを使ってJSONを見やすい形にしたいと思って、↓としてみたら、

$ echo '{"hoge":"fuga", "piyo" : {"fizz": "buzz"}}' | perl -MJSON -E "say to_json(decode_json(<>), { pretty => 1 });"
Can't locate JSON.pm in @INC (@INC contains: /usr/local/lib64/perl5 /usr/local/share/perl5 /usr/lib64/perl5/vendor_perl /usr/share/perl5/vendor_perl /usr/lib64/perl5 /usr/share/perl5 .).
BEGIN failed--compilation aborted.
と怒られた。

単純に、PerlのJSONライブラリが入っていなかっただけらしい。
$ sudo yum search perl json
Loaded plugins: fastestmirror, presto
Loading mirror speeds from cached hostfile
 * base: ftp.iij.ad.jp
 * extras: ftp.iij.ad.jp
 * updates: ftp.iij.ad.jp
================================== N/S Matched: perl, json ==================================
perl-JSON.noarch : Parse and convert to JSON (JavaScript Object Notation)

  Full name and summary matches only, use "search all" for everything.

$ sudo yum install perl-JSON

中略

Installed:
  perl-JSON.noarch 0:2.15-5.el6                                                              

Dependency Installed:
  perl-Compress-Raw-Zlib.x86_64 1:2.020-127.el6 perl-Compress-Zlib.x86_64 0:2.020-127.el6   
  perl-HTML-Parser.x86_64 0:3.64-2.el6          perl-HTML-Tagset.noarch 0:3.20-4.el6        
  perl-IO-Compress-Base.x86_64 0:2.020-127.el6  perl-IO-Compress-Zlib.x86_64 0:2.020-127.el6
  perl-libwww-perl.noarch 0:5.833-2.el6        

Complete!

これでインストール終了。再度挑戦↓
$ echo '{"hoge":"fuga", "piyo" : {"fizz": "buzz"}}' | perl -MJSON -E "say to_json(decode_json(<>), { pretty => 1 });"
{
   "piyo" : {
      "fizz" : "buzz"
   },
   "hoge" : "fuga"
}

↑できた。

Macの場合は、sudo port として(つまりMacPortsで)、↓とする。
[Users/piyo] > install p5-json
--->  Computing dependencies for p5-json
--->  Dependencies to be installed: p5.12-json
--->  Fetching archive for p5.12-json
--->  Attempting to fetch p5.12-json-2.530.0_2.darwin_10.noarch.tbz2 from http://packages.macports.org/p5.12-json
--->  Attempting to fetch p5.12-json-2.530.0_2.darwin_10.noarch.tbz2.rmd160 from http://packages.macports.org/p5.12-json
--->  Installing p5.12-json @2.530.0_2
--->  Activating p5.12-json @2.530.0_2
--->  Cleaning p5.12-json
--->  Fetching archive for p5-json
--->  Attempting to fetch p5-json-2.530.0_2.darwin_10.noarch.tbz2 from http://packages.macports.org/p5-json
--->  Attempting to fetch p5-json-2.530.0_2.darwin_10.noarch.tbz2.rmd160 from http://packages.macports.org/p5-json
--->  Installing p5-json @2.530.0_2
--->  Activating p5-json @2.530.0_2
--->  Cleaning p5-json
--->  Updating database of binaries: 100.0%
--->  Scanning binaries for linking errors: 100.0%
--->  No broken files found.

Redmineのファイルの項目で、コメントを表示する

環境:

$ cat /etc/redhat-release 
CentOS release 6.3 (Final)

$ rails -v
Rails 3.2.8

$ ruby -v
ruby 1.9.3p286 (2012-10-12 revision 37165) [x86_64-linux]

$ cat /var/lib/redmine/lib/redmine/version.rb 
require 'rexml/document'

module Redmine
  module VERSION #:nodoc:
    MAJOR = 2
    MINOR = 1
    TINY  = 2

# => Redmine 2.1.2 stable

Redmineは便利なことにファイルのやりとりに使える。HTTPSで運用すればそれなりにセキュアだしお手軽である。

さっそく使ってみた。

だけどアップロードの際にコメント欄が存在するにも関わらず、アップロードしてから見てみるとコメントが表示されない。リンクの上にマウスポインタを載せると数秒して表示されるけれどもUI的にゴミクズ。最初から表示汁。

Redmineがどういう構造になっているのかまったく知らないので、プラグインを入れるのが楽だろうと思ってあれこれ探ってみたがソースをいじる他ないようである。

端的に言えば、
vi /var/lib/redmineapp/views/files/index.html.erb

として、↓の2行を追加である。
<%= sort_header_tag('description', :caption => l(:field_description)) %>
と
<%= file.description %>

具体的な場所は、↓。
index.html.erb
<div class="contextual">
<%= link_to(l(:label_attachment_new), new_project_file_path(@project), :class => 'icon icon-add') if User.current.allowed_to?(:manage_files, @project) %>
</div>

<h2><%=l(:label_attachment_plural)%></h2>

<% delete_allowed = User.current.allowed_to?(:manage_files, @project) %>

<table class="list files">
  <thead><tr>
    <%= sort_header_tag('filename', :caption => l(:field_filename)) %>

	<%= sort_header_tag('description', :caption => l(:field_description)) %>
	<!-- ↑この行を追加! -->

    <%= sort_header_tag('created_on', :caption => l(:label_date), :default_order => 'desc') %>
    <%= sort_header_tag('size', :caption => l(:field_filesize), :default_order => 'desc') %>
    <%= sort_header_tag('downloads', :caption => l(:label_downloads_abbr), :default_order => 'desc') %>
    <th>MD5</th>
    <th></th>
  </tr></thead>
  <tbody>
<% @containers.each do |container| %>
  <% next if container.attachments.empty? -%>
  <% if container.is_a?(Version) -%>
  <tr>
    <th colspan="6" align="left">
      <%= link_to(h(container), {:controller => 'versions', :action => 'show', :id => container}, :class => "icon icon-package") %>
    </th>
  </tr>
  <% end -%>
  <% container.attachments.each do |file| %>
  <tr class="file <%= cycle("odd", "even") %>">
    <td class="filename"><%= link_to_attachment file, :download => true, :title => file.description %></td>

    <td class="description"><%= file.description %></td>
	<!-- ↑この行を追加! -->

	<td class="created_on"><%= format_time(file.created_on) %></td>
    <td class="filesize"><%= number_to_human_size(file.filesize) %></td>
    <td class="downloads"><%= file.downloads %></td>
    <td class="digest"><%= file.digest %></td>
    <td align="center">
    <%= link_to(image_tag('delete.png'), attachment_path(file),
                                         :data => {:confirm => l(:text_are_you_sure)}, :method => :delete) if delete_allowed %>
    </td>
  </tr>
  <% end
  reset_cycle %>
<% end %>
  </tbody>
</table>

<% html_title(l(:label_attachment_plural)) -%>

そして、httpdの再起動。
service httpd restart

こんな感じになりました↓