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 selectorvsl.rss3.io/active=true,这样无论哪一个 Pod 作为当前的出块节点,都能够正确、自动地把流量路由到 Pod.

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。