service服務(wù)詳解

Service原理機(jī)制

Kubernetes Pod 是有生命周期的,它們可以被創(chuàng)建献雅,也可以被銷(xiāo)毀玫鸟,然而一旦被銷(xiāo)毀生命就永遠(yuǎn)結(jié)束缓熟。 通過(guò) ReplicationController 能夠動(dòng)態(tài)地創(chuàng)建和銷(xiāo)毀 Pod(例如梢卸,需要進(jìn)行擴(kuò)縮容走诞,或者執(zhí)行 滾動(dòng)升級(jí))。 每個(gè) Pod 都會(huì)獲取它自己的 IP 地址蛤高,即使這些 IP 地址不總是穩(wěn)定可依賴(lài)的蚣旱。 這會(huì)導(dǎo)致一個(gè)問(wèn)題:在 Kubernetes 集群中,如果一組 Pod(稱(chēng)為 backend)為其它 Pod (稱(chēng)為 frontend)提供服務(wù)戴陡,那么那些 frontend 該如何發(fā)現(xiàn)塞绿,并連接到這組 Pod 中的哪些 backend 呢?

Kubernetes Service 定義了這樣一種抽象:一個(gè) Pod 的邏輯分組恤批,一種可以訪問(wèn)它們的策略 —— 通常稱(chēng)為微服務(wù)异吻。 這一組 Pod 能夠被 Service 訪問(wèn)到,通常是通過(guò) Label Selector實(shí)現(xiàn)的喜庞。Service 通過(guò)標(biāo)簽來(lái)選取服務(wù)后端涧黄,一般配合 Replication Controller 或者 Deployment 來(lái)保證后端容器的正常運(yùn)行。這些匹配標(biāo)簽的 Pod IP 和端口列表組成 endpoints赋荆,由 kube-proxy 負(fù)責(zé)將服務(wù) IP 負(fù)載均衡到這些 endpoints 上。


image.png

1懊昨、Service定義服務(wù)入口:
即k8s的Service定義了一個(gè)服務(wù)的訪問(wèn)入口地址窄潭,前端的應(yīng)用通過(guò)這個(gè)入口地址訪問(wèn)其背后的一組由Pod副本組成的集群實(shí)例,來(lái)自外部的訪問(wèn)請(qǐng)求被負(fù)載均衡到后端的各個(gè)容器應(yīng)用上酵颁。

2嫉你、Service與pod:
Service與其后端Pod副本集群之間則是通過(guò)Label Selector來(lái)實(shí)現(xiàn)對(duì)接的。而RC的作用相當(dāng)于是保證Service的服務(wù)能力和服務(wù)質(zhì)量始終處于預(yù)期的標(biāo)準(zhǔn)躏惋。

通過(guò)分析幽污、識(shí)別并建模系統(tǒng)中的所有服務(wù)為微服務(wù)-Kubernetes Service,最終我們的系統(tǒng)由多個(gè)提供不同業(yè)務(wù)能力而又彼此獨(dú)立的微服務(wù)單元所組成,服務(wù)之間通過(guò)TCP/IP進(jìn)行通信,從而形成了我們強(qiáng)大而又靈活的彈性網(wǎng)格,擁有了強(qiáng)大的分布式能力、彈性擴(kuò)展能力簿姨、容錯(cuò)能力,與此同時(shí),我們的程序架構(gòu)也變得簡(jiǎn)單和直觀許多,如圖所示距误。


image.png

3簸搞、Service的負(fù)載均衡器kube-proxy
Kubernetes也遵循了上述常規(guī)做法,運(yùn)行在每個(gè)Node上的kube-proxy進(jìn)程其實(shí)就是一個(gè)智能的軟件負(fù)載均衡器,它負(fù)責(zé)把對(duì)Service的請(qǐng)求轉(zhuǎn)發(fā)到后端的某個(gè)Pod實(shí)例上,并在內(nèi)部實(shí)現(xiàn)服務(wù)的負(fù)載均衡與會(huì)話保持機(jī)制。但Kubernetes發(fā)明了一種很巧妙又影響深遠(yuǎn)的設(shè)計(jì): Service不是共用一個(gè)負(fù)載均衡器的IP地址,而是每個(gè)Service分配了一個(gè)全局唯一的虛擬IP地址,這個(gè)虛擬IP被稱(chēng)為Cluster IP,這樣一來(lái),每個(gè)服務(wù)就變成了具備唯一IP地址的“通信節(jié)點(diǎn)”,服務(wù)調(diào)用就變成了最基礎(chǔ)的TCP網(wǎng)絡(luò)通信問(wèn)題.

4准潭、Cluster IP
我們知道, Pod的Endpoint地址會(huì)隨著Pod的銷(xiāo)毀和重新創(chuàng)建而發(fā)生改變,因?yàn)樾翽od的IP地址與之前舊Pod的不同趁俊。而Service一旦創(chuàng)建, Kubernetes就會(huì)自動(dòng)為它分配一個(gè)可用的Cluster IP,而且在Service的整個(gè)生命周期內(nèi),它的Cluster IP不會(huì)發(fā)生改變。于是,服務(wù)發(fā)現(xiàn)這個(gè)棘手的問(wèn)題在Kubernetes的架構(gòu)里也得以輕松解決:只要用Service的Name與Service的Cluster IP地址做一個(gè)DNS域名映射即可完美解決問(wèn)題⌒倘唬現(xiàn)在想想,這真是一個(gè)很棒的設(shè)計(jì)寺擂。

對(duì) Kubernetes 集群中的應(yīng)用,Kubernetes 提供了簡(jiǎn)單的 Endpoints API泼掠,只要 Service 中的一組 Pod 發(fā)生變更怔软,應(yīng)用程序就會(huì)被更新。 對(duì)非 Kubernetes 集群中的應(yīng)用择镇,Kubernetes 提供了基于 VIP 的網(wǎng)橋的方式訪問(wèn) Service挡逼,再由 Service 重定向到 backend Pod。

Service的虛擬IP地址Cluster IP:外部網(wǎng)絡(luò)無(wú)法ping通沐鼠,只有kubernetes集群內(nèi)部訪問(wèn)使用,但可以在各個(gè)node節(jié)點(diǎn)上直接通過(guò)ClusterIP:port訪問(wèn)挚瘟。

kubernetes查詢(xún)Cluster IP: kubectl get service

image.png

Cluster IP是一個(gè)虛擬的IP,但更像是一個(gè)偽造的IP網(wǎng)絡(luò)饲梭,原因有以下幾點(diǎn)

  • Cluster IP僅僅作用于Kubernetes Service這個(gè)對(duì)象乘盖,并由Kubernetes管理和分配P地址
  • Cluster IP無(wú)法被ping,他沒(méi)有一個(gè)“實(shí)體網(wǎng)絡(luò)對(duì)象”來(lái)響應(yīng).
  • Cluster IP只能結(jié)合Service Port組成一個(gè)具體的通信端口憔涉,單獨(dú)的Cluster IP不具備通信的基礎(chǔ)订框,并且他們屬于Kubernetes集群這樣一個(gè)封閉的空間。
  • 在不同Service下的pod節(jié)點(diǎn)在集群間相互訪問(wèn)可以通過(guò)Cluster IP

Service定義和使用

Service 定義可以基于 POST 方式兜叨,請(qǐng)求 apiserver 創(chuàng)建新的實(shí)例穿扳。一個(gè) Service 在 Kubernetes 中是一個(gè) REST 對(duì)象。本文對(duì)Service的使用進(jìn)行詳細(xì)說(shuō)明国旷,包括Service的負(fù)載均衡矛物、外網(wǎng)訪問(wèn)、DNS服務(wù)的搭建跪但、Ingress7層路由機(jī)制等履羞。

yaml格式的Service定義文件的完整內(nèi)容

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

上述配置將創(chuàng)建一個(gè)名稱(chēng)為 “my-service” 的 Service 對(duì)象,它會(huì)將請(qǐng)求代理到使用 TCP 端口 9376屡久,并且具有標(biāo)簽 "app=MyApp" 的 Pod 上忆首。 這個(gè) Service 將被指派一個(gè) IP 地址(通常稱(chēng)為 “Cluster IP”),它會(huì)被服務(wù)的代理使用被环。 該 Service 的 selector 將會(huì)持續(xù)評(píng)估糙及,處理結(jié)果將被 POST 到一個(gè)名稱(chēng)為 “my-service” 的 Endpoints 對(duì)象上。

需要注意的是筛欢, Service 能夠?qū)⒁粋€(gè)接收端口映射到任意的 targetPort浸锨。 默認(rèn)情況下唇聘,targetPort 將被設(shè)置為與 port 字段相同的值。 可能更有趣的是揣钦,targetPort 可以是一個(gè)字符串雳灾,引用了 backend Pod 的一個(gè)端口的名稱(chēng)。 但是冯凹,實(shí)際指派給該端口名稱(chēng)的端口號(hào)谎亩,在每個(gè) backend Pod 中可能并不相同。 對(duì)于部署和設(shè)計(jì) Service 宇姚,這種方式會(huì)提供更大的靈活性匈庭。 例如,可以在 backend 軟件下一個(gè)版本中浑劳,修改 Pod 暴露的端口阱持,并不會(huì)中斷客戶(hù)端的調(diào)用。

Kubernetes Service 能夠支持 TCP 和 UDP 協(xié)議魔熏,默認(rèn) TCP 協(xié)議衷咽。

我們看怎么使用:

1、我們定義一個(gè)提供web服務(wù)的RC:

由兩個(gè)springboot容器副本組成蒜绽,每個(gè)容器通過(guò)containerPort設(shè)置提供服務(wù)號(hào)為9081

apiVersion: v1
kind: ReplicationController
metadata:
  name: webapp
spec:
  replicas: 2
  template:
    metadata:
     name: webapp
     labels:
       app: webapp
    spec:  
      containers:
      - name: springboot-webapp
        image: registry.xxxx.com/springboot:latest
        ports:
        - containerPort: 9081
      imagePullSecrets:
      - name: registry-key-secret

創(chuàng)建該RC:
#kubectl create -f webapp-rc.yaml
獲取Pod的IP地址:

image.png

直接通過(guò)這兩個(gè)Pod的IP地址和端口號(hào)訪問(wèn)sringboot服務(wù):


image.png

直接通過(guò)Pod的IP地址和端口號(hào)可以訪問(wèn)容器內(nèi)的應(yīng)用服務(wù)镶骗,但是Pod的IP地址是不可靠的,例如Pod所在的Node發(fā)生故障躲雅,Pod將被k8s重新調(diào)度到另一臺(tái)Node鼎姊。Pod的IP地址將發(fā)生變化,更重要的是相赁,如果容器應(yīng)用本身是分布式的部署方式相寇,通過(guò)多個(gè)實(shí)例共同提供服務(wù),就需要在這些實(shí)例的前端設(shè)置一個(gè)負(fù)載均衡器來(lái)實(shí)現(xiàn)請(qǐng)求的分發(fā)钮科。kubernetes中的Service就是設(shè)計(jì)出來(lái)用于解決這些問(wèn)題的核心組件唤衫。

(2)通過(guò)kubectl expose命令來(lái)創(chuàng)建service

為了讓客戶(hù)端應(yīng)用能夠訪問(wèn)到兩個(gè)sprintbootPod 實(shí)例,需要?jiǎng)?chuàng)建一個(gè)Service來(lái)提供服務(wù)
k8s提供了一種快速的方法绵脯,即通過(guò)kubectl expose命令來(lái)創(chuàng)建:

kubectl expose rc webapp

查看新創(chuàng)建的Service可以看到系統(tǒng)為它分配了一個(gè)虛擬的IP地址(clusterIP)战授,而Service所需的端口號(hào)則從Pod中的containerPort復(fù)制而來(lái):

[root@bogon ~]# kubectl expose rc webapp
service "webapp" exposed

#kubectl get svc

image.png

接下來(lái),我們就可以通過(guò)Service的IP地址和Service的端口號(hào)訪問(wèn)該Service了:
# curl 192.168.14.242:9081
這里桨嫁,對(duì)Service地址 curl 192.168.14.242:9081的訪問(wèn)被自動(dòng)負(fù)載分發(fā)到了后端兩個(gè)Pod之一。

