Kubernetes(k8s)中文文件 管理應用:連線應用_Kubernetes中文社群
譯者:it2afl0rd
Kubernetes容器連線模型
既然已經有了一個可持續執行的、可複製的應用,現在就可以在網路中將它暴露出來了。在討論Kubernetes的網路連線方式之前,很值得和Docker的常規網路連線方式做個對比。
Dokcer預設使用私有網路連線方式,所以只有在同一臺物理機器上的容器之前才可以通訊。為了能讓Docker容器可以跨節點通訊,必須要給機器的IP地址分配埠號,這個埠之後會被用來轉發或者路由給容器。很明顯,這意味著容器要麼很小心地協調使用埠,要麼有動態分配地埠。
在一定的規模下,為多個開發者協調埠號非常困難。這也會把叢集級別的問題暴露給使用者,這是在使用者的控制之外的。Kubernetes假定pods之間是可以通訊的,不管它們落到哪個主機上。我們給每個pod指定叢集私有的IP地址(cluster-private-IP address),所以不需要顯示地建立pod之間的連結,也不需要對映容器的埠到主機的埠。這意味著
這個指南中用了一個簡單的nginx服務來演示驗證這個概念(proof of concept)。同樣的原理也在一個更完整的Jenkins CI 應用中體現了。
在叢集中暴露Pod
在前面的例子中已經演示過,讓我們把注意力集中在網路的視角在來一次。建立一個nginx的 Pod ,請注意它定義了容器的埠:
$ cat nginxrc.yaml apiVersion: v1 kind: ReplicationController metadata: name: my-nginx spec: replicas: 2 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx ports: - containerPort: 80
這使得它從叢集中的任一節點都可以被訪問到。檢查一下供pod執行的節點:
$ kubectl create -f ./nginxrc.yaml $ kubectl get pods -l app=nginx -o wide my-nginx-6isf4 1/1 Running 0 2h e2e-test-beeps-minion-93ly my-nginx-t26zt 1/1 Running 0 2h e2e-test-beeps-minion-93ly
檢查pod的IP地址:
$ kubectl get pods -l app=nginx -o json | grep podIP "podIP": "10.245.0.15", "podIP": "10.245.0.14",
你應該可以ssh到叢集裡的任何一個節點,而且用curl也能夠訪問這兩個IP。要注意的是容器並沒有用節點的80埠,也沒用任何特殊的會把流量路由到pod的NAT規則。這意味著你可以在同一個節點上用同樣的 containerPort 配置執行多個nginx pod,而且通過IP就可以在其他pod或者叢集裡的其他節點訪問它們。和Docker類似,埠也可以在節點的網路介面中釋出出來,但是在Kubernetes的這種網路模型下,這樣的需求從根本上減少了。
建立Service
現在我們有了執行態的nginx,它們執行在一個水平的,叢集範圍的地址空間內。理論上,我們已經可以和這些pod直接互動了,但是如果一個節點死掉了會發生什麼?它裡面的Pod也會死掉,然後Replication Controller會建立一個新的Pod,但是IP是不一樣的。這就是Service可以解決的問題。
Kubernetes Service是對在叢集中某處執行的一系列Pod的邏輯集合的抽象定義,這些Pod提供的功能是一樣的。每個Service被建立的時候會被分配一個唯一的IP地址(也叫 clusterIP )。這個地址和Service繫結,只要Service活著就不會改變。Pod可以配置成和Service互動,並且知道和Service的通訊會被自動地負載均衡到Service成員中的某個Pod。
可以用下面的yaml為兩個nginx副本建立一個Service:
$ cat nginxsvc.yaml apiVersion: v1 kind: Service metadata: name: nginxsvc labels: app: nginx spec: ports: - port: 80 protocol: TCP selector: app: nginx
這個定義會建立一個Service,這個Service會把帶有app=nginx Label的Pod的TCP 80埠暴露到Service的抽象埠(targetPort:是容器可以接收流量的埠,port:是Service的抽象埠,可以是除用來訪問Service的埠之外的任何埠)。在Service定義中支援的所有的域可以在Service API 物件中檢視。
檢視Service:
$ kubectl get svc NAME LABELS SELECTOR IP(S) PORT(S) nginxsvc app=nginx app=nginx 10.0.116.146 80/TCP
在前面提到過,Service是由一組Pod支撐的。這些Pod通過endpoints暴露出來。Service會持續評估Selector,並把結果傳送給Endpoint物件(也叫做nginxsvc)。當一個Pod死了之後,它就會被自動地從Endpoint裡面刪掉,能夠匹配Service的Selector的新Pod會被自動加到Endpoint裡。檢查Endpoint的時候也會看到IP和前一步裡建立的Pod是一樣的:
$ kubectl describe svc nginxsvc Name: nginxsvc Namespace: default Labels: app=nginx Selector: app=nginx Type: ClusterIP IP: 10.0.116.146 Port: <unnamed> 80/TCP Endpoints: 10.245.0.14:80,10.245.0.15:80 Session Affinity: None No events. $ kubectl get ep NAME ENDPOINTS nginxsvc 10.245.0.14:80,10.245.0.15:80
現在你應該可以從叢集裡的任一節點上用curl命令訪問10.0.116.146:80上的nginx Service了。要注意的是Service的IP完全是虛擬的,跟物理網路沒有關係。
訪問Service
Kubernetes支援兩種主要的模式來發現Service:環境變數和DNS。環境變數在安裝之後就可以直接使用,DNS模式需要kube-dns 叢集外掛。
環境變數
當一個Pod在某個節點上執行的時候, kubelet 為每個活躍的Service新增一系列的環境變數。這會引入環境變數排序的問題。想知道為什麼,檢查一下執行中的nginx Pod的環境:
$ kubectl exec my-nginx-6isf4 -- printenv | grep SERVICE KUBERNETES_SERVICE_HOST=10.0.0.1 KUBERNETES_SERVICE_PORT=443
注意這裡並沒有提到Service,這是因為這些副本是在Service之前建立的。這樣做的另一個缺點是,排程器也許會把兩個Pod放到相同的機器上,如果機器出問題,整個Service就不工作了。正確的方式是把這兩個Pod殺掉,然後等Replication Controller重新建立它們。現在Service是在Pod副本之前存在了,因此Service獲得了排程器級別的Pod擴散能力(只要所有的節點的容量是一樣的),而且環境變數也是正確的:
$ kubectl scale rc my-nginx --replicas=0; kubectl scale rc my-nginx --replicas=2; $ kubectl get pods -l app=nginx -o wide NAME READY STATUS RESTARTS AGE NODE my-nginx-5j8ok 1/1 Running 0 2m node1 my-nginx-90vaf 1/1 Running 0 2m node2 $ kubectl exec my-nginx-5j8ok -- printenv | grep SERVICE KUBERNETES_SERVICE_PORT=443 NGINXSVC_SERVICE_HOST=10.0.116.146 KUBERNETES_SERVICE_HOST=10.0.0.1 NGINXSVC_SERVICE_PORT=80
DNS
Kubernetes提供了一個DNS叢集外掛Service,這個Service使用 skydns 自動給其他Service分配DNS。可以用下面的命令檢查它是否在叢集中執行:
$ kubectl get services kube-dns --namespace=kube-system NAME LABELS SELECTOR IP(S) PORT(S) kube-dns <none> k8s-app=kube-dns 10.0.0.10 53/UDP 53/TCP
If it isn’t running, you can enable it. The rest of this sectionwill assume you have a Service with a long lived ip (nginxsvc), and a dns server that hasassigned a name to that ip (the kube-dns cluster addon), so you can talk to the Service fromany pod in your cluster using standard methods (e.g. gethostbyname). Let’s create anotherpod to test this:
$ cat curlpod.yaml apiVersion: v1 kind: Pod metadata: name: curlpod spec: containers: - image: radial/busyboxplus:curl command: - sleep - "3600" imagePullPolicy: IfNotPresent name: curlcontainer restartPolicy: Always
And perform a lookup of the nginx Service
$ kubectl create -f ./curlpod.yaml default/curlpod $ kubectl get pods curlpod NAME READY STATUS RESTARTS AGE curlpod 1/1 Running 0 18s $ kubectl exec curlpod -- nslookup nginxsvc Server: 10.0.0.10 Address 1: 10.0.0.10 Name: nginxsvc Address 1: 10.0.116.146
Securing the Service
Till now we have only accessed the nginx server from within the cluster. Before exposing theService to the internet, you want to make sure the communication channel is secure. Forthis, you will need:
- Self signed certificates for https (unless you already have an identitiy certificate)
- An nginx server configured to use the cretificates
- A secret that makes the certificates accessible to pods
You can acquire all these from the nginx https example, in short:
$ make keys secret KEY=/tmp/nginx.key CERT=/tmp/nginx.crt SECRET=/tmp/secret.json $ kubectl create -f /tmp/secret.json secrets/nginxsecret $ kubectl get secrets NAME TYPE DATA default-token-il9rc kubernetes.io/service-account-token 1 nginxsecret Opaque
Now modify your nginx replicas to start a https server using the certificate in the secret, andthe Service, to expose both ports (80 and 443):