読者です 読者をやめる 読者になる 読者になる

オープンソースこねこね

Webプログラミングなどについてあれこれ。

Mac OSX El Capitanにphpenvをいれる

phpenvをMacにいれました。PHPのビルド時にいろいろエラーが出てツライ目にあいました。いろいろやることあります。詳細は末尾の参考サイトを参照してください。ここにはやった結果だけ記述します。

依存物のインストール。

$ brew install re2c
$ brew install openssl
$ brew install bison
$ brew install libxml2

libzがエラーを出すので以下のコマンドを叩きます。

$ xcode-select --install

phpenv本体とphp-buildのインストール。phpenvは知名度の高いCHH/phpenvではなくてmadumlao/phpenvをいれます。こちらのほうがプロダクトとして高品質なので。

$ git clone https://github.com/madumlao/phpenv.git ~/.phpenv
$ git clone https://github.com/php-build/php-build.git ~/.phpenv/plugins/php-build

.zshrcにパスとか必要な環境変数の設定

export PATH="$HOME/.phpenv/bin:$PATH"
eval "$(phpenv init -)"

export PHP_BUILD_CONFIGURE_OPTS="--with-openssl=$(brew --prefix openssl) --with-libxml-dir=$(brew --prefix libxml2)"

シェルをリロードしてから、phpをインストールします。ひとまずphp.5.6のこの時点での最新版。

$ phpenv install 5.6.23 

インストールが成功したら標準で使うphpをそれに設定します。

$ phpenv global 5.6.23
5.6.23

バージョンを確認。

$ php -v
PHP 5.6.23 (cli) (built: Aug  4 2016 13:23:46) 
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
    with Zend OPcache v7.0.6-dev, Copyright (c) 1999-2016, by Zend Technologies
    with Xdebug v2.3.3, Copyright (c) 2002-2015, by Derick Rethans

参考サイト

SSL証明書の発行手順

Webサーバとかに配置するSSL証明書。毎度手作業なので自分用にメモ。

いつも「ドメイン名.拡張子」というファイル名で秘密鍵CSR、証明書を作っています。

作業手順

パスフレーズなしの秘密鍵を作成。

$ openssl genrsa -out example.co.jp.key 2048

秘密鍵を指定してCSRの作成。適宜項目を埋める。

