关于后端:谈谈Service与Ingress

2次阅读

共计 8370 个字符,预计需要花费 21 分钟才能阅读完成。

你好,我是张磊。明天我和你分享的主题是:谈谈 Service 与 Ingress。

在上一篇文章中,我为你具体解说了将 Service 裸露给外界的三种办法。其中有一个叫作 LoadBalancer 类型的 Service,它会为你在 Cloud Provider(比方:Google Cloud 或者 OpenStack)里创立一个与该 Service 对应的负载平衡服务。

然而,置信你也应该能感触到,因为每个 Service 都要有一个负载平衡服务,所以这个做法实际上既节约老本又高。作为用户,我其实更心愿看到 Kubernetes 为我内置一个全局的负载均衡器。而后,通过我拜访的 URL,把申请转发给不同的后端 Service。

这种全局的、为了代理不同后端 Service 而设置的负载平衡服务,就是 Kubernetes 里的 Ingress 服务。

所以,Ingress 的性能其实很容易了解:所谓 Ingress,就是 Service 的“Service”。

举个例子,如果我当初有这样一个站点:https://cafe.example.com。其中,https://cafe.example.com/coffee,对应的是“咖啡点餐零碎”。而,https://cafe.example.com/tea,对应的则是“茶水点餐零碎”。这两个零碎,别离由名叫 coffee 和 tea 这样两个 Deployment 来提供服务。

那么当初,[strong_begin]我如何能应用 Kubernetes 的 Ingress 来创立一个对立的负载均衡器,从而实现当用户拜访不同的域名时,可能拜访到不同的 Deployment 呢?[strong_end]

上述性能,在 Kubernetes 里就须要通过 Ingress 对象来形容,如下所示:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: cafe-ingress
spec:
  tls:
  - hosts:
    - cafe.example.com
    secretName: cafe-secret
  rules:
  - host: cafe.example.com
    http:
      paths:
      - path: /tea
        backend:
          serviceName: tea-svc
          servicePort: 80
      - path: /coffee
        backend:
          serviceName: coffee-svc
          servicePort: 80

在下面这个名叫 cafe-ingress.yaml 文件中,最值得咱们关注的,是 rules 字段。在 Kubernetes 里,这个字段叫作:IngressRule

IngressRule 的 Key,就叫做:host。它必须是一个规范的域名格局(Fully Qualified Domain Name)的字符串,而不能是 IP 地址。

备注:Fully Qualified Domain Name 的具体格局,能够参考 RFC 3986 规范。

而 host 字段定义的值,就是这个 Ingress 的入口。这也就意味着,当用户拜访 cafe.example.com 的时候,实际上拜访到的是这个 Ingress 对象。这样,Kubernetes 就能应用 IngressRule 来对你的申请进行下一步转发。

而接下来 IngressRule 规定的定义,则依赖于 path 字段。你能够简略地了解为,这里的每一个 path 都对应一个后端 Service。所以在咱们的例子里,我定义了两个 path,它们别离对应 coffee 和 tea 这两个 Deployment 的 Service(即:coffee-svc 和 tea-svc)。

通过下面的解说,不难看到,所谓 Ingress 对象,其实就是 Kubernetes 我的项目对“反向代理”的一种形象。

一个 Ingress 对象的次要内容,实际上就是一个“反向代理”服务(比方:Nginx)的配置文件的形容。而这个代理服务对应的转发规定,就是 IngressRule。

这就是为什么在每条 IngressRule 里,须要有一个 host 字段来作为这条 IngressRule 的入口,而后还须要有一系列 path 字段来申明具体的转发策略。这其实跟 Nginx、HAproxy 等我的项目的配置文件的写法是统一的。

而有了 Ingress 这样一个对立的形象,Kubernetes 的用户就无需关怀 Ingress 的具体细节了。

在理论的应用中,你只须要从社区里抉择一个具体的 Ingress Controller,把它部署在 Kubernetes 集群里即可。

而后,这个 Ingress Controller 会依据你定义的 Ingress 对象,提供对应的代理能力。目前,业界罕用的各种反向代理我的项目,比方 Nginx、HAProxy、Envoy、Traefik 等,都曾经为 Kubernetes 专门保护了对应的 Ingress Controller。

接下来,[strong_begin]我就以最罕用的 Nginx Ingress Controller 为例,在咱们后面用 kubeadm 部署的 Bare-metal 环境中,和你实际一下 Ingress 机制的应用过程。[strong_end]

部署 Nginx Ingress Controller 的办法非常简单,如下所示:

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml

其中,在 mandatory.yaml 这个文件里,正是 Nginx 官网为你保护的 Ingress Controller 的定义。咱们来看一下它的内容:

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-configuration
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: nginx-ingress-controller
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/part-of: ingress-nginx
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/part-of: ingress-nginx
      annotations:
        ...
    spec:
      serviceAccountName: nginx-ingress-serviceaccount
      containers:
        - name: nginx-ingress-controller
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.20.0
          args:
            - /nginx-ingress-controller
            - --configmap=$(POD_NAMESPACE)/nginx-configuration
            - --publish-service=$(POD_NAMESPACE)/ingress-nginx
            - --annotations-prefix=nginx.ingress.kubernetes.io
          securityContext:
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            # www-data -> 33
            runAsUser: 33
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
            - name: http
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
            - name: https
              containerPort: 443

能够看到,在上述 YAML 文件中,咱们定义了一个应用 nginx-ingress-controller 镜像的 Pod。须要留神的是,这个 Pod 的启动命令须要应用该 Pod 所在的 Namespace 作为参数。而这个信息,当然是通过 Downward API 拿到的,即:Pod 的 env 字段里的定义(env.valueFrom.fieldRef.fieldPath)。

而这个 Pod 自身,就是一个监听 Ingress 对象以及它所代理的后端 Service 变动的控制器。

当一个新的 Ingress 对象由用户创立后,nginx-ingress-controller 就会依据 Ingress 对象里定义的内容,生成一份对应的 Nginx 配置文件(/etc/nginx/nginx.conf),并应用这个配置文件启动一个 Nginx 服务。

而一旦 Ingress 对象被更新,nginx-ingress-controller 就会更新这个配置文件。须要留神的是,如果这里只是被代理的 Service 对象被更新,nginx-ingress-controller 所治理的 Nginx 服务是不须要从新加载(reload)的。这当然是因为 nginx-ingress-controller 通过 Nginx Lua 计划实现了 Nginx Upstream 的动静配置。

此外,nginx-ingress-controller 还容许你通过 Kubernetes 的 ConfigMap 对象来对上述 Nginx 配置文件进行定制。这个 ConfigMap 的名字,须要以参数的形式传递给 nginx-ingress-controller。而你在这个 ConfigMap 里增加的字段,将会被合并到最初生成的 Nginx 配置文件当中。

能够看到,一个 Nginx Ingress Controller 为你提供的服务,其实是一个能够依据 Ingress 对象和被代理后端 Service 的变动,来主动进行更新的 Nginx 负载均衡器。

当然,为了让用户可能用到这个 Nginx,咱们就须要创立一个 Service 来把 Nginx Ingress Controller 治理的 Nginx 服务裸露进来,如下所示:

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/provider/baremetal/service-nodeport.yaml

因为咱们应用的是 Bare-metal 环境,所以 service-nodeport.yaml 文件里的内容,就是一个 NodePort 类型的 Service,如下所示:

apiVersion: v1
kind: Service
metadata:
  name: ingress-nginx
  namespace: ingress-nginx
  labels:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx
spec:
  type: NodePort
  ports:
    - name: http
      port: 80
      targetPort: 80
      protocol: TCP
    - name: https
      port: 443
      targetPort: 443
      protocol: TCP
  selector:
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/part-of: ingress-nginx

能够看到,这个 Service 的惟一工作,就是将所有携带 ingress-nginx 标签的 Pod 的 80 和 433 端口裸露进来。

而如果你是私有云上的环境,你须要创立的就是 LoadBalancer 类型的 Service 了。

上述操作实现后,你肯定要记录下这个 Service 的拜访入口,即:宿主机的地址和 NodePort 的端口,如下所示:

$ kubectl get svc -n ingress-nginx
NAME            TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx   NodePort   10.105.72.96   <none>        80:30044/TCP,443:31453/TCP   3h

为了前面方便使用,我会把上述拜访入口设置为环境变量:

$ IC_IP=10.168.0.2 # 任意一台宿主机的地址
$ IC_HTTPS_PORT=31453 # NodePort 端口

[strong_begin]在 Ingress Controller 和它所须要的 Service 部署实现后,咱们就能够应用它了。[strong_end]

备注:这个“咖啡厅”Ingress 的所有示例文件,都在这里。

首先,咱们要在集群里部署咱们的利用 Pod 和它们对应的 Service,如下所示:

$ kubectl create -f cafe.yaml

而后,咱们须要创立 Ingress 所需的 SSL 证书(tls.crt)和密钥(tls.key),这些信息都是通过 Secret 对象定义好的,如下所示:

$ kubectl create -f cafe-secret.yaml

这一步实现后,咱们就能够创立在本篇文章一开始定义的 Ingress 对象了,如下所示:

$ kubectl create -f cafe-ingress.yaml

这时候,咱们就能够查看一下这个 Ingress 对象的信息,如下所示:

$ kubectl get ingress
NAME           HOSTS              ADDRESS   PORTS     AGE
cafe-ingress   cafe.example.com             80, 443   2h

$ kubectl describe ingress cafe-ingress
Name:             cafe-ingress
Namespace:        default
Address:          
Default backend:  default-http-backend:80 (<none>)
TLS:
  cafe-secret terminates cafe.example.com
