Mạng Linux & Container/Network namespaces — vì sao container thấy mạng riêng
8/21
Bài 8 / 21~13 phútContainer NetworkingMiễn phí lượt xem

Network namespaces — vì sao container thấy mạng riêng

Tạo network namespace, mỗi netns có stack mạng độc lập (interface, route, iptables riêng). Đây là isolation layer Docker/Kubernetes dùng cho mỗi container/pod.

TL;DR: Network namespace (netns) là cơ chế kernel Linux tách stack mạng thành các “máy logic” trên cùng host: mỗi netns có interface, bảng route, iptables/nft ruleset, và socket riêng. Container “có mạng riêng” vì runtime đưa process vào một netns — không phải vì Docker viết stack TCP mới. Bạn tạo netns bằng ip netns add, chạy lệnh bên trong bằng ip netns exec, và chứng minh isolation bằng ip addr / ip route / iptables khác host. Pitfall số một: netns mới chỉ có lolo mặc định DOWNping 127.0.0.1 fail cho đến khi ip link set lo up.

On-call: junior báo “container không listen được localhost:8080 — trên host curl 127.0.0.1:8080 vẫn thấy service cũ”. Họ đã ss -lntp trên host và thấy process app… nhưng app chạy trong netns container. 127.0.0.1 của host ≠ 127.0.0.1 của container. Bạn ip netns identify <pid> (hoặc nsenter/docker exec) rồi ss bên trong netns — bind đúng chỗ, chỉ đang nhìn nhầm stack. Không phải firewall, không phải app “im lặng” — sai namespace.

Module 1 đã dạy stack, veth, bridge, netfilter trên một view host. Bài này mở chiều isolation: cùng kernel, nhiều stack mạng song song. Không có netns, “container network” không tồn tại; không hiểu netns, mỗi lần debug bạn chỉ restart daemon.

Bài này implement netns bằng ip netns và chứng minh stack độc lập — trước khi nối hai netns bằng veth.

1. Analogy — Nhiều căn hộ, mỗi căn một tủ điện mạng

Hình dung một toà nhà (host Linux) có nhiều căn hộ (network namespace):

  1. Mỗi căn có tủ mạng riêng: cổng RJ45 ảo (interface), sơ đồ dây (route), nội quy ra vào (firewall rules).
  2. 127.0.0.1 là chuông cửa trong căn — bấm chuông căn 5 không mở cửa căn 7.
  3. Hành lang chung (kernel) mang điện/nước (CPU, memory) cho mọi căn — nhưng không tự nối LAN căn này với căn kia.
  4. Muốn hai căn nói chuyện: thợ (runtime) phải kéo cáp nhảy (veth pair) giữa hai tủ — bài sau.
Đời thườngNetwork namespace
Căn hộMột netns
Tủ điện / switch trong cănInterface list trong netns
Bản đồ “đi đâu ra ngoài”Route table của netns
Nội quy ra vàoiptables/nft của netns
Chuông localhostlo / 127.0.0.1 chỉ trong netns đó
Cáp giữa hai cănveth pair (bài 02)
💡 Cách nhớ

Netns không tạo kernel mới — nó tạo view mạng riêng. Cùng softirq/sk_buff/netfilter Module 1, nhưng device, route, rule, socket gắn theo netns. Debug sai view = đọc đúng lệnh trên nhầm tủ điện.

2. Network namespace cô lập những gì?

Namespace Linux là cơ chế partition tài nguyên kernel theo process. Có nhiều loại (pid, mnt, uts, user…). Network namespace (CLONE_NEWNET) tách riêng phần networking:

