LIO 타겟 프레임워크 (Linux I/O Target)

리눅스 커널의 범용 스토리지 타겟 프레임워크 LIO를 엔터프라이즈 SAN/NAS 운영 관점에서 심층 분석합니다. Target Core(TCM)의 객체 모델과 I/O 파이프라인(Pipeline), iSCSI/FC/NVMe-oF Fabric 모듈의 접속·세션·큐 처리 방식, configfs 기반 선언형 설정의 계층 구조와 자동화 포인트, 인증/접근 제어(Access Control)/ALUA 경로 정책, Persistent Reservations를 포함한 클러스터 안전성 기능, 장애 시 세션 복구 및 재접속 흐름, drivers/target/ 소스 트리를 통한 코드 추적 방법, 성능 측정과 병목(Bottleneck) 제거 절차까지 실무 구축과 운영에 필요한 세부 내용을 다룹니다.

전제 조건: SCSI / iSCSI 서브시스템, Block I/O, NVMe 문서를 먼저 읽으세요. LIO는 블록 디바이스를 여러 전송 프로토콜로 내보내는 타겟 프레임워크입니다. 블록 계층과 SCSI 스택에 대한 이해가 선행되어야 합니다.

핵심 요약

  • LIO(Linux I/O Target) — Linux 2.6.38부터 메인라인에 포함된 커널 스토리지 타겟 프레임워크입니다. SAN 환경에서 서버가 스토리지를 제공(Expose)하는 역할을 합니다.
  • Target Core(TCM) — Fabric 프로토콜과 독립적인 공통 백엔드 계층입니다. SCSI 명령 처리, 세션 관리, LUN 매핑(Mapping)을 담당합니다.
  • Fabric 모듈 — iSCSI, FC, SRP, NVMe-oF 등 다양한 전송 프로토콜을 TCM에 연결하는 플러그인 계층입니다.
  • configfs/sys/kernel/config/target/을 통해 타겟 설정을 파일시스템(Filesystem) 트리로 관리합니다. targetcli가 이를 활용합니다.
  • ALUA — Asymmetric Logical Unit Access. 멀티패스 환경에서 포트별 접근 상태(Active/Optimized/Non-Optimized)를 정의합니다.
  • Persistent Reservations(PR) — 클러스터 환경에서 LUN 점유 제어. SCSI PR(PROUT/PRIN) 명령으로 독점·공유 예약을 관리합니다.
  • NVMe-oF 타겟 — nvmet.ko로 NVMe 네임스페이스(Namespace)를 TCP/RDMA/FC로 내보냅니다. iSCSI 대비 레이턴시가 낮습니다.
  • iSCSI 타겟 — TCP 위에서 SCSI 명령을 전송합니다. 별도 HBA 없이 이더넷만으로 SAN 구성이 가능합니다.

단계별 이해

  1. 타겟 vs 이니시에이터 개념 구분 — 스토리지를 제공하는 쪽이 타겟(Target), 접근하는 쪽이 이니시에이터(Initiator)입니다.

    LIO는 타겟 역할을 담당합니다. iSCSI 클라이언트(open-iscsi)가 이니시에이터 역할을 합니다.

  2. TCM + Fabric 구조 이해 — TCM이 공통 SCSI 로직을, Fabric이 전송 계층을 담당하는 분리 구조입니다.

    iSCSI를 쓰든 NVMe-oF를 쓰든 TCM의 SCSI 처리 코드는 동일하게 재사용됩니다.

  3. configfs로 설정 관리targetcli 명령으로 backstore, target, lun, acl을 대화형으로 설정합니다.

    설정은 /sys/kernel/config/target/에 가상 파일로 반영됩니다.

  4. iSCSI 타겟 설정 실습 — targetcli로 fileio/block backstore를 만들고 IQN을 생성하여 클라이언트가 접속하도록 합니다.

    iscsiadm -m discovery로 타겟을 발견하고 -m node --login으로 접속합니다.

  5. ALUA로 멀티패스 제어 — 여러 포트 그룹에 Active/Standby를 지정하여 경로 장애 시 자동 페일오버를 구성합니다.

    클라이언트에서 multipathd가 ALUA 상태를 읽어 최적 경로를 선택합니다.

  6. PR로 클러스터 잠금(Lock) 구현 — Pacemaker 클러스터에서 SCSI PR을 사용하여 한 번에 하나의 노드만 쓰기 접근하도록 제어합니다.

    DRBD와 결합하면 완전한 HA 스토리지 클러스터를 구성할 수 있습니다.

LIO 타겟 프레임워크 개요

LIO(Linux I/O Target)는 Linux 커널 2.6.38(2011년)부터 메인라인에 포함된 범용 스토리지 타겟 프레임워크입니다. 이전에는 SCST, tgt, iSCSI Enterprise Target 등 여러 경쟁 구현이 있었으나, LIO가 공식 커널 타겟으로 통합되었습니다.

LIO는 Target Core(TCM)Fabric 모듈의 이중 계층 구조로 설계되어, 하나의 공통 SCSI 처리 엔진 위에 iSCSI, FC, SRP(InfiniBand), NVMe-oF 등 다양한 전송 계층을 플러그인 방식으로 연결할 수 있습니다.

특성내용
메인라인 포함Linux 2.6.38 (2011년 3월)
소스 위치drivers/target/
설정 인터페이스configfs (/sys/kernel/config/target/)
사용자 도구targetcli, rtslib-fb, targetcli-fb
지원 FabriciSCSI, FC(tcm_qla2xxx), SRP, NVMe-oF(TCP/RDMA/FC), Loopback
백엔드 타입fileio, block, pscsi, ramdisk, user
ALUA 지원포트 그룹별 Active/Optimized/Non-Optimized 상태
PR 지원SCSI Persistent Reservations (SPC-4)
LIO vs SCST vs tgt: Linux 2.6.38 이전에는 SCST(SCSI Target)와 tgt(User-space Target)가 주요 대안이었습니다. LIO는 커널 공간(Kernel Space)에서 완전한 SCSI 타겟을 구현하여 tgt 대비 낮은 레이턴시와 높은 처리량(Throughput)을 제공합니다. SCST는 여전히 out-of-tree 프로젝트로 유지되지만, 메인라인 커널에서는 LIO가 유일한 공식 타겟입니다.

LIO 발전 역사

커널 버전변경 사항영향
2.6.38 (2011)LIO Target Core 메인라인 통합iSCSI, FC 타겟 지원 시작
3.5 (2012)iblock 백엔드 성능 개선블록 디바이스 직접 접근 최적화
3.10 (2013)SCSI PR(Persistent Reservations) 강화클러스터 환경 안정성 향상
4.4 (2016)TCMU(TCM in Userspace) 추가사용자 공간(User Space) 백엔드 확장
4.8 (2016)NVMe-oF 타겟(nvmet) 추가NVMe over Fabrics 지원 시작
4.10 (2017)nvmet-rdma 전송 추가RDMA 기반 NVMe-oF 타겟
5.0 (2019)nvmet-tcp 전송 추가TCP 기반 NVMe-oF 타겟 (범용 네트워크)
5.15 (2021)nvmet passthrough 모드물리 NVMe 디바이스 직접 노출
6.0 (2022)TCM 성능 최적화, percpu 개선고부하 환경 확장성 개선
6.5+ (2023)NVMe-oF discovery controller 강화자동 발견 기능 개선

LIO 레이어드 아키텍처

LIO는 이니시에이터(클라이언트)에서 스토리지 백엔드까지 명확하게 분리된 계층 구조를 가집니다.

이니시에이터 open-iscsi / nvme-cli 네트워크 전송 TCP/RDMA/FC/IB Fabric 모듈 계층 iscsi_target_mod tcm_qla2xxx (FC) nvmet (NVMe-oF) target_loopback Target Core (TCM) — target_core_mod SCSI 명령 처리 · 세션/LUN 관리 · ALUA · PR · 큐잉 백엔드 스토리지 fileio block (블록 디바이스) pscsi (pass-through) ramdisk

핵심 개념

개념설명커널 구조체(Struct)
Target스토리지를 제공하는 노드 (서버)se_device
Initiator스토리지를 사용하는 노드 (클라이언트)se_node_acl
LUNLogical Unit Number — 타겟이 노출하는 논리 디바이스 단위se_lun
Portal타겟이 수신 대기하는 네트워크 엔드포인트 (IP:포트)iscsi_tpg_np
TPGTarget Portal Group — 포털들의 그룹se_portal_group
Session이니시에이터-타겟 간 연결 상태se_session
ACLAccess Control List — 이니시에이터별 접근 권한se_node_acl
Backstore실제 데이터를 저장하는 백엔드 (파일, 블록 디바이스 등)se_device

TCM I/O 파이프라인 상세

TCM의 I/O 처리는 여러 단계를 거치며, 각 단계에서 보안 검사, ALUA 상태 확인, PR 검사가 수행됩니다. 다음 다이어그램은 하나의 SCSI 명령이 Fabric에서 백엔드까지 처리되는 전체 흐름을 보여줍니다.

1. Fabric: CDB 수신 iscsit_handle_scsi_cmd() / nvmet_req_init() 2. target_submit_cmd() se_cmd 초기화, CDB 파싱 3. LUN 조회 + ACL 검사 target_lookup_lun() → se_lun → se_device LUN 없음 → SENSE 4. PR 예약 검사 target_check_reservation() PR 충돌 → CONFLICT 5. ALUA 상태 검사 target_alua_state_check() 6. 백엔드 I/O 실행 target_execute_cmd() → backend->execute_rw() 7. 완료 + 응답 전송 transport_generic_complete_ok() → queue_data_in() 8. Fabric → 이니시에이터 응답 iSCSI Data-In PDU / NVMe CQE
se_cmd 생명주기: se_cmd는 TCM의 핵심 I/O 단위입니다. Fabric이 target_submit_cmd()로 명령을 제출하면 TCM이 소유권을 가지고, transport_generic_complete_ok() 또는 에러 경로를 통해 Fabric에게 소유권을 돌려줍니다. se_cmd가 완료되기 전에 Fabric이 세션을 종료하면 target_wait_for_sess_cmds()로 진행 중인 모든 명령의 완료를 대기합니다.

백엔드 스토리지 비교

TCM은 여러 백엔드 드라이버를 지원하며, 각 백엔드는 target_backend_ops 구조체를 통해 TCM에 등록됩니다. 용도에 따라 적절한 백엔드를 선택해야 합니다.

백엔드소스 파일데이터 소스장점단점사용 사례
iblocktarget_core_iblock.c블록 디바이스최고 성능, 직접 I/O블록 디바이스 필요프로덕션 SAN
fileiotarget_core_file.c파일 (VFS 경유)희소 파일 지원, 유연함VFS 오버헤드(Overhead)개발/테스트, 씬 프로비저닝
pscsitarget_core_pscsi.cSCSI 디바이스 (pass-through)SCSI 명령 직접 전달제한적 TCM 기능테이프 드라이브, 특수 SCSI 디바이스
ramdisktarget_core_rd.c커널 메모리최저 레이턴시비휘발성 아님벤치마크, 프로토타이핑
user (TCMU)target_core_user.c사용자 공간 프로세스(Process)유연한 백엔드 확장커널-유저 전환 오버헤드Ceph RBD, GlusterFS 등

target_backend_ops 구조체

/* 백엔드 드라이버가 TCM에 등록하는 연산자 테이블 */
struct target_backend_ops {
    char                    name[16];
    struct module           *owner;

    /* 디바이스 생성/삭제 */
    struct se_device *(*alloc_device)(struct se_hba *, const char *);
    int  (*configure_device)(struct se_device *);
    void (*free_device)(struct se_device *);

    /* I/O 실행 (핵심) */
    sense_reason_t (*execute_rw)(struct se_cmd *, struct scatterlist *, u32,
                                enum dma_data_direction);
    sense_reason_t (*execute_sync_cache)(struct se_cmd *);
    sense_reason_t (*execute_write_same)(struct se_cmd *);
    sense_reason_t (*execute_unmap)(struct se_cmd *, sector_t, sector_t);

    /* 디바이스 속성 조회 */
    sector_t (*get_blocks)(struct se_device *);
    u32      (*get_alignment_offset_lbas)(struct se_device *);
};
pscsi 백엔드 주의: pscsi(pass-through SCSI)는 TCM의 SCSI 에뮬레이션을 우회하여 CDB를 물리 SCSI 디바이스에 직접 전달합니다. 이 모드에서는 ALUA, PR(Persistent Reservations), WRITE_SAME 등 TCM의 고급 기능이 동작하지 않습니다. 프로덕션 환경에서는 iblock 또는 fileio 사용을 권장합니다.

TCMU (TCM in Userspace)

