早在去年,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的行为的。 总体的架构如下图:
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,如下:
1 $ curl -L https://git.io/getLatestIstio | sh -
等下载完之后,我们可以进入文件夹,并把bin目录加到path里面:
1 2 $ cd istio-0.8.0$ export PATH=$PWD /bin:$PATH
使用helm进行安装 要使用helm来安装istio,首先需要在集群里面配置好helm和tiller,如下:
1 2 $ kubectl create -f install/kubernetes/helm/helm-service-account.yaml $ helm init --service-account tiller
等helm和tiller配置完之后,就可以使用helm来一键安装Istio了:
1 $ helm install install/kubernetes/helm/istio --name istio --namespace istio-system
这样,Istio就安装好了。
为了验证安装是否成功,我们可以看一下是否部署了以下的service:
1 2 3 4 5 6 7 8 9 10 11 12 $ 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状态:
1 2 3 4 5 6 7 8 9 10 11 12 13 $ 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,这个应用由四个微服务所组成,具体架构图如下:
这个应用是用不同的语言所写的,让我们来见识一下Istio的魔力吧。
安装这个应用非常简单,我们只要执行以下命令即可:
1 2 $ kubectl apply -f samples/bookinfo/kube/bookinfo.yaml $ istioctl create -f samples/bookinfo/routing/bookinfo-gateway.yaml
我们可以注意一下,在bookinfo.yaml
中的manifest如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 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 --- ...
但是我们真正部署出来后,变成了这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 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版本上(就是没有星星的版本),路由规则如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 ...
命令如下:
1 $ istioctl create -f samples/bookinfo/routing/route-rule-all-v1.yaml
然后我们再去刷新,就会发现不管怎么刷新星星都不见了。
接着,假如我们有一个用户是jason,我们希望他能测试v2的backend,就可以用下面的路由规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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
命令如下:
1 $ istioctl replace -f samples/bookinfo/routing/route-rule-reviews-test-v2.yaml
这时候,我们打开网页,以jason这个用户登录(密码随便填),就会发现每一次访问到的都是带有黑星星的版本。
这就是Istio提供的路由功能。
总结 本文中我们简单讲了Service Mesh的概念,如何创建Istio以及简单的使用过程,如果大家有兴趣探索Istio更多的功能,可以直接访问Istio的官网 。