2018年 07月 20日

L2スイッチとルーター

今更ながらいまいち理解できてないポイントがあったので、いっかい自分でまとめてみることにする。当たり前のことではあるが……

L2スイッチ

セグメントを構築する機器 スイッチングハブ

用途はリピーターハブと同じで、あるポートに届いたイーサネットフレームを他の全てのポートへ転送する。ただし帯域を有効利用するため転送されてきたポートと MAC アドレスの対応を記憶して、次回から必要のないポートに転送することをやめる。

ルーター

セグメント間を接続する機器

IPヘッダを解釈し、ルーティングテーブルから次に転送すべきルーターへパケットを転送する。

頻出事例

(IPv4) L2スイッチに接続されたコンピュータ同士の通信

同一セグメント内の通信ということになる。L2スイッチで完結する。

192.168.0.101 から 192.168.0.102 への通信の例

  • 101 は 102 の IP アドレスをのせてARPリクエストをイーサネットにブロードキャスト
  • 102 は自分の MAC アドレスをのせてARPレスポンス
  • 101 は取得した MAC アドレス宛にIPパケットをのせたイーサネットフレームを送信

L2 スイッチは多段接続しても1つのネットワーク。

(IPv6)L2スイッチに接続されたコンピュータ同士の通信

ほとんど IPv4 と同じだが ARP ではなく ICMPv6 で MAC アドレスが解決される。Neighbor Solicitation メッセージと Neighbor Advertisement メッセージが使用される。

(IPv4) ルーターを介したコンピュータ同士の通信 (外部ネットワークなど)

192.168.0.101 から 93.184.216.34 への通信の例

  • 101 は自分のルーティングテーブルを参照する
  • 接続先が同一セグメントではないため、デフォルトゲートウェイ(またはルーティングテーブルにあるホスト) へ転送しようとする
  • 転送するホストのIPアドレス (例:192.168.0.1) の MAC アドレスを取得する (ARP)
  • 取得した MAC アドレス宛にIPパケットをのせたイーサネットフレームを送信
  • ルータは送られてきたイーサネットフレームとIPパケットを解釈し、次のルータにIPパケットを転送する。このときIPパケットを載せるプロトコルはイーサネットとは限らない。PPP ということもある。

(IPv6) ルーターを介したコンピュータ同士の通信 (外部ネットワークなど)

こちらも ARP 部分が ICMPv6 でおきかわる。ルーターの場合はネットワーク接続時の要求や、定期的にブロードキャストされる Router Advertisement メッセージから MAC アドレスがキャッシュされているため、基本的にルーター MAC アドレスが既知のものとなる。

BBR 輻輳制御アルゴリズムの考えかた

  • これまで採用されていた輻輳制御アルゴリズムの多くはパケットロスベースであった
    • CUBIC / (New) Reno など
  • しかし実際はパケットロスと輻輳は厳密に対応しない
  • 輻輳はネットワークの処理能力の限界を超えたときに発生する
    • 処理すべきデータ容量が処理可能なデータ容量を超えると発生する
  • パケットロスは瞬間的なトラフィック増加や、電気的ノイズなどネットワーク処理能力以外の要素でも発生する現象
    • 無線ネットワークのように原理的にパケットロス率が高いことも多い

BBR では通信先との間の実際に使える帯域を推定し、パケットロスが起こっていても、あるいはパケットロスが起こらなくても、輻輳しない程度に最大のスループットを出す。

実際のアルゴリズムは解説してるサイトを見るほうが早い。

ref

2018年 07月 19日

curl の Expect: 100-continue を抑制する

curl は POST や PUT でリクエストボディの長さが長いなどの特定条件になると、まず Expect: 100-continue をつけてリクエストを送り、サーバ側の対応を待ってからリクエストボディを改めて送るという行儀が良い実装になっている。

しかし、特に IoT っぽい機器では Expect: 100-continue に対応していないものもあるので、抑制したい場合がでてくる。そういうときは以下のようにする。

curl -H "Expect:" -d ... url

空の Expect ヘッダを指定することで上記のような挙動をしなくなり、最初からリクエストをフルで送りつけるようになる。

