全部产品
Search
文档中心

Container Service for Kubernetes:Menggunakan multi-attach disk cloud NVMe dan Reservation untuk berbagi data antar aplikasi

更新时间:Nov 11, 2025

Jika Anda memerlukan beberapa node untuk membaca dan menulis secara konkuren ke disk cloud yang sama guna mencapai berbagi data yang efisien dan failover cepat, Anda dapat menggunakan fitur multi-attach untuk menyambungkan satu ESSD, ESSD AutoPL, atau jenis disk cloud lainnya ke beberapa node yang mendukung protokol NVMe dalam zona yang sama, atau menyambungkan satu ESSD penyimpanan redundan zona ke beberapa node dalam wilayah yang sama. Topik ini menjelaskan cara menggunakan fitur multi-attach dan Reservation disk cloud NVMe dalam kluster ACK.

Sebelum memulai

Untuk memanfaatkan fitur multi-attach dan Reservation disk cloud NVMe secara optimal, kami menyarankan agar Anda memahami informasi berikut sebelum membaca dokumen ini:

Skenario

Fitur multi-attach cocok untuk skenario berikut:

  • Berbagi data

    Berbagi data merupakan skenario penggunaan paling sederhana dari NVMe. Setelah data ditulis ke disk NVMe bersama dari satu node yang terhubung, semua node terhubung lainnya dapat mengakses data tersebut. Hal ini mengurangi biaya penyimpanan dan meningkatkan kinerja baca/tulis. Misalnya, satu citra kontainer yang kompatibel NVMe di cloud dapat dibaca dan dimuat oleh beberapa instance yang menjalankan sistem operasi yang sama.

  • Failover ketersediaan tinggi

    Ketersediaan layanan tinggi merupakan salah satu skenario aplikasi paling umum dari disk bersama. Basis data berbasis SAN tradisional, seperti Oracle Real Application Clusters (RAC), SAP High-performance ANalytic Appliance (HANA), dan basis data ketersediaan tinggi berbasis cloud-native, mungkin mengalami titik kegagalan tunggal (SPOF) dalam skenario bisnis nyata. Disk NVMe bersama dapat digunakan untuk memastikan kelangsungan bisnis dan ketersediaan tinggi dalam hal penyimpanan dan jaringan berbasis cloud jika terjadi SPOF. Node komputasi sering mengalami gangguan, downtime, dan kegagalan perangkat keras. Untuk mencapai ketersediaan tinggi node komputasi, Anda dapat menerapkan bisnis dalam mode utama/sekunder.

    Misalnya, dalam skenario basis data, jika database utama gagal, database sekunder akan segera mengambil alih untuk menyediakan layanan. Setelah instance yang menjalankan database utama dialihkan ke instance yang menjalankan database sekunder, Anda dapat menjalankan perintah Persistent Reservation (PR) NVMe untuk mencabut izin tulis pada database utama yang rusak. Hal ini membantu mencegah data ditulis ke database utama yang rusak, sehingga memastikan konsistensi data. Gambar berikut menunjukkan proses failover.

    Catatan

    PR adalah bagian dari protokol NVMe yang dapat mengontrol secara tepat izin baca dan tulis pada disk cloud untuk memastikan bahwa node komputasi dapat menulis data sesuai harapan. Untuk informasi selengkapnya, lihat NVM Express Base Specification.

    1. Instansiasi database utama (Database Instance 1) gagal, sehingga layanan berhenti.

    2. Jalankan perintah PR NVMe untuk mencegah data ditulis ke Database Instance 1 dan mengizinkan data ditulis ke instansiasi database sekunder (Database Instance 2).

    3. Pulihkan Database Instance 2 ke status yang sama dengan Database Instance 1 menggunakan berbagai metode seperti pemutaran ulang log.

    4. Database Instance 2 mengambil alih sebagai instansiasi database utama untuk menyediakan layanan eksternal.

  • Akselerasi cache data terdistribusi

    Disk cloud yang diaktifkan multi-attach memberikan kinerja tinggi, IOPS, dan throughput tinggi serta dapat memfasilitasi akselerasi kinerja untuk sistem penyimpanan berkecepatan lambat dan menengah. Misalnya, data lake umumnya dibangun di atas Object Storage Service (OSS). Setiap data lake dapat diakses secara simultan oleh banyak klien. Data lake memberikan throughput baca sekuensial tinggi dan throughput tulis append tinggi, tetapi memiliki throughput baca/tulis sekuensial rendah, latensi tinggi, dan kinerja baca/tulis acak rendah. Untuk meningkatkan secara signifikan kinerja akses dalam skenario seperti data lake, Anda dapat menyambungkan disk cloud berkecepatan tinggi yang diaktifkan multi-attach sebagai cache ke node komputasi.

  • Pembelajaran mesin

    Dalam skenario pembelajaran mesin, setelah sampel diberi label dan ditulis, sampel tersebut dipisahkan dan didistribusikan ke beberapa node untuk memfasilitasi komputasi terdistribusi paralel. Fitur multi-attach memungkinkan setiap node komputasi mengakses langsung sumber daya penyimpanan bersama tanpa perlu sering mentransmisikan data melalui jaringan. Hal ini mengurangi latensi transfer data dan mempercepat proses pelatihan model. Kombinasi kinerja tinggi dan fitur multi-attach memungkinkan disk cloud menyediakan solusi penyimpanan yang efisien dan fleksibel untuk skenario pembelajaran mesin, seperti tugas pelatihan model berskala besar yang memerlukan akses dan pemrosesan data berkecepatan tinggi. Solusi penyimpanan ini secara signifikan meningkatkan efisiensi dan efektivitas proses pembelajaran mesin.

