kube-dnsがCrashLoopBackOffで起動しなくなった

  • このエントリーをはてなブックマークに追加

突然いろいろなサービスの名前解決が出来なくなってPODがエラーを吐き始めました。GKEのConsoleで見ていたら、しばらく経つとエラーは無くなりましたがkube-dnsだけCrashLoopBackOffでステータスがDoes not have minimum availabilityと表示されていました。

調べたこと

kube-dnsはnamespaceがkube-systemDeployment,Replica=2として動くように定義されています。

# kubectl -n kube-system get pod | grep kube-system

4/4 Running 0 45m 10.4.0.132 gke-pn-cluster-2-pn-pool-1-efba661f-pz54 <none>
kube-dns-76dbb796c5-9gljt 3/4 CrashLoopBackOff 13 45m 10.4.0.134 gke-pn-cluster-2-pn-pool-1-efba661f-pz54 <none>
kube-dns-autoscaler-67c97c87fb-rj4fq 1/1 Running 0 45m 10.4.0.135 gke-pn-cluster-2-pn-pool-1-efba661f-pz54 <none>

このpodの定義を見ると、4つのcontainerが含まれており、kubedns, dnsmasq, sidecar, prometheus-to-sdがありました。

# kubectl -n kube-system describe pod kube-dns-76dbb796c5-9gljt

