概念

首先介绍几个概念,Ingress 指的是进入到 k8s 集群中的 traffic,比如一个 client 发起的 HTTP 请求,经过层层网络最终到达了 k8s cluster 的外部,那么让不让它进入到 cluster 内部就是 ingress controller 做的事。

Kubernetes 原生提供了自己的 Ingress Controller,此外还有很多第三方的 Ingress Controller,istio 就是其中之一。需要注意的是,本文所有部署都是基于 GKE,其他的云平台可能略有不同。

在 k8s 中安装了 istio 之后,就可以用 istio 来控制所有进入 cluster 的流量。如何安装 istio 不在本文的范围,读者可以参考 istio 官方文档

安装 istio 完成之后,kubectl get namespace 命令可以看到有个名叫 istio-system 的空间,所有 istio 组件的 pod 都在这个空间中。

然后用如下命令查看 istio ingressgateway

$ kubectl -n istio-system get svc
NAME                   TYPE           CLUSTER-IP    EXTERNAL-IP      PORT   
istio-ingressgateway   LoadBalancer   10.0.23.180   35.222.xxx.xxx   15020:32011/TCP,80:30444/TCP

我们会看到这个名叫 istio-ingressgateway 的 LoadBalancer 它有公网 IP 地址 35.222.xxx.xxx,也就是说任何人都可以 ping 这个地址。

至此,所有想访问 k8s cluster 内部服务的 traffic 都只能先到达 istio-ingressgateway,由 istio 决定该把请求发给后端哪个服务。

与控制、转发流量相关的是 istio 的 Gateway 和 VirtualService :

  • Gateway,负责控制 istio 开放哪些端口、域名。比如 gateway.yaml 文件中指明 domain.com port 80, 那么只有到 domain.com 的HTTP 请求才允许进入cluster,其他的请求全部被拒绝。
  • VirtualService,负责按照 url 把流量转发到后端服务。

接下来就用 istio 官方的一个例子来部署一个 helloworld,一共有两个后端 POD,每次由其中一个 pod 返回结果,效果如下:

$ curl  http://test2.xxx.info/hello
Hello version: v1, instance: helloworld-6f64f86f66-jcnh8

$ curl  http://test2.xxx.info/hello
Hello version: v1, instance: helloworld-6f64f86f66-gn9nv

部署

首先是一个非常简单的 Deployment 和 Service 部署,这是最基本的例子,不用太多介绍。

apiVersion: v1
kind: Service
metadata:
  name: helloworld
  labels:
    app: helloworld
spec:
  ports:
  - port: 5000
    name: http
  selector:
    app: helloworld
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: helloworld
spec:
  replicas: 2
  selector:
    matchLabels:
      app: helloworld
  template:
    metadata:
      labels:
        app: helloworld
    spec:
      containers:
      - name: helloworld
        image: istio/examples-helloworld-v1
        resources:
          requests:
            cpu: "100m"
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 5000

接下来是部署 istio 的 Gateway。如果之前在安装 istio 时已经有一个自动生成的gateway,我们可以选择复用它,或者也可以新建一个。

如果要查看已有的 gateway,可以使用这条命令

$ kubectl -n istio-system get gateway
NAME                              AGE
istio-autogenerated-k8s-ingress   6d15h

如果要将已有的 gateway 导出到 yaml 格式的文件,以便之后修改,可以用

kubectl -n istio-system get gateway -o yaml

这里贴一个 Gateway 例子, 它的作用就是告诉 istio:host 为 test2.xxx.info,端口为 80 的请求允许进入 cluster。

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  annotations:
  labels:
    app: istio-ingressgateway
    istio: ingressgateway
    operator.istio.io/component: IngressGateways
    operator.istio.io/managed: Reconcile
    operator.istio.io/version: 1.5.0
    release: istio
  name: istio-autogenerated-k8s-ingress
  namespace: istio-system
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - 'test2.xxx.info'
    port:
      name: http
      number: 80
      protocol: HTTP

还剩最后一个问题:当请求进入到 cluster 之后,该把它转发给集群中的那个服务(Service)呢? 这就需要定义 istio 的 VirtualService

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: istio-vs-for-helloworld
  #namespace: istio-system
spec:
  hosts:
    - "test2.xxx.info"
  gateways:
    - istio-system/istio-autogenerated-k8s-ingress
  http:
    - match:
      - uri:
          exact: '/hello'
      route:
      - destination:
          host: helloworld

这个 VirtualService 的意思就是,把来自于 istio-system 空间的 gateway istio-autogenerated-k8s-ingress,并且host 为 test2.xxx.info的 traffic 做转发,如果它是访问 /hello,那么转发给后端的 helloworld service。

helloworld service 是从哪来的? 是之前部署 Deployment 和 Service 时候定义的,可以通过 kubectl get svc -n default 查看。

注意事项

在部署上面的服务时,踩了一些坑,导致 curl http://test2.xxx.info/hello 总是返回 404,现在把这些坑记录下来。

Namespace 的问题

istio 是部署在 istio-sysmtem空间的,自动生成的 Gateway istio-autogenerated-k8s-ingress 也在 istio-system 空间,而 helloworld 是部署在 default 空间的。

所以 VirtualService 中需要明确指明从 哪个gateway来,转发到哪个 svc 去

其中,我的 VirtualService 部署在了 default 空间,所以 istio-system/istio-autogenerated-k8s-ingress 开头就是指明了 gateway 的名空间。

另外,host: helloworld 也可以写的更加具体:helloworld.default.svc.cluster.local

选择哪个 Ingress

istio 官网的例子 中,helloworld 还部署了 Ingress。

我认为这是例子没有及时更新,既然用了 istio,那么转发服务应该交给 VirtualService,不应该使用 k8s 自带的 Ingress。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: istio
  name: helloworld-ingress
spec:
  rules:
    - host: "$INGRESS_DOMAIN"
      http:
        paths:
          - path: /hello
            backend:
              serviceName: helloworld
              servicePort: 5000

当部署 Ingress 之后,不需要部署 VirtualService,client 也可以获得返回结果。

参考资料