微服務動態路由實現:OpenResty+K8s

作者: xudawenfighting 

原文:https://blog.csdn.net/xudawenfighting/article/details/80125723

K8sService能夠提供很強大的功能,通過提供ClusterIP可以作爲Pod的對外訪問接口,並提供軟負載均衡。但是Service的ClusterIP地址只能在集羣內部訪問,如何讓集羣外部的用戶訪問Service呢,如果選擇了NodePort方式對外暴露服務,會造成用戶對端口敏感,端口的變化會對用戶造成不便,如何既能享受到Service的好處,又不讓用戶需要敏感內部實現呢?

大家好,今天向各位分享的主題是《微服務的路由實現: OpenResty+K8s》,介紹在新一代平臺中如何將OpenResty與Kubernetes結合使用的經驗,有些理解不對或者使用不對的地方還請大家指正。

本次分享分爲:

1.介紹OpenResty是什麼,以及通過一個hello world簡單瞭解下OpenResty

2.爲什麼需要OpenResty,主要解決哪些問題?

3.如何在K8s上部署OpenResty,如何使用ConfigMap,DaemonSet

4.新的選擇:Ingress

第一部分:OpenResty是什麼

圖片描述

OpenResty是一個基於 Nginx 與Lua的高性能 Web 平臺,其內部集成了大量精良的Lua庫、第三方模塊以及大多數的依賴項。用於方便地搭建能夠處理超高併發、擴展性極高的動態Web 應用、Web 服務和動態網關。主要有章亦春維護。

OpenResty通過匯聚各種設計精良的 Nginx 模塊(主要由OpenResty團隊自主開發),從而將 Nginx 有效地變成一個強大的通用 Web 應用平臺。右邊的列表中的組件被用於構建OpenResty。

圖片描述

先通過一個hello world的例子,來對OpenResty有個大概瞭解。

