5

迈向istio-13 自定义adapter(修改请求头)

 2 years ago
source link: https://tangxusc.github.io/2019/05/%E8%BF%88%E5%90%91istio-13-%E8%87%AA%E5%AE%9A%E4%B9%89adapter%E4%BF%AE%E6%94%B9%E8%AF%B7%E6%B1%82%E5%A4%B4/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

迈向istio-13 自定义adapter(修改请求头)

May 28, 2019

in istio

在istio中mixer组件负责策略控制和遥测收集数据,是高度模块化和可扩展的组件.

mixer处理不同基础设施后端的灵活性是通过适配器模型插件来实现的,每个插件都被成为Adapter,用户通过配置使用Adapter向mixer注册自身,并设置适配规则,绑定模板,mixer通过和每个插件进行grpc连接,对策略和遥测进行操作

在istio的整体设计中,流量管理和mixer应该是我们交互最多的组件了.

流量管理我们侧重使用配置文件对我们的服务流量进行分流管理,而mixer的扩展我们更多的就需要写代码进行扩展了.

在isito1.1.x版本中mixer的adapter已经能修改请求的header了,这给我们带来了太多的便宜.

本文我们将在istio1.1.x版本中实现一个自定义的adapter,使用自定义的template,并在请求的header中注入user,serviceName等信息.

注意: 在1.1.x版本中使用mixer的check功能,需要启用check功能,详见 升级到1.1.2安装

自定义adapter

1.准备 基础环境

2.克隆istio源代码并编译

$ mkdir -p $GOPATH/src/istio.io/ && cd $GOPATH/src/istio.io/  && git clone https://github.com/istio/istio
## 编译istio
$ cd $GOPATH/src/istio.io/istio && go build ./...

编译主要是为了生成出mixs服务端和mixc客户端,在我们测试时将会使用到.

3.定义模板

在istio的源码目录中创建文件authservice,并在其中生成我们的模板文件:

$ mkdir authservice 

在文件夹中创建文件template.proto,内容如下:

syntax = "proto3";

//注意,和文件夹名称一致
package authservice;

import "mixer/adapter/model/v1beta1/extensions.proto";
//声明 模板种类
option (istio.mixer.adapter.model.v1beta1.template_variety) = TEMPLATE_VARIETY_CHECK_WITH_OUTPUT;
//声明 输入参数
message Template {
  string request_jwt = 1;
  string request_path = 2;
  string request_method = 3;
  string source_name= 4;
  string source_namespace= 5;
  string destination_name= 6;
  string destination_namespace= 7;
  string source_workload_name= 8;
  string source_workload_namespace= 9;
  string destination_workload_name= 10;
  string destination_workload_namespace= 11;
  string destination_service_host= 12;
}
//声明输出参数
message OutputTemplate {
  string user = 1;
  string service_name= 2;
}

在文件中我们声明了模板template_variety,可选值为Check,Report,Quota,AttributeGenerator等.

模板的template_variety决定了适配器必须实现的方法签名,并且决定了mixer的整体行为.

在文件中我们定义了TemplateOutputTemplate对象,并分别设置了我们关注的字段(输入和输出)

整体的格式可以参照 istio的mixer测试实例

模板原型文件的具体格式可以在 模板原型文件 中看到

在定义了模板之后,我们需要将模板生成为go文件,命令如下:

#在istio的根目录执行
$ bin/mixer_codegen.sh -t authservice/template.proto
no comment found for authservice
authservice/template.proto:19:0: no comment found for OutputTemplate
#省略很多...
authservice/template.proto:16:2: no comment found for destination_service_host
rm:是否删除有写保护的普通文件 '/istio.io/istio/authservice/template.pb.go'?n
#此时目录下的文件
$ tree
.
├── authservice.pb.html
├── template_handler.gen.go
├── template_handler_service.descriptor_set
├── template_handler_service.pb.go
├── template_handler_service.proto
├── template.pb.go
├── template.proto
├── template_proto.descriptor_set
└── template.yaml  #模板文件,描述istio的模板配置

其中的警告部分输出为提示我们为proto添加注释,便于生成文档

grpc真香.

4.生成adapter配置

authservice文件夹中创建子目录config

$ cd authservice && mkdir config && cd config

在目录内创建文件config.proto,内容如下:

syntax = "proto3";
import "google/protobuf/duration.proto";
import "gogoproto/gogo.proto";
package config;
message Params {
  google.protobuf.Duration valid_duration = 1 [(gogoproto.nullable)=false, (gogoproto.stdduration) = true];
}

然后通过命令生成适配器的定义:

$ cd .. && bin/mixer_codegen.sh -a authservice/config/config.proto -x "-s=false -n authservice -t authservice"
no comment found for config
authservice/config/config.proto:4:0: no comment found for Params
authservice/config/config.proto:5:2: no comment found for valid_duration
#此时目录下的文件
$tree
.
├── authservice.pb.html
├── config
│   ├── authservice.yaml  #此文件为istio的adapter描述文件
│   ├── config.pb.go
│   ├── config.pb.html
│   ├── config.proto
│   └── config.proto_descriptor
├── template_handler.gen.go
├── template_handler_service.descriptor_set
├── template_handler_service.pb.go
├── template_handler_service.proto
├── template.pb.go
├── template.proto
├── template_proto.descriptor_set
└── template.yaml

-n: 适配器名称

-t: 模板名称

-s: 基于无会话的模型

传递这三个参数就是指定 输出的模板文件中的适配器名称,模板名称

到此处,templateadapter的描述文件就生成出来了,handler需要实现的接口也生成为了go文件

5.实现adapter

gopath外建立go项目,并初始化依赖:

$ mkdir authService-1.1 && cd authService-1.1 && go mod init authService

istio/authservice 拷贝至authService-1.1目录下

$ cp -R istio/authservice authService-1.1/

authService-1.1目录下建立main.go,内容如下:

package main

import (
	"authService/authservice"
	"encoding/json"
	"fmt"
	"github.com/gogo/googleapis/google/rpc"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"istio.io/api/mixer/adapter/model/v1beta1"
	"istio.io/istio/mixer/pkg/status"
	"log"
	"net"
)

type AuthAdapter struct {
}

func (*AuthAdapter) HandleAuthservice(ctx context.Context, request *authservice.HandleAuthserviceRequest) (*authservice.HandleAuthserviceResponse, error) {
	bytes, e := json.Marshal(request)
	if e != nil {
		return nil, e
	}
	fmt.Println("===========请求开始=============")
	fmt.Printf("%s \n", string(bytes))
	fmt.Println("===========请求结束=============")

    //此处模拟认证过程,实际认证需要自行处理
    //当传入了jwt就认为认证通过.
    //返回user:user1,ServiceName:serviceName1 作为模拟数据
	if len(request.Instance.RequestJwt) > 0 {
		return &authservice.HandleAuthserviceResponse{
			Result: &v1beta1.CheckResult{
				Status: status.OK,
			},
			Output: &authservice.OutputMsg{
				User:        "user1",
				ServiceName: "serviceName1",
			},
		}, nil
	} else {
		return &authservice.HandleAuthserviceResponse{
			Result: &v1beta1.CheckResult{
				Status: rpc.Status{Code: int32(rpc.PERMISSION_DENIED)},
			},
			Output: &authservice.OutputMsg{
				User:        "User_PERMISSION_DENIED",
				ServiceName: "ServiceName_PERMISSION_DENIED",
			},
		}, nil
	}

}

func main() {
	//创建grpc服务器
	server := grpc.NewServer()
	auth := &AuthAdapter{}
	//注册服务到grpc
	authservice.RegisterHandleAuthserviceServiceServer(server, auth)
	//启动9999端口监听tcp数据
	listener, e := net.Listen("tcp", fmt.Sprintf(":%s", "9999"))
	if e != nil {
		log.Fatal(fmt.Sprintln("tcp监听错误,%s", e.Error()))
	}
	//启动
	if e := server.Serve(listener); e != nil {
		log.Fatal(fmt.Sprintln("grpc启动错误,%s", e.Error()))
	}
}

6.测试[可选]

1, 编译mixs和mixc (如果前面已经编译,则此处不用再编译)

cd $GOPATH/src/istio.io/istio/ && make mixs && make mixc

2, 创建测试文件夹及文件

mkdir 项目根目录/testdata

新建文件项目根目录/testdata/istio.yaml,内容如下:

# handler adapter
apiVersion: "config.istio.io/v1alpha2"
kind: handler
metadata:
  name: my
  namespace: istio-system
