Skip to main content

OVS Phần 3: Kiến Trúc Bên Trong — vswitchd, ovsdb-server và Datapath

Phần 3/21 trong loạt Open vSwitch (OVS) — nối tiếp Phần 1 (đã giới thiệu sơ lược ovs-vswitchd/ovsdb-server ở Mục 3) và Phần 2 (đã khẳng định OVS và Linux bridge là hai kernel module hoàn toàn tách biệt). Bài này mở nắp máy: bên trong OVS thực sự có những mảnh nào, và một packet đi qua chúng theo đường nào. Đọc xong, Bài 4 sẽ đưa bạn vào cài đặt và dựng lab OVS đầu tiên bằng tay.

Mục tiêu bài học: sau bài này bạn sẽ phân biệt được ba mảnh ghép ovsdb-server/ovs-vswitchd/datapath làm việc gì và ai nói chuyện với ai, hiểu vì sao OVS có "hai loại flow" hoàn toàn khác nhau — OpenFlow flow và datapath flow — và tại sao gộp chung hai cái này là lỗi hiểu sai phổ biến nhất khi mới học OVS, đồng thời nắm được sự khác biệt giữa kernel datapath và userspace datapath trước khi Bài 4 dùng chính kiến thức này để chọn cách cài đặt.

Ba câu hỏi mà bài này trả lời:

  1. ovsdb-server, ovs-vswitchd, và datapath chia nhau làm việc gì, ai nói chuyện với ai?
  2. "OpenFlow flow" và "datapath flow" khác nhau thế nào, và vì sao lẫn lộn hai khái niệm này là lỗi hay gặp nhất?
  3. Một packet đi qua OVS lần đầu tiên khác gì so với packet thứ hai của cùng luồng đó?

Mục lục


1. Nhắc lại nhanh: Bài 1 và Bài 2 đã nói gì?

Bài 1 đã vẽ sơ đồ tổng: ovsdb-server giữ trạng thái cấu hình (bridge nào, port nào, VLAN gì), ovs-vswitchd nói chuyện OpenFlow với ai cần, và "datapath" (được gọi ngắn gọn là kernel module) mới là nơi packet thực sự bị forward. Bài 2 khẳng định thêm: datapath đó — openvswitch.ko — là một kernel module hoàn toàn tách biệt với bridge.ko của Linux bridge, không dùng chung một dòng code nào.

Cả hai bài đều dừng ở mức sơ đồ. Bài này trả lời câu hỏi còn bỏ ngỏ: "datapath" chính xác là cái gì (hoá ra có tới ba dạng, không chỉ một), và khi một packet thật sự đi vào OVS, nó chạm vào từng mảnh theo thứ tự nào.


2. Ba mảnh ghép bên trong OVS — và hai loại flow dễ gộp nhầm nhất

Ba tiến trình/module dưới đây luôn cùng tồn tại trên một host chạy OVS, mỗi bên giữ đúng một vai trò và không giẫm chân nhau:

  • ovsdb-server — giữ config state: bridge nào tồn tại, port nào gắn vào bridge nào, VLAN tag bao nhiêu. Đây là "bộ nhớ" tĩnh, không tham gia vào việc forward từng packet.
  • ovs-vswitchd — "bộ não" duy nhất chạy hoàn toàn ở userspace. Nó đọc config từ ovsdb-server, giữ toàn bộ logic OpenFlow (nhiều table, priority, action — cú pháp cụ thể sẽ học kỹ ở Bài 6 và Bài 12), và quan trọng nhất: dịch quyết định OpenFlow đó thành hành động mà datapath hiểu được. Phần dịch này có tên riêng trong chính mã nguồn OVS — engine ofproto-dpif-xlate (file thật: ofproto/ofproto-dpif-xlate.c trong kho mã OVS, nếu bạn tò mò muốn đọc).
  • datapath — "cơ bắp" thực thi forwarding tốc độ cao. Có thể là kernel module (openvswitch.ko, giao tiếp với ovs-vswitchd qua Generic Netlink — một họ giao thức IPC có sẵn trong kernel Linux để trao đổi message có cấu trúc giữa kernel và userspace — phần này tên nội bộ là dpif-netlink), hoặc hoàn toàn nằm trong chính tiến trình ovs-vswitchd ở userspace (tên nội bộ dpif-netdev) — Mục 4 sẽ nói kỹ khi nào dùng loại nào.