編寫一個nginx.conf,在nginx.conf使用了content_by_lua,主要是ngx.say(“

hello,world

http://xxx.xxx.xxx.xxx

通過這個例子大概可以看到OpenResty能做些什麼事,可以直接在nginx.conf中通過編寫Lua腳本,實現一些需要編寫代碼來完成的功能。後面我們會繼續介紹如何使用OpenResty。

第二部分:爲什麼要需要OpenResty

圖片描述

先來看看遇到的問題,大家都知道K8s Service能夠提供很強大的功能,通過提供ClusterIP可以作爲Pod的對外訪問接口,並提供軟負載均衡。

但是Service的ClusterIP地址只能在集羣內部訪問,如果是集羣外部的用戶要如何訪問Service呢?Kubernetes通過兩種方式來實現上述需求,一個是“NodePort”,另一個是“LoadBalancer”。

我們現在用的是NodePort的方式來使得Service可以被外部用戶訪問,這樣帶來的問題是:

1.外部訪問服務時需要帶NodePort

2.每次部署服務後,NodePort端口會改變

對於這2個問題,我們選擇的是使用Nginx做反向代理,給服務暴露的http的端口起一個端口名(如web),通過“http://web. svc01.tenant01.cluster01.devops.tp”來代替“http:// svc01.tenant01.cluster01.devops.tp:35089”去訪問服務,這樣對於用戶就屏蔽NodePort,多次部署後用戶也不需要知道新的NodePort。

圖片描述

前面介紹了遇到的問題:需要屏蔽NodePort,這裏介紹下爲什麼需要OpenResty,引入了OpenResty後如何做動態路由。

按照設想希望用戶通過輸入“http://web.svc01.tenant01.cluster01.devops.tp”地址來訪問服務,這樣就可以對用戶屏蔽NodePort。這樣就需要一層host轉換來實現動態路由,如果直接使用nginx,就需要動態的修改nginx.conf,這樣帶來的問題就是需要能夠動態的對nginx.conf做內容增減(添加/刪除服務時),以及需要同時修改多個nginx.conf。這個聽起來好像也不算方便。

使用OpenResty的話,可以和Redis結合。Redis裏保存了service的host和clusterip:port的映射,當用戶訪問“http://web.svc01.tenant01.cluster01.devops.tp”時請求會被OpenResty攔截,OpenResty根據請求的host到redis裏查詢對應的service的clusterip:port,根據clusterip:port再做一次upstream去訪問K8s Service。如果沒有找到host對應的value則會拋出相應的httpstatus code(500,400)。

圖片描述

前面介紹了OpenResty如何利用Redis中的數據做動態路由,那麼Redis中的數據是在何時寫進去的?

因爲使用了Reids,服務信息維護也相對簡單,只需要在服務有變更時去操作Redis的主結點進行信息的增/刪即可。

現在在新一代裏在以下幾個時機會去操作Redis中的數據:

• 服務創建:在服務創建後,如果服務的端口名帶有web,則會向Redis寫入服務的域名(key)以及對應的clusterip:port(value)。

• 服務銷燬:在服務刪除前,刪除Redis中相應的服務的域名。

• 租戶拉黑:查找租戶相應的所有環境[開發、測試、…..],把這些環境裏的所有服務在Redis裏的key加上“$_.”前綴。

• 租戶恢復:將租戶拉黑時修改的key,去掉“$_.”的前綴。

• 租戶銷燬:查找租戶相應的所有環境[開發、測試、…..],把這些環境裏的所有服務在Redis裏的key刪除。

第三部分:如何在K8s上部署OpenResty

圖片描述

前面介紹完大致思路,接下來就進入實際操作階段,第一步就是製作鏡像。

使用到的鏡像爲:

OpenResty1.9.15.1

Redis3.2.1

phpRedisAdminmaster

鏡像製作完成後提交到鏡像私庫供後續使用。

鏡像製作時需要考慮鏡像的配置可以通過配置文件,命令行參數和環境變量的組合配置來完成。這些配置應該從image內容中解耦,以此來保持容器化應用程序的便攜性。

所以我們在製作鏡像時將配置文件和啓動腳本可以從外部mount,這樣在調試時方便修改,不需要每次重新打鏡像。

圖片描述

這裏插播一下K8s ConfigMap,前面說了鏡像製作時需要配置和鏡像分離,那麼在真正使用時,就需要將配置注入容器,這時候使用的就是K8s ConfigMap特性。

ConfigMap提供了將配置數據注入容器的方式,同時保持容器是不知道Kubernetes的。ConfigMap可以被用來保存單個屬性,也可以用來保存整個配置文件或者JSON二進制大對象。

ConfigMap使用鍵-值對配置數據,這個數據可以在pods裏使用。data 一欄包括了配置數據。就如同看到的那樣,ConfigMap可以被用來保存單個屬性,也可以用來保存一個配置文件。

配置數據可以通過很多種方式在Pods裏被使用。ConfigMaps可以被用來:

• 設置環境變量的值

• 在容器裏設置命令行參數

• 在數據卷裏面創建config文件

在OpenResty部署中我們使用的是在數據卷裏面創建config文件

圖片描述

先創建一個configmap目錄,在configmap目錄裏有2個文件:

• redis.conf:保存的是reids的配置。

• run.sh:保存的是redis的啓動腳本,根據環境變量來確定按那種模式啓動redis。

通過使用”kubectl –namespace=euler-system createconfigmapsem-redis-configmap –from-file redis/configmap”可以將目錄創建爲ConfigMap。

ConfigMap裏會有2個key,一個是”redis.conf”,一個是”run.sh”。 value分別對應的是文件內容。

創建完成後可以執行” kubectl –namespace=euler-system get configmapsem-redis-configmap-o yaml”查看ConfigMap的內容。

在部署時可以通過volume將ConfigMap的內容變成文件掛載到容器內。

圖片描述

Redis是按主從方式部署,主結點上還會安裝phpRedisAdmin方便查看維護Redis的信息。從結點部署時需要指定需要關聯主結點的服務名和端口號。

使用時需要注意volumes和volumeMounts。無論主從在部署時,都需要將ConfigMap作爲一個volume,並且要將ConfigMap的key對應的內容保存成指定的文件名,如key=“redis.conf”,path=“redis.conf”表示將ConfigMap中key=”redis.conf”的內容保存到path=“redis.conf”的文件。

這個ConfigMap的volume會mount到容器內的一個目錄”/app/configmap”。因爲前面製作的鏡像就會在/app/configmap目錄下查找run.sh的啓動腳本,並且腳本在啓動時也使用到了/app/configmap/redis.conf的配置。這樣就能正常啓動。

這裏沒有使用Redis的sentinel,而是使用了K8s的RS來保證Redis主結點的可用性(Master停止後自動重啓)。

圖片描述

步驟和創建Redis的ConfigMap一樣,先創建一個configmap目錄,在configmap目錄裏有2個文件:

• nginx.conf:保存的是nginx的配置。

• run.sh:保存的是nginx的啓動腳本。

通過使用”kubectl –namespace=euler-system createconfigmapsem-openresty-configmap –from-file openresty/configmap”可以將目錄創建爲ConfigMap。

需要注意的是nginx.conf中的%resolver%,%redis_slave_svc_host%,%redis_slave_svc_port%

• resolver:是告訴nginx用哪個dns server去解析域名。在這裏使用的是K8s集羣裏的skydns的地址。

• redis_slave_svc_host:指定需要連接到redis的slave的host地址。

• redis_slave_svc_port:指定需要連接到redis的slave的port。

這3個變量在容器啓動時會由run.sh先進行變量替換,再啓動ngixn

圖片描述

這裏先介紹一下K8s Daemon Set,因爲OpenResty的部署用到了Daemon Set,而不是Deployment。Daemon Set可確保所有的節點運行一個Pod。有新的節點添加到羣集時,Pod會被被添加到其中。當節點從羣集中移除,Pod會被刪除。

DaemonSet的一些典型的用途是︰

• 在每個節點上運行羣集存儲守護進程,如 glusterd,ceph。

• 在每個節點上運行日誌收集守護進程,如 fluentd ,logstash。

• 在每個節點上運行監控守護進程,如collectd,gmond。

可以看到主要用途是在每個節點上裝一些守護進程,而我們的需求正好是在每個節點上都裝一個OpenResty,這樣經過前端DNS解析後可以轉到任意一個節點的OpenResty。

本來打算是在每個節點上通過systemd管理這些服務,然後發現不是很方便,而K8s正好提供了Daemon Set,就用了Daemon Set。

圖片描述

OpenResty是按DaemonSet方式部署,注意kind是DaemonSet,然後需要設置REDIS_HOST和REDIS_PORT,告訴OpenResty需要連接哪的Redis。

需要注意volumes和volumeMounts。將ConfigMap作爲一個volume,並且將ConfigMap的key對應的內容保存成指定的文件名,如key=“nginx.conf”,path=“nginx.conf”表示將ConfigMap中key=”nginx.conf”的內容保存到path=“ngixn.conf”的文件。

這個ConfigMap的volume會mount到容器內的一個目錄”/app/configmap”。

因爲前面製作的鏡像就會在/app/configmap目錄下查找run.sh的啓動腳本,並且腳本在啓動時也使用到了/app/configmap/nginx.conf的配置。這樣就能正常啓動。

圖片描述

到了這裏OpenResty就部署完成了,可以看到在整個K8s集羣中的每個monion節點上都部署了一個OpenResty的Pod,並在集羣裏部署了1個Redismaster Pod,2個Redis slave Pod。可以執行kubectl –namespace=euler-system get pod 查看namespace下的所有Pod。

當有K8s的Service被創建後,SEM會向Redis Master註冊服務域名和clusterip:port的鍵值對。

這樣用戶就可以通過如“http://web.svc01.tenant01.cluster01.devops.tp”的url訪問到服務了。

第四部分:新的選擇Ingress

圖片描述

說是新的選擇,不是指它是個新特性,是我自己知道的比較晚,原本以爲ingress只能用於GCE/GKE環境,經我司春龍、瀟男提醒,也可以用於本地環境。

一個Ingress(入口)是一系列允許訪問集羣服務的連接規則. 它可以爲服務配置一個外部訪問 url,負載均衡,SSL,以及提供基於名稱的虛擬主機等。用戶通過將入口資源發佈到 API 服務器請求入口。進入控制器(Ingress Controller)負責履行入口,通常與一個負載均衡器一起工作。如在GoogleGCE上的Http Load Balancer,或者本地的Nginx。

IngressController 的大概工作流程是監控Ingress的變化,並將變化寫Load Balancer的配置。在Nginx的Ingress Controller實現中會監聽Ingress、Service、Endpoints、Secret對象的變化,並將變化寫入nginx.conf文件,並重新加載nginx.conf。

上面的示例就是創建了一個Ingress,按照hostname和path可以將請求路由到K8s Service對應的Pod上。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章