Batasan

  • Satu disk cloud NVMe dapat disambungkan ke maksimal 16 Instance ECS dalam zona yang sama secara bersamaan.

  • Jika Anda ingin membaca dan menulis ke disk cloud dari beberapa node secara bersamaan, Anda harus memasang disk cloud menggunakan volumeDevices. Metode ini memasang disk cloud sebagai perangkat blok dan tidak mendukung akses melalui sistem file.

  • Untuk informasi lebih lanjut mengenai batasan, lihat Batasan fitur multi-attach.

Persiapan

  • Kluster ACK yang dikelola telah dibuat, dan versi Kubernetes kluster tersebut adalah 1.20 atau lebih baru. Untuk informasi selengkapnya, lihat Membuat Kluster ACK yang dikelola.

  • Komponen csi-plugin dan csi-provisioner telah diinstal, dan versi komponen tersebut adalah v1.24.10-7ae4421-aliyun atau lebih baru. Untuk informasi tentang cara memperbarui komponen csi-plugin dan csi-provisioner, lihat Mengelola komponen csi-plugin dan csi-provisioner.

  • Kluster berisi minimal dua node yang berada dalam zona yang sama dan mendukung fitur multi-attach. Untuk informasi mengenai keluarga instance yang mendukung fitur multi-attach, lihat Batasan fitur multi-attach.

  • Aplikasi bisnis yang memenuhi persyaratan berikut telah disiapkan dan dikemas menjadi gambar kontainer untuk diterapkan di kluster ACK:

    • Aplikasi mendukung akses data pada disk cloud yang sama dari beberapa replika secara bersamaan.

    • Aplikasi dapat memastikan konsistensi data dengan menggunakan fitur standar seperti NVMe Reservation.

Deskripsi penagihan

Fitur multi-attach tidak dikenakan biaya tambahan. Sumber daya yang mendukung protokol NVMe tetap ditagih berdasarkan metode penagihan aslinya. Untuk informasi selengkapnya mengenai penagihan disk cloud, lihat Volume Elastic Block Storage.

Contoh aplikasi

