使用服務連線應用程式

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 叢集外掛

環境變數

當 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 的 TypeNodePort 更改為 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
...

下一步

最後修改時間:2025 年 8 月 1 日,太平洋標準時間晚上 7:49:修復 https-nginx 示例中的損壞連結 (efb3f28d72)