Name: kube-dns-76dbb796c5-9gljt
Namespace: kube-system
Priority: 2000000000
PriorityClassName: system-cluster-critical
Node: gke-pn-cluster-2-pn-pool-1-efba661f-pz54/10.146.0.2
Start Time: Sun, 05 May 2019 14:46:19 +0900
Labels: k8s-app=kube-dns
pod-template-hash=3286635271
Annotations: scheduler.alpha.kubernetes.io/critical-pod=
seccomp.security.alpha.kubernetes.io/pod=docker/default
Status: Running
IP: 10.4.0.134
Controlled By: ReplicaSet/kube-dns-76dbb796c5
Containers:
kubedns:
Container ID: docker://2e341ab157aee24b63d95eefb4da434c79306229055d135abf6b730708589d68
Image: k8s.gcr.io/k8s-dns-kube-dns-amd64:1.14.13
Image ID: docker-pullable://k8s.gcr.io/k8s-dns-kube-dns-amd64@sha256:618a82fa66cf0c75e4753369a6999032372be7308866fc9afb381789b1e5ad52
Ports: 10053/UDP, 10053/TCP, 10055/TCP
Host Ports: 0/UDP, 0/TCP, 0/TCP
Args:
--domain=cluster.local.
--dns-port=10053
--config-dir=/kube-dns-config
--v=2
State: Running
Started: Sun, 05 May 2019 14:46:42 +0900
Ready: True
Restart Count: 0
Limits:
memory: 170Mi
Requests:
cpu: 100m
memory: 70Mi
Liveness: http-get http://:10054/healthcheck/kubedns delay=60s timeout=5s period=10s #success=1 #failure=5
Readiness: http-get http://:8081/readiness delay=3s timeout=5s period=10s #success=1 #failure=3
Environment:
PROMETHEUS_PORT: 10055
Mounts:
/kube-dns-config from kube-dns-config (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-dns-token-jdksn (ro)
dnsmasq:
Container ID: docker://5cc16055a401b91bd15ba6507c9f2b7b4e4b20647496746d978cb211e1a0555d
Image: k8s.gcr.io/k8s-dns-dnsmasq-nanny-amd64:1.14.13
Image ID: docker-pullable://k8s.gcr.io/k8s-dns-dnsmasq-nanny-amd64@sha256:45df3e8e0c551bd0c79cdba48ae6677f817971dcbd1eeed7fd1f9a35118410e4
Ports: 53/UDP, 53/TCP
Host Ports: 0/UDP, 0/TCP
Args:
-v=2
-logtostderr
-configDir=/etc/k8s/dns/dnsmasq-nanny
-restartDnsmasq=true
--
-k
--cache-size=1000
--no-negcache
--log-facility=-
--server=/cluster.local/127.0.0.1#10053
--server=/in-addr.arpa/127.0.0.1#10053
--server=/ip6.arpa/127.0.0.1#10053
State: Waiting
Reason: CrashLoopBackOff
Last State: Terminated
Reason: Error
Exit Code: 255
Started: Sun, 05 May 2019 15:28:15 +0900
Finished: Sun, 05 May 2019 15:28:16 +0900
Ready: False
Restart Count: 13
Requests:
cpu: 150m
memory: 20Mi
Liveness: http-get http://:10054/healthcheck/dnsmasq delay=60s timeout=5s period=10s #success=1 #failure=5
Environment: <none>
Mounts:
/etc/k8s/dns/dnsmasq-nanny from kube-dns-config (rw)
/var/run/secrets/kubernetes.io/serviceaccount from kube-dns-token-jdksn (ro)
sidecar:
Container ID: docker://f8c87600c704dd709d27c518d0b3ce20d944608be16f1436442970454715977a
Image: k8s.gcr.io/k8s-dns-sidecar-amd64:1.14.13
Image ID: docker-pullable://k8s.gcr.io/k8s-dns-sidecar-amd64@sha256:cedc8fe2098dffc26d17f64061296b7aa54258a31513b6c52df271a98bb522b3
Port: 10054/TCP
Host Port: 0/TCP
Args:
--v=2
--logtostderr
--probe=kubedns,127.0.0.1:10053,kubernetes.default.svc.cluster.local,5,SRV
--probe=dnsmasq,127.0.0.1:53,kubernetes.default.svc.cluster.local,5,SRV
State: Running
Started: Sun, 05 May 2019 14:46:50 +0900
Ready: True
Restart Count: 0
Requests:
cpu: 10m
memory: 20Mi
Liveness: http-get http://:10054/metrics delay=60s timeout=5s period=10s #success=1 #failure=5
Environment: <none>
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-dns-token-jdksn (ro)
prometheus-to-sd:
Container ID: docker://82d84415443253693be30368ed14555ff24ba21844ae607abf990c369008f70e
Image: k8s.gcr.io/prometheus-to-sd:v0.4.2
Image ID: docker-pullable://gcr.io/google-containers/prometheus-to-sd@sha256:aca8ef83a7fae83f1f8583e978dd4d1ff655b9f2ca0a76bda5edce6d8965bdf2
Port: <none>
Host Port: <none>
Command:
/monitor
--source=kubedns:http://localhost:10054?whitelisted=probe_kubedns_latency_ms,probe_kubedns_errors,dnsmasq_misses,dnsmasq_hits
--stackdriver-prefix=container.googleapis.com/internal/addons
--api-override=https://monitoring.googleapis.com/
--pod-id=$(POD_NAME)
--namespace-id=$(POD_NAMESPACE)
--v=2
State: Running
Started: Sun, 05 May 2019 14:46:51 +0900
Ready: True
Restart Count: 0
Environment:
POD_NAME: kube-dns-76dbb796c5-9gljt (v1:metadata.name)
POD_NAMESPACE: kube-system (v1:metadata.namespace)
Mounts:
/var/run/secrets/kubernetes.io/serviceaccount from kube-dns-token-jdksn (ro)
Conditions:
Type Status
Initialized True
Ready False
PodScheduled True
Volumes:
kube-dns-config:
Type: ConfigMap (a volume populated by a ConfigMap)
Name: kube-dns
Optional: true
kube-dns-token-jdksn:
Type: Secret (a volume populated by a Secret)
SecretName: kube-dns-token-jdksn
Optional: false
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: CriticalAddonsOnly
node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 46m default-scheduler Successfully assigned kube-system/kube-dns-76dbb796c5-9gljt to gke-pn-cluster-2-pn-pool-1-efba661f-pz54
Normal SuccessfulMountVolume 46m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 MountVolume.SetUp succeeded for volume "kube-dns-config"
Normal SuccessfulMountVolume 46m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 MountVolume.SetUp succeeded for volume "kube-dns-token-jdksn"
Normal Pulling 46m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 pulling image "k8s.gcr.io/k8s-dns-kube-dns-amd64:1.14.13"
Normal Pulled 46m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Successfully pulled image "k8s.gcr.io/k8s-dns-kube-dns-amd64:1.14.13"
Normal Created 46m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Created container
Normal Started 46m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Started container
Normal Pulling 46m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 pulling image "k8s.gcr.io/k8s-dns-dnsmasq-nanny-amd64:1.14.13"
Normal Pulled 46m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Successfully pulled image "k8s.gcr.io/k8s-dns-dnsmasq-nanny-amd64:1.14.13"
Normal Pulling 46m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 pulling image "k8s.gcr.io/k8s-dns-sidecar-amd64:1.14.13"
Normal Created 45m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Created container
Normal Pulled 45m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Successfully pulled image "k8s.gcr.io/k8s-dns-sidecar-amd64:1.14.13"
Normal Pulling 45m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 pulling image "k8s.gcr.io/prometheus-to-sd:v0.4.2"
Normal Started 45m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Started container
Normal Created 45m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Created container
Normal Started 45m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Started container
Normal Pulled 45m kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Successfully pulled image "k8s.gcr.io/prometheus-to-sd:v0.4.2"
Normal Created 45m (x2 over 46m) kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Created container
Normal Started 45m (x2 over 46m) kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Started container
Normal Pulled 45m (x2 over 45m) kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Container image "k8s.gcr.io/k8s-dns-dnsmasq-nanny-amd64:1.14.13" already present on machine
Warning BackOff 1m (x211 over 45m) kubelet, gke-pn-cluster-2-pn-pool-1-efba661f-pz54 Back-off restarting failed container

Logを見ますが、普通に起動できている(ように見えてました)。

# kubectl -n kube-system logs kube-dns-76dbb796c5-9gljt -c kubedns

I0505 05:46:42.318712 1 dns.go:48] version: 1.14.13
I0505 05:46:42.322157 1 server.go:69] Using configuration read from directory: /kube-dns-config with period 10s
I0505 05:46:42.322453 1 server.go:121] FLAG: --alsologtostderr="false"
I0505 05:46:42.322508 1 server.go:121] FLAG: --config-dir="/kube-dns-config"
I0505 05:46:42.322517 1 server.go:121] FLAG: --config-map=""
I0505 05:46:42.322636 1 server.go:121] FLAG: --config-map-namespace="kube-system"
I0505 05:46:42.322718 1 server.go:121] FLAG: --config-period="10s"
I0505 05:46:42.322739 1 server.go:121] FLAG: --dns-bind-address="0.0.0.0"
I0505 05:46:42.322746 1 server.go:121] FLAG: --dns-port="10053"
I0505 05:46:42.322756 1 server.go:121] FLAG: --domain="cluster.local."
I0505 05:46:42.322856 1 server.go:121] FLAG: --federations=""
I0505 05:46:42.322878 1 server.go:121] FLAG: --healthz-port="8081"
I0505 05:46:42.322886 1 server.go:121] FLAG: --initial-sync-timeout="1m0s"
I0505 05:46:42.322895 1 server.go:121] FLAG: --kube-master-url=""
I0505 05:46:42.322983 1 server.go:121] FLAG: --kubecfg-file=""
I0505 05:46:42.323003 1 server.go:121] FLAG: --log-backtrace-at=":0"
I0505 05:46:42.323016 1 server.go:121] FLAG: --log-dir=""
I0505 05:46:42.323099 1 server.go:121] FLAG: --log-flush-frequency="5s"
I0505 05:46:42.323117 1 server.go:121] FLAG: --logtostderr="true"
I0505 05:46:42.323123 1 server.go:121] FLAG: --nameservers=""
I0505 05:46:42.323129 1 server.go:121] FLAG: --stderrthreshold="2"
I0505 05:46:42.323137 1 server.go:121] FLAG: --v="2"
I0505 05:46:42.323144 1 server.go:121] FLAG: --version="false"
I0505 05:46:42.323253 1 server.go:121] FLAG: --vmodule=""
I0505 05:46:42.323511 1 server.go:169] Starting SkyDNS server (0.0.0.0:10053)
I0505 05:46:42.331087 1 server.go:179] Skydns metrics enabled (/metrics:10055)
I0505 05:46:42.331168 1 dns.go:188] Starting endpointsController
I0505 05:46:42.331375 1 dns.go:191] Starting serviceController
I0505 05:46:42.331754 1 dns.go:184] Configuration updated: {TypeMeta:{Kind: APIVersion:} Federations:map[] StubDomains:map[] UpstreamNameservers:[]}
I0505 05:46:42.335926 1 logs.go:41] skydns: ready for queries on cluster.local. for tcp://0.0.0.0:10053 [rcache 0]
I0505 05:46:42.337525 1 logs.go:41] skydns: ready for queries on cluster.local. for udp://0.0.0.0:10053 [rcache 0]
I0505 05:46:42.835211 1 dns.go:222] Initialized services and endpoints from apiserver
I0505 05:46:42.835268 1 server.go:137] Setting up Healthz Handler (/readiness)
I0505 05:46:42.835298 1 server.go:142] Setting up cache handler (/cache)
I0505 05:46:42.835317 1 server.go:128] Status HTTP port 8081

