Service Mesh Istio 初探

569 阅读4分钟

早在去年,Service Mesh 这个概念就开始火起来了,今年的时候 Service Mesh 更是爆发式地发展,Service Mesh 中的明星项目 Istio 更是只用了几个月的时间就已经从 0.1 到了 0.8 LTS 了。由于工作和毕业的压力,之前一直没有时间深入研究 Service Mesh。现在稍微有些时间了,所以打算写点什么关于 Service Mesh 的。

介绍

首先,我们需要了解一下什么是 Service Mesh。今天我们的主角是 Istio,Istio 的背景我不过多介绍,G 家等大厂搞出来并且在后面推动支持的肯定不会弱。

根据 Istio 的官方文档,是这么定义自己的:一个用来连接、管理和加密微服务(流量)的开放平台。

an open platform to connect, manage, and secure microservices

Istio 可以让你在不修改微服务源代码的情况之下,很轻松地给微服务加上诸如负载均衡、身份验证、监控等等的功能。Istio 通过在你的微服务中部署一个 sidecar 作为所有流量的代理来达成这个目标。

总结下来,Istio 提供了以下功能:

  • 流量管理(Traffic Management)
  • 服务的身份认证和安全(Service Identity and Security)
  • 策略配置(Policy Enforcement)
  • 遥感(Telemetry)

除了这些之外,Istio 还支持很多不同的平台(尤其是 Kubernetes),并且支持自定义的组件和集成。

通过这些功能,微服务的开发和迁移会变得非常容易,而运维人员也可以更方便的更改部署的策略。

架构

Istio 是两层架构的,分别是数据层控制层

  • 数据层是由所有的部署为 sidecar 的 Envoy 所组成的。
  • 控制层有三个组件:Pilot、Mixer 和 Citadel,顾名思义是用来控制 Service Mesh 的行为的。

Istio Architecture

Envoy

Istio 用了一个扩展版本的 Envoy 作为底层的代理。Envoy 是一个用 C++ 开发的高性能的代理,具有非常多功能,具体的可以参考官方文档,在此不做赘述。

Envoy 在 Istio 中是以 sidecar 模式部署在 pod 里面的,Istio 通过控制 Envoy 来控制所有的流量,获取监控数据等。

Mixer

Mixer 是一个平台无关的组件,用来控制访问策略和使用策略,同时会收集监控信息,将收集到的信息传给用户可以自定义的后端进行处理。

Pilot

Pilot 为 Envoy 提供服务发现、智能路由(如 AB 测试、金丝雀部署)和弹性流量管理功能(如超时、重试、熔断)。它负责将高层的抽象的路由规则转化成低级的 envoy 的配置。

Citadel

Citadel 提供了服务间和服务到终端用户的认证,同时可以直接将 http 流量升级成 https 流量。具体的可以查看官方文档。

安装

在这里我打算使用 helm 进行安装。

Prerequisite

首先,你得有一个可运行的 k8s 集群,我是在 gke 上开了一个三节点的集群作为测试使用。

其次,你得需要有 helm 的客户端。mac 用户可以通过 brew 来安装。

下载 release

Istio 提供了一个很方便的脚本来下载并解压最新版的 Istio,如下:

$ curl -L https://git.io/getLatestIstio | sh -

等下载完之后,我们可以进入文件夹,并把 bin 目录加到 path 里面:

$ cd istio-0.8.0
$ export PATH=$PWD/bin:$PATH

使用 helm 进行安装

要使用 helm 来安装 istio,首先需要在集群里面配置好 helm 和 tiller,如下:

$ kubectl create -f install/kubernetes/helm/helm-service-account.yaml
$ helm init --service-account tiller

等 helm 和 tiller 配置完之后,就可以使用 helm 来一键安装 Istio 了:

$ helm install install/kubernetes/helm/istio --name istio --namespace istio-system

这样,Istio 就安装好了。

为了验证安装是否成功,我们可以看一下是否部署了以下的 service:

$ kubectl get svc -n istio-system
NAME                       TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)                                                               AGE
istio-citadel              ClusterIP      10.19.247.33    <none>            8060/TCP,9093/TCP                                                     2m
istio-egressgateway        ClusterIP      10.19.244.143   <none>            80/TCP,443/TCP                                                        2m
istio-ingress              LoadBalancer   10.19.248.42    104.199.155.220   80:32000/TCP,443:30434/TCP                                            2m
istio-ingressgateway       LoadBalancer   10.19.254.155   35.229.183.83     80:31380/TCP,443:31390/TCP,31400:31400/TCP                            2m
istio-pilot                ClusterIP      10.19.252.30    <none>            15003/TCP,15005/TCP,15007/TCP,15010/TCP,15011/TCP,8080/TCP,9093/TCP   2m
istio-policy               ClusterIP      10.19.242.187   <none>            9091/TCP,15004/TCP,9093/TCP                                           2m
istio-sidecar-injector     ClusterIP      10.19.252.155   <none>            443/TCP                                                               2m
istio-statsd-prom-bridge   ClusterIP      10.19.246.99    <none>            9102/TCP,9125/UDP                                                     2m
istio-telemetry            ClusterIP      10.19.240.18    <none>            9091/TCP,15004/TCP,9093/TCP,42422/TCP                                 2m
prometheus                 ClusterIP      10.19.255.53    <none>            9090/TCP                                                              2m

并且确认以下的 Pod 是否在 running 状态:

$ kubectl get pods -n istio-system
NAME                                       READY     STATUS      RESTARTS   AGE
istio-citadel-7bdc7775c7-ntfkf             1/1       Running     0          3m
istio-egressgateway-795fc9b47-2hw69        1/1       Running     0          3m
istio-ingress-84659cf44c-dkgf4             1/1       Running     0          3m
istio-ingressgateway-7d89dbf85f-9kgth      1/1       Running     0          3m
istio-mixer-post-install-vg5gh             0/1       Completed   0          3m
istio-pilot-66f4dd866c-nwr2j               2/2       Running     0          3m
istio-policy-76c8896799-7l9nz              2/2       Running     0          3m
istio-sidecar-injector-645c89bc64-6rs5k    1/1       Running     0          3m
istio-statsd-prom-bridge-949999c4c-mpk6d   1/1       Running     0          3m
istio-telemetry-6554768879-vqmjd           2/2       Running     0          3m
prometheus-86cb6dd77c-vhf9s                1/1       Running     0          3m

当然,我们也可以自定义一些参数,具体的请看 [官方文档]($ helm install install/kubernetes/helm/istio –name istio –namespace istio-system)。

样例应用

让我们部署我们的一个样例应用来看看 Istio 到底干了啥。

我们的样例应用叫做 BookInfo,这个应用由四个微服务所组成

Bookinfo Application without Istio

这个应用是用不同的语言所写的,让我们来见识一下 Istio 的魔力吧。

安装这个应用非常简单,我们只要执行以下命令即可:

$ kubectl apply -f samples/bookinfo/kube/bookinfo.yaml
$ istioctl create -f samples/bookinfo/routing/bookinfo-gateway.yaml

我们可以注意一下,在 bookinfo.yaml 中的 manifest 如下:

# Copyright 2017 Istio Authors
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.

##################################################################################################
# Details service
##################################################################################################
apiVersion: v1
kind: Service
metadata:
  name: details
  labels:
    app: details
spec:
  ports:
  - port: 9080
    name: http
  selector:
    app: details
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: details-v1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: details
        version: v1
    spec:
      containers:
      - name: details
        image: istio/examples-bookinfo-details-v1:1.5.0
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 9080
---
...

但是我们真正部署出来后,变成了这样:

apiVersion: v1
kind: Pod
metadata:
  annotations:
    sidecar.istio.io/status: '{"version":"55c9e544b52e1d4e45d18a58d0b34ba4b72531e45fb6d1572c77191422556ffc","initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["istio-envoy","istio-certs"],"imagePullSecrets":null}'
  creationTimestamp: 2018-07-05T09:10:55Z
  generateName: details-v1-5f94c6d66b-
  labels:
    app: details
    pod-template-hash: "1950728226"
    version: v1
  name: details-v1-5f94c6d66b-jj6lz
  namespace: default
  ownerReferences:
  - apiVersion: apps/v1
    blockOwnerDeletion: true
    controller: true
    kind: ReplicaSet
    name: details-v1-5f94c6d66b
    uid: 528aa360-8033-11e8-8cec-0e04fb7e7092
  resourceVersion: "15620"
  selfLink: /api/v1/namespaces/default/pods/details-v1-5f94c6d66b-jj6lz
  uid: 528d5618-8033-11e8-8cec-0e04fb7e7092
