1. 快速安装

1.1. 使用 Helm 安装

在线安装

helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace

离线安装

wget https://github.com/kubernetes/ingress-nginx/releases/download/helm-chart-4.13.3/ingress-nginx-4.13.3.tgz

helm install ingress-nginx ingress-nginx-4.13.3.tgz --version 4.13.3 --namespace ingress-nginx --create-namespace

1.2. 使用 yaml 安装

wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.13.3/deploy/static/provider/cloud/deploy.yaml

kubectl apply -f deploy.yaml

1.3. 安装日志

NAME: ingress-nginx
LAST DEPLOYED: Mon Oct  6 13:55:42 2025
NAMESPACE: ingress-nginx
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The ingress-nginx controller has been installed.
It may take a few minutes for the load balancer IP to be available.
You can watch the status by running 'kubectl get service --namespace ingress-nginx ingress-nginx-controller --output wide --watch'

# 省略 ……

1.4. 查看服务

$ kubectl get svc ingress-nginx-controller -n ingress-nginx

NAME                      TYPE          CLUSTER-IP    EXTERNAL-IP  PORT(S)
ingress-nginx-controller  LoadBalancer  10.99.75.212  <pending>    80:31647/TCP,443:32648/TCP

ingress-nginx-controller 对外曝露了 2 个端口:http 为 31647;https 为 32648。

2. 应用示例

这一节,我将先部署 my-nginx 服务,然后创建 ingress 规则和配置 TLS 证书,将对 https://www.igeeksky.com 的请求转发给 my-nginx 进行处理。

2.1. 部署 nginx

创建 my-nginx-deploy.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
  namespace: dev
spec:
  selector:
    matchLabels:
      app: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx:1.29.1
        ports:
        - containerPort: 80

---

apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  namespace: dev
spec:
  selector:
    app: my-nginx
  type: ClusterIP  
  ports:
    - port: 8080
      targetPort: 80

部署服务:

kubectl apply -f my-nginx-deploy.yaml

2.2. 创建 secret

如果想要实现 HTTPS 通信,需先创建 tls 类型的 secret 配置,然后在 Ingress 配置中指定使用此 secret 即可。

我这里已提前准备好了证书和私钥,因此直接创建 secret 即可。

kubectl create secret tls igeeksky-com-tls \
  --cert=./igeeksky.com.crt \
  --key=./igeeksky.com.key \
  --namespace=dev

2.3. 配置 Ingress 规则

创建 igeeksky-com-ingress.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: igeeksky-com-ingress
  namespace: dev
  annotations:
    # Use this annotation to enforce a redirect from HTTP to HTTPS
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
spec:
  ingressClassName: nginx
  rules:
    - host: www.igeeksky.com
      http:
        paths:
          - pathType: Prefix
            path: /
            backend:
              service:
                name: my-nginx
                port:
                  number: 8080
  tls:
    - hosts:
      - www.igeeksky.com
      secretName: igeeksky-com-tls

应用 Ingress 规则:

kubectl apply -f igeeksky-com-ingress.yaml

查看 Ingress

$ kubectl get ingress -A

NAMESPACE   NAME                  CLASS   HOSTS             ADDRESS   PORTS     AGE
dev         igeeksky-com-ingress   nginx   www.igeeksky.com             80, 443   2d

我们可以看到,关于 www.igeeksky.com 的规则已经成功应用。

2.4. 外部访问

先在集群外部主机添加 DNS 记录:

C:\Windows\System32\drivers\etc\hosts

192.168.50.130                www.igeeksky.com
192.168.50.135                www.igeeksky.com
192.168.50.136                www.igeeksky.com

然后,在该集群外部主机打开浏览器,输入 https://www.igeeksky.com:32648 即可通过 Ingress 访问后端的 my-nginx 服务。

3. Service 配置

默认情况下,Service: ingress-nginx-controller 的部分配置如下:

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx-controller
spec:
  externalIPs:
  type: LoadBalancer # NodePort, ClusterIP
  externalTrafficPolicy: Cluster # Local

接下来,我们来测试不同的配置项,并重点关注以下问题:

① 是否可以使用标准的 HTTP 端口 80/443

② 是否任意节点均可接受请求?

③ 是否可以获取真实的源 IP?

3.1. LoadBalancer or NodePort

spec:
  externalIPs:
  type: LoadBalancer # NodePort
  externalTrafficPolicy: Cluster # Local

示意图

ingress-service-nodeport

这个示意图中,仅有 192.168.50.135 节点存在 Pod: ingress-nginx-controller

流量路径:ClientService(Ingress-nginx:31647/32648) → kube-proxyClusterIPPod(Ingress-nginx:80/443) → App