Containerに入って、色々と確認してみます。
Livenessを手動で確認するという事をしてみます。curlがインストールされていないImageだったのでwgetを使っています。すると、dnsmasqのエラーが出てきました。

# kubectl exec -n kube-system -c kubedns -it kube-dns-76dbb796c5-9gljt sh

wget -O - http://localhost:10054/healthcheck/dnsmasq
Connecting to localhost:10054 (127.0.0.1:10054)
wget: server returned error: HTTP/1.1 503 Service Unavailable

dnsmasqにはLoginできないのでこっちがこけているっぽい。

kubectl exec -n kube-system -c dnsmasq -it kube-dns-76dbb796c5-9gljt sh
# kubectl -n kube-system logs kube-dns-76dbb796c5-9gljt -c dnsmasq

I0505 06:38:30.432185 1 main.go:74] opts: {{/usr/sbin/dnsmasq [-k --cache-size=1000 --no-negcache --log-facility=- --server=/cluster.local/127.0.0.1#10053 --server=/in-addr.arpa/127.0.0.1#10053 --server=/ip6.arpa/127.0.0.1#10053] true} /etc/k8s/dns/dnsmasq-nanny 10000000000}
I0505 06:38:30.432498 1 nanny.go:94] Starting dnsmasq [-k --cache-size=1000 --no-negcache --log-facility=- --server=/cluster.local/127.0.0.1#10053 --server=/in-addr.arpa/127.0.0.1#10053 --server=/ip6.arpa/127.0.0.1#10053]
I0505 06:38:30.992206 1 nanny.go:119]
W0505 06:38:30.992278 1 nanny.go:120] Got EOF from stdout
I0505 06:38:30.992242 1 nanny.go:116]
I0505 06:38:30.992337 1 nanny.go:116] dnsmasq: failed to create inotify: No file descriptors available
I0505 06:38:30.992385 1 nanny.go:123]
E0505 06:38:30.992407 1 nanny.go:124] Error reading from stderr: read |0: file already closed
F0505 06:38:30.992583 1 nanny.go:190] dnsmasq exited: exit status 5

dnsmasq: failed to create inotify: No file descriptors available とても怪しい。

# cat /proc/sys/fs/inotify/max_user_instances
128
# cat /proc/sys/fs/inotify/max_user_watches
8192

nodeが2つ(GCEのVMが2つ)あって、VMのログなどを見ると1つのVMで以下のログを発見。

sudo journalctl -f
Failed to get journal fd: Too many open files

YAMLの定義を抜き出してきて、直接VM上で起動してみると起動できるVMとできないVMがあり、できないVMではjournallogに上記のログが出ていました。

docker run -it --rm k8s.gcr.io/k8s-dns-dnsmasq-nanny-amd64:1.14.13 \
-v=2 \
-logtostderr \
-configDir=/etc/k8s/dns/dnsmasq-nanny \
-restartDnsmasq=true \
-- \
-k \
--cache-size=1000 \
--no-negcache \
--log-facility=- \
--server=/cluster.local/127.0.0.1#10053 \
--server=/in-addr.arpa/127.0.0.1#10053 \
--server=/ip6.arpa/127.0.0.1#10053 \

対策

NodePoolを作ってそっちに全部移動させるか、NodePoolのNode数を増やしてdrainさせるか等を考えました。今回はVMで消費しているFDを見て、怪しいプロセスを消したり再起動したりする事にしました。
もう使っていないPODを消したり、ResourceLeakしてそうなPodを再起動したり。

