2015年4月30日木曜日

cannot send list of active checks to [$IP]: host [$HOSTNAME] not found

概要

Zabbix Proxy で監視設定をしている際にタイトルのエラーがずっと発生していつまで経ってもホストの監視が有効にならなかった

環境

  • CentOS 6.6 64bit
  • Zabbix Server 2.0.5
  • Zabbix Proxy 2.0.14
  • Zabbix Agent 2.0.14

エラーの詳細

エージェント側とプロキシ側で以下のログがずっと出続けいる状態が発生しました

  • プロキシ側
    cannot send list of active checks to …
  • エージェント側
    no active checks on server …

監視対象のZabbix Agentの設定、分散監視するZabbix Proxyの設定はすでに完了しているものとします
設定とは具体的にはzabbix_agentd.confやzabbix_proxy.confの記載が完了していてプロセスも起動している状態です
またACL等も問題なく設定されている状態です

この状態で当該エラーが発生してホストの監視がずっと行われていませんでした

原因調査

Zabbix Agent側の設定やZabbix Proxy側の設定を散々変更してみたのですが、一向に解消されませんでした
原点に立ち返って再度ログを眺めてみると/var/log/zabbix/zabbix_proxy.logに以下のようなログが出ていました

failed to update local proxy configuration copy: invalid field name "items.filter"

このログを頼りに調査を続けるとどうやらZabbix Proxyに必要なMySQLのスキーマがおかしいことがわかりました

Zabbix ProxyはZabbix Serverとデータの同期を行っています
具体的にはホストの監視設定(アイテム等)になります
このときにZabbix Proxy側のスキーマがZabbix Server側のスキーマとあわずにうまくのデータが同期できていなかったようです

ここまで来てピンと来たのですが、Zabbix Serverは古くから使われているサーバでバージョンが古く「2.0.5」でした
対してZabbix ProxyとZabbix Agentは最近構築したサーバでここには最新のZabbix ProxyとZabbix Agentをインストールしていました
最新バージョンは2.4.5になります

対応方法

結論は

2.4.5のZabbix ProxyとZabbix Agentを一旦アンインストールして2.0.14のProxyとAgentを再インストールしました

になります
すべてyumで管理していたので

yum erase zabbix_proxy
yum erase zabbix_agent
yum erase zabbix

で最新バージョンのパッケージを削除し

rpm -ivh http://repo.zabbix.com/zabbix/2.0/rhel/6/x86_64/zabbix-release-2.0-1.el6.noarch.rpm
yum clean all
yum install zabbix-proxy
yum install zabbix-agent

で再度インストールしたあとに

mysql -u user -p zabbix < /usr/share/doc/zabbix-proxy-mysql-2.0.14/create/schema.sql

という感じで2.0系のDBスキーマをZabbix Proxy用に当ててあげれば問題なく動作しました

最後に

今回学んだ教訓は

Zabbix Serverのバージョンに合わせてZabbix ProxyとZabbix Agentのバージョンも合わせよう
そうしないとDBのスキーマが異なりうまく監視データをZabbix Proxy上に登録することができない

ということです
本当はセキュリティの関係とかもあって最新版を使いたいところなのですが、どうやらDBのスキーマの関係で無理なようです
マイナーバージョンは異なっていても問題ないようです

  • Zabbix Server 2.0.5 <–> Zabbix Proxy 2.0.14 の組み合わせでは問題なく動作した

新規でZabbix Serverも構築する場合は今回のような問題に遭遇することは少ないと思いますが
Zabbix Proxyを使って監視対象の新規サービスを増やす場合などに陥りそうです

2015年4月28日火曜日

CentOS に公式の MySQL の最新版を yum でインストールする方法

概要

CentOSにMySQLの最新版をyumコマンドでインストールしてみました
備忘録として残しておきます

環境

  • CentOS 6.6 64bit
  • MySQL 5.6.24

インストール

  • MySQL公式のリポジトリをインストール
rpm -ivh http://dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm
  • clean 後にインストール
yum clean all
yum -y install mysql-community-server

mysql-community-client, mysql-community-common, mysql-community-libs, mysql-community-libs-compat も同時にインストールされます

  • 動作確認
service mysqld start

mysql -u root
  • 自動起動
chkconfig mysqld on
  • アドミンのパスワード設定
/usr/bin/mysqladmin -u root password 'new-password'
  • 任意のユーザ作成
select password('hogehoge');

でハッシュ化されたパスワードを作成して

grant select on test.* to 'test_r'@'localhost' IDENTIFIED BY PASSWORD 'ハッシュ化されたパスワードを入力';

2015年4月24日金曜日

CentOS でsupervisor を使ってシェルスクリプトとデーモン化してみた

概要

Supervisor は launchd, daemontools, runit のようなプロセスをデーモン化するためのツールです
デーモン化をプログラム上で実装するのが面倒くさい場合に使えるツールです
今回はCentOS上で supervisor を使ってシェルスクリプトをデーモン化してみました

環境

  • CentOS 6.3 64bit
  • supervisor 2.1.9

Supervisorのインストール

今回はepelリポジトリを追加してyumでsupervisorをインストールします
理由はインストールが簡単なのと起動スクリプトなど必要なリソースが揃っているからです
ただ、最新版のsupervisorをインストールしたい場合はyumではなくpythonのパッケージ管理の仕組みであるpiporeasy_installを使ってインストールすることをおすすめします
もしくはCentOSの6.6以上を使えばepelでも3系がインストールできるかもしれません
supervisorの2系はバージョン的にはかなり古く特にconfigファイルの書き方が3系とは全然違うのでご注意ください

rpm -ivh http://ftp.riken.jp/Linux/fedora/epel/6/x86_64/epel-release-6-8.noarch.rpm
yum clean all
yum -y install supervisor

バージョンを確認してみます