$ openssl req -new -key example.co.jp.key -out example.co.jp.csr
Country Name (2 letter code) [GB]:JP
State or Province Name (full name) [Berkshire]:Tokyo
Locality Name (eg, city) [Newbury]:shinjuku-ku
Organization Name (eg, company) [Internet Widgits Pty Ltd]:your company name
Organizational Unit Name (eg, section) []: your section
Common Name (eg, your name or your server's hostname) []:example.co.jp
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

CSRから自己署名で証明書を作成(本番の時は認証局CSRを提出)。

$ openssl req -in example.co.jp.csr -key example.co.jp.key -x509 -out example.co.jp.crt

メモ

  • 更新時、鍵に変更がなければCSRは使いまわせる。
  • そろそろLet's Encryptについても触っておきたい。。。

CentOS用にSupervisor3.3.0のRPMパッケージを作成した

Pythonも同封した、他パッケージへの依存がないsupervisorのRPMパッケージをOmnibusで作成してみました。

github.com

背景

CentOSの各バージョン(5, 6, 7)でSupervisorを使いたい、のだが。。。

CentOSではepelのyumリポジトリからsupervisorのRPMパッケージが提供されているというので、調べてみたのですが、CentOS5と6だとかなりバージョンが古い。提供されているのはバージョン2.xで、今の最新は3.3.0。かといってpythonのpipを使って最新をシステムワイドのpythonパッケージとしてインストールするのはイヤだなあと。

システムへのソフトウェアインストールはyumrpmなどのプラットフォーム標準のパッケージ管理システムをなるべく使うというポリシーです。特にデーモンなどは、実用するならinitスクリプトやlogrotateの設定も合わせてインストールしなければならないし。

またSupervisorを動かすのにpythonが必要なのですが、これのバージョンがCentOS5,6,7それぞれ違っていて、しかもCentOS5はpython2.4。古!

要するに古いCentOSの運用はつらい。

とにかく、CentOS5,6,7の環境で同じバージョン(最新のバージョン)のSupervisorを動かしたい。そこでOmnibusです。

Omnibus

github.com

chef社が提供している、依存物をまるごと同封したパッケージをクロスプラットフォームで作ってくれるツールです。

これを利用しているソフトウェアは、有名ドコロだとchefやtd-agentなど。これらのソフトウェアはRubyで書かれているのですが、OmnibusでRubyのランタイムを同封したパッケージを作って、ユーザに提供しています。このためユーザは別途Rubyをインストールする必要がなく、複数のパッケージによる管理の複雑さや依存性の問題を気にする必要がなくなります。余談ですが、これでパッケージングされたchef-soloが提供された時、劇的にインストールが簡単になって大変便利になったと記憶しています。

さて、Omnibusの利用方法は公式リポジトリのREADMEと、td-agentなどの利用例を参考にすればいいと思います。というか、私自身がそのようにして作業してました。個人的に特に参考にさせていただいたリポジトリは以下。

Omnibusではソフトウェアコンポーネントという単位で、パッケージングするソフトウェアの情報を定義するのですが、幸いなことにGithubで検索すると、先人の方々がpythonのそれを作っていて、そのへんも参考にさせていただきました。また公式に主要なソフトウェアコンポーネントの設定が公開、共有されていて、これも簡単に利用できるようになっています。

とはいえ、CentOS5はOS自体がやや古いのでOmnibusに必要なパッケージが直接ダウンロードできなかったりして、いろいろワークアラウンドをいれたりしてます。詳細は最初に記載した私のリポジトリのソースを見ていただければと思います。

本来はRPMだけでなくdebパッケージなども作成できるのですが、今回は自分が使うRPMのみを作成するように設定しました。

細かい調整など

supervisordを起動するinitスクリプトは当初epelのsrc.rpmを解凍して、中に入っていたものを使用していたのですが、reload処理がstopしてstartするというrestartと同じものになっており、これはイケてないな、と。 調べていたらユーザによるinitスクリプト集がsupervisorのリポジトリにあったので、こちらを参考に修正したものを使いました。

GitHub - Supervisor/initscripts: User-contributed OS init scripts for Supervisor

積み残し

Omnibusで作成したパッケージはデフォルトで/opt/{PACKAGE_NAME}ディレクトリ配下に全ての依存物もまとめてイントールされます。これはこれでいいのですが、initスクリプトなど、このディレクトリ以外の領域にファイルを配置する場合が悩ましく、現状postinstというパッケージインストール後に実行されるスクリプトの設定を使ってcpコマンドでコピーするという方式をとっています。

cp -f /opt/supervisor/etc/init.d/supervisord /etc/init.d/supervisord

他の参考にさせていただいたリポジトリも私が調べた限りでは、同様の手法で対応しているのですが、この方法だと少し問題があって、パッケージがインストールしたファイル一覧を見るコマンドrpm -qlの結果にコピーしたファイルが表示されないのです。

$ rpm -ql supervisor
/opt
/opt/supervisor
/opt/supervisor/LICENSE
/opt/supervisor/LICENSES
/opt/supervisor/LICENSES/bzip2-LICENSE
/opt/supervisor/LICENSES/cacerts-README
...
... (/etc/init.d/supervisord は出てこない。。。)

カスタムのspecファイルを書いたり、いろいろ試行錯誤してみましたがうまくいかず、ベストな方法は今のところわかっていません。 これについては実用上、明らかな問題にはならないので、現状、課題として積んでおいています。

成果物

作成したRPMファイルはgithubのreleasesページにアップロードしました。

https://github.com/kohkimakimoto/omnibus-supervisor/releases

これで、インストールはyumコマンド一発でできるし、外部に依存関係をもたないので、必要なくなったら他への影響を考えずにサクッとアンインストールもできるようになりました。CentOSのバージョンも5~7まで対応。ただしちょっとファイルサイズは大きいです。

インストールして動作させてみましたが、ひとまず問題はなさそうです。

Goでプロビジョニングツールを作った

golang

GitHub - kohkimakimoto/cofu: Minimum configuration management tool written in Go.github.com

CofuというサーバプロビジョニングツールをGoで実装しました。Itamaeを参考に作りました。実装言語の違い(ItamaeはRubyによる実装)はありますが、外部仕様、内部実装、共にかなり似せて作ってあるので、ItamaeまたはItamaeが参考にしているchefを使ったことがあると、理解は簡単かと思います。特徴をざっくり説明すると、

  • ローカルでのプロビジョニングのみ対応。SSHなどでのリモートサーバのプロビジョニングはサポートしない
  • Goなので実行ファイル一個で動く。導入が簡単
  • レシピはLuaDSLで記述する
  • 今のところ動作プラットフォームはRedHat(CentOS)のみをサポート

あたりでしょうか。以下に軽く使い方を記載します。

インストール

ビルド済みバイナリがありますので

https://github.com/kohkimakimoto/cofu/releases

からファイルをダウンロードして解凍し、パスの通ったディレクトリに配置するだけでOKです。RPMパッケージも用意してありますが、これは/usr/bin/cofuをインストールしているだけなので、利用はお好みでどうぞ。

使い方

レシピファイルをこんなふうに記述して、

-- recipe.lua
software_package "httpd" {
    action = "install",
}

service "httpd" {
    action = {"enable", "start"},
}

cofuコマンドの引数に指定して実行します

$ sudo cofu recipe.lua 
==> Starting cofu...
==> Loaded 2 resources.
==> Evaluating software_package[httpd]
    software_package[httpd]: 'installed' will change from 'false' to 'true'
==> Evaluating service[httpd]
    service[httpd]: 'enabled' will change from 'false' to 'true'
    service[httpd]: 'running' will change from 'false' to 'true'
==> Complete!

レシピを指定して、実行する。これだけ。

リソース

Itamaeと同様にレシピファイルにはサーバの状態をリソースという単位で記述します。以下が実装済みのリソースです。だいたいItamaeで用意されているのと同じです。

  • directory
  • execute
  • file
  • group
  • link
  • lua_function
  • remote_file
  • service
  • software_package
  • template
  • user

実体はLuaの関数なのですが、リソース記述のsyntaxは以下のようになります

resource_type "name" {
   attribute = "value",
   action = "type_of_action",
}

Tips

では、もうちょっと実用的な使い方やサンプルなどを幾つか紹介

dry-run

-nオプションをつければdry-runで実行します。実際には変更が行われないモードですね。変更内容の確認のために使います。

$ sudo cofu -n recipe.lua 

executeリソースでコマンド実行

コマンドを実行します。not_ifonly_ifは全てのリソースで使用できる共通のアトリビュートです。

execute "echo hello > /tmp/example" {
    not_if = "grep hello /tmp/example",     -- このコマンドが失敗した時のみ実行される
    -- only_if = "grep -v hello /tmp/example",    -- このコマンドが成功した時のみ実行される
}

templateで設定ファイルを更新

templateリソースで設定ファイルを更新します。テンプレートはGoのtext/templateで記述します。

template "/etc/httpd/conf.d/foo.conf" {
    owner = "root",
    group = "root",
    mode = "0644",
    source = "foo.conf.tmpl",
}

sourceを指定しない場合、配置先のファイルパスからデフォルトでテンプレートファイルの位置を決定します。上記の例ですとレシピファイルからの相対パス

templates/etc/httpd/conf.d/foo.conf

または

templates/etc/httpd/conf.d/foo.conf.tmpl

を使用します。

remote_fileでファイルを配置

テンプレート処理が必要なく、単純にファイルを配置したいだけのときはremote_fileリソースを使います。

remote_file "/path/to/hoge.zip" {
    owner = "root",
    group = "root",
    mode = "0644",
    source = "hoge.zip",
}

この場合もsourceを指定しない場合はデフォルトのパスを使用します。上記の例ですと

files/path/to/hoge.zip

となります。

変数

chefやItamaeでいうところのノードアトリビュートにあたるものとして、実行時にコマンドラインから任意の変数を割り当てられます。-varオプションをつかってJSONを直接指定します。

$ cofu recipe.lua -var='{"name": "kohkimakimoto"}'

これでテンプレートなどでは

{{var.name}}

で値を参照できます。レシピ内ではLuaグローバル変数varに割り当てられるので

var.name

で参照できます。

JSONを直接文字列からではなくファイルから読みたいときは-var-fileオプションを使用してください。

$ cofu recipe.lua -var-file=variables.json

パッケージのインストールとサービスの起動

software_packageでパッケージのインストール、アンインストール。serviceでサービスの起動、停止、再起動ができます。software_packageはItamaeやchefでいうところのpackageリソースと同等なのですが、Luaだとpackageがビルトインのモジュールが利用している予約語なので、名前を変えています。

software_package "httpd" {
    action = "install",
}

service "httpd" {
    action = {"enable", "start"},
}

notifiesでリソース更新に合わせて別のリソースのアクションを実行する

設定ファイルが更新されたらサービスをリロードさせたりするのに使います。

template "/etc/httpd/conf/httpd.conf" {
    owner = "root",
    group = "root",
    mode = "0644",
    notifies = {"reload", "service[httpd]"},
}

service "httpd" {
    action = "nothing",
}

Itamaeだとこれの逆バージョンのsubscribes(別のリソースの更新を検知してアクションを実行する)があるのですが、個人的にnotifiesしか使っていなかったのでCofuではnotifiesのみを実装しています。

別のレシピを読み込む

include_recipe関数で別のレシピを読み込みます。

include_recipe "nginx.lua"
include_recipe "php.lua"

ドキュメント

まだまだ欠けていますが、ここにあります。

https://github.com/kohkimakimoto/cofu/blob/master/docs/README.md

動作プラットフォームについて

前述のようにCentOSでしか動作確認、サポートしていません。

単純に私が他のOSは普段使っていないので今のところ対応しないというだけで、プラットフォームごとに処理を切り分ける部分はItamaeが利用しているSpecinfraを参考にして実装してあるので、マルチプラットフォーム対応ができる構造にはなっています。Specinfraを参考にプラットフォームによって異なる部分のコードを追加すれば、対応できるはずです。

ただテストを自動化してないので、本格的にマルチプラットフォーム対応させる際にはそのへんも考えていかなくてはいけないのかも。

開発の経緯と実際のユースケース

Itamaeに比べて機能は少ないし、SSHバックエンドをサポートしてないし、対応プラットフォームもCentOSのみというプロダクトなので、機能面でいうとこれは劣化版Itamaeという趣になっています。実際のところ私自身今はプロビジョニングツールにItamaeを使っていて、既存のItamaeのレシピをCofuで置き換えるつもりはなく、今後もItamaeを使い続ける予定であります。

実はこのCofuを作成した経緯は、フルスタックなプロビジョニングツールを作ろうと思っていたわけではなく、 そもそもの発端は「アプリケーションのデプロイ時に、サーバーのミドルウェアの設定ファイルを更新したい」という要件でした。

具体的なもののひとつがcronの設定ファイルで、これにアプリケーション固有のバッチ処理などが定期実行されるように記述されているわけです。アプリでバッチ処理を追加する場合、アプリケーションのリポジトリとサーバーコンフィグレーションを管理しているItamaeレシピのリポジトリの二つにコミットする必要がありました。

またアプリ固有の処理なので、プログラムロジック自体も特定の定期実行のスケジューリングを前提にした作りになっていることも多々あり、スケジューリングの設定と本質的に不可分で、この場合cronの設定自体アプリケーションのレイヤで管理すべきものです。それができないため、バッチプログラムのコメントに「日次バッチ。cronで実行される(/etc/cron.d/app参照)」とかメモを書いていたりしました。

こういう背景から、アプリケーション側に寄せるべき設定を管理するために、デプロイ時に特定の設定ファイルを書き換える仕組みがほしいと思っていて、最初はシェルスクリプトを書いていたのですが、いろいろ考えていくと冪等性が欲しかったり、設定ファイルが更新された場合にはコマンドを実行したりする必要が出てきたりして、Itamaeのようなプロビジョニングツールと機能要件が似てきたという次第です。

まあそれならデプロイ時にitamaeコマンドを叩けばいい、というのも当然あるのですが、設定ファイル一個をリポジトリルートにおいておいてデプロイ時にこれを実行すればOKというシンプルな仕組みが欲しかったので、結局最後まで作りました。以下がそのサンプルです

-- config_cron.lua
template "/etc/cron.d/app" {
    owner = "root",
    group = "root",
    mode = "0644",
    content = [=[# This is generated by cofu. don't edit it manually!
MAILTO=""

*/1  * * * * kohkimakimoto /path/to/app/batch1
*/1  * * * * kohkimakimoto /path/to/app/batch2
*/30 * * * * kohkimakimoto /path/to/app/batch3
0    * * * * kohkimakimoto /path/to/app/batch3
]=],
}

これをアプリのリポジトリルートにおいておき、デプロイ時のスクリプトsudo -E cofu config_cron.luaを実行するようにしてあります。

よって私の環境では実際に使っているリソースはtemplateservice,executeあたりのみになります。ただ、今後の個人の趣味プロジェクトなどではサーバプロビジョニングの用途でも使っていこうかな、とも思っています。せっかくその他のパッケージやユーザを管理するリソースも実装したので。。。

ちなみにレシピはシバンで使うこともできます。

https://ja.wikipedia.org/wiki/%E3%82%B7%E3%83%90%E3%83%B3_(Unix)

レシピを以下のように記述して、実行権限をつければ、レシピファイル自体を実行可能な設定スクリプトとして扱うことができます。

#!/usr/bin/env cofu
-- config.lua
template "/path/to/foo" {
    -- ...
}

template "/path/to/bar" {
    -- ...
}

-- 実行権限をつけて直接実行。
-- $ ./config.lua

実装のはなし

今回のCofuがそうなのですが、最近CLIツールを作るのにGoとGoPherLuaというGoのLua実装をセットで使うのが気に入っていて、これをベースにLuaDSL風に使った設定ファイルを設計してアプリケーションを組み上げるのが最近、個人的によく使う手法になっています。

LuaDSLはnginxの設定ファイルやHCLに見た目が似ていて、かつ変数や制御構文もつかえるので、なかなか便利だと思っています。

他にも幾つかこの技術スタックでツールを書いているので、後日それについても記事を書こうかと思っています。

CentOS6でサーバを構築する際におこなっている基本設定

CentOS

2016年にもなって今さらという感じですが、CentOS6でサーバを構築する際の自分がやっている初期の基本設定、確認事項をまとめておこうと思います。

CentOS6のサポート期限が2020年までで、自分が今後メインに使っていく環境もCentOS7などに移行しようと思っています。遅まきながらローカルのVirtualBoxにCentOS7をいれていろいろ試していますが、CentOSは6と7で大幅な変更がなされ、今まで普通に出来ていたことを一から学び直す必要があったりしてツライ日々ではあります。

このように徐々にCentOS6から離れていくことになるので、そのあたりの記憶が急速に失われていく前に、今まで書いたメモなどを元に情報を整理しておこうかと思い立ったのがきっかけです。

※ここではサーバのロールにかかわらず行う基本的な作業をまとめています。通常この環境の上にWebサーバ(httpd, nginxなど)やDB(MySQLなど)をインストールすることになります。

※本文中のコマンドは基本的にrootユーザで行っているものとします。一般ユーザを使い必要に応じて都度root権限を使うようにしている方はsudoをつけるなど、コマンドを読み替えてください。

ランレベルの確認

OSのランレベルを確認します。ここではサーバ用途なので3が設定されていればいいでしょう

# cat /etc/inittab 
# inittab is only used by upstart for the default runlevel.
#
# ADDING OTHER CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM.
#
# System initialization is started by /etc/init/rcS.conf
#
# Individual runlevels are started by /etc/init/rc.conf
#
# Ctrl-Alt-Delete is handled by /etc/init/control-alt-delete.conf
#
# Terminal gettys are handled by /etc/init/tty.conf and /etc/init/serial.conf,
# with configuration in /etc/sysconfig/init.
#
# For information on how to write upstart event handlers, or how
# upstart works, see init(5), init(8), and initctl(8).
#
# Default runlevel. The runlevels used are:
#   0 - halt (Do NOT set initdefault to this)
#   1 - Single user mode
#   2 - Multiuser, without NFS (The same as 3, if you do not have networking)
#   3 - Full multiuser mode
#   4 - unused
#   5 - X11
#   6 - reboot (Do NOT set initdefault to this)
# 
id:3:initdefault:

id:3:initdefault:となっていることを確認します。

サービス起動設定の確認

chkconfigで起動設定になっているサービス(デーモン)を確認します。

# chkconfig --list
auditd          0:off   1:off   2:on    3:on    4:on    5:on    6:off
blk-availability    0:off   1:on    2:on    3:on    4:on    5:on    6:off
cgconfig        0:off   1:off   2:off   3:off   4:off   5:off   6:off
cgred           0:off   1:off   2:off   3:off   4:off   5:off   6:off
crond           0:off   1:off   2:on    3:on    4:on    5:on    6:off
...

不要と思われるサービスはサーバリソースの無駄使いになるので、以下のコマンドで停止設定にしておきます。

# chkconfig <サービス名> off

これはOS起動時のサービスの自動起動設定なので、合わせて

# /etc/init.d/<サービス名> stop

として現在の実行状態も停止します。

さて、サーバ 用途でランレベルは3になっているので3:onとなっているサービスを上から順に調べて、offにしていくのですが、自分の知識範囲でよくわからないものもあったします。その場合はとりあえずonにしておいています。もちろんすべて把握できるのが良いのですが。。。

逆にOSの起動時に実行させたいサービスは

# chkconfig <サービス名> on

とします。

ディクスサイズの確認

# df -h

-hをつけるとサイズにM(メガ)とかG(ギガ)とかがついてわかりやすく表示されます。 サーバスペックにあったディクスサイズになっていることや、パーティションを確認します。

ネットワーク関連の設定

幾つかあるので、順番に見ていきます。

ネットワークの設定

/etc/sysconfig/network

NETWORKINGyesになっているのとHOSTNAMEを確認する

NETWORKING=yes
HOSTNAME=your-hostname

ネットワーク・インターフェースの確認

ネットワーク・インターフェースは/etc/sysconfig/network-scripts配下にインターフェースごとにifcfg-xxxというファイルに設定されている。

例えばifcfg-eth0イーサネットの0番目のインターフェース

DEVICE="eth0"
BOOTPROTO="none"
IPADDR=192.168.56.14
NETMASK=255.255.255.0
ONBOOT="yes"
TYPE="Ethernet"
UUID="c67412a6-19cf-427a-9f4d-96ca89217519"

静的はIPを割り当てている場合はIPADDRNETMASKなどの設定を確認、編集する。またネットワークの設定を変更したら

# /etc/init.d/network restart

で反映させる。

IPアドレスの確認

# ifconfig

IPアドレスの設定が、設定通りか確認します。

ネットワークの疎通確認

# ping yahoo.co.jp

pingが外に向けて通るかを確認します。0% packet lossとなればOK。

# traceroute yahoo.co.jp

外につなぎに行く際にどのようなIPを経由するかを確認。ネットワーク管理者ではないので、ざっと結果を眺めるだけです。

yumリポジトリ

yumCentOSの標準パッケージ管理システムです。システムワイドにパッケージをインストールする場合、基本的にyumを使う方針にしています。ソースからのインストールに代表されるその他のインストール方法はファイルシステムのどこに何が配置されるか把握、管理するのが難しく、これはアンインストールなどの作業を困難にします。

ただCentOSでは標準のyumリポジトリでインストールできるパッケージは数が少なかったり、バージョンが古かったりする(特にPHPのバージョンが古いのがLAMP環境では致命的です)ので、以下のサードパーティリポジトリを追加して使っています。俗に「野良リポジトリ」と呼ばれるものです。

  • epel
  • remi

追加は以下のコマンドを実行します。

# rpm -ivh http://ftp.riken.jp/Linux/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm
# rpm -ivh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm

追加したサードパーティリポジトリは標準では想定されていない環境をインストールするので、予期せぬ不具合を発生させる可能性があると思われます。デフォルトでは使わないように設定し、インストールやアップデート時、必要に応じて有効化して使うのが広く知られている手法のようです(※といいつつ、私はデフォルトで有効化してしまっています。後述します)。その場合は以下のように設定ファイルを修正、確認しましょう。

/etc/yum.repos.d/epel.repo

[epel]
# 中略...
enabled=0

/etc/yum.repos.d/epel.repo

[remi]
# 中略...
enabled=0

これでデフォルトは無効化されます。利用する場合は以下のようにします。

# yum install php --enablerepo=remi,epel

コマンドメモ

インストール(phpをインストールの例)

# yum install php

アップデート

# yum update php

アンインストール

# yum remove php

インストール済みパッケージの情報

# rpm -qi php

インストール済みパッケージのファイル一覧

# rpm -ql php

補足(個人的なデフォルトの設定)

前述したように、サードパーティリポジトリ利用のデフォルト設定を無効にはしていません。というのも実際にパッケージをインストールする作業はchefなどのプロビジョニングツールを使っているので、デフォルトで有効になっていないと、これらのツールから正しくインストールできないという理由があるからです。

bash-completion

素のbashのままだとzshになれた補完脳にはツライので、bash-completionを入れます。

# yum install bash-completion

インストールするだけで、ある程度補完が効くようになります。カスタマイズは特にしていません。

iptables

iptablesファイアウォールです。iptablesコマンドから設定できるのですが、よく覚えてないし全体の設定を眺めて設定できるので、設定ファイルを直接編集しています。(設定ファイルの先頭にManual customization of this file is not recommended.って書かれているので本来は非推奨なのですが。。。)

iptablesの設定の詳細は覚えていないのですが(よくググります)、基本的に以下のようにINPUTチェインに外部接続を許可する設定を書いていきます。 最後にそれ以外はすべて拒否するように設定しておきます。

/etc/sysconfig/iptables

# Firewall configuration written by system-config-firewall
# Manual customization of this file is not recommended.

*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT

# httpd
-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT

# ssh
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT

# ...中略

# 最後に拒否
-A INPUT -j REJECT --reject-with icmp-host-prohibited

これで必要のない外部アクセスを遮断でき、不正アクセス対策になります。最後にrestartして設定を反映します。

# /etc/init.d/iptables restart

なお、iptablesの設定をミスってsshでリモートサーバに繋げなくなって詰む、という状況を避けたいので今のターミナルのセッションが生きているうちに、restartしたらまず別のターミナルを立ち上げてssh接続を試します。

sshd

ssh接続はセキュリティを強くするため、ポート番号を変えて、鍵認証オンリーにします。以下、編集および確認箇所のみを記載します。

/etc/ssh/sshd_config

# 22以外に変えます
Port 2222
# rootログインは禁止
PermitRootLogin no
# パスワード認証は禁止
PasswordAuthentication no

再起動して設定を反映します。

/etc/init.d/sshd restart

logrotate

ログのローテート設定。ログが無尽蔵に大きくなってディクスサイズを圧迫しないように設定、確認します。 初期設定時の他にhttpdなどのデーモンやログを出力するアプリケーションを追加した時にも目を通します。

/etc/logrotate.d/以下にログの対象ごとに設定ファイルがあるのでそれを確認します。例えばnginxは以下のようになっていたりします。

/etc/logrotate.d/nginx

/var/log/nginx/*.log {
        daily
        missingok
        rotate 52
        compress
        delaycompress
        notifempty
        create 640 nginx adm
        sharedscripts
        postrotate
                [ -f /var/run/nginx.pid ] && kill -USR1 `cat /var/run/nginx.pid`
        endscript
}

実際の設定はログのサイズや保存期間の要件などによって異なるので、それを考慮し設定します。

sysctl

カーネルパラメータの設定です。/etc/sysctl.confを編集します。以下のパラメータを追加、変更。

net.nf_conntrack_max = 1000000
net.netfilter.nf_conntrack_max = 1000000
net.core.somaxconn = 1024
vm.overcommit_memory = 1

net.nf_conntrack_maxnet.netfilter.nf_conntrack_maxネットワークアクセスが高負荷になったときパケットのトラッキングがデフォルトの上限値を上回ることがあるのでそれに対応するため、実際の数値はメモリの量と相談です。

参考:

https://www.e-agency.co.jp/column/20121225.html

net.core.somaxconnはソケットをListenするキューの最大数。memcacnedとかredisはこれがカーネルのデフォルト値(128)より大きく設定されているので、増やす

参考:

net.core.somaxconnについて調べてみた - tetsuyai’s blog

vm.overcommit_memoryはメモリが足りなくなったときのmallocの動作についての制御のよう。デフォルトだと高負荷時にredis起動時にバックグランドセーブが失敗する可能性がある、という警告がでる。

参考:

passingloop • Linux のオーバーコミットについて調べてみた

設定値を反映するために以下のコマンドを打つ。

sysctl -p

確認。

sysctl -a

環境変数

環境変数はサーバにデプロイするアプリケーションの振る舞いを制御するためなどに使います。 サーバのロールやdevelopmentproductionのような環境情報を設定しておきます。 アプリケーションは実行時にこの環境変数を読むことでデバッグモードに変えたり、本番用の設定に切り替えたりできるように実装します。

ただ環境変数はsudo時、sshでログイン時、sshで直接コマンド実行時、実行中のhttpdプロセス内、などプロセスの実行のコンテキストによって変わるので、 常に一貫した設定を読めるようにしておくのがなかなか難しく、悩みどころになっています。現状は同じ環境変数を反映させる設定を複数の箇所に行っています。

/etc/envrionment

SERVER_ENVIRONMENT=production
SERVER_ROLE=web

/etc/profile.d/environment.sh

export SERVER_ENVIRONMENT=production
export SERVER_ROLE=web

以上2つが基本。あとはhttpdアプリケーションサーバに使うときは/etc/httpd/conf/httpd.confにも設定します

SetEnv SERVER_ENVIRONMENT "production"
SetEnv SERVER_ROLE "web"

設定の記述が重複してしまうのでコピペにならないように、chefやitamaeのようなプロビジョニングツールで変数にしてカバーしています。。。

Bashスクリプトのチートシートと便利なスニペットまとめ

bash

Bashスクリプトを書く際によく自分が使っている小技や関数などです。 またBashでは他のプログラミング言語以上に$:などの記号の使い方が独特でググラビリティが低いので、基本文法などもチートシートとしてまとめておきます。

基本文法

変数と配列

変数

# 変数
v="variable"
echo $v

配列。bashは配列しかデータ構造がない。つらい。。。

# 配列
declare -a array=()

# 初期値
declare -a array=("a" "b" "c")

# 要素数
echo ${#array[@]}

# 先頭に追加
array=("x" "${array[@]}")

# 末尾に追加
array=("${array[@]}" "d") 

# indexを指定して取得
echo ${array[0]}
echo ${array[1]}

# 配列全体を取得
echo ${array[@]}

# 配列をforループで参照する
for v in "${array[@]}"
do
    echo $v
done

参考:

bash 配列まとめ - Qiita

制御構文

while

v=0
while [ $v -lt 10 ]
do
    echo $v
    v=$((v+1))
done

for-in

for i in {0..9}; do
    echo $i
done

if - elif - else

v="hoge"
if [ "$v" = "hoge" ]; then
    echo "v is hoge"
elif [ "$v" = "foo" ]; then
    echo "v is foo"
elif [ ! "$v" = "foo" ]; then
    echo "v is not foo"
else
    echo "v is unknown"
fi

条件判定のあとのブロックを空にすることはできない。

変数は"で囲ったほうがいい。[ $v = "~" ];は変数が空のときsyntaxエラーになる。参考

条件の否定は先頭に!

文字列比較

文字列が等しい (=)

v="hoge"
if [ "$v" = "hoge" ]; then
    echo "equal"
fi

文字列が等しくない (!=)

v="hoge"
if [ "$v" != "foo" ]; then
    echo "not equal"
fi

空文字、文字列長が0 (-z)

v=""
if [ -z "$v" ]; then
    echo "zero length"
fi

空文字でない、文字列長が0でない (-n)

v="aaa"
if [ -n "$v" ]; then
    echo "not zero length"
fi

数値比較

数値が等しい (-eq)

v=55
if [ "$v" -eq 55 ]; then
    echo "equal"
fi

数値が等しくない (-ne)

v=55
if [ "$v" -ne 20 ]; then
    echo "not equal"
fi

数値がより小さい (-lt)

v=10
if [ "$v" -lt 20 ]; then
    echo "less than"
fi

数値がより大きい (-gt)

v=30
if [ "$v" -gt 20 ]; then
    echo "greater than"
fi

ファイルの判定

存在する (-e)

v="/tmp/aaa"
if [ -e "$v" ]; then
    echo "exists"
fi

ファイルである (-f)

v="/path/to/file"
if [ -f "$v" ]; then
    echo "file"
fi

ディレクトリである (-d)

v="/tmp"
if [ -d "$v" ]; then
    echo "directory"
fi

シンボリックリンクである (-L)

v="/path/to/link"
if [ -L "$v" ]; then
    echo "symbolic link"
fi

関数

function hoge() {
  local v="local variable"  # ローカル変数定義
  echo $1   # n番目引数
  echo $2
  echo $@   # 引数全体
  echo $#   # 引数の数
  return 0  # 戻り値
}

hoge "arg1" "arg2"
# 実行結果
# arg1
# arg2
# arg1 arg2
# 2

ブロック内を空にすることはできない。

戻り値は数値のみ。コマンドと同様に0が正常でそれ以外はエラー。

$0は関数名ではなく、呼び出し元スクリプト名が入る。

文字列を戻したいときはechoなどで標準出力を使う。

function hoge() {
    echo "hogehoge"
}

$v=$(hoge)
echo $v

便利なスニペット

実行スクリプトがあるディレクトリを絶対パスで取得する

READLINK=$(type -p greadlink readlink | head -1)
if [ -z "$READLINK" ]; then
  echo "cannot find readlink - are you missing GNU coreutils?" >&2
  exit 1
fi

resolve_link() {
  $READLINK "$1"
}

# get absolute path.
abs_dirname() {
  local cwd="$(pwd)"
  local path="$1"

  while [ -n "$path" ]; do
    # cd "${path%/*}" does not work in "$ bash script.sh"
    # cd "${path%/*}"
    cd "$(dirname $path)"
    local name="${path##*/}"
    path="$(resolve_link "$name" || true)"
  done

  pwd -P
  cd "$cwd"
}

