├── 2021 ├── 12 │ └── how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall │ │ ├── img │ │ ├── magic-firewall-00.png │ │ ├── magic-firewall-01.png │ │ └── magic-firewall-02.png │ │ └── index.md ├── 08 │ ├── calico-wireguard-support-with-azure-cni │ │ ├── 1.1.png │ │ ├── 2.1.png │ │ └── index.md │ ├── compute-a-brief-history │ │ ├── 0_H1N60ZgdO2FIJQlg.jpeg │ │ └── index.md │ ├── getting-started-with-eBPF-and-go │ │ ├── 1.1.png │ │ └── index.md │ └── making-javaScript-run-fast-on-webAssembly │ │ ├── 01-02-how-it-works.png │ │ ├── 01-03-how-it-works.png │ │ ├── 02-01-but-why.png │ │ ├── 02-02-interp02.png │ │ ├── 02-02-non-jit-devices.png │ │ ├── 02-03-cloud.png │ │ ├── 02-04-serverless-at-risk.png │ │ ├── 02-05-serverless-protected.png │ │ ├── 02-06-jit09.png │ │ ├── 03-01-office-handshake.png │ │ ├── 03-02-office-ikea.png │ │ ├── 03-03-office-kickoff.png │ │ ├── 03-04-office-kanban.png │ │ ├── 03-05-office-sequence.png │ │ ├── 04-01-startup-latency-vs-isolate.png │ │ ├── 04-02-wasm-file-copy-mem.png │ │ ├── 04-03-wasm-file-data-vs-code.png │ │ ├── 04-04-preinitiatlized-engine.png │ │ ├── index.md │ │ └── talk-stub-coverage.png └── 09 │ ├── an-ebpf-overview-part-1-introduction │ ├── eBPF-flowchart.png │ └── index.md │ ├── an-ebpf-overview-part-2-machine-bytecode │ └── index.md │ ├── an-ebpf-overview-part-3-walking-up-the-software-stack │ ├── eBPF-Part3-Diagram1.png │ ├── eBPF-Part3-Diagram2.jpg │ ├── eBPF-Part3-Diagram3.jpg │ └── index.md │ ├── an-ebpf-overview-part-4-working-with-embedded-systems │ ├── eBPF-Part4-Diagram1.jpg │ ├── eBPF-Part4-Diagram2.jpg │ ├── eBPF-Part4-Diagram3.jpg │ └── index.md │ ├── an-ebpf-overview-part-5-tracing-user-processes │ └── index.md │ └── how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF │ ├── Screen-Shot-2021-08-13-at-11.44.30-AM.png │ ├── Screen-Shot-2021-08-13-at-11.57.31-AM-1.png │ ├── Screen-Shot-2021-08-13-at-11.57.31-AM.png │ ├── Screen-Shot-2021-08-13-at-11.57.43-AM.png │ ├── Screen-Shot-2021-08-13-at-12.42.42-PM.png │ ├── Screen-Shot-2021-08-13-at-12.44.50-PM.png │ ├── Screen-Shot-2021-08-13-at-12.55.30-PM.png │ ├── eBPF-monitoring-1536x932.png │ └── index.md ├── .github └── ISSUE_TEMPLATE │ └── docs.yaml ├── LICENSE ├── PULL_REQUEST_TEMPLATE.md ├── README.md └── img └── kubesphere-swag.png /.github/ISSUE_TEMPLATE/docs.yaml: -------------------------------------------------------------------------------- 1 | name: 新增文章 2 | description: 新增一篇云原生领域相关文章,类型可以是翻译、原创和转载 3 | labels: [kind/translation] 4 | body: 5 | - type: markdown 6 | id: preface 7 | attributes: 8 | value: | 9 | ## 类型 10 | - type: input 11 | id: type 12 | validations: 13 | required: true 14 | attributes: 15 | label: 本文的类型为? 16 | description: 只可填写三个选项:翻译、原创、转载 17 | - type: markdown 18 | id: info 19 | attributes: 20 | value: | 21 | ## 文章信息 22 | - type: input 23 | id: original 24 | validations: 25 | required: true 26 | attributes: 27 | label: 原文链接 28 | description: "这里填写的是英文原文链接,例如:https://medium.com/@wajdi.fathallah/compute-a-brief-history-f65f4e7e5caf" 29 | - type: input 30 | id: time 31 | validations: 32 | required: true 33 | attributes: 34 | label: 原文发布时间 35 | description: "格式为:2021-08-18" 36 | - type: input 37 | id: author 38 | validations: 39 | required: true 40 | attributes: 41 | label: 作者 42 | - type: input 43 | id: label 44 | validations: 45 | required: true 46 | attributes: 47 | label: 标签 48 | description: "该 Issue 所属标签,例如 `kind/translation`" 49 | - type: markdown 50 | id: progress 51 | attributes: 52 | value: | 53 | ## 当前进度 54 | - type: input 55 | id: progress_1 56 | validations: 57 | required: true 58 | attributes: 59 | label: 已提交文章线索? 60 | description: 只能填写 `yes` 或者 `no` 61 | - type: input 62 | id: progress_2 63 | validations: 64 | required: true 65 | attributes: 66 | label: 已有人认领翻译或为原创文章? 67 | description: 只能填写 `yes` 或者 `no` 68 | - type: input 69 | id: progress_3 70 | validations: 71 | required: true 72 | attributes: 73 | label: 已提交 PR? 74 | description: 只能填写 `yes` 或者 `no` 75 | - type: input 76 | id: progress_4 77 | validations: 78 | required: true 79 | attributes: 80 | label: 正在review? 81 | description: 只能填写 `yes` 或者 `no` 82 | - type: input 83 | id: progress_5 84 | validations: 85 | required: true 86 | attributes: 87 | label: review 完成等待发布? 88 | description: 只能填写 `yes` 或者 `no` 89 | - type: input 90 | id: progress_6 91 | validations: 92 | required: true 93 | attributes: 94 | label: 已发布到 http://kubesphere.com.cn? 95 | description: 只能填写 `yes` 或者 `no` 96 | - type: input 97 | id: progress_7 98 | validations: 99 | required: true 100 | attributes: 101 | label: 已发布到 「KubeSphere 云原生」 微信公众号? 102 | description: 只能填写 `yes` 或者 `no` -------------------------------------------------------------------------------- /2021/08/calico-wireguard-support-with-azure-cni/1.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/calico-wireguard-support-with-azure-cni/1.1.png -------------------------------------------------------------------------------- /2021/08/calico-wireguard-support-with-azure-cni/2.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/calico-wireguard-support-with-azure-cni/2.1.png -------------------------------------------------------------------------------- /2021/08/calico-wireguard-support-with-azure-cni/index.md: -------------------------------------------------------------------------------- 1 | ``` 2 | 3 | --- 4 | original: https://thenewstack.io/calico-wireguard-support-with-azure-cni/ 5 | author: Peter Kelly 6 | translator: https://github.com/t819088691/ 7 | reviewer: 8 | title: Calico WireGuard Support with Azure CNI 9 | summary: 讲述 WireGuard 可以在 k8s 中使用的解决方案 10 | categories: kubernetes 11 | tags: calico 12 | originalPublishDate: 2021-08-12 13 | publishDate: 14 | --- 15 | 16 | ``` 17 | 18 | 19 | 20 | 21 | 22 | # 在 Azure CNI 中启用 Calico WireGuard 23 | 24 | 25 | 26 | 去年6月,[Tigera](https://www.tigera.io/?utm_content=inline-mention) 宣布首次在 k8s 上支持用于集群内加密传输的开源 VPN,[WireGuard](https://www.wireguard.com/) 。我们从来不喜欢坐以待毙,所以我们一直在努力为这项技术开发一些令人兴奋的新功能,其中第一个功能是使用 [Azure 容器网络接口](https://github.com/Azure/azure-container-networking/blob/master/docs/cni.md)(CNI)在 [Azure Kubernetes 服务](https://azure.microsoft.com/en-us/services/kubernetes-service/)(AKS)上支持WireGuard。 27 | 28 | 29 | 30 | 首先,这里简单回顾一下什么是 WireGuard 以及我们如何在 Calico 中使用它。 31 | 32 | WireGuard 是一种 VPN 技术,从 linux 5.6 内核开始默认包含在内核中,它被定位为 IPsec 和 OpenVPN 的替代品。它的目标是更加快速、安全、易于部署和管理。正如不断涌现的 SSL/TLS 的漏洞显示,密码的敏捷性会极大增加复杂性,这与 WireGuard 的目标不符,为此,WireGuard 故意将密码和算法的配置灵活性降低,以减少该技术的可攻击面和可审计性。它的目标是更加简单快速,所以使用标准的 Linux 网络命令便可以很容易的对它进行配置,并且只有约 4000 行代码,使得它的代码可读性高,容易理解和接受审查 33 | 34 | 35 | 36 | WireGuard 是一种 VPN 技术,通常被认为是 C/S 架构。它同样能在端对端的网格网络架构中配置使用,这就是 Tigera 设计的 WireGuard可以在 Kubernetes 中启用的解决方案。使用 Calico,所有启用 WireGuard 的节点将端对端形成一个加密的网格。Calico甚至支持在同一集群内同时包含启用 WireGuard 的节点与未启用 WireGuard 的节点,并且可以相互通信。 37 | 38 | 39 | 40 | ![](1.1.png) 41 | 42 | 43 | 44 | 我们选择 WireGuard 并不是一个折中的方案。我们希望提供最简单、最安全、最快速的方式来加密传输 Kubernetes 集群中的数据,而无需使用mTLS、IPsec 或其他复杂的配置。事实上,您可以把 WireGuard 看成是另一个具有加密功能的 overlay。 45 | 46 | 47 | 48 | 用户只需一条命令就可以启用 WireGuard,而 Calico 负责完成剩余的工作,包括: 49 | 50 | - 在每个节点创建 WireGuard 的网络接口 51 | - 计算并编写最优的 MTU 52 | - 为每个节点创建 WireGuard 公钥私钥对 53 | - 向每个节点添加公钥,以便在集群中共享资源 54 | - 为每个节点编写端对端节点 55 | - 使用防火墙标记(fwmark)编写IP route、IP tables和 Routing tables,以此正确处理各自节点上的路由 56 | 57 | 58 | 59 | 您仅需指明意图,其他的事情都由集群完成 60 | 61 | 62 | 63 | ## 使用 WireGuard 时数据包流量的情况 64 | 65 | 下图显示了启用 WireGuard 后集群中的各种数据包流量情况。 66 | 67 | 68 | 69 | ![](2.1.png) 70 | 71 | 72 | 73 | 同一主机上的 Pod: 74 | 75 | - 数据包被路由到 WireGuard 表。 76 | - 如果目标 IP 是同一主机上的 Pod,Calico 则在 WireGuard 路由表中插入一个 “ throw ” 条目,将数据包引导回主路由表。由此,数据包将被定向到目标 Pod 匹配的 veth 接口,并且它将在未加密的情况下流动(在图中以绿色显示)。 77 | 78 | 79 | 80 | 不同节点上的 Pod: 81 | 82 | - 数据包被路由到 WireGuard 表。 83 | 84 | - 路由条目与目标 Pod IP 匹配并发送到 WireGuard组件: cali.wireguard 85 | 86 | - WireGuard 组件加密并封装数据包(在图中以红色显示)并设置 fwmark 以防止路由环路。 87 | 88 | - WireGuard 组件使用与目标 Pod IP(允许的 IP)匹配的对等方的公钥对数据包进行加密,将其封装在 UDP 中,并使用特殊的 fwmark 对其进行标记以防止路由环路。 89 | 90 | - 数据包通过 eth0 发送到目标节点并解密。 91 | 92 | - 这也适用于主机流量(例如,节点联网的 Pod)。 93 | 94 | 95 | 96 | 在以下动画中,您可以看到 3 种流量: 97 | 98 | 1. 同一主机上 Pod 到 Pod 未被加密的流量。 99 | 2. 不同主机上的 Pod 到 Pod 被加密的流量。 100 | 3. 主机到主机的流量也会被加密。 101 | 102 | 103 | 104 | Key:绿色表示未加密流量,红色表示加密流量。 105 | 106 | ​ [动画演示](https://tigera.wistia.com/medias/ddl8bmhpgp?utm_source=thenewstack&utm_medium=website&utm_campaign=platform) 107 | 108 | 109 | 110 | ## WireGuard 在 AKS 的应用 111 | 112 | 在 AKS 上使用 Azure CNI 支持使用 WireGuard 带来了一些非常有趣的挑战。 113 | 114 | 首先,使用 Azure CNI 意味着不使用 Calico IPAM( IP 地址管理)和 CIDR(无类域间路由)块来分配 Pod IP 。相反,它们是采用与节点IP相同的分配方式从底层 VNet 进行分配。这对 WireGuard 路由来说是一个有趣的挑战,以往我们可以在 WireGuard 配置中的 Allowed IPs 列表中添加一个 CIDR 块,相比之下,我们现在必须写出该节点上所有 Pod IP。这需要 Calico 将 routeSource 的配置设为 workloadIPs。如果您使用的是我们的 operator 方式部署 AKS 集群,便无需额外配置。 115 | 116 | 使用 wireguard-tools 中优秀的工具 [wg](https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8),可以查看集群内端对端节点允许通过的 IP 列表,其中包括每个节点的 Pod IP 和主机 IP(注意终端 IP也在允许 IP 列表中)。在 AKS 集群上,WireGuard 提供了业务流量加密和主机到主机的加密。 117 | 118 | 119 | 120 | ```shell 121 | interface: wireguard.cali 122 | public key: bbcKpAY+Q9VpmIRLT+yPaaOALxqnonxBuk5LRlvKClA= 123 | private key: (hidden) 124 | listening port: 51820 125 | fwmark: 0x100000 126 | 127 | peer: /r0PzTX6F0ZrW9ExPQE8zou2rh1vb20IU6SrXMiKImw= 128 | endpoint: 10.240.0.64:51820 129 | allowed ips: 10.240.0.64/32, 10.240.0.65/32, 10.240.0.66/32 130 | latest handshake: 11 seconds ago 131 | transfer: 1.17 MiB received, 3.04 MiB sent 132 | 133 | peer: QfUXYghyJWDcy+xLW0o+xJVsQhurVNdqtbstTsdOp20= 134 | endpoint: 10.240.0.4:51820 135 | allowed ips: 10.240.0.4/32, 10.240.0.5/32, 10.240.0.6/32 136 | latest handshake: 46 seconds ago 137 | transfer: 83.48 KiB received, 365.77 KiB sent 138 | ``` 139 | 140 | 141 | 142 | 第二个挑战是正确处理 MTU(最大传输单元)。[Azure 设置的 MTU 是 1500](https://docs.microsoft.com/en-us/azure/virtual-network/virtual-network-tcpip-performance-tuning#azure-and-vm-mtu),而 WireGuard 在数据包上设置了一个 DF(Don't Fragment)标记。如果没有正确调整 WireGuard MTU,我们会在启用 WireGuard 时发现有丢包和低带宽。我们可以在 AKS 中通过 Calico 设置为自动检测,从而为 WireGuard 配置正确的开销和 [MTU](https://docs.projectcalico.org/networking/mtu) 来优化。 143 | 144 | 我们还可以将节点 IP 本身添加为端对端节点允许通信的 IP ,并通过 AKS 中的 WireGuard 处理主机联网的 Pod 和主机到主机通信。主机到主机通信的方法是,当 [RPF](https://en.wikipedia.org/wiki/Reverse-path_forwarding)(反向路径转发)发生时,通过 WireGuard 接口获得路由返回的响应。通过在目的节点的接收数据包上设置一个标记,并配置内核确保遵守 sysctl 中的 RPF 标记来解决这个问题。 145 | 146 | 现在您使用AKS时,节点之间的业务流量和主机到主机通信都会被加密。您仅需指明意图,其他的事情都由集群完成。 147 | -------------------------------------------------------------------------------- /2021/08/compute-a-brief-history/0_H1N60ZgdO2FIJQlg.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/compute-a-brief-history/0_H1N60ZgdO2FIJQlg.jpeg -------------------------------------------------------------------------------- /2021/08/compute-a-brief-history/index.md: -------------------------------------------------------------------------------- 1 | ``` 2 | --- 3 | original: https://medium.com/@wajdi.fathallah/compute-a-brief-history-f65f4e7e5caf 4 | author: Wajdi Fathallah 5 | translator: https://github.com/klaylin 6 | reviewer: 7 | title: 计算简史 8 | summary: 讲述计算机从物理机,虚拟机到容器技术的过程 9 | categories: 译文 10 | tags: 容器,云技术,虚拟化 11 | originalPublishDate: 2021-08-3 12 | publishDate: 13 | --- 14 | ``` 15 | 16 | # 计算简史 17 | ![](0_H1N60ZgdO2FIJQlg.jpeg) 18 | 19 | 你是否考虑过,为什么容器技术的传播会如此广泛?为什么会成为迁移和部署应用程序的标准? 20 | 21 | 我经常被问到这些问题。而我是通过讲述计算机的历史来回答这个问题,因为可以从中突出容器技术越来越受欢迎的原因。 22 | 23 | ## 物理机时代 24 | 几十年前,一个公司的 IT 部门负责运维一组物理服务器。而这些物理服务器通常就是安装在公司所在的场所。 25 | 26 | 在这段时期,软件主要是基于这个公司的特定需求来进行开发,通常就是一些模块加上数据库服务。 27 | 28 | 当时的软件部署是一个相对复杂的过程。首先开发团队需要将程序开发出来并将其交付给计算基础架构团队。后者需要根据程序所需的内存,存储等资源来进行计算环境的配置,最后完成软件的部署。 29 | 30 | 所以,对软件部署的灵活性以及弹性的需求促进了虚拟化的兴起。 31 | 32 | 虚拟化技术的发展使得不同的计算环境可以共享同一套计算机物理基础设施,从而可以更灵活地管理计算资源。 33 | 34 | 但是仅靠虚拟化不足以解决未来会出现的问题。随着部署的应用程序越来越多,需要的服务器也越来越多,维护成本以及数据中心的场地、设备等前期投资都在不断增长。 35 | 36 | 37 | ## 云计算时代 38 | 简单来说,云计算是一种网络上的基础设施服务。用户不再需要自己购买部署物理设备,通过以太网就可以使用到计算服务。 39 | 40 | 随着需要对更高的弹性,灵活性作出可持续发展的承诺,越来越多的公司使用云计算服务。 41 | 42 | 云计算的使用也产生了积极的影响,应用程序的交付变得更快更多,因为开发周期变得更加流畅。 43 | 44 | 所以公司可以部署更多的应用程序,可以存储和处理更多的数据。 45 | 46 | 云计算也推动了横向扩展技术的出现。 47 | 48 | 先来了解一下可扩展性。可扩展性有两种,分别是横向扩展和纵向扩展。纵向扩展通常是指在一个机柜式物理服务器上添加更多的硬件资源。有趣的是,除了技术上的限制,纵向扩展还会受到天花板高度的限制。而另一方面,横向扩展允许用户加入更多的机柜节点,多个物理服务器形成一个同一的计算资源池,资源池的性能由多个节点共同提供。相比较来看,横向扩展不会受到硬件限制。 49 | 50 | 虽然横向扩展没有硬件限制,但是在这种架构下的灵活性以及如何对集群的节点故障都是需要重新考虑的问题。在这种情况下,容器技术可以成为解决方案。 51 | 52 | ## 容器时代 53 | 让我们先看一下容器的定义是什么:从最简单的概念来说,容器是一个轻量级的,独立的可执行软件包,容器中包含着应用程序所需的一切。 54 | 55 | 容器化时代的重点是如何让应用程序的部署具有可移植性,速度更快,扩展性更强。一个新的术语伴随着横向扩展产生:集群。现在讨论更多的不再是一个机器,而是集群。集群通常形容在同一个网络环境中,协作执行分布式工作负载的计算节点的集合。 56 | 57 | 毫无疑问,横向扩展促进了大规模应用程序的部署,同时也带来了更高的复杂性。连接,同步,维护集群中的机器是一个巨大的挑战,除此之外还有更多的节点也就带来更多的故障风险。为了克服这个问题,就需要一个更轻量级的,更灵活的系统环境来快速高效地适应负载变化以及故障切换等事件。 58 | 59 | 启动一个容器远远比创建一个虚拟机系统,安装程序所需要的依赖,将虚拟机加入分布式集群这个繁重过程来得简单。 60 | 61 | 而越来越多的容器同样会带来维护和编排的难题。Kubernetes 这一技术的出现,解决了管理编排容器的问题,减少了管理大量容器的复杂性。 62 | 63 | 可以用巴黎地铁网络来形容 Kubernetes:将容器类比成一列一列的地铁,而 Kubernetes 就像是车站,交通灯,控制室等协调地铁运行的基础设施。 64 | 65 | Kubernetes 运行在多个节点的集群之上。通常是在用户的云空间(AWS、GCP、Azure 或其他)中配置的机器。而云服务供应商也嗅到了提供 Kubernetes 托管集群的商机,这可以使得团队更好地专注于应用的开发交付而不用花更多时间来对 Kubernetes 集群进行管理。 66 | 67 | ## 混合云时代 68 | 混合云时代是指不仅限于一个,而是同时使用多个云服务提供商的产品。例如在结合了 AWS、Azure 和 GCP 的服务、成本优化和规模的云上运行您的工作负载。而从更高的层面来说,只需要管理容器应用而不再需要管理云服务。 69 | 70 | 从技术上来说,如果坚持使用容器技术,以上所说的是能够发生的。实际上就是一个使用多个云服务提供商提供的系统构建的 Kubernetes 集群。唯一的要求就是将所有的机器在网络上连接起来,这不是什么新技术的革新,仅仅是联网。 71 | 72 | GCP 多年前就有这个愿景,他们为此设计了 Anthos。这是一段 5 分钟的介绍短片: https://www.youtube.com/watch?v=Qtwt7QcW4J8 73 | 74 | 如上所述,混合云时代是计算进化过程上的一条必经之路,我很确定还有更多的道路。从你的角度来看,计算的未来会是怎样的?欢迎在下面留言或者发表你的看法到 wajdi@siffletapp.com。 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /2021/08/getting-started-with-eBPF-and-go/1.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/getting-started-with-eBPF-and-go/1.1.png -------------------------------------------------------------------------------- /2021/08/getting-started-with-eBPF-and-go/index.md: -------------------------------------------------------------------------------- 1 | ``` 2 | 3 | --- 4 | original: https://networkop.co.uk/post/2021-03-ebpf-intro/ 5 | author: Michael Kashin 6 | translator: https://github.com/bigdimple 7 | reviewer: 8 | title: Getting Started with eBPF and Go 9 | summary: 使用GO开始入门eBPF 10 | categories: kubernetes 11 | tags: eBPF/go 12 | originalPublishDate: 2021-09-06 13 | publishDate: 2021-09-29 14 | --- 15 | 16 | ``` 17 | 18 | # 用GO开始入门eBPF 19 | 20 | [eBPF](https://ebpf.io/what-is-ebpf/) 本身及其周边应用,包括 [XDP](https://github.com/xdp-project/xdp-tutorial) 拥有一个蓬勃发展的生态系统,同时也拥有大量的教程资料。每当用户在选择库以及工具与 eBPF 进行交互和编排时候,往往会无从下手。 21 | 因为你必须在基于 python 的 [BCC](https://github.com/iovisor/bcc) 框架或者基于 c 的 [libbpf](https://github.com/libbpf/libbpf) ,以及来自 [Dropbox](https://github.com/dropbox/goebpf)、 [Cilium](https://github.com/cilium/ebpf)、 [Aqua](https://github.com/aquasecurity/tracee/tree/main/libbpfgo) 和 [Calico](https://github.com/projectcalico/felix/tree/master/bpf) 等一系列基于 go 的库之间进行选择。 22 | 另一个经常被忽略的重要领域是 eBPF 代码的“生产化”,即从手动检测示例转向生产级应用程序,如 Cilium程序。 23 | 在这篇文章中,我将记录我在这方面的一些心得,尤其是使用 Go 编写的用户空间控制器的网络(XDP)应用程序时的经验。 24 | 25 | ## 选择一个 eBPF 库 26 | 27 | 在大多数情况下,eBPF 库可以帮助你实现以下两点: 28 | 29 | * **加载 eBPF 程序和 map** 到内核中,然后执行[重定位](https://kinvolk.io/blog/2018/10/exploring-bpf-elf-loaders-at-the-bpf-hackfest/#common-steps),通过文件描述符将 eBPF 程序的 map 正确的关联起来。 30 | * **与 eBPF map 交互**,允许对存储在这些 map 中的 key/value 执行所有标准的 CRUD 操作。 31 | 32 | 有些库还可以将 eBPF 程序附加到特定的 [hook](https://ebpf.io/what-is-ebpf/#hook-overview) 上,对于网络用例来说,可以很容易的使用任何 netlink API 库来完成。 33 | 所以在选择 eBPF 库时,我并不是唯一一个感到困惑的人(参考[[1]](https://k8s.networkop.co.uk/cni/cilium/#a-day-in-the-life-of-a-packet)和[[2]](http://arthurchiao.art/blog/cilium-life-of-a-packet-pod-to-service/))。每个 librarie 都有自己的适用范围和限制条件: 34 | 35 | * [Calico](https://github.com/projectcalico/felix/tree/master/bpf) 实现了一个围绕由 [bpftool](https://twitter.com/qeole/status/1101450782841466880) 和 iproute2 制作的 CLI 命令的 Go 包装器。 36 | * [Aqua](https://github.com/aquasecurity/tracee/tree/main/libbpfgo) 实现了一个围绕 libbpf c 库的 Go 包装器 37 | * [Dropbox](https://github.com/dropbox/goebpf) 支持一小部分程序,但有一个非常干净和方便的用户 API 38 | * IO Visor 的 [gobpf](https://github.com/iovisor/gobpf) 是 BCC 框架的 go 绑定集合,它更侧重于跟踪和分析 39 | * [Cilium 和 Cloudflare](https://github.com/cilium/ebpf) 维护的一个[Go语言的库](chrome-extension://ikhdkkncnoglghljlkmcimlnlhkeamad/pdf-viewer/web/viewer.html?file=https%3A%2F%2Flinuxplumbersconf.org%2Fevent%2F4%2Fcontributions%2F449%2Fattachments%2F239%2F529%2FA_pure_Go_eBPF_library.pdf)(在下面称为 libbpf-Go) ,将所有 eBPF 系统调用抽象到本地 Go 接口之后。 40 | 41 | 对于我的特定于网络的用例,虽然我很喜欢 Dropbox(因为简单),而且之前也用过它。但我最终使用了 libbpf-go,因为 Cilium 和 Cloudflare 使用的就是这个库,而且它有一个活跃的社区。 42 | 43 | 为了熟悉开发过程,我决定实现一个 XDP 交叉连接应用程序,它在网络拓扑模拟中有一个非常合适而且重要的[用例](https://netdevops.me/2021/transparently-redirecting-packets/frames-between-interfaces/)。我们的目标是让应用程序监视配置文件,并确保本地接口根据该文件中的 YAML 规范相关联。下面是对 xdp-xconnect 工作原理的高级概述: 44 | 45 | ![](1.1.png) 46 | 47 | 48 | 以下部分将逐步描述应用程序构建和交付过程,更多地关注集成,而较少关注实际代码。可以在Github上找到 Xdp-xconnect 完整的代码。 49 | 50 | 51 | ## 第一步:编写 eBPF 代码 52 | 53 | 一般这是任何“使用 eBPF 入门”文章的主要部分,但这一次它不是重点。我不认为我能教别人如何写 eBPF,但是我可以提供一些非常好的参考资源,希望对你有所帮助: 54 | * 学习常用的 eBPF 理论和一些技术细节点,在 [eBPF.io](https://ebpf.io/what-is-ebpf/) 网站中以及 Cilium 中的 eBPF 和 XDP [参考指南](https://docs.cilium.io/en/stable/bpf/)中。 55 | * 使用 eBPF 和 XDP 进行一些实践操作的最佳参考项目是 [XDP-tutorial](https://github.com/xdp-project/xdp-tutorial)。这是个非常棒的参考项目,即使你最终没有完成任务,也绝对值得一读。 56 | * Cilium 的[源代码](https://github.com/cilium/cilium/tree/master/bpf)及其在[[1]](https://k8s.networkop.co.uk/cni/cilium/#a-day-in-the-life-of-a-packet)和[[2]](http://arthurchiao.art/blog/cilium-life-of-a-packet-pod-to-service/)中的分析。 57 | 58 | 我的 eBPF 程序非常简单,它由一个对 eBPF [helper function](https://man7.org/linux/man-pages/man7/bpf-helpers.7.html) 的调用组成,该函数根据输入接口的索引将所有数据包从一个接口重定向到另一个接口。 59 | 60 | ```yaml 61 | #include 62 | #include 63 | SEC("xdp") 64 | int xdp_xconnect(struct xdp_md *ctx) 65 | { 66 | return bpf_redirect_map(&xconnect_map, ctx->ingress_ifindex, 0); 67 | } 68 | ``` 69 | 70 | 为了编译上面的程序,我们需要为所有包含的头文件提供搜索路径。最简单的方法是复制 [linux/tools/lib/bpf/](https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/tree/tools/lib/bpf) 下的所有文件,但是这包括了许多不必要的文件。因此,另一种方法是创建一个依赖项列表: 71 | ```yaml 72 | $ clang -MD -MF xconnect.d -target bpf -I ~/linux/tools/lib/bpf -c xconnect.c 73 | ``` 74 | 现在,我们只能对 xconnect.d 中指定的某些文件进行本地复制,并使用以下命令为本地 CPU 架构编译 eBPF 代码: 75 | ```yaml 76 | $ clang -target bpf -Wall -O2 -emit-llvm -g -Iinclude -c xconnect.c -o - | \ 77 | llc -march=bpf -mcpu=probe -filetype=obj -o xconnect.o 78 | ``` 79 | 得到的 ELF 文件是我们下一步需要提供给 Go 库的。 80 | 81 | ## 第二步:编写 Go 代码 82 | 通过 libbpf 加载已编译的 eBPF 程序和 map 只需要几条指令。通过添加带 ebpf 标签的结构,我们可以自动化重定位过程,这样我们的程序就知道在哪里找到它的 map。 83 | ```yaml 84 | spec, err := ebpf.LoadCollectionSpec("ebpf/xconnect.o") 85 | if err != nil { 86 | panic(err) 87 | } 88 | 89 | var objs struct { 90 | XCProg *ebpf.Program `ebpf:"xdp_xconnect"` 91 | XCMap *ebpf.Map `ebpf:"xconnect_map"` 92 | } 93 | if err := spec.LoadAndAssign(&objs, nil); err != nil { 94 | panic(err) 95 | } 96 | defer objs.XCProg.Close() 97 | defer objs.XCMap.Close() 98 | ``` 99 | Type ebpf.Map 有一组方法,这些方法对加载的 Map 的内容执行标准的 CRUD 操作: 100 | ```yaml 101 | err = objs.XCMap.Put(uint32(0), uint32(10)) 102 | 103 | var v0 uint32 104 | err = objs.XCMap.Lookup(uint32(0), &v0) 105 | 106 | err = objs.XCMap.Delete(uint32(0)) 107 | ``` 108 | Libbpf-go 没有涵盖的步骤是将程序附加到 network hook 上。然而任何现有的网络链接库,例如 [vishvananda/netlink](https://github.com/vishvananda/netlink),都可以通过将网络链接与加载程序的文件描述符关联起来,轻松实现这一点: 109 | ```yaml 110 | link, err := netlink.LinkByName("eth0") 111 | err = netlink.LinkSetXdpFdWithFlags(*link, c.objs.XCProg.FD(), 2) 112 | ``` 113 | 注意,我使用 [SKB_MODE](https://github.com/torvalds/linux/blob/master/tools/include/uapi/linux/if_link.h#L966) XDP 标志来解决退出的驱动程序[caveat](https://github.com/xdp-project/xdp-tutorial/tree/master/packet03-redirecting#sending-packets-back-to-the-interface-they-came-from)。虽然本机 XDP 模式比任何其他 eBPF hook 都要[快很多](https://www.netronome.com/media/images/fig3.width-800.png),但 SKB_MODE 可能不会那么快,因为网络堆栈必须预先解析包头([参见视频](https://www.youtube.com/watch?v=q3gjNe6LKDI))。 114 | 115 | 116 | ## 第三步:代码分发 117 | 118 | 此时,如果我们的应用程序没有出现问题 —— 根据 eBPF [代码可移植性](https://facebookmicrosites.github.io/bpf/blog/2020/02/19/bpf-portability-and-co-re.html#the-problem-of-bpf-portability),那么一切应该准备就绪,可以进行打包和发布了。 119 | 120 | 从历史上看,这个过程包括将 eBPF 源代码复制到目标平台,提取所需的内核头并为特定的内核版本编译它。这个问题在跟踪/监视/分析用例时尤其明显,这些用例可能需要访问几乎任何内核数据结构,因此唯一的解决方案是引入另一个间接层([参见 CO-RE](https://facebookmicrosites.github.io/bpf/blog/2020/02/19/bpf-portability-and-co-re.html))。 121 | 122 | 另一方面,网络用例依赖于一个相对较小且稳定的内核类型子集,因此它们不会遇到跟踪和分析对应方所遇到的同类问题。根据我目前看到的,两种最常见的代码打包方法是: 123 | * 将 eBPF 代码与所需的 kernel headers 一起发布,前提是它们与底层内核匹配([参见 Cilium](https://github.com/cilium/cilium/tree/master/bpf)) 124 | * 发布 eBPF 代码并提取目标平台上的 kernel headers 文件 125 | 126 | 在这两种情况下,eBPF 代码仍然是在目标平台上编译的,这是在用户空间应用程序启动之前需要执行的额外步骤。 127 | 128 | 还有一种替代方法是预编译 eBPF 代码,只发送 ELF 文件。这正是 bpf2go 可以做到的,它可以将编译后的代码嵌入到 Go 包中。它依靠 go generate 生成一个[新文件](https://github.com/networkop/xdp-xconnect/blob/main/pkg/xdp/xdp_bpf.go),其中包含已编译的 eBPF 和 libbpf-go 框架代码,唯一的要求是需要 //go: generate 指令。一旦生成,我们的 eBPF 程序可以只加载几行代码(注意没有任何参数) : 129 | ```yaml 130 | specs, err := newXdpSpecs() 131 | objs, err := specs.Load(nil) 132 | ``` 133 | 这种方法的明显好处是:我们不再需要在目标机器上进行编译,可以将 eBPF 和用户空间 Go 代码同时发布在一个包或 Go 二进制文件中。还有一个优点是,它允许我们不仅将应用程序作为二进制文件使用,而且还可以将其导入到任何第三方 Go 应用程序中([参见使用示例](https://github.com/networkop/xdp-xconnect#usage))。 134 | 135 | ## 阅读一些参考资料 136 | 137 | 通用理论知识: 138 | ``` 139 | Https://github.com/xdp-project/xdp-tutorial 140 | Https://docs.cilium.io/en/stable/bpf/ 141 | Https://qmonnet.github.io/whirl-offload/2016/09/01/dive-into-bpf/ 142 | ``` 143 | BCC 和 libbpf: 144 | ``` 145 | Https://facebookmicrosites.github.io/bpf/blog/2020/02/20/bcc-to-libbpf-howto-guide.html 146 | Https://nakryiko.com/posts/libbpf-bootstrap/ 147 | Https://pingcap.com/blog/why-we-switched-from-bcc-to-libbpf-for-linux-bpf-performance-analysis 148 | Https://facebookmicrosites.github.io/bpf/blog/ 149 | ``` 150 | eBPF/XDP 性能相关: 151 | ``` 152 | Https://www.netronome.com/blog/bpf-ebpf-xdp-and-bpfilter-what-are-these-things-and-what-do-they-mean-enterprise/ 153 | ``` 154 | linux 内核编码方式: 155 | ``` 156 | Https://www.kernel.org/doc/html/v5.9/process/coding-style.html 157 | ``` 158 | Libbpf-go 示例程序: 159 | ``` 160 | https://github.com/takehaya/goxdp-template 161 | https://github.com/hrntknr/nfNat 162 | https://github.com/takehaya/Vinbero 163 | https://github.com/tcfw/vpc 164 | Https://github.com/florianl/tc-skeleton 165 | https://github.com/cloudflare/rakelimit 166 | Https://github.com/b3a-dev/ebpf-geoip-demo 167 | ``` 168 | Bpf2go: 169 | ``` 170 | Https://github.com/lmb/ship-bpf-with-go 171 | Https://pkg.go.dev/github.com/cilium/ebpf/cmd/bpf2go 172 | ``` 173 | XDP 示例程序: 174 | ``` 175 | Https://github.com/cpmarvin/lnetd-ctl 176 | Https://gitlab.com/mwiget/crpd-l2tpv3-xdp 177 | ``` 178 | 179 | 180 | -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/01-02-how-it-works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/01-02-how-it-works.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/01-03-how-it-works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/01-03-how-it-works.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/02-01-but-why.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/02-01-but-why.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/02-02-interp02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/02-02-interp02.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/02-02-non-jit-devices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/02-02-non-jit-devices.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/02-03-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/02-03-cloud.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/02-04-serverless-at-risk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/02-04-serverless-at-risk.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/02-05-serverless-protected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/02-05-serverless-protected.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/02-06-jit09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/02-06-jit09.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/03-01-office-handshake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/03-01-office-handshake.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/03-02-office-ikea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/03-02-office-ikea.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/03-03-office-kickoff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/03-03-office-kickoff.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/03-04-office-kanban.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/03-04-office-kanban.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/03-05-office-sequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/03-05-office-sequence.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/04-01-startup-latency-vs-isolate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/04-01-startup-latency-vs-isolate.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/04-02-wasm-file-copy-mem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/04-02-wasm-file-copy-mem.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/04-03-wasm-file-data-vs-code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/04-03-wasm-file-data-vs-code.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/04-04-preinitiatlized-engine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/04-04-preinitiatlized-engine.png -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | original: https://bytecodealliance.org/articles/making-javascript-run-fast-on-webassembly 3 | author: Lin Clark 4 | translator: https://github.com/1020973418 5 | reviewer: 6 | title: Making JavaScript run fast on WebAssembly 7 | summary: 阐述 JavaScript 如何在 WebAssembly 上快速的运行 8 | categories: 译文 9 | tags: ["kind/translation","WebAssembly"] 10 | originalPublishDate: 2021-06-02 11 | publishDate: 12 | --- 13 | 14 | # 让 JavaScript 在 WebAssembly 上快速运行 15 | 16 |

