k8s系列09-服務(wù)發(fā)現(xiàn)與流量暴露

本文主要介紹了K8S集群中的服務(wù)發(fā)現(xiàn)和流量暴露機(jī)制焰望,包括K8S中的workload類型、service類型已亥、DNS解析原理以及四層服務(wù)暴露和七層服務(wù)暴露的規(guī)則熊赖。

1、云原生基礎(chǔ)概念

1.1 K8S架構(gòu)

下圖為K8S官方文檔中對K8S架構(gòu)設(shè)計的一個簡要介紹示意圖陷猫,這個架構(gòu)圖側(cè)重于從云廠商的角度展示了云廠商的API秫舌、K8S集群中控制面(Control Plane)工作節(jié)點(diǎn)(Node)之間的關(guān)系,但是將留給第三方實現(xiàn)的如CRI绣檬、CNI足陨、CSI等從中剝離出去了。

[圖片上傳失敗...(image-4becbc-1668327890312)]

在官方架構(gòu)圖的基礎(chǔ)上我們將CRI和CNI引入到架構(gòu)圖中娇未,可以得到下面的這個模型:

[圖片上傳失敗...(image-70cc98-1668327890312)]

  • kube-apiserver對外暴露了Kubernetes API墨缘。它是的 Kubernetes 前端控制層。它被設(shè)計為水平擴(kuò)展零抬,即通過部署更多實例來縮放镊讼。
  • etcd用于 Kubernetes 的后端存儲。etcd 負(fù)責(zé)保存Kubernetes Cluster的配置信息和各種資源的狀態(tài)信息平夜,始終為 Kubernetes 集群的 etcd 數(shù)據(jù)提供備份計劃蝶棋。當(dāng)數(shù)據(jù)發(fā)生變化時,etcd 會快速地通知Kubernetes相關(guān)組件忽妒。
  • kube-scheduler主要的工作就是調(diào)度新創(chuàng)建的Pod玩裙,當(dāng)集群中出現(xiàn)了新的Pod還沒有確定分配到哪一個Node節(jié)點(diǎn)的時候兼贸,kube-scheduler會根據(jù)各個節(jié)點(diǎn)的負(fù)載,以及應(yīng)用對高可用吃溅、性能溶诞、數(shù)據(jù)親和性的需求等各個方面進(jìn)行分析并將其分配到最合適的節(jié)點(diǎn)上。
  • kube-controller-manager運(yùn)行控制器决侈,它們是處理集群中常規(guī)任務(wù)的后臺線程螺垢。邏輯上,每個控制器是一個單獨(dú)的進(jìn)程赖歌,但為了降低復(fù)雜性枉圃,它們都被編譯成獨(dú)立的可執(zhí)行文件,并在單個進(jìn)程中運(yùn)行俏站。這些控制器包括:節(jié)點(diǎn)控制器(Node Controller)讯蒲、副本控制器(Replication Controller)、端點(diǎn)控制器(Endpoints Controller)肄扎、服務(wù)帳戶和令牌控制器(Service Account & Token Controllers)等
  • kube-proxy是集群中每個節(jié)點(diǎn)上運(yùn)行的網(wǎng)絡(luò)代理, kube-proxy通過維護(hù)主機(jī)上的網(wǎng)絡(luò)規(guī)則并執(zhí)行連接轉(zhuǎn)發(fā)赁酝,實現(xiàn)了Kubernetes服務(wù)抽象犯祠。service在邏輯上代表了后端的多個Pod,外界通過service訪問Pod酌呆。service接收到的請求就是通過kube-proxy轉(zhuǎn)發(fā)到Pod上的衡载,kube-proxy服務(wù)負(fù)責(zé)將訪問service的TCP/UDP數(shù)據(jù)流轉(zhuǎn)發(fā)到后端的容器。如果有多個副本隙袁,kube-proxy會實現(xiàn)負(fù)載均衡痰娱。
  • K8S的三大插件分別管控運(yùn)行時網(wǎng)絡(luò)存儲菩收,即Container Runtime Interface (CRI)梨睁、Container Network Interface (CNI)Container-Storage-Interface (CSI)。注意CRI和CNI是每個K8S集群都必須要部署的基礎(chǔ)組件娜饵,而CSI則不一定坡贺,一般來說只有在我們需要運(yùn)行有狀態(tài)服務(wù)的時候才需要用到CSI。

1.2 CNI基礎(chǔ)

K8S本身不實現(xiàn)集群內(nèi)的網(wǎng)絡(luò)模型箱舞,而是通過將其抽象出來提供了CNI接口給第三方實現(xiàn)遍坟,這樣一來節(jié)省了開發(fā)資源可以集中精力到K8S本身,二來可以利用開源社區(qū)的力量打造一整個豐富的生態(tài)晴股,CNI的一些實現(xiàn)細(xì)節(jié)和要求我們都可以在github上面找到愿伴,我們這里暫不深入解析。

重點(diǎn)來看一下K8S對集群內(nèi)的網(wǎng)絡(luò)模型定義:

  • K8S集群中任意兩個POD可以直接通信电湘,并且不需要進(jìn)行NAT
  • K8S集群中的每個Pod都必須要有自己的唯一隔节、獨(dú)立且可被訪問的IP(IP-per-Pod)

K8S并不關(guān)心各個CNI如何具體實現(xiàn)上述基礎(chǔ)規(guī)則万搔,只要最終的網(wǎng)絡(luò)模型符合標(biāo)準(zhǔn)即可。因此我們可以確保不論使用什么CNI官帘,K8S集群內(nèi)的Pod網(wǎng)絡(luò)都是一張巨大的平面網(wǎng)絡(luò)瞬雹,每個Pod在這張網(wǎng)絡(luò)中地位是平等的,這種設(shè)計對于集群內(nèi)的服務(wù)發(fā)現(xiàn)刽虹、負(fù)載均衡酗捌、服務(wù)遷移應(yīng)用配置等諸多場景都帶來了極大的便利涌哲。

1.3 Overlay networks

