在 17 Media 擔任 SRE 的所見及所聞

17 Media Technology
17LIVE Tech Insight
15 min readJan 22, 2020
圖為當時 17 Media 工程團隊進行 migration 時的留影

前言

大家好,我是 17 Media 的 SRE Rick,進入 17 Media 已過兩年半,希望能藉這篇文章紀錄在這間公司的心得及成長。進入 17 Media 之前,工作內容以維運實體機房上的服務為主,包括設備採購、安裝、維護及故障排除,也撰寫 Perl/Bash 解決各種使用場景的 script。那時正是 Docker 嶄露頭角,DevOps 一詞也越來越多人討論的時間點,因此閒暇之餘,便會查看一些技術文章做些 lab 練習,同時追蹤一些產業新知,漸漸地對於 DevOps 相關的內容越感興趣,在 2016 年時,Google 發布了 SRE Book,內容詳述 Google 內部如何去維運龐大的伺服器集群,並提出實現 DevOps 精神的方法論。當時正巧看見 17 Media 正在招募 SRE 工程師,因此便嘗試投遞履歷,也很幸運地加入了 17 Media。

雖然稍微讀過 DevOps/SRE 的相關文章,大致上瞭解它們的意涵,但在未實際參與及投入的狀況下感覺並不深,直到加入後才慢慢體會其有趣之處及挑戰。與上一間公司的工作內容相比,有許多差異的地方,17 Media 是以手機 app 為主,backend 與 client 間透過 RESTful 的方式溝通,基本上是 web-based 的服務,從非 web-based 轉換到 web-based 這是第一點落差。而 backend 的架構又包括了三種主要資料庫及支撐 application 的各種服務,這些服務全部運行在 AWS 上(於 2017/5 時)完全的 Cloud Native,這是第二點落差。最後一點則是從較為傳統的 IT 公司轉到新創公司,從封閉到開放、從穩定到彈性、從緩慢到迅速,我認為這兩者的差異並無所謂優劣之分,而是面對不同的商業場景而發展出的不同文化差異之處。

簡述 backend 架構

在經歷過遷移(於後面的段落補充)之後,我們 backend 服務從 AWS ECS 搬到了 GCP GKE,目前的架構大致如下,

  • Route 53(DNS)
    我們的 DNS 基本上是託管在 AWS Route 53 上,並未隨著主要服務進行 migrate,考慮到 AWS Route 53 功能及穩定性後,並無 migrate 的必要。使用者在經過 AWS 的 GeoDNS 的判斷後,我們會依照不同的地區給予不同的 api endpoint,為的是服務的穩定性。
  • Wansu/Qiniu(streaming CDN)
    Wansu/Qiniu 是我們負責視訊流的第三方服務,app 發出 request 給 backend 後經過一些邏輯後會得到推/拉流網址,app 在得到推/拉流網址後便會向 Wansu/Qiniu 的 PoP(point of presence)進行連線,視訊流本身並不會經過我們的 backend。
  • Akamai(static file CDN)
    靜態網頁及圖檔則是透過 Akamai 與 AWS S3 提供給使用者。
  • PubNub(message CDN)
    PubNub 用於我們 app 內的訊息及一些事件廣播的服務。
  • Google Cloud(GCP, cloud provider)
    我們的主要服務都隨著遷移進入到 GCP,所有微服務基本上都運行在 GKE 上,搭配 etcd 做為我們的 config service,GCP PubSub 則作為我們的 job queue。
  • Fluentd & ElasticSearch & Kibana(log)
    Log system 則是 EFK 的架構,之所以用 Fluentd 而不是 Logstash 是因為在我們的使用場景上的效能考量。
  • MySQL(DB)
    MySQL 用作儲存所有跟金流相關資料的資料庫,目前的配置是透過 ProxySQL 去連線到 1 個 master 及 6 個 slave,並不是所有的 slave 都會拿來服務 application 的流量,其中有部分節點是作為報表系統所有,但也能在緊急情況時去分擔過大的流量。
  • MongoDB(DB)
    MongoDB 儲存 user 相關資料以及在一些 NoSQL 相較於 relation DB 更易用的場景,目前託管在 MongoDB Atlas,包含了 6 個 shard 及各 3 個 replica set。
  • Redis(DB)
    Redis 目前託管在 RedisLabs,主要作為 cache 使用,同時也儲存部分效能敏感的服務所使用到的資料來加速 api 的回應。
  • GCP Kubernetes Engine, GKE(application)
    最後則是 backend 服務主要運行的環境 — GKE。以 revprox 作為我們對外的入口,在更早的環境中我們是以 Node.js 運行後端服務,但考慮效能及後續維護等問題後我們逐步以 Go 淘汰 Node.js,也因此 revprox 負責判斷 request 並導到正確的地方,而在完成階段性任務後,revprox 以此為基礎上加入了 rate limiter 的功能,繼續為整體服務扮演單一入口及控制流量的角色。goapi 則是我們主要運行的微服務,主要以單體的方式運作,正逐步拆解成更獨立的微服務。如觀看直播的服務已分成另一個服務 golives,金流則放在 gotrade 上。以上就是我們服務基本上的架構,但隨著業務的成長也會時不時增減,其他的服務就暫不一一帶過。

難得的 migration 經驗

隨著公司規模逐漸擴大,業務面的需求也慢慢多樣化,我們於 2017 年四月開始討論將現有的服務從原本的 AWS 遷移到 Google Cloud Platform(GCP)的可能性,AWS 並不是不好用,主要是 SRE 在功能在整合起來是較為耗費心力,舉例來說,在管理 Docker 上, AWS 並不是那麼便利。所以在評估 migration 的方面,我們著重在四個要點

  1. 新的雲端可以提供更好的 Docker 支援
  2. 新的雲端可以降低現有的成本
  3. 易於整合的資料分析工具
  4. 主機離使用者更近的連線

第一是 GCP 上的 GKE 支援程度,Kubernetes 原本是 Google 內部使用的 Borg ,於 2015 年交給 Cloud Native Computing Foundation(CNCF)託管,也因此對於當時正在使用 AWS Elastic Container Service(ECS)的我們,GKE 成為我們所感興趣及躍躍欲試的新工具,而由於業務特性的關係,我們的尖峰流量與非尖峰流量落差極大,透過 Kubernetes 上便利的自動擴容機制:HPA 給了我們極大的幫助。HPA 幫助我們在使用者突然變多、產品使用量上升導致 CPU 使用率超過一定值,例如 60% 時,來擴展容器使服務持續運行,不會因為人過多而使服務品質下降。除此之外,由於先天上設計的差異,Kubernetes 在部署新版服務時從原本的 30~40 分鐘左右縮短至 6~10 分鐘,減少 rolling state(新舊版本交替之間)造成的不便,更重要的是 Kubernetes 對 canary release 有更好的支援。

第二點是由於 GCP 在雲端服務提供商的市場上屬於追趕者,因此提供了更好的價格,對於我們有相當程度的吸引力。

第三點,當時我們的數據分析工作流程已使用 GCP 上的一些服務,例如 BigQueryDataflow 所建立起來的,在跨雲的使用上並無遇到問題,但考量在能在同一個平台上整合就可以省去權限管控上相當多瑣碎的設定,也是一個評估的重點。

最後一點,當時只有 GCP 在台灣擁有機房,對於當時主要市場在台灣的我們,能夠提供更快速的網路反應,在以毫秒來計算 api 回應速度的世界裡,不跨海的連線基本上就省去了 100 毫秒的時間。我們會這樣重視服務提供商與區域之間連線所造成的延遲,可以參考這篇文章《Public Cloud Inter-region Network Latency as Heat-maps》。

當日凌晨 migration 時程表

由於大部分參與的成員都未經歷過這樣大型的搬遷服務,因此遇到的困難自然也不少,從最早 2017 年四月的發想,到 2017 年十月將預備環境搬進 GCP 的 GKE 中,最後於 2018 年五月完成正式環境的遷移,整整耗時了一年左右。我們並不是一整年都將時間花在遷移這項工作,從前期的會議及各項評估,到後來的撰寫執行計畫及演練,我們花費了相當多精神去達到各 module 盡可能的在 30 分鐘內完成,資料庫連線的中斷不超過 2 分鐘(最快的 Redis 僅在 10 秒內),而由於執行時間皆是在離峰時間,使用者基本上是完全沒有任何感覺,業務面影響的程度極小化。關於我們如何進行 migration,有興趣的讀者可以參考我們架構師 Roy 大大於 2018 DevOpsDays 上所講的 keynote,Cloud migration:Why ? How ? What happened ?以及當時簡報的共筆,其中我們評估了 plan A 一次性的 downtime 遷移及 plan B 多次的 online migration,plan A 乾淨簡單,一天就能搞定,但伴隨著的是需要有服務中斷時間,雖然在 17 Media 並沒有絕對不能使服務中斷,只要工程團隊提出合理的評估及計畫,管理階層都是可以被說服的。這點很好的體現了我們是尊重專業的公司,不會有外行領導到內行的現象,但身為工程師的尊嚴是,如果可以有更好的方案,何必選擇一個雖然省事但暴力的做法,況且對於 GCP 的使用體驗當時我們是不可知的,所謂的不可知,是指當我們真正把正式流量運作在 GCP 上,會發生什麼事呢?我們審慎樂觀的看待這件事,因此我們選了一條可能比較辛苦但卻相對有信心且能在有任何萬一發生時,對公司最不造成影響的 plan B,當時 Roy 給了一個比喻,今天當你在過河的時候不會一口氣就往對面衝過去,而是在每次踏下一步的時候都確認石頭是穩固的,才會踏出下一步,也因此 plan B 成為了我們最後的決定,並且我們也進行了十次左右的演習。

於遷移任務中,我負責的項目是 Redis、container(GKE)、DNS 的切換,也是整份遷移計畫的最後一步,由於我們將 Redis 做為 cache 服務使用,自然會需要將服務與資料庫靠得夠近以獲得較低的延時,曾經我們測試過在不同的雲之間讓 container 跟 Redis 連線,但測試出來的結果並不可用,因此我們便捨棄分次遷移的做法,改為要一次到位。至於 DNS 則是很直觀的,由於服務的 endpoint 更改,因此需要更動,但技術上的細節是,我們除了服務對外的 load balancer 在前面多加了一組存在於 GCP 上的 HAProxy 讓使用者會先進入到 HAProxy 後,透過 Google 的 backbone 連線到海外後再進入運行在 AWS 上的服務,之所以會有這組 HAProxy 存在的原因是,在 2017 年四月左右的中華電信海纜故障事件,在那段時間一到晚上對海外的連線便會有很高的延遲,而正巧我們的用戶的活動時間大多集中在晚上因此影響甚大,當時某位前輩便靈機一動,透過額外建立的 HAProxy 利用 Google 服務的特性避免跟大家擠在同一條海纜上,讓我們得以緩解此問題。

這三個 module(Redis, container, DNS)本身並不算複雜,尤其 Redis 是託管在 RedisLabs,也因此主要的資料搬遷會是由 RedisLabs 的人幫我們負責處理,但這同時是優點也是缺點,優點是我們並不需實際設立環境及操作,資料的還原也會是 RedisLabs 的人幫我們處理,但缺點是對我們而言,RedisLabs 就是個黑盒子,我們無法得知他即時運作的狀態或是能在意外發生時第一時間做出反應,為此,我們與 RedisLabs 前後開了數次會議,ticket 的長度也是前所未有的長,不斷地確認計畫細節及評估,最終我們資料不僅是備份到 GCP 上的 DB ,更要求在 AWS 上也新增備用 DB 來同時備份,以防一有狀況時還能有退路讓我們 rollback 回到原本的狀態,即便是部分資料的丟失,也能保證服務能持續運作。

在前置事項準備就緒後,我們終於開始進行整體演練,模擬整個服務的 migration,而演練本身是枯燥且令人感到挫折的,第一次的演練甚至在開頭 30 分鐘內就宣告終止,起初幾次演練就算順利完成也無法將時間壓在目標內,大家不斷來回修改擠出更多時間,相關 script 也逐漸從半人工轉化成全自動,協助執行遷移的 co-pilot 都僅需要確認狀態如預期後便能不斷按 Y 繼續下一步,終於能得到令人滿意的時間,而最後遷移也如同大家所知的順利完成。在這幾行的段落中我無法詳盡記錄下所有的細節,若有興趣也歡迎與我們聊聊。

克服的難點

前段所提的遷移是一次性的大任務,日常的 SRE 工作當然不會是~一言不合就 migration~,諸如每天(除假日前)的切版上 production,到回應各種外部需求,開權限、調整架構、與廠商溝通、將重複的事自動化,其中也包括了 on-call。而說到 on-call 必然就會談到 outage,最好的學習便是能不斷地對自己提出問題然後找出解答或者遇上不得不找出解答的問題,例如每次 on-call 遇到的 outage,透過每次追 outage 的過程,能夠更對該 module 更加深入了解:QPS 不等價、page fault 讓 MongoDB 非常忙碌、Redis 的 eviction 雖然是合理的現象但需要被控制、SSD 的 IOPS、資料庫類的服務並不適合用 container 來運作、每個監控 metrics 的因果關係、CPU 的使用率也包含了 waiting 的時間、GKE 的 DNS bug 少了結尾的 . 會讓 kube-dns 過度忙碌。有些限制或原理可能在求學時已經有個概念,但直到每次遇上狀況就又能建立更深的認知成為反射動作。

Hope is not a strategy,這是 Google 的 SRE Book 開章第一句話,我們不能去期待系統能自己運作的完美無缺,而 outage 也不會平白無故的解決,然而的確在某些情況下,在有限的知識範圍內我們對根因並不可知,但總歸來說,在 outage 是不可避免的前提下,我們要如何慢慢地建立一套有系統且可靠的制度及基礎建設,自然得依靠我們對 outage 更深一層的認識,而這樣認識的過程,則是我們每次產出的 postmortem。

Postmortem 的中文直譯是「驗屍報告」,其中最大的意涵是我們從這次事件學習到了什麼,我們撰寫 postmortem 是有其固定的格式,包括「摘要」、「事件時間表」、「根因分析」、「改進事項」,而在這之中「改進事項」是最為重要的一環,我們可以很淺薄的調整資源,增加成本來去避免這樣的問題,這只解了表面的問題;或是追根究底地找到程式運行時發生的 bug,甚至是改進底層用到的 package,根本的改進系統。在日常的工作內容中,上述兩者都是有可能發生的,端看當時的我們手上有多少資源可以運用,新創公司在做某些決定或發展時,往往時間比金錢來的珍貴,這是在過去公司所沒有體驗到的。

結語

如開頭所述,至今在 17 Media 作 SRE 已約兩年半,其實感到最有趣的是,這裡的新鮮事永無止境,當你開始對一項工作開始感到了無新意時,永遠有其他東西給你研究。過去花了比較多的時間在基礎設施的維護上,而最近為了實踐 SRE 的精神之一「減少工作重複事項」,我們團隊也以 backend 主要的語言 — Go 開發一些內部的工具,在減少雜事及學習新的語言保持競爭力的同時又能對系統運作的有更深的認識,這是在過去只作維護相關工作時很難接觸到的部分。

而伴隨著流量慢慢增加,服務也越來越龐大,我們也開始需要嘗試不同的架構去解決當前的問題。

例如,MySQL 內有不少過大的表,對整體的效能造成負擔,目前已經對它切出不同 partition,但當時如何切出 partition 是個棘手的問題,這邊我們比較了 Percona 的 pt-online-schema-change 以及 GitHub 的 gh-ost,兩套對 MySQL schema 修改的工具,但比較的結果都是會對線上流量造成影響,lock 時間過久以及 binlog 相關的錯誤,最終仍然選擇了以 script 去 migrate 資料,有時簡單反而最有效。

17 的 Redis 目前仍在 3.2.11,計畫會升級到 Redis 5,我們主要的 Redis 有兩座,用來作為 cache 使用的 RedisDB 在之前升級 Redis 4.0.2 時曾經因為 evict 量過大,同時 client buffer 的行為也在這版有不同行為,造成當 Redis evict 時便會嚴重拉高延時造成 outage,這次我們決定透過改善 backend code 的效能,減少不需要的 Redis 用量。

最後則是發生在前陣子的 GCP outage 事件,有興趣的讀者請參考從 GCP 故障看 17 Media 工程團隊的災難應變,目前我們規劃了 cross-cloud 的災備方案,能夠在必要時,將流量導到 AWS 的 EKS cluster,來讓整體的服務的容錯率更高。

以上,簡略的紀錄在 17 Media SRE 兩年半的所見及所聞,礙於篇幅其實還有些有趣的內容無法完整呈現,也歡迎加入有興趣的人一同討論或加入我們打造世界級的工程團隊。

Site Reliability Engineer(網站可靠性工程師):http://bit.ly/2TKsH7z
職缺列表:http://bit.ly/2qnPLwm

--

--

17 Media Technology
17LIVE Tech Insight

17 Meida Technology 希望能夠構築一個專屬於技術的社群,讓知識得以流轉