在做傳統業務開發的時候,當我們的服務提供方有多個範例時,往往我們需要將對方的服務列表儲存在本地,然後採用一定的演演算法進行呼叫;當服務提供方的列表變化時還得及時通知呼叫方。
student:
url:
- 192.168.1.1:8081
- 192.168.1.2:8081
這樣自然是對雙方都帶來不少的負擔,所以後續推出的服務呼叫框架都會想辦法解決這個問題。
以 spring cloud
為例:
服務提供方會向一個服務註冊中心註冊自己的服務(名稱、IP等資訊),使用者端每次呼叫的時候會向服務註冊中心獲取一個節點資訊,然後發起呼叫。
但當我們切換到 k8s
後,這些基礎設施都交給了 k8s
處理了,所以 k8s
自然得有一個元件來解決服務註冊和呼叫的問題。
也就是我們今天重點介紹的 service
。
在介紹 service
之前我先調整了原始碼:
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 的方式呼叫服務提供者的服務,然後重新打包。
make docker
同時也新增了一個 deployment-service.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: k8s-combat-service # 通過標籤選擇關聯
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 # 通過標籤選擇關聯
type: ClusterIP
ports:
- port: 8081 # 本 Service 的埠
targetPort: 8081 # 容器埠
name: app
使用相同的映象部署一個新的 deployment,名稱為 k8s-combat-service
,重點是新增了一個kind: Service
的物件。
這個就是用於宣告 service
的元件,在這個元件中也是使用 selector
標籤和 deployment
進行了關聯。
也就是說這個 service
用於服務於名稱等於 k8s-combat-service
的 deployment
。
下面的兩個埠也很好理解,一個是代理的埠, 另一個是 service 自身提供出去的埠。
至於 type: ClusterIP
是用於宣告不同型別的 service
,除此之外的型別還有:
NodePort
LoadBalancer
ExternalName
ClusterIP
,現在不用糾結這幾種型別的作用,後續我們在講到 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
呼叫 k8s-combat-service
的介面。
resp, err := http.Get("http://k8s-combat-service:8081/ping")
其中 k8s-combat-service
服務的域名就是他的服務名稱。
如果是跨 namespace 呼叫時,需要指定一個完整名稱,在後續的章節會演示。
我們整個的呼叫流程如下:
相信大家也看得出來相對於 spring cloud
這類微服務架構提供的使用者端負載方式,service
是一種伺服器端負載,有點類似於 Nginx
的反向代理。
為了更直觀的驗證這個流程,此時我將 k8s-combat-service
的副本數增加到 2:
spec:
replicas: 2
只需要再次執行:
❯ k apply -f deployment/deployment-service.yaml
deployment.apps/k8s-combat-service configured
service/k8s-combat-service unchanged
不管我們對
deployment
的做了什麼變更,都只需要apply
這個yaml
檔案即可, k8s 會自動將當前的deployment
調整為我們預期的狀態(比如這裡的副本數量增加為 2);這也就是k8s
中常說的宣告式 API。
可以看到此時 k8s-combat-service
的副本數已經變為兩個了。
如果我們此時檢視這個 service
的描述時:
❯ k describe svc k8s-combat-service |grep Endpoints
Endpoints: 192.168.130.133:8081,192.168.130.29:8081
會發現它已經代理了這兩個 Pod
的 IP。
此時我進入了 k8s-combat-7867bfb596-67p5m
的容器:
k exec -it k8s-combat-7867bfb596-67p5m bash
curl http://127.0.0.1:8081/service
並執行兩次 /service
介面,發現請求會輪訓進入 k8s-combat-service
的代理的 IP 中。
由於 k8s service
是基於 TCP/UDP
的四層負載,所以在 http1.1
中是可以做到請求級的負載均衡,但如果是類似於 gRPC
這類長連結就無法做到請求級的負載均衡。
換句話說 service
只支援連線級別的負載。
如果要支援 gRPC
,就得使用 Istio 這類服務網格,相關內容會在後續章節詳解。
總的來說 k8s service
提供了簡易的服務註冊發現和負載均衡功能,當我們只提供 http 服務時是完全夠用的。
相關的原始碼和 yaml 資原始檔都存在這裡:
https://github.com/crossoverJie/k8s-combat
作者: crossoverJie
歡迎關注博主公眾號與我交流。
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出, 如有問題, 可郵件(crossoverJie#gmail.com)諮詢。