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。

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