Topik ini menggunakan kode sumber dan Dockerfile dari contoh aplikasi berikut. Setelah aplikasi dibuat, unggah ke repository image untuk diterapkan di kluster. Dalam contoh aplikasi ini, beberapa replika secara bersama mengelola sebuah lease, tetapi hanya satu replika yang memegang lease tersebut. Jika replika tidak dapat berfungsi dengan baik, replika lain secara otomatis mengambil alih lease tersebut. Perhatikan hal berikut saat menulis aplikasi:

  • Dalam contoh ini, O_DIRECT digunakan untuk membuka perangkat blok guna operasi baca dan tulis guna mencegah cache apa pun memengaruhi pengujian.

  • Dalam contoh ini, antarmuka sederhana Reservation yang disediakan oleh kernel Linux digunakan. Anda juga dapat menggunakan salah satu metode berikut untuk menjalankan perintah terkait Reservation. Metode-metode ini memerlukan hak istimewa.

    • Kode C: ioctl(fd, NVME_IOCTL_IO_CMD, &cmd);

    • Antarmuka baris perintah: nvme-cli

  • Untuk informasi selengkapnya mengenai fitur NVMe Reservation, lihat Spesifikasi NVMe.

Perluas untuk melihat kode sumber contoh aplikasi

#define _GNU_SOURCE
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/pr.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <time.h>
#include <unistd.h>

const char *disk_device = "/dev/data-disk";
uint64_t magic = 0x4745D0C5CD9A2FA4;

void panic(const char *restrict format, ...) {
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    va_end(args);
    exit(EXIT_FAILURE);
}

struct lease {
    uint64_t magic;
    struct timespec acquire_time;
    char holder[64];
};

volatile bool shutdown = false;
void on_term(int signum) {
    shutdown = true;
}

struct lease *lease;
const size_t lease_alloc_size = 512;

void acquire_lease(int disk_fd) {
    int ret;

    struct pr_registration pr_reg = {
        .new_key = magic,
        .flags = PR_FL_IGNORE_KEY,
    };
    ret = ioctl(disk_fd, IOC_PR_REGISTER, &pr_reg);
    if (ret != 0)
        panic("failed to register (%d): %s\n", ret, strerror(errno));

    struct pr_preempt pr_pre = {
        .old_key = magic,
        .new_key = magic,
        .type  = PR_WRITE_EXCLUSIVE,
    };
    ret = ioctl(disk_fd, IOC_PR_PREEMPT, &pr_pre);
    if (ret != 0)
        panic("failed to preempt (%d): %s\n", ret, strerror(errno));

    // register again in case we preempted ourselves
    ret = ioctl(disk_fd, IOC_PR_REGISTER, &pr_reg);
    if (ret != 0)
        panic("failed to register (%d): %s\n", ret, strerror(errno));
    fprintf(stderr, "Register as key %lx\n", magic);


    struct pr_reservation pr_rev = {
        .key   = magic,
        .type  = PR_WRITE_EXCLUSIVE,
    };
    ret = ioctl(disk_fd, IOC_PR_RESERVE, &pr_rev);
    if (ret != 0)
        panic("failed to reserve (%d): %s\n", ret, strerror(errno));

    lease->magic = magic;
    gethostname(lease->holder, sizeof(lease->holder));

    while (!shutdown) {
        clock_gettime(CLOCK_MONOTONIC, &lease->acquire_time);
        ret = pwrite(disk_fd, lease, lease_alloc_size, 0);
        if (ret < 0)
            panic("failed to write lease: %s\n", strerror(errno));
        fprintf(stderr, "Refreshed lease\n");
        sleep(5);
    }
}

int timespec_compare(const struct timespec *a, const struct timespec *b) {
    if (a->tv_sec < b->tv_sec)
        return -1;
    if (a->tv_sec > b->tv_sec)
        return 1;
    if (a->tv_nsec < b->tv_nsec)
        return -1;
    if (a->tv_nsec > b->tv_nsec)
        return 1;
    return 0;
}

