本文轉載自https://freeaihub.com/kubernetes/service-selector.html,可在線進行體驗
K8S中的Service是一個抽象概念,它定義了一個服務的多個pod邏輯合集和訪問pod的策略,一般把service稱為微服務
舉個例子:一個a服務運行3個pod,b服務怎么訪問a服務的pod,pod的ip都不是持久化的重啟之后就會有變化。
這時候b服務可以訪問跟a服務綁定的service,service信息是固定的提前告訴b就行了,service通過Label Selector跟a服務的pod綁定,無論a的pod如何變化對b來說都是透明的。
創建服務對應的后端
先創建一個可以充當 backend pods 的 deployment
文件名:service-deployment-hello.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
labels:
app: hello
spec:
selector:
matchLabels:
app: hello
replicas: 5
template:
metadata:
labels:
app: hello
spec:
containers:
- name: hello
image: nginx
imagePullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 100Mi
ports:
- containerPort: 80
這個普通的 Deployment。唯一需要注意的是 ports 信息,它顯示了這個 Pod 暴露了哪些端口,類似於 docker 中 expose 的意義。但是一般來講,這個信息是沒必要的。因為 Pod 具有唯一的集群內可訪問的 ip, 不會跟其他 Pod 產生沖突,任何監聽了 0.0.0.0 這樣地址的服務都可以通過 :
的方式訪問。這里寫上的作用主要是提供一些更明確的信息,也方便一些工具比如 kubectl 的使用而已。
在終端執行如下命令創建 deployment:
ctr -n k8s.io i import /share/images/nginx.tar
kubectl create -f /share/lesson/kubernetes/service-deployment-hello.yaml
看下最終我們創建出來的 pods 結果
kubectl get po
然后我們來創建對應的 Service
文件名:service-svc-hello.yaml
apiVersion: v1
kind: Service
metadata:
name: hello
labels:
app: hello
spec:
ports:
- port: 80
targetPort: 80
selector:
app: hello
Service 的 spec 部分就是它的主要信息,主要包含以下內容:
ports
: 是一個端口信息列表,也就是說一個 Service 可以管理多個端口的訪問。本身 Pod 可以對外暴露多個端口port
: service 綁定的端口,也就是這個 Service 所對應的 ip 監聽的端口targetPort
: backend 的端口,也就是 Pod 所暴露的端口
selector
: 標簽選擇,符合這個標簽的 Pod 會作為這個 Service 的 backend。
在終端執行如下命令創建並查看:
kubectl create -f /share/lesson/kubernetes/service-svc-hello.yaml
kubectl get svc
這條命令用來查看 default namespace 下的 Service 信息列表。因為 default namespace 是默認值,可不寫。 -o wide
會增加一些額外的信息展示,看下結果:
除了 yaml 中我們填寫的信息之外,kubernetes 幫我們補充了其他的默認信息:
Type
: 類型,決定着 Service 如何對外提供服務。因為我們在 yaml 里未設置,所以這里用的默認值:ClusterIP
, 具體有哪些可選的會在下面講解。Cluster IP
: 系統幫我們自動生成的 ip 地址。這個 ip 地址的范圍是可配置的,並且只能在集群內部訪問。
看下完整的 yaml:
kubectl get svc hello -o yaml
這里又比剛才的列表頁顯示了更多的信息,需要注意的如下:
sessionAffinity
: 這是負載均衡里面比較常見的一個概念,就是讓來自於同一個 client 的請求落到同一個 backend 上。默認為 None。ports[0].protocol
: 端口協議,默認為 TCP ,目前只支持 TCP/UDP。
容器的輕量級特性,讓作為 backend 的 Pod 可以比傳統方式更加隨意地起停。Service 只記錄了 labelSelector,但具體的映射關系仍然需要有個地方記錄下來。當然這個工作是由 kubernetes 來完成,而不是用戶。記錄這個映射關系的資源就是 Endpoints,同一個 namespace 下與 Service 同名的 Endpoints Resource 即是這個映射關系的管理者。剛才我們創建好了 Service 之后,Kubernetes 已經幫我們創建好了相應的 Endpoints 資源:
kubectl get ep hello -o yaml
注:ep 是 endpoints 的簡稱。
主要信息在 subsets 里面,addresses 列表記錄了每個 backend 的信息,這里面的兩個 Pod 信息就是我們剛才創建的兩個 Pod 。addresses 里面記錄了 Pod 的 ip、所在的主機名稱、以及具體的 Resource Object 的引用。
下面我們可以通過訪問這個 service 的 ip(即CLUSTERIP) 來訪問這個服務了。
helloclusterip=$(kubectl get svc hello -o go-template --template='{{.spec.clusterIP}}')
echo $helloclusterip
curl $helloclusterip
其中$helloclusterip
即上面的 service 生成之后所分配的 ip ,每次創建的 ip 都是不一樣的。
注意:
- 我們的hello程序運行了一個頁面,其中顯示了運行其的 host 的 hostname, 在這個場景中就是 pod 的 hostname, 而 pod 的 hostname 一般就是其名字。
- 這里我們可以觀察出來, Service 做了負載均衡,將流量轉發到了不同的pod實例上。
DNS
上面的使用 ip 的使用有一個很致命的問題,就是它不好記,而且不方便使用。如果你讓系統自動分配,那很難知道生成的結果是什么,無法提前配置。如果是自己選好固定的 ip ,服務多了又不好管理。而服務發現就解決了這個問題。我們仍然拿剛才的實驗繼續驗證下。
首先要注意的是,service 的 ip 是可以在 kubernetes 的 node 上和 pod 里面直接訪問的。但 dns 只能在 pod 里面訪問,因為它需要配置 kubernetes 的 dns 服務為解析服務器,而且還要配置其他參數。 kubernetes 在啟動每個 pod 時都會將這些配置好。
准備busybox pod
ctr -n k8s.io i import /share/images/busybox.latest.tar
kubectl create -f /share/lesson/kubernetes/busybox.yaml
進入busybox pod
kubectl exec -it busybox sh
使用域名訪問服務,而不是IP
wget -O- hello
可以看到,運行結果與上面通過 curl 是一樣的。那么它是怎么實現的呢?
我們用 nslookup 命令來探究下:
nslookup hello
nslookup 命令用於解析某一個域名,會得到如下類似的結果
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: hello.default.svc.cluster.local
Address: 10.96.177.43
這里我們需要注意的是:
(1)server 的地址,這里顯示的 server 就是 dns server, 它就是 kubernetes 中 dns 服務的地址。
kubectl get svc -n kube-system
在部署完集群后,這個 svc 就自動創建了。
(2)解析到的名字
dns server 幫我們解析到了正確的 ip ,但是后面顯示的域名比較長。這里涉及到了一個 dns search-path 的概念。我們看下 dns resolve 的配置文件。
cat /etc/resolv.conf
簡單來講,當我們查詢 hello 這個域名時,dns server 會幫我們查詢下面的幾個名字:
- hello
- hello.default.svc.cluster.local
- hello.svc.cluster.local
- hello.cluster.local
其中 hello.default.svc.cluster.local
是正確的域名,它的格式為..svc.
。其中 suffix 是集群配置,而 namespace 則視具體的情況而定。有了 search path, kubernetes 的服務不僅實現了通過域名訪問,而且實現了最簡單的僅通過名字即可訪問的模式。在實際的業務場景中,這是非常便利的一個條件。想象一個常見的場景, 有一個 namespace 叫 http,里面有服務 a 、 b, 有一個 namespace 叫 storage, 里面有 redis、db 兩個服務,那么 a、b 之間的互相訪問只需要知道對方的名字即可,不需要知道它們所處的 namespace(可以方便地一起遷移)。而 a b 想要訪問 redis 或者 db, 那么也僅僅需要用 redis.storage
或者 db.storage
這兩個名字即可。