TCMU는 커널 TCM과 사용자 공간 백엔드를 연결하는 브릿지입니다. /dev/tcmu/ 디바이스를 통해 커널과 사용자 공간 간 공유 메모리 링 버퍼(Ring Buffer)로 SCSI 명령을 교환합니다.

/* TCMU 공유 메모리 구조 (간략화) */
struct tcmu_mailbox {
    __u32   version;        /* 프로토콜 버전 */
    __u32   flags;
    __u32   cmdr_off;       /* 명령 링 버퍼 오프셋 */
    __u32   cmdr_size;      /* 명령 링 버퍼 크기 */
    __u32   cmd_head;       /* 커널이 쓰는 헤드 포인터 */
    __u32   cmd_tail;       /* 유저가 읽는 테일 포인터 (mmap) */
};

/* TCMU 명령 엔트리 */
struct tcmu_cmd_entry {
    struct tcmu_cmd_entry_hdr hdr;
    union {
        struct {
            __u32  iov_cnt;    /* scatter/gather 벡터 수 */
            __u32  iov_bidi_cnt;
            __u32  iov_dif_cnt;
            __u64  cdb_off;    /* CDB 오프셋 */
            __u64  __pad2;
            struct iovec iov[0];
        } req;
        struct {
            __u8   scsi_status; /* SCSI 상태 코드 */
            __u8   __pad1;
            __u16  __pad2;
            __u32  read_len;
            __u8   sense_buffer[TCMU_SENSE_BUFFERSIZE];
        } rsp;
    };
};
# TCMU 기반 Ceph RBD 백엔드 설정 예시
# tcmu-runner 데몬이 사용자 공간에서 RBD 이미지에 I/O 수행

# 1. tcmu-runner 설치 및 시작
$ yum install tcmu-runner tcmu-runner-handler-rbd
$ systemctl start tcmu-runner

# 2. targetcli에서 user:rbd 백엔드 생성
$ targetcli /backstores/user:rbd create name=ceph0 \
    size=100G cfgstring=pool/rbd_image

# 3. iSCSI 타겟에 LUN 매핑 (이후 절차는 iblock과 동일)

Target Core (TCM) 내부 구조

TCM은 Fabric으로부터 SCSI 명령(CDB)을 받아 처리하는 공통 엔진입니다. drivers/target/target_core_*.c에 구현되어 있습니다.

핵심 구조체

/* se_device: 백엔드 스토리지 디바이스 추상화 */
struct se_device {
    struct se_dev_attrib   dev_attrib;    /* 블록 사이즈, 큐 깊이 등 */
    struct target_backend  *transport;    /* fileio/block/pscsi 백엔드 */
    struct se_subsystem_api *transport_ops;
    struct alua_dev_group  *t10_alua;     /* ALUA 상태 */
    struct t10_pr_registration *dev_pr_res; /* Persistent Reservation */
    spinlock_t              execute_task_lock;
    struct list_head        state_list;   /* 실행 중인 태스크 */
    u64                     dev_sectors;  /* 디바이스 크기 (섹터) */
};

/* se_lun: Logical Unit Number 매핑 */
struct se_lun {
    u64                     unpacked_lun;  /* LUN 번호 */
    struct se_device        *lun_se_dev;   /* 연결된 디바이스 */
    struct se_portal_group  *lun_tpg;     /* 소속 TPG */
    struct percpu_ref       lun_ref;       /* 참조 카운트 */
    struct rcu_head         rcu_head;
};

/* se_session: 이니시에이터 세션 */
struct se_session {
    struct se_node_acl  *se_acl;       /* 이니시에이터 ACL */
    struct se_portal_group *se_tpg;    /* 소속 TPG */
    struct list_head     sess_acl_list; /* ACL 목록 */
    void                *fabric_sess_ptr; /* Fabric 전용 세션 데이터 */
    u64                  sess_cmd_map;
};

TCM 명령 처리 흐름

/* Fabric이 TCM으로 SCSI 명령을 전달하는 일반적인 흐름 */

/* 1. Fabric: 수신한 CDB를 se_cmd에 매핑 */
int target_submit_cmd(struct se_cmd *se_cmd, struct se_session *se_sess,
                      const unsigned char *cdb, struct scatterlist *sgl,
                      u32 data_length, int task_attr, int data_dir,
                      int flags);

/* 2. TCM: LUN 조회 → 디바이스 매핑 */
/*    → target_core_mod.c: target_check_reservation() PR 검사 */
/*    → target_core_mod.c: target_execute_cmd() 백엔드 호출 */

/* 3. 백엔드(fileio 예시): 실제 I/O 수행 */
static sense_reason_t
fd_execute_rw(struct se_cmd *cmd, struct scatterlist *sgl, u32 sgl_nents,
              enum dma_data_direction data_direction)
{
    struct fd_dev *fd_dev = TCM_FD_DEV(cmd->se_dev);
    struct fd_prot fd_prot;

    if (data_direction == DMA_FROM_DEVICE)
        return fd_do_rw(cmd, fd_dev->fd_file, sgl, sgl_nents, 0);
    else
        return fd_do_rw(cmd, fd_dev->fd_file, sgl, sgl_nents, 1);
}

/* 4. 완료: Fabric의 완료 콜백 호출 */
transport_generic_complete_ok(cmd);   /* → Fabric이 이니시에이터에게 응답 전송 */

Fabric 모듈 구조

Fabric 모듈은 target_core_fabric_ops 구조체를 통해 TCM과 인터페이스합니다. 각 Fabric은 고유한 전송 프로토콜을 구현하고 TCM API를 호출합니다.

/* Fabric 모듈이 TCM에 등록하는 연산자 테이블 */
struct target_core_fabric_ops {
    struct module  *module;
    const char     *name;

    /* 이니시에이터 식별자 (IQN, WWN 등) */
    char *(*get_fabric_name)(void);
    char *(*get_wwn)(struct se_portal_group *);

    /* 세션 관리 */
    int  (*sess_get_index)(struct se_session *);
    u32  (*sess_get_initiator_sid)(struct se_session *,
                                    unsigned char *, u32);

    /* 응답 전송 */
    int  (*write_pending)(struct se_cmd *);
    int  (*queue_data_in)(struct se_cmd *);
    int  (*queue_status)(struct se_cmd *);

    /* configfs 등록 */
    const struct config_item_type *tfc_wwn_cit;
    const struct config_item_type *tfc_tpg_cit;
    const struct config_item_type *tfc_tpg_lun_cit;
};
Fabric 모듈커널 모듈(Kernel Module)명전송 프로토콜소스 경로
iSCSI Targetiscsi_target_modTCP (포트 3260)drivers/target/iscsi/
FC Targettcm_qla2xxxFibre Channeldrivers/scsi/qla2xxx/
SRP Targettcm_loopInfiniBand SRPdrivers/infiniband/ulp/srpt/
NVMe-oF TCPnvmet + nvmet-tcpTCP (포트 4420)drivers/nvme/target/
NVMe-oF RDMAnvmet + nvmet-rdmaInfiniBand/RoCEdrivers/nvme/target/
NVMe-oF FCnvmet + nvmet-fcFibre Channeldrivers/nvme/target/
Loopbacktcm_loop로컬 (테스트용)drivers/target/loopback/

Fabric 모듈 등록 흐름

Fabric 모듈은 커널 모듈 초기화 시 target_register_template()를 호출하여 TCM에 등록합니다. 등록이 완료되면 configfs에 해당 Fabric의 디렉토리가 자동 생성됩니다.

/* iSCSI Fabric 모듈 등록 예시 (iscsi_target_mod) */
static const struct target_core_fabric_ops iscsi_ops = {
    .module                = THIS_MODULE,
    .fabric_name           = "iscsi",
    .node_acl_size         = sizeof(struct iscsi_node_acl),
    .tpg_get_wwn           = lio_tpg_get_endpoint_wwn,
    .tpg_get_tag           = lio_tpg_get_tag,
    .sess_get_index        = lio_sess_get_index,
    .sess_get_initiator_sid = lio_sess_get_initiator_sid,
    .write_pending         = lio_write_pending,
    .queue_data_in         = iscsit_queue_data_in,
    .queue_status          = iscsit_queue_status,
    .queue_tm_rsp          = iscsit_queue_tm_rsp,
    .aborted_task          = iscsit_aborted_task,
};

/* 모듈 초기화 시 등록 */
static int __init iscsi_target_init_module(void)
{
    int ret;

    ret = target_register_template(&iscsi_ops);
    if (ret < 0)
        return ret;

    /* iSCSI 네트워크 포털 스레드 시작 등 추가 초기화 */
    ret = iscsit_init_global();
    if (ret < 0) {
        target_unregister_template(&iscsi_ops);
        return ret;
    }
    return 0;
}
새 Fabric 개발: 새로운 전송 프로토콜용 Fabric 모듈을 개발하려면 target_core_fabric_ops의 필수 콜백(Callback)을 구현하고 target_register_template()로 등록하면 됩니다. drivers/target/loopback/tcm_loop.c가 가장 단순한 참조 구현으로, 새 Fabric 개발 시 좋은 출발점입니다.

configfs 기반 설정 체계

LIO의 모든 설정은 /sys/kernel/config/target/ configfs 마운트(Mount) 포인트를 통해 이루어집니다. targetcli는 이 인터페이스를 래핑한 사용자 친화적 도구입니다.

configfs 트리 구조

/sys/kernel/config/target/
├── core/                          # TCM 백엔드 설정
│   ├── fileio_0/                  # fileio 백엔드 그룹
│   │   └── disk1/                 # 개별 backstore 객체
│   │       ├── udev_path          # 파일 경로 또는 블록 디바이스
│   │       ├── enable             # 활성화 (1 쓰면 활성화)
│   │       └── attrib/            # 속성 (블록 사이즈, 큐 깊이)
│   └── iblock_0/                  # block 백엔드 그룹
├── iscsi/                         # iSCSI Fabric 설정
│   └── iqn.2024-01.com.example:storage/
│       └── tpgt_1/               # Target Portal Group
│           ├── acls/             # 이니시에이터 ACL
│           │   └── iqn.1991-05.com.redhat:client/
│           ├── lun/              # LUN 매핑
│           │   └── lun_0 -> core/fileio_0/disk1
│           └── np/               # Network Portals (IP:포트)
│               └── 0.0.0.0:3260
└── nvmet/                        # NVMe-oF Fabric 설정
    ├── subsystems/
    └── ports/

targetcli 단계별 설정

# targetcli 시작 (대화형 모드)
$ targetcli

# 또는 직접 명령어 실행
$ targetcli /backstores/block create name=lun0 dev=/dev/sdb

===================== 단계별 iSCSI 타겟 설정 =====================

# 1. Block 백엔드 생성
/> backstores/block create name=lun0 dev=/dev/sdb
Created block storage object lun0 using /dev/sdb.

# 2. iSCSI 타겟 생성 (IQN 자동 생성)
/> iscsi/ create iqn.2024-01.com.example:storage.lun0
Created target iqn.2024-01.com.example:storage.lun0.
Created TPG 1.

# 3. Network Portal 설정 (모든 IP, 기본 포트 3260)
/> iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/portals/ create
Using default IP port 3260

# 4. LUN 매핑
/> iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/luns/ create /backstores/block/lun0
Created LUN 0.

# 5. 이니시에이터 ACL 추가
/> iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/acls/ create iqn.1991-05.com.redhat:client
Created Node ACL for iqn.1991-05.com.redhat:client
Created mapped LUN 0.

# 6. 인증 없이 접근 (개발/테스트 환경)
/> iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/ set attribute authentication=0
# 프로덕션에서는 CHAP 인증 필수!

# 7. 설정 저장 및 확인
/> saveconfig
/> ls

fileio 백엔드 (파일 기반)

# 희소 파일로 가상 디스크 생성 (테스트용)
$ dd if=/dev/zero of=/var/lib/target/disk.img bs=1M count=0 seek=10240

# fileio 백엔드 생성
$ targetcli /backstores/fileio create name=testdisk file_or_dev=/var/lib/target/disk.img size=10G write_back=false

# write_back=false: 쓰기마다 동기화 (데이터 안전성 우선)
# write_back=true: 페이지 캐시 사용 (성능 우선)

iSCSI 타겟

iSCSI 타겟은 TCP 소켓(Socket) 위에서 iSCSI PDU(Protocol Data Unit)를 처리합니다. drivers/target/iscsi/에 구현되어 있습니다.

iSCSI 로그인 시퀀스

# iSCSI 로그인 3단계: Security → Operational → Full Feature

