第6回ZABBIX-JP勉強会で登壇しました

約2年ぶりのZabbixの日本コミュニティZABBIX-JPの勉強会が昨日開催されました。

今回の勉強会は、執筆させていただいた書籍Amazon CAPTCHAの出版をきっかけにコミュニティの皆様にご協力いただき、開催にいたりました。
本当に皆様ありがとうございました。

発表資料はこちらに公開しています。

書籍の宣伝が中心にはなってしまいましたが、書籍に書けなかった機能の中にも有用な機能がたくさんあるよと少しは知ってもらえたかなと思います。
発表中に紹介したクラウド監視モジュールはもう少し仕上げて、後日公開します。

他の方の発表を聞いて

他の方の発表資料は、@sechiroさんがこちらのブログ第6回 Zabbix-JP勉強会は、2年の空白を埋める非常に濃い勉強会だった #zabbix_jp - 双六工場日誌にてまとめていただいてます。

いろんな活用方法の紹介があり、とても勉強になりました。
自分だけでは思いつく範囲にも限界があるので、こういった沢山の方の考え方が聞ける場というのはとても貴重ですね。
zabbix_senderを使ってLLDをプッシュ型で実行させるとか、とても面白いアイデアなのでぜひ参考にさせていただきます。
ZabbixのLLDとかローダブルモジュールとかユーザの心をくすぐる機能はやっぱり興味を持たれるようで、今後まだまだいろんな活用の可能性が見えました。

最後に

こういったコミュニティの活動を通して、運用の現場をよりよくする、仕事を「楽しく」するということに少しでも貢献できるといいなと改めて感じました。
なかなか定期的に勉強会を開催するというのはパワーが必要なことなので、難しいかもしれませんが、アイデアや意見を持ち寄り交換できる場として
コミュニティを活性化していけるよう今後も活動したいと思います。

「Zabbix統合監視徹底活用 ~複雑化・大規模化するインフラの一元管理」を執筆いたしました

本日2014/2/7、「Zabbix統合監視徹底活用」が技術評論社さんより出版されました。

どんな本?

この書籍は、Zabbixの基本的な使い方の解説というわけではなく、仮想化やクラウド化など、昨今の変化する環境の中で
Zabbixを活用しつつ、いかに効率良く運用することができるのかをまとめたものになります。

なので、Zabbixを初めて使ってみるという方は寺島さんの書籍や公式マニュアル(つい先日Zabbix2.0系のマニュアルの日本語版が公開されました。)を一度ご覧いただいてからお読み下さい。

Zabbixの便利な機能の解説や、Zabbixの監視の仕組みなど、少し深くつっこんでまとめたつもりです。
Zabbixを使っていて、「標準の監視テンプレートにはない値も収集してみたい」「もっと効率良く監視設定を済ませたい」「ここの挙動がよくわからない」などお思いの方には参考になるかもしれません。

Zabbixだけでなく、fluentdやChef等の他のツールも活用しつつ、仮想環境、クラウド環境、またそれらが混在した環境を管理する方法の解説も書いています。
この書籍で紹介した内容が必ずしも最適解ではないかもしれないですが、これまで色々試してきて有用だと感じたものを盛り込みました。


どんな思いで書いたか?

私自身、Zabbixは実はそれほど初期の頃から利用していたわけではなく、2010年頃に寺島さんが雑誌に書かれていた記事を見て興味を持ちました。
これまで、いろんなツールを利用しながら運用を回していたところを、実はZabbixなら1ツールで実現できるのではないかと思い、導入したのがきっかけです。
使っていくうちに、Zabbixのアーキテクチャの作りの良さからくるカスタマイズ性の高さを感じ、これを何か活かせないかと思うようになりました。

そんな中、Zabbixのいろいろな情報を調べたり、活用したツールを作ったりしてきました。

こういった活動の中で得た知識を、ぜひ多くの方に知っていただき、運用で困っている方の助けになれればと思い執筆させていただきました。
そのため、実は、書籍の内容にはやや偏りがあります。個人的に気になる点がどうしても多くなっています。
その点はご理解いただければと思います。

最新のZabbix2.2の機能についてもできるだけ解説を入れ込みたいので原稿の締め切りギリギリまで調査して追加しました。
しかし、確認が間に合わず入れることができなかった部分もあります(Windows WMI監視機能やLoadable Module機能など)。
その点はまた別の機会にどこかで発信させていただければなと思っています。


謝辞

今回、この書籍は、非常に多くの方の支えのもと作り上げることができました。
この場を借りて御礼申し上げます。
また、執筆の機会を与えてくださったTISにも感謝します。

