Please enable Javascript to view the contents

使用 Dragonfly V2 分发集群的镜像

 ·  ☕ 7 分钟

1. Dragonfly 简介

Dragonfly 的相关文档在社区 https://d7y.io/zh/docs/ 已经有详细说明。这里只是简单介绍一下,V2 版本的主要组件:

  • Manager,提供 UI 界面、用户管理、集群监控、任务管理等功能
  • Scheduler,调度 Peer 之间的流量、提供预热等功能
  • Seed Peer,回源节点,用于从源站(Harbor、Docker.io 等)下载数据,也可以作为 Peer 节点
  • Peer,提供下载数据的终端节点

其中 Manager、Scheduler 是单独的容器镜像,Seed Peer 和 Peer 是同一个容器镜像。

Dragonfly 支持的镜像预热功能,可以和 Harbor 进行集成,但本文不会涉及。本文主要是介绍我们在支撑 AI 业务时,生产环境下的一些实践。值得注意的是 Dragonfly V2 实际上构建了一个 P2P 分发的网络,不仅可以分发镜像,还可以分发文件,这就打开了想象空间。

2. IDC 机房中的 Dragonfly 集群

我们 AI 模型的推理和训练都是基于 Kubernetes 集群,后端存储采用的是企业版 JuiceFS,在每个 Node 节点都挂载了几个 T 的 SSD 磁盘,用来挂载 JuiceFS 的缓存目录。

因此,Kubernetes 集群中的每个 Node 节点都具备作为 Dragonfly Peer 节点的条件。但 Peer 组网时,我们不希望有额外的负担,包括:

  • 跨 VPC 的 NAT 流量
  • 公网传输数据

下面是 Dragonflyv2 机房多 VPC 部署拓扑图:

  • LB 需要公网 IP,作为 Peer 的接入点
  • 一个 VPC 对应一个 Dragonfly 的 Cluster 抽象
  • 虽然 IDC 打通了 VPC 之间的网络,但一个 VPC 内的 Peer 才允许组网
  • 集群内每个 Node 节点部署一个 Peer

VPC 内,下面这张图给出了详细的高可用方案。

  • LB 只需要内网 IP 即可
  • 使用云厂的 MySQL 8.0、Redis 6 服务
  • 两台 VM 部署 Manager、Scheduler、Seed Peer
  • 每个 VM 部署的是一套完整的 Dragonfly 集群,包括 Manager、Scheduler、Seed Peer,不用经过 LB 也能用
  • 每个 Node 节点部署 Peer

Dragonfly 构建的 P2P 分发网络,不应该和 PaaS 层耦合太紧密,避免循环依赖。因此,这里采用双 VM 的方案,共享数据存储,保障可用性。在 Kubernetes 集群的 Master 节点上,我们也不会进行加速优化,保障 PaaS 层控制面的简洁和独立。

3. VM 上部署 Dragonfly 控制平面

需要提前安装好 Docker,分别在两台 VM 上进行独立部署。

3.1 安装 docker-compose

  • 下载 docker-compose
1
curl -L https://ghp.ci/https://github.com/docker/compose/releases/download/v2.23.3/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
  • 添加执行权限
1
chmod +x /usr/local/bin/docker-compose
  • 查看版本
1
docker-compose -v

3.2 安装 dragonfly

参考 https://d7y.io/zh/docs/getting-started/quick-start/docker-compose/

  • 下载 docker-compose 部署文件
1
2
3
4
cd /data
wget https://ghp.ci/https://github.com/dragonflyoss/Dragonfly2/archive/refs/tags/v2.1.28.tar.gz
tar -zxvf v2.1.28.tar.gz
cp -r Dragonfly2-2.1.28/deploy/docker-compose ./
  • 清理不需要的文件
1
rm -rf *2.1.28*
  • 生成默认的配置文件
1
cd docker-compose

由于默认的发布包中,没有配置文件,这里先生成一份配置文件,然后再修改。

1
2
export IP=VM_IP
./run.sh

立即终止执行,然后继续修改配置文件。

  • 固定镜像版本
1
sed -i 's/latest/v2.1.28/g' docker-compose.yaml
  • 修改存储账号及其他配置

修改 Redis、MySQL 地址和密码

1
vim config/manager.yaml

修改 Redis 密码

1
vim config/scheduler.yaml

