使用服務連線應用程式
Kubernetes 連線容器的模型
現在,您已經擁有了一個持續執行且具有副本的應用,接下來可以將其暴露在網路上。
Kubernetes 假定 Pod 之間可以相互通訊,無論它們落在哪個主機上。Kubernetes 為每個 Pod 分配了其自己的叢集內部 IP 地址,因此您無需明確地在 Pod 之間建立連結或將容器埠對映到主機埠。這意味著 Pod 中的所有容器都可以透過 localhost 訪問彼此的埠,並且叢集中的所有 Pod 都可以相互通訊而無需 NAT。本文件的其餘部分將詳細說明如何在這樣的網路模型上執行可靠的服務。
本教程使用一個簡單的 nginx Web 伺服器來演示此概念。
將 Pod 暴露給叢集
我們已經在前面的示例中完成了此操作,但讓我們再次執行此操作,並重點關注網路方面。建立一個 nginx Pod,並注意它具有容器埠規範。
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
NAME READY STATUS RESTARTS AGE IP NODE
my-nginx-3800858182-jr4a2 1/1 Running 0 13s 10.244.3.4 kubernetes-minion-905m
my-nginx-3800858182-kna2y 1/1 Running 0 13s 10.244.2.5 kubernetes-minion-ljyd
檢查您的 Pod IP。
kubectl get pods -l run=my-nginx -o custom-columns=POD_IP:.status.podIPs
POD_IP
[map[ip:10.244.3.4]]
[map[ip:10.244.2.5]]
您應該能夠透過 SSH 連線到叢集中的任何節點,並使用 `curl` 等工具對這兩個 IP 進行查詢。請注意,容器**沒有**使用節點上的 80 埠,也沒有任何特殊的 NAT 規則將流量路由到 Pod。這意味著您可以在同一節點上執行多個 nginx Pod,它們都使用相同的 `containerPort`,並且可以使用分配給 Pod 的 IP 地址從叢集中的任何其他 Pod 或節點訪問它們。如果您想安排主機節點上的特定埠轉發到後端 Pod,您可以這樣做,但網路模型意味著您不需要這樣做。
如果您好奇,可以閱讀更多關於 Kubernetes 網路模型 的資訊。
建立 Service
現在我們有了在叢集範圍內扁平地址空間中執行 nginx 的 Pod。理論上,您可以直接與這些 Pod 通訊,但是當節點宕機時會發生什麼?Pod 也會隨之宕機,Deployment 中的 ReplicaSet 將建立新的 Pod,並分配不同的 IP。這就是 Service 所解決的問題。
Kubernetes Service 是一種抽象,它定義了叢集中某個位置執行的邏輯 Pod 集合,這些 Pod 都提供相同的功能。建立 Service 後,每個 Service 都被分配一個唯一的 IP 地址(也稱為 ClusterIP)。此地址與 Service 的生命週期繫結,在 Service 存活期間不會改變。Pod 可以配置為與 Service 通訊,並且知道與 Service 的通訊將自動負載均衡到屬於該 Service 的某個 Pod。
您可以使用 kubectl expose
為您的 2 個 nginx 副本建立 Service。
kubectl expose deployment/my-nginx
service/my-nginx exposed
這等同於以下 YAML 檔案中的 kubectl apply -f
命令:
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
ports:
- port: 80
protocol: TCP
selector:
run: my-nginx
此規範將建立一個 Service,它會針對任何帶有 run: my-nginx
標籤的 Pod 的 TCP 埠 80,並透過一個抽象的 Service 埠(targetPort
:容器接受流量的埠,port
:抽象的 Service 埠,可以是任何其他 Pod 用於訪問 Service 的埠)將其暴露。檢視 Service API 物件以檢視 Service 定義中支援的欄位列表。檢查您的 Service。
kubectl get svc my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx ClusterIP 10.0.162.149 <none> 80/TCP 21s
如前所述,Service 由一組 Pod 提供支援。這些 Pod 透過 EndpointSlice 暴露。Service 的選擇器將持續評估,結果將 POST 到透過標籤連線到 Service 的 EndpointSlice。當 Pod 死亡時,它會自動從包含其作為端點的 EndpointSlice 中移除。與 Service 選擇器匹配的新 Pod 將自動新增到該 Service 的 EndpointSlice 中。檢查端點,並注意 IP 與第一步中建立的 Pod 相同。
kubectl describe svc my-nginx
Name: my-nginx
Namespace: default
Labels: run=my-nginx
Annotations: <none>
Selector: run=my-nginx
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.0.162.149
IPs: 10.0.162.149
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.2.5:80,10.244.3.4:80
Session Affinity: None
Events: <none>
kubectl get endpointslices -l kubernetes.io/service-name=my-nginx
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
my-nginx-7vzhx IPv4 80 10.244.2.5,10.244.3.4 21s
現在,您應該能夠從叢集中的任何節點,透過 `<CLUSTER-IP>:<PORT>` curl 訪問 nginx Service。請注意,Service IP 完全是虛擬的,它從不觸及物理網路。如果您對它是如何工作的感到好奇,可以閱讀更多關於 服務代理 的資訊。
訪問 Service
Kubernetes 支援兩種主要的服務發現模式:環境變數和 DNS。前者開箱即用,而後者需要 CoreDNS 叢集外掛。
注意
如果不需要服務環境變數(因為可能與預期的程式變數衝突,變數過多無法處理,僅使用 DNS 等),您可以透過將 Pod 規範中的enableServiceLinks
標誌設定為 false
來停用此模式。環境變數
當 Pod 在節點上執行時,kubelet 會為每個活躍的 Service 新增一組環境變數。這帶來了一個排序問題。為了理解原因,檢查您正在執行的 nginx Pod 的環境(您的 Pod 名稱將不同)。
kubectl exec my-nginx-3800858182-jr4a2 -- printenv | grep SERVICE
KUBERNETES_SERVICE_HOST=10.0.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443
請注意,沒有提及您的 Service。這是因為您在 Service 之前建立了副本。這樣做 的另一個缺點是排程器可能會將兩個 Pod 放在同一臺機器上,如果它宕機,將導致您的整個 Service 停止。我們可以透過殺死這兩個 Pod 並等待 Deployment 重新建立它們來正確處理。這次 Service 存在於副本**之前**。這將為您提供 Pod 的排程器級別服務傳播(前提是所有節點都具有相同的容量),以及正確的環境變數。
kubectl scale deployment my-nginx --replicas=0; kubectl scale deployment my-nginx --replicas=2;
kubectl get pods -l run=my-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE
my-nginx-3800858182-e9ihh 1/1 Running 0 5s 10.244.2.7 kubernetes-minion-ljyd
my-nginx-3800858182-j4rm4 1/1 Running 0 5s 10.244.3.8 kubernetes-minion-905m
您可能會注意到 Pod 的名稱不同,因為它們被殺死並重新建立了。
kubectl exec my-nginx-3800858182-e9ihh -- printenv | grep SERVICE
KUBERNETES_SERVICE_PORT=443
MY_NGINX_SERVICE_HOST=10.0.162.149
KUBERNETES_SERVICE_HOST=10.0.0.1
MY_NGINX_SERVICE_PORT=80
KUBERNETES_SERVICE_PORT_HTTPS=443
DNS
Kubernetes 提供了一個 DNS 叢集外掛 Service,該 Service 會自動為其他 Service 分配 DNS 名稱。您可以檢查它是否在您的叢集中執行。
kubectl get services kube-dns --namespace=kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.0.0.10 <none> 53/UDP,53/TCP 8m
本節的其餘部分將假定您有一個具有長期 IP(my-nginx)的 Service,以及一個已為該 IP 分配名稱的 DNS 伺服器。這裡我們使用 CoreDNS 叢集外掛(應用程式名稱為 kube-dns
),因此您可以使用標準方法(例如 gethostbyname()
)從叢集中的任何 Pod 與 Service 通訊。如果 CoreDNS 未執行,您可以參考 CoreDNS README 或 安裝 CoreDNS 來啟用它。讓我們執行另一個 curl 應用程式來測試一下。
kubectl run curl --image=radial/busyboxplus:curl -i --tty --rm
Waiting for pod default/curl-131556218-9fnch to be running, status is Pending, pod ready: false
Hit enter for command prompt
然後,按回車鍵並執行 nslookup my-nginx
。
[ root@curl-131556218-9fnch:/ ]$ nslookup my-nginx
Server: 10.0.0.10
Address 1: 10.0.0.10
Name: my-nginx
Address 1: 10.0.162.149
保護服務
到目前為止,我們只從叢集內部訪問了 nginx 伺服器。在將服務暴露到網際網路之前,您需要確保通訊通道是安全的。為此,您需要:
- 用於 HTTPS 的自簽名證書(除非您已有身份證書)
- 配置為使用證書的 Nginx 伺服器
- 一個Secret,使證書可供 Pod 訪問
您可以從nginx https 示例中獲取所有這些。這需要安裝 Go 和 make 工具。如果您不想安裝這些工具,請稍後按照手動步驟操作。簡而言之:
make keys KEY=/tmp/nginx.key CERT=/tmp/nginx.crt
kubectl create secret tls nginxsecret --key /tmp/nginx.key --cert /tmp/nginx.crt
secret/nginxsecret created
kubectl get secrets
NAME TYPE DATA AGE
nginxsecret kubernetes.io/tls 2 1m
還有 ConfigMap
kubectl create configmap nginxconfigmap --from-file=default.conf
您可以在Kubernetes 示例專案倉庫中找到 default.conf
的示例。
configmap/nginxconfigmap created
kubectl get configmaps
NAME DATA AGE
nginxconfigmap 1 114s
您可以使用以下命令檢視 nginxconfigmap
ConfigMap 的詳細資訊。
kubectl describe configmap nginxconfigmap
輸出類似於:
Name: nginxconfigmap
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
default.conf:
----
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
listen 443 ssl;
root /usr/share/nginx/html;
index index.html;
server_name localhost;
ssl_certificate /etc/nginx/ssl/tls.crt;
ssl_certificate_key /etc/nginx/ssl/tls.key;
location / {
try_files $uri $uri/ =404;
}
}
BinaryData
====
Events: <none>
以下是如果執行 make 時遇到問題(例如在 Windows 上)時要遵循的手動步驟:
# Create a public private key pair
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /d/tmp/nginx.key -out /d/tmp/nginx.crt -subj "/CN=my-nginx/O=my-nginx"
# Convert the keys to base64 encoding
cat /d/tmp/nginx.crt | base64
cat /d/tmp/nginx.key | base64
使用前述命令的輸出建立如下的 YAML 檔案。Base64 編碼的值應全部位於一行中。
apiVersion: "v1"
kind: "Secret"
metadata:
name: "nginxsecret"
namespace: "default"
type: kubernetes.io/tls
data:
# NOTE: Replace the following values with your own base64-encoded certificate and key.
tls.crt: "REPLACE_WITH_BASE64_CERT"
tls.key: "REPLACE_WITH_BASE64_KEY"
現在使用該檔案建立 Secret。
kubectl apply -f nginxsecrets.yaml
kubectl get secrets
NAME TYPE DATA AGE
nginxsecret kubernetes.io/tls 2 1m
現在修改您的 nginx 副本,以使用 Secret 中的證書啟動一個 https 伺服器,並使用 Service 暴露兩個埠(80 和 443)。
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: NodePort
ports:
- port: 8080
targetPort: 80
protocol: TCP
name: http
- port: 443
protocol: TCP
name: https
selector:
run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
volumes:
- name: secret-volume
secret:
secretName: nginxsecret
- name: configmap-volume
configMap:
name: nginxconfigmap
containers:
- name: nginxhttps
image: bprashanth/nginxhttps:1.0
ports:
- containerPort: 443
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx/ssl
name: secret-volume
- mountPath: /etc/nginx/conf.d
name: configmap-volume
關於 nginx-secure-app 清單的值得注意的幾點:
- 它在同一個檔案中包含了 Deployment 和 Service 規範。
- nginx 伺服器在埠 80 上提供 HTTP 流量,在埠 443 上提供 HTTPS 流量,並且 nginx Service 暴露了這兩個埠。
- 每個容器都可以透過掛載到
/etc/nginx/ssl
的卷訪問金鑰。這在 nginx 伺服器啟動**之前**就已設定。
kubectl delete deployments,svc my-nginx; kubectl create -f ./nginx-secure-app.yaml
此時,您可以從任何節點訪問 nginx 伺服器。
kubectl get pods -l run=my-nginx -o custom-columns=POD_IP:.status.podIPs
POD_IP
[map[ip:10.244.3.5]]
node $ curl -k https://10.244.3.5
...
<h1>Welcome to nginx!</h1>
請注意,我們在上一步中是如何給 curl 提供了 -k
引數的,這是因為在證書生成時我們對執行 nginx 的 Pod 一無所知,所以我們必須告訴 curl 忽略 CName 不匹配。透過建立 Service,我們將證書中使用的 CName 與 Service 查詢期間 Pod 實際使用的 DNS 名稱關聯起來。讓我們從 Pod 中測試一下(為簡單起見,重複使用了相同的 Secret,Pod 只需要 nginx.crt 即可訪問 Service)。
apiVersion: apps/v1
kind: Deployment
metadata:
name: curl-deployment
spec:
selector:
matchLabels:
app: curlpod
replicas: 1
template:
metadata:
labels:
app: curlpod
spec:
volumes:
- name: secret-volume
secret:
secretName: nginxsecret
containers:
- name: curlpod
command:
- sh
- -c
- while true; do sleep 1; done
image: radial/busyboxplus:curl
volumeMounts:
- mountPath: /etc/nginx/ssl
name: secret-volume
kubectl apply -f ./curlpod.yaml
kubectl get pods -l app=curlpod
NAME READY STATUS RESTARTS AGE
curl-deployment-1515033274-1410r 1/1 Running 0 1m
kubectl exec curl-deployment-1515033274-1410r -- curl https://my-nginx --cacert /etc/nginx/ssl/tls.crt
...
<title>Welcome to nginx!</title>
...
暴露 Service
對於您的應用程式的某些部分,您可能希望將 Service 暴露到外部 IP 地址。Kubernetes 支援兩種方式:NodePort 和 LoadBalancer。上一節中建立的 Service 已經使用了 NodePort
,因此如果您的節點具有公共 IP,您的 nginx HTTPS 副本已準備好為網際網路上的流量提供服務。
kubectl get svc my-nginx -o yaml | grep nodePort -C 5
uid: 07191fb3-f61a-11e5-8ae5-42010af00002
spec:
clusterIP: 10.0.162.149
ports:
- name: http
nodePort: 31704
port: 8080
protocol: TCP
targetPort: 80
- name: https
nodePort: 32453
port: 443
protocol: TCP
targetPort: 443
selector:
run: my-nginx
kubectl get nodes -o yaml | grep ExternalIP -C 1
- address: 104.197.41.11
type: ExternalIP
allocatable:
--
- address: 23.251.152.56
type: ExternalIP
allocatable:
...
$ curl https://<EXTERNAL-IP>:<NODE-PORT> -k
...
<h1>Welcome to nginx!</h1>
現在,讓我們重新建立 Service 以使用雲負載均衡器。將 my-nginx
Service 的 Type
從 NodePort
更改為 LoadBalancer
。
kubectl edit svc my-nginx
kubectl get svc my-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx LoadBalancer 10.0.162.149 xx.xxx.xxx.xxx 8080:30163/TCP 21s
curl https://<EXTERNAL-IP> -k
...
<title>Welcome to nginx!</title>
EXTERNAL-IP
列中的 IP 地址是公共網際網路上可用的地址。CLUSTER-IP
僅在您的叢集/私有云網路內部可用。
請注意,在 AWS 上,型別為 LoadBalancer
會建立一個 ELB,它使用一個(很長的)主機名,而不是 IP。實際上,它太長了,無法完全顯示在標準 kubectl get svc
輸出中,因此您需要執行 kubectl describe service my-nginx
才能看到它。您會看到類似以下內容:
kubectl describe service my-nginx
...
LoadBalancer Ingress: a320587ffd19711e5a37606cf4a74574-1142138393.us-east-1.elb.amazonaws.com
...
下一步
- 瞭解更多關於 使用 Service 訪問叢集中的應用程式 的資訊。
- 瞭解更多關於 使用 Service 連線前端到後端 的資訊。
- 瞭解更多關於 建立外部負載均衡器 的資訊。