Phase 1: Security Negotiation
  Initiator: Login Request (AuthMethod=CHAP,None)
  Target:    Login Response (AuthMethod=CHAP)
  Initiator: CHAP_A=5 (MD5 선택)
  Target:    CHAP_I=1,CHAP_C=<challenge>
  Initiator: CHAP_N=username,CHAP_R=<response>
  Target:    Login Response (status=0x0000)

Phase 2: Operational Negotiation
  MaxRecvDataSegmentLength, MaxBurstLength, FirstBurstLength
  ImmediateData, InitialR2T, HeaderDigest, DataDigest

Phase 3: Full Feature Phase
  → SCSI Command PDU 교환 시작

커널 내부 iSCSI 처리

/* iscsi_target_mod 주요 처리 함수 */

/* TCP 수신 → PDU 파싱 */
static int iscsit_get_rx_pdu(struct iscsi_conn *conn)
{
    struct kvec iov;
    u32 checksum = 0, iov_count = 0;
    struct iscsi_hdr *hdr = &conn->work_buf[0];

    /* BHS(Basic Header Segment) 수신 */
    iscsit_recv_data_segment(conn, &iov, &iov_count, ISCSI_HDR_LEN);

    switch (hdr->opcode & ISCSI_OPCODE_MASK) {
    case ISCSI_OP_SCSI_CMD:
        iscsit_handle_scsi_cmd(conn, cmd, buf);
        break;
    case ISCSI_OP_NOOP_OUT:
        iscsit_handle_nop_out(conn, cmd, buf);
        break;
    case ISCSI_OP_SCSI_TMFUNC:
        iscsit_handle_task_mgt_cmd(conn, cmd, buf);
        break;
    }
}

iSCSI 오프로드 지원

일부 NIC는 iSCSI 오프로드(TOE: TCP Offload Engine + iSCSI 가속)를 지원합니다.

기능소프트웨어 iSCSIiSCSI 오프로드 NIC
TCP 처리커널 TCP 스택NIC 하드웨어
CRC(HeaderDigest)소프트웨어 CRC32C하드웨어 CRC32C
CPU 사용률높음 (대역폭(Bandwidth) 비례)낮음
드라이버iscsi_tcp (커널)bnx2i, cxgbi, be2iscsi
iSER (RDMA)해당 없음ib_iser + RDMA 어댑터

CHAP 인증 설정

프로덕션 환경에서는 반드시 CHAP(Challenge-Handshake Authentication Protocol) 인증을 활성화해야 합니다. LIO는 단방향(One-way) CHAP과 양방향(Mutual) CHAP을 모두 지원합니다.

보안 경고: authentication=0으로 설정하면 모든 이니시에이터가 인증 없이 타겟에 접속할 수 있습니다. 프로덕션 환경에서는 반드시 CHAP 인증을 활성화하고, 강력한 비밀번호(최소 12자, 영문·숫자·특수문자 혼합)를 사용하세요. 양방향 CHAP을 사용하면 이니시에이터도 타겟의 신원을 검증하여 중간자 공격(MITM)을 방지할 수 있습니다.
# ==================== 단방향 CHAP (타겟이 이니시에이터 인증) ====================

# 1. TPG에서 인증 활성화
/> iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/ set attribute authentication=1

# 2. 이니시에이터 ACL에 CHAP 자격 증명 설정
/> iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/acls/iqn.1991-05.com.redhat:client/ \
    set auth userid=iscsi_user
/> iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/acls/iqn.1991-05.com.redhat:client/ \
    set auth password=S3cur3P@ssw0rd!

# ==================== 양방향 CHAP (이니시에이터도 타겟 인증) ====================

# 3. 양방향 CHAP을 위한 타겟 자격 증명 설정
/> iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/acls/iqn.1991-05.com.redhat:client/ \
    set auth mutual_userid=target_user
/> iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/acls/iqn.1991-05.com.redhat:client/ \
    set auth mutual_password=T@rg3tP@ss!

# ==================== 이니시에이터 측 CHAP 설정 (open-iscsi) ====================

# /etc/iscsi/iscsid.conf 에서 CHAP 설정
node.session.auth.authmethod = CHAP
node.session.auth.username = iscsi_user
node.session.auth.password = S3cur3P@ssw0rd!
# 양방향 CHAP용 (선택)
node.session.auth.username_in = target_user
node.session.auth.password_in = T@rg3tP@ss!

iSCSI 세션 복구 메커니즘

iSCSI는 네트워크 장애 시 세션을 복구하는 3단계 에러 복구 레벨(Error Recovery Level, ERL)을 정의합니다.

ERL복구 범위동작성능 영향
0세션 레벨에러 시 전체 세션 재설정 (Session Recovery)가장 낮음
1디지스트/PDU 레벨손상된 PDU만 재전송 (Digest Recovery)중간
2연결 레벨실패한 연결만 재설정, 다른 연결 유지 (Connection Recovery)가장 높음
# ERL 설정 (타겟 측)
/> iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/ set parameter ErrorRecoveryLevel=0

# 이니시에이터 측 타임아웃 설정
# /etc/iscsi/iscsid.conf
node.session.timeo.replacement_timeout = 120   # 세션 복구 대기 시간(초)
node.conn[0].timeo.noop_out_interval = 5       # Nop-Out 주기(초)
node.conn[0].timeo.noop_out_timeout = 5        # Nop-Out 응답 대기(초)

# 이니시에이터 재접속 정책
node.session.initial_login_retry_max = 8       # 초기 로그인 재시도 횟수
node.session.reopen_max = 32                   # 최대 세션 재개 시도
Nop-Out/Nop-In 핑: iSCSI는 ISCSI_OP_NOOP_OUT / ISCSI_OP_NOOP_IN PDU를 교환하여 연결 상태를 모니터링합니다. 타겟이 noop_out_timeout 내에 응답하지 않으면 이니시에이터는 연결 장애로 판단하고 세션 복구를 시작합니다. LIO 타겟 측에서는 nopin_timeoutnopin_response_timeout 파라미터로 이 동작을 제어합니다.

NVMe-oF 타겟

NVMe-oF 타겟은 drivers/nvme/target/에 구현되어 있으며, nvmet.ko가 공통 타겟 코어 역할을 합니다. iSCSI/LIO와는 별도의 스택이지만 유사한 configfs 기반 설정을 사용합니다.

iSCSI 타겟 (LIO) TCP 소켓 (포트 3260) iscsi_target_mod (PDU 파싱) Target Core (TCM) SCSI 처리 백엔드 (block/fileio) NVMe-oF 타겟 (nvmet) TCP/RDMA/FC (포트 4420) nvmet-tcp/rdma/fc (전송 계층) nvmet 코어 (NVMe 명령 처리) NVMe 네임스페이스 (블록/파일)

nvmet 설정

# 커널 모듈 로드
$ modprobe nvmet
$ modprobe nvmet-tcp

# configfs 마운트 (보통 자동 마운트됨)
$ mount -t configfs none /sys/kernel/config

# NVMe-oF 서브시스템 생성
$ mkdir /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:nvme0

# 모든 호스트 허용 (개발용)
$ echo 1 > /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:nvme0/attr_allow_any_host

# 네임스페이스 생성 및 블록 디바이스 연결
$ mkdir /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:nvme0/namespaces/1
$ echo /dev/sdb > /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:nvme0/namespaces/1/device_path
$ echo 1 > /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:nvme0/namespaces/1/enable

# 포트 생성 (TCP, 포트 4420)
$ mkdir /sys/kernel/config/nvmet/ports/1
$ echo 0.0.0.0 > /sys/kernel/config/nvmet/ports/1/addr_traddr
$ echo 4420    > /sys/kernel/config/nvmet/ports/1/addr_trsvcid
$ echo tcp     > /sys/kernel/config/nvmet/ports/1/addr_trtype
$ echo ipv4    > /sys/kernel/config/nvmet/ports/1/addr_adrfam

# 서브시스템을 포트에 연결
$ ln -s /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:nvme0 \
        /sys/kernel/config/nvmet/ports/1/subsystems/

# 클라이언트에서 발견 및 접속
$ nvme discover -t tcp -a 192.168.1.100 -s 4420
$ nvme connect -t tcp -a 192.168.1.100 -s 4420 -n nqn.2024-01.com.example:nvme0
항목iSCSI (LIO)NVMe-oF (TCP)NVMe-oF (RDMA)
레이턴시~100µs~30µs~5µs
대역폭 오버헤드높음 (SCSI 변환)낮음매우 낮음
CPU 사용률높음중간낮음 (커널 바이패스)
네트워크 요구일반 이더넷일반 이더넷RoCE/InfiniBand
포트326044204420
커널 모듈iscsi_target_modnvmet-tcpnvmet-rdma

nvmet 핵심 구조체

NVMe-oF 타겟은 LIO(TCM) 스택과는 별도로 독립적인 코드 베이스를 가지지만, 유사한 계층 구조를 따릅니다.

/* nvmet_subsys: NVMe 서브시스템 (iSCSI의 Target에 해당) */
struct nvmet_subsys {
    struct kref          ref;
    char                subsysnqn[NVMF_NQN_MAXLEN];
    enum nvme_subsys_type type;       /* discovery / IO */
    struct list_head    namespaces;   /* 네임스페이스 목록 */
    struct list_head    ctrls;        /* 연결된 컨트롤러 목록 */
    bool                allow_any_host;
    struct config_group group;
};

/* nvmet_ns: NVMe 네임스페이스 (iSCSI의 LUN에 해당) */
struct nvmet_ns {
    struct percpu_ref   ref;
    struct block_device *bdev;        /* 백엔드 블록 디바이스 */
    struct file         *file;        /* 또는 파일 백엔드 */
    u32                 nsid;         /* 네임스페이스 ID */
    u32                 blksize_shift;
    loff_t              size;
    bool                enabled;
    struct nvmet_subsys *subsys;
};

/* nvmet_ctrl: NVMe 컨트롤러 (이니시에이터 세션에 해당) */
struct nvmet_ctrl {
    struct nvmet_subsys  *subsys;
    struct nvmet_sq     **sqs;        /* Submission Queue 배열 */
    u16                  cntlid;       /* 컨트롤러 ID */
    u32                  kato;         /* Keep Alive Timeout */
    struct nvmet_port   *port;
};

NVMe-oF Passthrough 모드

Linux 5.15부터 nvmet은 passthrough 모드를 지원합니다. 이 모드에서는 물리 NVMe 디바이스의 NVMe 명령을 그대로 원격 이니시에이터에게 전달하여, NVMe 고유 기능(벤더 명령, 포맷 등)을 원격에서 사용할 수 있습니다.

# NVMe-oF Passthrough 설정
# 물리 NVMe 디바이스를 원격에 직접 노출

$ mkdir /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:passthru0
$ echo 1 > /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:passthru0/attr_allow_any_host
$ mkdir /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:passthru0/passthru

# 물리 NVMe 컨트롤러 지정 (/dev/nvme0)
$ echo /dev/nvme0 > /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:passthru0/passthru/ctrl_path
$ echo 1 > /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:passthru0/passthru/enable

# 포트에 연결 (기존 포트 설정 재사용)
$ ln -s /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:passthru0 \
        /sys/kernel/config/nvmet/ports/1/subsystems/
Passthrough 제약: passthrough 모드에서는 하나의 물리 NVMe 컨트롤러를 하나의 서브시스템에만 연결할 수 있습니다. 같은 컨트롤러를 여러 서브시스템에 공유하는 것은 불가능합니다. 또한 이니시에이터가 포맷(Format NVM) 같은 관리 명령을 실행할 수 있으므로 접근 제어에 주의가 필요합니다.

ALUA (Asymmetric Logical Unit Access)

ALUA는 멀티패스 환경에서 각 포트 그룹의 LUN 접근 상태를 비대칭적으로 정의하는 SCSI 표준(SPC-4)입니다. Active/Standby 포트 구성으로 고가용성을 실현합니다.

ALUA 포트 그룹 상태

상태설명I/O 처리
Active/Optimized (AO)최적 경로 — 로컬 컨트롤러I/O 직접 처리
Active/Non-Optimized (ANO)비최적 경로 — 원격 컨트롤러 경유I/O 처리 (성능 저하)
Standby (SB)대기 상태I/O 거부 (페일오버 대기)
Unavailable (UA)사용 불가I/O 거부
Offline (OF)오프라인I/O 거부
Transitioning (TR)상태 전환 중일시적 거부
# targetcli로 ALUA 포트 그룹 설정
/> /backstores/block/lun0 set alua_write_metadata=true

# 기본 ALUA 그룹 확인
/> /backstores/block/lun0/alua/ ls
o- alua
  o- default_tg_pt_gp [ALUA state: Active/Optimized]

# 새 포트 그룹 생성 (Active/Non-Optimized)
/> /backstores/block/lun0/alua/ create standby_group
/> /backstores/block/lun0/alua/standby_group set alua_access_state=2
# 0=Active/Optimized, 1=Active/Non-Optimized, 2=Standby, 3=Unavailable

