SpringBoot、Kubernetes和Istio微服务网格演示源码


如果单纯使用kubernetes的pod部署Spring微服务,K8s的负载平衡以及代理设置和你微服务应用之间不是非常的智能衔接,。无论如何,部署新的应用程序版本pod需要更加软化的方法。以下是典型的需求:

  • 智能调拨流量,在部署新的应用程序版本容器时,您经常需要以某种比例(即金丝雀Canary测试)分割新容器和当前生产之间的流量
  • 蓝绿部署,在部署新的应用程序版本pod时,假设您需要蓝色/绿色部署
  • 在部署新的应用程序版本pod时,让我们说只有HTTP cookie识别的一些用户可以测试新版本

所有这些问题都可以通过名为istio的神奇工具来解决。

Istio安装
首先是在Istio安装之前,需要以至少4GB的内存启动 Minikube ,否则将无法启动pilot ,阅读stackoverflow讨论;第二个重要的是始终使用Istio Custom Resources Definitions开始安装:

$ kubectl apply -f <ISTIO_INSTALL_HOME> /install/kubernetes/helm/istio/templates/crds.yaml

这是安装后所需的输出:

kubernetes tomask79$ kubectl get pods -n istio-system
NAME                                      READY   STATUS      RESTARTS   AGE
grafana-59b8896965-6jcb8                  1/1     Running     4          7d
istio-citadel-856f994c58-jwdp2            1/1     Running     4          7d
istio-cleanup-secrets-glmrz               0/1     Completed   0          7d
istio-egressgateway-5649fcf57-5b885       1/1     Running     4          7d
istio-galley-7665f65c9c-89wsz             1/1     Running     13         7d
istio-grafana-post-install-z9v7z          0/1     Completed   0          7d
istio-ingressgateway-6755b9bbf6-qrvq8     1/1     Running     4          7d
istio-pilot-698959c67b-xpqnj              2/2     Running     11         7d
istio-policy-6fcb6d655f-8mkf4             2/2     Running     18         7d
istio-security-post-install-jn4sr         0/1     Completed   0          7d
istio-sidecar-injector-768c79f7bf-p8xqr   1/1     Running     4          7d
istio-telemetry-664d896cf5-zc8sr          2/2     Running     17         7d
istio-tracing-6b994895fd-wpc2c            1/1     Running     7          7d
prometheus-76b7745b64-hqnrq               1/1     Running     4          7d
servicegraph-5c4485945b-jql54             1/1     Running     12         7d

演示示例
在向您展示Istio流量管理魔术之前,我们将介绍第一个非常简单的Spring Boot MVC应用程序,我们将在两个版本中部署到kubernetes。

版本1:

@RestController
public class ControllerV1 {

    @GetMapping(path = "/service")
    public String getResult() {
        return
"Hello I'm V1!";
    }
}

下面是这个版本的k8s的部署,标记mvc-service开始部分:
(mvc-1/istio/kubernetes-deploy/v1-deploy.yaml)

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: mvc-service
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: mvc-service
        version: v1
    spec:
      containers:
      - name: mvc-service
        image: service-v1:0.0.1-SNAPSHOT
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080

版本2:

@RestController
public class ControllerV2 {

    @GetMapping(path = "/service")
    public String getResult() {
        return
"Hello i'm V2!";
    }
}

下面是这个版本的k8s的部署,标记mvc-service开始部分:
(mvc-2/istio/kubernetes-deploy/v2-deploy.yaml)

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: mvc-service-v2
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: mvc-service
        version: v2
    spec:
      containers:
      - name: mvc-service
        image: service-v2:0.0.1-SNAPSHOT
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080

我们在标有“mvc-service”字符串的pod中有两个版本的应用程序。因此kubernetes服务还应该使用标签'mvc-service'来定位pod后端:(mvc-1/istio/kubernetes-deploy/v1-deploy.yaml)

apiVersion: v1
kind: Service
metadata:
  name: mvc-service
  labels:
    app: mvc-service
spec:
  type: NodePort
  ports:
  - port: 8080
    name: http
  selector:
    app: mvc-service

使用Istio支持部署到Kubernetes

