This page looks best with JavaScript enabled

Nydus Lazy-Loading Image Configuration and Practice

 ·  ☕ 9 min read

Statistics show that most files inside a container are never used. Based on this characteristic, Nydus defines a custom filesystem in the Rafs format, enabling on-demand loading of image files to solve the slow startup and storage consumption caused by large images. In AI scenarios, whether for inference or training, images often start at several GB, or even tens of GB, so Nydus is very well suited.

This post is mainly a set of concrete operation steps for quickly configuring the image lazy-loading solution Nydus, along with ways to handle common problems.

1. Install nerdctl and nydus

  • Install Opscli
1
curl -sfL https://raw.githubusercontent.com/shaowenchen/ops/main/getcli.sh |VERSION=latest sh -

If it is already installed, you can update Opscli to the latest version.

1
opscli upgrade
  • Install nerdctl
1
opscli task -f ~/.ops/tasks/install-nerdctl.yaml

Use the -i ~/.kube/config parameter to target the entire cluster.

  • Install nydus
1
opscli task -f ~/.ops/tasks/install-nydus.yaml

Use the -i ~/.kube/config parameter to target the entire cluster.

2. Start nydus-snapshotter

The documentation at https://github.com/containerd/nydus-snapshotter/tree/main/misc/snapshotter contains many example configurations. Not all of our production Linux Kernels are above 5.19, so the fuse approach was chosen here.

2.1 Create the configuration file /etc/nydus/config.toml

1
2
mkdir -p /etc/nydus
wget https://raw.githubusercontent.com/shaowenchen/demo/master/nydus/config.toml -O /etc/nydus/config.toml

2.2 Create the configuration file /etc/nydus/nydusd-config.fusedev.json

  • Configure the backend and cache
1
wget https://raw.githubusercontent.com/shaowenchen/demo/master/nydus/nydusd-config.fusedev.json -O /etc/nydus/nydusd-config.fusedev.json
  • Create the Systemd Unit startup file
1
wget https://raw.githubusercontent.com/shaowenchen/demo/master/nydus/nydus-snapshotter.service -O /etc/systemd/system/nydus-snapshotter.service
  • Create the data directory
1
mkdir -p /data/containerd/io.containerd.snapshotter.v1.nydus
  • Start the configuration
1
2
3
systemctl enable nydus-snapshotter
systemctl start nydus-snapshotter
systemctl status nydus-snapshotter
  • View the service logs
1
journalctl -u nydus-snapshotter -f

3. Integrate Nydus into Containerd

  • Add the nydus snapshotter plugin

Edit the configuration file

1
vim /etc/containerd/config.toml

Add the following content

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[plugins]
  [plugins."io.containerd.grpc.v1.cri"]
    [plugins."io.containerd.grpc.v1.cri".containerd]
      default_runtime_name = "runc"
      ignore_rdt_not_enabled_errors = false
      no_pivot = false
      discard_unpacked_layers = false
      disable_snapshot_annotations = false
      snapshotter = "nydus"
[proxy_plugins]
  [proxy_plugins.nydus]
    type = "snapshot"
    address = "/run/containerd-nydus/containerd-nydus-grpc.sock"
  • Restart containerd
1
systemctl restart containerd
  • Check whether the installation succeeded
1
2
3
ctr -a /run/containerd/containerd.sock plugin ls | grep nydus

io.containerd.snapshotter.v1          nydus                    -              ok

4. Convert an OCI Image Directly into a Nydus Image

  • Log in to the image registry
1
nerdctl login https://index.docker.io/v1/

nerdctl is used in the same way as docker; if other registries are involved, log in to those as well.

  • Convert the image
1
2
3
4
5
6
7
8
nydusify convert --source shaowenchen/demo:ubuntu:latest --target shaowenchen/demo:ubuntu:latest-nydus

