Wasm Internals: Stack Machine

Wasm 中的“栈机”(Stack Machine),这正是其核心执行模型之一。Wasm 是一种基于栈的虚拟机,这意味着它的所有操作都通过从一个操作数栈中弹出值、执行操作并将结果压回栈中来完成。它没有传统的“寄存器”概念。

什么是栈机?

在计算机科学中,栈机是一种计算模型,其中指令操作数被隐含地从一个被称为“操作数栈”的内存区域中获取,并且结果被隐含地压回这个栈。这种模型与基于寄存器或基于累加器的模型形成对比。

Wasm 栈机的工作原理

Wasm 模块中的函数是由一系列指令组成的。这些指令会操作一个中央的操作数栈。

  1. 操作数栈(Operand Stack)

    • 这是 Wasm 执行函数时最核心的数据结构。
    • 所有的操作数(如整数、浮点数)和操作结果都临时存储在这个栈上。
    • 指令不会像在注册机中那样直接指定操作数的位置(如“将 R1 的值加到 R2”)。相反,它们会假定操作数已经在栈的顶部。
  2. 局部变量(Local Variables)

    • 除了操作数栈,每个函数调用还有一个独立的“局部变量”区域。
    • 局部变量是命名的存储位置,可以在函数的整个执行过程中被访问和修改。
    • 虽然局部变量不是栈的一部分,但有很多指令允许你将局部变量的值压入栈中,或者将栈顶部的值存储到局部变量中。
  3. 参数(Parameters)

    • 函数的参数在函数被调用时,会被初始化为局部变量的一部分(通常是前几个局部变量)。
    • 它们也可以被认为是函数执行上下文的一部分。
  4. 指令的操作

    • 压栈(Push):很多指令会将值压入栈中。例如:
      • i32.const 42:将整数常量 42 压入栈。
      • local.get <idx>:获取索引为 <idx> 的局部变量的值并压入栈。
    • 弹栈(Pop):大多数操作指令会从栈顶弹出所需数量的操作数。例如:
      • i32.add:弹出栈顶的两个 i32 整数,将它们相加,然后将结果压回栈。
      • if/else/loop 等控制流指令的条件值也会从栈中弹出。
    • 复合操作:一些指令可能弹出一个值,执行一些副作用(如内存写入),而不压入任何新值。例如:
      • i32.store:弹出内存地址和要存储的值,将值写入内存。

栈机模型的优势与特点

  1. 紧凑性(Compactness)

    • 指令更短,因为它们不需要编码操作数的位置。例如,一个加法操作,在寄存器机中可能需要指定两个源寄存器和一个目标寄存器;在栈机中,它只是一个简单的 add 指令。
    • 这有助于生成更小的二进制代码,对于 Web 环境中的快速下载和解析非常有利。
  2. 简化编译器后端(Simplified Compiler Backends)

    • IR(中间表示)到指令的映射通常更直接。许多高级语言的语义本身就可以很容易地映射到栈操作。
    • 这使得将 C/C++/Rust 等语言编译到 Wasm 变得相对容易。
  3. 易于验证(Easy to Validate)

    • Wasm 在加载时会进行严格的类型检查和结构验证。栈机模型使得验证其类型安全变得相对容易。例如,当检查 i32.add 指令时,验证器只需确保栈顶有两个 i32 类型的值。
    • 这对于沙盒环境中的安全性至关重要。
  4. 独立于目标架构(Architecture-Independent)

Kubernetes CSI 简介

K8s CSI 是 Kubernetes 中非常重要的一个组件,它解决了存储与计算分离的复杂性,并为容器化应用提供了持久化存储的能力。

1. 基本概念

CSI (Container Storage Interface) 译为 容器存储接口。它是由 Kubernetes 社区与存储厂商共同制定的一套标准接口规范。