# 클라이언트에서 ALUA 상태 확인
$ sg_rtpg /dev/sdb    # REPORT TARGET PORT GROUPS

ALUA 커널 구현

ALUA 상태 전환은 target_core_alua.c에서 처리됩니다. 상태 전환 시 TCM은 진행 중인 모든 I/O를 일시 중지하고 새 상태로 전환한 후 I/O를 재개합니다.

/* ALUA 상태 검사 — 매 I/O마다 호출됨 */
static sense_reason_t
target_alua_state_check(struct se_cmd *cmd)
{
    struct se_device *dev = cmd->se_dev;
    struct t10_alua_tg_pt_gp *tg_pt_gp;
    int state;

    tg_pt_gp = cmd->se_lun->lun_tg_pt_gp;
    if (!tg_pt_gp)
        return 0;  /* ALUA 미사용 */

    state = tg_pt_gp->tg_pt_gp_alua_access_state;

    switch (state) {
    case ALUA_ACCESS_STATE_ACTIVE_OPTIMIZED:
        return 0;   /* I/O 허용 */
    case ALUA_ACCESS_STATE_ACTIVE_NON_OPTIMIZED:
        return 0;   /* I/O 허용 (성능 저하) */
    case ALUA_ACCESS_STATE_STANDBY:
        return TCM_ALUA_TG_PT_STANDBY;  /* I/O 거부 */
    case ALUA_ACCESS_STATE_UNAVAILABLE:
        return TCM_ALUA_TG_PT_UNAVAILABLE;
    case ALUA_ACCESS_STATE_TRANSITION:
        /* 상태 전환 중 — 잠시 대기 후 재시도 */
        msleep_interruptible(tg_pt_gp->tg_pt_gp_trans_delay_msecs);
        return TCM_ALUA_STATE_TRANSITION;
    }
    return TCM_INVALID_PARAMETER_LIST;
}
ALUA + multipathd 연동: Linux 이니시에이터에서 multipathdREPORT TARGET PORT GROUPS (RTPG) 명령으로 각 경로의 ALUA 상태를 주기적으로 조회합니다. /etc/multipath.conf에서 path_grouping_policy "group_by_prio"prio "alua"를 설정하면 ALUA 상태에 따라 자동으로 최적 경로를 선택합니다.

Persistent Reservations (PR)

SCSI PR은 클러스터 환경에서 여러 이니시에이터 중 하나(또는 그룹)가 LUN 접근을 독점하거나 공유하는 메커니즘입니다. Pacemaker/Corosync와 함께 활성-수동 클러스터를 구성할 때 핵심 역할을 합니다.

PR 예약 타입

타입 (16진)설명사용 사례
0x01 WEWrite Exclusive — 등록자만 쓰기 가능단일 노드 쓰기 보호(Write Protection)
0x03 EAExclusive Access — 등록자만 읽기/쓰기활성-수동 클러스터
0x05 WEARWrite Exclusive, All Registrants — 등록된 모든 이니시에이터 쓰기공유 쓰기
0x06 EAARExclusive Access, All Registrants — 등록된 모든 이니시에이터 독점클러스터 공유
# sg_persist로 PR 명령 테스트

# 키 등록 (PROUT REGISTER)
$ sg_persist --out --register --param-rk=0x0000000000000001 /dev/sdb

# 예약 획득 (PROUT RESERVE, EA 타입)
$ sg_persist --out --reserve --param-rk=0x0000000000000001 --prout-type=3 /dev/sdb

# 현재 예약 상태 확인 (PRIN READ RESERVATION)
$ sg_persist --in --read-reservation /dev/sdb

# 모든 등록자 확인 (PRIN READ FULL STATUS)
$ sg_persist --in --read-full-status /dev/sdb

# 예약 해제
$ sg_persist --out --release --param-rk=0x0000000000000001 --prout-type=3 /dev/sdb

Pacemaker + PR 연동

# Pacemaker SBD(Storage-Based Death) + SCSI PR 조합
# 노드 장애 시 SCSI PR로 스토리지 잠금 해제 → 다른 노드가 인수

# SBD 디바이스 초기화
$ sbd -d /dev/sdb create

# corosync.conf에서 SBD fence 에이전트 설정
$ crm configure primitive p_sbd stonith:external/sbd \
    params pcmk_delay_max=30 \
    op monitor interval=15 timeout=15

PR 커널 구현

PR 처리는 target_core_pr.c에서 구현됩니다. 모든 I/O에 대해 target_check_reservation()이 호출되어 현재 예약 상태를 검사합니다.

/* PR 예약 검사 — 매 I/O마다 호출 */
sense_reason_t
target_check_reservation(struct se_cmd *cmd)
{
    struct se_device *dev = cmd->se_dev;
    struct t10_pr_registration *pr_res;
    bool is_write;

    /* PR이 설정되지 않은 디바이스는 무조건 통과 */
    if (!dev->dev_pr_res_holder)
        return 0;

    pr_res = dev->dev_pr_res_holder;
    is_write = (cmd->data_direction == DMA_TO_DEVICE);

    /* 예약 보유자 자신의 명령은 통과 */
    if (cmd->se_sess == pr_res->pr_reg_sess)
        return 0;

    /* 예약 타입별 접근 제어 */
    switch (pr_res->pr_res_type) {
    case PR_TYPE_WRITE_EXCLUSIVE:
        /* 비보유자의 읽기는 허용, 쓰기는 거부 */
        if (is_write)
            return TCM_RESERVATION_CONFLICT;
        break;
    case PR_TYPE_EXCLUSIVE_ACCESS:
        /* 비보유자의 모든 접근 거부 */
        return TCM_RESERVATION_CONFLICT;
    case PR_TYPE_WRITE_EXCLUSIVE_ALLREG:
        /* 등록된 이니시에이터는 쓰기 허용 */
        if (core_scsi3_pr_is_registered(dev, cmd->se_sess))
            return 0;
        if (is_write)
            return TCM_RESERVATION_CONFLICT;
        break;
    }
    return 0;
}

보안 강화 가이드

LIO 타겟을 프로덕션 환경에 배포할 때는 다음 보안 사항을 반드시 점검해야 합니다.

보안 체크리스트

항목권장 설정위험도비고
CHAP 인증양방향 CHAP 활성화높음인증 없으면 누구나 접속 가능
ACL 제한허용 IQN만 등록높음generate_node_acls=0
네트워크 격리(Isolation)스토리지 전용 VLAN/서브넷높음데이터 네트워크와 분리
포탈 바인딩특정 IP에만 바인딩중간0.0.0.0 사용 지양
IPseciSCSI 트래픽 암호화(Encryption)중간대안: iSER over RDMA
데이터 다이제스트HeaderDigest=CRC32C낮음데이터 무결성(Integrity) 검증
NVMe-oF TLSnvmet-tcp TLS 1.3 활성화높음Linux 6.7+에서 지원
호스트 NQN 제한attr_allow_any_host=0높음허용 호스트 NQN 명시적 등록

스토리지 네트워크 격리

# 스토리지 네트워크 전용 인터페이스 설정
# iSCSI 트래픽을 별도 VLAN으로 격리

# 1. 스토리지 VLAN 인터페이스 생성
$ ip link add link eth0 name eth0.100 type vlan id 100
$ ip addr add 10.0.100.1/24 dev eth0.100
$ ip link set eth0.100 up

# 2. iSCSI 포탈을 스토리지 VLAN IP에만 바인딩
/> iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/portals/ delete 0.0.0.0 3260
/> iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/portals/ create 10.0.100.1 3260

# 3. 방화벽으로 iSCSI 포트 제한
$ nft add rule inet filter input iifname "eth0.100" tcp dport 3260 accept
$ nft add rule inet filter input tcp dport 3260 drop

# 4. NVMe-oF 포트도 동일하게 제한
$ nft add rule inet filter input iifname "eth0.100" tcp dport 4420 accept
$ nft add rule inet filter input tcp dport 4420 drop
generate_node_acls 위험: generate_node_acls=1로 설정하면 모든 이니시에이터에 대해 자동으로 ACL이 생성되어 인증 없이 접속됩니다. 이 설정은 개발/테스트 환경에서만 사용하고, 프로덕션에서는 반드시 generate_node_acls=0으로 설정하여 명시적으로 허용된 IQN만 접근하도록 하세요.

고가용성 클러스터 구성

LIO 타겟을 고가용성(HA)으로 운영하려면 Pacemaker/Corosync 클러스터와 DRBD 또는 공유 스토리지를 결합합니다. 다음은 Active-Passive iSCSI HA 구성의 핵심 요소입니다.

이니시에이터들 (multipathd) VIP (10.0.100.10) Node 1 (Active) Pacemaker LIO Target DRBD Primary Node 2 (Standby) Pacemaker LIO (대기) DRBD Secondary Corosync DRBD 동기화 DRBD 복제 디스크 (또는 공유 SAN LUN)
# Pacemaker + LIO HA 리소스 정의

# 1. VIP 리소스
$ crm configure primitive p_vip ocf:heartbeat:IPaddr2 \
    params ip=10.0.100.10 cidr_netmask=24 nic=eth0.100 \
    op monitor interval=10s

# 2. iSCSI 타겟 리소스
$ crm configure primitive p_iscsi_target ocf:heartbeat:iSCSITarget \
    params iqn="iqn.2024-01.com.example:storage.lun0" \
    op start timeout=30 \
    op stop timeout=30 \
    op monitor interval=30

# 3. iSCSI LUN 리소스
$ crm configure primitive p_iscsi_lun ocf:heartbeat:iSCSILogicalUnit \
    params target_iqn="iqn.2024-01.com.example:storage.lun0" \
    lun=0 path=/dev/drbd0 \
    op monitor interval=30

# 4. 리소스 순서 및 코로케이션 제약
$ crm configure group g_iscsi p_vip p_iscsi_target p_iscsi_lun
$ crm configure order o_drbd_iscsi inf: ms_drbd:promote g_iscsi:start
$ crm configure colocation c_iscsi_drbd inf: g_iscsi ms_drbd:Master

성능 튜닝

큐 깊이 및 스레드(Thread) 설정

# iSCSI 타겟 스레드 수 조정 (기본: 논리 CPU 수)
$ targetcli /iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1 \
    set parameter MaxRecvDataSegmentLength=262144
$ targetcli /iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1 \
    set parameter MaxBurstLength=16776192
$ targetcli /iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1 \
    set parameter FirstBurstLength=262144

# 백엔드 블록 디바이스 큐 깊이 설정
$ targetcli /backstores/block/lun0 set attrib queue_depth=128

# iSCSI 이니시에이터 큐 깊이
$ iscsiadm -m node -T iqn.2024-01.com.example:storage.lun0 \
    --op update -n node.session.cmds_max -v 1024
$ iscsiadm -m node -T iqn.2024-01.com.example:storage.lun0 \
    --op update -n node.session.queue_depth -v 128

성능 측정 기준

시나리오iSCSI (10GbE)NVMe-oF/TCPNVMe-oF/RDMA
순차 읽기 대역폭~9.5 GB/s~9.8 GB/s~10 GB/s
4K 랜덤 읽기 IOPS~800K~1.2M~2M+
4K 읽기 레이턴시 (avg)~100µs~35µs~8µs
CPU 오버헤드 (10GbE)높음중간낮음

네트워크 스택(Network Stack) 튜닝

# ==================== iSCSI/NVMe-oF 최적 네트워크 설정 ====================

# 1. Jumbo Frame 활성화 (MTU 9000)
$ ip link set eth0 mtu 9000
# 타겟/이니시에이터 양쪽 + 중간 스위치 모두 설정해야 함

# 2. TCP 버퍼 크기 확대
$ sysctl -w net.core.rmem_max=16777216
$ sysctl -w net.core.wmem_max=16777216
$ sysctl -w net.ipv4.tcp_rmem="4096 1048576 16777216"
$ sysctl -w net.ipv4.tcp_wmem="4096 1048576 16777216"

# 3. iSCSI 전용 IRQ 어피니티 설정
# 스토리지 네트워크 NIC의 IRQ를 전용 CPU에 할당
$ echo 2 > /proc/irq/$(cat /proc/interrupts | grep eth0 | awk '{print $1}' | tr -d ':')/smp_affinity

# 4. TCP 큐 깊이 (RX/TX Ring 버퍼)
$ ethtool -G eth0 rx 4096 tx 4096

