Categories
程式開發

Docker開發環境的滑坡謬誤


本文最初發佈於Test Double官方博客,經原作者授權由InfoQ中文站翻譯並分享。

最近,我構建了一個本地開發環境,將Docker用於一些關鍵的集成測試路徑。當我完成這項工作時,我意識到,我在開始這項工作之前沒有考慮到下面這些深遠的影響:

  • 要求開發人員的本地機器上有Docker和Docker-Compose;
  • 需要做相當多的配置環境才能真正可用;
  • 我編寫了shell腳本用於“緩解”這些配置問題,但卻混淆了系統的實際工作方式;
  • 我編寫的shell腳本最後看來也相當短視——它在某些環境下工作得很好,但是如果你在Windows環境下工作,就只能靠自己了;
  • 我花了大半天的時間來解決一些簡單的數據庫連接問題,結果發現我的數據庫容器沒有正確配置。

我在這項工作上投入的所有時間,結果確實使我的團隊受益了,並且最終幫助我們解決了集成測試中的一些挑戰。但對我來說,更有趣的是它所帶來的挑戰,更不用說我提交的Pull請求在它最終被合併之前的熱烈討論了。

更重要的是,這個環境最終服務於一個單一的目的——提供集成測試明確性,而不是像我最初希望的那樣提供一個整體的開發環境。最終結果是,我們將該環境從開發人員的機器中移走,並最終將其以容器的形式部署到雲提供商那裡,用於創建集成測試資源。

除了這個亮點,我的努力基本上是失敗的,尤其是考慮到我最初的動機。

我怎麼會走錯路呢,我所有的努力難道只換來了一個花哨的測試環境?我決定更深入地研究這個基於容器的開發環境問題,從那以後,我學到的東西極大地改變了我將來處理這個問題的方式。

容器現狀

大量的調查表明,Docker的使用將繼續增長,特別是隨著基礎設施的增長和日益複雜。DataDog 2018年6月的一項調查顯示,大約25%的公司使用Docker部署了某種形式的基礎設施。這些環境中有一半是通過某種方式精心策劃的,從2017年到2018年,部署規模增長了75%。根據這些消息來源,Docker“革命”正在如火如荼地進行,沒有放緩或停止的跡象。 (我仍然很好奇,75%的公司在他們的部署中使用的是什麼,跑題了哈。)

DataDog在2018年的調查報告中提到,使用最廣泛的Docker鏡像是“Nginx、Redis和Postgres”。我認為這是合理的,因為運行應用程序依賴項的容器似乎是容器化的第一步。Docker Compose為多容器應用程序提供了一個相對簡單的工具;它似乎也是一個很好的工具,讓開發人員可以為他們自己的環境運行特定的、底層的基礎設施。也就是說,你在項目中準備好一個docker-compose.yml文件,就可以開始了。

有25%的公司在生產環境中運行Docker,其中有多少公司使用Docker作為開發工具呢?2019年Stack Overflow的調查報告顯示,38.4%的受訪者在開發工作中使用容器,但大約一半的受訪者目前沒有使用任何容器技術。我想知道,是否有一種方法可以進一步了解為什麼開發人員沒有像我最初設想的那樣頻繁地使用Docker。我決定再深入一點,粗略地研究一下開發人員對Docker的看法。

許多開發人員討厭在自己的環境中使用Docker,並且有很好的理由——引入容器似乎會減慢開發人員和他們正在構建的環境之間的反饋循環。開發環境的容器化似乎也為操作人員創建了一個不必要的抽象,需要他們能夠直接深入代碼、運行時環境,甚至更底層的操作系統——所有這些都是在他們構建特性的時候進行的。

支持將Docker作為開發工具的人表示,這樣做還是有好處的。使用容器的開發環境還可以實現開發團隊之間的一致。如果每個人都為他們的數據庫、緩存或其他各種基礎設施使用容器,那麼準備編寫代碼就會像docker-compose up一樣簡單,完整的開發環境觸手可及。假設你的團隊願意在本地運行容器,那麼你就永遠不會在開發環境和生產環境之間造成差異。在運行brew upgrade時,不會再出現令人不快的意外——容器將始終與你的需求保持同步。

坦率地說,我對這兩類人都深有同感。作為一個一隻腳牢牢踏在運營上的開發人員,我認為運行容器有巨大的好處。然而,我不認為這些好處能夠清晰地映射到開發人員的工作流程裡。我相信這與在開發環境中使用Docker的體驗存在差異,因為Docker不是開發人員的工具。然而,我不認為這意味著開發團隊不應該考慮利用Docker來滿足他們自己的需求。不過我覺得,將Docker作為另一種操作工具可以幫助減輕在本地開發環境中運行Docker的痛苦。

容器是開發人員友好的抽象嗎?

