Eisen's Blog

© 2023. All rights reserved.

unbound 的安装与基础应用

2022 December-06

了解到 unbound 可以用于做本地的 recursive dns server 同时也能支持本地的域名解析,打算用这个东西给内网做域名解析。而用 unbound 有这么两个好处:

  1. 使用 recursive dns server 可以避免把请求都发给上级的缓存服务器,很大程度上保证了个人隐私,也相对会更安全,当然搭配 pi-hole 这样的东西使用效果更佳 https://docs.pi-hole.net/guides/dns/unbound/
  2. unbound 除了做 recursive dns server 外也能接管内网域名解析,作为一个 authoritative server 使用(当然,它并不是专门做这个的,但是规模比较小的网络是没问题的)

unbound 的安装

以下是在 ubuntu 20.04 的安装流程:

sudo apt update && sudo apt install unbound -y

基础配置

先来一个简单的配置:

server:
    # can be uncommented if you do not need user privilige protection
    # username: ""

    # can be uncommented if you do not need file access protection
    # chroot: ""

    # location of the trust anchor file that enables DNSSEC. note that
    # the location of this file can be elsewhere
    auto-trust-anchor-file: "/usr/local/etc/unbound/root.key"
    # auto-trust-anchor-file: "/var/lib/unbound/root.key"

    # send minimal amount of information to upstream servers to enhance privacy
    qname-minimisation: yes

    # specify the interface to answer queries from by ip-address.
    interface: 0.0.0.0
    # interface: ::0

    # addresses from the IP range that are allowed to connect to the resolver
    access-control: 192.168.0.0/16 allow
    # access-control: 2001:DB8/64 allow

把它放到 /etc/unbound/unbound.conf.d/myunbound.conf 这里,然后 systemctl restart unbound 重启服务。

解决 53 端口冲突的问题

不出意外的话,重启 unbound 服务会报错,大概的报错信息是说 53 端口已经被占用了。这个时候可以通过 netstat -tulpn 来查看端口占用情况,发现是 systemd-resolved 占用了 53 端口,简单搜索会找到 https://unix.stackexchange.com/questions/304050/how-to-avoid-conflicts-between-dnsmasq-and-systemd-resolved 这么一个问题。按照其中内容修改 /etc/systemd/resolved.conf 设置 DNSStubListener=no 并重启 systemd-resolved 服务就可以了。

测试 unbound 是否工作

$ dig openbayes.com @127.0.0.1

; <<>> DiG 9.16.1-Ubuntu <<>> openbayes.com @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 52191
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;openbayes.com.			IN	A

;; ANSWER SECTION:
openbayes.com.		600	IN	A	106.75.109.110

;; Query time: 1524 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sun Dec 04 14:59:32 CST 2022
;; MSG SIZE  rcvd: 58

可以看到第一次很慢,但是第二次由于已经有了缓存,速度会快起来:

$ dig openbayes.com @127.0.0.1

; <<>> DiG 9.16.1-Ubuntu <<>> openbayes.com @127.0.0.1
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26243
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;openbayes.com.			IN	A

;; ANSWER SECTION:
openbayes.com.		535	IN	A	106.75.109.110

;; Query time: 0 msec
;; SERVER: 127.0.0.1#53(127.0.0.1)
;; WHEN: Sun Dec 04 15:00:37 CST 2022
;; MSG SIZE  rcvd: 58

让 unbound 接管本机的域名解析

上面的 dig 命令需要主动选择 @127.0.0.1 作为域名解析的服务。我们当然是希望默认就使用 unbound 来做域名解析。这里我参考的 unbound 文旦 https://unbound.docs.nlnetlabs.nl/en/latest/use-cases/home-resolver.html#setting-up-for-a-single-machine 进行配置。

首先继续修改 /etc/systemd/resolved.conf:

[Resolve]
DNS=127.0.0.1
#FallbackDNS=
#Domains=
DNSSEC=yes
#DNSOverTLS=no
#MulticastDNS=no
#LLMNR=no
#Cache=no-negative
DNSStubListener=no
#DNSStubListenerExtra=

