KVM で macvtap (direct) を使ってみるメモ

下記の記事で KVM の macvtap (direct) というものを知ったので使ってみたメモ。

ホスト・ゲストともに下記で試しています。

# cat /etc/centos-release
CentOS Linux release 7.2.1511 (Core)

sv01 と sv02 というゲストがセットアップ済です。

# virsh list --all
 Id    Name                           State
----------------------------------------------------
 -     sv01                           shut off
 -     sv02                           shut off

既存のインタフェースはすべて削除しています。

virsh detach-interface sv01 bridge --config
virsh detach-interface sv02 bridge --config
virsh detach-interface sv01 direct --config
virsh detach-interface sv02 direct --config
virsh detach-interface sv01 network --config
virsh detach-interface sv02 network --config

direct-bridge を libvirt のインタフェースで作る

インタフェースの定義ファイルを作成。

/tmp/attach-device.xml

<interface type='direct'>
  <source dev='eth0' mode='bridge'/>
</interface>

インタフェースをゲストに追加。

virsh attach-device sv01 /tmp/attach-device.xml --config
virsh attach-device sv02 /tmp/attach-device.xml --config

開始してコンソールに接続して ping とかで疎通を確認する。

virsh start sv01
virsh start sv02
virsh console sv01
virsh console sv02

この時点で、「ゲスト~ゲスト」とか「ゲスト~外部」は疎通があるが、「ゲスト~ホスト」は接続できない。

direct-bridge を libvirt のネットワークで作る

ネットワークの定義ファイルを作成。

/tmp/net-direct.xml

<network>
  <name>direct</name>
  <forward mode="bridge">
    <interface dev="eth0"/>
  </forward>
</network>

ネットワークを追加して開始。

virsh net-define --file /tmp/net-direct.xml
virsh net-start direct

インタフェースの定義ファイルを作成。

/tmp/attach-device.xml

<interface type='network'>
  <source network='direct'/>
</interface>

インタフェースをゲストに追加。

virsh attach-device sv01 /tmp/attach-device.xml --config
virsh attach-device sv02 /tmp/attach-device.xml --config

開始してコンソールに接続して ping とかで疎通を確認する。

virsh start sv01
virsh start sv02
virsh console sv01
virsh console sv02

「ゲスト~ホスト」が接続できないのは同じ。

「ゲスト~ホスト」を通す

下記の記事の通り。

macvlan デバイスを作成して eth0 のアドレスとルーティングを macvlan0 に付ける。

ip link add link eth0 name macvlan0 type macvlan mode bridge
ip link set macvlan0 up
ip addr del 192.0.2.123/24 dev eth0
ip addr add 192.0.2.123/24 dev macvlan0
ip route flush dev eth0
ip route add default via 192.0.2.1 dev macvlan0 proto static metric 100

「ゲスト~ホスト」も接続できるようになる。

macvlan0 の永続化

man にはないけど下記だと nmclitypemacvlan が指定出来そうだったので試してみたところ・・

# nmcli con add ifname macvlan0 type macvlan dev eth0 mode bridge
Error: invalid connection type; 'macvlan' not among [generic, 802-3-ethernet (ethernet), pppoe, 802-11-wireless (wifi), wimax, gsm, cdma, infiniband, adsl, bluetooth, vpn, 802-11-olpc-mesh (olpc-mesh), vlan, bond, team, bridge, bond-slave, team-slave, bridge-slave].

だめっぽい。

参考

HAProxy を使ってみたメモ

CentOS 7 の yum でさくっとインストールできるバージョンで試しています。

  • haproxy-1.5.14-3.el7.x86_64

その他に、ログとかスティッキーセッションとかについても書いてます。

あと、いまのところ使う予定がなかったので mode tcp は試していません。

WEB で統計レポートを表示

プロキシのセクション(defaults/listen/frontend/backend)で stats enable を指定すれば、http://localhost/haproxy?stats のような URL で HAProxy の統計レポートを表示できる。

frontend main *:80
    stats enable
    :

URL は stats uri で変更できる。下記の例だと http://localhost/admin?stats となる。

frontend main *:80
    stats enable
    stats uri /admin?stats
    :

stats http-requestacl を組み合わせればソース IP アドレスで制限できる。

frontend main *:80
    acl is_private src 192.168.33.0/24
    stats enable
    stats uri /admin?stats
    stats http-request allow if is_private
    :

stats admin で条件を指定すると、その条件に該当するときだけ管理レベルの操作が可能になる。通常は統計レポート画面はリードオンリーな操作しかできないが、この設定が有効だとバックエンドサーバのメンテナンスモードへの切り替えや、ヘルスチェックの有効/無効、などの操作が可能になる。

frontend main *:80
    acl is_private src 192.168.33.0/24
    stats enable
    stats uri /admin?stats
    stats http-request allow if is_private
    stats admin if LOCALHOST
    :

↑で指定した LOCALHOST は組み込みの acl で、自分で定義した acl も指定できる。

stats auth で統計レポートの画面に認証を設けることもできる。

frontend main *:80
    acl is_private src 192.168.33.0/24
    stats enable
    stats uri /admin?stats
    stats http-request allow if is_private
    stats admin if TRUE
    stats auth admin:AdMiN123
    :

CLI で統計レポートを表示

グローバルセクションで stats socket を指定すると Unix ドメインソケット経由で CLI で統計レポートを得ることができる。

global
    stats socket /var/lib/haproxy/stats level admin
    :

例えば、次のように nc とかで統計情報レポートを得ることができる。

echo "show stat -1 4 -1" | nc -U /var/lib/haproxy/stats | grep -v -E '^#|^$' | cut -d, -f2,18

level で指定している admin はソケット経由で操作可能なレベルを表していて、admin ならすべての操作が可能。