最後に

早速、書籍を読んでいただきブログに書評を書いてくださった馬場さん、並河さん、安達さんありがとうございます!
(並河さんに新人の頃にかけていただいたお言葉、今でもはっきり記憶に残っています。)

まだまだ勉強不足で至らぬところも多々有りますが、地道に少しずつ前に進んでいこうと思います!!!

AWS Summit Tokyo 2013 Day1 レポート

6/5-6/6に開催されたAWS Summit Tokyo 2013のレポートです。
まずはDay1の模様です。


昨年に引き続きの参加だったのですが、今回はさらに参加者が多く感じました。
登録者数9429人という規模。去年の2倍だそうです。


Day1は主に上級者向けのテクノロジーセッションに参加してきたのですが、
昨年に比べると、よりエンタープライズも意識した「ハイブリッド」のテーマが多く見られたように感じました。
ハイブリッド環境での利用事例も実際に出てきていて、次のステップに着実に進んでいるようです。

以下、聴講した中で特に興味深かった2セッションのメモを残しておきます。
なお、資料は後日公開されるそうなので、気になる方はそちらをご覧ください。

クラウド技術を活用したリアルタイム広告 "Logicad"の入札・配信・ログ解析

ソネット・メディア・ネットワーク株式会社 開発リーダー 安田 崇浩氏

Logicad

Logicadは、リアルタイム広告配信サービスを提供。
ユーザからの1アクセス毎に広告権を広告主間で入札する仕組みをサービス化。
広告主は最適なユーザに自社の広告を打ち出せる。
ウェブサイト側は入札制度により最大の広告料収入がある。
広告主・ウェブサイト両者にメリットがあるサービス。


1秒間に数万件の入札処理を高速で処理しなければならない
AWSだけでは実現できない
→オンプレミスも利用。

  • CPU性能を極端に高くしなければならない
  • 3億件のユーザ情報を1ms以内に応答しなければならない
    • SSDを活用したKVSを利用。AEROSPIKEを活用

局所的な性能を実現するにはやはりオンプレミスが必要。

Logicadでは入札処理を行う高性能が求められる箇所はオンプレミス
それ以外の広告配信部分はAWSを活用。

AWSの活用シーン

広告配信の結果の蓄積のために。
RabbitMQを利用し、出力先をS3に。
広告主向けレポート配信Webアプリケーション
→S3に格納された配信結果ログを定期的にEMRで集計分析→DynamoDBに格納→アプリケーションはDynamoDBから情報取得

ハイブリッドを実現する仕組み

入札処理情報(オンプレミス)と広告配信部分(AWS)を連携する仕組みに非常に工夫がある。
オンプレミス-AWS間の接続はAWS Direct Connectを利用。
専用線接続になるので非常に高速に。
Direct Connectの利点

  • 速い
  • 安い

DirectConnect(ビットアイルのDirect Connectサービスを利用) の場合、EC2インスタンスとの接続のレイテンシが約4msのみ。
ダウンロードの通信コストも1TB辺りインターネット経由より1/4の価格に。

ここで更に課題。

レイテンシが4msで短くなったとはいえ、通常のLAN間のレイテンシに比べると40倍程度かかってしまう。
そこで、通信を効率よく行うための仕組みが必要に。

  • 複数のコネクションを同時に張るようにする方法
  • 1コネクションで複数のメッセージを送信する方法

2つを検討して、1コネクションで複数メッセージを送信する方法を採用。
そこでRabbitMQを利用。
キューを導入することでレイテンシが多少発生してもレスポンスを待つことなく複数のメッセージを連続で送付できる。

ハイブリッド構成を支えるAWSテクノロジー

アマゾンデータサービスジャパン株式会社プリンシパルソリューションアーキテクト 荒木 靖宏氏

ハイブリッドの代表的な利用シーン4パターン

1.開発での利用パターン

本番はオンプレミス、開発はAWS
この場合の課題
→環境の持ち運び
→再現性

2. DRで利用するバターン

いざというときに復旧できるようにデータをAWSにおいておく
この場合の課題
→データ同期
→データベース同期
→誘導・切り替え

3. 複数のシステムがハイブリッドでやり取りするパターン

一部の業務アプリがオンプレ、一部がAWSで実現する
この場合の課題
→移行、データ移行
→切り替え
→監視、制御

4. 1つのシステムがハイブリッド
バースト対応、バッチなどを大規模な処理が必要な部分をAWS上で。
それ以外のデータ管理等をオンプレミスに。
この場合の課題
→レイテンシ等