spec:
  adapter: authservice
  connection:
    # 请一定注意,这里的地址  和正式部署的地址不一样
    address: "localhost:9999"
  params:
    valid_duration: 1h
---
# instances
apiVersion: "config.istio.io/v1alpha2"
kind: instance
metadata:
  name: my
  namespace: istio-system
spec:
  template: authservice
  params:
    request_jwt: request.headers["jwt"] | ""
    request_path: request.path | "/"
    request_method: request.method | "GET"
    source_name: source.name | ""
    source_namespace: source.namespace | ""
    destination_name: destination.name | ""
    destination_namespace: destination.namespace | ""
    source_workload_name: source.workload.name | ""
    source_workload_namespace: source.workload.namespace | ""
    destination_workload_name: destination.workload.name | ""
    destination_workload_namespace: destination.workload.namespace | ""
    destination_service_host: destination.service.host | ""

---
# rule to dispatch to handler h1
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: r1
  namespace: istio-system
spec:
  match: ""
  actions:
  - handler: my.handler.istio-system
    instances:
      - my.instance.istio-system
    name: result
  requestHeaderOperations:
  - name: user
    values:
    - result.output.user
  - name: serviceName
    values:
    - result.output.service_name

拷贝其他文件到testdata:

cp $GOPATH/src/istio.io/istio/mixer/testdata/config/attributes.yaml 项目根目录/testdata/
cp $GOPATH/src/istio.io/istio/authservice/template.yaml 项目根目录/testdata/
cp $GOPATH/src/istio.io/istio/authservicetest/config/authservice.yaml 项目根目录/testdata/
#拷贝完成后目录是这样的:
/testdata$ tree
├── attributes.yaml
├── authservice.yaml
├── istio.yaml
└── template.yaml

3, 启动adapter

# 在此,我们启动grpc服务器,并暴露在`9999`端口上
$ go run main.go

4, 启动mixs

./mixs server --configStoreURL=fs:///项目路径/testdata/ --log_output_level=attributes:debug

注意: 如果出现未找到mixs等错误,请配置你的Path,$GOPATH/out/linux_amd64/release/必须包含在path中,才能找到mixs和mixc的执行文件

注意: fs:// 标示文件系统,后面是文件系统的路径,所以才会出现三个/

5, 启动mixc

./mixc check -s destination.service="svc.cluster.local" -s request.path="/test222"

在执行了以上命令后就可以在控制台和adapter上均可以看到结果了

7,编译&制作镜像

$ go build main.go

编译后将生成main这个可执行文件

2,制作为镜像

在目录下创建Dockerfile文件,文件内容如下:

FROM ubuntu
WORKDIR /
ADD main /main
EXPOSE 9999
CMD ["./main"]

制作docker镜像命令如下:

$ docker build -t authservice:v1 .

8,部署到k8s集群中

1,部署k8s编排

新建文件k8s.yaml,内容如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my
  namespace: istio-system
  labels:
    app: my
spec:
  replicas: 1
  template:
    metadata:
      name: my
      labels:
        app: my
    spec:
      containers:
        - name: my
          # 使用上一步构建的镜像
          image: authservice:v1
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 9999
              protocol: TCP
      restartPolicy: Always
  selector:
    matchLabels:
      app: my
---
apiVersion: v1
kind: Service
metadata:
  name: authservice
  namespace: istio-system
spec:
  selector:
    app: my
  ports:
  - port: 9999
    targetPort: 9999
    protocol: TCP

使用kubectl部署Deployment:

$ kubectl apply -f k8s.yaml

2,部署istio配置

新建istio.yaml,内容如下:

# handler adapter
apiVersion: "config.istio.io/v1alpha2"
kind: handler
metadata:
  name: my
  namespace: istio-system
spec:
  adapter: authservice
  connection:
  # 请一定注意,这里的地址  和正式部署的地址不一样
    address: "authservice.istio-system:9999"
#    address: "localhost:9999"
# params的值为 config.proto中的配置
  params:
    valid_duration: 1h
---
# instances
apiVersion: "config.istio.io/v1alpha2"
kind: instance
metadata:
  name: my
  namespace: istio-system
