4. Service

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:使用雲提供程序的負載平衡器對外公開服務,NodePortClusterIp服務將自動創建且外部負載平衡器將路由到過去;
  • 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:80curl 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章