10

Apisix 1.5 升级到 2.2 踩坑备忘

 3 years ago
source link: http://www.blogjava.net/yongboy/archive/2021/02/23/435806.html
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.

线上运行的 APISIX 为 1.5 版本,而社区已经发布了 Apisix 2.2,是时候需要升级到最新版了,能够享受最版本带来的大量的BugFix,性能增强,以及新增特性的支持等~

从Apisix 1.5升级到Apisix 2.2过程中,不是一帆风顺的,中间踩了不少坑,所谓前车之鉴后事之师,这里给大家简单梳理一下我们团队所在具体业务环境下,升级过程中踩的若干坑,以及一些需要避免的若干注意事项等。

下文所说原先版本,皆指Apisix 1.5,新版则是Apisix 2.2版本。

一、已有服务发现机制无法正常工作

针对上游Upstream没有使用服务发现的路由来讲,本次升级没有遇到什么问题。

公司内部线上业务大都基于Consul KV方式实现服务注册和服务发现,因此我们自行实现了一个 consul_kv.lua 模块实现服务发现流程。

这在Apisix 1.5下面一切工作正常。

但在Apisix 2.2下面,就无法直接工作了,原因如下:

  • 服务发现配置指令变了
  • 上游对象包含服务发现时需增加字段 discovery_type 进行索引

2.1 服务发现配置指令变了

原先运行中仅支持一种服务发现机制,需要配置在 apisix层级下面:

apisix:
    ......
    discover: consul_kv
    ......    

新版需要直接在config*.yaml文件中顶层层级下进行配置,可支持多种不同的路由发现机制,如下:

discovery:                      # service discovery center
  eureka:
    host:                       # it's possible to define multiple eureka hosts addresses of the same eureka cluster.
      - "http://127.0.0.1:8761"
    prefix: "/eureka/"
    fetch_interval: 30          # default 30s
    weight: 100                 # default weight for node
    timeout:
      connect: 2000             # default 2000ms
      send: 2000                # default 2000ms
      read: 5000

我们有所变通,直接在配置文件顶层配置consul_kv多个集群相关参数,避免 discovery 层级过深。

 discovery:
    consul_kv: 1
consul_kv:
  servers:
    -
      host: "172.19.5.30"
      port: 8500
    -
      host: "172.19.5.31"
      port: 8500
  prefix: "upstreams"
  timeout:
    connect: 6000
    read: 6000
    wait: 60
  weight: 1
  delay: 5
  connect_type: "long" # long connect
  ......

当然,这仅仅保证了服务发现模块能够在启动时被正常加载。

推荐阅读:

2.2 upstream对象新增字段discovery_type

Apisix当前同时支持多种服务发现机制,这个很赞。对应的代价,就是需要额外引入 discovery_type 字段,用于索引可能同时存在的多个服务发现机制。

以 Cousul KV方式服务发现为例,那么需要在已有的 upstream 对象中需要添加该字段:

"discovery_type" : "consul_kv"

原先的一个upstream对象,仅仅需要 service_name 字段属性指定服务发现相关地址即可:

{
    "id": "d6c1d325-9003-4217-808d-249aaf52168e",
    "name": "grpc_upstream_hello",
    ......
    "service_name": "http://172.19.5.30:8500/v1/kv/upstreams/grpc/grpc_hello",
    "create_time": 1610437522,
    "desc": "demo grpc service",
    "type": "roundrobin"
}

而新版的则需要添加discovery_type字段,表明该service_name 字段对应的具体模块名称,效果如下:

{
    "id": "d6c1d325-9003-4217-808d-249aaf52168e",
    "name": "grpc_upstream_hello",
    ......
    "service_name": "http://172.19.5.30:8500/v1/kv/upstreams/grpc/grpc_hello",
    "create_time": 1610437522,
    "desc": "demo grpc service",
    "type": "roundrobin",
    "discovery_type":"consul_kv"
}

后面我们若支持Consul Service或ETCD KV方式服务发现机制,则会非常弹性和清晰。

调整了配置指令,添加上述字段之后,后端服务发现其实就已经起作用了。

但gRPC代理路由并不会生效……

二、gRPC当前不支持upstream_id

在我们的系统中,上游和路由是需要单独分开管理的,因此创建的HTTP或GRPC路由需要处理支持upstream_id的索引。

这在1.5版本中,grpc路由是没问题的,但到了apisix 2.2版本中,维护者 @spacewander 暂时没做支持,原因是规划grpc路由和dubbo路由处理逻辑趋于一致,更为紧凑。从维护角度我是认可的,但作为使用者来讲,这就有些不合理了,直接丢弃了针对以往数据的支持。

作为当前Geek一些方式,在 apisix/init.lua 中,最小成本 (优雅和成本成反比)修改如下,找到如下代码:

    -- todo: support upstream id
    api_ctx.matched_upstream = (route.dns_value and
                                route.dns_value.upstream)
                               or route.value.upstream 