按照Docker官方的定義,容器是:

一種標準的軟件單元,它將代碼及其所有依賴項打包,使得應用程序可以快速、可靠地從一個計算環境遷移到另一個計算環境上運行。

下面是一個更精闢些的定義:

容器是應用程序層的抽象,它將代碼和依賴項打包在一起。

聽起來不錯,對吧?是的,特別是當你試圖部署代碼時。換句話說,如果我的主要問題是讓我們的代碼在任何地方運行都不出意外,容器似乎是最好的抽象——但我不需要對我們運行的代碼有太多了解,相反,我需要可以預見我們如何運行它。對於操作人員而言,這是非常強大的。

作為開發人員,這種抽象可能會帶來一些麻煩。容器本身並不關心運行的是什麼。操作系統被有意地抽象掉,就像運行容器所必需的任何依賴項一樣。應用層本身和底層操作系統一樣短暫,訪問這些抽象層需要了解Docker希望以何種方式運行代碼。

關於容器的預期用途,我已經說了很多,但是我認為,說容器只是為運營團隊而準備的是錯誤的。我認為開發人員可以從容器化他們環境的某些部分中獲益。挑戰在於創建一個真正的開發人員友好的、基於容器的工作流。

基於Docker的開發環境的常見陷阱

最後,我選擇在自己的開發工作中大量使用Docker。這對我很有效,尤其是對我每天所做的工作。在Test Double,我主要從事DevOps和SRE,因此,在日常工作中使用容器是很有意義的。

當然,這可能不適合你的團隊。但是,如果你確實想在開發環境中探索Docker的使用,我想提醒你注意一些我親身經歷過的常見陷阱。

假設容器具有普惠性

在容器化開發環境中,最大的陷阱可能是假設在部署中獲得的好處與開發人員在本地體驗到的好處相同。

類似地,假設一個團隊希望廣泛地使用Docker,這種假設可能不適用於你的團隊的工作模式。如果開發人員與運維工作相對孤立,那麼假設他們可能希望每天都在本地使用容器是不安全的。但是,如果你在一個開發人員也做一些運維工作的團隊中工作,假設你在容器開發環境之外還提供了其他選項,這可能會使你的團隊受益。

在本地運行容器的實際情況是,它們會消耗大量資源。在一個典型的工作日,Docker僅在我的機器上就佔用了大約36GB的存儲空間。對於我這台特定品牌和型號的工作站來說,這不是一個驚人的系統使用量,但是我很容易就可以推斷出,當我在工作流程中包含更多的容器時,它會明顯增加。在我的CPU、內存和磁盤資源活動監視器中,Docker也始終排在前面。

更重要的是,雖然這可能不會對我的機器造成很大的拖累,但這並不意味著它對其他機器也不會,最終是否決定將這麼多資源分配給Docker應該取決於開發人員自己的個人偏好。也就是說,你的筆記本電腦不是服務器,它可能根本不需要按照服務器標準構建的容器資源。

但是,即使拋開系統資源不談,開發環境也不能很好地與運行代碼的任何系統相匹配。過去,這個差距是如此之大,我們常常不得不通過隧道技術連接到一個和生產服務器完全一樣的環境,如果出現問題,有些人(可悲的是,包括我自己在內)甚至被迫在線修改這些系統的代碼。

開發人員需要專注於編寫可維護、可靠且經過良好測試的代碼。我認為,在受限的環境中工作可以催生更好的代碼實踐和決策——你被迫依賴於編寫乾淨、可維護且可行的代碼,而不是希望通過配置服務器來處理性能瓶頸和低效的實現。

將技術棧中的部分內容裝入容器,其挑戰在於減少運行容器所需的認知開銷。開發人員在工作中需要構建和維護的上下文已經很多。如果本地Docker環境破壞了這個上下文,例如,確定一個容器是否正在運行,那麼從長遠來看,只會導致失望。

類似地,要求開發人員使用docker run命令跳轉到容器會增加上下文切換的認知開銷。這不僅與開發人員在自己的本地環境中進行開發時所構建的模式背道而馳,而且還需要額外掌握docker CLI,這就給開發人員實現目標帶來了負擔。當我不得不跳轉到Docker容器中調試某些東西時,我也不禁感到有些奇怪。這有點像我之前提到過的一件有違常理的事:通過隧道技術連接到生產服務器。

其他服務也是如此。如果你在一個完全依賴於微服務的團隊中工作,而其他團隊需要各種服務來構建自己的特性,那麼你在將這些鏡像作為容器提供時需要非常謹慎。在這些情況下,團隊利用自己的環境支撐服務,實際上可能比用容器混淆服務更有利。

