diff --git a/csi/Dockerfile b/csi/Dockerfile index 11f6f12f..eea44a9e 100644 --- a/csi/Dockerfile +++ b/csi/Dockerfile @@ -1,14 +1,15 @@ # Compile stage -FROM golang:buster AS build +FROM golang:bookworm AS build ADD go.sum go.mod /app/ RUN cd /app; CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go mod download -x ADD . /app -RUN perl -i -e '$/ = undef; while(<>) { s/\n\s*(\{\s*\n)/$1\n/g; s/\}(\s*\n\s*)else\b/$1} else/g; print; }' `find /app -name '*.go'` -RUN cd /app; CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o vitastor-csi +RUN perl -i -e '$/ = undef; while(<>) { s/\n\s*(\{\s*\n)/$1\n/g; s/\}(\s*\n\s*)else\b/$1} else/g; print; }' `find /app -name '*.go'` && \ + cd /app && \ + CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o vitastor-csi # Final stage -FROM debian:buster +FROM debian:bookworm LABEL maintainers="Vitaliy Filippov " LABEL description="Vitastor CSI Driver" @@ -18,19 +19,30 @@ ENV CSI_ENDPOINT="" RUN apt-get update && \ apt-get install -y wget && \ - (echo deb http://deb.debian.org/debian buster-backports main > /etc/apt/sources.list.d/backports.list) && \ (echo "APT::Install-Recommends false;" > /etc/apt/apt.conf) && \ apt-get update && \ - apt-get install -y e2fsprogs xfsprogs kmod && \ + apt-get install -y e2fsprogs xfsprogs kmod iproute2 \ + # dependencies of qemu-storage-daemon + libnuma1 liburing2 libglib2.0-0 libfuse3-3 libaio1 libzstd1 libnettle8 \ + libgmp10 libhogweed6 libp11-kit0 libidn2-0 libunistring2 libtasn1-6 libpcre2-8-0 libffi8 && \ apt-get clean && \ (echo options nbd nbds_max=128 > /etc/modprobe.d/nbd.conf) COPY --from=build /app/vitastor-csi /bin/ -RUN (echo deb http://vitastor.io/debian buster main > /etc/apt/sources.list.d/vitastor.list) && \ +RUN (echo deb http://vitastor.io/debian bookworm main > /etc/apt/sources.list.d/vitastor.list) && \ + ((echo 'Package: *'; echo 'Pin: origin "vitastor.io"'; echo 'Pin-Priority: 1000') > /etc/apt/preferences.d/vitastor.pref) && \ wget -q -O /etc/apt/trusted.gpg.d/vitastor.gpg https://vitastor.io/debian/pubkey.gpg && \ apt-get update && \ apt-get install -y vitastor-client && \ + apt-get download qemu-system-common && \ + apt-get download qemu-block-extra && \ + dpkg -x qemu-system-common*.deb tmp1 && \ + dpkg -x qemu-block-extra*.deb tmp1 && \ + cp -a tmp1/usr/bin/qemu-storage-daemon /usr/bin/ && \ + mkdir -p /usr/lib/x86_64-linux-gnu/qemu && \ + cp -a tmp1/usr/lib/x86_64-linux-gnu/qemu/block-vitastor.so /usr/lib/x86_64-linux-gnu/qemu/ && \ + rm -rf tmp1 *.deb && \ apt-get clean ENTRYPOINT ["/bin/vitastor-csi"] diff --git a/csi/deploy/001-csi-config-map.yaml b/csi/deploy/001-csi-config-map.yaml index 7a39523b..f9b4529e 100644 --- a/csi/deploy/001-csi-config-map.yaml +++ b/csi/deploy/001-csi-config-map.yaml @@ -2,6 +2,7 @@ apiVersion: v1 kind: ConfigMap data: + # You can add multiple configuration files here to use a multi-cluster setup vitastor.conf: |- {"etcd_address":"http://192.168.7.2:2379","etcd_prefix":"/vitastor"} metadata: diff --git a/csi/deploy/004-csi-nodeplugin.yaml b/csi/deploy/004-csi-nodeplugin.yaml index 8c40ca30..1fbfa611 100644 --- a/csi/deploy/004-csi-nodeplugin.yaml +++ b/csi/deploy/004-csi-nodeplugin.yaml @@ -82,6 +82,8 @@ spec: name: host-sys - mountPath: /run/mount name: host-mount + - mountPath: /run/vitastor-csi + name: run-vitastor-csi - mountPath: /lib/modules name: lib-modules readOnly: true @@ -132,6 +134,9 @@ spec: - name: host-mount hostPath: path: /run/mount + - name: run-vitastor-csi + hostPath: + path: /run/vitastor-csi - name: lib-modules hostPath: path: /lib/modules diff --git a/csi/src/controllerserver.go b/csi/src/controllerserver.go index f3075d48..4b213a25 100644 --- a/csi/src/controllerserver.go +++ b/csi/src/controllerserver.go @@ -90,7 +90,7 @@ func GetConnectionParams(params map[string]string) (map[string]string, error) switch config["etcd_address"].(type) { case string: - url := strings.Trim(config["etcd_address"].(string), " \t\r\n") + url := strings.TrimSpace(config["etcd_address"].(string)) if (url != "") { etcdUrl = strings.Split(url, ",") @@ -105,24 +105,28 @@ func GetConnectionParams(params map[string]string) (map[string]string, error) return ctxVars, nil } +func system(program string, args ...string) ([]byte, error) +{ + c := exec.Command(program, args...) + var stdout, stderr bytes.Buffer + c.Stdout, c.Stderr = &stdout, &stderr + err := c.Run() + if (err != nil) + { + stdoutStr, stderrStr := string(stdout.Bytes()), string(stderr.Bytes()) + klog.Errorf(program+" "+strings.Join(args, " ")+" failed: %s, status %s\n", stdoutStr+stderrStr, err) + return nil, status.Error(codes.Internal, stdoutStr+stderrStr+" (status "+err.Error()+")") + } + return stdout.Bytes(), nil +} + func invokeCLI(ctxVars map[string]string, args []string) ([]byte, error) { if (ctxVars["configPath"] != "") { args = append(args, "--config_path", ctxVars["configPath"]) } - c := exec.Command("/usr/bin/vitastor-cli", args...) - var stdout, stderr bytes.Buffer - c.Stdout = &stdout - c.Stderr = &stderr - err := c.Run() - stderrStr := string(stderr.Bytes()) - if (err != nil) - { - klog.Errorf("vitastor-cli %s failed: %s, status %s\n", strings.Join(args, " "), stderrStr, err) - return nil, status.Error(codes.Internal, stderrStr+" (status "+err.Error()+")") - } - return stdout.Bytes(), nil + return system("/usr/bin/vitastor-cli", args...) } // Create the volume diff --git a/csi/src/nodeserver.go b/csi/src/nodeserver.go index c84ca6a5..e89a39db 100644 --- a/csi/src/nodeserver.go +++ b/csi/src/nodeserver.go @@ -5,11 +5,14 @@ package vitastor import ( "context" + "errors" + "encoding/json" "os" "os/exec" - "encoding/json" + "path/filepath" + "strconv" "strings" - "bytes" + "syscall" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -25,16 +28,91 @@ import ( type NodeServer struct { *Driver + useVduse bool + stateDir string mounter mount.Interface } +type DeviceState struct +{ + ConfigPath string `json:"configPath"` + VdpaId string `json:"vdpaId"` + Image string `json:"image"` + Blockdev string `json:"blockdev"` + Readonly bool `json:"readonly"` + PidFile string `json:"pidFile"` +} + // NewNodeServer create new instance node func NewNodeServer(driver *Driver) *NodeServer { - return &NodeServer{ + stateDir := os.Getenv("STATE_DIR") + if (stateDir == "") + { + stateDir = "/run/vitastor-csi" + } + if (stateDir[len(stateDir)-1] != '/') + { + stateDir += "/" + } + ns := &NodeServer{ Driver: driver, + useVduse: checkVduseSupport(), + stateDir: stateDir, mounter: mount.New(""), } + if (ns.useVduse) + { + ns.restoreVduseDaemons() + } + return ns +} + +func checkVduseSupport() bool +{ + // Check VDUSE support (vdpa, vduse, virtio-vdpa kernel modules) + vduse := true + for _, mod := range []string{"vdpa", "vduse", "virtio-vdpa"} + { + _, err := os.Stat("/sys/module/"+mod) + if (err != nil) + { + if (!errors.Is(err, os.ErrNotExist)) + { + klog.Errorf("failed to check /sys/module/%s: %v", mod, err) + } + c := exec.Command("/sbin/modprobe", mod) + c.Stdout = os.Stderr + c.Stderr = os.Stderr + err := c.Run() + if (err != nil) + { + klog.Errorf("/sbin/modprobe %s failed: %v", mod, err) + vduse = false + break + } + } + } + // Check that vdpa tool functions + if (vduse) + { + c := exec.Command("/sbin/vdpa", "-j", "dev") + c.Stderr = os.Stderr + err := c.Run() + if (err != nil) + { + klog.Errorf("/sbin/vdpa -j dev failed: %v", err) + vduse = false + } + } + if (!vduse) + { + klog.Errorf( + "Your host apparently has no VDUSE support. VDUSE support disabled, NBD will be used to map devices."+ + " For VDUSE you need at least Linux 5.15 and the following kernel modules: vdpa, virtio-vdpa, vduse.", + ) + } + return vduse } // NodeStageVolume mounts the volume to a staging path on the node. @@ -61,6 +139,303 @@ func Contains(list []string, s string) bool return false } +func (ns *NodeServer) mapNbd(volName string, ctxVars map[string]string, readonly bool) (string, error) +{ + // Map NBD device + // FIXME: Check if already mapped + args := []string{ + "map", "--image", volName, + } + if (ctxVars["configPath"] != "") + { + args = append(args, "--config_path", ctxVars["configPath"]) + } + if (readonly) + { + args = append(args, "--readonly", "1") + } + dev, err := system("/usr/bin/vitastor-nbd", args...) + return strings.TrimSpace(string(dev)), err +} + +func (ns *NodeServer) unmapNbd(devicePath string) +{ + // unmap NBD device + unmapOut, unmapErr := exec.Command("/usr/bin/vitastor-nbd", "unmap", devicePath).CombinedOutput() + if (unmapErr != nil) + { + klog.Errorf("failed to unmap NBD device %s: %s, error: %v", devicePath, unmapOut, unmapErr) + } +} + +func findByPidFile(pidFile string) (*os.Process, error) +{ + pidBuf, err := os.ReadFile(pidFile) + if (err != nil) + { + return nil, err + } + pid, err := strconv.ParseInt(strings.TrimSpace(string(pidBuf)), 0, 64) + if (err != nil) + { + return nil, err + } + proc, err := os.FindProcess(int(pid)) + if (err != nil) + { + return nil, err + } + return proc, nil +} + +func killByPidFile(pidFile string) error +{ + proc, err := findByPidFile(pidFile) + if (err != nil) + { + return err + } + return proc.Signal(syscall.SIGTERM) +} + +func startStorageDaemon(vdpaId, volName, pidFile, configPath string, readonly bool) error +{ + // Start qemu-storage-daemon + blockSpec := map[string]interface{}{ + "node-name": "disk1", + "driver": "vitastor", + "image": volName, + "cache": map[string]bool{ + "direct": true, + "no-flush": false, + }, + "discard": "unmap", + } + if (configPath != "") + { + blockSpec["config-path"] = configPath + } + blockSpecJson, _ := json.Marshal(blockSpec) + writable := "true" + if (readonly) + { + writable = "false" + } + _, err := system( + "/usr/bin/qemu-storage-daemon", "--daemonize", "--pidfile", pidFile, "--blockdev", string(blockSpecJson), + "--export", "vduse-blk,id="+vdpaId+",node-name=disk1,name="+vdpaId+",num-queues=16,queue-size=128,writable="+writable, + ) + return err +} + +func (ns *NodeServer) mapVduse(volName string, ctxVars map[string]string, readonly bool) (string, string, error) +{ + // Generate state file + stateFd, err := os.CreateTemp(ns.stateDir, "vitastor-vduse-*.json") + if (err != nil) + { + return "", "", status.Error(codes.Internal, err.Error()) + } + stateFile := stateFd.Name() + stateFd.Close() + vdpaId := filepath.Base(stateFile) + vdpaId = vdpaId[0:len(vdpaId)-5] // remove ".json" + pidFile := ns.stateDir + vdpaId + ".pid" + // Map VDUSE device via qemu-storage-daemon + err = startStorageDaemon(vdpaId, volName, pidFile, ctxVars["configPath"], readonly) + if (err == nil) + { + // Add device to VDPA bus + _, err = system("/sbin/vdpa", "-j", "dev", "add", "name", vdpaId, "mgmtdev", "vduse") + if (err == nil) + { + // Find block device name + matches, err := filepath.Glob("/sys/bus/vdpa/devices/"+vdpaId+"/virtio*/block/*") + if (err == nil && len(matches) == 0) + { + err = errors.New("/sys/bus/vdpa/devices/"+vdpaId+"/virtio*/block/* is not found") + } + if (err == nil) + { + blockdev := "/dev/"+filepath.Base(matches[0]) + _, err = os.Stat(blockdev) + if (err == nil) + { + // Generate state file + stateJSON, _ := json.Marshal(&DeviceState{ + ConfigPath: ctxVars["configPath"], + VdpaId: vdpaId, + Image: volName, + Blockdev: blockdev, + Readonly: readonly, + PidFile: pidFile, + }) + err = os.WriteFile(stateFile, stateJSON, 0600) + if (err == nil) + { + return blockdev, vdpaId, nil + } + } + } + if (err != nil) + { + err = status.Error(codes.Internal, err.Error()) + } + } + if (err != nil) + { + killErr := killByPidFile(pidFile) + if (killErr != nil) + { + klog.Errorf("Failed to kill started qemu-storage-daemon: %v", killErr) + } + os.Remove(stateFile) + os.Remove(pidFile) + } + } + return "", "", err +} + +func (ns *NodeServer) unmapVduse(devicePath string) +{ + if (len(devicePath) < 6 || devicePath[0:6] != "/dev/v") + { + klog.Errorf("%s does not start with /dev/v", devicePath) + return + } + vduseDev, err := os.Readlink("/sys/block/"+devicePath[5:]) + if (err != nil) + { + klog.Errorf("%s is not a symbolic link to VDUSE device (../devices/virtual/vduse/xxx): %v", devicePath, err) + return + } + vdpaId := "" + p := strings.Index(vduseDev, "/vduse/") + if (p >= 0) + { + vduseDev = vduseDev[p+7:] + p = strings.Index(vduseDev, "/") + if (p >= 0) + { + vdpaId = vduseDev[0:p] + } + } + if (vdpaId == "") + { + klog.Errorf("%s is not a symbolic link to VDUSE device (../devices/virtual/vduse/xxx), but is %v", devicePath, vduseDev) + return + } + ns.unmapVduseById(vdpaId) +} + +func (ns *NodeServer) unmapVduseById(vdpaId string) +{ + _, err := os.Stat("/sys/bus/vdpa/devices/"+vdpaId) + if (err != nil) + { + klog.Errorf("failed to stat /sys/bus/vdpa/devices/"+vdpaId+": %v", err) + } + else + { + _, _ = system("/sbin/vdpa", "-j", "dev", "del", vdpaId) + } + stateFile := ns.stateDir + vdpaId + ".json" + os.Remove(stateFile) + pidFile := ns.stateDir + vdpaId + ".pid" + _, err = os.Stat(pidFile) + if (os.IsNotExist(err)) + { + // ok, already killed + } + else if (err != nil) + { + klog.Errorf("Failed to stat %v: %v", pidFile, err) + return + } + else + { + err = killByPidFile(pidFile) + if (err != nil) + { + klog.Errorf("Failed to kill started qemu-storage-daemon: %v", err) + } + os.Remove(pidFile) + } +} + +func (ns *NodeServer) restoreVduseDaemons() +{ + pattern := ns.stateDir+"vitastor-vduse-*.json" + matches, err := filepath.Glob(pattern) + if (err != nil) + { + klog.Errorf("failed to list %s: %v", pattern, err) + } + if (len(matches) == 0) + { + return + } + devList := make(map[string]interface{}) + // example output: {"dev":{"test1":{"type":"block","mgmtdev":"vduse","vendor_id":0,"max_vqs":16,"max_vq_size":128}}} + devListJSON, err := system("/sbin/vdpa", "-j", "dev", "list") + if (err != nil) + { + return + } + err = json.Unmarshal(devListJSON, &devList) + devs, ok := devList["dev"].(map[string]interface{}) + if (err != nil || !ok) + { + klog.Errorf("/sbin/vdpa -j dev list returned bad JSON (error %v): %v", err, string(devListJSON)) + return + } + for _, stateFile := range matches + { + vdpaId := filepath.Base(stateFile) + vdpaId = vdpaId[0:len(vdpaId)-5] + // Check if VDPA device is still added to the bus + if (devs[vdpaId] != nil) + { + // Check if the storage daemon is still active + pidFile := ns.stateDir + vdpaId + ".pid" + exists := false + proc, err := findByPidFile(pidFile) + if (err == nil) + { + exists = proc.Signal(syscall.Signal(0)) == nil + } + if (!exists) + { + // Restart daemon + stateJSON, err := os.ReadFile(stateFile) + if (err != nil) + { + klog.Warningf("error reading state file %v: %v", stateFile, err) + } + else + { + var state DeviceState + err := json.Unmarshal(stateJSON, &state) + if (err != nil) + { + klog.Warningf("state file %v contains invalid JSON (error %v): %v", stateFile, err, string(stateJSON)) + } + else + { + klog.Warningf("restarting storage daemon for volume %v (VDPA ID %v)", state.Image, vdpaId) + _ = startStorageDaemon(vdpaId, state.Image, pidFile, state.ConfigPath, state.Readonly) + } + } + } + } + else + { + // Unused, clean it up + ns.unmapVduseById(vdpaId) + } + } +} + // NodePublishVolume mounts the volume mounted to the staging path to the target path func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublishVolumeRequest) (*csi.NodePublishVolumeResponse, error) { @@ -120,30 +495,19 @@ func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis return nil, err } - // Map NBD device - // FIXME: Check if already mapped - args := []string{ - "map", "--image", volName, - } - if (ctxVars["configPath"] != "") + var devicePath, vdpaId string + if (!ns.useVduse) { - args = append(args, "--config_path", ctxVars["configPath"]) + devicePath, err = ns.mapNbd(volName, ctxVars, req.GetReadonly()) } - if (req.GetReadonly()) + else { - args = append(args, "--readonly", "1") + devicePath, vdpaId, err = ns.mapVduse(volName, ctxVars, req.GetReadonly()) } - c := exec.Command("/usr/bin/vitastor-nbd", args...) - var stdout, stderr bytes.Buffer - c.Stdout, c.Stderr = &stdout, &stderr - err = c.Run() - stdoutStr, stderrStr := string(stdout.Bytes()), string(stderr.Bytes()) if (err != nil) { - klog.Errorf("vitastor-nbd map failed: %s, status %s\n", stdoutStr+stderrStr, err) - return nil, status.Error(codes.Internal, stdoutStr+stderrStr+" (status "+err.Error()+")") + return nil, err } - devicePath := strings.TrimSpace(stdoutStr) diskMounter := &mount.SafeFormatAndMount{Interface: ns.mounter, Exec: utilexec.New()} if (isBlock) @@ -225,11 +589,13 @@ func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis return &csi.NodePublishVolumeResponse{}, nil unmap: - // unmap NBD device - unmapOut, unmapErr := exec.Command("/usr/bin/vitastor-nbd", "unmap", devicePath).CombinedOutput() - if (unmapErr != nil) + if (!ns.useVduse || len(devicePath) >= 8 && devicePath[0:8] == "/dev/nbd") { - klog.Errorf("failed to unmap NBD device %s: %s, error: %v", devicePath, unmapOut, unmapErr) + ns.unmapNbd(devicePath) + } + else + { + ns.unmapVduseById(vdpaId) } return nil, status.Error(codes.Internal, err.Error()) } @@ -250,7 +616,10 @@ func (ns *NodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu } if (devicePath == "") { - return nil, status.Error(codes.NotFound, "Volume not mounted") + // volume not mounted + klog.Warningf("%s is not a mountpoint, deleting", targetPath) + os.Remove(targetPath) + return &csi.NodeUnpublishVolumeResponse{}, nil } // unmount err = mount.CleanupMountPoint(targetPath, ns.mounter, false) @@ -261,10 +630,13 @@ func (ns *NodeServer) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu // unmap NBD device if (refCount == 1) { - unmapOut, unmapErr := exec.Command("/usr/bin/vitastor-nbd", "unmap", devicePath).CombinedOutput() - if (unmapErr != nil) + if (!ns.useVduse) { - klog.Errorf("failed to unmap NBD device %s: %s, error: %v", devicePath, unmapOut, unmapErr) + ns.unmapNbd(devicePath) + } + else + { + ns.unmapVduse(devicePath) } } return &csi.NodeUnpublishVolumeResponse{}, nil diff --git a/docs/config/client.en.md b/docs/config/client.en.md index b64ea4a9..986b40d5 100644 --- a/docs/config/client.en.md +++ b/docs/config/client.en.md @@ -15,6 +15,9 @@ the cluster. - [client_max_buffered_bytes](#client_max_buffered_bytes) - [client_max_buffered_ops](#client_max_buffered_ops) - [client_max_writeback_iodepth](#client_max_writeback_iodepth) +- [nbd_timeout](#nbd_timeout) +- [nbd_max_devices](#nbd_max_devices) +- [nbd_max_part](#nbd_max_part) ## client_max_dirty_bytes @@ -101,3 +104,34 @@ Multiple consecutive modified data regions are counted as 1 write here. - Can be changed online: yes Maximum number of parallel writes when flushing buffered data to the server. + +## nbd_timeout + +- Type: seconds +- Default: 300 + +Timeout for I/O operations for [NBD](../usage/nbd.en.md). If an operation +executes for longer than this timeout, including when your cluster is just +temporarily down for more than timeout, the NBD device will detach by itself +(and possibly break the mounted file system). + +You can set timeout to 0 to never detach, but in that case you won't be +able to remove the kernel device at all if the NBD process dies - you'll have +to reboot the host. + +## nbd_max_devices + +- Type: integer +- Default: 64 + +Maximum number of NBD devices in the system. This value is passed as +`nbds_max` parameter for the nbd kernel module when vitastor-nbd autoloads it. + +## nbd_max_part + +- Type: integer +- Default: 3 + +Maximum number of partitions per NBD device. This value is passed as +`max_part` parameter for the nbd kernel module when vitastor-nbd autoloads it. +Note that (nbds_max)*(1+max_part) usually can't exceed 256. diff --git a/docs/config/client.ru.md b/docs/config/client.ru.md index 22704dd3..a8652de9 100644 --- a/docs/config/client.ru.md +++ b/docs/config/client.ru.md @@ -15,6 +15,9 @@ - [client_max_buffered_bytes](#client_max_buffered_bytes) - [client_max_buffered_ops](#client_max_buffered_ops) - [client_max_writeback_iodepth](#client_max_writeback_iodepth) +- [nbd_timeout](#nbd_timeout) +- [nbd_max_devices](#nbd_max_devices) +- [nbd_max_part](#nbd_max_part) ## client_max_dirty_bytes @@ -101,3 +104,34 @@ - Можно менять на лету: да Максимальное число параллельных операций записи при сбросе буферов на сервер. + +## nbd_timeout + +- Тип: секунды +- Значение по умолчанию: 300 + +Таймаут для операций чтения/записи через [NBD](../usage/nbd.ru.md). Если +операция выполняется дольше таймаута, включая временную недоступность +кластера на время, большее таймаута, NBD-устройство отключится само собой +(и, возможно, сломает примонтированную ФС). + +Вы можете установить таймаут в 0, чтобы никогда не отключать устройство по +таймауту, но в этом случае вы вообще не сможете удалить устройство, если +процесс NBD умрёт - вам придётся перезагружать сервер. + +## nbd_max_devices + +- Тип: целое число +- Значение по умолчанию: 64 + +Максимальное число NBD-устройств в системе. Данное значение передаётся +модулю ядра nbd как параметр `nbds_max`, когда его загружает vitastor-nbd. + +## nbd_max_part + +- Тип: целое число +- Значение по умолчанию: 3 + +Максимальное число разделов на одном NBD-устройстве. Данное значение передаётся +модулю ядра nbd как параметр `max_part`, когда его загружает vitastor-nbd. +Имейте в виду, что (nbds_max)*(1+max_part) обычно не может превышать 256. diff --git a/docs/config/src/client.en.md b/docs/config/src/client.en.md index 2e94c864..8ed856e5 100644 --- a/docs/config/src/client.en.md +++ b/docs/config/src/client.en.md @@ -1,4 +1,4 @@ # Client Parameters -These parameters apply only to clients and affect their interaction with -the cluster. +These parameters apply only to Vitastor clients (QEMU, fio, NBD and so on) and +affect their interaction with the cluster. diff --git a/docs/config/src/client.ru.md b/docs/config/src/client.ru.md index 8b9c1eec..8c613aef 100644 --- a/docs/config/src/client.ru.md +++ b/docs/config/src/client.ru.md @@ -1,4 +1,4 @@ # Параметры клиентского кода -Данные параметры применяются только к клиентам Vitastor (QEMU, fio, NBD) и +Данные параметры применяются только к клиентам Vitastor (QEMU, fio, NBD и т.п.) и затрагивают логику их работы с кластером. diff --git a/docs/config/src/client.yml b/docs/config/src/client.yml index 99b696fc..3bebd783 100644 --- a/docs/config/src/client.yml +++ b/docs/config/src/client.yml @@ -122,3 +122,47 @@ Maximum number of parallel writes when flushing buffered data to the server. info_ru: | Максимальное число параллельных операций записи при сбросе буферов на сервер. +- name: nbd_timeout + type: sec + default: 300 + online: false + info: | + Timeout for I/O operations for [NBD](../usage/nbd.en.md). If an operation + executes for longer than this timeout, including when your cluster is just + temporarily down for more than timeout, the NBD device will detach by itself + (and possibly break the mounted file system). + + You can set timeout to 0 to never detach, but in that case you won't be + able to remove the kernel device at all if the NBD process dies - you'll have + to reboot the host. + info_ru: | + Таймаут для операций чтения/записи через [NBD](../usage/nbd.ru.md). Если + операция выполняется дольше таймаута, включая временную недоступность + кластера на время, большее таймаута, NBD-устройство отключится само собой + (и, возможно, сломает примонтированную ФС). + + Вы можете установить таймаут в 0, чтобы никогда не отключать устройство по + таймауту, но в этом случае вы вообще не сможете удалить устройство, если + процесс NBD умрёт - вам придётся перезагружать сервер. +- name: nbd_max_devices + type: int + default: 64 + online: false + info: | + Maximum number of NBD devices in the system. This value is passed as + `nbds_max` parameter for the nbd kernel module when vitastor-nbd autoloads it. + info_ru: | + Максимальное число NBD-устройств в системе. Данное значение передаётся + модулю ядра nbd как параметр `nbds_max`, когда его загружает vitastor-nbd. +- name: nbd_max_part + type: int + default: 3 + online: false + info: | + Maximum number of partitions per NBD device. This value is passed as + `max_part` parameter for the nbd kernel module when vitastor-nbd autoloads it. + Note that (nbds_max)*(1+max_part) usually can't exceed 256. + info_ru: | + Максимальное число разделов на одном NBD-устройстве. Данное значение передаётся + модулю ядра nbd как параметр `max_part`, когда его загружает vitastor-nbd. + Имейте в виду, что (nbds_max)*(1+max_part) обычно не может превышать 256. diff --git a/docs/installation/kubernetes.en.md b/docs/installation/kubernetes.en.md index 0a6a166d..aeeaade9 100644 --- a/docs/installation/kubernetes.en.md +++ b/docs/installation/kubernetes.en.md @@ -19,6 +19,14 @@ for i in ./???-*.yaml; do kubectl apply -f $i; done After that you'll be able to create PersistentVolumes. +**Important:** For best experience, use Linux kernel at least 5.15 with [VDUSE](../usage/qemu.en.md#vduse) +kernel modules enabled (vdpa, vduse, virtio-vdpa). If your distribution doesn't +have them pre-built - build them yourself ([instructions](../usage/qemu.en.md#vduse)), +I promise it's worth it :-). When VDUSE is unavailable, CSI driver uses [NBD](../usage/nbd.en.md) +to map Vitastor devices. NBD is slower and prone to timeout issues: if Vitastor +cluster becomes unresponsible for more than [nbd_timeout](../config/client.en.md#nbd_timeout), +the NBD device detaches and breaks pods using it. + ## Features Vitastor CSI supports: @@ -27,5 +35,8 @@ Vitastor CSI supports: - Raw block RWX (ReadWriteMany) volumes. Example: [PVC](../../csi/deploy/example-pvc-block.yaml), [pod](../../csi/deploy/example-test-pod-block.yaml) - Volume expansion - Volume snapshots. Example: [snapshot class](../../csi/deploy/example-snapshot-class.yaml), [snapshot](../../csi/deploy/example-snapshot.yaml), [clone](../../csi/deploy/example-snapshot-clone.yaml) +- [VDUSE](../usage/qemu.en.md#vduse) (preferred) and [NBD](../usage/nbd.en.md) device mapping methods +- Upgrades with VDUSE - new handler processes are restarted when CSI pods are restarted themselves +- Multiple clusters by using multiple configuration files in ConfigMap. Remember that to use snapshots with CSI you also have to install [Snapshot Controller and CRDs](https://kubernetes-csi.github.io/docs/snapshot-controller.html#deployment). diff --git a/docs/installation/kubernetes.ru.md b/docs/installation/kubernetes.ru.md index 8e83a08a..966df002 100644 --- a/docs/installation/kubernetes.ru.md +++ b/docs/installation/kubernetes.ru.md @@ -19,6 +19,14 @@ for i in ./???-*.yaml; do kubectl apply -f $i; done После этого вы сможете создавать PersistentVolume. +**Важно:** Лучше всего использовать ядро Linux версии не менее 5.15 с включёнными модулями +[VDUSE](../usage/qemu.ru.md#vduse) (vdpa, vduse, virtio-vdpa). Если в вашем дистрибутиве +они не собраны из коробки - соберите их сами, обещаю, что это стоит того ([инструкция](../usage/qemu.ru.md#vduse)) :-). +Когда VDUSE недоступно, CSI-плагин использует [NBD](../usage/nbd.ru.md) для подключения +дисков, а NBD медленнее и имеет проблему таймаута - если кластер остаётся недоступным +дольше, чем [nbd_timeout](../config/client.ru.md#nbd_timeout), NBD-устройство отключается +и ломает поды, использующие его. + ## Возможности CSI-плагин Vitastor поддерживает: @@ -27,5 +35,8 @@ CSI-плагин Vitastor поддерживает: - Сырые блочные RWX (ReadWriteMany) тома. Пример: [PVC](../../csi/deploy/example-pvc-block.yaml), [под](../../csi/deploy/example-test-pod-block.yaml) - Расширение размера томов - Снимки томов. Пример: [класс снимков](../../csi/deploy/example-snapshot-class.yaml), [снимок](../../csi/deploy/example-snapshot.yaml), [клон снимка](../../csi/deploy/example-snapshot-clone.yaml) +- Способы подключения устройств [VDUSE](../usage/qemu.ru.md#vduse) (предпочитаемый) и [NBD](../usage/nbd.ru.md) +- Обновление при использовании VDUSE - новые процессы-обработчики устройств успешно перезапускаются вместе с самими подами CSI +- Несколько кластеров через задание нескольких файлов конфигурации в ConfigMap. Не забывайте, что для использования снимков нужно сначала установить [контроллер снимков и CRD](https://kubernetes-csi.github.io/docs/snapshot-controller.html#deployment). diff --git a/docs/intro/architecture.ru.md b/docs/intro/architecture.ru.md index 911c3e1d..78a64f7c 100644 --- a/docs/intro/architecture.ru.md +++ b/docs/intro/architecture.ru.md @@ -54,7 +54,8 @@ виртуальные диски, их снимки и клоны. - **Драйвер QEMU** — подключаемый модуль QEMU, позволяющий QEMU/KVM виртуальным машинам работать с виртуальными дисками Vitastor напрямую из пространства пользователя с помощью клиентской - библиотеки, без необходимости отображения дисков в виде блочных устройств. + библиотеки, без необходимости отображения дисков в виде блочных устройств. Тот же драйвер + позволяет подключать диски в систему через [VDUSE](../usage/qemu.ru.md#vduse). - **vitastor-nbd** — утилита, позволяющая монтировать образы Vitastor в виде блочных устройств с помощью NBD (Network Block Device), на самом деле скорее работающего как "BUSE" (Block Device In Userspace). Модуля ядра Linux для выполнения той же задачи в Vitastor нет diff --git a/docs/usage/nbd.en.md b/docs/usage/nbd.en.md index b2e24cfb..1884fe2e 100644 --- a/docs/usage/nbd.en.md +++ b/docs/usage/nbd.en.md @@ -11,9 +11,9 @@ NBD stands for "Network Block Device", but in fact it also functions as "BUSE" NBD slighly lowers the performance due to additional overhead, but performance still remains decent (see an example [here](../performance/comparison1.en.md#vitastor-0-4-0-nbd)). -Vitastor Kubernetes CSI driver is based on NBD. +See also [VDUSE](qemu.en.md#vduse) as a better alternative to NBD. -See also [VDUSE](qemu.en.md#vduse). +Vitastor Kubernetes CSI driver uses NBD when VDUSE is unavailable. ## Map image diff --git a/docs/usage/nbd.ru.md b/docs/usage/nbd.ru.md index a4f5ea80..f7a7294c 100644 --- a/docs/usage/nbd.ru.md +++ b/docs/usage/nbd.ru.md @@ -14,9 +14,9 @@ NBD на данный момент необходимо, чтобы монтир NBD немного снижает производительность из-за дополнительных копирований памяти, но она всё равно остаётся на неплохом уровне (см. для примера [тест](../performance/comparison1.ru.md#vitastor-0-4-0-nbd)). -CSI-драйвер Kubernetes Vitastor основан на NBD. +Смотрите также [VDUSE](qemu.ru.md#vduse), как лучшую альтернативу NBD. -Смотрите также [VDUSE](qemu.ru.md#vduse). +CSI-драйвер Kubernetes Vitastor использует NBD, когда VDUSE недоступен. ## Подключить устройство