# 5. TSO/GSO 활성화 확인
$ ethtool -K eth0 tso on gso on gro on
iSCSI 성능 병목 진단 순서: (1) iostat -x 1로 백엔드 디바이스 활용률 확인 → (2) sar -n DEV 1로 네트워크 대역폭 포화 확인 → (3) perf top으로 CPU 핫스팟 확인 (커널 TCP 스택 vs TCM 처리) → (4) targetcli ls에서 세션별 큐 깊이 확인. 일반적으로 10GbE iSCSI의 병목은 CPU(TCP 처리)이며, 25GbE 이상에서는 NVMe-oF/RDMA로 전환하는 것이 효과적입니다.

백엔드별 성능 최적화

백엔드최적화 항목설정 방법
iblockI/O 스케줄러(Scheduler)echo none > /sys/block/sdX/queue/scheduler (NVMe는 기본 none)
iblock큐 깊이targetcli set attrib queue_depth=256
iblockBlock 사이즈targetcli set attrib block_size=4096 (4K 정렬)
fileiowrite_back성능 우선 시 write_back=true, 안전 우선 시 false
fileioemulate_tpuUNMAP/TRIM 에뮬레이션: set attrib emulate_tpu=1
ramdiskrd_pages할당 페이지(Page) 수로 용량 제어

커널 소스 구조

경로내용
drivers/target/LIO Target Core 루트
drivers/target/target_core_mod.cTCM 코어 — 명령 처리, PR, ALUA
drivers/target/target_core_file.cfileio 백엔드
drivers/target/target_core_iblock.cblock 백엔드 (블록 디바이스)
drivers/target/target_core_pscsi.cpscsi 패스스루 백엔드
drivers/target/target_core_alua.cALUA 구현
drivers/target/target_core_pr.cPersistent Reservations 구현
drivers/target/target_core_configfs.cconfigfs 인터페이스
drivers/target/iscsi/iSCSI Fabric 모듈
drivers/target/loopback/Loopback Fabric (테스트용)
drivers/nvme/target/NVMe-oF 타겟 (nvmet)
include/target/LIO 공개 헤더
# 주요 Kconfig 옵션
CONFIG_TARGET_CORE=m              # LIO Target Core
CONFIG_ISCSI_TARGET=m             # iSCSI Fabric 모듈
CONFIG_LOOPBACK_TARGET=m          # Loopback 테스트 Fabric
CONFIG_TCM_FC=m                   # FC Fabric (tcm_qla2xxx 등)
CONFIG_NVME_TARGET=m              # NVMe-oF 타겟 코어
CONFIG_NVME_TARGET_TCP=m          # NVMe-oF TCP 전송
CONFIG_NVME_TARGET_RDMA=m         # NVMe-oF RDMA 전송

디버깅(Debugging) 도구

# targetcli 현재 설정 전체 보기
$ targetcli ls /

# 활성 세션 확인
$ targetcli /iscsi/iqn.2024-01.com.example:storage.lun0/tpgt1/acls/ ls

# /proc를 통한 iSCSI 세션 정보
$ cat /proc/net/iscsi/session
$ cat /proc/net/iscsi/connection

# LIO tracepoint 활성화
$ ls /sys/kernel/debug/tracing/events/target/
$ echo 1 > /sys/kernel/debug/tracing/events/target/target_cmd_complete/enable

# nvmet 디버그 로그
$ dmesg | grep nvmet

# iSCSI 세션 통계
$ cat /sys/kernel/config/target/iscsi/iqn.2024-01.com.example:storage.lun0/tpgt_1/sess_err_stats

# 백엔드 I/O 통계
$ cat /sys/kernel/config/target/core/iblock_0/lun0/statistics/scsi_dev/

# bpftrace로 TCM 완료 레이턴시 측정
$ bpftrace -e '
tracepoint:target:target_cmd_complete {
    @usecs = hist(args->data_length);
}'

트러블슈팅 가이드

LIO 타겟 운영 중 자주 발생하는 문제와 해결 방법을 정리합니다.

자주 발생하는 문제

증상원인해결 방법
이니시에이터에서 타겟 발견 실패방화벽(Firewall), 포탈 바인딩 오류ss -tlnp | grep 3260 확인, 방화벽 규칙 점검
로그인 시 CHAP 인증 실패자격 증명 불일치타겟/이니시에이터 양쪽 username/password 일치 확인
RESERVATION CONFLICT 에러PR이 설정된 LUN에 비등록 이니시에이터 접근sg_persist --in --read-full-status로 예약 상태 확인
세션 끊김 반복 (flapping)네트워크 불안정, Nop-Out 타임아웃noop_out_timeout 증가, 네트워크 품질 점검
I/O 레이턴시 급증백엔드 디바이스 포화, 큐 깊이 부족iostat -x 확인, queue_depth 증가
saveconfig 후 재부팅 시 설정 복원 실패/etc/target/saveconfig.json 손상JSON 파일 수동 검증, 백업에서 복원
NVMe-oF 이니시에이터에서 네임스페이스 미출력서브시스템-포트 심볼릭 링크 누락ls -la /sys/kernel/config/nvmet/ports/*/subsystems/ 확인

진단 명령 모음

# ==================== iSCSI 타겟 진단 ====================

# 활성 세션 목록
$ targetcli sessions list
# 또는
$ ls /sys/kernel/config/target/iscsi/*/tpgt_*/acls/*/info

# 세션별 연결 상태
$ cat /sys/kernel/config/target/iscsi/iqn.*/tpgt_1/param/MaxRecvDataSegmentLength

# iSCSI 에러 카운터
$ find /sys/kernel/config/target/iscsi/ -name "sess_err_stats" -exec cat {} \;

# LUN별 I/O 통계
$ find /sys/kernel/config/target/core/ -name "statistics" -exec ls {} \;

# ==================== NVMe-oF 타겟 진단 ====================

# nvmet 서브시스템 상태
$ cat /sys/kernel/config/nvmet/subsystems/*/attr_serial
$ ls /sys/kernel/config/nvmet/subsystems/*/namespaces/*/

# 연결된 컨트롤러 확인
$ dmesg | grep "nvmet: creating controller"

# ==================== 공통 진단 ====================

# TCM 디바이스 상태
$ cat /sys/kernel/config/target/core/*/*/info

# 커널 로그에서 타겟 관련 메시지
$ dmesg | grep -E '(target|iscsi|nvmet)' | tail -50

# configfs 설정 JSON 내보내기 (백업용)
$ targetcli saveconfig /tmp/target-backup-$(date +%Y%m%d).json
saveconfig 백업: targetcli saveconfig/etc/target/saveconfig.json에 설정을 저장합니다. 이 파일은 부팅 시 target.service가 자동으로 복원합니다. 프로덕션 환경에서는 이 파일을 정기적으로 백업하고 버전 관리하세요. 설정 변경 전에 targetcli saveconfig /tmp/backup.json으로 스냅샷을 먼저 저장하는 습관을 들이면 장애 시 빠르게 복구할 수 있습니다.

TCM 백엔드

TCM은 네 가지 커널 내장 백엔드(fileio, iblock, pscsi, ramdisk)와 하나의 사용자 공간 백엔드(TCMU)를 제공합니다. 각 백엔드는 내부 I/O 경로가 크게 다르며, 워크로드 특성에 맞는 선택이 성능과 안정성에 직접적으로 영향을 줍니다.

Target Core (TCM) — se_cmd dispatch fileio fd_execute_rw() VFS: vfs_readv/writev (페이지 캐시 경유) 파일시스템 (ext4/xfs) iblock iblock_execute_rw() submit_bio() 직접 호출 (VFS 우회) pscsi pscsi_execute_cmd() scsi_execute_cmd() (SCSI 미드레이어 경유) TCMU tcmu_queue_cmd() UIO + mmap (유저스페이스) tcmu-runner 핸들러 블록 계층 (blk-mq) / SCSI 미드레이어 / Ceph RBD·GlusterFS 물리 스토리지 (NVMe SSD / HDD / SAN LUN / 네트워크 스토리지) * fileio: 페이지 캐시 + VFS 경유 → 유연하나 오버헤드 큼 * iblock: submit_bio() 직접 → 최고 성능, 블록 디바이스 필요 * pscsi: CDB 패스스루 → ALUA/PR 미지원, 특수 SCSI 장치용

fileio 내부 구조

fileio 백엔드는 target_core_file.c에 구현되어 있으며, VFS(vfs_readv()/vfs_writev())를 통해 파일에 I/O를 수행합니다. 페이지 캐시(Page Cache)를 활용하므로 읽기 캐싱이 가능하지만, 쓰기 안전성은 write_back 옵션에 따라 달라집니다.

/* fileio 백엔드 디바이스 구조체 */
struct fd_dev {
    struct se_device    dev;
    struct file         *fd_file;       /* 열린 파일 디스크립터 */
    u32                 fbd_flags;      /* FDBD_HAS_BUFFERED_IO_WCE 등 */
    struct bio_set       bs;
};

/* fileio write_back 모드 동작 차이 */
/* write_back=false (기본): O_SYNC로 열림 → 매 쓰기마다 디스크 동기화 */
/* write_back=true: O_DSYNC 없이 열림 → 페이지 캐시에만 기록, 커널이 비동기 flush */

static sense_reason_t
fd_execute_rw(struct se_cmd *cmd, struct scatterlist *sgl, u32 sgl_nents,
              enum dma_data_direction data_direction)
{
    struct fd_dev *fd_dev = FD_DEV(cmd->se_dev);
    struct file *file = fd_dev->fd_file;
    struct scatterlist *sg;
    struct iov_iter iter;
    ssize_t ret;
    loff_t pos = cmd->t_task_lba * cmd->se_dev->dev_attrib.block_size;

    /* scatterlist를 iov_iter로 변환 후 VFS I/O 수행 */
    iov_iter_bvec(&iter, ...);

    if (data_direction == DMA_FROM_DEVICE)
        ret = vfs_iter_read(file, &iter, &pos, 0);
    else
        ret = vfs_iter_write(file, &iter, &pos, 0);

    /* write_back=false이면 추가로 vfs_fsync_range() 호출 */
    if (!fd_dev->fbd_flags & FDBD_HAS_BUFFERED_IO_WCE)
        vfs_fsync_range(file, pos, pos + cmd->data_length - 1, 1);

    target_complete_cmd(cmd, SAM_STAT_GOOD);
    return 0;
}

iblock 내부 구조

iblock 백엔드는 VFS를 우회하여 submit_bio()로 블록 디바이스에 직접 I/O를 수행합니다. SCSI 명령의 scatter/gather 리스트를 struct bio로 변환하여 블록 계층에 전달합니다.

/* iblock 핵심: scatterlist → bio 변환 후 submit */
static sense_reason_t
iblock_execute_rw(struct se_cmd *cmd, struct scatterlist *sgl, u32 sgl_nents,
                  enum dma_data_direction data_direction)
{
    struct iblock_dev *ib_dev = IBLOCK_DEV(cmd->se_dev);
    struct block_device *bdev = ib_dev->ibd_bd;
    struct bio *bio;
    sector_t block_lba = cmd->t_task_lba;

    /* bio 할당 및 블록 디바이스 지정 */
    bio = bio_alloc(bdev, sgl_nents, ...);
    bio->bi_iter.bi_sector = block_lba;
    bio->bi_end_io = iblock_bio_done;

    /* scatterlist의 각 세그먼트를 bio에 추가 */
    for_each_sg(sgl, sg, sgl_nents, i)
        bio_add_page(bio, sg_page(sg), sg->length, sg->offset);

    /* 블록 계층에 직접 제출 — VFS/파일시스템 무관 */
    submit_bio(bio);
    return 0;
}

/* iblock은 WRITE_SAME, UNMAP(TRIM)도 네이티브 지원 */
static sense_reason_t
iblock_execute_unmap(struct se_cmd *cmd, sector_t lba, sector_t nolb)
{
    /* blkdev_issue_discard()로 TRIM/UNMAP 전달 */
    blkdev_issue_discard(ib_dev->ibd_bd, lba, nolb, GFP_KERNEL);
}

백엔드 선택 가이드

기준fileioiblockpscsiTCMU
I/O 경로 길이VFS+FS+BlockBlock 직접SCSI mid커널↔유저
4K 랜덤 읽기 IOPS~70% of iblock기준 (100%)~80%~50-60%
희소 파일 / 씬 프로비저닝지원미지원미지원백엔드 의존
ALUA / PR 지원완전완전제한적완전
WRITE_SAME / UNMAP에뮬레이션네이티브디바이스 의존핸들러(Handler) 의존
캐싱페이지 캐시없음 (O_DIRECT)없음핸들러 의존
권장 사용개발/테스트프로덕션 SAN테이프/특수 SCSICeph/분산 스토리지

TCMU (TCM in Userspace)

