bobuhiro11's diary

Linux Observability with BPF 読書メモ

14 Dec 2019

Linux Observability with BPFを読んだので、メモしておく。 今月、Brendan Gregg氏のBPF本も出るので、 それも読みたいな。

  • BPFを使うことで、カーネルのイベントをフックして安全にコードを実行できる
    • そのコードがシステムを破壊したりクラッシュさせたりすることが無いよう、BPF側で検証してくれる
    • カーネルモジュールと異なり、BPFプログラムはカーネル再コンパイルの必要がない
    • BPFコードが検証された後、BPFバイトコードは機械命令にJITされる
    • BPFプログラムは、bpf syscall によってBPF VMへとロードされる
  • 2014年前半にAlexei Starovoitov氏がeBPFを導入した
    • 過去のBPFでは2つの32bit registerのみが使えたが、eBPFでは10個の64bit registerまで使える
    • 2014年6月にはeBPFはユーザスペースにも拡張された
  • BPFプログラムタイプ:トレーシングとネットワーキングに大きく分類できる
    • Socket Filter:カーネルに最初に入ったプログラムタイプ。観測目的のみ
    • Kprobe:kprobe ハンドラとしてBPFプログラムを動かす。関数に入るときと出る時が、それぞれSEC(kprobe/sys_exec)SEC(kretprobe/sys_exec)に対応する
    • Tracepoint:事前にカーネル側で定義されたtracepointに対してBPFプログラムを動かす。/sys/kernel/debug/tracing/eventsで一覧を確認できる
    • XDP:パケット着信時に、早い段階でBPFプログラムを動かす。
    • Perf:perf eventに対してBPFプログラムを動かす。perfがデータを吐き出す度にBPFプログラムが実行される
    • Cgroup Socket:そのcgroup内のすべてのプロセスにアタッチされる
    • Socket Option:Facebookはデータセンター内の接続において、RTOs(recovery time objectives)の制御に、これを使っている。
    • Socket Map:BPFでロードバランサを実装するときに使う。CilliumやFacebook Katranはこれを使っている。
  • BFP Vefirier
    • CVE-2017-16995 のように、過去BFPをバイパスしてカーネルメモリをバイパスできる脆弱性があった
    • DFSで、そのプログラムが終了すること、危険なコードパスが存在しないことを検証する
    • 無限ループを弾くために、すべてのループを禁止している。ループ許可については、本書を書いている時点では、まだ提案の段階。
    • 命令数は4096に制限されている
    • bpf syscall の log_*を設定すれば、検証結果を確認できる
  • BPFプログラムは tail calls によって、他のBPFプログラムを呼び出すことができる
    • 呼び出し時にすべてのコンテキストは失われるので、何らかの方法で情報を共有する必要がある
  • BPFマップ
    • 直接bpf syscall でBPFマップを作成することができる
    • bpf_map_createヘルパ関数を使うのが簡単
    • bpf_map_update_elem のシグネチャは、カーネル側bpf/bpf_helpers.hとユーザ側tools/lib/bpf/bpf.hで異なるので注意する
    • エラー番号でsteerror(errno)で文字列にすると便利
    • bpf_map_get_next_keyはそのほかのヘルパ関数と違って、ユーザ側でのみ使えるので注意する
    • array, hash, cgroup storage maps については、スピンロックがあるので、並行アクセス可能
    • array map は要素数だけ事前にメモリが確保されて、ゼロで初期化される。グローバルな変数確保のために使う?
    • LRUハッシュやLRM(Longest Prefix Match)Trie mapもある
    • Linux 4.4 からマップとBPFプログラムを仮想FSから扱えるよう2つのsyscallが追加された
  • Tracing
    • kprobes/kretprobes:stableでないABIなので、事前にprobe対象の関数シグネチャを調べておく必要がある。Linuxバージョンごとに変わる可能性がある
    • BPFプログラムのcontextはプログラムによって変わる
    • トレーシングポイントAPIはLinuxバージョンごとに互換性がある。/sys/kernel/debug/tracing/eventsで確認できる
    • USDTs(user statically defined tracepoints):ユーザプログラムの静的なトレースポイント
  • BPFTool
    • 開発中が活発なので、Linux src からコンパイルする
    • bpftool featureで何が利用できるか確認できる
    • もしJITが無効ならecho 1 > /proc/sys/net/core/bpf_jit_enableで有効にできる
    • bpftool map showbpftool prog show でBPFプログラムやBPFマップの一覧を確認できる
    • batch fileをバージョン管理しておくのがオススメ
  • BPFTrace
    • BPFの高レベルDSL
    • awkのような感じでBEGINENDと実際のトレーシング部分という構成
    • BPFマップを自動的に作ってくれるので便利
  • kubectl-trace
    • BPFTraceプログラムをkubernetes jobとして実行する
  • eBPF Exported
    • BPFトレーシング結果をPrometheusに転送。Cloudflareで使われている
  • XDP
    • cotextとして渡されるxdp_buffは、sk_buffを簡単にしたもの。
    • 3つの動作モードがある
    • Native XDP:ドライバから出てすぐのところでBPFプログラムを動かす。git grep -l XDP_SETUP_PROG drive/で、対応するNICか確認する。
    • Offloaded XDP:git grep -l XDP_SETUP_PROG_HW drivers/で対応するNICを調べる。BPFプログラムをNICへオフロードする。
    • Generic XDP:開発者向けのテストモード。
    • XDPはユニットテストができる
  • ユースケース
    • Sysdig では troubleshooting tool を eBPF でOSSとして開発している
    • Flowmill では データセンターネットワークの監視ツールを開発している。CPUオーばヘッドは0.1%~0.25%程度。