2021 年 6 月 2 日 林 · 克拉克

17 | 18 | 浏览器中 JavaScript 的运行速度相较于20年前快了很多倍。那是因为浏览器服务厂商花费了很多时间去做了相关方面密集的性能优化。 19 | 20 | 今天,我们开始为完全不同的环境优化 JavaScript 的性能,并在这些环境中应用不同的规则。因为有了WebAssembly,这是可能的! 21 | 22 | 这里我们应该明白 —— 如果你在浏览器中运行 JavaScript ,简单的部署 JS 仍然是最有意义的。浏览器中的 JS 引擎经过了高度调优,以运行交付给它们的 JS 。 23 | 24 | 但是,如果你在 Serverless function 中运行 JS 呢?又或者,如果你想将 JS 运行到像 iOS 或游戏机那样不允许一般 JIT( just-in-time ) 编译环境中呢? 25 | 26 | 对于这些用例,你需要去关注 JS 优化的新浪潮。并且,这项工作也可以服务于其他编程语言模型,例如Python , Ruby 和 Lua,它们也想在这些环境中快速运行。 27 | 28 | 但是,在我们探索这种方法如何快速运行之前,我们需要先了解 WebAssembly 的基本工作原理。 29 | 30 | ## WebAssembly 如何工作? 31 | 32 | 每当您运行 JS 时, JS 源代码都需要以一种或其他方式作为机器代码去执行。这是由 JS 引擎使用解释器和 JIT 编译器等多种技术完成的。(更多详细信息,请参阅[即时( JIT )编译器中的速成课程](https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/)) 33 | 34 | ![](02-02-interp02.png) 35 | 36 | 但是,如果您的目标平台没有 JS 引擎怎么办呢?那么你需要在你的代码中部署一个 JS 引擎。 37 | 38 | 为了做到这一点,我们把 JS 引擎部署成为一个 WebAssembly 模块,这让 JS 引擎可以跨越不同类型的计算机体系结构进行移植。并且有了 WASI ,我们也可以让 JS 引擎在不同的操作系统之间进行移植。 39 | 40 | 这意味着整个 JS 环境都将绑定到这个 WebAssembly 实例中。一旦部署了这一实例,你需要做的就是输入 JS 代码,然后实例就会运行该 JS 代码。 41 | 42 | ![](01-02-how-it-works.png) 43 | 44 | JS 引擎不是直接在机器的内存中工作,而是将从字节码到字节码正在操纵 GCed 对象在内的所有内容都放在 Wasm 模块的线性内存中。 45 | 46 | ![](01-03-how-it-works.png) 47 | 48 | 我们使用了 Firefox 的 SpiderMonkey 作为 JS 引擎。SpiderMonkey 是具有业界实力的 JS 虚拟机之一,并经过了浏览器的 “ 实战演练 ”。当你运行或者处理不受信任的输入代码时,这种针对安全的 ” 实战演练 “ 和投入很重要。 49 | 50 | SpiderMonkey 还使用了一种精确堆栈扫描的技术,这对于我在下面解释一些优化十分重要。因为来自于三个不同的组织(Fastly,Mozilla & Igalia)的 BA 成员在为此进行通力协作,SpiderMonkey 还有一个非常容易上手的代码库,这也很重要 51 | 52 | 到目前为止,我所描述的方法没有任何革命性!人们已经这样使用 WebAssembly 运行了 JS 很多年了。 53 | 54 | 但问题是这样很慢, WebAssembly 不允许你动态生成新的机器代码并在纯 WebAssembly 代码中去运行它。这意味着你不可以使用 JIT 。你只可以使用解释器。 55 | 56 | 鉴于此种约束,你可能会发问...... 57 | 58 | ## 但你为何这样做? 59 | 60 | 因为 JIT 是浏览器让 JS 快速运行的方式(并且由于你不能够在 WebAssembly 模块内部进行 JIT 编译),因此这样处理似乎违反常识。 61 | 62 | ![](02-01-but-why.png) 63 | 64 | 但是,即便这样,如果我们能够让 JS 运行的更快呢? 65 | 66 | 让我们去看几个用例,在这些用例中,这种方法的快速版本可能真的有用。 67 | 68 | ## 在 iOS 上运行 JS (和其他 JIT 限制环境) 69 | 70 | 出于安全方面的考虑,有些场景不能使用 JIT —— 例如,非特权的 iOS 应用程序和一些智能电视 & 游戏机。 71 | 72 | ![](02-02-non-jit-devices.png) 73 | 74 | 在这些平台上,你必须使用解释器。但是,在这些平台上长时间运行着各种应用,并且他们需要大量的代码......而这些正是过去你不想使用解释器的情形,因为会减慢执行。 75 | 76 | 如果我们的方法更快,那么这些开发者就可以在无 JIT 平台上使用 JS,且不会对性能造成巨大影响。 77 | 78 | ## Serverless 瞬间冷启动 79 | 80 | 在其他地方,JIT 不是问题,但启动时间是问题,比如在 Serverless functions 下。这就是所谓的冷启动延迟问题。 81 | 82 | ![](02-03-cloud.png) 83 | 84 | 即使你使用的是最合适的 JS 环境 —— 一个只启动裸 JS 引擎的隔离服务 —— 你也会看到至少大约5ms的启动延迟。这甚至不包含初始化应用所需要的时间。 85 | 86 | 有一些方法可以隐藏传入请求的启动延迟。但是随着连接时间在网络层中得到优化(例如 QUIC 等提案),隐藏这一点变得更困难。当你把多个 Serverless functions 链接到一起时,将更难隐藏这一点。 87 | 88 | 使用这些技术延迟的平台也经常在请求之间复用实例。在一些情况下,这意味着可以在不同请求之间观察到全局状态,这是一个安全隐患。 89 | 90 | 因为这种冷启动问题,开发者经常不遵循最佳实践。他们将大量 functions 放入一个 Serverless deployment。这导致了另一个安全问题 —— 更大的威胁攻击面。如果 Serverless deployment 的一部分弱点被利用,攻击者就可以访问该 deployment 的中的所有内容。 91 | 92 | ![](02-04-serverless-at-risk.png) 93 | 94 | 但是如果我们可以在这些环境下使 JS 启动时间足够低,那我们是不需要用任何技巧来隐藏启动延迟。我们可以在几微妙内启动一个实例。 95 | 96 | 有了这个,我们可以为每一个请求提供一个实例,这意味着请求之间没有状态。 97 | 98 | 并且因为实例非常轻量,开发者们能够把他们的代码拆分成细粒度的代码片段,将任何单代码片段的威胁攻击面降至最低。 99 | 100 | ![](02-05-serverless-protected.png) 101 | 102 | 这种方法还有另一个安全优势。除了轻量和细粒度的代码隔离之外, WebAssembly 引擎提供的安全边界更加可靠。 103 | 104 | 由于用于创建隔离的 JS 引擎是大型代码库,包含了大量进行超复杂优化的底级代码,因此很容易引入漏洞,使攻击者能够逃脱 VM(虚拟机)并访问 VM 运行的系统。这就是 [Chrome](https://www.chromium.org/Home/chromium-security/site-isolation) 和 [Firefox](https://blog.mozilla.org/security/2021/05/18/introducing-site-isolation-in-firefox/) 等浏览器竭尽全力确保网站在完全分离的进程中运行的原因。 105 | 106 | 相比之下, WebAssembly 引擎需要的代码要少的多,因此更容易代码审计,而且有许多代码使用 Rust(一种内存安全的编程语言) 编写的。那么[可以验证](http://cseweb.ucsd.edu/~dstefan/pubs/johnson:2021:veriwasm.pdf)从 WebAssembly 模块生成的本机内存隔离的二进制文件。 107 | 108 | 通过 WebAssembly 引擎内部运行 JS 引擎,我们将这个更安全的外部沙箱边界作为另一道防线。 109 | 110 | 因此,对于这些用例,在 WebAssembly 上快速使用 JS 有很大的好处。但是我们该怎么做呢? 111 | 112 | 为了解决这个问题,我们需要了解 JS 引擎把时间消耗在了哪里。 113 | 114 | ## JS 引擎时间花费在了两个地方 115 | 116 | 我们可以将 JS 引擎所做的工作大致分为两部分:初始化 & 运行时。 117 | 118 | 我认为 JS 引擎是一个承包商。这个承包商被保留来完成一项工作:运行 JS 代码并获得运行结果。 119 | 120 | ![](03-01-office-handshake.png) 121 | 122 | ## 初始化阶段 123 | 124 | 在这个承包商真正开始运行项目之前,需要准备一些初步工作。初始化阶段包括开始执行时只需要发生一次的每一件事情。 125 | 126 | ### 应用初始化 127 | 128 | 对于任何项目,浏览器都需要查看客户端希望其完成的工作,并且设置完成该任务所需要的的资源。 129 | 130 | 例如,浏览器通读项目简报和其他支持文档,并将其转化为可以处理的内容,比如建立一个项目管理系统,包括其中存储和组织的所有文档。 131 | 132 | ![](03-03-office-kickoff.png) 133 | 134 | 在 JS 引擎的情况下,这项工作看起来跟像是通读源代码的顶层并将 functions 解析成字节码,为已声明的变量分配内存,并在已定义的地方设置值。 135 | 136 | ### 引擎初始化 137 | 138 | 在某些环境下,例如 Serverless,初始化还有另一部分发生在每个应用初始化之前。 139 | 140 | 这是引擎初始化。首先需要启动 JS 引擎本身,并需要在环境中添加内置函数。 141 | 142 | 我认为这就像在开始工作之前设置办公室 —— 做些像组装 IKEA 椅子和桌子等的事情。 143 | 144 | ![](03-02-office-ikea.png) 145 | 146 | 这可能需要相当长的时间,并且是 Serverless 用例冷启动问题的一部分。 147 | 148 | ## 运行阶段 149 | 150 | 一旦完成初始化阶段,JS 引擎就开始运行代码了。 151 | 152 | ![](03-04-office-kanban.png) 153 | 154 | 这部分工作的速度被称为吞吐量,吞吐量受许多不同的因素的影响。例如: 155 | 156 | - 使用了哪些语言特性 157 | - 从 JS 引擎角度来看,代码行为是否可以预测 158 | - 使用什么样的数据结构 159 | - 代码运行时间是否足够受益于 JS 引擎的优化编译器 160 | 161 | 这是 JS 引擎花费时间的两个阶段。 162 | 163 | ![](03-05-office-sequence.png) 164 | 165 | 如何让这两个阶段的工作更快? 166 | 167 | ## 大幅减少初始化时间 168 | 169 | 我们首先使用名为 [Wizer](https://github.com/bytecodealliance/wizer) 的工具快速进行初始化。下面我将补充 Wizer 如何运行,但对于那些不耐烦的人,你可以理解这是我们在运行一个非常简单的 JS 应用时看到的加速。 170 | 171 | ![](04-01-startup-latency-vs-isolate.png) 172 | 173 | 使用 Wizer 运行这个小应用程序时,只需要 0.36 毫秒(或 360 微秒)。这比我们对 JS 隔离方法所期望的速度快 13 倍以上。 174 | 175 | 我们使用称为快照的东西来获得这种快速启动。Nick Fitzgerald 在他[关于 Wizer 的 WebAssembly 峰会演讲](https://youtu.be/C6pcWRpHWG0?t=1338)中更详细地解释了所有这些。 176 | 177 | 那么这是如何工作的呢?在部署代码之前,作为构建步骤的一部分,我们使用 JS 引擎运行 JS 代码直到初始化结束。 178 | 179 | 至此,JS 引擎已经解析了所有的 JS,并将其转化为字节码,并且由 JS 引擎模块存储在线性内存中。引擎在这个阶段也做了大量的内存分配和初始化。 180 | 181 | 因为这个线性内存是如此独立,一旦所有的值都被填满,我们就可以把内存作为数据部分附加到一个 Wasm ( WebAssembly )模块上。 182 | 183 | 当 JS 引擎模块被实例化时, JS 引擎可以访问数据部分中的所有数据。每当引擎需要一点内存时,就可以将引擎需要的部分(或者更确切地说,内存页)复制到自己的线性内存中。有了这个,JS 引擎在启动时不需要做任何设置。所有预初始化的值都已准备就绪。 184 | 185 | ![](04-02-wasm-file-copy-mem.png) 186 | 187 | 目前,我们将此数据部分附加到与 JS 引擎相同的模块。但是未来,一旦[模块链接](https://github.com/WebAssembly/module-linking/blob/master/proposals/module-linking/Explainer.md)到位,我们将能够将数据部分作为单独的模块发布,从而允许 JS 引擎模块被许多不同的 JS 应用程序复用。 188 | 189 | 这提供了较低耦合度。 190 | 191 | JS 引擎模块只包含引擎相关代码。这意味着一旦它被编译,该代码就可以被有效地缓存并在许多不同的实例之间复用。 192 | 193 | 另一方面,特定于应用程序的模块不包含 Wasm( WebAssembly ) 代码。它只包含线性内存,而后者又包含 JS 字节码,以及已初始化的其余 JS 引擎状态。这使得移动该内存并将其发送到任何需要去的地方变得非常容易。 194 | 195 | ![](04-03-wasm-file-data-vs-code.png) 196 | 197 | 这有点像 JS 引擎承包商,甚至根本不需要设立办公室。JS 引擎只会收到一个旅行箱。那个旅行箱有整个办公室,里面有所有的东西。所有的设置都准备好, JS 引擎开始直接工作。 198 | 199 | ![](04-04-preinitiatlized-engine.png) 200 | 201 | 最酷的是它不依赖于 JS ——它只是使用 WebAssembly 的现有属性。因此,您也可以在 Python、Ruby、Lua 或其他运行时中使用相同的技术。 202 | 203 | ## 下一步:提高吞吐量 204 | 205 | 因此,通过这种方法,我们可以获得超快的启动时间。但是吞吐量呢? 206 | 207 | 对于某些用例,吞吐量实际上还不错。如果你有一段很短的 JavaScript 代码,它无论如何都不会通过 JIT——它会一直留在解释器中。因此,在这种情况下,吞吐量将与浏览器中的吞吐量大致相同,并且在传统 JS 引擎完成初始化之前完成。 208 | 209 | 但是对于需要更长时间运行的 JS,JIT 开始运行并不需要很长时间。一旦发生这种情况,吞吐量差异就开始变得明显。 210 | 211 | 正如我上面所说,目前不可能在纯 WebAssembly 中 JIT 编译代码。但事实证明,我们可以将 JIT 附带的一些想法应用到提前编译模型中。 212 | 213 | ### 快速 AOT 编译的 JS(无需分析) 214 | 215 | JIT 使用的一种优化技术是内联缓存。使用内联缓存,JIT 创建一个存根链表,其中包含过去运行一些 JS 字节码的所有方式的快速机器代码路径。(有关更多详细信息,请参阅[实时 (JIT) 编译器中的速成课程](https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/)。) 216 | 217 | ![](02-06-jit09.png) 218 | 219 | 您需要列表的原因是因为 JS 中的动态类型。每次代码行使用不同的类型时,您都需要生成一个新的存根并将其添加到列表中。但是如果您之前遇到过这种类型,那么您可以只使用已经为它生成的存根。 220 | 221 | 由于内联缓存 (IC) 通常用于 JIT,人们认为它们非常动态且特定于每个程序。但事实证明,它们也可以应用于 AOT 上下文中。 222 | 223 | 甚至在我们看到 JS 代码之前,我们就已经知道我们将需要生成的许多 IC 存根。那是因为 JS 中有一些模式被大量使用。 224 | 225 | 一个很好的例子是访问对象的属性。这在 JS 代码中经常发生,并且可以通过使用 IC 存根来加速。对于具有特定“形状”或“隐藏类”(即以相同方式布置的属性)的对象,当您从这些对象获取特定属性时,该属性将始终处于相同的偏移量。 226 | 227 | 传统上,JIT 中的这种 IC 存根将硬编码两个值:指向形状的指针和属性的偏移量。这需要我们事先没有的信息。但是我们可以做的是参数化 IC 存根。我们可以将形状和属性偏移量视为传入存根的变量。 228 | 229 | 这样,我们可以创建一个从内存加载值的单个存根,然后在任何地方使用相同的存根代码。我们可以将这些常见模式的所有存根烘焙到 AOT 编译模块中,而不管 JS 代码实际做什么。即使在浏览器设置中,这种 IC 共享也是有益的,因为它让 JS 引擎生成更少的机器代码,改善启动时间和指令缓存局部性。 230 | 231 | 但对于我们的用例来说,这尤其重要。这意味着我们可以将这些常见模式的所有存根烘焙到 AOT 编译模块中,而不管 JS 代码实际做什么。 232 | 233 | 我们发现,只需几 KB 的 IC 存根,我们就可以覆盖绝大多数 JS 代码。例如,使用 2 KB 的 IC 存根,我们可以覆盖 Google Octane 基准测试中 95% 的 JS。从初步测试来看,这个百分比似乎也适用于一般的网络浏览。 234 | 235 | ![](talk-stub-coverage.png) 236 | 237 | 因此,使用这种优化,我们应该能够达到与早期 JIT 相当的吞吐量。一旦我们完成了这项工作,我们将添加更多细粒度的优化并提高性能,就像浏览器厂商的 JS 引擎团队对他们早期的 JIT 所做的那样。 238 | 239 | ### 下一步:也许添加一些分析? 240 | 241 | 我们可以做到 AOT(ahead-of-time)一样,而无需程序知道执行什么以及使用的类型。 242 | 243 | 但是,如果我们可以访问 JIT 拥有的相同类型的分析信息呢?然后我们可以完全优化代码。 244 | 245 | 但是这里有一个问题——开发者通常很难分析他们自己的代码,很难提出具有代表性的示例工作负载。所以我们不确定是否可以获得正确的分析数据。 246 | 247 | 不过,如果我们能找到一种方法来放置好的工具进行分析,那么我们就有可能使 JS 的运行速度几乎与今天的 JIT 一样快(而且无需预热时间!) 248 | 249 | ## 今天如何开始 250 | 251 | 我们对这种新方法感到兴奋,并期待看到可以将其推进多远。我们也很高兴看到其他动态类型语言以这种方式进入 WebAssembly。 252 | 253 | 那么今天就介绍几种入门方法,有什么问题可以在[Zulip](https://bytecodealliance.zulipchat.com/)中提问。 254 | 255 | ### 对于其他想要支持 JS 的平台 256 | 257 | 要在自己的平台上运行 JS,需要嵌入一个支持 WASI 的 WebAssembly 引擎。我们为此使用了[Wasmtime](https://github.com/bytecodealliance/wasmtime)。 258 | 259 | 那么你需要你的 JS 引擎。作为这项工作的一部分,我们添加了对 Mozilla 构建系统的全面支持,用于将 SpiderMonkey 编译为 WASI。Mozilla 即将为 SpiderMonkey 添加 WASI 构建到用于构建和测试 Firefox 的相同 CI 设置。这使得 WASI 成为 SpiderMonkey 的生产质量目标,并确保 WASI 构建随着时间的推移继续工作。这意味着您可以像我们在这里一样[使用 SpiderMonkey](https://spidermonkey.dev/)。 260 | 261 | 最后,您需要用户带上他们预先初始化的 JS。为了解决这个问题,我们还开源了[Wizer](https://github.com/bytecodealliance/wizer),您可以将其[集成到构建工具中](https://github.com/bytecodealliance/wizer#using-wizer-as-a-library),该工具生成特定于应用程序的 WebAssembly 模块,该模块将填充 JS 引擎模块的预初始化内存。 262 | 263 | ### 对于想要使用这种方法的其他语言 264 | 265 | 如果您是 Python、Ruby、Lua 或其他语言的语言社区的一员,你也可以为你的语言构建一个版本。 266 | 267 | 首先,您需要将运行时编译为 WebAssembly,使用 WASI 进行系统调用,就像我们对 SpiderMonkey 所做的那样。然后,为了通过快照获得快速启动时间,您可以[将 Wizer 集成到构建工具中](https://github.com/bytecodealliance/wizer#using-wizer-as-a-library)以生成内存快照。 268 | 269 | -------------------------------------------------------------------------------- /2021/08/making-javaScript-run-fast-on-webAssembly/talk-stub-coverage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/08/making-javaScript-run-fast-on-webAssembly/talk-stub-coverage.png -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-1-introduction/eBPF-flowchart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/an-ebpf-overview-part-1-introduction/eBPF-flowchart.png -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-1-introduction/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | original: "https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/" 3 | author: "Adrian Ratiu" 4 | translator: "DavadDi" 5 | reviewer: ["yangchuansheng“] 6 | title: "eBPF 概述:第 1 部分:介绍" 7 | summary: "有兴趣了解更多关于 eBPF 技术的底层细节?那么请继续移步,我们将深入研究 eBPF 的底层细节,从其虚拟机机制和工具,到在远程资源受限的嵌入式设备上运行跟踪。" 8 | categories: "译文" 9 | tags: ["ebpf","overview"] 10 | originalPublishDate: 2019-04-05 11 | publishDate: 2021-09-03 12 | --- 13 | 14 | 15 | 16 | ## 1. 前言 17 | 18 | **有兴趣了解更多关于 eBPF 技术的底层细节?那么请继续移步,我们将深入研究 eBPF 的底层细节,从其虚拟机机制和工具,到在远程资源受限的嵌入式设备上运行跟踪。** 19 | 20 | 注意:本系列博客文章将集中在 eBPF 技术,因此对于我们来讲,文中 BPF 和 eBPF 等同,可相互使用。BPF 名字/缩写已经没有太大的意义,因为这个项目的发展远远超出了它最初的范围。BPF 和 eBPF 在该系列中会交替使用。 21 | 22 | - [第 1 部分](https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/)和[第 2 部分](https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/) 为新人或那些希望通过深入了解 eBPF 技术栈的底层技术来进一步了解 eBPF 技术的人提供了深入介绍。 23 | - [第 3 部分](https://www.collabora.com/news-and-blog/blog/2019/04/26/an-ebpf-overview-part-3-walking-up-the-software-stack/)是对用户空间工具的概述,旨在提高生产力,建立在第 1 部分和第 2 部分中介绍的底层虚拟机机制之上。 24 | - [第 4 部分](https://www.collabora.com/news-and-blog/blog/2019/05/06/an-ebpf-overview-part-4-working-with-embedded-systems/)侧重于在资源有限的嵌入式系统上运行 eBPF 程序,在嵌入式系统中完整的工具链技术栈(BCC/LLVM/python 等)是不可行的。我们将使用占用资源较小的嵌入式工具在 32 位 ARM 上交叉编译和运行 eBPF 程序。只对该部分感兴趣的读者可选择跳过其他部分。 25 | - [第 5 部分](https://www.collabora.com/news-and-blog/blog/2019/05/14/an-ebpf-overview-part-5-tracing-user-processes/)是关于用户空间追踪。到目前为止,我们的努力都集中在内核追踪上,所以是时候我们关注一下用户进程了。 26 | 27 | 如有疑问时,可使用该流程图: 28 | 29 | ![eBPF flowchart](eBPF-flowchart.png) 30 | 31 | ## 2. eBPF 是什么? 32 | 33 | eBPF 是一个基于寄存器的虚拟机,使用自定义的 64 位 RISC 指令集,能够在 Linux 内核内运行即时本地编译的 "BPF 程序",并能访问内核功能和内存的一个子集。这是一个完整的虚拟机实现,不要与基于内核的虚拟机(KVM)相混淆,后者是一个模块,目的是使 Linux 能够作为其他虚拟机的管理程序。eBPF 也是主线内核的一部分,所以它不像其他框架那样需要任何第三方模块([LTTng](https://lttng.org/docs/v2.10/#doc-lttng-modules) 或 [SystemTap](https://kernelnewbies.org/SystemTap)),而且几乎所有的 Linux 发行版都默认启用。熟悉 DTrace 的读者可能会发现 [DTrace/BPFtrace 对比](http://www.brendangregg.com/blog/2018-10-08/dtrace-for-linux-2018.html)非常有用。 34 | 35 | 在内核内运行一个完整的虚拟机主要是考虑便利和安全。虽然 eBPF 程序所做的操作都可以通过正常的内核模块来处理,但直接的内核编程是一件非常危险的事情 - 这可能会导致系统锁定、内存损坏和进程崩溃,从而导致安全漏洞和其他意外的效果,特别是在生产设备上(eBPF 经常被用来检查生产中的系统),所以通过一个安全的虚拟机运行本地 JIT 编译的快速内核代码对于安全监控和沙盒、网络过滤、程序跟踪、性能分析和调试都是非常有价值的。部分简单的样例可以在这篇优秀的 [eBPF 参考](http://www.brendangregg.com/ebpf.html)中找到。 36 | 37 | 基于设计,eBPF 虚拟机和其程序有意地设计为**不是**图灵完备的:即不允许有循环(正在进行的工作是支持有界循环【译者注:已经支持有界循环,\#pragma unroll 指令】),所以每个 eBPF 程序都需要保证完成而不会被挂起、所有的内存访问都是有界和类型检查的(包括寄存器,一个 MOV 指令可以改变一个寄存器的类型)、不能包含空解引用、一个程序必须最多拥有 BPF_MAXINSNS 指令(默认 4096)、"主"函数需要一个参数(context)等等。当 eBPF 程序被加载到内核中,其指令被验证模块解析为有向环状图,上述的限制使得正确性可以得到简单而快速的验证。 38 | 39 | > 译者注: BPF_MAXINSNS 这个限制已经被放宽至 100 万条指令(BPF_COMPLEXITY_LIMIT_INSNS),但是非特权执行的 BPF 程序这个限制仍然会保留。 40 | 41 | 历史上,eBPF (cBPF) 虚拟机只在内核中可用,用于过滤网络数据包,与用户空间程序没有交互,因此被称为 "伯克利数据包过滤器"【译者注:早期的 BPF 实现被称为经典 cBPF】。从内核 v3.18(2014 年)开始,该虚拟机也通过 [bpf() syscall](https://github.com/torvalds/linux/blob/v4.20/tools/lib/bpf) 和[uapi/linux/bpf.h](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h) 暴露在用户空间,这导致其指令集在当时被冻结,成为公共 ABI,尽管后来仍然可以(并且已经)添加新指令。 42 | 43 | 因为内核内的 eBPF 实现是根据 GPLv2 授权的,它不能轻易地被非 GPL 用户重新分发,所以也有一个替代的 Apache 授权的用户空间 eBPF 虚拟机实现,称为 "uBPF"。撇开法律条文不谈,基于用户空间的实现对于追踪那些需要避免内核-用户空间上下文切换成本的性能关键型应用很有用。 44 | 45 | 46 | 47 | ## 3. eBPF 是怎么工作的? 48 | 49 | eBPF 程序在事件触发时由内核运行,所以可以被看作是一种函数挂钩或事件驱动的编程形式。从用户空间运行按需 eBPF 程序的价值较小,因为所有的按需用户调用已经通过正常的非 VM 内核 API 调用("syscalls")来处理,这里 VM 字节码带来的价值很小。事件可由 kprobes/uprobes、tracepoints、dtrace probes、socket 等产生。这允许在内核和用户进程的指令中钩住(hook)和检查任何函数的内存、拦截文件操作、检查特定的网络数据包等等。一个比较好的参考是 [Linux 内核版本对应的 BPF 功能](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md)。 50 | 51 | 如前所述,事件触发了附加的 eBPF 程序的执行,后续可以将信息保存至 map 和环形缓冲区(ringbuffer)或调用一些特定 API 定义的内核函数的子集。一个 eBPF 程序可以链接到多个事件,不同的 eBPF 程序也可以访问相同的 map 以共享数据。一个被称为 "program array" 的特殊读/写 map 存储了对通过 bpf() 系统调用加载的其他 eBPF 程序的引用,在该 map 中成功的查找则会触发一个跳转,而且并不返回到原来的 eBPF 程序。这种 eBPF 嵌套也有限制,以避免无限的递归循环。 52 | 53 | 运行 eBPF 程序的步骤: 54 | 55 | 1. 用户空间将字节码和程序类型一起发送到内核,程序类型决定了可以访问的内核区域【译者注:主要是 BPF 帮助函数的各种子集】。 56 | 2. 内核在字节码上运行验证器,以确保程序可以安全运行(kernel/bpf/verifier.c)。 57 | 3. 内核将字节码编译为本地代码,并将其插入(或附加到)指定的代码位置。【译者注:如果启用了 JIT 功能,字节码编译为本地代码】。 58 | 4. 插入的代码将数据写入环形缓冲区或通用键值 map。 59 | 5. 用户空间从共享 map 或环形缓冲区中读取结果值。 60 | 61 | map 和环形缓冲区结构是由内核管理的(就像管道和 FIFO 一样),独立于挂载的 eBPF 或访问它们的用户程序。对 map 和环形缓冲区结构的访问是异步的,通过文件描述符和引用计数实现,可确保只要有至少一个程序还在访问,结构就能够存在。加载的 JIT 后代码通常在加载其的用户进程终止时被删除,尽管在某些情况下,它仍然可以在加载进程的生命期之后继续存在。 62 | 63 | 为了方便编写 eBPF 程序和避免进行原始的 bpf()系统调用,内核提供了方便的 [libbpf 库](https://github.com/torvalds/linux/blob/v4.20/tools/lib/bpf),包含系统调用函数包装器,如[bpf_load_program](https://github.com/torvalds/linux/blob/v4.20/tools/lib/bpf/bpf.c#L214) 和结构定义(如 [bpf_map](https://github.com/torvalds/linux/blob/v4.20/tools/lib/bpf/libbpf.c#L157)),在 LGPL 2.1 和 BSD 2-Clause 下双重许可,可以静态链接或作为 DSO。内核代码也提供了一些使用 libbpf 简洁的例子,位于目录 [samples/bpf/](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/) 中。 64 | 65 | 66 | 67 | ## 4. 样例学习 68 | 69 | 内核开发者非常可怜,因为内核是一个独立的项目,因而没有用户空间诸如 Glibc、LLVM、JavaScript 和 WebAssembly 诸如此类的好东西! - 这就是为什么内核中 eBPF 例子中会包含原始字节码或通过 libbpf 加载预组装的字节码文件。我们可以在 [sock_example.c](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c) 中看到这一点,这是一个简单的用户空间程序,使用 eBPF 来计算环回接口上统计接收到 TCP、UDP 和 ICMP 协议包的数量。 70 | 71 | 我们跳过微不足道的的 [main](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L98) 和 [open_raw_sock](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.h#L13) 函数,而专注于神奇的代码 [test_sock](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L35)。 72 | 73 | ```c 74 | static int test_sock(void) 75 | { 76 | int sock = -1, map_fd, prog_fd, i, key; 77 | long long value = 0, tcp_cnt, udp_cnt, icmp_cnt; 78 | 79 | map_fd = bpf_create_map(BPF_MAP_TYPE_ARRAY, sizeof(key), sizeof(value), 256, 0); 80 | if (map_fd < 0) {printf("failed to create map'%s'\n", strerror(errno)); 81 | goto cleanup; 82 | } 83 | 84 | struct bpf_insn prog[] = {BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), 85 | BPF_LD_ABS(BPF_B, ETH_HLEN + offsetof(struct iphdr, protocol) /* R0 = ip->proto */), 86 | BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4), /* *(u32 *)(fp - 4) = r0 */ 87 | BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), 88 | BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = fp - 4 */ 89 | BPF_LD_MAP_FD(BPF_REG_1, map_fd), 90 | BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem), 91 | BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2), 92 | BPF_MOV64_IMM(BPF_REG_1, 1), /* r1 = 1 */ 93 | BPF_RAW_INSN(BPF_STX | BPF_XADD | BPF_DW, BPF_REG_0, BPF_REG_1, 0, 0), /* xadd r0 += r1 */ 94 | BPF_MOV64_IMM(BPF_REG_0, 0), /* r0 = 0 */ 95 | BPF_EXIT_INSN(),}; 96 | size_t insns_cnt = sizeof(prog) / sizeof(struct bpf_insn); 97 | 98 | prog_fd = bpf_load_program(BPF_PROG_TYPE_SOCKET_FILTER, prog, insns_cnt, 99 | "GPL", 0, bpf_log_buf, BPF_LOG_BUF_SIZE); 100 | if (prog_fd < 0) {printf("failed to load prog'%s'\n", strerror(errno)); 101 | goto cleanup; 102 | } 103 | 104 | sock = open_raw_sock("lo"); 105 | 106 | if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_BPF, &prog_fd, sizeof(prog_fd)) < 0) {printf("setsockopt %s\n", strerror(errno)); 107 | goto cleanup; 108 | } 109 | ``` 110 | 111 | 首先,通过 libbpf API 创建一个 BPF map,该行为就像一个最大 256 个元素的固定大小的数组。按 [IPROTO_*](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/in.h#L28) 定义的键索引网络协议(2 字节的 word),值代表各自的数据包计数(4 字节大小)。除了数组,eBPF 映射还实现了[其他数据结构类型](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L113),如栈或队列。 112 | 113 | 接下来,eBPF 的字节码指令数组使用方便的[内核宏](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/bpf_insn.h)进行定义。在这里,我们不会讨论字节码的细节(这将在第 2 部分描述机器后进行)。更高的层次上,字节码从数据包缓冲区中读取协议字,在 map 中查找,并增加特定的数据包计数。 114 | 115 | 然后 BPF 字节码被加载到内核中,并通过 libbpf 的 bpf_load_program 返回 fd 引用来验证正确/安全。调用指定了 eBPF [程序类型](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L138),这决定了它可以访问哪些内核子集。因为样例是一个 SOCKET_FILTER 类型,因此提供了一个指向当前网络包的参数。最后,eBPF 的字节码通过套接字层被附加到一个特定的原始套接字上,之后在原始套接字上接受到的每一个数据包运行 eBPF 字节码,无论协议如何。 116 | 117 | 剩余的工作就是让用户进程开始轮询共享 map 的数据。 118 | 119 | ```c 120 | for (i = 0; i < 10; i++) { 121 | key = IPPROTO_TCP; 122 | assert(bpf_map_lookup_elem(map_fd, &key, &tcp_cnt) == 0); 123 | 124 | key = IPPROTO_UDP; 125 | assert(bpf_map_lookup_elem(map_fd, &key, &udp_cnt) == 0); 126 | 127 | key = IPPROTO_ICMP; 128 | assert(bpf_map_lookup_elem(map_fd, &key, &icmp_cnt) == 0); 129 | 130 | printf("TCP %lld UDP %lld ICMP %lld packets\n", 131 | tcp_cnt, udp_cnt, icmp_cnt); 132 | sleep(1); 133 | } 134 | } 135 | ``` 136 | 137 | 138 | 139 | ## 5. 总结 140 | 141 | 第 1 部分介绍了 eBPF 的基础知识,我们通过如何加载字节码和与 eBPF 虚拟机通信的例子进行了讲述。由于篇幅限制,编译和运行例子作为留给读者的练习。我们也有意不去分析具体的 eBPF 字节码指令,因为这将是第 2 部分的重点。在我们研究的例子中,用户空间通过 libbpf 直接用 C 语言从内核虚拟机中读取 eBPF map 值(使用 10 次 1 秒的睡眠!),这很笨重,而且容易出错,而且很快就会变得很复杂,所以在第 3 部分,我们将研究更高级别的工具,通过脚本或特定领域的语言自动与虚拟机交互。 142 | 143 | **[继续阅读(eBPF 概述,第 2 部分:机器和字节码)](https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/)...**。 144 | 145 | -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-2-machine-bytecode/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | original: https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/ 3 | author: Adrian Ratiu 4 | translator: https://github.com/1020973418 5 | reviewer: 6 | title: An eBPF overview, part 2 : Machine & bytecode 7 | summary: eBPF 概述,第 2 部分:机器和字节码 8 | categories: 译文 9 | tags: ["eBPF", "machine", "bytecode"] 10 | originalPublishDate: 2019-04-15 11 | publishDate: 2021-09-06 12 | --- 13 | 14 | # eBPF 概述,第二部分:机器和字节码 15 | 16 |

