当前位置:网站首页>【Kubernetes 系列】一文學會Kubernetes Service安全的暴露應用

【Kubernetes 系列】一文學會Kubernetes Service安全的暴露應用

2022-07-06 02:44:00 半身風雪

作者:半身風雪
上一節:K8S 中Pod 的生命周期
簡介:上一節我們一起學習了,Kubernetes 應用Pod 生命周期的三種狀態 Pending -> Running -> Succeeded/Failed。本節內容,我們將一起學習怎麼暴露我們的應用。



目標

  • 學習 Kubernetes 中的 Service
  • 理解 標簽(Label) 和 標簽選擇器(Label Selector) 對象如何與 Service 關聯
  • 使用Service 連接到應用
  • 在 Kubernetes 集群外用 Service 暴露應用

一、什麼是Kubernetes Service

在上一節中,我們學習到了pod 的生命周期,當一個工作 Node 掛掉後, 在 Node 上運行的 Pod 也會消亡。 ReplicaSet 會自動地通過創建新的 Pod 驅動集群回到目標狀態,以保證應用程序正常運行。

Kubernetes 中的服務(Service)是一種抽象概念,它定義了 Pod 的邏輯集和訪問 Pod 的協議。Service 使從屬 Pod 之間的松耦合成為可能。 和其他 Kubernetes 對象一樣, Service 用 YAML (更推薦) 或者 JSON 來定義 Service 下的一組 Pod 通常由 LabelSelector 來標記。

盡管每個 Pod 都有一個唯一的 IP 地址,但是如果沒有 Service ,這些 IP 不會暴露在集群外部。Service 允許你的應用程序接收流量。Service 也可以用在 ServiceSpec 標記type的方式暴露

  • ClusterIP (默認) - 在集群的內部 IP 上公開 Service 。這種類型使得 Service 只能從集群內訪問。
  • NodePort - 使用 NAT 在集群中每個選定 Node 的相同端口上公開 Service 。使用<NodeIP>:<NodePort> 從集群外部訪問 Service。是 ClusterIP 的超集。
  • LoadBalancer - 在當前雲中創建一個外部負載均衡器(如果支持的話),並為 Service 分配一個固定的外部IP。是 NodePort 的超集。
  • ExternalName - 通過返回帶有該名稱的 CNAME 記錄,使用任意名稱(由 spec 中的externalName指定)公開 Service。不使用代理。這種類型需要kube-dns的v1.7或更高版本。

二、使用 Service 連接到應用

Kubernetes 假設 Pod 可與其它 Pod 通信,不管它們在哪個主機上。 Kubernetes 給每一個 Pod 分配一個集群私有 IP 地址,所以沒必要在 Pod 與 Pod 之間創建連接或將容器的端口映射到主機端口。 這意味著同一個 Pod 內的所有容器能通過 localhost 上的端口互相連通,集群中的所有 Pod 也不需要通過 NAT 轉換就能够互相看到。

2.1、在集群中暴露 Pod

首先我們先啟動一個項目,具體的啟動方式,我們前面都講過了,在這裏就不多做贅述。

  1. 項目運行之後,打開Kubernetes 儀錶板(Dashboard),點擊右上角的號,使用YAML 的方式,創建一個pod 節點。

在這裏插入圖片描述

  1. 如上圖所示,在YAML 中添加如下代碼:
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
  1. 點擊上傳,再次點擊左側列錶欄的pod 選項,就回到了我們之前 所熟悉的pod 頁面。

在這裏插入圖片描述

當然,我們也可以直接使用命令行的方式,來檢查我們的節點:

$ kubectl apply -f ./run-my-nginx.yaml

如果報以下錯誤的話:

在這裏插入圖片描述

直接執行命令:

$ unset KUBECONFIG

再次執行:

$ 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

輸出當前節點IP:

在這裏插入圖片描述

我們能够通過 ssh 登錄到集群中的任何一個節點上,並使用諸如 curl 之類的工具向這兩個 IP 地址發出查詢請求。 需要注意的是,容器不會使用該節點上的 80 端口,也不會使用任何特定的 NAT 規則去路由流量到 Pod 上。 這意味著可以在同一個節點上運行多個 Nginx Pod,使用相同的 containerPort,並且可以從集群中任何其他的 Pod 或節點上使用 IP 的方式訪問到它們。

2.2、創建 Service

