“AI 技术+人才”如何成为企业增长新引擎?戳此了解>>> 了解详情
写点什么

Service Mesh · Istio · 以实践入门

  • 2020-02-24
  • 本文字数:15879 字

    阅读完需:约 52 分钟

Service Mesh · Istio · 以实践入门

前言

本文是笔者在学习官方文档、相关博客文章和实践过程中,整理了一些知识概念和自己的思考,主要在探索 lstio 的实际应用场景, Sidecar 原理, Service Mesh 为什么出现、要解决什么问题等,帮助我们思考微服务技术架构的升级和落地的可行性。


本文不是 Istio 的全部,但是希望入门仅此一篇就够。

概念

围绕云原生(CN)的概念,给人一种知识大爆炸的感觉,但假如你深入了解每一个概念的细节,你会发现它和你很近,甚至就是你手里每天做的事情。



图片来源:https://landscape.cncf.io/


关键词:Service Mesh、Istio、Sidecar、Envoy 等。


服务网格


服务网格( Service Mesh )是一个新瓶装旧酒的概念,它的发展随着微服务兴起,必然是早于 Kubernates 出现了。但 Kubernates 和 Istio 的出现,促使它成为了一种更火更标准化的概念。


Sidecar 是服务网格技术中常用的(其中)一种设计架构,在 Kubernates 中,不同的容器允许被运行在同一个 Pod 中(即多个进程运行在同一个 cgroup 下),这在很大程度上给 Sidecar 模式提供了良好的土壤。


首先看看 Sidecar 的设计:



图片来源于网络


为什么是新瓶旧酒?任何技术的发展都不是凭空地跳跃式发展的。


历史



原始的应用程序--图片来源于网络



独立的网络层--图片来源于网络



出现网络层(4 层协议)控制的需求--图片来源于网络



控制逻辑下移到网络层--图片来源于网络


早期,应用程序随着功能迭代发展,尤其是一个大型项目,程序堆积了越来越多的功能,功能之间紧密耦合在一起,变得越来越难以维护(因为模块耦合度较高,没有人敢动古老的模块代码),迭代周期变长(工程复杂度成几何增长)。


于是,人们提出,将不同的功能分离到不同的程序(进程)中,减低模块的耦合度,敏捷开发迭代,这就是微服务概念的兴起。



出现新的应用层(7 层协议)需求(服务发现、熔断、超时重试等)--图片来源于网络



封装成三方库(服务发现:Dubbo/HSF)--图片来源于网络


困难


服务被拆分成众多的微服务,最困难的问题就是——调用它自己:


1、原本在进程中互相调用那么简单的事情,都要变成一次在 7 层网络上的远程调用。


2、原本公共工具类做的事情,现在需要写成二方库 SDK 等,在每一个进程中使用,版本迭代成为了灾难。


3、原本是内部透明调用的不需要做任何防护,分离后却要额外增加安全防护和隔离的工作。


4、不再是代码即文档,需要维护大量的 API 定义和版本管理。



封装到隔离的进程中代理--图片来源于网络


到这里,独立进程的方式基本成型,即为 Sidecar 模式。


Sidecar 解决什么问题?


这里有个服务网格里的概念:微服务之间的调用,一般在架构图中是横向的,被称为东西流量。服务暴露到外部被公网可见的外部调用,被称为南北流量。


Sidecar 的设计就是为了解决微服务互相调用(东西流量)的问题。


先来一张我们比较熟悉的图:



图片来源于网络


Consumer 与 Provider 就是微服务互相调用的一种解决方案。


毫无疑问,我们熟知的一整套中间件解决方案,解决的正是东西流量的问题,图为 Dubbo 架构。


只不过, Dubbo 中间件一整套组件是基于 SPI 机制以一种较为隔离的方式侵入到运行时的代码中。并且,这只能限定 Java 这样被官方支持的语言来开发服务应用。


小结


归纳一下与东西流量有关的问题:


流量管理(服务发现、负载均衡、路由、限流、熔断、容错等)、可观测性(监控、日志聚合、计量、跟踪)、安全(认证、授权),再甚至更高级的动态配置、故障注入、镜像流量等


相比来说, Sidecar 的模式更为巧妙并更进一步。通过容器机制,在进程上是隔离的,基于 L7 代理进行通讯,允许微服务是由任何语言进行开发的。



图片来源于网络


以下是微服务集群基于 Sidecar 互相通讯的简化场景:



图片来源于网络


所以说,回到服务网格的概念上来,虽然概念是不同的,但是逻辑上我们可以理解成:所有使用中间件的服务组成了一个大的服务网格,这样可以帮助我们理解。服务网格基于 Kubernates 这样的容器技术,将东西流量的问题解决得更加透明无感。


一句话总结,服务网格( Service Mesh )是解决微服务之间的网络问题和可观测性问题的(事实)标准,并且正在走向标准化。


Service Mesh 是 Kubernetes 支撑微服务能力拼图的最后一块


Istio 和 Envoy


Istio,第一个字母是(ai)。


Istio 实现的服务网格分为数据平面和控制平面。核心能力包括 4 大块:


1、流量控制(Traffic Management)。


2、安全(Security)。


3、动态规则(Policy)。


4、可观测能力(Observability)。


