动态修改 Istio 路由策略

上一篇我们讲了如何对路由策略进行配置,但是登录到服务器并执行命令,对于用户来说是一种非常不好的体验,那么本篇就来讲一下,如何对命令进行一系列的包装,以使得操作可以直观简便。

需要知道的是,在 K8S 体系下,我们可以将宿主机的命令和文件等挂载到 Pod 内,以便在 Pod 里面也可以访问到这些内容,或是执行宿主机上的命令,我们需要修改一下部署的 yaml:

apiVersion: v1
kind: Pod
metadata:
  name: sample-service
  labels:
    app: sample-service
spec:
  containers:
    - name: sample-service-container
      image: sample-service:v0
      ports:
      - containerPort: 9001
        hostPort: 32001
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - mountPath: /usr/local/bin/kubectl
          name: sample-service-kube
          readOnly: true
        - mountPath: /root/.kube/config
          name: sample-service-config
          readOnly: true
        - mountPath: /usr/local/bin/istioctl
          name: sample-service-istio
          readOnly: true
  volumes:
  - hostPath:
      path: /usr/bin/kubectl
    name: sample-service-kube
  - hostPath:
      path: /home/rarnu/.kube/config
    name: sample-service-config
  - hostPath:
      path: /usr/local/bin/istioctl
    name: sample-service-istio

这里需要特别注意的是,必须挂载 ~/.kube/config 文件到 Pod 内,这个文件决定了 kubectl 命令的权限,如果没有这个文件,将不能在 Pod 内执行 kubectl 命令。

然后删去原先的 Pod 并重新创建之:

$ kubectl delete -f sample-service.yaml
$ kubectl apply -f sample-service.yaml

等 Pod 启动后,可以进入 Pod 内查看命令是否已挂载,并且尝试执行它:

$ kubectl exec -it sample-service sh
# which kubectl
/usr/local/bin/kubectl
# which istioctl
/usr/local/bin/istioctl
# kubectl get pods

此时可以发现,在 Pod 内拥有命令并且可以正常执行。


接下来只需要写一些代码就可以完成对路由的修改了:

data class ReqClusters(val clusters: List<String>)
data class Response<T>(val code: String, val message: String, val data: T? = null) {
    companion object {
        fun success(): Response<*> = Response("200", "succ", null)
        fun fatal(): Response<*> = Response("500", "fail", null)
        fun<T> success(item: T): Response<T> = Response("200", "succ", item)
        fun<T> fatal(item: T): Response<T> = Response("500", "fail", item)
    }
}

fun Routing.istioControllerRouting() {
    post<ReqClusters>("/update") { param ->
        addNewClusters(param.clusters)
        call.respond(Response.success())
    }
}

fun addNewClusters(list: List<String>) {
    if (list.isNotEmpty()) {
        val script = when (list.size) {
            1 -> "    - destination:\n" +
                 "        host: reviews\n" +
                 "        subset: ${list[0]}"
            2 -> "    - destination:\n" +
                 "        host: reviews\n" +
                 "        subset: ${list[0]}\n" +
                 "      weight: 50\n" +
                 "    - destination:\n" +
                 "        host: reviews\n" +
                 "        subset: ${list[1]}\n" +
                 "      weight: 50\n"
            3 -> "    - destination:\n" +
                 "        host: reviews\n" +
                 "        subset: ${list[0]}\n" +
                 "      weight: 33\n" +
                 "    - destination:\n" +
                 "        host: reviews\n" +
                 "        subset: ${list[1]}\n" +
                 "      weight: 33\n" +
                 "    - destination:\n" +
                 "        host: reviews\n" +
                 "        subset: ${list[2]}\n" +
                 "      weight: 34\n"
            else -> ""
        }
        val scriptText = ROUTE_TEMPLATE.format(script)
        val destFile = File(SCRIPT_FILE_PATH, "script.yaml")
        destFile.writeText(scriptText)
        runCommand("kubectl apply -f ${destFile.absolutePath}").apply {
            println("output: $output, error: $error")
        }
    }
}

private const val ROUTE_TEMPLATE = """
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
    - reviews
  http:
  - route:
%s
"""

然后只需要在前端写个简单的页面,请求一下接口即可:

image.png