# SHELL
# BPFプログラムをELFバイナリへコンパイル
clang -O2 -target bpf -c bpf_program.c -o bpf_program.o

# BPF用仮想FSのマウント
mount -t bpf /sys/fs/bpf /sys/fs/bpf

# USDTの確認
tplist -l ./hello_usdt

# スタックトレースの取得とflamegraphの作成
./profiler.py `pgrep -nx go` > /tmp/profile.out
./flamegraph.pl /tmp/profile.out > /tmp/flamegraph.svg

# kubectl-traceの実行例
kubectl trace run pod/pod_identifier -n application_name -e <<PROGRAM
  uretprobe:/proc/$container_pid/exe:"main.main" {
    printf("exit: %d\n", retval)
  }
PROGRAM

# XDP BPFプログラムのロード
# native mode がダメなら、generic mode で動く。強制することもできる
ip link set dev eth0 xdp obj program.o sec mysection
// C
// bpf syscall でBPFマップを作成
int fd = bpf(BPF_MAP_REATE, &my_map, sizeof(my_map));

// bpf map 一覧取得
int next_key, lookup_key;  = -1;
while (bpf_map_get_next\key(map_data[0].fd, &lookup_key, &next_key) == 0) {
  printf("The next key in the map: %d\n", next_key);
  lookup_key = next_key;
}
# BCC (python)
from bcc import BPF

# kprobesの例
bpf_source = """
int do_sys_execve(struct pt_regs *ctx, void filename, void argv, void envp) {
  char comm[16];
  bpf_get_current_comm(&comm, sizeof(comm));
  bpf_trace_printk("executing program: %s", comm);
  return 0;
}
"""
bpf = BPF(text = bpf_source)
execve_function = bpf.get_syscall_fnname("execve")
bpf.attach_kprobe(event = execve_function, fn_name = "do_sys_execve")
bpf.trace_print()

# tracepointの例
bpf_source = """
int trace_bpf_prog_load(void ctx) {
  char comm[16];
  bpf_get_current_comm(&comm, sizeof(comm));
  bpf_trace_printk("%s is loading a BPF program", comm);
  return 0;
}
"""
bpf = BPF(text = bpf_source)
bpf.attach_tracepoint(tp = "bpf:bpf_prog_load",
fn_name = "trace_bpf_prog_load")
bpf.trace_print()

# uprobesの例
bpf_source = """
int trace_go_main(struct pt_regs *ctx) {
  u64 pid = bpf_get_current_pid_tgid();
  bpf_trace_printk("New hello-bpf process running with PID: %d", pid);
}
"""
bpf = BPF(text = bpf_source)
bpf.attach_uprobe(name = "hello-bpf",
sym = "main.main", fn_name = "trace_go_main")
bpf.trace_print()

# USDTの例
from bcc import BPF, USDT
bpf_source = """
#include <uapi/linux/ptrace.h>
int trace_binary_exec(struct pt_regs *ctx) {
  u64 pid = bpf_get_current_pid_tgid();
  bpf_trace_printk("New hello_usdt process running with PID: %d", pid);
}
"""
usdt = USDT(path = "./hello_usdt")
usdt.enable_probe(probe = "probe-main", fn_name = "trace_binary_exec")
bpf = BPF(text = bpf_source, usdt = usdt)
bpf.trace_print()
# BPFTrace のDSL
# bpftrace /tmp/examble.bt で実行
BEGIN
{
  printf("starting BPFTrace program\n")
}
kprobe:do_sys_open
{
  printf("opening file descriptor: %s\n", str(arg1))
  @opens[str(arg1)] = count()
}
END
{
  printf("exiting BPFTrace program\n")
}