Envoy 面向数据平面,也就是服务之间调用的代理。


Envoy 是 Istio Service Mesh 中默认的 Sidecar 方案。


Istio 在 Enovy 的基础上按照 Envoy 的 xDS 协议扩展了其控制平面。




Istio 基于 Envoy 实现 Service Mesh 数据平面--图片来源于网络



Envoy 角色--图片来源于网络


Envoy 是一个由 C++ 实现的高性能代理,与其等价的,还有 Nginx、Traefik ,这就不难理解了。


也就是下图中的 Proxy :



图片来源于 Istio 官网


Istio 在控制平面上主要解决流量管理、安全、可观测性三个方面的问题,也就是前面提到的东西流量相关的问题。类似一个有配置中心的微服务集群架构。具体细节不在这里赘述。


Sidecar 注入


前面在介绍服务网格时,只是简单地提到 Sidecar 设计在其中的作用和特性,这里详细展开介绍其中的原理。


首先是一些预备概念:


1、Sidecar 模式:容器应用模式之一,Service Mesh 架构的一种实现方式


2、Init 容器:Pod 中的一种专用的容器,在应用程序容器启动之前运行,用来包含一些应用镜像中不存在的实用工具或安装脚本。


3、iptables:流量劫持是通过 iptables 转发实现的。


Sidecar 模式解决微服务之间的网络通讯(远程调用),通常通讯层的实现方式,有以下选择:


1、在微服务应用程序中导入 SDK 类库。


2、节点代理(使用纵向的 API 网关或者是本地 Agent ),代理接口的调用路由规则,转发到特定的机器。


3、用 Sidecar 容器的形式运行,和应用容器一同运行,透明地劫持所有应用容器的出入流量。


SDK 库的方式是很自然的,并且调用方式是进程内的,没有安全隔离的包袱。但是随着编程语言的发展,很多新的语言为特定的场景而生,而 SDK 库的方式限制了使用方必须用支持列表中的语言。


节点代理的方式,是使用一个特定的服务专门代理微服务中的请求,是一个中间人的角色。但这个代理人的安全性要求非常高,因为它需要处理来自不同微服务的请求,并鉴别它们各自的身份。


Sidecar 模型是介于 SDK 库和节点代理中间的一种形式,相当于是给每个微服务都配上一个自己独有的代理。这样,每个微服务自己的 Sidecar 就代表了自己特定的身份,有利于调用的安全审计。因此,从外部看, Sidecar 与其附属的应用程序具有相同的权限。



图片来源:


以 Istio 为例:在 Istio 中, Sidecar 模式启动时会首先执行一个 init 容器 istio-init ,容器只做一件事情,通过 iptables 命令配置 Pod 的网络路由规则,让 Envoy 代理可以拦截所有的进出 Pod 的流量。


之后,微服务应用通过 Pod 中共享的网络命名空间内的 loopback ( localhost )与 Sidecar 通讯。而外部流量也会通过 Sidecar 处理后,传入到微服务。


因为它们共享一个 Pod ,对其他 Pod 和节点代理都是不可见的,可以理解为两个容器共享存储、网络等资源,可以广义的将这个注入了 Sidecar 容器的 Pod 理解为一台主机,两个容器共享主机资源。


下图是具体 iptables 与 Sidecar 之间互作用原理,来源:https://jimmysong.io/posts/envoy-sidecar-injection-in-istio-service-mesh-deep-dive/



具体原理上的细节,我们可以通过实践,慢慢挖掘。


小结


最后给概念章节有个阶段性的总结:



图片来源于网络


所以我们打算卖什么?

实践

铺垫这么多概念,我们可以实操起来了。具体怎么做?从安装 Istio 开始。


准备工作


首先,预备一个 Kubernates 集群,这里不赘述。


如果是本地测试,Docker-Desktop 也可以启动一个单机的 k8s 集群


装 Istio 的命令行工具 istioctl :下载 istio-release(包括 istioctl 、示例文件和安装配置)。


curl -sL "https://github.com/istio/istio/releases/download/1.4.2/istio-1.4.2-osx.tar.gz" | tar xz
复制代码


安装 helm (可选):


从 1.4.0 版本开始,不再使用 helm 来安装 Istio



# helm工具$ brew install kubernetes-helm
复制代码


安装 Istio


进入到安装文件的目录,开始将 Istio 安装到 k8s 上。


首先确认 kubectl 连接的正确的 k8s 集群。


选择以下其中一种方式:


方式 1、使用 istioctl 安装


cd istio-1.4.2# 安装istioctlcp bin/istioctl /usr/local/bin/ # 也可以加一下PATH# (可选)先查看配置文件istioctl manifest generate --set profile=demo > istio.demo.yaml# 安装istioistioctl manifest apply --set profile=demo## 以下是旧版本istio的helm安装方式 ### 创建istio专属的namespacekubectl create namespace istio-system# 通过helm初始化istiohelm template install/kubernetes/helm/istio-init --name istio-init --namespace istio-system | kubectl apply -f -# 通过helm安装istio的所有组件helm template install/kubernetes/helm/istio --name istio --namespace istio-system | kubectl apply -f -
复制代码


方式 2、使用 helm 安装


