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) 제거 절차까지 실무 구축과 운영에 필요한 세부 내용을 다룹니다.
핵심 요약
- 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 대비 지연(Latency)가 낮습니다.
- iSCSI 타겟 — TCP 위에서 SCSI 명령을 전송합니다. 별도 HBA 없이 이더넷만으로 SAN 구성이 가능합니다.
단계별 이해
- 타겟 vs 이니시에이터 개념 구분 — 스토리지를 제공하는 쪽이 타겟(Target), 접근하는 쪽이 이니시에이터(Initiator)입니다.
LIO는 타겟 역할을 담당합니다. iSCSI 클라이언트(open-iscsi)가 이니시에이터 역할을 합니다.
- TCM + Fabric 구조 이해 — TCM이 공통 SCSI 로직을, Fabric이 전송 계층을 담당하는 분리 구조입니다.
iSCSI를 쓰든 NVMe-oF를 쓰든 TCM의 SCSI 처리 코드는 동일하게 재사용됩니다.
- configfs로 설정 관리 —
targetcli명령으로 backstore, target, lun, acl을 대화형으로 설정합니다.설정은
/sys/kernel/config/target/에 가상 파일로 반영됩니다. - iSCSI 타겟 설정 실습 — targetcli로 fileio/block backstore를 만들고 IQN을 생성하여 클라이언트가 접속하도록 합니다.
iscsiadm -m discovery로 타겟을 발견하고-m node --login으로 접속합니다. - ALUA로 멀티패스 제어 — 여러 포트 그룹에 Active/Standby를 지정하여 경로 장애 시 자동 페일오버를 구성합니다.
클라이언트에서 multipathd가 ALUA 상태를 읽어 최적 경로를 선택합니다.
- 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 |
| 지원 Fabric | iSCSI, 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 발전 역사
| 커널 버전 | 변경 사항 | 영향 |
|---|---|---|
| 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는 이니시에이터(클라이언트)에서 스토리지 백엔드까지 명확하게 분리된 계층 구조를 가집니다.
핵심 개념
| 개념 | 설명 | 커널 구조체(Struct) |
|---|---|---|
| Target | 스토리지를 제공하는 노드 (서버) | se_device |
| Initiator | 스토리지를 사용하는 노드 (클라이언트) | se_node_acl |
| LUN | Logical Unit Number — 타겟이 노출하는 논리 디바이스 단위 | se_lun |
| Portal | 타겟이 수신 대기하는 네트워크 엔드포인트 (IP:포트) | iscsi_tpg_np |
| TPG | Target Portal Group — 포털들의 그룹 | se_portal_group |
| Session | 이니시에이터-타겟 간 연결 상태 | se_session |
| ACL | Access Control List — 이니시에이터별 접근 권한 | se_node_acl |
| Backstore | 실제 데이터를 저장하는 백엔드 (파일, 블록 디바이스 등) | se_device |
TCM I/O 파이프라인 상세
TCM의 I/O 처리는 여러 단계를 거치며, 각 단계에서 보안 검사, ALUA 상태 확인, PR 검사가 수행됩니다. 다음 다이어그램은 하나의 SCSI 명령이 Fabric에서 백엔드까지 처리되는 전체 흐름을 보여줍니다.
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에 등록됩니다. 용도에 따라 적절한 백엔드를 선택해야 합니다.
| 백엔드 | 소스 파일 | 데이터 소스 | 장점 | 단점 | 사용 사례 |
|---|---|---|---|---|---|
| iblock | target_core_iblock.c | 블록 디바이스 | 최고 성능, 직접 I/O | 블록 디바이스 필요 | 프로덕션 SAN |
| fileio | target_core_file.c | 파일 (VFS 경유) | 희소 파일 지원, 유연함 | VFS 오버헤드(Overhead) | 개발/테스트, 씬 프로비저닝 |
| pscsi | target_core_pscsi.c | SCSI 디바이스 (pass-through) | SCSI 명령 직접 전달 | 제한적 TCM 기능 | 테이프 드라이브, 특수 SCSI 디바이스 |
| ramdisk | target_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 *);
};
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 Target | iscsi_target_mod | TCP (포트 3260) | drivers/target/iscsi/ |
| FC Target | tcm_qla2xxx | Fibre Channel | drivers/scsi/qla2xxx/ |
| SRP Target | tcm_loop | InfiniBand SRP | drivers/infiniband/ulp/srpt/ |
| NVMe-oF TCP | nvmet + nvmet-tcp | TCP (포트 4420) | drivers/nvme/target/ |
| NVMe-oF RDMA | nvmet + nvmet-rdma | InfiniBand/RoCE | drivers/nvme/target/ |
| NVMe-oF FC | nvmet + nvmet-fc | Fibre Channel | drivers/nvme/target/ |
| Loopback | tcm_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;
}
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 가속)를 지원합니다.
| 기능 | 소프트웨어 iSCSI | iSCSI 오프로드 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만 재전송(Retransmission) (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 # 최대 세션 재개 시도
ISCSI_OP_NOOP_OUT / ISCSI_OP_NOOP_IN PDU를 교환하여 연결 상태를 모니터링합니다.
타겟이 noop_out_timeout 내에 응답하지 않으면 이니시에이터는 연결 장애로 판단하고 세션 복구를 시작합니다.
LIO 타겟 측에서는 nopin_timeout과 nopin_response_timeout 파라미터로 이 동작을 제어합니다.
NVMe-oF 타겟
NVMe-oF 타겟은 drivers/nvme/target/에 구현되어 있으며, nvmet.ko가 공통 타겟 코어 역할을 합니다. iSCSI/LIO와는 별도의 스택이지만 유사한 configfs 기반 설정을 사용합니다.
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 |
| 포트 | 3260 | 4420 | 4420 |
| 커널 모듈 | iscsi_target_mod | nvmet-tcp | nvmet-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/
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;
}
multipathd는 REPORT 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 WE | Write Exclusive — 등록자만 쓰기 가능 | 단일 노드 쓰기 보호(Write Protection) |
0x03 EA | Exclusive Access — 등록자만 읽기/쓰기 | 활성-수동 클러스터 |
0x05 WEAR | Write Exclusive, All Registrants — 등록된 모든 이니시에이터 쓰기 | 공유 쓰기 |
0x06 EAAR | Exclusive 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 사용 지양 |
| IPsec | iSCSI 트래픽 암호화(Encryption) | 중간 | 대안: iSER over RDMA |
| 데이터 다이제스트 | HeaderDigest=CRC32C | 낮음 | 데이터 무결성(Integrity) 검증 |
| NVMe-oF TLS | nvmet-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=1로 설정하면 모든 이니시에이터에 대해 자동으로 ACL이 생성되어 인증 없이 접속됩니다.
이 설정은 개발/테스트 환경에서만 사용하고, 프로덕션에서는 반드시 generate_node_acls=0으로 설정하여 명시적으로 허용된 IQN만 접근하도록 하세요.
고가용성 클러스터 구성
LIO 타겟을 고가용성(HA)으로 운영하려면 Pacemaker/Corosync 클러스터와 DRBD 또는 공유 스토리지를 결합합니다. 다음은 Active-Passive iSCSI HA 구성의 핵심 요소입니다.
# 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/TCP | NVMe-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
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로 전환하는 것이 효과적입니다.
백엔드별 성능 최적화
| 백엔드 | 최적화 항목 | 설정 방법 |
|---|---|---|
| iblock | I/O 스케줄러(Scheduler) | echo none > /sys/block/sdX/queue/scheduler (NVMe는 기본 none) |
| iblock | 큐 깊이 | targetcli set attrib queue_depth=256 |
| iblock | Block 사이즈 | targetcli set attrib block_size=4096 (4K 정렬) |
| fileio | write_back | 성능 우선 시 write_back=true, 안전 우선 시 false |
| fileio | emulate_tpu | UNMAP/TRIM 에뮬레이션: set attrib emulate_tpu=1 |
| ramdisk | rd_pages | 할당 페이지(Page) 수로 용량 제어 |
커널 소스 구조
| 경로 | 내용 |
|---|---|
drivers/target/ | LIO Target Core 루트 |
drivers/target/target_core_mod.c | TCM 코어 — 명령 처리, PR, ALUA |
drivers/target/target_core_file.c | fileio 백엔드 |
drivers/target/target_core_iblock.c | block 백엔드 (블록 디바이스) |
drivers/target/target_core_pscsi.c | pscsi 패스스루 백엔드 |
drivers/target/target_core_alua.c | ALUA 구현 |
drivers/target/target_core_pr.c | Persistent Reservations 구현 |
drivers/target/target_core_configfs.c | configfs 인터페이스 |
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
targetcli saveconfig는 /etc/target/saveconfig.json에 설정을 저장합니다.
이 파일은 부팅 시 target.service가 자동으로 복원합니다. 프로덕션 환경에서는 이 파일을 정기적으로 백업하고 버전 관리하세요.
설정 변경 전에 targetcli saveconfig /tmp/backup.json으로 스냅샷을 먼저 저장하는 습관을 들이면 장애 시 빠르게 복구할 수 있습니다.
TCM 백엔드
TCM은 네 가지 커널 내장 백엔드(fileio, iblock, pscsi, ramdisk)와 하나의 사용자 공간 백엔드(TCMU)를 제공합니다. 각 백엔드는 내부 I/O 경로가 크게 다르며, 워크로드 특성에 맞는 선택이 성능과 안정성에 직접적으로 영향을 줍니다.
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);
}
백엔드 선택 가이드
| 기준 | fileio | iblock | pscsi | TCMU |
|---|---|---|---|---|
| I/O 경로 길이 | VFS+FS+Block | Block 직접 | SCSI mid | 커널↔유저 |
| 4K 랜덤 읽기 IOPS | ~70% of iblock | 기준 (100%) | ~80% | ~50-60% |
| 희소 파일 / 씬 프로비저닝 | 지원 | 미지원 | 미지원 | 백엔드 의존 |
| ALUA / PR 지원 | 완전 | 완전 | 제한적 | 완전 |
| WRITE_SAME / UNMAP | 에뮬레이션 | 네이티브 | 디바이스 의존 | 핸들러(Handler) 의존 |
| 캐싱 | 페이지 캐시 | 없음 (O_DIRECT) | 없음 | 핸들러 의존 |
| 권장 사용 | 개발/테스트 | 프로덕션 SAN | 테이프/특수 SCSI | Ceph/분산 스토리지 |
TCMU (TCM in Userspace)
TCMU는 커널 TCM 프레임워크를 사용자 공간 백엔드로 확장하는 브릿지입니다. target_core_user.c에서 커널 측 구현을, tcmu-runner 데몬이 사용자 공간 측 핸들러 디스패치(Dispatch)를 담당합니다. Ceph RBD, GlusterFS, QCOW2 등 커널에 없는 스토리지 백엔드를 LIO 타겟으로 내보낼 수 있습니다.
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
/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, 타겟 자체 판단)으로 발생합니다.
명시적 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로 전환
/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와 달리 서버 재부팅 후에도 예약이 유지됩니다.
PR 타입별 접근 제어 매트릭스
| PR 타입 | 코드 | 보유자 읽기 | 보유자 쓰기 | 비보유자 읽기 | 비보유자 쓰기 | 등록자 쓰기 |
|---|---|---|---|---|---|---|
| Write Exclusive | 0x01 | 허용 | 허용 | 허용 | 거부 | 거부 |
| Exclusive Access | 0x03 | 허용 | 허용 | 거부 | 거부 | 거부 |
| WE, All Registrants | 0x05 | 허용 | 허용 | 허용 | 거부 | 허용 |
| EA, All Registrants | 0x06 | 허용 | 허용 | 거부 | 거부 | 허용 |
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로 객체를 생성/삭제/설정합니다.
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
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 단계에서도 별도의 인증을 설정할 수 있습니다.
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)로 멀티패스를 구현합니다.
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
RTPI와 LUN 매핑
RTPI(Relative Target Port Identifier)는 SPC-4 표준에서 정의된 타겟 포트의 상대적 식별자입니다. 멀티패스 환경에서 이니시에이터가 동일한 LUN에 접근하는 여러 포트를 구별하는 데 사용됩니다. I_T nexus(Initiator-Target nexus)는 특정 이니시에이터와 타겟 포트 간의 논리적 연결을 나타냅니다.
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 (백엔드) | 128 | 128-1024 | 동시 처리 I/O 수, 높을수록 처리량 증가 |
block_size | 512 | 512-4096 | 4K 정렬 시 SSD 성능 최적 |
MaxRecvDataSegmentLength | 8192 | 65536-262144 | iSCSI PDU 크기, 순차 I/O 대역폭 증가 |
MaxBurstLength | 262144 | 262144-16776192 | R2T 없는 최대 전송량 |
ImmediateData | Yes | Yes | 소규모 쓰기 지연 감소 |
InitialR2T | Yes | No | 초기 데이터 즉시 전송, 지연 감소 |
| MTU | 1500 | 9000 (Jumbo) | 대역폭 오버헤드 감소 |
| TCP 버퍼(Buffer) | 자동 | rmem/wmem 16MB | 고대역폭 환경 처리량 증가 |
HA Failover 시나리오
LIO 타겟의 고가용성(HA) 구성은 페일오버 방식에 따라 Active-Passive, Active-Active로 나뉩니다. 공유 스토리지, DRBD 복제, 또는 분산 스토리지(Ceph) 기반으로 구현할 수 있습니다.
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 fencing | PR PREEMPT AND ABORT로 상대 노드 퇴거 | SAN 환경에 최적 | SAN 경로 필요 |
| IPMI/iLO | BMC를 통한 물리 전원 차단 | 가장 확실함 | BMC 네트워크 필요 |
| fence_kdump | kdump 수행 후 리부트 | 크래시 덤프(Dump) 수집 가능 | 시간이 오래 걸림 |
stonith-enabled=true로 설정하세요.
DRBD의 경우 fencing resource-and-stonith를 설정하면 DRBD 레벨에서도 추가 보호가 적용됩니다.
HA 시나리오별 복구 시간 비교
| 시나리오 | RTO (목표 복구 시간) | RPO (데이터 손실) | 복잡도 | 비용 |
|---|---|---|---|---|
| DRBD + Pacemaker (동기) | 10-30초 | 0 (RPO=0) | 중간 | 낮음 (로컬 디스크) |
| DRBD + Pacemaker (비동기) | 10-30초 | 수초 분량 | 중간 | 낮음 |
| 공유 SAN + Pacemaker | 5-15초 | 0 | 낮음 | 높음 (SAN 비용) |
| Ceph TCMU + ALUA A-A | 1-5초 (경로 전환) | 0 | 높음 | 중간 (Ceph 클러스터) |
| LIO + Corosync (VIP만) | 5-10초 | 세션 상태 | 낮음 | 최저 |
최신 LIO/nvmet 동향 (커널 6.10~6.14)
LIO iSCSI/FC/SRP 프런트엔드 자체는 2024~2025년 동안 대규모 신규 기능이 추가되기보다, 안정성 수정과 nvmet(NVMe-oF 타겟) 쪽 확장이 주를 이뤘습니다. 운영 관점에서 반드시 알아야 하는 변경을 정리합니다.
nvmet debugfs (6.11)
6.11에서 nvmet에 debugfs 인터페이스가 추가되어, 각 subsystem·controller·port의 상태와 내부 큐 깊이, 연결된 호스트 세션 정보를 실시간(Real-time)으로 관찰할 수 있게 되었습니다. 기존에는 dmesg와 configfs 속성만으로 파편화된 정보를 조합해야 했지만, 이제 하나의 트리에서 타겟 측 동작을 추적할 수 있습니다.
$ mount -t debugfs none /sys/kernel/debug
$ tree /sys/kernel/debug/nvmet/
/sys/kernel/debug/nvmet/
├── subsystems/
│ └── nqn.2024-01.example:ssd/
│ ├── ctrl1/
│ │ ├── state # live / error / reset
│ │ ├── host_traddr # 연결된 호스트 주소
│ │ ├── cmd_stats # 커맨드 종류별 카운터
│ │ └── log_pages
│ └── namespaces/
└── ports/
└── 1/
├── addr_traddr
└── sessions
NVMe/TCP TLS 타겟 성숙 (6.10~6.13)
커널 6.10~6.13 구간에서 nvmet-tcp의 TLS 1.3 지원이 안정화되었고, tlshd 데몬과의 핸드셰이크 업콜 경로가 정식 ABI로 굳혀졌습니다. 6.12에서는 호스트 측과 마찬가지로 DH-HMAC-CHAP 결과를 TLS PSK로 연결하는 Concatenation이 타겟에서도 지원되어, 클라이언트가 nvme connect --tls --concat으로 접속할 때 컨트롤 플레인 직후 전 구간 암호화가 적용됩니다.
# configfs에서 nvmet-tcp 포트에 TLS 활성화
$ mkdir /sys/kernel/config/nvmet/ports/1
$ echo tcp > /sys/kernel/config/nvmet/ports/1/addr_trtype
$ echo 10.0.0.10 > /sys/kernel/config/nvmet/ports/1/addr_traddr
$ echo 4420 > /sys/kernel/config/nvmet/ports/1/addr_trsvcid
$ echo ipv4 > /sys/kernel/config/nvmet/ports/1/addr_adrfam
# TLS 필수(only) / 허용(allowed) / 비활성(none)
$ echo required > /sys/kernel/config/nvmet/ports/1/tls
# tlshd가 업콜을 처리하므로 systemd 유닛 활성
$ systemctl enable --now tlshd
nvmet-pci-epf 타겟 (6.14)
NVMe 페이지에서도 다룬 것과 같이, 6.14의 PCI Endpoint Function 타겟은 LIO와는 별개의 nvmet 기반 경로입니다. 호스트 Linux 자체가 Endpoint 역할로 다른 루트 컴플렉스에 NVMe 디스크를 제공하는 구성이어서, SmartNIC·브리지(Bridge) 제품에 업스트림 스택만으로 접근할 수 있게 되었습니다. configfs는 기존 NVMe-oF 설정과 일관되며, pci-epf 트랜스포트만 새로 지정합니다.
LIO 프런트엔드 안정성 수정
- iscsi_target_mod: 6.12 stable에서 세션 재인증(reauth) 중 커맨드 레퍼런스 카운트 race 수정.
- qla2xxx(FC 타겟): 6.11 이후 ABTS 처리 경로의 잠금 순서 정리로 라이브 마이그레이션 중 발생하던 hung task 제거.
- tcm_loop: 테스트 용도의 루프백 백엔드에
REPORT SUPPORTED OPERATION CODES구현 보완. - tcmu: 6.10에서 UIO 미러링 버그 수정으로 대용량 백엔드 데몬(스토리지 게이트웨이류) 안정성 향상.
운영 지침: LIO iSCSI 타겟은 여전히 targetcli-fb가 사실상의 관리 도구이며 configfs 계층은 안정적입니다. 2026년 현재 신규 NVMe-oF 배포는 nvmet + TLS + DH-HMAC-CHAP 스택이 권장되며, 고가용성은 멀티-컨트롤러(ANA) 모델이 LIO의 ALUA보다 구현 난이도가 낮습니다. LIO는 레거시 iSCSI 대응용, nvmet은 신규 설계용이라는 2-트랙 전략이 현실적입니다.
nvmet 타겟: Persistent Reservation 지원 (6.13)
커널 6.13에서 nvmet 서브시스템이 Persistent Reservation(PR)을 지원하기 시작했습니다. PR은 NVMe 사양 섹션 8.19에 정의된 예약 메커니즘으로, 여러 호스트가 하나의 공유 네임스페이스에 접근할 때 배타적 쓰기 소유권을 등록·예약·해제하는 데 사용됩니다. VMware vSphere, Windows Server Failover Cluster(WSFC) 등 클러스터 소프트웨어가 공유 스토리지를 펜싱(fencing)하는 데 PR을 의존하며, 이제 nvmet 기반 NVMe-oF 타겟이 이를 투명하게 처리할 수 있습니다. LIO의 기존 SCSI PR(target_core_pr.c) 구현과 별개로 nvmet 코드 경로(drivers/nvme/target/pr.c)에 새로 추가된 것입니다.
# nvmet PR tracing 이벤트 활성화 (ftrace)
$ echo 1 > /sys/kernel/debug/tracing/events/nvmet/nvmet_pr_register/enable
$ echo 1 > /sys/kernel/debug/tracing/events/nvmet/nvmet_pr_reserve/enable
$ echo 1 > /sys/kernel/debug/tracing/events/nvmet/nvmet_pr_preempt/enable
$ cat /sys/kernel/debug/tracing/trace_pipe
커널 6.13부터: nvmet 타겟이 NVMe Persistent Reservation 명령(REGISTER, RESERVE, RELEASE, PREEMPT, CLEAR)을 처리합니다. 클러스터 스토리지 연동 검증 시 nvmet_pr_* ftrace 이벤트를 활성화하면 PR 상태 전이를 실시간으로 추적할 수 있습니다.
참고자료
- Kernel.org — Target (LIO) Documentation — 리눅스 커널 공식 타겟 서브시스템 문서입니다
- Bootlin Elixir — drivers/target/ — LIO 타겟 코어 및 백엔드 드라이버의 커널 소스 코드를 탐색할 수 있습니다
- Bootlin Elixir — target_core_configfs.c — configfs 기반 타겟 설정 인터페이스의 구현 코드입니다
- Bootlin Elixir — drivers/target/iscsi/ — iSCSI 타겟 프런트엔드(iscsi_target_mod)의 소스 코드입니다
- LWN: LIO — the Linux SCSI target — LIO 타겟 프레임워크의 아키텍처와 커널 통합 과정을 설명하는 기사입니다
- LWN: The TCM/LIO SCSI target — TCM(Target Core Module)의 설계 원리와 백엔드 구조를 분석합니다
- targetcli-fb (GitHub) — LIO 설정 관리 도구
targetcli의 공식 저장소입니다 - rtslib-fb (GitHub) — targetcli의 기반이 되는 Python 라이브러리로, LIO configfs 인터페이스를 추상화합니다
- Bootlin Elixir — target_core_transport.c — SCSI 명령 처리 경로와 태스크(Task) 관리의 핵심 코드입니다
- Bootlin Elixir — target_core_user.c — TCMU(Target Core Module Userspace) 백엔드의 구현 코드입니다
관련 문서
LIO 타겟 프레임워크와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.