Kubernetes完全ガイド 読書メモ

23 Nov 2019

Kubernetes完全ガイド を読んだので、メモを残しておく。 普段、意識していなかったコマンドや仕組みをまとめておく。

  • サイズの小さなイメージを作りたければ scratch や apline をベースにする
  • ENTRYPOINT と CMD が設定されているときは、$ENTRYPOINT $CMD が実行されるようなイメージ
  • マルチステージビルド:ビルド専用のコンテナでだけ処理を行い、成果物を実行専用コンテナにコピーすることが出来る
  • CNCF ではプロジェクトの成熟度を「Graduated」「Incubating」「Sandbox」の3段階に設定している
  • オンプレで構築する場合はAWS や GCP でのインスタンスサイズが目安になる
  • Flannel:ノード間でオーバーレイネットワークを構成する
  • Docker, Inc. が提供するkubernetes のプレイグラウンドがある
  • kubernetes のリソースは大きくわけて5種類
    • Workloads, Discoverty &LB, Config & Storage, Cluser, Metadata
    • 特に Metadata はクラスタ内で他のリソースを操作するためのリソース(HorizontalPodAutoscaler など)
    • Deployment/CronJob > ReplicationController/ReplicaSet/DaemonSet/StatefulSet/Job > Pod という階層構造がある
    • kubectl get all:ほぼすべてのリソースを一覧取得
    • Stateful Set:0番目の Pod が最初に作られ、最後に削除される
  • kubeconfig
    • clusters、users、contexts の3種類を設定(どれも複数登録可能)
    • context や namespace の切り替えが冗長であれば、kubectx や kubens を使うと良いかも
    • kube-ps1 を使うと cluster と namespace をプロンプトに出力できる
    • source <(kubectl completion bash)などで zsh/bash の補完機能を使える
  • コンウェイの法則:組織図と、マニフェストの管理方法やマイクロサービスのアーキテクチャが似ている
  • kubectl scale:ReplicaSet, ReplicationController, Deployment, StatefulSet, Job, CronJob でスケーリング
  • kubectl apply --prune:実行時にマニフェストから削除されたリソースを検知して、自動で削除
    • CI/CDでは、単純にこのコマンドを投げ続けるだけでよい
    • kubectl apply --prune --all:クラスタ内に存在するすべてのマニフェストを読み込ませないと、漏れがあったリソースが消えてしまい危険
    • kubectl apply --prune -l system=a:基本的にラベルを指定しておく
  • デバッグ
    • kubectl cp:コンテナとローカルマシンの間でファイル転送
    • kubectl port-forward:コンテナとローカルマシンの間でポート転送
    • kubectl -v:デバッグ出力
  • サービスディスカバリ
    • spec.hostAliases:Pod内の全コンテナの/etc/hostsを書き換える
    • Pod内からは同 namespace に含まれるサービスを環境変数から確認できる
    • {service name}.{namespace}.svc.cluster.local でサービスのAレコードが登録されている
    • spec.externalTrafficPolicy=Local とすると Node を跨ぐロードバランシングを行わない
    • Headless Service を使うと、VIP経由のトラフィックのロードバランシングではなく、DNSラウンドロビンによる負荷分散を行う
    • 特定の Node のIPアドレス:ポート番号で受信したトラフィックを外部へ転送するために ExternalIP Service を使える
    • ExternalName Service:事前に設定したCNAMEを返す
  • Ingress
    • Ingress:L7ロードバランサを提供する。Service とは違う位置づけなので、独立したリソース
    • Ingress用Podをデプロイする場合:Ingress Pod 宛の Loadbalancer Service を作っておき、そこから先を L7 ロードバランスする
    • Nginx Ingress は Inress Controller 自体が L7 相当の処理を行う Pod にもなっている(コントローラという名前だが)
  • Secret が定義されたマニフェストは Git リポジトリにアップロードする際に kubesec を使って暗号化できる
  • Dynamic Provisioning を使うと、PersistentVolumeChain が発行されたタイミングで動的に PersistentVolume を作成・割り当て
  • スケジューリング
    • Requests はリソースの下限、Limit はリソースの上限
    • これら2つの差は小さくしておく
    • 基本的に Requests を基準にスケジューリング
    • kubectl describe resourcequota で確認できる
    • Beyond CPU: horizontal pod autoscaling with custom metrics in Google Kubernetes Engine が参考になる
    • postStart/preStop:コンテナの起動後と停止直前に任意のコマンドを実行できる
    • ただ確実に慈善処理を行うなら initContainers を使った方が良い
    • None に対して Taints(汚れ) を付けておいて、Pod がそれを Tolerations(許容)するとうスケジューリング
  • ログを中長期に安定保存するには Fluentd を使って外部に転送する構成を取るのがよい
  • Spinnaker(Netflix)、skaffold(Google) などで CI/CD できる
  • マニフェストファイルのLinter kubeval やユニットテストツール kubetest がある
  • サービスメッシュ
    • マイクロサービス間の通信や経路を制御する考え方
    • Istio/Conduit(Linkerd v2)/Linkerd など
  • etcd
    • クラスタの全ての情報を保存するもの
    • 単一障害店(SPoF)になってはならない
    • Raft の性質上3/5/7台といった奇数台で組むのが良い
    • etcd snapshot でバックアップをとれる
  • Operator Framework を使って独自のリソースを作成できる
  • X as a Service:Vitess(Mysql)、NATS(Queue)、Rook(Ceph)、Kubeflow(ML)

