距離2017年的見聞技術架構調整接近2年,隨着業務線的發展,見聞技術部的項目數量、項目架構類型、基礎設施規模、服務變動頻率都在不斷地增加,帶給SRE的挑戰是如何能更快地助力於開發人員更快更穩定地部署服務,保障線上服務的穩定。前端
咱們的後端開發團隊仍然以Golang爲主,不一樣業務線的技術選型不盡相同,同時存在Python,Java服務,這就須要SRE提供更易接入的微服務基礎組件,常見的方案就是爲每種語言提供適配的微服務基礎組件,但痛點是基礎組件更新維護的成本較高。git
爲了解決痛點,咱們將目光放到服務網格,它能利用基礎設施下沉解決多語言基礎庫依賴問題,不一樣的語言不須要再引入各類不一樣的服務發現、監控等依賴庫,只需簡單的配置並運行在給定的環境下,就能享有以上功能,同時網絡做爲最重要的通訊組件,能夠基於它實現不少複雜的功能,譬如根據不一樣可用區進行的智能路由、服務熔斷降級等。github
爲此,咱們調研了一些服務網格方案,包括Istio、Linkerd,基於咱們的當前的後端架構特色:golang
對比下來,Istio擁有更多活躍的開源貢獻者,迭代速度快,以及Istio架構可行性討論,咱們選擇Istio做爲實踐方案。docker
這張圖介紹了見聞典型的服務網格架構,左半圖介紹了一個用戶請求是如何處理,右半圖介紹運維繫統是如何監控服務,若無特殊說明,服務都是部署在騰訊雲託管Kubernetes。數據庫
Istio(1.0.0)
服務網格開源方案,支持與Kubernetes集成。apache
Service A所在Pod接收Ingress請求後端
Proxy進程發起對Service B所在Pod的請求api
Service B所在Pod接收請求緩存
Service B Proxy接收請求並路由到Service B所在進程
以上的流程能夠觀察到,服務之間通訊徹底依靠Proxy進程完成,Proxy進程接管同一個Pod中服務的出入流量,完成請求的路由。
經過架構圖以及以上流程,咱們拆分出如下關鍵組件,觀察其性能、可用性、拓展性。
Istio Ingress用來處理用戶入流量,使用Envoy實現,轉發性能高。掛載在負載均衡後,經過增長實例實現可拓展。
Istio Proxy以Sidecar形式隨應用一塊兒部署,增長2次流量轉發,存在性能損耗。
性能: 4核8G服務器,上面運行Proxy服務和API服務,API服務只返回ok字樣。(此測試只測試極限QPS)
單獨測試API服務的QPS在59k+,平均延時在1.68ms,CPU佔用4核。
經過代理訪問的QPS7k+,平均延時14.97ms,代理CPU佔用2核,API服務CPU佔用2核。
CPU消耗以及轉發消耗下降了QPS,增長了延時,經過增長機器核數並增長服務部署數量緩解該問題,通過測試環境測試,延時能夠接受。
可用性:基於Envoy,咱們認爲Envoy的可用性高於應用。依賴Pilot Discovery進行服務路由,可用性受Pilot Discovery影響。
拓展性:Sidecar形式,隨應用數拓展
Istio Policy須要在服務調用前訪問,是同步請求,會增長服務調用延時,經過拓展服務數量增長處理能力。屬於可選服務,見聞生產未使用該組件。
性能: 未測試
可用性:若開啓Policy,必須保證Policy高可用,不然正常服務將不可用
拓展性:增長實例數量進行拓展
性能: 從監控上觀察Report 5000qps,使用25核,響應時間p99在72ms。異步調用不影響應用的響應時間。
可用性:Telemetry不影響服務可用性
拓展性:增長實例數量進行拓展
性能: 服務發現組件1.0.5版本通過監控觀察,300個Service,1000個Pod,服務變動次數1天100次,平均CPU消耗在0.01核,內存佔用在1G之內。
可用性: 在服務更新時須要保證可用,不然新建立的Pod沒法獲取最新路由規則,對於已運行Pod因爲Proxy存在路由緩存不受Pilot Discovery關閉的影響。
拓展性:增長實例數量能夠增長處理量。
能夠看到各個組件的可用性、拓展性都有相應的策略達到保障,咱們認爲Istio是具備可實施性的。
Pilot Discovery負責Istio服務發現,支持在Kubernetes裏部署,它讀取K8S資源配置,並生成Proxy可用的路由表。如下面的Service A服務爲例,介紹Istio如何進行精細路由。
要知道若是在Istio訪問一個服務,必須得聲明K8S Service。
Istio經過K8S CRD拓展K8S已有的服務訪問能力,咱們列舉網絡相關經常使用的配置:
控制Istio Ingress的路由轉發及TLS證書綁定。
服務流量控制,實現如A/B測試、錯誤注入、服務保護等。
用於目標服務的版本管理,根據Pod的Label區分目標服務的版本,聯合VirtualService進行流量控制。
如下舉個例子介紹如何利用它們配置一樣大小流量到服務的不一樣版本,
# serviceA.yaml kind: Service apiVersion: v1 metadata: name: serviceA labels: app: serviceA spec: ports: - name: http-8080 protocol: TCP port: 8080 targetPort: 8080 selector: app: serviceA type: ClusterIP # virtualServiceA.yaml apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: serviceA spec: hosts: - serviceA http: - route: - destination: host: serviceA subset: v1 - route: - destination: host: serviceA subset: v2 --- # destinationRule apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: serviceA spec: host: serviceA subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2
以上實現了Istio服務調用serviceA時,會隨機地50%機率到serviceA的v1版本,50%機率到serviceA的v2版本。
能夠看到,VirtualService經過hosts關聯serviceA,在http區域有兩個route,分別是subset v1, subset v2,v1,v2依賴DestinationRule來定義,一樣用host來標註該DestinationRule控制哪一個host的訪問,以及經過pod label中version來劃分不一樣版本。
流量控制方面,Istio有至關豐富的功能支持,同時也帶來了至關的複雜度,建議用戶根據平常的使用頻率在後臺實現相應的前端控制檯,利用自動化來完成流量控制。
在K8S 1.9以後的版本,Istio利用K8S提供的MutatingAdmissionWebhook在K8S建立Pod前回調Istio提供的istio-sidecar-injector動態修改Pod配置,添加以Sidecar形式運行的Proxy。這裏開啓自動注入有兩個維度,一是namespace,namespace須要添加istio-injection : enabled
標籤,這樣實現該namespace下的全部Pod自動注入Proxy;二是deployment能夠設置annotation關閉自動注入。
若是K8S版本不夠,能夠利用命令行工具修改Deployment的配置。
Service A所在Pod至少運行Service A應用容器以及用於代理的Envoy容器,建立Pod時proxy-init命令負責獲取Pod監聽的端口和具體協議,以此初始化網絡,利用iptables將容器網絡出入流量都轉發到Proxy監聽的localhost端口。
若Service A的Pod聲明servicePort爲8080:HTTP,最終Proxy將會接收8080端口的Pod入流量和所有的Pod出流量。
Proxy基於Envoy,與Pilot Discovery鏈接,動態同步Kubernetes集羣中全部的服務信息:服務與Pod IP、端口之間的映射表,經過路由信息實現智能路由,從而使服務發現從業務代碼中剝離。
Proxy支持設置Zipkin URL,異步上報鏈路追蹤數據。
Proxy將屬性包上報給Telemetry服務,Telemetry根據用戶的配置生成指標數據並由Prometheus收集。
咱們目前的服務部署在騰訊雲託管Kubernetes,節點使用16核32G的網絡加強型機器,全部的後端服務都以Docker部署,K8S集羣外部署高可用ETCD支持集羣內服務發現,數據庫以MySQL、Cassandra、MongoDB爲主,消息隊列採用Kafka、NSQ。在應用Istio的過程當中,咱們對基礎庫進行了修改,刪減了Istio已提供的功能並完成了對Istio的適配。
見聞舊後端服務架構,全部Golang服務以打包成Docker鏡像,以"gRPC"協議通訊。
見聞Golang後端使用go-micro框架,一個支持多插件的Golang微服務框架,做者將組件分紅transport,server,client,registry,codec等,經過組合不一樣類型的組件很是靈活地配置微服務技術棧。對於有定製需求的微服務架構,是值得推薦的選擇。
通訊協議做爲服務互通的基石,Istio對gRPC和HTTP很是友好,根據協議Istio能解析HTTP頭中的信息,支持提取指標以供分析。go-micro只是利用HTTP和gRPC做爲通訊協議,可是協議的解析邏輯是協議無關的,因此能夠說它只是用了這些通訊協議的外殼,傳輸的報文內容是"micro方言",這就致使了Golang暴露的服務沒法被其它語言其它框架調用。爲了將協議能在多語言中徹底統一,也爲了更好地使用Istio的監控統計功能,這個時候咱們開始對go-micro的存留有一些新的思考,咱們是否還須要go-micro?通過近2年的生產實踐,咱們是否是能夠更精簡咱們的框架?
通過這些思考事後,咱們的決定是去go-micro框架,擁抱更輕量級的基礎框架,這個框架只要支持:
純原生便可
支持Istio的基礎功能,譬如一些HTTP header轉發等
咱們已經存在上百個Golang項目,避免改動Golang項目代碼,將改動放到基礎庫爲佳
go-micro經過定義自制protobuf插件的方式在stub代碼中集成框架功能,通過對邏輯的梳理,咱們決定複寫protobuf插件,生成兼容micro的stub代碼,經過對micro接口的向後兼容,開發人員不須要修改代碼,只須要在CI階段運行protoc即時生成新版代碼。
詳情可見再見,micro
右半圖描述運維人員如何利用運維後臺運維Kubernetes集羣,Istio的運維必須有自動化的工具來減小人工配置帶來的錯誤,見聞的舊運維後臺基於騰訊雲容器平臺暴露的開放API,在引入Istio後,功能依賴於更細節的label以及CRD(Custom Resource Definition),因而得依託更細粒度的Kubernetes API,新的後臺須要能完成基本的Kubernetes運維,並且結合Istio的實際進行平常更新,通過選型,見聞基於Kubernetes Dashboard二次開發了Istio部分的一些功能(APP部署、更新,Istio配置更新等),利用Istio Dashboard實現APP建立、部署接口,並由此重構原有的運維後臺。
最終,SRE提供兩個後臺,精細控制的Istio Dashboard;提供給開發人員平常更新使用的簡化版後臺。
平常最重要、最高頻的功能,服務版本變動。
服務建立包括對老服務的改造,一個K8S服務要通過一些配置上的更新才能成爲Istio APP。一個Kubernetes服務須要知足如下要求來得到Istio的功能支持:
這裏的服務端口聲明中name字段值須要以協議名爲起始值,譬如grpc、http、tcp、udp等,istio識別前綴,用於初始化Proxy,譬如grpc-svc
,http-svc
,不正確的端口命名會引發Proxy的異常行爲,以及監控服務沒法捕獲該協議下的指標。
每一個後端服務提供一個HTTP探活接口,這樣服務啓動時,不至於讓其它服務訪問到未就緒的狀態。
對於HTTP探活接口的定義包括Proxy以及APP是否初始化完成,見聞的實踐是在基礎鏡像中打入一個探活程序:
經過Proxy的配置同步時間與Pilot Discovery的配置更新時間對比,相同時認爲其就緒。
APP能夠在指定端口提供就緒API。
# serviceA.yaml kind: Service apiVersion: v1 metadata: name: serviceA namespace: default labels: app: serviceA spec: ports: - name: grpc protocol: TCP port: 10088 targetPort: 10088 selector: app: serviceA type: ClusterIP
Deployment資源要求
# deploymentA.yaml kind: Deployment apiVersion: extensions/v1beta1 metadata: name: serviceA-v1 labels: app: serviceA version: v1 spec: replicas: 1 selector: matchLabels: app: serviceA version: v1 template: metadata: labels: app: serviceA version: v1 spec: containers: - name: serviceA image: 'some-image' ports: - containerPort: 10088 protocol: TCP resources: requests: cpu: 1000m livenessProbe: httpGet: path: /health port: 54321 scheme: HTTP initialDelaySeconds: 1 timeoutSeconds: 2 periodSeconds: 5 successThreshold: 1 failureThreshold: 3 terminationMessagePath: /dev/termination-log terminationMessagePolicy: File imagePullPolicy: Always securityContext: privileged: false restartPolicy: Always terminationGracePeriodSeconds: 30 dnsPolicy: ClusterFirst
符合以上要求,服務能正確接入Istio系統並得到服務發現和監控的能力。
Istio提供流量控制,給運維帶來方便的A/B測試,用於根據指定規則遷移流量。
見聞的服務更新依靠Istio流量遷移功能,發佈服務的新版本,並將流量經過Istio規則遷移到新版本,實際細節以下:
更新流量控制將流量指向已有版本
如下實例利用VirtualService將ServiceA的服務流量所有指向已存在的v1版本
# virtualService apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: serviceA spec: hosts:
http: - route: - destination: host: serviceA subset: v1 --- # destinationRule apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: serviceA spec: host: serviceA subsets: - labels: version: v1 name: v1 ```
查找符合app label的deployment,運維人員基於該deployment建立v2版本的deployment,並向destinationRule中增長v2版本。
# destinationRule apiVersion: networking.istio.io/v1alpha3 kind: DestinationRule metadata: name: serviceA spec: host: serviceA subsets: - labels: version: v1 name: v1 - labels: version: v2 name: v2
如下實例利用VirtualService將ServiceA的服務流量所有指向v2版本
# virtualService apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: serviceA spec: hosts: - serviceA http: - route: - destination: host: serviceA subset: v2
使用Istio Dashboard來實現上述流程
爲何使用Istio Ingress做爲新的Ingress方案?
過去咱們使用騰訊雲託管的Kubernetes Ingress,爲了對Ingress流量控制而引入Istio Ingress。咱們以前提到Istio Ingress是基於Envoy,它讀取Istio控制的配置進行路由,與其它內部服務同樣方便地接入Istio全部功能。
除了VirtualService和DestinationRule,Istio定義了Gateway來控制實例支持的Host和證書。具體的流程是:
建立Deployment ingressgateway時,以ConfigMap的形式掛載Ingress須要的證書。
配置Ingress接收具體域名(如wallstreetcn.com)的流量,以及對應的TLS證書位置,這裏的證書路徑已經掛在到Ingress的Deployment上。如下是一個典型的Gateway配置。
apiVersion: networking.istio.io/v1alpha3 kind: Gateway metadata: name: wallstreetcn-com namespace: istio-system spec: selector: istio: ingressgateway servers: - hosts: - wallstreetcn.com port: name: http number: 80 protocol: HTTP - hosts: - wallstreetcn.com port: name: https number: 443 protocol: HTTPS tls: mode: SIMPLE privateKey: /etc/istio/ingressgateway-certs/tls.key serverCertificate: /etc/istio/ingressgateway-certs/tls.crt
配置完成後,再配合VirtualService的路由控制,控制Ingress的反向代理到default命名空間下的gateway服務80端口,以下所示:
apiVersion: networking.istio.io/v1alpha3 kind: VirtualService metadata: name: wallstreetcn-com namespace: istio-system spec: gateways: - wallstreetcn-com hosts: - wallstreetcn.com http: - route: - destination: host: gateway.default.svc.cluster.local port: number: 80
Istio支持Prometheus拉取集羣指標,並提供Grafana看板展現。這裏建議初期使用Istio自帶的Grafana看板配置,而且注意Kubernetes主機的類型劃分,Prometheus服務適合放在內存型機器。能夠與Dashboard集成,在發佈服務過程當中即時查看指標。
Istio自帶一些默認的Grafana面板,統計全部能夠被訪問的HTTP/gRPC服務的返回碼以及延時狀況。
對於返回碼,認爲5xx爲錯誤,並在面板上使用label_join((sum(rate(istio_requests_total{reporter="destination", response_code!~"5.*"}[1m])) by (destination_workload, destination_workload_namespace) / sum(rate(istio_requests_total{reporter="destination"}[1m])) by (destination_workload, destination_workload_namespace)), "destination_workload_var", ".", "destination_workload", "destination_workload_namespace")
計算服務錯誤率。
對於延時狀況採用histogram_quantile
獲取多維度p50、p90、p9五、p99的延時分佈。
以前提到Proxy由Envoy實現,Envoy支持設置Zipkin上報API,Proxy在收發請求時將鏈路指標上報到Zipkin,爲了實現鏈路追蹤,Proxy在流量轉發中解析協議中的HTTP或gRPC請求頭,找出其中的追蹤頭,組裝成指標。
因此應用端須要在收到調用方請求時解析出請求頭,並持續攜帶該請求頭向後傳遞。
因爲見聞在Ingress以後映射一個HTTP gateway,請求從Ingress轉發到HTTP gateway,再發送到後續的gRPC服務,因此HTTP gateway有段代碼生成gRPC請求頭。
import ( "github.com/labstack/echo" gmeta "google.golang.org/grpc/metadata" ) // Create a gRPC context from Echo. func NewContextFromEcho(ec echo.Context) context.Context { md := gmeta.MD{} for _, header := range []string{ "x-request-id", "x-b3-traceid", "x-b3-spanid", "x-b3-parentspanid", "x-b3-sampled", "x-b3-flags", "x-ot-span-context", } { md.Set(header, ec.Request().Header.Get(header)) } md.Set("x-b3-parentspanid", ec.Request().Header.Get("x-b3-spanid")) return gmeta.NewOutgoingContext(context.Background(), md) }
在後續的gRPC服務調用中使用該Context,至於gRPC服務之間的調用,咱們發現會自動將context傳遞到下一個服務,因此沒有作相似處理。
這裏追蹤的數據若是全量捕獲將會是很是大的,而且對於監控來講也沒必要要,因此能夠設置抽樣率,Istio提供ConfigMap中設置抽樣率,通常來講設置成1%便可。
在Istio實踐過程當中,有哪些須要注意的問題。
Istio對Kubernetes的API有很強的依賴,諸如流量控制(Kubernetes資源)、集羣監控(Prometheues經過Kubernetes服務發現查找Pod)、服務權限控制(Mixer Policy)。因此須要保障API server的高可用,咱們曾遇到Policy組件瘋狂請求Kubernetes API server使API server沒法服務,從而致使服務發現等服務沒法更新配置。
* 爲避免這種請求,建議使用者瞭解與API server直接通訊組件的原理,並儘可能減小直接通訊的組件數量,增長必要的Rate limit。 * 儘可能將與API server通訊的服務置於能夠隨時關閉的環境,這是考慮若是部署在同一Kubernetes集羣,若是API server掛掉,沒法關閉這些有問題的服務,致使死鎖(又想恢復API server,又要依靠API server關閉服務)
服務配置是Istio部署後的重頭戲,避免使用手動方式更改配置,使用代碼更新配置,將經常使用的幾個配置更新操做作到運維後臺,相信手動必定會犯錯的事實。
Pilot Discovery 1.0.0版本有很大的性能問題,1.0.4有很大的性能提高,但引入了一個新bug,因此請使用1.0.5及以上的版本,該版本在見聞的平均CPU負載從10核降到了0.5核,大大下降了Proxy同步配置的延時。
這個組件曾致使API server負載太高(很高的list pods請求),因此咱們暫時束之高閣,慎用。
在使用Proxy、Telemetry時,默認它們會打印訪問日誌,咱們選擇在生產上關閉該日誌。
時刻觀察Istio社區的最新版本,查看新版本各個組件的性能優化以及bug修復狀況,將Istio當作高度模塊化的系統,單獨升級某些組件。上面就提到咱們在Istio1.0的基礎上使用了1.0.5版本的Policy、Telemetry、Pilot Discovery等組件。
Istio依靠Proxy來幫助APP進行路由,考慮幾種狀況會出現意外的狀態:
* APP啓動先於Proxy,並開始調用其它服務,這時Proxy還沒有初始化完畢,APP調用失敗。 * Service B關閉時,調用者Service A的Proxy還沒有同步更新Service A關閉的狀態,向Service B發送請求,調用失敗。
第一種狀況要求APP有重試機制,能適當重試請求,避免啓動時的Proxy初始化與APP初始化的時差。
第二種狀況,一種是服務更新時,咱們使用新建新服務,再切流量;一種是服務異常退出,這種狀況是在客戶端重試機制。但願使用Istio的開發人員有更好的解決方案。
見聞Istio化已於去年10月份完成並上線,咱們的線上集羣中Istio和非Istio的APP混合部署,上千的Pod數量曾對不夠健壯的服務發現組件形成巨大的壓力,在這期間曾遇到Istio的一些驚喜,並不斷總結經驗,但願給以後使用Istio的同窗一些借鑑。以後的過程當中,SRE的目標依然是保障線上服務的健壯性。
優化APP部署流程,考慮自動部署的功能,經過服務指標自動完成灰度發佈和流量遷移。
Prometheus的高可用、可拓展方案的探索。