これを行ったら、なんとか復旧しました。

その他メモ

GKEでNodePoolを作るときにubuntuではなくCOSにしていましたが、lsofが使えませんでした。toolkitを使ってapt-get update && apt-get install lsofで使えるようになりました。

ref: https://cloud.google.com/container-optimized-os/docs/how-to/toolbox

参考

NTPDがSyncできなかった理由

  • このエントリーをはてなブックマークに追加

とあるサーバのNTPDの同期ができていなかったので調べたメモ。

ntpq -p
remote refid st t when poll reach delay offset jitter
==============================================================================
169.254.169.123 .INIT. 16 u - 1024 0 0.000 0.000 0.000

*が付いていないしreachも0なので同期できていない。

ntpdcで該当NTPサーバとの通信の状況を見てみる。サーバはAWSのTimeServerを利用しています。

# ntpdc -c 'showpeer 169.254.169.123'
remote 169.254.169.123, local 10.0.103.31
hmode client, pmode unspec, stratum 16, precision -22
leap 11, refid [73.78.73.84], rootdistance 0.00000, rootdispersion 0.00000
ppoll 10, hpoll 10, keyid 0, version 4, association 35382
reach 000, unreach 213, flash 0x1600, boffset 0.00000, ttl/mode 0
timer 0s, flags config, bclient, prefer
reference time: 00000000.00000000 Mon, Jan 1 1900 0:00:00.000
originate timestamp: e0754e65.00000000 Thu, May 2 2019 11:09:25.000
receive timestamp: 00000000.00000000 Mon, Jan 1 1900 0:00:00.000
transmit timestamp: 00000000.00000000 Mon, Jan 1 1900 0:00:00.000
filter delay: 0.00000 0.00000 0.00000 0.00000
0.00000 0.00000 0.00000 0.00000
filter offset: 0.000000 0.000000 0.000000 0.000000
0.000000 0.000000 0.000000 0.000000
filter order: 0 1 2 3
4 5 6 7
offset 0.000000, delay 0.00000, error bound 3.99217, filter error 0.00000

unreach 213, flash 0x1600 unreachのカウンタが増えていますし、flashの値にも何か記録されています。

ntp.hの定義を見ると、次のようになっています。0x1600ということはTEST10|TEST11|TEST13ということ?

ntp.h
#define TEST10          0x0200  /* peer bad synch or stratum */
#define TEST11 0x0400 /* peer distance exceeded */
#define TEST12 0x0800 /* peer synchronization loop */
#define TEST13 0x1000 /* peer unreacable */

NICが2枚刺さっているサーバらしい

eth0が10.0.103.31, eth1が10.0.100.111でeth1がDEFROUTE=1
ntpdがlistenしているのはeth0のみ。

netstat -tulpn | grep :123
udp 0 0 10.0.103.31:123 0.0.0.0:* 2995/ntpd
udp 0 0 127.0.0.1:123 0.0.0.0:* 2995/ntpd
udp 0 0 0.0.0.0:123 0.0.0.0:* 2995/ntpd

設定ファイルを見ると、listenはeth0のみ。なのでここにinterface listen eth1を追加してntpdを再起動しました。

/etc/ntp.conf
interface listen eth0
interface ignore ipv6

結果、同期されるようになりました。

疑問点

NICは同じネットワークアドレスを持っている。そして、ip routerouteを見ると、eth1のFlags=Gが2つあるのがすごい怪しい。

route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.0.100.1 0.0.0.0 UG 0 0 0 eth1
0.0.0.0 10.0.100.1 0.0.0.0 UG 10001 0 0 eth1
10.0.100.0 0.0.0.0 255.255.252.0 U 0 0 0 eth0
10.0.100.0 0.0.0.0 255.255.252.0 U 0 0 0 eth1
169.254.169.254 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0

どういう設定をしたサーバなのか調査する時間がないので、ここまで。

参考

Javaで複数のRange検索を比較的高速に行うコード

  • このエントリーをはてなブックマークに追加

複数のCIDR形式で記されたネットワークに該当するか、複数の定義された時間帯に現時刻が該当するか、などなどRange検索をしたい事がそれなりにあります。

単純なRange検索であれば、from <= v && v <= toのような単純なコードで判定できますが、Rangeの定義が複数ある場合、愚直に実装するとO(N)になります。それはとても嫌なのでO(log N)で判定できるようにします。全部自前で書いてやろうと思ったのですが、ライブラリに任せることにしました。

Guavaのコードを見ると、追加するたびにRangeをMergeするという方法を取っていました。あるクエリにHitする定義はどれだみたいな事は出来ないのですが、今回は含まれるか(contains)を判定できれば良いので妥協します。

実際のコード その1

ライブラリはデータ構造としてguavaRangeSet、CIDRの扱いとしてcommons-netSubnetUtilsを使います。コードの記述を減らすためにLombok、JSONの処理にはgsonを使いました。

pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>at.orz</groupId>
<artifactId>sample-range-checker</artifactId>
<version>1.0.0-SNAPSHOT</version>

