一. 简介

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

IngressRuleKey,就叫做: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-1svc-2 这两个 DeploymentService

三. Ingress Controller

Ingress Controller 会根据定义的Ingress 对象,提供对应的代理能力。目前,业界常用的各种反向代理项目,比如Nginx、HAProxyEnvoyTraefik等,都已经为 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/svc1https://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带来的灵活度和自由度,对于使用容器时代来说,其实是非常有意义的。

转自:https://blog.wyatt.plus/archives/158

最后编辑: kuteng  文档更新时间: 2022-05-19 15:31   作者:kuteng