[圖片上傳失敗...(image-cf5405-1668327890312)]

Overlay網(wǎng)絡(luò)可以理解為建立在另一個網(wǎng)絡(luò)之上的虛擬網(wǎng)絡(luò)胖缤,這個概念在SDN里面里面經(jīng)常出現(xiàn)。和虛擬網(wǎng)卡需要依賴實際存在的物理網(wǎng)卡才能通信類似阀圾,Overlay網(wǎng)絡(luò)也不能憑空出現(xiàn)哪廓,它需要依賴的底層網(wǎng)絡(luò)通常被稱為Underlay網(wǎng)絡(luò)。Underlay 網(wǎng)絡(luò)是專門用來承載用戶 IP 流量的基礎(chǔ)架構(gòu)層初烘,它與 Overlay 網(wǎng)絡(luò)之間的關(guān)系有點(diǎn)類似物理機(jī)和虛擬機(jī)涡真。Underlay 網(wǎng)絡(luò)和物理機(jī)都是真正存在的實體,它們分別對應(yīng)著真實存在的網(wǎng)絡(luò)設(shè)備和計算設(shè)備肾筐,而 Overlay 網(wǎng)絡(luò)和虛擬機(jī)都是依托在下層實體使用軟件虛擬出來的層級哆料。

在使用了Overlay網(wǎng)絡(luò)的K8S集群中,我們可以把底層的Underlay網(wǎng)絡(luò)看作是K8S集群的Node節(jié)點(diǎn)所在的網(wǎng)絡(luò)吗铐,而上層的Overlay網(wǎng)絡(luò)一般用來處理Pod之間的網(wǎng)絡(luò)通信东亦。正常情況下,Underlay網(wǎng)絡(luò)和Overlay網(wǎng)絡(luò)之間互不干擾唬渗,兩者并不知道對方的網(wǎng)絡(luò)情況典阵。但是由于Overlay網(wǎng)絡(luò)是需要依賴Underlay網(wǎng)絡(luò)進(jìn)行傳輸數(shù)據(jù)的,因此在Overlay網(wǎng)絡(luò)的數(shù)據(jù)發(fā)送到Underlay網(wǎng)絡(luò)進(jìn)行傳輸?shù)臅r候镊逝,需要進(jìn)行數(shù)據(jù)包的封裝壮啊,將其變?yōu)閁nderlay網(wǎng)絡(luò)可以理解的數(shù)據(jù)包;反之當(dāng)數(shù)據(jù)從Underlay網(wǎng)絡(luò)傳送回Overlay網(wǎng)絡(luò)的時候需要進(jìn)行數(shù)據(jù)包的解封蹋半。在K8S的Overlay網(wǎng)絡(luò)實現(xiàn)中他巨,用于封裝的兩種常見網(wǎng)絡(luò)協(xié)議是 VXLAN 和 IP-in-IP。

使用Overlay網(wǎng)絡(luò)的主要優(yōu)點(diǎn)是:

  • 高度靈活性减江,Overlay網(wǎng)絡(luò)和底層硬件網(wǎng)絡(luò)設(shè)施分離染突,因此在跨機(jī)房、跨數(shù)據(jù)中心等場景有著傳統(tǒng)的Underlay網(wǎng)絡(luò)無法比擬的優(yōu)勢

使用Overlay網(wǎng)絡(luò)的主要缺點(diǎn)是:

  • 輕微的性能影響辈灼。封裝數(shù)據(jù)包的過程占用少量 CPU份企,數(shù)據(jù)包中用于編碼封裝(VXLAN 或 IP-in-IP 標(biāo)頭)所需的額外字節(jié)減少了可以發(fā)送的內(nèi)部數(shù)據(jù)包的最大大小,進(jìn)而可以意味著需要為相同數(shù)量的總數(shù)據(jù)發(fā)送更多數(shù)據(jù)包巡莹。
  • Pod IP 地址不可在集群外路由司志。

1.4 邊界網(wǎng)關(guān)協(xié)議(BGP)

BGP(Border Gateway Protocol/邊界網(wǎng)關(guān)協(xié)議)是一種基于標(biāo)準(zhǔn)的網(wǎng)絡(luò)協(xié)議甜紫,用于在網(wǎng)絡(luò)中共享路由。它是互聯(lián)網(wǎng)的基本組成部分之一骂远,具有出色的擴(kuò)展特性囚霸。在K8S中,BGP是出場率很高的一個路由協(xié)議激才,有很多相關(guān)的CNI或者是LoadBalancer都會使用BGP協(xié)議來實現(xiàn)諸如路由可達(dá)或者是ECMP等特性拓型。

目前對BGP協(xié)議支持最好、使用最廣泛的CNI應(yīng)該是Calico瘸恼,另外Cilium也有仍處于beta階段的BGP模式的支持劣挫。

1.5 可路由性(routability)

不同的K8S集群網(wǎng)絡(luò)的一個重要區(qū)別就是Pod的IP在K8S集群外的可路由性。

由于K8S集群內(nèi)的Pod之間必然是路由可達(dá)的东帅,因此這里探討的是集群外的服務(wù)到集群內(nèi)的Pod之間的路由可達(dá)压固。

[圖片上傳失敗...(image-55cd03-1668327890312)]

路由不可達(dá)

所謂路由不可達(dá),即K8S集群外的機(jī)器沒辦法和集群內(nèi)的Pod直接建立連接靠闭,集群外的服務(wù)器不知道如何將數(shù)據(jù)包路由到 Pod IP帐我。

這種情況下當(dāng)集群內(nèi)的Pod需要主動和集群外的服務(wù)建立連接的時候,會通過K8S進(jìn)行SNAT(Source Network Address Translation)阎毅。此時在集群外的服務(wù)器看到的連接對端IP是這個Pod所在的K8S集群節(jié)點(diǎn)的Node IP而不是Pod自身的IP焚刚,對于集群外的服務(wù)器發(fā)送返回數(shù)據(jù)的目的IP也永遠(yuǎn)都是這個K8S集群節(jié)點(diǎn)的Node IP,數(shù)據(jù)在Node IP上面再轉(zhuǎn)換發(fā)送回Pod扇调。這種情況下,集群外的服務(wù)器是無法得知Pod的IP抢肛,也無法直接獲取真實的請求IP狼钮。