在这两个配置文件中,还有一些其他的配置项,可以根据实际情况进行修改。比如,manager 的 addr 指向当前主机的服务、将日志输出到控制台、开启 Metrics 等。

  • 修改 seed-peer 的缓存目录
1
vim docker-compose.yaml
1
2
3
volumes:
  - ./cache:/var/cache/dragonfly
  - ./data:/var/lib/dragonfly

如果关闭了 seed-peer 作为 peer 节点的功能,可以跳过这一步,同时,VM 的磁盘空间也可以不用太大。

  • 启动服务
1
docker-compose up -d
  • 查看服务
1
2
3
4
5
6
docker-compose ps

NAME        IMAGE                            COMMAND                  SERVICE     CREATED        STATUS                  PORTS
manager     dragonflyoss/manager:v2.1.28     "/opt/dragonfly/bin/…"   manager     14 hours ago   Up 14 hours (healthy)   0.0.0.0:8080->8080/tcp, 0.0.0.0:65003->65003/tcp
scheduler   dragonflyoss/scheduler:v2.1.28   "/opt/dragonfly/bin/…"   scheduler   14 hours ago   Up 14 hours (healthy)   0.0.0.0:8002->8002/tcp
seed-peer   dragonflyoss/dfdaemon:v2.1.28    "/opt/dragonfly/bin/…"   seed-peer   14 hours ago   Up 14 hours (healthy)   65001/tcp, 0.0.0.0:65006-65008->65006-65008/tcp
  • 打开管理页面看看

访问 http://${VM_IP}:8080 端口可以看到 Dragonfly 的管理界面,如果机器没有公网 IP,可以使用 socat 进行端口转发。找一台有公网 IP 的机器,执行以下命令,将 30000 端口转发到 8080 端口:

1
2
export IP=VM_IP
socat TCP-LISTEN:30000,fork TCP:$IP:8080

两台 VM 部署完成,在 Dashboard 中可以看到这样一个集群,两个 Scheduler、两个 Seed Peer。如下图:

4. 在集群部署 Peer 节点

部署 Peer 的节点,需要对两台 VM 的 8002 、65001、65003 、65006-65008 端口有访问权限。

  • 创建命名空间
1
kubectl create ns dragonfly-system
  • 创建配置文件

这里需要将 LB 的 IP 地址填入配置文件中,Peer 才能接入到 Dragonfly 集群中。

1
export MANAGER_IP=LB_IP

有很多参数,可以根据实际情况进行修改,这里提供了一份默认的配置文件。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
kubectl apply -f - <<EOF
apiVersion: v1
data:
  dfget.yaml: |
    aliveTime: 0s
    gcInterval: 1m0s
    keepStorage: false
    workHome: /usr/local/dragonfly
    logDir: /var/log/dragonfly
    cacheDir: /var/cache/dragonfly
    pluginDir: /usr/local/dragonfly/plugins
    dataDir: /var/lib/dragonfly
    console: true
    health:
      path: /server/ping
      tcpListen:
        port: 40901
    verbose: true
    pprof-port: 18066
    metrics: ":8000"
    jaeger: ""
    scheduler:
      manager:
        enable: true
        netAddrs:
          - type: tcp
            addr: $MANAGER_IP:65003
        refreshInterval: 10m
      netAddrs:
      scheduleTimeout: 30s
      disableAutoBackSource: false
      seedPeer:
        clusterID: 1
        enable: false
        type: super
    host:
      idc: ""
      location: ""
    download:
      calculateDigest: true
      downloadGRPC:
        security:
          insecure: true
          tlsVerify: true
        unixListen:
          socket: ""
      peerGRPC:
        security:
          insecure: true
        tcpListen:
          port: 65000
      perPeerRateLimit: 5120Mi
      prefetch: false
      totalRateLimit: 10240Mi
    upload:
      rateLimit: 10240Mi
      security:
        insecure: true
        tlsVerify: false
      tcpListen:
        port: 65002
    objectStorage:
      enable: false
      filter: Expires&Signature&ns
      maxReplicas: 3
      security:
        insecure: true
        tlsVerify: true
      tcpListen:
        port: 65004
    storage:
      diskGCThreshold: 1000Gi
      multiplex: true
      strategy: io.d7y.storage.v2.simple
      taskExpireTime: 72h
    proxy:
      defaultFilter: Expires&Signature&ns
      defaultTag:
      tcpListen:
        port: 65001
      security:
        insecure: true
        tlsVerify: false
      registryMirror:
        dynamic: true
        insecure: false
        url: https://index.docker.io
      proxies:
        - regx: blobs/sha256.*
        - regx: s3.*amazonaws.com.*
        - regx: oss.*aliyuncs.com.*
        - regx: obs.*myhuaweicloud.com.*
        - regx: ks3.*ksyun.com.*
    security:
      autoIssueCert: false
      caCert: ""
      certSpec:
        dnsNames: null
        ipAddresses: null
        validityPeriod: 4320h
      tlsPolicy: prefer
      tlsVerify: false
    network:
      enableIPv6: false
    announcer:
      schedulerInterval: 30s