然后强制更新下 /etc/resolv.conf:

ln -fs /run/systemd/resolve/resolv.conf /etc/resolv.conf

最后重启 systemd-resolved 服务:

systemctl restart systemd-resolved

执行 dig 的时候,就默认使用 127.0.0.1#53 了呢。

到此为止,unbound 的基本配置就完成了。

添加 local-zone

最后就是利用 unbound 所提供的 local-zone 配置实现内网域名解析了:

server:
    # can be uncommented if you do not need user privilige protection
    # username: ""

    # can be uncommented if you do not need file access protection
    # chroot: ""

    # location of the trust anchor file that enables DNSSEC. note that
    # the location of this file can be elsewhere
    # auto-trust-anchor-file: "/usr/local/etc/unbound/root.key"
    # auto-trust-anchor-file: "/var/lib/unbound/root.key"

    # send minimal amount of information to upstream servers to enhance privacy
    qname-minimisation: yes

    # specify the interface to answer queries from by ip-address.
    interface: 0.0.0.0
    # interface: ::0

    # addresses from the IP range that are allowed to connect to the resolver
    access-control: 192.168.0.0/16 allow
    access-control: 10.23.0.0/16 allow
    # access-control: 2001:DB8/64 allow

    local-zone: "home.lan." static
    local-data: "abc.home.lan. A 127.0.0.1"
    local-data: "bbc.home.lan. A 127.0.0.1"

dig abc.home.lan 就发现域名指向了 127.0.0.1 了。


记一次 k8s gpu 集群中半精度性能差异引发的系统调优

2022 November-17

最近发现跑 pytorch gpu benchmark 的时候,AMD epyc cpu 下的 rtx 3090 明显要比 intel 下 3090 慢,而且差距挺大的,非常不能理解。折腾了挺长一段时间才最终定位到是因为 cpu 在容器里的调度问题导致的。过程兜兜转转花了不少时间,这里就不说太多了,记录下大体过程和最后调优的措施。

最开始的测试

测试脚本

  • python gpu benchmark: https://github.com/ryujaehun/pytorch-gpu-benchmark
  • model: models.resnet.__all__[1:] 只测试 resnet 模型
  • batch size: 64
  • cpu limit: 12
  • memory limit: 30G
  • gpu limit: 1
  • shm: docker 给 30G 而 k8s 里由于无法直接设置,就给了机器内存的大小

nvidia docker 脚本

docker run --rm -it --shm-size=30g --cpus=12 --memory=30G --
gpus '"device=1"' uhub.service.ucloud.cn/openbayesruntimes/pytorch:1.9.0-py36-cu111.70

两个差异很大的结果

Intel 平台

start
benchmark start : 2022/11/17 06:15:23
Number of GPUs on current device : 1
CUDA Version : 11.1
Cudnn Version : 8005
Device Name : NVIDIA GeForce RTX 3090
uname_result(system='Linux', node='d1d271bdf102', release='5.4.0-131-generic', version='#147-Ubuntu SMP Fri Oct 14 17:07:22 UTC 2022', machine='x86_64', processor='x86_64')
                     scpufreq(current=1184.1902125, min=800.0, max=3400.0)
                    cpu_count: 80
                    memory_available: 258789797888
Benchmarking Training half precision type resnet18
/usr/local/lib/python3.6/site-packages/torch/nn/functional.py:718: UserWarning: Named tensors and all their associated APIs are an experimental feature and subject to change. Please do not use them for anything important until they are released as stable. (Triggered internally at  /pytorch/c10/core/TensorImpl.h:1156.)
  return torch.max_pool2d(input, kernel_size, stride, padding, dilation, ceil_mode)