3)配置文件定義Service

除了使用kubectl expose命令創(chuàng)建Service份帐,我們也可以通過(guò)配置文件定義Service璃吧,再通過(guò)kubectl create命令進(jìn)行創(chuàng)建。
例如前面的webapp就用废境,我們可以設(shè)置一個(gè)Service:
webapp-svc.yaml

apiVersion: v1
kind: Service
metadata:
  name: webapp2
spec:
  ports:
  - port: 9082
    targetPort: 9081
  selector:
    app: webapp

Service定義中的關(guān)鍵字段是ports和selector畜挨。
本例中ports定義部分指定了Service所需的虛擬端口號(hào)為9082筒繁,由于與Pod容器端口號(hào)9081不一樣,所以需要在通過(guò)targetPort來(lái)指定后端Pod的端口巴元。
selector定義部分設(shè)置的是后端Pod所擁有的label: app=webapp

image.png

curl 192.168.22.2:9082

4)目前kubernetes提供了兩種負(fù)載分發(fā)策略:RoundRobin和SessionAffinity

  • RoundRobin:輪詢(xún)模式毡咏,即輪詢(xún)將請(qǐng)求轉(zhuǎn)發(fā)到后端的各個(gè)Pod上
  • SessionAffinity:基于客戶(hù)端IP地址進(jìn)行會(huì)話保持的模式,第一次客戶(hù)端訪問(wèn)后端某個(gè)Pod逮刨,之后的請(qǐng)求都轉(zhuǎn)發(fā)到這個(gè)Pod上

默認(rèn)是RoundRobin模式呕缭。

2.2 對(duì)Service定義文件中各屬性的說(shuō)明表


image.png

2.3 沒(méi)有 selector 的 Service
Servcie 抽象了該如何訪問(wèn) Kubernetes Pod,但也能夠抽象其它類(lèi)型的 backend修己,例如:

  • 希望在生產(chǎn)環(huán)境中使用外部的數(shù)據(jù)庫(kù)集群恢总,但測(cè)試環(huán)境使用自己的數(shù)據(jù)庫(kù)。
  • 希望服務(wù)指向另一個(gè) Namespace 中或其它集群中的服務(wù)睬愤。
  • 正在將工作負(fù)載轉(zhuǎn)移到 Kubernetes 集群片仿,和運(yùn)行在 Kubernetes 集群之外的 backend。

在任何這些場(chǎng)景中尤辱,都能夠定義沒(méi)有 selector 的 Service :

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

由于這個(gè) Service 沒(méi)有 selector砂豌,就不會(huì)創(chuàng)建相關(guān)的 Endpoints 對(duì)象」舛剑可以手動(dòng)將 Service 映射到指定的 Endpoints:

kind: Endpoints
apiVersion: v1
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 1.2.3.4
    ports:
      - port: 9376

注意:Endpoint IP 地址不能是 loopback(127.0.0.0/8)阳距、 link-local(169.254.0.0/16)、或者 link-local 多播(224.0.0.0/24)可帽。

訪問(wèn)沒(méi)有 selector 的 Service娄涩,與有 selector 的 Service 的原理相同。請(qǐng)求將被路由到用戶(hù)定義的 Endpoint(該示例中為 1.2.3.4:9376)映跟。

ExternalName Service 是 Service 的特例蓄拣,它沒(méi)有 selector,也沒(méi)有定義任何的端口和 Endpoint努隙。 相反地球恤,對(duì)于運(yùn)行在集群外部的服務(wù),它通過(guò)返回該外部服務(wù)的別名這種方式來(lái)提供服務(wù)荸镊。

kind: Service
apiVersion: v1
metadata:
  name: my-service
  namespace: prod
spec:
  type: ExternalName
  externalName: my.database.example.com

當(dāng)查詢(xún)主機(jī) my-service.prod.svc.CLUSTER時(shí)咽斧,集群的 DNS 服務(wù)將返回一個(gè)值為 my.database.example.com 的 CNAME 記錄。 訪問(wèn)這個(gè)服務(wù)的工作方式與其它的相同躬存,唯一不同的是重定向發(fā)生在 DNS 層张惹,而且不會(huì)進(jìn)行代理或轉(zhuǎn)發(fā)。 如果后續(xù)決定要將數(shù)據(jù)庫(kù)遷移到 Kubernetes 集群中岭洲,可以啟動(dòng)對(duì)應(yīng)的 Pod宛逗,增加合適的 Selector 或 Endpoint,修改 Service 的 type盾剩。

2.4雷激、service的類(lèi)型
Kubernetes ServiceTypes 允許指定一個(gè)需要的類(lèi)型的 Service替蔬,默認(rèn)是 ClusterIP 類(lèi)型。
Type 的取值以及行為如下:

1)ClusterIP:通過(guò)集群的內(nèi)部 IP 暴露服務(wù)屎暇,選擇該值承桥,服務(wù)只能夠在集群內(nèi)部可以訪問(wèn),這也是默認(rèn)的 ServiceType根悼。
2)NodePort:通過(guò)每個(gè) Node 上的 IP 和靜態(tài)端口(NodePort)暴露服務(wù)凶异。NodePort 服務(wù)會(huì)路由到 ClusterIP 服務(wù),這個(gè) ClusterIP 服務(wù)會(huì)自動(dòng)創(chuàng)建番挺。通過(guò)請(qǐng)求 <NodeIP>:<NodePort>唠帝,可以從集群的外部訪問(wèn)一個(gè) NodePort 服務(wù)。
3)LoadBalancer:使用云提供商的負(fù)載均衡器玄柏,可以向外部暴露服務(wù)襟衰。外部的負(fù)載均衡器可以路由到 NodePort 服務(wù)和 ClusterIP 服務(wù)。

  1. ExternalName:通過(guò)返回 CNAME 和它的值粪摘,可以將服務(wù)映射到 externalName 字段的內(nèi)容(例如瀑晒, foo.bar.example.com)。 沒(méi)有任何類(lèi)型代理被創(chuàng)建徘意,這只有 Kubernetes 1.7 或更高版本的 kube-dns 才支持苔悦。

2)通過(guò)設(shè)置nodePort映射到物理機(jī),同時(shí)設(shè)置Service的類(lèi)型為NodePort:

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  type:nodePort
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
      nodePort:30376

使用nodePort的缺點(diǎn):

  • 每個(gè)端口只能是一種服務(wù)
  • 端口范圍只能是 30000-32767
  • 和節(jié)點(diǎn)node的 IP 地址緊密耦合。
    (2)通過(guò)設(shè)置LoadBalancer映射到云服務(wù)商提供的LoadBalancer地址椎咧。

這種用法僅用于在公有云服務(wù)提供商的云平臺(tái)上設(shè)置Service的場(chǎng)景玖详。在下面的例子中, status.loadBalancer.ingress.ip設(shè)置的146.148.47.155為云服務(wù)商提供的負(fù)載均衡器的IP地址。對(duì)該Service的訪問(wèn)請(qǐng)求將會(huì)通過(guò)LoadBalancer轉(zhuǎn)發(fā)到后端Pod上,負(fù)載分發(fā)的實(shí)現(xiàn)方式則依賴(lài)于云服務(wù)商提供的LoadBalancer的實(shí)現(xiàn)機(jī)制勤讽。

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
     nodePort: 19376
     clusterIP: 10.0.171.239
     loadBalancerIP: 78.11.24.19
     type: LoadBalancer
  status:
    LoadBalancer:
     ingress:
     -ip: 146.148.47.155
 

k8s中有3種IP地址:

  • Node IP: Node節(jié)點(diǎn)的IP地址蟋座,這是集群中每個(gè)節(jié)點(diǎn)的物理網(wǎng)卡的IP地址;
  • Pod IP: Pod的IP地址脚牍,這是Docker Engine根據(jù)docker0網(wǎng)橋的IP地址段進(jìn)行分配的向臀,通常是一個(gè)虛擬的二層網(wǎng)絡(luò);
  • Cluster IP:Service 的IP地址诸狭,這也是一個(gè)虛擬的IP券膀,但它更像是一個(gè)“偽造”的IP地址,因?yàn)樗鼪](méi)有一個(gè)實(shí)體網(wǎng)絡(luò)對(duì)象驯遇,所以無(wú)法響應(yīng)ping命令芹彬。它只能結(jié)合Service Port組成一個(gè)具體的通信服務(wù)端口,單獨(dú)的Cluster IP不具備TCP/IP通信的基礎(chǔ)叉庐。在k8s集群之內(nèi)舒帮,Node IP網(wǎng)、Pod IP網(wǎng)與Cluster IP網(wǎng)之間的通信采用的是k8s自己設(shè)計(jì)的一種編程實(shí)現(xiàn)的特殊的路由規(guī)則,不同于常見(jiàn)的IP路由實(shí)現(xiàn)会前。

此模式會(huì)提供一個(gè)集群內(nèi)部的虛擬IP(與Pod不在同一網(wǎng)段),以供集群內(nèi)部的Pod之間通信使用匾竿。

headless service 需要將 spec.clusterIP 設(shè)置成 None瓦宜。

因?yàn)闆](méi)有ClusterIP,kube-proxy 并不處理此類(lèi)服務(wù)岭妖,因?yàn)闆](méi)有l(wèi)oad balancing或 proxy 代理設(shè)置临庇,在訪問(wèn)服務(wù)的時(shí)候回返回后端的全部的Pods IP地址,主要用于開(kāi)發(fā)者自己根據(jù)pods進(jìn)行負(fù)載均衡器的開(kāi)發(fā)(設(shè)置了selector)昵慌。

Service 服務(wù)的VIP 和 Service 網(wǎng)絡(luò)代理

1假夺、Service 服務(wù)的VIP
在 Kubernetes 集群中,每個(gè) Node 運(yùn)行一個(gè) kube-proxy 進(jìn)程斋攀。運(yùn)行在每個(gè)Node上的kube-proxy進(jìn)程其實(shí)就是一個(gè)智能的軟件負(fù)載均衡器已卷,它會(huì)負(fù)責(zé)把對(duì)Service的請(qǐng)求轉(zhuǎn)發(fā)到后端的某個(gè)Pod實(shí)例上并在內(nèi)部實(shí)現(xiàn)服務(wù)的負(fù)載均衡與會(huì)話保持機(jī)制。

Service不是共用一個(gè)負(fù)載均衡器的IP淳蔼,而是被分配了一個(gè)全局唯一的虛擬IP地址侧蘸,稱(chēng)為Cluster IP。在Service的整個(gè)生命周期內(nèi)鹉梨,它的Cluster IP不會(huì)改變

kube-proxy 負(fù)責(zé)為 Service 實(shí)現(xiàn)了一種 VIP(虛擬 IP)的形式讳癌,而不是 ExternalName 的形式。

  • 在 Kubernetes v1.0 版本存皂,代理完全在 userspace晌坤。
  • 在 Kubernetes v1.1 版本,新增了 iptables 代理旦袋,但并不是默認(rèn)的運(yùn)行模式骤菠。
  • 從 Kubernetes v1.2 起,默認(rèn)就是 iptables 代理猜憎。
  • 在 Kubernetes v1.0 版本娩怎,Service 是 “4層”(TCP/UDP over IP)概念。 在 Kubernetes v1.1 版本胰柑,新增了 Ingress API(beta 版)截亦,用來(lái)表示 “7層”(HTTP)服務(wù)。

每當(dāng)我們?cè)趉8s cluster中創(chuàng)建一個(gè)service柬讨,k8s cluster就會(huì)在–service-cluster-ip-range的范圍內(nèi)為service分配一個(gè)cluster-ip崩瓤,比如:


image.png

通過(guò) kubectl get ep可以看到對(duì)應(yīng)Endpoints信息,即代理的pod踩官。


image.png

另外却桶,也可以將已有的服務(wù)以 Service 的形式加入到 Kubernetes 集群中來(lái),只需要在創(chuàng)建 Service 的時(shí)候不指定 Label selector,而是在 Service 創(chuàng)建好后手動(dòng)為其添加 endpoint颖系。

2嗅剖、service網(wǎng)絡(luò)代理模式:
擁有三種代理模式:userspace、iptables和ipvs嘁扼。

現(xiàn)在默認(rèn)使用iptables信粮,在1.8版本之后增加了ipvs功能。

1)早期 userspace 代理模式

client先請(qǐng)求serviceip趁啸,經(jīng)由iptables轉(zhuǎn)發(fā)到kube-proxy上之后再轉(zhuǎn)發(fā)到pod上去强缘。這種方式效率比較低。

這種模式不傅,kube-proxy 會(huì)監(jiān)視 Kubernetes master 對(duì) Service 對(duì)象和 Endpoints 對(duì)象的添加和移除旅掂。 對(duì)每個(gè) Service,它會(huì)在本地 Node 上打開(kāi)一個(gè)端口(隨機(jī)選擇)访娶。 任何連接到“代理端口”的請(qǐng)求商虐,都會(huì)被代理到 Service 的backend Pods 中的某個(gè)上面(如 Endpoints 所報(bào)告的一樣)。 使用哪個(gè) backend Pod震肮,是基于 Service 的 SessionAffinity 來(lái)確定的称龙。 最后,它安裝 iptables 規(guī)則戳晌,捕獲到達(dá)該 Service 的 clusterIP(是虛擬 IP)和 Port 的請(qǐng)求鲫尊,并重定向到代理端口,代理端口再代理請(qǐng)求到 backend Pod沦偎。

網(wǎng)絡(luò)返回的結(jié)果是疫向,任何到達(dá) Service 的 IP:Port 的請(qǐng)求,都會(huì)被代理到一個(gè)合適的 backend豪嚎,不需要客戶(hù)端知道關(guān)于 Kubernetes搔驼、Service、或 Pod 的任何信息侈询。

默認(rèn)的策略是舌涨,通過(guò) round-robin 算法來(lái)選擇 backend Pod。 實(shí)現(xiàn)基于客戶(hù)端 IP 的會(huì)話親和性扔字,可以通過(guò)設(shè)置 service.spec.sessionAffinity 的值為 "ClientIP" (默認(rèn)值為 "None")囊嘉。

image.png
  1. 當(dāng)前iptables 代理模式
    client請(qǐng)求serviceip后會(huì)直接轉(zhuǎn)發(fā)到pod上。這種模式性能會(huì)高很多革为。kube-proxy就會(huì)負(fù)責(zé)將pod地址生成在node節(jié)點(diǎn)iptables規(guī)則中扭粱。

這種模式,kube-proxy 會(huì)監(jiān)視 Kubernetes master 對(duì) Service 對(duì)象和 Endpoints 對(duì)象的添加和移除震檩。 對(duì)每個(gè) Service琢蛤,它會(huì)安裝 iptables 規(guī)則蜓堕,從而捕獲到達(dá)該 Service 的 clusterIP(虛擬 IP)和端口的請(qǐng)求,進(jìn)而將請(qǐng)求重定向到 Service 的一組 backend 中的某個(gè)上面博其。 對(duì)于每個(gè) Endpoints 對(duì)象套才,它也會(huì)安裝 iptables 規(guī)則,這個(gè)規(guī)則會(huì)選擇一個(gè) backend Pod慕淡。

默認(rèn)的策略是霜旧,隨機(jī)選擇一個(gè) backend。 實(shí)現(xiàn)基于客戶(hù)端 IP 的會(huì)話親和性儡率,可以將 service.spec.sessionAffinity 的值設(shè)置為 "ClientIP" (默認(rèn)值為 "None")。

和 userspace 代理類(lèi)似以清,網(wǎng)絡(luò)返回的結(jié)果是儿普,任何到達(dá) Service 的 IP:Port 的請(qǐng)求,都會(huì)被代理到一個(gè)合適的 backend掷倔,不需要客戶(hù)端知道關(guān)于 Kubernetes眉孩、Service、或 Pod 的任何信息勒葱。 這應(yīng)該比 userspace 代理更快浪汪、更可靠。然而凛虽,不像 userspace 代理死遭,如果初始選擇的 Pod 沒(méi)有響應(yīng),iptables 代理能夠自動(dòng)地重試另一個(gè) Pod凯旋,所以它需要依賴(lài) readiness probes呀潭。


image.png

3)、ipvs代理方式
這種方式是通過(guò)內(nèi)核模塊ipvs實(shí)現(xiàn)轉(zhuǎn)發(fā)至非,這種效率更高钠署。


image.png

3、多端口 Service
很多 Service 需要暴露多個(gè)端口荒椭。對(duì)于這種情況谐鼎,Kubernetes 支持在 Service 對(duì)象中定義多個(gè)端口。 當(dāng)使用多個(gè)端口時(shí)趣惠,必須給出所有的端口的名稱(chēng)狸棍,這樣 Endpoint 就不會(huì)產(chǎn)生歧義,例如:

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
    selector:
      app: MyApp
    ports:
      - name: http
        protocol: TCP
        port: 80
        targetPort: 9376
      - name: https
        protocol: TCP
        port: 443
        targetPort: 9377

4信卡、 選擇自己的 IP 地址
在 Service 創(chuàng)建的請(qǐng)求中隔缀,可以通過(guò)設(shè)置 spec.clusterIP 字段來(lái)指定自己的集群 IP 地址。 比如傍菇,希望替換一個(gè)已經(jīng)已存在的 DNS 條目猾瘸,或者遺留系統(tǒng)已經(jīng)配置了一個(gè)固定的 IP 且很難重新配置。 用戶(hù)選擇的 IP 地址必須合法,并且這個(gè) IP 地址在 service-cluster-ip-range CIDR 范圍內(nèi)牵触,這對(duì) API Server 來(lái)說(shuō)是通過(guò)一個(gè)標(biāo)識(shí)來(lái)指定的淮悼。 如果 IP 地址不合法,API Server 會(huì)返回 HTTP 狀態(tài)碼 422揽思,表示值不合法袜腥。

service為何使用vip而不是不使用 round-robin DNS迅箩?
一個(gè)不時(shí)出現(xiàn)的問(wèn)題是家淤,為什么我們都使用 VIP 的方式笼吟,而不使用標(biāo)準(zhǔn)的 round-robin DNS毯炮,有如下幾個(gè)原因:

長(zhǎng)久以來(lái)盟猖,DNS 庫(kù)都沒(méi)能認(rèn)真對(duì)待 DNS TTL女坑、緩存域名查詢(xún)結(jié)果
很多應(yīng)用只查詢(xún)一次 DNS 并緩存了結(jié)果.
就算應(yīng)用和庫(kù)能夠正確查詢(xún)解析螟左,每個(gè)客戶(hù)端反復(fù)重解析造成的負(fù)載也是非常難以管理的
我們盡力阻止用戶(hù)做那些對(duì)他們沒(méi)有好處的事情谈飒,如果很多人都來(lái)問(wèn)這個(gè)問(wèn)題卢未,我們可能會(huì)選擇實(shí)現(xiàn)它肪凛。

服務(wù)發(fā)現(xiàn)和DNS

Kubernetes 支持2種基本的服務(wù)發(fā)現(xiàn)模式 —— 環(huán)境變量和 DNS。

1辽社、環(huán)境變量
當(dāng) Pod 運(yùn)行在 Node 上伟墙,kubelet 會(huì)為每個(gè)活躍的 Service 添加一組環(huán)境變量。 它同時(shí)支持 Docker links兼容 變量(查看 makeLinkVariables)滴铅、簡(jiǎn)單的 {SVCNAME}_SERVICE_HOST 和 {SVCNAME}_SERVICE_PORT 變量戳葵,這里 Service 的名稱(chēng)需大寫(xiě),橫線被轉(zhuǎn)換成下劃線汉匙。

舉個(gè)例子譬淳,一個(gè)名稱(chēng)為 "redis-master" 的 Service 暴露了 TCP 端口 6379,同時(shí)給它分配了 Cluster IP 地址 10.0.0.11盹兢,這個(gè) Service 生成了如下環(huán)境變量:

REDIS_MASTER_SERVICE_HOST=10.0.0.11
REDIS_MASTER_SERVICE_PORT=6379
REDIS_MASTER_PORT=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP=tcp://10.0.0.11:6379
REDIS_MASTER_PORT_6379_TCP_PROTO=tcp
REDIS_MASTER_PORT_6379_TCP_PORT=6379
REDIS_MASTER_PORT_6379_TCP_ADDR=10.0.0.11

這意味著需要有順序的要求 —— Pod 想要訪問(wèn)的任何 Service 必須在 Pod 自己之前被創(chuàng)建邻梆,否則這些環(huán)境變量就不會(huì)被賦值。DNS 并沒(méi)有這個(gè)限制绎秒。

2浦妄、DNS
一個(gè)可選(盡管強(qiáng)烈推薦)集群插件 是 DNS 服務(wù)器。 DNS 服務(wù)器監(jiān)視著創(chuàng)建新 Service 的 Kubernetes API见芹,從而為每一個(gè) Service 創(chuàng)建一組 DNS 記錄剂娄。 如果整個(gè)集群的 DNS 一直被啟用,那么所有的 Pod 應(yīng)該能夠自動(dòng)對(duì) Service 進(jìn)行名稱(chēng)解析玄呛。

例如:

Service為:webapp阅懦,Namespace 為: "my-ns"。 它在 Kubernetes 集群為 "webapp.my-ns" 創(chuàng)建了一條 DNS 記錄徘铝。

1)在同一個(gè)集群(名稱(chēng)為 "my-ns" 的 Namespace 中)內(nèi)的 Pod 應(yīng)該能夠簡(jiǎn)單地通過(guò)名稱(chēng)查詢(xún)找到 "webapp"耳胎。

2)在另一個(gè) Namespace 中的 Pod 必須限定名稱(chēng)為 "webapp.my-ns"惯吕。 這些名稱(chēng)查詢(xún)的結(jié)果是 Cluster IP。

Kubernetes 也支持對(duì)端口名稱(chēng)的 DNS SRV(Service)記錄怕午。 如果名稱(chēng)為 "webapp.my-ns" 的 Service 有一個(gè)名為 "http" 的 TCP 端口废登,可以對(duì) "_http._tcp.webapp.my-ns" 執(zhí)行 DNS SRV 查詢(xún),得到 "http" 的端口號(hào)郁惜。

Kubernetes DNS 服務(wù)器是唯一的一種能夠訪問(wèn) ExternalName 類(lèi)型的 Service 的方式堡距。 更多信息可以查看https://guisu.blog.csdn.net/article/details/93501650

集群外部訪問(wèn)服務(wù)

k8s集群外如何訪問(wèn)集群內(nèi)的服務(wù),主要方式有:hostPort或hostNetwork兆蕉、NodePort羽戒、Ingress

1、hostPort或hostNetwork
hostPort和hostNetwork 放在首位是因?yàn)榇蠹液苋菀缀雎运鼈兓⒃希鼈円部勺尲和庠L問(wèn)集群內(nèi)應(yīng)用半醉,

hostNetwork 用法:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec: 
      nodeSelector:      # node節(jié)點(diǎn)選擇器
        role: master     # node節(jié)點(diǎn)標(biāo)簽(Label)
      hostNetwork: true  # 使用node節(jié)點(diǎn)網(wǎng)絡(luò)
      containers:
      - image: nginx
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 8080

重點(diǎn)在和containers平級(jí)的hostNetwork: true,表示pod使用宿主機(jī)網(wǎng)絡(luò),配合nodeSelector劝术,把pod實(shí)例化在固定節(jié)點(diǎn),如上呆奕,我給mater節(jié)點(diǎn)加上標(biāo)簽role: master,通過(guò)nodeSelector,nginx就會(huì)實(shí)例化在master節(jié)點(diǎn)养晋,這樣就可以通過(guò)master節(jié)點(diǎn)的ip和8080端口訪問(wèn)這個(gè)nginx了。

hostPort用法:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: nginx
  name: nginx-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec: 
      nodeSelector:      # node節(jié)點(diǎn)選擇器
        role: master     # node節(jié)點(diǎn)標(biāo)簽(Label)
      containers:
      - image: nginx
        imagePullPolicy: IfNotPresent
        name: nginx
        ports:
        - containerPort: 8080
          hostPort: 80                #重點(diǎn)

