在企業環境中佈署 Ray 叢集時,安全性是首要考量。企業通常會使用安全掃描工具,例如 Grype、Anchore 和 Dagda,來檢測潛在的漏洞。Ray 的某些依賴項可能會被標記為不安全,需要移除、升級或重建 Ray。此外,整合現有工具也是關鍵步驟,Ray 的資料集通用 Arrow 介面和 Parquet 格式能有效地與其他資料工具互動。在 CI/CD 流程中,可以選擇以本地模式執行 Ray 或使用工作提交 API 進行測試,並搭配虛擬環境和 CI/CD 工具。Ray 預設不包含驗證機制,企業需要透過 TLS 或端點存取限制來強化安全性。多租戶方面,Ray 透過將工作節點繫結到作業來實作使用者隔離,但系統級函式庫的隔離仍需額外組態。監控方面,Ray 提供豐富的度量系統,可與 Prometheus 和 Grafana 整合,實作視覺化和告警功能。除了內建度量,還可以透過 ray.util.metrics 新增自定義度量,例如計數器和計量儀,來監控應用程式健康狀況。可以使用全域性單例模式或在 Actor 中建立度量物件。此外,Ray 也支援透過 subprocess 模組執行 Docker 容器化應用程式,提升佈署彈性。

企業環境中的 Ray 佈署

在企業環境中佈署軟體通常伴隨著額外的要求,特別是安全性方面。企業佈署往往涉及多個利益相關者,並需要為更大規模的科學家和工程師提供服務。儘管不是必需的,但許多企業叢集傾向於採用某種形式的多租戶結構,以實作資源(包括人力資源,如營運人員)的更有效利用。

Ray 的依賴性安全問題

不幸地,Ray 的預設需求檔案引入了一些不安全的函式庫。許多企業環境都有一些形式的容器掃描或類別似系統來檢測這類別問題。

依賴性安全掃描工具

一些常見的安全掃描工具包括 Grype、Anchore 和 Dagda。

在某些情況下,您可以簡單地移除或升級標記的依賴性問題,但當 Ray 在其輪子(wheel)中包含依賴性時(例如 Apache Log4j 問題),限制自己使用預建的輪子會有嚴重缺點。如果您發現 Java 或本機函式庫被標記,您需要從原始碼重建 Ray,並升級版本。例如,Derwen.ai 在其 ray_base 儲存函式庫中有使用 Docker 進行此操作的範例。

與現有工具的互動

企業佈署通常涉及與現有工具及其產生的資料進行互動。這裡的一些潛在整合點是使用 Ray 的資料集通用 Arrow 介面與其他工具進行互動。當資料「靜態存放」時,Parquet 是與其他工具進行互動的最佳格式。

與 CI/CD 工具一起使用 Ray

在大型團隊中工作時,持續整合和交付(CI/CD)是有效合作專案的重要組成部分。使用 Ray 與 CI/CD 的最簡單選擇是以本地模式執行 Ray,並將其視為普通 Python 專案。或者,您可以使用 Ray 的工作提交 API 提交測試工作,並驗證結果。這可以讓您在單台機器之外測試 Ray 工作。無論您是使用 Ray 的工作 API 還是 Ray 的本地模式,您都可以使用任何 CI/CD 工具和虛擬環境與 Ray 一起使用。

Ray 的驗證

Ray 的預設佈署使您可以輕鬆上手,因此它不包括客戶端和伺服器之間的任何驗證。這種驗證缺失意味著任何能夠連線到您的 Ray 伺服器的人都可能提交工作並執行任意程式碼。通常,企業環境需要比預設組態提供更高階別的存取控制。

TLS 驗證

Ray 的 gRPC 端點(而非工作伺服器)可以組態為使用傳輸層安全性(TLS),以進行客戶端和伺服器之間的雙向驗證。Ray 在客戶端和頭節點之間以及工作節點之間使用相同的 TLS 通訊機制。

Ray 的 TLS 實作要求客戶端擁有私鑰。您應該將 Ray 的 TLS 實作視為分享金鑰加密,但速度較慢。

Endpoint Access Restriction

另一個選擇是保持端點不安全,但限制誰可以與端點通訊。這可以透過入口控制器、網路規則或虛擬私人網路(VPN)的一部分(例如 Tailscale 的 Grafana RBAC 規則範例)來完成。幸運的是,Ray 的儀錶板以及 job server 端點已經繫結到 localhost/127.0.0.1 並執行在埠 8265 上。例如,如果您在 Kubernetes 上使用 Traefik 作為入口控制器來執行 Ray 頭節點,則可以如下所示對 job API 執行基本驗證:

apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
  name: basicauth
  namespace: ray-cluster
spec:
  basicAuth:
    secret: basic-auth
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: longhorn-ingress
namespace: longhorn-system
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.tls.certresolver: le
traefik.ingress.kubernetes.io/router.tls: "true"
kubernetes.io/ingress.class: traefik
traefik.ingress.kubernetes.io/router.middlewares: ba-ray-cluster@kubernetescrd
spec:
rules:
- host: "mymagicendpoints.pigscanfly.ca"
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: ray-head-svc
port:
number: 8265

依賴於限制端點存取有一個缺點:任何能夠存取該電腦的人都可以向您的叢集提交工作,因此它不適合分享計算資源。

Ray 的多租戶支援

從箱子裡取出來,Ray 叢集支援多個正在執行的工作。當所有工作來自同一個使用者且不關心隔離工作時,您不需要考慮多租戶影響。

根據我們的觀點,租戶隔離比 Ray 的其他部分開發得少。Ray 透過將不同的工作節點繫結到作業中來實作每個使用者多租戶安全性,減少了不同使用者之間意外資訊洩露的機會。與 Ray 的執行環境一樣,您的使用者可以安裝不同的 Python 函式庫,但 Ray 不隔離系統級函式庫(例如 CUDA)。

Ray 在企業中的監控與度量

Ray 是一個強大的分散式計算框架,廣泛應用於各種企業級應用中。在這篇文章中,玄貓將探討如何在 Ray 中實作監控與度量,並結合實際案例進行詳細解析。

瞭解 Ray 的度量系統

Ray 提供了豐富的度量系統,可以幫助我們監控叢集的健康狀況。這些度量資料可以透過 JSON 檔案組態,並且不建議公開暴露儀錶板。Ray 的度量資料可以匯出到 Prometheus,並且預設情況下會選擇一個隨機埠。你可以透過 ray.init 的結果檢視 metrics_export_port,或者在啟動 Ray 的頭節點時指定固定埠。

此外,Ray 與 Prometheus 的整合不僅提供了度量資料的視覺化(如 Grafana),還增加了告警功能,當某些引數超出預設範圍時會觸發告警。

Prometheus 組態與 Ray 整合

要取得匯出的度量資料,Prometheus 需要組態要抓取的主機或 Pod。對於靜態叢集,這通常只需要提供一個主機檔案即可。而對於動態叢集,Kubernetes 使用者可以使用 Pod 偵測器來組態 Prometheus 抓取 Pod。由於 Ray 叢集沒有統一標籤來標識所有節點,這裡我們使用兩個 Pod 偵測器——一個用於頭節點,一個用於工作節點。

非 Kubernetes 使用者可以使用 Prometheus 檔案基礎發現功能來使用 Ray 在頭節點自動生成的檔案 /tmp/ray/prom_metrics_service_discovery.json

內建度量與自定義度量

Ray 的內建度量資料能夠很好地報告叢集健康狀況,但我們往往更關心應用程式的健康狀況。例如,如果叢集中所有任務都卡住了,那麼即使記憶體使用率低也並不代表一切正常。因此,我們可以在 Ray 中新增自己的度量資料來監控應用程式的使用情況。

Ray 支援計數器、計量儀和直方圖等度量型別,這些都可以在 ray.util.metrics 中找到。這些度量物件不能被序列化,因為它們參照了 C 物件。你需要在使用之前明確建立這些度量物件。

示例程式碼:在 Actor 中使用 Ray 計數器

# 範例程式碼:在 Actor 中使用 Ray 計數器
@ray.remote
class MySpecialActor(object):
    def __init__(self, name):
        self.total = 0
        from ray.util.metrics import Counter, Gauge
        self.failed_withdrawls = Counter(
            "failed_withdrawls", description="Number of failed withdrawls.",
            tag_keys=("actor_name",), # 用於 Actor 分片
        )
        self.failed_withdrawls.set_default_tags({"actor_name": name})
        self.total_guage = Gauge(
            "money",
            description="How much money we have in total. Goes up and down.",
            tag_keys=("actor_name",), # 用於 Actor 分片
        )
        self.total_guage.set_default_tags({"actor_name": name})
        self.accounts = {}

    def deposit(self, account, amount):
        if account not in self.accounts:
            self.accounts[account] = 0
        self.accounts[account] += amount
        self.total += amount
        self.total_guage.set(self.total)

    def withdrawl(self, account, amount):
        if account not in self.accounts:
            self.failed_withdrawls.inc()
            raise Exception("No account")
        if self.accounts[account] < amount:
            self.failed_withdrawls.inc()
            raise Exception("Not enough money")
        self.accounts[account] -= amount
        self.total -= amount
        self.total_guage.set(self.total)

內容解密:

在這段程式碼中,我們定義了一個名為 MySpecialActor 的遠端 Actor。這個 Actor 包含了一個計數器 failed_withdrawls 和一個計量儀 total_guage。計數器用於記錄失敗的提款次數,而計量儀則用於記錄總金額。每次存款和提款操作都會更新這些度量值。

全域性單例模式

由於這些度量物件不能被序列化,你需要在 Actor 中建立和使用它們。或者你也可以使用全域性單例模式來實作這一點。

示例程式碼:全域性單例模式

# 全域性單例模式範例程式碼
class FailureCounter(object):
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            print('Creating the object')
            cls._instance = super(FailureCounter, cls).__new__(cls)
            from ray.util.metrics import Counter
            cls._instance.counter = Counter(
                "failure",
                description="Number of failures (goes up only).")
        return cls._instance

# 這個遠端函式會因為除以零而失敗
@ray.remote
def remote_fun(x):
    try:
        return 10 / x
    except:
        FailureCounter().counter.inc()
    return None

內容解密:

在這段程式碼中,我們定義了一個名為 FailureCounter 的全域性單例類別。這個類別只會建立一次例項,並且包含了一個計數器 counter。每次遠端函式 remote_fun 在執行過程中失敗時,都會增加這個計數器的值。

在 Ray 中包裝自定義程式

Python 的強大之處之一是能夠透過 subprocess 模組啟動子程式。這些子程式可以是任何 shell 命令或系統上的應用程式。這使得我們可以在 Ray 實作中執行任何自定義 Docker 構像。

示例程式碼:在遠端函式中執行 Docker 構像

# 在遠端函式中執行 Docker 構像範例程式碼
ray.init(address='ray://<your IP>:10001')
@ray.remote(num_cpus=6)
def runDocker(cmd):
    with open("result.txt", "w") as output:
        result = subprocess.run(
            cmd,
            shell=True, # 將單行字串傳遞給 shell,讓它處理。
            stdout=output,
            stderr=output
        )
    print(f"return code {result.returncode}")
    with open("result.txt", "r") as output:
        log = output.read()
    return log

cmd='docker run --rm busybox echo "Hello world"'
result=runDocker.remote(cmd)
print(f"result: {ray.get(result)}")

內容解密:

在這段程式碼中,我們定義了一個簡單的遠端函式 runDocker,它執行外部命令並傳回執行結果。主要函式向它傳遞一個簡單的 docker run 命令,然後列印呼叫結果。

跨切片處理與錯誤處理

在實際應用中,我們可能需要處理跨切片中的錯誤和邏輯完整性問題。例如:

try:
    # 處理跨切片邏輯部分一
except SomeError as e:
    # 錯誤處理邏輯部分一

try:
    # 處理跨切片邏輯部分二
except SomeOtherError as e:
    # 錯誤處理邏輯部分二

# 處理跨切片邏輯完整性部分三及之後的邏輯...

內容解密:

在此範例中展示瞭如何處理跨切片中的錯誤和邏輯完整性問題。根據不同錯誤情況採取相應的錯誤處理策略及保證跨切片後邏輯完整性。