TP-Link Archer T3U AC1300をLinuxから使う

09 Nov 2019

USB接続の無線LAN子機 TP-Link Archer T3U AC1300 を、Thinkpad X1 Carbon 2015(Fedora release 29 (Twenty Nine)、linux 5.3.6-100.fc29.x86_64)から使えるようにしたので手順を残しておく。

# 公式HPではドライバが配布されていなかったので rtl88x2bu を使った
git clone https://github.com/cilynx/rtl88x2bu
cd rtl88x2bu/
git rev-parse HEAD # 962cd6b1660d3dae996f0bde545f641372c28e12
VER=$(sed -n 's/\PACKAGE_VERSION="\(.*\)"/\1/p' dkms.conf)
sudo rsync -rvhP ./ /usr/src/rtl88x2bu-${VER}
# /usr/src/<module_name>_<module_version> に移動させて dkms で管理する
sudo dkms add -m rtl88x2bu -v ${VER}
sudo dkms build -m rtl88x2bu -v ${VER}
sudo dkms install -m rtl88x2bu -v ${VER}
sudo dkms status
sudo modprobe 88x2bu

2種類の子機(オンボードの子機とTP-Linkの子機)を同一のネットワークにつなぐと、metric によって優先順位が決まる。 metric の値を見ると、オンボードの子機側を優先していた(値が小さい)ので入れ替えた。

ip route
sudo nmcli con mod <connection name> ipv4.route-metric 599
sudo systemctl restart NetworkManager

追記: Fedora 30、31にアップデートしたあとも特に問題なかった。

sudo dkms status
# rtl88x2bu, 5.6.1, 5.3.6-100.fc29.x86_64, x86_64: installed
# rtl88x2bu, 5.6.1, 5.3.8-200.fc30.x86_64, x86_64: installed
# rtl88x2bu, 5.6.1, 5.3.8-300.fc31.x86_64, x86_64: installed

2019 越後湯沢 秋桜ハーフマラソン

29 Sep 2019

越後湯沢で開催された秋桜ハーフマラソンに参加してきた。 大会自体はすごいよかったけど、コースの半分あたりから胃の調子が悪くなり、 13kmでリタイヤという悔しい結果となった。 色々反省点があったので、記録として残しておく。

まず、そもそも食事のタイミングが悪かった。9時30分スタートで、8時あたりでおにぎりを食べた。 3時間前までに取るようにしたい。直前に口にいれるのであれば、ゼリー状のものにしたい。

6.7kmから15kmまで続く下り坂で飛ばしすぎた事も影響していると思う。 お腹が激しく上下して気持ち悪くなってしまった。 スタートから6.7kmまでの上り坂は走り方に気をつけていたが、それ以降については、なんとかなるだろうと考えていなかった。 加えて、腰にポーチを巻いていたので、お腹を圧迫していたのかもしれない。 塩飴を少量持っていただけなので、ポケットにいれておけば事足りていた。

前半の上下の坂でも貯金を作っておこうと、いつも以上に早めのペースで走っていたので、 バテてしまったんだろうな。

ただ、自分でリタイヤを決断できたのは唯一よかったことだった。 以前膝を故障してしまったときは、脚を引きずりながらも、無理して走っていて、 沿道で応援してくれた方からリタイヤを勧められた。

どうも消化不良な感じなので、どこかでリベンジしたい。


TCP技術入門 読書メモ

25 Sep 2019