这里你可能看到了,上面有展示当前的路由情况,这个内容获取也很容易:

fun getRouteCluster(): List<String> {
    val jsonStr = runCommand("istioctl pc route $POD_NAME --name $POD_PORT -o json").output.trim()
    return parseRouteClusterList(jsonStr)
}
private fun parseRouteClusterList(jsonStr: String): List<String> {
    val ret = mutableListOf<String>()
    val jVirtualService = (JSONArray(jsonStr).first() as JSONObject).getJSONArray("virtualHosts").filter {
        it as JSONObject
        it.getString("name") == "reviews.default.svc.cluster.local:9080"
    }.first() as JSONObject
    val jRoute = (jVirtualService.getJSONArray("routes").first() as JSONObject).getJSONObject("route")
    if (jRoute.has("cluster")) {
        ret.add(jRoute.getString("cluster"))
    } else if (jRoute.has("weightedClusters")) {
        ret.addAll(
            jRoute.getJSONObject("weightedClusters").getJSONArray("clusters").map {
                it as JSONObject
                it.getString("name")
            })
    }
    return ret
}   

看到这里你可能要说了,直接挂载并调用命令会产生不安全的情况,比如说命令被非法使用了,可能整个 K8S 体系都完蛋,所以接下去我们需要把相应的代码改成 SDK 调用的方式。

先来看如何获取当前的路由列表:

fun getRouteCluster(): List<String> {
    val istio: IstioClient = DefaultIstioClient()
    val list = istio.v1alpha3VirtualService().list().items
    return list.firstOrNull { it.spec.hosts[0] == "reviews" }?.spec?.http?.get(0)?.route?.map { it.destination.subset } ?: listOf()
}

然后再来看看如何新增路由,本质上新增路由的代码,就是将 yaml 内的代码改为用 SDK 方案来实现:

fun addRouteClusters() {
    val vs23 = VirtualServiceBuilder()
        .withApiVersion("networking.istio.io/v1alpha3")
        .withKind("VirtualService")
        .withNewMetadata()
            .withName("reviews")
            .withNamespace("default")
        .endMetadata()
        .withNewSpec()
            .withHosts("reviews")
            .addNewHttp()
            .addNewRoute()
                .withNewDestination()
                .withHost("reviews")
                .withSubset("v2")
                .endDestination()
                .withWeight(50)
            .endRoute()
            .addNewRoute()
                .withNewDestination()
                .withHost("reviews")
                .withSubset("v3")
                .endDestination()
                .withWeight(50)
            .endRoute()
            .endHttp()
        .endSpec()
    .build()
    val istio: IstioClient = DefaultIstioClient()
    istio.istio.v1alpha3VirtualService().create(vs23)
}

不得不说,写起来确实有些麻烦,不过好在编程语言是灵活的,如此封装一下就好了:

fun makeVirtualService(name: String, namespace: String = "default", host: String, subsets: List<Pair<String, Int>>): VirtualService {
    val http = VirtualServiceBuilder()
        .withApiVersion("networking.istio.io/v1alpha3")
        .withKind("VirtualService")
        .withNewMetadata()
        .withName(name)
        .withNamespace(namespace)
        .endMetadata()
        .withNewSpec()
        .withHosts(host)
        .addNewHttp()

    subsets.forEach { (subset, weight) ->
        http
            .addNewRoute()
            .withNewDestination()
            .withHost(host)
            .withSubset(subset)
            .endDestination()
            .withWeight(weight)
            .endRoute()

    }
    return http
        .endHttp()
        .endSpec()
        .build()
}

最后还有必要提的是,Istio SDK 对于依赖版本有相当高的要求,经过测试,发现依赖必须如下配置:

compile 'me.snowdrop:istio-client:1.7.5-Beta2'
compile 'io.fabric8:kubernetes-client:4.10.2'
compile 'com.squareup.okhttp3:okhttp:3.12.12'

以上三个依赖项的版本号必须是如此,才能正常通过编译并正常工作,对于一些库本身就依赖了 okhttp 等的,需要进行 exclude 操作,如:

compile ("com.github.isyscore:common-ktor:1.3.1") {
    exclude group: 'com.squareup.okhttp3', module: 'okhttp'
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容