<name>sample-range-checker</name>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.1-jre</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.6</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
App.java
public class App {

@Data
public static class AWSIpRanges implements Serializable {
private String syncToken;
private String createDate;
private List<IPv4Prefix> prefixes;
private List<IPv6Prefix> ipv6Prefixes;
@Data
public static class IPv4Prefix implements Serializable {
private String ipPrefix;
private String region;
private String service;
}
@Data
public static class IPv6Prefix implements Serializable {
private String ipv6Prefix;
private String region;
private String service;
}
}

public static class ImmutableIntRangeChecker {
@Delegate
private final ImmutableRangeSet<Integer> rangeSet;
public ImmutableIntRangeChecker(List<Range<Integer>> ranges) {
this.rangeSet = ImmutableRangeSet.unionOf(ranges);
}
public boolean isIPAddrContains(String ipAddr) {
int query = InetAddresses.coerceToInteger(InetAddresses.forString(ipAddr));
return rangeSet.contains(query);
}
}

public static void main(String[] args) throws IOException, InterruptedException {

URI awsIpRangesURI = URI.create("https://ip-ranges.amazonaws.com/ip-ranges.json");

// get aws-ip json file
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder(awsIpRangesURI).build();
String json = client.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)).body();

// parse json
Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create();
AWSIpRanges ipRanges = gson.fromJson(json, AWSIpRanges.class);

List<Range<Integer>> ranges = ipRanges.getPrefixes().stream()
.map(AWSIpRanges.IPv4Prefix::getIpPrefix) // List<IP-String>にする
.map(ip -> ip.contains("/") ? ip : ip + "/32") // SubnetUtilsでエラーにならないように
.map(ip -> {
// CIDR形式をパースし、ネットワークアドレスの範囲を求め、そのIPアドレスを32ビット変数に変換
SubnetUtils subnet = new SubnetUtils(ip);
subnet.setInclusiveHostCount(true);
SubnetUtils.SubnetInfo info = subnet.getInfo();
return Range.closed(info.asInteger(info.getLowAddress()), info.asInteger(info.getHighAddress()));
})
.collect(Collectors.toList());

ImmutableIntRangeChecker checker = new ImmutableIntRangeChecker(ranges);
System.out.println(checker.isIPAddrContains("35.172.155.127")); // -> true
System.out.println(checker.isIPAddrContains("8.8.8.8")); // -> false

}
}

実際のコード その2

Intで扱わない場合です。RangeクラスはComparatorの実装を要求しますがInetAddrクラスは実装していないので、ラッパークラスを作成します。

またCheckerクラスを汎用的に実装し、FunctionクラスによってKeyの変換も定義します。今回はStringからComparableInetAddrに変換する関数を定義します。

App2.java

public static class ComparableInetAddr implements Serializable, Comparable<ComparableInetAddr> {
@Delegate
private final InetAddress inetAddress;
private final int intIP;
private ComparableInetAddr(InetAddress inetAddress) {
this.inetAddress = inetAddress;
this.intIP = InetAddresses.coerceToInteger(inetAddress);
}
public ComparableInetAddr(String ipAddr) {
this(InetAddresses.forString(ipAddr));
}
@Override
public int compareTo(ComparableInetAddr o) {
return Integer.compare(this.intIP, o.intIP);
}
}

public static class ImmutableRangeChecker<T, C extends Comparable> {
@Delegate
private final ImmutableRangeSet<C> rangeSet;
private final Function<T, C> queryTransformer;
public ImmutableRangeChecker(List<Range<C>> ranges, Function<T, C> queryTransformer) {
this.rangeSet = ImmutableRangeSet.unionOf(ranges);
this.queryTransformer = queryTransformer;
}
public boolean contains(T value) {
C query = queryTransformer.apply(value);
return rangeSet.contains(query);
}
}

差分のコードだけ。ComparableIntAddrをRangeクラスに入れるようにした

List<Range<ComparableInetAddr>> ranges = ipRanges.getPrefixes().stream()
.map(AWSIpRanges.IPv4Prefix::getIpPrefix)
.map(ip -> ip.contains("/") ? ip : ip + "/32")
.map(ip -> {
SubnetUtils subnet = new SubnetUtils(ip);
subnet.setInclusiveHostCount(true);
SubnetUtils.SubnetInfo info = subnet.getInfo();
// ここを変更した
return Range.closed(new ComparableInetAddr(info.getLowAddress()), new ComparableInetAddr(info.getHighAddress()));
})
.collect(Collectors.toList());

ImmutableRangeChecker<String, ComparableInetAddr> checker =
new ImmutableRangeChecker<>(
ranges,
ipString -> new ComparableInetAddr(InetAddresses.forString(ipString)));

System.out.println(checker.contains("35.172.155.127")); // false
System.out.println(checker.contains("8.8.8.8")); // true

時間の範囲を判定する場合

例えば、1日のうちの10時から12時と14時から16時。とか。1週間のうちの月曜日の午前中と火曜日の午前中とか。前者の場合は0から86400の整数として扱えば良いし、後者は月曜始まりの0時を0とした整数として扱うとかすれば良いと思います。

もしくは上記のコードでComparableが実装されているDate系のクラスを使えば良いと思います。

その他

今回はImmutableRangeSetを使いましたが、他にもTreeRangeSetの実装があります。こっちはInstanceを生成後にRangeを追加することができます。個人的にはIntegerではなくPrimitiveのintで扱いたいです。

RangeがMergeされるのを確認したコード

@Test
public void rangeSample1() {
ImmutableList<Range<Integer>> x = ImmutableList.of(Range.open(1, 100), Range.open(200, 300)).asList();
TreeRangeSet<Integer> im = TreeRangeSet.create();
im.addAll(x);
im.add(Range.open(50, 250));
System.out.println(im); // [(1..300)]
}
@Test
public void rangeSample2() {
ImmutableList<Range<Integer>> x = ImmutableList.of(Range.closed(1, 100), Range.closed(200, 300)).asList();
TreeRangeSet<Integer> im = TreeRangeSet.create();
im.addAll(x);
im.add(Range.open(50, 150)); // [[1..150), [200..300]]
System.out.println(im);
}

参考

CIDRに限ってしまえばもっと高速にできるみたいです。