ハイブリッドシステムへの移行時に考えること
  • インフラ基盤の構成をどう実現するか
  • データ:移行とバックアップをどうするか
  • 運用をどうするか
ネットワーク分割のベストプラクティス
  • ログインする必要のないELB,RDS,Elasticache用のサブネットを作る
    • これらはスケールすると勝手にIPを食いつぶす→/22,/24あたりで大きめに切っておく
  • ログインする必要のあるEC2は目的別に
  • AWSAPI使用にはインターネット接続が必要
    • EIPを使用
    • NATインスタンスを使用
    • オンプレ側インターネット線の使用
  • AWSのリソースへのアクセスは原則ホスト名でアクセスを
DirectConnectの活用

DirectConnectをうまく活用。
VPCは暗号化処理などどうしてもオーバーヘッドがかかる。
そこでDirectConnectも併用して活用を。
例えば、本社のDCとAWS間はより高速に転送できるようDirectConnect。
それ以外の支社のDCとAWS間はVPCで。
AWS内にGatewayを立て、各環境間のHubとして実現。

ファイルコピーのベストプラクティス

AWSへのファイルコピーには2パターン考える。
1. S3に直接コピー
2. インスタンスを介してコピー

S3へ

  • バケットのリージョンを確認←できるだけ近いところの方がもちろん速い
  • 数10MBを超えるならばマルチパート化を活用
  • 並列転送して最後に統合する機能がある
  • 数十TPSを超えるならばキー名を分散化
    • S3への転送が長時間続くと制限を受けることがある
    • そのため、キーの頭の3文字ぐらいをランダムに設定することで制限をうけず送れるので非常に速度が改善される
  • 無駄なオペレーションはできるだけ使わない
    • リスト処理とかを頻繁にやらない
    • 転送時は転送だけに注力できるように

インスタンス

  • rsyncとか使うならインスタンス上に転送すればOK
  • Tsunami-UDP、Adpera、Skeedなどが高速転送を実現する数多くのツールもある
    • Tsunami-UDPはObama of AmericaのPJでも活用された
ブロックデバイスコピーのベストプラクティス

S3へ

  • AWS Storage Gateway
    • キャッシュ型と保管型がある
    • オンプレミスのローカルにデータを格納して高速に処理させつつ、非同期でS3に転送するなら保管型を

インスタンス

  • DRBDを利用
  • 高遅延の場合はDRBD proxy
VM Import/Exportを使ったシステムコピー

OSイメージごと移行
現在WindowsOSのみ

データベースの同期

キャッシュメモリの内容も含めコピーするには?
転送最適化サービスを利用して同期を実現

  • CloudOpt
  • Silver Peak
運用支援サービス
  • IAM
    • ユーザの権限を分けることが可能
  • STS
    • シングルサインオンなどの仕組みをすでに持っている場合等に一時的にユーザに権限を与えることが可能に
サードパーティツールの活用

構成管理:Puppet,Chef
監視:AppDynamics、New relic、AppNeta等
ログ:TreasureData等

VPCによるステージング環境テスト
Route53による重み付け

ハイブリッド環境を扱う場合、リクエストをRoute53で重みを付けて振り分けることが可能に。

SDKによるサービスカスタマイズと省力化

サードパーティ製も含めるとほぼ全ての言語を網羅

まとめ

AWSクラウドはハイブリッド構成への近道
その時、意識すべきことはシステムを'''疎結合にしていくことが重要'''。

Zabbix API host.update実行時の挙動について整理

Zabbix APIを使ってホスト情報を更新する場合のメソッドについて、改めて整理してみました。
Zabbix APIについてはこの辺りを参照してください。

Zabbix APIには、ホスト情報を管理するためのメソッドが用意されています。

  • host.create
  • host.delete
  • host.exists
  • host.get
  • host.getobjects
  • host.isreadable
  • host.iswritable
  • host.massadd
  • host.massremove
  • host.massupdate
  • host.update

この中のhost.update、host.massadd、host.massupdateの挙動について確認してみました。
特に気になっていたのが、ホストへのテンプレート割り当てを変更したい場合の挙動です。
ホストへのテンプレート割り当て設定を変更することで過去の監視結果データが削除されてしまうのか?、
されずにテンプレート割り当てだけが解除されるのか?などを確認してみました。
以下、3つのケースで試してみました。

ケース1 host.updateを使って割り当てテンプレートを入れ替えた場合

初期状態

ホスト: host1 (hostid : 10332)
割り当てテンプレート: template1 (templateid : 10331)
テンプレートアイテム: item1