和hostNetwork相比多了映射能力梁钾,可以把容器端口映射為node節(jié)點(diǎn)不同端口绳泉,hostPort,當(dāng)然也需要nodeSelector來(lái)固定節(jié)點(diǎn),不然每次創(chuàng)建姆泻,節(jié)點(diǎn)不同零酪,ip也會(huì)改變

訪問(wèn)方式:nodeSelector所選節(jié)點(diǎn)ip:hostPort, 如上:role=Master標(biāo)簽節(jié)點(diǎn)Ip:80

2、NodePort
NodePort是最常見(jiàn)的提供集群外訪問(wèn)的方式之一拇勃,該方式使用Service提供集群外訪問(wèn):

通過(guò)設(shè)置nodePort映射到物理機(jī),同時(shí)設(shè)置Service的類(lèi)型為NodePort:

kind: Service
apiVersion: v1
metadata:
  name: my-service
spec:
  type:nodePort
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
      nodePort:30376

訪問(wèn)方式:集群內(nèi)任意節(jié)點(diǎn)ip加nodePort所配端口號(hào)四苇,如上:集群內(nèi)任一節(jié)點(diǎn)ip:30376,即可訪問(wèn)服務(wù)了方咆。

使用nodePort的缺點(diǎn):

  • 每個(gè)端口只能是一種服務(wù)
  • 端口范圍只能是 30000-32767
  • 和節(jié)點(diǎn)node的 IP 地址緊密耦合月腋。
    在本機(jī)可以訪問(wèn)nodePort, 其他服務(wù)器無(wú)法問(wèn)題,例如node 172.16.1.23上可以訪問(wèn)curl 172.16.1.23:30018,但是在服務(wù)器172.16.1.21上無(wú)法訪問(wèn):


    image.png

    image.png

解決的辦法是:

cat > /etc/sysctl.d//etc/sysctl.conf <<EOF
net.ipv4.ip_forward=1
EOF
sysctl -p

具體原因:https://github.com/moby/moby/pull/28257

如果net.ipv4.ip_forward=1參數(shù)是由Docker所設(shè)置瓣赂,則iptables的FORWARD會(huì)被設(shè)置為DORP策略榆骚。如果是由用戶(hù)設(shè)置net.ipv4.ip_forward=1參數(shù),則用戶(hù)可能會(huì)進(jìn)行某些意圖需要FORWARD為ACCEPT策略煌集,這時(shí)候Docker就不會(huì)去修改FORWARD策略妓肢。

3、LoadBalancer
通過(guò)設(shè)置LoadBalancer映射到云服務(wù)商提供的LoadBalancer地址苫纤。

這種用法僅用于在公有云服務(wù)提供商的云平臺(tái)上設(shè)置Service的場(chǎng)景碉钠。在下面的例子中, status.loadBalancer.ingress.ip設(shè)置的146.148.47.155為云服務(wù)商提供的負(fù)載均衡器的IP地址纲缓。對(duì)該Service的訪問(wèn)請(qǐng)求將會(huì)通過(guò)LoadBalancer轉(zhuǎn)發(fā)到后端Pod上,負(fù)載分發(fā)的實(shí)現(xiàn)方式則依賴(lài)于云服務(wù)商提供的LoadBalancer的實(shí)現(xiàn)機(jī)制。

kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
  app: MyApp
ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
   nodePort: 19376
   clusterIP: 10.0.171.239
   loadBalancerIP: 78.11.24.19
   type: LoadBalancer
status:
  LoadBalancer:
   ingress:
   -ip: 146.148.47.155

4放钦、Ingress
可以簡(jiǎn)單理解為部署了一個(gè)nginx服務(wù)色徘,該服務(wù)使用hostNetwork或hostPort方式提供集群外訪問(wèn),再根據(jù)配置的路由規(guī)則操禀,路由的集群內(nèi)部各個(gè)service褂策。

根據(jù)前面對(duì)Service的使用說(shuō)明,我們知道Service的表現(xiàn)形式為IP:Port颓屑,即工作在TCP/IP層斤寂,而對(duì)于基于HTTP的服務(wù)來(lái)說(shuō),不同的URL地址經(jīng)常對(duì)應(yīng)到不同的后端服務(wù)或者虛擬服務(wù)器揪惦,這些應(yīng)用層的轉(zhuǎn)發(fā)機(jī)制僅通過(guò)kubernetes的Service機(jī)制是無(wú)法實(shí)現(xiàn)的遍搞。kubernetes V1.1版本中新增的Ingress將不同URL的訪問(wèn)請(qǐng)求轉(zhuǎn)發(fā)到后端不同的Service,實(shí)現(xiàn)HTTP層的業(yè)務(wù)路由機(jī)制器腋。在kubernetes集群中溪猿,Ingress的實(shí)現(xiàn)需要通過(guò)Ingress的定義與Ingress Controller的定義結(jié)合起來(lái),才能形成完整的HTTP負(fù)載分發(fā)功能纫塌。

1)诊县、創(chuàng)建Ingress Controller
使用Nginx來(lái)實(shí)現(xiàn)一個(gè)Ingress Controller,需要實(shí)現(xiàn)的基本邏輯如下:

  • 監(jiān)聽(tīng)apiserver措左,獲取全部ingress的定義
  • 基于ingress的定義依痊,生成Nginx所需的配置文件/etc/nginx/nginx.conf
  • 執(zhí)行nginx -s relaod命令,重新加載nginx.conf文件怎披,寫(xiě)個(gè)腳本胸嘁。

通過(guò)直接下載谷歌提供的nginx-ingress鏡像來(lái)創(chuàng)建Ingress Controller:
文件nginx-ingress-rc.yaml

apiVersion: v1
kind: ReplicationController
matadata:
  name: nginx-ingress
  labels:
    app: nginx-ingress
spec:
  replicas: 1
  selector:
    app: nginx-ingress
  template:
    metadata:
      labels:
        app: nginx-ingress
    spec:
      containers:
      - image: gcr.io/google_containers/nginx-ingress:0.1
        name: nginx
        ports:
        - containerPort: 80
          hostPort: 80

這里,Nginx應(yīng)用配置設(shè)置了hostPort凉逛,即它將容器應(yīng)用監(jiān)聽(tīng)的80端口號(hào)映射到物理機(jī)性宏,以使得客戶(hù)端應(yīng)用可以通過(guò)URL地址“http://物理機(jī)IP:80”來(lái)訪問(wèn)該Ingress Controller
#kubectl create -f nginx-ingress-rc.yaml
#kubectl get pods

2)、定義Ingress
為mywebsite.com定義Ingress状飞,設(shè)置到后端Service的轉(zhuǎn)發(fā)規(guī)則:

apiVersion: extensions/vlbeta1
kind: Ingress
metadata:
  name: mywebsite-ingress
spec:
  rules:
  - host: mywebsite.com
    http:
      paths:
      - path: /web
        backend:
          serviceName: webapp
          servicePort: 80

這個(gè)Ingress的定義說(shuō)明對(duì)目標(biāo)http://mywebsite.com/web的訪問(wèn)將被轉(zhuǎn)發(fā)到kubernetes的一個(gè)Service上 webapp:80

創(chuàng)建該Ingress
#kubectl create -f Ingress.yaml

#kubectl get ingress

NAME |Hosts |Address |Ports |Age
mywebsite-ingress |mywebsite.com |80 |17s

創(chuàng)建后登陸nginx-ingress Pod衔沼,查看自動(dòng)生成的nginx.conf內(nèi)容

3)訪問(wèn)http://mywebsite.com/web
我們可以通過(guò)其他的物理機(jī)對(duì)其進(jìn)行訪問(wèn)。通過(guò)curl --resolve進(jìn)行指定

curl --resolve mywebsite.com:80:192.169.18.3 mywebsite.com/web

3)昔瞧、使用Ingress 場(chǎng)景
Ingress 可能是暴露服務(wù)的最強(qiáng)大方式指蚁,但同時(shí)也是最復(fù)雜的。Ingress 控制器有各種類(lèi)型自晰,包括 Google Cloud Load Balancer凝化, Nginx,Contour酬荞,Istio搓劫,等等瞧哟。它還有各種插件,比如 cert-manager枪向,它可以為你的服務(wù)自動(dòng)提供 SSL 證書(shū)勤揩。
如果你想要使用同一個(gè) IP 暴露多個(gè)服務(wù),這些服務(wù)都是使用相同的七層協(xié)議(典型如 HTTP)秘蛔,那么Ingress 就是最有用的陨亡。如果你使用本地的 GCP 集成,你只需要為一個(gè)負(fù)載均衡器付費(fèi)深员,且由于 Ingress是“智能”的负蠕,你還可以獲取各種開(kāi)箱即用的特性(比如 SSL,認(rèn)證倦畅,路由遮糖,等等)。

5叠赐、總結(jié)各方式利弊

  • hostPort和hostNetwork直接使用節(jié)點(diǎn)網(wǎng)絡(luò)欲账,部署時(shí)節(jié)點(diǎn)需固定,訪問(wèn)ip也固定(也可以用host)芭概,端口為正常端口

  • nodeport方式部署時(shí)不要求固定節(jié)點(diǎn)赛不,可通過(guò)集群內(nèi)任一ip進(jìn)行訪問(wèn),就是端口為30000以上谈山,很多時(shí)候由于公司安全策略導(dǎo)致不能訪問(wèn)。

  • LoadBalancer依賴(lài)于云服務(wù)商提供的LoadBalancer的實(shí)現(xiàn)機(jī)制宏怔。

ingress需要額外安裝ingress模塊奏路,配置路由規(guī)則,且僅能通過(guò)所配置域名訪問(wèn)臊诊,配置好域名后鸽粉,可以直接對(duì)外提供服務(wù),和傳統(tǒng)的nginx作用類(lèi)似

Headless Service

1抓艳、定義:有時(shí)不需要或不想要負(fù)載均衡触机,以及單獨(dú)的Service IP。遇到這種情況玷或,可以通過(guò)指定Cluster IP(spec.clusterIP)的值為“None”來(lái)創(chuàng)建Headless Service儡首。

2、和普通Service相比:
對(duì)這類(lèi)Headless Service并不會(huì)分配Cluster IP偏友,kube-proxy不會(huì)處理它們蔬胯,而且平臺(tái)也不會(huì)為它們進(jìn)行負(fù)載均衡和路由。
它會(huì)給一個(gè)集群內(nèi)部的每個(gè)成員提供一個(gè)唯一的DNS域名來(lái)作為每個(gè)成員的網(wǎng)絡(luò)標(biāo)識(shí)位他,集群內(nèi)部成員之間使用域名通信氛濒。

3产场、無(wú)頭服務(wù)管理的域名是如下的格式:(service_name).(k8s_namespace).svc.cluster.local。其中的"cluster.local"是集群的域名,除非做了配置舞竿,否則集群域名默認(rèn)就是cluster.local京景。
因此選項(xiàng)spec.clusterIP允許開(kāi)發(fā)人員自由的尋找他們自己的方式,從而降低與Kubernetes系統(tǒng)的耦合性骗奖。應(yīng)用仍然可以使用一種自注冊(cè)的模式和適配器确徙,對(duì)其他需要發(fā)現(xiàn)機(jī)制的系統(tǒng)能夠很容易的基于這個(gè)API來(lái)構(gòu)建。
因?yàn)闆](méi)有l(wèi)oad balancing或 proxy 代理設(shè)置重归,在訪問(wèn)服務(wù)的時(shí)候回返回后端的全部的Pods IP地址米愿,主要用于開(kāi)發(fā)者自己根據(jù)pods進(jìn)行負(fù)載均衡器的開(kāi)發(fā)(設(shè)置了selector)。
DNS如何實(shí)現(xiàn)自動(dòng)配置鼻吮,依賴(lài)于Service時(shí)候定義了selector育苟。
(1)編寫(xiě)headless service配置清單

vim myapp-svc-headless.yaml

apiVersion: v1
kind: Service
metadata:
  name: myapp-headless
  namespace: default
spec:
  selector:
    app: myapp
    release: canary
  clusterIP: "None" #headless的clusterIP值為None
  ports:
  - port: 80
    targetPort: 80

(2)創(chuàng)建headless service
# kubectl apply -f myapp-svc-headless.yaml