在 CSI 出现之前,Kubernetes 存储插件的开发和管理存在以下痛点:

  • 紧耦合问题: Kubernetes 内部集成了大量的存储驱动(In-tree 存储插件),例如 AWS EBS、GCE PD、Azure Disk、Ceph RBD 等。这意味着每当存储厂商需要支持 Kubernetes 时,他们都必须将其存储驱动代码提交到 Kubernetes 的核心代码库中。这种方式导致了:
    • Kubernetes 代码库臃肿: 集成了大量存储逻辑,增加了核心代码的复杂性和维护难度。
    • 发布周期长: 存储驱动的更新需要跟随 Kubernetes 的发布周期,新功能和 bug 修复不能及时推送到用户。
    • 存储厂商开发受限: 每次更新都需要与 Kubernetes 社区协调,开发和测试流程繁琐。
  • 兼容性问题: 不同存储厂商的存储系统差异巨大,缺乏统一的接口规范,导致存储系统与 Kubernetes 之间的集成困难。

CSI 的目标就是解决这些问题,实现存储系统与 Kubernetes 的解耦。 它定义了一套通用的接口,允许任何存储厂商开发自己的 CSI 驱动,然后通过这些驱动来与 Kubernetes 进行交互,从而为容器提供存储服务。

1.1. 资源定义

1.1.1. Volumes

Volumes 是 Kubernetes 中 Pod 通过文件系统访问和共享数据的抽象。它主要提供了如下功能:

  • 通过 ConfigMap 或者 Secret 共享配置;
  • 跨容器、跨 Pod 甚至跨 Node 共享数据;
  • 数据持久化。在 Pod 销毁之后仍能继续访问数据。 对于 Pod 来说,Volumes 通过 .spec.volumes 提供给 Pod,容器通过 .spec.containers[*].volumeMounts 来将指定 Volumes 挂载到指定目录。

1.1.2. Persistent Volumes 和 Persistent Volumes Claim

PV 和 PVC 提供了两套 API 将存储的提供和消费分离。

Spurious Retransmit 导致 NAT 设备响应 RST

在一个线上问题排查过程中,出现了一个新的问题:wget 下载文件在连接建立很短的时间内(1 - 2s)就被 RESET 了。 首先先简要说明一下链路:

客户端经过一个SNAT设备出公网,在服务器上下载一个大文件。端到端的 RTT 大约 90 ms。由于最开始并没有怀疑到 SNAT 设备,并且 server 端无法抓包,我们分别在客户端 和 SNAT 设备到 server 间一台网元设备上抓包。 中间网元设备

SNAT 设备

如上,中间网元设备显示客户端(端口 34567)先发送了 RST 报文,随后服务器(端口 443)响应 RST 报文。但是客户端抓包显示它根本没有发送过 RST 报文。

此时,我们再从 SNAT 设备上抓包,发现就是 SNAT 设备首先给服务器发送了 RST 报文。

这里的 SNAT 设备实际上是一台基于 IPtables 的 Linux 设备。

抓包可以看到,RST 是对一个 TCP Spurious Retransmission 的响应。为什么会发出这个 RST 呢?正好网上有一个相似的场景:Add workaround for spurious retransmits leading to connection resets

总的来说,就是 spurious retransmits 报文在序列号超出 TCP 窗口时,会被 conntrack 认为是 invalid 包,从而不再经过反向 SNAT 规则的处理。由于目的地址没有被转换,报文会按照原来的目的地址送往 INPUT,而本地又没有这个 socket,则响应一个 RST 报文。

VRF: An Overview

什么是 VRF(virtual routing forwarding)

VRF是一种实现三层网络隔离的关键技术。它通过创建多个路由表,为不同的网络流量提供独立的转发路径。 这意味着,任何三层网络结构,如接口的IP地址、静态路由的配置,甚至BGP(边界网关协议)会话,都可以被映射到特定的VRF中。这种映射机制就像是为每个VRF构建了一个独立的网络空间,彼此之间相互隔离,极大地增强了网络的安全性和管理的便利性。在MPLS VPN(多协议标签交换虚拟专用网络)等应用场景中,VRF为实现大规模的网络隔离和灵活的路由策略提供了基础框架。就像 VLAN 隔离了二层网络一样,VRF 隔离了三层网络。

