iSCSI로 구성하는 IP 기반 블록 스토리지
iSCSI의 Initiator·Target 구조, IQN과 EUI 식별, 보안과 성능 설정, Linux·Windows 구현 방식을 정리한다.
2026-08-14 · 최초 발행 2026-01-06
IP 네트워크 위에서 SCSI 블록 장치를 연결하는 방식
iSCSI는 SCSI 명령어를 TCP/IP로 전달하는 블록 수준 스토리지 프로토콜이다. Initiator가 네트워크를 통해 Target에 접속하고, Target이 내놓은 LUN을 로컬 블록 장치처럼 사용한다.
전용 인프라가 필요한 Fibre Channel SAN과 달리 기존 이더넷 네트워크를 활용할 수 있다는 점이 출발점이다. IETF RFC 3720 표준은 2004년에 제정되었다. 파일 수준으로 접근하는 NFS·SMB와 달리 파일시스템에 독립적인 블록 장치를 제공하며, LAN과 WAN에서 모두 사용할 수 있다.
소프트웨어로 구현할 수 있고 하드웨어 가속도 지원한다. 가상화 환경에서도 사용할 수 있으며, 거리 제약이 거의 없는 네트워크 구성과 기존 장비 활용이 가능한 점이 특징이다.
Initiator와 Target이 만드는 접속 구조
Initiator는 호스트 서버에서 실행되는 iSCSI 클라이언트로, SCSI 명령어를 발생시킨다. Target은 스토리지를 노출하는 iSCSI 서버이며 LUN(Logical Unit Number)을 제공한다. Portal은 Target의 네트워크 주소를 뜻하며 IP:Port 조합으로 표현한다. 다중 경로 구성을 위해 여러 Portal을 둘 수 있다.
SCSI 명령은 iSCSI PDU에 담긴 뒤 TCP 세그먼트, IP 패킷, Ethernet 프레임 순으로 전달된다.
+------------------------+
| SCSI Application |
+------------------------+
| iSCSI Protocol |
+------------------------+
| TCP |
+------------------------+
| IP |
+------------------------+
| Ethernet |
+------------------------+
전용 네트워크에서는 스토리지 트래픽을 별도 경로에 둔다.
[Initiator] ---+
|
[Initiator] ---+--- [Switch] --- [Target]
|
[Initiator] ---+
공유 네트워크를 쓸 때는 VLAN으로 iSCSI 트래픽을 분리할 수 있다.
[Initiator] ---+
|
[General PC] --+--- [Switch] --- [Target]
| (VLAN 10: iSCSI)
[Web Server] --+
PDU, 로그인, 명령 처리
iSCSI PDU의 Basic Header Segment(BHS)는 명령 유형, 데이터 길이, LUN, 태그, 명령 시퀀스 번호를 담는다.
struct iscsi_bhs {
uint8_t opcode; // 명령어 타입
uint8_t flags;
uint8_t reserved[2];
uint8_t total_ahs_length; // Additional Header Segments
uint8_t data_segment_length[3];
uint64_t lun; // Logical Unit Number
uint32_t initiator_task_tag;
uint32_t target_transfer_tag;
uint32_t cmdsn; // Command Sequence Number
uint32_t expcmdsn; // Expected Command SN
uint32_t maxcmdsn; // Maximum Command SN
uint8_t header_digest[4]; // CRC (선택적)
} __attribute__((packed));
PDU에는 SCSI Command(0x01), SCSI Response(0x21), SCSI Data-Out(0x05), SCSI Data-In(0x25), Text Request/Response(0x04/0x24), Login Request/Response(0x03/0x23)가 있다.
로그인은 Security Negotiation, Login Operational Negotiation, Full Feature Phase 순으로 진행된다. 앞선 단계에서는 인증 방식과 파라미터를 협상하고, 마지막 단계에서 정상 I/O를 수행한다.
struct iscsi_login_req {
struct iscsi_bhs bhs;
uint8_t version_max;
uint8_t version_min;
uint8_t flags;
uint8_t isid[6]; // Initiator Session ID
uint16_t tsih; // Target Session Identifying Handle
uint32_t cid; // Connection ID
// Key=Value pairs in data segment
};
세션은 논리적 연결이고, 그 안의 연결은 TCP 연결이다.
// 세션: 논리적 연결
struct iscsi_session {
char target_name[256];
uint64_t isid;
uint16_t tsih;
struct list_head connections;
};
// 연결: TCP 연결
struct iscsi_connection {
int sockfd;
uint32_t cid;
struct sockaddr_storage portal;
uint32_t cmdsn;
uint32_t expcmdsn;
};
READ(10) 같은 SCSI 명령은 iSCSI 명령 PDU로 만들어 전송한다.
// READ(10) 명령어 전송
void iscsi_send_read10(struct iscsi_connection *conn,
uint64_t lun, uint32_t lba, uint16_t length) {
struct iscsi_scsi_cmd pdu;
memset(&pdu, 0, sizeof(pdu));
pdu.bhs.opcode = ISCSI_OP_SCSI_CMD;
pdu.bhs.flags = ISCSI_FLAG_CMD_FINAL | ISCSI_FLAG_CMD_READ;
pdu.bhs.lun = htobe64(lun);
pdu.bhs.initiator_task_tag = next_task_tag++;
pdu.bhs.cmdsn = htobe32(conn->cmdsn++);
pdu.bhs.expcmdsn = htobe32(conn->expcmdsn);
// SCSI CDB
uint8_t *cdb = pdu.cdb;
cdb[0] = 0x28; // READ(10)
cdb[2] = (lba >> 24) & 0xFF;
cdb[3] = (lba >> 16) & 0xFF;
cdb[4] = (lba >> 8) & 0xFF;
cdb[5] = lba & 0xFF;
cdb[7] = (length >> 8) & 0xFF;
cdb[8] = length & 0xFF;
send(conn->sockfd, &pdu, sizeof(pdu), 0);
}
응답 PDU는 상태를 확인하고 성공 또는 오류 처리로 이어진다.
void iscsi_receive_response(struct iscsi_connection *conn) {
struct iscsi_scsi_rsp pdu;
recv(conn->sockfd, &pdu, sizeof(pdu), 0);
if (pdu.bhs.opcode == ISCSI_OP_SCSI_RSP) {
uint8_t status = pdu.status;
if (status == SCSI_STATUS_GOOD) {
// 성공
} else {
// 에러 처리
}
}
}
IQN, EUI, Portal로 대상을 찾는 방법
IQN(iSCSI Qualified Name)은 iSCSI 장치를 식별하는 이름이다.
iqn.YYYY-MM.reversed.domain.name:unique_string
예시:
iqn.2024-01.com.example:storage.disk1
iqn.2024-01.com.example.server1:initiator
iqn 접두사 뒤에 도메인 등록 년월, 역순 도메인, 고유 식별자를 붙인다.
// IQN 파싱
struct iqn {
char prefix[4]; // "iqn"
char date[8]; // "YYYY-MM"
char domain[256];
char unique[256];
};
bool parse_iqn(const char *iqn_str, struct iqn *iqn) {
return sscanf(iqn_str, "%3s.%7[^.].%[^:]:%s",
iqn->prefix, iqn->date, iqn->domain, iqn->unique) == 4;
}
EUI(Extended Unique Identifier)는 IEEE EUI-64 기반의 식별자이며 OUI(Organizationally Unique Identifier)를 포함한다.
eui.NNNNNNNNNNNNNNNN (16자리 16진수)
예시:
eui.02004567A425678D
Target 탐색에는 SendTargets Discovery를 사용할 수 있다.
// Discovery 요청
struct iscsi_text_req {
struct iscsi_bhs bhs;
// Data segment: "SendTargets=All"
};
// Discovery 응답
struct iscsi_text_rsp {
struct iscsi_bhs bhs;
// Data segment:
// TargetName=iqn.2024-01.com.example:disk1
// TargetAddress=192.168.1.100:3260,1
};
iSNS(Internet Storage Name Service)는 중앙 집중식 디스커버리를 제공하며 DNS와 유사한 방식으로 대규모 환경에 적합하다.
인증, 암호화, 접근 제어의 경계
CHAP(Challenge-Handshake Authentication Protocol)은 Challenge, 사용자 이름, 응답을 이용해 인증한다. Mutual CHAP은 Initiator와 Target이 모두 인증하는 양방향 방식이다.
// CHAP 인증 과정
struct chap_auth {
uint8_t algorithm; // CHAP Algorithm
uint8_t identifier;
uint8_t challenge[16]; // 랜덤 값
char username[256];
uint8_t response[16]; // MD5(id|secret|challenge)
};
// CHAP 응답 생성
void chap_compute_response(const char *secret,
uint8_t identifier,
const uint8_t *challenge,
uint8_t *response) {
MD5_CTX ctx;
MD5_Init(&ctx);
MD5_Update(&ctx, &identifier, 1);
MD5_Update(&ctx, secret, strlen(secret));
MD5_Update(&ctx, challenge, 16);
MD5_Final(response, &ctx);
}
IPsec은 IP 계층에서 암호화를 수행하며 ESP(Encapsulating Security Payload)를 이용할 수 있다.
# IPsec 설정 (Linux)
# ESP 터널 모드
setkey -c << EOF
spdadd 192.168.1.10 192.168.1.100 any -P out ipsec
esp/tunnel/192.168.1.10-192.168.1.100/require;
spdadd 192.168.1.100 192.168.1.10 any -P in ipsec
esp/tunnel/192.168.1.100-192.168.1.10/require;
EOF
TLS는 애플리케이션 계층 암호화와 인증서 기반 구성을 제공하며, iSCSI Extension for RDMA(iSER)와 결합할 수 있는 미래 표준이다.
Target ACL은 Initiator IQN별 LUN 접근 권한과 읽기 전용 여부를 관리한다.
// Target 설정
struct iscsi_target_acl {
char initiator_iqn[256];
uint64_t permitted_luns; // Bitmask
bool read_only;
};
// 접근 확인
bool check_access(const char *initiator_iqn, uint64_t lun) {
struct iscsi_target_acl *acl = find_acl(initiator_iqn);
if (!acl) {
return false;
}
return (acl->permitted_luns & (1ULL << lun)) != 0;
}
네트워크 ACL은 IP 주소와 네트워크 마스크를 기준으로 접속을 제한한다.
// IP 주소 기반 필터링
struct network_acl {
struct in_addr network;
struct in_addr netmask;
};
bool is_allowed_ip(struct sockaddr_in *addr,
struct network_acl *acls, size_t count) {
for (size_t i = 0; i < count; i++) {
if ((addr->sin_addr.s_addr & acls[i].netmask.s_addr) ==
acls[i].network.s_addr) {
return true;
}
}
return false;
}
네트워크와 경로에서 조정하는 성능
Jumbo Frame은 MTU를 1500에서 9000 바이트로 늘려 세그멘테이션과 CPU 오버헤드를 줄이는 방식이다. 처리량은 10~30% 향상될 수 있고, CPU 사용률과 레이턴시 감소를 기대할 수 있다.
# 이더넷 MTU 변경
ip link set eth0 mtu 9000
# iSCSI 파라미터
MaxRecvDataSegmentLength=262144
FirstBurstLength=262144
MaxBurstLength=1048576
TOE(TCP Offload Engine)는 TCP/IP 처리를 NIC로 오프로드해 CPU 부하를 낮추는 하드웨어 가속 방식이다.
// TOE NIC 사용
int enable_toe(int sockfd) {
int on = 1;
// TCP Segmentation Offload
setsockopt(sockfd, IPPROTO_TCP, TCP_CORK, &on, sizeof(on));
// Checksum Offload
// (NIC 드라이버 자동 처리)
return 0;
}
MPIO(Multipath I/O)는 여러 네트워크 경로를 이용해 부하를 분산하고 장애에 대응한다.
// 다중 경로 설정
struct iscsi_multipath {
struct iscsi_session *session;
struct iscsi_connection *paths[4];
int active_paths;
int current_path;
};
// 라운드 로빈 경로 선택
struct iscsi_connection *select_path(struct iscsi_multipath *mp) {
mp->current_path = (mp->current_path + 1) % mp->active_paths;
return mp->paths[mp->current_path];
}
Fibre Channel, 파일 공유, SAS와의 차이
| 항목 | iSCSI | Fibre Channel |
|---|---|---|
| 매체 | 이더넷 | 광섬유/구리 |
| 비용 | 낮음 | 높음 |
| 최대 속도 | 100GbE | 32GFC |
| 거리 | LAN/WAN | 10km (확장 시 더 멀리) |
| 구성 | 간단 | 복잡 |
| 용도 | SMB, 중소 기업 | 엔터프라이즈 |
| 항목 | iSCSI | NFS/SMB |
|---|---|---|
| 접근 단위 | 블록 | 파일 |
| 파일시스템 | 클라이언트 | 서버 |
| 성능 | 높음 | 중간 |
| 유연성 | 낮음 | 높음 |
| 용도 | 데이터베이스, VM | 파일 공유 |
| 항목 | iSCSI | SAS |
|---|---|---|
| 연결 | 네트워크 | 직접 연결 |
| 거리 | 무제한 (LAN/WAN) | 10m |
| 속도 | 네트워크 속도 | 12Gb/s (SAS-3) |
| 비용 | 낮음 | 중간 |
| 용도 | 원격 스토리지 | 로컬 스토리지 |
Linux와 Windows에서 연결하기
Linux Initiator에서는 Open-iSCSI로 Target을 탐색하고 로그인한 뒤 블록 장치를 사용할 수 있다.
# 설치
apt-get install open-iscsi
# Discovery
iscsiadm -m discovery -t st -p 192.168.1.100
# 로그인
iscsiadm -m node -T iqn.2024-01.com.example:disk1 -p 192.168.1.100 -l
# 확인
lsblk
# sdb 8:16 0 100G 0 disk
# 마운트
mkfs.ext4 /dev/sdb
mount /dev/sdb /mnt/iscsi
CHAP과 연결 유지 관련 설정은 다음과 같다.
# /etc/iscsi/iscsid.conf
node.session.auth.authmethod = CHAP
node.session.auth.username = initiator_user
node.session.auth.password = initiator_pass
node.conn[0].timeo.noop_out_interval = 5
node.conn[0].timeo.noop_out_timeout = 10
Linux Target은 LIO(Linux-IO Target)로 구성할 수 있다.
# 설치
apt-get install targetcli-fb
# 설정
targetcli
/backstores/block create disk1 /dev/sda1
/iscsi create iqn.2024-01.com.example:disk1
/iscsi/iqn.2024-01.com.example:disk1/tpg1/luns create /backstores/block/disk1
/iscsi/iqn.2024-01.com.example:disk1/tpg1/acls create iqn.2024-01.com.client:initiator1
/iscsi/iqn.2024-01.com.example:disk1/tpg1/portals create 192.168.1.100
saveconfig
exit
Windows에서는 Microsoft iSCSI Initiator의 PowerShell 명령으로 Portal을 등록하고 Target에 연결한다.
# Discovery
New-IscsiTargetPortal -TargetPortalAddress 192.168.1.100
# 연결
Get-IscsiTarget | Connect-IscsiTarget
# 확인
Get-Disk | Where-Object BusType -eq iSCSI
CHAP 인증을 사용한 지속 연결은 다음과 같이 설정한다.
# CHAP 인증 설정
Set-IscsiChapSecret -ChapSecret "initiator_pass"
# Target 연결
Connect-IscsiTarget -NodeAddress "iqn.2024-01.com.example:disk1" `
-IsPersistent $true `
-AuthenticationType ONEWAYCHAP `
-ChapUsername "initiator_user"
가상화, 백업, 데이터베이스에서의 사용
가상화 환경에서는 VMware vSphere의 Datastore, vMotion, HA/DRS 클러스터에 사용할 수 있다. Hyper-V에서는 CSV(Cluster Shared Volume), Live Migration, SMB over iSCSI 구성이 대상이 된다.
백업 시스템에서는 VTL(Virtual Tape Library), Deduplication, 원격 복제에 쓰이며, 재해 복구에서는 원격 사이트 복제, 스냅샷, 클론 구성을 지원한다.
데이터베이스 환경에서는 Oracle ASM(Automatic Storage Management), RAC(Real Application Clusters), Data Guard와 함께 사용할 수 있다. SQL Server에서는 FCI(Failover Cluster Instance), AlwaysOn, 공유 디스크 구성이 해당된다.