spec:
  #模板的名称一定要和template.proto对应
  template: authservice
  #模板的参数需要对应
  params:
    request_jwt: request.headers["jwt"] | ""
    request_path: request.path | "/"
    request_method: request.method | "GET"
    source_name: source.name | ""
    source_namespace: source.namespace | ""
    destination_name: destination.name | ""
    destination_namespace: destination.namespace | ""
    source_workload_name: source.workload.name | ""
    source_workload_namespace: source.workload.namespace | ""
    destination_workload_name: destination.workload.name | ""
    destination_workload_namespace: destination.workload.namespace | ""
    destination_service_host: destination.service.host | ""

---
# rule to dispatch to handler h1
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
  name: r1
  namespace: istio-system
spec:
  match: ""
  actions:
  - handler: my.handler.istio-system
    instances:
      - my.instance.istio-system
    name: result
  #此属性用来配置header
  requestHeaderOperations:
  - name: user
    values:
    - result.output.user
  - name: serviceName
    values:
    - result.output.service_name

requestHeaderOperations的具体配置,请参照 requestHeaderOperations

然后将template,adapter和istio配置文件依次部署到k8s中.

$ kubectl apply -f authservice/template.yaml
$ kubectl apply -f authservice/config/authservice.yaml
$ kubectl apply -f istio.yaml

至此,完成了整体的自定义adapter的使用.

官方的测试adapter 使用示例如下

https://istio.io/docs/tasks/policy-enforcement/control-headers/

在定义mixer的模板中 很多人都会问,这template_variety的值在哪里定义的呢?

答: 在定义模板的时候文件中有这么一句:

import "mixer/adapter/model/v1beta1/extensions.proto";

此处声明了导入mixer/adapter/model/v1beta1/extensions.proto文件,但是这个文件是一个相对的位置,并且使用protoc的--proto_path参数指定上下文.

bin/mixer_codegen.sh文件中(源代码):

IMPORTS=(
  "--proto_path=${ROOTDIR}"
  "--proto_path=${ROOTDIR}/vendor/istio.io/api"
  "--proto_path=${ROOTDIR}/vendor/github.com/gogo/protobuf"
  "--proto_path=${ROOTDIR}/vendor/github.com/gogo/googleapis"
  "--proto_path=$optimport"
)

--proto_path: 指定了要去哪个目录中搜索import中导入的和要编译为.go的proto文件,可以定义多个

所以实际的extensions.proto文件位置为:istio.io/istio/vendor/istio.io/api/mixer/adapter/model/v1beta1/extensions.proto,在这个文件中对于TemplateVariety定义如下(源代码):


// The available varieties of templates, controlling the semantics of what an adapter does with each instance.
enum TemplateVariety {
    // Makes the template applicable for Mixer's check calls. Instances of such template are created during
    // check calls in Mixer and passed to the handlers based on the rule configurations.
    TEMPLATE_VARIETY_CHECK = 0;
    // Makes the template applicable for Mixer's report calls. Instances of such template are created during
    // report calls in Mixer and passed to the handlers based on the rule configurations.
    TEMPLATE_VARIETY_REPORT = 1;
    // Makes the template applicable for Mixer's quota calls. Instances of such template are created during
    // quota check calls in Mixer and passed to the handlers based on the rule configurations.
    TEMPLATE_VARIETY_QUOTA = 2;
    // Makes the template applicable for Mixer's attribute generation phase. Instances of such template are created during
    // pre-processing attribute generation phase and passed to the handlers based on the rule configurations.
    TEMPLATE_VARIETY_ATTRIBUTE_GENERATOR = 3;
    // Makes the template applicable for Mixer's check calls. Instances of such template are created during
    // check calls in Mixer and passed to the handlers that produce values.
    TEMPLATE_VARIETY_CHECK_WITH_OUTPUT = 4;
}

生成template出现没找到镜像

Unable to find image 'gcr.io/istio-testing/protoc:xxxx' locally

答: mixer_codegen.sh通过docker方式工作,依赖gcr.io/istio-testing/protoc,所以需要拉取:

$ docker pull tangxusc/istio-protoc-mirror:latest
$ docker tag tangxusc/istio-protoc-mirror:latest gcr.io/istio-testing/protoc:2018-06-12

在使用mixer的适配器的时候,同时我们也需要注意到,mixer本身是无状态的,带有缓存的,所以这也同时要求我们的adapter需要最好设计为无状态的,带有缓存的,通过这两种缓存机制才能大大降低istio的qps延迟.

mixer的性能问题一直是社区中纠结的所在,并且在1.1中暂时关闭了mixer的check功能,希望在1.2中有所改善吧.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK