美文网首页
极简istio wasmplugin go语言开发示例

极简istio wasmplugin go语言开发示例

作者: Hally_W | 来源:发表于2023-05-30 14:34 被阅读0次

前置条件

k8s集群,版本1.23.8

安装方法略

istio安装完成demo,版本1.17.2

curl -L https://git.io/getLatestIstio | ISTIO_VERSION=1.17.2 sh
cd istio-1.17.2/
./bin/istioctl install --set profile=demo -y
kubectl label namespace default istio-injection=enabled --overwrite

安装httpbin,sleep 示例

httpbin主要为了提供http响应,也可以用已有的能够提供http服务的任意服务。

sleep 为了在pod里使用curl命令,有别的pod中能执行curl命令也可以。

kubectl apply -f samples/httpbin/httpbin.yaml
kubectl apply -f samples/sleep/sleep.yaml

本地docker环境,版本 20.10.21

docker安装略

go语言环境,版本1.18.1

go安装略

阿里云或其他镜像仓库

类似部署一个普通应用,wasm插件来源于某个镜像仓库。

为了方便使用了阿里云的免费镜像仓库,也可以本地搭建或使用别的仓库。

注意需要将阿里云的仓库设置为公开!

示例功能

  • 在请求头增加自定义的返回信息

  • 将插件的配置信息放到头信息中返回(测试读取配置的功能)

  • 简单的限流功能

直接看效果

使用我编译好的镜像直接部署,保存以下内容为 gowasm.yaml

apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: my-go-wasm-plugin
  namespace: default
spec:
  selector:
    matchLabels:
      app: httpbin
  ## 编译好的镜像    
  url: oci://registry.cn-hangzhou.aliyuncs.com/hallywang/gowasm:20230530181612
  
  #插件的配置信息,在代码中可以获取到json string
  pluginConfig:
    testConfig: abcddeeeee
    listconfig:
     - abc
     - def

部署到k8s中

kubectl apply -f gowasm.yaml

示例验证方法

  • 执行以下命令,从sleep pod中发送http 请求到 httpbin ,打印出返回的header
SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name}

kubectl exec ${SLEEP_POD} -c sleep -- sh -c 'for i in $(seq 1 3); do curl --head -s httpbin:8000/headers; sleep 0.1; done'

  • 在未部署gowasm.yaml之前,每次请求都会返回200成功,且头信息中没有自定义的内容

  • 在部署了gowasm.yaml之后,返回如下结果(两次请求的结果),有自定义的头信息和403的返回说明插件部署成功。

    ## 第一次请求的返回结果:
    HTTP/1.1 200 OK
    server: envoy
    date: Wed, 31 May 2023 03:20:12 GMT
    content-type: application/json
    content-length: 526
    access-control-allow-origin: *
    access-control-allow-credentials: true
    x-envoy-upstream-service-time: 3
    ## 下面是插件增加的头信息
    who-am-i: wasm-extension
    injected-by: istio-api!
    hally: wang
    wang: 1234567
    configdata: {"listconfig":["abc","def"],"testConfig":"abcddeeeee"}
    
    
    ## 第二次请求的返回结果:
    ## 限流起作用,返回403
    HTTP/1.1 403 Forbidden
    powered-by: proxy-wasm-go-sdk!!
    content-length: 29
    content-type: text/plain
    who-am-i: wasm-extension
    injected-by: istio-api!
    hally: wang
    wang: 1234567
    configdata: {"listconfig":["abc","def"],"testConfig":"abcddeeeee"}
    date: Wed, 31 May 2023 03:20:12 GMT
    server: envoy
    x-envoy-upstream-service-time: 0
    
    

从代码开始

安装tinygo

tinygo可以将go编译成wasm文件

https://tinygo.org/getting-started/install/

创建go工程

mkdir go-wasm-plugin
cd go-wasm-plugin
go mod init go-wasm

新增文件main.go

package main

import (
    "time"

    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
    "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
)

func main() {
    proxywasm.SetVMContext(&vmContext{})
}

type vmContext struct {
    // Embed the default VM context here,
    // so that we don't need to reimplement all the methods.
    types.DefaultVMContext
}

// Override types.DefaultVMContext.
func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
    return &pluginContext{}
}

type pluginContext struct {
    // Embed the default plugin context here,
    // so that we don't need to reimplement all the methods.
    types.DefaultPluginContext
    // the remaining token for rate limiting, refreshed periodically.
    remainToken int
    // // the preconfigured request per second for rate limiting.
    // requestPerSecond int
    // NOTE(jianfeih): any concerns about the threading and mutex usage for tinygo wasm?
    // the last time the token is refilled with `requestPerSecond`.
    lastRefillNanoSec int64
}

// Override types.DefaultPluginContext.
func (p *pluginContext) NewHttpContext(contextID uint32) types.HttpContext {
    return &httpHeaders{contextID: contextID, pluginContext: p}
}

type httpHeaders struct {
    // Embed the default http context here,
    // so that we don't need to reimplement all the methods.
    types.DefaultHttpContext
    contextID     uint32
    pluginContext *pluginContext
}

// Additional headers supposed to be injected to response headers.
var additionalHeaders = map[string]string{
    "who-am-i":    "go-wasm-extension",
    "injected-by": "istio-api!",
    "hally":       "wang",
    "wang":        "1234567",
    // 定义自定义的header,每个返回中都添加以上header
}

// 读取部署yaml中的 pluginConfig 内容,用于插件的一些配置信息
var configData string