2019 年 4 月 15 日 阿德里安 · 拉蒂乌

17 | 18 | 在我们的第一篇文章中,我们[介绍了 eBPF VM](https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/)、它刻意的设计限制以及如何从用户空间进程与其交互。如果您还没有阅读它,您可能需要在继续阅读本篇文章之前阅读上一篇文章,因为如果没有适当了解,直接从机器和字节码细节开始学习可能会很困难。如有疑问,请参阅[第一部分](https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/)开头的流程图。 19 | 20 | 本系列的第二部分更深入地研究了第一部分中研究的 eBPF VM 和程序。掌握这种底层知识不是强制性的,但对于本系列的其余部分来说是非常有用的基础,我们将在其中检查建立在这些机制之上的更高级别的工具。 21 | 22 | ## 虚拟机 23 | eBPF 是一个 RISC 寄存器机,共有[ 11 个 64 位寄存器](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L45),一个程序计数器和一个 512 字节固定大小的堆栈。九个寄存器是通用读写的,一个是只读堆栈指针,程序计数器是隐式的,即我们只能跳转到计数器的某个偏移量。VM 寄存器始终为 64 位宽(即使在 32 位 ARM 处理器内核中运行!)并且如果最高有效的 32 位为零,则支持 32 位子寄存器寻址 - 这将在第四部分在嵌入式设备上交叉编译和运行 eBPF 程序非常有用。 24 | 25 | 这些寄存器是: 26 | | | | 27 | | --- | --- | 28 | | r0:| 存储函数调用和当前程序退出代码的返回值 | 29 | | r1 - r5:| 作为函数调用的参数,在程序开始时 r1 包含 “上下文” 参数指针 | 30 | | r6 - r9:| 这些在内核函数调用之间被保留 | 31 | | r10:| 每个 eBPF 程序512字节堆栈的只读指针 | 32 | | | | 33 | 34 | 在加载时提供的 eBPF [程序类型](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L136)准确地决定了哪些内核函数子集可以调用,以及在程序启动时通过 r1 提供的 “上下文” 参数。r0 中存储的程序退出值的含义也是由程序类型决定的。 35 | 36 | 每个函数调用在寄存器 r1 - r5 中最多可以有 5 个参数;这适用于 eBPF 到 eBPF 和内核函数的调用。寄存器 r1 - r5 只能存储数字或指向堆栈的指针(作为参数传递给函数),从不直接指向任意内存的指针。所有内存访问都必须先将数据加载到 eBPF 堆栈中,然后才能在 eBPF 程序中使用它。此限制有助于 eBPF 验证器,它简化了内存模型以实现更轻松的正确性检查。 37 | 38 | BPF 可访问的内核“辅助”函数由内核核心(不可通过模块扩展)通过类似于定义系统调用的 API 定义,使用 [BPF_CALL_*](https://github.com/torvalds/linux/blob/v4.20/include/linux/filter.h#L441) 宏。[bpf.h](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L420) 试图为所有 BPF 可访问的内核“辅助”函数提供参考。例如 [bpf_trace_printk](https://github.com/torvalds/linux/blob/v4.20/kernel/trace/bpf_trace.c#L163) 的定义使用 BPF_CALL_5 和 5 对类型/参数名称。定义[参数数据类型](https://github.com/torvalds/linux/blob/v4.20/kernel/trace/bpf_trace.c#L276)很重要,因为在每个 eBPF 程序加载时,eBPF 验证器确保寄存器数据类型与被调用方参数类型匹配。 39 | 40 | eBPF 指令也是固定大小的 64 位编码,大约 100 条指令(目前...)分为[ 8 类](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf_common.h#L5)。VM 支持来自通用内存( map 、堆栈、“上下文”如数据包缓冲区等)的 1 - 8 字节加载/存储、向前/向后(非)条件跳转、算术/逻辑运算和函数调用。如需深入了解操作码格式,请参阅 Cilium 项目[指令集文档](https://cilium.readthedocs.io/en/latest/bpf/#instruction-set)。IOVisor 项目还维护了一个有用的[指令规范](https://github.com/iovisor/bpf-docs/blob/master/eBPF.md)。 41 | 42 | 在本系列第一部分研究的示例中,我们使用了一些有用的[内核宏](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/bpf_insn.h)来使用以下[结构](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/bpf.h#L64)创建 eBPF 字节码指令数组(所有指令都以这种方式编码): 43 | 44 | ```c 45 | struct bpf_insn { 46 | __u8 代码;/* opcode */ 47 | __u8 dst_reg:4; /* dest register */ 48 | __u8 src_reg:4; /* source register */ 49 | __s16 off; /* signed offset */ 50 | __s32 imm; /* signed immediate constant */ 51 | }; 52 | 53 | msb lsb 54 | +------------------------+----------------+----+----+--------+ 55 | |immediate |offset |src |dst |opcode | 56 | +------------------------+----------------+----+----+--------+ 57 | ``` 58 | 让我们看一下 [BPF_JMP_IMM](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/bpf_insn.h#L167) 指令,它针对立即值对条件跳转进行编码。下面的宏注释对指令逻辑应该是不言自明的。操作码编码指令类 BPF_JMP 、操作(通过 BPF_OP 位域传递以确保正确性)和表示它是对立即数/常量值 BPF_K 的操作的标志进行编码。 59 | 60 | ```c 61 | #define BPF_OP(code) ((code) & 0xf0) 62 | #define BPF_K 0x00 63 | 64 | /* 针对立即数的条件跳转,if (dst_reg 'op' imm32) goto pc + off16 */ 65 | 66 | #define BPF_JMP_IMM(OP, DST, IMM, OFF) \ 67 | ((struct bpf_insn) { \ 68 | .code = BPF_JMP | BPF_OP(OP) | BPF_K, \ 69 | .dst_reg = DST, \ 70 | .src_reg = 0 71 | , \ 72 | .off = OFF, \ .imm = IMM }) 73 | ``` 74 | 如果我们计算值或反汇编包含 BPF_JMP_IMM ( BPF_JEQ , BPF_REG_0 , 0 , 2 ) 的 eBPF 字节码二进制文件,我们会发现它是 0x020015 格式。这个特定的字节码非常频繁地用于测试存储在 r0 中的函数调用的返回值;如果 r0 == 0,它会跳过接下来的 2 条指令。 75 | 76 | ## 重温我们的字节码 77 | 78 | 现在我们已经掌握了必要的知识来完全理解本系列第一部分中使用的字节码 eBPF 示例,我们将逐步解释它。请记住,[sock_example.c](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c) 是一个简单的用户态程序,它使用 eBPF 来计算在环回接口上接收到的 TCP、UDP 和 ICMP 协议数据包的数量。 79 | 80 | 在高层次上,代码所做的是从接收到的数据包中读取协议编号,然后将其推送到 eBPF 堆栈上,用作 map_lookup_elem 调用的索引,该调用获取相应协议的数据包计数。map_lookup_elem 函数采用 r0 中的索引(或键)指针和 r1 中的 map 文件描述符。如果查找调用成功,r0 将包含一个指向存储在协议索引处的 map 值的指针。然后我们原子地增加 map 值并退出。 81 | 82 | ```c 83 | BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), 84 | ``` 85 | 当 eBPF 程序启动时,上下文(在这种情况下是数据包缓冲区)由 r1 中的地址指向。r1 将在函数调用期间用作参数,因此我们也将其存储在 r6 中作为备份。 86 | 87 | ```c 88 | BPF_LD_ABS(BPF_B, ETH_HLEN + offsetof(struct iphdr, protocol) /* R0 = ip->proto */), 89 | ``` 90 | 91 | 该指令将一个字节( BPF_B )从上下文缓冲区(在本例中为网络数据包缓冲区)中的偏移量加载到 r0 中,因此我们提供要加载到 r0 的 [iphdr 结构](https://github.com/torvalds/linux/blob/v4.20/include/uapi/linux/ip.h#L86)中的协议字节的偏移量。 92 | 93 | ```c 94 | BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4), /* *(u32 *)(fp - 4) = r0 */ 95 | ``` 96 | 将包含先前读取的协议的字 ( BPF_W ) 压入堆栈(由 r10 指向,以偏移量 -4 字节开头)。 97 | 98 | ```c 99 | BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), 100 | BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4), /* r2 = fp - 4 */ 101 | ``` 102 | 103 | 将堆栈地址指针移至 r2 并减去 4,因此现在 r2 指向协议值,用作下一次 map 键查找的参数。 104 | 105 | ```c 106 | BPF_LD_MAP_FD(BPF_REG_1, map_fd), 107 | ``` 108 | 109 | 将本地进程内的文件描述符引用包含协议包计数的 map 到 r1 寄存器。 110 | 111 | ```c 112 | BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem), 113 | ``` 114 | 115 | 使用 r2 指向的堆栈中的协议值作为键执行 map 查找调用。结果存储在 r0 中:指向由键索引的值的指针地址。 116 | 117 | ```c 118 | BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 2), 119 | ``` 120 | 121 | 还记得 0x020015 格式吗?这与第一部分的字节码相同。如果 map 查找没有成功,则 r0 == 0 所以我们跳过接下来的两条指令。 122 | 123 | ```c 124 | BPF_MOV64_IMM(BPF_REG_1, 1), /* r1 = 1 */ 125 | BPF_RAW_INSN(BPF_STX | BPF_XADD | BPF_DW, BPF_REG_0, BPF_REG_1, 0, 0), /* xadd r0 += r1 */ 126 | ``` 127 | 128 | 增加 r0 指向的地址处的 map 值。 129 | 130 | ```c 131 | BPF_MOV64_IMM(BPF_REG_0, 0), /* r0 = 0 */ 132 | BPF_EXIT_INSN(), 133 | ``` 134 | 将 eBPF retcode 设置为 0 并退出。 135 | 136 | 尽管这个 sock_example 逻辑非常简单(它只是在 map 中增加的一些数字),但在原始字节码中实现或理解是困难的。更复杂的任务在像这样的汇编程序中完成时变得极其困难。展望未来,我们将开始使用更高级的语言和工具,以更少的工作开启更强大的 eBPF 用例。 137 | 138 | ## 总结 139 | 140 | 在这一部分中,我们仔细观察了 eBPF 虚拟机的寄存器和指令集,了解了 eBPF 可访问的内核函数是如何从字节码中调用的,以及它们是如何被核心内核通过类似 syscall 的特殊目的 API 定义的。我们也完全理解了第一部分例子中使用的字节码。还有一些未探索的领域,如创建多个 eBPF 程序函数或链式 eBPF 程序以绕过 Linux 发行版的 4096 条指令限制。也许我们会在以后的文章中探讨这些。 141 | 142 | 现在,主要的问题是编写原始字节码很困难的,这非常像编写汇编代码,而且编写效率低下。在第三部分中,我们将开始研究使用高级语言编译成 eBPF 字节码,到此为止我们已经了解了虚拟机工作的底层基础知识。 -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/eBPF-Part3-Diagram1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/eBPF-Part3-Diagram1.png -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/eBPF-Part3-Diagram2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/eBPF-Part3-Diagram2.jpg -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/eBPF-Part3-Diagram3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/eBPF-Part3-Diagram3.jpg -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-3-walking-up-the-software-stack/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | original: "https://www.collabora.com/news-and-blog/blog/2019/04/26/an-ebpf-overview-part-3-walking-up-the-software-stack/" 3 | author: "Adrian Ratiu" 4 | translator: "DavadDi" 5 | reviewer: ["yangchuansheng“] 6 | title: "eBPF 概述:第 3 部分:软件开发生态" 7 | summary: "为了理解这些工具是如何工作的,我们先定义一下 eBPF 程序的高层次组件:后端、加载器、前端和数据结构等。开发方式包括 LLVM eBPF 编译器/BCC/BPFTrace/IOVisor等实现" 8 | categories: "译文" 9 | tags: ["ebpf","overview"] 10 | originalPublishDate: 2019-04-26 11 | publishDate: 2021-09-06 12 | --- 13 | 14 | 15 | 16 | ## 1. 前言 17 | 18 | 在本系列的[第 1 部分](https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/)和[第 2 部分](https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/)中,我们对 eBPF 虚拟机进行了简洁的深入研究。阅读上述部分并不是理解第 3 部分的必修课,尽管很好地掌握了低级别的基础知识确实有助于更好地理解高级别的工具。为了理解这些工具是如何工作的,我们先定义一下 eBPF 程序的高层次组件: 19 | 20 | - **后端**:这是在内核中加载和运行的 eBPF 字节码。它将数据写入内核 map 和环形缓冲区的**数据结构**中。 21 | - **加载器:**它将字节码**后端**加载到内核中。通常情况下,当加载器进程终止时,字节码会被内核自动卸载。 22 | - **前端:**从**数据结构**中读取数据(由**后端**写入)并将其显示给用户。 23 | - **数据结构**:这些是**后端**和**前端**之间的通信手段。它们是由内核管理的 map 和环形缓冲区,可以通过文件描述符访问,并需要在**后端**被加载之前创建。它们会持续存在,直到没有更多的**后端**或**前端**进行读写操作。 24 | 25 | 在第 1 部分和第 2 部分研究的 [sock_example.c](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c) 中,所有的组件都被放置在一个 C 文件中,所有的动作都由用户进程完成。 26 | 27 | - [第 40-45 行](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L40-L45)创建 map**数据结构**。 28 | - [第 47-61 行](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L47-L61)定义**后端**。 29 | - [第 63-76 行](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L63-L76)在内核中**加载**后端 30 | - [第 78-91 行](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L78-L91)是**前端**,负责将从 map 文件描述符中读取的数据打印给用户。 31 | 32 | eBPF 程序可以更加复杂:多个**后端**可以由一个(或单独的多个!)**加载器**进程加载,写入多个**数据结构**,然后由多个**前端**进程读取,所有这些都可以发生在一个跨越多个进程的用户 eBPF 应用程序中。 33 | 34 | ![Single user eBPF application spanning multiple processes](eBPF-Part3-Diagram1.png) 35 | 36 | 37 | 38 | ## 2. 层级 1:容易编写的后端:LLVM eBPF 编译器 39 | 40 | 我们在前面的文章中看到,在内核中编写原始的 eBPF 字节码是不仅困难而且低效,这非常像用处理器的汇编语言编写程序,所以很自然地开发了一个能够将 LLVM 中间表示编译成 eBPF 程序的模块,并从 2015 年的 v3.7 开始发布(GCC 到现在为止仍然不支持 eBPF)。这使得多种高级语言如 C、Go 或 Rust 的子集可以被编译到 eBPF。最成熟和最流行的是基于 C 语言编写的方式,因为内核也是用 C 写的,这样就更容易复用现有的内核头文件。 41 | 42 | LLVM 将 "受限制的 C" 语言(记住,没有无界循环,最大 4096 条指令等等,见第 1 部分开始)编译成 ELF 对象文件,其中包含特殊区块(section),并可基于 bpf()系统调用,使用 libbpf 等库加载到内核中。这种设计有效地将**后端**定义从**加载器**和**前端**中分离出来,因为 eBPF 字节码包含在 ELF 文件中。 43 | 44 | 内核还在 [samples/bpf/](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/) 下提供了使用这种模式的例子:\*\_kern.c 文件被编译为 \*\_kern.o(**后端**代码),被 \*\_user.c(**装载器**和**前端**)加载。 45 | 46 | 将本系列第 1 和第 2 部分的 [sock_exapmle.c 原始字节码](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L47-L61) 转换为 "受限的 C" 代码“ [sockex1_kern.c](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c#L47-L61),这比原始字节码更容易理解和修改。 47 | 48 | ```c 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include "bpf_helpers.h" 54 | 55 | struct bpf_map_def SEC("maps") my_map = { 56 | .type = BPF_MAP_TYPE_ARRAY, 57 | .key_size = sizeof(u32), 58 | .value_size = sizeof(long), 59 | .max_entries = 256, 60 | }; 61 | 62 | SEC("socket1") 63 | int bpf_prog1(struct __sk_buff *skb) 64 | {int index = load_byte(skb, ETH_HLEN + offsetof(struct iphdr, protocol)); 65 | long *value; 66 | 67 | value = bpf_map_lookup_elem(&my_map, &index); 68 | if (value) 69 | __sync_fetch_and_add(value, skb->len); 70 | 71 | return 0; 72 | } 73 | char _license[] SEC("license") = "GPL"; 74 | ``` 75 | 76 | 产生的 eBPF ELF 对象 sockex1_kern.o,包含了分离的**后端**和**数据结构**定义。**加载器**和**前端**[sockex1_user.c](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sockex1_user.c),用于解析 ELF 文件、[创建](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/bpf_load.c#L270)所需的 map 和[加载字节码](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/bpf_load.c#L630)中内核函数 bpf_prog1(),然后**前端**像以前一样[继续运行](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sockex1_user.c#L32-L48)。 77 | 78 | 引入这个 "受限的 C" 抽象层所做的权衡是使 eBPF**后端**代码更容易用高级语言编写,代价是增加**加载器**的复杂性(现在需要解析 ELF 对象),而**前端**大部分不受影响。 79 | 80 | 81 | 82 | ## 3. 层级 2:自动化后端/加载器/前端的交互:BPF 编译器集合(BCC) 83 | 84 | 并不是每个人手头都有内核源码,特别是在生产中,而且一般来说,将基于 eBPF 工具与特定的内核源码版本捆绑在一起并不是一个好主意。设计和实现 eBPF 程序的**后端**,**前端**,**加载器**和**数据结构**之间的相互作用可能是非常复杂,这也比较容易出错和耗时(特别是在 C 语言中),这被认为是一种危险的低级语言。除了这些风险之外,开发人员还经常为常见问题重新造轮子,会造成无尽的设计变化和实现。为了减轻这些痛苦,社区创建了 BCC 项目:其为编写、加载和运行 eBPF 程序提供了一个易于使用的框架,除了上面举例的 "限制性 C" 之外,还可以通过编写简单的 python 或 lua 脚本来实现。 85 | 86 | BCC 项目有两个部分。 87 | 88 | - 编译器集合(BCC 本身):这是用于编写 BCC 工具的框架,也是我们文章的重点。请继续阅读。 89 | - BCC-tools:这是一个不断增长的基于 eBPF 且经过测试的程序集,提供了使用的例子和手册。更多信息见[本教程](https://github.com/iovisor/bcc/blob/master/docs/tutorial.md)。 90 | 91 | BCC 的安装包很大:它依赖于 LLVM/clang 将 "受限的 C"、python/lua 等编译成 eBPF,它还包含像 libbcc(用 C++ 编写)、libbpf 等库实现【译者注:原文 python/lua 顺序有错,另外 libcc 是 BCC 项目,libbpf 目前已经是内核代码一部分】。部分内核代码的也被复制到 BCC 代码中,所以它不需要基于完整的内核源(只需要头文件)进行构建。它可以很容易地占用数百 MB 的空间,这对于小型嵌入式设备来说不友好,我们希望这些设备也可以从 eBPF 的力量中受益。探索嵌入式设备由于大小限制问题的解决方案,将是我们在第 4 部分的重点。 92 | 93 | eBPF 程序组件在 BCC 组织方式如下: 94 | 95 | - **后端**和**数据结构**:用 "限制性 C" 编写。可以在单独的文件中,或直接作为多行字符串存储在**加载器/前端**的脚本中,以方便使用。参见:[语言参考](https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#bpf-c)。【译者注:在 BCC 实现中,后端代码采用面向对象的做法,真正生成字节码的时候,BCC 会进行一次预处理,转换成真正的 C 语言代码方式,这也包括 map 等数据结构的定义方面】。 96 | 97 | - **加载器**和**前端**:可用非常简单的高级 python/lua 脚本编写。参见:[语言参考](https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#bcc-python)。 98 | 99 | 因为 BCC 的主要目的是简化 eBPF 程序的编写,因此它尽可能地标准化和自动化:在后台完全自动化地通过 LLVM 编译 "受限的 C"**后端**,并产生一个标准的 ELF 对象格式类型,这种方式允许加载器对所有 BCC 程序只实现一次,并将其减少到最小的 API(2 行 python)。它还将**数据结构**的 API 标准化,以便于通过[**前端**](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c)访问。简而言之,它将开发者的注意力集中在编写**前端**上,而不必担心较低层次的细节问题。 100 | 101 | 为了最好地说明它是如何工作的,我们来看一个简单的具体例子,它是对前面文章中的 [sock_example.c](https://github.com/torvalds/linux/blob/v4.20/samples/bpf/sock_example.c) 的重新实现。该程序统计回环接口上收到了 TCP、UDP 和 ICMP 数据包的数量。![Counting how many TCP, UDP and ICMP packets are received on the loopback interface.](eBPF-Part3-Diagram2.jpg) 102 | 103 | 与此前直接用 C 语言编写的方式不同,用 BCC 实现具有以下优势: 104 | 105 | - 忘掉原始字节码:你可以用更方便的 "限制性 C" 编写所有**后端**。 106 | - 不需要维护任何 LLVM 的 "限制性 C" 构建逻辑。代码被 BCC 在脚本执行时直接编译和加载。 107 | - 没有危险的 C 代码:对于编写**前端**和**加载器**来说,Python 是一种更安全的语言,不会出现像空解引用(null dereferences)的错误。 108 | - 代码更简洁,你可以专注于应用程序的逻辑,而不是具体的机器问题。 109 | - 脚本可以被复制并在任何地方运行(假设已经安装了 BCC),它不会被束缚在内核的源代码目录中。 110 | - 等等。 111 | 112 | 在上面的例子中,我们使用了 BPF.SOCKET_FILTER 程序类型,其结果是我们挂载的 C 函数得到一个网络数据包缓冲区作为 context 上下文参数【译者注:本例中为 struct \__sk_buff \*skb】。我们还可以使用 BPF.KPROBE 程序类型来探测任意的内核函数。我们继续优化,不再使用与上面相同的接口,而是使用一个特殊的 kprobe__* 函数名称前缀,以描述一个更高级别的 BCC API。 113 | 114 | ![Using a special kprobe__* function name prefix to illlustrace an even higher level BCC API.](eBPF-Part3-Diagram3.jpg) 115 | 116 | 这个例子来自于 [bcc/examples/tracing/bitehist.py](https://github.com/iovisor/bcc/blob/v0.8.0/examples/tracing/bitehist.py)。它通过挂载在 blk_account_io_completion() 内核函数来打印一个 I/O 块大小的直方图。 117 | 118 | 请注意:eBPF 的加载是根据 **kprobe**__blk_account_io_completion() 函数的名称自动发生的(加载器隐含实现)! 【译者注:kprobe\_\_ 前缀会被 BCC 编译代码过程中自动识别并转换成对应的附加函数调用】从用 libbpf 在 C 语言中编写和加载字节码以来,我们已经走了很远。 119 | 120 | 121 | 122 | ## 4. 层级 3:Python 太低级了:BPFftrace 123 | 124 | 在某些用例中,BCC 仍然过于底层,例如在事件响应中检查系统时,时间至关重要,需要快速做出决定,而编写 python/"限制性 C" 会花费太多时间,因此 BPFtrace 建立在 BCC 之上,通过特定领域语言(受 AWK 和 C 启发)提供更高级别的抽象。根据[声明帖](http://www.brendangregg.com/blog/2018-10-08/dtrace-for-linux-2018.html),该语言类似于 DTrace 语言实现,也被称为 DTrace 2.0,并提供了良好的介绍和例子。 125 | 126 | BPFtrace 在一个强大而安全(但与 BCC 相比仍有局限性)的语言中抽象出如此多的逻辑,是非常让人惊奇的。这个单行 shell 程序统计了每个用户进程系统调用的次数(访问[内置变量](https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#1-builtins)、[map 函数](https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#map-functions) 和[count()](https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md#2-count-count)文档获取更多信息)。 127 | 128 | ```bash 129 | bpftrace -e 'tracepoint:raw_syscalls:sys_enter {@[pid, comm] = count();}' 130 | ``` 131 | 132 | BPFtrace 在某些方面仍然是一个正在进行的工作。例如,目前还没有简单的方法来定义和运行一个套接字过滤器来实现像我们之前所列举的 sock_example 这样的工具。它可能通过在 BPFtrace 中用 kprobe:netif_receive_skb 钩子完成,但这种情况下 BCC 仍然是一个更好的套接字过滤工具。在任何情况下(即使在目前的状态下),BPFTrace 对于在寻求 BCC 的全部功能之前的快速分析/调试仍然非常有用。 133 | 134 | 135 | 136 | ## 5. 层级 4:云环境中的 eBPF:IOVisor 137 | 138 | [IOVisor](https://www.iovisor.org/) 是 Linux 基金会的一个[合作项目](https://www.linuxfoundation.org/projects/),基于本系列文章中介绍的 eBPF 虚拟机和工具。它使用了一些非常高层次的热门概念,如 "通用输入/输出",专注于向云/数据中心开发人员和用户提供 eBPF 技术。 139 | 140 | - 内核 eBPF 虚拟机成为 "IO Visor 运行时引擎" 141 | - 编译器后端成为 "IO Visor 编译器后端" 142 | - 一般的 eBPF 程序被重新命名为 "IO 模块" 143 | - 实现包过滤器的特定 eBPF 程序成为 "IO 数据平面模块/组件" 144 | - 等等。 145 | 146 | 考虑到原来的名字(扩展的伯克利包过滤器),并没有代表什么意义,也许所有这些重命名都是受欢迎和有价值的,特别是如果它能使更多的行业利用 eBPF 的力量。 147 | 148 | IOVisor 项目创建了 [Hover 框架](https://github.com/iovisor/iomodules),也被称为 "IO 模块管理器",它是一个管理 eBPF 程序(或 IO 模块)的用户空间后台服务程序,能够将 IO 模块推送和拉取到云端,这类似于 Docker daemon 发布/获取镜像的方式。它提供了一个 CLI,Web-REST 接口,也有一个[花哨的 Web UI](https://github.com/iovisor/hoverui)。Hover 的重要部分是用 Go 编写的,因此,除了正常的 BCC 依赖性外,它还依赖于 Go 的安装,这使得它体积变得很大,这并不适合我们最终在第 4 部分中的提及的小型嵌入式设备。 149 | 150 | 151 | 152 | ## 6. 总结 153 | 154 | 在这一部分,我们研究了建立在 eBPF 虚拟机之上的用户空间生态系统,以提高开发人员的工作效率和简化 eBPF 程序部署。这些工具使得使用 eBPF 非常容易,用户只需 "apt-get install bpftrace" 就可以运行单行程序,或者使用 Hover 守护程序将 eBPF 程序(IO 模块)部署到 1000 台机器上。然而,所有这些工具,尽管它们给开发者和用户提供了所有的力量,但却需要很大的磁盘空间,甚至可能无法在 32 位 ARM 系统上运行,这使得它们不是很适合小型嵌入式设备,所以这就是为什么在第 4 部分我们将探索其他项目,试图缓解运行针对嵌入式设备生态系统的 eBPF 程序。 155 | 156 | **[继续阅读(eBPF 概述:第 4 部分:在嵌入式系统运行)](https://www.collabora.com/news-and-blog/blog/2019/05/06/an-ebpf-overview-part-4-working-with-embedded-systems/)...** 157 | 158 | -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-4-working-with-embedded-systems/eBPF-Part4-Diagram1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/an-ebpf-overview-part-4-working-with-embedded-systems/eBPF-Part4-Diagram1.jpg -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-4-working-with-embedded-systems/eBPF-Part4-Diagram2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/an-ebpf-overview-part-4-working-with-embedded-systems/eBPF-Part4-Diagram2.jpg -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-4-working-with-embedded-systems/eBPF-Part4-Diagram3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/an-ebpf-overview-part-4-working-with-embedded-systems/eBPF-Part4-Diagram3.jpg -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-4-working-with-embedded-systems/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | original: "https://www.collabora.com/news-and-blog/blog/2019/05/06/an-ebpf-overview-part-4-working-with-embedded-systems/" 3 | author: "Adrian Ratiu" 4 | translator: "DavadDi" 5 | reviewer: ["yangchuansheng“] 6 | title: "eBPF 概述:第 4 部分:在嵌入式系统运行" 7 | summary: "在这一部分中,我们将从另外一个视角来分析项目,尝试解决嵌入式 Linux 系统所面临的一些独特的问题:如需要非常小的自定义操作系统镜像,不能容纳完整的 BCC LLVM 工具链/python 安装,或试图避免同时维护主机的交叉编译(本地)工具链和交叉编译的目标编译器工具链,以及其相关的构建逻辑,即使在使用像 OpenEmbedded/Yocto 这样的高级构建系统时也很重要。" 8 | categories: "译文" 9 | tags: ["ebpf","overview"] 10 | originalPublishDate: 2019-05-06 11 | publishDate: 2021-09-09 12 | --- 13 | 14 | ## 1. 前言 15 | 16 | 在本系列的[第 1 部分](https://www.collabora.com/news-and-blog/blog/2019/04/05/an-ebpf-overview-part-1-introduction/)和[第 2 部分](https://www.collabora.com/news-and-blog/blog/2019/04/15/an-ebpf-overview-part-2-machine-and-bytecode/),我们介绍了 eBPF 虚拟机内部工作原理,在[第 3 部分](https://www.collabora.com/news-and-blog/blog/2019/04/26/an-ebpf-overview-part-3-walking-up-the-software-stack/)我们研究了基于底层虚拟机机制之上开发和使用 eBPF 程序的主流方式。 17 | 18 | 在这一部分中,我们将从另外一个视角来分析项目,尝试解决嵌入式 Linux 系统所面临的一些独特的问题:如需要非常小的自定义操作系统镜像,不能容纳完整的 BCC LLVM 工具链/python 安装,或试图避免同时维护主机的交叉编译(本地)工具链和交叉编译的目标编译器工具链,以及其相关的构建逻辑,即使在使用像 OpenEmbedded/Yocto 这样的高级构建系统时也很重要。 19 | 20 | 21 | 22 | ## 2. 关于可移植性 23 | 24 | 在第 3 部分研究的运行 eBPF/BCC 程序的主流方式中,可移植性并不是像在嵌入式设备上面临的问题那么大:eBPF 程序是在被加载的同一台机器上编译的,使用已经运行的内核,而且头文件很容易通过发行包管理器获得。嵌入式系统通常运行不同的 Linux 发行版和不同的处理器架构,与开发人员的计算机相比,有时具有重度修改或上游分歧的内核,在构建配置上也有很大的差异,或还可能使用了只有二进制的模块。 25 | 26 | eBPF 虚拟机的字节码是通用的(并未与特定机器相关),所以一旦编译好 eBPF 字节码,将其从 x86_64 移动到 ARM 设备上并不会引起太多问题。当字节码探测内核函数和数据结构时,问题就开始了,这些函数和数据结构可能与目标设备的内核不同或者会不存在,所以至少目标设备的内核头文件必须存在于构建 eBPF 程序字节码的主机上。新的功能或 eBPF 指令也可能被添加到以后的内核中,这可以使 eBPF 字节码向前兼容,但不能在内核版本之间向后兼容(参见[内核版本与 eBPF 功能](https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md))。建议将 eBPF 程序附加到稳定的内核 ABI 上,如跟踪点(tracepoint),这可以缓解常见的可移植性。 27 | 28 | 最近一个重要的工作已经开始,通过在 LLVM 生成的 eBPF 对象代码中嵌入数据类型信息,通过增加 BTF(BTF 类型格式)数据,以增加 eBPF 程序的可移植性(CO-RE 一次编译,到处运行)。更多信息见这里的[补丁](https://lwn.net/Articles/750695/)和[文章](https://lwn.net/Articles/773198/)。这很重要,因为 BTF 涉及到 eBPF 软件技术栈的所有部分(内核虚拟机和验证器、clang/LLVM 编译器、BCC 等),但这种方式可带来很大的便利,允许重复使用现有的 BCC 工具,而不需要特别的 eBPF 交叉编译和在嵌入式设备上安装 LLVM 或运行 BPFd。截至目前,CO-RE BTF 工作仍处于早期开发阶段,还需要付出相当多的工作才能可用【译者注:当前在高版本内核已经可以使用或者编译内核时启用了 BTF 编译选项】。也许我们会在其完全可用后再发表一篇博文。 29 | 30 | 31 | 32 | ## 3. BPFd 33 | 34 | [BPFd](https://lwn.net/Articles/744522/)(项目地址 )更像是一个为 Android 设备开发的概念验证,后被放弃,转而通过 [adeb](https://github.com/joelagnel/adeb) 包运行一个完整的设备上的 BCC 工具链【译者注:BCC 在 adeb 的编译文档参见[这里](https://github.com/joelagnel/adeb/blob/master/BCC.md)】。如果一个设备足够强大,可以运行 Android 和 Java,那么它也可能可以安装 BCC/LLVM/python。尽管这个实现有些不完整(通信是通过 Android USB 调试桥或作为一个本地进程完成的,而不是通过一个通用的传输层),但这个设计很有趣,有足够时间和资源的人可以把它拿起来合并,继续搁置的 [PR 工作](https://github.com/iovisor/bcc/pull/1675)。 35 | 36 | 简而言之,BPFd 是一个运行在嵌入式设备上的守护程序,作为本地内核/libbpf 的一个远程过程调用(RPC)接口。Python 在主机上运行,调用 BCC 来编译/部署 eBPF 字节码,并通过 BPFd 创建/读取 map。BPFd 的主要优点是,所有的 BCC 基础设施和脚本都可以工作,而不需要在目标设备上安装 BCC、LLVM 或 python,BPFd 二进制文件只有 100kb 左右的大小,并依赖 libc。 37 | 38 | ![BPFd](eBPF-Part4-Diagram1.jpg) 39 | 40 | ## 4. Ply 41 | 42 | [ply](https://wkz.github.io/ply/) 项目实现了一种与 BPFtrace 非常相似的高级领域特定语言(受到 AWK 和 C 的启发),其明确的目的是将运行时的依赖性降到最低。它只依赖于一个现代的 libc(不一定是 GNU 的 libc)和 shell(与 sh 兼容)。Ply 本身实现了一个 eBPF 编译器,需要根据目标设备的内核头文件进行构建,然后作为一个单一的二进制库和 shell 包装器部署到目标设备上。 43 | 44 | ![Ply](eBPF-Part4-Diagram2.jpg) 45 | 46 | 为了更好解释 ply,我们把第 3 部分中的 BPFtrace 例子和与 ply 实现进行对比: 47 | 48 | - BPFtrace:要运行该例子,你需要数百 MB 的 LLVM/clang、libelf 和其他依赖项: 49 | 50 | `bpftrace -e 'tracepoint:raw_syscalls:sys_enter {@[pid, comm] = count();}'` 51 | 52 | - ply:你只需要一个 ~50kb 的二进制文件,它产生的结果是相同的,语法几乎相同: 53 | 54 | `ply 'tracepoint:raw_syscalls/sys_enter {@[pid, comm] = count();}'` 55 | 56 | Ply 仍在大量开发中(最近的 v2.0 版本是完全重写的)【译者注:当前最新版本为 2.1.1,最近一次代码提交是 8 个月前,活跃度一般】,除了一些示例之外,该语言还不不稳定或缺乏文档,它不如完整的 BCC 强大,也没有 BPFtrace 丰富的功能特性,但它对于通过 ssh 或串行控制台快速调试远程嵌入式设备仍然非常有用。 57 | 58 | 59 | 60 | ## 5. Gobpf 61 | 62 | [Gobpf](https://github.com/iovisor/gobpf) 及其合并的子项目(goebpf, gobpf-elf-loader),是 IOVisor 项目的一部分,为 BCC 提供 Golang 语言绑定。eBPF 的内核逻辑仍然用 "限制性 C" 编写,并由 LLVM 编译,只有标准的 python/lua 用户空间脚本被 Go 取代。这个项目对嵌入式设备的意义在于它的 eBPF [elf 加载模块](https://github.com/iovisor/gobpf/tree/master/elf),其可以被交叉编译并在嵌入式设备上独立运行,以加载 eBPF 程序至内核并与与之交互。 63 | 64 | ![Gobpf](eBPF-Part4-Diagram3.jpg) 65 | 66 | 值得注意的是,go 加载器可以被写成通用的(我们很快就会看到),因此它可以加载和运行任何 eBPF 字节码,并在本地重新用于多个不同的跟踪会话。 67 | 68 | 使用 gobpf 很痛苦的,主要是因为缺乏文档。目前最好的 "文档" 是 [tcptracer 源码](https://github.com/weaveworks/tcptracer-bpf),它相当复杂(他们使用 kprobes 而不依赖于特定的内核版本!),但从它可以学到很多。Gobpf 本身也是一项正在进行的工作:虽然 elf 加载器相当完整,并支持加载带有套接字、(k|u)probes、tracepoints、perf 事件等加载的 eBPF ELF 对象,但 bcc go 绑定模块还不容易支持所有这些功能。例如,尽管你可以写一个 socket_ilter ebpf 程序,将其编译并加载到内核中,但你仍然不能像 BCC 的 python 那样从 go 用户空间轻松地与 eBPF 进行交互,BCC 的 API 更加成熟和用户友好。无论如何,gobpf 仍然比其他具有类似目标的项目处于更好的状态。 69 | 70 | 让我们研究一个简单的例子来说明 gobpf 如何工作的。首先,我们将在本地 x86_64 机器上运行它,然后交叉编译并在 32 位 ARMv7 板上运行它,比如流行的 Beaglebone 或 Raspberry Pi。我们的文件目录结构如下: 71 | 72 | ```bash 73 | $ find . -type f 74 | ./src/open-example.go 75 | ./src/open-example.c 76 | ./Makefile 77 | ``` 78 | 79 | **open-example.go**:这是建立在 gobpf/elf 之上的 eBPF ELF 加载器。它把编译好的 "限制性 C" ELF 对象作为参数,加载到内核并运行,直到加载器进程被杀死,这时内核会自动卸载 eBPF 逻辑【译者注:通常情况是这样的,也有场景加载器退出,ebpf 程序继续运行的】。我们有意保持加载器的简单性和通用性(它加载在对象文件中发现的任何探针),因此加载器可以被重复使用。更复杂的逻辑可以通过使用 [gobpf 绑定](https://github.com/iovisor/gobpf/blob/master/bcc/module.go) 模块添加到这里。 80 | 81 | ```go 82 | package main 83 | 84 | import ( 85 | "fmt" 86 | "os" 87 | "os/signal" 88 | "github.com/iovisor/gobpf/elf" 89 | ) 90 | 91 | func main() {mod := elf.NewModule(os.Args[1]) 92 | 93 | err := mod.Load(nil); 94 | if err != nil {fmt.Fprintf(os.Stderr, "Error loading'%s'ebpf object: %v\n", os.Args[1], err)os.Exit(1) 95 | } 96 | 97 | err = mod.EnableKprobes(0) 98 | if err != nil {fmt.Fprintf(os.Stderr, "Error loading kprobes: %v\n", err) 99 | os.Exit(1) 100 | } 101 | 102 | sig := make(chan os.Signal, 1) 103 | signal.Notify(sig, os.Interrupt, os.Kill) 104 | // ... 105 | } 106 | ``` 107 | 108 | **open-example.c**:这是上述加载器加载至内核的 "限制性 C" 源代码。它挂载在 do_sys_open 函数,并根据 [ftrace format](https://raw.githubusercontent.com/torvalds/linux/v4.20/Documentation/trace/ftrace.rst) 将进程命令、PID、CPU、打开文件名和时间戳打印到跟踪环形缓冲区,(详见 "输出格式" 一节)。打开的文件名作为 [do_sys_open call](https://github.com/torvalds/linux/blob/v4.20/fs/open.c#L1048) 的第二个参数传递,可以从代表函数入口的 CPU 寄存器的上下文结构中访问。 109 | 110 | ```c 111 | #include 112 | #include 113 | #include 114 | 115 | SEC("kprobe/do_sys_open") 116 | int kprobe__do_sys_open(struct pt_regs *ctx) 117 | {char file_name[256]; 118 | 119 | bpf_probe_read(file_name, sizeof(file_name), PT_REGS_PARM2(ctx)); 120 | 121 | char fmt[] = "file %s\n"; 122 | bpf_trace_printk(fmt, sizeof(fmt), &file_name); 123 | 124 | return 0; 125 | } 126 | 127 | char _license[] SEC("license") = "GPL"; 128 | __u32 _version SEC("version") = 0xFFFFFFFE; 129 | ``` 130 | 131 | 在上面的代码中,我们定义了特定的 "SEC" 区域,这样 gobpf 加载器就可获取到哪里查找或加载内容的信息。在我们的例子中,区域为 kprobe、license 和 version。特殊的 0xFFFFFFFE 值告诉加载器,这个 eBPF 程序与任何内核版本都是兼容的,因为打开系统调用而破坏用户空间的机会接近于 0。 132 | 133 | **Makefile**:这是上述两个文件的构建逻辑。注意我们是如何在 include 路径中加入 "arch/x86/..." 的;在 ARM 上它将是 "arch/arm/..."。 134 | 135 | ```makefile 136 | SHELL=/bin/bash -o pipefail 137 | LINUX_SRC_ROOT="/home/adi/workspace/linux" 138 | FILENAME="open-example" 139 | 140 | ebpf-build: clean go-build 141 | clang \ 142 | -D__KERNEL__ -fno-stack-protector -Wno-int-conversion \ 143 | -O2 -emit-llvm -c "src/${FILENAME}.c" \ 144 | -I ${LINUX_SRC_ROOT}/include \ 145 | -I ${LINUX_SRC_ROOT}/tools/testing/selftests \ 146 | -I ${LINUX_SRC_ROOT}/arch/x86/include \ 147 | -o - | llc -march=bpf -filetype=obj -o "${FILENAME}.o" 148 | 149 | go-build: 150 | go build -o ${FILENAME} src/${FILENAME}.go 151 | 152 | clean: 153 | rm -f ${FILENAME}* 154 | ``` 155 | 156 | 运行上述 makefile 在当前目录下产生两个新文件: 157 | 158 | * open-example:这是编译后的 src/*.go 加载器。它只依赖于 libc 并且可以被复用来加载多个 eBPF ELF 文件运行多个跟踪。 159 | 160 | * open-example.o:这是编译后的 eBPF 字节码,将在内核中加载。 161 | 162 | “open-example" 和 "open-example.o" ELF 二进制文件可以进一步合并成一个;加载器可以包括 eBPF 二进制文件作为资产,也可以像 [tcptracer](https://github.com/weaveworks/tcptracer-bpf/blob/master/pkg/tracer/tcptracer-ebpf.go#L80) 那样在其源代码中直接存储为字节数。然而,这超出了本文的范围。 163 | 164 | 运行例子显示以下输出(见 [ftrace 文档]((https://raw.githubusercontent.com/torvalds/linux/v4.20/Documentation/trace/ftrace.rst)) 中的 "输出格式" 部分)。 165 | 166 | ```bash 167 | # (./open-example open-example.o &) && cat /sys/kernel/debug/tracing/trace_pipe 168 | electron-17494 [007] ...3 163158.937350: 0: file /proc/self/maps 169 | systemd-1 [005] ...3 163160.120796: 0: file /proc/29261/cgroup 170 | emacs-596 [006] ...3 163163.501746: 0: file /home/adi/ 171 | (...) 172 | ``` 173 | 174 | 沿用我们在本系列的第 3 部分中定义的术语,我们的 eBPF 程序有以下部分组成: 175 | - **后端**:是 open-example.o ELF 对象。它将数据写入内核跟踪环形缓冲区。 176 | 177 | - **加载器**:这是编译过的 open-example 二进制文件,包含 gobpf/elf 加载器模块。只要它运行,数据就会被添加到跟踪缓冲区中。 178 | 179 | - **前端**:这就是 `cat /sys/kernel/debug/tracing/trace_pipe`。非常 UNIX 风格。 180 | 181 | - **数据结构**:内核跟踪环形缓冲区。 182 | 183 | 现在将我们的例子交叉编译为 32 位 ARMv7。 基于你的 ARM 设备运行的内核版本: 184 | - 内核版本>=5.2:只需改变 makefile,就可以交叉编译与上述相同的源代码。 185 | - 内核版本<5.2:除了使用新的 makefile 外,还需要将 PT_REGS_PARM* 宏从 [这个 patch](https://lore.kernel.org/bpf/20190304205019.15071-1-adrian.ratiu@collabora.com/) 复制到 "受限制 C" 代码。 186 | 187 | 新的 makefile 告诉 LLVM/Clang,eBPF 字节码以 ARMv7 设备为目标,使用 32 位 eBPF 虚拟机子寄存器地址模式,以便虚拟机可以正确访问本地处理器提供的 32 位寻址内存(还记得第 2 部分中介绍的所有 eBPF 虚拟机寄存器默认为 64 位宽),设置适当的包含路径,然后指示 Go 编译器使用正确的交叉编译设置。在运行这个 makefile 之前,需要一个预先存在的交叉编译器工具链,它被指向 CC 变量。 188 | 189 | ```makefile 190 | SHELL=/bin/bash -o pipefail 191 | LINUX_SRC_ROOT="/home/adi/workspace/linux" 192 | FILENAME="open-example" 193 | 194 | ebpf-build: clean go-build 195 | clang \ 196 | --target=armv7a-linux-gnueabihf \ 197 | -D__KERNEL__ -fno-stack-protector -Wno-int-conversion \ 198 | -O2 -emit-llvm -c "src/${FILENAME}.c" \ 199 | -I ${LINUX_SRC_ROOT}/include \ 200 | -I ${LINUX_SRC_ROOT}/tools/testing/selftests \ 201 | -I ${LINUX_SRC_ROOT}/arch/arm/include \ 202 | -o - | llc -march=bpf -filetype=obj -o "${FILENAME}.o" 203 | 204 | go-build: 205 | GOOS=linux GOARCH=arm CGO_ENABLED=1 CC=arm-linux-gnueabihf-gcc \ 206 | go build -o ${FILENAME} src/${FILENAME}.go 207 | 208 | clean: 209 | rm -f ${FILENAME}* 210 | ``` 211 | 212 | 运行新的 makefile,并验证产生的二进制文件已经被正确地交叉编译: 213 | 214 | ```bash 215 | [adi@iwork]$ file open-example* 216 | open-example: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter (...), stripped 217 | open-example.o: ELF 64-bit LSB relocatable, *unknown arch 0xf7* version 1 (SYSV), not stripped 218 | ``` 219 | 220 | 然后将加载器和字节码复制到设备上,与在 x86_64 主机上使用上述相同的命令来运行。记住,只要修改和重新编译 C eBPF 代码,加载器就可以重复使用,用于运行不同的跟踪。 221 | 222 | ```bash 223 | [root@ionelpi adi]# (./open-example open-example.o &) && cat /sys/kernel/debug/tracing/trace_pipe 224 | ls-380 [001] d..2 203.410986: 0: file /etc/ld-musl-armhf.path 225 | ls-380 [001] d..2 203.411064: 0: file /usr/lib/libcap.so.2 226 | ls-380 [001] d..2 203.411922: 0: file / 227 | zcat-397 [002] d..2 432.676010: 0: file /etc/ld-musl-armhf.path 228 | zcat-397 [002] d..2 432.676237: 0: file /usr/lib/libtinfo.so.5 229 | zcat-397 [002] d..2 432.679431: 0: file /usr/bin/zcat 230 | gzip-397 [002] d..2 432.693428: 0: file /proc/ 231 | gzip-397 [002] d..2 432.693633: 0: file config.gz 232 | ``` 233 | 234 | 由于加载器和字节码加起来只有 2M 大小,这是一个在嵌入式设备上运行 eBPF 的相当好的方法,而不需要完全安装 BCC/LLVM。 235 | 236 | 237 | 238 | ## 6. 总结 239 | 240 | 在本系列的第 4 部分,我们研究了可以用于在小型嵌入式设备上运行 eBPF 程序的相关项目。不幸的是,当前使用这些项目还是比较很困难的:它们有的被遗弃或缺乏人力,在早期开发时一切都在变化,或缺乏基本的文档,需要用户深入到源代码中并自己想办法解决。正如我们所看到的,gobpf 项目作为 BCC/python 的替代品是最有活力的,而 ply 也是一个有前途的 BPFtrace 替代品,其占用空间最小。随着更多的工作投入到这些项目中以降低使用者的门槛,eBPF 的强大功能可以用于资源受限的嵌入式设备,而无需移植/安装整个 BCC/LLVM/python/Hover 技术栈。 241 | 242 | **[继续阅读(eBPF 概述:第 5 部分:跟踪用户进程)](https://www.collabora.com/news-and-blog/blog/2019/05/14/an-ebpf-overview-part-5-tracing-user-processes/)...** 243 | -------------------------------------------------------------------------------- /2021/09/an-ebpf-overview-part-5-tracing-user-processes/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | original: "https://www.collabora.com/news-and-blog/blog/2019/05/14/an-ebpf-overview-part-5-tracing-user-processes/" 3 | author: "Adrian Ratiu" 4 | translator: "DavadDi" 5 | reviewer: ["yangchuansheng“] 6 | title: "eBPF 概述:第 5 部分:跟踪用户进程" 7 | summary: " eBPF 成为内核工作的不二之选。 然而,到目前为止,我们故意避免深入讨论用户空间跟踪,因为它值得特别对待,因此我们在第 5 部分中专门讨论" 8 | categories: "译文" 9 | tags: ["ebpf","overview"] 10 | originalPublishDate: 2019-05-14 11 | publishDate: 2021-09-13 12 | --- 13 | 14 | ## 1. 前言 15 | 16 | 在之前的部分中,我们专注于 Linux 内核跟踪,在我们看来,基于 eBPF 的项目是最安全、最广泛可用和最有效的方法(eBPF 在 Linux 中完全是上游支持的,保证稳定的 ABI,在几乎所有的发行版中都默认启用,并可与所有其他跟踪机制集成)。 eBPF 成为内核工作的不二之选。 然而,到目前为止,我们故意避免深入讨论用户空间跟踪,因为它值得特别对待,因此我们在第 5 部分中专门讨论。 17 | 18 | 首先,我们将讨论为什么使用,然后我们将 eBPF 用户跟踪分为静态和动态两类分别讨论。 19 | 20 | ## 2. 为什么要在用户空间使用 eBPF? 21 | 22 | 最重要的用户问题是,既然有这么多其他的调试器/性能分析器/跟踪器,这么多针对特定语言或操作系统的工具为同样的任务而开发,为什么还要使用 eBPF 来跟踪用户空间进程?答案并不简单,根据不同的使用情况,eBPF 可能不是最佳解决方案;在庞大的用户空间生态系统中,并没有一个适合所有情况的调试/跟踪的项目。 23 | 24 | eBPF 跟踪具有以下优势: 25 | 26 | - 它为内核和用户空间提供了一个统一的跟踪接口,与其他工具([k,u]probe, (dtrace)tracepoint 等)使用的机制兼容。2015 年的文章[选择 linux 跟踪器](http://www.brendangregg.com/blog/2015-07-08/choosing-a-linux-tracer.html)虽然有些过时,但其提供了很好的见解,说明使用所有不同的工具有多困难,要花多少精力。有一个统一的、强大的、安全的、可广泛使用的框架来满足大多数跟踪的需要,是非常有价值的。一些更高级别的工具,如 Perf/SystemTap/DTrace,正在 eBPF 的基础上重写(成为 eBPF 的前端),所以了解 eBPF 有助于使用它们。 27 | 28 | - eBPF 是完全可编程的。Perf/ftrace 和其他工具都需要在事后处理数据,而 eBPF 可直接在内核/应用程序中运行自定义的高级本地编译的 C/Python/Go 检测代码。它可以在多个 eBPF 事件运行之间存储数据,例如以基于函数状态/参数计算每个函数调用统计数据。 29 | 30 | - eBPF 可以跟踪系统中的一切,它并不局限于特定的应用程序。例如可以在共享库上设置 uprobes 并跟踪链接和调用它的所有进程。 31 | 32 | - 很多调试器需要暂停程序来观察其状态或降低运行时性能,从而难以进行实时分析,尤其是在生产工作负载上。因为 eBPF 附加了 JIT 的本地编译的检测代码,它的性能影响是最小的,不需要长时间暂停执行。 33 | 34 | 诚然,eBPF 也有一些缺点: 35 | 36 | - eBPF 不像其他跟踪器那样可以移植。该虚拟机主要是在 Linux 内核中开发的(有一个正在进行的 BSD 移植),相关的工具是基于 Linux 开发的。 37 | - eBPF 需要一个相当新的内核。例如对于 MIPS 的支持是在 v4.13 中加入的,但绝大多数 MIPS 设备在运行的内核都比 v4.13 老。 38 | - 一般来说,eBPF 不容易像语言或特定应用的用户空间调试器那样提供一样多的洞察力。例如,Emacs 的核心是一个用 C 语言编写的 ELISP 解释器:eBPF 可以通过挂载在 Emacs 运行时的 C 函数调用来跟踪/调试 ELISP 程序,但它不了解更高级别的 ELISP 语言实现,因此使用 Emacs 提供的特殊 ELISP 语言特定跟踪器和调试器变得更加有用。另一个例子是调试在 Web 浏览器引擎中运行的 JavaScript 应用程序。 39 | - 因为 "普通 eBPF" 在 Linux 内核中运行,所以每次 eBPF 检测用户进程时都会发生内核 - 用户上下文切换。这对于调试性能关键的用户空间代码来说可能很昂贵(也许可以使用[用户空间 eBPF 虚拟机](https://github.com/iovisor/ubpf)项目来避免这种切换成本?)。这对于调试性能关键的用户空间代码来说是很昂贵的(也许[用户空间 eBPF VM](https://github.com/iovisor/ubpf) 项目可以用来避免这种切换成本?)。这种上下文切换比正常的调试器(或像 strace 这样的工具)要便宜得多,所以它通常可以忽略不计,但在这种情况下,像 LTTng 这样能够完全运行在用户空间的跟踪器可能更合适。 40 | 41 | ## 3. 静态跟踪点(USDT 探针) 42 | 43 | 静态跟踪点(tracepoint),在用户空间也被称为 USDT(用户静态定义的跟踪)探针(应用程序中感兴趣的特定位置),跟踪器可以在此处挂载检查代码执行和数据。它们由开发人员在源代码中明确定义,通常在编译时用 "--enable-trace" 等标志启用。静态跟踪点的优势在于它们不会经常变化:开发人员通常会保持稳定的静态跟踪 ABI,所以跟踪工具在不同的应用程序版本之间工作,这很有用,例如当升级 PostgreSQL 安装并遇到性能降低时。 44 | 45 | ### 3.1 预定义的跟踪点 46 | [BCC-tools](https://github.com/iovisor/bcc/tree/master/tools) 包含很多有用的且经过测试的工具,可以与特定应用程序或语言运行时定义的跟踪点进行交互。对于我们的示例,我们将跟踪 Python 应用程序。确保你在构建了 python3 时启用了 "--enable-trace" 标识,并在 python 二进制文件或 libpython(取决于你构建方式)上运行 [tplist](https://github.com/iovisor/bcc/blob/master/tools/tplist.py) 以确认跟踪点被启用: 47 | 48 | ```bash 49 | $ tplist -l /usr/lib/libpython3.7m.so 50 | b'/usr/lib/libpython3.7m.so' b'python':b'import__find__load__start' 51 | b'/usr/lib/libpython3.7m.so' b'python':b'import__find__load__done' 52 | b'/usr/lib/libpython3.7m.so' b'python':b'gc__start' 53 | b'/usr/lib/libpython3.7m.so' b'python':b'gc__done' 54 | b'/usr/lib/libpython3.7m.so' b'python':b'line' 55 | b'/usr/lib/libpython3.7m.so' b'python':b'function__return' 56 | b'/usr/lib/libpython3.7m.so' b'python':b'function__entry' 57 | ``` 58 | 59 | 首先我们使用 BCC 提供的一个很酷的跟踪工具 [uflow](https://github.com/iovisor/bcc/blob/master/tools/lib/uflow_example.txt),来跟踪 python 的[简单 http 服务器](https://github.com/python/cpython/blob/3.7/Lib/http/server.py)的执行流程。跟踪应该是不言自明的,箭头和缩进表示函数的进入/退出。我们在这个跟踪中看到的是一个工作线程如何在 CPU 3 上退出,而主线程则准备在 CPU 0 上为其他传入的 http 请求提供服务。 60 | 61 | ```bash 62 | $ python -m http.server >/dev/null & sudo ./uflow -l python $! 63 | [4] 11727 64 | Tracing method calls in python process 11727... Ctrl-C to quit. 65 | CPU PID TID TIME(us) METHOD 66 | 3 11740 11757 7.034 /usr/lib/python3.7/_weakrefset.py._remove 67 | 3 11740 11757 7.034 /usr/lib/python3.7/threading.py._acquire_restore 68 | 0 11740 11740 7.034 /usr/lib/python3.7/threading.py.__exit__ 69 | 0 11740 11740 7.034 /usr/lib/python3.7/socketserver.py.service_actions 70 | 0 11740 11740 7.034 /usr/lib/python3.7/selectors.py.select 71 | 0 11740 11740 7.532 /usr/lib/python3.7/socketserver.py.service_actions 72 | 0 11740 11740 7.532 73 | ``` 74 | 75 | 接下来,我们希望在跟踪点被命中时运行我们的自定义代码,因此我们不完全依赖 BCC 提供的任何工具。 以下示例将自身挂钩到 python 的 function__entry 跟踪点(请参阅 [python 检测](https://docs.python.org/3/howto/instrumentation.html)文档)并在有人下载文件时通知我们: 76 | 77 | ```python 78 | #!/usr/bin/env python 79 | from bcc import BPF, USDT 80 | import sys 81 | 82 | bpf = """ 83 | #include 84 | 85 | static int strncmp(char *s1, char *s2, int size) {for (int i = 0; i < size; ++i) 86 | if (s1[i] != s2[i]) 87 | return 1; 88 | return 0; 89 | } 90 | 91 | int trace_file_transfers(struct pt_regs *ctx) { 92 | uint64_t fnameptr; 93 | char fname[128]={0}, searchname[9]="copyfile"; 94 | 95 | bpf_usdt_readarg(2, ctx, &fnameptr); 96 | bpf_probe_read(&fname, sizeof(fname), (void *)fnameptr); 97 | 98 | if (!strncmp(fname, searchname, sizeof(searchname))) 99 | bpf_trace_printk("Someone is transferring a file!\\n"); 100 | return 0; 101 | }; 102 | """ 103 | 104 | u = USDT(pid=int(sys.argv[1])) 105 | u.enable_probe(probe="function__entry", fn_name="trace_file_transfers") 106 | b = BPF(text=bpf, usdt_contexts=[u]) 107 | while 1: 108 | try: 109 | (_, _, _, _, ts, msg) = b.trace_fields() 110 | except ValueError: 111 | continue 112 | print("%-18.9f %s" % (ts, msg)) 113 | ``` 114 | 115 | 我们通过再次附加到简单的 http-server 进行测试: 116 | 117 | ```bash 118 | $ python -m http.server >/dev/null & sudo ./trace_simplehttp.py $! 119 | [14] 28682 120 | 34677.450520000 b'Someone is transferring a file!' 121 | ``` 122 | 123 | 上面的例子告诉我们什么时候有人在下载文件,但它不能比这更详细的信息,比如关于谁在下载、下载什么文件等。这是因为 python 只默认启用了几个非常通用的跟踪点(模块加载、函数进入/退出等)。为了获得更多的信息,我们必须在感兴趣的地方定义我们自己的跟踪点,以便我们能够提取相关的数据。 124 | 125 | ### 3.2 定义我们自己的跟踪点 126 | 127 | 到目前为止,我们只使用别人定义的跟踪点,但是如果我们的应用程序并没有提供任何跟踪点,或者我们需要添加比现有跟踪点更多的信息,那么我们将不得不添加自己的跟踪点。 128 | 129 | 添加跟踪点方式有多种,例如 python-core 通过 [pydtrace.h](https://github.com/python/cpython/blob/v3.7.2/Include/pydtrace.h) 和 [pydtrace.d](https://github.com/python/cpython/blob/v3.7.2/Include/pydtrace.d) 使用 systemtap 的开发包 "systemtap-sdt-dev",但我们将采取另一种方法,使用 [libstapsdt](https://github.com/sthima/libstapsdt),因为它有一个更简单的 API,更轻巧(只依赖于 libelf),并支持多种语言绑定。为了保持一致性,我们再次把重点放在 python 上,但是跟踪点也可以用其他语言添加,这里有[一个 C 语言示例](https://github.com/sthima/libstapsdt/blob/master/example/demo.c)。 130 | 131 | 首先,我们给简单的 http 服务器打上补丁,公开跟踪点。代码应该是不言自明的:注意跟踪点的名字 **file_transfer** 及其参数,足够存储两个字符串指针和一个 32 位无符号整数,代表客户端 IP 地址,文件路径和文件大小。 132 | 133 | ```python 134 | diff --git a/usr/lib/python3.7/http/server.py b/usr/lib/python3.7/http/server.py 135 | index ca2dd50..af08e10 100644 136 | --- a/usr/lib/python3.7/http/server.py 137 | +++ b/usr/lib/python3.7/http/server.py 138 | @@ -107,6 +107,13 @@ from functools import partial 139 | 140 | from http import HTTPStatus 141 | 142 | +import stapsdt 143 | +provider = stapsdt.Provider("simplehttp") 144 | +probe = provider.add_probe("file_transfer", 145 | + stapsdt.ArgTypes.uint64, 146 | + stapsdt.ArgTypes.uint64, 147 | + stapsdt.ArgTypes.uint32)+provider.load() 148 | 149 | # Default error message template 150 | DEFAULT_ERROR_MESSAGE = """\ 151 | @@ -650,6 +657,8 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): 152 | f = self.send_head() 153 | if f: 154 | try: 155 | + path = self.translate_path(self.path) 156 | + probe.fire(self.address_string(), path, os.path.getsize(path)) 157 | self.copyfile(f, self.wfile) 158 | finally: 159 | f.close() 160 | ``` 161 | 162 | 运行打过补丁的服务器,我们可以使用 tplist 验证我们的 **file_transfer** 跟踪点在运行时是否存在: 163 | 164 | ```bash 165 | $ python -m http.server >/dev/null 2>&1 & tplist -p $! 166 | [1] 13297 167 | b'/tmp/simplehttp-Q6SJDX.so' b'simplehttp':b'file_transfer' 168 | b'/usr/lib/libpython3.7m.so.1.0' b'python':b'import__find__load__start' 169 | b'/usr/lib/libpython3.7m.so.1.0' b'python':b'import__find__load__done' 170 | ``` 171 | 172 | 我们将对上述示例中的跟踪器示例代码进行以下最重要的修改: 173 | 174 | - 它将其逻辑挂接到我们自定义的 **file_transfer** 跟踪点。 175 | - 它使用 [PERF EVENTS](https://perf.wiki.kernel.org/index.php/Tutorial) 来存储可以将任意结构传递到用户空间的数据,而不是我们之前使用的 ftrace 环形缓存区只能传输单个字符串。 176 | - 它**不**使用 **bpf_usdt_readarg** 来获取 USDT 提供的指针,而是直接在处理程序函数签名中声明它们。 这是一个显着的质量改善,可用于所有处理程序。 177 | - 此跟踪器明确使用 **[python2](https://perf.wiki.kernel.org/index.php/Tutorial)**,即使到目前为止我们所有的示例(包括上面的 python http.server 补丁) 使用 **[python3](https://perf.wiki.kernel.org/index.php/Tutorial)**。 希望将来所有 BCC API 和文档都能移植到 python 3。 178 | 179 | ```python 180 | #!/usr/bin/env python2 181 | from bcc import BPF, USDT 182 | import sys 183 | 184 | bpf = """ 185 | #include 186 | 187 | BPF_PERF_OUTPUT(events); 188 | 189 | struct file_transf {char client_ip_str[20]; 190 | char file_path[300]; 191 | u32 file_size; 192 | u64 timestamp; 193 | }; 194 | 195 | int trace_file_transfers(struct pt_regs *ctx, char *ipstrptr, char *pathptr, u32 file_size) {struct file_transf ft = {0}; 196 | 197 | ft.file_size = file_size; 198 | ft.timestamp = bpf_ktime_get_ns(); 199 | bpf_probe_read(&ft.client_ip_str, sizeof(ft.client_ip_str), (void *)ipstrptr); 200 | bpf_probe_read(&ft.file_path, sizeof(ft.file_path), (void *)pathptr); 201 | 202 | events.perf_submit(ctx, &ft, sizeof(ft)); 203 | return 0; 204 | }; 205 | """ 206 | 207 | def print_event(cpu, data, size): 208 | event = b["events"].event(data) 209 | print("{0}: {1} is downloding file {2} ({3} bytes)".format(event.timestamp, event.client_ip_str, event.file_path, event.file_size)) 210 | 211 | u = USDT(pid=int(sys.argv[1])) 212 | u.enable_probe(probe="file_transfer", fn_name="trace_file_transfers") 213 | b = BPF(text=bpf, usdt_contexts=[u]) 214 | b["events"].open_perf_buffer(print_event) 215 | 216 | while 1: 217 | try: 218 | b.perf_buffer_poll() 219 | except KeyboardInterrupt: 220 | exit() 221 | ``` 222 | 223 | 跟踪已打过补丁的服务器: 224 | 225 | ```bash 226 | $ python -m http.server >/dev/null 2>&1 & sudo ./trace_stapsdt.py $! 227 | [1] 5613 228 | 325540469950102: 127.0.0.1 is downloading file /home/adi/ (4096 bytes) 229 | 325543319100447: 127.0.0.1 is downloading file /home/adi/.bashrc (827 bytes) 230 | 325552448306918: 127.0.0.1 is downloading file /home/adi/workspace/ (4096 bytes) 231 | 325563646387008: 127.0.0.1 is downloading file /home/adi/workspace/work.tar (112640 bytes) 232 | (...) 233 | ``` 234 | 235 | 上面自定义的 **file_transfer** 跟踪点看起来很简单(直接 python 打印或日志记录调用可能有相同的效果),但它提供的机制非常强大:良好放置的跟踪点保证 ABI 稳定性,提供动态运行的能力安全、本地快速、**可编程**逻辑可以非常有助于快速分析和修复各种问题,而无需重新启动有问题的应用程序(重现问题可能需要很长时间)。 236 | 237 | ## 4. 动态探针(uprobes) 238 | 239 | 上面举例说明的静态跟踪点的问题在于,它们需要在源代码中明确定义,并且在修改跟踪点时需要重新构建应用程序。保证现有跟踪点的 ABI 稳定性对维护人员如何重新构建/重写跟踪点数据的代码施加了限制。因此,在某些情况下,完全运行时动态用户空间探测器(uprobes)是首选:它们以特别的方式直接在运行应用程序的内存中进行探测,而无需任何特殊的源代码定义。动态探测器可能会比较容易在应用程序版本之间失效,但即便如此,它们对于实时调试正在运行的实例也很有用。 240 | 241 | 虽然静态跟踪点对于跟踪用 Python 或 Java 等高级语言编写的应用程序很有用,但 uprobes 对此不太有用,因为它们工作比较底层,并且不了解语言运行时实现(静态跟踪点之所以可以工作,因为开发人员自行承担公开高级应用程序的相关数据)。然而,动态探测器对于调试语言实现/引擎本身或用没有运行时的语言(如 C)编写的应用程序很有用。 242 | 243 | 可以将 uprobe 添加到优化过(stripped)的二进制文件中,但用户必须手动计算进程内内存偏移位置,uprobe 应通过 objdump 和 /proc//maps 等工具附加到该位置([参见示例](https://github.com/torvalds/linux/blob/v4.20/Documentation/trace/uprobetracer.rst)),但这种方式比较痛苦且不可移植。 由于大多数发行版都提供调试符号包(或使用调试符号构建的快速方法)并且 BCC 使得使用带有符号名称解析的 uprobes 变得简单,因此绝大多数动态探测使用都是以这种方式进行的。 244 | 245 | [gethostlatency](https://github.com/iovisor/bcc/blob/master/tools/gethostlatency.py) BCC 工具通过将 uprobes 附加到 gethostbyname 和 libc 中的相关函数来打印 DNS 请求延迟。 要验证 libc 未优化(stripped)以便可以运行该工具(否则会引发 sybol-not-found 错误): 246 | 247 | ```bash 248 | $ file /usr/lib/libc-2.28.so 249 | /usr/lib/libc-2.28.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, (...), not stripped 250 | $ nm -na /usr/lib/libc-2.28.so | grep -i -e getaddrinfo 251 | 0000000000000000 a getaddrinfo.c 252 | ``` 253 | 254 | [gethostlatency](https://github.com/iovisor/bcc/blob/master/tools/gethostlatency.py) 代码与我们上面检查的跟踪点示例非常相似(并且在某些地方相同,它还使用 BPF_PERF_OUTPUT) ,所以我们不会在这里完整地发布它。 最相关的区别是使用 [BCC uprobe API](https://github.com/iovisor/bcc/blob/master/docs/reference_guide.md#4-attach_uprobe): 255 | 256 | ```python 257 | b.attach_uprobe(name="c", sym="getaddrinfo", fn_name="do_entry", pid=args.pid) 258 | b.attach_uretprobe(name="c", sym="getaddrinfo", fn_name="do_return", pid=args.pid) 259 | ``` 260 | 261 | 这里需要理解和记住的关键思想是:只要对我们的 BCC eBPF 程序做一些小的改动,我们就可以通过静态和动态探测来跟踪非常不同的应用程序、库甚至是内核。之前我们是静态跟踪 Python 应用程序,现在我们是动态地测量 libc 的主机名解析延时。通过对这些小的(小于 150LOC,很多是模板)例子进行类似的修改,可在运行的系统中跟踪任何内容,这非常安全,没有崩溃的风险或其他工具引起的问题(如调试器应用程序暂停/停顿)。 262 | 263 | ## 5. 总结 264 | 265 | 在第 5 部分中,我们研究了如何使用 eBPF 程序来跟踪用户空间应用程序。 使用 eBPF 完成这项任务的最大优势是它提供了一个统一的接口来安全有效地跟踪整个系统:可以在应用程序中重现错误,然后进一步跟踪到库或内核中,通过统一的编程框架/接口提供完整的系统可见性。 然而,eBPF 并不是银弹,尤其是在调试用高级语言编写的应用程序时,特定语言的工具可以更好地提供洞察力,或者对于那些运行旧版本 Linux 内核或需要非 Linux 系统的应用程序。 266 | 267 | -------------------------------------------------------------------------------- /2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-11.44.30-AM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-11.44.30-AM.png -------------------------------------------------------------------------------- /2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-11.57.31-AM-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-11.57.31-AM-1.png -------------------------------------------------------------------------------- /2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-11.57.31-AM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-11.57.31-AM.png -------------------------------------------------------------------------------- /2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-11.57.43-AM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-11.57.43-AM.png -------------------------------------------------------------------------------- /2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-12.42.42-PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-12.42.42-PM.png -------------------------------------------------------------------------------- /2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-12.44.50-PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-12.44.50-PM.png -------------------------------------------------------------------------------- /2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-12.55.30-PM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/Screen-Shot-2021-08-13-at-12.55.30-PM.png -------------------------------------------------------------------------------- /2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/eBPF-monitoring-1536x932.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/eBPF-monitoring-1536x932.png -------------------------------------------------------------------------------- /2021/09/how-to-monitor-your-disks-and-filesystems-now-also-with-eBPF/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | original: https://www.netdata.cloud/blog/how-to-monitor-your-disks-and-filesystems-now-also-with-ebpf/ 3 | author: Manos Saratsis 4 | translator: https://github.com/lxy96Scorpion 5 | reviewer: 6 | title: 现在使用 eBPF 如何监视您的磁盘和文件系统? 7 | summary: 如何通过 eBPF 监视磁盘和文件系统 8 | categories: 译文 9 | tags: ["eBPF", "monitor", "disk", "filesystem"] 10 | originalPublishDate: 2021-08-16 11 | publishDate: 2021-09-08 12 | --- 13 | 14 | # 现在使用 eBPF 如何监视您的磁盘和文件系统? 15 |

2021 年 8 月 16 日 马诺斯 · 萨拉蒂斯

16 | 17 | ![](eBPF-monitoring-1536x932.png) 18 | 19 | ## eBPF 简介 20 | 当前的 IT 监控软件缺乏系统和应用程序最小停机时间的必要指标度量。大多数 IT 监控软件提供系统和应用程序指标,但要正确监控您的基础架构所需的远不止这些。随着[ eBPF ](https://ebpf.io/)的产生和不断进步,eBPF 技术允许监视软件从 Linux 内核提供了更丰富的监控信息并将其呈现出来。具体而言,eBPF 监控可以更好地了解内部系统究竟发生了什么,这有助于确定基础架构可以改进性能的地方。 21 | 22 | 在 Netdata,我们能够充分利用 eBPF 的能力使您的团队能够: 23 | 24 | + 充分利用 Linux 内核,提供基于从 Linux 内核直接采集出的监控数据,这些数据是前所未有的。 25 | + 通过新的、附加的指标和图表改进了监控范围,这些指标和图表具有关于硬盘、文件系统等方面更完整的信息。 26 | + 基于新的 eBPF 图表,使用一组新的用例补充警报, 27 | + 了解有关 Linux 内核及其隐藏但有实际用途的更多秘密信息。 28 | 29 | 作为基础,我们使用的代码最初是由[ IOvisor ](https://www.iovisor.org/)中的[ BCC ](https://github.com/iovisor/bcc)工具,[ Cloudflare ](https://www.cloudflare.com/)中的[ eBPF exporter ](https://github.com/cloudflare/ebpf_exporter)开发的;我们非常感谢他们的贡献,并打算扩大他们的工作范围,将这些工具的价值分享给你们所有人。 30 | 31 | Netdata 是所有 eBPF 工具的超集。此外,通过消除配置上的开销,我们简化了可视化信息之前的整个过程。Netdata,你可以使用一个命令安装我们的监控代理,把它连接到一个在[ Cloud ](https://app.netdata.cloud/?utm_source=website&utm_content=get_netdata_button1)上的[ Space ](https://learn.netdata.cloud/docs/cloud/spaces),开箱即用的 eBPF 监控,避免了寻找 eBPF工具,需要编译,运行,收集的复杂性和存储输出以及开发用于故障排除的可视化所需的配置。我们为您处理这些所有的复杂性。只需[在您的代理上启用 eBPF ](https://learn.netdata.cloud/docs/agent/collectors/ebpf.plugin),您就可以开始使用了! 32 | 33 | ## 磁盘监控和预警 34 | 35 | ![](Screen-Shot-2021-08-13-at-11.44.30-AM.png) 36 | 37 | Netdata 通过在现有的仪表盘上提供的许多开箱即用的图表来监控您的所有磁盘。这些数据包括但不限于: 38 | + 磁盘 I/O 带宽:磁盘的数据流的输入和输出 39 | + 丢弃数据量:从块设备读取、写入或[丢弃](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/managing_storage_devices/discarding-unused-blocks_managing-storage-devices)的扇区数。要配置丢弃,请访问此[文档](https://wiki.gentoo.org/wiki/SSD),获取有关使用丢弃和 Netdata 仪表板的更多信息,您也可以参阅这篇[文章](https://lwn.net/Articles/347511/)以了解更多信息。 40 | + 磁盘完成的 I/O 操作:读取或写入成功完成的总数据量 41 | + 磁盘已完成扩展 I/O 操作: I/O 操作完成后被丢弃的 blocks 数和刷新数 42 | + 磁盘当前 I/O 操作与 I/O:当前正在进行的操作。 43 | + 磁盘积压: 待处理的磁盘操作的等待时间 44 | + 磁盘拥塞时间测量磁盘拥塞的时间 45 | + 磁盘使用时间 46 | + 平均完成 I/O 操作时间成功完成的读取和写入的操作 47 | + 平均完成的扩展 I/O 操作时间与丢弃和刷新时间 48 | + 平均完成的 I/O 操作带宽 49 | + 平均丢弃数据量 50 | + 已完成 I/O 操作的平均服务时间 51 | + 磁盘合并操作作为彼此相邻的读取和写入可以合并以提高效率 52 | + 磁盘合并丢弃操作所有已完成 I/O 的总计的 I/O 时间 53 | + 磁盘总计 I/O 时间每次刷新、丢弃的扩展操作 54 | 55 | 现在,使用 eBPF,您还可以获得以下额外指标: 56 | + 磁盘延迟 ( eBPF ):延迟图表显示直方图以及读取和写入数据所需的时间。 57 | + 同步 ( eBPF ):当数据从内存页面缓存移动到硬盘时。 58 | + 页面缓存 ( eBPF ):数据如何在主机上实时更改。 59 | + 挂载点监控 ( eBPF ):当分区/磁盘被移除并插入到您的主机上时。 60 | 61 | 我们为以下项目提供预建预警: 62 | + 磁盘利用率 63 | + Inode 利用率,您可以在[此处](https://en.wikipedia.org/wiki/Inode)找到有关磁盘 Inode 的更多信息。 64 | + 磁盘积压 65 | + 延迟 66 | 67 | 对于每个挂载点 Netdata 提供: 68 | 69 | 磁盘空间使用 70 | 71 | 磁盘空间利用率。reserved for root 是系统自动预留的,防止 root 用户空间不足。 72 | 73 | 磁盘文件( inode )使用——耗尽可用的 inode(或索引节点)会导致无法创建新文件的问题。 74 | inodes(或索引节点)是文件系统对象(例如文件和目录)。在许多类型的文件系统实现中,inode 的最大数量在文件系统创建时是固定的,限制了文件系统可以容纳的最大文件数量。设备可能会耗尽 inode。发生这种情况时,即使可能有可用空间,也无法在设备上创建新文件。 75 | 76 | 挂载系统调用 ( eBPF ) 77 | + 当系统调用挂载和卸载时,调用 Monitor 。 78 | + 当设备从计算机中插入或移除时,这可能是硬件问题、安全问题。 79 | 80 | 还有一个[ SMART 插件](https://learn.netdata.cloud/docs/agent/collectors/python.d.plugin/smartd_log),但我们不建议使用它,因为您的数据可能会受到损害。默认情况下不启用。 81 | 82 | ## 文件系统监控 83 | Netdata 监控不同的文件系统,例如: 84 | + 虚拟文件系统( eBPF ) 85 | + 删除文件 – 监控 Linux 上删除文件的频率; 86 | + 对 IO 的调用 - 计算从硬盘写入(输入)和读取(输出)事件的数量 87 | + 写入和读取的字节数 - 对上一个图表的补充,但为了测量(字节数)调用。 88 | + 调用 vfs_fsync – 监视操作系统何时调用文件同步以将内存中更改的页面存储到磁盘。 89 | + 调用 vfs_open – 调用打开文件 90 | + 调用 vfs_create – 调用创建新文件的函数 91 | + EXT4、XFS、BTRFS、ZFS 延迟( eBPF ) 92 | + 读取请求的延迟以及从 ext4 文件系统读取数据所需的时间。 93 | + 写入请求的延迟以及执行写入事件所需的时间。 94 | + 打开请求的延迟以及执行打开事件所需的时间。 95 | + 同步延迟请求执行同步所需的时间。 96 | + NFS ( eBPF ) 97 | + 读取请求的 NFS 延迟以及执行读取事件所需的时间。 98 | + 写入请求的 NFS 延迟以及执行写入事件所需的时间。 99 | + 打开请求的 NFS 延迟以及执行打开事件所需的时间。 100 | + getattr 请求的 NFS 延迟以及执行 getattr 请求所需的时间。 101 | + 挂载文件系统( eBPF ) 102 | + Netdata 正在监视文件系统何时在操作系统上安装和卸载。 103 | 104 | ## 同步 105 | 106 | + 同步 ( eBPF ):Netdata 监控六个不同的系统调用(fsync 、fdatasync 、msync 、sync 、syncfs 和 syncfilerange ),负责将数据从页面缓存同步到磁盘。 107 | + 页面缓存( eBPF ):Netdata 监控同步到磁盘的页面缓存的访问和更改。 108 | 109 | ![](Screen-Shot-2021-08-13-at-11.57.31-AM.png) 110 | 111 | ![](Screen-Shot-2021-08-13-at-11.57.43-AM.png) 112 | 我们现在提供更多增强指标用于监控虚拟文件系统 ( [VFS](https://en.wikipedia.org/wiki/Virtual_file_system) ) ,还提供对某些文件系统执行打开文件、在磁盘上写入数据、从磁盘读取数据、删除文件和同步等操作所需的延迟的监控. 113 | 114 | ## eBPF 与 apps.plugin 的集成 115 | 116 | 一些图表显示文件系统和内存部分,并且在启用与 apps.plugin 的集成时也会按应用程序显示。由于这种集成,您可以看到特定应用程序如何使用硬盘。 117 | 118 | ![](Screen-Shot-2021-08-13-at-12.42.42-PM.png) 119 | 120 | 应用程序集成 121 | 122 | ![](Screen-Shot-2021-08-13-at-12.44.50-PM.png) 123 | 124 | 让我们看一个例子: 125 | 126 | 延迟是完成事件所需的时间。Netdata 计算函数调用与其返回之间的差异。最终结果按时间间隔存储。 127 | 128 | 每个硬盘都有自己的执行读写操作的延迟,要正确设置预警,我们建议您查看硬盘手册。 129 | 130 | ![](Screen-Shot-2021-08-13-at-11.57.31-AM-1.png) 131 | 132 | 为了补充为硬盘提供的信息,Netdata 还监视文件系统上特定操作的延迟。 133 | 134 | ![](Screen-Shot-2021-08-13-at-12.55.30-PM.png) 135 | 136 | ## 概括 137 | 感谢您光临并学习如何使用 Netdata 监控您的磁盘和文件系统!要获得丰富的体验,请免费注册[ Netdata Cloud ](https://app.netdata.cloud/sign-in?cloudRoute=spaces?utm_source=email&utm_content=ebpf_blog)(如果您还没有注册)并访问: 138 | + 来自多个节点的指标聚合到一个图表中。为此,只需创建一个帐户,将您的节点连接到一个 Space ,然后转到概览探索您的指标 139 | + 运行[指标关联](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations)等智能功能,将特定指标的使用模式[关联](https://learn.netdata.cloud/docs/cloud/insights/metric-correlations)起来,这些指标在 Netdata 为您的系统收集的所有指标中具有相同的模式 140 | 141 | 最后,请记住[在您的代理上启用 eBPF ](https://learn.netdata.cloud/docs/agent/collectors/ebpf.plugin)!我们为磁盘和文件系统监控提供最全面的实时监控体验。只需轻松的加入监控! 142 | 143 | 如果您是 Netdata 的新手,请务必下载 Agent!如果您已经拥有代理,请注册 Netdata Cloud。 -------------------------------------------------------------------------------- /2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/img/magic-firewall-00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/img/magic-firewall-00.png -------------------------------------------------------------------------------- /2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/img/magic-firewall-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/img/magic-firewall-01.png -------------------------------------------------------------------------------- /2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/img/magic-firewall-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/img/magic-firewall-02.png -------------------------------------------------------------------------------- /2021/12/how-we-used-ebpf-to-build-programmable-packet-filtering-in-magic-firewall/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | original: https://blog.cloudflare.com/programmable-packet-filtering-with-magic-firewall/ 3 | author: Chris J Arges 4 | translator: https://github.com/nevermosby 5 | reviewer: 6 | title: How We Used eBPF to Build Programmable Packet Filtering in Magic Firewall 7 | summary: 我们如何在Magic Firewall中使用eBPF打造可编程的网络包过滤能力 8 | categories: 译文 9 | tags: ["eBPF", "firewall", "packet", "filtering"] 10 | originalPublishDate: 2021-12-06 11 | publishDate: 2021-12-13 12 | --- 13 | 14 | # 我们如何在Magic Firewall中使用eBPF打造可编程的网络包过滤能力 15 |

2021 年 12 月 6 日 克里斯 · J · 阿尔杰斯

16 | 17 | ![](./img/magic-firewall-00.png) 18 | 19 | Cloudflare 作为全球最强大的网络服务提供商之一,我们日复一日地积极保护用户服务免受各种各样的网络攻击。对于使用 Magic Transit 服务的用户来说,Magic Transit 提供的 DDoS 保护功能可以检测和阻止攻击行为,从而保护他们的服务。而我们的 [Magic Firewall](https://www.cloudflare.com/magic-firewall/) 服务则允许数据包级别的自定义规则,使用户能够淘汰防火墙硬件设备,直接在 Cloudflare 网络服务上就可以屏蔽恶意流量。 20 | 21 | 当前网络攻击类型和复杂性不断发展,如最近[针对 VoIP 服务](http://blog.cloudflare.com/update-on-voip-attacks/)的 DDoS 和反射[攻击](http://blog.cloudflare.com/attacks-on-voip-providers/)就表明了这一点,这些攻击针对的是会话初始协议(SIP)协议。对抗这些攻击需要将数据包过滤的极限,已经提升到传统防火墙所能做到的范围之外了。我们通过采用先进一流的技术,并结合新的方式将它们整合起来,使 Magic Firewall 服务成为一种速度极快、完全可编程的防火墙,甚至可以抵御目前最复杂的网络攻击。 22 | 23 | ## 魔力防火墙 24 | [Magic Firewall](http://blog.cloudflare.com/introducing-magic-firewall/) 服务是一个基于 Linux nftables 技术的分布式无状态网络数据包防火墙。它运行在 Cloudflare 全球每个数据中心的每台服务器上。为了提供更好的隔离性和灵活性,每个客户的 nftables 规则都配置在他们自己的 Linux 网络命名空间内。 25 | 26 | ![](./img/magic-firewall-01.png) 27 | 28 | 这张图展示了使用 Magic Transit 服务时网络数据包的生命周期,Magic Transit 内置了 Magic Firewall 服务。首先,数据包刚进入服务器时,DDoS 保护开始生效,它会尽可能早地阻止攻击行为。接下来,数据包被路由到客户特定的网络命名空间,此时 nftables 规则被作用到数据包上。然后,数据包通过GRE隧道被路由回原始路径。Magic Firewall 用户可以使用灵活的 [Wirefilter 语法](https://github.com/cloudflare/wirefilter),只要通过[单一 API 接口](https://developers.cloudflare.com/magic-firewall)就能构建防火墙声明策略。同时,也可以通过 Cloudflare 仪表板,使用友好的 UI 拖拽元素来配置规则。 29 | 30 | Magic Firewall 服务为匹配各种网络数据包参数,提供了一种非常强大的语法,但它也仅限于 nftables 提供的匹配能力。虽然这对大多数的使用场景来说已是绰绰有余,但它没有提供足够的灵活性,来实现我们想要的高级数据包解析和内容匹配能力。因此,我们需要更多的力量。 31 | 32 | ## 你好 eBPF,Nftables 这厢有礼了 33 | 34 | 为了满足 Linux 网络需求,想要增加更多的功能时,扩展型伯克利包过滤技术([eBPF](https://ebpf.io/))是你的不二选择。有了 eBPF,你可以插入数据包处理程序并 *在内核空间* 执行,这样能让你拥有熟悉的编程范式带来的灵活性,同时兼具内核执行的高效性。Cloudflare [热爱 eBPF ](http://blog.cloudflare.com/tag/ebpf/),这项技术在打造我们自身产品期间,起到了变革作用。因而我们希望找到一种方法,通过 eBPF 来扩展我们在 Magic Firewall 中对nftables的使用场景。这意味着能够在 nftables 表内和链内,使用 eBPF 程序来实现规则匹配。这样做能保留我们现有的基础设施和代码,并进一步扩展它,从而就能实现“鱼和熊掌兼得”。 35 | 36 | 如果 nftables 能够原生利用eBPF的话,这就会变得简单多了。。。唉,我们不得不继续我们的探索,目前已经知道 iptables 可以跟 eBPF 集成,让我们从这里开始入手。例如,可以使用 iptables 和一个持久化eBPF程序来丢弃数据包,命令如下: 37 | 38 | ```bash 39 | iptables -A INPUT -m bpf --object-pinned /sys/fs/bpf/match -j DROP 40 | ``` 41 | 42 | 这条线索帮助我们走上了正确的道路。Iptables 使用 [xt_bpf](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/xt_bpf.c#n60) 扩展模块来对接一个eBPF程序。这个扩展模块使用 **BPF_PROG_TYPE_SOCKET_FILTER** eBPF程序类型,它允许我们在套接字缓冲区(socket buffer)加载数据包信息并根据我们的代码返回值。 43 | 44 | 既然我们知道 iptables 可以使用 eBPF,为什么不直接利用它呢?Magic Firewall 目前使用的还是 nftables,它灵活的语法和可编程的接口,对于我们的用户场景是一个不错的选择。因此,我们需要找到一种方法,实现基于 nftables 使用 **xt_bpf** 扩展模块。 45 | 46 | ![](./img/magic-firewall-02.png) 47 | 48 | 这张[示意图](https://developers.redhat.com/blog/2020/08/18/iptables-the-two-variants-and-their-relationship-with-nftables#using_iptables_nft) 有助于解释 iptables、nftables 和内核之间的关系。nftables API 可以被iptables 和nft 用户空间程序使用,也可以配置使用xtables 模块(包含 xt_bpf)和一般nftables 模块进行匹配。 49 | 50 | 这意味着,只要使用正确的 API 调用(发送 netlink/netfilter 消息),我们就可以将 xt_bpf 匹配嵌入到 nftables 规则中。为了做到这一点,我们需要了解应该发送哪些 netfilter 信息。通过使用 strace、Wireshark 等工具,尤其当我们使用[源码](https://github.com/torvalds/linux/blob/master/net/netfilter/xt_bpf.c),就能够构建一个消息体,可以针对特定的 nftables 表和链,附加一个 eBPF 规则。 51 | 52 | ```bash 53 | NFTA_RULE_TABLE table 54 | NFTA_RULE_CHAIN chain 55 | NFTA_RULE_EXPRESSIONS | NFTA_MATCH_NAME 56 | NFTA_LIST_ELEM | NLA_F_NESTED 57 | NFTA_EXPR_NAME "match" 58 | NLA_F_NESTED | NFTA_EXPR_DATA 59 | NFTA_MATCH_NAME "bpf" 60 | NFTA_MATCH_REV 1 61 | NFTA_MATCH_INFO ebpf_bytes 62 | ``` 63 | 64 | 为 netlink/netfilter 消息结构体添加eBPF匹配后,应该看起来像上面的例子。当然,这个消息需要正确嵌入,同时当有一个匹配需求时,需要包含一个条件型步骤,就像一次判决。下一步是对 `ebpf_bytes` 格式进行解码,如下例所示: 65 | 66 | ```C 67 | struct xt_bpf_info_v1 { 68 | __u16 mode; 69 | __u16 bpf_program_num_elem; 70 | __s32 fd; 71 | union { 72 | struct sock_filter bpf_program[XT_BPF_MAX_NUM_INSTR]; 73 | char path[XT_BPF_PATH_MAX]; 74 | }; 75 | }; 76 | ``` 77 | 78 | `ebpf_bytes` 字节格式的定义,可以在内核头文件中定义的结构体 [xt_bpf_info_v1](https://git.netfilter.org/iptables/tree/include/linux/netfilter/xt_bpf.h#n27) 中找到。上面的代码例子显示了该结构体的相关部分。 79 | 80 | xt_bpf 模块既支持原始字节码,也支持持久化到某个文件路径的 ebpf 程序。接下来的内容就是我们整合 ebpf 程序和 nftables 的技术详情。 81 | 82 | 有了这些信息,我们就可以通过编写代码创建 netlink 信息,并正确地序列化任何相关的数据字段。这只是第一步,我们还在研究如何将其整合到合适的工具中去,取代发送自定义 netfilter 消息的方式。 83 | 84 | ## 整合 eBPF 85 | 86 | 现在我们需要构建一个 eBPF 程序,并将其加载到一个现有的 nftables 表和链中。刚开始使用 eBPF 可能会有点难度,比如应该使用哪种程序类型、如何编译和加载我们的 eBPF 程序。我们通过做了一些探索和研究,开启了我们的旅程。 87 | 88 | 首先,我们构建了一个示例程序来进行尝试。 89 | 90 | ```C 91 | SEC("socket") 92 | int filter(struct __sk_buff *skb) { 93 | /* get header */ 94 | struct iphdr iph; 95 | if (bpf_skb_load_bytes(skb, 0, &iph, sizeof(iph))) { 96 | return BPF_DROP; 97 | } 98 | 99 | /* read last 5 bytes in payload of udp */ 100 | __u16 pkt_len = bswap_16(iph.tot_len); 101 | char data[5]; 102 | if (bpf_skb_load_bytes(skb, pkt_len - sizeof(data), &data, sizeof(data))) { 103 | return BPF_DROP; 104 | } 105 | 106 | /* only packets with the magic word at the end of the payload are allowed */ 107 | const char SECRET_TOKEN[5] = "xyzzy"; 108 | for (int i = 0; i < sizeof(SECRET_TOKEN); i++) { 109 | if (SECRET_TOKEN[i] != data[i]) { 110 | return BPF_DROP; 111 | } 112 | } 113 | 114 | return BPF_OK; 115 | } 116 | ``` 117 | 118 | 上面摘录的是是一个 eBPF 程序例子,它只接受在网络请求末端有一串“魔法”字符的数据包。我们需要检查数据包的总长度,来找到开始搜索的位置。为了表述更清楚,这个例子省略了错误检查和头文件引用的相关代码。 119 | 120 | 一旦我们编写好了程序,下一步就是把它整合到我们的工具中。我们尝试了一些技术来加载这个程序,比如 BCC、libbpf,甚至我们还自研了一个自定义加载器。最后,我们最终使用了 [cilium的 ebpf 库](https://github.com/cilium/ebpf/),因为我们的控制面程序使用的也是Golang 语言,而 cilium 提供的 ebpf 库使得生成、嵌入和加载 eBPF 程序变得容易很多。 121 | 122 | ```bash 123 | # nft list ruleset 124 | table ip mfw { 125 | chain input { 126 | #match bpf pinned /sys/fs/bpf/mfw/match drop 127 | } 128 | } 129 | ``` 130 | 131 | 一旦程序被编译和持久化,我们就可以使用 `netlink` 命令将匹配的内容添加到 nftables 中。通过列出规则集,就能显示刚刚添加的匹配内容。这真是不可思议,现在我们能在 Magic Firewall 规则集内部署定制的 C 程序,实现了先进的匹配能力了! 132 | 133 | ## 更多魔力 134 | 135 | 随着 eBPF 整合到我们的工具包里,Magic Firewall 服务变成了一种能保护你的网络免受坏人侵害的,更灵活和强大的方式。我们现在能够更深入地研究网络数据包,实现比 nftables 自身提供的更复杂的匹配逻辑。由于我们的防火墙服务,是作为软件运行在所有 Cloudflare 服务器上的,因此可以实现快速迭代和功能更新。 136 | 137 | 这个项目的成果之一是实现 SIP 保护能力,当然,它目前还处于 beta 测试阶段。这仅仅是个开始,目前我们正在探索使用 eBPF 进行协议验证、高级字段匹配、网络请求分析、支持更大的 IP 列表集。 138 | 139 | 欢迎任何人给我们提供帮助! 如果你有其他使用场景和想法,请与我们的客户团队沟通讨论。如果你觉得这项技术很有趣,就来[加入我们的团队吧](https://www.cloudflare.com/careers/)! -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 请审校者在合并该 PR 前需要检查以下内容: 2 | 3 | - [ ] Header 设置正确,包括文章的类型、tag、分类、作者、译者、摘要、reviewer 发布时间等信息 4 | - [ ] 关联 Issue 编号 https://github.com/kubesphere-sigs/awesome-cloud-native-blogs/issues/${ISSUE_ID} 5 | - [ ] 该 PR Assigned 给了提交者 6 | - [ ] 指定了 Reviewer 7 | - [ ] 为 PR 明确了 label 信息,如 categories、kind、size、tag、status 8 | - [ ] 所有代码片段都指定了代码语言 9 | - [ ] 文章遵守了路径规范 `YEAR/MONTH/TITLE/index.md`,例如 `2021/08/new-blog/index.md` 10 | - [ ] 图片保存了在 GitHub上,跟文章在同级目录 11 | - [ ] 文章标题简短且语义明确 12 | - [ ] 文章语句通顺,没有语病和格式问题 13 | - [ ] 文中没有不可达的链接 14 | - [ ] 提交者同意授权「KubeSphere 云原生」公众号和 http://kubesphere.com.cn 发布原创或翻译的文章 15 | 16 | 当所有以上问题都确定之后,可以由管理者合并 PR 并关闭关联 issue。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Awesome Cloud Native Blogs 2 | 3 | ## 关于本仓库 4 | 5 | 在本仓库中,您可以: 6 | 7 | - 提交英文文章线索 8 | - 参与翻译 9 | - 提交个人原创文章 10 | - 提交个人已发布文章的转载要求 11 | 12 | 文章题材包括但不限于: 13 | 14 | - Kubernetes 15 | - Service Mesh 16 | - Cloud Native 17 | - Serverless 18 | - DevOps 19 | - Container 20 | 21 | 您提交的文章将首发于: 22 | 23 | - KubeSphere 社区官网:https://kubesphere.com.cn 24 | - 「KubeSphere 云原生」微信公众号 25 | - 其他渠道(保留作者署名或 URL 共享) 26 | 27 | ## 奖励机制 28 | 29 | 为了鼓励和奖励大家对社区的贡献,社区特别设置了奖励机制。凡是通过以上任意方式参与贡献,都可以获得相对应的奖励,包括但不限于:社区贡献者证书(统一发放)、KubeSphere 周边纪念礼品(免费邮寄)。 30 | 如果您还想获取其他类型的奖励,可以提交 Issue,我们将视具体情况判断,要求合理即可满足。 31 | 32 | ![](https://cdn.jsdelivr.net/gh/kubesphere-sigs/awesome-cloud-native-blogs@master/img/kubesphere-swag.png) 33 | 34 | ## 如何参与 35 | 36 | 参与该项目包括两种方式: 37 | 38 | 1. 通过[提交 Issue](https://github.com/kubesphere-sigs/awesome-cloud-native-blogs/issues/new?assignees=&labels=kind%2Ftranslation&template=docs.yaml)的方式提交文章线索 39 | 2. 通过 PR 参与文章翻译或提交原创文章 40 | 3. 原则上所有认领的文章要在 5 个工作日内完成翻译或创作,**可以多人认领同一篇文章协作翻译** 41 | 42 | ### 提交 Issue 43 | 44 | [提交Issue](https://github.com/kubesphere-sigs/awesome-cloud-native-blogs/issues/new?assignees=&labels=kind%2Ftranslation&template=docs.yaml),其中的选项只要打钩即可。 45 | 46 | **注意**:请在 Issue 标题中增加类型前缀,如`Translation:`、`Original:`、`Reprint:`。 47 | 48 | ### 提交 PR 49 | 50 | [提交PR](https://github.com/kubesphere-sigs/awesome-cloud-native-blogs/pulls),可以为译文、原创或个人文章转载,请在文章头部添加元信息,格式如下: 51 | 52 | ```yaml 53 | --- 54 | original: "原文链接或者原创作者的 GitHub 账号" 55 | author: "作者姓名" 56 | translator: "译者的 GitHub 账号" 57 | original: "原文地址" 58 | reviewer: ["审阅者 A 的 GitHub 账号","审阅者 B 的 GitHub 账号"] 59 | title: "标题" 60 | summary: "这里是文章摘要。" 61 | categories: "译文、原创或转载" 62 | tags: ["taga","tagb","tagc"] 63 | originalPublishDate: 2021-08-18 64 | publishDate: 2019-08-18 65 | --- 66 | ``` 67 | 68 | 注:其中 `originalPublishDate` 为所翻译的文章原文的发布日期,`publishDate` 为原创文章或译文的 PR 合并日期。 69 | 70 | ## 关于 PR 的注意事项 71 | 72 | 提交 PR 请注意以下事项: 73 | 74 | - 请在 PR 中标题中增加类型前缀,如`Translation:`、`Original:`、`Reprint:` 75 | - 提交的文件名必须全英文、小写、单词间使用连字符 76 | 77 | 管理员在合并 PR 前请先确认 [PR 模板]()中的内容。 78 | 79 | 注:提交 PR 表明您授权您的创作被发布到 KubeSphere 社区或以保留您署名的方式转载。 80 | 81 | ## 文档要求 82 | 83 | 文档格式为 Markdown,尽量保留原文格式,可以根据中文阅读习惯适度调整,**中英文之间、中文和数字之间要加空格**。 84 | 85 | **命名规则** 86 | 87 | 文件使用英文命名,单词之间使用 `-` 连接,所有字母均为小写,例如 `x509-certificate-exporter.md `。 88 | 89 | **目录结构** 90 | 91 | 新的文档根据提交 PR 的时间在对应的目录中创建。如2021年8月18日提交的PR需要在 `2021/08/` 目录中创建新的文档,即所有文档是按照月来归档的。加入文章标题为 `new-post`,则创建的新文件为 `2021/08/new-post/index.md`。 92 | 93 | **关于图片** 94 | 95 | 图片请随文章一同上传到 Github 中,与 `index.md` 文件位于同一目录。 96 | -------------------------------------------------------------------------------- /img/kubesphere-swag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubesphere-sigs/awesome-cloud-native-blogs/2566c8079884302dccf19bf3bb26afa977dd4aeb/img/kubesphere-swag.png --------------------------------------------------------------------------------