また、次のように指定すれば TCP でも接続できる。

global
    stats socket ipv4@127.0.0.1:9999 level admin
    :

monitor-uri / monitor fail

monitor failmonitor-uri を使うと、特定の URL へのリクエストを、条件によって 200 または 503 を返すようにすることができる。

frontend main *:80
    acl is_dead nbsrv(app) lt 2
    monitor fail if is_dead
    monitor-uri /haproxy?alive
    :

この例だと /haproxy?alive へリクエストしたときに app バックエンドの生きているサーバが 2 台以上あるなら 200 を、2 台未満なら 503 を返す。

URL にクエリストリングを含めているのには特に深い意味は無い、クエリストリング無しで /alive とかでも構わない。

monitor-net

monitor-net を使うと、特定のアドレス帯からのリクエストは即座に 200 を応答するようになる。

frontend main *:80
    monitor-net 192.168.33.0/24
    :

この例だと 192.168.33.0/24 からのリクエストは直ちに 200 を応答する。

バックエンドのサーバが全滅しても 200 を応答するけど・・どういう用途に使うものなの? これ。

fullconn / maxconn / minconn

backendfullconnservermaxconnminconn の関係。

http://permalink.gmane.org/gmane.comp.web.haproxy/5378

上記によると、例えば次のような設定だと、

backend dynamic
    fullconn 10000
    server srv1 dyn1:80 minconn 100 maxconn 1000
    server srv2 dyn2:80 minconn 100 maxconn 1000
    :

低負荷時には最大接続数の制限は minconn の 100 で、バックエンド全体に対する同時接続数が増えてくると最大接続数の制限も増加する。その増加は線形で、バックエンド全体に対する同時接続数が fullconn の 10000 になったときに maxconn の 1000 になるように増加する。ただし maxconn を超えることはない。

ニュースサイトとかでトラフィックパターンに大きな変動を伴うサイトでは静的な maxconn だとちょうど良い値を設定することができない(低負荷時は小さい値にしたいし、高負荷時は大きい値にしたい)。

fullconnminconn を使えばサイトの負荷に応じて動的に同時接続数の制限を変動することができる。

・・らしいんだけど maxconn だけじゃダメな理由が良くわからない。

maxconn の設定箇所ごとの意味

globalmaxconn はプロセスに適用されるもので、frontend の接続の合計。

default/frontend/listenmaxconnfrontend ごとに適用される。

frontend がもたらすものよりも大きい値を servermaxconn に設定した場合、キューが使われることがなくなるだけで、とくに問題ではない。

HTTP のモードの設定

下記を参考に・・・

https://blog.cloudpack.jp/2014/07/28/ha-proxy-translation-document-http-transaction/ https://blog.cloudpack.jp/2014/07/28/ha-proxy-translation-document-option-httpclose/ https://blog.cloudpack.jp/2014/08/08/tips-haproxy-option-httpclose-and-http-server-close-with-tcpdump/

http-tunnelhttpclose は非奨励のようなので、下記のいずれかを設定する。

  • option http-keep-alive
    • サーバ側とクライアント側の両方で keep-alive する
  • option http-server-close
    • 応答の終わりでサーバ側はクローズするがクライアント側は keep-alive する
  • option forceclose
    • 応答の終わりでクライアントとサーバの両方をクローズする

おおむね、次のようにしておくと良い。

  • option http-keep-alive
    • 静的コンテンツのサーバ
  • option http-server-close
    • アプリケーションサーバ

タイムアウト関係の設定

  • timeout connect
    • HAProxy からバックエンドサーバへの接続のタイムアウト時間
    • クライアントからの接続のプロキシ時とヘルスチェックでの両方で適用される
  • timeout client
    • クライアントから HAProxy への接続で無応答のタイムアウト時間
    • つまり、HAProxy がクライアントから ACK やデータ送信を期待したときに、どれだけ待つかの時間
    • なので、リクエスト〜レスポンスのトータル時間とは異なる
  • timeout server
    • HAProxy からバックエンドサーバへの接続で無応答のタイムアウト時間
    • timeout client のバックエンドサーバの版
    • なので、リクエスト〜レスポンスのトータル時間とは異なる
  • timeout http-request
    • 完全なリクエスト〜レスポンスのタムアウト時間
    • クライアントから最初のバイトを受信してからクライアントに最後のバイトを送信するまでの時間
    • クライアントからのリクエストの開始からリクエストヘッダを受けきるまでのタムアウト時間
    • リクエストボディやレスポンスにはこのタイムアウト時間は適用されない
  • timeout http-keep-alive
    • いわゆる keep-alive のタイムアウト時間
    • クライアントに最後のバイトを送信してから次のリクエストの最初のバイトを受信するまでの時間
  • timeout check
    • ヘルスチェックで接続が確立された後の追加のタイムアウト時間
    • ヘルスチェックでは接続のタイムアウト時間として timeout connectinter の小さい方が使用される
    • 接続が確立されると read のタイムアウト時間として timeout check が追加される
  • timeout queue
    • キューに入った接続を解放するまでのタイムアウト時間
    • HAProxy への接続が servermaxconn に達すると接続はキューに入って server の接続の空きを待つ
    • タイムアウト時間が経過するとクライアントには 503 が応答される

option redispatch

スティッキークッキーが有効な場合、振り分けられるバックエンドのサーバが死ぬと他のサーバが生きていても HAProxy が当該サーバをダウンと判断するまでクライアントはサービスにアクセスすることができなくなる。

下記のオプションを指定すると、そのようなケースでも別のサーバへの振り分けが行われる。

defaults
    option redispatch
    :

また、retries も指定されている場合、リトライの最後の施行だけ別のサーバへ振り分けられる。

defaults
    option redispatch
    retries 3
    :

option httplog

