原文作者:Elijah Zupancic & Jason Schmidt of F5
原文連結:現代應用參考架構之 OpenTelemetry 內建進展報告
轉載來源:NGINX 官方網站
去年秋天我們在 Sprint 2.0 上介紹 NGINX 現代應用參考架構 (MARA) 項目時,就曾強調過這不是一個随随便便的架構,我們的初衷是打造一款“穩定可靠、經過測試且可以部署到在 Kubernetes 環境中運作的實時生産應用”的解決方案。對于這樣一個項目來說,可觀測性工具可以說是必不可少。MARA 團隊的所有成員都曾親身體驗過,缺乏狀态和性能洞察會讓應用開發和傳遞變得多麼困難。我們很快就達成了共識,即 MARA 必須添加可以在生産環境中進行調試和跟蹤的工具。
MARA 的另一項指導原則是首選開源解決方案。本文描述了我們對多功能開源可觀測性工具的追求是如何使我們将目光轉向 OpenTelemetry 的,然後詳細介紹了其中的利弊權衡和設計決策,以及采用了哪些技術将 OpenTelemetry 與使用 Python、Java 和 NGINX 建構的微服務應用相內建。
我們希望我們的經驗可以幫助您避開可能的陷阱,并加快您對 OpenTelemetry 的采用。請注意,本文是一份具有時效性的進展報告 —— 我們讨論的技術預計将在一年内成熟。此外,盡管我們指出了某些項目目前的不足之處,但我們仍然對所有已經完成的開源工作深表感激,并期待它們未來取得更好的進展。
我們的應用
我們選擇了 Bank of Sirius 作為內建可觀測性解決方案的應用。Bank of Sirius 是我們從 Google 提供的 Bank of Anthos 示例應用引出的分支,是一個具有微服務架構的 Web 應用,可以通過基礎架構即代碼進行部署。雖然我們可以通過很多種方式來改進此應用的性能和可靠性,但它已經足夠成熟,可以被合理地認為是一種“棕地”應用。是以,我們認為這是一個展示如何将 OpenTelemetry 內建到應用中的好例子,因為理論上分布式跟蹤可以生成有關應用架構缺點的寶貴洞察。
如圖所示,支援應用的 service 相對簡單。
我們是怎麼選擇了 OpenTelemetry
我們選擇 OpenTelemetry 的道路相當曲折,經曆了幾個階段。
建立功能清單
在評估可用的開源可觀測性工具之前,我們确定了哪些方面需要關注。根據過去的經驗,我們列出了以下清單。
- 日志記錄—— 顧名思義,這意味着從應用中生成經典的、以換行符分隔的消息集;Bank of Sirius 應用會以Bunyan 格式建構日志
- 分布式跟蹤—— 整個應用中每個元件的Timing和中繼資料,例如供應商提供的應用性能管理 (APM)
- 名額—— 在一段時間内捕獲并以時間序列資料的形式繪制的測量值/li>
- 異常/錯誤聚合和通知—— 一個聚合了最常見的異常和錯誤的集合,該集合必須是可搜尋的,以便我們可以确定哪些應用錯誤是最常見的錯誤
- 健康檢查—— 發送給 service 的定期探針,用于确定它們是否在應用中正常運作
- 運作時狀态自檢—— 一組僅對管理者可見的 API,可以傳回有關應用運作時狀态的資訊
- 堆轉儲/核心轉儲—— service運作時狀态的綜合快照;考慮到我們的目的,很重要的一點就是看在需要時或在 service 崩潰時擷取這些堆轉儲的難易程度
對照功能清單比較工具功能
當然,我們并不指望一個開源工具或一種方法就能搞定所有功能,但至少它為我們提供了一個比較可用工具的理想依據。我們查閱了每個工具的文檔,确定了各個工具可支援七項功能清單中的哪些功能。下表對我們的發現進行了總結。
技術 | 日志記錄 | 分布式跟蹤 | 名額 | 錯誤聚合 | 健康檢查 | 運作時自檢 | 堆/核心轉儲 |
ELK + Elastic APM | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
Grafana | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ |
Graylog | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
Jaeger | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
OpenCensus | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
OpenTelemetry | Beta | ✅ | ✅ | ✅ | ✅ * | ❌ | ❌ |
Prometheus | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
StatsD | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
Zipkin | ❌ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
作為一項擴充功能
做完這張表格後,我們突然意識到一個問題。各個工具的功能和預期用途差别很大,我們根本不能将它們歸為一類,這樣的比較可以說是驢唇不對馬嘴!
舉例來說,ELK(Elasticsearch-Logstash-Kibana,再加上 Filebeat)和 Zipkin 的作用具有根本性的不同,兩相比較隻會讓人摸不着頭腦。遺憾的是,為了響應使用者請求,有些工具無疑還會添加一些次要的功能,進而與其他工具産生功能上的重疊,這就是臭名昭著的“任務蔓延”問題。從表面上看,ELK 主攻日志存儲和可視化,Zipkin 擅長分布式跟蹤。但如果您稍微深入研究一下 Elastic 産品組合,您很快就會發現其中的 Elastic APM 也支援分布式跟蹤,甚至相容 Jaeger。
除了任務蔓延的問題外,許多工具還可以互相內建,進而産生各種不同的收集器、聚合器、儀表闆等功能組合。有些技術互相相容,有些則不相容。
執行定性調查
綜上所述,這個比較表格無法幫助我們足夠準确地做出選擇。考慮到一個項目的價值觀與我們的價值觀越相似,我們就越有可能在一段時間内保持相容,我們決定對每個項目的目标、指導原則和可能的未來方向進行定性調查。通路 OpenCensus 頁面時,我們一眼就發現了這段話。
OpenCensus 和 OpenTracing 合并形成了 OpenTelemetry,以作為 OpenCensus 和 OpenTracing 的下一個主版本。OpenTelemetry 将向後相容現有的 OpenCensus 內建,我們将在未來兩年内繼續為現有 OpenCensus 庫打安全更新檔。
這條資訊對我們來說很關鍵。我們知道我們無法保證我們的選擇一定會經得起未來考驗,但我們至少要知道未來它是否有堅實的社群支援。有了這些資訊,我們就可以将 OpenCensus 從候選名單中剔除了。使用 Jaeger 可能也不是一個好主意,因為它是現已正式棄用的 OpenTracing 項目的參考實作 — 大部分新貢獻将落在 OpenTelemetry 上。
然後我們查閱了 OpenTelemetry Collector 的網頁。
OpenTelemetry Collector 針對如何接收、處理和導出遙測資料提供了的實作,并且不受供應商的限制。此外,它無需運作、操作和維護多個代理/收集器,即可支援将開源遙測資料格式(例如 Jaeger、Prometheus 等)發送到多個開源或商用背景系統。
OpenTelemetry Collector 充當聚合器,允許我們使用不同的後端來混搭不同的可觀測性收集和檢測方法。簡單來說,我們的應用可以使用 Zipkin 收集跟蹤資訊、使用 Prometheus 擷取名額,這些資訊都可以被發送到可配置的後端,然後我們再使用 Grafana 對其進行可視化。這種設計的其他一些排列組合也是可行的,是以我們可以多嘗試幾種方法,看看究竟哪些技術适合我們的用例。
標明 OpenTelemetry 內建作為前進方向
我們最終将目光落在了 OpenTelemetry Collector 上,因為理論上它允許我們切換不同的可觀測性技術。盡管該項目相對不那麼成熟,但我們決定大膽一試,使用僅具有開源內建的 OpenTelemetry——畢竟東西好不好,用了才知道。
但是,OpenTelemetry Collector 還是缺少一些功能,我們必須添加其他技術來填補這些缺口。以下小節總結了我們的選擇及其背後的原因。
- 實施日志記錄
- 實施分布式跟蹤
- 實施名額收集
- 實施錯誤聚合
- 實施健康檢查和運作時自檢
- 實施堆轉儲和核心轉儲
實施日志記錄
在選擇可觀測性工具時,日志記錄這一要素看似簡單,實則并不好抉擇。說它簡單,是因為您隻需從容器中擷取日志輸出即可;說它複雜,是因為您需要決定将資料存儲在哪裡、如何将其傳輸到該存儲庫、如何進行索引才能使其發揮效用,以及這些資料需要保留多長時間。隻有支援根據足夠多的不同标準(以滿足不同搜尋者的需求)輕松進行搜尋,日志檔案才能發揮效用。
我們研究了 OpenTelemetry Collector 的日志記錄支援,在撰寫本文時它還處于 Beta 測試階段。我們決定短期内使用 ELK 進行日志記錄,同時繼續調查其他選擇。
在沒有更好的選擇之前,我們預設使用 Elasticsearch 工具,以便将部署分為ingest、coordinating、master 和 data 節點。為了便于部署,我們使用了 Bitnami 圖表。為了傳輸資料,我們将 Filebeat 部署到 Kubernetes DaemonSet 中。通過部署 Kibana 并利用預加載的索引、搜尋、可視化和儀表盤,我們輕松添加了搜尋功能。
很快我們便發現,盡管這個解決方案可行,但其預設配置非常耗費資源,是以很難在 K3S 或 Microk8s 等資源占用較小的環境中運作。雖然我們通過調整每個元件的副本數量解決了上述問題,但卻出現了一些故障,這些故障可能是由于資源耗盡或資料量過多所緻。
我們并未是以氣餒,而是将借機對采用不同配置的日志記錄系統進行基準測試,并研究其他選項,如 Grafana Loki 和 Graylog。我們很可能會發現輕量級的日志記錄解決方案無法提供某些使用者需要的全套功能,而這些功能可能為資源密集型工具所提供。鑒于 MARA 的子產品化特性,我們可能會為這些選項建構額外的子產品,進而為使用者提供更多的選擇。
實施分布式跟蹤
除了需要确定哪種工具可提供我們所需的跟蹤功能之外,我們還需考慮解決方案的實施方式以及需要與之內建的技術。
首先,我們希望確定任何工具都不會影響應用本身的服務品質。我們都有過這樣的經曆:在導出日志時,系統性能每小時就會下降一次,是以我們不想再重蹈覆轍。在這方面,OpenTelemetry Collector 的架構引人注目,因為它支援您在每個主機上運作一個執行個體。每個收集器從主機上運作的所有不同應用(容器化或其他類型)中的用戶端和代理中接收資料。收集器會整合,并(可能)會壓縮這些資料,然後将其發送到存儲後端。這聽起來棒極了。
接下來,我們評估了應用中所用的不同程式設計語言和架構對 OpenTelemetry 的支援。此時,事情變得有點棘手。盡管我們隻使用了兩種程式設計語言和相關架構(如下表所示),但複雜程度之高令人咂舌。
語言 | 架構 | Service 數量 |
Java | Spring Boot | 3 |
Python | Flask | 3 |
為了添加語言級跟蹤,我們首先嘗試了 OpenTelemetry 的自動檢測代理,但卻發現其輸出資料混亂不堪。我們相信,随着自動檢測庫的日趨成熟,這種情況會有所改善。但目前我們不考慮 OpenTelemetry 代理,并決定将跟蹤語句加入我們的代碼。
在開始在代碼中直接實作跟蹤之前,我們首先連接配接了 OpenTelemetry Collector,以便将所有跟蹤資料輸出到本地運作的 Jaeger 執行個體,進而更輕松地從中檢視輸出結果。這樣做有很大幫助,因為當我們了解如何全面內建 OpenTelemetry 時,便可使用跟蹤資料的可視圖像。例如,如果在調用從屬服務時發現 HTTP 用戶端庫不含跟蹤資料,我們可以立即将該問題添加到修複清單中。Jaeger 在單個跟蹤中清晰呈現了所有不同跨度:
Python 分布式跟蹤
在我們的 Python 代碼中添加跟蹤語句相對來說比較簡單。我們添加了兩個我們所有 service 都引用的 Python 源檔案,并更新了各自的 requirements.txt 檔案,以添加相關的
opentelemetry-instrumentation-*
依賴項。這意味着我們不僅可以對所有 Python 服務使用相同的跟蹤配置,而且還能夠将每個請求的跟蹤 ID 添加到日志消息中,并将跟蹤 ID 嵌入從屬 service 的請求。
Java 分布式跟蹤
接下來,我們将視線轉向 Java 服務。在“綠地”項目中直接使用 OpenTelemetry Java 庫相對簡單 —— 您隻需導入必要的庫并直接使用跟蹤 API 即可。但是,如果您也和我們一樣使用 Spring,則需做出其他決定。
Spring 已有一個分布式跟蹤 API —— Spring Cloud Sleuth。它為底層分布式跟蹤實作提供了一個 façade,并具有以下功能(如文檔中所述):
- 将跟蹤和跨度 ID 添加到 Slf4J MDC,以便從日志聚合器中的給定跟蹤或跨度中提取所有日志。
- 檢測 Spring 應用的通用進出點(servlet filter、rest template、scheduled actions、message channels、feign client)。
- 如果
可用,……,則通過 HTTP (生成并報告)相容 Zipkin 格式的跟蹤資料。預設情況下,它将這些資料發送到 localhost(端口 9411)上的 Zipkin 收集器服務。您可使用spring-cloud-sleuth-zipkin
配置服務的位置。spring.zipkin.baseUrl
此外,該 API 還支援我們将跟蹤資料添加到
@Scheduled
注解的任務。
換句話說,我們隻使用 Spring Cloud Sleuth 便可從一開始就獲得 HTTP 服務端點級的跟蹤資料 —— 這堪稱一大優勢。由于我們的項目使用了 Spring,是以我們決定全面擁抱該架構并利用其所提供的功能。但在使用 Maven 将這一切連接配接在一起時,我們發現了一些問題:
- Spring Cloud Sleuth Autoconfigure 子產品仍然處于裡程碑版本。
- Spring Cloud Sleuth Autoconfigure 子產品依賴于過時的内測版
。該庫目前沒有最新的非内測 1.x版本。opentelemetry-instrumentation-api
- 鑒于 Spring Cloud Sleuth 為裡程碑版本編碼其依賴項引用的方式,該項目必須從 Spring Snapshot 存儲庫中拉取父項目對象模型 (POM)
。spring-cloud-build
我們的 Maven 項目定義是以變得有些複雜,因為我們必須從 Spring 存儲庫和 Maven Central 中拉取 Maven(這充分表明 Spring Cloud 很早便提供了 OpenTelemetry 支援)。盡管如此,我們仍持續推進,建立了一個通用遙測子產品,以使用 Spring Cloud Sleuth 和 OpenTelemetry 配置分布式跟蹤,并開發了多種遙測相關的輔助功能和擴充功能。
在通用遙測子產品中,我們通過提供以下特性擴充了 Spring Cloud Sleuth 和 OpenTelemetry 庫的跟蹤功能:
- 支援 Spring 的自動配置類,為項目設定跟蹤和擴充功能,并加載其他跟蹤資源屬性。
- NoOp 接口實作,這樣我們可以将NoOp 執行個體注入所有 Spring Cloud Sleuth 接口,以便在啟動時禁用跟蹤。
- 跟蹤命名攔截器,用于規範跟蹤名稱。
- 通過slf4j 和 Spring Cloud Sleuth 跟蹤輸出錯誤的錯誤處理程式。
- 跟蹤屬性的增強實作,它将附加資訊編碼到每個發出的跟蹤中,包括 service 的版本、service 執行個體的 ID、機器 ID、pod 名稱、容器 ID、容器名稱和命名空間名稱。
- 跟蹤語句檢查器,它将跟蹤 ID 注入 Hibernate 發出的 SQL 語句之前的注釋中。顯然,這項工作現在可以使用SQLCommenter 完成,但我們尚未進行遷移。
此外,依托于 Apache HTTP 用戶端,我們還實作了相容 Spring 的 HTTP 用戶端,因為我們希望在 service 之間進行 HTTP 調用時獲得更多的名額和更高的可定制性。在此實作中,當調用從屬服務時,跟蹤和跨度辨別符将作為 HTTP 請求頭傳入,以包含在跟蹤輸出中。此外,這一實作提供了由 OpenTelemetry 聚合的 HTTP 連接配接池名額。
總而言之,使用 Spring Cloud Sleuth 和 OpenTelemetry 進行跟蹤是一段艱難的曆程,但我們相信一切都值得。希望本項目以及本文能夠為做出這一選擇的人們照亮前路。
NGINX 分布式跟蹤
為了在請求的整個生命周期中擷取連接配接所有 service 的跟蹤資訊,我們需要将 OpenTelemetry 內建到 NGINX 中。為此,我們使用了 OpenTelemetry NGINX 子產品(仍處于beta測試階段)。考慮到可能很難獲得适用于所有 NGINX 版本的子產品的工作二進制檔案,是以我們建立了一個容器鏡像的 GitHub 代碼倉庫,其中包含不受支援的 NGINX 子產品。我們運作夜間建構,并通過易于從中導入的 Docker 鏡像分發子產品二進制檔案。
我們尚未将此流程整合到 MARA 項目的 NGINX Ingress Controller 的建構流程中,但計劃盡快實施。
實施名額收集
完成 OpenTelemetry 跟蹤內建後,接下來我們将重點放在名額上。當時,我們基于 Python 的應用沒有現成名額,于是我們決定暫時推遲添加相關名額。對于 Java 應用,原始的 Bank of Anthos 源代碼(結合使用 Micrometer與 Google Cloud 的 Stackdriver)為名額提供了支援。但我們從 Bank of Sirius 中删除了該代碼,原因是它不支援配置名額後端。盡管如此,名額鈎子的存在說明需要進行适當的名額內建。
為了制定一個實用的可配置解決方案,我們首先研究了 OpenTelemetry Java 庫和 Micrometer 中的名額支援。我們搜尋了這兩種技術之間的比較,許多搜尋結果都列舉了 OpenTelemetry 在 JVM 中用作名額 API 的缺陷,盡管在撰寫本文時 OpenTelemetry 名額仍處于内測階段。Micrometer 是一個成熟的 JVM 名額門面層,類似于 slf4j,提供了一個通用 API 包裝器,對接可配置的名額實作,而非自身名額實作。有趣的是,它是 Spring 的預設名額 API。
這時,我們對以下實際情況進行了權衡:
- OpenTelemetry Collector 可以使用幾乎任何來源的名額,包括Prometheus、StatsD和原生OpenTelemetry Protocol (OTLP)
- Micrometer 支援大量的名額後端,包括 Prometheus 和 StatsD
- 面向 JVM 的 OpenTelemetry Metrics SDK 支援通過 OTLP 發送名額
經過幾次試驗後,我們确定,最實用的方法是搭配使用 Micrometer Façade 和 Prometheus 支援實作,并配置 OpenTelemetry Collector 進而可以使用 Prometheus API 從應用中提取名額。許多文章都曾介紹過,OpenTelemetry 中缺少名額類型可能會導緻一些問題,但我們的用例無需這些名額類型,是以可以在這點上讓步。
我們發現 OpenTelemetry Collector 有趣的一點是:即使我們已将其配置為通過 OTLP 接收跟蹤資料和通過 Prometheus API 接收名額,它仍然可以配置為使用 OTLP 或任何其他支援協定将這兩種類型的資料發送到外部資料接收器。這有助于我們輕松地使用 LightStep 來試用我們的應用。
總體而言,使用 Java 編寫名額非常簡單,因為我們按照 Micrometer API 标準進行編寫,後者有大量示例和教程可供參考。對于名額和跟蹤而言,最困難之處可能是恰好在面向
telemetry-common
名額的 pom.xml 檔案中擷取 Maven 依賴關系圖。
實施 error 聚合
OpenTelemetry 項目本身的任務中不包含 error 聚合,而且它所提供的 error 标記實作也不像 Sentry 或 Honeybadger.io 等解決方案那樣簡潔。盡管如此,我們還是決定在短期内使用 OpenTelemetry 進行 error 聚合,而非添加其他工具。借助 Jaeger 之類的工具,我們可以搜尋
error=true
,以找到所有帶有 error 條件的跟蹤。這至少能夠讓我們了解常見問題之所在。未來,我們可能會考慮添加 Sentry 內建。
實施健康檢查和運作時自檢
在我們應用的上下文中,通過健康檢查,Kubernetes 能夠知道 service 是否健康或是否已完成啟動階段。如果 service 不健康,則可将 Kubernetes 配置為終止或重新開機執行個體。因為我們發現文檔資料的欠缺,我們決定不在我們的應用中使用 OpenTelemetry 健康檢查。
而在 JVM 服務中,我們使用了名為 Spring Boot Actuator 的 Spring 項目,它不僅提供健康檢查端點,而且還提供運作時自檢和堆轉儲端點。在 Python 服務中,我們使用 Flask Management Endpoints 子產品,該子產品提供了 Spring Boot Actuator 功能的子集。目前,它隻提供可定制的應用資訊和健康檢查。
Spring Boot Actuator 可接入 JVM 和 Spring,以提供自檢、監控和健康檢查端點。此外,它還提供了一個架構,用于将自定義資訊添加到其端點上呈現的預設資料中。端點可提供對緩存狀态、運作時環境、資料庫遷移、健康檢查、可定制應用資訊、名額、周期作業、HTTP 會話狀态和線程轉儲等要素的運作時自檢。
Spring Boot Actuator 實作的健康檢查端點采用了子產品化配置,是以 service 的健康狀況将包括多個單獨的檢查(分成“存活”、“就緒”等類别)。此外,它還提供了顯示所有檢查子產品的完整健康檢查,通常如此處所示。
資訊端點在 JSON 文檔中被定義為單個進階 JSON 對象和一系列分級密鑰和值。通常,文檔會指定 service 名稱和版本、架構、主機名、作業系統資訊、程序 ID、可執行檔案名稱以及有關主機的詳細資訊,例如機器 ID 或唯一 service ID。
實施堆轉儲和核心轉儲
您可能還記得上述“對照功能清單比較工具功能”部分的表格,其中沒有一個工具支援運作時自檢或堆轉儲/核心轉儲。但作為我們的底層架構,Spring 提供了對二者的支援 —— 盡管将可觀測性功能添加到應用中需要花一些功夫。如上一節所述,在運作時自檢中,我們結合使用了 Python 子產品與 Spring Boot Actuator。
同樣,在堆轉儲中,我們使用了 Spring Boot Actuator 提供的線程轉儲端點來實作所需功能的子集。我們無法按需獲得核心轉儲,也無法以理想的細粒度級别獲得 JVM 的堆轉儲,但我們可以輕松地獲得一些所需的功能。然而,Python 服務的核心轉儲将需要開展大量額外工作,這有待在後期進行。
總結
在反複嘗試和比較之後,我們最終選擇使用以下技術來實作 MARA 的可觀測性(在下文中,OTEL 代表 OpenTelemetry):
- 日志記錄(适用于所有容器) – Filebeat → Elasticsearch / Kibana
- 分布式跟蹤
- Java– Spring Cloud Sleuth → 用于 OTEL 的 Spring Cloud Sleuth 導出器 → OTEL Collector → 可插式導出器,如 Jaeger、Lightstep、Splunk 等。
- Python– OTEL Python 庫 → OTEL Collector → 可插式存儲
- NGINX 和 NGINX Plus(尚不支援 NGINX Ingress Controller) – NGINX OTEL 子產品 → OTEL Collector → 可插式導出器
- 名額收集
- Java– Micrometer(通過 Spring) → Prometheus 導出器 → OTEL Collector
- Python– 尚未實作
- Python WSGI– GUnicorn StatsD → Prometheus (通過 StatsD/ServiceMonitor)
- NGINX– Prometheus 端點 → Prometheus (通過 ServiceMonitor)
- 錯誤聚合– OTEL 分布式跟蹤 → 可插式導出器 → 導出器的搜尋功能,用于查找标明
的跟蹤error
- 健康檢查
- Java– Spring Boot Actuator → Kubernetes
- Python– Flask Management Endpoints 子產品 → Kubernetes
- 運作時自檢
- Java– Spring Boot Actuator
- Python– Flask Management Endpoints 子產品
- 堆/核心轉儲
- Java– Spring Boot Actuator 支援線程轉儲
- Python– 尚不支援
此實作隻是目前進展情況,必将随着技術的發展而演進。不久之後,我們将通過廣泛的負載測試來運作應用。我們希望全面了解可觀測性方法的不足之處并添加更多可觀測性功能。
歡迎親自試用現代應用參考架構和示例應用 (Bank of Sirius)。如果您對幫助我們改進服務有任何想法,歡迎您在我們的 GitHub 倉庫中建言獻策!
更多資源
想要更及時全面地擷取 NGINX 相關的技術幹貨、互動問答、系列課程、活動資源?
請前往 NGINX 開源社群:
官網:https://www.nginx.org.cn/