078-Lua远程调试(以Apisix,Redis为例)

1,505 阅读6分钟

这是坚持技术写作计划(含翻译)的第78篇,定个小目标999,每周最少2篇。

Lua 是一门轻量级类C高性能脚本语言,广泛用于 游戏领域,Redis 自定义脚本,Nginx系(openresty,kong,apisix等),wireshark 的脚本,wrk压测的脚本,路由器(比如 openwrt ), 物联网领域(比如 nodemcu,PlatformIO,OpenMQTTGateway,tasmota 等),游戏领域没有涉猎。其他列出的方面都多多少少接触过,写过 lua 代码。

lua 的语法教程不是本文的重点,可以网上搜索,比如 LUA简明教程

本文主要以 Apisix(Nginx系) 和 Redis 两个为例,讲解如何进行断点调试,而不是 Print 大法来逮bug。

安装 Redis

如果是 Mac/Linux 那可以去 redis.io/download/#r… 下载对应的系统,或者从源代码编译。

如果是 Windows ,并且是 Windows 10+,可以用 windows 子系统功能,安装 Linux 版本 Redis。或者在 Windows 上安装 Docker。

如果是 低版本 Windows。只能安装网上提供的低版本(好像是redis 3和4) 的非官方版本的Redis(最新GA是6.x预览版是7.x)

本文一律采用 Docker 方式进行演示。对于 Docker 基础知识不做介绍,不了解可以去网上搜索相关内容。

# docker run --name redis-6-demo -p6379:6379 -d redis:6-alpine redis-server

# docker ps 
CONTAINER ID   IMAGE            COMMAND                  CREATED          STATUS          PORTS                                       NAMES       
713c1863c29e   redis:6-alpine   "docker-entrypoint.s…"   53 seconds ago   Up 52 seconds   0.0.0.0:6379->6379/tcp, :::6379->6379/tcp   redis-6-demo

常用 Redis GUI 工具

Redis 官方也有调试工具 LDB,参考 Debugging Lua scripts in Redis

Redis 为啥要用 Lua

  • 减少网络开销。可以将多个请求通过脚本的形式一次发送,减少网络时延。
  • 原子操作。Redis会将整个脚本作为一个整体执行,中间不会被其他请求插入。因此在脚本运行过程中无需担心会出现竞态条件,无需使用事务。
  • 复用。客户端发送的脚本会永久存在redis中,这样其他客户端可以复用这一脚本,而不需要使用代码完成相同的逻辑。

Java 系的 Redis 库 redisson/redisson 大量用了 Lua ,可以参考,借鉴一下。

安装并配置 ZeroBrane Studio

下载 ZeroBrane Studio 到本地并解压。

下载 ZeroBranePackage/master/redis.lua 并保存到 /path/to/zerobranestudio/packages/ 目录下。

启动 ZB Studio -> Project -> Lua Interpreter -> Redis
image.png
创建 demo.lua

local key = KEYS[1]
local val = ARGV[1]

redis.call("set", key, val)

val = redis.call("get", key)

return val

使用 ZB Studio 调试 Redis Lua Scripts

image.png
Project -> Command Line Parameters... 添加参数,注意参数用英文,分隔,前后加空格,也就是 key空格,空格value

点击 Project -> Start Debugging 或者直接按快进键 F5 启动调试。
image.png
Step Into(F10) 没有函数时跟 Step Over(Shift + F10) 一样,都是执行下一步。区别是,遇到函数时,Step Into 会进入函数内部,Step Over 会将函数当成一步.

安装 Apisix

为了简化步骤,本文以 docker-compose 安装为例。
文件结构如下所示

.
├── config.yaml
├── docker-compose.yaml
└── plugins
    └── usr
        └── local
            └── apisix
                └── apisix
                    └── plugins
                        ├── example.lua
                        └── mobdebug.lua

下载 raw.githubusercontent.com/pkulchenko/… 保存到 ./plugins/usr/local/apisix/apisix/plugins文件夹下

config.yaml

apisix:
  node_listen: 9080              # APISIX listening port
  enable_ipv6: false

  allow_admin:                  # http://nginx.org/en/docs/http/ngx_http_access_module.html#allow
    - 0.0.0.0/0              # We need to restrict ip access rules for security. 0.0.0.0/0 is for test.

  admin_key:
    - name: "admin"
      key: edd1c9f034335f136f87ad84b625c8f1
      role: admin                 # admin: manage all configuration data
                                  # viewer: only can view configuration data
    - name: "viewer"
      key: 4054f7cf07e344346cd3f287985e76a2
      role: viewer

  enable_control: true
  control:
    ip: "0.0.0.0"
    port: 9092

etcd:
  host:                           # it's possible to define multiple etcd hosts addresses of the same etcd cluster.
    - "http://etcd:2379"     # multiple etcd address
  prefix: "/apisix"               # apisix configurations prefix
  timeout: 30                     # 30 seconds

