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 대비 레이턴시가 낮습니다.
- 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만 재전송 (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초 | 세션 상태 | 낮음 | 최저 |
참고 링크
- 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 명령 처리 경로와 태스크 관리의 핵심 코드입니다
- Bootlin Elixir — target_core_user.c — TCMU(Target Core Module Userspace) 백엔드의 구현 코드입니다
관련 문서
LIO 타겟 프레임워크와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.