Linux 트래픽 제어(tc): 실무 가이드

12분 소요 - 2026년 6월 5일

hero section cover
목차
  • Linux 트래픽 제어(tc): 실용 가이드
  • tc의 작동 방식
  • 리눅스 서버에서 tc 설정하기
  • HTB를 이용한 아웃바운드 트래픽 셰이핑
  • IFB를 이용한 인바운드 트래픽 셰이핑
  • DSCP를 이용한 트래픽 우선순위 지정
  • 모니터링 및 문제 해결
공유

Tc로 Linux에서 대역폭을 제어하고, 트래픽 우선 순위를 지정하고, 수신 및 발신 형식을 지정하세요. 실제 서버에 대한 HTB, IFB, DSCP 및 fq_codel 구성 작업.

Linux 트래픽 제어(tc): 실용 가이드

Linux의 tc 명령을 사용하면 서버가 네트워크 트래픽을 처리하는 방식을 직접 제어할 수 있습니다. 서비스별 대역폭을 제한하고, 대량 전송이 급증할 때 SSH와 같은 대화형 세션의 응답성을 유지하며, 단일 도구로 발신 및 수신 트래픽을 모두 조절할 수 있습니다. 이 가이드에서는 핵심 개념, 작동하는 HTB 설정, IFB를 사용한 인그레스 셰이핑, DSCP 기반 우선순위 지정, 그리고 문제가 발생했을 때 디버깅하는 방법을 다룹니다.


 

tc의 작동 방식

모든 tc 설정은 네 가지 구성 요소로 이루어집니다:

  • qdisc(큐잉 디스플라이너). 네트워크 인터페이스에 연결된 스케줄러입니다. 패킷이 큐에 추가되고 제거되는 방식을 결정합니다.
  • 클래스(Class). 클래스 기반 qdisc 내의 하위 구분입니다. 자체 속도 제한이 있는 차선이라고 생각하면 됩니다.
  • 필터(Filter). 패킷 헤더(IP, 포트, 마크)를 검사하여 각 패킷을 특정 클래스에 할당합니다.
  • 액션(Action). 패킷이 조건에 일치하면 수행되는 작업: 전달(forward), 폐기(drop), 재전송(redirect).

이들은 트리 구조를 이룹니다. 패킷은 루트 qdisc에서 진입하여 필터를 통과하고, 핸들에 의해 클래스로 분류된 후 major:minor 전송을 위해 리프 qdisc에 큐에 들어갑니다.

포트 기반 일치보다 더 복잡한 처리가 필요한 경우, iptables나 nftables의 mangle 테이블에서 패킷에 마크를 부여한 다음 fw 필터를 사용하여 tc 를 사용하여 마크별로 분류합니다. 이는 모든 트래픽 유형에 대해 원시 u32 규칙을 연결하는 것보다 확장성이 훨씬 뛰어납니다.

출구 대 입구

방향은 중요합니다. 커널은 아웃바운드 패킷을 버퍼링하고 지연시킬 수 있으며, 이것이 진정한 셰이핑을 가능하게 합니다. 인바운드 패킷은 사용자가 확인하는 시점에는 이미 네트워크를 통과한 상태이므로, 먼저 IFB 장치로 리디렉션하지 않는 한 패킷을 제어(임계값 초과 시 드롭)할 수만 있습니다.

기능출구인그레스
방향발신인바운드
셰이핑네이티브IFB 필요
폴리싱지원됨지원됨
일반적인 용도QoS, 대역폭 공유, 페이싱속도 제한, 기본 DDoS 완화