int main() {
    assert(lease_alloc_size >= sizeof(struct lease));
    lease = aligned_alloc(512, lease_alloc_size);
    if (lease == NULL)
        panic("failed to allocate memory\n");

    // char *reg_key_str = getenv("REG_KEY");
    // if (reg_key_str == NULL)
    //     panic("REG_KEY env not specified");

    // uint64_t reg_key = atoll(reg_key_str) | (magic << 32);
    // fprintf(stderr, "Will register as key %lx", reg_key);


    int disk_fd = open(disk_device, O_RDWR|O_DIRECT);
    if (disk_fd < 0)
        panic("failed to open disk: %s\n", strerror(errno));

    // setup signal handler
    struct sigaction sa = {
        .sa_handler = on_term,
    };
    sigaction(SIGTERM, &sa, NULL);
    sigaction(SIGINT, &sa, NULL);

    struct timespec last_active_local;
    struct timespec last_active_remote;

    int ret = pread(disk_fd, lease, lease_alloc_size, 0);
    if (ret < 0)
        panic("failed to read lease: %s\n", strerror(errno));

    if (lease->magic != magic) {
        // new disk, no lease
        acquire_lease(disk_fd);
    } else {
        // someone else has the lease
        while (!shutdown) {
            struct timespec now;
            clock_gettime(CLOCK_MONOTONIC, &now);
            if (timespec_compare(&lease->acquire_time, &last_active_remote)) {
                fprintf(stderr, "Remote %s refreshed lease\n", lease->holder);
                last_active_remote = lease->acquire_time;
                last_active_local = now;
            } else if (now.tv_sec - last_active_local.tv_sec > 20) {
                // remote is dead
                fprintf(stderr, "Remote is dead, preempting\n");
                acquire_lease(disk_fd);
                break;
            }
            sleep(5);
            int ret = pread(disk_fd, lease, lease_alloc_size, 0);
            if (ret < 0)
                panic("failed to read lease: %s\n", strerror(errno));
        }
    }

    close(disk_fd);
}
#!/bin/bash

set -e

DISK_DEVICE="/dev/data-disk"
MAGIC=0x4745D0C5CD9A2FA4

SHUTDOWN=0
trap "SHUTDOWN=1" SIGINT SIGTERM

function acquire_lease() {
    # racqa:
    # 0: aquire
    # 1: preempt

    # rtype:
    # 1: write exclusive

    nvme resv-register $DISK_DEVICE --iekey --nrkey=$MAGIC
    nvme resv-acquire $DISK_DEVICE --racqa=1 --rtype=1 --prkey=$MAGIC --crkey=$MAGIC
    # register again in case we preempted ourselves
    nvme resv-register $DISK_DEVICE --iekey --nrkey=$MAGIC
    nvme resv-acquire $DISK_DEVICE --racqa=0 --rtype=1 --prkey=$MAGIC --crkey=$MAGIC

    while [[ $SHUTDOWN -eq 0 ]]; do
        echo "$MAGIC $(date +%s) $HOSTNAME" | dd of=$DISK_DEVICE bs=512 count=1 oflag=direct status=none
        echo "Refreshed lease"
        sleep 5
    done
}

LEASE=$(dd if=$DISK_DEVICE bs=512 count=1 iflag=direct status=none)

if [[ $LEASE != $MAGIC* ]]; then
    # new disk, no lease
    acquire_lease
else
    last_active_remote=-1
    last_active_local=-1
    while [[ $SHUTDOWN -eq 0 ]]; do
        now=$(date +%s)
        read -r magic timestamp holder < <(echo $LEASE)
        if [ "$last_active_remote" != "$timestamp" ]; then
            echo "Remote $holder refreshed the lease"
            last_active_remote=$timestamp
            last_active_local=$now
        elif (($now - $last_active_local > 10)); then
            echo "Remote is dead, preempting"
            acquire_lease
            break
        fi
        sleep 5
        LEASE=$(dd if=$DISK_DEVICE bs=512 count=1 iflag=direct status=none)
    done
fi

File YAML yang digunakan untuk penerapan pada bagian berikut hanya berlaku untuk versi bahasa C. Saat menerapkan versi Bash, Anda perlu memberikan izin kepada kontainer dalam YAML:

securityContext:
  capabilities:
    add: ["SYS_ADMIN"]

Perluas untuk melihat Dockerfile

Dockerfile untuk versi bahasa C:

# syntax=docker/dockerfile:1.4

FROM buildpack-deps:bookworm as builder

COPY lease.c /usr/src/nvme-resv/
RUN gcc -o /lease -O2 -Wall /usr/src/nvme-resv/lease.c

FROM debian:bookworm-slim

COPY --from=builder --link /lease /usr/local/bin/lease
ENTRYPOINT ["/usr/local/bin/lease"]