pulling image docker.io/shaowenchen/demo:ubuntu:latest  module=converter
pulled image docker.io/shaowenchen/demo:ubuntu:latest, elapse 9.743015898s  module=converter
converting image docker.io/shaowenchen/demo:ubuntu:latest  module=converter
converted image docker.io/shaowenchen/demo:ubuntu:latest-nydus, elapse 4.002142728s  module=converter
pushing image docker.io/shaowenchen/demo:ubuntu:latest-nydus  module=converter
pushed image docker.io/shaowenchen/demo:ubuntu:latest-nydus, elapse 1m25.054493982s  module=converter

You can specify the registry credentials with --backend-config-file ~/.docker/config.json.

5. Use Buildkit to Build a Dockerfile and Produce a Nydus Image

Besides converting with Nydusify, you can also build a Dockerfile directly to generate a Nydus image.

  • Download Buildkit

The source provided by https://github.com/moby/buildkit does not support Nydus; you need the version provided by https://github.com/nydusaccelerator/buildkit, but the latter has no release, so it must be downloaded and compiled. I have compiled a version here that you can download and use directly.

1
wget https://github.com/shaowenchen/nydusaccelerator-buildkit/releases/download/latest/buildkit-linux-amd64.tar.gz
  • Install Buildkit
1
2
tar xvf buildkit-linux-amd64.tar.gz
mv bin/* /usr/local/bin/
  • Configure Buildkitd
1
mkdir -p /etc/buildkit /data/buildkit
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
cat > /etc/buildkit/buildkitd.toml <<EOF
debug = true
root = "/data/buildkit"
[worker.oci]
  enabled = false

[worker.containerd]
  address = "/run/containerd/containerd.sock"
  enabled = true
  platforms = [ "linux/amd64", "linux/arm64" ]
  namespace = "buildkit"
  gc = true
  gckeepstorage = 9000
  cniPoolSize = 16
EOF

Generate the Systemd Unit file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
cat > /etc/systemd/system/buildkitd.service << EOF
[Unit]
Description=buildkitd service
Documentation=https://github.com/moby/buildkit

[Service]
Environment="NYDUS_BUILDER=/usr/local/bin/nydus-image"
ExecStart=/usr/local/bin/buildkitd --config /etc/buildkit/buildkitd.toml

[Install]
WantedBy=multi-user.target
EOF
  • Start the Buildkitd service
1
2
3
systemctl enable buildkitd
systemctl start buildkitd
systemctl status buildkitd
  • Test building a Nydus image with Buildkitd
1
2
3
4
cat << EOF >Dockerfile
FROM ubuntu
RUN touch 123
EOF
1
2
3
4
buildctl build --frontend=dockerfile.v0 \
  --local context=. \
  --local dockerfile=. \
  --output type=image,name=demo:nydus,push=true,compression=nydus,force-compression=true,oci-mediatypes=true

You can also build with nerdctl, which automatically invokes buildkit to do the build.

1
nerdctl build -f Dockerfile --output type=image,name=demo:nydus,push=true,compression=nydus,force-compression=true,oci-mediatypes=true .

The credentials used for pushing come from ~/.docker/config.json; you can log in with nerdctl login.

  • Verify the image
1
nerdctl --snapshotter nydus run --rm -it demo:nydus

6. Verify Nydus at the Application Layer

6.1 Containerd

1
nerdctl --snapshotter nydus run --rm -it demo:nydus

Other Nydus images include

  • dragonflyoss/python:3.9.15-nydus
  • ghcr.io/dragonflyoss/image-service/ubuntu:nydus-nightly-v5

6.2 Kubernetes

  • Create a workload
1
kubectl create deployment nydus-test --image=demo:nydus

You can also create a DaemonSet directly to test on every node.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nydus-test-daemonset
spec:
  selector:
    matchLabels:
      app: nydus-test-daemonset
  template:
    metadata:
      labels:
        app: nydus-test-daemonset
      name: nydus-test-daemonset
    spec:
      tolerations:
      - key: "node-role.kubernetes.io/control-plane"
        operator: "Exists"
        effect: "NoSchedule"
      containers:
      - image: demo:nydus
        name: nydus-test-daemonset
EOF
  • View the workload
1
kubectl get deployment nydus-test -o wide
  • Switch to a different node
1
kubectl patch deployment nydus-test -p '{"spec":{"template":{"spec":{"nodeName":"node1"}}}}'
  • Clean up the workload
1
kubectl delete deployment nydus-test

7. Configure a Grafana Monitoring Dashboard to View Nydus Metrics

  • Edit the prometheus-server configuration
1
kubectl -n monitor edit cm prometheus-server
  • Add a Job that scrapes Nydus metrics
1
2
3
4
5
6
7
scrape_configs:
  - job_name: nydus
    metrics_path: /v1/metrics
    static_configs:
    - targets:
      - x.x.x.x:9110
      - x.x.x.x:9110
  • Add a dashboard in Grafana

I drew a simple dashboard and shared it on the Grafana website; you can import it directly. The ID is 20245, and the link is https://grafana.com/grafana/dashboards/20245-nydus-dashboard/ .

The final result looks like this:

8. Handling Common Errors

When using Nydus you may run into various problems; here I record the problems I ran into and how I handled them, updated continuously. It is best to use a clean environment and get the configuration right in one pass — configuring repeatedly may produce strange problems.

8.1 no processor for media-type when pulling an image

  • Error message
1
FATA[0000] failed to extract layer sha256:58e33caaf7a78562cc25629ed0414320c3d755b66bf1f313fdcff75748102013: failed to get stream processor for application/vnd.oci.image.layer.nydus.blob.v1: no processor for media-type: unknown
1
failed to register layer: Error processing tar file(exit status 1): archive/tar: invalid tar header
  • How to handle it

The nydus-snapshotter service is abnormal; check the service logs, or pull the image without Nydus configured.

1
journalctl -u nydus-snapshotter.service -f

8.2 The application will not start, snapshot already exists

  • Error message
1
FATA[0001] unable to prepare extraction snapshot: target snapshot "sha256:58e33caaf7a78562cc25629ed0414320c3d755b66bf1f313fdcff75748102013": already exists
1
create snapshot: missing parent "k8s.io/14/sha256:e3e5579ddd43c08e4b5c74dc12941a4ef656fab070b1087a1fd5a8a836b71e7d" bucket: not found
  • How to handle it

Delete the application’s image first, clean the cache, then retry.

1
nerdctl rmi demo:nydus
1
nerdctl image prune --force --all

8.3 nydus-snapshotter will not start, failed to initialize snapshotter: initialize filesystem

  • Error message
1
2
3
level=error msg="Process 770795 has been a zombie"
...
level=fatal msg="failed to start nydus-snapshotter" error="failed to initialize snapshotter: initialize filesystem thin layer: wait for daemon cmb35s0g2p2n0dt9tqag: wait until daemon is RUNNING: get daemon state: daemon socket /data/containerd/io.containerd.snapshotter.v1.nydus/socket/cmb35s0g2p2n0dt9tqag/api.sock: not found"

There may also be another containerd-nydus-grpc that has not fully exited.

  • How to handle it
1
ps aux |grep containerd-nydus-grpc

Find and kill the other process, then restart the nydus-snapshotter service.

8.4 nydus-snapshotter will not start, failed to parse configuration information

  • Error message
1
2
3
4
5
6
7
"failed to parse configuration information"
containerd-nydus-grpc[21763]:         at api/src/config.rs:243
containerd-nydus-grpc[21763]:         note: enable `RUST_BACKTRACE=1` env to display a backtrace
containerd-nydus-grpc[21763]: [2024-01-04 17:19:49.125269 +08:00] ERROR [/src/error.rs:22] Error:
containerd-nydus-grpc[21763]:         Rafs(LoadConfig(Os { code: 22, kind: InvalidInput, message: "Invalid argument" }))
containerd-nydus-grpc[21763]:         at service/src/lib.rs:121
containerd-nydus-grpc[21763]:         note: enable `RUST_BACKTRACE=1` env to display a backtrace
  • How to handle it

According to the hint, there is a problem with the configuration file, including the configuration format, key values, whether directories exist, and so on. The problem I ran into was that the root directory did not exist.

1
mkdir -p /data/containerd/io.containerd.snapshotter.v1.nydus
1
systemctl restart nydus-snapshotter

8.5 Kubelet reports an error, the directory cannot be found

  • Error
1
Failed to get the info of the filesystem with mountpoint" err="failed to get device for dir "/var/lib/containerd/io.containerd.snapshotter.v1.nydus": stat failed on /var/lib/containerd/io.containerd.snapshotter.v1.nydus with error: no such file or directory" mountpoint="/var/lib/containerd/io.containerd.snapshotter.v1.nydus"
  • How to handle it

According to https://github.com/containerd/nydus-snapshotter/issues/288 , Nydus currently requires its root directory to be $containerd_root_dir/io.containerd.snapshotter.v1.nydus.

Check containerd’s root directory

1
2
3
cat /etc/containerd/config.toml |grep root

root = "/var/lib/containerd"

Edit the Nydus configuration file

1
vim /etc/nydus/config.toml

Change the root directory to the subdirectory io.containerd.snapshotter.v1.nydus under containerd’s root directory. Create that directory, then restart the nydus-snapshotter service.

8.6 After a restart, Pods managed by Containerd fail to start with an Init:CreateContainerError error

  • How to handle it

First turn off the nydus configuration in containerd

1
2
sed -i 's/snapshotter = "nydus"/snapshotter = ""/g' /etc/containerd/config.toml
systemctl restart containerd.service

Wait a while, then turn the nydus configuration back on.

1
2
sed -i 's/snapshotter = ""/snapshotter = "nydus"/g' /etc/containerd/config.toml
systemctl restart containerd.service

8.7 The application will not start, reporting bucket: not found

  • Error message
1
containerd[937]: time="2024-01-07T08:17:13.044578960+08:00" level=error msg="RunPodSandbox for &PodSandboxMetadata{Name:kube-scheduler-k8s-master-03,Uid:d63b14268dcd89918c2eba5fa110d396,Namespace:kube-system,Attempt:2,} failed, error" error="rpc error: code = NotFound desc = failed to create containerd container: create snapshot: missing parent \"k8s.io/14/sha256:e3e5579ddd43c08e4b5c74dc12941a4ef656fab070b1087a1fd5a8a836b71e7d\" bucket: not found"
  • How to handle it

Go to the host and pull the image directly to see whether it can be pulled successfully.

If it still fails, try cleaning up the image and trying again.

Wipe Containerd’s root directory and reboot the machine; this has been tested and works.

8.8 input/output error when the application starts

  • Error message
1
FATA[0001] mount callback failed on /run/user/0/containerd-mount3967735320: read /run/user/0/containerd-mount3967735320/etc/group: input/output error
  • How to handle it

Clean up Containerd’s root directory and reboot the machine; this has been tested and works.

1
2
3
systemctl disable nydus-snapshotter
systemctl disable containerd
systemctl disable kubelet
1
reboot
1
2
3
4
rm -rf /data/containerd/*
systemctl enable nydus-snapshotter
systemctl enable containerd
systemctl enable kubelet
1
reboot

8.9 Error reported when nydus-snapshotter starts

  • Error message
1
2
3
journalctl -u nydus-snapshotter.service | grep error

Aug 08 19:15:01 k8s-4090-10 containerd-nydus-grpc[5151]: time="2024-08-08T19:15:01.311252782+08:00" level=error msg="failed to destroy cgroup, err cgroups: unable to remove path \"/sys/fs/cgroup/system.slice/nydusd\": still contains running processes"
  • How to handle it

First stop nydus-snapshotter and the processes related to nydus

1
2
3
systemctl stop nydus-snapshotter

ps aux |grep nydus

Delete the cgroup that reported the error

1
rmdir /sys/fs/cgroup/system.slice/nydusd

Restart Nydus-snapshotter

1
systemctl restart nydus-snapshotter

微信公众号
WRITTEN BY
微信公众号