rpm -qa | grep supervisor

supervisor-2.1-9.el6.noarch

デーモン化するシェルスクリプトの準備

基本は何でもOKです

今回は自分がGistで公開しているシェルスクリプトを使ってみます
何をするシェルスクリプトか説明すると

  • 指定したディレクトリ配下にあるファイルを別の指定したディレクトリに1ファイルずつ移行するシェルスクリプト

になります
以下一応、実行するためのインストール用のコマンドを記載しておきます

mkdir /opt/supervisor
cd /opt/supervisor
wget https://gist.githubusercontent.com/kakakikikeke/10dc7dc6dca2f0675989/raw/342f9defd96862a97573a3e08a2ac6737ce9ee6b/dir2dir.sh
wget http://www.mitchy-world.jp/itmemo/shell/download/06.zip
unzip 06.zip
mv 06/common1.sh .
sed -i -e 's/LOG_DIR=\/home\/mitchy\/log\//LOG_DIR=`pwd`"\/"/' common1.sh
sed -i -e 's/SYSTEM_LOG_LEVEL=INFO/SYSTEM_LOG_LEVEL=DEBUG/' common1.sh
sed -i -e 's/.\/common1.sh/\/opt\/supervisor\/common1.sh/' dir2dir.sh

デーモン化するためのsupervisorスクリプトを作成

supervisor.confという設定ファイルがあるのでこれに直接記載します
[include]という仕組みがありこれを使って別ファイルに設定を切り出すことも可能です

  • vim /etc/supervisord.conf
[program:dir2dir]
command=sh /opt/supervisor/dir2dir.sh /opt/supervisor/target_dir/ /path/to/source_dir/
autostart=true
autorestart=true
logfile=/var/log/supervisor/dir2dir.log

commandはそのまんまでsupervisor上で動作させるコマンドを記述します
シェル内でファイルの参照などで相対パスを記載している場合にうまく動作しない場合があるので絶対パスで記載しておくといいです
autostartはsupervisorの起動と同時にプログラムを起動するかのオプションです
trueの場合は自動で起動します
autorestartは起動しているプログラムがハングしたり何かしたらの影響で停止したときに自動で再起動させるかどうかを指定します
trueの場合はsupervisorが自動で再起動してくれます

他にもいろいろとオプションが存在しています
Webで検索するか/etc/supervisord.conf内にサンプルもあるので参考にするといいと思います

Supervisorを起動

ではsupervisorを起動してみましょう

service supervisord start

でOKです
supervisorには管理用のコマンドが用意されておりsupervisorctlを使うと現在supervisor上で動作しているプロセスの状態を確認することができます

supervisorctl で状態の確認

まずsupervisorctlを実行してみましょう
するとsupervisor>というプロンプトに変更すると思います
この状態でsupervisorctl用のコマンドを発行するができます

主に使用するのは以下の通りです

  • status
    動作中のプラグラムの一覧を表示します
    起動中のプログラムはRUNNINGになっています、FATALはプログラム自体が壊れている可能性があるのでプラグラムの修正が必要です
  • start
    指定したプログラムを起動します
  • stop
    指定したプログラムを停止します
  • reload
    supervisor.confを書き換えた場合にgracefulに設定を反映することができます

上記以外のコマンドはhelpというコマンドで確認できます
更に詳細にコマンドを確認したい場合はhelpのあとに確認したいコマンドを付与して実行すればOKです

ログを確認してみる

ログはプログラム本体が吐くログはもちろん出力されます
それとは別にsupervisorが各プログラムごとにログを出力してくれています
今回の場合はsupervisor.confに記載したlogfileという項目のパスに出力してくれています
基本的には何も出力されませんが、supervisorがプログラムを再起動した場合や開始、終了時にログを残してくれます

最後に

今回はバージョン2で実施しました
冒頭も記載しましたがバージョン3ではconfigファイルの書き方が変わっているのでご注意ください
また、今回のシェルもそうですがシェル自体が終了してしまうシェルの場合リトライを何度か実施して最終的にはFATALのステータスになります
supervisorの使いどころとしてはnohupや&でバックグラウンド実行していたプログラムをsupervisorに移行する感じでだと思います

2015年4月23日木曜日

Graphviz 各種 Tips

概要

GraphvizのTipsをメモ

環境

  • CentOS 6.6 64bit
  • Graphviz 2.26.0-10

Tips

画像を埋め込む方法

jenkins [label = <<TABLE><TR><TD><IMG SRC="jenkins.png"/></TD></TR></TABLE>>, shape = plaintext];

HTMLライクな構文を使用すると埋め込むことができる
上記の場合はdotファイルと同じディレクトリにpngファイルを配置すればOK
shape=plaintextを画像のオブジェクトを枠線を削除するために利用

複数のclusterに所属させる方法

複数のsubgraphに所属される方法とも言うと思います

subgraph cluster_one {
  label = "one";
  subgraph cluster_two {
    host001;
    label = "two";
    color = blue;
  }
}

subgraphの宣言の中にsubgraphを書けばOK
上記の場合はこんな感じになります
test.png

cluster内のオブジェクトがエッジで接続されている場合にオブジェクトを並列にする方法

subgraph cluster_one {
  label = "one";
  subgraph cluster_two {
    rank = same {
      host001 -> host002;
    }
    label = "two";
    color = blue;
  }
}

rank = sameを使います
host001とhost002は横に並びます
rankdir=LRにすると縦に並びます
test2.png
rank = sameを使わないと以下の通り
test2-2.png

2015年4月22日水曜日

Jenkins の Workflow Plugin を使ってみた

概要

JenkinsのWorkflow Pluginを使ってみました
ビルドをchainするプラグインは他にもBuild Pipelineなどがあります
今回使うWorkflowプラグインも同じような種類のプラグインですがBuild Pipelineで問題だった点をいろいろ解決してくれているプラグインになります
インストールから簡単なサンプルの動作まで紹介します