この状態のホストに対して、APIでhost.updateを実行し、割り当てテンプレートを変更する
変更後の割り当てテンプレート: template2 (templateid : 10333)
変更後の割り当てテンプレートアイテム: item2

API実行時のパラメータは以下。

"method": "host.update",
"params": {
    "hostid":10332,
    "templates": [
        {"templateid":10333}   ←新たなテンプレートのIDのみを指定
    ]
}
※host.updateはパラメータとして、更新したい内容のみ指定すればOKです。

変更後の状態

ホスト: host1 (hostid : 10332)
割り当てテンプレート: template2 (templateid : 10333)
テンプレートアイテム: item1 , item2

item1はテンプレートアイテムではなく、ホストアイテムに自動変更。
item1のこれまでの監視結果は残ったまま。

ケース2 host.updateを使って割り当てテンプレートを追加した場合

初期状態はケース1と同様。

この状態のホストに対して、APIでhost.updateを実行し、割り当てテンプレートを追加する
追加テンプレート: template2 (templateid : 10333)
追加テンプレートアイテム: item2

API実行時のパラメータは以下。

"method": "host.update",
"params": {
    "hostid":10332,
    "templates": [
        {"templateid":10332},    ←元のテンプレートのIDを指定
        {"templateid":10333}   ←新たに追加するテンプレートのIDを指定
    ]
}

変更後の状態

ホスト: host1 (hostid : 10332)
割り当てテンプレート: template1, template2 (templateid : 10331,10333)
テンプレートアイテム: item1 , item2

item1のこれまでの監視結果は残ったまま。
あらたにitem2の監視が開始した。

ケース3 host.updateを使ってテンプレートを削除し、アイテム自体も削除する場合

ケース2の変更後の状態からtemplate2の割り当てを解除し、アイテム自体も削除する

API実行時のパラメータは以下。

"method": "host.update",
"params": {
    "hostid":10332,
    "templates_clear": [
        {"templateid":10333}    ←削除するtemplate2を指定
    ]
}

変更後の状態

ホスト: host1 (hostid : 10332)
割り当てテンプレート: template1 (templateid : 10331)
テンプレートアイテム: item1

template2の割り当てが解除され、item2のアイテム自体も削除された。
DBに入っているhistoryデータも全て削除された。

結論

host.updateのパラメータとして指定するtemplatesにtemplateidを追加したり、削除したりしても、基本的にこれまでの監視アイテムのデータがなくなることはありません。
host.update時にテンプレートIDを削除するとテンプレートリンクのみを削除したのと同じ状態になります。
テンプレートリンクと監視アイテム割り当て、監視データ全てを削除したい場合は、明示的にtemplates_clearでホスト情報を更新する必要があります。

なお、massaddを利用する場合、テンプレートの追加は追加したいテンプレートIDだけを指定すれば可能です。
ケース2をhost.massaddを使って実現する場合、次のパラメータ指定で実行

"method": "host.massadd",
"params": {
    "hosts":[
        {"hostid":10332}
    ],
    "templates":[
        {"templateid":10333}
    ]
}

massupdateを使う場合、個々のホストに対する処理はhost.updateと同じ挙動になります。
ケース1をhost.massupdateを使って実現する場合、次のパラメータ指定で実行

"method": "host.massupdate",
"params": {
    "hosts":[
        {"hostid":10332}
    ],
    "templates":[
        {"templateid":10333}
    ]
}

これらのメソッドを有効に活用することで益々自動化を促進することができます。

serverspecでZabbixサーバの稼働テストを書いてみた

先日http:/http://togetter.com/li/501076togetter.com/li/501076(5/10)開催されたProvisioning Frameworks Casual Talks vol.1に参加しました。
Provisioning Frameworks Casual Talks vol.1の中身は他の方がたくさんブログを書かれているのでそちらを参照して下さい。

Provisioning Frameworks Casual Talks vol.1 - Togetter

serverspecの話がたくさん出てきたので自分も試しにservespecを使ってテストコードを書いてみました。
ついでに、serverspecのver.0.3.0から利用できるようになったattributeの機能も使ってみました。

serverspecのattributeはこちらを参考にしました。

今回テストコードを書いたのは、Zabbixサーバが正しい状態で稼働しているかのテストです。
以下のようなテストを書いています。

前提条件
Zabbixサーバのテスト
  • 必要なパッケージがインストールされていること
  • Zabbixプロセスが稼働していること
  • Zabbixの初期起動設定がされていること
  • Zabbixの起動ユーザが作成されていること
  • Zabbixが正しいポートでリッスンしていること
  • Zabbixの設定ファイル(zabbix_server.conf)に最低限の設定(DB設定が行われていること)