AWSで特定のサーバの時刻が急激にずれていく

  • このエントリーをはてなブックマークに追加

NTPDの設定をしているにも関わらず、急激に時間がずれていった時の話。

ntpqで確認したらoffsetが大きくずれていたが、**dateでみると時間はずれていなかった。**正確には私が見る前に時間が大きくずれていたので1回ntpdateをして時間を合わせたのだが、offsetはそのままでまた時間がすごい勢いでずれていくという現象が発生しました。

ntpqで状態を確認。

ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*169.254.169.123 10.66.1.54 3 u 47 64 377 0.098 531.046 12.220

driftファイル(/var/lib/ntp/drift)を見ると、ほかのサーバは-0.403,0.013,-0.090といった小さい数字(正しくは0に近い値)なのに対して、このサーバは-499.822と極端に大きな値(0から大きくかけ離れた値)が記載されていた。

解決方法

  1. ntpd stop
  2. driftファイルを削除(実際にはmove)
  3. ntpdate -b (大きくずれていたのでslewモードではなくstepモードで一気に設定)
  4. ntpd start

これはVMのImage(AMI)を作るときには削除しておくのが良いファイルの1つです。

この操作によって大きく時間がずれていくことがなくなりました。

ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*169.254.169.123 10.66.1.54 3 u 23 256 377 0.117 0.890 1.268

参考

Macで自分のHostNameの解決が遅い

  • このエントリーをはてなブックマークに追加

Macで開発していたらHostNameの参照が遅いという事がありました。
例えばJavaのLoggerのLogbackではHostnameを自動で取得します。

HOSTNAME変数とCONTEXT_NAME変数はよく使われるので、コンテキストスコープを持つ変数として自動的に定義されます。

自分のHostNameを正しく解決できないと10秒ほどBlockされるので、テストケースが100個あるとその都度待たされてしまうわけです。

実際に詰まっているのを確認したら、IPv6のアドレスを求めるところで詰まってました。

"pool-1-thread-1-ScalaTest-running-FormDefinitionsTest" #12 prio=5 os_prio=31 tid=0x00007fc13882d800 nid=0xa903 runnable
[0x00007000026bb000]
java.lang.Thread.State: RUNNABLE
at java.net.Inet6AddressImpl.lookupAllHostAddr(Native Method)
at java.net.InetAddress$2.lookupAllHostAddr(InetAddress.java:928)
at java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1323)
at java.net.InetAddress.getLocalHost(InetAddress.java:1500)
- locked <0x0000000740866718> (a java.lang.Object)
at ch.qos.logback.core.util.ContextUtil.getLocalHostName(ContextUtil.java:37)
at ch.qos.logback.core.util.ContextUtil.addHostNameAsProperty(ContextUtil.java:67)
at ch.qos.logback.classic.joran.action.ConfigurationAction.begin(ConfigurationAction.java:59)
at ch.qos.logback.core.joran.spi.Interpreter.callBeginAction(Interpreter.java:269)
at ch.qos.logback.core.joran.spi.Interpreter.startElement(Interpreter.java:145)
at ch.qos.logback.core.joran.spi.Interpreter.startElement(Interpreter.java:128)
at ch.qos.logback.core.joran.spi.EventPlayer.play(EventPlayer.java:50)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:148)
- locked <0x0000000740f7f370> (a ch.qos.logback.core.spi.LogbackLock)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:135)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:100)
at ch.qos.logback.core.joran.GenericConfigurator.doConfigure(GenericConfigurator.java:50)
at play.api.Logger$$anonfun$configure$3.apply(Logger.scala:256)
at play.api.Logger$$anonfun$configure$3.apply(Logger.scala:256)
at scala.Option.foreach(Option.scala:257)
....

jshellで確認できます。

jshell> InetAddress localhost = InetAddress.getLocalHost();
localhost ==> SN0298.local/192.168.11.4

原因と解決策

次のコマンドを実行した時に、scutil --get HostNameの結果が空なのが原因です。

scutil --get HostName       #
scutil --get LocalHostName # SN0298
scutil --get ComputerName # SN0298

次のコマンドで設定する事で対応できます。

sudo scutil --set HostName $(scutil --get LocalHostName)

jshell> InetAddress localhost = InetAddress.getLocalHost();
localhost ==> SN0298/192.168.11.4

.localが無くなりますが、まぁ気にしない方向で。

検索すると/etc/hostsSN0298 127.0.0.1のようにする解決策を見かけます。この方法でも良いのですが、それだと上記のコードでDHCPのアドレスが取得できなくなります。

Maven RepositoryをGCSに構築する

  • このエントリーをはてなブックマークに追加

Maven Repositoryを作るとき、一般的にはNexusやJFrogを使いますが、APIサーバが必要なわけでもないのでGCPのCloud Storageに構築します。

これを使います。こちらのサイトを参考にします。

まずGCSにBucketを作る

適当な名前で作ります。Private Repositoryとして作成するのでPublicアクセスはOFFのままにしておきます。

pom.xmlにplugin(extension)の設定を追記

<build>
<extensions>
<extension>
<groupId>com.gkatzioura.maven.cloud</groupId>
<artifactId>google-storage-wagon</artifactId>
<version>1.7</version>
</extension>
</extensions>
</build>

バージョンは1.7を指定します。今時点では2.0がリリースされていますが、1.8からバグっていて、GCSにはアップロードできません。おそらくこのコミットで動かなくなったのだと思います。

成果物のアップロード先を指定します。