[root@k8s-master mainfests]# kubectl get svc
NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes       ClusterIP   10.96.0.1        <none>        443/TCP        36d
myapp            NodePort    10.101.245.119   <none>        80:30080/TCP   1h
myapp-headless   ClusterIP   None             <none>        80/TCP         5s
redis            ClusterIP   10.107.238.182   <none>        6379/TCP       2h

(3)使用coredns進(jìn)行解析驗(yàn)證

[root@k8s-master mainfests]# dig -t A myapp-headless.default.svc.cluster.local. @10.96.0.10

; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7 <<>> -t A myapp-headless.default.svc.cluster.local. @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 62028
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;myapp-headless.default.svc.cluster.local. IN A

;; ANSWER SECTION:
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.1.18
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.1.19
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.2.15
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.2.16
myapp-headless.default.svc.cluster.local. 5 IN A 10.244.2.17

;; Query time: 4 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Thu Sep 27 04:27:15 EDT 2018
;; MSG SIZE  rcvd: 349

[root@k8s-master mainfests]# kubectl get svc -n kube-system
NAME       TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)         AGE
kube-dns   ClusterIP   10.96.0.10   <none>        53/UDP,53/TCP   36d

[root@k8s-master mainfests]# kubectl get pods -o wide -l app=myapp
NAME                            READY     STATUS    RESTARTS   AGE       IP            NODE
myapp-deploy-69b47bc96d-4hxxw   1/1       Running   0          1h        10.244.1.18   k8s-node01
myapp-deploy-69b47bc96d-95bc4   1/1       Running   0          1h        10.244.2.16   k8s-node02
myapp-deploy-69b47bc96d-hwbzt   1/1       Running   0          1h        10.244.1.19   k8s-node01
myapp-deploy-69b47bc96d-pjv74   1/1       Running   0          1h        10.244.2.15   k8s-node02
myapp-deploy-69b47bc96d-rf7bs   1/1       Running   0          1h        10.244.2.17   k8s-node02

(4)對(duì)比含有ClusterIP的service解析

[root@k8s-master mainfests]# dig -t A myapp.default.svc.cluster.local. @10.96.0.10

; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7 <<>> -t A myapp.default.svc.cluster.local. @10.96.0.10
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 50445
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;myapp.default.svc.cluster.local. IN    A

;; ANSWER SECTION:
myapp.default.svc.cluster.local. 5 IN    A    10.101.245.119

;; Query time: 1 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Thu Sep 27 04:31:16 EDT 2018
;; MSG SIZE  rcvd: 107

# kubectl get svc

NAME             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
kubernetes       ClusterIP   10.96.0.1        <none>        443/TCP        36d
myapp            NodePort    10.101.245.119   <none>        80:30080/TCP   1h
myapp-headless   ClusterIP   None             <none>        80/TCP         11m
redis            ClusterIP   10.107.238.182   <none>        6379/TCP       2h

從以上的演示可以看到對(duì)比普通的service和headless service,headless service做dns解析是直接解析到pod的椎木,而servcie是解析到ClusterIP的违柏,那么headless有什么用呢?這將在statefulset中應(yīng)用到香椎,這里暫時(shí)僅僅做了解什么是headless service和創(chuàng)建方法漱竖。

VIP注意事項(xiàng)

對(duì)很多想使用 Service 的人來(lái)說(shuō),前面的信息應(yīng)該足夠了畜伐。 然而馍惹,有很多內(nèi)部原理性的內(nèi)容,還是值去理解的玛界。

避免沖突

Kubernetes 最主要的哲學(xué)之一万矾,是用戶(hù)不應(yīng)該暴露那些能夠?qū)е滤麄儾僮魇 ⒌植皇撬麄兊倪^(guò)錯(cuò)的場(chǎng)景慎框。 這種場(chǎng)景下良狈,讓我們來(lái)看一下網(wǎng)絡(luò)端口 —— 用戶(hù)不應(yīng)該必須選擇一個(gè)端口號(hào),而且該端口還有可能與其他用戶(hù)的沖突笨枯。 這就是說(shuō)薪丁,在彼此隔離狀態(tài)下仍然會(huì)出現(xiàn)失敗。

為了使用戶(hù)能夠?yàn)樗麄兊?Service 選擇一個(gè)端口號(hào)馅精,我們必須確保不能有2個(gè) Service 發(fā)生沖突严嗜。 我們可以通過(guò)為每個(gè) Service 分配它們自己的 IP 地址來(lái)實(shí)現(xiàn)。

為了保證每個(gè) Service 被分配到一個(gè)唯一的 IP洲敢,需要一個(gè)內(nèi)部的分配器能夠原子地更新 etcd 中的一個(gè)全局分配映射表阻问,這個(gè)更新操作要先于創(chuàng)建每一個(gè) Service。 為了使 Service能夠獲取到 IP沦疾,這個(gè)映射表對(duì)象必須在注冊(cè)中心存在称近,否則創(chuàng)建 Service 將會(huì)失敗第队,指示一個(gè) IP 不能被分配。 一個(gè)后臺(tái) Controller 的職責(zé)是創(chuàng)建映射表(從 Kubernetes 的舊版本遷移過(guò)來(lái)刨秆,舊版本中是通過(guò)在內(nèi)存中加鎖的方式實(shí)現(xiàn))凳谦,并檢查由于管理員干預(yù)和清除任意 IP 造成的不合理分配,這些 IP 被分配了但當(dāng)前沒(méi)有 Service 使用它們衡未。

IP 和 VIP

不像 Pod 的 IP 地址尸执,它實(shí)際路由到一個(gè)固定的目的地,Service 的 IP 實(shí)際上不能通過(guò)單個(gè)主機(jī)來(lái)進(jìn)行應(yīng)答缓醋。 相反如失,我們使用 iptables(Linux 中的數(shù)據(jù)包處理邏輯)來(lái)定義一個(gè)虛擬IP地址(VIP),它可以根據(jù)需要透明地進(jìn)行重定向送粱。 當(dāng)客戶(hù)端連接到 VIP 時(shí)褪贵,它們的流量會(huì)自動(dòng)地傳輸?shù)揭粋€(gè)合適的 Endpoint抗俄。 環(huán)境變量和 DNS动雹,實(shí)際上會(huì)根據(jù) Service 的 VIP 和端口來(lái)進(jìn)行填充槽卫。

Userspace

作為一個(gè)例子歼培,考慮前面提到的圖片處理應(yīng)用程序。 當(dāng)創(chuàng)建 backend Service 時(shí)躲庄,Kubernetes master 會(huì)給它指派一個(gè)虛擬 IP 地址梗搅,比如 10.0.0.1无切。 假設(shè) Service 的端口是 1234哆键,該 Service 會(huì)被集群中所有的 kube-proxy 實(shí)例觀察到闪盔。 當(dāng)代理看到一個(gè)新的 Service听绳, 它會(huì)打開(kāi)一個(gè)新的端口椅挣,建立一個(gè)從該 VIP 重定向到新端口的 iptables鼠证,并開(kāi)始接收請(qǐng)求連接。

當(dāng)一個(gè)客戶(hù)端連接到一個(gè) VIP娩鹉,iptables 規(guī)則開(kāi)始起作用,它會(huì)重定向該數(shù)據(jù)包到 Service代理 的端口锈嫩。 Service代理 選擇一個(gè) backend,并將客戶(hù)端的流量代理到 backend 上对雪。

這意味著 Service 的所有者能夠選擇任何他們想使用的端口,而不存在沖突的風(fēng)險(xiǎn)迈套。 客戶(hù)端可以簡(jiǎn)單地連接到一個(gè) IP 和端口,而不需要知道實(shí)際訪問(wèn)了哪些 Pod率拒。

Iptables

再次考慮前面提到的圖片處理應(yīng)用程序俏橘。 當(dāng)創(chuàng)建 backend Service 時(shí),Kubernetes master 會(huì)給它指派一個(gè)虛擬 IP 地址召耘,比如 10.0.0.1。 假設(shè) Service 的端口是 1234衫贬,該 Service會(huì)被集群中所有的 kube-proxy 實(shí)例觀察到。 當(dāng)代理看到一個(gè)新的 Service, 它會(huì)安裝一系列的 iptables 規(guī)則贴捡,從 VIP 重定向到 per-Service 規(guī)則。 該 per-Service 規(guī)則連接到 per-Endpoint 規(guī)則汛骂,該 per-Endpoint 規(guī)則會(huì)重定向(目標(biāo) NAT)到 backend手销。

當(dāng)一個(gè)客戶(hù)端連接到一個(gè) VIP诈悍,iptables 規(guī)則開(kāi)始起作用适袜。一個(gè) backend 會(huì)被選擇(或者根據(jù)會(huì)話親和性,或者隨機(jī))疫萤,數(shù)據(jù)包被重定向到這個(gè) backend。 不像 userspace 代理尾序,數(shù)據(jù)包從來(lái)不拷貝到用戶(hù)空間,kube-proxy 不是必須為該 VIP 工作而運(yùn)行,并且客戶(hù)端 IP 是不可更改的痕慢。 當(dāng)流量打到 Node 的端口上,或通過(guò)負(fù)載均衡器塔次,會(huì)執(zhí)行相同的基本流程,但是在那些案例中客戶(hù)端 IP 是可以更改的巾表。

集群的服務(wù)分類(lèi)

在K8S運(yùn)行的服務(wù),從簡(jiǎn)單到復(fù)雜可以分成三類(lèi):無(wú)狀態(tài)服務(wù)鞠苟、普通有狀態(tài)服務(wù)和有狀態(tài)集群服務(wù)。下面分別來(lái)看K8S是如何運(yùn)行這三類(lèi)服務(wù)的。

1扼鞋、無(wú)狀態(tài)服務(wù)(Stateless Service):
1)定義:是指該服務(wù)運(yùn)行的實(shí)例不會(huì)在本地存儲(chǔ)需要持久化的數(shù)據(jù),并且多個(gè)實(shí)例對(duì)于同一個(gè)請(qǐng)求響應(yīng)的結(jié)果是完全一致的。

2)隨意擴(kuò)容和縮容:這些節(jié)點(diǎn)可以隨意擴(kuò)容或者縮容昏滴,只要簡(jiǎn)單的增加或減少副本的數(shù)量就可以。K8S使用RC(或更新的Replica Set)來(lái)保證一個(gè)服務(wù)的實(shí)例數(shù)量,如果說(shuō)某個(gè)Pod實(shí)例由于某種原因Crash了蛇捌,RC會(huì)立刻用這個(gè)Pod的模版新啟一個(gè)Pod來(lái)替代它回溺,由于是無(wú)狀態(tài)的服務(wù)祥诽,新啟的Pod與原來(lái)健康狀態(tài)下的Pod一模一樣。在Pod被重建后它的IP地址可能發(fā)生變化维哈,為了對(duì)外提供一個(gè)穩(wěn)定的訪問(wèn)接口,K8S引入了Service的概念。一個(gè)Service后面可以掛多個(gè)Pod迂求,實(shí)現(xiàn)服務(wù)的高可用。

3)多個(gè)實(shí)例可以共享相同的持久化數(shù)據(jù):例如數(shù)據(jù)存儲(chǔ)到mysql。

相關(guān)的k8s資源有:ReplicaSet、ReplicationController砸西、Deployment等,由于是無(wú)狀態(tài)服務(wù),所以這些控制器創(chuàng)建的pod序號(hào)都是隨機(jī)值绩郎。并且在縮容的時(shí)候并不會(huì)明確縮容某一個(gè)pod,而是隨機(jī)的状植,因?yàn)樗袑?shí)例得到的返回值都是一樣必怜,所以縮容任何一個(gè)pod都可以暖途。

2、普通有狀態(tài)服務(wù)(Stateful Service):
和無(wú)狀態(tài)服務(wù)相比,它多了狀態(tài)保存的需求纸巷。即有數(shù)據(jù)存儲(chǔ)功能竖伯。這類(lèi)服務(wù)包括單實(shí)例的mysql祟偷。

