从部署 httpd 入手,理清 k8s 配置中的 containerPort、port、nodePort、targetPort
注:文中各種內網、公網 IP 僅為示例,不保證所有 IP 都可以訪問,且你的環境中 IP 可能不同。
背景
在上一篇文章?自己搭建一個k8s環境?中,我們一頓操作猛如虎,搭建出了由 1 個 master 和 1 個 worker 節點組成的 k8s 集群,大概是這樣的效果:
為了驗證我們搭建的環境是否好使,本篇文章就借搭建 httpd 來復習復習 k8s 的部署,順便理清 k8s 配置中的 containerPort、port、nodePort、targetPort。
如果你沒有聽說過 httpd,那么只需要知道它是一款功能類似于?nginx、IIS?的 Web 服務器,它的全名是 Apache HTTP Server。當部署好后訪問它默認的首頁,會收獲?It Works?這樣一行令人愉快的提示語。
nginx?https://nginx.org/en/
IIS?https://www.iis.net/
Apache HTTP Server?https://httpd.apache.org/
先把最終效果放上,圖中展示了下面會用到的各種 k8s 資源以及訪問效果,閱讀后面的內容時可以對照著圖看看:
部署 httpd
為了部署 httpd,我們需要用到以下知識:
Namespace:通過 Namespace 可以對集群內的資源進行分組,例如我希望所有和部署 httpd 有關的資源都在同一個分組中,那么就可以通過設置 namespace 實現(當然,Namespace 還影響著諸如 DNS 等內容,可以在官方文檔中查看)
Deployment:我們知道,Pod 是可以在 Kubernetes 中創建和管理的、最小的可部署的計算單元,但通常我們不需要直接創建 Pod,而是通過 Deployment 之類的?工作負載?資源來管理它。所以我們通過 Deployment 來部署 httpd
Service:當部署妥當后,需要有一種方式來訪問部署好的程序,Service 就是將 Pod 里的程序公開為網絡服務的抽象方法
Namespace https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
Deployment https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
Pod https://kubernetes.io/zh/docs/concepts/workloads/pods/
工作負載 https://kubernetes.io/zh/docs/concepts/workloads/
Service https://kubernetes.io/zh/docs/concepts/services-networking/service/
了解了相關知識后,馬上開干,我們通過?kubectl?來管理集群,所以需要登錄到 master 節點上來使用?kubectl。
kubectl https://kubernetes.io/zh/docs/reference/kubectl/overview/
下面會通過?yaml?文件的方式創建相關資源,
YAML is a human-friendly data serialization language for all programming languages.
YAML 是一種人類友好的數據序列化語言,適用于所有編程語言。
簡而言之就是通過一種易于書寫和閱讀的格式,來描述數據(和大家熟悉的 JSON 以及 XML 做的事情一樣)。
創建 Namespace
首先創建一個?httpd-namespace.yaml?文件:
apiVersion: v1 kind: Namespace metadata:name: httpd通過?kind?指定要創建的資源為?Namespace
通過?metadata.name?來指定 Namespace 的名稱
然后執行
kubectl create -f ./httpd-namespace.yaml表示通過文件創建資源。
然后查看我們創建的 namespace:
kubectl get namespace httpd得到結果:
NAME STATUS AGE httpd Active 11s一些資源有自己的縮寫,例如?namespace?可以縮寫為?ns:
kubectl get ns httpd這樣能少打幾個字母,提高效率。
通過?kubectl api-resources?可以查看相關列表。
創建 Deployment
接下來創建?httpd-deployment.yaml?文件:
apiVersion: apps/v1 kind: Deployment metadata:namespace: httpdname: httpd spec:selector:matchLabels:app: httpdtemplate:metadata:labels:app: httpdspec:containers:- name: httpdimage: httpd:alpineports:- containerPort: 80通過?kind?指定要創建的資源為?Deployment
metadata
通過?namespace?設置該 Deployment 屬于?httpd?Namespace 下
通過?name?將該 Deployment 的名稱設為?httpd(有的朋友喜歡加上前綴或后綴表示是 Deployment,例如?httpd-deployment。都是可以的,只是我個人更喜歡資源類型加名稱已經可以唯一標識一個資源了,就不再添加前后綴了)
spec
selector?表示 Deployment 如何找到要管理的 Pod。這里使用的?matchLabels?表示,匹配帶有?app?這個 label,且值為?httpd?的 Pod
template?中的
metadata.labels?會為 Pod 增加?app: httpd?label,這個 label 必須和前面?selector?定義的匹配
spec.containers?表示創建一個名為 httpd 的 container,使用?httpd:alpine?鏡像,并開放容器的 80 端口
然后創建 Deployment:
kubectl create -f ./httpd-deployment.yaml查看創建好的 Deployment:
kubectl get deploy -n httpddeploy?是?deployments?的縮寫
因為我們的 Deployment 在?httpd?Namespace 下,所以還需要?-n?設置 Namespace
可以看到創建好的 Deployment:
NAME READY UP-TO-DATE AVAILABLE AGE httpd 1/1 1 1 17s由于 Deployment 會為我們管理 Pod,這時 Pod 已經創建好了,查看 Pod:
kubectl get pods -n httpd可以看到創建好的 Pod:
NAME READY STATUS RESTARTS AGE httpd-76f7455774-mdk4t 1/1 Running 0 33s由 Deployment 生成的 Pod 的名稱為?httpd?和隨機字符串組成,所以你看到的 Pod 名稱可能和我不同。
觀察 Pod 的網絡
讓我們來看看這個 Pod 的 IP 地址,通過?-o?可以設置輸出的格式:
kubectl get pod -n httpd -o wide得到的返回如下:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES httpd-76f7455774-mdk4t 1/1 Running 0 1m15s 10.10.229.95 bun-worker-01 <none> <none>可以看到 httpd Pod 運行于?bun-worker-01?節點,并且有一個 IP 地址。由于我們開放的端口為?80?,試試訪問一下:
wget -qO - 10.10.229.95wow,可以訪問,并得到了響應:
<html><body><h1>It works!</h1></body></html>說明 httpd 工作良好。
再來看看這個 IP 地址?10.10.229.95,和之前通過?kubeadm init?設置的?pod-network-cidr=10.10.0.0/16?正好一致,使用的是我們為 Pod 劃分的網段。
現在,我們的 Worker 節點里大概是這個模樣:
這是不是意味著只要在 master 或 worker 節點上裝一個 nginx 來反向代理這個 IP 地址,就能把 Pod 里的內容發布出去了呢?是,也不是 🤭
我們的這個 Pod 是通過 Deployment 管理的,可以試試刪掉這個 Pod,然后 Deployment 應該自動會重新創建一個新的 Pod:
kubectl delete pod httpd-76f7455774-mdk4t -n httpd kubectl get pods -n httpd -o wide果然,又創建出一個新的 Pod:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES httpd-76f7455774-mrx79 1/1 Running 0 6s 10.10.229.96 bun-worker-01 <none> <none>不過 IP 地址變了😂
當然,也有奇技淫巧可以固定住 Pod 的 IP,但我們并不需要,因為有專業人士負責把 Pod 內的資源公開出去,那就是 Service。
創建 Service
創建?httpd-service.yaml?文件:
apiVersion: v1 kind: Service metadata:namespace: httpdname: httpd spec:selector:app: httpdtype: NodePortports:- protocol: TCPport: 8081targetPort: 80nodePort: 30000通過?kind?指定要創建的資源為?Service
spec
selector?用于查找 Service 要服務的 Pod
type?這里指定了?NodePort,表示通過 Node 節點的端口暴露服務
ports?下面設置了使用的協議為?TCP?以及一系列的端口,后面會介紹(這里特地為各種端口設置了不同的值,方便下面進行試驗)
在生產環境中,一般直接使用的是云廠商提供的負載均衡服務(即?type?設為?LoadBalancer)。但我們的宗旨是低成本(qiong)自建 k8s,所以選用?NodePort?方式非常合適。
創建 Service:
kubectl create -f ./httpd-service.yaml查看創建好的 Service:
kubectl get svc -n httpd返回的內容:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE httpd NodePort 10.1.96.193 <none> 8081:30000/TCP 6sCluster IP 和 port
首先來看?CLUSTER-IP,這也是通過?kubeadm init?設置的?service-cidr=10.1.0.0/16?決定的,通過 Cluster IP 可以在集群內部進行通信。
我們進入 httpd 的 Pod 里試驗一下:
kubectl exec -it httpd-76f7455774-mrx79 -n httpd /bin/shexec?可以對 Pod 內的容器執行命令,例如這里執行的是?/bin/sh?命令,讓我們可以直接與容器內部進行交互。注意將?exec?后方的 Pod 名稱換成你自己的。
注意:現在,終端的內容來自 httpd Pod 內部。
在 Service 中,我們指定的?port?為?8081,那么通過?ClusterIP:port?就可以訪問到通過 Service 公開的內容:
wget -qO - 10.1.96.193:8081確實可以,相當于我(httpd Pod)繞了一圈訪問了我自己:
<html><body><h1>It works!</h1></body></html>k8s 中還有另一種訪問 Service 的方式,那就是通過?<Service 名稱>.<Namespace 名稱>.svc.cluster.local?域名,來試一試:
wget -qO - httpd.httpd.svc.cluster.local:8081鵝妹子嚶,也可以訪問!
它背后的原理則是創建 Service 時,k8s 會創建對應的?DNS 記錄?,用?nslookup?命令看看?httpd.httpd.svc.cluster.local?解析后的 IP 是哪里:
結果:
Server: 10.1.0.10 Address: 10.1.0.10:53Name: httpd.httpd.svc.cluster.local Address: 10.1.96.193域名解析后又指向了 Cluster IP。
DNS 記錄 https://kubernetes.io/zh/docs/concepts/services-networking/dns-pod-service/
輸入?exit?退出和 Pod 的交互,回到 master。
Node IP 和 nodePort
nodePort?顧名思義就是從節點上開放的端口,它確實在物理上占用了這個節點(主機)的一個端口,可以通過?NodeIP:nodePort?訪問。在這里 httpd Pod 運行在?bun-worker-01?節點上,節點的 IP 是?172.17.0.2,那么這樣也可以訪問到 httpd:
wget -qO - 172.17.0.2:30000默認情況下,nodePort?可用的范圍為?30000-32767,當不設置?nodePort?時會在這個范圍內隨機分配一個。
Pod IP 和 targetPort
在 Service 的配置中,如果不設置?targetPort,那么它默認會被設為和?port?一樣的值。不過我們為 Pod 開放的端口為?80,所以在 Service 中配置的?targetPort?需要和 Deployment 中的?containerPort?對應上。
這樣通過?NodeIP:nodePort?以及?ClusterIP:port?來的流量才能通過?PodIP:targetPort?進入到容器中。
通過公網 IP 訪問 httpd
最后,我們可以通過公網訪問 Pod 里的 httpd!因為?NodePort?類型的 Service 是真的在節點上開放了一個端口,即?nodePort,那么我們只需要用節點的公網 IP +?nodePort?就能訪問到 httpd 了!
記得先在云主機的防火墻中開放?30000?端口:
接下打開瀏覽器,見證奇跡。我的?bun-worker-01?節點公網 IP 為?101.35.132.54:
但是不管靜態設置或動態分配節點的 Port 總歸是有些麻煩,而且目前這樣如果想通過域名解析來訪問 httpd,意味著域名后還需要接上?30000?端口才行,且不易實現負載均衡訪問,不是主流的方案。
下一篇我們將使用?ingress-nginx,直接公開節點的?80?和?443?端口,像是安裝了 nginx 一樣,通過簡單的配置,就能使用域名直接訪問 Pod 里的資源。
ingress-nginx https://kubernetes.github.io/ingress-nginx/
總結
以上是生活随笔為你收集整理的从部署 httpd 入手,理清 k8s 配置中的 containerPort、port、nodePort、targetPort的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【亲身经验】如何在 6 个月内成为“微软
- 下一篇: Xamarin效果第十七篇之AR GIS