作者 | 王夕宁 阿里云高级技术专家
导读:本文摘自于阿里云高级技术专家王夕宁撰写的《Istio 服务网格技术解析与实战》一书,讲述了如何使用 Istio 进行多集群部署管理来阐述服务网格对多云环境、多集群即混合部署的支持能力。
前文详情:
在多控制平面拓扑的配置中,每个 Kubernetes 集群都会安装相同的 Istio 控制平面,并且每个控制平面只会管理自己集群内的服务端点。通过使用 Istio 网关、公共根证书颁发机构(CA)以及服务条目 ServiceEntry,可以将多个集群配置组成一个逻辑上的单一服务网格。这种方法没有特殊的网络要求,因此通常被认为是在 Kubernetes 集群之间没有通用网络连接时的一种最简单方法。
在这种拓扑配置下,Kubernetes 跨集群通信需要服务之间的双向 TLS 连接,要在集群之间启用双向 TLS 通信,每个集群的 Citadel 将配置由共享的根 CA 生成的中间 CA 证书,如图所示。
(多控制平面)
部署控制平面
从共享的根 CA 为每个集群的 Citadel 生成中间 CA 证书,共享的根 CA 启用跨不同集群的双向 TLS 通信。为了便于说明,我们将 samples/certs 目录下 Istio 安装中提供的示例根 CA 证书用于两个集群。在实际部署中,你可能会为每个集群使用不同的 CA 证书,所有 CA 证书都由公共根 CA 签名。
在每个 Kubernetes 集群中实施以下步骤,以在所有集群中部署相同的 Istio 控制平面配置。
- 使用以下的命令为生成的 CA 证书创建 Kubernetes 密钥,如下所示:
kubectl
create namespace istio-system
kubectl
create secret generic cacerts -n istio-system \
--from-file=samples/certs/ca-cert.pem \
--from-file=samples/certs/ca-key.pem \
--from-file=samples/certs/root-cert.pem \
--from-file=samples/certs/cert-chain.pem
- 安装 Istio 的 CRD 并等待几秒钟,以便将它们提交给 Kubernetes API 服务器,如下所示:
for
i in install/kubernetes/helm/istio-init/files/crd*yaml; do kubectl apply -f $i;
done
- 部署 Istio 控制平面:如果 helm 依赖项缺失或者不是最新的,可以通过 helm dep update 来更新这些依赖项。注意因为没有使用 istio-cni,可以暂时将其从依赖项 requirements.yaml 中去掉再执行更新操作。具体执行命令如下:
helm
template install/kubernetes/helm/istio --name istio --namespace istio-system \
-f
install/kubernetes/helm/istio/values-istio-multicluster-gateways.yaml >
./istio.yaml
kubectl
apply -f ./istio.yaml
确保上述步骤在每个 Kubernetes 集群中都执行成功。当然,通过 helm 生成 istio.yaml 的命令执行一次即可。
设置 DNS
为远程集群中的服务提供 DNS 解析,则现有应用程序不需要做修改就可以运行,因为应用程序通常期望通过其 DNS 名称来解析服务并访问所得到的 IP 地址。Istio 本身不使用 DNS 在服务之间路由请求,同一个 Kubernetes 集群下的服务会共享一个相同的 DNS 后缀(例如 svc.cluster.local)。Kubernetes DNS 为这些服务提供 DNS 解析能力。为了给远程集群中的服务提供相似的设置,将远程集群中的服务以 <name>.<namespace>.global 的格式命名。
Istio 安装包中附带了一个 CoreDNS 服务器,该服务器将为这些服务提供DNS解析能力。为了利用这个 DNS 解析能力,需要配置 Kubernetes 的 DNS 服务指向该 CoreDNS 服务。该 CoreDNS 服务将作为 .global DNS 域的 DNS 服务器。
对于使用 kube-dns 的集群,请创建以下配置项或更新现有的配置项:
kubectl
apply -f - <<EOF
apiVersion:
v1
kind:
ConfigMap
metadata:
name: kube-dns
namespace: kube-system
data:
stubDomains: |
{"global": ["$(kubectl get
svc -n istio-system istiocoredns -o jsonpath={.spec.clusterIP})"]}
EOF
对于使用 CoreDNS 的集群,请创建以下配置项或更新现有的配置项:
kubectl
apply -f - <<EOF
apiVersion:
v1
kind:
ConfigMap
metadata:
name: coredns
namespace: kube-system
data:
Corefile: |
.:53 {
errors
health
kubernetes cluster.local in-addr.arpa
ip6.arpa {
pods insecure
upstream
fallthrough in-addr.arpa ip6.arpa
}
prometheus :9153
proxy . /etc/resolv.conf
cache 30
reload
loadbalance
}
global:53 {
errors
cache 30
proxy . $(kubectl get svc -n
istio-system istiocoredns -o jsonpath={.
spec.clusterIP})
}
EOF
部署示例应用
为了演示跨集群访问,在一个 Kubernetes 集群中部署 sleep 应用服务,在第二个集群中部署 httpbin 应用服务,然后验证 sleep 应用是否可以调用远程集群的 httpbin 服务。
- 部署 sleep 服务到第一个集群 cluster1 中,执行如下命令:
kubectl
create namespace app1
kubectl
label namespace app1 istio-injection=enabled
kubectl
apply -n app1 -f samples/sleep/sleep.yaml
export
SLEEP_POD=$(kubectl get -n app1 pod -l app=sleep -o
jsonpath={.items..metadata.name})
- 部署 httpbin 服务到第二个集群 cluster2 中,执行如下命令:
kubectl
create namespace app2
kubectl
label namespace app2 istio-injection=enabled
kubectl
apply -n app2 -f samples/httpbin/httpbin.yaml
- 获取集群 cluster2 的入口网关地址,如下所示:
export
CLUSTER2_GW_ADDR=$(kubectl get svc --selector=app=istio-ingressgateway \
-n istio-system -o
jsonpath="{.items[0].status.loadBalancer.ingress[0].ip}")
- 为了让在集群 cluster1 中的服务 sleep 能够访问集群 cluster2 中的服务 httpbin,我们需要在集群 cluster1 中为服务 httpbin 创建一个服务条目 ServiceEntry 资源。服务条目 ServiceEntry 的主机名应该是<name>.<namespace>.globalname,其中 name 和 namespace 分别对应于集群 cluster2 中的远程服务的名称和命名空间。
对于 *.global 域下服务的 DNS 解析,需要为这些服务分配一个 IP 地址,并且保证 .globalDNS 域中的每个服务在集群中必须具有唯一的 IP 地址。这些 IP 地址在 pod 之外是不可路由的。在这个例子中,我们将使用网段 127.255.0.0/16 来避免与其他的IP冲突。这些 IP 的应用流量将由 Sidecar 代理捕获并路由到适当的其他远程服务。
在集群 cluster1 中创建该 httpbin 服务对应的 ServiceEntry,执行如下命令:
kubectl
apply -n app1 -f - <<EOF
apiVersion:
networking.istio.io/v1alpha3
kind:
ServiceEntry
metadata:
name: httpbin-app2
spec:
hosts:
# must be of form name.namespace.global
- httpbin.app2.global
# Treat remote cluster services as part of
the service mesh
# as all clusters in the service mesh share
the same root of trust.
location: MESH_INTERNAL
ports:
- name: http1
number: 8000
protocol: http
resolution: DNS
addresses:
# the IP address to which httpbin.bar.global
will resolve to
# must be unique for each remote service,
within a given cluster.
# This address need not be routable. Traffic
for this IP will be captured
# by the sidecar and routed appropriately.
- 127.255.0.2
endpoints:
# This is the routable address of the ingress
gateway in cluster2 that
# sits in front of sleep.bar service. Traffic
from the sidecar will be
# routed to this address.
- address: ${CLUSTER2_GW_ADDR}
ports:
http1: 15443 # Do not change this port
value
EOF
上面的配置将会使集群 cluster1 中访问 httpbin.app2.global 的所有流量,包括访问它的任何端口的流量,都会被路由到启用了双向 TLS 连接的端点 <IPofCluster2IngressGateway>:15443 上。
端口 15443 的网关是一个特殊的 SNI 感知的 Envoy 代理,它是在前面开始部分中作为多集群 Istio 安装步骤的一部分预先配置和安装的。进入端口 15443 的流量将在目标集群的适当内部服务的 pod 中进行负载均衡。
在集群 cluster1 下执行如下命令查看容器 istiocoredns,可以看到上述 ServiceEntry 的域名映射关系已经被加载:
export
ISTIO_COREDNS=$(kubectl get -n istio-system po -l app=istiocoredns -o
jsonpath={.items..metadata.name})
kubectl
logs --tail 2 -n istio-system ${ISTIO_COREDNS} -c istio-coredns-plugin
执行结果如下所示:
- 验证在集群 cluster1 中的 sleep 服务是否可以正常调用位于集群 cluster2 中的 httpbin 服务,在集群 cluster1 执行如下命令:
kubectl
exec $SLEEP_POD -n app1 -c sleep -- curl httpbin.app2.global:8000/headers
执行结果如下所示:
至此,集群 cluster1 与 cluster2 在多控制平面配置下完成了连通。
跨集群的版本路由
通过前面的文章,我们已经了解了 Istio 的很多功能,例如基本版本的路由等,可以在单个 Kubernetes 集群上很容易地实现。而很多真实的业务场景中,基于微服务的应用程序并非那么简单,而是需要在多个位置跨集群去分配和运行服务。那么问题就来了,是否 Istio 的这些功能同样可以很简单地运行在这些真实的复杂环境中呢?
下面我们将会通过一个示例来了解 Istio 的流量管理功能如何在具有多个控制平面拓扑的多集群网格中正常运行。
- 首先,部署版本 v1 的 helloworld 服务到第一个集群 cluster1 中,执行如下命令:
kubectl
create namespace hello
kubectl
label namespace hello istio-injection=enabled
kubectl
apply -n hello -f samples/sleep/sleep.yaml
kubectl
apply -n hello -f samples/helloworld/service.yaml
kubectl
apply -n hello -f samples/helloworld/helloworld.yaml -l version=v1
- 部署版本 v2 与 v3 的 helloworld 服务到第二个集群 cluster2 中,执行如下命令:
kubectl
create namespace hello
kubectl
label namespace hello istio-injection=enabled
kubectl
apply -n hello -f samples/helloworld/service.yaml
kubectl
apply -n hello -f samples/helloworld/helloworld.yaml -l version=v2
kubectl
apply -n hello -f samples/helloworld/helloworld.yaml -l version=v3
- 如前面章节中所述,多控制平面下,需要使用以 .global 为后缀的 DNS 名称访问远程服务。
在我们的例子中,它是 helloworld.hello.global,所以我们需要在集群 cluster1 中创建服务条目 ServiceEntry 和目标规则 DestinationRule。服务条目 ServiceEntry 将使用集群 cluster2 的入口网关作为端点地址来访问服务。
通过使用以下命令在集群 cluster1 中创建 helloworld 服务对应的服务条目 ServiceEntry 和目标规则DestinationRule:
kubectl
apply -n hello -f - <<EOF
apiVersion:
networking.istio.io/v1alpha3
kind:
ServiceEntry
metadata:
name: helloworld
spec:
hosts:
- helloworld.hello.global
location: MESH_INTERNAL
ports:
- name: http1
number: 5000
protocol: http
resolution: DNS
addresses:
- 127.255.0.8
endpoints:
- address: ${CLUSTER2_GW_ADDR}
labels:
cluster: cluster2
ports:
http1: 15443 # Do not change this port
value
---
apiVersion:
networking.istio.io/v1alpha3
kind:
DestinationRule
metadata:
name: helloworld-global
spec:
host: helloworld.hello.global
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
subsets:
- name: v2
labels:
cluster: cluster2
- name: v3
labels:
cluster: cluster2
EOF
- 在两个集群上创建目标规则。在集群 cluster1 中创建子集 v1 对应的目标规则,执行如下命令:
kubectl
apply -n hello -f - <<EOF
apiVersion:
networking.istio.io/v1alpha3
kind:
DestinationRule
metadata:
name: helloworld
spec:
host: helloworld.hello.svc.cluster.local
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
subsets:
- name: v1
labels:
version: v1
EOF
而在集群 cluster2 中创建子集 v2 和 v3 对应的目标规则,执行如下命令:
kubectl
apply -n hello -f - <<EOF
apiVersion:
networking.istio.io/v1alpha3
kind:
DestinationRule
metadata:
name: helloworld
spec:
host: helloworld.hello.svc.cluster.local
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
subsets:
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
EOF
- 创建虚拟服务以路由流量。
应用下面的虚拟服务将会使得来自用户 jason 对 helloworld 的流量请求指向位于集群 cluster2 中的版本 v2 和 v3,其中 v2 比例为 70%,v3 比例为 30%;来自任何其他用户对 helloworld 的流量请求都将转到位于集群 cluster1 中的版本 v1:
kubectl
apply -n hello -f - <<EOF
apiVersion:
networking.istio.io/v1alpha3
kind:
VirtualService
metadata:
name: helloworld
spec:
hosts:
- helloworld.hello.svc.cluster.local
- helloworld.hello.global
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: helloworld.hello.global
subset: v2
weight: 70
- destination:
host: helloworld.hello.global
subset: v3
weight: 30
- route:
- destination:
host:
helloworld.hello.svc.cluster.local
subset: v1
EOF
执行多次调用,可以从下面的执行结果中看出,上述流量路由的规则生效,这也说明了在多控制平面拓扑下,用于路由的规则定义与在本地集群的使用方式是一样的:
设置多集群网格的最简单方法是使用多控制平面拓扑,因为它没有特殊的网络要求。通过上述示例可以看出,在单个 Kubernetes 集群上运行的路由功能同样很容易地在多个集群中使用运行。
《Istio服务网格技术解析与实战》读者可免费体验 ASM 产品进行学习!点击了解阿里云服务网格产品 ASM:www.aliyun.com/product/servicemesh
作者简介
王夕宁 阿里云高级技术专家,阿里云服务网格产品 ASM 及 Istio on Kubernetes 技术负责人,专注于 Kubernetes、云原生、服务网格等领域。曾在 IBM 中国开发中心工作,担任过专利技术评审委员会主席,拥有 40 多项相关领域的国际技术专利。《Istio 服务网格解析与实战》一书由其撰写,详细介绍了 Istio 的基本原理与开发实战,包含大量精选案例和参考代码可以下载,可快速入门 Istio 开发。Gartner 认为,2020 年服务网格将成为所有领先的容器管理系统的标配技术。本书适合所有对微服务和云原生感兴趣的读者,推荐大家对本书进行深入的阅读。
课程推荐
为了更多开发者能够享受到 Serverless 带来的红利,这一次,我们集结了 10+ 位阿里巴巴 Serverless 领域技术专家,打造出最适合开发者入门的 Serverless 公开课,让你即学即用,轻松拥抱云计算的新范式——Serverless。
点击即可免费观看课程:https://developer.aliyun.com/learning/roadmap/serverless
“阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”