Tài nguyênCó tách theo netns?Ý nghĩa debug
Network interfaces (ip link)Host không thấy eth0 của container (và ngược lại), trừ khi share/move
Địa chỉ IPCùng 10.0.0.5 có thể tồn tại ở hai netns khác nhau
Route tableDefault gateway container ≠ host
iptables / nftables rulesetRule NAT host không “tự” là rule trong netns container
Socket / port listenHai process hai netns cùng bind :80 được
/proc/net/*, ss, netstat (view theo netns)Phải exec/nsenter đúng netns mới thấy
CPU, RAM, filesystem (trừ mount ns)Không (netns không lo)Netns chỉ là lớp mạng
flowchart TB
    subgraph HOST["Host / init netns"]
        H_IF["eth0, docker0, veth..."]
        H_RT["route table host"]
        H_FW["iptables host"]
    end
    subgraph NS1["Netns container A"]
        A_IF["lo, eth0"]
        A_RT["route table A"]
        A_FW["iptables A"]
    end
    subgraph NS2["Netns container B"]
        B_IF["lo, eth0"]
        B_RT["route table B"]
        B_FW["iptables B"]
    end
    HOST -.->|"no auto link"| NS1
    HOST -.->|"no auto link"| NS2
    NS1 -.->|"isolated"| NS2

Cơ chế bên dưới (mức đủ để debug): process gắn một network ns (file descriptor /proc/<pid>/ns/net). Mọi lời gọi socket, ip, netfilter lookup của process đó resolve trong ns đó. Kernel vẫn một bản; object mạng (netdev, fib route, xtables) mang con trỏ “thuộc netns nào”. Vì vậy ip addr trên host không liệt kê iface chỉ nằm trong netns con — không phải “Docker che giấu”, mà object không thuộc view hiện tại.

Vì sao container cần netns riêng?

Nếu mọi container share netns host:

  • Cùng port 8080conflict bind.
  • iptables -F trong một “container đặc quyền” có thể phá rule host.
  • Không có ranh giới rõ cho multi-tenant / least privilege mạng.

Netns cho phép: mỗi container (bridge mode) một stack, IP private riêng, port riêng; host giữ bridge/NAT ở init netns. Kubernetes pod thường một netns cho cả pod (các container trong pod share network — chi tiết Module 3); mô hình vẫn là “một stack logic / một netns”, không phải “mỗi process một TCP stack user-space”.

📌 Không phải mọi namespace Linux

PID namespace, mount namespace, user namespace… khác network namespace. Bài này chỉ netns. Container runtime thường kết hợp nhiều namespace; khi debug mạng, vào đúng net ns (ip netns exec, nsenter -t <pid> -n, docker exec).

3. Tạo và vào netns bằng ip netns

iproute2 quản lý netns có tên qua thư mục (thường /var/run/netns/): mỗi tên là một bind-mount tới file ns. Đây là cách lab và nhiều script ops dùng — không cần Docker.

Chạy trên Linux (VM/WSL2/host có netns), cần sudo. Copy-paste từng khối, đọc output thật trên máy bạn.

# 1) Create a named network namespace
sudo ip netns add ns1

# 2) List named namespaces (ns1 should appear)
ip netns list

# 3) What interfaces exist *inside* ns1? (expect lo only, often DOWN)
sudo ip netns exec ns1 ip link
sudo ip netns exec ns1 ip addr

# 4) Bring loopback up — classic first step in a fresh netns
sudo ip netns exec ns1 ip link set lo up

# 5) Re-check addresses / state
sudo ip netns exec ns1 ip addr
sudo ip netns exec ns1 ip route

# 6) Loopback should answer only *inside* this netns
sudo ip netns exec ns1 ping -c 2 127.0.0.1

Kết quả kỳ vọng:

Lệnh (trong ns1)Trước lo upSau lo up
ip linkloDOWNloUP
ip addrlo không usable / chưa có traffic local127.0.0.1/8 trên lo (thường kèm ::1)
ping 127.0.0.1fail / unreachable0% packet loss
ip routerỗng hoặc gần rỗng (chưa default gw)vẫn không có route ra Internet — đúng

So sánh cùng lệnh trên host (không netns exec):

# Host (init netns) — your real NIC still here
ip -br link
ip route | head

# Inside ns1 — still isolated; no eth0 of the host
sudo ip netns exec ns1 ip -br link

Host vẫn thấy eth0/ens*/wlan*; trong ns1 không thấy NIC vật lý host (trừ khi bạn move device vào netns — hiếm cho lab container; container dùng veth thay vì cướp NIC host).