Rules:
  Host              Path  Backends
  ----              ----  --------
  cafe.example.com  
                    /tea      tea-svc:80 (<none>)
                    /coffee   coffee-svc:80 (<none>)
Annotations:
Events:
  Type    Reason  Age   From                      Message
  ----    ------  ----  ----                      -------
  Normal  CREATE  4m    nginx-ingress-controller  Ingress default/cafe-ingress

能够看到,这个 Ingress 对象最外围的局部,正是 Rules 字段。其中,咱们定义的 Host 是cafe.example.com,它有两条转发规定(Path),别离转发给 tea-svc 和 coffee-svc。

当然,在 Ingress 的 YAML 文件里,你还能够定义多个 Host,比方 restaurant.example.commovie.example.com 等等,来为更多的域名提供负载平衡服务。

接下来,咱们就能够通过拜访这个 Ingress 的地址和端口,拜访到咱们后面部署的利用了,比方,当咱们拜访 https://cafe.example.com:443/coffee 时,应该是 coffee 这个 Deployment 负责响应我的申请。咱们能够来尝试一下:

$ curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/coffee --insecureServer address: 10.244.1.56:80
Server name: coffee-7dbb5795f6-vglbv
Date: 03/Nov/2018:03:55:32 +0000
URI: /coffee
Request ID: e487e672673195c573147134167cf898

咱们能够看到,拜访这个 URL 失去的返回信息是:Server name: coffee-7dbb5795f6-vglbv。这正是 coffee 这个 Deployment 的名字。

而当我拜访 https://cafe.example.com:433/tea 的时候,则应该是 tea 这个 Deployment 负责响应我的申请(Server name: tea-7d57856c44-lwbnp),如下所示:

$ curl --resolve cafe.example.com:$IC_HTTPS_PORT:$IC_IP https://cafe.example.com:$IC_HTTPS_PORT/tea --insecure
Server address: 10.244.1.58:80
Server name: tea-7d57856c44-lwbnp
Date: 03/Nov/2018:03:55:52 +0000
URI: /tea
Request ID: 32191f7ea07cb6bb44a1f43b8299415c

能够看到,Nginx Ingress Controller 为咱们创立的 Nginx 负载均衡器,曾经胜利地将申请转发给了对应的后端 Service。

以上,就是 Kubernetes 里 Ingress 的设计思维和应用办法了。

不过,你可能会有一个疑难,如果我的申请没有匹配到任何一条 IngressRule,那么会产生什么呢?

首先,既然 Nginx Ingress Controller 是用 Nginx 实现的,那么它当然会为你返回一个 Nginx 的 404 页面。

不过,Ingress Controller 也容许你通过 Pod 启动命令里的 –default-backend-service 参数,设置一条默认规定,比方:–default-backend-service=nginx-default-backend。

这样,任何匹配失败的申请,就都会被转发到这个名叫 nginx-default-backend 的 Service。所以,你就能够通过部署一个专门的 Pod,来为用户返回自定义的 404 页面了。

总结

在这篇文章里,我为你具体解说了 Ingress 这个概念在 Kubernetes 里到底是怎么一回事儿。正如我在文章里所形容的,Ingress 实际上就是 Kubernetes 对“反向代理”的形象。

目前,Ingress 只能工作在七层,而 Service 只能工作在四层。所以当你想要在 Kubernetes 里为利用进行 TLS 配置等 HTTP 相干的操作时,都必须通过 Ingress 来进行。

当然,正如同很多负载平衡我的项目能够同时提供七层和四层代理一样,未来 Ingress 的进化中,也会退出四层代理的能力。这样,一个比较完善的“反向代理”机制就比拟成熟了。

而 Kubernetes 提出 Ingress 概念的起因其实也非常容易了解,有了 Ingress 这个形象,用户就能够依据本人的需要来自由选择 Ingress Controller。比方,如果你的利用对代理服务的中断十分敏感,那么你就应该思考抉择相似于 Traefik 这样反对“热加载”的 Ingress Controller 实现。

更重要的是,一旦你对社区里现有的 Ingress 计划感到不称心,或者你曾经有了本人的负载平衡计划时,你只须要做很少的编程工作,就能够实现一个本人的 Ingress Controller。

在理论的生产环境中,Ingress 带来的灵便度和自由度,对于应用容器的用户来说,其实是十分有意义的。要晓得,当年在 Cloud Foundry 我的项目里,不晓得有多少人为了给 Gorouter 组件配置一个 TLS 而伤透了脑筋。

思考题

如果我的需要是,当拜访 www.mysite.com forums.mysite.com时,别离拜访到不同的 Service(比方:site-svc 和 forums-svc)。那么,这个 Ingress 该如何定义呢?请你形容出 YAML 文件中的 rules 字段。

正文完
 0