TCMU는 커널 TCM 프레임워크를 사용자 공간 백엔드로 확장하는 브릿지입니다. target_core_user.c에서 커널 측 구현을, tcmu-runner 데몬이 사용자 공간 측 핸들러 디스패치(Dispatch)를 담당합니다. Ceph RBD, GlusterFS, QCOW2 등 커널에 없는 스토리지 백엔드를 LIO 타겟으로 내보낼 수 있습니다.

커널 공간 사용자 공간 Target Core (TCM) target_core_user.ko tcmu_queue_cmd() → ring buffer에 cmd 엔큐 공유 메모리 (mmap) — tcmu_mailbox + 명령 링 버퍼 cmd_head (커널→유저) | cmd_tail (유저→커널) | data area (scatterlist) UIO eventfd 알림 커널/유저 경계 tcmu-runner 데몬 poll(UIO fd) → ring buffer 디큐 → 핸들러 디스패치 handler_rbd Ceph RBD handler_glfs GlusterFS handler_qcow QCOW2 이미지 handler_file_* 파일 기반 (POSIX) Ceph 클러스터 Gluster 볼륨

TCMU 링 버퍼 프로토콜

커널과 사용자 공간은 mmap()으로 공유되는 메모리 영역을 통해 SCSI 명령을 교환합니다. 링 버퍼의 구조는 다음과 같습니다.

/* TCMU 공유 메모리 레이아웃 */
/*
 * +---------------------------+  오프셋 0
 * | struct tcmu_mailbox       |  헤더 (버전, 플래그, 포인터)
 * +---------------------------+  cmdr_off
 * | Command Ring Buffer       |  tcmu_cmd_entry 배열
 * |  [cmd_head → 커널이 push]  |  커널: head 증가 → UIO 알림
 * |  [cmd_tail → 유저가 pop]   |  유저: tail 증가 → 완료 통보
 * +---------------------------+  cmdr_off + cmdr_size
 * | Data Area                 |  scatter/gather 데이터 복사 영역
 * | (iov[].iov_base 오프셋)    |
 * +---------------------------+
 */

/* 커널 → 유저: 새 명령 전달 */
static int tcmu_queue_cmd(struct se_cmd *se_cmd)
{
    struct tcmu_cmd_entry *entry;

    /* 링 버퍼에 공간 확인 */
    entry = tcmu_get_cmd_entry(udev, cmd->cmd_id);

    /* CDB 복사, 데이터 영역 iov 설정 */
    memcpy((void *)mb + entry->req.cdb_off, se_cmd->t_task_cdb, scsi_command_size(...));

    /* cmd_head 증가 → mb->cmd_head에 새 위치 기록 */
    smp_store_release(&mb->cmd_head, new_head);

    /* UIO eventfd로 유저에게 새 명령 알림 */
    uio_event_notify(&udev->uio_info);
    return 0;
}

tcmu-runner 설정 예시

# tcmu-runner 주요 설정 파일: /etc/tcmu/tcmu.conf

# 로그 레벨 (0=error, 1=warning, 2=info, 3=debug)
log_level = 2

# 핸들러별 최대 동시 I/O 수
max_iov_per_cmd = 1024

# Ceph RBD 핸들러 설정
[handler_rbd]
    osd_op_timeout = 30

# GlusterFS 핸들러 설정
[handler_glfs]
    glfs_log_level = WARNING

# =====================================================
# TCMU 백엔드 생성 예시

# Ceph RBD 백엔드
$ targetcli /backstores/user:rbd create name=ceph_disk0 \
    size=500G cfgstring=rbd_pool/disk0

# GlusterFS 백엔드
$ targetcli /backstores/user:glfs create name=gluster_vol0 \
    size=200G cfgstring=gluster_vol/disk0

# TCMU 디바이스 상태 확인
$ cat /sys/kernel/config/target/core/user_*/*/info
Status: ACTIVATED
HBA: user
Vendor: LIO-ORG
Model: tcmu-runner
TCMU 타임아웃: tcmu-runner가 명령을 지정 시간 내에 완료하지 못하면 커널 TCMU 드라이버가 타임아웃을 발생시킵니다. /sys/kernel/config/target/core/user_*/*/control에서 cmd_time_out 값(초)을 조정할 수 있습니다. 기본값은 30초이며, Ceph RBD처럼 네트워크 지연(Latency)이 큰 환경에서는 60초 이상으로 설정하는 것을 권장합니다.

ALUA 상태 전이 다이어그램

ALUA(Asymmetric Logical Unit Access)는 SPC-4 표준에 정의된 5가지 접근 상태와 1가지 전환 상태를 가집니다. 상태 전이는 명시적(Explicit, 이니시에이터 요청) 또는 암시적(Implicit, 타겟 자체 판단)으로 발생합니다.

Active/Optimized (AO) I/O 허용 — 최적 경로, 최소 레이턴시 Active/Non-Optimized (ANO) I/O 허용 — 비최적 경로, 성능 저하 Standby (SB) I/O 거부 — 페일오버 대기 Unavailable (UA) I/O 거부 — 포트 사용 불가 Offline (OF) I/O 거부 — 오프라인 Transitioning (TR) 상태 전환 중 — 일시적 I/O 거부 STPG 승격 STPG 페일오버 장애 해제 전환 중

명시적 vs 암시적 ALUA 전환

전환 방식발동 주체SCSI 명령사용 사례
명시적(Explicit)이니시에이터SET TARGET PORT GROUPS (STPG)수동 페일오버, multipathd 정책
암시적(Implicit)타겟 자체내부 이벤트 (장애 감지 등)자동 페일오버, 컨트롤러 이상
혼합양쪽 모두STPG + 내부 이벤트대부분의 프로덕션 환경
# ALUA 전환 모드 설정
# implicit_trans_secs: 암시적 전환 대기 시간 (0=즉시)
$ echo 10 > /sys/kernel/config/target/core/iblock_0/lun0/alua/default_tg_pt_gp/implicit_trans_secs

# ALUA 전환 유형 설정
# alua_access_type: 1=implicit only, 2=explicit only, 3=both
$ echo 3 > /sys/kernel/config/target/core/iblock_0/lun0/alua/default_tg_pt_gp/alua_access_type

# 이니시에이터에서 ALUA 상태 조회
$ sg_rtpg -d /dev/sdb
Report target port groups:
  target port group 0 (hex):
    Active/Optimized
    T_SUP=1, O_SUP=1, U_SUP=1, S_SUP=1, AN_SUP=1, AO_SUP=1
  target port group 1 (hex):
    Standby
    T_SUP=1, O_SUP=1, U_SUP=1, S_SUP=1, AN_SUP=1, AO_SUP=1

# 명시적 ALUA 상태 전환 (STPG 명령)
$ sg_stpg --set=0:ao --set=1:s /dev/sdb
# 그룹 0을 Active/Optimized, 그룹 1을 Standby로 전환
multipath.conf ALUA 연동: multipathd가 ALUA 상태를 올바르게 인식하려면 /etc/multipath.conf에 다음 설정이 필요합니다: prio "alua", path_grouping_policy "group_by_prio", failback "immediate". 이 설정으로 Active/Optimized 경로를 우선 사용하고, 장애 시 Active/Non-Optimized나 Standby 경로로 자동 전환됩니다.

Persistent Reservations 동작

SCSI Persistent Reservations(PR)은 SPC-4 표준에 정의된 고급 잠금 메커니즘으로, 클러스터 환경에서 여러 이니시에이터 간 LUN 접근을 제어합니다. PR은 일시적인 SCSI Reserve/Release와 달리 서버 재부팅 후에도 예약이 유지됩니다.

이니시에이터 A Key: 0xAAAA0001 이니시에이터 B Key: 0xBBBB0002 1단계: REGISTER (PROUT Service Action 0x00) 양쪽 이니시에이터가 각자의 키를 등록 → 디바이스의 등록자 목록에 추가 2단계: RESERVE (PROUT Service Action 0x01) 이니시에이터 A가 Exclusive Access(0x03)로 예약 획득 → B의 I/O 거부됨 B: CONFLICT 3단계: I/O 처리 (예약 중) A: READ/WRITE 허용 B: RESERVATION CONFLICT 4단계: PREEMPT (PROUT Service Action 0x04) B가 A의 키를 PREEMPT → A의 예약 해제, B가 새 예약 보유자 5단계: RELEASE (PROUT Service Action 0x02) B가 자발적으로 예약 해제 → 양쪽 모두 I/O 가능 (등록은 유지) 최종 상태: 예약 없음, 등록 키 유지 (A: 0xAAAA0001, B: 0xBBBB0002)

PR 타입별 접근 제어 매트릭스

PR 타입코드보유자 읽기보유자 쓰기비보유자 읽기비보유자 쓰기등록자 쓰기
Write Exclusive0x01허용허용허용거부거부
Exclusive Access0x03허용허용거부거부거부
WE, All Registrants0x05허용허용허용거부허용
EA, All Registrants0x06허용허용거부거부허용

PR Aptpl (Persist Through Power Loss)

/* LIO는 PR 데이터를 디스크에 저장하여 재부팅 후에도 유지 (Aptpl) */
/* target_core_pr.c: __core_scsi3_do_alloc_registration() */

static int
core_scsi3_update_aptpl_buf(struct se_device *dev, unsigned char *buf, u32 pr_aptpl_buf_len)
{
    struct t10_pr_registration *pr_reg;

    /* 모든 등록 정보를 텍스트 형식으로 직렬화 */
    list_for_each_entry(pr_reg, &dev->t10_pr.registration_list, pr_reg_list) {
        snprintf(buf + off, ...,
            "HA=%d\nRK=0x%016llx\nTYPE=0x%02x\n",
            pr_reg->pr_reg_aptpl,
            pr_reg->pr_res_key,
            pr_reg->pr_res_type);
    }
    /* 결과를 configfs 아래 파일에 기록 */
    return core_scsi3_pr_write_aptpl(dev, buf, pr_aptpl_buf_len);
}

/* Aptpl 활성화: sg_persist 명령에서 --param-aptpl 플래그 사용 */
# Aptpl(재부팅 유지) PR 등록
$ sg_persist --out --register --param-rk=0x0000000000000001 --param-aptpl /dev/sdb

# PR 등록 전체 상태 확인 (Full Status 포함)
$ sg_persist --in --read-full-status /dev/sdb
  Registration Key: 0x0000000000000001
  Transport ID: iqn.1991-05.com.redhat:client
  Relative Target Port Identifier: 0x0001
  Aptpl: 1 (활성)

# PREEMPT AND ABORT: 기존 보유자를 강제 퇴거 + 진행 중 I/O 중단
$ sg_persist --out --preempt-abort --param-rk=0xBBBB0002 --param-sark=0xAAAA0001 \
    --prout-type=3 /dev/sdb

configfs 트리 구조 상세

LIO의 전체 설정은 /sys/kernel/config/target/ 아래 계층적 디렉토리 트리로 표현됩니다. 각 디렉토리와 파일은 커널 struct config_item에 1:1 매핑되며, mkdir/rmdir/echo로 객체를 생성/삭제/설정합니다.

/sys/kernel/config/target/ core/ (백엔드 스토리지) fileio_0/disk1/ iblock_0/lun0/ enable, udev_path, attrib/ alua/, statistics/, info iscsi/ (iSCSI Fabric) iqn.2024-01...:storage/ tpgt_1/ acls/ (이니시에이터별 ACL) lun/ (LUN→backstore 심볼릭링크) np/ (네트워크 포탈 IP:포트) nvmet/ (NVMe-oF) subsystems/ ports/ nqn.2024-01...:nvme0/ ├── namespaces/1/ (device_path, enable) ├── allowed_hosts/ (NQN 허용 목록) └── attr_allow_any_host 1/ addr_traddr, addr_trsvcid addr_trtype, subsystems/→ 주요 configfs 노드 역할 core/<type>_N/<name>/enable ............. 백엔드 디바이스 활성화 (echo 1 > enable) core/<type>_N/<name>/attrib/ ............ block_size, queue_depth, emulate_tpu 등 디바이스 속성 core/<type>_N/<name>/alua/ .............. ALUA 포트 그룹 설정 (alua_access_state, access_type) iscsi/<iqn>/tpgt_N/acls/ ............... 이니시에이터 ACL (userid, password, mapped_lun) iscsi/<iqn>/tpgt_N/lun/lun_N → core/... LUN과 백엔드 디바이스 간 심볼릭 링크 매핑 iscsi/<iqn>/tpgt_N/np/<ip>:<port> ..... 네트워크 포탈 (수신 대기 주소) iscsi/<iqn>/tpgt_N/param/ .............. iSCSI 파라미터 (MaxRecvDataSegmentLength 등) nvmet/subsystems/<nqn>/namespaces/N/ .... NVMe 네임스페이스 (device_path, enable) nvmet/ports/N/subsystems/ → symlink ..... 포트와 서브시스템 연결 nvmet/ports/N/addr_trtype ............... 전송 유형 (tcp, rdma, fc)