HTTP のアクセスログを記録する。

次のように指定すると CLF という Apache のアクセスログでお馴染みの標準的な形式のログになる。

defaults
    option httplog clf
    :

option dontlognull

いわゆる NULL スキャンのログを記録しないオプション、、、というわけではなくて、TCP で 80 番ポートを開いて直ぐ閉じるような、内容が HTTP ではないアクセスもログをログに記録しないようにするオプション。

nmap でいうところの -sT オプションのスキャンがログに残るかどうか、ということ(-sN はどっちにしろ残らない)。

例えば、死活監視でポートが開いているかを監視するために bash で < /dev/tcp/127.0.0.1/80 みたいなことをやっていると、通常はこのアクセスまでログに記録されてしまうが、option dontlognull を指定しておけば、このようなアクセスはログには残らなくなる。

インターネットのような制御されていない環境(uncontrolled environments)では指定しないことをおすすめする。

forwardfor

次のように指定すると X-Forwarded-ForX-Original-To ヘッダがバックエンドサーバへのリクエストに追加される。

defaults
    option forwardfor
    option originalto
    :

既に X-Forwarded-ForX-Original-To が付与されていた場合(HAProxy へのリクエストの時点でこれらのヘッダが付いていた場合)、カンマ区切りで追記される。

次のように except を追加すると、そのネットワークからのリクエストでは X-Forwarded-ForX-Original-To を追加しなくなる。

defaults
    option forwardfor except 127.0.0.0/8
    option originalto except 127.0.0.0/8
    :

例えば、HAProxy へのアクセスが、クライアントから直接と、既知のリバースプロキシ経由との 2 つが混在するような場合に、except でリバースプロキシを指定すれば、既知のアドレスが X-Forwarded-For に追加されないようにすることができる。

他にも、HAProxy のホスト上から http://localhost/ などとアクセスすると X-Forwarded-For127.0.0.1 になり、バックエンドサーバでこの値をアクセスログに記録している場合、まるで意味のないログになってしまう。そこで、except 127.0.0.0/8 を指定すれば X-Forwarded-For が記録されなくなるので、Web サーバで X-Forwarded-For がなければ通常のリモートアドレスにフォールバックするようになっていれば、アクセスログには Web サーバから見た HAProxy のアドレスが記録されるようになる・・・という使い方もできるかもしれない。

option logasap

デフォルトではログはレスポンスの完了時に記録されるが(じゃないと総転送量とかがわからない)、次のように設定するとレスポンスヘッダが送信された時点でアクセスログが記録されるようになる。

defaults
    option logasap
    :

ただ、その場合、ログに記録される転送量や転送時間は不完全なものになる。実際に見比べた感じ、このオプションがなければ 688 のような具体的な値が記録されるのに対して、このオプションを指定していると +445 のように記録される。

option log-health-checks

デフォルトではヘルスチェックのログは risefall で指定された回数のリクエストの結果を元にステータスが変化したときにだけ記録されるが、次の設定をすると、ステータスが変化する前のヘルスチェックの結果もログに記録される。

defaults
    option log-health-checks
    :

例えば、ヘルスチェックの設定が次のようになっているとき、

defaults
    default-server inter 2s rise 2 fall 3
    :

ステータスが OK の状態でヘルスチェックに失敗したとき、上記のオプションが指定されていなければ 3 回失敗して初めてログに記録されるが、上記のオプションが指定されている場合は最初の 1 回目からログに記録され、3 回ログに記録された後にステータスが変わった旨のログが記録される。

agent-check

バックエンドサーバの Up/Down や重みをサーバの特定のポートに TCP でアクセスして返ってきた値に応じて行なう。

次のように設定すると、サーバの 1234 ポートに定期的にアクセスする。

backend app
    balance roundrobin

    default-server inter 2s rise 2 fall 3 agent-port 1234 agent-inter 3s

    server ap01 192.168.33.21:80 check agent-check
    server ap02 192.168.33.22:80 check agent-check

    option httpchk GET /ok.html
    http-check expect status 200

そのポートが 80% のような値を返すと、振り分けの重みが 80% になる。

他にも、次のような値を返すことができる。

  • サーバの Up/Down の状態
    • up
    • down または failed または stopped
  • サーバの管理状態
    • ready
    • maint
    • drain
  • 重み
    • 80% など

ポートが返さなかった値は変更されない。つまり 80% とだけ返した場合は重みだけが変更されて、Up/Down やメンテナンスなどの状態は変更されない。

また、ポートが応答がなかった場合でも Down になるわけではないので(ステータスが更新されないだけ)、option httpchk と併用はしなければならない。

READY/MAINT/DRAIN

agent-check の説明ででてきた READY/MAINT/DRAIN はバックエンドサーバの管理状態を意味するもので、それぞれ次のような意味。

  • READY
    • MAINT や DRAIN が解除された通常の状態
  • DRAIN
    • 新しい接続を受け入れない
  • MAINT
    • 新しい接続を受け入れず、かつ、死活監視も停止する

動的なサーバの取り外し

稼働中の HAProxy から一時的に特定のバックエンドサーバへの振り分けを停止する方法。

WEB での統計レポートの管理モードを有効にして、特定のサーバを DRAIN や MAINT に切り替えることで実現できる。

もしくは、CLI での統計レポートの管理モードが有効になっていれば、次のようなコマンドで切り替えることができる。

# MAINT にする
echo "disable server app/ap01" | nc -U /var/lib/haproxy/stats

# MAINT から元に戻す
echo "enable server app/ap01" | nc -U /var/lib/haproxy/stats
# DRAIN
echo "set weight app/ap01 0%" | nc -U /var/lib/haproxy/stats

# DRAIN から元に戻す
echo "set weight app/ap01 100%" | nc -U /var/lib/haproxy/stats