2018年 07月 18日

10GBASE-T LANケーブル CAT6 CAT6A CAT7 CAT8

結論からいうと CAT6 か CAT6A を使う。33m までなら CAT6 でも良いことになっているので家庭なら CAT6 でも十分ということになる。ただ伝送帯域に余裕がないので、取り回しに問題がないなら CAT6A を使うほうが良さそう。

8P8C (RJ45) コネクタを使う CAT7 CAT8 規格は存在しない

簡単にいうと CAT7 CAT8 のコネクタは CAT6A 以前とは互換性がない。市場には 8P8C で CAT7/CAT8 なケーブルが売っているではないか?と思うかもしれないが、あれはケーブルだけ CAT7/CAT8 なだけで、全体としては規格に適合していない野蛮なものである。

ケーブルにいくら CAT7 CAT8 適合なものを使っても、コネクタに 8P8C (RJ45) を使う場合には CAT6A と同等の扱いとなる。ただの硬い CAT6A ケーブルでしかない。それなら CAT6A で良い。

また、CAT7 以上では STP (シールド付き) なので、機器側でアースがしっかりとれていなければならない。家庭用機器でアースをとるものはほとんどない (接地用端子・接地極がついたコンセントがそもそも普及してない) で、この点でもやはり意味がない。

電位が浮いてる状態の導体があると、そこがアンテナになってしまう。また、適切に両端を接地したとしても、今度はグラウンドループが形成されるため、かえってノイズが増えることもある。接地すべきかどうかがケースバイケースで決まってしまう。ノイズ対策は「グランド繋げば解決」みたいな簡単なものでは全くない。

この点でシールドケーブルは素人には光ケーブルよりも取り扱いが難しい。なので10Gbpsを超えるようになってくると本格的にファイバーに移行する必要が出てくるのではないかと思う

RaspberryPi を家庭内 LAN の DNS キャッシュサーバーに

LAN向けのDNSキャッシュサーバ

経緯としてRTX1200 の DNS 機能が TCP フォールバックに対応してないのでオフにした、というのがある。直接 8.8.8.8 8.8.4.4 を DHCP で広告するようにしてみたが、RTT が 8ms ぐらいあるので、やはり LAN 内にキャッシュサーバがあったほうがいいかなと思いはじめた。

LAN内には常時動いている Raspberry Pi のホストがいるので、ついでにこのホストにDNS機能もやらせることにしてみた。

unbound

unbound がキャッシュサーバ専用でよさそうなのでこれにする。

sudo apt-get install unbound
#/etc/unbound/unbound.conf.d/my.conf   
# See /usr/share/doc/unbound/examples/unbound.conf for a commented
server:
        verbosity: 1
        num-threads: 4
        interface: 0.0.0.0
        interface: ::
        msg-cache-size: 64m
        msg-cache-slabs: 4
        rrset-roundrobin: yes
        rrset-cache-size: 128m
        rrset-cache-slabs: 4
        infra-cache-slabs: 4
        access-control: 192.168.0.0/16 allow
        key-cache-size: 64m
        key-cache-slabs: 4
        neg-cache-size: 64m
        prefetch: yes
        minimal-responses: yes
        incoming-num-tcp: 100
        outgoing-num-tcp: 100
forward-zone:
    name: "."
    forward-addr: 8.8.8.8
    forward-addr: 8.8.4.4
# 自動起動
sudo systemctl enable unbound
# 起動
sudo systemctl start unbound
# ステータス
sudo systemctl status unbound
# ログ
sudo journalctl -r

以下のようなコマンドでクエリ数やキャッシュヒット率が見れる。

unbound-control stats_noreset | grep total

今後

キャッシュヒット率を見つつ、あんまり意味がなさそうなら (数%ぐらいしかヒットしないとか) やめるつもり。

自宅は昼間は誰もいないので、このタイミングでTTLが短いほとんどのキャッシュは無効になってしまう。unbound の prefetch は TTL が残り10%になったときにクエリがくると再問合せする機能なので、クエリがこなければ一切 prefetch はされない。

