關於平臺工程的開發者工具鏈,你還想加點啥?

前言

從 Kubernetes 誕生以來,以 DevOps、容器化、可觀測、微服務、Serverless 等技術爲代表的雲原生,催生了應用架構新一輪的升級。有意思的是,與以往的技術迭代更新不同,原本是一個技術圈常規的一次技術實踐,在千行百業數字化轉型大背景,疊加持續疫情衝擊的雙重影響之下,加上部分傳統行業科技自主政策的催化;這一次的技術迭代幾乎變成了 IT 從業人員全民參與的一次盛宴。但是陡峭的學習曲線、複雜的技術體系、瞬態的基礎資源形態,讓企業的信息體系建設在研發、構建、交付、運維等多方面都帶來了不少的挑戰。這些挑戰也加深了日益更新的技術棧與習慣於聚焦在一線業務開發的開發者之間矛盾,這個矛盾直接催生了最近的平臺工程理念的誕生。

這個理念抓住這個衝突點,提出了“內部研發自助平臺”的構想:“企業應該以平臺化建設的方式,提供一系列的自助型工具,協助開發者在各個環節中解決遇到的各種技術問題”。這個說法一下戳中衆多開發者的癢點,這也是這一概念突然之間大火的原因之一。理念背後又引申出來了一個更爲直接的問題:這個工具裏面應該有點啥?

揭開問題的面紗

早在 2018 年,EDAS 產研團隊拜訪一傢俱有百人研發團隊的客戶,當時客戶正在進行微服務拆分和遷移上雲,他們遇到了一些新問題:

  • 本地因爲依賴問題,沒法起動完整的環境,導致開發困難。
  • 雲上環境調用關係複雜,無法做調試。

客戶期望將特定的實例啓動到本地,雲端能調本地,本地調雲端,實現微服務端雲聯調。對於這些問題我們沒有任何準備,於是回來後趕緊開始調研分析,於是慢慢揭開了藏在水面下的冰山。

客戶的訴求很簡單,就是想把微服務應用起在本地,應用能跟雲端微服務互相調用,如下所示:

遷移上雲後,客戶的網關、應用、消息隊列、緩存、數據庫等組件模塊都部署在雲端網絡內,本地需要經過堡壘機才能進行訪問。在這種情況下,本地節點是不可能正常啓動的(因爲連不通數據庫等關鍵組件),也是不可能跟雲端服務互相調用的。採取了雲原生架構之後,客戶卻再也回不去原來簡單高效的本地開發方式了。

這個問題只有這個客戶遇到嗎?不是的,這幾年我們跟很多客戶聊下來他們都有這個問題。但其實也不是一點解決辦法都沒有,最直接的辦法是:通過架設私有網絡的方式,連通本地辦公網跟雲端網絡,實現網絡互通。但實際上這個辦法有三大缺陷,導致並不是很多客戶採用。

  • 成本高昂:搭建專線網絡的投入相當大,相比於收益來說並不划算。
  • 安全性低:打通本地到雲上網絡,對本地辦公網和雲上生產網都帶來了不穩定因素,本質上擴大了安全域,也擴大了攻擊面。
  • 運維複雜:網絡運維是相當複雜的,在高度可伸縮的雲原生架構下打平本地和雲端網絡,這個是很多網絡工程師的噩夢。本地和雲端兩張網絡必須做好規劃,兩邊網段不可衝突,同時雙向網絡路由和安全策略都需要人工管理,複雜費力且容易出現問題。

對於這些問題,有的企業採取折中的辦法,在雲端找一臺機器作爲 VPN 服務器,搭建本地到雲端的 VPN 鏈路。這個方案同樣需要維護網絡路由以實現網絡互通,另外 OpenVPN 雖便宜但不穩定,專用 VPN 性能高但費用昂貴,魚與熊掌不可兼得。

意識到這些問題之後,我們便開始了“路漫漫其修遠兮”的探索之路。

端雲互聯,問題的初解答