具體來說,對於這些內部服務,可能審核它們的文檔比將其裝入容器更值得做。維護不善的文檔與容器相結合可能會造成相當的認知失調,從而導致放棄Docker環境。另外,我認為,我們所有人都應該更仔細地查看文檔,並頻繁地進行審計,而不是創建新的文檔。

我上面提到的調查結果指出,Nginx、Redis和Postgres在採用容器的團隊中非常受歡迎。它們為什麼如此受歡迎,這顯而易見。除非你的團隊正在編寫自己的RDBMS或Web服務器/負載平衡器,否則在你的技術棧中引入這樣的開源應用程序,將使你受益匪淺。

但是在容器化之前,運營團隊獲得的好處,可能無法與開發團隊在決定將它們包含到技術棧中時所希望獲得的好處相匹配。通過將這些依賴項封裝進容器,運營團隊可以從類似的加速器中獲益,這與開發人員不編寫自己的RDBMS所獲得的好處類似。

容器化的Postgres為部署、監控和擴展這個關鍵依賴項提供了大量的選項。它還減少了系統更新、升級和管理的開銷。對於運營團隊來說,這很好,但是它符合開發人員的需求嗎?

一句話,不符合。即使設置應用程序棧只是運行docker-compose up -d這麼簡單,它也不能很好地契合大多數開發人員在本地運行環境的心智模型。這兩種工作流程之間的差異是一種根本性的差異。

開發人員希望能夠深入研究他們需要的抽象,並要求團隊使用一個全新的工具,使用一種完全不同的方法來運行他們的本地環境,這是個大問題。具體來說,開發人員需要能夠運行數據庫遷移、跳轉到數據庫CLI並跟踪數據庫日誌。對於技術棧中的任何關鍵組件都要如此。

這並不是說沒有開發人員樂於在本地使用Docker來處理各種事情。但是我敢打賭,使用Docker的開發人員已經找到瞭如何讓它在他們的工作流程中無縫工作的方法,無論他們只是做了一個習慣性的改變,還是他們已經編寫了腳本使其更符合他們對自己環境的設想。

圍繞容器命令編寫新腳本

說到腳本,如果你正在構建一個像這樣的本地環境,並且你試圖減少開發人員在Docker上運行各種東西的開銷,那麼你最初的想法(就像我一樣)可能是將許多與容器的工作相關的重複任務編寫成腳本。

這種偏好不一定是因為誤導。畢竟,我們被教導要用腳本解決重複的任務,這樣我們才能專注於重複性更少的工作。但是要注意,不要編寫太多的腳本,特別是當你的團隊對容器領域還比較陌生時。如果不同團隊中的許多開發人員都採用這種方法,那麼你得考慮一下,為了讓他們在自己的本地環境中運行容器,你的團隊有多大的動機和興趣去維護一個定制的bash腳本。

我提到過,使用docker CLI會有一些開銷,但是如果有一組開發人員有意願使用容器,那麼更合理的做法是,給他們時間,對他們進行培訓,讓他們使用Docker自己的工具,而不是潛在地混淆Docker本身的內部工作。它確實是一個必須掌握的工具,但是,盡量不要圍繞這些命令編寫新的腳本,這樣可以減少故障診斷的認知壓力,並更廣泛地了解工具本身。

再強調下,這也是我最關心的,定制容器封裝器以及圍繞容器編寫新腳本增加了更多的代碼,因此而需要維護的東西也就更多。你的團隊要承擔這些開銷。如果你的團隊認為,這對他們的工作流程來說是一個淨收益,那就繼續。我們想要避免的陷阱是一個被拋棄的定制化shell腳本,對於那些試圖在你的環境中工作的人,它是一個潛在的時間陷阱,特別是在新人剛開始參與開發工作的過程中。

無選擇地容器化

如果你對遷移到容器化的開發環境感興趣,我認為你應該花相當多的時間為本地運行的系統構建簡單的替代方案。換句話說,不要認為在本地環境中使用Docker是一個孤注一擲的決定。記錄在標準本地部署中設置應用程序的步驟,並為那些對運行容器感興趣的人提供替代方法。讓你的團隊決定這個工作流程是否適合。

總之,在不了解開發環境取捨的情況下,無選擇地容器化將使開發團隊的工作變得困難。

結束語

設置開發環境沒有所謂正確的方法。雖然鼓勵團隊採用容器可能會帶來一些額外的好處,但是最終的目標是改進開發人員的工作流程。如果我們為了技術抽象而犧牲了編寫優秀代碼的能力,那麼我們就是在創造低效。

與任何事情一樣,最終的決策應該是由團隊選擇,不應該因為個人偏好而保留任何環境,無論是傳統的還是容器的。與你的團隊一起確定本地開發中的痛點,你就能取得良好的平衡。

原文鏈接:

The Slippery Slope of Docker Dev Environments