環境

  • CentOS 6.6 64bit
  • Jenkins 1.6.10
  • Workflow Plugin 1.5

各種インストール

  • Jenkins
    ここを参考にyumでインストールしました
    Workflowプラグインを動かすためには1.580.1以上のバージョンが必要です

  • Workflowプラグインのインストール

Jenkinsの管理 -> プラグインの管理 -> Workflow: Aggregator

をインストールします
依存するプラグインも一緒にインストールされます
プラグインをインストールしたらJenkinsを一旦再起動しましょう

Workflowジョブを作成する

普通にジョブを作成する手順と同じです
Build Pipelineの場合はジョブを「下流」「上流」でchainすることで実現し、ビューでBuild Pipelineビューを作りましたが、Workflowプラグインはビューを作成しません

ジョブの種類を選択する部分で「Workflow」という項目が増えているのでこれを選択することでWorkflow用のジョブを作成することができます
create_workflow_job.png
これを選択して適当なWorkflowジョブを作成してください

Groovy でジョブのフローを記述する

ここがWorkflowプラグインの一番の肝になると思います
ジョブの設定を開くと「Definition Groovy CPS DSL」という項目でフリーのテキストエリアでScriptを記載する部分があると思います
ここにGroovyスクリプトを記載していきます

Workflowプラグインはジョブの流れや処理をGroovyのスクリプトで制御できるプラグインになります

とりあえずHello world

まずはジョブを成功させてみましょう
以下のScriptを記述してジョブを保存してください

echo 'hello from Workflow'

hello_world_workflow.png

そしてジョブを実行してみましょう
ビルド結果を確認するとechoした文章が出力されていると思います
result_hello_world_workflow.png

またScriptは以下のように記載することもできます

echo("hello from Workflow");

書きやすい方で書いてみましょう

複数のジョブを実行してみる

今回は既存の複数のジョブをWorkflow用のジョブから順番に実行できるようにしてみます
既存のジョブ名はそれぞれ「test1_job」「test2_job」とします
以下のようのScriptを記載しましょう

build 'test1_job'
build 'test2_job'

保存してジョブを実行してみます

実行結果をみると指定したジョブが上から順に実行されましたというログだけが出ています
なのでWorkflowから実行したジョブのビルド結果は各ジョブのビルド結果を見る必要があります

とりあえず既存のジョブをchainするだけならWorkflow Pluginでも簡単にできました

最後に

今回はBuild Pipelineのようにジョブをchainすることが目的だったので紹介は以上ですが、Workflow Pluguinで用意されている機能はもっとたくさんあります
SCMからリソースを取得したり、分散ビルドしたり、成果物を保存したりといったビルドの基本的な機能も実装されているので、これらをGroovyで書くことができます

感想も

かなりちょっとしか使っていませんが使ってみた感想としてはまだまだ発展の余地はあるかなと思いました
Build Pipelineに比べて優れているのは、それぞれのジョブ同士が疎結合のままchainすることができるので、ジョブを単体で実行することができる状態を保つことができます
もう少し言うとBuild Pipelineを使うと必ず下流、上流が結びついているので単体で実行すると勝手に下流のジョブも流れてしまいます
また上流のビルドパラメータを下流に引き継ぐようなジョブを作成していると、必ず上流から実行しないとパラメータの値がないので下流のジョブを単体で実行することができません
この辺のジョブの疎結合はWorkflow Pluginのほうが優れている点だと思います

ただ、Workflow Pluginではフローの可視化ができなかったり、ビルド結果の詳細を確認するには実行したジョブ側を見なければいけなかったりといろいろと改善してほしいポイントはあると思いました
ジョブの一覧でも、どのジョブがWorkflowジョブなのか一見するとわからないので命名規則でカバーしなければいけないのか、、とかも感じまいsた

なので今のところは各自のユースケースにあったプラグインを選択すればいいと思いますが、個人的には今後はWorkflow Pluginが来ると思います
理由としてはやはりビルドの内容をGroovyで完全にコード化できる点かなと思います
Infrastructure as a Codeではないですが、ジョブの状態がコードがされていれば見える化もしやすいですし、git で管理することもできるのでジョブの内容自体をCIすることもできるようになると思います

えー、いろいろ書きましたがとりあえず好感触を得ることができました
今後の発展も期待して使い続けたいと思います

参考サイト

2015年4月16日木曜日

CentOS 6.6 に yum でJenkins をインストールする方法

概要

専用のリポジトリを追加すればyumでインストールおよびパッケージ管理できるみたいなので試してみました
過去に紹介した記事だとTomcat+warファイル方式でやっています
yum でインストールすれば起動スクリプトも作ってくれるので便利です

環境

  • CentOS 6.6 64bit
  • Jenkins
  • Java 1.8.0_31 (OpenJDK)

インストール

リポジトリ追加

wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key

で完了です
/etc/yum.repos.dにJenkins専用のリポジトリファイルを作成して検証のために公開鍵をrpmにインポートしています
インポートした鍵情報を確認した場合は

rpm -qa | grep gpg-pubkey

で表示される鍵情報のRPMに対して詳細情報を表示するオプションを付与して実行すればOKです

rpm -qli gpg-pubkey-xxxxxxxx-yyyyyy

xxxxxxxx-yyyyyy はランダムな文字列が入っているはずです

yum インストール

yum -y install jenkins

でOKです
2015/04/16 時点では1.609-1.1という最新版のJenkinsがインストールされました

インストールされたものを確認

  • JENKINS_HOME
    デフォルトでは/var/lib/jenkinsになっていました
    起動オプションで指定するだけなので変更したい場合はJenkinsの起動スクリプトを変更すればOKだと思います

  • warファイルの場所
    /usr/lib/jenkins/jenkins.war

  • Jenkinsのログ
    /var/log/jenkins/jenkins.log

  • その他一覧

