「Kubernetes」- 部署 NGINX Ingress Controller 组件

  CREATED BY JENKINSBOT

问题描述

仅当在 Kubernetes Cluster 中部署 Ingress Controller 之后,我们定义的 Ingress 资源才能生效。当然 Ingress Controller 有很多实现,比如 NGINX Ingress Controller、Traefik Ingress Controller、HAProxy Ingress 等等(参考 Ingress Controllers | Kubernetes 页面)。

针对我们的场景,我们使用 NGINX Ingress Controller 实现,这完全是基于我们具有很多 Nginx 使用经验,此外 Nginx 完全能满足我们的需要。即使日后 NGINX Ingress Controller 无法满足需求,我们亦可安装其他 Ingress Controller 实现,它们之间能够并存。

该笔记将记录:在 Kubernetes Cluster 中,如何部署 NGINX Ingress Controller 组件,以及相关问题处理。

解决方案

我们参照官方 Installation Guide 文档,并结合我们自身需求,我们整理出此文,所以该笔记是环境相关的,并不一定适合每种环境。

我们强烈建议参照官方文档,并结合自己的环境进行部署。

部署环境

我们为 Kubernetes v1.18.9 on Bare-metal 环境(自建 Kubernetes 集群)。如果是在 minikube、AWS、Azure 中部署,需要执行不同的命令,参考 Installation Guide 文档。

第一步、下载并且部署

官方提供 deploy.yaml 部署文件(如果无法下载,使用 ./deploy.yaml 文件):

wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.45.0/deploy/static/provider/baremetal/deploy.yaml

# 注意事项,因为 Deploymnet 的 nodeSelector: kubernetes.io/os: linux 设置,
# 因此保证 lable 包含 kubernetes.io/os: linux 的节点存在于集群中,额外留意就好,一般无需设置。

kubectl apply -f deploy.yaml

第二步、查看容器状态

查看 NGINX Ingress Controller 组件的状态:

# kubectl get pods --all-namespaces -l app.kubernetes.io/name=ingress-nginx --watch
NAMESPACE       NAME                                       READY   STATUS      RESTARTS   AGE
ingress-nginx   ingress-nginx-admission-create-hq52n       0/1     Completed   0          6m52s
ingress-nginx   ingress-nginx-admission-patch-9xqhj        0/1     Completed   0          6m52s
ingress-nginx   ingress-nginx-controller-cdfb85746-5qqlp   1/1     Running     0          6m52s

第三步、关于 Ingress 访问

已知问题

在这之前,我们已经完成 Ingress 部署,但是还有个非常重要的问题:Ingress Controller 的本质作用是作为 WebServer 存在,接收 HTTP 与 HTTPS 请求,然后它根据 Ingress 资源的定义来完成请求的传发。但是 Ingress Controller 是以 Pod 运行的,它所监听的端口是内部网络可见,外部是无法直接访问的。即使通过 Service NodePort 暴露出来,也并非 80 443 端口,因此用户访问的时候,需要使用 http://hostname:nodePort/ 的形式来访问站点。很显然,这是不能接受的。

注意事项:我们这些的讨论都是基于 Bare-metal 部署的(即自建 Kubernetes 集群),其他部署环境也许会有类似问题。

解决方案

针对该问题,官方文档(Bare-metal considerations)提出以下解决方案:
1)MetalLB(纯软件解决方案)
2)NodePort
3)Host network
4)Using a self-provisioned edge(NodePort + LoadBalancer)
5)External IPs

篇幅原因,我们这里不再赘述,建议详细阅读官方文档。

我们的方案

针对我们的场景,方案 1 3 4 皆可,我们最终选择 4 方案(这里面有些不可抗力的存在)。而我们在测试环境中采用 3 方案,因为测试在内网,没有不是 LoadBalancer 服务。方案 1 其实是最好的,但是我们不想折腾了:-)

针对 4 方案,我们还有个需要考虑的问题:使用三层(TCP),还是七层(HTTP(s)):

1)如果采用三层(TCP):我们需要将 Nginx Ingress 的 NodePort 固定为 30080 30443 端口,然后外部的 LoadBalancer 将 80 请求转发到 30080 端口,将 443 请求转发到 30443 端口。但是,这会损失客户端的原始请求地址,需要使用 PROXY 协议又会增加复杂度。