5eb20c3e919fe3724b92c2ae7a66a7da_MD5

为什么需要 VRF

在 VRF 出现之前,Linux 用户主要采用两种方式来尝试实现类似的功能:策略路由(policy routing)和网络命名空间(net namespace)。然而,这两种方法都存在明显的局限性。

策略路由虽然能够通过多个路由表和策略规则来模拟 VRF 的部分功能(事实上,在 Linux 中,也是基于策略路由来去对 VRF 做的实现),但它的缺点十分突出。这种方式在配置和管理上非常复杂,难以确保网络隔离的有效性,在面对严格的网络审计时,往往无法通过。其复杂性不仅增加了运维的难度,还可能导致网络故障的风险上升,因此不被推荐使用。

网络命名空间在容器技术兴起后得到了广泛应用,它能够为容器提供全面的网络隔离。但在模拟 VRF 功能时,却显得有些“大材小用”。网络命名空间会对所有网络相关的资源进行完全隔离,包括设备、接口、ARP 表和路由表等。这意味着,即使是一些不需要隔离的服务,也会被隔离在不同的命名空间中。以 LLDP(链路层发现协议)为例,在使用网络命名空间的情况下,若要在不同的网络隔离环境中使用 LLDP,就需要在每个命名空间中单独运行实例,并且由于默认套接字相同,还需要为每个实例创建独特的套接字。这不仅增加了系统的开销,还使得管理变得更加复杂。相比之下,VRF 在隔离三层网络结构的同时,允许全局配置的共享和非三层感知服务的统一运行,大大提高了资源的利用效率。

Policy Routing VRF Net Namespace
隔离路由表 隔离三层网络 整个协议栈从二层到 socket 隔离

a9f7c55f8f39572d339b138fb1e12429_MD5c9b6614c864c7d35a8ef0a4f12ecdbfa_MD59edc8b051f504bf72140d1238513d687_MD5

VRF 配置

在 Linux 系统中配置 VRF,主要借助iproute2包来完成一系列操作。

  • 创建 VRF,并关联到 table 1
test1@test1:~$ ip link add vrf-1 type vrf table 1
test1@test1:~$ ip link set vrf-1 up
  • 添加接口到 VRF,可以看到 wg0 的 master 是 vrf-1,所有 wg0 的流量会使用关联的 vrf-1 路由表进行路由
test1@test1:~$ ip link set wg0 master vrf-1
test1@test1:~$ ip -d link show wg0 
9: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1400 qdisc noqueue master vrf-1 state UNKNOWN mode DEFAULT group default qlen 1000
    link/none  promiscuity 0 minmtu 0 maxmtu 2147483552 
    wireguard 
    vrf_slave table 1 addrgenmode none numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
  • 添加和查看 VRF 静态路由
root@test1:~# ip route add default via 10.1.0.10 vrf vrf-1
root@test1:~# ip route show table 1 
default via 10.1.0.10 dev wg0 
local 10.1.0.10 dev wg0 proto kernel scope host src 10.1.0.10 
root@test1:~# ip route show vrf vrf-1 
default via 10.1.0.10 dev wg0 

VRF 之间路由

有两种方法可以执行跨 VRF 路由。第一种方法涉及一个 VRF 的表中配置的路由,指向绑定到不同 VRF 的设备。

C³: Common Coding Conventions

C³: Common Coding Conventions

https://google.github.io/eng-practices/

https://github.com/tum-esi/common-coding-conventions

The goal of these conventions is to be concise, universal, and remarkable. It targets emerging code enthusiasts under time pressure and covers 7 topics:

  1. General Clarifications,
  2. Architecture,
  3. Implementation,
  4. Naming,
  5. Code Layout,
  6. Documentation, and
  7. Languages.

To follow this guide, you should already have heard about Object Oriented Programming and know basic programming rules, such as writing loops and meaningful functions instead of copy pasting instructions. In this Readme, we will shortly summarize the most important rules for every topic.