インストールされた全資材を確認したい場合は-qliオプションで確認できます

rpm -qli jenkins-1.609-1.1.noarch

一応自分が確認した段階では以下の通りでした

/etc/init.d/jenkins
/etc/logrotate.d/jenkins
/etc/sysconfig/jenkins
/usr/lib/jenkins
/usr/lib/jenkins/jenkins.war
/usr/sbin/rcjenkins
/var/cache/jenkins
/var/lib/jenkins
/var/log/jenkins

動作確認

  • 起動と停止
service jenkins start
service jenkins stop

起動すると8080でLISTENします
既存のプロセス(Tomcatとか)が8080を使っている場合は停止してから実行してください
親プロセス配下に50個ほど子プロセスができるようです

java,28629 -Dcom.sun.akuma.Daemon=daemonized -Djava.awt.headless=true -DJENKINS_HOME=/var/lib/jenkins -jar /usr/lib/jenkins/jenkins.war --logfile=/var/log/jenkins/jenkins.log --webroot=/var/cache/jenkins/war --daemon--httpPo
  {java},28632
  {java},28635
  {java},28638
  ・・・
  この配下に更に50個ほどの子プロセスがあった
  • UIにアクセス

http://hostname:8080/
でJenkinsのホームにアクセスできます
デフォルトではID/PWは設定されていません

  • 自動起動設定
chkconfig jenkins on

service起動できるのでもちろんchkconfig も使うことができます

最後に

アップデートの検証までは実施していないのですがおそらくyumでupdateできると思います
できない場合はJenkinsの画面から実施する感じだと思います

2015年4月15日水曜日

Capistrano 3.4 を触ったのでメモ

概要

ようやくバージョン3を触ったので忘れないようにメモしておきます
インストールから簡単なレシピ作成まで実施してみました

環境

  • CentOS 6.6 64bit
  • Ruby 2.2.1
  • Gem 2.4.6
  • Capistrano 3.4.0

インストール

事前にRubyおよびGemのインストールは完了しておいてください

gem install capistrano

でインストールは完了です

初期化

cap install

適当なディレクトリに移動した上で上記のコマンドを実行しましょう
Capistranoの実行に必要なファイル群を自動で生成してくれます
Capistrano2ではcapifyというコマンドが使われていたのですが、それは使えなくなったようです

ファイル確認

cap install後に作成されるファイルは以下の通りでした

合計 12
drwxr-xr-x 3 root root 4096  4 15 20:17 2015 config
drwxr-xr-x 3 root root 4096  4 15 20:17 2015 lib
-rw-r--r-- 1 root root  837  4 15 20:17 2015 Capfile

動作確認

cap console

指定したホストに任意のコマンドを発行できるようにしてみます

  • vim Capfile
require 'capistrano/console'

上記を追記します

  • vim config/deploy/production.rb
role :web, %w{host1 host2}

host1, host2 にはアクセスしたいサーバを指定します
IPでもOKです
host1, host2 にノンパスでSSHアクセスできるのであれば設定はこれでOKです
SSHに認証がある場合は以下も実施します

  • vim config/deploy/production.rb
set :ssh_options, {
  user: 'root',
  keys: %w(/path/to/key.pem),
  forward_agent: false,
  auth_methods: %w(publickey),
  passphrase: 'password'
}

上記はユーザがrootで公開鍵/path/to/key.pemを使ってパスワードに「password」を使って認証する場合の設定です
ポートは指定していないので22番になります
また上記の設定はすべてのサーバの認証に使われる情報なので今回の場合はhost1とhost2の両方で同じ認証である必要があります

設定は以上です
では実行してみましょう

cap production console ROLES=web

とするとproduction>というプロンプトになるのでコマンドを何か実行してみましょう
role :webで指定したhost1とhost2に同じコマンドが発行されてその結果が返ってくると思います

実行時に指定しているproductionを指定しないとconfig/deploy/staging.rbというファイルが読み込まれて実行されます
Capistrano3ではマルチ環境に対応しているため、サービス環境に対して実行したい場合はproductionを付与する必要があります

対話的ではありますがcap consoleだけでもできるようになっていればかなりオペレーションがかなり簡易になると思います

レシピを作成する

せっかくなので独自のレシピも作成してみましょう

  • vim config/deploy.rb
namespace :test do

  desc 'Show hostname'
  task :show_hostname do
    on roles :web do
      execute 'hostname'
    end
  end

end

上記を最下部に追記します
タスク自体は非常に単純でロール:webに属するサーバ郡に対してhostnameコマンドを実行するだけです
追記したらタスクを実行してみます

cap -T

でタスクの一覧が確認できます

cap production test:show_hostname

でタスクを実行することができます

今回はconfig/deploy.rbにレシピを追加しましたがどうやらlib/capistrano/tasks配下に.rakeファイルを作成していくのがCapistrano3では普通のようです

最後に

Capistrano2に慣れているとだいぶ使い勝手変わっているので初めは困惑するかもしれません
タスクの書き方は基本的には変わりませんが、duplicatedになっているメソッド等もありそうなので、ドキュメントを見ながら作成したほうがいいかと思います

Tips

デフォルトで用意してあるデプロイタスクが邪魔という場合にはCapfileに記載してあるrequire 'capistrano/deploy'をコメントアウトすればOKです
ただそうするとrestartというメソッドが使えなくなるのでconfig/deploy.rbに記載してあるdeployタスクも削除しないとエラーで怒られてしまいます

2015年4月14日火曜日

1つの git を複数のリポジトリに分割する方法

概要

1つの巨大なgitを複数のgit環境に分割してみたいと思います
初めは小さなプロジェクトで1つのgitからスタートしたが、次第にプロジェクトが大きくなり、その過程でgitを複数作らなくて同じgitにpushし続けた結果いろんなソースコードが混じった巨大なgitが出来てしまった
なんてことはあると思います

環境

  • git 2.1.1

分割する方法

例として自分のGitHubアカウントを使って分割してみます

分割先のリポジトリを作成する

まず分割先のリポジトリを作成しましょう
名前は何でもOKです
できれば空のリポジトリとして作成しましょう
空じゃなくてもOKですが、空リポジトリの方が簡単です
create-repo-for-split.png

分割対象のリポジトリをcloneする

これが巨大な1つgitリポジトリになります
ご自身の環境に合わせて分割したいリポジトリをローカルにcloneしてください
すでにある場合は特に何もしなくてOKです
今回はテスト用に自分のリポジトリを使ってみます

git clone https://github.com/kakakikikeke/cookbooks-emacs.git

ローカルでリポジトリをcloneする

Remoteからcloneしてきたリポジトリをローカルで操作します
まずは分割元のリポジトリを分割先のリポジトリに丸々cloneします

mkdir split-repo-test
git clone cookbooks-emacs/ split-repo-test/

これでローカル上で全く同じリポジトリができました

cloneした分割先のリポジトリでフィルタをかける

これが今回の肝となる作業だと思います
先ほどcloneした分割先のリポジトリに移動してフィルタをかけます
フィルタをかけることで必要なディレクトリ配下のみを管理git配下で管理できるようになります

cd split-repo-test
git filter-branch --subdirectory-filter files/ HEAD

とすることで files/ ディレクトリ配下をgitリポジトリで管理できるようになります
今回はサンプルにfilesというディレクトリがあるのでそれを指定していますが、ここは自身の環境に合わせて分割したいディレクトリを指定してください
完了後にlsでリポジトリを確認するとフィルタされたディレクトリ配下のリソースだけがあると思います
またgit logでログを確認すると他のディレクトリに対するコミットログがなくなっていると思います

リモートリポジトリを変更する

このままだとリモートリポジトリは分割元のリモートリポジトリになっているので push すると大変なことになります
分割後のリポジトリは作業冒頭でGithub上に作成したリモートリポジトリになるのでこれに変更します

cd split-repo-test
git remote set-url origin https://github.com/kakakikikeke/split-repo-test.git

これでpushしてみましょう

git push -u origin master

認証が成功して push できたあとでGithubを見てみると分割されたリソースがpushされていることを確認できると思います
goal-repo-for-split.png

すでに誰かがmasterブランチにpushしてしまっている場合は一旦git pullしてから push しましょう

最後に

今後は分割したリポジトリにコミットしていけばOKです
既存のリポジトリはgit rmしちゃっていいと思いますが、git rmだけだと歴史には残ってしまうので歴史からも完全に削除したい場合は、こちらもgit filter-branchを使うといいみたいです

2015年4月10日金曜日

Handler processing failed; nested exception is java.lang.NoClassDefFoundError: Could not initialize class org.hibernate.validator.internal.engine.messageinterpolation.InterpolationTerm

概要

Springでバリデーションルールを作成するときに@Maxや@Minなどを使います
バリデーションルールを作成した上でテストを実施すると掲題のエラーが発生しました
プロジェクト上ではエラーにならずテストを実行すると発生する感じで若干ハマったので対応方法を紹介します

環境

  • Windows 7 64bit
  • Java 1.8.0_25
  • Maven 3.2.2
  • Eclipse 4.4 Luna
  • Spring 4.1.5 RELEASE

開発シーン詳細

開発はEclipse上で行っておりSpringフレームワークを使ったWebAPIを作成しています
プロジェクトはMavenでビルドすることができるようになっています
Springにあるバリデーションの機能を使うためにBeansクラスを作成してそのBeansクラスがリクエストのパラメータをバインドするように実装しています
バインドするBeansクラス上で各パラメータにバリデーション用のアノテーションを付与しています
この状態でMaven経由でテストを実施するとバリデーションを使う部分で掲題のエラーが発生しました

対応方法

バリデーション用のアノテーションを使うために以下のようにpom.xmlを記載しました

<!-- Hibernate Validation -->
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.1.3.Final</version>
</dependency>
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.1-b04</version>
</dependency>
<dependency>
    <groupId>org.glassfish.web</groupId>
    <artifactId>el-impl</artifactId>
    <version>2.2</version>
    <scope>test</scope>
</dependency>

ポイントは最後のel-implでこれがないと掲題のエラーが発生する感じです
もちろんel-implがなくてもプロジェクトのエラーは発生しませんがmvn testを実行するとエラーが発生する感じでした

開発している環境にもよりますがといあえず自分は上記で解決できたので紹介します

2015年4月9日木曜日

Signature Version 4 を計算する Java 実装を作った

概要

http://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-signed-request-examples.html
で紹介されているpython実装をJavaに書き換えました

環境

  • Java 1.8.0_25

ソースコード

Githubで公開しているので最新版はそちらを御覧ください

package com.kakakikikeke.sample.v4;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;

import com.kakakikikeke.sample.utils.Utils;

public class Signature4Creator {

