AI 基础设施之使用 Dragonfly V2 分发集群的镜像

技术
  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 分发的网络,不仅可以分发镜像,还可以分发文件,这就打开了想象空间。

  1. IDC 机房中的 Dragonfly 集群

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

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

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

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

picture.image

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

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

picture.image

  • 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 层控制面的简洁和独立。

  1. VM 上部署 Dragonfly 控制平面

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

3.1 安装 docker-compose

  • 下载 docker-compose

        
          
curl -L https://mirror.ghproxy.com/https://github.com/docker/compose/releases/download/v2.23.3/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose  

      
  • 添加执行权限

        
          
chmod +x /usr/local/bin/docker-compose  

      
  • 查看版本

        
          
docker-compose -v  

      

3.2 安装 dragonfly

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

  • 下载 docker-compose 部署文件

        
          
cd /data  
wget https://mirror.ghproxy.com/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 ./  

      
  • 清理不需要的文件

        
          
rm -rf *2.1.28*  

      
  • 生成默认的配置文件

        
          
cd docker-compose  

      

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


        
          
export IP=VM_IP  
./run.sh  

      

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

  • 固定镜像版本

        
          
sed -i 's/latest/v2.1.28/g' docker-compose.yaml  

      
  • 修改存储账号及其他配置

修改 Redis、MySQL 地址和密码


        
          
vim template/manager.template.yaml  

      

修改 Redis 密码


        
          
vim template/scheduler.template.yaml  

      

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

  • 修改 seed-peer 的缓存目录

        
          
vim docker-compose.yaml  

      

        
          
volumes:  
  - ./cache:/var/cache/dragonfly  
  - ./data:/var/lib/dragonfly  

      

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

  • 启动服务

        
          
docker-compose up -d  

      
  • 查看服务

        
          
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 端口:


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

      

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

picture.image

  1. 在集群部署 Peer 节点

  • 创建命名空间

        
          
kubectl create ns dragonfly-system  

      
  • 创建配置文件

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


        
          
export MANAGER_IP=LB_IP  

      

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


        
          
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: 512Mi  
      prefetch: false  
      totalRateLimit: 1024Mi  
    upload:  
      rateLimit: 1024Mi  
      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.*  
    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 会自动创建。


        
          
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  
    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  
          hostIP: 127.0.0.1  
          hostPort: 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  
      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  

      
  • 查看负载

        
          
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  

      
  1. 在 VM 上部署 Peer 节点

  • 创建目录

        
          
mkdir -p /data/dfget && cd /data/dfget  

      
  • 设置 IP

        
          
wget https://mirror.ghproxy.com/https://raw.githubusercontent.com/shaowenchen/hubimage/main/nydus/dfget.template.yaml -O dfget.yaml  

      

        
          
export MANAGER_IP=LB_IP  
sed -i "s/\_\_MANAGER\_IP\_\_/$MANAGER\_IP/g" dfget.yaml  

      
  • 启动 Peer

        
          
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  

      
  1. 使用节点配置

6.1 Docker

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

  • 添加代理

        
          
mkdir -p /etc/systemd/system/docker.service.d  

      

        
          
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

        
          
systemctl daemon-reload  
systemctl restart docker  

      

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

  • 查看环境变量

        
          
systemctl show --property=Environment docker  
  
Environment=HTTP_PROXY=http://127.0.0.1:65001 HTTPS_PROXY=http://127.0.0.1:65001  

      
  • 镜像拉取测试

        
          
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

        
          
mkdir -p /etc/containerd/certs.d/docker.io  

      

        
          
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  

      
  • 配置其他、私有镜像仓库

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


        
          
wget https://mirror.ghproxy.com/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"] 会出现如下错误:


        
          
unknow type: text/html  

      

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

  • 镜像拉取测试

        
          
nerdctl pull nginx  

      

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

  1. 集成 Nydus

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

  • 给 Nydusd 添加 mirror

        
          
vim /etc/nydus/nydusd-config.fusedev.json  

      

        
          
{  
  "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

        
          
systemctl restart nydus-snapshotter  

      
  • 镜像拉取测试

        
          
nerdctl pull shaowenchen/demo-ubuntu:latest-nydus  

      
  1. 总结

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

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

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

0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
边缘云原生操作系统设计与思考
《火山引擎边缘云原生操作系统设计与思考》 徐广治 | 火山引擎边缘云资深架构师
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论