反之則更復(fù)雜,因為集群外的服務(wù)器不知道如何將數(shù)據(jù)包路由到 Pod IP 捡絮,所以也沒辦法主動去請求這些Pod熬芜,此時只能通過K8S的services(NodePort、LoadBalancer福稳、Ingress)來將服務(wù)暴露到集群外涎拉,此時集群外的服務(wù)器的訪問對象是某個K8S的服務(wù),而不是具體的某個Pod的圆。

路由可達(dá)

如果 Pod IP 地址可在集群外部路由鼓拧,則 pod 可以在沒有 SNAT 的情況下直接連接到集群外的服務(wù)器,而集群外的服務(wù)器也可以直接連接到 pod越妈,而無需通過 通過K8S的services(NodePort季俩、LoadBalancer、Ingress)梅掠。

可在集群外路由的 Pod IP 地址的優(yōu)點(diǎn)是:

  • 減少網(wǎng)絡(luò)層級酌住、降低網(wǎng)絡(luò)層面架構(gòu)復(fù)雜性店归、降低使用人員的理解成本、維護(hù)成本和Debug成本等
  • 針對一些特殊的應(yīng)用場景(如集群外的機(jī)器需要直接和Pod進(jìn)行連接)酪我,在這種架構(gòu)中實現(xiàn)更加簡單

可在集群外路由的 Pod IP 地址的主要缺點(diǎn)是:

  • Pod IP 在集群外的網(wǎng)絡(luò)中也必須要唯一消痛。如果有多個K8S集群都需要實現(xiàn)集群外路由可達(dá),那么就需要給每個集群的Pod使用不同的CIDR都哭。這對內(nèi)部IP的使用規(guī)劃有一定的要求秩伞,并且當(dāng)集群足夠大的時候,還需要考慮內(nèi)網(wǎng)IP耗盡的可能质涛。

可路由性的決定因素

  • 如果集群使用的是overlay網(wǎng)絡(luò)稠歉,一般來說Pod IP無法在集群外部路由
  • 如果不使用overlay網(wǎng)絡(luò),則取決于部署的環(huán)境(云廠商/本地部署)汇陆、使用的CNI(Calico-BGP怒炸、Cilium-BGP等)以及實際網(wǎng)絡(luò)規(guī)劃等
  • 目前K8S網(wǎng)絡(luò)的集群外可路由性實現(xiàn)一般都是通過BGP協(xié)議

2、K8S服務(wù)暴露

正常情況下毡代,我們部署在K8S集群中的工作負(fù)載是需要對外提供服務(wù)的阅羹。這里的“對外”指的是對該負(fù)載以外的所有外部服務(wù)器提供服務(wù),而根據(jù)這些外部服務(wù)器是否位于K8S集群中教寂,我們可以分為K8S集群內(nèi)部流量和K8S集群外流量捏鱼。

2.1 Workload與SVC

開始之前,我們要明確幾個點(diǎn):

  • K8S中的工作負(fù)載(Workload)一般指的是集群中的真實工作任務(wù)酪耕。比如無狀態(tài)服務(wù)常用的deployments导梆、有狀態(tài)服務(wù)常用的statefulsets、一些特殊用途的daemonsets迂烁、定時服務(wù)常用的cronjobs等看尼,這些都屬于是K8S中的工作負(fù)載(Workload)。
  • K8S中的service(SVC)更多傾向于是一種規(guī)則集合盟步,將符合某些特定條件的pod全部歸屬到一個Service中藏斩,然后組成一個特定的Service。注意這些pod是可以屬于不同的工作負(fù)載(Workload)却盘。
  • K8S中的每個SVC都會有一個對應(yīng)的域名狰域,域名的組成格式為$service_name.$namespace_name.svc.$cluster_name,一般來說k8s集群中的$cluster_name就是cluster.local黄橘,這個字段一般在集群創(chuàng)建的時候就會設(shè)定好兆览,之后想要再更改會十分麻煩。

