一. 简介
Ingress 服务是为全局的、代理不同后端 Service 而设置的负载均衡服务。所以,所谓 Ingress,就是 Service 的 “Service”。
1.2 场景
Kubernetes 的 Service 只有四层代理,暂时只支持这样的格式:IP:Port
访问。
而 Ingress api
支持实现七层代理,可以用来绑定外部域名。
1.3 组成
Ingress 由两部分组成:
- Ingress Controller
这是一个标准,可以有很多实现,例如下文的ingress-nginx
,它以pod形式运行的。 - IngressRule
以 yaml 形式为载体的一组声明式的策略,ingress-controller
会动态地按照策略生成配置文件(如:nginx.conf)
关于本文的项目的代码,都放于链接:GitHub资源
二. IngressRule
IngressRule
的Key
,就叫做:host
。它必须是一个标准的域名格式(Fully Qualified Domain Name)
的字符串,而不能是IP
地址。
2.1 案例
demo-ingress.yaml
文件内容如下:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: demo-ingress
annotations:
# use the shared ingress-nginx
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- ingress.wyatt.plus
secretName: demo-secret
rules:
- host: ingress.wyatt.plus
http:
paths:
- path: /svc1
backend:
serviceName: svc-1
servicePort: 80
- path: /svc2
backend:
serviceName: svc-2
servicePort: 80
2.2 分析
上面的IngressRule
规则的定义,则依赖于path
字段。这里的每一个path
都对应一个后端Service
。所以在我们的例子里,定义了两个path
,它们分别对应svc-1
和svc-2
这两个 Deployment
的 Service
。
三. Ingress Controller
Ingress Controller
会根据定义的Ingress
对象,提供对应的代理能力。目前,业界常用的各种反向代理项目,比如Nginx、HAProxy
、Envoy
、Traefik
等,都已经为 Kubernetes 专门维护了对应的 Ingress Controller。
当一个新的 Ingress 对象由用户创建后,nginx-ingress-controller
就会根据Ingress
对象里定义的内容,生成一份对应的 Nginx 配置文件(/etc/nginx/nginx.conf
),并使用这个配置文件启动一个Nginx 服务。而一旦 Ingress 对象被更新,nginx-ingress-controller
就会更新这个配置文件。
3.1 案例(mac平台)
对于 deploy.yaml
文件,我截取核心内容如下:
# Source: ingress-nginx/templates/controller-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
helm.sh/chart: ingress-nginx-3.23.0
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/version: 0.44.0
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: controller
name: ingress-nginx-controller
namespace: ingress-nginx
spec:
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
revisionHistoryLimit: 10
minReadySeconds: 0
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/component: controller
spec:
dnsPolicy: ClusterFirst
containers:
- name: controller
image: k8s.gcr.io/ingress-nginx/controller:v0.44.0@sha256:3dd0fac48073beaca2d67a78c746c7593f9c575168a17139a9955a82c63c4b9a
imagePullPolicy: IfNotPresent
lifecycle:
preStop:
exec:
command:
- /wait-shutdown
args:
- /nginx-ingress-controller
- --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
- --election-id=ingress-controller-leader
- --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
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
runAsUser: 101
allowPrivilegeEscalation: true
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
livenessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 5
readinessProbe:
httpGet:
path: /healthz
port: 10254
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
ports:
- name: http
containerPort: 80
protocol: TCP
- name: https
containerPort: 443
protocol: TCP
- name: webhook
containerPort: 8443
protocol: TCP
volumeMounts:
- name: webhook-cert
mountPath: /usr/local/certificates/
readOnly: true
resources:
requests:
cpu: 100m
memory: 90Mi
nodeSelector:
kubernetes.io/os: linux
serviceAccountName: ingress-nginx
terminationGracePeriodSeconds: 300
volumes:
- name: webhook-cert
secret:
secretName: ingress-nginx-admission
3.2 分析
以看到,在上述 YAML 文件中,我们定义了一个使用 nginx-ingress-controller 镜像的 Pod。需要注意的是,这个 Pod 的启动命令需要使用该 Pod 所在的 Namespace 作为参数。而这个信息,当然是通过 Downward API 拿到的,即:Pod 的 env 字段里的定义(env.valueFrom.fieldRef.fieldPath)。
四. Demo
我们做一个如下的场景站点:https://ingress.wyatt.plus ,这是一个微服务系统。
这两个系统,分别由名叫 “svc-1” 和 “svc-2” 这样两个 Deployment 来提供服务。
由于每个平台配置文件有区别,所以我将按照mac平台的方式按照配置文件。各平台的nginx controller配置
4.1 按照 Ingress Controller
我使用了Docker for mac相关的配置,执行如下指令:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.44.0/deploy/static/provider/cloud/deploy.yaml
4.2 配置Service与Deployment
4.2.1 demo-svc-1
该文件包了 Deployment 和 Service 这俩个配置,内容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: svc-1
spec:
replicas: 2
selector:
matchLabels:
app: svc-1
template:
metadata:
labels:
app: svc-1
spec:
containers:
- name: svc-1
image: nginxdemos/hello:plain-text
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: svc-1
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: svc-1
该配置通过 label selector 来进行匹配 app: svc-1 的 Pod。
4.2.2 demo-svc-2
该文件包了 Deployment 和 Service 这俩个配置,内容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: svc-2
spec:
replicas: 3
selector:
matchLabels:
app: svc-2
template:
metadata:
labels:
app: svc-2
spec:
containers:
- name: svc-2
image: nginxdemos/hello:plain-text
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: svc-2
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: svc-2
该配置通过 label selector 来进行匹配 app: svc-2 的 Pod。
4.3 创建Secret
创建 Secret 有2种方式,如下:
4.3.1 命令行创建
kubectl create secret tls demo-secret --key tls.key --cert tls.crt
4.3.2 以 YAML方式创建
apiVersion: v1
kind: Secret
metadata:
name: demo-secret
type: Opaque
data:
tls.crt: **************************
tls.key: **************************
4.4 配置Ingress
注意 Ingress 的版本,最新的 API 变动较多,最新的为 networking.k8s.io/v1。
“demo-ingress.yaml” 配置如下:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: demo-ingress
annotations:
# use the shared ingress-nginx
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- ingress.wyatt.plus
secretName: demo-secret
rules:
- host: ingress.wyatt.plus
http:
paths:
- path: /svc1
backend:
serviceName: svc-1
servicePort: 80
- path: /svc2
backend:
serviceName: svc-2
servicePort: 80
当我们执行 kubectl apply 创建成功后,我们可以再通过如下指令查看 Ingress 情况。
kubectl get ingress
# result
NAME CLASS HOSTS ADDRESS PORTS AGE
demo-ingress <none> ingress.wyatt.plus localhost 80, 443 110m
4.5 检查
关于当前的 “demo-ingress” ,我们可以通过更详细的指令查看里面内容:
kubectl describe ingress demo-ingress
# result
Name: demo-ingress
Namespace: default
Address: localhost
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
TLS:
demo-secret terminates ingress.wyatt.plus
Rules:
Host Path Backends
---- ---- --------
ingress.wyatt.plus
/svc1 svc-1:80 (10.1.3.124:80,10.1.3.126:80)
/svc2 svc-2:80 (10.1.3.123:80,10.1.3.125:80,10.1.3.127:80)
Annotations: kubernetes.io/ingress.class: nginx
可以看到,这个Ingress 对象最核心的部分,正是Rules 字段。其中,我们定义的 Host 是 ingress.wyatt.plus,它有两条转发规则(Path),分别转发给 /svc1 和 /svc2。
实际执行结果如下:
4.6 验证
我们可以通过访问这个 Ingress 的地址和端口,访问到我们前面部署的应用,
我们使用浏览器可以访问 https://ingress.wyatt.plus/svc1
和 https://ingress.wyatt.plus/svc2
这俩个 url 。
我们将分别看到如下的俩个页面:
- svc1
Server address: 10.1.3.124:80
Server name: svc-1-67c6fdbf4d-bwwlp
Date: 31/Mar/2021:22:53:06 +0000
URI: /svc1
Request ID: d37a5c0b9aaae3c71865ff7fcb3c615b
我们可以看到,访问这个 URL 得到的返回信息是:Server name: svc-1-67c6fdbf4d-bwwlp 。这正是 svc-1 这个 Deployment 的名字。
- svc2
Server address: 10.1.3.127:80
Server name: svc-2-77f44d76b-bx77x
Date: 31/Mar/2021:22:54:13 +0000
URI: /svc2
Request ID: 17645ccb197fe3fb6daf6200aba289bf
我们可以看到,访问这个 URL 得到的返回信息是:Server name: svc-2-77f44d76b-bx77x
。这正是 svc-2 这个 Deployment 的名字。
可以确认 Nginx Ingress Controller
创建的 Nginx 负载均衡器,已经成功地将请求转发给了对应的后端 Service。
我们也可以在 Kubernetes Dashboard
里面查看 Ingress 对于的路径匹配规则,如下图:
五. Ingress部署方式
5.1 Deployment + LoadBalancer 模式的 Service
5.1.1 细节
如果要把 Ingress 部署在公有云,那可以选择这种方式。用 Deployment 部署 ingress-controller ,创建一个 type 为 LoadBalancer
的 Service 关联这组 pod 。大部分公有云,都会为 LoadBalancer
的 Service 自动创建一个负载均衡器,通常还绑定了公网地址。只要把域名解析指向该地址,就实现了集群服务的对外暴露。
缺点:
- 需要额外购买公有云的服务,与平台挂钩。
5.1.2 架构图
5.2 Deployment + NodePort 模式的 Service
5.2.1 细节
同样用 Deployment 模式部署 ingress-controller ,并创建对应的服务,但是 type 为 NodePort。这样,ingress 就会暴露在集群节点ip的特定端口上。由于 NodePort 暴露的端口是随机端口,一般会在前面再搭建一套负载均衡器来转发请求。该方式一般用于宿主机是相对固定的环境ip地址不变的场景。
缺点:
NodePort 方式暴露 ingress 虽然简单方便,但是 NodePort 多了一层 NAT ,在请求量级很大时可能对性能会有一定影响。
请求节点会是类似https://www.wyatt.plus:30076
, 其中 30076 是kubectl get svc -n ingress-nginx
的 svc 暴露出来的 NodePort 端口。
5.2.2 架构
5.3 DaemonSet + HostNetwork + nodeSelector
5.3.1 细节
用 DaemonSet 结合 nodeSelector 来部署 ingress-controller 到特定的node上,然后使用 HostNetwork 直接把该 pod 与宿主机 node 的网络打通,直接使用宿主机的 80/433 端口就能访问服务。这时,ingress-controller 所在的node机器就很类似传统架构的边缘节点,比如机房入口的nginx服务器。
优点:
- 该方式整个请求链路最简单,性能相对 NodePort 模式更好。
缺点:
- 由于直接利用宿主机节点的网络和端口,一个 node 只能部署一个 ingress-controller pod。
5.2.3 架构
六. 总结
Ingress
只能工作在七层,而 Service 只能工作在四层。所以当我们想要在 Kubernetes 里为应用进行 TLS 配置等 HTTP 相关的操作时,都必须通过Ingress
来进行。
有了Ingress
这个抽象,我们就可以根据自己的需求来自由选择Ingress Controller
。我们只需要做很少的编程工作,就可以实现一个自己的Ingress Controller
。
Ingress
带来的灵活度和自由度,对于使用容器时代来说,其实是非常有意义的。