さらには一番影響がでるブラウザでは、DNS prefetch が実装されているので、体感的にはネットワークの近くにDNSがあっても意味がないことが多い。

自分で DNS キャッシュサーバを運用するコストと釣り合わないかもしれない。

(備考)raspberry pi の ip アドレスを固定

raspi の IP アドレスを固定しておく

#/etc/dhcpcd.conf 
interface eth0
static ip_address=192.168.0.222/24
static routers=192.168.0.1
static domain_name_servers=127.0.0.1
sudo systemctl daemon-reload
sudo systemctl stop dhcpcd
sudo ip addr flush dev eth0 ; sudo systemctl start dhcpcd
sudo systemctl restart avahi-daemon.service 

確認

dig @192.168.0.222 example.com

同一ホストで

sudo unbound-control stats_noreset | grep total
sudo unbound-control dump_cache
2018年 07月 17日

localhost と書くと IPv6 対応したときに死ぬことがある

127.0.0.1 より localhost のほうが書きやすいし良さそう、と思って localhost と書いているとしばしばハマります。

というかなんとなく localhost = 127.0.0.1 と考えがちではないでしょうか? そんなことはないので気をつけましょう。

最近のシステムなら /etc/hosts を確認すると少なくとも2つの localhost エントリを見ることができるでしょう。

127.0.0.1       localhost
::1             localhost 

上は IPv4、下は IPv6 です。localhost はどちらのアドレスにも解決しうるホスト名となっています。

どういうときにハマるか

例えば同一サーバでリバースプロキシを行ってバックエンドサーバと繋ぎこみを行う場合、フロントのリバースプロキシで localhost:5000 と書くと IPv4 IPv6 どちらでアクセスされるか不定です。バックエンドサーバが IPv4 しか bind していないと IPv6 アドレスが選択されたとき接続不可になります。

正しい方法

すべてのサービスで IPv4 IPv6 ともに接続可能にし、ホスト名でアクセスできるようにすること。

非公開な内部向けサーバとかだと割と IPv4 しか使えないようにしがちではないかと思いますが、ちゃんとやったほうが無難です。

次善の策

ホスト名でひける IP アドレスの種類と実際に受けつけるプロトコルの種類をあわせること。

localhost の替わりに 127.0.0.1 か ::1 を使ってプロトコルを明示すること。

localhost に限らない

実際にところは localhost に限らず、ホスト名でアクセスするときには必ず気をつけるポイントです。DNS で IPv4 IPv6 いずれのアドレスも解決できるようにするなら、そのホスト上で待ち受けるサービスは全てデュアルスタックになっていないと、あらぬところでハマったりします。

2018年 07月 16日

RTX1200 prometheus exporter Lua スクリプト

RTX1200 から直接 prometheus 形式でメトリクスを取得できる Lua スクリプトを書いた。

prometheus + grafana のセットアップ (Ubuntu 16.04.4 LTS) | tech - 氾濫原 自宅 raspberrypi への prometheus セットアップとリモートの prometheus との連携 | tech - 氾濫原 とやってきて、自宅内のメトリクスを grafana で表示できるようにしたので、自宅のルーターの RTX1200 のメトリクスをとるようにした。

RTX1200 はユーザー Lua を実行できる環境があり、そこそこ高級なことができる。TCP サーバは簡単に立てられる。HTTP サーバはないが、不特定多数のリクエストを受けるものでもないので、自力で簡単なものを実装した。

Luaスクリプト

https://github.com/cho45/rtx1200_exporter/blob/master/rtx1200_exporter.lua

エンコーディングが sjis なのが注意点。9100 を listen している。IPv6 を listen する方法はないっぽいので、必ず IPv4 でアクセスする必要がある。

出力サンプル

メトリクス名が prometheus のベストプラクティスから外れてるのは、もともと SNMP でとっていた値があるため。

というか SNMP ではとれるけど Lua からは直接とることのできないメトリクスがあるのでちょっと困る。 Lua から自分向けに SNMP 使えばとれると思うが今回やっていない。