TCP技術入門を読んだので、 気になったところを雑多な感じだがメモしておく。まず前半部分。 ネットワークまわりの話は聞いたことはあるが、使わずに忘れてしまっていることが多い。

1章 TCP入門

  • 伝送効率
    • Ethernetフレーム 1500 バイト、TCPヘッダが 60 バイト、IPヘッダが20バイトとする
    • アプリケーションが使えるのは 1420 バイト
    • Ethernetヘッダ 14 バイト、FCS(Frame Check Sequence)4バイトを考慮すると、伝送効率は$1420/(1500+18) \times 100=93.5 \%$
  • UDP
    • 実際にUDPがやっていることは、転送先へデータを送ることと、チェックサムの確認だけ
    • TCPはユニキャストだが、UDPはユニキャストに加えて、マルチキャストとブロードキャストにも対応
    • シーケンス番号とタイムスタンプを追加したRTP(Real-time Transport Protocol)や、それに加えて再送機能も追加したRUDP(Reliable user Datagram Protocol)もある
    • QUIC はUDPベースで、それを使ったHTTP/3(HTTP-over-QUIC)。コネクション確立とTLS確立を同時に実施。
  • TCP
    • ACKのシーケンス番号は、次に受信したい番号。当該セグメントが届くまで、同じシーケンス番号を返送し続ける。
    • フロー制御:受信側のバッファ溢れを防ぐために、送信データ量を調整すること。TCPでは一度に送信可能なサイズをウィンドウと呼ぶ。特に、送信側がもつパラメータを swnd (send window)と表記する。対して、受信側はrwnd(recieve wndoe)。
  • TCP輻輳制御
    • Loss-based:データの消失から輻輳を判断
    • Delay-based:RTTから輻輳を判断
  • 特定の用途向けプロトコル
    • RUDP(Reliable User Datagram Protocol):UDPベースで、シーケンス番号、ACK、再送、フロー制御を加えたもの
    • DCCP(Datagram Congestion Control Protocol):UDPにおける輻輳緩和が目的

2章 TCP/IPの変遷

  • ARPNETなどではネットワークが信頼性を担保していたが、TCP/IPは各ノードがその役割を担う
  • Nagleアルゴリズム:TCP/IPの送出パケット数削減の方法。輻輳制御アルゴリズムのはしり
  • Windows95(OSR2以降)にTCP/IPが搭載されたことが普及のきっかけになった
  • IEEE 802.11ではMACレイヤーでCSMA/CAを採用

3章 TCPとデータ転送

  • MTUは、イーサネットは1500バイト、PPPoEでは1492バイト、ATMは9180バイト。ただ最近は任意に設定できる
  • MSS(Maximum Segment Size):TCPが区切る最大パケット長さ
  • MTU(1500バイト)= IPヘッダ(20バイト)+ TCPヘッダ(20~60バイト)+ アプリケーションデータ(MSS以下)
    • MSSは、TCPのペイロードのみ
    • MTUは、TCPのペイロードからTCPヘッダやIPヘッダまで。Ethernetヘッダは含まない
  • ACK(Acknoledgement Number):確認応答番号。次に送ってほしいシーケンス番号
  • コントロールフラグ
    • RST(Reset the connection):コネクションを強制切断。使われていないポート番号に送るなど、異常があれば有効になる
    • CWR(Congestion Window Reduced):輻輳ウィンドウの減少を通知する。ECNとセットで使われる
    • ECE(ECN echo):輻輳が発生したら通知
  • ウィンドウ制御:一度に送信可能なウィンドウサイズ $swnd$ を調整 $swnd = min(rwnd, cwnd)$
    • フロー制御:受信側から送信側に送信量を通知して、調整する。受信側は、送信側へ受信可能なバッファ量$rnwd$を通知
    • 輻輳制御:輻輳ウィンドウ $cwnd$ を調整
  • スロースタート
    • 通信開始時点で、ACKを受け取るたびに $cwnd$ を1セグメントずつ増やしていく $cwnd = cwnd + mss$
    • 指数的に $cwnd$ が拡大
  • 輻輳回避
    • スロースタートを行うと、再び再送が起きやすい
    • 再送が起きた時の $cwnd$ の半分 $cwnd/2$を閾値として、それを越えた時は更新を緩やかにする $cwnd = cwnd + mss/cwnd$
    • RTTごとに線形に増える
  • 高速リカバリー
    • 輻輳のたびに、スロースタート→輻輳回避という挙動をするのは効率が悪い
    • 重複ACKを契機とする
    • Recoで採用
  • セグメントの消失
    • 再送タイマーがタイムアウトした場合
    • 重複ACKが一定数届いた場合