## 以下是旧版本istio的helm安装方式 ### 创建istio专属的namespacekubectl create namespace istio-system# 通过helm初始化istiohelm template install/kubernetes/helm/istio-init --name istio-init --namespace istio-system | kubectl apply -f -# 通过helm安装istio的所有组件helm template install/kubernetes/helm/istio --name istio --namespace istio-system | kubectl apply -f -
复制代码


等待所有的 Istio 组件的容器启动,直到:


$ kubectl get crds | grep 'istio.io' | wc -l23
复制代码


如果是阿里云 ACS 集群,安装完 Istio 后,会有对应的一个 SLB 被创建出来,转发到 Istio 提供的虚拟服务器组


示例:Hello World


示例代码在源码的 samples 目录中


cd samples/hello-world
复制代码


注入


Istio Sidecar 的注入有两种方式:自动、手动。


这里先通过 istioctl 命令直接手工 inject:


istioctl kube-inject -f helloworld.yaml -o helloworld-istio.yaml
复制代码


实际上就是通过脚本修改了原文件,增加了:


1、sidecar init 容器。


2、istio proxy sidecar 容器。


分析


我们可以简单对比一下注入的配置,原文件:


apiVersion: v1kind: Servicemetadata:  name: helloworld  labels:    app: helloworldspec:  ports:  - port: 5000    name: http  selector:    app: helloworld---apiVersion: apps/v1kind: Deploymentmetadata:  creationTimestamp: null  labels:    version: v1  name: helloworld-v1spec:  replicas: 1  selector:    matchLabels:      app: helloworld      version: v1  strategy: {}  template:    metadata:      labels:        app: helloworld        version: v1    spec:      containers:      - image: docker.io/istio/examples-helloworld-v1        imagePullPolicy: IfNotPresent        name: helloworld        ports:        - containerPort: 5000        resources:          requests:            cpu: 100m---apiVersion: apps/v1kind: Deploymentmetadata:  creationTimestamp: null  labels:    version: v2  name: helloworld-v2spec:  replicas: 1  selector:    matchLabels:      app: helloworld      version: v2  strategy: {}  template:    metadata:      labels:        app: helloworld        version: v2    spec:      containers:      - image: docker.io/istio/examples-helloworld-v2        imagePullPolicy: IfNotPresent        name: helloworld        ports:        - containerPort: 5000        resources:          requests:            cpu: 100m
复制代码


可以看到,需要部署两个版本 helloworld-v1/v2 的容器,挂载在同一个服务下。


这是一个典型的蓝绿部署方式,后续我们可以通过配置 Istio ,来调整它们的流量权重,这是真实生产环境版本升级的场景。


再来看增加的部分:



这里增加了一部分 Istio 的配置,是 K8s 中的标准做法 annotations 。



