万字长文:Service Mesh · Istio · 以实践入门
Posted 云技术
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了万字长文:Service Mesh · Istio · 以实践入门相关的知识,希望对你有一定的参考价值。
Photo @ Jez Timms
文 | 三辰
本文不是 Istio 的全部,但是希望入门仅此一篇就够。
概念
围绕云原生(CN)的概念,给人一种知识大爆炸的感觉,但假如你深入了解每一个概念的细节,你会发现它和你很近,甚至就是你手里每天做的事情。
图片来源:https://landscape.cncf.io/
服务网格
历史
原始的应用程序--图片来源于网络
图片来源于网络
Consumer 与 Provider 就是微服务互相调用的一种解决方案。
以下是微服务集群基于Sidecar互相通讯的简化场景:
图片来源于网络
Service Mesh 是 Kubernetes 支撑微服务能力拼图的最后一块
Istio 和 Envoy
Istio,第一个字母是(ai)。
Istio基于Envoy实现Service Mesh数据平面--图片来源于网络
Sidecar注入
小结
所以我们打算卖什么?
实践
准备工作
如果是本地测试,Docker-Desktop也可以启动一个单机的k8s集群
curl -sL "https://github.com/istio/istio/releases/download/1.4.2/istio-1.4.2-osx.tar.gz" | tar xz
从 1.4.0 版本开始,不再使用 helm 来安装 Istio
# helm工具
$ brew install kubernetes-helm
安装Istio
首先确认 kubectl 连接的正确的 k8s 集群。
istioctl
安装
cd istio-1.4.2
# 安装istioctl
cp bin/istioctl /usr/local/bin/ # 也可以加一下PATH
# (可选)先查看配置文件
istioctl manifest generate --set profile=demo > istio.demo.yaml
# 安装istio
istioctl manifest apply --set profile=demo
## 以下是旧版本istio的helm安装方式 ##
# 创建istio专属的namespace
kubectl create namespace istio-system
# 通过helm初始化istio
helm 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的helm安装方式 ##
# 创建istio专属的namespace
kubectl create namespace istio-system
# 通过helm初始化istio
helm 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 -
$ kubectl get crds | grep 'istio.io' | wc -l
23
如果是阿里云ACS集群,安装完Istio后,会有对应的一个SLB被创建出来,转发到Istio提供的虚拟服务器组
示例:Hello World
cd samples/hello-world
注入
istioctl kube-inject -f helloworld.yaml -o helloworld-istio.yaml
实际上就是通过脚本修改了原文件,增加了:
分析
apiVersion: v1
kind: Service
metadata:
name: helloworld
labels:
app: helloworld
spec:
ports:
- port: 5000
name: http
selector:
app: helloworld
---
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
version: v1
name: helloworld-v1
spec:
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/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
version: v2
name: helloworld-v2
spec:
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
- 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 。
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.yaml
service/helloworld created
deployment.apps/helloworld-v1 created
deployment.apps/helloworld-v2 created
$ kubectl get deployments.apps -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
helloworld-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=v1
helloworld-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.yaml
gateway.networking.istio.io/helloworld-gateway created
virtualservice.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
深入探索
流量控制 - 切流
$ kubectl get gw helloworld-gateway -o yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: helloworld-gateway
spec:
selector:
istio: ingressgateway # use istio default controller
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
$ kubectl get vs helloworld -o yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: helloworld
spec:
hosts:
- "*"
gateways:
- helloworld-gateway
http:
- match:
- uri:
exact: /hello
route:
- destination:
host: helloworld # short for helloworld.${namespace}.svc.cluster.local
port:
number: 5000
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: helloworld
spec:
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
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: helloworld-destination
spec:
host: helloworld.default.svc.cluster.local
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
$ kubectl apply -f helloworld-gateway.yaml
gateway.networking.istio.io/helloworld-gateway unchanged
virtualservice.networking.istio.io/helloworld configured
destinationrule.networking.istio.io/helloworld-destination created
$ while true;do sleep 0.05 ;curl localhost/hello;done
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Hello version: v2, instance: helloworld-v2-76d6cbd4d-tgsq6
Gateway
VirtualService
DestinationRule
示例:Bookinfo
$ cd samples/bookinfo
注入
$ kubectl label namespace default istio-injection=enabled
部署
$ kubectl apply -f platform/kube/bookinfo.yaml
service/details created
serviceaccount/bookinfo-details created
deployment.apps/details-v1 created
service/ratings created
serviceaccount/bookinfo-ratings created
deployment.apps/ratings-v1 created
service/reviews created
serviceaccount/bookinfo-reviews created
deployment.apps/reviews-v1 created
deployment.apps/reviews-v2 created
deployment.apps/reviews-v3 created
service/productpage created
serviceaccount/bookinfo-productpage created
deployment.apps/productpage-v1 created
$ kubectl apply -f networking/bookinfo-gateway.yaml
gateway.networking.istio.io/bookinfo-gateway created
virtualservice.networking.istio.io/bookinfo created
流量控制 - 网络可见性
$ 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
$ 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
$ kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: default
namespace: istio-system
spec:
egress:
- hosts:
- "./*"
- "istio-system/*"
EOF
sidecar.networking.istio.io/default configured
每个namespace只允许一个无 workloadSelector 的配置 rootNamespace中无 workloadSelector 的配置是全局的,影响所有namespace,默认的rootNamespace=istio-system
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 0
curl: (56) Recv failure: Connection reset by peer
command terminated with exit code 56
效果是:外网已经访问不通。
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: baidu
spec:
hosts:
- www.baidu.com
ports:
- number: 80
name: http
protocol: HTTP
resolution: DNS
location: MESH_EXTERNAL
---
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: default
spec:
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:9080
curl: (56) Recv failure: Connection reset by peer
command terminated with exit code 56
配置上ServiceEntryapiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: default
spec:
egress:
- hosts:
- "./www.baidu.com"
- "./productpage.default.svc.cluster.local" # 这里必须用长名称
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: baidu
spec:
hosts:
- www.baidu.com
resolution: DNS
location: MESH_EXTERNAL
---
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: productpage
spec:
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/v1alpha3
kind: Sidecar
metadata:
name: productpage-sidecar
spec:
workloadSelector:
labels:
app: productpage
ingress:
- port:
number: 9080
protocol: HTTP
defaultEndpoint: 127.0.0.1:10080
egress:
- hosts:
- "*/*"
这个配置的效果是让 productpage 应用的容器收到 9080 端口的 HTTP 请求时,转发到容器内的10080端口。
$ kubectl exec -it $(kubectl get pod -l run=probe -o jsonpath='{..metadata.name}') -c probe -- curl -s productpage:9080
upstream connect error or disconnect/reset before headers. reset reason: connection failure
小结
概述
策略(Policy)
TLS
认证(Authentication)与鉴权(Authorization)
认证(Authentication)
示例:配置Policy
准备环境:
#!/bin/bash
kubectl create ns foo
kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n foo
kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n foo
kubectl create ns bar
kubectl apply -f <(istioctl kube-inject -f samples/httpbin/httpbin.yaml) -n bar
kubectl apply -f <(istioctl kube-inject -f samples/sleep/sleep.yaml) -n bar
kubectl create ns legacy
kubectl apply -f samples/httpbin/httpbin.yaml -n legacy
kubectl apply -f samples/sleep/sleep.yaml -n legacy
#!/bin/bash
for 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} "; done; done$ ./check.sh
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 200
sleep.legacy to httpbin.bar: 200
sleep.legacy to httpbin.legacy: 200
打开TLS:
$ kubectl apply -f - <<EOF
apiVersion: "authentication.istio.io/v1alpha1"
kind: "MeshPolicy"
metadata:
name: "default"
spec:
peers:
- mtls: {}
EOF
$ ./check.sh
sleep.foo to httpbin.foo: 503
sleep.foo to httpbin.bar: 503
sleep.foo to httpbin.legacy: 200
sleep.bar to httpbin.foo: 503
sleep.bar to httpbin.bar: 503
sleep.bar to httpbin.legacy: 200
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 000
command terminated with exit code 56
sleep.legacy to httpbin.legacy: 200
配置托管的 mTLS 能力
kubectl apply -f - <<EOF
apiVersion: "networking.istio.io/v1alpha3"
kind: "DestinationRule"
metadata:
name: "default"
namespace: "istio-system"
spec:
host: "*.local"
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
EOF
$ ./check.sh
sleep.foo to httpbin.foo: 200
sleep.foo to httpbin.bar: 200
sleep.foo to httpbin.legacy: 503
sleep.bar to httpbin.foo: 200
sleep.bar to httpbin.bar: 200
sleep.bar to httpbin.legacy: 503
sleep.legacy to httpbin.foo: 000
command terminated with exit code 56
sleep.legacy to httpbin.bar: 000
command terminated with exit code 56
sleep.legacy to httpbin.legacy: 200
sleep.foo to httpbin.bar: 503
apiVersion: networking.istio.io/v1alpha3
kind: Sidecar
metadata:
name: default
namespace: istio-system
spec:
egress:
- hosts:
- ./* # <--
- istio-system/*
分析
ns=legacy中的行为仍然不变
鉴权(Authorization)
不是必要前提 有一部分鉴权规则是不依赖mTLS的,但是很少。
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: foo
spec:
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
name: deny-all
namespace: foo
spec:
rules:
- from:
- source:
namespaces:
- "istio-system"
其它
参考文档
-
Istio官方文档 https://istio.io/docs/ -
Istio Handbook https://www.servicemesher.com/istio-handbook/concepts-and-principle/what-is-service-mesh.html -
Pattern Service Mesh https://philcalcado.com/2017/08/03/pattern_service_mesh.html
作者信息:
↓↓ 点击"阅读原文" 【加入云技术社区】
相关阅读:
更多文章请关注
文章好看点这里[在看] 以上是关于万字长文:Service Mesh · Istio · 以实践入门的主要内容,如果未能解决你的问题,请参考以下文章