使用 CLion 搭建 istioproxy (envoy) 开发环境

2023年 7月 10日 48.4k 0

背景

要想深入学习 istio,还得学习下数据面的实现,istio 的数据面使用了 envoy,在 istio group 下有个叫 proxy 的仓库,包含了一些 istio 用到的一些 envoy 扩展,编译时将 envoy 代码作为库来引用,最终使用 bazel 编译出 istio 版本的 Envoy。

代码量非常庞大,如果没有智能的代码跳转、查找引用与实现,阅读和开发起来简直低效的要命。如何更加高效呢?关键在于 IDE/编辑器 的代码索引能力要好,需要能够准确跳转和查询,vscode 用的同学比较多,但它的 c/c++ 插件不够智能,很多情况无法跳转,而且效率较低;它还有个 clangd 的插件,基于 LSP,但不够成熟。这方面做的最好的目前还是来自 JetBrains 的 CLion,不过它需要依赖 CMakeLists.txt 文件来解析项目结构,由于 c/c++ 没有统一的结构标准,不同项目结构千差万别,不太好自动生成 CMakeLists.txt,需要我们先理解项目结构,然后编写 CMakeLists.txt 来让 CLion 进行解析。

虽然社区有人针对 bazel 构建的项目写了一个通用脚本 bazel-cmakelists ,但很久没维护,测试了用它来生成最新 envoy 的 CMakeLists.txt ,由于代码量庞大,最终会 OOM 而失败。

所以我们需要另寻更好的方法,不太了解这方面的同学弄起来会比较麻烦,本人也折腾了好一段时间才搞定,本文记录下方法和心得,以供大家参考。

克隆代码

首先克隆 istio-proxy 的代码:

git clone https://github.com/istio/proxy.git istio-proxy

最好切到某个稳定的 release 分支上:

cd istio-proxy
git checkout -b release-1.9 origin/release-1.9

项目分析

istio-proxy 代码库中主要只包含了在 istio 里用到的一些 envoy 扩展,代码量不大,源码主要分布在 src 与 extensions 目录,但编译需要很久,因为它实际编译的是 envoy,只是利用 bazel 将自身代码作为扩展编译进 envoy(得益于 envoy 的扩展机制),从这个 bazel 的 BUILD 文件 就能看得出来:

envoy_cc_binary(
    name = "envoy",
    repository = "@envoy",
    visibility = ["//visibility:public"],
    deps = [
        "//extensions/access_log_policy:access_log_policy_lib",
        "//extensions/attributegen:attributegen_plugin",
        "//extensions/metadata_exchange:metadata_exchange_lib",
        "//extensions/stackdriver:stackdriver_plugin",
        "//extensions/stats:stats_plugin",
        "//src/envoy/extensions/wasm:wasm_lib",
        "//src/envoy/http/alpn:config_lib",
        "//src/envoy/http/authn:filter_lib",
        "//src/envoy/tcp/forward_downstream_sni:config_lib",
        "//src/envoy/tcp/metadata_exchange:config_lib",
        "//src/envoy/tcp/sni_verifier:config_lib",
        "//src/envoy/tcp/tcp_cluster_rewrite:config_lib",
        "@envoy//source/exe:envoy_main_entry_lib",
    ],
)

其中 @envoy 表示引用 envoy 代码库,main 函数也位于 envoy 代码库中。那么 envoy 代码库从哪儿来的呢?bazel 在构建时会自动下载指定的依赖,envoy 的代码来源在 WORKSPACE 中有指定:

http_archive(
    name = "envoy",
    sha256 = ENVOY_SHA256,
    strip_prefix = ENVOY_REPO + "-" + ENVOY_SHA,
    url = "https://github.com/" + ENVOY_ORG + "/" + ENVOY_REPO + "/archive/" + ENVOY_SHA + ".tar.gz",
)

bazel 会自动下载指定版本的源码包来编译。

如果获取依赖源文件?

由于 istio-proxy 依赖了大量的第三方源文件,我们要阅读代码需要将这些源文件都下下来,只要将它编译一次,所有依赖源文件以及 generated 的代码都可以自动给你备好,所以我们需要对它进行一次编译。

由于编译 envoy 有复杂的工具链依赖,官方推荐使用容器进行编译,在执行 make 前加个 BUILD_WITH_CONTAINER=1 即可指定使用容器编译,免去复杂的环境依赖。但 bazel 编译会将依赖和 generated 的源文件都软链到临时目录,如果用容器编译,就会丢失这部分代码,而我们阅读 istio-proxy 代码时最关键的就是这部分代码了,所以不能用容器编译。

安装 bazelisk

不用容器编译就需要本机环境基本满足工具链要求,首先是需要安装 bazel,由于 bazel 版本很多,不同 istio-proxy (envoy) 版本依赖的 bazel 版本也不一样,我们可以直接安装 bazelisk ,一个用于 bazel 多版本管理的工具,它可以自动识别项目中 .bazelversion 文件,选取指定版本的 bazel 来进行构建(可以自动下载对应版本的 bazel 二进制文件)。

如果是 macOS 用户,安装很简单:

brew install bazelisk

如果之前已安装过 bazel,可以使用 brew link --overwrite bazelisk 强制覆盖。

其它平台的可以在 release 页面下载最新的二进制文件,重命名为 bazel 然后放到 PATH 下。

其它依赖

如果是 macOS 用户,确保务必安装好 xcode,方便跳转系统库函数,安装命令:

xcode-select --install

另外主要还有 python3(macOS 自带),其它依赖通常都系统自带,可以先不用管,等如果编译报错了再看。

更多依赖可参考 官方文档 。

编译

在 istio-proxy 代码根目录执行以下命令进行编译:

make build_envoy

环境没问题的话会经过漫长的构建和编译,通常可能几十分钟,取决于电脑配置。

编译完后会发现 bazel 为我们生成了一些目录软链:

bazel 输出目录结构可参考官方文档 Output Directory Layout 。

我们主要关注以下两个目录:

  • bazel-istio-proxy:包含构建 istio-proxy 用到的源文件(包含依赖)。
  • bazel-bin:包含一些 generated 代码。

生成源码文件列表

在 istio-proxy 根目录创建脚本文件 generate-srcs.sh

#!/bin/bash

set -ex

bazel_dir="bazel-${PWD##*/}"

find -L -E $bazel_dir/external src extensions -regex '.*.(cc|c|cpp)' > sourcefiles.txt

执行此脚本可以生成 istio-proxy 及其依赖的源文件列表(sourcefiles.txt),用于在 CMakeLists.txt 中引用。

注: $bazel_dir/external 下包含内容较多,全部索引的话 CLion 可能会比较卡,很多代码基本也都不会看,可以适当缩小范围,按需来配置,比如先只添加 $bazel_dir/external/envoy,后续有需要再添加其它目录,然后 Reload Cmake Project 重新索引。

生成 CMakeLists.txt

然后就可以在 istio-proxy 项目根目录创建下 CMakeLists.txt:

cmake_minimum_required(VERSION 3.15)
STRING( REGEX REPLACE ".*/(.*)" "\1" CURRENT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR} )
project(istio-proxy)

macro(print_all_variables)
    message(STATUS "print_all_variables------------------------------------------{")
    get_cmake_property(_variableNames VARIABLES)
    foreach (_variableName ${_variableNames})
        message(STATUS "${_variableName}=${${_variableName}}")
    endforeach()
    message(STATUS "print_all_variables------------------------------------------}")
endmacro()

set(CMAKE_CXX_STANDARD 17)
add_definitions(-DNULL_PLUGIN) # enable wasm nullvm navigation

file(STRINGS sourcefiles.txt all_SRCS)

message(STATUS "CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR}")
message(STATUS "CMAKE_HOME_DIRECTORY=${CMAKE_HOME_DIRECTORY}")

add_executable(istio-proxy ${all_SRCS})

set(istio_include_dirs
        "./"
        "./src"
        "./extensions"

        "./bazel-${CURRENT_FOLDER}/external/envoy"
        "./bazel-${CURRENT_FOLDER}/external/envoy/source"
        "./bazel-${CURRENT_FOLDER}/external/envoy/include"
        "./bazel-${CURRENT_FOLDER}/external/envoy/api/wasm/cpp"
        "./bazel-${CURRENT_FOLDER}/external/boringssl/src/include/"
        "./bazel-${CURRENT_FOLDER}/external/com_github_gabime_spdlog/include"
        "./bazel-${CURRENT_FOLDER}/external/com_github_c_ares_c_ares"
        "./bazel-${CURRENT_FOLDER}/external/com_google_absl"
        "./bazel-${CURRENT_FOLDER}/external/com_google_cel_cpp"
        "./bazel-${CURRENT_FOLDER}/external/com_google_protobuf/src"
        "./bazel-${CURRENT_FOLDER}/external/com_github_fmtlib_fmt/include"
        "./bazel-${CURRENT_FOLDER}/external/com_github_eile_tclap/include"
        "./bazel-${CURRENT_FOLDER}/external/com_github_grpc_grpc/include"
        "./bazel-${CURRENT_FOLDER}/external/com_envoyproxy_protoc_gen_validate/"
        "./bazel-${CURRENT_FOLDER}/external/com_github_tencent_rapidjson/include/"
        "./bazel-${CURRENT_FOLDER}/external/com_github_datadog_dd_opentracing_cpp/include/"
        "./bazel-${CURRENT_FOLDER}/external/com_github_libevent_libevent/include"
        "./bazel-${CURRENT_FOLDER}/external/com_github_mirror_tclap/include"
        "./bazel-${CURRENT_FOLDER}/external/com_github_grpc_grpc"
        "./bazel-${CURRENT_FOLDER}/external/com_github_circonus_labs_libcircllhist/src/"
        "./bazel-${CURRENT_FOLDER}/external/com_github_nodejs_http_parser"
        "./bazel-${CURRENT_FOLDER}/external/com_github_nghttp2_nghttp2/lib/includes/"
        "./bazel-${CURRENT_FOLDER}/external/com_github_cyan4973_xxhash/"
        "./bazel-${CURRENT_FOLDER}/external/com_github_google_flatbuffers/include/"
        "./bazel-${CURRENT_FOLDER}/external/com_github_fmtlib_fmt/test"
        
        "./bazel-bin"
        "./bazel-bin/external/envoy_api"
        "./bazel-bin/external/mixerapi_git"
        "./bazel-bin/external/com_envoyproxy_protoc_gen_validate"
        "./bazel-bin/external/com_google_googleapis"
        "./bazel-bin/external/com_github_cncf_udpa"
)

target_include_directories(istio-proxy PUBLIC ${istio_include_dirs})

解释一下:

  • add_executable 将需要索引的源文件列表 (sourcefiles.txt) 加进索引。
  • target_include_directories 将用到的一些纯头文件目录加进索引(不包含实现代码,主要是一些接口),这里也是可以按需进行增删。

使用 CLion 阅读

不要直接打开 istio-proxy 目录,而是 Open 时选中 CMakeLists.txt,然后 Open as Project:

弹出 Load Project 时不要勾选 Clean project,不然退出 CLion 时会执行 make clean,导致把 bazel 生成的源文件都给删除掉,就没法跳转了:

然后就会开始索引,完成后就可以愉快的看代码了,先从 main 看起吧 (bazel-istio-proxy/external/envoy/source/exe/main.cc):

查找引用:

跳转到实现:

小结

本文介绍了如何使用 CLion 来阅读 istio-proxy (envoy) 的代码,包含源码结构分析、环境搭建,以及生成 CLion 所需要的 CMakeLists.txt 文件的方法,最后也展示了效果,希望对你有所帮助。

相关文章

KubeSphere 部署向量数据库 Milvus 实战指南
探索 Kubernetes 持久化存储之 Longhorn 初窥门径
征服 Docker 镜像访问限制!KubeSphere v3.4.1 成功部署全攻略
那些年在 Terraform 上吃到的糖和踩过的坑
无需 Kubernetes 测试 Kubernetes 网络实现
Kubernetes v1.31 中的移除和主要变更

发布评论