bin_dir="$(abs_dirname "$0")"
# /usr/local/bin
# などがbin_dirに取得できる

シンボリックに対応しないなら、以下の記述でもOK。

# スクリプトがシンボリックリンクから呼び出されたときは、リンクを辿らず、リンク先のパスを返す
bin_dir=$(cd $(dirname $0); pwd)

参考:

https://github.com/rbenv/rbenv/blob/master/libexec%2Frbenv

標準出力にプリフィクスをつける

prefix() {
  local p="${1:-prefix}"
  local c="s/^/$p/"
  case $(uname) in
    Darwin) sed -l "$c";; # mac/bsd sed: -l buffers on line boundaries
    *)      sed -u "$c";; # unix/gnu sed: -u unbuffered (arbitrary) chunks of data
  esac
}

# 使い方
echo "message" | prefix "[hoge] "
# [hoge] message

# 標準エラー出力も対象とする場合はエラーを標準出力にリダイレクトしてから行う
echo "message" 2>&1 | prefix "[hoge] " 

参考:

https://github.com/heroku/heroku-buildpack-php

標準出力にインデントをつける

プリフィクスをつける、の応用。

indent() {
    local n="${1:-4}"
    local p=""
    for i in `seq 1 $n`; do
        p="$p "
    done;

    local c="s/^/$p/"
    case $(uname) in
      Darwin) sed -l "$c";; # mac/bsd sed: -l buffers on line boundaries
      *)      sed -u "$c";; # unix/gnu sed: -u unbuffered (arbitrary) chunks of data
    esac
}