# Collecting metrics...
# TYPE yrhCpuUtil5sec gauge
yrhCpuUtil5sec 3
# TYPE yrhCpuUtil1min gauge
yrhCpuUtil1min 2
# TYPE yrhCpuUtil5min gauge
yrhCpuUtil5min 2
# TYPE yrhInboxTemperature gauge
yrhInboxTemperature 46
# TYPE yrhMemoryUtil gauge
yrhMemoryUtil 24
# TYPE ifOutOctets counter
# TYPE ifInOctets counter
ifOutOctets{if="1"} 51255053848
ifInOctets{if="1"} 8057224805
ifOutPkts{if="1"} 43465561
ifInPkts{if="1"} 27396255
ifOutOctets{if="2"} 8411163805
ifInOctets{if="2"} 52401299138
ifOutPkts{if="2"} 25933809
ifInPkts{if="2"} 42718551
ifOutOctets{if="3"} 0
ifInOctets{if="3"} 0
ifOutPkts{if="3"} 0
ifInPkts{if="3"} 0
# TYPE ipSession counter
ipSession{proto="v4"} 59
ipSession{proto="v6"} 35
# TYPE ipChannel counter
ipChannel{proto="v4"} 59
ipChannel{proto="v6"} 35

Lua のデバッグ方法

いまいちベストのデバッグ方法がわからないが、以下のような手順でやった。

以下の upload.sh を保存するたびに実行

#!/bin/sh

PASS=adminpass

cat <<EOF | tftp 192.168.0.1
put rtx1200_exporter.lua /rtx1200_exporter.lua/$PASS
quit
EOF

telnet 繋ぎっぱなしで以下を手動で実行

terminate lua all
lua /rtx1200_exporter.lua
show status lua
2018年 07月 15日

prometheus + grafana のセットアップ (Ubuntu 16.04.4 LTS)

メトリクスの可視化をもうちょっとやりたくて prometheus と grafana をセットアップした。監視は mackerel でやってるのでやらない。

prometheus

Ubuntu の prometheus が古いので最新を自分でおく。/usr/local/prometheus にバイナリを置いていく。node_exporter も同様に /usr/local/node_exporter に置く。

 lm /usr/local/{node_exporter*,prometheus*}
lrwxrwxrwx 1 root  root    28  7月 14 22:32 /usr/local/prometheus -> prometheus-2.3.2.linux-amd64/
lrwxrwxrwx 1 root  root    32  7月 14 23:07 /usr/local/node_exporter -> node_exporter-0.16.0.linux-amd64/

/usr/local/node_exporter-0.16.0.linux-amd64:
合計 17M
-rwxr-xr-x  1 cho45 cho45  17M  5月 16 00:53 node_exporter*
-rw-r--r--  1 cho45 cho45  463  5月 16 00:57 NOTICE
-rw-r--r--  1 cho45 cho45  12K  5月 16 00:57 LICENSE
drwxr-xr-x  2 cho45 cho45 4.0K  5月 16 00:58 ./
drwxr-xr-x 14 root  root  4.0K  7月 14 23:07 ../

/usr/local/prometheus-2.3.2.linux-amd64:
合計 111M
-rwxr-xr-x  1 cho45 cho45  68M  7月 12 23:04 prometheus*
-rwxr-xr-x  1 cho45 cho45  43M  7月 12 23:05 promtool*
-rw-r--r--  1 cho45 cho45  926  7月 13 00:04 prometheus.yml
drwxr-xr-x  2 cho45 cho45 4.0K  7月 13 00:04 consoles/
drwxr-xr-x  2 cho45 cho45 4.0K  7月 13 00:04 console_libraries/
-rw-r--r--  1 cho45 cho45 2.8K  7月 13 00:04 NOTICE
-rw-r--r--  1 cho45 cho45  12K  7月 13 00:04 LICENSE
drwxr-xr-x  4 cho45 cho45 4.0K  7月 13 00:05 ./
drwxr-xr-x 14 root  root  4.0K  7月 14 23:07 ../

systemd の設定を置く

/etc/systemd/system/prometheus.service

[Unit]
Description=Prometheus service
After=network.target

[Service]
ExecStart=/usr/local/prometheus/prometheus --config.file=/etc/prometheus/prometheus.yml --log.level=debug --storage.tsdb.path=/var/lib/prometheus
Restart=always