サービスダウンのログ

バックエンドサーバが全滅すると次のようなログが発生する。

Server app/ap02 is DOWN, reason: Layer7 wrong status, code: 404, info: "HTTP status check returned code <3C>404<3E>", check duration: 0ms. 0 active and 0 backup servers left. 0 sessions active, 0 requeued, 0 remaining in queue.
backend app has no server available!

1個目のログは alert だけど2個目のログは emerg で発生する。

emerg のログは rsyslog の設定に依らず(というか rsyslog を停止しても)コンソールにブロードキャストされる。

Broadcast message from systemd-journald@ha01 (Tue 2016-04-19 14:04:43 JST):

haproxy[7758]: backend app has no server available!

見ての通り systemd-journald が出しているもので、/etc/systemd/journald.confMaxLevelWall のデフォルトが emerg だからなのだと思われる。

参考

rsyslog のメモ

rsyslogd - reliable and extended syslogd

信頼できる拡張された syslogd

Remote syslogd ではない(syslogd 時代からリモートからの受信は可能)

設定ファイル

設定ファイルは /etc/rsyslog.conf ですが、このファイルで下記の通りに記述されているので、

$IncludeConfig /etc/rsyslog.d/*.conf

/etc/rsyslog.d/ の拡張子 .conf のファイルもマージされます。

/dev/log

/dev/log という Unix ドメインソケットに書き込めばログを送ることができます。

echo "<133>$(LANG=C date '+%b %d %H:%M:%S') oreore: XXX" | nc -Uu /dev/log

CentOS 6 の nc だと -u と -U を同時に指定できなかったので、代わりに socat で送れます。

echo "<133>$(LANG=C date '+%b %d %H:%M:%S') oreore: XXX" | socat STDIN UNIX-SENDTO:/dev/log

<133> の部分はファシリティとプライオリティで、次の計算式で求めます。

facility * 8 + severity

例えば、local0.notice だと次のとおりです。

local0(16) * 8 + info(5) =  133

日時の部分を改ざんしても、実際にログを送信した日時が記録されました。

echo "<133>Apr 01 06:12:34 oreore: uso" | nc -Uu /dev/log
sudo tail -1 /var/log/messages
# Apr 14 09:15:48 ore-no-server oreore: uso
LANG=C date '+%b %d %H:%M:%S'
# Apr 14 09:15:48

ただまあ、普通はこんなことせずに logger コマンドを使います。

logger -p local0.notice -t oreore -i message
sudo tail -1 /var/log/messages
# Apr 14 09:15:48 ore-no-server oreore[1234]: message

なお、CentOS 7 だと実は /dev/log は journald がリッスンしていて、rsyslog は journald からログを取得しています。

UDP

デフォでは UDP でリッスンしていないので /etc/rsyslog.conf の下記をコメントインするか、

$ModLoad imudp
$UDPServerRun 514

/etc/rsyslog.d/udp.conf のような名前で下記のファイルを作成する必要があります。

$ModLoad imudp
$UDPServerRun 514

UDP で送る場合は次の形式です。

echo "<133>$(LANG=C date '+%b %d %H:%M:%S') $(hostname -s) oreore: XXX" | nc -u localhost 514

/dev/log とは異なり、日時を改ざんするとその通りにログが記録されます。

echo "<133>Apr 01 06:12:34 ore-no-server oreore: uso" | nc -u localhost 514
sudo tail -1 /var/log/messages
# Apr  1 06:12:34 ore-no-server oreore: uso
LANG=C date '+%b %d %H:%M:%S'
# Apr 14 09:16:19

journald

systemd から起動するサービスが標準入力や標準出力に書き込むと journald を経由して rsyslog にも出力されます。

プロパティベースのフィルター

rsyslog でログファイルを分けたい場合、設定ファイルで昔ながらの次のようなフィルターが使えますが、

# local1 のログを出さない
*.info;mail.none;authpriv.none;cron.none;local1.none    /var/log/messages

# local1 のログは oreore.log に出す
local1.*                                                /var/log/oreore.log

次のような、プロパティベースのフィルターも使用できます。

/etc/rsyslog.d/oreore.conf

:programname, isequal, "oreore" /var/log/oreore.log

この例だと programnameoreore であるログは /var/log/oreore.log に出力されます。

:programname というのは下記のログの oreore の部分です。

Apr 14 09:15:48 hostname oreore[1234]: message

& は、直前のパターンにマッチしたもの、という意味です。また、ログファイル名に ~ を指定するとログは破棄されます。

なので、次のように指定すると、oreore のログは /var/log/oreore.log に書き込まれて破棄されます(/var/log/messages)には書かれない。

:programname, isequal, "oreore" /var/log/oreore.log
& ~

なお、最近の rsyslog だと ~ を使うと警告が表示されるので代わりに stop を使うのが正しいようです。

:programname, isequal, "oreore" /var/log/oreore.log
& stop

なお :programname は CentOS 6 の rsyslog では使えませんでした、代わりに次のようにすると良いでしょう。

:syslogtag, startswith, "oreore" /var/log/oreore.log
& stop

:syslogtag というのは下記のようなログの oreore[1234] の部分です。

Apr 14 09:15:48 hostname oreore[1234]: message

式ベースのフィルター

式ベースのフィルターというものを使えばもっと複雑なこともできます。

if $programname == 'prog1' then {
   action(type="omfile" file="/var/log/prog1.log")
   if $msg contains 'test' then
     action(type="omfile" file="/var/log/prog1test.log")
   else
     action(type="omfile" file="/var/log/prog1notest.log")
}

が、そこまでやることってあるのだろうか・・・

テンプレート

テンプレート機能を使えばログファイルの形式をカスタマイズできます。

次のようにテンプレートを登録します。

$template oretemplate, "%timestamp% %hostname% %syslogtag% <%syslogfacility-text%.%syslogseverity-text%> %msg%\n"

ログファイル名を指定するときにテンプレート名も一緒に指定します。

:programname, isequal, "oreore" /var/log/oreore.log;oretemplate
& stop

すると、ログに次のようにファシリティとプライオリティも記録されるようになります。

Apr 14 09:15:48 hostname oreore[1234]: <local0.notice> message

参考

Logwatch で独自の通知を追加するメモ

Logwatch とは

  • Logwatch
  • 日次でシステム内のさまざまなログから見つかった問題を通知する
    • /etc/cron.daily/0logwatch で実行される
  • デフォだと root へのメールで通知される
  • デフォでいろいろなログの通知が組み込まれている
    • ls /usr/share/logwatch/scripts/services/

独自の通知の追加

例えば oreore というサービスのログを追加することにします。

ログファイルの設定

oreore のログファイルに関する設定を追加します。

/etc/logwatch/conf/logfiles/oreore.conf

LogFile = oreore.log
LogFile = oreore.log-*[0-9]
Archive = oreore.log-*.gz

LogFile にはログファイル名を glob 形式で指定します。パスは /var/log/ からの相対です。このディレクトリ以外のログを指定したい場合は絶対パスで指定します。

Archive には gzip でアーカイブされたログファイルを指定します。ただ、この設定は普段は使われません(logwatch の実行時のオプションでアーカイブされたログも走査するかのオプションがあるけどデフォで指定されていない)。

また、通常はこの設定ファイルでログファイルを日付でフィルタするための設定を記述します。例えば syslog 形式のログであれば次のように記述すれば日付でフィルタされます。

*ApplyStdDate

この記述によって /usr/share/logwatch/scripts/shared/applystddate にログファイルがパイプされるようになります。

他にも /usr/share/logwatch/scripts/shared/ ディレクトリにあるスクリプトを指定することができます。ログファイルに独自の処理を行いたい場合は下記のディレクトリにスクリプトを配置します。

  • /etc/logwatch/scripts/logfiles/oreore/

このディレクトリに配置したスクリプトは oreore のログの走査時に自動的に適用されます。

例えば次のような内容で作成します。

/etc/logwatch/scripts/logfiles/oreore/applydate

use Logwatch ':dates';

my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0;

my $SearchDate = TimeFilter('\[%Y-%m-%dT%H:%M:%S\]');

if ( $Debug > 5 ) {
    print STDERR "DEBUG: Inside oreore..\n";
    print STDERR "DEBUG: Looking For: (" . $SearchDate . ")\n";
}

while (defined($ThisLine = <STDIN>)) {
    if ($ThisLine =~ s/^$SearchDate\s+//o) {
        print $ThisLine;
    }
}

既存のスクリプトからほとんどコピペです。Logwatch モジュールの TimeFilter を使えば日時でフィルタするための正規表現を簡単に作ることができます。例えば↑の例だと $SearchDate\[2016-04-14T..:..:..\] のようになります。

この例では perl でスクリプトを作成していますが、シェバングを書けばそのインタプリタで実行されるので、好きな言語で作成することができます。

サービスの設定

oreore のサービスに関する設定を追加します。

/etc/logwatch/conf/services/oreore.conf

Title = "oreore report"
LogFile = oreore

Title は通知のレポートで使われるサービスの名称です。

LogFile には↑で設定したログファイルの設定名を指定します。この例ではログファイルとサービスで同じ oreore という設定名ですが、例えばこのサービスが /var/log/messages にログを出すようになっているのなら LogFile = messages と指定したりすることもあります。

また、ログファイルの設定と同じように、フィルタを下記のように記述することができます。

*Remove = "^(DEBUG|INFO):"

指定できるものはログファイルの設定と同じです(/usr/share/logwatch/scripts/shared/ のスクリプト)。

最後に、レポートを出力するためのスクリプトを /etc/logwatch/scripts/services/oreore に作成します。ログファイルの設定やサービスの設定によってフィルタされたログが標準入力から入ってくるので、それっぽい出力にしてやれば OK です。

例えば、単にそのまま流すだけですが、次のような内容です。

/etc/logwatch/scripts/services/oreore

while (defined($ThisLine = <STDIN>)) {
    print $ThisLine;
}

もちろん perl 以外でも作成できるので、そのまま流すだけなら下記で十分です。

#!/bin/bash
cat

環境変数

ログファイルやサービスの設定ファイルで次のように変数を記述すると、

$oreore_value = 123

スクリプトで環境変数として取り出せます。

my $oreore_value = $ENV{'oreore_value'} || 0;

変数名に大文字を用いてもすべて小文字に置換されます。

ログファイルの設定ファイルで記述した変数は、ログファイルとサービスの両方のスクリプトで有効になります。ただ、見た感じ既存のログファイルの設定ファイルで変数が記述されているものはありませんでした。

サービスの設定ファイルで記述した変数は、サービスのスクリプトのみで有効になります。

動作確認

次のように環境変数を設定しつつログファイルからスクリプトをパイプしていくと最終的な出力が得られます。

export LANG=C PERL5LIB=/usr/share/logwatch/lib/ LOGWATCH_DEBUG=9 LOGWATCH_DATE_RANGE=today
sudo cat /var/log/oreore.log |
  perl /etc/logwatch/scripts/logfiles/oreore/applydate |
  perl /usr/share/logwatch/scripts/shared/remove "^(DEBUG|INFO):" |
  perl /etc/logwatch/scripts/services/oreore

切り貼りすれば途中までの結果を出力したりもできるのでデバッグが捗ります。

最終的な動作確認には 次のように logwatch を実行します(CentOS 7)。

sudo logwatch --service oreore --output stdout --range today

CentOS 6 だと --output オプションが無くて、代わりに --print を使います。

sudo logwatch --service oreore --print--range today

フィルタ

ログファイルやサービスの設定で指定可能なフィルタ(/usr/share/logwatch/scripts/shared/ のスクリプト)には以下のようなものがあります。

  • applybinddate
  • applyeurodate
  • applyhttpdate
  • applystddate
  • applytaidate
  • applyusdate
  • applyvsftpddate
    • 日付でフィルタ(ログファイルの形式により様々)
  • eventlogonlyservice
  • eventlogremoveservice
    • 謎・・MSWinEventLog とかいう記述が見えるけど
  • expandrepeats
    • last message repeated 10 times のように省略されたログを展開
  • hosthash
    • 謎・・syslog 形式のログからホスト名の一覧を出力するっぽいけど
  • hostlist
    • 謎・・syslog 形式のログからホスト名の一覧をテンポラリに書き出すっぽいけど
  • multiservice
    • syslog 形式のログで特定のサービスの以外の行を除去
    • *RemoveService = ntpd,ntpdate のように引数をカンマ区切りで指定する
  • onlycontains
    • 指定されたパターンにマッチする行のみを抽出
    • *OnlyContains = ^(ERROR|WARN): のように正規表現で指定する
  • onlyhost
    • syslog 形式のログで特定のホストの行のみを抽出
    • ホスト名は logwatch.confHostLimit または引数で指定する
    • logwatch.conf で指定するときはカンマ区切りまたは正規表現で指定する
      • カンマの有無で意味が変わる
    • 引数で指定するときは正規表現で指定する
  • onlyservice
    • syslog 形式のログで特定のサービスの行のみを抽出
    • *OnlyService = (spamd|sendmail) のように引数を正規表現で指定する
  • remove
    • 指定されたパターンにマッチする行を除去する
    • *remove = ^(DEBUG|INFO): のように正規表現で指定する
  • removeheaders
    • syslog 形式のログのメッセージ以外の部分を削除
    • Apr 13 23:20:01 localhost systemd: hogehoge -> hogehoge
  • removeservice
    • syslog 形式のログで特定のサービスの行を除去
    • *RemoveService = anacron,cron のように引数をカンマ区切りで指定する

詳しいことは less /usr/share/doc/logwatch-*/HOWTO-Customize-LogWatch で。