dpif (viết tắt "datapath interface") là lớp trừu tượng đứng giữa ofproto (nơi giữ logic OpenFlow) và datapath thật bên dưới — nhờ lớp này, ovs-vswitchd nói chuyện với kernel datapath hay userspace datapath bằng đúng một API nội bộ, không cần biết bên dưới là module kernel hay chỉ là vòng lặp trong chính nó.

Hai loại flow — điểm dễ gộp nhầm nhất khi mới học OVS

Đây là chỗ rất nhiều người mới học OVS nhầm: nghĩ "flow" chỉ có một loại. Thực ra OVS có hai loại flow hoàn toàn khác nhau, tài liệu chính thức của dự án trả lời thẳng câu hỏi "OVS có mấy loại flow?" như sau — "Người dùng và controller chỉ trực tiếp điều khiển OpenFlow flow table. Open vSwitch tự quản lý datapath flow table, nên người dùng bình thường không cần bận tâm tới nó."

OpenFlow flowDatapath flow
Ai kiểm soátBạn/controller (qua ovs-ofctl)OVS tự quản lý
Cấu trúcNhiều table, có priorityĐúng một table, không priority
Vai trò"Luật" — bạn viết ra"Cache" — kết quả đã tính sẵn
Xem bằng lệnhovs-ofctl dump-flows br0 (đã thấy sơ ở Bài 1 Mục 4)ovs-appctl dpctl/dump-flows (Mục 5 bài này)

💡 Hình dung như thế này: OpenFlow flow table giống cuốn sổ công thức đầy đủ mà bếp trưởng (ovs-vswitchd) tự tay viết ra — có thứ tự ưu tiên, có nhiều bước, ai đọc lại cũng hiểu vì sao món này được nấu như vậy. Datapath flow là tờ giấy dán tạm cạnh bếp phụ (datapath), ghi tắt đúng một dòng: "khách gọi món y hệt lần trước → cứ làm y vậy, khỏi hỏi lại bếp trưởng". Tờ giấy đó không có thứ tự ưu tiên, không có nhiều bước — chỉ một hành động đã chốt sẵn, dán tạm rồi gỡ đi khi không còn ai gọi món đó nữa.

OVS Architecture — Who Talks to Whomovsdb-server(config state)ovs-vswitchdofproto (OpenFlow logic)ofproto-dpif-xlate (dịch)OVSDB — configdpif (datapath interface)datapathkernel hoặc userspace — Mục 4dpif-netlink /dpif-netdevupcall khi miss →Packet forwarding thật xảy ra ở tầng datapath — chi tiết ở Mục 3

3. Đường đi của một packet: lần đầu (miss) và lần sau (hit)

Đây chính là lúc "tờ giấy dán tạm" ở Mục 2 được viết ra và được dùng lại. Một packet đi qua OVS theo đúng hai kịch bản sau, tuỳ nó có phải packet đầu tiên của một luồng (flow) mới hay không.

Packet đầu tiên — MISS: gói tin tới một port — OVS gọi chính thức là vport ("virtual port", tên chung cho mọi điểm gắn vào datapath, bất kể đó là NIC vật lý, veth, port nội bộ hay tunnel port) — datapath tra flow table của chính nó trước — chưa có entry nào khớp, gọi là miss. Theo đúng mô tả trong tài liệu kernel chính thức: khi miss, datapath "đưa packet đó vào hàng đợi gửi lên userspace để xử lý" — hành động này gọi là upcall. ovs-vswitchd nhận upcall, chạy packet qua engine ofproto-dpif-xlate để đánh giá toàn bộ pipeline OpenFlow (mọi table, mọi priority — Bài 12 sẽ mổ xẻ chi tiết bước này), rồi cài kết quả thành một datapath flow mới (thường ở dạng wildcarded — gọi là "megaflow" vì một entry có thể khớp cả một nhóm packet, không chỉ đúng một kết nối) vào flow table của datapath. Packet gốc được forward ngay theo hành động đó.