plugins:
  - example

example.lua

local plugin_name = "example"
local core = require("apisix.core")
local mobdebug = require("apisix.plugins.mobdebug")


local schema = {
    type = "object",
    properties = {}
}

local _M = {
    version = 0.1,
    priority = 0,
    name = plugin_name,
    schema = schema
}

function _M.check_schema(conf)
    return core.schema.check(schema, conf)
end

function _M.access(conf, ctx)
    -- 把 192.168.xx.xxx 换成 idea 运行主机 ip
    mobdebug.start("192.168.xx.xxx", 8172)
    core.log.warn(core.json.encode(conf))
end

return _M

注意更换 192.168.xx.xxx为实际ip
更多 Apisix 插件开发信息,详见 插件开发

docker-compose.yaml

version: "3"

services:
  apisix:
    image: apache/apisix:2.15.0-alpine
    restart: always
    volumes:
      - ./config.yaml:/usr/local/apisix/conf/config.yaml:ro
      - ./plugins/usr/local/apisix/apisix/plugins/example.lua:/usr/local/apisix/apisix/plugins/example.lua:ro
      - ./plugins/usr/local/apisix/apisix/plugins/mobdebug.lua:/usr/local/apisix/apisix/plugins/mobdebug.lua:ro
    depends_on:
      - etcd
    ports:
      - "9080:9080/tcp"
      - "9091:9091/tcp"
      - "9443:9443/tcp"
      - "9092:9092/tcp"
    networks:
      apisix:

  etcd:
    image: bitnami/etcd:3.4.9
    restart: always
    environment:
      ETCD_ENABLE_V2: "true"
      ALLOW_NONE_AUTHENTICATION: "yes"
      ETCD_ADVERTISE_CLIENT_URLS: "http://0.0.0.0:2379"
      ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379"
    ports:
      - "2379:2379/tcp"
    networks:
      apisix:

  web1:
    image: nginx:1.18.0-alpine
    restart: always
    ports:
      - "9081:80/tcp"
    environment:
      - NGINX_PORT=80
    networks:
      apisix:

networks:
  apisix:
    driver: bridge

  • 运行 docker-compose up -d
  • 查看 apisix 日志docker-compose logs -f apisix
  • 重启 apisixdocker-compose restart apisix
  • 只重新加载配置或者 lua 文件,不重启 apisix docker-compose exec apisix apisix reload
# 新增路由
curl --location --request PUT 'http://127.0.0.1:9080/apisix/admin/routes/1' \
--header 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
--header 'Content-Type: application/json' \
--data-raw '{
    "methods": ["GET"],
    "uri": "/index.html",
    "id": 1,
    "plugins": {
        "example": {
        }
    },
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "web1:80": 1
        }
    }
}'
# 访问路由,测试断点是否生效
curl --location --request GET 'http://127.0.0.1:9080/index.html' \
--header 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
--header 'Content-Type: application/json' 

通过 EmmyLua 调试 Apisix 插件

右键 plugins 文件夹,将目录标记为 源代码根目录
将要加的断点加上
在他之前加上 mobdebug.start("192.168.xx.xxx", 8172)
访问路由,正常情况下,就会进断点。
image.png
image.png
注意:

  • idea 里将某个文件夹设置成源代码根目录后,其子目录的lua文件要与被调试的环境下的lua文件的路径完全一致。比如 docker 里,apisix 插件目录的绝对路径是 /usr/local/apisix/apisix/plugins,那就要求 plugins 目录下对应创建这个层级关系才行。不然 mobdebug 不走断点。
  • 调试时 mobdebug.start(ip,端口)其实是连接 emmylua 插件,或者 ZB Studio 的 DebugServer 开放的端口,此处要保持一致。可以在 apisix 所在主机用 telnet ip 端口 试试能否通信。
  • 执行了 restart apisix 或者 apisix reload 后,client 端连接断开了,但是 idea 端还没释放,再次访问会报错,所以需要重新点击调试按钮。
  • 不建议生产环境下用 mobdebug 进行调试。

其他

此种方法也可以用 ZeroBrane Studio 作为 debug server 端,进行调试,而不用 emmylua 插件。 可以参考 lua学习笔记(4)-- 搭建mobdebug 远程开发环境

如果是在本机调试(不是本机跑docker),也可以试试 EmmyLua 的 Attach Debug 附加调试

如果是本机调试 Apisix 自身代码,可以参考 APISIX Runtime Debug/动态调试 写的挺详细了,不再赘述。

结合 077-加快云原生应用开发速度(Nocalhost篇) 是可以调试通过 helm 安装到 k8s 的 apisix 的。具体原理上篇已经写的挺详细了,不再赘述。

招聘小广告

山东济南的小伙伴欢迎投简历啊 加入我们 , 一起搞事情。
长期招聘,Java程序员,大数据工程师,运维工程师,前端工程师。

参考资料