resnet18 model average train time : 41.39636039733887ms
Benchmarking Training half precision type resnet34
resnet34 model average train time : 55.32251834869385ms
Benchmarking Training half precision type resnet50
resnet50 model average train time : 92.97645568847656ms
Benchmarking Training half precision type resnet101
resnet101 model average train time : 147.91772842407227ms
Benchmarking Training half precision type resnet152
resnet152 model average train time : 209.90628242492676ms
Benchmarking Training half precision type resnext50_32x4d
resnext50_32x4d model average train time : 132.71542072296143ms
Benchmarking Training half precision type resnext101_32x8d
resnext101_32x8d model average train time : 336.4134645462036ms
Benchmarking Training half precision type wide_resnet50_2
wide_resnet50_2 model average train time : 156.14235401153564ms
Benchmarking Training half precision type wide_resnet101_2
wide_resnet101_2 model average train time : 259.703106880188ms
Benchmarking Inference half precision type resnet18
resnet18 model average inference time : 31.02853298187256ms
Benchmarking Inference half precision type resnet34
resnet34 model average inference time : 39.35199737548828ms
Benchmarking Inference half precision type resnet50
resnet50 model average inference time : 41.26767635345459ms
Benchmarking Inference half precision type resnet101
resnet101 model average inference time : 48.41951370239258ms
Benchmarking Inference half precision type resnet152
resnet152 model average inference time : 67.41719722747803ms
Benchmarking Inference half precision type resnext50_32x4d
resnext50_32x4d model average inference time : 44.739885330200195ms
Benchmarking Inference half precision type resnext101_32x8d
resnext101_32x8d model average inference time : 103.05868148803711ms
Benchmarking Inference half precision type wide_resnet50_2
wide_resnet50_2 model average inference time : 49.078497886657715ms
Benchmarking Inference half precision type wide_resnet101_2
wide_resnet101_2 model average inference time : 83.67201805114746ms
benchmark end : 2022/11/17 06:21:41
end

AMD 平台

start
benchmark start : 2022/11/17 06:14:11
Number of GPUs on current device : 1
CUDA Version : 11.1
Cudnn Version : 8005
Device Name : NVIDIA GeForce RTX 3090
uname_result(system='Linux', node='925b73b78805', release='5.15.0-43-generic', version='#46-Ubuntu SMP Tue Jul 12 10:30:17 UTC 2022', machine='x86_64', processor='x86_64')
                     scpufreq(current=2784.047382812501, min=1500.0, max=2200.0)
                    cpu_count: 256
                    memory_available: 485026041856
Benchmarking Training half precision type resnet18
/usr/local/lib/python3.6/site-packages/torch/nn/functional.py:718: UserWarning: Named tensors and all their associated APIs are an experimental feature and subject to change. Please do not use them for anything important until they are released as stable. (Triggered internally at  /pytorch/c10/core/TensorImpl.h:1156.)
  return torch.max_pool2d(input, kernel_size, stride, padding, dilation, ceil_mode)
resnet18 model average train time : 75.70790767669678ms
Benchmarking Training half precision type resnet34
resnet34 model average train time : 82.6269006729126ms
Benchmarking Training half precision type resnet50
resnet50 model average train time : 111.1276912689209ms
Benchmarking Training half precision type resnet101
resnet101 model average train time : 161.16506576538086ms
Benchmarking Training half precision type resnet152
resnet152 model average train time : 228.9912509918213ms
Benchmarking Training half precision type resnext50_32x4d
resnext50_32x4d model average train time : 143.40569496154785ms
Benchmarking Training half precision type resnext101_32x8d
resnext101_32x8d model average train time : 354.08830165863037ms
Benchmarking Training half precision type wide_resnet50_2
wide_resnet50_2 model average train time : 164.76832389831543ms
Benchmarking Training half precision type wide_resnet101_2
wide_resnet101_2 model average train time : 271.076135635376ms
Benchmarking Inference half precision type resnet18
resnet18 model average inference time : 63.87866973876953ms
Benchmarking Inference half precision type resnet34
resnet34 model average inference time : 68.00977230072021ms
Benchmarking Inference half precision type resnet50
resnet50 model average inference time : 73.05157661437988ms
Benchmarking Inference half precision type resnet101
resnet101 model average inference time : 81.68745994567871ms
Benchmarking Inference half precision type resnet152
resnet152 model average inference time : 87.46984004974365ms
Benchmarking Inference half precision type resnext50_32x4d
resnext50_32x4d model average inference time : 83.56608867645264ms
Benchmarking Inference half precision type resnext101_32x8d
resnext101_32x8d model average inference time : 108.2996940612793ms
Benchmarking Inference half precision type wide_resnet50_2
wide_resnet50_2 model average inference time : 78.30146789550781ms
Benchmarking Inference half precision type wide_resnet101_2
wide_resnet101_2 model average inference time : 90.06356239318848ms
benchmark end : 2022/11/17 06:23:29
end