好的,我们已经准备好了kubernetes清单,但还没有部署任何东西!为了能够使用Istio流量管理,您需要向您的pod 注入sidecar代理。如果没有istio边车,你就不会形成服务网格。你有两个选择:


我选择了第一个选项并将边车sidecar设置注入到这样的清单中:

istioctl kube-inject -f v1-deploy.yaml >> v1-deploy-istio.yaml

对于第二个版本(文件夹mvc-2 / istio / kubernetes-deploy)同样这么做:

istioctl kube-inject -f v2-deploy.yaml >> v2-deploy-istio.yaml

现在部署生成的kubernetes清单注入istio-sidecar:

kubectl apply -f v1-deploy-istio.yaml

然后部署第二个版本:

kubectl apply -f v2-deploy-istio.yaml

这是部署后所需的输出:

 

kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
mvc-service-76ffb4bc9f-sdrtn      2/2     Running   10         10d
mvc-service-v2-59ff7d6886-v87jt   2/2     Running   10         10d

每个POD都有两个容器,因为它运行app容器和istio代理边车容器。要查看为pod启动的边车代理,只需键入:

kubernetes - deploy  tomask79 $  kubectl  describe  pod  mvc - service - 76 ffb4bc9f - sdrtn

检查事件:

Events:
  Type    Reason          Age    From               Message
  ----    ------          ----   ----               -------
  Normal  SandboxChanged  8m41s  kubelet, minikube  Pod sandbox changed, it will be killed and re-created.
  Normal  Pulled          8m37s  kubelet, minikube  Container image "docker.io/istio/proxy_init:1.0.5" already present on machine
  Normal  Created         8m35s  kubelet, minikube  Created container
  Normal  Started         8m34s  kubelet, minikube  Started container
  Normal  Pulled          8m34s  kubelet, minikube  Container image
"service-v1:0.0.1-SNAPSHOT" already present on machine
  Normal  Created         8m33s  kubelet, minikube  Created container
  Normal  Started         8m33s  kubelet, minikube  Started container
  Normal  Pulled          8m33s  kubelet, minikube  Container image
"docker.io/istio/proxyv2:1.0.5" already present on machine
  Normal  Created         8m33s  kubelet, minikube  Created container
  Normal  Started         8m33s  kubelet, minikube  Started container

Istio形成服务网格
好的,部署了两个版本的Spring Boot MVC应用程序。现在我打赌你对像VirtualServiceDestinationRule这样的Istio对象感到困惑......什么时候使用它们,你还在Kubernetes服务吗?在stackoverflow有一个非常好的讨论,这对我来说非常有用。简而言之:

在我们的Spring MVC演示中,我们获得了名为mvc-service的kubernetes 服务。这将是DestinationRule 对象中的主机参数,因为这是提供目标服务的后端。现在我们的mvc-service提供了两个版本的Spring MVC应用程序v1和v2 ,它们将构成服务网格,因此DestinationRule看起来像:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: mvc-service
spec:
  host: mvc-service
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2


在我们的演示中,通过kubectl部署它:

spring-kubernetes-istio tomask79$ kubectl apply -f istio-destionation-rule.yaml 

现在VirtualService进入游戏:
VirtualService定义了一组要在主机被寻址时应用的流量路由规则。每个路由规则定义特定协议的流量的匹配标准。如果流量匹配,则将其发送到注册表中定义的命名目标服务(或其子集 /版本)。

换句话说,您首先部署K8s部署和服务。然后,通过Istio DestionRule定义微服务的网络,然后通过VirtualService设置HTTP路由规则。

用Istio进行金丝雀测试
因此,假设我们的演示SpringMVC应用程序的第二个版本(在服务网格中,子集v2)不够稳定,无法处理满负载,因此我们只将20%的流量路由到它。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: service-gateway
spec:
  hosts:
  - "*"
  gateways:
  - service-gateway
  http:
  - match:
    - uri:
        exact: /service
    route:
    - destination:
        host: mvc-service
        subset: v1
      weight: 80
    - destination:
        host: mvc-service
        subset: v2
      weight: 20

这真的很容易。作为最后一步,我们需要公开服务网关,即Istio-Ingress网关 ,它从服务网格外部获取流量并将其转发到该网关

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: service-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

