2019年6月30日日曜日

Zabbix Docker Monitoring を使ってコンテナ監視をしてみた

概要

Zabbix Docker Monitoring を使って Zabbix でコンテナ監視をしてみました
仕組みは cAdvisor などと同じようです

環境

  • Zabbix サーバ
    • Ubuntu 16.04 LTS
    • docker 18.09.6
    • Zabbix Server 4.2
  • コンテナホスト
    • macOS 10.14.5
    • docker for Mac 18.09.2
    • dockbix-agent-xxl-limited 3.4-3

仕組み

Zabbix Docker Monitoring はコンテナで動作します
このコンテナを Zabbix サーバにホストとして登録します

他のコンテナの情報をどうやって取得しているかというとコンテナホストの / をマウントし docker.sock を使って各コンテナの情報を取得しています
取得した情報は Zabbix サーバに登録された Zabbix Docker Monitoring コンテナのアイテムとして登録されます
なので各コンテナが Zabbix サーバにホストとして登録されるわけではありません

Zabbix サーバ起動

なんでも OK です
本記事内では docker で起動しています (参考)

テンプレート登録

まずはテンプレートを登録します
テンプレートはこの後起動する Zabbix Docker Monitoring コンテナに割り当てるテンプレートになります
このテンプレートには同一ホスト上で起動しているコンテナをディスカバリルールが登録されています

  • wget 'https://raw.githubusercontent.com/monitoringartist/zabbix-docker-monitoring/master/template/Zabbix-Template-App-Docker.xml'

あとは WebUI で XML ファイルをインポートすれば OK です
「Template App Docker」というテンプレートがインポートされれば OK です

またデフォルトだとディスカバリのインターバルが 600s と長いので 30s に変更しましょう
テスト後は 600s に戻して OK です

テンプレートを登録する用のコンテナがあるがそれは 4.2 では使えない

わざわざテンプレートをダウンロードして WebUI で登録するのが面倒な人のために API を使ってテンプレートを登録するコンテナがあるようなのですが 4.2 では使えませんでした

docker run --rm \
  -e XXL_apiurl=http://192.168.99.200 \
  -e XXL_apiuser=Admin \
  -e XXL_apipass=zabbix \
  monitoringartist/zabbix-templates

-32602 (Invalid params.): Invalid parameter "/rules/applications": unexpected parameter "updateExisting". というエラーが発生し登録できませんでした

Zabbix Docker Monitoring コンテナを起動する

監視したいコンテナが起動しているコンテナホストで実行しましょう
今回は docker for mac で起動しています

docker run \
  --name=dockbix-agent-xxl \
  --privileged \
  -p 10050:10050 \
  -v /:/rootfs \
  -v /var/run:/var/run \
  --restart unless-stopped \
  -e "ZA_Server=192.168.99.200,172.17.0.1" \
  -e "ZA_ServerActive=192.168.99.200" \
  -d monitoringartist/dockbix-agent-xxl-limited:latest

ZA_Server172.17.0.1 が入っているのはコンテナで Zabbix サーバを起動してるためなので Zabbix サーバがコンテナでない場合は不要です

10050 ポートは Zabbix サーバから見えないと行けないので LISTEN させます

ホストとして登録する

起動した Zabbix Docker Monitoring コンテナを Zabbix サーバにホストとして登録します
Discovery でも OK ですし手動でホストの 10050 番を登録しても OK です

またホストとして登録したら先程登録した「Template App Docker」を割り当てます
これでこのコンテナ経由で他のコンテナの情報をアイテムとして取得できるようになりました

動作確認

適当に Mac 上でコンテナを起動してみましょう

  • docker run -d -p 6379:6379 redis

あとはディスカバリの 30s 後に Monitoring -> Latest data を確認するとアイテムとしてコンテナの情報が格納されているのが確認できると思います

最後に

Zabbix Docker Monitoring を使ってみました
コンテナホストのルートパスをマウントして直接デーモンの情報を吸い上げるコンテナを起動します
そしてそのコンテナを Zabbix のホストとして登録することでそのホストのアイテムとして各コンテナの情報を登録するという仕組みでした

cAdvisor も同じような仕組みで各コンテナの情報を吸い上げているので仕組み的には既存の監視方法と変わらないと思います
ホストとしての登録やテンプレートの登録が面倒ですがすでに Zabbix を使っているのであればこれで十分かなと思います
トリガーやアクションも使えるので

あとはコンテナホストに 1 つ必ず起動する必要があるので docker-swarm などを使っている場合はホストが追加されたときに必ず Zabbix Docker Monitoring コンテナを起動する必要があります
k8s であれば DaemonSet を使えば簡単に実現できると思います

参考サイト

2019年6月29日土曜日

redis-rb でコネクションがどう扱われているか簡単に確認してみた

概要

Redis.new したオブジェクトをインスタンス変数として保持した場合と毎回生成するような処理を比べた場合に redis-server に対するコネクションがどうなっているか挙動を確認してみました

環境

  • macOS 10.14.5
  • Ruby 2.6.2p47
    • redis-rb 4.1.2
  • Redis 5.0.5

準備

  • bundle init
  • vim Gemfile
gem "redis"
  • bundle install --path vendor

インスタンス変数にした場合

Redis.new したオブジェクトをインスタンス変数にしてインスタンスメソッドで使う回すようにします
おそらくこれが最も一般的な使い方かなと思います

  • vim test.rb
require 'redis'

class CRedis
  def initialize
    @client = Redis.new
  end

  def keys
    @client.keys
  end

  def list
    @client.client('list')
  end
end

client = CRedis.new
3.times do
  client.list.each { |c| p c['id'] }
  client.keys.length
  sleep 10
end

key の数をチェックしているだけです
チェックしたあとにクライアントの状態が変化しているか確認します
redis-server は接続しているクライアントそれぞれに id を振り識別できるようにしています
id はクライアントの接続/切断があるたびにインクリメントしていきます
試しに redis-cli client list をたくさん実行するとどんどん id が増えていくのが確認できると思います

これを実行して見ましょう

  • bundle exec ruby test.rb
"49"                                                  
"49"                         
"49"

結果は同じ id が表示され続けました
つまりインスタンス変数にした場合は redis-rb では同じコネクションを使って接続していることがわかります (当然と言えば当然)

ちなみに上記を実行中に別のターミナルで確認コマンドとして以下を実行してみると以下のような結果が返ってきました

  • while true; do redis-cli client list; sleep 1; done
id=49 addr=127.0.0.1:59174 fd=8 name= age=5 idle=5 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys
id=50 addr=127.0.0.1:59175 fd=9 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
id=49 addr=127.0.0.1:59174 fd=8 name= age=6 idle=6 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys
id=51 addr=127.0.0.1:59176 fd=9 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
id=49 addr=127.0.0.1:59174 fd=8 name= age=7 idle=7 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys
id=52 addr=127.0.0.1:59177 fd=9 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
id=49 addr=127.0.0.1:59174 fd=8 name= age=8 idle=8 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys
id=53 addr=127.0.0.1:59178 fd=9 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
id=49 addr=127.0.0.1:59174 fd=8 name= age=9 idle=9 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys
id=54 addr=127.0.0.1:59179 fd=9 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
id=49 addr=127.0.0.1:59174 fd=8 name= age=10 idle=10 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys
id=55 addr=127.0.0.1:59180 fd=9 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
id=49 addr=127.0.0.1:59174 fd=8 name= age=11 idle=1 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys
id=56 addr=127.0.0.1:59181 fd=9 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
id=49 addr=127.0.0.1:59174 fd=8 name= age=12 idle=2 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys
id=57 addr=127.0.0.1:59182 fd=9 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
id=49 addr=127.0.0.1:59174 fd=8 name= age=13 idle=3 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys
id=58 addr=127.0.0.1:59183 fd=9 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client
id=49 addr=127.0.0.1:59174 fd=8 name= age=14 idle=4 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys
id=59 addr=127.0.0.1:59184 fd=9 name= age=0 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=32742 obl=0 oll=0 omem=0 events=r cmd=client

id=49 が redis-rb のクライアントになります
これの age フィールドが接続から切断までの時間になります
そして idel フィールドが接続はされているがコマンドが一切実行されていない時間です
確認用のスクリプトは 10 秒おきにコマンドを実行しているので idel=10 になると一度カウントがリセットされているのが確認できると思います

redis-server は idel 中のプロセスを kill しないのか

ここで疑問に思ったのは「この idel フィールドが無限に増え続けた場合に redis-server は切断したりするのか」ですが、どうやらデフォルトではしないようです

By default recent versions of Redis don't close the connection with the client if the client is idle for many seconds: the connection will remain open forever.

詳しく読んでいないので理由は不明ですがおそらくクライアント側はちゃんとルールを守って使ってくれるであろうという暗黙的な了解があるんだと思います
なので当然コネクションを張りっぱなしにしていると Too many open files や Out of memory エラーなどのカーネルエラーが発生するはずです

タイムアウトを設定してみる

とは言え運用するに辺りそういったクライアントが 100% 登場しないとは言い切れません
なのでちゃんとタイムアウトを設定することができます
timeout という config があるのでこれを設定します

  • redis-cli config get timeout
    • => 0
  • redis-cli config set timeout 5
    • => OK

これで再度先程のスクリプトを実行してみます
すると今度は id がカウントアップしているのが確認できると思います

"72"
"73"
"74"

idel が 5 秒以上あると強制的に切断されるので redis-rb は内部的に新しいコネクションを作成して実行しているようです
コネクション切断エラーが出ると思ったのですがそうではないようです

ちなみに切断の条件が idel だと確認したのはタイムアウトの時間を 15 などにして実行してもすべて同じ id が表示されたからです
もし age の時間がタイムアウトの条件であれば 3 回目に表示される id は別の id が表示されることになります

実行するたびに Redis.new する場合では

次にインスタンス変数ではなく module メソッド内で毎回 Redis.new するような場合の挙動を確認してみます

  • vim test2.rb
require 'redis'

module MRedis
  def keys
    client = Redis.new
    client.keys
  end

  def list
    client = Redis.new
    client.client('list')
  end

  module_function :keys, :list
end

3.times do
  MRedis.list.each { |c| p c['id'] }
  MRedis.keys.length
  sleep 10
end

keys, client list を実行するたびに Redis.new するようにしてみます
実行して挙動を確認しましょう

  • bundle exec ruby test2.rb
"85"
"86"
"87"
"88"
"89"

結果は上記のようになりました
1 回目は client list 分の id が表示されます
2 回目以降は keysclient list の 2 つ分が表示されます
2 回目以降に 2 つ表示されるのは前回の keys のコネクションが残っており次の keys が実行されるまでは破棄されないためです
つまりメソッド内で Redis.new している場合はそのメソッドが再度コールされるまではコネクションが保持され続けることになります

そんな場合のために意図的に close できます
なのでメソッド内で client を作成している場合などはちゃんと close をコールしてあげるのがお作法かなと思います

module MRedis
  def keys
    client = Redis.new
    ret = client.keys
    client.close
    ret
  end

  def list
    client = Redis.new
    ret = client.client('list')
    client.close
    ret
  end

  module_function :keys, :list
end

3.times do
  MRedis.list.each { |c| p c['id'] }
  MRedis.keys.length
  sleep 10
end

これで実行するとちゃんと keys コール後にコネクションが切断されており結果に表示されるのは client list の id のみになるが確認できると思います

"131"
"133"
"135"

おまけ: クライアントに名前を付与するには

client list の結果を見るとわかりますがクライアントには id 以外に name も付与できます
アプリを特定したい場合に便利です

  • vim test3.rb
client = Redis.new(:id => "hoge")
client.keys
sleep 5
  • bundle exec ruby test3.rb

でプロセスが実行中にクライアントを確認すると name が付与されているのが確認できます
:name ではなく :id を使うのが少し分かりづらいですが、、

  • redis-cil client list
id=139 addr=127.0.0.1:59480 fd=8 name=hoge age=1 idle=1 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=keys

最後に

redis-rb のクライアントのコネクションの挙動を確認してみました
基本はインスタンス変数として使うのが良いと思いますが場合によっては redis-cli のようにコマンドごとに接続してもいいのではと思います

Web アプリのように session があり特定の時間コネクションをつないでいたほうがリソースを節約できる場合はインスタンス変数にし、バッチなど単発で処理しすぐに破棄する場合はコマンドごとに接続しても問題ないと思います
ただ接続/切断を短時間で頻繁に繰り返すような処理は避けたほうが良いと思います

ちゃんとやるのであれば redis-server 側でコネクション数と idel 時間を監視しておかしなクライアントがいた場合は修正する感じにするのが良いかなと思います

2019年6月28日金曜日

Firebase のデータを Redis に格納する Ruby スクリプト

概要

メモがてら残しておきます
Firebase の認証はレガシーな Database Secret を使っています

環境

  • macOS 10.14.5
  • Ruby 2.6.2p47

準備

  • bundle init
  • vim Gemfile
gem "firebase"
gem "redis"
  • bundle install --path vendor

アプリ

  • vim app.rb
require './lib/my_fb.rb'
require './lib/my_redis.rb'

p MyRedis.store(MyFB.fetch_all)

firebase の処理

  • vim lib/my_fb.rb
require 'firebase'

module MyFB
  BASE_URL = 'https://product_id.firebaseio.com/'
  SECRET = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'

  def fetch_all
    client = Firebase::Client.new(BASE_URL, SECRET)
    ret = {}
    ret.store('category1', client.get('db1/category1').body)
    ret.store('category2', client.get('db1/category2').body)
    ret
  end

  module_function :fetch_all
end

Redis の処理

  • vim lib/my_redis.rb
require 'redis'
require 'json'

module MyRedis

  def store(ret)
    client = Redis.new
    sum = 0
    ret.each do |key, ary|
      ary.each_with_index do |p, i|
        client.set("#{key}_#{i}", p.to_json)
      end
      client.set("#{key}", ary.length)
      sum += ary.length
    end
    sum
  end

  module_function :store
end

Redis には category1_0 のような key で登録します
後からインデックスで検索、ループ処理しやすいように各カテゴリごとのデータのサイズを category1 という key に登録しています

実行

  • bundle exec ruby app.rb

動作確認

redis-cli get category1_0 で JSON の値が取得できれば OK
また redis-cli get category1 でコンテンツの数が取得できれば OK

最後に

サブカテゴリみたいに更に階層が深い場合にはループを増やせば OK です
Redis も localhost を前提にしているので外部なのであれば URL を指定してください

2019年6月27日木曜日

野良 SMTP サーバを使ってメールを送信する場合は SPF レコードをちゃんと設定しましょう

概要

そうしないと受信側に迷惑メールとして扱われてしまいます

SFP レコードを登録するのは

例えば 192.168.100.20 という野良 SMTP サーバを立てて from を hoge.domain.com のドメインとして送信したい場合は domain.com もしくはそこから include している SPF の DNS サーバに TXT レコードを登録する必要があります
自分がドメインの管理者でない場合はお願いするしかありません

SFP レコードに登録されている調べる方法

dig でテキストレコードを探れば OK です
例えば gmail の場合は

  • dig -t TXT gmail.com

ANSWER SECTION を見ると

gmail.com.              300     IN      TXT     "v=spf1 redirect=_spf.google.com"

というレコードがあるので更に探索すると

  • dig -t TXT _spf.google.com
_spf.google.com.        251     IN      TXT     "v=spf1 include:_netblocks.google.com include:_netblocks2.google.com include:_netblocks3.google.com ~all"

となり更にたどると

  • dig -t TXT _netblocks.google.com
_netblocks.google.com.  2888    IN      TXT     "v=spf1 ip4:35.190.247.0/24 ip4:64.233.160.0/19 ip4:66.102.0.0/20 ip4:66.249.80.0/20 ip4:72.14.192.0/18 ip4:74.125.0.0/16 ip4:108.177.8.0/21 ip4:173.194.0.0/16 ip4:209.85.128.0/17 ip4:216.58.192.0/19 ip4:216.239.32.0/19 ~all"

こんな感じで登録されている IP が確認できます

最後に

よく登録していないけど送れちゃうケースがありますがそれはおそらく受信メールサーバもしくは受信クライアントアプリもしくは SMTP のリレーサーバでちゃんと SFP レコードを見ていないんだと思います (たぶん)

ローカルのみのメールなら問題ないですが SP などのドメインを使う場合は単純に迷惑行為になるので注意しましょう

2019年6月26日水曜日

iptables で特定の IP への通信を遮断する方法

基本はセキュリティ的な観点から使いますが、開発だと特定のサービスへのアクセスに接続できない状況を擬似的に再現したりできるかなと

ルール追加

iptables -A OUTPUT -d 192.168.10.20 -o ens160 -j DROP

確認

iptables -L OUTPUT

ルール削除

iptables -D OUTPUT -d 192.168.10.20 -o ens160 -j DROP

簡単な解説

-A が追加、-D が削除です
そのあとにポリシーと呼ばれるカテゴリを指定します
-d は IP を指定するオプションで指定した IP への通信をすべて遮断します
-o はインタフェースを指定します
指定したインタフェースからの -d へのアクセスのみドロップします
-j はアクションを指定します

それ以外にもオプションがあるので詳しくは iptables -h で確認してください

2019年6月25日火曜日

golang でリトライ処理を試してみる

概要

retry-go というライブラリがあったので試してみました

環境

  • macOS 10.14.5
  • go 1.11.5
  • dep 0.5.0
    • retry-go 2.3.0

基本

  • vim $GOPATH/src/github.com/hawksnowlog/a/main.go
package main

import (
    "errors"
    "fmt"
    "github.com/avast/retry-go"
    "math/rand"
    "time"
)

func random() error {
    rand.Seed(time.Now().UnixNano())
    n := rand.Intn(10)
    if n >= 5 {
        return nil
    }
    return errors.New("error")
}

func main() {
    fmt.Println("start")
    err := retry.Do(
        func() error {
            ret := random()
            return ret
        },
        retry.DelayType(func(n uint, config *retry.Config) time.Duration {
            fmt.Println(n)
            return 0
        }),
    )
    fmt.Println(err)
}
  • dep init
  • go fmt github.com/hawksnowlog/a
  • go build github.com/hawksnowlog/a
  • ./a

少し解説

random メソッドは今回のロジックには関係ないので省略します
内容は 0 から 9 の数字をランダムに返すだけです

リトライさせたい処理を retry.Do で囲みます
デフォルトではリトライ回数は 10 になっています
後でリトライ回数を変更する方法を紹介します
retry.Do メソッドは 2 つの引数を持ちます
一つ目はリトライさせたい関数でもう一つは Option になります
リトライする条件は対象のメソッドが error の場合にリトライします
errornil の場合にはリトライせず次の処理に進みます

上記では Option の部分に retry.DelayType メソッドを指定しています
このメソッドの返り値が Option になっています
さらに retry.DelayType にはメソッドを指定できます
これはもし error が返ってきてリトライする場合にリトライ前に実施したい処理を指定することができます
上記ではリトライ前に fmt.Println しかしていませんが何かさせたい場合にはここに記載します
n uint にはリトライ回数が代入されています
もしデフォルトの 10 回のリトライの場合には 0 からカウントして最大 8 までインクリメントします

返り値が time.Duration になっていますがここを 0 以外にするとリトライ前にウェイトさせることができます

リトライ後に少しディレイを持たせる

例えばリトライ回数 x 1秒待たせる場合には以下のようになります

func main() {
    fmt.Println("start")
    err := retry.Do(
        func() error {
            ret := random()
            return ret
        },
        retry.DelayType(func(n uint, config *retry.Config) time.Duration {
            fmt.Println(n)
            return time.Duration(n) * time.Second
        }),
    )
    fmt.Println(err)
}

return time.Duration(n) * time.Second とすることで実現できます

リトライ回数を変更する

retry.Attempts を使います
これも Option を返すので retry.DelayType の後に続いて書けば OK です

func main() {
    fmt.Println("start")
    err := retry.Do(
        func() error {
            ret := random()
            return ret
        },
        retry.DelayType(func(n uint, config *retry.Config) time.Duration {
            fmt.Println(n)
            return time.Duration(n) * time.Second
        }),
        retry.Attempts(3),
    )
    fmt.Println(err)
}

上記の場合は 10 -> 3 回リトライします

最後に

retry-go を試してみました
既存の処理に対してリトライ処理を入れるのも簡単にできます

またリトライ時には必ずといっていいほど出る Exponential Backoff も簡単に実装できます (参考: BackOffDelay)

参考サイト

2019年6月24日月曜日

Zabbix4.2 でネットワークディスカバリ機能を使ってみた

概要

指定の IP の範囲に Zabbix Agent がいる場合にそれを検知して何かしらのアクションを起こすことができます
今回は検知したホストを自動で Hosts に登録することをやってみました
今回は docker 上で機能を試してみました

環境

  • Zabbix サーバ
    • Ubuntu 16.04 LTS
    • docker 18.09.6
    • Zabbix Server 4.2
  • Zabbix エージェント
    • macOS 10.14.5
    • docker 18.09.2
    • Zabbix Agent 4.2

Zabbix サーバ起動

Ubuntu 16.04 上で起動します
前回の記事で紹介しているのでそちらを参考にしてください
Zabbix サーバが起動していればいいので docker でなくても OK です

Zabbix Agent 起動

  • docker run -d -e ZBX_HOSTNAME="c2" -e ZBX_SERVER_HOST="192.168.99.200,172.17.0.1" -p 10050:10050 zabbix/zabbix-agent

こちらもコンテナでなくて OK です

ディスカバリ作成

まずはディスカバリを作成します
特定のネットワーク上にエージェントが出現したら発見させるようなディスカバリにします

Configuration -> Discovery
で「Create discovery rule」or 既存のルールを選択します
下記は既存の「Local network」を変更しています

  • IP range・・・192.168.99.1
  • Update interval・・・10s
  • Checks・・・Zabbix agent "system.uname"
  • Device uniqueness criteria, Host name, Visible name・・・Zabbix agent "system.uname"

IP range は 192.168.99.1-254192.168.99.0/24 などの範囲指定フォーマットもサポートしています

Update interval は好きな値を設定してください
今回はテストなので 10s にしています
数字が小さいと負荷が上がります

Checks は zabbix_agent -s 192.168.99.1 -k system.uname の値が取得できるかどうか判断するようにします
他にも ping や特定の tcp ポートでもチェックできます

Device uniqueness criteria, Host name, Visible name はすべて `Zabbix agent "system.uname" にしていますがこれも好きなものを設定してください
例えば Host name を IP address にするとホストが登録された際に IP で登録されます

作成 or 編集後のディスカバリは以下のような感じです

アクション作成

次に作成したディスカバリが真の場合に実行するアクションを作成します

Configuration -> Actions
で右上のプルダウンの「Event source」を「Discovery」にします
「Create action」でも OK です
以下は既存の「Auto discovery. Linux servers.」を編集しています

  • Conditions・・・Discovery rule equals Local network -> Add を押す
  • Enabled・・・チェック

次に Operations タブを選択します
Operations -> New を選択します

  • Operations・・・Add host -> Add を押す

これで OK です
Update を押して作成しましょう

作成 or 編集後のアクションは以下の通りです

動作確認

しばらくするとディスカバリされホストが追加されるはずです
まずは Monitoring -> Discovery を見てみましょう
ここでホストが見つからないとアクションも実行されます

アクションも問題なく実行されれば Configuration -> Hosts にホストが登録されています
今回は system.uname を Host name に使っているので以下のように少し長いホスト名として登録されます

Zabbix Server のログ的には 177:20190621:040419.777 enabling Zabbix agent checks on host "Linux be1c04e1d4aa 4.9.125-linuxkit _1 SMP Fri Sep 7 08_20_28 UTC 2018 x86_64": host became available が出ていれば成功です

トラブルシュート

手順的にはサクっとできますが実はいろいろハマっています、、
自分がトラブルシュートした方法をいくつか紹介します

Zabbix Server のロギングレベルを上げる

docker の場合は .env_srvZBX_DEBUGLEVEL=3 を 4 にすれば OK です
これで再度 zabbix-server コンテナを down -> up すれば OK です

IP range を短くする

ディスカバリのルールはあっているのに中々 Monitoring -> Discovery に登場しない場合はこれを試してみてください
確証はないですが Zabbix Server は当然していの範囲を精査します
アクションや発見したホストの登録を実行するタイミングがもしかすると精査後になっているので unreachable な IP が多いほどアクションが実行されるタイミングが遅くなっているのかもしれません
(ソースまで終えていないので自信なし)

最後に

Zabbix4.2 でディスカバリの機能を試してみました
機能自体は昔からあるので今更感がありますが UI はだいぶ変わっているのでその辺りは参考になるかなと思います

サーバもエージェントもコンテナなのでバイナリを直接ホストで動かしてる場合は少し設定値は変わるかなと思いますが流れは基本的に同じかなと思います

参考サイト

2019年6月23日日曜日

Zabbix4.2 で Inventory 機能を使ってみた

概要

Inventory 機能はアイテムで取得した値を Inventory という特別な領域に登録することができます
Inventory に登録した情報を使ってホストを検索したり GROUP BY してカウントしたりできます
今回は docker 上で機能を試してみました

環境

  • Zabbix サーバ
    • Ubuntu 16.04 LTS
    • docker 18.09.6
    • Zabbix Server 4.2
  • Zabbix エージェント
    • macOS 10.14.5
    • docker 18.09.2
    • Zabbix Agent 4.2

Zabbix サーバ起動

Ubuntu 16.04 上で起動します
前回の記事で紹介しているのでそちらを参考にしてください
Zabbix サーバが起動していればいいので docker でなくても OK です

Zabbix Agent 起動

Agent も docker で起動します
ホストを準備するのが面倒な場合に便利です

  • docker run -d -e ZBX_HOSTNAME="c1" -e ZBX_SERVER_HOST="192.168.99.200,172.17.0.1" -p 10050:10050 zabbix/zabbix-agent

ZBX_HOSTNAME はサーバ上に登録するホストの名前と同じにします
マシンのホスト名である必要がありません
ZBX_SERVER_HOST は Zabbix サーバの IP アドレスを指定します
2 つ指定しているのは Zabbix サーバの Incomming と Outgoing の IP が docker の場合異なっているからです
普通に VM などで Zabbix サーバを起動している場合は Incomming と Outgoing が同じになるので 1 つで OK です

Zabbix Agent コンテナが起動したログを確認してみましょう

  • docker logs -f 069e4e9ee319

active check configuration update from [172.17.0.1:10051] started to fail (cannot connect to [[172.17.0.1]:10051]: [111] Connection refused) というログが出ていますが無視で OK です
先程説明した Zabbix サーバコンテナの Outgoing IP にはアクセスしようとしているため出ているだけです
もし Zabbix サーバコンテナの Outgoing IP を指定しない場合は以下のエラーになりうまく監視対象に入りません

failed to accept an incoming connection: connection from "172.17.0.1" rejected, allowed hosts: "192.168.99.200"

Configuration -> Hosts 登録

サーバとエージェントが起動したらサーバ側に監視するホストの情報を登録しましょう (今回はテスト用のコンテナですが)
Zabbix は pull 型の監視がベースのためホスト側に監視対象の情報がないとデータの収集が始まりません

UI にアクセスして Configuration -> Hosts -> Create Host を選択します
そして以下のように登録します

  • Host name・・・c1
  • Groups・・・Linux servers
  • Agent Interfaces -> IP address ・・・192.168.99.1

まだ登録しません
Templates タブを選択し以下の通り入力します

  • Link new templates・・・Template OS Linux
  • Add を押す

忘れずに Add を押しましょう
これでホストを登録します

Inventory にデータを登録するためのアイテムを登録する

先程登録したホストに新規でアイテムを登録します
テンプレートを紐づけたのでテンプレートにアイテムを追加しましょう

Configuration -> Templates から Template OS Linux を選択します

そして Items タブを選択して Create tem を選択します
入力フィールドが出るので以下のように入力しましょう

  • Name・・・system.sw.arch
  • Key・・・system.sw.arch
  • Type of information・・・Text
  • Populates host inventory field・・・HW architecture

Name は何でも OK です
Populates host inventory field も正直何でも OK です
取得できるアイテムにあった項目を選択してください
今回はシステムアーキテクチャが取得できるのでそれっぽいのを選択しました

ホストの Inventory を有効にする

最後にホストの Inventory を有効にします
デフォルトではアイテムで取得できたとしても Inventory には登録してくれません
まずは Configuration -> Hosts で c1 ホストを選択しましょう

そして Inventory タブを選択して Automatic を選択します
すると Name や OS で使われるアイテムが右に表示されます

更に下に行くと HW architecture の欄があるので登録したアイテム名が割り当てられていることを確認しましょう

動作確認

しばらく (30秒) するとデータが入ってきます
まずは Monitoring -> Latest data で確認してみましょう
アイテム登録時にアプリケーションを指定していないので other にあります

あとは Inventory を確認してみましょう
Inventory -> Overview で右上のプルダウンから HW architecture を選択します
すると x86 ホストを GROUP BY してカウントしてくれるのが確認できると思います
今回は 1 台しかいないので 1 になれば OK です

Hosts タブでは Inventory 情報から検索して対象の Inventory を持つホストを探すことができます

最後に

Zabbix4.2 で Inventory 機能を試してみました
Inventory 機能自体は 2 系から登場しているので今更感しかないですがよくわからに機能だったので試してみました
うまく使えばおもしろそうなデータが見えるようにはなりそうですが、正直監視に必須な機能ではないかなと思います

今回はすべて docker 上で行いましたがバイナリでも同じようにできると思います

参考サイト

2019年6月22日土曜日

Zabbix4.2 を docker で立ち上げてみた

概要

公式の docker-compose ファイルがあったので試してみました

環境

  • Ubuntu 16.04 LTS
  • docker 18.09.6
  • docker-compose 1.24.0
  • Zabbix 4.2

Zabbix サーバ構築

  • git clone https://github.com/zabbix/zabbix-docker.git
  • cd zabbix-docker

デフォルトブランチは master ではなくバージョンのブランチになっているようです

  • git branch

=> 4.2

いくつか docker-compose ファイルを選択できます
alpine + mysql だったり ubuntu + postgres などが選択できます
今回は alpine + mysql にしました
また _local を使うと git clone した各種リソースを使ってイメージをビルドしてから立ち上がります

  • docker-compose -f docker-compose_v3_alpine_mysql_latest.yaml up -d

イメージのダウンロードが始まるので待ちましょう
最終的に 9 つのコンテナが起動すれば OK です

  • docker-compose -f docker-compose_v3_alpine_mysql_latest.yaml ps
               Name                             Command                       State                         Ports              
--------------------------------------------------------------------------------------------------------------------------------
zabbix-docker_db_data_mysql_1        sh                               Exit 0                                                   
zabbix-docker_mysql-server_1         docker-entrypoint.sh mysql ...   Up                                                       
zabbix-docker_zabbix-agent_1         /sbin/tini -- /usr/bin/doc ...   Up                                                       
zabbix-docker_zabbix-java-           docker-entrypoint.sh             Up                                                       
gateway_1                                                                                                                      
zabbix-docker_zabbix-proxy-mysql_1   /sbin/tini -- /usr/bin/doc ...   Up                      0.0.0.0:10071->10051/tcp         
zabbix-docker_zabbix-proxy-          /sbin/tini -- /usr/bin/doc ...   Up                      0.0.0.0:10061->10051/tcp         
sqlite3_1                                                                                                                      
zabbix-docker_zabbix-server_1        /sbin/tini -- /usr/bin/doc ...   Up                      0.0.0.0:10051->10051/tcp         
zabbix-docker_zabbix-snmptraps_1     /usr/bin/supervisord -c /e ...   Up                      0.0.0.0:162->162/udp             
zabbix-docker_zabbix-web-apache-     docker-entrypoint.sh             Up (health: starting)   0.0.0.0:443->443/tcp,            
mysql_1                                                                                       0.0.0.0:80->80/tcp               
zabbix-docker_zabbix-web-nginx-      docker-entrypoint.sh             Up (healthy)            0.0.0.0:8443->443/tcp,           
mysql_1                                                                                       0.0.0.0:8081->80/tcp

各役割ごとにコンテナが起動しています
ホストのポートを結構たくさんバインドするのですでに使っているポートがある場合は該当のプロセスを停止してから起動しましょう

管理画面にアクセスするには http://192.168.99.200 などのホストの IP に接続すれば OK です
パスワードは Admin/zabbix です (これはいいのだろうか、、)

ログインするといきなり zabbix-agent が zabbix-server を見つけられないエラーが、、

バグっぽいですが解決してあげます

  • docker-compose -f docker-compose_v3_alpine_mysql_latest.yaml logs zabbix-agent

no active checks on server [zabbix-server:10051]: host [07890bd93e4d] not found なるエラーが出ていました
この 0be108601a3f は zabbix-agent のホスト名でした

設定ファイルを確認してみます

  • docker-compose -f docker-compose_v3_alpine_mysql_latest.yaml exec zabbix-agent grep -v '^\s*#' /etc/zabbix/zabbix_agentd.conf |grep -v '^\s*$'

特に問題なさそうです
エラー的には zabbix-server が 0be108601a3f という名前のホスト名を探せないということなので怪しいのは Zabbix Server 側っぽいです
サーバ側のログを見てみます

  • docker-compose -f docker-compose_v3_alpine_mysql_latest.yaml logs zabbix-server | grep 'cannot send list of active checks to'

cannot send list of active checks to "172.16.239.6": host [0be108601a3f] not found

つまり agent が 0be108601a3f というホストが Zabbix Server だよ送っているのに実際に zabbix-server コンテナが 0be108601a3f に聞きにいこうとしてもそんなホストは見つからないと言っているのです

で実際どこを変更すれば良いかと言うと Configuration -> Hosts で Zabbix Server の設定を開きます
そして Host name に zabbix-agent 入力し Agent interfaces の DNS name にも zabbix-agent を入力します
Connect to のトグルスイッチも DNS 側に変更しましょう
これで Zabbix Server 用の agent である zabbix-agent コンテナに名前でアクセスできるようになるため問題が解決します

また実は YAML ファイルも変更しています
やらなくてもステータスは緑になりますがエラーログが出続けるのでやってもよいかなと思います

  • git diff
git diff
diff --git a/docker-compose_v3_alpine_mysql_latest.yaml b/docker-compose_v3_alpine_mysql_latest.yaml
index 166b431..d82e759 100644
--- a/docker-compose_v3_alpine_mysql_latest.yaml
+++ b/docker-compose_v3_alpine_mysql_latest.yaml
@@ -269,6 +269,7 @@ services:

  zabbix-agent:
   image: zabbix/zabbix-agent:alpine-4.2-latest
+  hostname: zabbix-agent
   ports:
    - "10050:10050"
   volumes:

ただこれは結局 Zabbix Server のためのエージェントではなく zabbix-agent コンテナのエージェントの情報を監視しているだけなので意味はありません

proxy のエラーも出ている

以下のエラーも Zabbix Server のログに出ていました
必須ではないですが何となく対応してみました

zabbix-server_1            |    175:20190619:095634.153 cannot parse proxy data from active proxy at "172.16.238.7": proxy "zabbix-proxy-mysql" not found
zabbix-server_1            |    178:20190619:095634.154 cannot parse proxy data from active proxy at "172.16.238.4": proxy "zabbix-proxy-sqlite3" not found

これも UI にデータを入れれば OK です
Administration -> Proxies -> Create proxy でエラーになっているホストと IP アドレスの情報を登録すれば OK です

  • Proxy name・・・zabbix-proxy-mysql
  • Proxy address・・・172.16.238.7

  • Proxy name・・・zabbix-proxy-sqlite3
  • Proxy address・・・172.16.238.4

ステータスが緑になれば OK です

ただしこの方法だと各プロキシの IP がコンテナの再作成で変わる可能性があるので、その場合は再度登録してください

ZabbixAgent をコンテナとして動作させるには

方法としてはシステムディレクトリをマウントすればエージェントをコンテナで起動できるっぽいのですがあまりよろしい感じはしません (参考)
正直自分でも zabbix/zabbix-agent コンテナの使い方というか用途というか使う場面が思い浮かびません
docker ホストの監視をしたい場合は素直に zabbix-agent をインストールするのが良いと思います

強いて言えば適当なホストを追加したいときに zabbix-agent コンテナを立ち上げればホストの追加になるのでテスト用途として使う感じかなと思います

コンテナを監視するには

Zabbix Docker Monitoring というコンテナを開発してくれている方がいるのでこれを使います
テンプレートなどもあるのでそれを使えばアイテムなども Zabbix Server に登録されます
Zabbix Docker Monitoring については別記事で紹介したいと思います

やり直したい場合は

MySQL などのデータはコンテナを再作成しても残ります
なので再度一からやり直したい場合は以下のディレクトリのデータを削除しましょう

  • sudo rm -rf zbx_env/var/lib/mysql/*

最後に

Zabbix を docker で使う場合には

  • Zabbix Server はコンテナで OK
  • Zabbix Agent は素直にバイナリをホストにインストールする
  • コンテナの監視には Zabbix Docker Monitoring を使う

というのが無難かなと思います

2019年6月21日金曜日

goquic 超入門

概要

QUIC (Quick UDP Internet Connections) は UDP 上で TCP のデータのやり取りをする高速かつ安全なプロトコルです
現在も開発が進められていますが golang でとりあえず触れる環境があったので試してみました

環境

  • Ubuntu 16.04 LTS (vagrant)
    • cmake 3.5.1
    • ninja-build 1.5.1
    • gcc 5.4
    • golang 1.12.6
  • goquic (rev: 1e49c0fcf10725575edc12e5834bc7d003da1581)

golang インストール

  • wget 'https://releases.hashicorp.com/vault/1.1.3/vault_1.1.3_linux_amd64.zip'
  • sudo tar -C /usr/local -xzf go1.12.6.linux-amd64.tar.gz
  • vim ~/.bashrc
export GOPATH="/home/vagrant/go"
export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin

一度ログアウトして再度ログインしましょう

ビルドに必要なパッケージのインストール

  • sudo apt -y install cmake
  • sudo apt -y install ninja-build

これ以外に g++ or gcc などの C/C++ のコンパイラが必要になります
自分はすでにインストールされていたので省略しています
各種バージョンは環境のところに記載しています

ソース取得

  • go get -u -d github.com/devsisters/goquic

goquic はソースからビルドして動作させます
go get で取得しましょう

ビルド

  • cd $GOPATH/src/github.com/devsisters/goquic
  • ./build_libs.sh

でビルドが始まります
失敗する場合はエラー内容を見てトラブルシューティングするしかありません
自分は一発でいけたので同じ環境 (vagrant + ubuntu/xenial) なら問題なくビルドできると思います
完了すると libgoquic.a が作成されます

  • ls -l lib/linux_amd64/libgoquic.a

server と client のサンプルをビルドする

実際に libgoquic.a を使ったサンプルのサーバとクライアントがあるのでそれをビルドして使ってみます

  • go build $GOPATH/src/github.com/devsisters/goquic/example/server.go
  • go build $GOPATH/src/github.com/devsisters/goquic/example/client.go

他に reverse_proxy.go もありますが特に使いませんでした

サーバを起動する

サーバの起動には証明書と鍵ファイルが必要になります
自分は Let’sEncrypt で取得した証明書 を使いましたが openssl や Mac であればキーチェインアクセスでも OK です

作成した証明書と鍵を指定してバイナリを実行しましょう

  • ./server -cert fullchain.pem -key privkey.pem

これで 8080 で LISTEN したサーバが起動します

動作確認

確認方法はコマンドとブラウザでもできます
コマンドの場合は以下のように実行すれば結果が取得できます

  • ./client -url "http://localhost:8080/numbers"

0 から 9999 までの数字が返ってくると思います
Chrome で https://192.168.99.200 にアクセスしても確認できます
証明書の警告が出ますが無視で OK です
トップページには動作確認ようのリンクが用意されています

「Numbers test」にアクセスすると先程コマンドで実行した結果と同じものがブラウザでも確認できます

「Files」はおそらくファイルの確認やダウンロードを確認するページかと思われます
デフォルトだと /tmp の内容を表示しているのでここにファイルなどをおけばリストやダウンロードの確認ができます
flag.StringVar(&serveRoot, "root", "/tmp", "Root of path to serve under https://127.0.0.1/files/")

おまけ: curl もやってみた

ヘッダ情報を確認しやすいので curl も実行してみました
QUIC を使っている場合ヘッダにいろいろと情報が付与されるようです
結果を貼っておきます

  • curl -k -sSL -D - https://localhost:8080
HTTP/1.1 200 OK
Alt-Svc: quic=":8080"; ma=86400; v="36,35,34,33,32,31,30"
Alternate-Protocol: 8080:quic
Content-Type: text/html; charset=utf-8
Trailer: AtEnd1, AtEnd2
Trailer: AtEnd3
Date: Thu, 20 Jun 2019 05:03:28 GMT
Transfer-Encoding: chunked

This HTTP response has both headers before this text and trailers at the end.<br/><a href='/numbers'>Numbers test (0~9999)</a><br/><a href='/files'>Files</a><br/>127.0.0.1:40472
Atend1: value 1
Atend2: value 2
Atend3: value 3

最後に

goquic で QUIC を使ったアプリにとりあえず触ってみました
これだけだと効果を実感することはできないので実際は QUIC を使っていない HTTP 環境と使ってる HTTP 環境で速度検証などをしたほうが良いかなと思います

golang だけではありますがサーバアプリの実装も難しそうではないので試しにチャレンジしてみるのも良いのかなと思います

2019年6月20日木曜日

docker-compose では link を指定しないでも同一ネットワーク内なら名前解決できる

概要

タイトル通りです
link が deprecated になり同一ネットワークにコンテナを配置することでホスト名でアクセスできるようになっているので動作確認してみました

環境

  • macOS 10.14.5
  • docker 18.09.2
  • docker-compose 1.23.2

docker-compose

  • vim docker-compose.yml
version: '2'
services:
  client:
    image: redis:latest
    command: ["redis-cli", "-h", "server", "info", "server"]
    depends_on:
      - "server"
  server:
    image: redis:latest

ポイントは command の部分で -h server という感じでホスト名を指定するところです
これで問題なく動作するか確認します

server が起動する前に確認のコマンドを投げないように depends_on を使っています

動作確認

  • docker-compose up -d
  • docker-compose logs client

info server の情報が表示されることを確認します
networks を指定しない場合は xxxx_default という名前のネットワークを自動で作成してそこに接続されます

  • docker inspect try_no_link_client_1 -f '{{.NetworkSettings.Networks}}'

=> map[try_no_link_default:0xc4205980c0]

参考サイト

2019年6月19日水曜日

Ruby で Dynamic Inventory 入門

概要

DynamicInventory は Ansible がスクリプトを実行しその実行結果のインベントリ情報を使って playbook を流すことができる機能です
スクリプトは出力結果が決められたフォーマットになっていればどんな言語で書いても OK です
今回は Ruby でやってみました

環境

  • macOS 10.14.5
  • ansible 2.5.5
  • Vagrant 2.1.1
  • Ruby 2.6.2p47

テストマシン

今回は Vagrant で構築していますが SSH で接続できれば何でも OK です

  • vim Vagrantfile
Vagrant.configure("2") do |config|
  config.vm.box = "ubuntu/xenial64"
  config.vm.network "private_network", type: "dhcp"
  config.vm.network "private_network", ip: "192.168.99.200"
  config.vm.provider "virtualbox" do |vb|
    vb.memory = "4096"
  end
end
  • vagrant up

SSH できるようにするがめ ssh-config を書き出します

  • vagrant ssh-config >> ~/.ssh/config

playbook

とりあえずテストマシンに playbook が流せる環境を作ります

  • vim example.yml
---
- hosts: role_web1
  tasks:
    - name: Install nginx
      shell: "amazon-linux-extras install nginx1.12"
      become: yes
    - name: Start nginx
      systemd:
        name: nginx.service
        state: started
      become: yes

インベントリファイルもとりあえずベタ書きします

  • vim inventory
[web1]
default

nginx をインストールして起動する playbook です
これでとりあえず実行できることを確認しましょう

  • ansible-playbook -i inventory example.yml -e 'ansible_python_interpreter=/usr/bin/python3'

Dynamic Inventory

さてここから Ruby を使った Dynamic Inventory を作成していきます
今回は特に動的に生成してる感じはないですが結果として「こんな感じの JSON が出力されれば良い」ということが理解できることを目的にしています

まずは単純なインベントリ情報を出力するスクリプトを書いてみます

  • vim inventory.rb
#! /usr/local/opt/ruby/bin/ruby
require 'json'

i = {
  :web1 => {
    :hosts => [
      'default'
    ]
  }
}
puts i.to_json
  • chmod +x inventory.rb

ファイルを指定するだけで実行できるようにシェバングを指定しています
シェバングのパスは環境によって異なると思うので適宜変更してください

このスクリプトを実行するとわかりますが単純な JSON が返ってきます

  • ./inventory.rb

=> {"web1":{"hosts":["default"]}}

このスクリプトを使って ansible-playbook を実行することができます

  • ansible-playbook -i ./inventory.rb example2.yml -e 'ansible_python_interpreter=/usr/bin/python3'

_meta

インベントリファイルには host_varsgroup_vars を書くことができます
Dynamic Inventory を使っても書くことができるので試してみました
スクリプトを以下のように書き換えましょう

  • vim inventory.rb
#! /usr/local/opt/ruby/bin/ruby
require 'json'

i = {
  :web1 => {
    :hosts => [
      'default'
    ],
    :vars => {
      :gvar => "gvalue"
    }
  },
  :_meta => {
    :hostvars => {
      :default => {
        :hvar => "hvalue"
      },
    }
  }
}
puts i.to_json

group_vars はホストグループの定義配下に vars を使って定義します
host_vars_meta を使って定義します
_meta 配下に hostvars を定義しその配下に key/value を定義することで参照することができるようになります

確認のための playbook も書き換えます

  • vim example.yml
---
- hosts: web1
  tasks:
    - name: Install nginx
      apt:
        name: nginx
        state: present
      become: yes
    - name: Start nginx
      systemd:
        name: nginx.service
        state: started
      become: yes
    - name: Debug host_vars
      debug:
        msg: _meta host_vars is {{hvar}}
    - name: Debug group_vars
      debug:
        msg: _meta group_vars is {{gvar}}

debug を使って変数の内容を表示しているだけです
これで先程同様に実行すると変数が問題なく参照できるのが確認できると思います

  • ansible-playbook -i ./inventory.rb example2.yml -e 'ansible_python_interpreter=/usr/bin/python3'

最後に

Dynamic Inventory を Ruby で試してみました
当然ですが ansible-playbook コマンドを実行するマシンに ruby がインストールされている必要があります
少し調べた感じだと --list--host オプションを実装したほうが良さそうだったのですが実装しないでも動作しました
スクリプトがインベントリファイルの形式で出力できたほうが良い場合などは実装すると良いかなと思います

実際は API や Terraform の .tfstate ファイルなどをパースしてインベントリ情報の JSON を生成することになると思います

参考サイト

2019年6月18日火曜日

terraform-inventory 最速入門

概要

terraform-inventory の挙動をとりあえず動かしてサクっと確認したい人向けの記事です
ただ執筆時点での結論から言うと terraform の最新バージョン 0.12 には対応していないため対応までは採用見送りかなと思っています

環境

  • macOS 10.14.5
  • ansible 2.5.5
  • terraform 0.12.1 -> 0.11.14

terraform インストール

  • brew install terraform@0.11

0.12 にはまだ対応していないので 0.11 をインストールする

example.tf

  • vim example.tf
provider "aws" {
  region = "us-east-2"
}

resource "aws_instance" "web1" {
  ami = "ami-0ebbf2179e615c338"
  instance_type = "t2.nano"
  key_name = "sshkey"
  tags = {
    Role = "web1"
  }
}

key_name = "sshkey" は事前に作成済みを想定しています

AWS にインスタンス作成

  • terraform init
  • terraform plan
  • terraform apply

ansible インストール

  • brew install ansible

terraform-inventory インストール

  • brew install terraform-inventory
  • terraform-inventory -inventory terraform.tfstate

Inventory ファイルの情報が出力されることを確認します

playbook 作成

  • vim example.yml
---
- hosts: role_web1
  tasks:
    - name: Install nginx
      shell: "amazon-linux-extras install nginx1.12"
      become: yes
    - name: Start nginx
      systemd:
        name: nginx.service
        state: started
      become: yes

playbook 適用

  • ansible-playbook -i $(which terraform-inventory) example.yml -u ec2-user --private-key /path/to/sshkey.pem

動作確認

  • curl ec2-3-14-84-102.us-east-2.compute.amazonaws.com

パブリック DNS の部分は適宜変更してください

解説

terraform

provider aws を使って AWS 上にインスタンスを作成します
ami = "ami-0ebbf2179e615c338" は Amazon Linux2 になります
ポイントは tags でこれを付与することで ansible の hosts としてインスタンスを指定することができます

ansible

terraform.tfstate というファイルを元に terraform-inventory というコマンドが自動的にインベントリ情報を生成してくれます
このようにプログラムが動的にインベントリ情報を生成する仕組みを Dynamic Inventory といいます
ec2 に生成したインスタンスに ssh ログインするには ec2-user ユーザの指定と鍵の指定が必要になります

また今回の動作確認は nginx をインストールして起動しています
ec2 に適用されるセキュリティブループは default を想定しているおり 80 でのアクセスができるようにしておいてください

最後に

terraform-inventory を試してみました
.tfstate ファイルを元にインベントリ情報を動的に生成してくれるツールです
最新版に対応するのは時間の問題かなと思いますが、今後も terraform のバージョンアップにより .tfstate のフォーマットが変更されることを考えると Dynamic Inventory 用のスクリプトを自作するほうが良いかもしれません

参考サイト

2019年6月17日月曜日

Vault の One-Time SSH Password を試してみた

概要

登場マシンは本来 3 台以上だが今回は検証なので 2 台で実施

  • Vault サーバ (192.168.99.200 - Ubuntu)
  • SSH されるサーバ (192.168.99.200 - Ubuntu)
  • SSH するマシン (192.168.99.1 - Mac)

それぞれで行う作業があるので設定方法を紹介
実際に OTP を発行して接続テストも実施した

環境

  • サーバ
    • Ubuntu 16.04 LTS
    • vault 1.1.3
    • vault-ssh-helper 0.1.4
  • クライアント
    • macOS 10.14.5

Vault サーバ構築 (Ubuntu)

Vault インストール

  • mkdir bin
  • wget 'https://releases.hashicorp.com/vault/1.1.3/vault_1.1.3_linux_amd64.zip'
  • unzip vault_1.1.3_linux_amd64.zip
  • mv vault bin
  • vault server -dev -dev-listen-address=0.0.0.0:8200

SSH されるサーバ設定 (Ubuntu)

vault-ssh-helper インストール

  • wget https://releases.hashicorp.com/vault-ssh-helper/0.1.4/vault-ssh-helper_0.1.4_linux_amd64.zip
  • sudo unzip vault-ssh-helper_0.1.4_linux_amd64.zip -d /usr/local/bin
  • sudo chmod 0755 /usr/local/bin/vault-ssh-helper
  • sudo chown root:root /usr/local/bin/vault-ssh-helper

config.hcl ファイル作成

  • sudo mkdir -p /etc/vault-ssh-helper.d/
  • sudo touch /etc/vault-ssh-helper.d/config.hcl
  • sudo vim /etc/vault-ssh-helper.d/config.hcl
vault_addr = "http://192.168.99.200:8200:8200"
ssh_mount_point = "ssh"
tls_skip_verify = true
allowed_roles = "*"

/etc/pam.d/sshd 編集

  • sudo vim /etc/pam.d/sshd
# PAM configuration for the Secure Shell service

# Standard Un*x authentication.
# @include common-auth
auth requisite pam_exec.so quiet expose_authtok log=/tmp/vaultssh.log /usr/local/bin/vault-ssh-helper -dev -config=/etc/vault-ssh-helper.d/config.hcl
auth optional pam_unix.so not_set_pass use_first_pass nodelay

/etc/ssh/sshd_config 編集

  • vim sudo /etc/ssh/sshd_config
ChallengeResponseAuthentication yes
PasswordAuthentication no
UsePAM yes

ssd 再起動

  • sudo systemctl restart sshd

Vault サーバ設定 (Ubuntu)

  • export VAULT_ADDR='http://192.168.99.200:8200'
  • vault secrets enable ssh
  • vault write ssh/roles/otp_key_role key_type=otp default_user=ubuntu cidr_list=0.0.0.0/0

SSH するマシン (Mac)

  • export VAULT_ADDR='http://192.168.99.200:8200'
  • vault login s.sizM2TmC2nc5pXwAFYchpl1L

ここのトークンは -dev で Vault サーバを起動した際に表示されるトークンを使用する
本来であればちゃんとポリシーを作成してから専用のトークンを発行する

  • vault write ssh/creds/otp_key_role ip=192.168.99.200
Key                Value
---                -----
lease_id           ssh/creds/otp_key_role/DSULrJFHq7HiZQ7sEnDFkOgP
lease_duration     768h
lease_renewable    false
ip                 192.168.99.200
key                36899d1d-9f28-87e5-649f-58cfe3a80922
key_type           otp
port               22
username           ubuntu
  • ssh ubuntu@192.168.99.200

36899d1d-9f28-87e5-649f-58cfe3a80922 をパスワードに入力してログインできることを確認する

ワインラインでもできる

sshpass コマンド

  • vault ssh -role otp_key_role -mode otp -strict-host-key-checking=no ubuntu@192.168.99.200

最後に

SSH するリモートホストの設定が一番大変なので、そこを自動化できればあとはクライアントが毎回 write ssh/creds/otp_key_role するだけなのでセキュアなログインを実現できる

鍵認証の場合に鍵を配布する必要もなくなる

key_type=otp で複数のユーザが登録可能かどうか要調査
Mac で login したトークンが -dev の root トークンなので本番では使えない
実際はそこのトークン払い出し戦略は考える必要がありそう

参考サイト