Packet thứ hai trở đi, cùng luồng — HIT: datapath tra flow table, thấy khớp ngay lập tức — hit. Toàn bộ xử lý diễn ra gọn trong datapath, không upcall, không chạm tới ovs-vswitchd, không chạy lại pipeline OpenFlow — nhanh hơn hẳn vì bỏ qua toàn bộ vòng dịch ở Mục 2.

Nếu bạn đã quen vận hành switch vật lý, đây không phải khái niệm hoàn toàn mới: switch ASIC bạn đang dùng cũng tách biệt control plane (CPU chạy routing daemon — RPD trên JunOS, tên gọi tương đương trên các vendor khác — tính toán FIB, chạy protocol) khỏi data plane (ASIC forward theo TCAM/FIB đã nạp sẵn, không hỏi lại CPU cho từng packet). OVS làm đúng việc đó bằng phần mềm: ovs-vswitchd đóng vai control plane (chậm, chỉ chạy khi cần tính flow mới), datapath đóng vai data plane (nhanh, chạy cho mọi packet sau đó).

Datapath flow không tồn tại mãi, và việc dọn dẹp nó không chỉ đơn thuần là "hết giờ thì xoá". Một daemon riêng bên trong ovs-vswitchd — revalidator thread — quét định kỳ (mặc định mỗi max-revalidator = 500 ms) và làm đúng ba việc: gỡ flow idle quá max-idle = 10000 ms (10 giây) theo mặc định; đồng bộ ngược counter packet/byte mà datapath vừa đếm được lên đúng OpenFlow flow tương ứng — đây chính là nguồn gốc thật của các cột n_packets/n_bytes bạn thấy khi chạy ovs-ofctl dump-flows (Mục 2), vì bản thân OpenFlow flow không tự đếm packet, việc đếm luôn diễn ra ở datapath; và quan trọng không kém — phát hiện khi bạn vừa đổi một OpenFlow rule, rồi cài lại hoặc xoá ngay các datapath flow cũ đã lỡ cache theo hành vi trước đó, không đợi tới hết max-idle. Đây chính là cơ chế đứng sau câu "OVS tự quản lý datapath flow table" đã trích ở Mục 2 — không phải một câu khẩu hiệu, mà là công việc cụ thể của revalidator.

Hai con số max-idle/max-revalidator này không phải hằng số cứng trong code — chúng nằm trong other_config của bảng Open_vSwitch, tức đúng loại config state mà ovsdb-server giữ (Mục 2). Bạn có thể tự xem giá trị đang áp dụng bằng chính công cụ đã học ở Bài 1:

ovs-vsctl get Open_vSwitch . other_config
# Output minh họa — máy chưa từng chỉnh gì
{}

(Output minh họa — cấu trúc đúng theo lệnh thật, số liệu cụ thể chỉ mang tính ví dụ.) Kết quả rỗng {} không có nghĩa là "chưa cấu hình gì" theo nghĩa lỗi — nó nghĩa là đang dùng đúng giá trị mặc định (max-idle=10000, max-revalidator=500) vì bạn chưa từng ghi đè. Muốn đổi, dùng đúng cú pháp set <table> <record> <cột>=<giá_trị> chuẩn của OVSDB: ovs-vsctl set Open_vSwitch . other_config:max-idle=20000.

Điều bạn sẽ tự quan sát ở bài tập cuối bài chỉ là phần dễ thấy nhất trong ba việc trên: flow xuất hiện ngay sau ping đầu tiên, và biến mất nếu im lặng đủ lâu.

Packet Path — First Miss vs Cached HitPacket #1 (đầu tiên của flow) — MISSPort (vport)DP flow tableno match — MISSUpcall toovs-vswitchdofproto-dpif-xlate(chạy OpenFlow pipeline)Cài flow mớivào datapath (megaflow)Forward packetPacket #2 trở đi (cùng flow) — HIT (cache)Port (vport)DP flow tablematch found — HITForward packetKhông upcall — ovs-vswitchd không được gọi tới trên đường đi nàyFlow trong datapath cache hết hạn sau tối đa max-idle = 10000ms (10 giây)không có packet nào khớp — dọn bởi revalidator thread quét định kỳ (mặc định 500ms).

4. Kernel datapath vs userspace datapath: ba lựa chọn, không phải hai