在这个repo中提到的网关和虚拟服务都在istio-gateway.yaml文件中,所以让我们部署它:

spring-kubernetes-istio tomask79$ kubectl apply -f istio-gateway.yaml 

好的,这是测试之前所需的输出,服务网格在前:

istioctl get destinationrules -o yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"networking.istio.io/v1alpha3","kind":"DestinationRule","metadata":{"annotations":{},"name":"mvc-service","namespace":"default"},"spec":{"host":"mvc-service","subsets":[{"labels":{"version":"v1"},"name":"v1"},{"labels":{"version":"v2"},"name":"v2"}]}}
  creationTimestamp: null
  name: mvc-service
  namespace: default
  resourceVersion:
"5722"
spec:
  host: mvc-service
  subsets:
  - labels:
      version: v1
    name: v1
  - labels:
      version: v2
    name: v2
---

然后是virtualservices:

istioctl get virtualservices -o short
VIRTUAL-SERVICE NAME   GATEWAYS          HOSTS         HTTP     TCP      NAMESPACE   AGE
mvc-service                              mvc-service       1        0      default     11d
service-gateway        service-gateway   *                 1        0      default     11d

金丝雀测试
我们需要获取istio-ingress网关和端口的IP地址。

kubectl get service istio-ingressgateway -n istio-system
NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                                                                                                                   AGE
istio-ingressgateway   LoadBalancer   10.111.15.19   <pending>     80:31380/TCP,443:31390/TCP,31400:31400/TCP,15011:32464/TCP,8060:30626/TCP,853:30365/TCP,15030:31121/TCP,15031:31359/TCP   13d

istio-ingress文档如是说:
如果设置了EXTERNAL-IP值,则您的环境具有可 用于入口网关的外部负载平衡器。如果EXTERNAL-IP值为<none>(或永久<pending>),则您的环境 不会为入口网关提供外部负载平衡器。在这种情况下,您可以 使用service s节点端口访问网关。

这意味着调用我们的金丝雀版本,我们将继续:

 minikube ip
192.168.99.110

现在终于调用了后台,请注意文档中指出的nodeport端口:

10个点击中:2个进入V2版本,8个点击进入V1。

tomask79:spring-kubernetes-istio tomask79$ curl http://192.168.99.110:31380/service
Hello I'm V1!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello I'm V1!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello I'm V1!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello i'm V2!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello I'm V1!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello I'm V1!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello I'm V1!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello I'm V1!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello i'm V2!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello I'm V1!tomask79:spring-kubernetes-istio tomask79$ 

使用Istio进行蓝/绿部署
现在让我们说我们的应用程序的V2版本足够稳定,我们可以将100%的流量路由到它。要使用Istio实现这一点,我们将更改VirtualService中的规则:
将istio-gateway.yaml文件中的VirtualService修改为:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: service-gateway
spec:
  hosts:
  - "*"
  gateways:
  - service-gateway
  http:
  - match:
    - uri:
        exact: /service
    route:
    - destination:
        host: mvc-service
        subset: v2


重新部署:

tomask79:spring-kubernetes-istio tomask79$ kubectl apply -f istio-gateway.yaml 
gateway.networking.istio.io/service-gateway unchanged
virtualservice.networking.istio.io/service-gateway configured

测试蓝/绿部署
现在V2版本应该成为我们的生产版本并处理100%的流量:

tomask79:spring-kubernetes-istio tomask79$ curl http://192.168.99.110:31380/service
Hello i'm V2!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello i'm V2!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello i'm V2!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello i'm V2!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello i'm V2!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello i'm V2!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello i'm V2!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello i'm V2!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello i'm V2!tomask79:spring-kubernetes-istio tomask79$ curl http:
//192.168.99.110:31380/service
Hello i'm V2!tomask79:spring-kubernetes-istio tomask79$ 

总结
Istio看起来对我来说是超级强大的工具。但它的学习曲线有点长。此外,他们还在配置模型之间进行了重大更改。无论如何,他们支持粘性会话甚至websockets。例如,对于我在EmbedIT中使用的系统,这是两个“必须拥有”的东西。像istio这样的另一个类似工具是Linkerd。你也可以看一下:)

本文源码