    static byte[] getHmacSHA256(String data, byte[] key) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(key, "HmacSHA256"));
        return mac.doFinal(data.getBytes("UTF-8"));
    }

    static byte[] getSignatureKey(String key, String dateStamp, String regionName, String serviceName) throws Exception {
        byte[] kSecret = ("AWS4" + key).getBytes("UTF-8");
        byte[] kDate = getHmacSHA256(dateStamp, kSecret);
        byte[] kRegion = getHmacSHA256(regionName, kDate);
        byte[] kService = getHmacSHA256(serviceName, kRegion);
        byte[] kSigning = getHmacSHA256("aws4_request", kService);
        return kSigning;
    }

    static String getHmacSHA256Digest(String data, byte[] key) throws Exception {
        Mac mac = Mac.getInstance("HmacSHA256");
        mac.init(new SecretKeySpec(key, "HmacSHA256"));
        byte[] digest = mac.doFinal(data.getBytes("UTF-8"));
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < digest.length; i++) {
            String tmp = Integer.toHexString(digest[i] & 0xff);
            if (tmp.length() == 1) {
                buffer.append("0").append(tmp);
            } else {
                buffer.append(tmp);
            }
        }
        return buffer.toString();
    }

    static String getDigest(String str) throws UnsupportedEncodingException, NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        md.update(str.getBytes("UTF-8"));
        byte[] digest = md.digest();
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < digest.length; i++) {
            String tmp = Integer.toHexString(digest[i] & 0xff);
            if (tmp.length() == 1) {
                buffer.append("0").append(tmp);
            } else {
                buffer.append(tmp);
            }
        }
        return buffer.toString();
    }

    static void execUrl(String url, Map<String, String> headers) throws HttpException, IOException {
        // HttpMethodBase hmb = new GetMethod(url);
        // for POST Method
        HttpMethodBase hmb = new PostMethod(url);
        for (Entry<String, String> entry : headers.entrySet()) {
            hmb.setRequestHeader(entry.getKey(), entry.getValue());
        }
        System.out.println("======url======");
        System.out.println(url);
        System.out.println("======headers======");
        for (Header header : hmb.getRequestHeaders()) {
            System.out.println(header.getName());
            System.out.println(header.getValue());
        }
        HttpClient hc = new HttpClient();
        hc.getHostConfiguration().setProxy("sample.proxy.com", 8080);
        int code = hc.executeMethod(hmb);
        String body = hmb.getResponseBodyAsString();
        System.out.println(code);
        System.out.println(body);
    }

    public static void main(String[] args) throws Exception {
        // String method = "GET";
        // for POST Method
        String method = "POST";
        String service = "ec2";
        String host = "ec2.amazonaws.com";
        String region = "us-east-1";
        String endpoint = "https://ec2.amazonaws.com";
        String requestParameters = "Action=DescribeRegions&Version=2013-10-15";
        String accessKey = "Please input your aws accessKey";
        String secretKey = "Please input your aws secretKey";
        String amzdate = Utils.getTimestamp("yyyyMMdd'T'HHmmss'Z'", "UTC");
        String datestamp = Utils.getTimestamp("yyyyMMdd", "UTC");
        // Take 1
        String canonicalURI = "/";
        String canonicalQueryString = requestParameters;
        String canonicalHeaders = "host:" + host + "\n" + "x-amz-date:" + amzdate + "\n";
        String signedHeaders = "host;x-amz-date";
        String payloadHash = getDigest("");
        String canonicalRequest = method + "\n" + canonicalURI + "\n" + canonicalQueryString + "\n" + canonicalHeaders + "\n" + signedHeaders + "\n" + payloadHash;
        System.out.println("======canonicalRequest======");
        System.out.println(canonicalRequest);
        // Take 2
        String algorithm = "AWS4-HMAC-SHA256";
        String credentialScope = datestamp + "/" + region + "/" + service + "/" + "aws4_request";
        String stringToSign = algorithm + "\n" + amzdate + "\n" + credentialScope + "\n" + getDigest(canonicalRequest);
        System.out.println("======stringToSign======");
        System.out.println(stringToSign);
        // Take 3
        byte[] signingKey = getSignatureKey(secretKey, datestamp, region, service);
        String signature = getHmacSHA256Digest(stringToSign, signingKey);
        System.out.println("======signature======");
        System.out.println(signature);
        // Take 4
        String authorizationHeader = algorithm + " " + "Credential=" + accessKey + "/" + credentialScope + ", " + "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature;
        System.out.println("======authorizationHeader======");
        System.out.println(authorizationHeader);
        // Send Request
        String requestURL = endpoint + "?" + canonicalQueryString;
        System.out.println("======requestURL======");
        System.out.println(requestURL);
        Map<String, String> headers = new HashMap<String, String>();
        headers.put("host", host);
        headers.put("x-amz-date", amzdate);
        headers.put("Authorization", authorizationHeader);
        execUrl(requestURL, headers);
    }

}

mainで宣言している文字列部分を必要な情報に書き換えれば別のAPIも呼び出すことができると思います
サンプルではEC2のDescribeRegionsをコールしています

AccessKeyとSecretKeyを自分のキーに書き換えてください

プロキシを通すのが前提になっているソースなので不要であればexecUrlメソッドのプロキシの部分をコメントアウトするか削除してください

結構いろいろなライブラリを使っているので必要に合わせてjarをインポートしてください
Mavenであればpom.xmlを公開しているので、同じものを書けばOKです

書き換えたらこのクラス自体を実行すればOKです

最後に

Signature Version 4はややこしすぎて困ります
Java SDKを使えばいいじゃんという話ですが、AWS互換APIとかを作っている方とかは実装がどうなっているのか知りたくなると思います
SDKの実装見ればいいじゃんという話にもなりますが、SDKのソースを見るのが結構たいへんで見てもどこを持ってくればいいのかよくわからず、結局いろいろドキュメントとか読みながら頑張ってJavaで書いてみました

2015年4月7日火曜日

Serverspec で公開鍵+ Passphrase で SSH ログインしてテストする方法

概要

サーバへのSSHログインが公開鍵+パスフレーズによる認証の場合にServerspecを実行する方法を紹介します

環境

  • CentOS release 6.6 (Final)
  • Serverspec 2.14.0

設定方法

spec_helper.rb の修正

以下の設定をspec_helper.rbに追記します

options[:keys] = ENV['KEY'];
options[:passphrase] = ENV['PASSPHRASE'];
options[:user] = ENV['USER'] || Etc.getlogin

