微軟開發部⾨ DevOps 經驗談 (一) - 從 Agile 邁向 DevOps

在 2013 年 11 月 13 日,我們正式發行了 Visual Studio 2013,以及全新的 Visual Studio Online 服務。但在服務發表之後,Visual Studio Online 卻發⽣了異常,造成七個小時服務中斷,這是因為在服務上線時,我們沒有預想到它會⾯臨如此大的流量衝擊,所以僅使⽤⼀個擴展單元(Scale Unit)來運行我們的服務,但在歐洲和美國的服務上線時,我們的系統遭遇了流量的頂峰,必須要同時提供服務給上百萬的使⽤者,系統不足以乘載這麼大量的使用者導致服務中斷。在技術上來說,這次的上線過程可說是⼀個⾎淋淋的慘痛經驗,就算我們擁有許多已經開發完成,但暫時透過功能開關(Feature flag)隱藏,等待著推出給使⽤者的新功能,卻無法監控服務與服務的網路層級問題,才會造成線上服務中斷,這些監控機制其實對我們來說才是最重要的,也因為這次的教訓,這些機制馬上被我們列入接下來的主要開發項目中。

圖一、 Visual Studio 2013 發表時, Visual Studio Online 因為過⼤流量造成的服務中斷,但在市場層⾯來說,這次的服務發表其實是非常成功的,因為 Visual Studio Online 的使⽤者每個月以成倍速度成⻑。(而且在接下來的一年中,使⽤服務的⼈數持續成⻑,已擁有超過兩百萬名使⽤者)

在新功能開發與線上維運中取得平衡

⼀開始,Visual Studio Online 只有使⽤芝加哥資料中心的⼀個擴展單元(Scale Unit)提供服務。我們當然知道服務如果要能夠穩定運行,必須具備橫向擴展 (Scale out)到多個地區的能力,也要讓每個地區的部署可以獨⽴進⾏不互相干擾,但總是有更多重要的新功能等著我們開發,讓我們不得不將這些事情往後排。⽽在經過了 Visual Studio Online 上線時慘痛的教訓之後,我們決定推遲新功能開發,優先專注在提供穩定的服務上,我們調整了 Visual Studio Online 部署更新流程,改為使⽤循序漸進部署(Canary Release)⽅式。不變部署到芝加哥資料中心的動作,⽽是在部署到芝加哥資料中心之前,增加⼀個部署階段,稱之為 SU0 (Scale Unit 0)。 在新的流程中,每⼀個 Sprint 開發完成之後,都會先部署到聖安東尼奧 (SU0),也是我們工作所在地。然後我們會在內部進⾏⼀⼩段時間的新功能試⽤,當功能都沒有問題時,才會進行下一階段部署到芝加哥(SU1)。而如果我們在 SU0 遇到問題時,我們就可以即時停⽌部署過程並回頭修正問題,不會影響到其他部署單位的服務。在這之後,我們也陸陸續續把其他 Azure 資料中心加⼊我們的部署流程,在 2014 年秋天時,我們加入阿姆斯特丹作為第五個部署階段(SU5),很快地我們將要增加澳洲作為下⼀個部署階段。我們主要是透過 Visual Studio Release Management 來處理整個部署流程,也透過 Release Management 來將產品部署到全世界。

圖⼆、在 2013 年 11 月, Visual Studio Online 由單一資料中⼼擴展為多個,這讓我們可以進⾏循序漸進部署( Canary Release ) 及提供服務到全球。

在下圖中可以清楚看到我們一直強調維持線上服務品質的結果。在 2013 年 11 ⽉時是 43 LSIs,⽽在六個⽉後,我們已經減低到了 7 LSIs,並且只有兩個是公開的服務,我們也持續在 DevOps 上學習成長,讓我們提供的服務更加穩定。

圖三、相較於 6 個⽉之前 (2013 年 11 月 ) 發⽣了 43 次 LSIs,Visual Studio Online 在 2014 年 4 ⽉只發⽣了 7 次 LSIs ,只有 2 個是發⽣在公開的功能中。

從 Agile 到 DevOps 的過程

微軟擁抱 Agile 超過七年,並汲取了 XP(eXtreme Programming)的精華,使⽤更為 SOLID 的⽅式改善了超過 15x 個技術債。我們透過 Scrum,搭配跨部門或是不同的產品線協同合作,來訓練每⼀個團隊成員,並專注在創造對於客戶更有價值的產品,這也讓我們在推出 Visual Studio 2010 時,在客⼾中獲得了空前好評。 在發表 VS2010 之後,我們知道是時候開始將 Team Foundation Server 轉換為 SaaS 服務了。我們決定使用 Azure 作為 SaaS 版本的 TFS(也就是現在的 Visual Studio Online)的服務運⾏平台,這也代表著我們必須開始把資源投注在 DevOps 上,也必須將我們過去使用 Agile 的經驗,延伸到 DevOps 之中,但這兩者⼜有什麼差別呢?在使用 Agile 時,⼤家往往習慣遵循經驗豐富的 Product Owner 規劃,有經驗的 Product Owner 也會準備好 backlog 讓⼤家進⾏開發。相較之下,DevOps 的精神可以說是從使用中學習。在 DevOps 的開發流程中,我們會直接開發具有實驗性質的新功能,發行到正式環境並收集使用者對於這些功能的使⽤狀況及反饋,以此為基礎來決定下⼀輪 backlog 的內容,同時也由於在 DevOps 中,發⾏新功能是沒有負擔⽽且可靠的,所以我們可以經常的進行新功能的部署,快速的開發新產品或是測試市場反應的新功能,並且很迅速的從第一線使用者的使⽤體驗中得到回饋,再將所獲得的資料作為規劃下一輪 backlog 的基礎。相較於在傳統的軟體開發流程,我們總是會假設性的進行產品⼤範圍的規劃與使⽤情境想像,再經過漫⻑的開發過程,最後才能投入到市場一次定生死, DevOps 講求的是直接根據市場反應來修正產品的功能,與其不斷假設使用者可能的需求以及行為,DevOps 會直接開發新功能,投⼊市場取得產品營運的狀況以及使⽤者的回饋,馬上根據這些回饋修改⾃己產品來更貼近使用者。這也讓產品開發的⾵險⼤大降低,縮短從開發到獲得市場反應的時程,並且可以即時修正產品方向,讓產品永遠是針對使⽤者的需求提供服務。

在圖四之中,DevOps 延伸了 Agile 的四個準則進⾏開發。

圖四、從 Agile 延伸到 DevOps 。

和大部分一開始就提供雲端服務的公司不同,我們並不是一開始就有提供 SaaS 版本的服務,大部分的客⼾原本都是購買軟體在⾃己公司內部使用(像是 Team Foundation Server,從最早的 2005 版本到目前最新的 2015 版本)。⽽在我們決定開發 Visual Studio Online 時,我們決定讓雲端和地端版本的 Visual Studio Online 使⽤同⼀份程式碼基礎進行開發,並優先進⾏雲端部分的功能開發。當⼯程師簽入程式碼時,會⾺上觸發持續整合的流程進行軟體建置、自動化測試及檢查。每三週 Sprint 結束之後,我們就會更新雲端版本的產品,在 Visual Studio Online 中提供新功能給使⽤者搶先使⽤,累積了 4~5 個 Sprint 後,我們就會發⾏地端版本 Team Foundation Server 季度的更新 Patch 檔。(參考圖五)不論在 Agile 或 DevOps 之中,持續整合(Continuous Integration)的機制都是十分重要的,我們可以設定持續整合伺服器在程式碼被簽入時第⼀時間被執 ⾏,並同時進⾏程式碼建置、⾃動化測試及產⽣程式碼品質報告,讓我們在第一時間確認被簽入的程式碼是否有潛在風險,也讓問題可以在第⼀時間被解決,這麼一來可以避免掉許多以往系統上線時可能發生的環境問題,甚⾄是產品部署問題,隨時確保版本控制系統(Version Control)中的程式碼是可靠的。並透過自動化讓程式碼維持水準時,也不會增加⼤家開發的負擔。

圖五、我們使用同一份程式碼來同時滿足 Visual Studio Online 和 Team Foundation Server ,每三週會將最新版本的 Release 發行到 Visual Studio Online ,每⼀季發⾏一次 TFS 的更新。當我們發行 TFS 的主要更新時,其實也是由⽬前 Visual Studio Online 功能⽽來的。

控制功能曝光對象

當你開始使用 DevOps 的流程經營一個服務時,你會開始習慣頻繁的發⾏新版本,就像我們在開發 Visual Studio Online 時,每隔三週(⼀個 Sprint)會發行⼀次最新的版本到線上服務。這也同時讓我們有很多將新功能曝光的機會,因此我們必須對於要能夠讓哪些⼈看到這些新功能進⾏管理,以下是⼀些⽐比較常⾒的問題:

  1. 如何開發跨 Sprint 的需求?
  2. 如果有功能必須調整時,我們如何透過實驗來獲得使用者的操作體驗以及反饋(例如 A/B Testing)?
  3. 如何在你準備好將產品推出到市場之前,隱藏線上的新功能不被使⽤者發現?

基於以上這些原因,我們開始使⽤用了⼀個叫做功能開關模式( Feature flag pattern ) 的方法來控制新功能要讓誰可以使用。功能開關其實是⼀種演算法,用來控制所有正式環境中的功能可以被誰看到。當團隊開始開發一個新功能時,你可以在功能開關服務( feature flag service ) 註冊這個功能,它預設是關閉不會被任何⼈人看到的,而當你的功能開發完成,準備好給某些⼈試用時,你可以在正式環境中針對特定使用者或群組開放你的功能。當你想要修改某個功能時,你只要關閉它的開關,不需要經過任何線上版本的發⾏或更動,就可以讓這個功能不會被任何⼈人看到或使用。 透過控制各種功能的開放與關閉,使用功能開關(feature flags)也提供了一個在正式環境進行測試的⽅法,我們通常會先對⾃⼰⼈開放新功能進⾏開發或測試,再來開放給先⾏使⽤者們試用,最後都沒有問題才會開放給其他⼤部分的使用者。透過功能開關,並搭配觀察新功能在線上運行的效能和使⽤者的使用狀況,可以讓我們確保新服務不會有意料之外的問題發⽣。 ⽽在某些⾓度來說,其實這也是循序漸進部署(Canary Release)另⼀⽅面的延伸,不會讓系統存在潛在風險時(例如在線上環境時某些被期待的功能無法順利使用),就一次⾯對大量的使用者,⽽造成⼤部分使用者不好的使用體驗,而是透過逐步開放的⽅式,讓⼤多數人的使用者體驗都是好的,若有問題也能即時停止開放,修正後再提供給使用者。

參考資料

  1. Canary Release - 這是一個可以讓軟體在正式環境發行時,盡可能降低⾵險的發⾏⽅式,它的作法是透過逐步把功能開放給小部分的使用者,如果穩定了再慢慢開放給更多使用者,而不是在產品發佈時就⼀次開 放給所有人使用,避免一瞬間面對無法處理的系統衝擊(例如過大的使⽤者流量),或是系統異常造成的使⽤者負面評價。
  2. SOLID - 物件導向程式設計的五⼤原則。