3.1.1. type

spec:typeLoadBalancerNodePort 时,会在所有节点曝露服务端口(示例中的 31647/32648)。

spec: typeNodePort 时,服务信息如下:

# 查看服务
$ kubectl get svc ingress-nginx-controller -n ingress-nginx

NAME                       TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)
ingress-nginx-controller   NodePort   10.99.75.212   <none>        80:31647/TCP,443:32648/TCP

spec:typeLoadBalancer 时,服务信息如下:

NAME                      TYPE          CLUSTER-IP    EXTERNAL-IP  PORT(S)
ingress-nginx-controller  LoadBalancer  10.99.75.212  <pending>    80:31647/TCP,443:32648/TCP

LoadBalancer 相当于是 NodePort 的云环境增强版,还会期望云厂商的外部负载均衡器提供公网 IP。

因为这里是用 VMware 搭建的本地虚拟机集群,无云厂商的负载均衡器分配公网 IP,所以 EXTERNAL-IP 会一直显示 pending 状态。

因此,如果未使用云平台的负载均衡器,将 spec:type 设为 NodePort 即可。

关于端口

根据 Kubernetes 的端口规则, service 默认曝露的端口范围为 30000-32767

因此,一般情况下,我们无法使用标准的 80 和 443 端口,即无法直接使用 https://www.igeeksky.com 来访问后端服务,而是必须附加端口 32648

3.1.2. externalTrafficPolicy

spec:externalTrafficPolicyCluster 时,客户端可访问任意集群节点,最终都会转发到 192.168.50.135 节点去处理。

spec:externalTrafficPolicyLocal 时,客户端仅能访问 192.168.50.135 节点,其它节点接收到的请求会被丢弃。

关于 源 IP

对于后端应用,可能会需要根据客户端 IP 来执行类似于白名单、黑名单这样的逻辑。

所以,我们来测试下如何配置才能获取真实的客户端 IP。

测试之前,先添加 DNS 记录,将 www.igeeksky.com 指向 192.168.50.135

# 查看 App(my-nginx) 日志
$ kubectl logs my-nginx-788998658d-24hk6 -n dev -f


# 测试 spec:externalTrafficPolicy:Cluster
curl -k -v https://www.igeeksky.com:32648