MySQLサーバのテスト
  • 必要なパッケージがインストールされていること
  • MySQLプロセスが稼働していること
  • MySQLの初期起動設定がされていること
  • MySQLの起動ユーザが作成されていること
  • MySQLが正しいポートでリッスンしていること
  • MySQLの設定ファイル(/etc/my.cnf)にZabbix監視する上で有用な設定が行われていること
Apacheのテスト
  • 必要なパッケージがインストールされていること
  • httpdプロセスが稼働していること
  • httpdの初期起動設定がされていること
  • httpdの起動ユーザが作成されていること
  • httpdが正しいポートでリッスンしていること
  • httpdのzabbix用設定ファイル(/etc/httpd/conf.d/zabbix.conf)にZabbixのフロントエンドを利用する上で有用なphpのパラメータ設定が行われていること
実際に書いたテスト

実際に作成したファイルは次のようになります。
なお、今回は全て同じサーバ上で稼働している前提で以下のようにspecファイル類を作成しました。

$ serverspec-init
Select a backend type:

  1) SSH
  2) Exec (local)
  3) Puppet providers (local)

Select number: 2

attributes.yml
(設定は環境に応じて書き換えて下さい)

zabbix_server:
    :listen_port: 10051
    :user: zabbix
    :db_name: zabbix
    :db_user: zabbix
    :db_password: zabbixpassword
    :db_socket: /var/lib/mysql/mysql.sock
mysql_server:
    :listen_port: 3306
    :user: mysql
    :innodb_buffer_pool_size: 64M
    :innodb_log_file_size: 16M
    :innodb_log_files_in_group: 2
httpd:
    :listen_port: 80
    :user: apache
    :max_execution_time: 300
    :memory_limit: 128M
    :post_max_size: 16M
    :upload_max_filesize: 2M
    :max_input_time: 300
    :timezone: Asia/Tokyo

Rakefile
(attributes.ymlから値を取得し、サービス毎にテストができるよう設定)

require 'rake'
require 'rspec/core/rake_task'
require 'yaml'

attributes = YAML.load_file('attributes.yml')

desc "Run serverspec to all services"
task :serverspec => 'serverspec:all'

namespace :serverspec do
    task :all => attributes.keys.map {|key| 'serverspec:' + key }
    attributes.keys.each do |key|
        desc "Run serverspec to #{key}"
        RSpec::Core::RakeTask.new(key.to_sym) do |t|
            ENV['TARGET_SERVICE'] = key
            t.pattern = "spec/*/#{key}_spec.rb"
        end
    end
end

spec/spec_helper.rb
(attrubutes.ymlから値を取得し、各サービス毎に属性値を設定)

require 'serverspec'
require 'pathname'
require 'yaml'
### include requirements ###

include Serverspec::Helper::Exec
include Serverspec::Helper::DetectOS
include Serverspec::Helper::Attributes

attributes = YAML.load_file('attributes.yml')

RSpec.configure do |c|
  if ENV['ASK_SUDO_PASSWORD']
    require 'highline/import'
    c.sudo_password = ask("Enter sudo password: ") { |q| q.echo = false }
  else
    c.sudo_password = ENV['SUDO_PASSWORD']
  end
  c.before :all do
    c.os = backend(Serverspec::Commands::Base).check_os
  end
  attr_set attributes[ENV['TARGET_SERVICE']]
end

spec/localhost/zabbix_server_spec.rb

require 'spec_helper'

describe 'zabbix-server' do
    it { should be_installed }
    it { should be_enabled }
    it { should be_running   }
end

### related package install check ###
%w(zabbix-release zabbix-server-mysql zabbix-web-mysql zabbix-web zabbix-web-japanese zabbix zabbix-get).each do |pkg|
    describe pkg do
        it { should be_installed }
    end
end

describe attr[:user] do
    it { should be_user }
end

describe "port #{attr[:listen_port]}" do
    it { should be_listening }
end

describe '/etc/zabbix/zabbix_server.conf' do
    it { should be_file }
    it { should contain "DBName=#{attr[:db_name]}" }
    it { should contain "DBUser=#{attr[:db_user]}" }
    it { should contain "DBPassword=#{attr[:db_password]}" }
    it { should contain "DBSocket=#{attr[:db_socket]}" }
end

spec/localhost/mysql_server_spec.rb

require 'spec_helper'

describe 'mysql55-server' do
    it { should be_installed }
end

describe 'mysqld' do
    it { should be_enabled }
    it { should be_running }
