什么是service mesh
service mesh,也翻译为服务网格。是一种在网络节点间通过动态路由的方式来进行资料与控制指令的传送。这种网络可以保持每个节点间的连线完整,当网络拓扑中有某节点失效或无法服务时,这种架构允许使用“跳跃”的方式形成新的路由后将讯息送达传输目的地(wikipedia)。
暂不谈这个抽象的概念,我们先来看下前两年比较流行的微服务。想到微服务,必然涉及一系列服务治理相关的名称:服务发现,配置中心,熔断,路由,监控,devops….。微服务架构的挑战不在于构建服务本身,而是服务间的通讯。可以想见,我们不得不在服务中引入一系列依赖和配置。
如上图:微服务由业务逻辑和通讯组件构成。
service mesh想要解决的,就是让服务专注于业务逻辑,而其他网络通讯以Sidecar的形式完成。service mesh是服务通讯的网络代理,解耦系统架构和业务逻辑。 如上图,图片来自pattern_service_mesh
istio
istio是目前最流行的service mesh开源软件,架构如下: 下面简单解释各个组件作用:
Envoy
上中的proxy,作为service mesh核心组件,使用开源软件Envoy,使用 C++ 编写的 L7 代理,CNCF 旗下的开源项目,以sidecar的形式部署。支持负载均衡,动态配置,http/grpc/MongoDB等代理,熔断,监控检查和丰富的度量指标等。下面简单看下如何独立使用。 config.yaml
admin:
access_log_path: /tmp/admin_access.log
address:
socket_address:
protocol: TCP
address: 0.0.0.0
port_value: 9901
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 10000 }
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
stat_prefix: ingress_http
codec_type: AUTO
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route: { host_rewrite: thoreauz.com, cluster: service_thoreauz }
http_filters:
- name: envoy.router
clusters:
- name: service_thoreauz
connect_timeout: 0.25s
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
hosts: [{ socket_address: { address: thoreauz.com, port_value: 80 }}]
dockerfile
FROM envoyproxy/envoy:latest
COPY envoy.yaml /etc/envoy/envoy.yaml
运行docker后,可以通过9901访问管理端,通过1000代理到thoreauz.com。 envoy运行时可以利用 xDS API来实现动态配置,这点很重要,nginx需要修改配置文件后reload,当然,nginx-plus、kong等具有这个功能。
Mixer
Mixer 是一个独立于平台的组件,负责在服务网格上执行访问控制和使用策略
Pilot
Pilot 为 Envoy sidecar 提供服务发现功能,为智能路由(例如 A/B 测试、金丝雀部署等)和弹性(超时、重试、熔断器等)提供流量管理功能。它将控制流量行为的高级路由规则转换为特定于 Envoy 的配置,并在运行时将它们传播到 sidecar。
Citadel
访问控件,内置身份和凭证管理可以提供强大的服务间和最终用户身份验证。
安装
安装kubernete集群;
省略
安装非认证istio
curl -L https://git.io/getLatestIstio | sh -
istio_tmp=$(echo istio*)
ISTIO_HOME="/opt/$istio_tmp"
mv -f "${istio_tmp}" /opt
echo "export ISTIO_HOME=${ISTIO_HOME}" >> /etc/profile
echo "export PATH=${ISTIO_HOME}/bin:$PATH" >> /etc/profile
source /etc/profile
# gcr.io可能无法拉取镜像,替换对应地址
sed -i 's#gcr.io/istio-release#istio#g' "${ISTIO_HOME}"/install/kubernetes/istio-demo.yaml
sed -i 's/memory: 2048Mi/memory: 200Mi/g' "${ISTIO_HOME}"/install/kubernetes/istio-demo.yaml
kubectl apply -f ${ISTIO_HOME}/install/kubernetes/helm/istio/templates/crds.yaml
kubectl apply -f ${ISTIO_HOME}/install/kubernetes/istio-demo.yaml
kubectl get svc -n istio-system
kubectl get pods -n istio-system
安装时间主要耗费在拉镜像,成功运行后如下:
[root@master ~]# kubectl get pods -n istio-system
NAME READY STATUS RESTARTS AGE
grafana-6d584567f9-6bhk9 1/1 Running 0 10h
istio-citadel-5d8b4ddfff-6vwrl 1/1 Running 0 10h
istio-cleanup-secrets-5pwk7 0/1 Completed 0 10h
istio-egressgateway-67bd6b9d94-h97fb 1/1 Running 0 10h
istio-galley-7597db6ff8-2hmp6 1/1 Running 0 10h
istio-grafana-post-install-tcxqh 0/1 Completed 0 10h
istio-ingressgateway-6fd5f84fdc-rzllg 1/1 Running 0 10h
istio-pilot-5cd4d78897-qqfgq 2/2 Running 1 10h
istio-policy-dd86756cf-xcrzm 2/2 Running 0 10h
istio-sidecar-injector-6995b57c94-f8cc6 1/1 Running 0 10h
istio-statsd-prom-bridge-549d687fd9-5qhgp 1/1 Running 0 10h
istio-telemetry-77c699bcdb-gtl5w 2/2 Running 0 10h
istio-tracing-7596597bd7-h6qtv 1/1 Running 0 10h
prometheus-6ffc56584f-92pvz 1/1 Running 0 10h
servicegraph-8678d5587-mwqzj 1/1 Running 0 10h
spring-boot 示例
使用spring-boot开发这样两个应用:
- user-service: 提供接口,比如/user/greeting
- user-ui: 访问user-service的接口 两个服务的关系如下:
kubernetes 原生api部署
使用kubernetes部署user-deployment.yaml,服务发现通过kube-dns。
[root@master ~]# kubectl apply -f user-deployment.yaml
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
user-service-deployment-5dbcb6cbff-cljmm 1/1 Running 0 2m
user-service-deployment-5dbcb6cbff-rjcz4 1/1 Running 0 2m
user-ui-deployment-84889c854d-cq5mq 1/1 Running 0 2m
[root@master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 15m
user-service ClusterIP 10.106.195.206 <none> 80/TCP 2m
user-ui ClusterIP 10.98.77.85 <none> 80/TCP 2m
[root@master ~]# curl -s 10.98.77.85/user/greet?name=thoreau| jq .
{
"id": 1,
"content": "Hello, thoreau",
"ip": "10.244.1.14",
"address": "user-service-deployment-5dbcb6cbff-cljmm",
"time": "2018-10-21 10:27:01"
}
[root@master ~]# curl -s 10.98.77.85/user/greet?name=thoreau| jq .
{
"id": 1,
"content": "Hello, thoreau",
"ip": "10.244.1.13",
"address": "user-service-deployment-5dbcb6cbff-rjcz4",
"time": "2018-10-21 10:28:19"
}
10.98.77.85 是user-ui的 CLUSTER-IP。两次访问路由到不同的user-ervice。
从上文示例可以看到,通过kubernete的服务发现功能,user-ui的请求自动路由到user-service。
istio部署
先删除之前的部署:kubectl delete -f user-deployment.yaml
使用istio部署:
kubectl apply -f <(istioctl kube-inject -f kubectl delete -f user-deployment.yaml)
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
user-service-deployment-76845bbfd-dtbkw 2/2 Running 0 1m
user-service-deployment-76845bbfd-mm7xw 2/2 Running 0 1m
user-ui-deployment-b88d5b76b-86xqz 2/2 Running 0 1m
可以看到,pod中的READY字段从1/1
变成了2/2
:
[root@master ~]#kubectl get pod user-service-deployment-76845bbfd-dtbkw -o json|jq .spec.containers[].name
"user-service"
"istio-proxy"
pod 中的容器多了istio-proxy,这就是之前提到的sidecar,此时验证接口访问和之前kubernetes部署一样正常。
iotio-gateway
刚才部署的服务,我希望可以从集群外部访问user-ui。可以使用kubernetes的ingress实现,但这儿我们使用iotio-gateway。
[root@master ~]# kubectl apply -f https://raw.githubusercontent.com/ThoreauZZ/spring-boot-istio/master/istio-rules/my-gateway.yaml
export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')
部署完成后,可以在集群外部可以通过http://主机外网ip:INGRESS_PORT如:
iotio路由
现在,我修改了user-service,作为2.0版本部署,同时存在1.0和2.0版本,我希望能做到只有30%的请求到2.0版本。创建user-service-deployment-v2,执行如下:
[root@master ~]# kubectl apply -f <(istioctl kube-inject -f kubectl delete -f user-service-deployment-v2.yaml)
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
user-service-deployment-76845bbfd-dtbkw 2/2 Running 0 1h
user-service-deployment-76845bbfd-mm7xw 2/2 Running 0 1h
user-service-deployment-v2-6c77744685-9cbdn 2/2 Running 0 2m
user-ui-deployment-b88d5b76b-86xqz 2/2 Running 0 1h
规则rute-userservice.yaml配置如下:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: user-service
spec:
host: user-service
subsets:
- name: v1
labels:
version: "1.0"
- name: v2
labels:
version: "2.0"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: spring-boot-user-service
spec:
hosts:
- user-service
http:
- route:
- destination:
host: user-service
subset: v1
port:
number: 80
weight: 70
- destination:
host: user-service
subset: v2
port:
number: 80
weight: 30
kubectl apply -f rute-userservice.yaml 这样,不用对代码做任何修改,就能做到流量的随意切换,如果把rute-userservice.yaml中的v2权重改成100,再试,会发现所有请求都路由到v2中。 从这个示例看得出来,service mesh的好处是把业务代码和网络配置解耦。
同样,通过路由配置,还可以做到http错误码、请求延时等故障注入,还有超时设置、熔断等丰富的网络配置。
值得一提的是,还可以配置mirror实现流量复制。
可视化指标
istio1.2默认安装的grafana,prometheus,servicegraph,zipkin。可以修改service的type为NodePort,比如
[root@master ~]#kubectl get svc servicegraph -n istio-system -o yaml| sed "s/ClusterIP/NodePort/g"|kubectl apply -f -
[root@master ~]# kubectl get svc servicegraph -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
servicegraph NodePort 10.105.150.5 <none> 8088:30639/TCP 1h
通过30639访问:
比如调用链查看:
kubectl get svc jaeger-query -n istio-system -o yaml| sed "s/ClusterIP/NodePort/g"|kubectl apply -f -
示例中使用了spring-cloud-slueth
总结
本文简单介绍了sever mesh,它以sidecar的形式接管服务的出入流量,sidecar负责服务治理,应用专注于业务逻辑。之后简单介绍最流行的server mesh开源实现istio,并运行了一个两个spring-boot应用。
参考 http://philcalcado.com/2017/08/03/pattern_service_mesh.html https://jimmysong.io/posts/envoy-sidecar-injection-in-istio-service-mesh-deep-dive/