极简istio wasmplugin go语言开发示例

2023-05-30  本文已影响0人  Hally_W

前置条件

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=$(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'

从代码开始

安装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

遇到的问题

本文代码

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

其他玩法

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

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

参考资料

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

上一篇 下一篇

猜你喜欢

热点阅读