func (p *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus {
    proxywasm.LogDebug("loading plugin config")
    data, err := proxywasm.GetPluginConfiguration()
    if data == nil {
        return types.OnPluginStartStatusOK
    }

    if err != nil {
        proxywasm.LogCriticalf("error reading plugin configuration: %v", err)
        return types.OnPluginStartStatusFailed
    }

    // 插件启动的时候读取配置
    configData = string(data)

    return types.OnPluginStartStatusOK
}

func (ctx *httpHeaders) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action {
    //添加headr
    for key, value := range additionalHeaders {
        proxywasm.AddHttpResponseHeader(key, value)
    }

    //为了便于演示观察,将配置信息也加到返回头里
    proxywasm.AddHttpResponseHeader("configData", configData)
    return types.ActionContinue
}

// 实现限流
func (ctx *httpHeaders) OnHttpRequestHeaders(int, bool) types.Action {
    current := time.Now().UnixNano()
    // We use nanoseconds() rather than time.Second() because the proxy-wasm has the known limitation.
    // TODO(incfly): change to time.Second() once https://github.com/proxy-wasm/proxy-wasm-cpp-host/issues/199
    // is resolved and released.
    if current > ctx.pluginContext.lastRefillNanoSec+1e9 {
        ctx.pluginContext.remainToken = 2
        ctx.pluginContext.lastRefillNanoSec = current
    }
    proxywasm.LogCriticalf("Current time %v, last refill time %v, the remain token %v",
        current, ctx.pluginContext.lastRefillNanoSec, ctx.pluginContext.remainToken)
    if ctx.pluginContext.remainToken == 0 {
        if err := proxywasm.SendHttpResponse(403, [][2]string{
            {"powered-by", "proxy-wasm-go-sdk!!"},
        }, []byte("rate limited, wait and retry."), -1); err != nil {
            proxywasm.LogErrorf("failed to send local response: %v", err)
            proxywasm.ResumeHttpRequest()
        }
        return types.ActionPause
    }
    ctx.pluginContext.remainToken -= 1
    return types.ActionContinue
}

新增文件 Dockerfile

# Dockerfile for building "compat" variant of Wasm Image Specification.
# https://github.com/solo-io/wasm/blob/master/spec/spec-compat.md

FROM scratch

COPY main.wasm ./plugin.wasm

编译go代码为wasm

go get

tinygo build -o main.wasm -scheduler=none -target=wasi main.go

编译成功后,工程目录中将出现 main.wasm 文件

build一个Docker镜像,推送到镜像仓库

docker build . -t registy.cn-hangzhou.aliyuncs.com/USER_NAME/gowasm:0.1
docker push registy.cn-hangzhou.aliyuncs.com/USER_NAME/gowasm:0.1

新增部署yaml

gowasm.yaml

apiVersion: extensions.istio.io/v1alpha1
kind: WasmPlugin
metadata:
  name: my-go-wasm-plugin
  namespace: default
spec:
  selector:
    matchLabels:
      app: httpbin
  url: oci://registry.cn-hangzhou.aliyuncs.com/USER_NAME/gowasm:0.1
  pluginConfig:
    testConfig: abcddeeeee
    listconfig:
     - abc
     - def

部署到k8s,执行测试脚本

kubectl apply -f gowasm.yaml

SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name}

kubectl exec ${SLEEP_POD} -c sleep -- sh -c 'for i in $(seq 1 3); do curl --head -s httpbin:8000/headers; sleep 0.1; done'

## 观察返回内容

删除插件

kubectl delete wasmplugins my-go-wasm-plugin

遇到的问题

  • 修改代码重新发布部署后,如果镜像的tag没变化,可能出现不生效,这是因为wasmplugin有自己的缓存机制,tag版本发生变化,不会出现该问题

本文代码

https://github.com/hallywang/go-wasm-plugin-example.git

其他玩法

  • istio官方提供的使用webassemblyhub来打包发布,内容参考

https://istio.io/latest/zh/blog/2020/deploy-wasm-declarative/

但是发现webassemblyhub提供的工具不支持最新版本的istio。

参考资料

https://tetrate.io/blog/istio-wasm-extensions-and-ecosystem/

相关文章

  • 极简开发语言go语言

    go语言目前满足了我的几个期望:语言简单,可应用于多种场合;工具简单,容易开发部署;编译快速,运行快速,占用资源少...

  • 微服务构建示例(istio k8s grpc-go echo)

    传送门 istio-micro istio-micro 使用go-micro构建微服务示例请到一下仓库 go-e...

  • 01 Go极简教程 目录

    极简教程的初衷是给已有其他语言基础的人阅读尽可能少的内容学习Go语言 Go极简教程 目录 Go极简教程 环境安装及...

  • Istio微服务治理

    Istio概述 部署Istio Sidercar注入-部署httpbin Web示例 部署 访问流程图 Istio...

  • Go 语言程序设计(5)

    polar2cartesian.go 示例代码: 知识点: Go 语言的并发依赖的并不是线程,而是协程 Go 语言...

  • Istio 范例

    一、Istio / 入门 - 部署示例应用[https://istio.io/latest/zh/docs/s...

  • Go语言入坑

    GO语言基础 认识并安装GO语言开发环境 Go语言简介 Go语言是谷歌2009年发布的第二款开源编程语言 go语言...

  • Go 语言极速入门

    本系列文章主要是记录《Go 语言实战》和《Google 资深工程师深度讲解 Go 语言》的学习笔记。 Go 语言极...

  • Go语言并发

    Go语言并发 Go语言级别支持协程,叫做goroutine Go 语言从语言层面支持并发和并行的开发操作 Go并发...

  • Go语言的网络编程简介

    本文通过 Go 语言写几个简单的通信示例,从 TCP 服务器过渡到 HTTP 开发,从而简单介绍 net 包的运用...

网友评论

      本文标题:极简istio wasmplugin go语言开发示例

      本文链接:https://www.haomeiwen.com/subject/eqfeedtx.html