end

describe attr[:user] do
    it { should be_user }
end

describe "port #{attr[:listen_port]}" do
    it { should be_listening }
end

describe '/etc/my.cnf' do
    it { should be_file }
    it { should contain "innodb_file_per_table" }
    it { should contain "innodb_buffer_pool_size=#{attr[:innodb_buffer_pool_size]}" }
    it { should contain "innodb_log_file_size=#{attr[:innodb_log_file_size]}" }
    it { should contain "innodb_log_files_in_group=#{attr[:innodb_log_files_in_group]}" }
    it { should contain "character-set-server=utf8" }
end

spec/localhost/httpd_spec.rb

require 'spec_helper'

describe 'httpd' do
    it { should be_installed }
    it { should be_enabled   }
    it { should be_running   }
end

describe attr[:user] do
    it { should be_user }
end

describe "port #{attr[:listen_port]}" do
    it { should be_listening }
end

describe '/etc/httpd/conf.d/zabbix.conf' do
    it { should be_file }
    it { should contain "php_value max_execution_time #{attr[:max_execution_time]}" }
    it { should contain "php_value memory_limit #{attr[:memory_limit]}" }
    it { should contain "php_value post_max_size #{attr[:post_max_size]}" }
    it { should contain "php_value upload_max_filesize #{attr[:upload_max_filesize]}" }
    it { should contain "php_value date.timezone #{attr[:timezone]}" }
end

実行するとこんな感じ

$ rake serverspec
/usr/bin/ruby -S rspec spec/localhost/zabbix_server_spec.rb
.................

Finished in 0.61575 seconds
17 examples, 0 failures
/usr/bin/ruby -S rspec spec/localhost/mysql_server_spec.rb
...........

Finished in 0.31917 seconds
11 examples, 0 failures
/usr/bin/ruby -S rspec spec/localhost/httpd_spec.rb
...........

Finished in 0.26358 seconds
11 examples, 0 failures

まだまだ勉強不足なため記述がこれで正しいのかは怪しいですが、非常に簡単にサーバの状態をテストできるので活用できそうです。
attributeの機能のおかげでテストコードの使い回しがとてもやりやすくなっているかと思います。

GateOneをAPI認証機能有効化してアプリケーションに組み込む

GateOneには、認証機能が備わっています。
Googleの認証と連携したり、PAM認証と連携するなどができるようです。
(GateOneについては前回記事GateOneで複数のSSH接続をブラウザで統合管理 - ike-daiの日記を参照して下さい。)

今回は、GateOneを別のアプリケーションに組み込んで使う場合のAPI認証を有効化する方法を試してみます。
手順は公式マニュアルをもとに実施しています。

server.confの変更

GateOneのserver.confの設定でauth = 'api'に設定します。(デフォルトはnone)

$ vim /opt/gateone/server.conf
・・・略
auth = 'api'
・・・略

APIキーの発行

gateone.pyスクリプトを使ってAPIキーを発行します。

$ /opt/gateone/gateone.py --new_api_key
[I 130322 09:35:19 gateone:2880] A new API key has been generated: MDg0MGFmY2I0YTBiNGU0OTg5NGRjMzcwNmM2NjBkNDEyA
[I 130322 09:35:19 gateone:2881] This key can now be used to embed Gate One into other applications.

発行されたAPIキーの情報はserver.confのapi_keysに登録されています。

api_keys = "MDg0MGFmY2I0YTBiNGU0OTg5NGRjMzcwNmM2NjBkNDEyA:ODg0MmRhYjJkYzU0NDg2M2E4NjQ5MTk3NzY0YjkzOWQwM"

APIキー:シークレットキーのセットで書きこまれているのがわかります。

アプリケーション側の設定

次に、GateOneを組み込みたいアプリケーション側の設定です。

まず、API認証を有効にしない場合にはこのような感じでGateOneを組み込むことができます。
https://hostnameでGateOneが稼働している場合の例です。

<html>
    <head>
        <script src="https://hostname/static/gateone.js"></script>
    </head>
    <body>
        <div style="width: 60em; height: 30em;">
            <div id="gateone"></div>
        </div>
       <span onclick="GateOne.init({url:'https://hostname/'});">Start GateOne</span>
    </body>
</html>

gateone.jsを読み込み、GateOne.initを実行すると、

のエリアにGateOneのコンソールを表示できます。
認証設定がない場合、GateOneはANONYMOUSユーザでアクセスした状態になります。
この状態では、ブックマークの情報や鍵情報が他の誰からも利用できるため、セキュリティ的にもよくありません。
そこで、このファイルを変更し、API認証を実現してみます。