# 使い方
# デフォルトで4スペースインデント
echo "message" | indent
#    message

# インデントサイズを引数に渡せる
echo "message" | indent 6
#      message

コンソールに確認用のプロンプトを出す

confirm() {
    local response
    # call with a prompt string or use a default
    read -r -p "${1:-Are you sure? [y/N]:} " response
    case $response in
        [yY][eE][sS]|[yY])
            return 0
            ;;
        *)
            return 1
            ;;
    esac
}

# 使い方
confirm
if [ $? -ne 0 ]; then
    exit 1
fi
# Are you sure? [y/N]:
# と表示されて入力まちになるのでyやyesを入力で続行。それ以外はexitする。

# 引数で表示メッセージを変えられる。
confirm "Please input yes!:"

# set -eしておけばconfirmの戻りが1のとき即終了するのであと条件分岐を書く必要がない
set -e
confirm

参考:

http://stackoverflow.com/questions/3231804/in-bash-how-to-add-are-you-sure-y-n-to-any-command-or-alias

コンソールにユーザー入力用のプロンプトを出す

ask() {
    local response
    # call with a prompt string or use a default
    read -r -p "${1:->} " response
    echo $response
}

# 使い方
v=$(ask)
echo $v

# > 
# と表示されて入力待ち状態になる。入力値はvに入る。