Mục 2 đã nói datapath có thể là kernel module hoặc userspace. Chi tiết hơn, thực tế có ba cấu hình phổ biến, không phải hai — vì "userspace" tự chia thêm một nhánh có DPDK và không có DPDK:

Loại datapathdatapath_type khi tạo bridgeThực thi ở đâuKhi nào dùng
Kernel (system)để trống (mặc định)Trong kernel, qua module openvswitch.ko, nói chuyện với ovs-vswitchd bằng Generic Netlink (dpif-netlink)Mặc định cho hầu hết cài đặt Linux — Bài 4 sẽ dùng đúng loại này
Userspace thuầnnetdevHoàn toàn trong tiến trình ovs-vswitchd (dpif-netdev), không cần nạp kernel moduleMáy không nạp được kernel module (một số container/cloud), hoặc môi trường thử nghiệm
Userspace + DPDKnetdev (giống trên) + port khai type=dpdkVẫn dpif-netdev, nhưng port đọc/ghi thẳng NIC qua poll-mode driver DPDK, bỏ qua kernel network stackCần throughput rất cao, có NIC hỗ trợ DPDK + hugepages — Bài 14 đào sâu

Cú pháp tạo bridge dùng datapath userspace (áp dụng cho cả hai hàng dưới của bảng trên, khác nhau ở loại port thêm vào sau):

ovs-vsctl add-br br0 -- set bridge br0 datapath_type=netdev

Điểm hay gây nhầm: userspace thuần và userspace+DPDK dùng chung một giá trị datapath_type=netdev — khác biệt nằm ở loại port bạn thêm vào sau đó — type=dpdk mới thật sự bật đường đi qua poll-mode driver (CPU liên tục chủ động hỏi vòng NIC xem có gói mới hay không, thay vì ngồi chờ NIC báo ngắt/interrupt như driver kernel thông thường — đây là lý do chính khiến đường đi này nhanh hơn hẳn, Bài 14 đào sâu cơ chế này), không phải ở giá trị datapath_type.

Vì sao kernel datapath không có Exact Match Cache (EMC)?

Đào sâu thêm một bậc: datapath userspace (dpif-netdev) tự cấy thêm một tầng cache cực nhanh phía trước tầng megaflow đã nói ở Mục 3, gọi là Exact Match Cache (EMC) — một bảng hash khớp tuyệt đối theo từng "microflow" cụ thể, chỉ khi miss ở EMC thì mới rơi xuống tầng megaflow (tên nội bộ dpcls, tuple space search) tổng quát hơn. Nhiều nguồn kỹ thuật (bài viết chính thức từ Intel về EMC, tài liệu OVS về DPDK bridge, mã nguồn dự án) đều xác nhận thống nhất: EMC là tính năng riêng của datapath userspace, kernel datapath (dpif-netlink, chính là openvswitch.ko) không có tầng này — kernel chỉ có đúng một tầng megaflow, như Mục 3 đã mô tả. Không phải thiếu sót — là lựa chọn kiến trúc.

(Suy luận hợp lý từ kiến trúc, không phải trích dẫn chính thức: userspace vốn chậm hơn kernel network stack gốc do phải đi qua nhiều lớp trừu tượng hơn, nên OVS bù lại bằng cách cấy thêm một tầng cache phụ ngay trong chính nó; còn kernel network stack đã được tối ưu sẵn ở mức thấp, nên không cần thêm tầng đó.)

Kernel vs Userspace vs DPDK DatapathSystem (kernel)Userspace (netdev)Userspace + DPDKovs-vswitchd(chỉ khi miss)openvswitch.ko(forwarding ở đây)NIC (eth0)ovs-vswitchd(dpif-netdev)— forwarding ở đâykernel stack(chỉ chuyển tiếp)NIC (eth0)ovs-vswitchd(dpif-netdev + DPDK PMD)— forwarding ở đâyNIC (DPDK-capable)✕ bỏ qua kernel(PMD đọc thẳng NIC)Đường liền = forwarding chính. Đường đứt nét = chỉ chạy khi miss/upcall (hiếm).

5. Nhìn thấy datapath flow cache bằng ovs-appctl

