Incubator4

Incubator4

github
steam
nintendo switch

RSS3 VSL 链和云原生的故事

众所周知, RSS3 自己的 L2 主网已经上线稳定运营了一段时间了,我想从 DevOps 的角度和大家分享一下如何运行自己的 L2 网络的故事,以及如何和云原生 Kubernetes 集成。

文中提到的各种 chart 已经在 rss3-network org 上开源
https://github.com/RSS3-Network/helm-charts

Optimism Node Architecture#

用一张图说明如下
image

Optimism (以下简称 OP) 的 L2 Network,主要是由 op-gethop-node 组成。和 L1 节点相同的是, geth 仍然用于存储数据和提供 RPC 响应;不同的是,由 op-node 来构建整个去中心化的网络,和决定数据的同步。

Cloudnative upgrade#

可能是我孤陋寡闻,也不清楚其他家的 L2 RPC 的云原生部署方式,并且 github 上发现其他的基于 op 的 helm chart 都不是很好用,所以就开始自己编写了一套 VSL 的 chart 图表 (理论上可以用于其他 L2 链的部署,但是未经任何测试)

Q1 security auth with JWT#

op-node 和 op-geth 必须是 1 <-> 1 对应的模式,所以采用了 Kubernetes Sidecar 模式, 它们作为一个 RPC Pod 的两个 container,共用一个 network namespace,可以通过 127.0.0.1 进行互相访问。

在 op-geth 的环境变量里,有一个 GETH_AUTHRPC_JWTSECRET ,相应的在 op-node 的环境变量里有一个 OP_NODE_L2_ENGINE_AUTH 需要配置,这两个容器通过一个相同的 jwt secret 进行通信。

上面已经讲到,使得这个 auth endpoint 永远在 127.0.0.1, 端口也只开放 localhost 的来解决这个问题。不仅如此, jwt token 的值的管理也是一个问题,因此我们采用了 initContainer 的命令 openssl rand -hex 32 > {{ .Values.jwt.path }} 来在每一次 Pod 创建时随机生成这个问题,这样不仅做到了 network namespace 上的安全,也做到了 secret 上的随机安全。

Q2 idempotent op-node peer id#

在 Q1 中讲到了,我们可以用 openssl rand 命令,在每个 pod 里都随机生成一串随机的字符串作为 jwt secret, 这使得 op-geth 和 op-node 的通信更加安全。

然而,op-node 有一个 peer id,其他的节点可以通过 固定的 OP_NODE_P2P_STATIC 来设置初始同步节点的地址。作为节点的运营方,自然是需要给每个节点一个固定的 peer id 的。

做到完全随机很容易,但是做到幂等,又不增加复杂度也是一个问题。经过研究发现,只要 op-node 的环境变量 OP_NODE_P2P_PRIV_PATH 不变,那么 peer id 就不会变。问题从固定一个 peer id ,变成固定一个文件的内容了。

自然的我们可以通过写一大串环境变量,例如 POD_PRIV_0 到 POD_PRIV_N 来解决问题。 但是这不优雅,没有办法快速的创建一个新的 pod ,每次都需要先新建一个环境变量,并且会被注入到每一个容器,因为 statefulset replicas 没有办法对单独的 pod 进行独立的设置。

幸运的是, openssl 支持传入参数作为随机数种子,那么我们只要保证随机数种子相同,生成出来的文件内容也会相同,自然就能做到幂等了。那么在一个 statefulset 里,有什么东西是每一个 pod 不同的呢, 没错就是 pod 本身的信息, pod 的 name 和 namespace 等。于是我们就可以通过 pod 的信息,配合 kubernetes downward api,生成一个幂等的 seed

部分配置如下

        - name: generate-key
          image: openquantumsafe/openssl3
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: P2P_ROOT_KEY
              valueFrom:
                secretKeyRef:
                  key: {{ .Values.node.p2p.generateSecretKey }}
                  name: {{ .Values.node.p2p.generateSecretName }}
            - name: P2P_GENERATE_KEY
              value: $(P2P_ROOT_KEY)-$(POD_NAMESPACE)-$(POD_NAME)
          command: ["/bin/sh", "-c"]
          args:
            - |
              echo ${P2P_GENERATE_KEY} | openssl sha256 -binary | xxd -p -c 32 > {{ .Values.node.p2p.privateKeyPath }};
              openssl rand -hex 32 > {{ .Values.jwt.path }};

这里引入了一个新的环境变量,P2P_ROOT_KEY,是为了保证如果有多个集群,多个相同的环境下, peer id 不会冲突

好了,经过如此操作,我们就能保证,我们每一个 L2 RPC 的 Pod, Peer id 都是唯一,并且调度 Pod 不会导致 id 变化。

Q3 HA sequencer#

第三个问题,由于 L2 网络里只能有一个活跃的 sequencer 负责出块,所以整个 sequencer 的高可用和出块设置也是一个比较头疼的问题。

这便是 VSL-Reconcile的由来了。

首先,我们会部署多个开启 sequencer 和 admin-api 的 sequencer 节点,但是默认都是处于停止出块的状态。然后将由 reconcile 决定哪一个节点作为当前的出块节点,并且把 sequencer 域名的流量切到出块节点。

这一部分,我参考了 Vault HA 的模式

image

Vault 是一个 secret manager, 高可用模式下,只有一个实例是 active 的,其他的都是 standby。因此我们的 sequencer 节点也是如此。

参考 Vault 的 Kubernetes 服务发现模式,我们也给 sequencer pod 打 label 来控制 pod 的调度和流量切换。

首先,我们会给所有的 sequencer pod 打上 vsl.rss3.io/synced=false 的标签,这意味着区块同步没有跟上进度。
然后会检查当前区块的同步进度,把标签更改成 vsl.rss3.io/synced=true
并且,service 只会选择 vsl.rss3.io/syncedtrue 的 Pod 作为负载,这样使得由于 RPC 请求流量过高,需要增加 Pod 副本数量时,新创建的 Pod 不会被放入请求中,因为它们还没有同步完毕。

然后我们会挑选所有 vsl.rss3.io/synced=true 的 sequencer Pod,打上 vsl.rss3.io/active=false 的 label,这个标记意味着,这个 Pod 可以成为出块节点。

当 reconcile 开启某一个节点的出块功能后, vsl.rss3.io/active 会变成 true, sequencer 域名的 loadbalancer 也带有 label selector vsl.rss3.io/active=true,这样无论哪一个 Pod 作为当前的出块节点,都能够正确、自动地把流量路由到 Pod.

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。