可以看到,越小的模型性能差异越大,这里就让我非常怀疑是 cpu 的问题。

关注主频

这里 AMD 的 cpu 是 7773x ,64 核心,128 线程,看起来是个 monster 可是这种超多核心的服务器 cpu 的主频相对都低一些,比如这个 Base Clock 是 2.2GHz,Boost Clock 是 3.5GHz。和 Intel 平台的比,差了一点点(那边是 3.8GHz)但这不足以引起如此大的性能差异,同时在通过 cat /proc/cpuinfo 命令查看运行时的主频后也确认很多核心的主频都可以跑到 3.4GHz 的水平,说明没有什么诡异的 BIOS 配置限制了性能的发挥。所以主频不是导致性能差的罪魁祸首。

继续翻阅 AMD 的 CPU 调优手册

虽然主频没问题,但还是觉得 cpu 哪里不太对,于是开始在 bios 里翻各种配置项目,越翻越懵逼,看不懂那些项目什么意思,就只能通过搜索引擎找到 AMD EPYC CPU 的配置手册,不经意间看到一个 Kubernetes Container Tuning Guide for AMD EPYC 7003 Series Processors 这不就是我目前所需要的?在 3.2 Container Pinning Settings 这部分提到默认的调度规则是在所有的 cpu 上做调度:

container pinning settings

那对于这个双路一共 256 线程的机器来说,调度频繁切换应该会是一个很大的开销吧?这里提到的 static 则是说只在特定的 cpu 里做调度。按照这个关键信息,我翻阅了 知道了 static 规则用的就是 cgroupcpuset 系统。那我是不是可以先用 docker 试试看?

使用 cpuset 命令测试

docker run --rm -it --shm-size=30g --cpuset-cpus 4-15 --cpus=12 --memory=30G --
gpus '"device=1"' uhub.service.ucloud.cn/openbayesruntimes/pytorch:1.9.0-py36-cu111.70

这里就是增加了 --cpuset-cpus 4-15 告知 docker 这个容器的可用 cpu 的区间。结果发现性能有了很大的提升,实际速度也超过了 Intel 平台。那么这里就大体确定了是默认调度规则的问题了。

解决 nvidia-docker 与 cpu 调度策略冲突的问题

按照 Changing the CPU Manager Policy 我很快对一台机器进行了调整,并准备在 k8s 平台上再做一次 benchmark 结果发现使用 nvidia-smi 命令查看显卡信息的时候报错了:

Failed to initialize NVML: Unknown Error

又是一番查询,发现是刚刚使用的 cpu-manager-polcy=static 会和 nvidia-docker 有冲突:容器因为 static 的调度规则必须修改容器的运行时,而这种行为是 nvidia docker 所不支持的。目前的解决方式是让所有调度 gpu 的 Pod 都必须具备 Guaranteed 级别的 QoS,同时所有的容器都必须有整数个的 cpu limitation。对于这种 Pod kubelet 会特殊对待,跳过对其 cpuset 进行更新(因为它锁定了 cpuset 所以按理说也不用更新)。同时这个支持也是在 k8s 1.22 版本之后才支持的。为此我们的集群也不得不升级到了 1.22 版本。


使用 OwnTracks 记录每天关键地点的停留时间

2022 July-18