ip netns exec làm gì?

ip netns exec ns1 <cmd> roughly:

  1. Mở ns file của ns1.
  2. setns() (hoặc tương đương) sang network namespace đó cho process con.
  3. Chạy <cmd> trong context đó (và thường mount /sys//etc netns nếu cấu hình).

Tương đương ngắn với nhiều lệnh ip:

# Same view as: sudo ip netns exec ns1 ip link
sudo ip -n ns1 link
sudo ip -n ns1 addr

Process lâu dài (server) cần start trong netns hoặc join ns — không phải chỉ chạy một exec rồi thoát là “app mãi trong ns” trừ khi bạn giữ process đó.

Thử đoán

Trước khi lo up, bạn chạy sudo ip netns exec ns1 ping -c 1 127.0.0.1. Kết quả là success, fail ngay, hay treo? Viết dự đoán — rồi chạy. Không gán IP thêm trên lo trước khi quan sát.

Mechanism: lo DOWN → stack local coi interface không sẵn sàng; ping loopback fail (hoặc không usable). Sau ip link set lo up, địa chỉ 127.0.0.1/8 hoạt động chỉ trong netns đó. Host ping 127.0.0.1 vẫn đập vào lo host — hai loopback độc lập.

4. Chứng minh stack độc lập — iface, route, iptables

Isolation không chỉ là “có lo riêng”. Ba trục bạn phải tự chứng trên lab (worked):

4.1 Interface list khác nhau

echo "=== HOST ==="
ip -br link

echo "=== NS1 ==="
sudo ip netns exec ns1 ip -br link

Host: nhiều iface. ns1: gần như chỉ lo (sau khi up).

4.2 Route table khác nhau

# Optional: add a dummy route only inside ns1 (does not touch host table)
sudo ip netns exec ns1 ip route add 198.51.100.0/24 via 127.0.0.1 dev lo

echo "=== HOST routes (should NOT show 198.51.100.0/24 via lo ns1) ==="
ip route | grep 198.51.100 || echo "host: no 198.51.100 route (expected)"

echo "=== NS1 routes ==="
sudo ip netns exec ns1 ip route

Route thêm trong ns1 không xuất hiện trên host. Xoá test:

sudo ip netns exec ns1 ip route del 198.51.100.0/24 || true

4.3 iptables ruleset khác nhau

# Count rules / list filter table in each view (nft backend may show via iptables-nft)
echo "=== HOST filter (first lines) ==="
sudo iptables -L -n 2>/dev/null | head -n 15 || echo "iptables not available on host"

echo "=== NS1 filter (fresh netns often empty/default) ==="
sudo ip netns exec ns1 iptables -L -n 2>/dev/null | head -n 15 || echo "iptables not available in ns1"

Netns mới thường không kế thừa rule NAT DOCKER của host. Đó là lý do publish port / MASQUERADE sống ở host netns (bài SNAT/DNAT), không “tự copy” vào container.

flowchart LR
    CMD["ip netns exec ns1 ..."] --> VIEW["Kernel objects in ns1"]
    VIEW --> IF["netdev list"]
    VIEW --> RT["FIB / routes"]
    VIEW --> FW["netfilter tables"]
    VIEW --> SK["sockets"]

4.4 Hai netns không nối = không ping chéo

sudo ip netns add ns2
sudo ip netns exec ns2 ip link set lo up

# Still no veth between ns1 and ns2 — isolation is total
sudo ip netns exec ns1 ping -c 1 127.0.0.1   # OK: own lo
# There is no "ns2 loopback address" reachable from ns1 without a link

Không có cáp ảo, không có L3 path giữa hai netns. Fail là thiết kế, không phải thiếu iptables -P ACCEPT. Bài 02 — veth mới kéo cáp.

5. Container = một network namespace — Docker map thế nào?

Runtime container (Docker, containerd, CRI-O…) khi tạo container bridge mode cổ điển roughly:

  1. Tạo (hoặc lấy) network namespace cho container/pod.
  2. Tạo veth pair; move một đầu vào netns → rename eth0, gán IP, lo up, default route qua gateway bridge.
  3. Đầu còn lại gắn bridge host (docker0 / CNI bridge) — Module 1 + bài bridge lab.
  4. Host netns: ip_forward, SNAT/MASQUERADE, DNAT publish port — bài 04/05 module này.
flowchart LR
    CTR["Process in container"] --> NS["Container netns"]
    NS --> ETH0["eth0 = veth end"]
    ETH0 -->|"peer"| HV["veth on host"]
    HV --> BR["bridge docker0"]
    BR --> NAT["iptables NAT host"]
    NAT --> PHY["physical NIC"]
Thứ bạn thấyObject thật
docker run “có IP”IP gán trong netns container trên eth0
docker exec … ip addrip netns exec (hoặc nsenter) vào netns đó
localhost trong containerlo của netns container
docker run -p 8080:80DNAT trên host netns, không “mở lỗ” trong netns app một mình
Pod K8s nhiều containerThường share một netns — cùng localhost trong pod

Bạn không cần Docker để hiểu mô hình: ip netns add + (bài sau) veth + bridge = cùng plumbing daemon script hoá.

Liên hệ Module 1

  • Stack RX/TX (Linux network stack) chạy per netns context cho object mạng.
  • vethcáp giữa netns; netns là hai đầu máy.
  • bridge / iptables trên host gom nhiều netns container thành LAN ảo + NAT.

6. Pitfall tổng hợp

Nhầm 1: “Quên ip link set lo up.”
✅ Netns mới: lo DOWN mặc định. App bind 127.0.0.1, healthcheck curl localhost, ping 127.0.0.1 đều fail trông như “mạng hỏng”. Checklist đầu tiên trong netns trống: lo UP.

Nhầm 2: “curl 127.0.0.1 trên host = test service trong container.”
127.0.0.1per-netns. Service listen trong container chỉ trả lời loopback của container. Từ host: dùng IP veth/bridge, publish port, hoặc docker exec/nsenter vào đúng ns.

Nhầm 3: “Hai container cùng host thì tự thấy nhau qua eth0 host.”
✅ Không share netns → không tự share iface host. Cần bridge/veth/CNI path. Ping fail giữa hai netns “trống” là đúng.

Nhầm 4: “Xem iptables -L trên host là đủ cho rule trong container.”
✅ Ruleset tách. NAT Docker chủ yếu host netns; một số CNI/policy chèn rule chỗ khác. Debug: xác định packet đang ở netns nào rồi mới iptables/nft đúng view.

Nhầm 5: “Xoá container / reboot là cách dọn netns lab.”
✅ Named netns từ ip netns add sống đến khi ip netns del. Lab sót ns1/ns2 → đụng tên lần sau.

# Cleanup this lesson's namespaces
sudo ip netns del ns1 2>/dev/null || true
sudo ip netns del ns2 2>/dev/null || true
ip netns list
⚠️ lo DOWN — fail giả 'no network'

Triệu chứng: netns mới, ip addr thấy lo nhưng ping/curl localhost fail; junior nghi DNS/iptables. Root cause: admin state DOWN. Fix một dòng: ip link set lo up (qua ip netns exec). Runtime container thường up lo giúp bạn; script tự tạo netns hay quên.

📚 Deep Dive — man page & kernel

📚 Deep Dive — tài liệu gốc

Spec / reference:

Ghi chú: Lab dùng ip netns (named). Production container thường anonymous ns gắn PID — nsenter -t <pid> -n hoặc ip netns identify. Cùng cơ chế kernel.

Liên hệ các bài khác

Tóm tắt

  • Network namespace = stack mạng độc lập (iface, route, iptables, socket) trên cùng kernel.
  • Tạo/vào: ip netns add <name>ip netns exec <name> <cmd> (hoặc ip -n <name> …).
  • Netns mới: chủ yếu lo, và lo DOWN cho đến khi ip link set lo up.
  • Container ≈ 1 netns (pod K8s: share netns trong pod); Docker script veth + bridge + NAT trên host.
  • Hai netns không link = cô lập tuyệt đối — nối bằng veth ở bài sau.
  • Pitfall: nhầm localhost host/container; đọc iptables sai netns; quên dọn ip netns del.

7. Tự kiểm tra

Tự kiểm tra
Q1
Network namespace cô lập những object mạng nào? Kể ít nhất bốn và một thứ netns không tách.
Tách (trong số hay gặp): network interfaces, địa chỉ IP, route table, iptables/nft ruleset, socket/port listen, view /proc/net. Không tách (netns không phải cgroup/pid): CPU, RAM — netns chỉ partition networking. Filesystem/PID là namespace loại khác.
Q2
Bạn vừa ip netns add ns1 và ip netns exec ns1 ping -c 1 127.0.0.1 thì fail. Chẩn đoán khả dĩ nhất và lệnh sửa tối thiểu?
Netns mới có lo nhưng admin state thường DOWN. Ping loopback fail không có nghĩa kernel hỏng. Sửa tối thiểu: sudo ip netns exec ns1 ip link set lo up rồi ping lại. Nếu vẫn fail, kiểm ip link/ip addr trong đúng ns1 (không phải host).
Q3
Vì sao curl 127.0.0.1:8080 trên host không chứng minh service trong container đang listen?
127.0.0.1 là địa chỉ loopback của netns hiện tại. Host và container là hai netns: listen trên lo container không xuất hiện trên lo host. Phải exec/nsenter vào netns container, hoặc dùng IP gắn veth/bridge, hoặc publish port (DNAT) trên host.
Q4
ip netns exec ns1 ip addr khác ip addr trên host ở điểm cơ chế nào — 'Docker che output' hay object khác?
Không phải che output. Process sau setns vào netns ns1 chỉ thấy netdev/route thuộc ns đó. NIC host nằm init netns — không list trong ns1 trừ khi move device. Cùng binary ip, khác kernel network view.
Q5
Trong Docker bridge mode cổ điển, 'một container một netns' nghĩa là gì cho port 80? Hai container cùng host bind :80 được không?
Mỗi container (thường) có netns riêng nên mỗi netns có không gian port riêng — hai container cùng bind :80 trên eth0/lo của mình được. Conflict xảy ra khi share netns (host network mode, hoặc cùng pod netns) hoặc khi publish cùng port host qua DNAT.
Q6
Hai netns ns1 và ns2 chỉ có lo UP, không veth. Vì sao ping từ ns1 sang 'IP của ns2' không phải lỗi iptables thiếu ACCEPT?
Không có device/link L2/L3 nối hai netns thì không có path forward — isolation mặc định. iptables ACCEPT trên host cũng không tạo cáp ảo. Cần veth (hoặc shared ns, hoặc interface move). Fail là đúng mô hình; sửa bằng plumbing (bài veth/bridge), không phải chỉ mở policy.
Q7
Bạn thêm route 198.51.100.0/24 trong ns1. Vì sao ip route trên host không thấy route đó — và điều đó chứng minh gì?
FIB/route table gắn netns. Thay đổi trong ns1 không ghi vào bảng host. Đây là bằng chứng thực nghiệm stack độc lập (cùng họ với iface list và iptables riêng). Cleanup bằng ip route del trong đúng ns1, không phải trên host.

Bài tiếp theo: Nối container bằng veth — ping qua 2 netns

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

Nối container bằng veth — ping qua 2 netns