# 192.168.50.135 为接收请求的集群节点 IP,日志如下:
172.30.230.33 - - [14/Oct/2025:08:20:30 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.13.0" "192.168.50.135"


# 测试 spec:externalTrafficPolicy:Local
curl -k -v https://www.igeeksky.com:32648

# 192.168.50.218 为客户端 IP,日志如下:
172.30.230.33 - - [14/Oct/2025:08:58:53 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/8.13.0" "192.168.50.218"

externalTrafficPolicyCluster 时,所有节点均可接收请求,但在转发请求过程中需执行 NAT 地址转换,所以源IP 地址将变成 Kubernetes 集群中接收请求的节点 IP。

所以,如果想获取真实的客户端 IP 地址,那么 externalTrafficPolicy 必须设为 Local

3.1.3. 小结

spec:type:当使用云厂商的负载均衡器时,设为 LoadBalancer;否则设为 NodePort

spec:externalTrafficPolicy: 当设为 Cluster 时,所有节点都可以接受请求,但无法保留源IP;当设为 Local 时,仅仅部署有 Pod: ingress-nginx-controller 的节点能处理请求,但可以保留源 IP。

3.2. External IPs

如果希望使用标准的 80/443 端口来接收请求,一种可行的办法是配置 externalIPs

示例配置:

spec:
  externalIPs:
  - 192.168.50.135
  - 192.168.50.136
  type: ClusterIP # NodePort, LoadBalancer
  externalTrafficPolicy: Cluster # Local

示意图

ingress-service-external-ips

流量路径:ClientService(Ingress-nginx:80/443) → kube-proxyClusterIPPod(Ingress-nginx:80/443) → App

查看服务

# 查看服务
kubectget svc ingress-nginx-controller -n ingress-nginx

NAME                       TYPE        CLUSTER-IP     EXTERNAL-IP                     PORT(S)
ingress-nginx-controller   ClusterIP   10.99.75.212   192.168.50.135,192.168.50.136   80/TCP,443/TCP

可以看到 EXTERNAL-IP 的状态显示为手动设定的 192.168.50.135,192.168.50.136

测试不同配置

关于源 IP、可接受请求节点、可接受请求端口等的测试结果如下:

spec:externalTrafficPolicy: Local:仅 135 节点可接受请求;可以获取真实的源 IP。

spec:externalTrafficPolicy: Cluster135136 节点均可接受请求;无法获取真实的源 IP。

spec:type: ClusterIP:仅 80/443 可接受请求。

spec:type: NodePortspec:type: LoadBalancer80/44331647/32648 均可接受请求。

注:Ingress-nginx官方文档说,当配置 externalIPs 以提供外部服务时,无论如何都无法保留真实源 IP。

经测试,这种说法是错误的,只要配置是 spec:externalTrafficPolicy:Local,同样可以获取真实源 IP。

4. HostNetwork

上一节的示例配置中,对外提供服务的都是 Service 层。

但对于 Ingress 这种对外曝露接口的应用网关而言,我们完全可以直接使用 Pod 来提供对外服务。

这样,既可以少一层网络转发,还可以直接曝露标准的 80/443 端口。

示意图

ingress-host-network

流量路径:ClientPod(Ingress-nginx:80/443) → App

4.1. 删除资源

这一节中,我们已不再需要 Service,并且需将 Deployment 改为 DaemonSet,所以先删除。

# 删除 Service
kubectl delete svc ingress-nginx-controller -n ingress-nginx

# 删除 Deployment 
kubectl delete deploy ingress-nginx-controller -n ingress-nginx

4.2. DaemonSet

增加节点标签(用以选择部署的节点):

kubectl label nodes k8s-worker-1 ingress-ready=true
kubectl label nodes k8s-worker-2 ingress-ready=true

修改清单

为了将 Deployment 改为 DaemonSet,我从 Ingress-nginx 官网下载的 deploy.yaml 中仅复制了 kind: Deployment 部分的内容进行修改,并命名为 ingress-daemonset.yaml

其它的诸如 RoleRoleBinding…… 之类的其它资源因为之前已经部署并且未删除,所以无需再重复部署。

关键修改如下:

# kind: Deployment
kind: DaemonSet

# dnsPolicy: ClusterFirst
dnsPolicy: ClusterFirstWithHostNet

# hostNetwork: false
hostNetwork: true

修改后完整的 ingress-daemonset.yaml 配置内容

apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
    app.kubernetes.io/version: 1.13.3
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  minReadySeconds: 0
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app.kubernetes.io/component: controller
      app.kubernetes.io/instance: ingress-nginx
      app.kubernetes.io/name: ingress-nginx
  #strategy:
  #  rollingUpdate:
  #    maxUnavailable: 1
  #  type: RollingUpdate
  template:
    metadata:
      labels:
        app.kubernetes.io/component: controller
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
        app.kubernetes.io/version: 1.13.3
    spec:
      automountServiceAccountToken: true
      containers:
      - args:
        - /nginx-ingress-controller
        #- --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
        - --election-id=ingress-nginx-leader
        - --controller-class=k8s.io/ingress-nginx
        - --ingress-class=nginx
        - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
        - --validating-webhook=:8443
        - --validating-webhook-certificate=/usr/local/certificates/cert
        - --validating-webhook-key=/usr/local/certificates/key
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: LD_PRELOAD
          value: /usr/local/lib/libmimalloc.so
        image: registry.k8s.io/ingress-nginx/controller:v1.13.3@sha256:1b044f6dcac3afbb59e05d98463f1dec6f3d3fb99940bc12ca5d80270358e3bd
        imagePullPolicy: IfNotPresent
        lifecycle:
          preStop:
            exec:
              command:
              - /wait-shutdown
        livenessProbe:
          failureThreshold: 5
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        name: controller
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
        - containerPort: 443
          name: https
          protocol: TCP
        - containerPort: 8443
          name: webhook
          protocol: TCP
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /healthz
            port: 10254
            scheme: HTTP
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          requests:
            cpu: 100m
            memory: 90Mi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            add:
            - NET_BIND_SERVICE
            drop:
            - ALL
          readOnlyRootFilesystem: false
          runAsGroup: 82
          runAsNonRoot: true
          runAsUser: 101
          seccompProfile:
            type: RuntimeDefault
        volumeMounts:
        - mountPath: /usr/local/certificates/
          name: webhook-cert
          readOnly: true
      hostNetwork: true
      dnsPolicy: ClusterFirstWithHostNet
      nodeSelector:
        # kubernetes.io/os: linux
        # 仅选择有 ingress-ready 标签的节点
        ingress-ready: "true"
      serviceAccountName: ingress-nginx
      terminationGracePeriodSeconds: 300
      volumes:
      - name: webhook-cert
        secret:
          secretName: ingress-nginx-admission

应用配置

kubectl apply -f ingress-daemonset.yaml

4.3. 测试

现在,我们已经删除了原有的 servicedeployment,并以 DaemonSet 方式在 135136 节点重新部署了 Pod:Ingress-nginx-controller

查看 pod 及 ingress

kubectl get pod -n ingress-nginx -o wide

NAME                             READY   STATUS    IP               NODE
ingress-nginx-controller-6b9lh   1/1     Running   192.168.50.136   k8s-worker-2
ingress-nginx-controller-s254t   1/1     Running   192.168.50.135   k8s-worker-1


kubectl get ingress -n dev

NAME                   CLASS   HOSTS              ADDRESS                         PORTS
igeeksky-com-ingress   nginx   www.igeeksky.com   192.168.50.135,192.168.50.136   80, 443

测试

# 查看 App(my-nginx) 日志
$ kubectl logs my-nginx-788998658d-24hk6 -n dev -f

curl -k -v https://www.igeeksky.com

# 192.168.50.218 为客户端 IP,192.168.50.136 为接受请求的节点 IP,日志如下:
192.168.50.136 - - [22/Oct/2025:15:32:32 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36" "192.168.50.218"

😊 可以顺利访问后端的 my-nginx,并且显示真实的客户端 IP,测试成功!

5. 负载均衡器

在此之前,我们都假设客户端与 K8s 集群位于同一局域网,客户端可以直接访问 K8s 集群。

但更一般的情况是,我们会再增加一层负载均衡器,DNS 记录中添加负载均衡器的外部 IP,客户端需经由负载均衡器来访问 K8s 集群。

如此一来,则可以较好地屏蔽 K8s 集群的节点变化。

5.1. 外部负载均衡器

关于外部负载均衡器,有硬件解决方案和软件解决方案,如果是云平台,还可以选择云厂商的负载均衡器。

软件解决方案中,又有 LVSNginxHAProxy 等,总之就是方案众多,需根据自身网络环境和业务需求而定。

因此,这里仅简单介绍下,并给出简化的网络架构示意图。

ingress-lb

当使用外部负载均衡器时:

1、Ingress 的曝露端口:如使用 LVSDR 模式时需曝露 80/443 端口,而 NAT 模式则随意。

2、Ingress 同样可以有两种方案:有 service 层;无 service 层。

3、源 IP:如使用 NginxStrem 模块作为负载均衡,应用获取到的源 IP 将是负载均衡器的 IP。

4、K8s 集群的网络参数:如使用 LVSDR 模式,需设置 K8s 集群节点的虚 IP、ARP 抑制等,并注意不能与 K8s 集群的网络插件配置发生冲突。

5、限定 Ingress-nginx 部署节点:为了让负载均衡器能将请求转发到正确节点,当配置为使用 HostNetworkspec:externalTrafficPolicy: Local 时,需配合节点亲和度,并使用 DaemonSet 方式将 Ingress-nginx 部署到指定节点。

5.2. 内部负载均衡器

当使用外部负载均衡器时,如 K8s 集群节点发生变更,可能还需调整外部负载均衡器的下游节点信息。

那么,有没有可能在 K8s 集群内部部署一个负载均衡器呢?这样,当 Ingress-nginx 的部署发生变化时就无需再进行额外的操作。

答案是有的,MetalLB 正是这样的一个项目。

但因为当前 MetalLB 仍处于 beta 阶段,所以这里不再过多赘述,如有兴趣可仔细阅读项目成熟度并自行作出评估。

5.3. 小结

关于负载均衡器的选择和使用,这是一个稍显复杂的话题。

技术决策时,通常需考虑部署复杂度、配置灵活度、性能、网络环境、是否能获取源 IP 等诸多因素。

另外,如果要实现高可用,可能还需搭配类似于 Keepalived 这样的组件。

再有,还可以在 DNS 服务器中为同一域名配置多个 IP,以实现 DNS 负载均衡和故障转移。

当然,如果只是实验性使用,【Nginx-Stream 模块】 + 【K8s-HostNetwork】是我认为最最简单的解决方案。

后续如果有时间的话,再来详细介绍 LVS 各种模式下与 K8s 集群的搭配。

6.总结

这篇文章首先介绍了 Ingress-nginx 的基本安装和应用示例。

然后,重点介绍了如何曝露 HTTP 标准端口,如何才能获取真实源 IP,以及不同配置下可接受请求的节点变化;

最后,概略介绍了负载均衡器的主要类型和注意事项。

另外,Ingress APIKubernetes v1.19 开始已是冻结状态,而 Gateway API 是其后继者。

因此,如果是新搭建的 Kubernetes 集群,更推荐直接使用 Gateway API

7. 参考文档

https://metallb.io/

https://github.com/kubernetes/ingress-nginx

https://kubernetes.github.io/ingress-nginx/deploy/

https://kubernetes.io/docs/concepts/services-networking/ingress/