綜上所訴旬陡,我們可以得出以下結(jié)論:

  1. K8S中的工作負(fù)載(Workload)和服務(wù)暴露(service)是相互隔離開來的拓颓,分別交給不同的api來實現(xiàn)
  2. 每個SVC都會有一個服務(wù)名+命名空間+svc+集群名/$service_name.$namespace_name.svc.$cluster_name的域名可以用來訪問(如app.namespace.svc.cluster.local
  3. K8S集群內(nèi)的服務(wù)之間訪問主要就是通過這個域名來實現(xiàn)的

2.2 SVC的種類

一般來說svc可以分為四類:HeadlessClusterIP描孟、NodePort驶睦、LoadBalancer砰左。四者之間的關(guān)系并非是完全互斥,具體如下:

[圖片上傳失敗...(image-55de8f-1668327890312)]

Headless Services

  • Headless類型服務(wù)和其他三者完全互斥场航,可以通過指定 Cluster IP(spec.clusterIP)的值為 "None" 來創(chuàng)建 Headless Service缠导;
  • 此時該服務(wù)的域名解析的結(jié)果就是這個服務(wù)關(guān)聯(lián)的所有Pod IP,使用該域名訪問的時候請求會直接到達(dá)pod溉痢;
  • 這時的負(fù)載均衡策略相當(dāng)于是僅使用了DNS解析做負(fù)載均衡僻造,并沒有使用k8s內(nèi)置的kube-proxy進(jìn)行負(fù)載均衡;
  • Headless類型服務(wù)不會創(chuàng)建對應(yīng)域名所屬的SRV記錄孩饼;

[圖片上傳失敗...(image-b2294d-1668327890312)]

Headless Services這種方式優(yōu)點(diǎn)在于足夠簡單髓削、請求的鏈路短,但是缺點(diǎn)也很明顯镀娶,就是DNS的緩存問題帶來的不可控立膛。很多程序查詢DNS并不會參考規(guī)范的TTL值,要么頻繁的查詢給DNS服務(wù)器帶來巨大的壓力梯码,要么查詢之后一直緩存導(dǎo)致服務(wù)變更了還在請求舊的IP宝泵。

ClusterIP Services

在 Kubernetes 集群中,每個 Node 運(yùn)行一個 kube-proxy 進(jìn)程轩娶。 kube-proxy 負(fù)責(zé)為 Service 實現(xiàn)了一種 VIP(虛擬 IP)的形式儿奶,一般稱之為ClusterIP Services

[圖片上傳失敗...(image-f68016-1668327890312)]

  • ClusterIP是最常用的服務(wù)類型鳄抒,也是默認(rèn)的服務(wù)類型闯捎,同時也是NodePortLoadBalancer這兩個服務(wù)的基礎(chǔ)许溅;
  • 對于ClusterIP類型的服務(wù)隙券,K8S會給該服務(wù)分配一個稱為CLUSTER-IP的VIP;
  • ClusterIP是單獨(dú)的IP網(wǎng)段闹司,區(qū)別于K8S的宿主機(jī)節(jié)點(diǎn)IP網(wǎng)段和Pod IP網(wǎng)段,也是在集群初始化的時候定義的沐飘;
  • ClusterIP可以在每一臺k8s宿主機(jī)節(jié)點(diǎn)上面的kube-ipvs0網(wǎng)卡里面看到游桩;
  • ClusterIP類型的服務(wù)的域名解析的結(jié)果就是這個VIP,請求會先經(jīng)過VIP耐朴,再由kube-proxy分發(fā)到各個pod上面借卧;
  • 如果k8s使用了ipvs,可以在K8S宿主機(jī)節(jié)點(diǎn)上面使用ipvsadm命令來查看這些負(fù)載均衡的轉(zhuǎn)發(fā)規(guī)則筛峭;
  • ClusterIP類型服務(wù)還會創(chuàng)建對應(yīng)域名所屬的SRV記錄铐刘,SRV記錄中的端口為ClusterIP的端口

ClusterIP Services這種方式的優(yōu)點(diǎn)是有VIP位于Pod前面,可以有效避免前面提及的直接DNS解析帶來的各類問題影晓;缺點(diǎn)也很明顯镰吵,當(dāng)請求量大的時候檩禾,kube-proxy組件的處理性能會首先成為整個請求鏈路的瓶頸。

NodePort Service

  • 從NodePort開始疤祭,服務(wù)就不僅局限于在K8S集群內(nèi)暴露盼产,開始可以對集群外提供服務(wù)
  • NodePort類型會從K8S的宿主機(jī)節(jié)點(diǎn)上面挑選一個端口分配給某個服務(wù)(默認(rèn)范圍是30000-32767),用戶可以通過請求任意一個K8S節(jié)點(diǎn)IP的該指定端口來訪問這個服務(wù)
  • NodePort服務(wù)域名解析的解析結(jié)果是一個CLUSTER-IP勺馆,在集群內(nèi)部請求的負(fù)載均衡邏輯和實現(xiàn)與ClusterIP Service是一致的
  • NodePort服務(wù)的請求路徑是從K8S節(jié)點(diǎn)IP直接到Pod戏售,并不會經(jīng)過ClusterIP,但是這個轉(zhuǎn)發(fā)邏輯依舊是由kube-proxy實現(xiàn)

[圖片上傳失敗...(image-5f5c96-1668327890312)]

NodePort Service這種方式的優(yōu)點(diǎn)是非常簡單的就能把服務(wù)通過K8S自帶的功能暴露到集群外部草穆;缺點(diǎn)也很明顯:NodePort本身的端口限制(數(shù)量和選擇范圍都有限)以及請求量大時的kube-proxy組件的性能瓶頸問題灌灾。

LoadBalancer Service

  • LoadBalancer服務(wù)類型是K8S對集群外服務(wù)暴露的最高級最優(yōu)雅的方式,同時也是門檻最高的方式悲柱;
  • LoadBalancer服務(wù)類型需要K8S集群支持一個云原生的LoadBalancer锋喜,這部分功能K8S本身沒有實現(xiàn),而是將其交給云廠商/第三方诗祸,因此對于云環(huán)境的K8S集群可以直接使用云廠商提供的LoadBalancer跑芳,當(dāng)然也有一些開源的云原生LoadBalancer,如MetalLB直颅、OpenELB博个、PureLB等;
  • LoadBalancer服務(wù)域名解析的解析結(jié)果是一個CLUSTER-IP功偿;
  • LoadBalancer服務(wù)同時會分配一個EXTERNAL-IP盆佣,集群外的機(jī)器可以通過這個EXTERNAL-IP來訪問服務(wù);
  • LoadBalancer服務(wù)默認(rèn)情況下會同時創(chuàng)建NodePort械荷,也就是說一個LoadBalancer類型的服務(wù)同時是一個NodePort服務(wù)共耍,同時也是一個clusterIP服務(wù);一些云原生LoadBalancer可以通過指定allocateLoadBalancerNodePorts: false來拒絕創(chuàng)建NodePort服務(wù)吨瞎;

我們還是借用OpenELB官網(wǎng)的圖來解釋一下這個流程痹兜,注意這里為BGP模式。

[圖片上傳失敗...(image-2053f5-1668327890312)]

LoadBalancer Service這種方式的優(yōu)點(diǎn)是方便颤诀、高效字旭、適用場景廣泛,幾乎可以覆蓋所有對外的服務(wù)暴露崖叫;缺點(diǎn)則是成熟可用的云原生LoadBalancer選擇不多遗淳,實現(xiàn)門檻較高。

2.3 Port概念辨析

在我們進(jìn)行SVC和Workload部署配置的時候心傀,經(jīng)常會碰到各種名字中帶有Port的配置項屈暗,這也是K8S中容易讓人混淆的幾個概念之一,這里主要介紹一下NodePortPort养叛、targetPortcontainerPort這個四個概念种呐。四者的關(guān)系我們可以通過下面這種圖比較清晰地區(qū)分出來:

[圖片上傳失敗...(image-619a86-1668327890312)]

  • nodePort: 只存在于Loadbalancer服務(wù)和NodePort服務(wù)中,用于指定K8S集群的宿主機(jī)節(jié)點(diǎn)的端口一铅,默認(rèn)范圍是30000-32767陕贮,K8S集群外部可以通過NodeIP:nodePort 來訪問某個service;
  • port: 只作用于CLUSTER-IPEXTERNAL-IP潘飘,也就是對Loadbalancer服務(wù)肮之、NodePort服務(wù)和ClusterIP服務(wù)均有作用,K8S集群內(nèi)部可以通過CLUSTER-IP:port來訪問卜录,K8S集群外部可以通過EXTERNAL-IP:port來訪問戈擒;
  • targetPort: Pod的外部訪問端口,port和nodePort的流量會參照對應(yīng)的ipvs規(guī)則轉(zhuǎn)發(fā)到Pod上面的這個端口艰毒,也就是說數(shù)據(jù)的轉(zhuǎn)發(fā)路徑是NodeIP:nodePort -> PodIP:targetPort筐高、CLUSTER-IP:port -> PodIP:targetPortEXTERNAL-IP:port -> PodIP:targetPort
  • containerPort:和其余三個概念不屬于同一個維度丑瞧,containerPort主要是在工作負(fù)載(Workload)中配置柑土,其余三者均是在service中配置。containerPort主要作用在Pod內(nèi)部的container绊汹,用來告知K8S這個container內(nèi)部提供服務(wù)的端口稽屏,因此理論上containerPort應(yīng)該要和container內(nèi)部實際監(jiān)聽的端口一致,這樣才能確保服務(wù)正常西乖;但是實際上由于各個CNI的實現(xiàn)不通以及K8S配置的網(wǎng)絡(luò)策略差異狐榔,containerPort的作用并不明顯,很多時候配置錯誤或者是不配置也能正常工作获雕;

綜上所述薄腻,我們可以得知四者的主要區(qū)別,那么我們在實際使用的時候届案,最好就需要確保targetPort庵楷、containerPort和Pod里面運(yùn)行程序?qū)嶋H監(jiān)聽的端口三者保持一致,即可確保請求的數(shù)據(jù)轉(zhuǎn)發(fā)鏈路正常楣颠。