因?yàn)橛袪顟B(tài)的容器異常重啟就會(huì)造成數(shù)據(jù)丟失,也無(wú)法多副本部署饲化,無(wú)法實(shí)現(xiàn)負(fù)載均衡。
比如PHP的Session數(shù)據(jù)默認(rèn)存儲(chǔ)在磁盤(pán)上,比如 /tmp 目錄夕冲,而多副本負(fù)載均衡時(shí)卜高,多個(gè)PHP容器的目錄是彼此隔離的。比如存在兩個(gè)副本A和B,用戶(hù)第一次請(qǐng)求時(shí)候,流量被轉(zhuǎn)發(fā)到A减拭,并生成了SESSION沧侥,而第二次請(qǐng)求時(shí)啥纸,流量可能被負(fù)載均衡器轉(zhuǎn)發(fā)到B上,而B(niǎo)是沒(méi)有SESSION數(shù)據(jù)的庭惜,所以就會(huì)造成會(huì)話超時(shí)等BUG砾跃。
如果采用主機(jī)卷的方式判耕,多個(gè)容器掛載同一個(gè)主機(jī)目錄碳竟,就可以共享SESSION數(shù)據(jù)昌执,但是如果多主機(jī)負(fù)載均衡場(chǎng)景厂汗,就需要將SESSION存儲(chǔ)于外部數(shù)據(jù)庫(kù)或Redis中了贾节。

Kubernetes提供了以Volume和Persistent Volume為基礎(chǔ)的存儲(chǔ)系統(tǒng)祈争,可以實(shí)現(xiàn)服務(wù)的狀態(tài)保存。

普通狀態(tài)服務(wù)只能有一個(gè)實(shí)例,因此不支持“自動(dòng)服務(wù)容量調(diào)節(jié)”忿墅。一般來(lái)說(shuō)扁藕,數(shù)據(jù)庫(kù)服務(wù)或者需要在本地文件系統(tǒng)存儲(chǔ)配置文件或其它永久數(shù)據(jù)的應(yīng)用程序可以創(chuàng)建使用有狀態(tài)服務(wù)。要想創(chuàng)建有狀態(tài)服務(wù)疚脐,必須滿(mǎn)足幾個(gè)前提:

1)待創(chuàng)建的服務(wù)鏡像(image)的Dockerfile中必須定義了存儲(chǔ)卷(Volume),因?yàn)橹挥写鎯?chǔ)卷所在目錄里的數(shù)據(jù)可以被備份
2)創(chuàng)建服務(wù)時(shí)棍弄,必須指定給該存儲(chǔ)卷分配的磁盤(pán)空間大小
3)如果創(chuàng)建服務(wù)的同時(shí)需要從之前的一個(gè)備份里恢復(fù)數(shù)據(jù)望薄,那么還要指明該存儲(chǔ)卷用哪個(gè)備份恢復(fù)。

無(wú)狀態(tài)服務(wù)和有狀態(tài)服務(wù)主要有以下幾點(diǎn)區(qū)別:

  • 實(shí)例數(shù)量:無(wú)狀態(tài)服務(wù)可以有一個(gè)或多個(gè)實(shí)例呼畸,因此支持兩種服務(wù)容量調(diào)節(jié)模式痕支;有狀態(tài)服務(wù)只能有一個(gè)實(shí)例,不允許創(chuàng)建多個(gè) 實(shí)例蛮原,因此也不支持服務(wù)容量調(diào)節(jié)模式卧须。
  • 存儲(chǔ)卷:無(wú)狀態(tài)服務(wù)可以有存儲(chǔ)卷,也可以沒(méi)有瞬痘,即使有也無(wú)法備份存儲(chǔ)卷里面的數(shù)據(jù)故慈;有狀態(tài)服務(wù)必須要有存儲(chǔ)卷板熊,并且在創(chuàng)建服務(wù)時(shí)框全,必須指定給該存儲(chǔ)卷分配的磁盤(pán)空間大小。
  • 數(shù)據(jù)存儲(chǔ):無(wú)狀態(tài)服務(wù)運(yùn)行過(guò)程中的所有數(shù)據(jù)(除日志和監(jiān)控?cái)?shù)據(jù))都存在容器實(shí)例里的文件系統(tǒng)中干签,如果實(shí)例停止或者刪除津辩,則這些數(shù)據(jù)都將丟失,無(wú)法找回容劳;而對(duì)于有狀態(tài)服務(wù)喘沿,凡是已經(jīng)掛載了存儲(chǔ)卷的目錄下的文件內(nèi)容都可以隨時(shí)進(jìn)行備份,備份的數(shù)據(jù)可以下載竭贩,也可以用于恢復(fù)新的服務(wù)蚜印。但對(duì)于沒(méi)有掛載卷的目錄下的數(shù)據(jù),仍然是無(wú)法備份和保存的留量,如果實(shí)例停止或者刪除窄赋,這些非掛載卷里的文件內(nèi)容同樣會(huì)丟失。

3楼熄、有狀態(tài)集群服務(wù)(Stateful cluster Service)
與普通有狀態(tài)服務(wù)相比忆绰,它多了集群管理的需求,即有狀態(tài)集群服務(wù)要解決的問(wèn)題有兩個(gè),一個(gè)是狀態(tài)保存可岂,另一個(gè)是集群管理错敢。

這類(lèi)服務(wù)包括kafka、zookeeper等缕粹。

有狀態(tài)集群服務(wù)應(yīng)用:StatefulSet

StatefulSet背景

有狀態(tài)集群服務(wù)的部署稚茅,意味著節(jié)點(diǎn)需要形成群組關(guān)系纸淮,每個(gè)節(jié)點(diǎn)需要一個(gè)唯一的ID(例如Kafka BrokerId, Zookeeper myid)來(lái)作為集群內(nèi)部每個(gè)成員的標(biāo)識(shí),集群內(nèi)節(jié)點(diǎn)之間進(jìn)行內(nèi)部通信時(shí)需要用到這些標(biāo)識(shí)亚享。

傳統(tǒng)的做法是管理員會(huì)把這些程序部署到穩(wěn)定的萎馅,長(zhǎng)期存活的節(jié)點(diǎn)上去,這些節(jié)點(diǎn)有持久化的存儲(chǔ)和靜態(tài)的IP地址虹蒋。這樣某個(gè)應(yīng)用的實(shí)例就跟底層物理基礎(chǔ)設(shè)施比如某臺(tái)機(jī)器糜芳,某個(gè)IP地址耦合在一起了。

K8S為此開(kāi)發(fā)了一套以StatefulSet(1.5版本之前叫做PetSet)為核心的全新特性魄衅,方便了有狀態(tài)集群服務(wù)在K8S上的部署和管理峭竣。Kubernets中StatefulSet的目標(biāo)是通過(guò)把標(biāo)識(shí)分配給應(yīng)用程序的某個(gè)不依賴(lài)于底層物理基礎(chǔ)設(shè)施的特定實(shí)例來(lái)解耦這種依賴(lài)關(guān)系。(消費(fèi)方不使用靜態(tài)的IP晃虫,而是通過(guò)DNS域名去找到某臺(tái)特定機(jī)器)

具體的工作原理:

1皆撩、是通過(guò)Init Container來(lái)做集群的初始化工作
2、用 Headless Service 來(lái)維持集群成員的穩(wěn)定關(guān)系哲银,
3扛吞、用動(dòng)態(tài)存儲(chǔ)供給來(lái)方便集群擴(kuò)容
4、最后用StatefulSet來(lái)綜合管理整個(gè)集群荆责。

要運(yùn)行有狀態(tài)集群服務(wù)要解決的問(wèn)題有兩個(gè),一個(gè)是狀態(tài)保存哄啄,另一個(gè)是集群管理。 我們先來(lái)看如何解決第一個(gè)問(wèn)題:狀態(tài)保存寥假。Kubernetes 有一套以Volume插件為基礎(chǔ)的存儲(chǔ)系統(tǒng)妄壶,通過(guò)這套存儲(chǔ)系統(tǒng)可以實(shí)現(xiàn)應(yīng)用和服務(wù)的狀態(tài)保存。

K8S的存儲(chǔ)系統(tǒng)從基礎(chǔ)到高級(jí)又大致分為三個(gè)層次:普通Volume键耕,Persistent Volume 和動(dòng)態(tài)存儲(chǔ)供應(yīng)寺滚。

從kubernetes 1.5 開(kāi)始, PetSet 功能升級(jí)到了 Beta 版本屈雄,并重新命名為StatefulSet村视。除了依照社區(qū)民意改了名字之外,這一 API 對(duì)象并沒(méi)有太大變化酒奶,kubernetes集群部署 Pod 增加了每索引最多一個(gè)”的語(yǔ)義蚁孔,有了順序部署、順序終結(jié)讥蟆、唯一網(wǎng)絡(luò)名稱(chēng)以及持久穩(wěn)定的存儲(chǔ)勒虾。

StatefulSet特性

StatefulSet為什么適合有狀態(tài)的程序,因?yàn)樗啾扔贒eployment有以下特點(diǎn):

  • 穩(wěn)定的瘸彤,唯一的網(wǎng)絡(luò)標(biāo)識(shí):可以用來(lái)發(fā)現(xiàn)集群內(nèi)部的其他成員修然。比如StatefulSet的名字叫kafka,那么第一個(gè)起來(lái)的Pet叫kafka-0,第二個(gè)叫kafk-1,依次類(lèi)推,基于Headless Service(即沒(méi)有Cluster IP的Service)來(lái)實(shí)現(xiàn)愕宋。
  • 穩(wěn)定的持久化存儲(chǔ):通過(guò)Kubernetes的PV/PVC或者外部存儲(chǔ)(預(yù)先提供的)來(lái)實(shí)現(xiàn)
  • 啟動(dòng)或關(guān)閉時(shí)保證有序:優(yōu)雅的部署和伸縮性: 操作第n個(gè)pod時(shí)玻靡,前n-1個(gè)pod已經(jīng)是運(yùn)行且準(zhǔn)備好的狀態(tài)。 有序的中贝,優(yōu)雅的刪除和終止操作:從 n, n-1, ... 1, 0 這樣的順序刪除囤捻。

在部署或者擴(kuò)展的時(shí)候要依據(jù)定義的順序依次依序進(jìn)行(即從0到N-1,在下一個(gè)Pod運(yùn)行之前所有之前的Pod必須都是Running和Ready狀態(tài))邻寿,基于init containers來(lái)實(shí)現(xiàn)

上述提到的“穩(wěn)定”指的是Pod在多次重新調(diào)度時(shí)保持穩(wěn)定蝎土,即存儲(chǔ),DNS名稱(chēng)绣否,hostname都是跟Pod綁定到一起的誊涯,跟Pod被調(diào)度到哪個(gè)節(jié)點(diǎn)沒(méi)關(guān)系。
所以Zookeeper蒜撮,Etcd或 Elasticsearch這類(lèi)需要穩(wěn)定的集群成員的應(yīng)用時(shí)暴构,就可以用StatefulSet。通過(guò)查詢(xún)無(wú)頭服務(wù)域名的A記錄段磨,就可以得到集群內(nèi)成員的域名信息取逾。

StatefulSet也有一些限制:

1)、在Kubernetes 1.9版本之前是beta版本苹支,在Kubernetes 1.5版本之前是不提供的砾隅。
2)、Pod的存儲(chǔ)必須是通過(guò) PersistentVolume Provisioner基于 storeage類(lèi)來(lái)提供沐序,或者是管理員預(yù)先提供的外部存儲(chǔ)琉用。
3)堕绩、刪除或者縮容不會(huì)刪除跟StatefulSet相關(guān)的卷策幼,這是為了保證數(shù)據(jù)的安全

4)、StatefulSet現(xiàn)在需要一個(gè)無(wú)頭服務(wù)(Headless Service)來(lái)負(fù)責(zé)生成Pods的唯一網(wǎng)絡(luò)標(biāo)示奴紧,此Headless服務(wù)需要通過(guò)手工創(chuàng)建特姐。

什么時(shí)候使用StatefulSet