<distributionManagement>
<snapshotRepository>
<id>hoge-fuga-snapshot</id>
<url>gs://hogefuga-bucket/snapshot</url>
</snapshotRepository>
<repository>
<id>hoge-fuga-release</id>
<url>gs://hogefuga-bucket/release</url>
</repository>
</distributionManagement>

GCSの認証

通常はsetting.xmlに記述しますが、このPluginはその設定情報を使いません。

gcloud auth login –brief で認証するか、環境変数のGOOGLE_APPLICATION_CREDENTIALSを設定する必要があります。

WSLでSSHの鍵を転送するとBSoDで死ぬ件について

  • このエントリーをはてなブックマークに追加

関連Issue: https://github.com/Microsoft/WSL/issues/3916

KB4489868 を削除して再起動したら直りました。いや、回避しただけで直ってはいないわけですが。

追記 2019/04/08

Windows10のパッチバージョン1809をインストールするとBSoDが発生しなくなりました。

HttpClientはデフォルトでAccept Headerを付けない

  • このエントリーをはてなブックマークに追加

ApacheのHttpClinetはデフォルトでAcceptヘッダを付与しません。
例えば、簡単なサンプルを次に示します。

SSLContextConnectionManager,RequestConfigを設定していませんが、そこは本質的ではないので省略しています。

HttpClientBuilder clientBuilder = HttpClientBuilder.create();
try (CloseableHttpClient client = clientBuilder.build()) {
HttpGet getMethod = new HttpGet();
getMethod.setURI(URI.create("https://httpstat.us/200"));
try (CloseableHttpResponse resp = client.execute(getMethod)) {
String body = EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
logger.info("header:{}", resp.getStatusLine());
logger.info("body:{}", body);
}
}

HttpClientのDebugLogをみたいので、LogレベルをDEBUGに設定していまいます。あとコピペしやすいようにPatternをちょっとだけ修正。

Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(Level.DEBUG);

PatternLayoutEncoder ple = new PatternLayoutEncoder();
ple.setContext(rootLogger.getLoggerContext());
ple.setPattern("%-50class{1} %msg%n");
ple.start();

ConsoleAppender consoleAppender = (ConsoleAppender) rootLogger.getAppender("console");
consoleAppender.setEncoder(ple);

実行結果です。必要なところだけを抜き出しています。

RunningLog
o.a.h.i.e.MainClientExec                           Executing request GET /200 HTTP/1.1
o.a.h.i.e.MainClientExec Target auth state: UNCHALLENGED
o.a.h.i.e.MainClientExec Proxy auth state: UNCHALLENGED