# プロンプトの表示も買えられる
v=$(ask "[input your name]> ")
echo $v

テキスト装飾(色、太字、アンダーライン)

if [ "${TERM:-dumb}" != "dumb" ]; then
    txtunderline=$(tput sgr 0 1)     # Underline
    txtbold=$(tput bold)             # Bold
    txtred=$(tput setaf 1)           # red
    txtgreen=$(tput setaf 2)         # green
    txtyellow=$(tput setaf 3)        # yellow
    txtblue=$(tput setaf 4)          # blue
    txtreset=$(tput sgr0)            # Reset
else
    txtunderline=""
    txtbold=""
    txtred=""
    txtgreen=""
    txtyellow=""
    txtblue=$""
    txtreset=""
fi

# 使い方 (装飾後は${txtreset}で戻すようにして使う)
${txtred}this text is red${txtreset}

参考:

https://linuxtidbits.wordpress.com/2008/08/11/output-color-on-bash-scripts/

http://stackoverflow.com/questions/2924697/how-does-one-output-bold-text-in-bash

エラーメッセージを出して終了する

上記のテキスト装飾を使って、赤文字のエラーメッセージを出力してプログラムを終了させる

abort() {
  { if [ "$#" -eq 0 ]; then cat -
    else echo "${txtred}${progname}: $*${txtreset}"
    fi
  } >&2
  exit 1
}