spec:
  containers:
  - image: istio/examples-bookinfo-details-v1:1.5.0
    imagePullPolicy: IfNotPresent
    name: details
    ports:
    - containerPort: 9080
      protocol: TCP
    resources: {}
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-f9mls
      readOnly: true
  - args:
    - proxy
    - sidecar
    - --configPath
    - /etc/istio/proxy
    - --binaryPath
    - /usr/local/bin/envoy
    - --serviceCluster
    - details
    - --drainDuration
    - 45s
    - --parentShutdownDuration
    - 1m0s
    - --discoveryAddress
    - istio-pilot.istio-system:15007
    - --discoveryRefreshDelay
    - 10s
    - --zipkinAddress
    - zipkin.istio-system:9411
    - --connectTimeout
    - 10s
    - --statsdUdpAddress
    - istio-statsd-prom-bridge.istio-system:9125
    - --proxyAdminPort
    - "15000"
    - --controlPlaneAuthPolicy
    - NONE
    env:
    - name: POD_NAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: metadata.name
    - name: POD_NAMESPACE
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: metadata.namespace
    - name: INSTANCE_IP
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: status.podIP
    - name: ISTIO_META_POD_NAME
      valueFrom:
        fieldRef:
          apiVersion: v1
          fieldPath: metadata.name
    - name: ISTIO_META_INTERCEPTION_MODE
      value: REDIRECT
    image: docker.io/istio/proxyv2:0.8.0
    imagePullPolicy: IfNotPresent
    name: istio-proxy
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
    securityContext:
      privileged: false
      readOnlyRootFilesystem: true
      runAsUser: 1337
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /etc/istio/proxy
      name: istio-envoy
    - mountPath: /etc/certs/
      name: istio-certs
      readOnly: true
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-f9mls
      readOnly: true
  dnsPolicy: ClusterFirst
  initContainers:
  - args:
    - -p
    - "15001"
    - -u
    - "1337"
    - -m
    - REDIRECT
    - -i
    - '*'
    - -x
    - ""
    - -b
    - 9080,
    - -d
    - ""
    image: docker.io/istio/proxy_init:0.8.0
    imagePullPolicy: IfNotPresent
    name: istio-init
    resources: {}
    securityContext:
      capabilities:
        add:
        - NET_ADMIN
      privileged: true
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
      name: default-token-f9mls
      readOnly: true
  nodeName: ip-172-31-39-23
  restartPolicy: Always
  schedulerName: default-scheduler
  securityContext: {}
  serviceAccount: default
  serviceAccountName: default
  terminationGracePeriodSeconds: 30
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
  volumes:
  - emptyDir:
      medium: Memory
    name: istio-envoy
  - name: istio-certs
    secret:
      defaultMode: 420
      optional: true
      secretName: istio.default
  - name: default-token-f9mls
    secret:
      defaultMode: 420
      secretName: default-token-f9mls

可以看到,本来只有一个 container 的,现在里面多了一个 container 和 initContainer。这个就是 Istio 的 Auto Injection,可以自动把 sidecar 注入到 Pod 里面,让我们不需要手动一个一个修改 yaml 文件,也防止手动修改过程中出错的可能。

使用实例

这里我们以路由设置为例子。

首先我们打开刚才部署好的这个应用的网页,可以看到页面右方的 Book Reviews 部分里面每次刷新都会随机性地出现黑星星、红星星和没有星星三种情况,这是因为我们有三个不同的 backend,路由在默认情况下会随机路由到任意一个 backend 上。

我们先尝试把所有的路由都路由到 v1 版本上(就是没有星星的版本),路由规则如下:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: details
  ...
spec:
  hosts:
  - details
  http:
  - route:
    - destination:
        host: details
        subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: productpage
  ...

命令如下:

$ istioctl create -f samples/bookinfo/routing/route-rule-all-v1.yaml

然后我们再去刷新,就会发现不管怎么刷新星星都不见了。

接着,假如我们有一个用户是 jason,我们希望他能测试 v2 的 backend,就可以用下面的路由规则:

kind: VirtualService
metadata:
  name: reviews
  ...
spec:
  hosts:
  - reviews
  http:
  - match:
    - headers:
        cookie:
          regex: ^(.*?;)?(user=jason)(;.*)?$
    route:
    - destination:
        host: reviews
        subset: v2
  - route:
    - destination:
        host: reviews
        subset: v1

命令如下:

$ istioctl replace -f samples/bookinfo/routing/route-rule-reviews-test-v2.yaml

这时候,我们打开网页,以 jason 这个用户登录(密码随便填),就会发现每一次访问到的都是带有黑星星的版本。

这就是 Istio 提供的路由功能。

总结

本文中我们简单讲了 Service Mesh 的概念,如何创建 Istio 以及简单的使用过程,如果大家有兴趣探索 Istio 更多的功能,可以直接访问 Istio 的官网