Chưa dựng lab thật ở đây — Bài 4 sẽ làm việc đó kỹ, từ cài đặt tới tạo bridge/port đầu tiên. Ở mục này, output dưới đây chỉ để bạn hiểu ý nghĩa từng cột trước khi tự tay chạy lại sau khi hoàn thành Bài 4.

ovs-appctl là công cụ khác hẳn hai công cụ bạn đã biết: ovs-vsctl nói chuyện với ovsdb-server (config state), ovs-ofctl nói chuyện qua giao thức OpenFlow (OpenFlow flow state, đã dùng sơ ở Bài 1 Mục 4). ovs-appctl thì kết nối thẳng vào một daemon đang chạy qua Unix domain socket, dùng để hỏi/điều khiển runtime của chính daemon đó — ở đây là hỏi ovs-vswitchd về đúng thứ Mục 2-3 vừa nói: datapath flow.

# Xem tổng quan datapath: tên, danh sách port, thống kê hit/miss
ovs-appctl dpctl/show -s
# Output minh họa
system@ovs-system:
lookups: hit:1523 missed:12 lost:0
flows: 3
masks: hit:1523 total:2 hit/pkt:1.00
port 0: ovs-system (internal)
port 1: br0 (internal)
port 2: eth0
port 3: vnet0

(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, số liệu cụ thể chỉ mang tính ví dụ.) Dòng lookups: hit:1523 missed:12 chính là con số thật của khái niệm "hit vs miss" ở Mục 3 — missed:12 là số lần packet phải upcall lên ovs-vswitchd, hit:1523 là số lần packet được xử lý thẳng trong datapath.

# Xem chi tiết từng datapath flow đang có trong cache
ovs-appctl dpctl/dump-flows
# Output minh họa
recirc_id(0),in_port(2),eth(src=52:54:00:12:34:01,dst=52:54:00:12:34:02),eth_type(0x0800),ipv4(frag=no), packets:42, bytes:4116, used:0.328s, actions:3

(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, số liệu cụ thể chỉ mang tính ví dụ.) Field recirc_id(0) ở đầu dòng là "mã tái tuần hoàn" — mặc định 0 nghĩa là packet chỉ đi qua pipeline đúng một lượt; giá trị khác 0 chỉ xuất hiện khi datapath phải xử lý packet qua nhiều lượt liên tiếp (ví dụ khi có tunnel hoặc bonding — sẽ gặp lại ở các bài sau). So dòng này với một dòng ovs-ofctl dump-flows br0 thật (dạng cookie=0x0, duration=65.630s, table=0, n_packets=4, n_bytes=234, priority=0 actions=NORMAL — kiểu bạn đã thấy phảng phất ở Bài 1) sẽ thấy ngay bằng chứng cho bảng ở Mục 2: dòng datapath không có table=, không có priority= — chỉ có packets/bytes/used (thời gian từ lần cuối packet khớp) và một action duy nhất. Đúng một table, không priority, đúng như tài liệu chính thức đã nói.

Nếu chỉ nhớ được một điều sau bài này, hãy nhớ đúng điều ở Mục 2: OpenFlow flow là thứ bạn viết ra, datapath flow là thứ OVS tự cất giữ cho riêng nó — nhầm hai cái này là nguồn gốc của phần lớn nhầm lẫn khi debug OVS về sau.


Bài tập cuối bài

  1. Sau khi hoàn thành Bài 4 (cài đặt + tạo bridge/port đầu tiên), gửi vài gói ping qua lab đó, rồi lập tức chạy ovs-appctl dpctl/dump-flows — xác nhận thấy một flow ICMP vừa xuất hiện. Ngừng ping, đợi qua mốc 10 giây (đúng con số max-idle mặc định ở Mục 3), chạy lại lệnh — ghi lại flow đó đã biến mất hay chưa.
  2. Trên cùng bridge ở bài tập 1, chạy song song ovs-ofctl dump-flows br0ovs-appctl dpctl/dump-flows, đặt hai output cạnh nhau — tự liệt kê ít nhất 2 khác biệt về cấu trúc bạn quan sát được (không chép lại bảng ở Mục 2, tự nhìn ra từ output thật của bạn).

Nguồn tham khảo


Tiếp theo: Module 1, Bài 4 — Cài đặt OVS và dựng lab đầu tiên.