裸机环境注意事项
在传统的 云 环境中,可以按需使用网络负载均衡器,单个 Kubernetes 清单就足以为 Ingress-Nginx Controller 提供外部客户端的单一访问入口,并间接为集群内运行的任何应用提供服务。而 裸机 环境缺乏这种便利性,需要稍有不同的设置才能为外部消费者提供相同类型的访问。
本文档其余部分将介绍在裸机上运行的 Kubernetes 集群中部署 Ingress-Nginx Controller 的几种推荐方案。
纯软件解决方案:MetalLB
MetalLB 为不在受支持云提供商上运行的 Kubernetes 集群提供了网络负载均衡器实现,实际上允许在任何集群中使用 LoadBalancer 类型的服务。
本节演示如何将 MetalLB 的 二层配置模式 与 NGINX Ingress 控制器结合使用于具有 可公开访问节点 的 Kubernetes 集群中。在此模式下,一个节点会吸引所有流向 ingress-nginx
服务 IP 的流量。详见 流量策略。
注意
本文档不描述其他支持的配置模式。
警告
MetalLB 目前处于 测试阶段。请阅读 项目成熟度说明,并确保通过彻底阅读官方文档来自行了解相关信息。
MetalLB 可以通过简单的 Kubernetes 清单或 Helm 部署。本示例假设 MetalLB 已按照 安装指南 部署,且 Ingress-Nginx Controller 已使用安装指南的 快速入门部分 中描述的步骤安装。
MetalLB 需要一个 IP 地址池才能接管 ingress-nginx
服务。这个池可以通过与 MetalLB 控制器相同命名空间中的 IPAddressPool
对象定义。这些 IP 必须 专供 MetalLB 使用,不能复用 Kubernetes 节点 IP 或 DHCP 服务器分配的 IP。
示例
给定以下 3 节点 Kubernetes 集群(外部 IP 为示例添加,大多数裸机环境中该值为
$ kubectl get node
NAME STATUS ROLES EXTERNAL-IP
host-1 Ready master 203.0.113.1
host-2 Ready node 203.0.113.2
host-3 Ready node 203.0.113.3
创建以下对象后,MetalLB 将获取池中一个 IP 地址的所有权,并相应更新 ingress-nginx
服务的 loadBalancer IP 字段。
`—- apiVersion: metallb.io/v1beta1 kind: IPAddressPool metadata: name: default namespace: metallb-system spec: addresses: • 203.0.113.10-203.0.113.15
autoAssign: true
apiVersion: metallb.io/v1beta1 kind: L2Advertisement metadata: name: default namespace: metallb-system spec: ipAddressPools: • default`
$ kubectl -n ingress-nginx get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
default-http-backend ClusterIP 10.0.64.249 <none> 80/TCP
ingress-nginx LoadBalancer 10.0.220.217 203.0.113.10 80:30100/TCP,443:30101/TCP
一旦 MetalLB 设置了 ingress-nginx
LoadBalancer 服务的外部 IP 地址,相应的条目就会在 iptables NAT 表中创建,选定的 IP 地址节点开始响应 LoadBalancer 服务配置端口上的 HTTP 请求:
$ curl -D- http://203.0.113.10 -H 'Host: myapp.example.com'
HTTP/1.1 200 OK
Server: nginx/1.15.2
提示
为了在发送到 NGINX 的 HTTP 请求中保留源 IP 地址,必须使用 Local
流量策略。流量策略在 流量策略 以及下一节中有更详细的描述。
通过 NodePort 服务
由于其简单性,这是用户在按照 安装指南 中描述的步骤部署时的默认设置。
信息
NodePort
类型的服务通过 kube-proxy
组件在每个 Kubernetes 节点(包括主节点)上暴露 相同的非特权 端口(默认:30000-32767)。更多信息请参阅 服务。
在此配置中,NGINX 容器仍与主机网络隔离。因此它可以安全地绑定到任何端口,包括标准 HTTP 端口 80 和 443。但由于容器命名空间隔离,位于集群网络外部的客户端(例如公共互联网)无法直接在 80 和 443 端口上访问 Ingress 主机。相反,外部客户端必须在 HTTP 请求中附加分配给 ingress-nginx
服务的 NodePort。
您可以通过设置 controller.service.nodePorts.*
Helm 值来 自定义暴露的节点端口号,但它们仍必须在 30000-32767 范围内。
示例
给定分配给 ingress-nginx
服务的 NodePort 30100
$ kubectl -n ingress-nginx get svc
NAME TYPE CLUSTER-IP PORT(S)
default-http-backend ClusterIP 10.0.64.249 80/TCP
ingress-nginx NodePort 10.0.220.217 80:30100/TCP,443:30101/TCP
以及具有公共 IP 地址 203.0.113.2
的 Kubernetes 节点(外部 IP 为示例添加,大多数裸机环境中该值为
$ kubectl get node
NAME STATUS ROLES EXTERNAL-IP
host-1 Ready master 203.0.113.1
host-2 Ready node 203.0.113.2
host-3 Ready node 203.0.113.3
客户端将通过 http://myapp.example.com:30100
访问 host: myapp.example.com
的 Ingress,其中 myapp.example.com 子域名解析为 203.0.113.2 IP 地址。
对主机系统的影响
虽然通过 API 服务器的 --service-node-port-range
标志重新配置 NodePort 范围以包含非特权端口并能够暴露 80 和 443 端口可能听起来很诱人,但这样做可能会导致意外问题,包括(但不限于)使用原本保留给系统守护进程的端口,以及需要授予 kube-proxy
原本不需要的特权。
因此 不鼓励 这种做法。请参阅本页提出的其他替代方案。
这种方法还有一些其他需要注意的限制:
源IP地址
NodePort 类型的服务默认执行 源地址转换。这意味着从 NGINX 的角度来看,HTTP 请求的源 IP 始终是 接收请求的 Kubernetes 节点的 IP 地址。
在 NodePort 设置中保留源 IP 的推荐方法是将 ingress-nginx
服务 spec 中的 externalTrafficPolicy
字段值设置为 Local
(示例)。
警告
此设置实际上会 丢弃 发送到未运行任何 Ingress-Nginx 控制器实例的 Kubernetes 节点的数据包。考虑 将 NGINX Pod 分配给特定节点 以控制应在哪些节点上调度或不调度 Ingress-Nginx Controller。
示例
在由 3 个节点组成的 Kubernetes 集群中(外部 IP 为示例添加,大多数裸机环境中该值为
$ kubectl get node
NAME STATUS ROLES EXTERNAL-IP
host-1 Ready master 203.0.113.1
host-2 Ready node 203.0.113.2
host-3 Ready node 203.0.113.3
具有 2 个副本的 ingress-nginx-controller
Deployment
$ kubectl -n ingress-nginx get pod -o wide
NAME READY STATUS IP NODE
default-http-backend-7c5bc89cc9-p86md 1/1 Running 172.17.1.1 host-2
ingress-nginx-controller-cf9ff8c96-8vvf8 1/1 Running 172.17.0.3 host-3
ingress-nginx-controller-cf9ff8c96-pxsds 1/1 Running 172.17.1.4 host-2
发送到 host-2
和 host-3
的请求将被转发到 NGINX 并保留原始客户端 IP,而发送到 host-1
的请求将被丢弃,因为该节点上没有运行 NGINX 副本。
在 NodePort 设置中保留源 IP 的其他方法在此描述:源IP地址。
Ingress状态
由于 NodePort 服务根据定义不会分配 LoadBalancerIP,Ingress-Nginx Controller 不会更新其管理的 Ingress 对象的状态。
$ kubectl get ingress
NAME HOSTS ADDRESS PORTS
test-ingress myapp.example.com 80
尽管没有负载均衡器为 Ingress-Nginx Controller 提供公共 IP 地址,但可以通过设置 ingress-nginx
服务的 externalIPs
字段强制更新所有受管 Ingress 对象的状态。
警告
设置 externalIPs
不仅仅是让 Ingress-Nginx Controller 更新 Ingress 对象的状态。请阅读官方 Kubernetes 文档 服务 页面以及本文档中关于 外部IP 的部分以获取更多信息。
示例
给定以下 3 节点 Kubernetes 集群(外部 IP 为示例添加,大多数裸机环境中该值为
$ kubectl get node
NAME STATUS ROLES EXTERNAL-IP
host-1 Ready master 203.0.113.1
host-2 Ready node 203.0.113.2
host-3 Ready node 203.0.113.3
可以编辑 ingress-nginx
服务并在对象 spec 中添加以下字段
spec:
externalIPs:
• 203.0.113.1
• 203.0.113.2
• 203.0.113.3
这反过来会反映在 Ingress 对象上:
$ kubectl get ingress -o wide
NAME HOSTS ADDRESS PORTS
test-ingress myapp.example.com 203.0.113.1,203.0.113.2,203.0.113.3 80
重定向
由于 NGINX 不知道 NodePort 服务执行的端口转换,后端应用程序负责生成考虑外部客户端使用的 URL(包括 NodePort)的重定向 URL。
示例
由 NGINX 生成的重定向,例如 HTTP 到 HTTPS 或 domain
到 www.domain
,生成时不带 NodePort:
$ curl -D- http://myapp.example.com:30100`
HTTP/1.1 308 Permanent Redirect
Server: nginx/1.15.2
Location: https://myapp.example.com/ #-> HTTPS重定向中缺少NodePort
通过主机网络
在没有外部负载均衡器可用但使用 NodePort 又不是可选方案的环境中,可以配置 ingress-nginx
Pod 使用它们运行所在主机的网络,而不是专用的网络命名空间。这种方法的好处是 Ingress-Nginx Controller 可以直接将端口 80 和 443 绑定到 Kubernetes 节点的网络接口,而无需 NodePort 服务施加的额外网络转换。
注意
此方法不利用任何服务对象来暴露 Ingress-Nginx Controller。如果目标集群中存在 ingress-nginx
服务,建议删除它。
这可以通过在 Pod 的 spec 中启用 hostNetwork
选项来实现。
template:
spec:
hostNetwork: true
安全考虑
启用此选项会将 每个系统守护进程暴露给 Ingress-Nginx Controller 的任何网络接口,包括主机的环回接口。请仔细评估这可能对系统安全产生的影响。
示例
考虑这个由 2 个副本组成的 ingress-nginx-controller
Deployment,NGINX Pod 继承自主机的 IP 地址,而不是内部 Pod IP。
$ kubectl -n ingress-nginx get pod -o wide
NAME READY STATUS IP NODE
default-http-backend-7c5bc89cc9-p86md 1/1 Running 172.17.1.1 host-2
ingress-nginx-controller-5b4cf5fc6-7lg6c 1/1 Running 203.0.113.3 host-3
ingress-nginx-controller-5b4cf5fc6-lzrls 1/1 Running 203.0.113.2 host-2
这种部署方法的一个主要限制是每个集群节点上只能调度 一个 Ingress-Nginx Controller Pod,因为在同一网络接口上多次绑定相同端口在技术上是不可行的。由于这种情况而无法调度的 Pod 会失败并显示以下事件:
`$ kubectl -n ingress-nginx describe pod
Warning FailedScheduling default-scheduler 0/3 nodes are available: 3 node(s) didn’t have free ports for the requested pod ports.`
确保只创建可调度 Pod 的一种方法是将 Ingress-Nginx Controller 部署为 DaemonSet 而不是传统的 Deployment。
信息
DaemonSet 在每个集群节点(包括主节点)上精确调度一种类型的 Pod,除非节点配置为 排斥这些 Pod。更多信息请参阅 DaemonSet。
由于 DaemonSet 对象的大多数属性与 Deployment 对象相同,本文档将相应清单的配置留给用户自行决定。
与 NodePort 一样,这种方法有一些需要注意的特殊之处。
DNS解析
配置了 hostNetwork: true
的 Pod 不使用内部 DNS 解析器(即 kube-dns 或 CoreDNS),除非它们的 dnsPolicy
spec 字段设置为 ClusterFirstWithHostNet
。如果 NGINX 因任何原因需要解析内部名称,请考虑使用此设置。
Ingress状态
由于在使用主机网络的配置中没有服务暴露 Ingress-Nginx Controller,标准云设置中使用的默认 --publish-service
标志 不适用,所有 Ingress 对象的状态保持空白。
$ kubectl get ingress
NAME HOSTS ADDRESS PORTS
test-ingress myapp.example.com 80
相反,由于裸机节点通常没有 ExternalIP,必须启用 --report-node-internal-ip-address
标志,该标志将所有 Ingress 对象的状态设置为运行 Ingress-Nginx Controller 的所有节点的内部 IP 地址。
示例
给定由 2 个副本组成的 ingress-nginx-controller
DaemonSet
$ kubectl -n ingress-nginx get pod -o wide
NAME READY STATUS IP NODE
default-http-backend-7c5bc89cc9-p86md 1/1 Running 172.17.1.1 host-2
ingress-nginx-controller-5b4cf5fc6-7lg6c 1/1 Running 203.0.113.3 host-3
ingress-nginx-controller-5b4cf5fc6-lzrls 1/1 Running 203.0.113.2 host-2
控制器将其管理的所有 Ingress 对象的状态设置为以下值:
$ kubectl get ingress -o wide
NAME HOSTS ADDRESS PORTS
test-ingress myapp.example.com 203.0.113.2,203.0.113.3 80
注意
或者,可以使用 --publish-status-address
标志覆盖写入 Ingress 对象的地址。参见 命令行参数。
使用自备边缘
与云环境类似,此部署方法需要一个边缘网络组件,为 Kubernetes 集群提供公共入口点。此边缘组件可以是硬件(例如供应商设备)或软件(例如 HAproxy),通常由运维团队在 Kubernetes 环境之外管理。
这种部署建立在上述 通过NodePort服务 的基础上,有一个显著区别:外部客户端不直接访问集群节点,只有边缘组件可以访问。这特别适用于没有任何节点具有公共 IP 地址的私有 Kubernetes 集群。
在边缘端,唯一的先决条件是分配一个公共 IP 地址,将所有 HTTP 流量转发到 Kubernetes 节点和/或主节点。TCP 端口 80 和 443 上的传入流量被转发到目标节点上相应的 HTTP 和 HTTPS NodePort,如下图所示:
外部IP方案
源IP地址问题
这种方法无法保留HTTP请求的源IP地址,因此不建议使用,尽管它看起来很简单。
externalIPs
服务选项在前面的NodePort部分已经提到过。
根据Kubernetes官方文档的服务页面,externalIPs
选项会使kube-proxy
将发送到任意IP地址和服务端口的流量路由到该服务的端点。这些IP地址必须属于目标节点。
示例说明
假设有以下3节点的Kubernetes集群(外部IP为示例添加,大多数裸机环境中该值为
$ kubectl get node
NAME STATUS ROLES EXTERNAL-IP
host-1 Ready master 203.0.113.1
host-2 Ready node 203.0.113.2
host-3 Ready node 203.0.113.3
以及以下ingress-nginx
NodePort服务
$ kubectl -n ingress-nginx get svc
NAME TYPE CLUSTER-IP PORT(S)
ingress-nginx NodePort 10.0.220.217 80:30100/TCP,443:30101/TCP
可以在服务spec中设置以下外部IP,这样NGINX就可以通过NodePort和服务端口访问:
spec:
externalIPs:
- 203.0.113.2
- 203.0.113.3
$ curl -D- http://myapp.example.com:30100
HTTP/1.1 200 OK
Server: nginx/1.15.2
$ curl -D- http://myapp.example.com
HTTP/1.1 200 OK
Server: nginx/1.15.2
这里假设myapp.example.com子域名解析到203.0.113.2和203.0.113.3这两个IP地址。
注意事项
- 源IP保留:这种方法无法保留客户端的原始IP地址
- IP分配:指定的外部IP必须属于Kubernetes节点
- 端口冲突:需要确保服务端口(80/443)没有被系统其他服务占用
- 负载均衡:缺乏真正的负载均衡能力,只是简单的端口转发
替代方案建议
对于生产环境,建议考虑: • 使用MetalLB提供真正的负载均衡服务 • 采用专业的硬件负载均衡设备 • 部署软件负载均衡如HAProxy/Nginx作为边缘节点
配置验证
部署后可以通过以下方式验证:
# 检查服务配置
kubectl -n ingress-nginx get svc ingress-nginx -o yaml
# 测试端口连通性
nc -zv 203.0.113.2 80
nc -zv 203.0.113.3 443
# 检查节点防火墙规则
iptables -L -n -t nat | grep 80
iptables -L -n -t nat | grep 443
故障排查
如果遇到问题,可以检查:
- 节点防火墙是否放行了80/443端口
kube-proxy
是否正常运行- 服务是否正确配置了externalIPs
- 节点网络配置是否正确
这种方法虽然简单,但在生产环境中存在诸多限制,建议仅在测试或开发环境中使用。
外部IP方案深入解析
核心工作机制
当配置externalIPs时,kube-proxy会在每个节点上创建以下规则:
- 在NAT表的PREROUTING链中添加规则,将目标为externalIP的流量重定向到服务端口
- 在NAT表的OUTPUT链中添加类似规则,处理本地生成的流量
- 通过KUBE-SERVICES链最终将流量转发到实际Pod
典型问题排查指南
现象1:无法通过externalIP访问服务
# 检查节点上的iptables规则
sudo iptables -t nat -L PREROUTING -n --line-numbers
sudo iptables -t nat -L KUBE-SERVICES -n | grep <externalIP>
# 验证kube-proxy日志
kubectl logs -n kube-system <kube-proxy-pod> | grep -i externalip
# 检查节点网络配置
ip addr show | grep <externalIP>
现象2:源IP地址丢失
# 检查服务定义中的externalTrafficPolicy
kubectl get svc <service-name> -o jsonpath='{.spec.externalTrafficPolicy}'
# 正确的配置应该是
spec:
externalTrafficPolicy: Local
高级配置示例
多IP地址负载分配
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
spec:
externalIPs:
- 203.0.113.10
- 203.0.113.11
- 203.0.113.12
ports:
- name: http
port: 80
targetPort: 80
selector:
app: ingress-nginx
type: NodePort
结合NodePort使用
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
spec:
externalIPs:
- 203.0.113.10
ports:
- name: http
port: 80
nodePort: 30080
targetPort: 80
selector:
app: ingress-nginx
type: NodePort
性能优化建议
连接追踪优化:
# 增大连接追踪表大小
sysctl -w net.netfilter.nf_conntrack_max=1000000
sysctl -w net.netfilter.nf_conntrack_buckets=65536
kube-proxy模式选择:
# 使用ipvs模式提高性能
kubectl edit cm -n kube-system kube-proxy
# 修改mode为ipvs
TCP参数调优:
# 增加TCP连接队列
sysctl -w net.core.somaxconn=32768
sysctl -w net.ipv4.tcp_max_syn_backlog=8192
安全注意事项
IP欺骗防护:
# 启用RPF过滤
sysctl -w net.ipv4.conf.all.rp_filter=1
sysctl -w net.ipv4.conf.default.rp_filter=1
防火墙规则:
# 只允许特定IP访问externalIP
iptables -A INPUT -d 203.0.113.10 -p tcp --dport 80 -s 192.168.1.0/24 -j ACCEPT
iptables -A INPUT -d 203.0.113.10 -p tcp --dport 80 -j DROP
服务暴露限制:
# 通过NetworkPolicy限制访问
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ingress-nginx-allow
spec:
podSelector:
matchLabels:
app: ingress-nginx
ingress:
- from:
- ipBlock:
cidr: 192.168.1.0/24
监控指标
建议监控以下关键指标:
- 节点上的连接追踪表使用率
- kube-proxy的规则同步延迟
- 服务的每秒请求数(RPS)
- 每个externalIP的连接数
配置Prometheus示例:
- job_name: 'kube-proxy'
metrics_path: /metrics
static_configs:
- targets: ['kube-proxy.kube-system:10249']
最终建议方案
对于生产环境,我们建议采用分层架构:
- 前端:硬件负载均衡或云LB
- 中间层:使用MetalLB提供LoadBalancer服务
- 后端:配置适当的externalTrafficPolicy保留源IP
- 安全层:通过NetworkPolicy实施微隔离
这种架构既保持了externalIP方案的灵活性,又通过MetalLB提供了生产级负载均衡能力。