参考

全部自分の記事だけど・・

HAProxy でスティッキーセッションするメモ

CentOS 7 の yum でさくっとインストールできるバージョンで試しています。

  • haproxy-1.5.14-3.el7.x86_64

appsession

アプリケーションが発行する cookie の値とサーバとの対応表のテーブルを HAProxy が保持する。

appsession PHPSESSID len 32 timeout 30m request-learn

この例だと PHPSESSID という名前のクッキーの値の先頭 32 バイトを用いてテーブルを作成する。有効期限は 30 分で有効期限が切れるとテーブルから削除される。

request-learn を付けると、HAProxy はリクエストの Cookie からもテーブルに追加する。request-learn がない場合、次のようなケースで問題になる。

  • サイトにアクセス
    • クライアントの Cookie なし
      • HAProxy は Cookie の値が対応表にないのでランダムに振り分ける
    • サーバが Set-Cookie を返す
      • HAProxy が Cookie の値を記録する
  • サイトにアクセス
    • クライアントが Cookie を送る
      • HAProxy が Cookie の値で対応表を引いて振り分けるサーバを決定する
    • サーバは Cookie を発行済みなので Set-Cookie を送らない
  • HAProxy を再起動する
  • サイトにアクセス
    • クライアントが Cookie を送る
      • HAProxy は Cookie の値が対応表にないのでランダムに振り分ける
    • サーバは Cookie を発行済みなので Set-Cookie を送らない
  • サイトにアクセス
    • クライアントが Cookie を送る
      • HAProxy は Cookie の値が対応表にないのでランダムに振り分ける
    • サーバは Cookie を発行済みなので Set-Cookie を送らない

