Tinder遷移至Kubernetes之路

a-01.jpeg

Why

大約兩年前,Tinder決定將其平臺移至Kubernetes。 Kubernetes爲咱們提供了一個經過不變的部署推進Tinder Engineering朝着容器化和少運維的方向發展的機遇。應用程序的構建,部署和基礎結構將定義爲代碼。前端

咱們還但願解決規模和穩定性方面的挑戰。當擴展變得相當重要時,咱們經常要等待幾分鐘才能等待新的EC2實例上線。容器在數秒而不是數分鐘內調度和服務流量的想法吸引了咱們。node

這並不容易。在2019年初的遷移過程當中,咱們在Kubernetes集羣中達到了臨界規模,而且因爲流量,集羣規模和DNS而開始遇到各類挑戰。咱們解決了遷移200個服務並運行Kubernetes集羣的有趣挑戰,該集羣的規模總計爲1,000個節點,15,000個Pod和48,000個正在運行的容器。數據庫

How

從2018年1月開始,咱們逐步完成了遷移工做的各個階段。咱們首先將全部服務容器化並將它們部署到一系列Kubernetes託管的各類環境中。從10月開始,咱們開始有條不紊地將全部舊版服務遷移到Kubernetes。次年3月,咱們完成了遷移,Tinder平臺如今僅在Kubernetes上運行。後端

爲Kubernetes構建鏡像

Kubernetes集羣中運行的微服務有30多個源代碼存儲庫。這些存儲庫中的代碼以不一樣的語言(例如,Node.js,Java,Scala,Go)編寫,而且具備針對同一語言的多個運行時環境。緩存

該構建系統旨在針對每一個微服務在徹底可定製的「構建上下文」上運行,該微服務一般由Dockerfile和一系列Shell命令組成。儘管它們的內容是徹底可定製的,可是這些構建上下文都是按照標準格式編寫的。構建上下文的標準化容許單個構建系統處理全部微服務。服務器

a-02.png

爲了在運行時環境之間實現最大的一致性,在開發和測試階段使用了相同的構建過程。當咱們須要設計一種方法來保證整個平臺上一致的構建環境時,這就提出了一個獨特的挑戰。結果,全部構建過程都在一個特殊的「 Builder」容器中執行。網絡

Builder容器的實現須要許多高級Docker技術。此Builder容器集成了訪問Tinder私有存儲庫所需的本地用戶ID和祕鑰(例如SSH密鑰,AWS憑據等)。它掛載包含源代碼的本地目錄,以天然方式存儲構建工件。這種方法提升了性能,由於它消除了在Builder容器和主機之間複製構建的工件的麻煩。下次無需進一步配置便可再次使用存儲的構建工件。架構

對於某些服務,咱們須要在Builder中建立另外一個容器,以使編譯時環境與運行時環境匹配(例如,安裝Node.js bcrypt庫會生成特定於平臺的二進制工件)。各個服務之間的編譯時要求可能有所不一樣,最終的Dockerfile是動態生成的。框架

Kubernetes集羣架構和遷移

Cluster 大小

咱們決定使用kube-aws在Amazon EC2實例上進行自動集羣配置。早期,咱們在一個通用節點池中運行全部內容。咱們迅速肯定了將工做負載分紅不一樣大小和類型的實例的需求,以便更好地利用資源。這樣作的理由是,與讓它們與大量單線程Pod共存相比,將更少的高線程Pod一塊兒運行能夠爲咱們帶來更多可預測的性能結果。運維

咱們決定:

  • m5.4xlarge for monitoring (Prometheus)
  • c5.4xlarge for Node.js workload (single-threaded workload)
  • c5.2xlarge for Java and Go (multi-threaded workload)
  • c5.4xlarge for the control plane (3 nodes)

遷移

從咱們的舊基礎架構遷移到Kubernetes的準備步驟之一是更改現有的服務到服務通訊,以指向在特定的虛擬私有云(VPC)子網中建立的新的Elastic Load Balancer(ELB)。該子網已與Kubernetes VPC對等。這使咱們能夠不依賴於服務依賴關係的特定順序而細粒度地遷移模塊。

這些端點是使用加權DNS記錄集創​​建的,該記錄集的CNAME指向每一個新的ELB。爲了進行切換,咱們添加了一條新記錄,指向權重爲0的新Kubernetes服務ELB。而後,將記錄上的生存時間(TTL)設置爲0。而後將新舊權重緩慢調整爲最終在新服務器上得到100%的流量。轉換完成後,將TTL設置爲更合理的值。

咱們的Java模塊使用低DNS TTL,但咱們的Node應用程序則不這樣作。咱們的一位工程師重寫了部分鏈接池代碼,以將其包裝在管理器中,該管理器每60秒刷新一次鏈接池。這對咱們來講很是有效,而性能沒有明顯降低。

網絡結構限制

在2019年1月8日凌晨,Tinder的平臺持續停機。爲響應當天凌晨平臺延遲的增長,在羣集上擴展了pod和節點數。這致使咱們全部節點上的ARP緩存耗盡。

有三個與ARP緩存相關的Linux值:

a-03.png

gc_thresh3是一個硬上限。若是您收到「neighbor table overflow」日誌記錄,則代表即便在ARP緩存的同步垃圾回收(GC)以後,也沒有足夠的空間來存儲 neighbor 記錄 。在這種狀況下,內核只會徹底丟棄數據包。

咱們使用Flannel做爲Kubernetes中的網絡結構。數據包經過VXLAN轉發。 VXLAN是第3層網絡上的第2層覆蓋方案。它使用MAC地址用戶數據報協議(MAC-UDP)封裝來提供擴展第2層網絡段的方法。物理數據中心網絡上的傳輸協議爲IP加UDP。

a-04.png

a-05.jpeg

每一個Kubernetes工做節點從一個較大的/9塊中分配本身的/24虛擬地址空間。對於每一個節點,這將致使1個路由表條目,1個ARP表條目(在flannel.1接口上)和1個轉發數據庫(FDB)條目。這些是在工做程序節點首次啓動時或在發現每一個新節點時添加的。

此外,節點到Pod(或Pod到Pod)的通訊最終會流經eth0接口(如上面的Flannel圖所示)。這將在ARP表中爲每一個相應的節點源和節點目的地添加一個附加條目。

在咱們的環境中,這種通訊很是廣泛。對於咱們的Kubernetes服務對象,將建立一個ELB,Kubernetes將向ELB註冊每一個節點。 ELB不支持Pod,而且所選節點可能不是數據包的最終目的地。這是由於當節點從ELB接收到數據包時,它將評估其iptables規則以獲取服務,並隨機選擇另外一個節點上的Pod。

中斷時,羣集中共有605個節點。因爲上述緣由,這將超出默認的gc_thresh3值。一旦發生這種狀況,不只會丟棄數據包,還會從ARP表中丟失整個Flannel/24虛擬地址空間。節點到Pod通信和DNS查找失敗。 (DNS託管在羣集中,這將在本文稍後詳細說明。)

要解決此問題,將提升gc_thresh1gc_thresh2gc_thresh3的值,而且必須從新啓動Flannel以從新註冊丟失的網絡。

預料以外的coredns的scale

爲了適應咱們的遷移,咱們大量利用DNS來促進流量整形和從舊版到Kubernetes的增量切換,以提供服務。咱們在關聯的Route53記錄集上設置相對較低的TTL值。當咱們在EC2實例上運行傳統基礎架構時,咱們的解析器配置指向Amazon的DNS。咱們認爲這是理所固然的,咱們的服務和Amazon服務(例如DynamoDB)的TTL相對較低的成本在很大程度上沒有引發注意。

隨着咱們爲Kubernetes加載愈來愈多的服務,咱們發現本身正在運行DNS服務,該服務每秒可響應250,000個請求。咱們在應用程序中遇到了間歇性且影響深遠的DNS查找超時。儘管進行了詳盡的調優工做,而且DNS提供商切換到了CoreDNS部署,該部署一次達到了1,000個Pod,消耗了120個內核,但仍是發生了這種狀況。

在研究其餘可能的緣由和解決方案時,咱們發現了一篇描述影響Linux數據包過濾框架netfilter的競爭條件的文章。咱們看到的DNS超時以及Flannel接口上增長的insert_failed計數器與本文的發現保持一致。

在源和目標網絡地址轉換(SNAT和DNAT)以及隨後插入conntrack表期間,會發生此問題。內部討論並由社區提出的一種解決方法是將DNS移到工做程序節點自己。在這種狀況下:

  • 不須要SNAT,由於流量在本地駐留在節點上。不須要經過eth0接口進行傳輸。
  • 不須要DNAT,由於目標IP在節點本地,而不是根據iptables規則隨機選擇的Pod。

咱們決定繼續採用這種方法。 CoreDNS在Kubernetes中做爲DaemonSet部署,咱們經過配置kubelet-cluster-dns命令標誌將節點的本地DNS服務器注入到每一個Pod的resolv.conf中。解決方法對於DNS超時有效。

可是,咱們仍然看到丟包,而且Flannel接口的insert_failed計數器增長。即便在上述解決方法以後,這種狀況仍將持續,由於咱們僅避免了DNS流量使用SNAT和/或DNAT。對於其餘類型的流量,競爭條件仍然會發生。幸運的是,咱們的大多數數據包都是TCP,而且在出現這種狀況時,數據包將被成功地從新傳輸。咱們仍在討論針對全部類型流量的長期解決方案。

使用Envoy實現更好的負載平衡

當咱們將後端服務遷移到Kubernetes時,咱們開始遭受pod負載不平衡的困擾。咱們發現因爲HTTP Keepalive,ELB鏈接卡在每一個滾動部署的第一個就緒Pod中,所以大多數流量流經一小部分可用Pod。咱們嘗試的首批緩解措施之一是針對最嚴重的違規者在新部署中使用100%MaxSurge。在某些較大型的部署中,這是微不足道的有效措施,而且不能長期持續。

咱們使用的另外一個緩解措施是人爲地增長關鍵服務上的資源請求,以使共置的Pod與其餘笨重的Pod相比具備更大的資源。從長遠來看,因爲資源浪費,這也不會成立,咱們的Node應用程序是單線程的,所以實際上限制在1個內核上。惟一明確的解決方案是利用更好的負載平衡。

咱們一直在內部評估Envoy。這爲咱們提供了以很是有限的方式部署它並得到直接收益的機會。 Envoy是爲大型面向服務的體系結構而設計的開源,高性能第7層代理。它可以實現高級的負載平衡技術,包括自動重試,斷路和全局速率限制。

咱們想到的配置是,在每一個Pod中有一個Envoy sidecar,該Pod具備一條路由和集羣以將流量導向本地容器端口。爲了最大程度地減小潛在的級聯並保持較小的爆炸半徑,咱們使用了一組前置代理Envoy Pod,在每一個可用區(AZ)中爲每種服務部署了一個。這是一種新的服務發現機制,該機制僅返回了每一個AZ中給定服務的Pod列表。

而後,服務前置Envoy將這種服務發現機制與一個上游羣集和路由一塊兒使用。咱們配置了合理的超時時間,提升了全部斷路器設置,而後進行了最小限度的重試配置,以幫助解決瞬態故障和平穩部署。咱們在每個前端Envoy服務中都使用TCP ELB。即便來自咱們的主要前端代理層的keepalive固定在某些Envoy容器上,它們也可以更好地處理負載,並配置爲經過minimum_request平衡到後端。

對於部署,咱們在應用程序和sidecar pod上都使用了preStop掛鉤。該鉤子調用sidecar健康監測失敗管理端點,並帶有少許sleep,以留出一些時間來容許機上鍊接完成並耗盡。

咱們之因此可以如此迅速地遷移的緣由之一是因爲咱們可以輕鬆地與常規Prometheus設置集成的豐富指標。這使咱們能夠準確地瞭解在迭代配置設置並減小流量時發生的狀況。

結果是當即而明顯的。咱們從最不平衡的服務開始,到如今爲止,它在集羣中最重要的十二個服務以前運行。今年,咱們計劃遷移到具備更多高級服務發現,斷路,異常檢測,速率限制和跟蹤功能的全服務網格。

下圖是切換Envoy先後服務CPU佔用變化:

a-06.png

a-07.png

a-08.png

最終結果

經過這些學習和其餘研究,咱們已經創建了強大的內部基礎架構團隊,對如何設計,部署和操做大型Kubernetes集羣很是熟悉。 Tinder的整個工程組織如今擁有如何在Kubernetes上容器化和部署其應用程序的知識和經驗。

在咱們的傳統基礎架構上,當須要進一步擴展時,咱們經常要等待幾分鐘才能等待新的EC2實例上線。如今,容器能夠在數秒而不是數分鐘內調度和服務流量。在單個EC2實例上調度多個容器還能夠提升水平密度。所以,咱們預計2019年EC2與上一年相比將節省大量成本。

它花了將近兩年的時間,但咱們於2019年3月完成了遷移。Tinder平臺僅在Kubernetes集羣上運行,該集羣包含200個服務,1,000個節點,15,000個Pod和48,000個運行中的容器。基礎架構再也不是咱們運營團隊的任務。取而代之的是,整個組織中的工程師共同承擔這項責任,並控制如何使用全部內容做爲代碼來構建和部署其應用程序。

相關文章
相關標籤/搜索