[Install]
WantedBy=multi-user.target

/etc/systemd/system/node_exporter.service

[Unit]
Description=Prometheus node_exporter
After=network.target

[Service]
ExecStart=/usr/local/node_exporter/node_exporter
Restart=always

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start prometheus
sudo systemctl enable prometheus

sudo systemctl start node_exporter
sudo systemctrl enable node_exporter

grafana

http://docs.grafana.org/installation/debian/ に従ってインストール。Ubuntu のレポジトリのやつはなんかおかしくてアイコンとかが表示されないので使わないほうが無難。

deb https://packagecloud.io/grafana/testing/debian/ stretch main

grafana は初回起動時に admin/admin で入れる。適当にパスワードをかえておくこと。

grafana へのデータソース追加方法はここ
https://prometheus.io/docs/visualization/grafana/

h2o prometheus exporeter

h2o の status に内部アクセスして prometheus 形式にするやつを書いている人がいるので、これを使うとよさそう。
https://gist.github.com/yannick/31d3d8ea6bcc3553dd422aad9d0e4173

リバースプロキシ

prometheus は 9090、grafana は 3000 がデフォルトポート。フロントの http サーバーからリバースプロキシを適当に設定しておく。prometheus は認証もつけておく。grafana は認証機構があるためフロントでは認証をかけない。

自宅 raspberrypi への prometheus セットアップとリモートの prometheus との連携


Public VPS 上の Ubuntu に prometheus と grafana をセットアップしたので、自宅のネットワークのメトリクスもとって、そちらで表示したい。

自宅は IPv4 は NAT 内、IPv6 はグローバルだが IPv4 + NAT 程度のアクセス制限をかけていて、あまりそこをいじりたくないので、なんとかして自宅側から外への接続ですませたい。

prometheus は pull 型なのでこういった環境でのメトリクス収集がやっかい。いくつか方法がある

pushgateway を使う。prometheus 公式で提供されている short live batch 用のゲートウェイ。POST すると pushgateway 側で値を保持してくれて、prometheus からの pull に答える。使うのが簡単なので最初はこれでやっていたが、prometheus 的にはこういうやりかたはよくないっぽい。

検討した結果以下のような形にする

  • 自宅内にも prometheus サーバを立てる
  • Public VPS 側からは federate で連携する

自宅セグメント内のメトリクスをひとまとめにしてから、連携させる。こうすることで、各 exporter のアクセスコントロールなどはやりやすくなる。ただ、結局この方法も /federate を pull する形になるので、そこはなんとかする (後述)

自宅 Raspberry Pi への prometheus セットアップ

自力で prometheus と node_exporter を置くところから。prometheus は arm ビルドを公式提供していないので、自分でビルドする。

prometheus のレポジトリの .promu.yml を書きかえ、linux/arm だけ有効にして、promu crossbuild を行う。 .build/linux-armv5 以下に prometheus のバイナリができるので、これを Raspberry Pi 側に scp する。

go get github.com/prometheus/prometheus/cmd/prometheus
cd $GOPATH/src/github.com/prometheus/prometheus
vim .promu.yml
ls  .build/linux-armv5

node_exporter はgithub の releases ページ に arm5 版があるのでそのまま使えば良い。

自宅 Raspberry Pi の prometheus を Public VPS の prometheus と繋ぐ (federate)

Public VPS 側から自宅側の /federate にアクセスして pull する。2つの方法を検討した。

  • 自宅側にさらに別のフロントhttpサーバを用意して、アクセス制限・認証をかけつつ /federate をリバースプロキシし、Public VPS 側から直接アクセスさせる。
  • Public VPS 側でリクエストを中継するサーバを用意して、自宅側からコネクションを貼りにいく。

1番目の方法は「自宅のIPアドレスを取得する」「アクセス制限・認証をかける」あたりが面倒くさい。DDNS の設定、ネットワーク(ルーター)の設定、アプリケーションレベルの認証設定と影響範囲が広い。