環境変数「KEY」「PASSPHRASE」「USER」を実行時にセットできるようにします
追記した版のspec_helper.rbの全貌はこちら

require 'serverspec'
require 'net/ssh'

set :backend, :ssh

if ENV['ASK_SUDO_PASSWORD']
  begin
    require 'highline/import'
  rescue LoadError
    fail "highline is not available. Try installing it."
  end
  set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false }
else
  set :sudo_password, ENV['SUDO_PASSWORD']
end

host = ENV['TARGET_HOST']

options = Net::SSH::Config.for(host)

## for login key + passphrase configurations
options[:keys] = ENV['KEY'];
options[:passphrase] = ENV['PASSPHRASE'];
options[:user] = ENV['USER'] || Etc.getlogin
# options[:user] ||= Etc.getlogin

set :host,        options[:host_name] || host
set :ssh_options, options

実行方法

上記を記載したら実行します
実行方法は以下の通りです

rake spec:targetHost USER=hoge PASSPHRASE=fuga KEY=/opt/keys/hoge_login_key.pem

USERは指定しないと現在ログイン中のユーザで実行されます

最後に

今回は環境変数を使って実現していますが、例えばJSONファイルに設定を記載してそれを読み込むことでログインすることも可能だと思います

2015年4月6日月曜日

Graphviz を使って dot 言語で Server のインフラ構成を管理してみる

概要

Graphvizを使ってインフラ構成図をdotファイルで管理してみました
過去にRubyからGraphvizを使う方法を紹介しましたが、今回はGraphvizのみを使ってDotという言語でサーバの構成管理をしてみました

環境

  • CentOS release 6.6 (Final)
  • Graphviz (dot) 2.26.0

インストール

Graphvizのインストール

CentOSであればyumコマンドを使ってインストールすることが可能です

yum -y install graphviz

サンプルの動作確認

軽くサンプルの動作確認をしてみます

  • touch sample.dot
    以下の内容を記載します
digraph sample {
  LB -> server001;
  LB -> server002;
  server001 -> db001;
  server002 -> db001;
}

これをPNG形式の画像に変換してみます
dotというコマンドを使って画像を作成します

dot -T png sample.dot -o sample.png

「-T」オプションで出力する画像のタイプを指定します
「-o」オプションで出力先のファイル名を指定します

出力されたPNGファイルを確認すると以下のようになっていると思います
sample.png

これだけでも構成図っぽいですがもう少し拡張してみます

もう少し拡張してみる

サーバをroleごとに管理する

例えば今回の場合はserver001とserver002はいわゆるWebアプリケーションサーバだとします
「web」というロールを作成してそこにserver001と002を属するようにrole分けしてみます

sample.dotを以下のように拡張します

digraph sample {
  LB -> server001;
  LB -> server002;
  server001 -> db001;
  server002 -> db001;

  subgraph cluster_web {
    // node [style = filled];
    // style = filled;
    server001;
    server002;
    label = "web";
    color = blue;
  }
}

subgraphという命令を使います
ポイントはsubgraphの名称が「cluster」で始まらないと線が描画されない点です

好きな名前を使うと線が描画されない現象にハマるので注意しましょう

最低限記載したほうが良さそうなのは属性は「label」と「node名」かなと思います
「label」を指定すると囲った線内にラベルを表示してくれます
「node名」を指定すると指定したnode名を線で囲ってくれます
その他、レイアウトや色を指定するオプションがありますが、ここは自由に指定するといいと思います

この状態で出力される画像は以下の通りです
sample2.png

LISTENしているポートを記載する

例えばLBは80番でLISTENしていてサーバの8080に流すと言ったパターンは多くあると思います
今回はポート情報をノード内のテーブル構成を使って表現してみたいと思います

sample.dotを更に以下のように拡張します

digraph sample {
  node [shape = record];
  LB [label = "{ 80 | LB }"];
  server001 [label = "{ 8080 | server001 }"];
  server002 [label = "{ 8080 | server002 }"];

  LB -> server001;
  LB -> server002;
  server001 -> db001;
  server002 -> db001;

  subgraph cluster_web {
    // node [style = filled];
    // style = filled;
    server001;
    server002;
    label = "web";
    color = blue;
  }
}

ポイントは各種ノード情報にlabel属性を指定して表示をテーブル構造に変更する点です
テーブル構造を使うためにはnode [shape = record]を指定する必要があります
shapeにはrecord or Mrecordを指定することができます
Mrecordを指定すると若干角が丸まった図形を使用することができます

これで画像を作成するとnodeの上部にポート番号が表示され下部にリソース名が表示されるようになると思います
sample3.png

DBのサーバだけ図形のスタイル変更する

現在はすべての図形が同じ形の図形ですがDBサーバはわかりやすいように別の形にしたいということはあると思います
なので、DBサーバだけ別の形にしてみたいと思います

sample.dotを更に以下のように拡張します

digraph sample {
  node [shape = record];
  LB [label = "{ 80 | LB }"];
  server001 [label = "{ 8080 | server001 }"];
  server002 [label = "{ 8080 | server002 }"];
  db001 [shape = box3d];

  LB -> server001;
  LB -> server002;
  server001 -> db001;
  server002 -> db001;

  subgraph cluster_web {
    // node [style = filled];
    // style = filled;
    server001;
    server002;
    label = "web";
    color = blue;
  }
}

db001のnodeに対してshape属性を追加することで個別に形を変更することができます
今回は「box3d」を指定しました
他にもいろいろな図形を指定できるので、その他の形についてはこちらを御覧ください
これでDBサーバだけそれっぽい形に変更することができました
sample4.png

なぜこんなことをするのか