Dockerfile untuk versi Bash:

# syntax=docker/dockerfile:1.4
FROM debian:bookworm-slim

RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
    --mount=type=cache,target=/var/lib/apt,sharing=locked \
    sed -i 's/deb.debian.org/mirrors.aliyun.com/g' /etc/apt/sources.list.d/debian.sources && \
    rm -f /etc/apt/apt.conf.d/docker-clean && \
    apt-get update && \
    apt-get install -y nvme-cli

COPY --link lease.sh /usr/local/bin/lease
ENTRYPOINT ["/usr/local/bin/lease"]

Langkah 1: Terapkan aplikasi dan konfigurasikan fitur multi-attach

Buat StorageClass bernama alicloud-disk-shared dan aktifkan fitur multi-attach untuk disk cloud.

Buat PVC bernama data-disk dan atur accessModes ke ReadWriteMany dan volumeMode ke Block.

Buat aplikasi StatefulSet bernama lease-test dan gunakan gambar contoh aplikasi dalam topik ini.

  1. Buat file lease.yaml dengan konten berikut.

    Ganti alamat gambar kontainer dalam YAML berikut dengan alamat gambar aktual aplikasi Anda.

    Penting
    • Karena NVMe Reservation berlaku di tingkat node, beberapa pod pada node yang sama dapat saling mengganggu. Oleh karena itu, podAntiAffinity digunakan dalam contoh ini untuk mencegah beberapa pod dijadwalkan ke node yang sama.

    • Jika kluster Anda mencakup node lain yang tidak menggunakan protokol NVMe, Anda perlu mengonfigurasi afinitas untuk memastikan pod dijadwalkan ke node yang menggunakan protokol NVMe.

    Perluas untuk melihat file lease.yaml

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: alicloud-disk-shared
    parameters:
      type: cloud_essd # Saat ini mendukung cloud_essd, cloud_auto, dan cloud_regional_disk_auto
      multiAttach: "true"
    provisioner: diskplugin.csi.alibabacloud.com
    reclaimPolicy: Delete
    volumeBindingMode: WaitForFirstConsumer
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: data-disk
    spec:
      accessModes: [ "ReadWriteMany" ]
      storageClassName: alicloud-disk-shared
      volumeMode: Block
      resources:
        requests:
          storage: 20Gi
    ---
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: lease-test
    spec:
      replicas: 2
      serviceName: lease-test
      selector:
        matchLabels:
          app: lease-test
      template:
        metadata:
          labels:
            app: lease-test
        spec:
          affinity:
            podAntiAffinity:
              requiredDuringSchedulingIgnoredDuringExecution:
              - labelSelector:
                  matchExpressions:
                  - key: app
                    operator: In
                    values:
                    - lease-test
                topologyKey: "kubernetes.io/hostname"
          containers:
          - name: lease
            image: <IMAGE OF APP>   # Ganti dengan alamat gambar aplikasi Anda.
            volumeDevices:
            - name: data-disk
              devicePath: /dev/data-disk  
          volumes:
          - name: data-disk
            persistentVolumeClaim:
              claimName: data-disk

    Parameter

    Deskripsi konfigurasi untuk fitur multi-attach

    Deskripsi konfigurasi untuk pemasangan normal

    StorageClass

    parameters.multiAttach

    Atur ke true untuk mengaktifkan fitur multi-attach untuk disk cloud.

    Tidak perlu konfigurasi

    PVC

    accessModes

    ReadWriteMany

    ReadWriteOnce

    volumeMode

    Block

    Filesystem

    Metode pemasangan volume penyimpanan

    volumeDevices: Mengakses data pada disk cloud secara langsung melalui perangkat blok.

    volumeMounts: Terutama digunakan untuk memasang volume bertipe sistem file.

  2. Jalankan perintah berikut untuk menerapkan aplikasi:

    kubectl apply -f lease.yaml

Langkah 2: Verifikasi efek multi-attach dan Reservation

Untuk memastikan konsistensi data pada disk cloud NVMe, Anda dapat mengontrol izin baca dan tulis melalui Reservation dalam aplikasi Anda. Jika satu pod melakukan operasi tulis, pod lain hanya dapat melakukan operasi baca.

