Tüm Ürünler
Search
Belge Merkezi

Container Service for Kubernetes:Using NVMe cloud disk multi-attach and Reservation for data sharing between applications

Son Güncelleme:Oct 06, 2026

Tek bir ESSD, ESSD AutoPL veya başka bir NVMe özellikli cloud diski aynı alandaki 16 adede kadar ECS örneğiyle aynı anda paylaşmak için çoklu iliştirmeyi kullanın; ya da bölge yedekli depolama ESSD'sini aynı bölgedeki düğümler arasında paylaşın. NVMe Kalıcı Rezervasyon (PR) ile birleştirilen çoklu iliştirme, iş yüklerinize kesin yazma izni denetimiyle paylaşılan depolama erişimi sunar ve ACK kümesinde verimli veri paylaşımının yanı sıra hızlı yük devretmeyi mümkün kılar.

Kullanım senaryoları

  • Veri paylaşımı: Bir düğüm paylaşılan bir NVMe diske veri yazdıktan sonra, bağlı olan diğer tüm düğümler bu veriyi anında okuyabilir. Tek bir konteyner görüntüsü NVMe diskte saklanarak aynı işletim sistemini çalıştıran birden fazla örnek tarafından yüklenebilir; bu sayede depolama maliyetleri düşer ve okuma/yazma performansı artar.

    image

  • Yüksek kullanılabilirlik yük devretmesi: Oracle Real Application Clusters (RAC), SAP High-performance ANalytic Appliance (HANA) ve cloud-native yüksek kullanılabilirlik (HA) veritabanları gibi geleneksel kümelenmiş veritabanları tek hata noktasına (SPOF) karşı savunmasızdır. Paylaşılan bir NVMe disk, bir işlem düğümü arızalandığında bile depolamanın erişilebilir kalmasını sağlar. İş yükünüzü birincil/ikincil modda dağıtın: birincil örnek arızalandığında, yazma izinlerini iptal etmek için bir NVMe PR komutu çalıştırın ve ardından ikincil örneği terfi ettirin. Bu yaklaşım bölünmüş beyin yazmalarını önler ve veri tutarlılığını garanti eder. Yük devretme sırası şu şekildedir:

    1. Birincil veritabanı örneği (Veritabanı Örneği 1) arızalanır ve trafiğe yanıt vermeyi durdurur.

    2. Veritabanı Örneği 1'e yazmayı engellemek ve Veritabanı Örneği 2'ye yazma erişimi vermek için bir NVMe PR komutu çalıştırın.

    3. Veritabanı Örneği 2'yi Veritabanı Örneği 1 ile aynı duruma geri yükleyin (örneğin, günlükleri yeniden oynatarak).

    4. Veritabanı Örneği 2 birincil örnek olarak görevi devralır.

    PR, NVMe teknik özelliklerinin bir parçasıdır. İşlem düğümlerinin verileri beklendiği şekilde yazmasını sağlamak amacıyla disk düzeyinde okuma ve yazma izinlerini denetler. Ayrıntılar için bkz. NVM Express Base Specification .

    image

  • Dağıtılmış veri önbelleği hızlandırma: Object Storage Service (OSS) üzerine kurulu veri göllerinde ardışık yazma aktarım hızı yüksek olsa da gecikme süresi uzundur ve rastgele okuma/yazma performansı düşüktür. Yüksek hızlı, çoklu iliştirme destekli bir cloud diski işlem düğümleri arasında paylaşılan bir önbellek katmanı olarak ekleyerek erişim performansını önemli ölçüde artırabilirsiniz.

    image

  • Makine öğrenimi: Örnek veriler etiketlendikten ve yazıldıktan sonra, ağ üzerinden kopyalamaya gerek kalmadan paralel eğitim için düğümlere dağıtılabilir. Her işlem düğümü doğrudan paylaşılan diskten okuma yaparak transfer gecikmesini azaltır ve büyük ölçekli model eğitimini hızlandırır.

    image

Faturalama

Çoklu iliştirme özelliği ek ücret oluşturmaz. NVMe protokolünü destekleyen kaynaklar orijinal faturalama yöntemlerine göre ücretlendirilir. Cloud disk fiyatlandırması için bkz. Elastic Block Storage birimleri.

Sınırlamalar

  • Tek bir NVMe cloud disk aynı anda aynı alandaki en fazla 16 ECS örneğine iliştirilebilir.

  • Birden fazla düğümden bir cloud diske eş zamanlı olarak okuma ve yazma yapmak için cloud diski volumeDevices kullanarak bağlayın. Bu yöntem diski blok aygıtı olarak bağlar ve dosya sistemi erişimini desteklemez. PersistentVolumeClaim (PVC) tanımınızda volumeMode: Block ve accessModes: ReadWriteMany kullanın.

  • Tüm sınırlamaların tam listesi için bkz. Çoklu iliştirme özelliğinin sınırlamaları.

Ön koşullar

Başlamadan önce aşağıdaki gereksinimlerin karşılandığından emin olun:

  • Kubernetes 1.20 veya sonraki bir sürümü çalışan yönetilen bir ACK kümesi. Oluşturma adımları için bkz. ACK yönetilen küme oluşturma.

  • v1.24.10-7ae4421-aliyun veya sonraki bir sürümde csi-plugin ve csi-provisioner bileşenleri. Yükseltme için bkz. csi-plugin ve csi-provisioner bileşenlerini yönetme.

  • Aynı alanda bulunan ve çoklu iliştirme özelliğini destekleyen en az iki düğüm. Desteklenen örnek aileleri için bkz. Çoklu iliştirme özelliğinin sınırlamaları.

  • Aşağıdaki iki gereksinimi de karşılayan kapsayıcılı bir uygulama:

    • Birden fazla çoğaltmanın aynı cloud diskine eş zamanlı erişimini destekler.

    • NVMe Rezervasyonu veya eşdeğer bir mekanizma kullanarak veri tutarlılığını sağlar.

Arka plan bilgisi için şunları inceleyebilirsiniz:

Uygulama örneği

Aşağıdaki örnek uygulama, paylaşılan bir NVMe blok aygıtı üzerinde kira tabanlı lider seçimi gösterir. Birden fazla çoğaltma, doğrudan diske yazılan bir kira için yarışır. Aynı anda yalnızca bir çoğaltma kirayı elinde tutar; yenilemeyi durdurduğunda başka bir çoğaltma NVMe Rezervasyon komutlarını kullanarak kirayı ele geçirir.

Önemli tasarım notları:

  • Blok aygıtını açmak için O_DIRECT kullanılır; bu, sayfa önbelleğini atlayarak okumaların diske gerçekten yazılan veriyi yansıtmasını sağlar.

  • Örnek, Linux çekirdeğinin basitleştirilmiş Rezervasyon arayüzünü (<linux/pr.h> ioctl'ları) kullanır. Ayrıcalıklı yetkiler gerektiren alternatifler:

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

    • CLI: nvme-cli

  • Tam NVMe Rezervasyon teknik özellikleri için bkz. NVMe Specification.

Expand to view the source code of the application example

C

#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);
}

Bash

#!/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

Aşağıdaki adımlardaki YAML yapılandırması C sürümü için geçerlidir. Bash sürümü için kapsayıcınızın securityContext bölümüne aşağıdakileri ekleyin:

securityContext:
  capabilities:
    add: ["SYS_ADMIN"]

Expand to view the Dockerfile

C sürümü için Dockerfile:

# 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"]

Bash sürümü için Dockerfile:

# 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"]

1. Adım: Uygulamayı dağıtın ve çoklu iliştirmeyi yapılandırın

Çoklu iliştirmeyi etkinleştiren bir StorageClass, blok aygıtı olarak yapılandırılmış bir PVC ve kira uygulaması görüntüsünü kullanan bir StatefulSet oluşturun.

  1. Aşağıdaki içerikle lease.yaml adlı bir dosya oluşturun. Kapsayıcı görüntüsü adresini kendi görüntü adresinizle değiştirin.

    Önemli
    • NVMe Rezervasyonu düğüm düzeyinde etkilidir. Aynı düğümde birden fazla pod çalışıyorsa birbirlerini etkileyebilirler. Bu örnek, bunu önlemek için podAntiAffinity kullanır.

    • Kümenizde NVMe protokolünü kullanmayan düğümler varsa, zamanlamayı NVMe uyumlu düğümlerle sınırlamak için düğüm benzeşimini yapılandırın.

    Expand to view the lease.yaml file

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: alicloud-disk-shared
    parameters:
      type: cloud_essd # Currently supports cloud_essd, cloud_auto, and 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>   # Replace with the image address of your application.
            volumeDevices:
            - name: data-disk
              devicePath: /dev/data-disk
          volumes:
          - name: data-disk
            persistentVolumeClaim:
              claimName: data-disk

    Aşağıdaki tabloda çoklu iliştirme ile standart bağlama yapılandırmaları arasındaki temel farklar özetlenmiştir:

    Kaynak Alan Çoklu iliştirme Standart bağlama
    StorageClass parameters.multiAttach "true" Gerekli değil
    PVC accessModes ReadWriteMany ReadWriteOnce
    PVC volumeMode Block Filesystem
    Birim bağlama Yöntem volumeDevices — doğrudan blok aygıtı erişimi volumeMounts — dosya sistemi bağlama
  2. Uygulamayı dağıtın:

    kubectl apply -f lease.yaml

2. Adım: Çoklu iliştirmeyi ve Rezervasyonu doğrulayın

Birden fazla düğümün aynı diske okuma ve yazma yapabildiğini doğrulayın

Pod günlüklerini görüntülemek için aşağıdaki komutu çalıştırın:

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

Beklenen çıktı:

[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

lease-test-1 hemen lease-test-0 tarafından yazılan veriyi okur; bu durum çoklu iliştirmenin çalıştığını doğrular.

NVMe Rezervasyonunun etkin olduğunu doğrulayın

  1. Cloud disk kimliğini alın:

    kubectl get pvc data-disk -ojsonpath='{.spec.volumeName}'
  2. İki düğümden herhangi birinde oturum açın ve aşağıdaki komutu çalıştırın. 2zxxxxxxxxxxx kısmını, önceki adımdaki disk kimliğinde d- sonrasındaki kısım ile değiştirin.

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

    Beklenen çıktı:

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

    rtype: 1 (write exclusive) ve regctl: 1 değerleri NVMe Rezervasyonunun etkin olduğunu doğrular.

Rezervasyonun başarısız bir düğümden gelen yazmaları engellediğini doğrulayın

  1. lease-test-0 çalıştıran düğümde oturum açın ve bir arızayı simüle etmek için işlemi duraklatın:

    pkill -STOP -f /usr/local/bin/lease
  2. 30 saniye bekleyin ve ardından günlükleri kontrol edin:

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

    Beklenen çıktı:

    [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

    lease-test-1 kirayı ele geçirdi ve birincil olarak görevi devraldı.

  3. lease-test-0 düğümünde duraklatılan işlemi devam ettirin:

    pkill -CONT -f /usr/local/bin/lease
  4. Günlükleri tekrar kontrol edin:

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

    Beklenen çıktı:

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

    lease-test-0 artık diske yazma yapamaz. Invalid exchange hatası, Rezervasyonun daha önce birincil olan düğümden gelen yazma G/Ç işlemini başarıyla engellediğini ve kira kapsayıcısının otomatik olarak yeniden başlatıldığını doğrular.

Sonraki adımlar

NVMe cloud diskinizde alan kalmazsa bkz. Cloud disk birimini genişletme.