调试 Istio 网格中运行的 Envoy sidecar C++ 代码
本文来源于我的开源书籍 《Istio Insider》 。
介绍
调试在 Istio 网格中运行的 Envoy sidecar C++ 代码。 它有助于在代码级别深入研究 sidecar。 它使我们在解决 Istio 问题或编写更好的 EnvoyFilter 或 eBPF 跟踪程序时更有信心。 本文介绍如何使用 VSCode
和 lldb
调试 Envoy istio-proxy sidecar。
我的动机
-
深入研究 Envoy Proxy 在 Istio 控制面控制下的真实行为和代码逻辑
多年前,我写过一篇文章:
gdb debug istio-proxy(envoy)(中文)。 它只是在 Istio 网格之外调试 Envoy 进程。对我来说,深入研究 Istio 服务网格中 sidecar (istio-proxy) 的行为让我更有信心完成我的书:《Istio Insider》。 我想使用 (lldb
/gdb
) + VSCode
来调试在 Istio 服务网格上运行的 Envoy
(C++ 代码)。
-
深入研究 Envoy Proxy 在 Istio 控制面控制下的真实行为和代码逻辑
去年,我在写《逆向工程与云原生现场分析 —— eBPF 跟踪 Istio/Envoy 系列》 时,感觉使用 BPF uprobe 动态注入 probe ,探测进程数据最难的是了解运行期的 C++ 对象内存结构。而 debug 进程无疑是最直接可靠的内存结构观察方法。可以用 debug 中的观察结果,指导 BPF uprobe 程序的准确编写。
环境架构
图: 使用 lldb 远程调试 istio-proxy
用 Draw.io 打开
环境说明
Istio 版本: 1.17.2
环境说明:
- k8s cluster
- node network CIDR: 192.168.122.0/24
- Istio 1.17.2 已安装
- 测试目标 k8s namespace: mark
- 测试目标 pod 运行于 node: 192.168.122.55
- 带 Linux 桌面的开发者 node
- IP addr: 192.168.122.1
- hostname:
labile-T30
- OS: Ubuntu 22.04.2 LTS
- user home: /home/labile
- 连通 k8s cluster node 网络
- 有 X11 GUI
- VSCode
- Docker 已安装
- 有 Internet 连接
环境搭建步骤
1. 构建带 debug 信息的 istio-proxy
1.1 Clone 源码
运行于 labile-T30
:
1
2
3
4
5
|
mkdir -p $HOME/istio-testing/
cd $HOME/istio-testing/
git clone https://github.com/istio/proxy.git work
cd work
git checkout tags/1.17.2 -b 1.17.2
|
1.2 启动 istio-proxy-builder 容器
编译像 istio-proxy 这样的大项目是环境相关的工作。 对于我这样的新手,我更愿意直接使用官方的 Istio CI 编译容器。 好处是:
1.环境与Istio官方版本一致,避免版本陷阱。 理论上生成的可执行文件是相同的
2.内置工具,简单易用
注意:build-tools-proxy 容器镜像列表可以在 https://console.cloud.google.com/gcr/images/istio-testing/global/build-tools-proxy 获取。 请选择你要编译的 istio-proxy 版本对应的镜像。 方法是利用网页中的 Filter 功能。 以下仅以 release-1.17 为例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# optional
docker network create --subnet=172.18.0.0/16 router
docker stop istio-proxy-builder
docker rm istio-proxy-builder
mkdir -p $HOME/istio-testing/home/.cache
# run istio-proxy-builder container
docker run --init --privileged --name istio-proxy-builder --hostname istio-proxy-builder \
--network router \
-v /var/run/docker.sock:/var/run/docker.sock:rw \
-v $HOME/istio-testing/work:/work \
-v $HOME/istio-testing/home/.cache:/home/.cache \
-w /work \
-d gcr.io/istio-testing/build-tools-proxy:release-1.17-latest-amd64 bash -c '/bin/sleep 300d'
|
1.3 构建 istio-proxy
1
2
3
4
5
6
|
## goto istio-proxy-builder container
docker exec -it istio-proxy-builder bash
## build istio-proxy with debug info in output ELF
cd /work
make build BAZEL_STARTUP_ARGS='' BAZEL_BUILD_ARGS='-s --explain=explain.txt --config=debug' BAZEL_TARGETS=':envoy'
|
在我的 2 core CPU 和 64GB RAM 机器上花了 3 个小时构建它。 更多的 core 会更快。
构建完成后可以查看输出的 ELF:
1
2
3
4
5
|
## goto istio-proxy-builder container
docker exec -it istio-proxy-builder bash
build-tools: # ls -lh /work/bazel-out/k8-dbg/bin/src/envoy/envoy
-r-xr-xr-x 1 root root 1.2G Feb 18 21:46 /work/bazel-out/k8-dbg/bin/src/envoy/envoy
|
2. 安装测试目标 pod
2.1 构建 istio-proxy docker image
在 labile-T30
上运行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
# start local private plain http docker image registry
docker run -d -p 5000:5000 --restart=always --name image-registry --hostname image-registry registry:2
cd mkdir -p image/gdb-istio-proxy
cd image/gdb-istio-proxy
# NOTICE: replae 1e0bb3bee2d09d2e4ad3523530d3b40c with the real path in your environment
sudo ln $HOME/istio-testing/home/.cache/bazel/_bazel_root/1e0bb3bee2d09d2e4ad3523530d3b40c/execroot/io_istio_proxy/bazel-out/k8-dbg/bin/envoy ./envoy
cat > proxyv2:1.17.2-debug.Dockerfile <<"EOF"
FROM docker.io/istio/proxyv2:1.17.2
COPY envoy /usr/local/bin/envoy
RUN apt-get -y update \
&& sudo apt -y install lldb
EOF
# build docker image
docker build . -f ./proxyv2:1.17.2-debug.Dockerfile -t proxyv2:1.17.2-debug
docker tag proxyv2:1.17.2-debug localhost:5000/proxyv2:1.17.2-debug
# push image to local image registry
docker push localhost:5000/proxyv2:1.17.2-debug
|
docker image 的大小:
- Envoy elf: 1.4G
- lldb package: 700Mb
- others
2.2 运行目标 pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
kubectl -n mark apply -f - <<"EOF"
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: fortio-server
labels:
app: fortio-server
spec:
serviceName: fortio-server
replicas: 1
selector:
matchLabels:
app: fortio-server
template:
metadata:
annotations:
sidecar.istio.io/proxyImage: 192.168.122.1:5000/proxyv2:1.17.2-debug
sidecar.istio.io/inject: "true"
sidecar.istio.io/proxyMemoryLimit: "4Gi"
sidecar.istio.io/proxyMemory: "512Mi"
labels:
app.kubernetes.io/name: fortio-server
app: fortio-server
spec:
restartPolicy: Always
containers:
- name: main-app
image: docker.io/fortio/fortio
imagePullPolicy: IfNotPresent
command: ["/usr/bin/fortio"]
args: ["server", "-M", "8070 http://fortio-server-l2:8080"]
ports:
- containerPort: 8080
protocol: TCP
name: http
- containerPort: 8070
protocol: TCP
name: http-m
- name: istio-proxy
image: auto
imagePullPolicy: IfNotPresent
EOF
|
2.3 启动 lldb server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
ssh 192.168.122.55
sudo su
# get PID of envoy
export POD="fortio-server-0"
ENVOY_PIDS=$(pgrep envoy)
while IFS= read -r ENVOY_PID; do
HN=$(sudo nsenter -u -t $ENVOY_PID hostname)
if [[ "$HN" = "$POD" ]]; then # space between = is important
sudo nsenter -u -t $ENVOY_PID hostname
export POD_PID=$ENVOY_PID
fi
done <<< "$ENVOY_PIDS"
echo $POD_PID
export PID=$POD_PID
sudo nsenter -t $PID -u -p -m bash #NO -n
sudo lldb-server platform --server --listen *:2159
|
测试 lldb-server(可选,可跳过)
在 labile-T30
上运行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
sudo lldb
# commands run in lldb:
platform select remote-linux
platform connect connect://192.168.122.55:2159
# list process of istio-proxy container
platform process list
file /home/labile/istio-testing/home/.cache/bazel/_bazel_root/1e0bb3bee2d09d2e4ad3523530d3b40c/execroot/io_istio_proxy/bazel-out/k8-dbg/bin/envoy
# Assuming pid of envoy is 15
attach --pid 15
# wait, please the big evnoy ELF
exit
|
3. attach debuger 到 istio-proxy
3.1 启动 lldb-vscode-server container
在 labile-T30
运行:
- 启动
lldb-vscode-server
container
1
2
3
4
5
6
7
8
9
10
11
12
|
docker stop lldb-vscode-server
docker rm lldb-vscode-server
docker run \
--entrypoint /bin/bash \
--init --privileged --name lldb-vscode-server --hostname lldb-vscode-server \
--network router \
-v /var/run/docker.sock:/var/run/docker.sock:rw \
-v $HOME/istio-testing/work:/work \
-v $HOME/istio-testing/home/.cache:/home/.cache \
-w /work \
-d localhost:5000/proxyv2:1.17.2-debug \
-c '/bin/sleep 300d'
|
3.2 VSCode attach lldb-vscode-server
container
- 在
labile-T30
桌面 启动 VSCode GUI.
- 在 vscode 执行命令(Ctrl+Shift+p):
Remote Containers: Attach to Running Container
, 选择 lldb-vscode-server
container.
- 在 attached 到 container 后, open folder:
/work
.
- 安装 VSCode extensions:
- CodeLLDB
- clangd (Optional)
3.3 lldb 远程 attach Envoy 进程
3.3.1 创建 launch.json
文件
在 /work
下创建 .vscode/launch.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
{
"version": "0.2.0",
"configurations": [
{
"name": "AttachLLDBRemote",
"type": "lldb",
"request": "attach",
"program": "/work/bazel-out/k8-dbg/bin/envoy",
"pid": "15", //pid of envoy in istio-proxy container
"sourceMap": {
"/proc/self/cwd": "/work/bazel-work",
"/home/.cache/bazel/_bazel_root/1e0bb3bee2d09d2e4ad3523530d3b40c/sandbox/linux-sandbox/263/execroot/io_istio_proxy": "/work/bazel-work"
},
"initCommands": [
"platform select remote-linux", // Execute `platform list` for a list of available remote platform plugins.
"platform connect connect://192.168.122.55:2159"
],
}
]
}
|
3.3.2 Attach 远程 Envoy 进程
在 VSCode 中 Run and debug: AttachLLDBRemote
.
加载 1GB 的 ELF 可能需要大约 1 分钟。 请耐心等待。
4. 开始调试
FAQ
containerd
allow pull image from plain http docker image registry
Update /etc/containerd/config.toml
of the node in k8s cluster:
1
2
3
4
5
6
7
8
9
|
sudo vi /etc/containerd/config.toml
version = 2
[plugins]
[plugins."io.containerd.grpc.v1.cri".registry]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."192.168.122.1:5000"]
endpoint = ["http://192.168.122.1:5000"]
|
动态 path
Please update /home/.cache/bazel/_bazel_root/1e0bb3bee2d09d2e4ad3523530d3b40c
path according to your environment.
为何用 lldb
而不是 gdb
我在使用 gdb
时遇到了很多问题。
更 Cloud Native 的远程调试的方法
多年前,我写过一篇文章:
重新思考云原生时代的开发环境——从Dev-to-Cloud到Dev@Cloud。 介绍如何在k8s集群中安装一个运行 X11 桌面环境的 Pod,并通过浏览器直接连接到桌面。
纯云原生风味是目标。 为了让调试 istio-proxy 更有云原生的味道。 您可以将下图中的一些组件替换为 k8s 组件。 这同时也可以降低开发者进入调试环境的门槛。 例如:
- 在
labile-T30
上运行的 docker 容器之间的共享文件夹可以用 k8s RWX(ReadWriteMany) PV 替换。 例如 NFS/CephFS。
istio-proxy-builder
和 lldb-vscode-server
容器可以作为 k8s Pod 运行并挂载上面的 RWX PVC。
Remote Containers: Attach to Running Container
可以替换为 VSCode-server
k8s 服务,它的好处是,可以通过任何网络浏览器轻松访问。 不再需要具有 X11 桌面/VSCode GUI 应用程序和 docker 或 ssh 连接的节点。 只需将 VSCode-server
发布为 k8s 服务并在开发者电脑的网络浏览器上访问它。
图: 使用 lldb 远程调试 istio-proxy
用 Draw.io 打开