在一開始我們就確定兩個目標:一是雙向打通本地和雲端鏈路,二是不需要對網絡架構進行傷筋動骨的改造。

在歷經三個月的閉關研發之後,我們在 2018 年底研發出來了這個工具。它支持雙向聯通,而且是插件化開箱即用,支持 Windows 和 MacOS 系統。我們把它命名爲端雲互聯,其整體組成如下所示:

端雲互聯插件會在啓動微服務的時候拉起一個sidecar進程--通道服務,通道服務負責接收本地微服務的流量,並通過堡壘機轉發至雲端目標微服務。下面進一步說明其中三個核心要點。

本地微服務調用轉發到 sidecar

我們使用了 Java 原生的流量代理技術,通過注入啓動參數,可以使得本地微服務流量以 socks 協議轉發至通道服務 sidecar 上。關於具體參數細節,可閱讀 Java Networking and Proxies 來了解細節。

sidecar 將調用轉發到雲端微服務

其實 SSH 本身就可以用來進行數據轉發,充當正向代理和反向代理。SSH 協議從下到上分爲傳輸層、認證層和連接層三層協議:

  • 傳輸層協議(Transport Layer Protocol):這層協議負責建立起安全的連接通道,是整個 SSH 的安全性基石。
  • 用戶認證協議(User Authentication Protocol):這層協議負責完成遠程身份認證。
  • 連接協議(Connection Protocol):這層協議實現 SSH 通道的多路複用和信息交互,可以通過它來實現遠程命令執行和數據轉發等多種功能。

我們基於 SSH 的連接協議來使得通道服務 sidecar 將調用轉發到雲端微服務,雖然SSH底層原理有點複雜,但上層使用是挺簡單的,主流的編程語言也基本都有現成的庫來使用。

雲端微服務調用轉發到堡壘機

這裏我們利用了微服務的機制,將堡壘機的 IP 和特定端口作爲本地微服務的地址信息註冊到註冊中心。這樣雲端微服務調用時就會通過註冊中心發現堡壘機,併發起服務請求,再結合 SSH 的數據轉發就能回到本地微服務。

衆裏尋他千百度

端雲互聯工具上線後,受到了很多客戶的歡迎,但客戶使用過程中遇到了新的問題:

  • NIO 流量代理問題:Java Networking and Proxies 裏的流量代理參數只對 BIO 的流量生效,NIO 框架並不支持。這個問題影響面非常大,因爲微服務應用基本都會直接或間接地使用 Java NIO 框架。具體簡單的例子,Netty 本身就是基於 Java Nio 的,而很多流行的中間件框架都使用 Netty 作爲傳輸框架。
  • 域名解析問題:對於域名解析,本地微服務應用在訪問之前會發起域名解析,而這些域名解析同樣是不支持 Java 流量代理的。也就是說,如果這個域名只能在雲端完成解析,那麼整個調用就會失敗。例如,K8s 裏的 service 域名只能在集羣節點上完成 DNS 解析,本地是無法解析的,從而導致本地無法調用 K8s service。

對於這些問題,業界通常的做法是採取容器來解決(例如 Telepresence),通過將應用跑在容器內,再結合 iptables 來攔截並轉發整個容器內的流量。這種方式是可行的,我們也支持了這種方式,整體架構如下所示:

這個鏈路跟之前是差不多的,除了本地引入了容器技術之外。在上圖中,我們通過 docker network connect,可以使得應用容器和 sidecard 容器共享網絡棧,這樣通道服務便可以通過 iptables 去攔截本地微服務進程的流量(包括 NIO 和 DNS 流量)並進行轉發。

方案很美好,但現實很骨感,很多客戶用不起來。究其原因,那就是:本地開發需要引入容器這個重量級的依賴。這裏有兩個問題,一個是“重”,另一個是“依賴”。“重”,是因爲本地開發機器的算力是非常小的。Chrome、IDE 和通信軟件等應用往往已經佔據了大部分的本地機器資源,在這背景下本地再啓動容器往往會導致死機。另外,Windows 和 MacOS 等主流操作系統也並不自帶 Docker 等容器軟件,開發者需要自己在本地自行安裝,由於網絡帶寬等原因整個安裝過程也會遇到許多問題。

那除了使用容器,還有別的辦法來解決應用的流量代理問題嗎?還是有的,不過侷限性也非常大。例如 torsocks 這個工具就可以實現進程級別的 TCP 和 DNS 流量攔截並轉發,但問題是它並不支持 Windows 系統。MacOS 也存在問題。由於 torsocks 是基於 LD_PRELOAD/DYLD_INSERT_LIBRARIES 機制來改寫系統調用來進行流量攔截的,而 MacOS 系統本身有系統完整性保護,會阻止特定系統調用被改寫,因此並不是所有的流量都能被攔截到。

難道就沒有更好的方案了嗎?

那人卻在,燈火闌珊處

回顧一下我們所面臨的問題:Java 原生流量代理不支持 NIO 和 DNS 流量轉發。這裏有一個非常重要的信息--Java。業界或者開源社區的流量攔截方案普遍追求通用性,從而引入了容器依賴,顧此失彼。

既然追求通用性有諸多問題,那麼聚焦到 Java 語言是否有更優的解法?答案是肯定的。Java 可以通過 Agent 字節碼技術,動態修改應用運行時的行爲,而上層代碼無需任何變動。比如像 Pinpoint、SkyWalking 等鏈路跟蹤工具,它們都是通過注入一個字節碼Agent來實現無侵入的鏈路埋點的。再比如診斷領域流行的 Arthas 工具,它也是基於字節碼技術來實現 Java 進程的調用跟蹤和 Profiling。

於是,我們開始探索基於字節碼技術的解決方案。整個探索過程是艱難且有趣的。在微服務框架層面,我們需要適配 SpringCloud、Dubbo、HSF 甚至是 gRPC 等主流框架;在組件層面,我們需要支持微服務、數據庫、消息隊列、任務調度、緩存等等組件;在 JDK 版本上,我們需要兼容從 JDK 1.7 到 JDK18 之間的版本...在這過程中,我們不斷進行迭代改進,也不斷收到客戶的正面反饋,讓工具日趨完美。

在經過 1 年時間的打磨之後,我們終於自研出基於字節碼的 Java 流量代理技術,架構如下所示:

這個方案只需要引入一個代理字節碼 Agent,並沒有引入外部依賴。這個代理 Agent 相比於容器來說輕量得多,而且會在啓動階段被端雲互聯插件自動拉取並注入,上層使用是無感知的。至此,我們終於很好地解決了本地微服務應用的流量代理問題。

獨上高樓,望盡天涯路

在這幾年裏,我們一直在低頭趕路,不斷地發現問題並解決問題。與此同時,雲原生社區也在逐步演進。同樣在 2018 年,kubernetes 社區發表了一篇名爲 Developing on Kubernetes 的文章,上面對不同的開發模式有一個非常好的總結:

remote 表示雲端,local 表示本地。cluster 爲 K8s 集羣,dev 爲開發環境。針對 dev 和 cluster 不同的位置,整體可分爲四種開發模式:

  • pure off-line:這種模式表示你的 K8s 集羣和開發環境都在本地。K3s 、 Minikube和 EDAS Core(這裏先賣個關子,下文再進行介紹)都屬於這種模式,你可以在本地直接啓動一個輕量級的開發集羣。
  • proxied:這種模式表示 K8s 集羣運行在雲端,開發環境在本地,雲端集羣和本地開發環境通過代理進行互聯。這個模式的典型代表爲社區的 Telepresence 和 EDAS 端雲互聯。在多語言通用性上 Telepresence 略勝一籌,而在 Java 上 EDAS 端雲互聯更加易用。
  • live:這種模式表示 K8s 集羣運行在雲端,開發環境在本地,本地代碼通過 CICD 等方式來更新雲端的應用。這個模式是最常見的模式,也是普遍效率最低的模式。如果通過 CICD 部署,意味着你每次代碼修改都需要經過漫長的構建和部署才能生效到集羣裏面。如果在開發過程中需要不斷修改代碼來調試,這個迭代部署過程是非常耗時的。
  • remote:這種模式表示 K8s 集羣和開發環境都在雲端。Cloud IDE 是典型的例子,代碼和運行環境都在雲端,本地通過瀏覽器或者輕量級的端應用來編輯代碼。實際上來看,這種方式仍未得到廣大開發者認可,本地 IDE 的體驗優於 Cloud IDE 體驗,本地開發仍然是主流。

在 proxied 模式上,我們已經把端雲互聯打磨的相當不錯了,但它不能解掉所有問題。這樣的場景並不罕見:本地開發調試都好好的,但一部署上去就是有問題。這種問題的根源是,本地運行的環境和雲端集羣裏運行環境是不一致的,這個不一致會產生種種問題。例如,本地能正常運行一個需要 2c4g 的 Java 進程,不代表雲上集羣也能正常分配一個 2c4g 的 Pod,因爲當前集羣內可能是沒有多餘資源的。

這樣的問題有很多,不可一一枚舉。這也促使我們進一步思考應該如何解決這些問題。在經過半年的調研、探索和研發,我們研發出雲原生工具箱(Cloud Native Development Kit,簡稱 CNKit),由它來解決這些問題,並提供雲原生架構下的開發、調試和診斷能力。

雲原生工具箱(Cloud Native Development Kit)

我們解決問題的思路是,要解決環境不一致的問題,只能回到環境中去。一個應用在雲原生環境下啓動雖然看上去很簡單,但實際上是要經歷很多個步驟的。應用需要經過從 K8s 調度,到 Pod 初始化,再到服務拉起,最終才能完成應用運行。在這個過程中,你可能會遇到以下問題:

對於這些問題,我們進行歸納總結,沉澱出一套解決方案:通過 CNKit 來快速複製 Pod,然後進行迭代開發、部署、調試和診斷。整體功能如下所示:

通過與 EDAS 全流量流控集成,CNKit 可使得只有符合特定規則的調試流量進入複製的 Pod,而不影響其他正常流量。對於複製的 Pod,你可使用 CNKit 開箱即用的部署、調試和診斷能力,並且可以使用基於審計的命令終端。下面來具體說明覆制、部署、調試和診斷能力。

複製

這個複製的 Pod 相當於我們自己的一個臨時工作空間,我們可以不斷通過瀏覽器或者 IDE 來部署自己的應用包,並進行調試和診斷。複製 Pod 當前支持如下配置:

具體作用爲:

  • 啓動命令:即 Pod 的啓動命令。默認下使用原鏡像的啓動命令,而如果需要進行迭代部署或者開啓調試的話,需要自定義啓動命令。這是因爲在生產環境中,原鏡像啓動命令往往會使用應用進程來作爲 1 號進程,一旦應用退出或重啓,這個 Pod 就會隨之釋放。因此,需要設置一個特殊的啓動命令來防止 Pod 隨應用退出而被釋放。
  • 複製模式:支持基於 Pod 複製或者從 Deployment 的 spec 進行創建。
  • 目標節點:即 Pod 運行在那個集羣節點上。默認通過 K8s 調度來運行 Pod,但你也可以直接指定特定集羣節點來運行此 Pod。
  • Pod 日誌:配置將 Pod 日誌打到標準輸出或者重定向到文件。
  • 流量控制:通過全鏈路流控,可使得只有符合特定規則的請求進入該 Pod 節點。
  • 診斷選項:支持應用啓動時立即運行 tcpdump 來進行監測、一鍵打開 JVM 異常記錄和去除 Liveness 探針。

這些選項都是基於 EDAS 長年累月的客戶支持所總結出來的經驗,可能看上去並不酷炫,但卻是非常實用的。拿“去除 Liveness 探針”這個配置項來說明。如果應用啓動階段就出現異常,這種情況下 Liveness 探針是會失敗的,K8s 會直接 Kill 掉這個 Pod 容器並重新拉起。我們會陷入 Liveness 失敗,Pod 容器被殺死,然後容器重啓導致現場丟失,Liveness 又失敗的無限循環當中。去除 Liveness 探針之後,你就可以進去 Pod 容器中進行問題排查了。

這裏還有一個非常有意思的配置項--全鏈路流控。全鏈路流控是 EDAS 上微服務治理的殺手鐗,可以使得整體微服務鏈路的流量指哪打哪。對於複製的 Pod,我們可能會希望只有自己的請求才進入這個 Pod,而不影響其他人的調用請求。這種情況只需要在界面上勾選加入特定流控分組即可,使用上非常簡單。

部署

還記得最初的問題嗎?在雲原生架構下,我們每次部署都需要經過 CICD、EDAS 部署和 K8s 調度來拉起應用,這個過程是漫長而痛苦的。而且,我們在排查問題時往往需要來臨時安裝特定工具,每次拉起新的應用 Pod 意味着需要重新安裝所需工具。

對於這個問題,我們推薦採取“一次複製,多次使用”策略。在上面提到,我們可以通過複製 Pod 來創建出屬於自己的“臨時工作區”(實質也是一個 Pod),然後可以通過瀏覽器或者 IDE 來直接把應用包部署到臨時工作區,並且進行調試診斷。基於 CICD 和 CNKit 的開發流程是截然不同的,如下所示:

CICD 部署路徑適合生產環境,通過標準流程保證了線上業務的穩定性。但同時它又是流程冗長的,在開發階段優勢不明顯,反而會降低開發效率。這種情況下 CNKit 部署流程是很好的互補,你只需要複製 Pod,然後便可以通過瀏覽器或者 IDE 來不斷更新應用包調試代碼。

調試

調試(這裏特指 remote debug)是應用開發過程中相當重要的一環,如果無法調試,那麼應用開發效率將會大大降低。在雲原生架構下,調試應用並不是那麼簡單,但總是可以完成的。在這一方面,CNKit 除了簡化調試流程外,還提供了流量控制能力:

  • 簡化流程:你只需要在頁面上點擊“開啓調試”,CNKit 便會重啓 Pod 裏的應用來開啓調試端口。然後本地通過 IDE 來一鍵連接到 CNKit,接着就可以開始斷點調試了。
  • 流量控制:通過集成 EDAS 全鏈路流控,CNKit 可使得只有特定請求能進入複製 Pod,觸發斷點調試邏輯。

通過這兩點,你可以非常方便地完成代碼調試。下圖是一個簡單的說明樣例,假如你在開發一個商品中心,上游爲交易中心,下游爲庫存中心,使用 CNKit 進行調試的整體鏈路如下所示:

標記爲開發版本的商品中心即爲複製出來的 Pod 節點,雲端環境中通過全鏈路流控來將特定流量轉發到該 Pod 中,開發者本地則通過 CNKit proxy 來連接到該 Pod 的調試端口。

實際上,在一個多人並行開發的服務中,每個人都可以擁有屬於自己的開發版本節點,只需要設定不同的流量控制規則即可,這樣可並行開發可互不干擾。

診斷

我們將問題診斷爲 K8s 調度、應用啓動、應用運行和應用下線四個階段。在不同階段,採取的診斷手段並不相同:

在 K8s 調度過程中,我們主要關注其產生的相關事件,發生調度異常時 K8s 會給出相關原因。下圖爲正常調度時的 K8s 事件:

當出現調度異常(例如資源不足導致調度失敗)的問題時,K8s 會產生相應事件:

而在應用啓動階段,除了 K8s 事件,我們還可以觀察 Pod 日誌,這部分日誌是應用產生的,裏面包含更詳盡的信息。下圖爲 Pod 的標準輸出日誌樣例:

另外,應用啓動階段會產生較多網絡訪問,應用啓動失敗很多情況下都是由於網絡請求異常引起的,因此 CNKit 支持在啓動前自動運行 Tcpdump 來記錄網絡請求。下圖爲應用啓動時自動抓取的 Tcpdump 包,CNKit 支持文本和 pcap 兩種格式,下圖爲文本格式的 Tcpdump 數據:

最後,在應用運行和下線階段,你仍然可以使用 K8s 事件、Pod 日誌和 Tcpdump,另外還可以一鍵使用 CNKit 集成的 Arthas 工具。通過在頁面上一鍵運行 Arthas,CNKit 會自動完成 Arthas 安裝並運行,整體交互如下所示:

至此,CNKit 的複製、部署、調試和診斷都一一分享完畢。但除了這些能力,CNKit 還有一些隱藏彩蛋,例如審計 Webshell 等等,這些地方留給讀者來慢慢探索,此處不再贅述。

EDAS Core

除了端雲互聯和 CNKit,我們還開放了 EDAS Core。如果按照上面 Developing on Kubernetes 劃分的標準來看,端雲互聯屬於 proxied 模式,CNKit 則爲 live 模式,而 EDAS Core 則爲 pure off-line 模式。

EDAS 本身是收費的商業化產品,它是一個應用託管和微服務管理的雲原生 PaaS 平臺,提供應用開發、部署、監控、運維等全棧式解決方案,同時支持 Spring Cloud 和 Apache Dubbo 等微服務運行環境。而 EDAS Core 則爲免費的輕量級 EDAS 內核版本,同樣支持上述能力,但剝離了商業化特性,不提供服務 SLA 和實時的運維支持,適合在開發階段使用。

EDAS Core 最低只需要 4 核 8g 的機器資源,我們完全可以在本地筆記本上來運行一個離線的 EDAS 平臺,並進行微服務開發。EDAS Core 整體架構如下所示:

這裏進行簡單說明:

  • EDAS Core:包含了 EDAS 應用託管能力,支持 Nacos 服務註冊發現和 Minio 持久化存儲,可運行於 Kind、K3s、Docker-Desktop 和 K8s 集羣之上,只需 4c8g 的資源佔用。
  • 開發者工具:支持使用 Jenkins 插件進行持續部署、Terraform 進行基礎設施維護、ACT 進行本地開發,併兼容 EDAS 開放 API 和 SDK。
  • 安裝介質:支持通過 Helm、OSS 和 ADP 進行 EDAS Core 安裝。
  • K8s Cluster:此 K8s 集羣即爲 EDAS Core 託管的集羣,上面運行微服務應用(並自動注入服務治 OneAgent)。
  • 服務集成:在橫向服務集成上,EDAS Core 支持和 EDAS 商業化應用進行一鍵轉換,並集成了 ARMS 和 SkyWalking 等鏈路跟蹤產品,同時支持使用 ACR 進行鏡像託管。

下面爲 EDAS Core 的運行界面(EDAS 老用戶應該對這個界面比較熟悉了):

當前 EDAS Core 處於內部邀測狀態,如果希望使用此能力,歡迎在阿里雲上向 EDAS 產品發起工單諮詢:)。

結語

雲原生架構和微服務開發這兩個都是非常流行的技術領域,但“雲原生架構下的微服務開發”這個命題卻甚少見國內廠商提及。EDAS 作爲微服務託管領域的先行者很早就開始了雲原生架構的支持,並一直在關注新架構下的微服務開發問題。

從最早的端雲互聯模式開始,到最近推出的雲原生工具箱(CNKit)和 EDAS Core,EDAS 一直站在開發者角度來思考雲原生技術演進所面臨的新問題,並不斷提供解決這些問題的工具和產品。最後,對於這幾個工具產品進行簡單的總結來結束本文:

參考資料

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章