Dragonfly V2 分发集群的镜像

k8s技术圈

共 22393字,需浏览 45分钟

 · 2024-03-24

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 部署拓扑图:

3a9782d638e406554e5cf82c570c2d64.webp

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

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

ead6456e236346b9e0f92fc3e368aaa9.webp

  • 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
      
      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。如下图:

a0dfe461174ea0580b0f84dc53db77a7.webp

4. 在集群部署 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:
        enabletrue
        netAddrs:
          - type: tcp
            addr: $MANAGER_IP:65003
        refreshInterval: 10m
      netAddrs:
      scheduleTimeout: 30s
      disableAutoBackSource: false
      seedPeer:
        clusterID: 1
        enablefalse
        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:
      enablefalse
      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

5. 在 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

6. 使用节点配置

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 节点缓存的镜像数据。

7. 集成 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

8. 总结

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

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

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

bc8d4ff952a98acf524367176df89b7a.webp

浏览 3
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报