インフラ構成図を作成するときに自分がよく見るケースはパワポ等を使ってオートシェイプでポチポチ作成するケースです
パワポ等で作成するとインフラ構成に変更があった場合などに、いちいちパワポを開いて編集しなければならないとかいろいろと面倒です

今回はとりあえずDot言語で管理できるようにしてみました
これだけでもgit等のバージョン管理システムを使えるので大きなメリットだと思っています
ただ、結局Dotファイルを手動で変更していると作業工数的にはパワポでの作業とあまり変わらなくなってしまいます

なので自分が最終的にやりたいと思っているのは

  • インフラ構成の情報をクラウドAPIを使って取得して
  • その情報をDotファイルに落としこんで
  • 作成したDotファイルをJenkinsがビルドして画像を作成し
  • 作成した画像をクラウドストレージにアップロードして
  • ブラウザ上で閲覧できるようにする
  • というJenkinsのジョブを毎日回す

ということが最終的にできればと思っています
これができればクラウド上に動的にサーバが作成されても何も編集することなく新しいサーバ構成図を作成し確認することができます
クラウドストレージにアップロードするのは、アップロードすること自体が目的ではなく、サーバ構成図をURIでアクセスできるようにするのが目的です

最後に

いかがでしたでしょうか
もちろん、この方法がインフラ構成の管理方法として完全な正解ではないと思っています
もっと効率的かつ簡易に管理する方法はあると思っています

ただ「Infrastructure as Code」という言葉があるように少なくともコードで管理することは重要だと思っています
もちろん Chef や Puppet のコードがすでに見える化されているならばこんなことはする必要ないと思いますし、二重管理にもなるのでやること自体微妙かと思います

それでも Chef や Puppet はあくまでもサーバの設定自体をコード化するツールかなと思っているのでインフラ構成自体を見える化するにはやはり別のツールや手法が必要なのかなと思っています
(もしかしたら Chef のレシピからサーバ構成図を作成するツールがすでにあったりするかもしれないですが)

2015年4月2日木曜日

Intel x86 の高速 Android エミュレータでスクリーンショットが取得できない

概要

Android のエミュレータを高速にする方法としてIntel x86のCPUをエミューレートする方法が有名ですが、高速エミュレータを使っていてスクリーンショットが取得できない現象に遭遇しました
これまではわざわざARM CPUを使ったエミュレータを別途作成して、それを起動した上でスクリーンショットを取得していたのですが高速エミュレータ上でもうまく取得できるようになったので紹介します

環境

  • Mac OS X 10.10.2
  • Eclipse Luna 4.4.1
  • Android ADT extentions 3.5.1
  • Android-SDK 24.1.2
  • Android 5.1 (API 22)

ポイント

高速エミュレータを動作させるための設定

エミュレータを作成する際にCPUにIntel x86のCPUを選択することはもちろんですが「Use Host GPU」のチェックボックスもONにすると思います
これをONにしないとエミュレータを起動してもずっと黒い画面から進まない状態になるからです(自分はそうなるので「Use Host GPU」をONにしています)

高速エミュレータとして設定したAVDの設定はだいたい以下のようになっていると思います
config_highspeed_emu.png

Use Host GPU をONにしていてもスクリーンショットを取得できるようにするには

これも動作したときの自分の状況ですがポイントはTargetで指定しているAPIのバージョンかなと思います
動作しないときは 4.4 や 5.0 の API Level を選択していたのですが 5.1 が Android SDK Manager からダウンロードできるようになり、それを設定してからはスクリーンショットが取得できるようになりました

なので今回の結果としては
Android SDK Manager で Android 5.1 (API 22) にアップデートする
というのが回答になります

Tips

実はこの問題は公式?でも2013年くらいから議論されています

2015年3月に入って進捗があったようでここでもアップデートしたら動作したという回答があるようです

バージョンや環境に依存する問題なのでアップデートしても動作しない可能性はありますが、とりあえず自分が「環境」の部分に記載したバージョンやマシンの組み合わせであれば動作したので紹介しました

2015年4月1日水曜日

CentOS 6.6 に yum コマンドで MongoDB をインストール

概要

過去にアーカイブ版をダウンロードしてインストール方法を紹介しました
MongoDBの最新版がyumでインストールできるみたいなのでインストールしてみました

環境

  • CentOS release 6.6 (Final)
  • MongoDB 3.0.1

インストール方法

まずmongodbが提供するリポジトリを追加します

touch /etc/yum.repos.d/mongodb-org-3.0.repo

作成した/etc/yum.repos.d/mongodb-org-3.0.repoに以下を記載します

[mongodb-org-3.0]
name=MongoDB Repository
baseurl=http://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/3.0/x86_64/
gpgcheck=0
enabled=1

これでyumインストールできるようになったのでインストールします
一旦yum clean allしておくといいかもしれません

yum clean all
yum install -y mongodb-org

正常に終了すればインストール完了です

動作確認

起動してみましょう
yumインストールしているのでserviceコマンドから起動できます

service mongod start

接続してみます
デフォルトのポート27017で接続します

mongo

接続が完了するとmongo用のプロンプトになります
DBを確認

> show databases
local  0.078GB

あとは普通にクエリを発行していけばOKです

Tips

  • 過去バージョンの2.6をインストールする場合はrepositoryを追加する際に以下のように記載します
[mongodb-org-2.6]
name=MongoDB 2.6 Repository
baseurl=http://downloads-distro.mongodb.org/repo/redhat/os/x86_64/
gpgcheck=0
enabled=1
  • 今回のyumコマンドでインストールされた主なパッケージは以下の通り
    • mongodb-org-server-3.0.1-1.el6.x86_64
    • mongodb-org-3.0.1-1.el6.x86_64
    • mongodb-org-tools-3.0.1-1.el6.x86_64
    • mongodb-org-shell-3.0.1-1.el6.x86_64
    • mongodb-org-mongos-3.0.1-1.el6.x86_64