PHP のセッションはクライアントが Cookie を送るとサーバは Set-Cookie を送らないので (session_regenerate_id とかしなければ) 、request-learn を指定しておくと無難。

prefixmode も指定できるけどあんまり使わなさそう。

この方法は対応表のテーブルを保持するために HAProxy でメモリを消費する。

ボットとか死活監視とかのセッションの必要がないリクエストは ignore-persist で除外しておけば、それらのリクエストによって appsession のテーブルが大きくなることを防止できる。

appsession PHPSESSID len 64 timeout 30m request-learn
acl ab hdr_sub(User-Agent) -i ApacheBench
ignore-persist if ab

cookie insert

HAProxy 自身が Cookie を発行する。

cookie HAPROXY insert nocache indirect preserve httponly secure maxidle 30m maxlife 8h
server ap01 192.168.33.21:80 check cookie ap01
server ap02 192.168.33.22:80 check cookie ap02

この例だと HAPROXY=ap01 のような Cookie が発行される。

nocache を指定すると、レスポンスヘッダに Cache-control: private を追加することでプロキシサーバがキャッシュしないようにする。スティッキーのための Cookie をプロキシに覚えさせるわけにはいかないので、nocache かもしくは後述の postonly を指定しておくのが無難。

indirect を指定すると、クライアントが送信した Cookie (上の例だと HAPROXY=ap01) は HAProxy が削除するので、サーバに Cookie の値は渡らない。

preserve を指定すると、サーバで setcookie('HAPROXY', '', time()-3600) とかで Cookie 削除のためのレスポンスを返すことができる。逆に、指定していなければ、サーバがそのようなレスポンスを送っても HAProxy が削除する。

httponlysecure はそのままの意味、domain も指定できる。

maxidle 30m は、指定した時間以上未アクセスだと無効になるように Cookie を発行する。