2番目の方法は Prometheus のサイトからリンクがある PushProx で実現できる。自宅側からは外向きにコネクションを貼るだけなので、影響範囲は狭い。ということでこれを使ってみることに。

設定

Public VPS 側では PushProx proxy プロセスを立てる。引数なしで立ち上げると 8080 ポートでプロキシサーバが立ちあがる。

./proxy

認証機能がついていないため、フロントの http サーバでリバースプロキシと適当な認証・アクセス制限をつけておく必要がある。

自宅側には client プロセスを立てる。このとき --proxy-url として、Public VPS 側の proxy のアドレスを指定する。リバースプロキシしているので、そのホストを指定する。

prometheus.yml

federate に関係するところを抜きだすと以下のように。raspberrypi となっているところは client 側で送っているホスト名になる。PushProx はこの文字列でプロキシすべき先を区別している。

# Public VPS 側 (federate を pull する側)
  - job_name: 'federate'
    scrape_interval: 15s
    honor_labels: true
    metrics_path: '/federate'
    params:
      'match[]':
        - '{job="node"}'

    proxy_url: http://127.0.0.1:8080/
    static_configs:
      - targets:
        - 'raspberrypi:9090'

また自宅側の prometheus.yml には external_labels を指定しておく。これは federate のときに固定でラベルを追加する設定。

# 自宅側 (/federate を配信する側)
global:
  external_labels:
    location: home

これで 127.0.0.1 のような指定をして instance 名がかぶっていても自宅のものと区別がつく。

PushProx の罠

現状の PushProx はかなり微妙な感じ。client 側がタイムアウトして接続を切っても、proxy 側ではそれがずっと残っていて、タイムアウト数ぶんのリクエストを受けきるまでまともに動作しなくなってしまう。

現状では pull するほうの prometheus (Public VPS側) を止めるときは proxy プロセスも止める必要がある。一応これに対応するパッチは書いた。

このへんが微妙なので、ちゃんと IPv6 で直接アクセスできる環境をつくってもいいかもなあと思っている。

2018年 07月 14日

mackerel-agent + snmpで RTX1200 のメトリクス取得

検索すると Lua スクリプトを RTX にいれる方法もあるみたいだが、別途プロキシが必要みたいなので LAN 内の raspberrypi から SNMP で取得して投げるほうを選んでみた。raspberrypi には既に mackerel-agent が入れてあるので、追加の設定をするだけ。

mackerel-agent-plugins

mackerel-agent は arm 版もリリースされているが、プラグインはリリースされていない。自分でビルドする必要がある。

mackerel-plugin-snmp だけあればいいので、mackerel-plugin-snmp のディレクトリで

env GOOS=linux GOARCH=arm GOARM=5 go build

するだけでバイナリができる。できたバイナリを raspberrypi の /usr/local/bin とかにコピーしておく。

設定

SNMP で取得するが、 mackerel の custom_identifier でホスト分けて表示する。

まず mkr create でホストをつくる

mkr --customIdentifier router1 router1

ここで指定した customIdentifier をプラグインの設定に書くとこちらのホストに投稿したことになる。

[plugin.metrics.snmp-router]
command = "mackerel-plugin-snmp  -community='public' -host='192.168.0.1' .1.3.6.1.2.1.2.2.1.16.1:ifOutOctets.2 .1.3.6.1.2.1.2.2.1.10.1:ifInOctets.2 .1.3.6.1.4.1.1182.2.1.15.0:temp.yrhInboxTemperature .1.3.6.1.4.1.1182.2.1.4.0:memory.yrhMemoryUtil .1.3.6.1.4.1.1182.2.1.5.0:cpu.yrhCpuUtil5sec .1.3.6.1.4.1.1182.2.1.6.0:cpu.yrhCpuUtil1min .1.3.6.1.4.1.1182.2.1.7.0:cpu.yrhCpuUtil5min"
custom_identifier = "router1"

SNMP の OID を調べるのがめちゃくちゃ面倒くさいがだいたい上記ぐらいでいいだろう。

この方法の欠点

  • cpu やメモリのメトリクスは mackerel で特別扱いされているが、この方法だとすべて snmp.* のメトリクスになってしまう