API認証を実施するには、GateOne.initの実行時に認証オブジェクト({auth: auth_obj})を渡す必要があります。
認証オブジェクトは次のようなユーザ名やAPIキー等を持ったオブジェクトです。

authobj = {
    'api_key': 'MjkwYzc3MDI2MjhhNGZkNDg1MjJkODgyYjBmN2MyMTM4M',
    'upn': 'joe@company.com',
    'timestamp': '1323391717238',
    'signature': "f6c6c82281f8d56797599aeee01a5e3efab05a63",
    'signature_method': 'HMAC-SHA1',
    'api_version': '1.0'
}

※GateOne公式マニュアルより引用

今回は試しにJavaScriptで認証オブジェクトを生成して許可するようにします。
このようになります。

<html>
    <head>
        <script src="https://localhost/static/gateone.js"></script>
        <script src='http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/hmac-sha1.js'></script>
        <script>
            function ConnectGateOne(){
                var api_key = 'MDg0MGFmY2I0YTBiNGU0OTg5NGRjMzcwNmM2NjBkNDEyA';
                var upn = 'ikeda@example.com';
                var now = new Date();
                var timestamp = now.getTime();
                var signature_method = 'HMAC-SHA1';
                var api_version = '1.0';
                var secret = 'ODg0MmRhYjJkYzU0NDg2M2E4NjQ5MTk3NzY0YjkzOWQwM';
                var hash = CryptoJS.HmacSHA1(api_key + upn + timestamp, secret);
                var signature = hash.toString(CryptoJS.enc.Hex);
                
                var authobj = {
                    'api_key':api_key,
                    'upn':upn,
                    'timestamp':timestamp.toString(),
                    'signature':signature,
                    'signature_method':signature_method,
                    'api_version':api_version
                }
                console.log(authobj);
                
                GateOne.init({auth: authobj, url:'https://hostname/'});
            }
        </script>
    </head>
    <body>
        <div style="width: 60em; height: 30em;">
            <div id="gateone"></div>
        </div>
       <span onclick="ConnectGateOne();">Start GateOne</span>
    </body>
</html>

注意点としては、timestampの値を文字列型で持たせる必要がある点です。
timestampをそのまま数値型で持たせていると、GateOne内で処理する際に、signature生成処理中にエラーが発生します。

[E 130322 10:51:48 websocket:261] Uncaught exception in /ws
    Traceback (most recent call last):
      File "/usr/lib/pymodules/python2.7/tornado/websocket.py", line 258, in wrapper
        return callback(*args, **kwargs)
      File "/opt/gateone/gateone.py", line 1102, in on_message
        self.commands[key](value)
      File "/opt/gateone/gateone.py", line 1245, in authenticate
        secret, api_key, upn, timestamp)
      File "/usr/lib/pymodules/python2.7/tornado/web.py", line 2068, in _create_signature
        hash.update(utf8(part))
      File "/usr/lib/pymodules/python2.7/tornado/escape.py", line 168, in utf8
        assert isinstance(value, unicode)
    AssertionError

このようにすることで、GateOneへのログインユーザがikeda@example.comになります。
ログインした状態でブックマークの追加や鍵の追加を行うと、他のユーザ(ANONYMOUS等)からはこの情報は見れなくなります。
ブックマーク設定や鍵については、「/opt/gateone/users/ユーザ名」ディレクトリの配下に保存されるようになります。

今回は簡単に動作確認するためにJavaScriptAPI認証を行う処理を記述しましたが、実際には、組み込むアプリケーション内で上記の処理を行うようにします。
PHPRubyPythonでの記述方法についてはGateOneの公式サイトに書かれているので参考にして下さい。
公式マニュアル

Zabbix Agentのプロセスを調べてみた

Zabbix AgentにはActiveチェックとPassiveチェックの2つの方法で監視を行う仕組みが備わっています。
PassiveチェックはZabbixServerからZabbixAgentに対してアイテムキーを送り、そのアイテムキーに該当する監視結果をZabbix AgentからZabbix Serverに返す仕組みです。
一方、Activeチェックは、次の仕組みで実現されます。

  1. Zabbix AgentからZabbix Serverに対して、どういった項目を監視すればいいのかを示したアイテムリストを送付するよう要求
  2. 取得したアイテムリストの内容に従ってZabbix Agentが監視を実施
  3. 監視結果を以下のような形式のデータとしてZabbix Serverに送付
{ "request":"agent data", "data":[ { "host":"", "key":"log[\/home\/zabbix\/logs\/zabbix_agentd.log]", "value":" 13039:20090907:184546.759 zabbix_agentd started. ZABBIX 1.6.6 (revision {7836}).", "lastlogsize":80, "clock":1252926015 }, { "host":"", "key":"agent.version", "value":"1.6.6", "clock":1252926015 } ], "clock":1252926016 }