실제로 사용할 qdisc

  • HTB (계층형 토큰 버킷). 클래스 기반. 서비스별로 최소 대역폭을 보장하면서 다른 클래스의 미사용 용량을 차용할 수 있는 기능이 필요할 때 사용합니다.
  • TBF (Token Bucket Filter). 클래스리스 방식입니다. 전체 인터페이스의 전송 속도를 단일 속도로 제한해야 할 때 사용합니다.
  • fq_codel (Fair Queuing Controlled Delay). 플로우별 공정성과 능동적 큐 관리를 결합하여 버퍼블로트를 방지합니다. systemd 217 이후 대부분의 리눅스 배포판에서 기본 qdisc로 사용되어 왔으며, RHEL 9에서는 기본으로 제공됩니다. 항상 HTB 클래스 아래의 리프 qdisc로 연결해야 합니다. 그렇지 않으면 탐욕적인 단일 플로우가 전체 클래스를 독점할 수 있습니다.

리눅스 서버에서 tc 설정하기

tciproute2 패키지와 함께 제공됩니다. Debian 및 Ubuntu에서는 다음 명령어로 설치하십시오 apt-get install iproute2. RHEL 및 파생 배포판에서는 yum install iproute를 사용하여 설치하십시오. root 권한이나 sudo가 필요합니다.

먼저 올바른 인터페이스 이름을 확인하십시오. 인터페이스 이름을 잘못 지정하는 것은 구성이 아무런 반응 없이 작동하지 않는 가장 흔한 원인입니다:

ip link show

실시간 카운터를 포함하여 인터페이스에 이미 무엇이 설정되어 있는지 확인하십시오:

tc -s qdisc show dev eth0

새로운 구성을 적용하기 전에 기존 root qdisc를 모두 지워 RTNETLINK answers: File exists 오류를 방지하십시오:

tc qdisc del dev eth0 root 2>/dev/null || true

처음부터 새로 시작하는 것이 아니라 기존 규칙을 업데이트하는 경우, replace 대신 add 를 사용하여 원자적 교체(atomic swap)를 수행하십시오.

TSO 및 GSO와 같은 하드웨어 오프로딩 기능은 셰이핑을 방해하는 방식으로 패킷을 묶습니다. 셰이핑이 적용된 인터페이스에서 해당 기능을 비활성화하십시오:

sudo ethtool -K eth0 tso off gso off

새 인터페이스에 대한 시스템 전체 기본 qdisc로 fq_codel 를 새 인터페이스의 시스템 전체 기본 qdisc로 설정하십시오:

sysctl -w net.core.default_qdisc=fq_codel

부하가 많은 서버의 경우, BBR 혼잡 제어 알고리즘(커널 4.9 이상)과 함께 사용하십시오. BBR은 큐를 늘리지 않으면서도 높은 처리량을 유지합니다:

sysctl -w net.ipv4.tcp_congestion_control=bbr

SSH를 통해 원격 시스템을 구성할 때의 안전 수칙: 두 번째 세션을 열고 tc qdisc del dev eth0 root 붙여넣을 수 있도록 준비해 두십시오. 잘못된 필터 규칙은 즉시 접속을 차단할 수 있습니다.

HTB를 이용한 아웃바운드 트래픽 셰이핑

HTB를 사용하면 각 서비스에 보장 최소값(rate) 및 상한선(ceil)을 할당할 수 있습니다. 사용되지 않은 대역폭은 우선순위 순서대로 필요한 사용자에게 배분됩니다. 다음은 1Gbps 업링크를 위한 작동하는 3단계 설정 예시입니다.

루트 HTB qdisc를 생성합니다. default 30 는 분류되지 않은 패킷을 class 1:30 로 전송하며, 규칙을 우회하지 못하게 합니다:

tc qdisc add dev eth0 root handle 1: htb default 30

총 처리량을 900 Mbps로 제한합니다. 항상 실제 링크 용량보다 약간 낮은 수준으로 쉐이핑해야 합니다. 그렇지 않으면 제어할 수 없는 상류 라우터나 모뎀에 큐가 형성됩니다:

tc class add dev eth0 parent 1: classid 1:1 htb rate 900mbit ceil 900mbit

서비스 계층을 정의합니다. 낮은 prio 값을 할당받은 쪽이 미사용 대역폭을 먼저 확보합니다:

# High priority: web and API traffic
tc class add dev eth0 parent 1:1 classid 1:10 htb rate 500mbit ceil 900mbit prio 1
 
# Medium priority: database replication
tc class add dev eth0 parent 1:1 classid 1:20 htb rate 300mbit ceil 900mbit prio 2
 
# Low priority: bulk and backup traffic
tc class add dev eth0 parent 1:1 classid 1:30 htb rate 100mbit ceil 900mbit prio 3

각 클래스의 리프 qdisc로 fq_codel 각 클래스에 리프 qdisc로 연결하여 단일 플로우가 해당 티어를 독점하지 못하게 하십시오:

tc qdisc add dev eth0 parent 1:10 handle 10: fq_codel
tc qdisc add dev eth0 parent 1:20 handle 20: fq_codel
tc qdisc add dev eth0 parent 1:30 handle 30: fq_codel

이제 트래픽을 분류하십시오. 간단한 포트 매칭의 경우, u32 이 방법이 가장 빠릅니다:

tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
  match ip dport 443 0xffff flowid 1:10

상태 기반 처리가 필요한 경우, iptables에서 마크를 지정하고 해당 마크와 fw:

iptables -t mangle -A OUTPUT -p tcp --dport 5432 -j MARK --set-mark 2
tc filter add dev eth0 protocol ip parent 1:0 prio 2 handle 2 fw flowid 1:20

IFB를 이용한 인바운드 트래픽 셰이핑

패킷이 도착할 때쯤이면 이미 대역폭을 사용한 상태이므로, 기본적으로 인그레스 트래픽을 셰이핑할 수 없습니다. 이를 해결하는 방법은 인그레스 트래픽을 IFB(Intermediate Functional Block) 가상 인터페이스로 리디렉션하는 것입니다. 커널은 이를 이그레스로 처리하므로 클래스 기반 큐디스크(classful qdiscs)를 적용할 수 있습니다.

모듈을 로드하고 인터페이스를 활성화합니다:

modprobe ifb numifbs=1
ip link set dev ifb0 up

물리 인터페이스에 인그레스 qdisc를 추가하고 모든 트래픽을 ifb0:

tc qdisc add dev eth0 ingress handle ffff:
tc filter add dev eth0 parent ffff: protocol all u32 \
  match u32 0 0 action mirred egress redirect dev ifb0

이 시점부터, ifb0 다른 인터페이스와 동일하게 동작합니다. 출력 트래픽에서와 똑같이 HTB 트리를 적용하십시오:

tc qdisc add dev ifb0 root handle 1: htb default 30
tc class add dev ifb0 parent 1: classid 1:1 htb rate 900mbit ceil 900mbit
tc class add dev ifb0 parent 1:1 classid 1:10 htb rate 500mbit ceil 900mbit prio 1

DSCP를 이용한 트래픽 우선순위 지정

DSCP(Differentiated Services Code Point)는 TOS 바이트에 6비트 값을 태그로 지정하므로, tc 필터는 규칙 집합 전체에서 포트를 일일이 확인하는 대신 태그를 기준으로 분류할 수 있습니다. DSCP를 매칭할 때 tc값을 왼쪽으로 2비트 이동시킵니다. DSCP EF(46)는 0xb8가 됩니다. 이 마스크는 0xfc 는 2비트의 ECN 비트에서 6비트의 DSCP 비트를 분리합니다.

서버 워크로드에 적합한 기본 매핑:

트래픽 유형DSCPTOS 16진수예시
대화형EF0xb8SSH, DNS, VoIP
비즈니스AF410x88HTTP, HTTPS, API
대량CS10x20백업, FTP, 패키지 업데이트
최선의 노력CS00x00그 외 모든 것

아웃바운드 패킷이 필터에 도달하기 전에 iptables에서 태그 지정 tc 필터에 도달하기 전에 태그를 지정하십시오:

iptables -t mangle -A OUTPUT -p tcp --dport 22 -j DSCP --set-dscp 46