kind: ConfigMap
metadata:
  labels:
    app: dragonfly
  name: dragonfly-dfdaemon
  namespace: dragonfly-system
EOF
  • 创建 DaemonSet

我们从官方的 Helm Chart 中提取出来的 DaemonSet 文件。需要注意的是,Peer 使用的缓存目录是主机上的 /data/dfget 目录。最好提前清理主机上的 /data/dfget 目录,避免出现权限问题,也不用提前创建,DaemonSet 会自动创建。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    app: dragonfly
  name: dragonfly-dfdaemon
  namespace: dragonfly-system
spec:
  selector:
    matchLabels:
      app: dragonfly
  template:
    metadata:
      labels:
        app: dragonfly
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/port: "8000"
        prometheus.io/path: "/metrics"
    spec:
      containers:
      - image: dragonflyoss/dfdaemon:v2.1.28
        livenessProbe:
          exec:
            command:
            - /bin/grpc_health_probe
            - -addr=:65000
        name: dfdaemon
        ports:
        - containerPort: 65001
          protocol: TCP
        - containerPort: 40901
          protocol: TCP
        - containerPort: 8000
          protocol: TCP
        readinessProbe:
          exec:
            command:
            - /bin/grpc_health_probe
            - -addr=:65000
          failureThreshold: 3
          initialDelaySeconds: 5
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        resources:
          limits:
            cpu: "2"
            memory: 2Gi
        securityContext:
          capabilities:
            add:
            - SYS_ADMIN
        volumeMounts:
        - mountPath: /etc/dragonfly
          name: config
        - mountPath: /var/cache/dragonfly
          name: dfgetcache
        - mountPath: /var/lib/dragonfly
          name: dfgetdata
      hostNetwork: true
      hostPID: true
      tolerations:
      - effect: NoSchedule
        operator: Exists
      - effect: NoExecute
        operator: Exists
      volumes:
      - configMap:
          defaultMode: 420
          name: dragonfly-dfdaemon
        name: config
      - hostPath:
          path: /data/dfget/cache
          type: DirectoryOrCreate
        name: dfgetcache
      - hostPath:
          path: /data/dfget/data
          type: DirectoryOrCreate
        name: dfgetdata
EOF
  • 查看负载
1
2
3
4
5
6
7
8
kubectl -n dragonfly-system get pod

NAME                       READY   STATUS    RESTARTS   AGE
dragonfly-dfdaemon-79qkw   1/1     Running   0          14h
dragonfly-dfdaemon-8hhzb   1/1     Running   3          14h
dragonfly-dfdaemon-nnfc5   1/1     Running   0          14h
dragonfly-dfdaemon-w7lff   1/1     Running   0          14h
dragonfly-dfdaemon-wrmzw   1/1     Running   0          14h

5. 在 VM 上部署 Peer 节点

  • 创建目录
1
mkdir -p /data/dfget && cd /data/dfget
  • 设置 IP
1
wget https://ghp.ci/https://raw.githubusercontent.com/shaowenchen/hubimage/main/nydus/dfget.template.yaml -O dfget.yaml
1
2
export MANAGER_IP=LB_IP
sed -i "s/__MANAGER_IP__/$MANAGER_IP/g" dfget.yaml
  • 启动 Peer
1
2
3
4
5
6
nerdctl run -d --name=peer --restart=always \
            -p 65000:65000 -p 65001:65001 -p 65002:65002 \
            -v $(pwd)/data:/var/lib/dragonfly \
            -v $(pwd)/cache:/var/cache/dragonfly \
            -v $(pwd)/dfget.yaml:/etc/dragonfly/dfget.yaml:ro \
            dragonflyoss/dfdaemon:v2.1.28