maxlife 8h は、Cookie を最初に発行してから指定時間以上経過すると無効になるように Cookie を発行する。

maxidlemaxlife は Cookie に HAPROXY=ap01|VwNGp|VwNGl のようなタイムスタンプに基づく値を付与することで実現される。

postonly を指定すれば POST リクエストのレスポンスにのみ Set-Cookie が発行されるようになる。 大抵のアプリではログイン画面の POST リクエストからがセッションの始まりになるので、postonly を指定しても良いかもしれない。

appsession とは異なり、Cookie の値から直接振り分け先が導出されるので、対応表のために余分なメモリを消費することはない。

cookie prefix

アプリケーションが発行するクッキーの値の先頭に HAProxy が追記する。

cookie PHPSESSID prefix
server ap01 192.168.33.21:80 check cookie ap01
server ap02 192.168.33.22:80 check cookie ap02

この例だと、アプリケーションが PHPSESSID=xyz123 という Cookie を発行したら、HAProxy がそれを PHPSESSID=ap01~xyz123 のように書き換える。

cookie rewrite

prefix とほとんど同じだけど、先頭に追記じゃなくてただの書き換えになる。

cookie HAPROXY rewrite
server ap01 192.168.33.21:80 check cookie ap01
server ap02 192.168.33.22:80 check cookie ap02

この例だと、アプリケーションが HAPROXY=hoge のように Cookie を発行したら、HAProxy がそれを HAPROXY=ap01 のように書き換える。 PHPSESSID のようなアプリケーションのセッション用 Cookie を指定してしまうと HAProxy がその値を書き換えてしまうので、セッションの維持ができない。

参考

HAProxy のログのメモ

CentOS 7 の yum でさくっとインストールできるバージョンで試しています。

  • haproxy-1.5.14-3.el7.x86_64

ログの宛先の設定

chroot しない場合

chroot しない場合は /dev/log を指定すれば簡単でした。

/etc/haproxy/haproxy.cfg

global
    log /dev/log local2
    #chroot /var/lib/haproxy
    :

chroot する場合で UDP を使う

chroot すると /dev/log が haproxy から見えなくなってしまいます。ので、UDP にします。

CentOS 7 の rsyslog はデフォだと UDP でリッスンしていないので、下記のように設定を変更して UDP を有効にします。

/etc/rsyslog.d/udp.conf

$ModLoad imudp
$UDPServerRun 514

haproxy.cfg でログの宛先に 127.0.0.1 を指定するのですが、このとき log-send-hostname も指定しなければログのホスト名が localhost になってしまったので、これも一緒に指定します。

/etc/haproxy/haproxy.cfg

global
    log 127.0.0.1 local2
    log-send-hostname
    chroot /var/lib/haproxy
    :

chroot する先に /dev/log を mount する

chroot した先で /dev/log が見えればいいので・・うーん、mount --bind で作っておく?

sudo mkdir -p /var/lib/haproxy/dev/
sudo touch /var/lib/haproxy/dev/log
sudo mount --bind /dev/log /var/lib/haproxy/dev/log

/etc/haproxy/haproxy.cfg

global
    log /dev/log local2
    chroot /var/lib/haproxy
    :

うーん、これはちょっと無いかな・・

chroot する先に rsyslog にリッスンさせる

chroot する先で rsyslog がリッスンするように設定します。

/etc/rsyslog.d/haproxy.conf

$AddUnixListenSocket /var/lib/haproxy/dev/log

ディレクトリを作成します。あと、↑で mount --bind したままなら解除しておきます。

sudo umount /var/lib/haproxy/dev/log
sudo rm -f /var/lib/haproxy/dev/log
sudo mkdir -p /var/lib/haproxy/dev/

/etc/haproxy/haproxy.cfg

global
    log /dev/log local2
    chroot /var/lib/haproxy
    :

ログを別ファイルに出力

↑の設定をしただけだとログが /var/log/messages に出てしまうので、rsyslog で別ファイルに書かれるように設定します。

/etc/rsyslog.d/haproxy.conf

:programname, isequal, "haproxy" /var/log/haproxy.log
& stop

haproxy をインストールしたときに下記のようなログローテートも設定されていたので /var/log/haproxy.log に出力しておけばローテートも行われます。

/etc/logrotate.d/haproxy

