早在去年,Service Mesh 这个概念就开始火起来了,今年的时候 Service Mesh 更是爆发式地发展,Service Mesh 中的明星项目 Istio 更是只用了几个月的时间就已经从 0.1 到了 1.0 了。

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的架构。Istio 是两层架构的,分别是数据层和控制层:

 数据层是由所有的部署为 sidecar 的 Envoy 所组成的。

控制层有三个组件:Pilot、Mixer 和 Citadel,顾名思义是用来控制 Service Mesh 的行为的。

 总体的架构如下图:

 

meshlab打开las格式_微服务

 

 

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

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

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

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

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

 下面简要介绍一下安装。在这里我打算使用 helm 进行安装。

 Prerequisite

 首先,你得有一个可运行的 Kubernetes 集群,我是在 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

 

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

 接下来我们部署一个应用,看看Istio是如何运作的。我们的样例应用叫做 BookInfo,这个应用由四个微服务所组成,具体架构图如下:

 

meshlab打开las格式_微服务_02

 

这个应用是用不同的语言所写的,让我们来见识一下 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 提供的路由功能。