configfs 직접 조작 (targetcli 없이)

# targetcli를 쓰지 않고 configfs를 직접 조작하는 로우레벨 방법
# 자동화 스크립트, Ansible, 임베디드 환경에서 유용

# 1. configfs 마운트 확인
$ mount | grep configfs
configfs on /sys/kernel/config type configfs (rw,relatime)

# 2. TCM 코어 모듈 로드
$ modprobe target_core_mod
$ modprobe target_core_iblock

# 3. HBA(Host Bus Adapter) 생성
$ mkdir -p /sys/kernel/config/target/core/iblock_0

# 4. 백엔드 디바이스 생성
$ mkdir /sys/kernel/config/target/core/iblock_0/data_disk
$ echo "udev_path=/dev/sdb" > /sys/kernel/config/target/core/iblock_0/data_disk/control
$ echo 1 > /sys/kernel/config/target/core/iblock_0/data_disk/enable

# 5. iSCSI Fabric 모듈 로드 및 타겟 생성
$ modprobe iscsi_target_mod
$ mkdir -p /sys/kernel/config/target/iscsi/iqn.2024-01.com.example:storage
$ mkdir /sys/kernel/config/target/iscsi/iqn.2024-01.com.example:storage/tpgt_1

# 6. LUN 매핑 (심볼릭 링크로 연결)
$ mkdir /sys/kernel/config/target/iscsi/iqn.2024-01.com.example:storage/tpgt_1/lun/lun_0
$ ln -s /sys/kernel/config/target/core/iblock_0/data_disk \
    /sys/kernel/config/target/iscsi/iqn.2024-01.com.example:storage/tpgt_1/lun/lun_0/data_disk

# 7. 네트워크 포탈 생성
$ mkdir /sys/kernel/config/target/iscsi/iqn.2024-01.com.example:storage/tpgt_1/np/0.0.0.0:3260

# 8. ACL 생성
$ mkdir -p /sys/kernel/config/target/iscsi/iqn.2024-01.com.example:storage/tpgt_1/acls/iqn.1991-05.com.redhat:client

# 9. TPG 활성화
$ echo 1 > /sys/kernel/config/target/iscsi/iqn.2024-01.com.example:storage/tpgt_1/enable
saveconfig.json 구조: targetcli saveconfig은 configfs 트리를 JSON 형식으로 직렬화(Serialization)합니다. /etc/target/saveconfig.json 파일에는 backstores, targets, acls, luns 등 모든 설정이 포함되며, target.service(systemd)가 부팅 시 이 JSON을 읽어 configfs에 복원합니다. 이 JSON을 직접 편집하여 대량 설정을 자동화할 수도 있지만, 구문 오류 시 전체 복원이 실패할 수 있으므로 주의가 필요합니다.

iSCSI 타겟 인증

iSCSI 타겟의 인증은 CHAP(Challenge-Handshake Authentication Protocol)을 기반으로 합니다. 단방향 CHAP은 타겟이 이니시에이터를 인증하고, 양방향(Mutual) CHAP은 양쪽이 서로를 인증합니다. Discovery 단계에서도 별도의 인증을 설정할 수 있습니다.

이니시에이터 타겟 (LIO) Login Request: AuthMethod=CHAP,None Login Response: AuthMethod=CHAP --- 단방향 CHAP (타겟→이니시에이터 인증) --- CHAP_A=5 (알고리즘: MD5) CHAP_I=id, CHAP_C=challenge (랜덤 바이트) CHAP_N=username, CHAP_R=MD5(id+secret+challenge) 응답 검증 성공 --- 양방향 CHAP (이니시에이터→타겟 인증) --- CHAP_I=id2, CHAP_C=challenge2 (타겟→이니시에이터 도전) 이니시에이터: CHAP_R=MD5(id2+mutual_secret+challenge2) 전송 타겟 검증 성공 --- Full Feature Phase --- Login Final Response: Status=0x0000 (성공) SCSI Command PDU 교환 시작

Discovery 인증

Discovery 단계에서도 별도의 CHAP 인증을 설정할 수 있습니다. 이를 통해 타겟 목록 자체의 노출을 제한합니다.

# Discovery 인증 설정 (타겟 측)
# discovery_auth는 TPG가 아닌 글로벌 설정

$ targetcli /iscsi set discovery_auth enable=1
$ targetcli /iscsi set discovery_auth userid=disc_user
$ targetcli /iscsi set discovery_auth password=D1sc0v3ry!
# 양방향 Discovery CHAP
$ targetcli /iscsi set discovery_auth mutual_userid=disc_target
$ targetcli /iscsi set discovery_auth mutual_password=D1scT@rg3t!

# 이니시에이터 측 Discovery CHAP 설정 (/etc/iscsi/iscsid.conf)
discovery.sendtargets.auth.authmethod = CHAP
discovery.sendtargets.auth.username = disc_user
discovery.sendtargets.auth.password = D1sc0v3ry!
discovery.sendtargets.auth.username_in = disc_target
discovery.sendtargets.auth.password_in = D1scT@rg3t!

# Discovery 테스트
$ iscsiadm -m discovery -t st -p 10.0.100.1:3260
# Discovery CHAP이 활성화되면 인증 실패 시 타겟 목록을 받지 못함

ACL 관리 전략

설정동작보안 수준사용 사례
generate_node_acls=0 + CHAP명시적 ACL + CHAP 인증 필요최고프로덕션
generate_node_acls=0 + 인증 없음명시적 ACL만 필요 (IQN 검증)중간폐쇄 네트워크
generate_node_acls=1 + CHAP자동 ACL + CHAP 인증중간동적 환경
generate_node_acls=1 + 인증 없음모든 이니시에이터 무조건 허용없음개발/테스트만
# ACL별 LUN 매핑 (세밀한 접근 제어)
# 이니시에이터 A에는 LUN 0만, B에는 LUN 0+1 노출

# 이니시에이터 A: LUN 0만 매핑
/> iscsi/.../tpgt1/acls/iqn...:clientA/ create mapped_lun=0 tpg_lun_or_backstore=/backstores/block/lun0

# 이니시에이터 B: LUN 0, 1 모두 매핑
/> iscsi/.../tpgt1/acls/iqn...:clientB/ create mapped_lun=0 tpg_lun_or_backstore=/backstores/block/lun0
/> iscsi/.../tpgt1/acls/iqn...:clientB/ create mapped_lun=1 tpg_lun_or_backstore=/backstores/block/lun1

# ACL 단위 쓰기 보호 (읽기 전용 LUN)
/> iscsi/.../tpgt1/acls/iqn...:clientA/mapped_lun_0 set write_protect=1

NVMe-oF Target

NVMe-oF 타겟(nvmet)은 LIO/TCM과는 독립된 코드 베이스를 가지지만, 유사한 configfs 기반 설정 모델을 따릅니다. TCP, RDMA, FC 세 가지 전송 계층을 지원하며, ANA(Asymmetric Namespace Access)로 멀티패스를 구현합니다.

NVMe-oF 이니시에이터 (nvme connect) nvmet-tcp TCP (포트 4420) nvmet-rdma RDMA (RoCE/IB) nvmet-fc FC (lpfc/qla2xxx) nvmet 코어 (nvmet.ko) NVMe Admin/IO 명령 처리, ANA, 컨트롤러 관리 NVMe Subsystem (nqn.2024-01.com.example:nvme0) nvmet_subsys → nvmet_ns[] → block_device / file ANA 그룹 1 (Optimized) NS 1, 2 — Port 1 (TCP:4420) ANA 그룹 2 (Non-Optimized) NS 1, 2 — Port 2 (TCP:4420) 백엔드: /dev/nvme0n1 (블록 디바이스) 또는 /path/to/file (파일)

ANA (Asymmetric Namespace Access) 설정

ANA는 NVMe-oF에서 ALUA에 해당하는 멀티패스 메커니즘입니다. 네임스페이스별로 접근 상태(Optimized, Non-Optimized, Inaccessible)를 포트 그룹 단위로 지정합니다.

# NVMe-oF ANA 그룹 설정 (configfs 직접 조작)

# ANA 그룹 ID 지정 (네임스페이스에 할당)
$ echo 1 > /sys/kernel/config/nvmet/subsystems/nqn.2024-01.com.example:nvme0/namespaces/1/ana_grpid

# 포트별 ANA 상태 설정
# 포트 1: 그룹 1 = optimized
$ mkdir -p /sys/kernel/config/nvmet/ports/1/ana_groups/1
$ echo optimized > /sys/kernel/config/nvmet/ports/1/ana_groups/1/ana_state

# 포트 2: 그룹 1 = non-optimized (대체 경로)
$ mkdir -p /sys/kernel/config/nvmet/ports/2/ana_groups/1
$ echo non-optimized > /sys/kernel/config/nvmet/ports/2/ana_groups/1/ana_state

# 이니시에이터에서 ANA 경로 확인
$ nvme list-subsys /dev/nvme0n1
+- nvme0 tcp traddr=10.0.100.1,trsvcid=4420,src_addr=10.0.100.10 live optimized
+- nvme1 tcp traddr=10.0.100.2,trsvcid=4420,src_addr=10.0.100.10 live non-optimized

# multipath 상태 확인
$ cat /sys/class/nvme-subsystem/nvme-subsys0/iopolicy
numa    # 또는 round-robin

# I/O 정책 변경
$ echo round-robin > /sys/class/nvme-subsystem/nvme-subsys0/iopolicy

NVMe-oF TLS 1.3 암호화 (Linux 6.7+)

# NVMe-oF TCP TLS 1.3 설정 (Linux 6.7 이상)

# 1. Pre-Shared Key(PSK) 생성
$ nvme gen-tls-key --hmac=1 --identity=nqn.2024-01.com.example:nvme0 \
    --secret=0x$(openssl rand -hex 32)

# 2. 타겟 측: TLS 활성화
$ echo 1 > /sys/kernel/config/nvmet/ports/1/addr_tsas
# addr_tsas: Transport Specific Address Subtype (1=TLS)

# 3. 이니시에이터 측: TLS로 연결
$ nvme connect -t tcp -a 10.0.100.1 -s 4420 \
    -n nqn.2024-01.com.example:nvme0 --tls

# TLS 연결 상태 확인
$ nvme list-subsys /dev/nvme0n1
+- nvme0 tcp traddr=10.0.100.1,trsvcid=4420 live optimized tls
NVMe-oF vs iSCSI 인증 비교: iSCSI는 CHAP으로 인증하지만 데이터 암호화는 별도(IPsec)가 필요합니다. NVMe-oF/TCP는 Linux 6.7+에서 TLS 1.3을 네이티브 지원하여 인증과 암호화를 동시에 제공합니다. NVMe-oF/RDMA는 현재 암호화를 지원하지 않으므로, 물리적으로 격리된 네트워크에서만 사용해야 합니다.

RTPI와 LUN 매핑

RTPI(Relative Target Port Identifier)는 SPC-4 표준에서 정의된 타겟 포트의 상대적 식별자입니다. 멀티패스 환경에서 이니시에이터가 동일한 LUN에 접근하는 여러 포트를 구별하는 데 사용됩니다. I_T nexus(Initiator-Target nexus)는 특정 이니시에이터와 타겟 포트 간의 논리적 연결을 나타냅니다.

이니시에이터 IQN: iqn...:client Port A (iSCSI) Port B (iSCSI) 타겟 (LIO) TPG 1 / Portal 10.0.1.1:3260 (RTPI=1) TPG 2 / Portal 10.0.2.1:3260 (RTPI=2) TPG 3 / Portal 10.0.3.1:3260 (RTPI=3) I_T Nexus 1 I_T Nexus 2 LUN 매핑 테이블 (se_lun → se_device) LUN 0 (RTPI=1,2,3) → se_device: iblock_0/data_disk (/dev/sdb) LUN 1 (RTPI=1,2) → se_device: fileio_0/backup (/var/lib/target/backup.img) LUN 2 (RTPI=3만) → se_device: user_rbd/ceph_disk (/dev/tcmu/ceph0) * RTPI별 ALUA 포트 그룹 할당 → multipathd가 RTPG로 최적 경로 선택 * ACL에서 mapped_lun으로 이니시에이터별 LUN 가시성 제어 가능

RTPI 설정과 확인

# RTPI 확인 (configfs)
$ cat /sys/kernel/config/target/iscsi/iqn.../tpgt_1/attrib/default_erl
$ cat /sys/kernel/config/target/core/iblock_0/lun0/alua/default_tg_pt_gp/tg_pt_gp_id
# tg_pt_gp_id가 ALUA 포트 그룹 ID, RTPI와 연동됨