그런 다음 tc 해당 태그와 일치시켜 올바른 HTB 클래스로 라우팅하십시오:

# EF (SSH, VoIP) goes to the high-priority class
tc filter add dev eth0 protocol ip parent 1:0 prio 1 u32 \
  match ip tos 0xb8 0xfc flowid 1:10
 
# AF41 (web traffic) goes to the medium class
tc filter add dev eth0 protocol ip parent 1:0 prio 2 u32 \
  match ip tos 0x88 0xfc flowid 1:20
 
# CS1 (bulk) goes to the low-priority class
tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 \
  match ip tos 0x20 0xfc flowid 1:30

모니터링 및 문제 해결

자주 사용하게 될 세 가지 명령어:

tc -s qdisc show dev eth0
tc -s class show dev eth0
tc -s filter show dev eth0

다음 droppedoverlimits 카운터를 주시하십시오. 패킷 손실은 큐가 포화 상태임을 의미하며, 한도 초과 시 클래스 상한에 도달하여 커널이 트래픽을 지연시키거나 버려야 했음을 나타냅니다. 실시간 보기를 원하시면:

watch -n 1 'tc -s class show dev eth0'

내부 매개변수(목표, 간격, 퀀텀)에 대해 -d 를 추가하여 내부 매개변수(target, interval, quantum)를 확인하고, -j 메트릭스 스택으로 파이프라인을 연결하는 경우 JSON 출력을 위해 를 추가하세요. ss -tin 와 함께 사용하면 TCP 계층의 RTT 추정값과 재전송 횟수를 확인할 수 있습니다.

대부분의 오류는 다음과 같은 몇 가지 유형으로 분류됩니다:

증상가능한 원인해결 방법
RTNETLINK answers: File exists루트 qdisc가 이미 구성되어 있음tc qdisc del dev eth0 root 첫 번째
규칙이 적용되지만 트래픽이 제한되지 않음잘못된 인터페이스이거나 TSO/GSO가 여전히 활성화되어 있음다음으로 확인 ip link show, 다음 명령어로 오프로드를 비활성화하십시오 ethtool -K
필터가 일치하지 않음잘못된 포트/IP 구문 또는 마스크 정렬카운터 조치를 추가하고 tc -s filter show
재부팅 후 규칙이 사라짐구성은 메모리에만 존재합니다스크립트로 감싸서 systemd 또는 NetworkManager 디스패처에서 호출하십시오
우선순위 트래픽에서 높은 지연 시간리프 qdisc가 없거나 버스트가 너무 낮음leaf 클래스에 연결 fq_codel leaf 클래스에 연결하고, burst

설정 오류로 인해 접속이 차단된 경우, 재설정은 간단합니다:

tc qdisc del dev eth0 root

tc 원래 없는 대역폭을 만들어낼 수는 없지만, 적절히 프로비저닝된 업링크에서는 예측 가능한 성능과, 단 한 명의 테넌트가 대용량 전송을 시작하는 순간 무너져 내리는 서버의 차이를 만들어 냅니다. 순수한 대역폭과 이를 원하는 대로 조절할 수 있는 자유가 필요하다면, FDC의 전용 서버를 확인해 보십시오.

블로그

이번 주 추천

더 많은 기사
Linux 트래픽 제어(tc): 실무 가이드

Linux 트래픽 제어(tc): 실무 가이드

Tc로 Linux에서 대역폭을 제어하고, 트래픽 우선 순위를 지정하고, 수신 및 발신 형식을 지정하세요. 실제 서버에 대한 HTB, IFB, DSCP 및 fq_codel 구성 작업.

12분 소요 - 2026년 6월 5일

강력하고 계량되지 않는 VPS가 중요한 이유

7분 소요 - 2025년 5월 9일

더 많은 기사
background image

질문이 있거나 맞춤형 솔루션이 필요하신가요?

icon

유연한 옵션

icon

글로벌 도달 범위

icon

즉시 배포

icon

유연한 옵션

icon

글로벌 도달 범위

icon

즉시 배포