裸机环境注意事项

在传统的 环境中,可以按需使用网络负载均衡器,单个 Kubernetes 清单就足以为 Ingress-Nginx Controller 提供外部客户端的单一访问入口,并间接为集群内运行的任何应用提供服务。而 裸机 环境缺乏这种便利性,需要稍有不同的设置才能为外部消费者提供相同类型的访问。

云环境 裸机环境

本文档其余部分将介绍在裸机上运行的 Kubernetes 集群中部署 Ingress-Nginx Controller 的几种推荐方案。

纯软件解决方案:MetalLB

MetalLB 为不在受支持云提供商上运行的 Kubernetes 集群提供了网络负载均衡器实现,实际上允许在任何集群中使用 LoadBalancer 类型的服务。

本节演示如何将 MetalLB 的 二层配置模式 与 NGINX Ingress 控制器结合使用于具有 可公开访问节点 的 Kubernetes 集群中。在此模式下,一个节点会吸引所有流向 ingress-nginx 服务 IP 的流量。详见 流量策略

L2模式下的MetalLB

注意

本文档不描述其他支持的配置模式。

警告

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。

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-2host-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 或 domainwww.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 … Events: Type Reason From Message


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 对象相同,本文档将相应清单的配置留给用户自行决定。

使用hostNetwork的DaemonSet流程

与 NodePort 一样,这种方法有一些需要注意的特殊之处。

DNS解析

配置了 hostNetwork: true 的 Pod 不使用内部 DNS 解析器(即 kube-dnsCoreDNS),除非它们的 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为示例添加,大多数裸机环境中该值为

  1. $ kubectl get node
  2. NAME STATUS ROLES EXTERNAL-IP
  3. host-1 Ready master 203.0.113.1
  4. host-2 Ready node 203.0.113.2
  5. host-3 Ready node 203.0.113.3

以及以下ingress-nginx NodePort服务

  1. $ kubectl -n ingress-nginx get svc
  2. NAME TYPE CLUSTER-IP PORT(S)
  3. ingress-nginx NodePort 10.0.220.217 80:30100/TCP,443:30101/TCP

可以在服务spec中设置以下外部IP,这样NGINX就可以通过NodePort和服务端口访问:

  1. spec:
  2. externalIPs:
  3. - 203.0.113.2
  4. - 203.0.113.3
  1. $ curl -D- http://myapp.example.com:30100
  2. HTTP/1.1 200 OK
  3. Server: nginx/1.15.2
  4. $ curl -D- http://myapp.example.com
  5. HTTP/1.1 200 OK
  6. Server: nginx/1.15.2

这里假设myapp.example.com子域名解析到203.0.113.2和203.0.113.3这两个IP地址。

注意事项

  1. 源IP保留:这种方法无法保留客户端的原始IP地址
  2. IP分配:指定的外部IP必须属于Kubernetes节点
  3. 端口冲突:需要确保服务端口(80/443)没有被系统其他服务占用
  4. 负载均衡:缺乏真正的负载均衡能力,只是简单的端口转发

替代方案建议

对于生产环境,建议考虑: • 使用MetalLB提供真正的负载均衡服务 • 采用专业的硬件负载均衡设备 • 部署软件负载均衡如HAProxy/Nginx作为边缘节点

配置验证

部署后可以通过以下方式验证:

  1. # 检查服务配置
  2. kubectl -n ingress-nginx get svc ingress-nginx -o yaml
  3. # 测试端口连通性
  4. nc -zv 203.0.113.2 80
  5. nc -zv 203.0.113.3 443
  6. # 检查节点防火墙规则
  7. iptables -L -n -t nat | grep 80
  8. iptables -L -n -t nat | grep 443

故障排查

如果遇到问题,可以检查:

  1. 节点防火墙是否放行了80/443端口
  2. kube-proxy是否正常运行
  3. 服务是否正确配置了externalIPs
  4. 节点网络配置是否正确

这种方法虽然简单,但在生产环境中存在诸多限制,建议仅在测试或开发环境中使用。

外部IP方案深入解析

核心工作机制

当配置externalIPs时,kube-proxy会在每个节点上创建以下规则:

  1. 在NAT表的PREROUTING链中添加规则,将目标为externalIP的流量重定向到服务端口
  2. 在NAT表的OUTPUT链中添加类似规则,处理本地生成的流量
  3. 通过KUBE-SERVICES链最终将流量转发到实际Pod

典型问题排查指南

现象1:无法通过externalIP访问服务

  1. # 检查节点上的iptables规则
  2. sudo iptables -t nat -L PREROUTING -n --line-numbers
  3. sudo iptables -t nat -L KUBE-SERVICES -n | grep <externalIP>
  4. # 验证kube-proxy日志
  5. kubectl logs -n kube-system <kube-proxy-pod> | grep -i externalip
  6. # 检查节点网络配置
  7. ip addr show | grep <externalIP>

现象2:源IP地址丢失

  1. # 检查服务定义中的externalTrafficPolicy
  2. kubectl get svc <service-name> -o jsonpath='{.spec.externalTrafficPolicy}'
  3. # 正确的配置应该是
  4. spec:
  5. externalTrafficPolicy: Local

高级配置示例

多IP地址负载分配

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: ingress-nginx
  5. spec:
  6. externalIPs:
  7. - 203.0.113.10
  8. - 203.0.113.11
  9. - 203.0.113.12
  10. ports:
  11. - name: http
  12. port: 80
  13. targetPort: 80
  14. selector:
  15. app: ingress-nginx
  16. type: NodePort

结合NodePort使用

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: ingress-nginx
  5. spec:
  6. externalIPs:
  7. - 203.0.113.10
  8. ports:
  9. - name: http
  10. port: 80
  11. nodePort: 30080
  12. targetPort: 80
  13. selector:
  14. app: ingress-nginx
  15. type: NodePort

性能优化建议

  1. 连接追踪优化

    1. # 增大连接追踪表大小
    2. sysctl -w net.netfilter.nf_conntrack_max=1000000
    3. sysctl -w net.netfilter.nf_conntrack_buckets=65536
  2. kube-proxy模式选择

    1. # 使用ipvs模式提高性能
    2. kubectl edit cm -n kube-system kube-proxy
    3. # 修改mode为ipvs
  3. TCP参数调优

    1. # 增加TCP连接队列
    2. sysctl -w net.core.somaxconn=32768
    3. sysctl -w net.ipv4.tcp_max_syn_backlog=8192

安全注意事项

  1. IP欺骗防护

    1. # 启用RPF过滤
    2. sysctl -w net.ipv4.conf.all.rp_filter=1
    3. sysctl -w net.ipv4.conf.default.rp_filter=1
  2. 防火墙规则

    1. # 只允许特定IP访问externalIP
    2. iptables -A INPUT -d 203.0.113.10 -p tcp --dport 80 -s 192.168.1.0/24 -j ACCEPT
    3. iptables -A INPUT -d 203.0.113.10 -p tcp --dport 80 -j DROP
  3. 服务暴露限制

    1. # 通过NetworkPolicy限制访问
    2. apiVersion: networking.k8s.io/v1
    3. kind: NetworkPolicy
    4. metadata:
    5. name: ingress-nginx-allow
    6. spec:
    7. podSelector:
    8. matchLabels:
    9. app: ingress-nginx
    10. ingress:
    11. - from:
    12. - ipBlock:
    13. cidr: 192.168.1.0/24

监控指标

建议监控以下关键指标:

  1. 节点上的连接追踪表使用率
  2. kube-proxy的规则同步延迟
  3. 服务的每秒请求数(RPS)
  4. 每个externalIP的连接数

配置Prometheus示例:

  1. - job_name: 'kube-proxy'
  2. metrics_path: /metrics
  3. static_configs:
  4. - targets: ['kube-proxy.kube-system:10249']

最终建议方案

对于生产环境,我们建议采用分层架构:

  1. 前端:硬件负载均衡或云LB
  2. 中间层:使用MetalLB提供LoadBalancer服务
  3. 后端:配置适当的externalTrafficPolicy保留源IP
  4. 安全层:通过NetworkPolicy实施微隔离

这种架构既保持了externalIP方案的灵活性,又通过MetalLB提供了生产级负载均衡能力。