3嫁乘、K8S中的DNS服務(wù)

眾所周知,在K8S中球碉,IP是隨時會發(fā)生變化的,變化最頻繁的就是Pod IP仓蛆,Cluster IP也并不是一定不會發(fā)生變化睁冬,EXTERNAL-IP雖然可以手動指定靜態(tài)IP保持不變,但是主要面向的是集群外部的服務(wù);因此在K8S集群中豆拨,最好的服務(wù)之間相互訪問的方式就是通過域名直奋。

3.1 DNS創(chuàng)建規(guī)則

在K8S集群中,Kubernetes 為 Service 和 Pod 創(chuàng)建 DNS 記錄施禾。

前面我們介紹了K8S中的每個SVC都會有一個對應(yīng)的域名脚线,域名的組成格式為$service_name.$namespace_name.svc.$cluster_name,同時也會給這個SVC下的所有Pod都創(chuàng)建一個$pod_name.$service_name.$namespace_name.svc.$cluster_name的這種域名弥搞,這個域名的解析結(jié)果就是Pod IP邮绿。

Pod域名有兩個比較明顯的特征:

  • 一是域名的組成比較特殊,因為域名中使用了Pod的名稱攀例,而pod名稱在K8S中是會發(fā)生變化的(例如在服務(wù)更新或者滾動重啟時)船逮,同時由于默認(rèn)情況下Pod的命名是沒有太明顯的規(guī)律(大部分名字中會包含一串隨機(jī)UUID)
  • 二是域名的解析結(jié)果特殊,相較于集群內(nèi)的其他類型域名粤铭,Pod域名的解析是可以精確到特定的某個Pod挖胃,因此一些特殊的需要點(diǎn)對點(diǎn)通信的服務(wù)可以使用這類Pod域名

3.2 DNS策略配置

DNS 策略可以逐個 Pod 來設(shè)定。目前 Kubernetes 支持以下特定 Pod 的 DNS 策略梆惯。 這些策略可以在 Pod 規(guī)約中的 dnsPolicy 字段設(shè)置:

  • Default: Pod 從運(yùn)行所在的K8S宿主機(jī)節(jié)點(diǎn)繼承域名解析配置酱鸭;
  • ClusterFirst: 不指定任何dnsPolicy配置情況下的默認(rèn)選項,所有查詢的域名都會根據(jù)生成的集群的K8S域名等信息生成的 /etc/resolv.conf 配置進(jìn)行解析和轉(zhuǎn)發(fā)到集群內(nèi)部的DNS服務(wù)進(jìn)行解析垛吗;
  • ClusterFirstWithHostNet:主要用于以 hostNetwork 方式運(yùn)行的 Pod凹髓,如果這些pod想要使用K8S集群內(nèi)的DNS服務(wù),則可以配置為這個字段职烧;
  • None: 此設(shè)置允許 Pod 忽略 Kubernetes 環(huán)境中的 DNS 設(shè)置扁誓,Pod 會使用其 dnsConfig 字段 所配置的 DNS 設(shè)置;

說明: 下面主要介紹ClusterFirst模式

3.3 DNS解析規(guī)則

DNS 查詢參照 Pod 中的 /etc/resolv.conf 配置蚀之,kubelet 會為每個 Pod 生成此文件蝗敢。因此在每個pod里面都有一個類似下面這樣的 /etc/resolv.conf文件,通過修改其中的配置可以更改DNS的查詢規(guī)則:

nameserver 10.32.0.10
search <namespace>.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