# 이니시에이터에서 RTPI 확인
$ sg_rtpg -d /dev/sdb
Reporting descriptor group 0:
  Relative Target Port Identifier: 0x0001
  Target descriptor 1: iqn.2024-01.com.example:storage,t,0x0001

# LUN 매핑 확인
$ sg_luns /dev/sdb
    0000 0000 0000 0000    # LUN 0
    0001 0000 0000 0000    # LUN 1

# I_T nexus 정보 (INQUIRY VPD 페이지 0x83)
$ sg_inq -p 0x83 /dev/sdb
  Relative target port: 0x0001
  Target port group: 0x0000

I_T Nexus 생명주기

/* I_T Nexus = 이니시에이터 포트 + 타겟 포트 조합 */
/* 세션 로그인 시 생성, 로그아웃/타임아웃 시 소멸 */

/* TCM에서 I_T nexus 표현: se_session */
struct se_session {
    struct se_node_acl     *se_node_acl;   /* 이니시에이터 식별 (IQN/WWN) */
    struct se_portal_group  *se_tpg;        /* 타겟 포트 그룹 (RTPI 포함) */
    u64                     sess_bin_isid;  /* iSCSI 세션 식별자 */
    /* PR 등록 키, ALUA 상태가 이 nexus에 바인딩됨 */
};

/* I_T nexus 기반 PR 키 등록 */
/* 같은 이니시에이터라도 다른 타겟 포트를 통해 접근하면 별도 nexus로 취급 */
/* → PR 키는 I_T nexus 단위로 등록됨 */

성능 튜닝

LIO 타겟의 성능은 백엔드 디바이스, 네트워크 스택, TCM 워커 스레드, NUMA 토폴로지(Topology), 큐 깊이 등 다양한 요소에 의해 결정됩니다. 체계적인 벤치마크와 병목 분석이 필요합니다.

TCM 워커 스레드 구조

/* TCM은 I/O 완료 처리를 위해 워크큐를 사용 */
/* 주요 워크큐 */

/* 1. target_completion_wq — I/O 완료 후 Fabric 응답 전송 */
target_completion_wq = alloc_workqueue("target_compl",
    WQ_MEM_RECLAIM | WQ_UNBOUND, 0);

/* 2. iSCSI 수신 스레드 — TCP 소켓 수신 전용 */
/* iscsi_target_mod: 연결당 kthread 1개 생성 */
conn->rx_thread = kthread_run(iscsi_target_rx_thread, conn,
    "iscsi_trx");
conn->tx_thread = kthread_run(iscsi_target_tx_thread, conn,
    "iscsi_ttx");

/* 3. NVMe-oF TCP: io_work 큐 */
/* nvmet-tcp: 연결당 work queue 항목으로 I/O 처리 */
INIT_WORK(&queue->io_work, nvmet_tcp_io_work);

NUMA 배치 최적화

# ==================== NUMA 토폴로지 기반 최적화 ====================

# 1. 스토리지 NIC와 백엔드 디바이스의 NUMA 노드 확인
$ cat /sys/class/net/eth0/device/numa_node
0
$ cat /sys/block/nvme0n1/device/device/numa_node
0
# 같은 NUMA 노드에 있으면 최적

# 2. iSCSI 수신 스레드를 해당 NUMA 노드에 고정
# iSCSI 타겟 kthread PID 확인
$ ps -eLo pid,comm,psr | grep iscsi_t
1234 iscsi_trx   0
1235 iscsi_ttx   2

# NUMA 노드 0의 CPU만 사용하도록 어피니티 설정
$ taskset -p -c 0-7 1234
$ taskset -p -c 0-7 1235

# 3. IRQ 어피니티 — NIC IRQ를 같은 NUMA 노드 CPU에 할당
$ for irq in $(grep eth0 /proc/interrupts | awk '{print $1}' | tr -d ':'); do
    echo 0-7 > /proc/irq/$irq/smp_affinity_list
done

# 4. 블록 디바이스 I/O 완료 CPU 힌트
$ echo 0 > /sys/block/nvme0n1/queue/rq_affinity
# 0=완료를 제출 CPU에서, 1=같은 CPU 그룹에서, 2=제출 CPU에서만

# 5. numactl로 tcmu-runner NUMA 바인딩
$ numactl --cpunodebind=0 --membind=0 tcmu-runner

벤치마크 방법론

# ==================== fio 기반 LIO 성능 벤치마크 ====================

# 이니시에이터에서 실행 (iSCSI/NVMe-oF 연결 후)

# 1. 순차 읽기 대역폭 측정
$ fio --name=seq-read --ioengine=libaio --direct=1 \
    --bs=128k --rw=read --numjobs=4 --iodepth=32 \
    --filename=/dev/sdb --runtime=60 --time_based \
    --group_reporting

# 2. 4K 랜덤 읽기 IOPS 측정
$ fio --name=rand-read --ioengine=libaio --direct=1 \
    --bs=4k --rw=randread --numjobs=16 --iodepth=128 \
    --filename=/dev/sdb --runtime=60 --time_based \
    --group_reporting

# 3. 4K 랜덤 쓰기 레이턴시 측정
$ fio --name=lat-write --ioengine=libaio --direct=1 \
    --bs=4k --rw=randwrite --numjobs=1 --iodepth=1 \
    --filename=/dev/sdb --runtime=60 --time_based \
    --lat_percentiles=1

# 4. 혼합 워크로드 (70% 읽기 / 30% 쓰기)
$ fio --name=mixed --ioengine=libaio --direct=1 \
    --bs=4k --rw=randrw --rwmixread=70 \
    --numjobs=8 --iodepth=64 \
    --filename=/dev/sdb --runtime=120 --time_based

# 타겟 측 병목 분석 (동시 실행)
$ iostat -xz 1                    # 백엔드 디바이스 활용률
$ sar -n DEV 1                    # 네트워크 대역폭
$ perf top -g                     # CPU 핫스팟
$ cat /proc/softirqs              # softirq 분포 (NET_RX 치우침 확인)
튜닝 파라미터기본값권장 범위영향
queue_depth (백엔드)128128-1024동시 처리 I/O 수, 높을수록 처리량 증가
block_size512512-40964K 정렬 시 SSD 성능 최적
MaxRecvDataSegmentLength819265536-262144iSCSI PDU 크기, 순차 I/O 대역폭 증가
MaxBurstLength262144262144-16776192R2T 없는 최대 전송량
ImmediateDataYesYes소규모 쓰기 레이턴시 감소
InitialR2TYesNo초기 데이터 즉시 전송, 레이턴시 감소
MTU15009000 (Jumbo)대역폭 오버헤드 감소
TCP 버퍼(Buffer)자동rmem/wmem 16MB고대역폭 환경 처리량 증가

HA Failover 시나리오

LIO 타겟의 고가용성(HA) 구성은 페일오버 방식에 따라 Active-Passive, Active-Active로 나뉩니다. 공유 스토리지, DRBD 복제, 또는 분산 스토리지(Ceph) 기반으로 구현할 수 있습니다.

HA Failover 시나리오 비교 시나리오 1: DRBD + Pacemaker (Active-Passive, 로컬 디스크 복제) Node A (Active) LIO + DRBD Primary VIP: 10.0.100.10 Node B (Standby) LIO 대기 DRBD Secondary 동기화 장애 시: VIP 이동 + DRBD promote + LIO 시작 RTO: 10-30초 | 데이터 손실: 0 (동기 복제) 시나리오 2: 공유 SAN + Pacemaker (Active-Passive, 공유 LUN) Node A (Active) LIO + SCSI PR 보유 VIP: 10.0.100.10 Node B (Standby) PR 등록만 SAN 연결 유지 공유 SAN LUN (FC/iSCSI) 장애 시: PR preempt + VIP 이동 + LIO 시작 RTO: 5-15초 | SAN 복제 불필요 시나리오 3: Ceph TCMU + ALUA Active-Active (양쪽 모두 I/O 처리, ALUA로 경로 우선순위 제어) Node A LIO + TCMU (handler_rbd) ALUA: Active/Optimized 10.0.100.1:3260 Node B LIO + TCMU (handler_rbd) ALUA: Active/Non-Optimized 10.0.100.2:3260 Ceph 클러스터 (RBD 이미지) 이니시에이터: multipathd + ALUA prio

DRBD + LIO 구성 상세

# ==================== DRBD + Pacemaker + LIO 전체 구성 ====================

# === 1단계: DRBD 리소스 설정 (양쪽 노드) ===
# /etc/drbd.d/lio.res
resource lio-data {
    protocol C;          # 동기 복제 (데이터 손실 0)
    disk {
        al-extents 6433;
        c-fill-target 24M;
    }
    net {
        max-buffers 8192;
        max-epoch-size 8192;
    }
    on node1 {
        device /dev/drbd0;
        disk /dev/sdb;
        address 10.0.200.1:7789;
        meta-disk internal;
    }
    on node2 {
        device /dev/drbd0;
        disk /dev/sdb;
        address 10.0.200.2:7789;
        meta-disk internal;
    }
}

# DRBD 초기화 (양쪽 노드)
$ drbdadm create-md lio-data
$ drbdadm up lio-data
# Primary 노드에서만:
$ drbdadm primary --force lio-data

# === 2단계: Pacemaker 리소스 정의 ===

# DRBD 리소스
$ crm configure primitive p_drbd ocf:linbit:drbd \
    params drbd_resource=lio-data \
    op monitor interval=15s role=Master \
    op monitor interval=30s role=Slave

$ crm configure ms ms_drbd p_drbd \
    meta master-max=1 master-node-max=1 \
    clone-max=2 clone-node-max=1 notify=true

# VIP 리소스
$ crm configure primitive p_vip ocf:heartbeat:IPaddr2 \
    params ip=10.0.100.10 cidr_netmask=24 nic=eth0.100

# LIO 타겟 리소스
$ crm configure primitive p_target ocf:heartbeat:iSCSITarget \
    params iqn="iqn.2024-01.com.example:ha-storage" \
    op start timeout=30 op stop timeout=30

# LUN 리소스
$ crm configure primitive p_lun ocf:heartbeat:iSCSILogicalUnit \
    params target_iqn="iqn.2024-01.com.example:ha-storage" \
    lun=0 path=/dev/drbd0

# 그룹 및 제약 조건
$ crm configure group g_iscsi p_vip p_target p_lun
$ crm configure order o_drbd inf: ms_drbd:promote g_iscsi:start
$ crm configure colocation c_drbd inf: g_iscsi ms_drbd:Master

# === 3단계: Fencing (STONITH) ===
# SCSI PR 기반 fencing (SBD 대안)
$ crm configure primitive p_fence_sbd stonith:external/sbd \
    params pcmk_delay_max=15 \
    op monitor interval=15 timeout=15

# 또는 IPMI 기반 fencing
$ crm configure primitive p_fence_node1 stonith:fence_ipmilan \
    params ipaddr=192.168.0.101 login=admin passwd=secret \
    pcmk_host_list=node1

# === 4단계: 상태 확인 ===
$ crm status
$ drbdadm status
$ targetcli ls /

Fencing 전략

Fencing 방식메커니즘장점단점
SBD(Storage-Based Death)공유 디스크에 poison pill 기록네트워크 독립적공유 디스크 필요
SCSI PR fencingPR PREEMPT AND ABORT로 상대 노드 퇴거SAN 환경에 최적SAN 경로 필요
IPMI/iLOBMC를 통한 물리 전원 차단가장 확실함BMC 네트워크 필요
fence_kdumpkdump 수행 후 리부트크래시 덤프(Dump) 수집 가능시간이 오래 걸림
Split-brain 위험: Fencing 없이 HA 클러스터를 운영하면 split-brain(양쪽 모두 Primary) 상태가 발생하여 데이터가 손상될 수 있습니다. 반드시 STONITH(Shoot The Other Node In The Head)를 구성하고, stonith-enabled=true로 설정하세요. DRBD의 경우 fencing resource-and-stonith를 설정하면 DRBD 레벨에서도 추가 보호가 적용됩니다.

HA 시나리오별 복구 시간 비교

시나리오RTO (목표 복구 시간)RPO (데이터 손실)복잡도비용
DRBD + Pacemaker (동기)10-30초0 (RPO=0)중간낮음 (로컬 디스크)
DRBD + Pacemaker (비동기)10-30초수초 분량중간낮음
공유 SAN + Pacemaker5-15초0낮음높음 (SAN 비용)
Ceph TCMU + ALUA A-A1-5초 (경로 전환)0높음중간 (Ceph 클러스터)
LIO + Corosync (VIP만)5-10초세션 상태낮음최저

참고 링크

LIO 타겟 프레임워크와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.