Beberapa node dapat membaca dan menulis ke disk cloud yang sama

Jalankan perintah berikut untuk melihat log pod:

kubectl logs -l app=lease-test --prefix -f

Hasil yang diharapkan:

[pod/lease-test-0/lease] Register as key 4745d0c5cd9a2fa4
[pod/lease-test-0/lease] Refreshed lease
[pod/lease-test-0/lease] Refreshed lease
[pod/lease-test-1/lease] Remote lease-test-0 refreshed lease
[pod/lease-test-0/lease] Refreshed lease
[pod/lease-test-1/lease] Remote lease-test-0 refreshed lease
[pod/lease-test-0/lease] Refreshed lease
[pod/lease-test-1/lease] Remote lease-test-0 refreshed lease
[pod/lease-test-0/lease] Refreshed lease
[pod/lease-test-1/lease] Remote lease-test-0 refreshed lease

Hasil yang diharapkan menunjukkan bahwa Pod lease-test-1 dapat langsung membaca konten yang ditulis oleh Pod lease-test-0.

NVMe Reservation berhasil dibuat

  1. Jalankan perintah berikut untuk mendapatkan ID disk cloud:

    kubectl get pvc data-disk -ojsonpath='{.spec.volumeName}'
  2. Login ke salah satu dari dua node tersebut dan jalankan perintah berikut untuk memeriksa apakah NVMe Reservation berhasil dibuat:

    Ganti 2zxxxxxxxxxxx dalam kode berikut dengan konten setelah d- pada ID disk cloud yang Anda peroleh pada langkah sebelumnya.

    nvme resv-report -c 1 /dev/disk/by-id/nvme-Alibaba_Cloud_Elastic_Block_Storage_2zxxxxxxxxxxx

    Hasil yang diharapkan:

    NVME Reservation status:
    
    gen       : 3
    rtype     : 1
    regctl    : 1
    ptpls     : 1
    regctlext[0] :
      cntlid     : ffff
      rcsts      : 1
      rkey       : 4745d0c5cd9a2fa4
      hostid     : 4297c540000daf4a4*****

    Hasil yang diharapkan menunjukkan bahwa NVMe Reservation berhasil dibuat.

Reservation dapat memblokir operasi I/O tulis dari node abnormal

  1. Login ke node tempat Pod lease-test-0 berada dan jalankan perintah berikut untuk menjeda proses guna mensimulasikan skenario kegagalan:

    pkill -STOP -f /usr/local/bin/lease
  2. Tunggu selama 30 detik lalu jalankan perintah berikut untuk melihat log lagi:

    kubectl logs -l app=lease-test --prefix -f

    Hasil yang diharapkan:

    [pod/lease-test-1/lease] Remote lease-test-0 refreshed lease
    [pod/lease-test-1/lease] Remote is dead, preempting
    [pod/lease-test-1/lease] Register as key 4745d0c5cd9a2fa4
    [pod/lease-test-1/lease] Refreshed lease
    [pod/lease-test-1/lease] Refreshed lease
    [pod/lease-test-1/lease] Refreshed lease

    Hasil yang diharapkan menunjukkan bahwa Pod lease-test-1 telah mengambil alih dan memegang lease sebagai node utama layanan.

  3. Login kembali ke node tempat Pod lease-test-0 berada dan jalankan perintah berikut untuk melanjutkan proses yang dijeda:

    pkill -CONT -f /usr/local/bin/lease
  4. Jalankan perintah berikut untuk melihat log lagi:

    kubectl logs -l app=lease-test --prefix -f

    Hasil yang diharapkan:

    [pod/lease-test-0/lease] failed to write lease: Invalid exchange

    Hasil yang diharapkan menunjukkan bahwa Pod lease-test-0 tidak dapat lagi menulis ke disk cloud, dan kontainer lease secara otomatis dimulai ulang. Hal ini menunjukkan bahwa operasi I/O tulis telah berhasil diblokir oleh Reservation.

Referensi

Jika disk cloud NVMe Anda tidak memiliki ruang yang cukup atau penuh, lihat Memperluas volume disk cloud.