K8S入門到實戰(zhàn)--跨服務調(diào)用
背景
在做傳統(tǒng)業(yè)務開發(fā)的時候,當我們的服務提供方有多個實例時,往往我們需要將對方的服務列表保存在本地,然后采用一定的算法進行調(diào)用;當服務提供方的列表變化時還得及時通知調(diào)用方。
student:
url:
- 192.168.1.1:8081
- 192.168.1.2:8081
這樣自然是對雙方都帶來不少的負擔,所以后續(xù)推出的服務調(diào)用框架都會想辦法解決這個問題。
以 spring cloud 為例:
圖片
服務提供方會向一個服務注冊中心注冊自己的服務(名稱、IP等信息),客戶端每次調(diào)用的時候會向服務注冊中心獲取一個節(jié)點信息,然后發(fā)起調(diào)用。
但當我們切換到 k8s 后,這些基礎設施都交給了 k8s 處理了,所以 k8s 自然得有一個組件來解決服務注冊和調(diào)用的問題。
也就是我們今天重點介紹的 service。
service
在介紹 service 之前我先調(diào)整了源碼:
func main() {
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
name, _ := os.Hostname()
log.Printf("%s ping", name)
fmt.Fprint(w, "pong")
})
http.HandleFunc("/service", func(w http.ResponseWriter, r *http.Request) {
resp, err := http.Get("http://k8s-combat-service:8081/ping")
if err != nil {
log.Println(err)
fmt.Fprint(w, err)
return
}
fmt.Fprint(w, resp.Status)
})
http.ListenAndServe(":8081", nil)
}
新增了一個 /service 的接口,這個接口會通過 service 的方式調(diào)用服務提供者的服務,然后重新打包。
make docker
同時也新增了一個 deployment-service.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: k8s-combat-service # 通過標簽選擇關聯(lián)
name: k8s-combat-service
spec:
replicas: 1
selector:
matchLabels:
app: k8s-combat-service
template:
metadata:
labels:
app: k8s-combat-service
spec:
containers:
- name: k8s-combat-service
image: crossoverjie/k8s-combat:v1
imagePullPolicy: Always
resources:
limits:
cpu: "1"
memory: 100Mi
requests:
cpu: "0.1"
memory: 10Mi
---
apiVersion: v1
kind: Service
metadata:
name: k8s-combat-service
spec:
selector:
app: k8s-combat-service # 通過標簽選擇關聯(lián)
type: ClusterIP
ports:
- port: 8081 # 本 Service 的端口
targetPort: 8081 # 容器端口
name: app
使用相同的鏡像部署一個新的 deployment,名稱為 k8s-combat-service,重點是新增了一個kind: Service 的對象。
這個就是用于聲明 service 的組件,在這個組件中也是使用 selector 標簽和 deployment 進行了關聯(lián)。
也就是說這個 service 用于服務于名稱等于 k8s-combat-service 的 deployment。
下面的兩個端口也很好理解,一個是代理的端口, 另一個是 service 自身提供出去的端口。
至于 type: ClusterIP 是用于聲明不同類型的 service,除此之外的類型還有:
- NodePort
- LoadBalancer
- ExternalName等類型,默認是 ClusterIP,現(xiàn)在不用糾結這幾種類型的作用,后續(xù)我們在講到 Ingress 的時候會具體介紹。
負載測試
我們先分別將這兩個 deployment 部署好:
k apply -f deployment/deployment.yaml
k apply -f deployment/deployment-service.yaml
? k get pod
NAME READY STATUS RESTARTS AGE
k8s-combat-7867bfb596-67p5m 1/1 Running 0 3h22m
k8s-combat-service-5b77f59bf7-zpqwt 1/1 Running 0 3h22m
由于我新增了一個 /service 的接口,用于在 k8s-combat 中通過 service 調(diào)用 k8s-combat-service 的接口。
resp, err := http.Get("http://k8s-combat-service:8081/ping")
其中 k8s-combat-service 服務的域名就是他的服務名稱。
如果是跨 namespace 調(diào)用時,需要指定一個完整名稱,在后續(xù)的章節(jié)會演示。
我們整個的調(diào)用流程如下:
圖片
相信大家也看得出來相對于 spring cloud 這類微服務框架提供的客戶端負載方式,service 是一種服務端負載,有點類似于 Nginx 的反向代理。
為了更直觀的驗證這個流程,此時我將 k8s-combat-service 的副本數(shù)增加到 2:
spec:
replicas: 2
只需要再次執(zhí)行:
? k apply -f deployment/deployment-service.yaml
deployment.apps/k8s-combat-service configured
service/k8s-combat-service unchanged
圖片
不管我們對 deployment 的做了什么變更,都只需要 apply 這個 yaml 文件即可, k8s 會自動將當前的 deployment 調(diào)整為我們預期的狀態(tài)(比如這里的副本數(shù)量增加為 2);這也就是 k8s 中常說的聲明式 API。
可以看到此時 k8s-combat-service 的副本數(shù)已經(jīng)變?yōu)閮蓚€了。如果我們此時查看這個 service 的描述時:
? k describe svc k8s-combat-service |grep Endpoints
Endpoints: 192.168.130.133:8081,192.168.130.29:8081
會發(fā)現(xiàn)它已經(jīng)代理了這兩個 Pod 的 IP。
圖片
此時,我進入了 k8s-combat-7867bfb596-67p5m 的容器:
k exec -it k8s-combat-7867bfb596-67p5m bash
curl http://127.0.0.1:8081/service
并執(zhí)行兩次 /service 接口,發(fā)現(xiàn)請求會輪訓進入 k8s-combat-service 的代理的 IP 中。
由于 k8s service 是基于 TCP/UDP 的四層負載,所以在 http1.1 中是可以做到請求級的負載均衡,但如果是類似于 gRPC 這類長鏈接就無法做到請求級的負載均衡。
換句話說 service 只支持連接級別的負載。
如果要支持 gRPC,就得使用 Istio 這類服務網(wǎng)格,相關內(nèi)容會在后續(xù)章節(jié)詳解。
總結
總的來說, k8s service 提供了簡易的服務注冊發(fā)現(xiàn)和負載均衡功能,當我們只提供 http 服務時是完全夠用的。