o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 >> GET /200 HTTP/1.1
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 >> Host: httpstat.us
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 >> Connection: Keep-Alive
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.4 (Java/11.0.2)
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 >> Accept-Encoding: gzip,deflate
o.a.h.i.c.Wire http-outgoing-0 >> "GET /200 HTTP/1.1[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 >> "Host: httpstat.us[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.4 (Java/11.0.2)[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 >> "[\r][\n]"

o.a.h.i.c.Wire http-outgoing-0 << "HTTP/1.1 200 OK[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "Cache-Control: private[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "Server: Microsoft-IIS/10.0[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "X-AspNetMvc-Version: 5.1[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "Access-Control-Allow-Origin: *[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "X-AspNet-Version: 4.0.30319[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "X-Powered-By: ASP.NET[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "Set-Cookie: ARRAffinity=beec3692495883b8df6195e900c12f49514e054d865a22ad2951c84f51dbaf93;Path=/;HttpOnly;Domain=httpstat.us[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "Date: Sun, 17 Mar 2019 08:21:05 GMT[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "Content-Length: 0[\r][\n]"
o.a.h.i.c.Wire http-outgoing-0 << "[\r][\n]"

o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << HTTP/1.1 200 OK
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << Cache-Control: private
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << Server: Microsoft-IIS/10.0
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << X-AspNetMvc-Version: 5.1
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << Access-Control-Allow-Origin: *
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << X-AspNet-Version: 4.0.30319
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << X-Powered-By: ASP.NET
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << Set-Cookie: ARRAffinity=beec3692495883b8df6195e900c12f49514e054d865a22ad2951c84f51dbaf93;Path=/;HttpOnly;Domain=httpstat.us
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << Date: Sun, 17 Mar 2019 08:21:05 GMT
o.a.h.i.c.LoggingManagedHttpClientConnection http-outgoing-0 << Content-Length: 0
o.a.h.i.e.MainClientExec Connection can be kept alive indefinitely

o.a.HttpClientSample header:HTTP/1.1 200 OK
o.a.HttpClientSample body:

これを見るとAcceptヘッダを送信していません。

何が問題か?

ブラウザやcurlはデフォルトでAcceptを送信しており、そこには*/*が含まれています。
参照: https://developer.mozilla.org/ja/docs/Web/HTTP/Content_negotiation/List_of_default_Accept_values

サンプルに利用したhttpstatでAcceptヘッダがある場合と無い場合とで確認してみましょう。

include_accept_header
$ curl http://httpstat.us/200 -v
* Trying 23.99.0.12...
* TCP_NODELAY set
* Connected to httpstat.us (23.99.0.12) port 80 (#0)
> GET /200 HTTP/1.1
> Host: httpstat.us
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Cache-Control: private
< Content-Length: 6
< Content-Type: text/plain; charset=utf-8
< Server: Microsoft-IIS/10.0
< X-AspNetMvc-Version: 5.1
< Access-Control-Allow-Origin: *
< X-AspNet-Version: 4.0.30319
< X-Powered-By: ASP.NET
< Set-Cookie: ARRAffinity=beec3692495883b8df6195e900c12f49514e054d865a22ad2951c84f51dbaf93;Path=/;HttpOnly;Domain=httpstat.us
< Date: Sun, 17 Mar 2019 08:33:52 GMT
<
* Connection #0 to host httpstat.us left intact
200 OK
exclude_accept_header
$ curl -H "Accept:"  http://httpstat.us/200 -v
* Trying 23.99.0.12...
* TCP_NODELAY set
* Connected to httpstat.us (23.99.0.12) port 80 (#0)
> GET /200 HTTP/1.1
> Host: httpstat.us
> User-Agent: curl/7.58.0
>
< HTTP/1.1 200 OK
< Cache-Control: private
< Server: Microsoft-IIS/10.0
< X-AspNetMvc-Version: 5.1
< Access-Control-Allow-Origin: *
< X-AspNet-Version: 4.0.30319
< X-Powered-By: ASP.NET
< Set-Cookie: ARRAffinity=beec3692495883b8df6195e900c12f49514e054d865a22ad2951c84f51dbaf93;Path=/;HttpOnly;Domain=httpstat.us
< Date: Sun, 17 Mar 2019 08:36:54 GMT
< Content-Length: 0
<
* Connection #0 to host httpstat.us left intact

Accpetヘッダを送信しないと、Content-Lengthヘッダが0になってBODYが空のレスポンスが返ってきました。

このサイトに限らず、例えばAcceptヘッダでapplication/jsonだとJSON形式で返すようなサイトでは、Frameworkによってはこういう挙動になるかもしれません。

どう書けばいいのか

いくつかやり方がありますがDefaultHeaderを設定するのが楽だと思います。

List<BasicHeader> defaultHeaders = Arrays.asList(new BasicHeader("Accept", "*/*"));
HttpClientBuilder clientBuilder = HttpClientBuilder.create()
.setDefaultHeaders(defaultHeaders);

もしくはGetMethodに設定します。

getMethod.setHeader("Accept", "*/*");

GCPのプロジェクトを組織なしで作成する方法

  • このエントリーをはてなブックマークに追加

GMAILアカウントでGCPを利用している場合は組織という概念がない(?)ので、常に「組織なし」の状態で作られます。
GSuiteで管理しているドメイン(会社のドメイン)でプロジェクトを作成すると、設定にもよりますがそのGSuiteの組織の下にプロジェクトが作られます。

組織の下にプロジェクトを作成すると、組織そのものにIAMを追加しておけば配下の全てのプロジェクトに引き継がれるので毎回オーナーを設定すると言った煩わしい作業が無くなるので楽なのですが、組織の下に置くとプロジェクトを組織から移動させる事が大変になります。

例えば、会社はサポートしているんだけどOSSとしてやっているプロジェクトなんかは組織に紐付けない方が良いものがあります。もしくは将来別組織に移動する予定があるプロジェクトとか。

どう作るか?

  1. 個人のアカウントでプロジェクトを作成する
  2. 課金アカウントを紐付けている場合は課金アカウントを外す
  3. そのプロジェクトに組織ドメインのユーザーをオーナーとして追加する
  4. そのプロジェクトの課金アカウントを組織ドメインの課金アカウントに紐付ける
  5. 個人のユーザーをそのプロジェクトから外す

普通はこんな事やりませんが、ちょっと諸事情で必要になったので調べたのでした。

ハマったこと

会社のアカウントでやる前に個人のGSuiteアカウントで予行練習しようとしたら、次の事にハマりました。マニュアル読めばきっと書いてある内容ではあると思う。

  • 最初GSuiteで管理しているGCPのアカウントを作ったら、プロジェクトが作れなかった。組織のIAM設定でユーザーを追加する必要があった
  • 組織の下にはフォルダを作ることができる。しかし組織の管理者でもオーナーでもこのフォルダを作ることが出来なかった。フォルダ作成者といった権限が必要だった。なんでもかんでもオーナーだからと言って設定出来るわけではないらしい。組織の管理者ではだめだった。

rsyslogのTCP転送のConnectionを定期的にRefreshする

  • このエントリーをはてなブックマークに追加

rsyslogでリモートサーバにログを転送する時、転送先を冗長化するためにLBを使うことがあると思います。AWSだとNLBを使うことになります。

rsyslogはログを転送するたびにsocketを貼るわけではなく、貼りっぱなしの状態になります。そのため、NLB配下に複数サーバがあっても均等に割り当てられるとは限らないため、負荷が均等に分散されません。解決策として、Client側で定期的にSocketを張り替える必要があります。

設定方法

こちらのマニュアルにRebindIntervalの項目があるため、これを利用します。TCPなので$ActionSendTCPRebindIntervalを使います。

Permits to specify an interval at which the current connection is broken and re-established. This setting is primarily an aid to load balancers. After the configured number of messages has been transmitted, the current connection is terminated and a new one started. Note that this setting applies to both TCP and UDP traffic. For UDP, the new connection'' uses a different source port (ports are cycled and not reused too frequently). This usually is perceived as a new connection’’ by load balancers, which in turn forward messages to another physical target system.

この設定項目は、公式サイトより、バージョン4.5.1からサポートされています。しかしながら、同じく公式サイトにはバージョン6.3.6からと書いてあります。

ソースコードを見ると4.5.1からサポートされていますし、AmazonLinuxでは5.8.10ですが当然このバージョンでもサポートされています。

指定する単位はメッセージ数です。秒ではないです。
例えば以下のように設定すると、10000メッセージを送ったタイミングで再接続します。数値は実際のログの流量によって調整すると良いです。

rsyslog.conf
$ActionSendTCPRebindInterval 10000 # reconnect per message count