AWS専用サーバ監視ツール「Livesonic」を作った時にAWS SDK For Ruby V2とGolangが大活躍した話


はじめましての方もそうでない方もこんにちは！　[@srockstyle](https://twitter.com/srockstyle)です！AWS Advent Calendar12/6分を担当させていただきます！　宜しくお願いします！

Advent Calendarはじめてですが宜しくお願いします！

最近社内向けにAWSのクラスタを監視するために死活監視ツールを作ってました。現在のところAWS-SDK For Ruby V2をこれでもかこれでもかと使いまくってます。

その作った経緯的な話をできればと。

## 概要

![rogo.png](https://qiita-image-store.s3.amazonaws.com/0/2820/c51520bd-cbf8-5633-0caa-5effc722ebc0.png "rogo.png")

これはnagiosやsensuと同じ、サーバを監視するツールです。Ruby On Railsでできていて、サーバにインストールするタイプのサーバ監視ツールです。現在うちの会社でベータ版としてテスト運用中で、現在機能拡張を続け中です。

ただこれ単独では動かず、僕がGolangで作ったもう一個のツールとセットで動く監視ツールになってます。それがserverscoreです。

![chararogo.png](https://qiita-image-store.s3.amazonaws.com/0/2820/e2500940-3639-e27f-64bc-497b2eea9434.png "chararogo.png")

こっちはサーバの負荷状況を1〜100の数値で表し、それを表示するものです。1に近づけば近づくほど危険で、100に近づくほど安全です。

これを監視したいサーバに仕込むことで、あとはLivesonicからリモートで叩いてスコアを取得し、負荷状況に応じたアクションをlivesonicサーバからAWSSDKをつかって実行します。

## インスタンス情報取得

だいたいの監視ツールはサーバを一台一台登録していくものですが、livesonicはロードバランサー、つまりELBの情報を登録すれば大丈夫です。elbidなどの必要な情報を登録すると、自動的にぶら下がっているインスタンスリストを取得してDBに保管、監視を開始します。

```ruby

## ELBのClient作成
elb = Aws::ElasticLoadBalancing::Client.new(
	:region => Settings.aws_region,
    :access_key_id => Settings.aws_access_key,
    :secret_access_key => Settings.aws_access_secret
)

## インスタンスのリスト取得
instances_struct = elb.describe_instance_health(load_balancer_name: elb_id)
instance_list = instances_struct.instance_states.map do |i| {
	:id => i.instance_id,
	:elb_state => i.state,
	:description =>  i.description
}
end

```

describe_instance_healthというメソッドを使うとインスタンスのリストを取得できます。そのリストを元に必要な情報の配列が入ったハッシュを作ります。

さらにそれを元に今度はEC2の情報を取得します。


```ruby

ec2 = Aws::EC2::Client.new(
	:region => Settings.aws_region,
	:access_key_id => Settings.aws_access_key,
	:secret_access_key => Settings.aws_access_secret
)
result = Array.new
instance_list.each do |instance|
	servers = [instance[:id]]
	ec2_infos = ec2.describe_instances(instance_ids: servers)
	ec2_infos.reservations[0].instances.each do |i|
		res = Hash.new
		res[:tag] = i.tags[0].value
		res[:state_from_elb] = instance[:state_from_elb]
		res[:elb_description] = instance[:description]
		res[:fqdn] = i.public_dns_name
		res[:ip] = i.private_ip_address
		result.push(res)
	end
end

```

これでELBのIDからELBの情報、そしてその下にぶら下がっているインスタンスの情報を取得できました。

「Settings」とあるように重要な情報は全て[Settingslogic](https://github.com/binarylogic/settingslogic)というGemでYamlファイルで管理しています。長大な設定が必要なので分割したいなあ......って感じです。

### これを行うメリット

僕はサーバ・インフラエンジニアを10年近くやってますが、Nagiosとか、サーバのIPやホスト名が変更が走るたびに設定ファイル書き換えてデプロイしなければならないのがすごく面倒だなと感じてました。

また、最近Immutable Infrastructureという言葉が流行っていることもあり、デプロイするたびにサーバを捨てるという運用も珍しくありません。

うちの会社ではインスタンスを使い捨てする運用を行なっているのでNagiosみたいに監視対象が変わるタイミングで監視ツールに手をいれることをしたくなかったというのがあります。

## 監視サービスと監視対象ELB

ここは手動登録しますが、基本あとは放置です。

監視サービスを登録するときはリモートサーバで実行するコマンド、それにより期待する戻り値を登録しておきます。

あまり変更が入らないところですし、手動でもいいかなって感じです。

監視対象ELBはサービスのクラスタとして登録します。

## アラート通知される仕組みについて

今のところはserverscoreをリモートサーバで実行し、もどってきた値の閾値、それと対象サービスがダウンしているかしていないかで決めます。

リモート実行はSSHですが、ここではopen3をえらびました。入力、出力系を全部取得できるしっていうことで剪定しました。

```ruby
  ## serverscore実行
  def self.chk_instance_status(fqdn)
    command =  "#{Settings.ssh_command} #{Settings.check_user}@#{fqdn} serverscore"
    res = Open3.capture3(command)
    return res[0]
  end

  ## 死活監視。
  def self.chk_service_status(server,service)
    command =  "#{Settings.ssh_command} #{Settings.check_user}@#{server.fqdn} #{service.check_command}"
    status_word = service.success_return
    res = Open3.capture3(command)
    return  res == status_word
  end
```

死活監視などは最後成功したかしてないかをあらかじめ期待された戻り値で判断します。

## EC2回りで付ける予定の機能

急激なアクセス急上昇などでSpotインスタンスを使ったサーバ運用を行う場合があると思います。なのでこちらで値段やタイミングを細かく指定したオートスケールの機能を実装予定です。

あとインスタンスの値段ですね。。アクセス状況に応じてインスタンス数を自動増減できれば、僕が手を出す必要もなくなると思うので。

Amazonの担当者の方に聞いた話だと、オートスケールを自前実装するのはよくある話だそうです。できてきたら、だいぶこの辺は改善できるかなーと思ってます。

これによりインスタンスの値段を最適化していくことができるかなっていうたくらみをしてます。

## 会社的に必要だからつけた機能

12月ですし会社管理ドメインのうちアクティブなやつをみたいという話が来てました。これはRoute53のAPIを使いました。

```ruby
  def self.chk_domain
    route53 = Aws::Route53::Client.new(
      :region => Settings.aws_region,
      :access_key_id => Settings.aws_access_key,
      :secret_access_key => Settings.aws_access_secret
    )
    domains = Route53Status.all
    domains = domains.map{|domain| domain[:name] }
    resp = route53.list_hosted_zones()
    resp.hosted_zones.each do |zone|
      if !Route53Status.exists?(:name => zone.name)
        route53 = Route53Status.new
        route53.name = zone.name
        route53.caller_reference = zone.caller_reference
        route53.comment = zone.config.comment
        route53.record_count = zone.resource_record_set_count
        route53.save
      else
        domains.delete(zone.name)
      end
    end
    if domains.length > 0
      domains.each do |name|
        domain = Route53Status.find_by(:name => name)
        domain.destroy
      end
    end
  end
```

DBに保存しているのでsaveとかfindとかたまにありますが、これはいちいちAPI叩いてリスト引っ張ってくるよりDBに保存しちゃったほうがいいかなーっていう安直な考えです。これを実行すれば現在Route53で管理されているドメインが取得できます。

## 定期監視

Cronに登録します。これらのプログラムは Railsのlib/tasks以下にあるので、

 * rails runner *****

で実行できます。これを[Whenever](https://github.com/javan/whenever)でスケジュール管理し、Cronで回しています。それぞれのインスタンスのスコアとELBにぶら下がっているインスタンスの合計スコアの平均が履歴として残っていく仕組みです。

これを一ヶ月ごとにローテートしてます。

## 将来的にやらなければいけないこと

DockerコンテナをELBから見ることができるAmazon ECSが発表されたので、そちらの対応はしなきゃいけないと思います。

今後のサーバリソースはVM単位ではなくコンテナ単位になっていくのはさけられない流れですし、だんだんインスタンス経由ではなくAWSのサービスとモバイルが直接やり取りするようなマルチティアアーキテクチャが進んでいけばサービス全体の構造も変わっていきます。

デプロイ方法も、今まで通りCapistranoで並列で！というのも自分のPCにあるコンテナをアップしてコピーという感じになるだろうし。デプロイ回りも含めた監視ツールにしていかないとなーという感じです。


## livesonicとserverscoreについて

需要があるかわかりませんが、必要な機能が揃い次第Githubでパブリックリポジトリでオープンソースとして公開予定です。もしよろしければ「こんな機能あると嬉しいなー」「AWSいじるときこれ監視できるとありがたいなー」みたいな話をいただけると実装した上でアップしますので、もしなにかあれば[@srockstyle](https://twitter.com/srockstyle)までご連絡ください！