/var/log/haproxy.log {
    daily
    rotate 10
    missingok
    notifempty
    compress
    sharedscripts
    postrotate
        /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
        /bin/kill -HUP `cat /var/run/rsyslogd.pid 2> /dev/null` 2> /dev/null || true
    endscript

systemd のユニットファイルに書かれている /usr/sbin/haproxy-systemd-wrapper からもログが出力されるようなのですが、↑の設定だとそのログは /var/log/messages に書かれるままです。

haproxy-systemd-wrapper というタグで出力されるので、下記のようにすればそのログも /var/log/haproxy.log に出力されます。

/etc/rsyslog.d/haproxy.conf

:programname, isequal, "haproxy" /var/log/haproxy.log
& stop
:programname, isequal, "haproxy-systemd-wrapper" /var/log/haproxy.log
& stop

もしくは、startswith でも良いと思います。

:programname, startswith, "haproxy" /var/log/haproxy.log
& stop

さいごに

うーん、どの方法が良いのだろうか。。ググった感じでは UDP にしているのが多いっぽいけど。

keepalived を使ってみたメモ

いまさらだけど keepalived を使ってみたメモ。LVS ではなく HAProxy の冗長化に使用するつもりなので LVS 関係の設定はありません。

CentOS 7 に yum で入れたバージョンで試しています。

  • keepalived-1.2.13-7.el7.x86_64

vrrp_script と track_script

vrrp_script でチェックスクリプトを定義して、

vrrp_script chk_haproxy {
    script "systemctl is-active haproxy"
    interval 3 # スクリプトが実行されるインターバル
    fall 3     # 非ゼロの終了コードが 3 回続いたら成功から失敗に遷移する
    rise 2     # 非ゼロの終了コードが 2 回続いたら失敗から成功に遷移する
}

vrrp_script chk_http_port {
    script "< /dev/tcp/127.0.0.1/80"
    interval 3
    fall 3
    rise 2
}

vrrp_instancetrack_script で VRRP インスタンスに設定する。

vrrp_instance VI_1 {

    :

    track_script {
      chk_haproxy
      chk_http_port # 複数指定できる
    }
}

vrrp_script の weight

vrrp_sync_group を設定していてかつ weight も設定しているとチェック自体が実行されなくなる。

vrrp_sync_group を設定していなければ weight の値によって次のように動作する。

  • weight が未設定
    • スクリプトが失敗ると FAULT になる
  • weight が正数
    • スクリプトが成功するとプライオリティが指定値だけ上がる
  • weight が負数
    • スクリプトが失敗するとプライオリティが指定値だけ下がる

weight を指定している場合に、nopreempt も指定しているとスクリプトが失敗してもフェールオーバーしない(MASTER が FAULT にならないかぎりフェイルオーバーしないため)。

smtp_alert

global_defs で通知先やメールサーバを設定し、vrrp_sync_groupvrrp_instancesmtp_alert を指定すると、ステートの変化時にメールで通知できる。

global_defs {
    notification_email {
        ore@example.com
    }
    notification_email_from keepalived@example.com
    smtp_server 127.0.0.1
    smtp_connect_timeout 10

    :
}

vrrp_sync_group VG_1 {
    :
    smtp_alert
}

vrrp_instance VI_1 {
    :
    smtp_alert
}

送信されるメールの内容は次のようなもの。

Sublect: [192.168.33.11] VRRP Group VG - Entering BACKUP state
Body: => All VRRP group instances are now in BACKUP state <=
Sublect: [192.168.33.11] VRRP Instance VIP - Entering BACKUP state
Body: => VRRP Instance is nolonger owning VRRP VIPs <=

MASTER や BACKUP になったときは通知されるけど FAULT になったときは通知されない。

notify

vrrp_sync_groupvrrp_instance でステートの変化時に実行するスクリプトを指定することができる。

vrrp_sync_group VG_1 {
    :
    notify        "/vagrant/notify.sh"
    notify_master "/vagrant/notify.sh master"
    notify_backup "/vagrant/notify.sh backup"
    notify_fault  "/vagrant/notify.sh fault"
}

vrrp_instance VI_1 {
    :
    notify        "/vagrant/notify.sh"
    notify_master "/vagrant/notify.sh master"
    notify_backup "/vagrant/notify.sh backup"
    notify_fault  "/vagrant/notify.sh fault"
    notify_stop   "/vagrant/notify.sh stop"
}

notify は指定した値がそのまま実行可能ファイル名だと認識されるので引数を指定することはできない。

notify "/vagrant/notify.sh"     # OK
notify "/vagrant/notify.sh arg" # NG

その他のスクリプトは引数を指定することができる。

notify_master "/vagrant/notify.sh master"
notify_backup "/vagrant/notify.sh backup"
notify_fault  "/vagrant/notify.sh fault"
notify_stop   "/vagrant/notify.sh stop"

notify は次のように引数が付けられて実行される。

notify.sh GROUP VG_1 MASTER 0
notify.sh INSTANCE VI_1 MASTER 100
notify.sh GROUP VG_1 BACKUP 0
notify.sh INSTANCE VI_1 BACKUP 100
notify.sh GROUP VG_1 FAULT 0
notify.sh INSTANCE VI_1 FAULT 100

最後の数字は優先度で GROUP のときは優先度とか無いので常に 0 になる。

その他のスクリプトは引数はつかない(設定で指定した引数がそのまま渡される)。

notify_stopvrrp_instance にのみ設定することができて、 VRRP インスタンスのシャットダウン時に実行される。

iptables や tcpdump で VRRP パケットを操作

iptables を有効にする。

sudo yum install -y iptables-services
sudo systemctl start iptables.service
sudo systemctl enable iptables.service
sudo iptables -F
sudo service iptables save

プロトコルには vrrp を指定する。

iptables -A INPUT -p vrrp -j DROP

tcpdump でも vrrp を指定できる。

tcpdump -nn -i any vrrp

vrrp のタイムアウト

vrrp のタイムアウト(MASTER が停止したと判断される時間)は advert_int の 3 倍で、変更できない。

unicast

自分自身に unicast しても届かないので、次のように2台で同じ値を設定しても多分大丈夫。

unicast_peer {
    192.168.33.10
    192.168.33.11
}

ログ

/etc/rsyslog.d/keepalived.conf 辺りで次のようにしておけば、ログを /var/log/keepalived.log に出力することができる(デフォだと /var/log/messages に出る)。

/etc/rsyslog.d/keepalived.conf

:programname, startswith, "Keepalived" /var/log/keepalived.log
& stop

:programname, startswith, "keepalived" /var/log/keepalived.log
& stop

ログローテートも必要。

/etc/logrotate.d/keepalived

/var/log/keepalived.log {
    daily
    rotate 10
    missingok
    sharedscripts
    postrotate
        /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
    endscript
}

これだと1回のローテートで rsyslogd に複数回 HUP される気がするので /etc/logrotate.d/syslog に追記するほうが良いかもしれない。

/etc/logrotate.d/syslog

/var/log/cron
/var/log/maillog
/var/log/messages
/var/log/secure
/var/log/spooler
/var/log/keepalived.log
{
    missingok
    sharedscripts
    postrotate
        /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
    endscript
}

参考