2)如果采用七层(HTTP(s)):后端 Nginx Ingress 收到的将完全是 HTTP 请求,仅需监听 30080 端口,然后 LoadBalancer 将请求转发到 30080 端口。这样能够保留客户端的网络地址。但是,我们无法使用 Cert Manager 来使用 Let’s Encrypt 的免费 SSL 证书。

3)如果采用二层:实际上就是 1 方案,MetalLB 能运行在二层模式下,这里就不再讨论,当我们遇到问题再说吧。

最后,我们选择方案 4 的三层(TCP)做法。这么完全就是为了使用 Let’s Encrypt 免费证书。如果我们出现需要客户端正式地址的方案,我们将在部署七层(HTTP(s))。最后会形成“三层与七层并存,针对不用业务采用不同 LoadBalancer 线路”的做法。

实施方案

这里没有很多需要配置的东西,更多的是各种技术方案的取舍。唯一需要我们配置的就是:修改 Service 的 NodePort 为 固定为 30080 30443 端口,然后配置 LoadBalancer 进行 80 443 的转发,这里不再展开。

在测试环境中

如果在测试环境中部署:

1)由于没有 Load Balancer 服务,所以将 Nginx Ingress Controller 运行在 hostNewwork 中:

# 修改 Pod 的 spec 配置,添加:
...
template:
  spec:
    hostNetwork: true
...

2)还要将 Nginx Ingress Controller 从 Deployment 修改为 DaemonSet 类型,否则仅有某个节点的 80/443 能够访问。

3)针对 hostNetwork: true 设置,还需要修改 Pod 的 dnsPolicy 为 ClusterFirstWithHostNet 类型。
# TODO 我们有个问题未清楚:默认使用 ClusterFirst 类型,Ingress 依旧能够通过 Service 名称来访问 Service 服务。

还有个问题是关于 Ingress 的状态,但是在测试环境这个问题无关紧要。

第四步、部署测试资源

当部署 NGINX Ingress Controller 组件部署完成时,便能够定义 Ingress、Server、Deployment 资源进行验证。

定义资源文件并应用(./ingress-example.yaml):

kubectl apply -f ingress-example.yaml

然后,在浏览器中访问,以验证 Ingress 能够正常工作。

简单 Ingress 管理

# 查看生成的配置文件
kubectl exec -it -n "ingress-nginx" nginx-ingress-controller-67956bf89d-fv58j cat /etc/nginx/nginx.conf

# 查看当前 Nginx 版本
kubectl exec -it $POD_NAME -n $POD_NAMESPACE -- /nginx-ingress-controller --version

补充说明

部署 Ingress Controller 时,不建议使用 DaemonSet 类型,而应该使用 Deployment 类型,然后将 POD 示例固定在某个节点上,或者使用 NodePort 方式。为什么这样做?假如我们的集群有 1000 个节点时,在每个节点都运行 Ingress Controller 并不是最优的。如此大规模的集群,一般仅会有少数节点接受外部请求。

常见问题汇总

nginx-ingress-controller in pending state

当我们部署 deploy.yaml 后,ingress-nginx Pod 一直处于 Pending 状态。当我们部署自定义的 Deployment 进行测试时,该普通的 Deployment 也处于 Pending 状态。

当我们向集群添加新节点后,调度到新节点的 Pod 能够正常运行,而不是 Pending 状态。因此我们怀疑是节点存在问题,而且其他节点的 Docker 版本是 19.3 版本,而这个节点是 20.10 版本(都是自己埋的坑)。

# 04/22/2021 因此我们尝试重启节点……然后问题得到解决(这并不算解决问题。也许是以为某些热修改的配置没有正确加载)。

# 04/23/2021 今天这个问题又出现了,我们查看节点的 kubelet 日志,发现 CNI 插件相关的错误。不管怎样,这个问题与 NGINX Ingress Controller 无关,因此这里不再讨论。

参考文献

Accessing Kubernetes Pods from Outside of the Cluster
Deployments | Kubernetes
How to sign in kubernetes dashboard? – Stack Overflow
ingress is not listening on port 80 #4799
Installing the Ingress Controller
k8s之nginx-ingress、 Daemonset实现生产案例
NGINX Ingress Controller for Kubernetes
NGINX Ingress Controller/Installation Guide
NGINX Ingress Controller/Troubleshooting
Bare-metal considerations – NGINX Ingress Controller