众所周知,手机是可以做定位的。我个人比较喜欢做一些 "自我量化" 来追溯具体的时间都去哪里了,那通勤时间和办公室停留时间这种信息自然也很想记录下来。所以在很久之前我就开始各种搜罗,想要找一个方案。但很遗憾,找了一大圈,也没找到一个让我满意的,非常失望,这里记录下之前踩的坑,然后记录下目前找到的一个还可以的方案。

使用 iOS 的关键指令(shortcuts)

shortcuts 这个东西有点像是一个手机版本的 IFTTT。它包含一些触发器,可以自动触发某些事件,然后去串联多个应用以实现一些很 fancy 的功能。常见的触发器包括:

  1. 地理位置相关的:离开、到达某个区域
  2. 时间相关:几点几分到了
  3. wifi 相关:wifi 开启、关闭、自动连接了什么局域网

等等等,具体就不细说了。

iphone short cuts

不过很显然,我可以使用那个地理位置的触发器去记录我什么时候离开了哪里、什么时候到达了哪里。比如对于我通勤的记录,那我就设置两个关键地点:家和公司,然后记录离开到达时间就行了。具体就是这么个样子:

arrive company shortcut

我是每天创建一个笔记本,然后把信息都追加到笔记本里了。

然后似乎大功告成了,可是很遗憾,实际上完全不生效,网上一搜很多相关问题,但就是没解决方案,我尝试了很多的方法都不成功。只有偶尔会成功那么一下下...我甚至咨询了在 Apple 工作的同学,最终也没有得到解决...

owntracks

iOS 自带方案不行,就只好自己写 app 了,不过很遗憾我并没有移动端开发的经验,所以这个事情就搁置了...直到我一天发现了 owntracks 这个东西。

owntracks

简单来说,owntracks 就是个不收集你个人信息,只负责将你的信息上报到你指定的服务器的一个工具。所以其实 owntracks 分为两个部分:

  1. app 使用手机的定位功能获取经纬度
  2. server 将 app 上报的信息存下来

安装配置 app

我最开始的需求很简单,就是周期性记录我的位置,然后周期性上报就好了,后续的数据处理交给服务端做好了。不过 OwnTracks 除了这个朴实的功能外,也有那种区域触发的功能。这样我就可以和 iOS 快捷指令一样关注几个地点就好了。

不过相比于 iOS 这边直接把数据记录到笔记本,我需要一个 track server 接受上报的信息。owntracks 支持两种协议:MQTT 和 HTTP,鉴于我只会 HTTP 就自己写了一个简单的 HTTP server 去接收这些信息了。然后在 app 这边配置下服务端信息就可以接收数据了。

owntracks server config

注意这里要配置到具体的某一个路径的,比如 http://112.118.221.2/pub 而不是只给个域名或者 ip 地址就完事了。

编写 track server

为啥不用官方的?

OwnTracks 有官方的 track server ,但有这么几个问题:

  1. 代码是 c 的,自己部署有点晦涩难懂,只能用 docker 部署吧,但自己修改的可能性就很小了
  2. 同时支持 MQTT 和 HTTP 而实际上我觉得对个人来说 HTTP 会更简单一些吧,那 MQTT 部分的代码就很没必要了
  3. 有一些和 Google 地图集成的功能,这些东西在国内统统没得用

OwnTracks 会以 json 的格式把所有的信息都上报到服务器,提交的具体结构可以直接 print 出来看看。这里给出我的 server 的一个片段:


import json

from flask import Flask, request


FILENAME = "data.jsonl"

app = Flask(__name__)


@app.route("/")
def hello():
    return "hello"


@app.route("/pub", methods=["POST"])
def pub():
    # print raw request data
    print(request.json)
    with open(FILENAME, "ab") as f:
        f.write(request.data)
        f.write(b"\n")
    return "done"

if __name__ == "__main__":
    print("Starting server...")
    app.run(host="0.0.0.0")

可以看到我就是把所有的数据到塞到了一个 data.jsonl 的文件里了,反正就是我自己的数据,没几条,这样就够了。

展示数据

有了这些数据,我就可以把每天的通勤给按照时间顺序展示出来了,代码如下:

@app.route("/report")
def report():
    data = []
    with open(FILENAME, "r", encoding="utf-8") as f:
        data = f.readlines()
    data = [json.loads(item) for item in data]
    data = [item for item in data if item.get("event") in ["leave", "enter"]]
    chinaTz = pytz.timezone("Asia/Shanghai")
    # datetime from unix timestamp
    data = [
        {
            "desc": item["desc"],
            "event": item["event"],
            "date": datetime.fromtimestamp(item["tst"], chinaTz).strftime(
                "%m/%d"
            ),
            "time": datetime.fromtimestamp(item["tst"], chinaTz).strftime(
                "%H:%M"
            ),
            "datetime": datetime.fromtimestamp(item["tst"], chinaTz).strftime(
                "%m/%d %H:%M"
            ),
            "tst": item["tst"]
        }
        for item in data
    ]
    # remove item with same time
    masks = [False] * len(data)
    for i in range(1, len(data)):
        if abs(data[i]["tst"] - data[i - 1]["tst"]) < 120 and \
           data[i]["desc"] == data[i - 1]["desc"]:
            masks[i - 1] = True
            masks[i] = True if data[i]["event"] != data[i - 1]["event"] else False
    data = [item for i, item in enumerate(data) if not masks[i]]
    # group by time
    grouped = OrderedDict()
    for item in data:
        if item["date"] not in grouped:
            grouped[item["date"]] = []
        grouped[item["date"]].append(item)

    result = [
        [
            date,
            "\n".join(
                [
                    f"    {item['time']}\t{item['event']}\t{item['desc']}"
                    for item in items
                ]
            ),
        ]
        for date, items in reversed(grouped.items())
    ]

    return (
        "\n".join([y for x in result for y in x]),
        200,
        {"Content-Type": "text/plain; charset=utf-8"},
    )

最终展示效果大概就是这样子:

07/18
    09:11	leave	home
    12:22	enter	gym
    13:41	leave	gym
    15:00	enter	office
07/17
    09:11	leave	home
    12:22	enter	gym
    13:41	leave	gym
    15:00	enter	office
    17:00 leave office

这个 server 的代码也放到了 owntracks-server

遇到的坑

很久之前就处理过这种坐标数据,还是有不少坑的。趁这次再次接触到也做一个记录。

ping-pang 问题

经纬度坐标貌似并不是非常稳定,会有误差,你在同一个位置可能会有不同的坐标,这就会导致你可能动都没动但是突然你就离开了一个地方然后又回到了这个地方。

对于这种情况就只能做个简单的过滤,把那些时间戳非常接近的事件直接忽略掉。当然,这种过滤也可以顺便把你路过的那种地点一并清理掉。

坐标系转换

出于一些特殊的考虑,不同的地图给的具体经纬度都是有区别的,就是说你所在的同一个位置对于不同的地图给你标记的经纬度是不同的。目前有三个主流的坐标系:

  1. WGS84 坐标系 地球坐标系,国际通用坐标系
  2. GCJ02 坐标系 火星坐标系,WGS84 坐标系加密后的坐标系;Google 国内地图、高德、QQ 地图 使用
  3. BD09 坐标系 百度坐标系,GCJ02 坐标系加密后的坐标系

在我 iphone 上我的实际位置和地图标记的位置是有偏差的,我想这应该是苹果没有考虑到中国和国外使用的坐标系是不同的,也就是说 iOS 给我获取的经纬度应该是 WGS84 的,但实际展示却用了 GCJ02 的高德地图,所以展示的位置就不对了。我猜测这也很有可能是快捷指令无法触发的原因所在。

在目前的情况下,这个问题只是对 app 本身的展示有影响,对事件触发并没有影响。可以先不管。

后续的工作

  1. 做一些基本的统计,比如通勤时间、比如办公时间
  2. 除了区域事件外也试试其他的数据,比如去自动发现停留区域,比如集成地图 api 展示具体的停留区域等
  3. 把数据直接塞进 data.jsonl 时间久了可能还是不太够,还是需要起码按照月份做个数据拆分