# 使い方
abort "error message"

コンソールにラインを引く

hr() {
    printf '%*s\n' "${2:-$(tput cols)}" '' | tr ' ' "${1:--}"
}

# 使い方
hr

# 引数でラインのキャラクタを変えられる(デフォルトは-)
hr "="

# 第2引数でラインの長さを変えられる(デフォルトはターミナルの幅いっぱい)
hr "=" 10

参考:http://wiki.bash-hackers.org/snipplets/print_horizontal_line

文字列を小文字->大文字に変換する

upper() {
    echo -n "$1" | tr '[a-z]' '[A-Z]'
}

# 使い方
v=$(upper "abcdefg")
echo $v

オプションとサブコマンドを扱うためのテンプレート

よくあるhoge.sh [<options...>] <command>という形式のコマンドをbashで作るためのテンプレート。

#!/usr/bin/env bash
set -eu

progname=$(basename $0)
progversion="0.1.0"

# actions.
usage() {
    echo "Usage: $progname [OPTIONS] COMMAND"
    echo
    echo "Options:"
    echo "  -h, --help         show help."
    echo "  -v, --version      print the version."
    echo "  -d, --dir <DIR>    change working directory."
    echo
    echo "Commands:"
    echo "  help        show help."
    echo
}

printversion() {
    echo "${progversion}"
}

# parse arguments and options.
declare -a params=()
for OPT in "$@"
do
    case "$OPT" in
        '-h'|'--help' )
            usage
            exit 0
            ;;
        '-v'|'--version' )
            # パラメータを取らないオプション
            printversion
            exit 0
            ;;
        '-d'|'--dir' )
            # パラメータを取るオプション。 "-d /tmp"のようにスペースで区切ってパラメータを渡す。
            if [[ -z "${2:-}" ]] || [[ "${2:-}" =~ ^-+ ]]; then
                echo "$progname: option '$1' requires an argument." 1>&2
                exit 1
            fi
            optarg="$2"

            cd $optarg
            shift 2
            ;;
        '--'|'-' )
            shift 1
            params+=( "$@" )
            break
            ;;
        -*)
            echo "$progname: illegal option -- '$(echo $1 | sed 's/^-*//')'" 1>&2
            exit 1
            ;;
        *)
            if [[ ! -z "${1:-}" ]] && [[ ! "${1:-}" =~ ^-+ ]]; then
                params+=( "$1" )
                shift 1
            fi
            ;;
    esac
done

# サブコマンドに対応して処理を実行
command="" && [ ${#params[@]} -ne 0 ] && command=${params[0]}
case $command in
    'help' )
        usage
        exit 0
        ;;
    '' )
        usage
        exit 0
        ;;
    *)
        echo "$progname: illegal command '$command'" 1>&2
        exit 1
        ;;
esac

参考:

bash によるオプション解析 - Qiita

Tips

標準出力を標準エラー出力にリダイレクト

echo "error" 1>&2

標準エラー出力を標準出力にリダイレクト

echo "error" 2>&1

プロセスIDを取得する($$)

echo $$
# 99807

コマンドの戻り値を取得する ($?)

[ "aaa" = "bbb" ]
echo $?
# 1

パイプでつないだコマンドの戻り値を取得する (${PIPESTATUS[0]})

$?は最後に実行されたコマンドの戻り値なので、前述のindent関数などをパイプでつなげた場合、もとの実行コマンドの戻り値は取れない。この場合はPIPESTATUSを使う。

[ "aaa" = "bbb" ] | indent; status=${PIPESTATUS[0]}
echo $status
# 1

参考: パイプでつないだコマンドの戻り値を調べる@bash | Mazn.net

未定義の変数を使用するとそこでスクリプトを終了する (set -u)

set -u

基本的に次のset -eも含めてset -euとしておくのがよさそう。

コマンドがエラーだった場合そこでスクリプトを終了する (set -e)

set -e

しておくと、エラー(戻り値0以外を戻すコマンド実行)があると即時終了する

set -eの状態でエラー後も処理を続ける (&&:)

安全のため基本的にset -eをしておいていいが、これはスクリプト内のすべてのコマンド実行の戻り値に対して効果をもつので、困ることも多い。 たとえばコマンド実行の結果をみてメッセージを出したい場合、以下のように書いたりする。

set -e

/path/to/yourcommand
if [ $? -eq 0 ]; then
    echo "yourcommand OK"
else
    echo "yourcommand NG"
fi

しかし/path/to/yourcommandがエラーの時は即時終了してしまうので[ $? -eq 0 ];は評価されず、結果yourcommand NGは絶対に表示されない。 このような場合はコマンドに&&:を続けておくとうまく動作する。(動作原理は下記参考のリンク先参照)

set -e

/path/to/yourcommand &&:
if [ $? -eq 0 ]; then
    echo "yourcommand OK"
else
    echo "yourcommand NG"
fi

参考:

`set -e` しているときにコマンドの戻り値を得る - Qiita

パイプで繋いだコマンドがエラーのとき終了させる

パイプでつなぐとset -eをしててもエラー時に終了しないので以下のようにかく。

foo | indent; status=${PIPESTATUS[0]}; [[ ! $status -eq 0 ]] && exit $status

スクリプト終了時にコマンドを実行する

trapコマンドでシグナルをハンドリングできる

echo "start"
trap "echo 'after end'" 0
echo "end"

参考: シグナルと trap コマンド | UNIX & Linux コマンド・シェルスクリプト リファレンス

一時ファイルを作る

# 一時ファイルを作成
tmpfile=$(mktemp -t prefix.XXXXXXXX)
echo $tmpfile

# 終了時に一時ファイルを削除
trap "rm $tmpfile" 0

osxlinuxで挙動が違う。どちらでもひとまず動くバージョン linuxだとプリフィクスの末尾に"X"が必要でここがランダムな文字列に置き換わる。 osxだとXがリプレースされずそのまま使用され、その後にランダムな文字列が追加される

一時ディレクトリを作る

# 一時ディレクトリを作成
tmpdir=$(mktemp -d -t prefix.XXXXXXXX)
echo $tmpdir

# 終了時に一時ディレクトリを削除
trap "rm -rf  $tmpdir" 0

rootユーザでのみ実行を許可する

user=`whoami`
if [ $user != "root" ]; then
    echo "you need to run it on the 'root' user." 1>&2
    exit 1
fi

標準入力からデータを受け取る

if [ -t 0 ]; then
  echo "stdin is not pipe" 1>&2
  exit 1
else
  cat -
fi | yourcommand

ヒアドキュメント

ヒアドキュメント内のシェル変数は展開される

cat << EOF
This is a heredoc
home is $HOME
EOF

# This is a heredoc
# home is /Users/kohkimakimoto

変数は展開をさせたくないときはコロンで囲む

cat << 'EOF'
This is a heredoc
home is $HOME
EOF

# This is a heredoc
# home is $HOME

ヒアドキュメントの内容を変数に入れる

heredoc=`cat << 'EOF'
This is a heredoc
home is $HOME
EOF`

echo $heredoc
# This is a heredoc
# home is $HOME

ヒアドキュメントの内容をファイルに出力する

cat << 'EOF' > heredocfile
This is a heredoc
home is $HOME
EOF

cat heredocfile
# This is a heredoc
# home is $HOME

スクリプトのロック(多重起動防止)

exec 9< $0
perl -mFcntl=:flock -e "open(LOCK,'<&=9');exit(!flock(LOCK,LOCK_EX|LOCK_NB))" || {
    echo "duplicate process." >&2
    exit 1
}

参照:

http://qiita.com/ngyuki/items/cc4f6aeaa9b53baa3b68

http://qiita.com/knaka/items/582289c5b98ca5f55506

yum updateでエラー(Error: Package: 2:irqbalance-1.0.7-5.el6.x86_64)

CentOS

古いCentOS6のサーバにyum updateをかけたら依存の問題で以下のエラーがでて、更新できなかった。

# yum update

...

Error: Package: 2:irqbalance-1.0.7-5.el6.x86_64 (base)
           Requires: kernel >= 2.6.32-358.2.1
           Installed: kernel-2.6.32-71.el6.x86_64 (@anaconda-CentOS-201106060106.x86_64/6.0)
               kernel = 2.6.32-71.el6
               kernel = 2.6.32-71.el6
           Installed: kernel-2.6.32-71.29.1.el6.x86_64 (@updates)
               kernel = 2.6.32-71.29.1.el6
               kernel = 2.6.32-71.29.1.el6

カーネルが古すぎて、あたらしいカーネルを必要とするパッケージが入れられないのはわかるので、yum update kernelなどを行ってみたが

No Packages marked for Update

と表示されてアップデートできず。調べたところ/etc/yum.confカーネルアップデートを除外するよう、デフォルトで設定が書かれていたからと判明する。

[main]
cachedir=/var/cache/yum/$basearch/$releasever
keepcache=0
debuglevel=2
logfile=/var/log/yum.log
exactarch=1
obsoletes=1
gpgcheck=1
plugins=1
installonly_limit=5
bugtracker_url=http://bugs.centos.org/set_project.php?project_id=16&ref=http://bugs.centos.org/bug_report_page.php?category=yum
distroverpkg=centos-release

#  This is the default, if you make this bigger yum won't see if the metadata
# is newer on the remote and so you'll "gain" the bandwidth of not having to
# download the new metadata and "pay" for it by yum not having correct
# information.
#  It is esp. important, to have correct metadata, for distributions like
# Fedora which don't keep old packages around. If you don't like this checking
# interupting your command line usage, it's much better to have something
# manually check the metadata once an hour (yum-updatesd will do this).
# metadata_expire=90m

# PUT YOUR REPOS HERE OR IN separate files named file.repo
# in /etc/yum.repos.d
exclude=kernel*

最後のexclude=kernel*コメントアウトしたら、無事アップデートできた。