上面創建了一個扁平的、集群範圍的地址空間中運行 Nginx 服務的 Pod ,要想把它暴露出去,我們還需要創建一個Service。

Kubernetes Service 是集群中提供相同功能的一組 Pod 的抽象錶達。 當每個 Service 創建時,會被分配一個唯一的 IP 地址(也稱為 clusterIP)。 這個 IP 地址與 Service 的生命周期綁定在一起,只要 Service 存在,它就不會改變。 可以配置 Pod 使它與 Service 進行通信,Pod 知道與 Service 通信將被自動地負載均衡到該 Service 中的某些 Pod 上。

直接使用 命令給 上面的 Nginx 副本創建一個 Service:

$ kubectl expose deployment/my-nginx
$ service/my-nginx exposed

當然,我們也可以直接使用YAML 方式去創建,創建方式和上面創建pod 的一樣,可以直接使用如下代碼:

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx

此時點擊左側菜單欄中的Serverces,你會看到當前Serverces 的詳細信息:

在這裏插入圖片描述

上面我們創建的 Service 會將所有具有標簽 run: my-nginx 的 Pod 的 TCP 80 端口暴露到一個抽象的 Service 端口上(targetPort:容器接收流量的端口;port:可任意取值的抽象的 Service 端口,其他 Pod 通過該端口訪問 Service)。

現在,我們執行下面的命令查看Service 資源:

$ kubectl get svc my-nginx

資源輸出如下圖所示:

在這裏插入圖片描述

前面我也提到,一個 Service 由一組 Pod 提供支撐。這些 Pod 通過 endpoints 暴露出來。 Service Selector 將持續評估,結果被 POST 到一個名稱為 my-nginxEndpoint 對象上。 當 Pod 終止後,它會自動從 Endpoint 中移除,新的能够匹配上 Service Selector 的 Pod 將自動地被添加到 Endpoint 中。 檢查該 Endpoint,注意到 IP 地址與在第一步創建的 Pod 是相同的。

$ kubectl describe svc my-nginx

執行上面的命令,我們可以得到當前serverces 的所有信息:

在這裏插入圖片描述

$ kubectl get ep my-nginx

現在,我們執行上面的命令,就可以暴露我們的service了,執行結果如下:

NAME ENDPOINTS AGE
my-nginx 172.17.0.3:80,172.17.0.8:80 23m

現在我們就能够從集群中任意節點上使用 curl 命令向 <CLUSTER-IP>:<PORT> 發送請求以訪問 Nginx Service。

2.3、訪問 Service

Kubernetes支持兩種查找服務的主要模式: 環境變量和 DNS。這裏我將只介紹環境變量的查找方式。

當 Pod 在節點上運行時,kubelet 會針對每個活躍的 Service 為 Pod 添加一組環境變量。 這就引入了一個順序的問題。為解釋這個問題,讓我們先檢查正在運行的 Nginx Pod 的環境變量。

我個人的環境變量是 my-nginx-cf54cdbf7-vr96x 你的可能會和我的不一樣,直接在pod 中查看直接的環境變量。

在這裏插入圖片描述

接下來執行命令:

$ kubectl exec my-nginx-cf54cdbf7-vr96x – printenv | grep SERVICE

運行結果如下:

KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
KUBERNETES_SERVICE_PORT_HTTPS=443

能看到環境變量中並沒有你創建的 Service 相關的值。這是因為副本的創建先於 Service。 這樣做的另一個缺點是,調度器可能會將所有 Pod 部署到同一臺機器上,如果該機器宕機則整個 Service 都會離線。 要改正的話,我們可以先終止這 2 個 Pod,然後等待 Deployment 去重新創建它們。 這次 Service 會先於副本存在。這將實現調度器級別的 Pod 按 Service 分布(假定所有的節點都具有同樣的容量),並提供正確的環境變量。

分別執行如下兩行命令:

$ kubectl scale deployment my-nginx --replicas=0; kubectl scale deployment my-nginx --replicas=2;
$ kubectl get pods -l run=my-nginx -o wide

在這裏插入圖片描述

你可能注意到,Pod 具有不同的名稱,這是因為它們是被重新創建的。

現在我們再次執行Nginx Pod 環境變量命令,注意:現在你的環境變量又變了,請驗證:

$ kubectl exec my-nginx-cf54cdbf7-fhghk – printenv | grep SERVICE

終於,我們在運行結果中,看到了Service 相關的值:

KUBERNETES_SERVICE_PORT_HTTPS=443
MY_NGINX_SERVICE_HOST=10.97.238.70
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
MY_NGINX_SERVICE_PORT=80

2.4、保護 Service

到現在為止,我們只在集群內部訪問了 Nginx 服務器。在將 Service 暴露到因特網之前,我們希望確保通信信道是安全的。 為實現這一目的,需要:

  • 用於 HTTPS 的自簽名證書(除非已經有了一個身份證書)
  • 使用證書配置的 Nginx 服務器
  • 使 Pod 可以訪問證書的 Secret

我們可以直接通過手動執行步驟執行操作下列命令:

$ 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
default-token-t7mbb kubernetes.io/service-account-token 3 56m

以下是 configmap:

$ kubectl create configmap nginxconfigmap --from-file=default.conf
$ configmap/nginxconfigmap created
$ ubectl get configmaps

輸出結果如下:

NAME DATA AGE
kube-root-ca.crt 1 58m

以下是你在運行 make 時遇到問題時要遵循的手動步驟(例如,在 Windows 上):

# 創建公鑰和相對應的私鑰
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"
# 對密鑰實施 base64 編碼
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:
  tls.crt: "這裏放你的編碼"
  tls.key: "這裏放你的編碼"

現在使用文件創建 Secret:

$ kubectl apply -f nginxsecrets.yaml
$ kubectl get secrets

執行結果:

NAME TYPE DATA AGE
default-token-t7mbb kubernetes.io/service-account-token 3 62m

現在修改 nginx 副本以啟動一個使用 Secret 中的證書的 HTTPS 服務器以及相應的用於暴露其端口(80 和 443)的 Service:

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 -o yaml | grep -i podip
    podIP: 10.244.3.5
node $ curl -k https://10.244.3.5
...
<h1>Welcome to nginx!</h1>

注意最後一步我們是如何提供 -k 參數執行 curl 命令的,這是因為在證書生成時, 我們不知道任何關於運行 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>
...

2.5、暴露 Service

對應用的某些部分,你可能希望將 Service 暴露在一個外部 IP 地址上。 Kubernetes 支持兩種實現方式:NodePort 和 LoadBalancer。 在上一段創建的 Service 使用了 NodePort,因此,如果你的節點有一個公網 IP,那麼 Nginx HTTPS 副本已經能够處理因特網上的流量。

$ kubectl get svc my-nginx -o yaml | grep nodePort -C 5

在這裏插入圖片描述

$ kubectl get nodes -o yaml | grep ExternalIP -C 1

在這裏插入圖片描述

讓我們重新創建一個 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

在 EXTERNAL-IP 列中的 IP 地址能在公網上被訪問到。CLUSTER-IP 只能從集群/私有雲網絡中訪問。

注意,在 AWS 上,類型 LoadBalancer 的服務會創建一個 ELB,且 ELB 使用主機名(比較長),而不是 IP。 ELB 的主機名太長以至於不能適配標准 kubectl get svc 的輸出,所以需要通過執行 kubectl describe service my-nginx 命令來查看它。 可以看到類似如下內容:

在這裏插入圖片描述

需要注意的是有一些 Service 的用例沒有在 spec 中定義selector。 一個沒有selector創建的 Service 也不會創建相應的端點對象。這允許用戶手動將服務映射到特定的端點。沒有 selector 的另一種可能是你嚴格使用type: ExternalName來標記。

三、Service 和 Label

在這裏插入圖片描述

Service 通過一組 Pod 路由通信。Service 是一種抽象,它允許 Pod 死亡並在 Kubernetes 中複制,而不會影響應用程序。在依賴的 Pod (如應用程序中的前端和後端組件)之間進行發現和路由是由Kubernetes Service 處理的。

Service 匹配一組 Pod 是使用 標簽(Label)和選擇器(Selector), 它們是允許對 Kubernetes 中的對象進行邏輯操作的一種分組原語。標簽(Label)是附加在對象上的鍵/值對,可以以多種方式使用:

  • 指定用於開發,測試和生產的對象
  • 嵌入版本標簽
  • 使用 Label 將對象進行分類

在這裏插入圖片描述

標簽(Label)可以在創建時或之後附加到對象上。他們可以隨時被修改。

總結

本節內容,主要講解了 service 模塊,及它的關聯標簽,還有 service 如何將集群暴露給外部,內容還是比較多的,創作不易,望支持。

原网站

版权声明
本文为[半身風雪]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/187/202207060243427310.html