※Zabbix公式マニュアルより引用:Zabbix2.0マニュアル

このような仕組みの違いがあります。

今回気になったのは、Zabbix Agentの設定ファイル内のStartAgentsの項目です。
StartAgentsの説明は次のように書かれています。

Number of pre-forked instances of zabbix_agentd that process passive checks.
If set to 0, disables passive checks and the agent will not listen on any TCP port.

StartAgentsで設定できるのはPassiveチェック用スレッドの数だけのようです。

では、Activeチェック用のスレッドはどうなっているのか?
ということでZabbixAgentのソースコードを眺めてみました。
(今回調査したのはZabbix2.0.5のソースコードです。)

Zabbixは3つの種類のスレッドで成り立っています。

  1. collectorスレッド
  2. listenerスレッド
  3. active_checksスレッド

1はZabbixAgentが稼働するサーバ内のCPUやメモリ等のリソース情報を収集するスレッドです。これはソースコード内で1個と指定されています。
2はStartAgentsで数を設定するPassiveチェック用の待受スレッドです。StartAgentsで指定した数だけ稼働します。ZabbixServerからより頻繁に問い合わせを行うなど負荷が気になる場合にはStartAgentsの数を増やすことで対応できます。
3はActiveチェック用のスレッドです。ソースコードを見ると、active_checksスレッドの数については次のように決められているようでした。

src/zabbix_agent/zabbix_agentd.c

286行目
static int      add_activechk_host(const char *host, unsigned short port)
{
        int     i;

        for (i = 0; i < CONFIG_ACTIVE_FORKS; i++)
        {
                if (0 == strcmp(CONFIG_ACTIVE_ARGS[i].host, host) && CONFIG_ACTIVE_ARGS[i].port == port)
                        return FAIL;
        }

        CONFIG_ACTIVE_FORKS++;
        CONFIG_ACTIVE_ARGS = zbx_realloc(CONFIG_ACTIVE_ARGS, sizeof(ZBX_THREAD_ACTIVECHK_ARGS) * CONFIG_ACTIVE_FORKS)
;
        CONFIG_ACTIVE_ARGS[CONFIG_ACTIVE_FORKS - 1].host = zbx_strdup(NULL, host);
        CONFIG_ACTIVE_ARGS[CONFIG_ACTIVE_FORKS - 1].port = port;

        return SUCCEED;
}

311行目
static void     get_serveractive_hosts(char *active_hosts)
{
        char    *l = active_hosts, *r;
        int     rc = SUCCEED;

        do
        {
                char            *host = NULL;
                unsigned short  port;

                if (NULL != (r = strchr(l, ',')))
                        *r = '\0';

                if (SUCCEED != parse_serveractive_element(l, &host, &port, (unsigned short)ZBX_DEFAULT_SERVER_PORT))
                        goto fail;

                rc = add_activechk_host(host, port);

CONFIG_ACTIVE_FORKSが稼働するactive_checksスレッドの数の設定です。
0からスタートし、Activeチェックホストの設定毎に1つ加算されるようになっています。

zabbix_agentd.confの設定でServerActive=hostname1:10051,hostname2:10051のようにカンマ区切りで対象のZabbixServerを複数指定することができます。
(※このActive監視のマルチサーバサポートはZabbixの1.8.12より実装されています。BlueSkyDetectorさんのslideshare, Zabbix Blog)
どうやらこのサーバの設定1つにつき1スレッドを立ち上げて処理を行うような仕組みになっているようです。

StartAgents=3
ServerActive=hostname1,hostname2
と設定した上でZabbixAgentを起動すると、
zabbix_agentd.logに以下のログが追記され、listnerスレッドが3個、active_checksスレッドが2個、collectorスレッドが1個起動しているのがわかります。

  1801:20130305:072636.607 agent #1 started [listener]
  1802:20130305:072636.607 agent #2 started [listener]
  1803:20130305:072636.607 agent #3 started [listener]
  1805:20130305:072636.608 agent #5 started [active checks]
  1804:20130305:072636.608 agent #4 started [active checks]
  1800:20130305:072636.609 agent #0 started [collector]

今回はここまで。
ここから先の具体的な処理内容はまた時間がある時にでも見てみようと思います。