6. 使用节点配置

6.1 Docker

Docker 的 Mirror 方式只能加速 Docker.io 的镜像,这里采用 Proxy 的方式,代理全部 Dockerd 的流量。Proxy 与 Mirror 的区别在于,Mirror 挂了,Dockerd 会拉取源站,而 Proxy 挂了,Dockerd 直接拉取失败。

  • 添加代理
1
mkdir -p /etc/systemd/system/docker.service.d
1
2
3
4
5
cat > /etc/systemd/system/docker.service.d/http-proxy.conf <<EOF
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:65001"
Environment="HTTPS_PROXY=http://127.0.0.1:65001"
EOF
  • 重启 Docker
1
2
systemctl daemon-reload
systemctl restart docker

注意,这里如果 /etc/docker/daemon.json 中没有配置 "live-restore": true ,会导致容器全部重启。

  • 查看环境变量
1
2
3
systemctl show --property=Environment docker

Environment=HTTP_PROXY=http://127.0.0.1:65001 HTTPS_PROXY=http://127.0.0.1:65001
  • 镜像拉取测试
1
docker pull nginx

此时,Dockerd 的流量会经过 Dragonfly Peer 节点。

6.2 Containerd

参考 https://github.com/containerd/containerd/blob/main/docs/cri/config.md#registry-configuration

/etc/containerd/config.toml[plugins."io.containerd.grpc.v1.cri".registry] 项的 config_path = "/etc/containerd/certs.d" 提供了类似于 mirror 的配置方式。

  • 配置 Docker.io
1
mkdir -p /etc/containerd/certs.d/docker.io
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
cat > /etc/containerd/certs.d/docker.io/hosts.toml <<EOF
server = "https://docker.io"

[host."http://127.0.0.1:65001"]
  capabilities = ["pull", "resolve"]
  [host."http://127.0.0.1:65001".header]
    X-Dragonfly-Registry = ["https://registry-1.docker.io"]
  [host."https://registry-1.docker.io"]
    capabilities = ["pull", "resolve"]
EOF
  • 配置其他、私有镜像仓库

其他镜像仓库的配置可以通过脚本生成,比如:

1
2
wget https://ghp.ci/https://raw.githubusercontent.com/dragonflyoss/Dragonfly2/main/hack/gen-containerd-hosts.sh
bash gen-containerd-hosts.sh ghcr.io

这里没有用脚本生成 docker.io 的配置是因为,生成的配置文件中,X-Dragonfly-Registryhttps://docker.io,而不是 https://registry-1.docker.io

使用 X-Dragonfly-Registry = ["https://docker.io"] 会出现如下错误:

1
unknow type: text/html

以上添加的 mirror,不用重启 Containerd,直接能生效。

  • 镜像拉取测试
1
nerdctl pull nginx

此时,在本地 /data/dfget/data 目录下可以看到 Peer 节点缓存的镜像数据。

7. 集成 Nydus

如果 Nydus 已经配置好,这里其实已经能轻松配置好。

  • 给 Nydusd 添加 mirror
1
vim /etc/nydus/nydusd-config.fusedev.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
{
  "device": {
    "backend": {
      "type": "registry",
      "config": {
        "mirrors": [
          {
            "host": "http://127.0.0.1:65001",
            "auth_through": false,
            "headers": {
              "X-Dragonfly-Registry": "https://index.docker.io"
            },
            "ping_url": "http://127.0.0.1:40901/server/ping"
          }
        ]
      }
    }
  }
}
  • 重启 Nydusd
1
systemctl restart nydus-snapshotter
  • 镜像拉取测试
1
nerdctl pull shaowenchen/demo-ubuntu:latest-nydus

8. 总结

本篇记录了这周在生产环境中,测试并部署 Dragonfly V2 的部分过程,主要内容包括:

  • 机房中 Dragonfly 集群的部署拓扑
  • 集群和 VM 上 Peer 节点的部署
  • Docker、Containerd、Nydus 的集成

说下不足,没有指标监控,在做 Benchmark 时,我们发现 AZ 内和跨 AZ 的 Peer 之间的数据传输都受限,如果想构建高性能的 P2P 分发网络,Peer 与 Peer、Peer 与 Seed Peer 之间的网络是一个重要的考量因素。


微信公众号
作者
微信公众号