StatefulSet 的目的就是給為數(shù)眾多的有狀態(tài)負(fù)載提供正確的控制器支持。然而需要注意的是黍氮,不一定所有的有存儲(chǔ)應(yīng)用都是適合移植到 Kubernetes 上的唐含,在移植存儲(chǔ)層和編排框架之前,需要回答以下幾個(gè)問(wèn)題沫浆。

  • 應(yīng)用是否可以使用遠(yuǎn)程存儲(chǔ)捷枯?
    目前,我們推薦用遠(yuǎn)程存儲(chǔ)來(lái)使用 StatefulSets专执,就要對(duì)因?yàn)榫W(wǎng)絡(luò)造成的存儲(chǔ)性能損失有一個(gè)準(zhǔn)備:即使是專(zhuān)門(mén)優(yōu)化的實(shí)例淮捆,也無(wú)法同本地加載的 SSD 相提并論。你的云中的網(wǎng)絡(luò)存儲(chǔ),能夠滿(mǎn)足 SLA 要求么攀痊?如果答案是肯定的桐腌,那么利用 StatefulSet 運(yùn)行這些應(yīng)用,就能夠獲得自動(dòng)化的優(yōu)勢(shì)苟径。如果應(yīng)用所在的 Node 發(fā)生故障案站,包含應(yīng)用的 Pod 會(huì)調(diào)度到其他 Node 上,在這之后會(huì)重新加載他的網(wǎng)絡(luò)存儲(chǔ)以及其中的數(shù)據(jù)棘街。

  • 這些應(yīng)用是否有伸縮需求蟆盐?
    用 StatefulSet 運(yùn)行應(yīng)用會(huì)帶來(lái)什么好處呢?你的整個(gè)組織是否只需要一個(gè)應(yīng)用實(shí)例遭殉?對(duì)該應(yīng)用的伸縮是否會(huì)引起問(wèn)題舱禽?如果你只需要較少的應(yīng)用實(shí)例數(shù)量,這些實(shí)例能夠滿(mǎn)足組織現(xiàn)有的需要恩沽,而且可以預(yù)見(jiàn)的是誊稚,應(yīng)用的負(fù)載不會(huì)很快增長(zhǎng),那么你的本地應(yīng)用可能無(wú)需移植罗心。

然而里伯,如果你的系統(tǒng)是微服務(wù)所構(gòu)成的生態(tài)系統(tǒng),就會(huì)比較頻繁的交付新服務(wù)渤闷,如果更近一步疾瓮,服務(wù)是有狀態(tài)的,那么 Kubernetes 的自動(dòng)化和健壯性特性會(huì)對(duì)你的系統(tǒng)有很大幫助飒箭。如果你已經(jīng)在使用 Kubernetes 來(lái)管理你的無(wú)狀態(tài)服務(wù)狼电,你可能會(huì)想要在同一個(gè)體系中管理你的有狀態(tài)應(yīng)用。

  • 預(yù)期性能增長(zhǎng)的重要性弦蹂?
    Kubernetes 還不支持網(wǎng)絡(luò)或存儲(chǔ)在 Pod 之間的隔離肩碟。如果你的應(yīng)用不巧和嘈雜的鄰居共享同一個(gè)節(jié)點(diǎn),會(huì)導(dǎo)致你的 QPS 下降凸椿。解決方式是把 Pod 調(diào)度為該 Node 的唯一租戶(hù)(獨(dú)占服務(wù)器)削祈,或者使用互斥規(guī)則來(lái)隔離會(huì)爭(zhēng)用網(wǎng)絡(luò)和磁盤(pán)的 Pod,但是這就意味著用戶(hù)必須鑒別和處置(競(jìng)爭(zhēng))熱點(diǎn)脑漫。

如果榨干有狀態(tài)應(yīng)用的最大 QPS 不是你的首要目標(biāo)髓抑,而且你愿意也有能力處理競(jìng)爭(zhēng)問(wèn)題,似的有狀態(tài)應(yīng)用能夠達(dá)到 SLA 需要优幸,又如果對(duì)服務(wù)的移植吨拍、伸縮和重新調(diào)度是你的主要需求,Kubernetes 和 StatefulSet 可能就是解決問(wèn)題的好方案了网杆。

  • 你的應(yīng)用是否需要特定的硬件或者實(shí)例類(lèi)型
    如果你的有狀態(tài)應(yīng)用在高端硬件或高規(guī)格實(shí)例上運(yùn)行羹饰,而其他應(yīng)用在通用硬件或者低規(guī)格實(shí)例上運(yùn)行握爷,你可能不想部署一個(gè)異構(gòu)的集群。如果可以把所有應(yīng)用都部署到統(tǒng)一實(shí)例規(guī)格的實(shí)例上严里,那么你就能夠從 Kubernetes 獲得動(dòng)態(tài)資源調(diào)度和健壯性的好處新啼。

Init Container初始化集群服務(wù)

什么是Init Container?

從名字來(lái)看就是做初始化工作的容器刹碾≡镒玻可以有一個(gè)或多個(gè),如果有多個(gè)迷帜,這些 Init Container 按照定義的順序依次執(zhí)行物舒,只有所有的Init Container 執(zhí)行完后,主容器才啟動(dòng)戏锹。由于一個(gè)Pod里的存儲(chǔ)卷是共享的冠胯,所以 Init Container 里產(chǎn)生的數(shù)據(jù)可以被主容器使用到。

Init Container可以在多種 K8S 資源里被使用到如 Deployment锦针、Daemon Set, Pet Set, Job等荠察,但歸根結(jié)底都是在Pod啟動(dòng)時(shí),在主容器啟動(dòng)前執(zhí)行奈搜,做初始化工作悉盆。

我們?cè)谑裁吹胤綍?huì)用到 Init Container呢?

第一種場(chǎng)景是等待其它模塊Ready馋吗,比如我們有一個(gè)應(yīng)用里面有兩個(gè)容器化的服務(wù)焕盟,一個(gè)是Web Server,另一個(gè)是數(shù)據(jù)庫(kù)宏粤。其中Web Server需要訪問(wèn)數(shù)據(jù)庫(kù)脚翘。但是當(dāng)我們啟動(dòng)這個(gè)應(yīng)用的時(shí)候,并不能保證數(shù)據(jù)庫(kù)服務(wù)先啟動(dòng)起來(lái)绍哎,所以可能出現(xiàn)在一段時(shí)間內(nèi)Web Server有數(shù)據(jù)庫(kù)連接錯(cuò)誤来农。為了解決這個(gè)問(wèn)題,我們可以在運(yùn)行Web Server服務(wù)的Pod里使用一個(gè)Init Container蛇摸,去檢查數(shù)據(jù)庫(kù)是否準(zhǔn)備好备图,直到數(shù)據(jù)庫(kù)可以連接,Init Container才結(jié)束退出赶袄,然后Web Server容器被啟動(dòng),發(fā)起正式的數(shù)據(jù)庫(kù)連接請(qǐng)求抠藕。

第二種場(chǎng)景是做初始化配置饿肺,比如集群里檢測(cè)所有已經(jīng)存在的成員節(jié)點(diǎn),為主容器準(zhǔn)備好集群的配置信息盾似,這樣主容器起來(lái)后就能用這個(gè)配置信息加入集群敬辣。

還有其它使用場(chǎng)景雪标,如將pod注冊(cè)到一個(gè)中央數(shù)據(jù)庫(kù)、下載應(yīng)用依賴(lài)等溉跃。

這些東西能夠放到主容器里嗎村刨?從技術(shù)上來(lái)說(shuō)能,但從設(shè)計(jì)上來(lái)說(shuō)撰茎,可能不是一個(gè)好的設(shè)計(jì)嵌牺。首先不符合單一職責(zé)原則,其次這些操作是只執(zhí)行一次的龄糊,如果放到主容器里逆粹,還需要特殊的檢查來(lái)避免被執(zhí)行多次。


image.png

這是Init Container的一個(gè)使用樣例

這個(gè)例子創(chuàng)建一個(gè)Pod炫惩,這個(gè)Pod里跑的是一個(gè)nginx容器僻弹,Pod里有一個(gè)叫workdir的存儲(chǔ)卷,訪問(wèn)nginx容器服務(wù)的時(shí)候他嚷,就會(huì)顯示這個(gè)存儲(chǔ)卷里的index.html 文件蹋绽。

而這個(gè)index.html 文件是如何獲得的呢?是由一個(gè)Init Container從網(wǎng)絡(luò)上下載的筋蓖。這個(gè)Init Container 使用一個(gè)busybox鏡像蟋字,起來(lái)后,執(zhí)行一條wget命令扭勉,獲取index.html文件鹊奖,然后結(jié)束退出。

由于Init Container和nginx容器共享一個(gè)存儲(chǔ)卷(這里這個(gè)存儲(chǔ)卷的名字叫workdir)涂炎,所以在Init container里下載的index.html文件可以在nginx容器里被訪問(wèn)到忠聚。

可以看到 Init Container 是在 annotation里定義的。Annotation 是K8S新特性的實(shí)驗(yàn)場(chǎng)唱捣,通常一個(gè)新的Feature出來(lái)一般會(huì)先在Annotation 里指定两蟀,等成熟穩(wěn)定了,再給它一個(gè)正式的屬性名或資源對(duì)象名震缭。

介紹完Init Container赂毯,千呼萬(wàn)喚始出來(lái),主角Pet Set該出場(chǎng)了拣宰。

集群服務(wù)的存儲(chǔ):K8S的存儲(chǔ)系統(tǒng)

K8S的存儲(chǔ)系統(tǒng)從基礎(chǔ)到高級(jí)又大致分為三個(gè)層次:普通Volume党涕,Persistent Volume 和動(dòng)態(tài)存儲(chǔ)供應(yīng)。

1.普通Volume
最簡(jiǎn)單的普通Volume是單節(jié)點(diǎn)Volume巡社。它和Docker的存儲(chǔ)卷類(lèi)似膛堤,使用的是Pod所在K8S節(jié)點(diǎn)的本地目錄。

第二種類(lèi)型是跨節(jié)點(diǎn)存儲(chǔ)卷晌该,這種存儲(chǔ)卷不和某個(gè)具體的K8S節(jié)點(diǎn)綁定肥荔,而是獨(dú)立于K8S節(jié)點(diǎn)存在的绿渣,整個(gè)存儲(chǔ)集群和K8S集群是兩個(gè)集群,相互獨(dú)立燕耿。

跨節(jié)點(diǎn)的存儲(chǔ)卷在Kubernetes上用的比較多中符,如果已有的存儲(chǔ)不能滿(mǎn)足要求,還可以開(kāi)發(fā)自己的Volume插件誉帅,只需要實(shí)現(xiàn)Volume.go 里定義的接口淀散。如果你是一個(gè)存儲(chǔ)廠商,想要自己的存儲(chǔ)支持Kubernetes 上運(yùn)行的容器堵第,就可以去開(kāi)發(fā)一個(gè)自己的Volume插件吧凉。

2.pv:persistent volume

PersistentVolume(PV)是集群之中的一塊網(wǎng)絡(luò)存儲(chǔ)。跟 Node 一樣踏志,也是集群的資源阀捅,并且不屬于特定的namespace。PV 跟 Volume (卷) 類(lèi)似针余,不過(guò)會(huì)有獨(dú)立于 Pod 的生命周期饲鄙。

它和普通Volume的區(qū)別是什么呢?

普通Volume和使用它的Pod之間是一種靜態(tài)綁定關(guān)系圆雁,在定義Pod的文件里忍级,同時(shí)定義了它使用的Volume。Volume 是Pod的附屬品伪朽,我們無(wú)法單獨(dú)創(chuàng)建一個(gè)Volume轴咱,因?yàn)樗皇且粋€(gè)獨(dú)立的K8S資源對(duì)象。

而Persistent Volume 簡(jiǎn)稱(chēng)PV是一個(gè)K8S資源對(duì)象烈涮,所以我們可以單獨(dú)創(chuàng)建一個(gè)PV朴肺。它不和Pod直接發(fā)生關(guān)系,而是通過(guò)Persistent Volume Claim坚洽,簡(jiǎn)稱(chēng)PVC來(lái)實(shí)現(xiàn)動(dòng)態(tài)綁定戈稿。Pod定義里指定的是PVC,然后PVC會(huì)根據(jù)Pod的要求去自動(dòng)綁定合適的PV給Pod使用讶舰。

PV的訪問(wèn)模式有三種:

第一種鞍盗,ReadWriteOnce:是最基本的方式,可讀可寫(xiě)跳昼,但只支持被單個(gè)Pod掛載般甲。