这部分可以看到,原有的服务容器没有任何改动,只是增加了一个 sidecar 容器,包括启动参数和环境变量(因为配置排序的问题, args 排在了最前面,整体的定义:


        - proxy        - sidecar        - ...        env:        - name: POD_NAME          valueFrom:            fieldRef:              fieldPath: metadata.name        - ...        image: docker.io/istio/proxyv2:1.3.2        imagePullPolicy: IfNotPresent        name: istio-proxy        ports:        - containerPort: 15090          name: http-envoy-prom          protocol: TCP        readinessProbe:          failureThreshold: 30          httpGet:            path: /healthz/ready            port: 15020          initialDelaySeconds: 1          periodSeconds: 2        resources:          limits:            cpu: "2"            memory: 1Gi          requests:            cpu: 100m            memory: 128Mi        securityContext:          readOnlyRootFilesystem: true          runAsUser: 1337        volumeMounts:        - mountPath: /etc/istio/proxy          name: istio-envoy        - mountPath: /etc/certs/          name: istio-certs          readOnly: true
复制代码


镜像名 docker.io/istio/proxyv2:1.3.2 。


另外一部分,就是 initContainer :


 initContainers:      - args:        - -p        - "15001"        - -z        - "15006"        - -u        - "1337"        - -m        - REDIRECT        - -i        - '*'        - -x        - ""        - -b        - '*'        - -d        - "15020"        image: docker.io/istio/proxy_init:1.3.2        imagePullPolicy: IfNotPresent        name: istio-init        resources:          limits:            cpu: 100m            memory: 50Mi          requests:            cpu: 10m            memory: 10Mi        securityContext:          capabilities:            add:            - NET_ADMIN          runAsNonRoot: false          runAsUser: 0      volumes:      - emptyDir:          medium: Memory        name: istio-envoy      - name: istio-certs        secret:          optional: true          secretName: istio.default
复制代码


部署


$ kubectl apply -f helloworld-istio.yamlservice/helloworld createddeployment.apps/helloworld-v1 createddeployment.apps/helloworld-v2 created$ kubectl get deployments.apps -o wideNAME            READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS               IMAGES                                                                 SELECTORhelloworld-v1   1/1     1            1           20m   helloworld,istio-proxy   docker.io/istio/examples-helloworld-v1,docker.io/istio/proxyv2:1.3.2   app=helloworld,version=v1helloworld-v2   1/1     1            1           20m   helloworld,istio-proxy   docker.io/istio/examples-helloworld-v2,docker.io/istio/proxyv2:1.3.2   app=helloworld,version=v2

并启用一个简单的gateway来监听,便于我们访问测试页面$ kubectl apply -f helloworld-gateway.yamlgateway.networking.istio.io/helloworld-gateway createdvirtualservice.networking.istio.io/helloworld created

部署完成之后,我们就可以通过gateway访问hello服务了:$ curl "localhost/hello"Hello version: v2, instance: helloworld-v2-7768c66796-hlsl5$ curl "localhost/hello"Hello version: v2, instance: helloworld-v2-7768c66796-hlsl5$ curl "localhost/hello"Hello version: v1, instance: helloworld-v1-57bdc65497-js7cm
复制代码


两个版本的 Deployment 都可以随机被访问到

深入探索

接着刚才我们部署好的 hello-world ,我们随着 Istio 的 feature 进行探索。


流量控制 - 切流


首先,我们尝试控制一下流量,比如只走 v2。参考 Traffic Shifting:https://istio.io/docs/tasks/traffic-management/traffic-shifting/


我们可以通过 VirtualService 配置控制版本流量,详情参考:https://istio.io/docs/reference/config/networking/v1alpha3/virtual-service/


先查看一下当前 Gateway 和 VirtualService 的配置:


$ kubectl get gw helloworld-gateway -o yamlapiVersion: networking.istio.io/v1alpha3kind: Gatewaymetadata:  name: helloworld-gatewayspec:  selector:    istio: ingressgateway # use istio default controller  servers:  - port:      number: 80      name: http      protocol: HTTP    hosts:    - "*"$ kubectl get vs helloworld -o yamlapiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata:  name: helloworldspec:  hosts:  - "*"  gateways:  - helloworld-gateway  http:  - match:    - uri:        exact: /hello    route:    - destination:        host: helloworld  # short for helloworld.${namespace}.svc.cluster.local        port:          number: 5000
复制代码


可以看到,VS 转发 /hello 路径的请求到 helloworld:5000 ,不过,这种 short 写法不推荐。我们可以改成 helloworld.${namespace}.svc.cluster.local 。


我们将其中 VirtualService 的配置修改为:


apiVersion: networking.istio.io/v1alpha3kind: VirtualServicemetadata:  name: helloworldspec:  hosts:  - "*"  gateways:  - helloworld-gateway  http:  - match:    - uri:        exact: /hello    route:    - destination:        host: helloworld.default.svc.cluster.local        subset: v1        weight: 0    - destination:        host: helloworld.default.svc.cluster.local        subset: v2        weight: 100
复制代码


在 http.route 里增加一个 destination ,并将 v2 的 weight 权重配置到 100 。


并增加一个 DestinationRule 对 subset 进行定义。


apiVersion: networking.istio.io/v1alpha3kind: DestinationRulemetadata:  name: helloworld-destinationspec:  host: helloworld.default.svc.cluster.local  subsets:  - name: v1    labels:      version: v1  - name: v2    labels:      version: v2
复制代码


然后应用更新:


$ kubectl apply -f helloworld-gateway.yamlgateway.networking.istio.io/helloworld-gateway unchangedvirtualservice.networking.istio.io/helloworld configureddestinationrule.networking.istio.io/helloworld-destination created
复制代码


测试一下效果:


$ while true;do sleep 0.05 ;curl localhost/hello;doneHello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
复制代码


流量完美切走。不过,到目前为止我们只接触了 Gateway 、VirtualService 和 DestinationRule。我们来回顾一下:


Gateway


Gateway 用于处理服务网格的边界,定义了出入负载的域名、端口、协议等规则。


VirtualService


VirtualService 可以控制路由(包括 subset/version 权重、匹配、重定向等)、故障注入、TLS 。


DestinationRule


DestinationRule 定义确定路由的细节规则,比如 subset 定义、负载均衡的策略,甚至可以针对特定的端口再重新定义规则。


示例:Bookinfo


前面的例子,通过控制流量权重达到版本切流的目的。


下面,我们再通过另外一个 Bookinfo 的例子继续探索其它 Istio 的 feature。



图片来源于 Istio 官网


本例是一个多实例微服务架构,并且由不同语言开发。


开始


$ cd samples/bookinfo
复制代码


注入


这次 Pod 定义比较多,我们打开 auto sidecar-injection


$ kubectl label namespace default istio-injection=enabled
复制代码


打开之后,每次创建的 Pod 都会自动注入上 istio-proxy 和相应的 initContainer


部署


$ kubectl apply -f platform/kube/bookinfo.yamlservice/details createdserviceaccount/bookinfo-details createddeployment.apps/details-v1 createdservice/ratings createdserviceaccount/bookinfo-ratings createddeployment.apps/ratings-v1 createdservice/reviews createdserviceaccount/bookinfo-reviews createddeployment.apps/reviews-v1 createddeployment.apps/reviews-v2 createddeployment.apps/reviews-v3 createdservice/productpage createdserviceaccount/bookinfo-productpage createddeployment.apps/productpage-v1 created
复制代码


创建一个 Gateway 用于查看页面:


$ kubectl apply -f networking/bookinfo-gateway.yamlgateway.networking.istio.io/bookinfo-gateway createdvirtualservice.networking.istio.io/bookinfo created
复制代码


访问 http://localhost/productpage 页面:



不断刷新可以看到右侧 Reviews 有三个版本:





流量控制 - 网络可见性


基于前面安装好的 Bookinfo 应用,起一个 Pod 探索一下网络可见性:


$ kubectl run --image centos:7 -it probe# 请求productpage服务上的接口[root@probe-5577ddd7b9-rbmh7 /]# curl -sL http://productpage:9080 | grep -o "<title>.*</title>"<title>Simple Bookstore App</title>$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl www.baidu.com | grep -o "<title>.*</title>"<title>百度一下,你就知道</title>
复制代码


我们可以看到,默认情况下,所有的微服务(容器)之间都是公开可访问的,并且微服务可以访问外部网络。


接下来,介绍 Sidecar 配置对可见性进行控制。


Sidecar


由于每个容器都自动注入了 Sidecar 容器,托管了所有的出入请求。所以基于这个 Sidecar 容器,我们可以很容易对它进行配置。


Sidecar 配置就是 Istio 中专门用于配置 sidecar 之间的网络可见性。


首先,修改全局配置,使用 blocked-by-default 的方式。


$ kubectl get configmap istio -n istio-system -o yaml | sed 's/mode: ALLOW_ANY/mode: REGISTRY_ONLY/g' | kubectl replace -n istio-system -f -configmap "istio" replaced$ kubectl get configmap istio -n istio-system -o yaml | grep -n1 -m1 "mode: REGISTRY_ONLY"67-    outboundTrafficPolicy:68:      mode: REGISTRY_ONLY
复制代码


outboundTrafficPolicy.mode=REGISTRY_ONLY 表示默认容器不允许访问外部网络,只允许访问已知的 ServiceEntry。


然后,我们设置一个全局性的 Sidecar 配置:


$ kubectl apply -f - <<EOFapiVersion: networking.istio.io/v1alpha3kind: Sidecarmetadata:  name: default  namespace: istio-systemspec:  egress:  - hosts:    - "./*"    - "istio-system/*"EOFsidecar.networking.istio.io/default configured
复制代码


  • 每个 namespace 只允许一个无 workloadSelector 的配置

  • rootNamespace 中无 workloadSelector 的配置是全局的,影响所有 namespace,默认的 rootNamespace=istio-system


这个配置的含义是:


所有 namespace 里的容器出流量(egress)只能访问自己的 namespace 或 namespace=istio-system 中提供的 services 。


egress


我们先测试一下外网连通性, Sidecar 的出流量被称作 egress 流量。


这里需要等待一会生效,或者直接销毁重新部署一个测试容器


$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -v www.baidu.com* About to connect() to www.baidu.com port 80 (#0)*   Trying 220.181.38.150...* Connected to www.baidu.com (220.181.38.150) port 80 (#0)> GET / HTTP/1.1> User-Agent: curl/7.29.0> Host: www.baidu.com> Accept: */*>* Recv failure: Connection reset by peer* Closing connection 0curl: (56) Recv failure: Connection reset by peercommand terminated with exit code 56
复制代码


效果是:外网已经访问不通。


恢复:这时,我们将需要访问的域名注册到 ServiceEntry 中,并且增加一个 Sidecar 的 egress 规则,例如:


apiVersion: networking.istio.io/v1alpha3kind: ServiceEntrymetadata:  name: baiduspec:  hosts:  - www.baidu.com  ports:  - number: 80    name: http    protocol: HTTP  resolution: DNS  location: MESH_EXTERNAL---apiVersion: networking.istio.io/v1alpha3kind: Sidecarmetadata:  name: defaultspec:  egress:  - hosts:    - "./www.baidu.com"    port:      number: 80      protocol: HTTP      name: http
复制代码


重新请求,确认恢复了。


$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -v www.baidu.com* About to connect() to www.baidu.com port 80 (#0)*   Trying 220.181.38.150...* Connected to www.baidu.com (220.181.38.150) port 80 (#0)> GET / HTTP/1.1> User-Agent: curl/7.29.0> Host: www.baidu.com> Accept: */*>< HTTP/1.1 200 OK< accept-ranges: bytes< cache-control: private, no-cache, no-store, proxy-revalidate, no-transform< content-length: 2381< content-type: text/html< date: Tue, 15 Oct 2019 07:45:33 GMT< etag: "588604c8-94d"< last-modified: Mon, 23 Jan 2017 13:27:36 GMT< pragma: no-cache< server: envoy< set-cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/< x-envoy-upstream-service-time: 21
复制代码


同样地,容器之间的流量同理:


$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl productpage:9080curl: (56) Recv failure: Connection reset by peercommand terminated with exit code 56配置上ServiceEntryapiVersion: networking.istio.io/v1alpha3kind: Sidecarmetadata:  name: defaultspec:  egress:  - hosts:    - "./www.baidu.com"    - "./productpage.default.svc.cluster.local"  # 这里必须用长名称---apiVersion: networking.istio.io/v1alpha3kind: ServiceEntrymetadata:  name: baiduspec:  hosts:  - www.baidu.com  resolution: DNS  location: MESH_EXTERNAL---apiVersion: networking.istio.io/v1alpha3kind: ServiceEntrymetadata:  name: productpagespec:  hosts:  - productpage  resolution: DNS  location: MESH_EXTERNAL
复制代码


$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl productpage:9080 | grep -o "<title>.*</title>"<title>Simple Bookstore App</title>
复制代码


需要留意的是,不带 workloadSelector 的(不指定特定容器的)Sidecar 配置只能有一个,所以规则都需要写在一起。


ingress


下面我们探究容器入流量的配置:


apiVersion: networking.istio.io/v1alpha3kind: Sidecarmetadata:  name: productpage-sidecarspec:  workloadSelector:    labels:      app: productpage  ingress:  - port:      number: 9080      protocol: HTTP    defaultEndpoint: 127.0.0.1:10080  egress:  - hosts:    - "*/*"
复制代码


这个配置的效果是让 productpage 应用的容器收到 9080 端口的 HTTP 请求时,转发到容器内的 10080 端口。


由于容器内没有监听 10080 ,所以会访问失败。


$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -s productpage:9080upstream connect error or disconnect/reset before headers. reset reason: connection failure
复制代码


小结


Sidecar 的示例说明就到这里,这只是一个示例。


egress 配置覆盖的域名访问规则没必要在 ingress 中重复,所以 ingress 配置主要用于配置代理流量的规则。例如,我们可以将所有的入口流量传入 sidecar 中的监听进程(做一些定制开发的权限检查等),然后再传给下游微服务。


egress 的配置更多的是关注在服务网格对外访问的能力,服务内部如果限制了,应用自身访问都会需要大量的 ServiceEntry 注册,所以微服务之间东西流量的信任访问,需要靠安全机制来解决。


安全机制


概述



图片来源于 Istio 官网


Istio 提供包含了南北流量和东西流量两部分的防御机制:


1、Security by default:微服务应用不需要提供任何代码或引入三方库来保证自身安全。


2、Defense in depth:能够与已有的安全体系融合,深度定制安全能力。


3、Zero-trust Network:提供安全的方案都是假定服务网格的内部和外部都是 0 信任(不安全)网络。


下图是 Istio 中每个组件的角色:



图片来源于 Istio 官网


1、Citadel,证书(CA)管理


2、Sidecar 等 Envoy Proxy,提供 TLS 保障


3、Pilot,策略(Policy)和身份信息下发


4、Mixer,认证和审计


策略(Policy)


Istio 支持在运行时实时地配置策略(Policy),支持:


1、服务入流量速率控制。


2、服务访问控制,黑白名单规则等。


3、Header 重写,重定向。


也可以自己定制 Policy Adapter 来定义业务逻辑。


TLS


在介绍安全机制之前,有必要先科普一下 TLS 。


SSL ( Security Socket Layer ,安全 Socket 层),是一个解决 4 层 TCP 和 7 层 HTTPS 中间的协议层,解决安全传输的问题。TLS ( Transport Layer Security ,传输层安全协议),是基于 SSL v3 的后续升级版本,可以理解为相当于 SSL v3.1 。


主要提供:


1、认证(Transport Authentication),用户、服务器的身份,确保数据发送到正确的目的地。


2、加密数据,防止数据明文泄露。


3、数据一致性,传输过程不被串改。


Istio 中的安全传输机制都是建立在 TLS 之上的。


更多信息可以查看官方概念,详情参考:https://istio.io/docs/concepts/security


认证(Authentication)与鉴权(Authorization)


这两个词很相近,甚至缩写 auth 都是相同的,所以有时候很容混淆。


在 istioctl 中有两个命令 authn 和 authz ,这样就可以区分它们。


认证和鉴权分别做什么,在后文两节会具体介绍。这里先说一下它们的关系。


认证 实际上是 鉴权 的必要条件


认证 实际上是 鉴权 的必要条件


认证 实际上是 鉴权 的必要条件


为什么?


1、认证是识别身份(Identification)。


2、鉴权是检查特定身份(Identity)的权限。


这样就很好理解了。二者时常相随,我们常说的比如登录,就是:


1、基于登录机制的 cookie 来识别访问来源的身份——认证。


2、然后判断来源的身份是否具备登录系统的权限(或者是访问某一个页面的具体细节的权限)——鉴权。


那么在 Istio 体系中,Authentication 是基于 mTLS 机制来做的,那么开启 mTLS 之后,就可以设置一些 AuthorizationPolicy 来做访问控制。细节可以看下文。


认证(Authentication)


Istio 中的认证包含两种:


1、Transport Authentication ,传输层认证。基于 mTLS ( Mutual TLS ),检查东西流量的合法性。


2、Origin Authentication ,客户端认证。基于 JWT 等校验南北流量的登录身份。


示例:配置 Policy


这次我们跟着 Task: Authentication Policy 例子走,这里简化一下过程不做全程搬运,只分析关键点。


准备环境:


这个例子中,创建了 3 个 namespace ,其中两个 foo 和 bar 注入了 Sidecar, legacy 不注入用于对比。


#!/bin/bash kubectl create ns fookubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n fookubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n fookubectl create ns barkubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n barkubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n barkubectl create ns legacykubectl apply -f samples/httpbin/httpbin.yaml -n legacykubectl apply -f samples/sleep/sleep.yaml -n legacy
复制代码


默认情况下,容器之间是互通的(mTLS 运行在 PRESSIVE_MODE)。


这里通过一个 check.sh 脚本检查连通性:


#!/bin/bashfor from in "foo" "bar" "legacy"; do for to in "foo" "bar" "legacy"; do kubectl exec $(kubectl get pod -l app=sleep -n ${from} -o jsonpath={.items..metadata.name}) -c sleep -n ${from} -- curl "http://httpbin.${to}:8000/ip" -s -o /dev/null -w "sleep.${from} to httpbin.${to}: %{http_code}\n"; done; done$ ./check.shsleep.foo to httpbin.foo: 200sleep.foo to httpbin.bar: 200sleep.foo to httpbin.legacy: 200sleep.bar to httpbin.foo: 200sleep.bar to httpbin.bar: 200sleep.bar to httpbin.legacy: 200sleep.legacy to httpbin.foo: 200sleep.legacy to httpbin.bar: 200sleep.legacy to httpbin.legacy: 200
复制代码


打开 TLS:


通过全局的 MeshPolicy 配置打开 mTLS:


$ kubectl apply -f - <<EOFapiVersion: "authentication.istio.io/v1alpha1"kind: "MeshPolicy"metadata:  name: "default"spec:  peers:  - mtls: {}EOF
复制代码


这时,原本互通的容器访问不通了


执行:


$ ./check.shsleep.foo to httpbin.foo: 503sleep.foo to httpbin.bar: 503sleep.foo to httpbin.legacy: 200sleep.bar to httpbin.foo: 503sleep.bar to httpbin.bar: 503sleep.bar to httpbin.legacy: 200sleep.legacy to httpbin.foo: 000command terminated with exit code 56sleep.legacy to httpbin.bar: 000command terminated with exit code 56sleep.legacy to httpbin.legacy: 200
复制代码


Sidecar 注入的 namespace 中,会返回 503. 而没有注入的 ns 上,连接会直接被重置(connection reset)。


配置托管的 mTLS 能力


接着,通过 DestinationRule ,重新对注入 Sidecar 的微服务增加 mTLS 能力:


kubectl apply -f - <<EOFapiVersion: "networking.istio.io/v1alpha3"kind: "DestinationRule"metadata:  name: "default"  namespace: "istio-system"spec:  host: "*.local"  trafficPolicy:    tls:      mode: ISTIO_MUTUALEOF
复制代码


1、*.local 配置的含义是,对所有 K8s 集群内任意 namespace 之间的东西流量有效


2、tls.mode=ISTIO_MUTUAL :查看文档,表示完全由 Istio 托管 mTLS 的实现,其它选项失效。具体配置后面再涉及。


重新运行 check.sh :


$ ./check.shsleep.foo to httpbin.foo: 200sleep.foo to httpbin.bar: 200sleep.foo to httpbin.legacy: 503sleep.bar to httpbin.foo: 200sleep.bar to httpbin.bar: 200sleep.bar to httpbin.legacy: 503sleep.legacy to httpbin.foo: 000command terminated with exit code 56sleep.legacy to httpbin.bar: 000command terminated with exit code 56sleep.legacy to httpbin.legacy: 200
复制代码


注意,如果走了前面的例子会有全局 default 的 Sidecar Egress 配置,限制了只能访问同 namespace 的服务,那么跨 namespace 的调用仍然会 503 :


sleep.foo to httpbin.bar: 503
复制代码


可以自己试验一下,回顾一下配置:


apiVersion: networking.istio.io/v1alpha3kind: Sidecarmetadata:  name: default  namespace: istio-systemspec:  egress:  - hosts:    - ./*   # <--    - istio-system/*
复制代码


分析


对比之前的结果,有两点变化:


1、同样注入 Sidecar 的微服务互相可以访问了(200)。


2、没有注入 Sidecar(ns=legacy)的微服务不能被已注入 Sidecar 的微服务访问(503)。


ns=legacy 中的行为仍然不变


变化 1:说明微服务之间的 TLS 已经由 Istio 托管,这个期间我们没有修改任何服务的代码,很魔性。


变化 2:说明服务网格对外部容器也要求具备 TLS 能力,因为 legacy 中的服务没有注入 Sidecar ,因此访问失败。


鉴权(Authorization)


Istio 的鉴权机制的前提就是打开 mTLS 认证,让每一个或特定的微服务的 sidecar 互相访问都基于 mTLS 机制。


不是必要前提

有一部分鉴权规则是不依赖 mTLS 的,但是很少。


鉴权基于 istio CRD :AuthorizationPolicy


例如,默认拒绝所有微服务互相访问:


apiVersion: security.istio.io/v1beta1kind: AuthorizationPolicymetadata: name: deny-all namespace: foospec:
复制代码


需要留意的是,如果默认全部拒绝,那么甚至 istio-system 中的 istio-ingressgateway 流量访问 foo 这个 namespace 的服务也都会被拒绝。就无法从外部访问所有 foo 的服务了。所以我们可以改为:


apiVersion: security.istio.io/v1beta1kind: AuthorizationPolicymetadata:  name: deny-all  namespace: foospec:  rules:  - from:    - source:        namespaces:        - "istio-system"
复制代码


AuthorizationPolicy 的规则文档里都已经很详细了,这里就不再赘述。


应用配置之后,在任意一个微服务中访问另外一个微服务,就都会遇到 403 错误,消息为 RBAC access denied 。

其它

Istio 能力本文仅覆盖了流量控制(Traffic Management)、安全机制(Security)中比较浅显的部分,有关高级的 Policy 设置(限流、路由的干预等)、服务观测(Telemetry)等能力没有涉及。


此外,如何地高效运维管理(比如升级 istio 版本、管理不同集群的配置等),0 信任基础下的安全访问策略配置,基于 istio 做二次开发扩展,等等问题都是在生产实践当中需要关注的点,以后有机会再分享整理。

参考文档


作者介绍


袁赓拓,花名三辰,阿里云智能-计算平台事业部技术专家,负责数加平台 &DataWorks 的微服务生态建设,目前主要关注微服务、Service Mesh 等相关技术方向。


本文转载自公众号阿里巴巴中间件(ID:Aliware_2018)。


原文链接


https://mp.weixin.qq.com/s?__biz=MzU4NzU0MDIzOQ==&mid=2247488963&idx=1&sn=b0b76ae96707c5e068e65b1d3b6f229b&chksm=fdeb27a3ca9caeb5986b57015550419b4c385ab148ed3da588f8fee9cd3f22bdf0750f6ff71f&scene=27#wechat_redirect


2020-02-24 10:157567

评论 2 条评论

发布
用户头像
简单易懂
2022-07-22 01:52
回复
用户头像
v good
2021-06-06 22:41
回复
没有更多了
发现更多内容

书单 | “实战派”系列,每一本都是学好用好一门技术的“航空母舰”

博文视点Broadview

Java开发Excel数据导入mysql的实用小技巧

@零度

Java MySQL

Redisson:这么强大的实现分布式锁框架,你还没有?

华为云开发者联盟

redis 分布式 分布式锁 可重入锁 Redisson框架

19《重学JAVA》--集合(一)

杨鹏Geek

Java25周年 28天写作 12月日更

被灵魂问倒:这个BUG为什么没测出来?

华为云开发者联盟

测试 bug 文档 测试用例 测试工程师

GaussDB(DWS)中共享消息队列实现的三大功能

华为云开发者联盟

线程 数据同步 GaussDB(DWS) 共享消息队列 共享消息

Hive查询的18种方式

编程江湖

大数据 hive

建木持续集成平台v2.1.0发布

Jianmu

DevOps CI/CD 开源社区

容器技术正在颠覆传统,重构整个软件世界

巨子嘉

容器 云原生

龙智第四次荣登“2021上海软件和信息技术服务业高成长百家”名单

龙智—DevSecOps解决方案

上海软件和信息技术服务业

以 Vuex 为引,一窥状态管理全貌

杨成功

JavaScript Vue 大前端 vuex

大数据开发hadoop之yarn基础架构详解

@零度

大数据 hadoop YARN

跟着动画学Go数据结构之选择排序

宇宙之一粟

golang 数据结构 选择排序 12月日更

读《思辨与立场》-07思维的标准

wood

28天写作 批判性思维 思辨与立场

即构科技 RTC 实践与深度解析 | 内容合集

ZEGO即构

音视频 RTC 内容合集 技术实践 技术专题合集

10个比较不错的 JavaScript 库

编程江湖

JavaScript 前端开发

【1分钟调研赢好礼】HarmonyOS Connect 视频课堂用户反馈问卷

HarmonyOS开发者

HarmonyOS

Go语言逆向技术:常量字符串

华为云开发者联盟

字符串 go语言 字符 逆向技术 常量字符串

对话龙智专家,共探DevSecOps实践难点

龙智—DevSecOps解决方案

DevOps DevSecOps

前端开发之JS中filter()的使用

@零度

JavaScript 前端开发

【征集令】寻找2022年鸿蒙智联“出行新爆款产品”

HarmonyOS开发者

HarmonyOS

Android 8.0 下载安装进入【安装未知应用】页面,两步简化一步

阿策小和尚

28天写作 Android 小菜鸟 12月日更

Kubernetes 集群无损升级实践

vivo互联网技术

容器 云原生 服务器集群 Kubernetes 集群

netty系列之:netty对SOCKS协议的支持

程序那些事

Java Netty 程序那些事 SOCKS 12月日更

比特币挖矿与源码解析

恒生LIGHT云社区

比特币 区块链 挖矿

从 WAN 到 SD-WAN 边缘设备的网络架构

devpoint

TLS ssl SD-WAN 12月日更

龙智宣布与ConnectALL成为合作伙伴 进一步提升DevOps解决方案水平

龙智—DevSecOps解决方案

DevOps ConnectALL 价值流 价值流管理

你可能不信,52小时能做出7款超酷产品!

LigaAI

程序员 技术 技术人生 技术分享 hackathon

Prometheus Exporter (三十二)Varnish Exporter

耳东@Erdong

Prometheus 28天写作 exporter 12月日更 Varnish

COG云原生优化遥感影像,瓦片切分的最佳实践

华为云开发者联盟

云原生 遥感影像 瓦片切分 云上遥感影像文件 华为云地理遥感平台

【LeetCode】在 D 天内送达包裹的能力Java题解

Albert

算法 LeetCode 12月日更

Service Mesh · Istio · 以实践入门_架构_三辰_InfoQ精选文章