這里的配置有幾個需要注意的點(diǎn):

  • nameserver:集群中的DNS服務(wù)器IP足删,一般來說就是CoreDNSClusterIP
  • search:需要搜索的域寿谴,默認(rèn)情況下會從該pod所屬的namespace開始逐級補(bǔ)充
  • options ndots:觸發(fā)上面的search的域名點(diǎn)數(shù),默認(rèn)為1失受,上限15讶泰,在K8S中一般為5;例如在Linux中tinychen.com這個域名的ndots是1拂到,tinychen.com.這個域名的ndots才是2(需要注意所有域名其實都有一個根域.痪署,因此tinychen.com的全稱應(yīng)該是tinychen.com.

這是一個比較通用的案例,我們再來看一個比較特殊的配置

# 首先進(jìn)入一個pod查看里面的DNS解析配置
[root@tiny-calico-master-88-1 tiny-calico]# kubectl exec -it -n ngx-system ngx-ex-deploy-6bf6c99d95-5qh2w /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
[root@ngx-ex-deploy-6bf6c99d95-5qh2w /]# cat /etc/resolv.conf
nameserver 10.88.0.10
search ngx-system.svc.cali-cluster.tclocal svc.cali-cluster.tclocal cali-cluster.tclocal k8s.tcinternal
options ndots:5
[root@ngx-ex-deploy-6bf6c99d95-5qh2w /]# exit

這個pod里面的/etc/resolv.conf配置文件有兩個和前面不同的地方:

  • cluster.local變成了cali-cluster.tclocal

    這里我們可以看到coredns的配置中就是配置的cali-cluster.tclocal兄旬,也就是說/etc/resolv.conf中的配置其實是和coredns中的配置一樣狼犯,更準(zhǔn)確的說是和該K8S集群初始化時配置的集群名一樣

    # 再查看K8S集群中的coredns的configmap 
    [root@tiny-calico-master-88-1 tiny-calico]# kubectl get configmaps -n kube-system coredns -oyaml
    apiVersion: v1
    data:
      Corefile: |
        .:53 {
            errors
            health {
               lameduck 5s
            }
            ready
            kubernetes cali-cluster.tclocal in-addr.arpa ip6.arpa {
               pods insecure
               fallthrough in-addr.arpa ip6.arpa
               ttl 30
            }
            prometheus :9153
            forward . 10.31.100.100 {
               max_concurrent 1000
            }
            cache 30
            loop
            reload
            loadbalance
        }
    kind: ConfigMap
    metadata:
      creationTimestamp: "2022-05-06T05:19:08Z"
      name: coredns
      namespace: kube-system
      resourceVersion: "3986029"
      uid: 54f5f803-a5ab-4c77-b149-f02229bcad0a
    
  • search新增了一個k8s.tcinternal

    實際上我們再查看K8S的宿主機(jī)節(jié)點(diǎn)的DNS配置規(guī)則時會發(fā)現(xiàn)這個k8s.tcinternal是從宿主機(jī)上面繼承而來的

    # 最后查看宿主機(jī)節(jié)點(diǎn)上面的DNS解析配置
    [root@tiny-calico-master-88-1 tiny-calico]# cat /etc/resolv.conf
    # Generated by NetworkManager
    search k8s.tcinternal
    nameserver 10.31.254.253
    

3.4 DNS解析流程

溫馨提示:閱讀這部分內(nèi)容的時候要特別注意域名結(jié)尾是否有一個點(diǎn)號.

當(dāng)ndots小于options ndots

前面我們說過options ndots的值默認(rèn)情況下是1,在K8S中為5,為了效果明顯悯森,我們這里使用K8S中的5作為示例:

這里同樣是在一個命名空間demo-ns中有兩個SVC宋舷,分別為demo-svc1demo-svc2,那么他們的/etc/resolv.conf應(yīng)該是下面這樣的:

nameserver 10.32.0.10
search demo-ns.svc.cluster.local svc.cluster.local cluster.local
options ndots:5

我們在demo-svc1中直接請求域名demo-svc2瓢姻,此時ndots為1祝蝠,小于配置中的5,因此會觸發(fā)上面的search規(guī)則幻碱,這時第一個解析的域名就是demo-svc2.demo-ns.svc.cluster.local绎狭,當(dāng)解析不出來的時候繼續(xù)下面的demo-svc2.svc.cluster.localdemo-svc2.cluster.local收班,最后才是直接去解析demo-svc2.坟岔。

注意上面的規(guī)則適用于任何一個域名,也就是當(dāng)我們試圖在pod中去訪問一個外部域名如tinychen.com的時候也會依次進(jìn)行上述查詢摔桦。

當(dāng)ndots大于等于options ndots

我們在demo-svc1中直接請求域名demo-svc2.demo-ns.svc.cluster.local社付,此時的ndots為4,還是會觸發(fā)上面的search規(guī)則邻耕。

而請求域名demo-svc2.demo-ns.svc.cluster.local.氨淌,ndots為5俺陋,等于配置中的5,因此不會觸發(fā)上面的search規(guī)則,直接去解析demo-svc2.demo-ns.svc.cluster.local.這個域名并返回結(jié)果

如果我們請求更長的域名如POD域名pod-1.demo-svc2.demo-ns.svc.cluster.local.丰榴,此時的ndots為6者祖,大于配置中的5缴川,因此也不會觸發(fā)上面的search規(guī)則厕诡,會直接查詢域名并返回解析

小結(jié)

通過上面的分析我們不難得出下面幾點(diǎn)結(jié)論:

  • 同命名空間(namespace)內(nèi)的服務(wù)直接通過$service_name進(jìn)行互相訪問而不需要使用全域名(FQDN),此時DNS解析速度最快削解;
  • 跨命名空間(namespace)的服務(wù)富弦,可以通過$service_name.$namespace_name進(jìn)行互相訪問,此時DNS解析第一次查詢失敗氛驮,第二次才會匹配到正確的域名腕柜;
  • 所有的服務(wù)之間通過全域名(FQDN)$service_name.$namespace_name.svc.$cluster_name.訪問的時候DNS解析的速度最快
  • 在K8S集群內(nèi)訪問大部分的常見外網(wǎng)域名(ndots小于5)都會觸發(fā)search規(guī)則矫废,因此在訪問外部域名的時候可以使用FQDN盏缤,即在域名的結(jié)尾配置一個點(diǎn)號.

4、四層服務(wù)暴露

對于K8S集群中的服務(wù)暴露到集群外部提供服務(wù)蓖扑,一般的方式可以分為四層服務(wù)暴露七層服務(wù)暴露唉铜,因為前者一般來說是后者的基礎(chǔ),因此這里我們先對四層服務(wù)暴露進(jìn)行介紹律杠。

在開始之前我們需要明確四層的概念打毛,這里的四層指的是OSI七層模型中的第四層柿赊,即TCP、UDP協(xié)議所處于的傳輸層幻枉,也就是我們常說的協(xié)議+IP+端口層面的負(fù)載均衡,常見的四層負(fù)載均衡器有LVS诡蜓、DPVS熬甫、Haproxy(四層七層均可)、nginx(四層七層均可)等蔓罚,在K8S中的四層服務(wù)暴露最常用的兩種手段就是我們前面提及的Nodeport和LoadBalancer椿肩。

我們先來看下面的這個架構(gòu)圖,注意整個藍(lán)色的點(diǎn)線范圍內(nèi)的是一個K8S集群豺谈,為了方便區(qū)分郑象,我們這里假設(shè)從集群外部進(jìn)來的流量都是南北流量(客戶端-服務(wù)器流量),集群內(nèi)部的流量全部都是東西流量(服務(wù)器-服務(wù)器流量)茬末。

[圖片上傳失敗...(image-20d0cc-1668327890312)]

我們從下到上開始看起

  • 圖中有frontend厂榛、backenddevops等多個命名空間丽惭,這是常見的用來隔離不同資源的手段击奶,在實際的落地場景中可以根據(jù)不同的業(yè)務(wù)、使用人群等進(jìn)行劃分责掏,具體的劃分維度和標(biāo)準(zhǔn)最好視實際業(yè)務(wù)情況而定柜砾;

  • 實際上不止是workload,service也是會根據(jù)不同的namespace進(jìn)行劃分换衬,包括K8S集群中的大部分api痰驱,我們在查找的時候都是需要指定namespace

  • 在k8s service這一層,圖中主要展示了用于集群內(nèi)部訪問的Cluster-IP和Headless兩種方式

  • 需要注意的是Headless服務(wù)是和其余三種服務(wù)類型相斥的瞳浦,同時它也不會經(jīng)過kube-proxy進(jìn)行負(fù)載均衡担映,因此在Headless服務(wù)的藍(lán)色實線框中是空白的,而Cluster-IP中則是kube-proxy組件

  • 再往上就是每個K8S集群中一般都會有的DNS服務(wù)术幔,CoreDNS從K8S的v1.11版本開始可以用來提供命名服務(wù)另萤,從v1.13 開始替代 kube-dns成為默認(rèn)DNS 服務(wù)

  • 在 Kubernetes 1.21 版本中,kubeadm 移除了對將 kube-dns 作為 DNS 應(yīng)用的支持诅挑。 對于 kubeadm v1.24四敞,所支持的唯一的集群 DNS 應(yīng)用是 CoreDNS

  • CoreDNS本身也是一個workload,它是位于kube-system這個命名空間下的一個deployments.apps

  • CoreDNS也是通過請求K8S集群內(nèi)的api-server來獲取k8s集群內(nèi)的服務(wù)信息

  • 再往上就是位于整個K8S集群的邊界處拔妥,這里首先必然會有一個api-server忿危,它會同時對集群內(nèi)和集群外暴露控制接口,我們可以通過這個接口來獲取K8S集群的信息

  • api-server本身并不存儲信息没龙,K8S集群本身的集群信息大部分都是存儲在etcd服務(wù)中铺厨,api-server會去etcd中讀取相關(guān)數(shù)據(jù)并返回缎玫;

  • 最后就是用來暴露四層服務(wù)的服務(wù),一般是NodePort或者是LoadBalancer解滓,因為端口和IP等原因赃磨,實際上使用的時候大部分都是以LoadBalancer的方式暴露出去;

  • LoadBalancer服務(wù)對外暴露的并不是一個IP洼裤,而是一個IP+端口邻辉,也就是說實際上一個IP的多個端口可以為不同類型的服務(wù)提供服務(wù),例如80和443端口提供http/https服務(wù)腮鞍,3306提供數(shù)據(jù)庫服務(wù)等值骇;

  • K8S并沒有內(nèi)置LoadBalancer,因此需要實現(xiàn)LoadBalancer移国,目前主流有兩種方式:一是使用云廠商如AWS吱瘩、Azure、阿里騰訊等提供的LoadBalancer迹缀,這些LoadBalancer基本都是閉源的解決方案使碾,基本僅適用于他們自家的云環(huán)境,但是作為收費(fèi)服務(wù)裹芝,在技術(shù)支持和售后已經(jīng)產(chǎn)品成熟度方面均有不錯的表現(xiàn)部逮;二是使用已有的一些開源LoadBalancer,主要就是MetalLB嫂易、OpenELB以及PureLB兄朋,關(guān)于三者的詳細(xì)介紹,之前已經(jīng)寫過相關(guān)的文章怜械,有興趣的可以點(diǎn)擊鏈接進(jìn)去看看颅和。

  • 開源的LoadBalancer基本都是擁有兩種主要工作模式:Layer2模式和BGP模式。無論是Layer2模式還是BGP模式缕允,核心思路都是通過某種方式將特定VIP的流量引到k8s集群中峡扩,然后再通過kube-proxy將流量轉(zhuǎn)發(fā)到后面的特定服務(wù)。

5障本、七層服務(wù)暴露

四層LoadBalancer服務(wù)暴露的方式優(yōu)點(diǎn)是適用范圍廣教届,因為工作在四層,因此幾乎能適配所有類型的應(yīng)用驾霜。但是也有一些缺點(diǎn):

  • 對于大多數(shù)應(yīng)用場景都是http協(xié)議的請求來說案训,并不需要給每個服務(wù)都配置一個EXTERNAL-IP來暴露服務(wù),這樣一來資源嚴(yán)重浪費(fèi)(公網(wǎng)IP十分珍貴)粪糙,二來包括IP地址已經(jīng)HTTPS使用的證書管理等均十分麻煩
  • 比較常見的場景是對應(yīng)的每個配置都配置一個域名(virtual host)或者是路由規(guī)則(routing rule)强霎,然后統(tǒng)一對外暴露一個或者少數(shù)幾個EXTERNAL-IP,將所有的請求流量都導(dǎo)入到一個統(tǒng)一個集中入口網(wǎng)關(guān)(如Nginx)蓉冈,再由這個網(wǎng)關(guān)來進(jìn)行各種負(fù)載均衡(load balancing)城舞、路由管理(routing rule)轩触、證書管理(SSL termination)等等

在K8S中,一般交由ingress來完成上述這些事情家夺。

5.1 Ingress

Ingress 是對集群中服務(wù)的外部訪問進(jìn)行管理的 API 對象脱柱,典型的訪問方式是 HTTP。Ingress 可以提供負(fù)載均衡(load balancing)拉馋、路由管理(routing rule)褐捻、證書管理(SSL termination)等功能。Ingress 公開從集群外部到集群內(nèi)服務(wù)的 HTTP 和 HTTPS 路由椅邓。 流量路由由 Ingress 資源上定義的規(guī)則控制。

下圖為K8S官方提供的一個關(guān)于ingres工作的示意圖:

[圖片上傳失敗...(image-240122-1668327890312)]

這里我們需要注意區(qū)分IngressIngress Controllers昧狮,兩者是兩個不同的概念景馁,并不等同

  • 從概念上來看,Ingress和前面提到的service很像逗鸣,Ingress本身并不是一個真實存在的工作負(fù)載(workload)
  • Ingress Controllers更傾向于部署在K8S集群中的一些特殊網(wǎng)關(guān)(如NGX)合住,以K8S官方維護(hù)的ingress-nginx為例,它本質(zhì)上其實是一個帶有Ingress資源類型的特殊deployments撒璧,Ingress ControllersIngress的具體實現(xiàn)

5.2 Ingress Controllers

上面我們得知所謂的Ingress Controllers本身其實就是特殊的workload(一般是deployment)透葛,因此它們本身也是需要通過某種方式暴露到集群外才能提供服務(wù),這里一般都是選擇通過LoadBalancer進(jìn)行四層服務(wù)暴露卿樱。

[圖片上傳失敗...(image-f28845-1668327890312)]

針對上面四層服務(wù)暴露的架構(gòu)圖僚害,我們在入口處的LoadBalancer后面加入一個Ingress,就可以得到七層Ingress服務(wù)暴露的架構(gòu)圖繁调。

  • 在這個圖中的處理邏輯和四層服務(wù)暴露一致萨蚕,唯一不同的就是HTTP協(xié)議的流量是先經(jīng)過入口處的loadbalancer,再轉(zhuǎn)發(fā)到ingress里面蹄胰,ingress再根據(jù)里面的ingress rule來進(jìn)行判斷轉(zhuǎn)發(fā)岳遥;
  • k8s的ingress-nginx是會和集群內(nèi)的api-server通信并且獲取服務(wù)信息,因為Ingress Controllers本身就具有負(fù)載均衡的能力裕寨,因此在把流量轉(zhuǎn)發(fā)到后端的具體服務(wù)時浩蓉,不會經(jīng)過ClusterIP(就算服務(wù)類型是ClusterIP也不經(jīng)過),而是直接轉(zhuǎn)發(fā)到所屬的Pod IP上宾袜;

6捻艳、總結(jié)

到這里關(guān)于K8S的基本服務(wù)暴露所需要了解的知識就介紹完了,由于K8S本身確實十分復(fù)雜试和,本文在介紹的時候只能蜻蜓點(diǎn)水讯泣,隨著K8S的不斷發(fā)展,現(xiàn)在普通的服務(wù)暴露已經(jīng)不能很好的滿足部分場景的高端需求阅悍,隨后又引發(fā)了很多諸如服務(wù)網(wǎng)格(service mesh)好渠、邊車模型(sidecar)昨稼、無邊車模型(sidecarless)等等的進(jìn)化。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拳锚,一起剝皮案震驚了整個濱河市假栓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌霍掺,老刑警劉巖匾荆,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異杆烁,居然都是意外死亡牙丽,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進(jìn)店門兔魂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烤芦,“玉大人,你說我怎么就攤上這事析校」孤蓿” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵智玻,是天一觀的道長遂唧。 經(jīng)常有香客問我,道長吊奢,這世上最難降的妖魔是什么盖彭? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮事甜,結(jié)果婚禮上谬泌,老公的妹妹穿的比我還像新娘。我一直安慰自己逻谦,他們只是感情好掌实,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著邦马,像睡著了一般贱鼻。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滋将,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天邻悬,我揣著相機(jī)與錄音,去河邊找鬼随闽。 笑死父丰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛾扇,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼攘烛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了镀首?” 一聲冷哼從身側(cè)響起坟漱,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎更哄,沒想到半個月后芋齿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡成翩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年觅捆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片麻敌。...
    茶點(diǎn)故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡惠拭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出庸论,到底是詐尸還是另有隱情,我是刑警寧澤棒呛,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布聂示,位于F島的核電站,受9級特大地震影響簇秒,放射性物質(zhì)發(fā)生泄漏鱼喉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一趋观、第九天 我趴在偏房一處隱蔽的房頂上張望扛禽。 院中可真熱鬧,春花似錦皱坛、人聲如沸编曼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掐场。三九已至,卻和暖如春贩猎,著一層夾襖步出監(jiān)牢的瞬間熊户,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工吭服, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留嚷堡,地道東北人。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓艇棕,卻偏偏與公主長得像蝌戒,于是被迫代替她去往敵國和親串塑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容