从部署 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: httpd
- name: httpd
- spec:
- selector:
- matchLabels:
- app: httpd
- template:
- metadata:
- labels:
- app: httpd
- spec:
- containers:
- - name: httpd
- image: httpd:alpine
- ports:
- - containerPort: 80
通过
kind指定要创建的资源为Deploymentmetadata通过
namespace设置该 Deployment 属于httpdNamespace 下通过
name将该 Deployment 的名称设为httpd(有的朋友喜欢加上前缀或后缀表示是 Deployment,例如httpd-deployment。都是可以的,只是我个人更喜欢资源类型加名称已经可以唯一标识一个资源了,就不再添加前后缀了)
specselector表示 Deployment 如何找到要管理的 Pod。这里使用的matchLabels表示,匹配带有app这个 label,且值为httpd的 Podtemplate中的metadata.labels会为 Pod 增加app: httpdlabel,这个 label 必须和前面selector定义的匹配spec.containers表示创建一个名为 httpd 的 container,使用httpd:alpine镜像,并开放容器的 80 端口
然后创建 Deployment:
kubectl create -f ./httpd-deployment.yaml
查看创建好的 Deployment:
kubectl get deploy -n httpd
deploy是deployments的缩写因为我们的 Deployment 在
httpdNamespace 下,所以还需要-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.95
wow,可以访问,并得到了响应:
<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: httpd
- name: httpd
- spec:
- selector:
- app: httpd
- type: NodePort
- ports:
- - protocol: TCP
- port: 8081
- targetPort: 80
- nodePort: 30000
通过
kind指定要创建的资源为Servicespecselector用于查找 Service 要服务的 Podtype这里指定了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 6s
Cluster 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/sh
exec 可以对 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 是哪里:
nslookup httpd.httpd.svc.cluster.local
结果:
- Server: 10.1.0.10
- Address: 10.1.0.10:53
-
-
- Name: 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/


浙公网安备 33010602011771号