直接替换为下面代码即可解决燃眉之急:

    local up_id = route.value.upstream_id
    if up_id then
        local upstreams = core.config.fetch_created_obj("/upstreams")
        if upstreams then
            local upstream = upstreams:get(tostring(up_id))
            if not upstream then
                core.log.error("failed to find upstream by id: " .. up_id)
                return core.response.exit(502)
            end
            if upstream.has_domain then
                local err
                upstream, err = lru_resolved_domain(upstream,
                                                    upstream.modifiedIndex,
                                                    parse_domain_in_up,
                                                    upstream)
                if err then
                    core.log.error("failed to get resolved upstream: ", err)
                    return core.response.exit(500)
                end
            end
            if upstream.value.pass_host then
                api_ctx.pass_host = upstream.value.pass_host
                api_ctx.upstream_host = upstream.value.upstream_host
            end
            core.log.info("parsed upstream: ", core.json.delay_encode(upstream))
            api_ctx.matched_upstream = upstream.dns_value or upstream.value
        end
    else
        api_ctx.matched_upstream = (route.dns_value and
                                route.dns_value.upstream)
                               or route.value.upstream  
    end

三、自定义auth插件需要微调

新版的apisix auth授权插件支持多个授权插件串行执行,这个功能也很赞,但此举导致了先前为具体业务定制的授权插件无法正常工作,这时需要微调一下。

原先调用方式:

    local consumers = core.lrucache.plugin(plugin_name, "consumers_key",
            consumer_conf.conf_version,
            create_consume_cache, consumer_conf)

因为新版的lrucache不再提供 plugin 函数,需要微调一下:

local lrucache = core.lrucache.new({
  type = "plugin",
})
......
    local consumers = lrucache("consumers_key", consumer_conf.conf_version,
        create_consume_cache, consumer_conf)

另一处是,顺利授权之后,需要赋值consumer相关信息:

    ctx.consumer = consumer
    ctx.consumer_id = consumer.consumer_id

此时需要替换成如下方式,为(可能存在的)后续的授权插件继续作用。

consumer_mod.attach_consumer(ctx, consumer, consumer_conf)

更多请参考:apisix/plugins/key-auth.lua 源码。

四、ETCD V2数据迁移到V3

迁移分为三步:

  1. 升级线上已有ETCD 3.3.*版本到3.4.*,满足新版Apisix的要求,这时ETCD实例同时支持了V2和V3格式数据
  2. 迁移V2数据到V3
    • 因为数据量不是非常多,我采取了一个非常简单和原始的方式
    • 使用 etcdctl 完成V2数据到导出
    • 然后使用文本编辑器vim等完成数据的替换,生成etcdctl v3格式的数据导入命令脚本
    • 运行之后V3数据导入脚本,完成V2到V3的数据导入
  3. 修改V3 /apisix/upstreams 中包含服务注册的数据,一一添加 "discovery_type" : "consul_kv"属性

基于以上操作之后,从而完成了ETCD V2到V3的数据迁移。

五、启动apisix后发现ETCD V3已有数据无法加载

我们在运维层面,使用 /usr/local/openresty/bin/openresty -p /usr/local/apisix -g daemon off; 方式运行网关程序。

这也就导致,自动忽略了官方提倡的:apisix start 命令自动提前为ETCD V3初始化的一些键值对内容。

因此,需要提前为ETCD V3建立以下键值对内容:

Key                         Value
/apisix/routes          :   init_dir
/apisix/upstreams       :   init_dir
/apisix/services        :   init_dir
/apisix/plugins         :   init_dir
/apisix/consumers       :   init_dir
/apisix/node_status     :   init_dir
/apisix/ssl             :   init_dir
/apisix/global_rules    :   init_dir
/apisix/stream_routes   :   init_dir
/apisix/proto           :   init_dir
/apisix/plugin_metadata :   init_dir

不提前建立的话,就会导致apisix重启后,无法正常加载ETCD中已有数据。

其实有一个补救措施,需要修改 apisix/init.lua 内容,找到如下代码:

            if not dir_res.nodes then
                dir_res.nodes = {}
            end

比较geek的行为,使用下面代码替换一下即可完成兼容:

                if dir_res.key then
                    dir_res.nodes = { clone_tab(dir_res) }
                else
                    dir_res.nodes = {}
                end

六、apisix-dashboard的支持

我们基于apisix-dashboard定制开发了大量的针对公司实际业务非常实用的企业级特性,但也导致了无法直接升级到最新版的apisix-dashboard。

因为非常基础的上游和路由没有发生多大改变,因此这部分升级的需求可以忽略。

实际上,只是在提交上游表单时,包含服务注册信息JSON字符串中需要增加 discovery_type 字段和对应值即可完成支持。

花费了一些时间完成了从Apisix 1.5升级到Apisix 2.2的行为,虽然有些坑,但整体来讲,还算顺利。目前已经上线并全量部署运行,目前运行良好。

针对还停留在Apisix 1.5的用户,新版增加了Control API以及多种服务发现等新特性支持,还是非常值得升级的。

升级之前,不妨仔细阅读每一个版本的升级日志(地址:https://github.com/apache/apisix/blob/2.2/CHANGELOG.md ),然后需要根据具体业务做好兼容测试准备和准备升级步骤,这些都是非常有必要的。

针对我们团队来讲,升级到最新版,一方面降低了版本升级的压力,另一方面也能够辅助我们能参与到开源社区中去,挺好~


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK