4. Service
k8s 中的Pod是朝生夕死的,並且是不會重生的,尤其是在ReplicaSets中動態創建或銷燬Pod。然而每個Pod可以獲取自己的IP地址,即使這些IP地址是不穩定的(重啓等行爲可能會導致IP變動),那在集羣中,一些Pods怎麼向其他的Pods提供可依賴的穩定服務?就像之前所說栗子,前端Pod怎麼如何找到、追蹤後端Pod。k8s定義了一種抽象,即Service
,在集羣中定義了一組邏輯Pod和訪問Pod的策略(有時稱爲微服務),Service中的目標Pod通常是由標籤選擇器(Label Selector)決定的。
一個運行着3個副本的圖像處理的後端,這些副本都是可替換的(前端並不關心它們使用的是哪個後端),組成後端的實際Pods可能會變化,前端不需要知道和追蹤實際的後端,Service這層抽象就是用來解耦兩者關係的。對於Kubernetes本機App,它提供了簡單的Endpoints API,當一個Service中的Pod發生變化時就會更新;而非本機App,Kubernetes爲Service提供了一個基於虛擬IP的橋接器,重定向到後端Pod。
4.1 定義Service
K8S中的Service和Pod類似是一個REST對象(REST對象通過POST方法請求apiserver創建新的實例),比如下面的栗子:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
上述yaml文件,創建了一個新的Service對象,名字爲my-service
,目標是所有帶MyApp
標籤的Pods上的9376 TCP端口,該Service將會被分配到一個IP地址(即集羣IP“cluster IP”),該IP主要用於服務之間的代理。服務選擇器(selector)將被連續評估,評估結果將被POST到一個也被命名爲“my-service”的Endpoints的對象。Service可以將傳入端口映射到任何targetPort
,默認情況targetPort
將會設置成和port
字段一樣的值,此外,targetPort
也可以是一個引用後端Pod中端口名稱的字符串,分配給該名稱的實際端口號在每個後端POD中可能不同,這爲部署和升級Service提供了很大的靈活性(比如可以在下個後端版本迭代中可以更改端口號而不用修改客戶端)。TCP
是Service的默認協議,可以修改爲支持的其他協議(如TCP
,UDP
,HTTP
,HTTPS
)。K8S還支持在一個Service中暴露多個端口,每個端口可以有定義相同或不同的協議,但此時必須指定每個端口的名字(爲了避免endpoints的歧義),下面是栗子:
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
注:端口名只能包含小寫字母、數字和-
,且只能以字母、數字開頭和結尾。
Service抽象的訪問Pod,還可以抽象其他類型的後端,如下場景:
- 測試環境中使用自己的數據庫,但實際的生產環境中使用外部的數據庫集羣;
- 將服務指定另外一個Namespace中或者另一個集羣中的服務;
- 一部分服務遷到K8S中,一部分服務還在外部運行;
ExternalName
Service是一個特例,使用DNS名字替換selector,此時也不需要選擇器;
上述的場景中就不能再指定選擇器了,此時的service就不需要selector,如:
kind: Service
apiVersion: v1
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
由於不指定選擇器,那相應的Endpoints對象也不會被創建,我們還可以手動指定該服務映射到我們自己指定的Endpoints(1.2.3.4:9376):
kind: Endpoints
apiVersion: v1
metadata:
name: my-service
subsets:
- addresses:
- ip: 1.2.3.4
ports:
- port: 9376
注:Endpoint IP不能是環回(127.0.0.0/8)、鏈路本地(169.254.0.0/16)或鏈路本地多播(224.0.0.0/24),也不能是其他kubernetes服務的集羣IP,因爲kube代理組件還不支持虛擬IP作爲目標。
4.2 虛擬IP和Service代理
K8S集羣中的每個節點都運行着一個kube-proxy,它負責爲ExternalName以外的類型的Service實現一種虛擬IP的形式。
4.3 發佈Service
有時會將App的某部分(如前端)暴露到集羣外部的IP地址,K8S中的ServiceTypes
可以指定Service的類型,Type
的值有:
ClusterIP
:在集羣內部暴露服務,只能在集羣內部訪問,它是默認的服務類型;NodePort
:在每個節點IP上的靜態端口上暴露服務,ClusterIp
服務且自動創建且NodePort
服務將路由過去。在集羣外部可以通過<nodeip>:<nodeport>
形式的請求訪問NodePort
服務;LoadBalancer
:使用雲提供程序的負載平衡器對外公開服務,NodePort
和ClusterIp
服務將自動創建且外部負載平衡器將路由到過去;ExternalName
:通過返回CNAME
記錄及其值,將服務映射到externalname
字段(例如foo.bar.example.com)的內容,未設置任何類型的代理,需要使用kube-dns
(版本1.7+);
4.4 將Pod暴露到集羣中
下面是創建Nginx的Pod(需要創建一個K8S的Deployment的配置):
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 2
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: nginx
ports:
- containerPort: 80
查看Pod的狀態:
# 創建
kubectl apply -f ./run-my-nginx.yaml
# 查看狀態
kubectl get pods -l run=my-nginx -o wide
# 查看Pod的IP
kubectl get pods -l run=my-nginx -o yaml | grep podIP
一旦創建了Deployment,K8S的master將會安排該應用的實例到集羣中的節點上(集羣中所有節點都可訪問),由Deployment Controller持續監測這些實例,如果某些節點掛了,那Deployment Controller將會在其他節點上年補充相對應數量的實例,這種行爲提供一定的自愈能力;
4.5 創建Service
在將Pod暴露到集羣后,理論上用戶可以直接和Pod進行通信,但如果節點掛了,那節點上的Pod也會隨之掛掉,Deployment將會創建一個新的,但他們IP不同,Service主要解決的問題就是這個。Service定義了一組運行在集羣中的邏輯Pod,它們功能相同,在創建時,每個Service都會被分配一個唯一IP(即集羣IP),這個IP只要Service活着就不會變。Pod可以通過配置和Service通信,知道到Service的通信將自動負載平衡到某個POD(該POD是Service的一個成員)。
可以使用kubectl expose
命令爲之前的Nginx創建2個副本:
kubectl expose deployment/my-nginx
service/my-nginx exposed
其實上述是和kubectl apply -f ./service-run-my-nginx.yaml
一樣的,配置文件如下:
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: my-nginx
該配置文件(規範specification)創建了一個Service,指向所有帶有run: my-nginx
標籤(2個Pod副本)的Pod的80目標端口(TCP協議),並將該Service暴露到抽象Service端口(目標端口targetPort:容器接受通信的端口;端口port:抽象Service的端口,可以是任何其他Pod和Service通信的端口)。查看創建的Service:
get svc my-nginx
# 結果
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.102.226.25 <none> 80/TCP 16m
服務是由一羣Pod支持的,這些Pod通過endpoints進行暴露。Service的選擇器將會被持續評估且結果將會POST到一個Endpoints對象(名字也爲my-nginx
)。檢查endpoints(2種方式),注意IP和第一步中創建的Pod一樣:
# 方式1
kubectl describe svc my-nginx
# 信息如下
Name: my-nginx
Namespace: default
Labels: run=my-nginx
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"run":"my-nginx"},"name":"my-nginx","namespace":"default"},"spe...
Selector: run=my-nginx
Type: ClusterIP
IP: 10.102.226.25
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.1.2:80,10.244.2.2:80
Session Affinity: None
Events: <none>
# 方式2
kubectl get ep my-nginx
# 輸出如下
NAME ENDPOINTS AGE
my-nginx 10.244.1.2:80,10.244.2.2:80 24m
應該可以在集羣中任意節點通過<CLUSTER-IP>:<PORT>
(這裏就是curl 10.244.2.2:80
和curl 10.244.1.2:80
)的方式訪問剛剛的Nginx服務,注意這裏的IP完全是虛擬的。
4.6 獲取Service
K8S支持2種獲取Service的主模式:環境變量和DNS,前者是開箱即用的,後者需要CoreDNS集羣插件。
1.環境變量
當Pod運行在Node上時,kubelet將爲每個活着的Service添加一系列的環境變量,但引出了一個順序的問題,可以查看之前運行的my-nginx的Pod的環境變量:
kubectl exec my-nginx-64fc468bd4-6p9bt -- printenv | grep SERVICE
# 輸出
KUBERNETES_SERVICE_PORT=443
JENKINS_SERVICE_HOST=10.97.164.246
JENKINS_SERVICE_PORT_WEB=80
JENKINS_SERVICE_PORT_AGENT=50000
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT_HTTPS=443
JENKINS_SERVICE_PORT=80
另一個缺點時調度器可能會將所有的Pod(本身+副本)都放在同一個機器上,這樣如果該機器掛掉了就會導致整個Service掛掉,這個場景可以通過殺死2個Pod等待部署重建它們實現(此時Service在副本之前存在):
# 移除所有my-nginx的Pod(Service不會移除)
kubectl scale deployment my-nginx --replicas=0
# 創建2個my-nginx的Pod
kubectl scale deployment my-nginx --replicas=2
# 查看
kubectl get pods -l run=my-nginx -o wide
2.DNS
K8S提供了DNS集羣插件Service,它將會自動給其他Service分配dns名字,可以查看當前集羣是否運行該插件:
# 查看
kubectl get services kube-dns --namespace=kube-system
# 結果
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 5d1h
也可以使用下面的方式來檢測:
# 等待一會將會進入一個容器
kubectl run curl --image=radial/busyboxplus:curl -i --tty
# 執行
nslookup my-nginx
# 退出容器
exit
4.7 讓Service安全【未完】
上述的案例只是在集羣內部訪問Service,在正式發佈應用到互聯網上,我們需要保證通信頻道是安全的,因此要做好下面的工作:
- HTTPS的自簽名證書(除非已經有了一個身份認證);
- 配置爲使用證書的nginx服務器;
- 使證書可供Pods訪問的密鑰;
docker tag registry.cn-shanghai.aliyuncs.com/google_k8s/node-hello:1.0 gcr.io/hello-minikube-zero-install/hello-node
docker rmi registry.cn-shanghai.aliyuncs.com/google_k8s/node-hello:1.0