第二種,ReadOnlyMany:可以以只讀的方式被多個(gè)Pod掛載庐舟。

第三種欣除,ReadWriteMany:這種存儲(chǔ)可以以讀寫(xiě)的方式被多個(gè)Pod共享。不是每一種存儲(chǔ)都支持這三種方式挪略,像共享方式历帚,目前支持的還比較少,比較常用的是NFS杠娱。在PVC綁定PV時(shí)通常根據(jù)兩個(gè)條件來(lái)綁定挽牢,一個(gè)是存儲(chǔ)的大小,另一個(gè)就是訪問(wèn)模式摊求。

剛才提到說(shuō)PV與普通Volume的區(qū)別是動(dòng)態(tài)綁定禽拔,我們來(lái)看一下這個(gè)過(guò)程是怎樣的。

image.png

這是PV的生命周期室叉,首先是Provision睹栖,即創(chuàng)建PV,這里創(chuàng)建PV有兩種方式茧痕,靜態(tài)和動(dòng)態(tài)野来。所謂靜態(tài),是管理員手動(dòng)創(chuàng)建一堆PV踪旷,組成一個(gè)PV池曼氛,供PVC來(lái)綁定。動(dòng)態(tài)方式是通過(guò)一個(gè)叫 Storage Class的對(duì)象由存儲(chǔ)系統(tǒng)根據(jù)PVC的要求自動(dòng)創(chuàng)建令野。

一個(gè)PV創(chuàng)建完后狀態(tài)會(huì)變成Available舀患,等待被PVC綁定。

一旦被PVC邦定气破,PV的狀態(tài)會(huì)變成Bound聊浅,就可以被定義了相應(yīng)PVC的Pod使用。

Pod使用完后會(huì)釋放PV现使,PV的狀態(tài)變成Released低匙。

變成Released的PV會(huì)根據(jù)定義的回收策略做相應(yīng)的回收工作。有三種回收策略朴下,Retain努咐、Delete 和 Recycle。Retain就是保留現(xiàn)場(chǎng)殴胧,K8S什么也不做渗稍,等待用戶(hù)手動(dòng)去處理PV里的數(shù)據(jù),處理完后团滥,再手動(dòng)刪除PV竿屹。Delete 策略,K8S會(huì)自動(dòng)刪除該P(yáng)V及里面的數(shù)據(jù)灸姊。Recycle方式拱燃,K8S會(huì)將PV里的數(shù)據(jù)刪除,然后把PV的狀態(tài)變成Available力惯,又可以被新的PVC綁定使用碗誉。

在實(shí)際使用場(chǎng)景里召嘶,PV的創(chuàng)建和使用通常不是同一個(gè)人。這里有一個(gè)典型的應(yīng)用場(chǎng)景:管理員創(chuàng)建一個(gè)PV池哮缺,開(kāi)發(fā)人員創(chuàng)建Pod和PVC弄跌,PVC里定義了Pod所需存儲(chǔ)的大小和訪問(wèn)模式,然后PVC會(huì)到PV池里自動(dòng)匹配最合適的PV給Pod使用尝苇。

前面在介紹PV的生命周期時(shí)铛只,提到PV的供給有兩種方式,靜態(tài)和動(dòng)態(tài)糠溜。其中動(dòng)態(tài)方式是通過(guò)StorageClass來(lái)完成的淳玩,這是一種新的存儲(chǔ)供應(yīng)方式。

使用StorageClass有什么好處呢非竿?除了由存儲(chǔ)系統(tǒng)動(dòng)態(tài)創(chuàng)建蜕着,節(jié)省了管理員的時(shí)間,還有一個(gè)好處是可以封裝不同類(lèi)型的存儲(chǔ)供PVC選用汽馋。在StorageClass出現(xiàn)以前侮东,PVC綁定一個(gè)PV只能根據(jù)兩個(gè)條件,一個(gè)是存儲(chǔ)的大小豹芯,另一個(gè)是訪問(wèn)模式悄雅。在StorageClass出現(xiàn)后,等于增加了一個(gè)綁定維度铁蹈。

比如這里就有兩個(gè)StorageClass宽闲,它們都是用谷歌的存儲(chǔ)系統(tǒng),但是一個(gè)使用的是普通磁盤(pán)握牧,我們把這個(gè)StorageClass命名為slow容诬。另一個(gè)使用的是SSD,我們把它命名為fast沿腰。

在PVC里除了常規(guī)的大小览徒、訪問(wèn)模式的要求外,還通過(guò)annotation指定了Storage Class的名字為fast颂龙,這樣這個(gè)PVC就會(huì)綁定一個(gè)SSD习蓬,而不會(huì)綁定一個(gè)普通的磁盤(pán)。

到這里Kubernetes的整個(gè)存儲(chǔ)系統(tǒng)就都介紹完了措嵌《愕穑總結(jié)一下,兩種存儲(chǔ)卷:普通Volume 和Persistent Volume企巢。普通Volume在定義Pod的時(shí)候直接定義枫慷,Persistent Volume通過(guò)Persistent Volume Claim來(lái)動(dòng)態(tài)綁定。PV可以手動(dòng)創(chuàng)建,也可以通過(guò)StorageClass來(lái)動(dòng)態(tài)創(chuàng)建。

用 Headless Service 來(lái)維持集群成員的穩(wěn)定關(guān)系

1或听、定義:有時(shí)不需要或不想要負(fù)載均衡探孝,以及單獨(dú)的Service IP。遇到這種情況神帅,可以通過(guò)指定Cluster IP(spec.clusterIP)的值為“None”來(lái)創(chuàng)建Headless Service再姑。
2萌抵、和普通Service相比:k8s對(duì)Headless Service并不會(huì)分配Cluster IP找御,kube-proxy不會(huì)處理它們,而且平臺(tái)也不會(huì)為它們進(jìn)行負(fù)載均衡和路由绍填。k8s會(huì)給一個(gè)集群內(nèi)部的每個(gè)成員提供一個(gè)唯一的DNS域名來(lái)作為每個(gè)成員的網(wǎng)絡(luò)標(biāo)識(shí)霎桅,集群內(nèi)部成員之間使用域名通信。

普通Service的Cluster IP 是對(duì)外的讨永,用于外部訪問(wèn)多個(gè)Pod實(shí)例滔驶。而Headless Service的作用是對(duì)內(nèi)的,用于為一個(gè)集群內(nèi)部的每個(gè)成員提供一個(gè)唯一的DNS名字卿闹,這樣集群成員之間就能相互通信了揭糕。所以Headless Service沒(méi)有Cluster IP,這是它和普通Service的區(qū)別锻霎。

3著角、無(wú)頭服務(wù)管理的域名是如下的格式:(service_name).(k8s_namespace).svc.cluster.local。其中的"cluster.local"是集群的域名,除非做了配置旋恼,否則集群域名默認(rèn)就是cluster.local吏口。

4、StatefulSet下創(chuàng)建的每個(gè)Pod的序號(hào)是唯一的冰更。

為了解決名字不穩(wěn)定的問(wèn)題产徊,StatefulSet下創(chuàng)建的每個(gè)Pod的名字不再使用隨機(jī)字符串,而是為每個(gè)pod分配一個(gè)唯一不變的序號(hào)蜀细,比如StatefulSet的名字叫 mysql舟铜,那么第一個(gè)啟起來(lái)的pod就叫 mysql-0,第二個(gè)叫 mysql-1奠衔,如此下去谆刨。

當(dāng)一個(gè)某個(gè)pod掉后,新創(chuàng)建的pod會(huì)被賦予跟原來(lái)pod一樣的名字涣觉。由于pod名字不變所以DNS名字也跟以前一樣痴荐,同時(shí)通過(guò)名字還能匹配到原來(lái)pod用到的存儲(chǔ),實(shí)現(xiàn)狀態(tài)保存官册。

StatefulSet下創(chuàng)建的每個(gè)Pod生兆,得到一個(gè)對(duì)應(yīng)的DNS子域名,格式如下:

(podname).(governing_service_domain),這里 governing_service_domain是由StatefulSet中定義的serviceName來(lái)決定。

舉例子鸦难,無(wú)頭服務(wù)管理的kafka的域名是:kafka.test.svc.cluster.local,

創(chuàng)建的Pod得到的子域名是 kafka-1.kafka.test.svc.cluster.local根吁。注意這里提到的域名,都是由kuber-dns組件管理的集群內(nèi)部使用的域名合蔽,可以通過(guò)命令來(lái)查詢(xún):

$ nslookup my-nginx
Server:    192.168.16.53
Address 1: 192.168.16.53

Name:      my-nginx
Address 1: 192.168.16.132

而普通Service情況下击敌,Pod名字后面是隨機(jī)數(shù),需要通過(guò)Service來(lái)做負(fù)載均衡拴事。

當(dāng)一個(gè)StatefulSet掛掉沃斤,新創(chuàng)建的StatefulSet會(huì)被賦予跟原來(lái)的Pod一樣的名字,通過(guò)這個(gè)名字來(lái)匹配到原來(lái)的存儲(chǔ)刃宵,實(shí)現(xiàn)了狀態(tài)保存衡瓶。因?yàn)樯衔奶岬搅耍總€(gè)Pod的標(biāo)識(shí)附著在Pod上牲证,無(wú)論pod被重新調(diào)度到了哪里哮针。

StatefuleSet示例

Kafka和zookeeper是在兩種典型的有狀態(tài)的集群服務(wù)。首先kafka和zookeeper都需要存儲(chǔ)盤(pán)來(lái)保存有狀態(tài)信息坦袍,其次kafka和zookeeper每一個(gè)實(shí)例都需要有對(duì)應(yīng)的實(shí)例Id(Kafka需要broker.id,zookeeper需要my.id)來(lái)作為集群內(nèi)部每個(gè)成員的標(biāo)識(shí)十厢,集群內(nèi)節(jié)點(diǎn)之間進(jìn)行內(nèi)部通信時(shí)需要用到這些標(biāo)識(shí)。

有兩個(gè)原因讓 [ZooKeeper] 成為 StatefulSet 的好例子捂齐。首先蛮放,StatefulSet 在其中演示了運(yùn)行分布式、強(qiáng)一致性存儲(chǔ)的應(yīng)用的能力辛燥;其次筛武,ZooKeeper 也是 Apache Hadoop 和 Apache Kafka 在 Kubernetes 上運(yùn)行的前置條件。在 Kubernetes 文檔中有一個(gè) 深度教程 說(shuō)明了在 Kubernetes 集群上部署 ZooKeeper Ensemble 的過(guò)程挎塌,這里會(huì)簡(jiǎn)要描述一下其中的關(guān)鍵特性徘六。

具體的部署過(guò)程包括以下幾個(gè)部署:

(1) Persistent Volume 存儲(chǔ)的創(chuàng)建

(2) StatefulSet(Petset)資源的創(chuàng)建

(3) headless服務(wù)的創(chuàng)建

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市榴都,隨后出現(xiàn)的幾起案子待锈,更是在濱河造成了極大的恐慌,老刑警劉巖嘴高,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件竿音,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拴驮,警方通過(guò)查閱死者的電腦和手機(jī)春瞬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)套啤,“玉大人宽气,你說(shuō)我怎么就攤上這事。” “怎么了萄涯?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵绪氛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我涝影,道長(zhǎng)枣察,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任燃逻,我火速辦了婚禮序目,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唆樊。我一直安慰自己宛琅,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布逗旁。 她就那樣靜靜地躺著,像睡著了一般舆瘪。 火紅的嫁衣襯著肌膚如雪片效。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天英古,我揣著相機(jī)與錄音淀衣,去河邊找鬼。 笑死召调,一個(gè)胖子當(dāng)著我的面吹牛膨桥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播唠叛,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼只嚣,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了艺沼?” 一聲冷哼從身側(cè)響起册舞,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎障般,沒(méi)想到半個(gè)月后调鲸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡挽荡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年藐石,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片定拟。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡于微,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情角雷,我是刑警寧澤祸穷,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站勺三,受9級(jí)特大地震影響雷滚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吗坚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一祈远、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧商源,春花似錦车份、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瓦胎。三九已至笼痹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留器罐,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓渐行,卻偏偏與公主長(zhǎng)得像轰坊,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子祟印,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

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