• 为什么我们的域名不能被解析
  • 搭配 resolver 指令
  • 进阶玩法
  • 高阶玩法
  • 最终的演示例子如下
  • 如何使用 /etc/hosts 自定义域名

    为什么我们的域名不能被解析

    最近经常有朋友在使用一个域名地址时发现无法被正确解析

    比如在使用 Mysql 实例时某些云会给一个私有的域名搭配自有的 nameserver 使用

    1. local client = mysql:new()
    2. client:connect({
    3. host = "rdsmxxxxxx.mysql.rds.xxxx.com",
    4. port = 3306,
    5. database = "test",
    6. user = "test",
    7. password = "123456"
    8. })

    以上代码在直接使用时往往会报一个无法解析的错误。那么怎么在 openresty 中使用域名呢

    搭配 resolver 指令

    我们可以直接在 nginx 的配置文件中使用 resolver 指令直接设置使用的 nameserver 地址。

    官方文档中是这么描述的

    1. Syntax: resolver address ... [valid=time] [ipv6=on|off];
    2. Default:
    3. Context: http, server, location

    一个简单的例子

    1. resolver 8.8.8.8 114.114.114.114 valid=3600s;

    不过这样的问题在于 nameserver 被写死在配置文件中,如果使用场景比较复杂或有内部 dns 服务时维护比较麻烦。

    进阶玩法

    我们的代码常常运行在各种云上,为了减少维护成本,我采用了动态读取本机 /etc/resolv.conf 的方法来做。

    废话不说,让我们一睹为快。

    1. local pcall = pcall
    2. local io_open = io.open
    3. local ngx_re_gmatch = ngx.re.gmatch
    4. local ok, new_tab = pcall(require, "table.new")
    5. if not ok then
    6. new_tab = function (narr, nrec) return {} end
    7. end
    8. local _dns_servers = new_tab(5, 0)
    9. local _read_file_data = function(path)
    10. local f, err = io_open(path, 'r')
    11. if not f or err then
    12. return nil, err
    13. end
    14. local data = f:read('*all')
    15. f:close()
    16. return data, nil
    17. end
    18. local _read_dns_servers_from_resolv_file = function()
    19. local text = _read_file_data('/etc/resolv.conf')
    20. local captures, it, err
    21. it, err = ngx_re_gmatch(text, [[^nameserver\s+(\d+?\.\d+?\.\d+?\.\d+$)]], "jomi")
    22. for captures, err in it do
    23. if not err then
    24. _dns_servers[#_dns_servers + 1] = captures[1]
    25. end
    26. end
    27. end
    28. _read_dns_servers_from_resolv_file()

    通过上述代码我们成功动态拿到了一组 nameserver 的地址,下面就可以通过 resty.dns.resolver 大杀四方了

    1. local require = require
    2. local ngx_re_find = ngx.re.find
    3. local lrucache = require "resty.lrucache"
    4. local resolver = require "resty.dns.resolver"
    5. local cache_storage = lrucache.new(200)
    6. local _is_addr = function(hostname)
    7. return ngx_re_find(hostname, [[\d+?\.\d+?\.\d+?\.\d+$]], "jo")
    8. end
    9. local _get_addr = function(hostname)
    10. if _is_addr(hostname) then
    11. return hostname, hostname
    12. end
    13. local addr = cache_storage:get(hostname)
    14. if addr then
    15. return addr, hostname
    16. end
    17. local r, err = resolver:new({
    18. nameservers = _dns_servers,
    19. retrans = 5, -- 5 retransmissions on receive timeout
    20. timeout = 2000, -- 2 sec
    21. })
    22. if not r then
    23. return nil, hostname
    24. end
    25. local answers, err = r:query(hostname, {qtype = r.TYPE_A})
    26. if not answers or answers.errcode then
    27. return nil, hostname
    28. end
    29. for i, ans in ipairs(answers) do
    30. if ans.address then
    31. cache_storage:set(hostname, ans.address, 300)
    32. return ans.address, hostname
    33. end
    34. end
    35. return nil, hostname
    36. end
    37. ngx.say(_get_addr("www.baidu.com"))
    38. ngx.say(_get_addr("192.168.0.1"))

    我这边把解析的结果放入了 lrucache 缓存了 5 分钟,你们同样可以把结果放入 shared 中来减少 worker 查询次数。

    高阶玩法

    现在我们已经实现了自缓存体系的 dns 查询,如果搭配 resty.http 就会达到更好的效果。

    一个简单的例子是,通过解析 uri 得到 hostnameportpath,把 hostname 扔给自缓存 dns 获取结果

    发起 request 请求,addr + port connect 之,设置 headerhosthostnamepath 等值来实现 ip 直接访问等高阶用法。

    这里就不过多的阐述了。

    最终的演示例子如下

    1. local client = mysql:new()
    2. client:connect({
    3. host = _get_addr(conf.mysql_hostname),
    4. port = 3306,
    5. database = "test",
    6. user = "test",
    7. password = "123456"
    8. })

    如何使用 /etc/hosts 自定义域名

    还有些同学可能会在 hosts 文件中自定义域名和 ip,这时候 resolve 是无法正常解析的。

    这个时候可以借助 dnsmasq 这个服务来缓存我们的 dns 结果,hosts 的定义可以被该服务识别。

    需要在 nginx 的配置文件中,设置 resolverdnsmasq 服务的监听地址即可。

    See:
    http://hambut.com/2016/09/09/how-to-resolve-the-domain-name-in-openresty/