首月有 140 名工程師登入我們花六個月蓋起來的內部開發者平台。到第三個月,剩下 28 個——而且大部分是平台團隊自己人,登入只是想確認它還沒掛。
我們蓋了一個 Backstage 內部開發者平台,三個月後八成工程師默默走回老路
這是一篇 HackerNoon 的 postmortem,作者 Akhil 記錄了一件在許多公司正在重演、卻很少被誠實寫下來的事:一支團隊照著 Spotify 的 Backstage 藍圖,蓋了一個內部開發者平台(Internal Developer Platform,IDP),三個月內八成工程師悄悄棄用它,走回「從舊 repo 複製貼上 Dockerfile」的日子。原文對這場失敗的描述是:「A full 80% of the engineering org had tried our 'Spotify-like' developer experience, quietly decided it was worse than what they had before, and gone back to copy-pasting Dockerfiles from legacy repositories.」關鍵字是 quietly——沒有人開會抗議,沒有人提 ticket,他們只是不再登入。這種靜默的棄用最難查,因為它不留下任何一個可以指著罵的錯誤。這篇文章要做的,就是把「為什麼會這樣」當成一樁調查來辦:先看採用曲線本身,再一個一個排除看似合理的嫌疑犯,直到真正的根源浮出來。
先把規模對齊,這決定了這場失敗的份量。原文寫道:「We had spent six months of dedicated engineering time building this. Four senior engineers.」六個月、四名資深工程師的專職投入。背景是一個中型組織:「We had roughly 40 microservices, a growing EKS cluster, and a severe onboarding problem.」大約 40 個 microservice、一個持續長大的 EKS 叢集,以及一個嚴重的 onboarding 問題。這個 onboarding 問題具體到什麼程度?「It took a new hire three weeks to get a service from their laptop to a staging environment. They had to know Terraform, Helm, GitHub Actions, and exactly which senior engineer to bribe in Slack to get an IAM role provisioned.」一個新人要花三週,才能把一個服務從自己的筆電推到 staging——他得懂 Terraform、Helm、GitHub Actions,還得知道該在 Slack 上「賄賂」哪一位資深工程師才能弄到 IAM role。這是個真實的痛。平台團隊要解決的問題不是虛構的,這一點很重要,因為它排除了「這平台根本沒必要」這個最偷懶的解釋。需求是真的,投入是真的,失敗也是真的。
140 → 52 → 28:三個月蒸發的採用曲線
調查從那條曲線開始。原文給的三個數字很乾淨:首月 140 個 unique user 登入這個新的 IDP;「In month two, that number dropped to 52.」第二個月掉到 52;「By month three—the week I was pulling the data—we had exactly 28 unique users.」到第三個月,作者拉資料的那一週,恰好 28 個 unique user。更刺眼的是那 28 個是誰:「Most of them were on my team (the platform team), checking if the IDP was still running.」大部分是平台團隊自己人,登入的目的只是確認這東西還活著。
把這條曲線畫出來,它的形狀本身就是第一條線索。這不是一條緩慢流失的曲線——不是那種「新工具熱度過了、慢慢冷掉」的自然衰減。它是斷崖:第一個月到第二個月直接腰斬,第二到第三個月再砍掉一半。一個東西如果是「不好用但堪用」,採用曲線會是緩坡;只有當使用者在真正試過之後判定它「比原本更糟」,才會出現這種第一個月衝上去、之後直線墜落的形狀。140 這個數字說明大家願意給它機會——八成的人來看過了;28 這個數字說明他們看完的結論是掉頭就走。曲線的斜率在告訴我們:問題不在「沒人知道」,而在「知道的人用過就跑」。
三個月的採用曲線是斷崖而非緩坡:首月 140、次月 52、第三月 28
唯一使用者數首月 140、次月腰斬到 52、第三月剩 28——三個月流失八成,且留下的多半是平台團隊自己人登入確認服務沒掛,是斷崖不是緩坡。
假說一:是不是 UI 做得不夠好?
面對一個被棄用的產品,最先跳出來的假說總是「介面體驗不夠好」——按鈕不夠順、頁面不夠美、載入太慢。這個假說吸引人,因為它有明確的補救方向:找設計師、加動畫、優化前端。如果死因是這個,那還算是個好消息。但原文用一句話把這條路堵死了:「We had custom plugins, a beautifully themed UI, and a mandate from the VP of Engineering.」他們有自訂 plugin、有精心主題化的 UI、還有工程副總的背書。三樣東西——技術完整度、視覺品質、行政推力——全齊了,結果還是崩。這幾乎是反過來的證據:當一個內部工具連 VP mandate 都壓不住流失,問題就不可能在「大家不知道有這東西」或「介面不夠漂亮」這一層。
要真正證偽「UI 假說」,得看使用者按下按鈕之後發生什麼。表面上,那顆 Create New Service 按鈕做得無可挑剔;但點下去之後,介面遮住的東西才是真相。原文描述得非常具體:「When a developer clicked 'Create New Service' in our shiny new UI, they thought they were getting a running container in Kubernetes. What they actually got was a GitHub repo with a 'Hello World' Node app, a broken CI pipeline because the secret wasn't provisioned, and a Jira ticket automatically assigned to the DevOps queue asking for AWS permissions.」開發者以為自己拿到的是一個在 Kubernetes 裡跑起來的 container;實際拿到的是一個裝著 Hello World Node app 的 GitHub repo、一條因為 secret 沒 provision 而失敗的 CI pipeline,以及一張自動丟進 DevOps queue、等著要 AWS 權限的 Jira ticket。下面這個對照把「你以為」與「你實際拿到」疊在同一格畫面裡——拖動中間那條線,看漂亮的按鈕底下究竟藏了什麼。
拖分隔線,比較「你以為」與「你實際拿到」· 拖曳
同一顆按鈕,兩種現實
點 Create New Service:你以為拿到跑起來的 K8s 服務,實際只拿到空 repo、壞掉的 CI、待補權限的 Jira ticket。
這一格畫面同時做了兩件事:它證偽了 UI 假說,也埋下了真正的根源。作者的診斷只有一句:「We hadn't reduced cognitive load. We just put a React frontend over the exact same friction.」我們沒有降低認知負擔,只是在一模一樣的摩擦上蓋了一個 React 前端。UI 越漂亮,落差反而越傷人——因為它讓使用者對「一鍵完成」的期待拉得更高,然後在 CI 紅燈與 Jira ticket 面前摔得更重。第一次上當的人會嘆口氣手動修完,第二次就不再點那顆按鈕了。UI 假說到此排除:介面不是死因,介面是共犯。
假說二:catalog-info.yaml 的維護稅
第二個嫌疑犯更具體,也更容易在資料上留下指紋:metadata 的維護負擔。Backstage 的 catalog 要能顯示你的服務,你得先交一份 metadata。原文:「To get your service to show up in the catalog, you had to write a `catalog-info.yaml` file.」而這份 YAML 不是三行了事——它掛著對 Datadog、PagerDuty、SonarQube、GitHub 整合的 annotation,還要手寫服務之間的相依。一份代表性的骨架長這樣:
apiVersion: backstage.io/v1alpha1
kind: Component
metadata:
name: billing-service
annotations:
datadoghq.com/dashboard-url: app.datadoghq.com/dashboard/billing
pagerduty.com/service-id: PD-BILLING
sonarqube.org/project-key: org_billing-service
github.com/project-slug: org/billing-service
spec:
type: service
owner: team-payments # 打錯一個字,服務就從 catalog 消失
lifecycle: production
consumesApis:
- service-b # 人肉維護的相依,機器本來能自己發現
問題不在寫這一份,而在維護它。原文把維護成本列得很清楚:API 改名?改 YAML。團隊重組?「Update the YAML across 12 repos.」跨 12 個 repo 更新。最惡毒的是打錯字的後果:「If you made a typo in the `owner` field, the YAML parser would silently fail, your service would vanish from the catalog, and you wouldn't know until the platform team pinged you two weeks later.」owner 欄位打錯一個字,YAML parser 靜默失敗,你的服務從 catalog 裡消失,而你要等兩週後平台團隊來 ping 你才會知道。這是一種最糟的失敗模式——沒有錯誤訊息、沒有紅燈、沒有立即回饋,只有兩週後的一個尷尬對話。
| 觸發事件 | 使用者被迫做的事 | 失敗模式 |
|---|---|---|
| API 改名 | 回去更新 catalog-info.yaml 對應欄位 | 容易漏改;catalog 與現實不同步 |
| 團隊重組 | 跨 12 個 repo 更新 owner 欄位 | 大範圍手動同步,一次改十幾處 |
| owner 打錯字 | (自己不會發現有事) | parser 靜默失敗,服務從 catalog 消失,兩週後平台團隊才 ping 你 |
| 服務相依改變 | 手動維護 consumesApis 清單 | 人會漂移,宣告的相依與真實呼叫關係逐漸背離 |
這條假說有沒有直接證據?有,而且是最有力的一種——使用者的原話。原文:「We asked engineers why they stopped using the portal. One senior backend developer told me, 'I spend my day writing business logic. I am not going to spend 20 minutes debugging YAML whitespace just so my service has a nice icon on a dashboard I never look at.'」一位資深後端工程師說:我一整天都在寫 business logic,我不會為了讓我的服務在一個我根本不看的 dashboard 上有個漂亮 icon,去花 20 分鐘 debug YAML 的縮排。作者自己的評語是:「He was entirely right. We had shifted the burden of platform maintenance onto the users.」他完全說對了,我們把平台的維護負擔轉嫁給了使用者。
但這裡要小心,別把症狀當成病因。YAML 維護稅確實是把人趕走的那根刺——它每天、具體、可感地折磨使用者。可是如果那份 catalog-info.yaml 交出去之後,真的換來一個能用的、自動 provision 好的服務,多數工程師會咬牙忍受那 20 分鐘的縮排地獄,因為報酬夠大。他們不忍,是因為報酬是零:填完 YAML 換來的只是一個「dashboard 上的漂亮 icon」,而底層那套 Terraform、Helm、IAM 求人的苦工一步都沒少。所以 YAML 假說解釋了「他們為什麼受不了」,卻沒解釋「他們為什麼覺得不值得受」。原文自己把這兩層關係點破:「If the lack of actual automation was the underlying disease, the metadata model was the symptom that drove engineers away.」如果缺乏真正的自動化是底層的病,那 metadata model 只是把工程師趕走的症狀。病在更下面一層。
表格最後一列——服務相依改變——常常被讀者略過,但它其實預告了本文稍後要處理的解方。consumesApis 這份清單要靠工程師在改動呼叫關係的當下,回頭手動維護;問題是沒有人會把「順手更新一份沒人看的清單」排進優先序,尤其是在改完程式碼、通過 CI、準備收工的那一刻,那份 YAML 早就不在腦子裡了。於是宣告的相依會慢慢跟真實的呼叫關係脫鉤——一開始只差一兩個服務,幾個月後整份 catalog 描述的是這套系統半年前的樣子,而不是現在的樣子。這跟 owner 欄位打錯字導致的靜默失敗,其實是同一種病:任何要求工程師手動宣告狀態的機制,都會隨時間漂移出真相,差別只在哪個欄位先發作、多久後才被發現。表格上四種觸發事件,沒有一種是靠平台自己偵測出問題的;全部得靠人記得、人回去改、人希望沒漏掉。這正是後面第三條規則要處理的核心——把「宣告」換成「發現」,讓平台自己去問系統現在長什麼樣子,而不是問工程師還記不記得。而這整份 catalog-info.yaml 帶來的維護稅,還有一層更隱蔽的成本:它讓「加入一個新服務」這件事,從單純的程式碼變更,變成一項跨檔案的行政作業。工程師得先想起有這道手續、再想起格式長什麼樣、再想起哪些 annotation key 對應哪個外部系統,任何一步卡住都足以讓人乾脆先跳過、之後再說——而「之後」往往就不會來了。而且這種維護稅不會隨著服務數量線性成長,它是乘法:40 個 microservice、每個都可能被拆分、合併、換 owner,任何一次組織調整都要重新盤點一次整份 catalog,工作量隨團隊規模與變動頻率一起放大。放在更長的時間尺度看,這也是為什麼「平台團隊自己去補 metadata」這種創可貼式的解法撐不久——他們可以在上線初期手動幫每個服務把 YAML 填好,但六個月後,當初那四個資深工程師忙著處理下一輪需求,沒人再有餘力去追每一份 catalog-info.yaml 是不是還跟得上現實。維護稅最終還是會落回原本要它自動消失的那群人身上。
根源:portal 不是 platform——一片玻璃蓋在同樣的摩擦上
真正的根源,作者用一組對照定義得很精準:「Backstage is a developer portal, not a developer platform.」Backstage 是一個開發者入口,不是一個開發者平台。這兩個詞在中文常被混用,但原文把差別釘死:「A portal is a pane of glass. It is a UI that aggregates information. A platform is the underlying engine that actually provisions infrastructure, manages state, and enforces policy.」portal 是一片玻璃,是一個把資訊聚合起來的 UI;platform 是底層那台引擎,真正去 provision 基礎設施、管理狀態、執行政策。他們犯的錯,一句話:「We stood up the pane of glass, but we didn't build the engine behind it.」我們立起了那片玻璃,卻沒蓋玻璃後面的引擎。
下面把這個結構畫出來,它是整場失敗的解剖圖。介面加在最上層,摩擦原封不動留在最下層,而中間那層本該存在的 provisioning engine——那台把 IAM、CI/CD、secret、routing 自動處理掉的引擎——從沒被蓋起來。所以按下 Create New Service 之後,請求穿過空空如也的中間層,直接落回底下那套沒被自動化的手動流程,最後吐出一個空 repo 加一張 Jira ticket。
找到根源之後,回頭看那位資深後端工程師的抱怨,意義就變了。他說他不看那個 dashboard——這不是他懶,是那個 dashboard 對他的工作沒有產生任何價值,因為它背後沒有引擎。這也解釋了採用曲線為什麼是斷崖:第一個月大家出於好奇與 mandate 去點了按鈕,發現按完什麼都沒真正發生、還得手動收尾,於是第二個月直接回到他們原本就會的老路——複製貼上 Dockerfile。老路很醜,但老路至少他們知道怎麼讓它「真的動起來」。作者最後才想通工程師到底要什麼:「We realized that our engineers didn't want a UI. They lived in their terminals and their IDEs.」工程師不要 UI,他們住在 terminal 和 IDE 裡;「They didn't want to click a button in a browser to create a service; they wanted to run a CLI command and have it *actually work*.」他們不想在瀏覽器點按鈕來建服務,他們想跑一行 CLI,然後它真的能用。
他們重寫時遵守的三條規則
診斷完之後,作者把這場失敗提煉成三條可以直接搬去自己組織用的規則。它們不是漂亮話,每一條都對應這次踩到的一個具體坑。下面把三條攤開——在桌機是一疊圖,在手機是可點開的卡片,點任一條看它對應哪個病灶。
點任一條規則看它對應哪個病灶 · 3 條規則
重寫時遵守的三條規則——每一條對應一個踩過的坑
三條規則不是抽象原則,而是把這次三個具體病灶(維護稅、缺席的引擎、手寫相依的漂移)各封成一條可執行的紀律
重寫的三條規則:鋪路不設收費站、自動化優先於 UI、state 靠發現不靠宣告。
照這三條規則重寫之後,作者給了一個結果數字:「Our 'time to first deployment' dropped from three weeks to 12 minutes.」首次部署的時間,從三週降到 12 分鐘。而他特別強調這個進步的來源,正好是整篇 postmortem 的骨:「That isn't because we built a beautiful dashboard. It's because we stopped trying to be Spotify, and started actually automating the miserable parts of our engineers' jobs.」這不是因為我們蓋了一個漂亮的 dashboard,而是因為我們不再想當 Spotify,開始真正去自動化工程師工作裡那些悲慘的部分。值得注意這裡沒有「所以我們廢掉了 Backstage」——原文的教訓不是「portal 沒用」,而是 portal 的價值排在引擎之後:先有能一行 CLI 跑通的自動化,玻璃才有東西可以映照。順序反了,玻璃就只是玻璃。
Next time:下次有人提議蓋一個內部開發者平台,先問一個能戳破 demo 的問題——把那層漂亮的 UI 整個拿掉,底下的 provisioning 能不能用一行 CLI 端到端跑完、而且真的成功?能,UI 才是錦上添花;不能,你蓋的就不是 platform,是一片更貴的玻璃,而八成的工程師會在三個月內繞過它、走回複製貼上 Dockerfile 的老路。