OVS Phần 5: ovs-vsctl — Bridge, Port, VLAN và OVSDB
Phần 5/21 trong loạt Open vSwitch (OVS) — nối tiếp Phần 4, nơi bạn đã gõ
ovs-vsctl add-br/add-portchỉ để dựng lab cho xong, chưa giải thích kỹ công cụ đang dùng là gì. Bài này quay lại đúngovs-vsctl, đào sâu: vòng đời đầy đủ của bridge/port, cách gán VLAN, và cách nhìn thẳng vào OVSDB — chính là database Bài 1 Mục 3 đã nhắc tên nhưng chưa ai thật sự mở ra xem. Đọc xong, Bài 6 sẽ chuyển hẳn sangovs-ofctl— công cụ đọc/ghi flow table OpenFlow, khác hoàn toàn vớiovs-vsctl.
Mục tiêu bài học: sau bài này bạn phân biệt rõ hai việc dễ gộp nhầm — cấu hình switch (OVSDB, việc ovs-vsctl làm) và hành vi forward packet (OpenFlow, việc ovs-ofctl làm ở Bài 6) — nắm được cú pháp tạo/xóa bridge/port đúng cách (kể cả khi script chạy lặp lại nhiều lần), gán được VLAN access/trunk cơ bản, và biết đọc OVSDB ở dạng thô bằng ovsdb-client dump thay vì chỉ tin vào bản tóm tắt thân thiện của ovs-vsctl show.
Ba câu hỏi mà bài này trả lời:
ovs-vsctlthao tác trên cái gì, và vì sao mọi thay đổi nó tạo ra đều bền qua việc khởi động lạiovs-vswitchd?- Gán một port vào VLAN, theo kiểu access hay trunk, cú pháp chính xác là gì?
ovs-vsctl showvà dữ liệu OVSDB thật khác nhau ở đâu, và vì sao đôi khi cần bỏ qua bản tóm tắt để đọc thẳng dữ liệu thô?
Mục lục
- 1. Nhắc lại: OVSDB nằm ở đâu trong những gì Bài 4 đã làm?
- 2. Vòng đời bridge và port: add-br, add-port, del-port, del-br
- 3. Gán VLAN cơ bản: access port bằng tag=, trunk port bằng trunks=
- 4. Hai cách nhìn cùng một dữ liệu: ovs-vsctl show và ovsdb-client dump
- 5. Transaction: gộp nhiều lệnh vào một atomic write
- Bài tập cuối bài
1. Nhắc lại: OVSDB nằm ở đâu trong những gì Bài 4 đã làm?
Bài 1 Mục 3 định nghĩa OVSDB là "một database thật... mô tả toàn bộ trạng thái switch: bridge nào, port nào, gán VLAN gì, tunnel nào". Bài 4 bạn đã ghi vào chính database đó bốn lần liền — mỗi lệnh ovs-vsctl add-br, add-port không "cấu hình trực tiếp" bridge như một switch vật lý nhận lệnh CLI, mà là một transaction ghi vào OVSDB; ovsdb-server lưu thay đổi đó xuống đĩa (mặc định tại /etc/openvswitch/conf.db), rồi ovs-vswitchd — daemon đang theo dõi database qua giao thức OVSDB — phát hiện thay đổi và tự áp nó xuống datapath thật (Bài 3 đã mổ xẻ chính xác vai trò từng daemon này).
Điều đó giải thích một hành vi bạn có thể chưa để ý ở Bài 4: nếu sudo systemctl restart openvswitch-switch.service ngay sau khi dựng lab, br0 và các port vẫn còn nguyên — không phải vì OVS "nhớ" trạng thái RAM, mà vì cấu hình đã nằm bền trong file conf.db trên đĩa, và ovs-vswitchd đọc lại đúng trạng thái đó lúc khởi động. Đây chính là khác biệt cốt lõi so với Linux bridge truyền thống mà Bài 1 Mục 2 đã nêu: Linux bridge không có "database cấu hình" tách rời khỏi trạng thái kernel runtime, còn OVS thì có, và ovs-vsctl là cửa ngõ chính thức để đọc/ghi vào đó.
2. Vòng đời bridge và port: add-br, add-port, del-port, del-br
Bài 4 đã dùng add-br/add-port nhưng bỏ qua hai chi tiết quan trọng khi chạy lại lệnh nhiều lần — đúng tình huống thường gặp khi viết script automation. Man page chính thức của ovs-vsctl mô tả bốn lệnh nền tảng:
add-br: "Creates a new bridge named bridge. Initially the bridge will have no ports" (trừ chính internal port của nó, như Bài 4 đã thấy ở dòngPort br0trongovs-vsctl show). Chạyadd-br br0lần thứ hai khibr0đã tồn tại sẽ báo lỗi — trừ khi thêm cờ--may-exist, lúc đó lệnh coi như thành công (no-op) thay vì báo lỗi.add-port: "Creates on bridge a new port named port from the network device of the same name" — cùng cờ--may-existáp dụng tương tự.del-port/del-br: xóa port/bridge; man page ghi rõdel-br"Deletes bridge and all of its ports" — xóa bridge tự động kéo theo xóa hết port con, không cần xóa tay từng port trước. Cờ đối xứng ở đây là--if-exists: xóa một bridge/port không tồn tại sẽ không báo lỗi.
Cờ --may-exist/--if-exists không phải chi tiết vặt — nó là khác biệt giữa một script idempotent (chạy lại bao nhiêu lần cũng ra cùng kết quả, không crash giữa chừng) và một script chỉ chạy đúng được một lần. Toàn bộ automation OVS thực chiến (Neutron, OVN — sẽ gặp lại ở Module 4) đều dựa trên các cờ này để an toàn gọi lại cùng một lệnh.
add-port còn nhận cú pháp column[:key]=value ngay trên cùng dòng lệnh, ví dụ add-port br0 eth0 tag=10 — gán cột tag của Port ngay lúc tạo, tương đương chạy thêm một lệnh set port eth0 tag=10 riêng nhưng gộp vào cùng một lần ghi.
Phân biệt ba loại interface hay gặp nhất: Bài 4 gắn veth0-ovs vào br0 mà không khai báo type= gì — đó là vì system, loại interface mặc định (chuỗi rỗng là đồng nghĩa của system), được định nghĩa là "An ordinary network device, e.g. eth0 on Linux", tức bất kỳ netdev nào kernel đã biết trước — kể cả một đầu veth pair, vì bản thân veth cũng chỉ là một netdev bình thường do iproute2 tạo ra, OVS không quan tâm nó "thật" hay là ảo. Ngược lại, internal — loại mà chính port br0 bạn thấy ở Bài 4 đang dùng — là "A simulated network device that sends and receives traffic", do OVS tự tạo ra chứ không cần ip link add trước. Loại thứ ba, tap, là "A TUN/TAP device managed by Open vSwitch" — TUN/TAP vốn là cơ chế của kernel Linux tạo ra một network device ảo mà một tiến trình userspace (không phải kernel) đọc/ghi packet trực tiếp qua một file descriptor, và QEMU/KVM cũng dùng đúng cơ chế kernel này để đẩy packet vào NIC ảo bên trong VM. Cẩn thận một điểm dễ nhầm: type tap trong cột type của OVS lại hẹp hơn vậy — chỉ dành cho khi chính OVS tự tạo và quản lý TUN/TAP device đó (dùng nội bộ khi triển khai userspace datapath), không phải kiểu bạn nên gán cho tap device do QEMU tạo cho VM. Một tap device VM đã tồn tại sẵn trong kernel trước khi gắn vào OVS thì vẫn rơi vào type system mặc định, y hệt veth — chi tiết đầy đủ và lý do dễ hiểu nhầm chỗ này để dành cho Bài 7, khi bạn thực sự gắn một VM qua libvirt.
Ví dụ tạo một internal port mới cho mục đích quản lý — đúng mẫu tài liệu chính thức khuyến nghị (mục "Configuration Cookbook" trong man page ovs-vsctl):
sudo ovs-vsctl add-port br0 mgmt0 -- set interface mgmt0 type=internal
sudo ip addr add 192.168.0.123/24 dev mgmt0
sudo ip link set mgmt0 up
ip addr show mgmt0
# Output minh họa
4: mgmt0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN
link/ether 5a:1f:2c:9e:0b:44 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.123/24 scope global mgmt0
valid_lft forever preferred_lft forever
(Output minh họa — cấu trúc đúng theo lệnh thật, số liệu cụ thể tùy máy.) Điểm đáng chú ý: mgmt0 không nối tới bất kỳ NIC vật lý hay veth nào cả — nó chỉ tồn tại trong OVSDB rồi được ovs-vswitchd "hiện" ra thành một netdev bình thường mà ip addr/ip link nhìn thấy được, đúng như định nghĩa internal ở trên.
3. Gán VLAN cơ bản: access port bằng tag=, trunk port bằng trunks=
💡 Hình dung như thế này: một access port trên OVS hoạt động giống hệt access port bạn cấu hình trên switch vật lý — cắm một thiết bị vào, gán đúng một VLAN, thiết bị đó không cần biết gì về 802.1Q. Một trunk port cũng vậy — cho qua nhiều VLAN cùng lúc, đầu bên kia (thường là một switch khác, hoặc ở đây là một OVS bridge khác) tự đọc tag để phân biệt. Khác biệt duy nhất: trên switch vật lý bạn gõ lệnh vào console của switch đó; trên OVS bạn gõ đúng một câu
ovs-vsctl, và nó ghi thẳng vào OVSDB — không có khái niệm "SSH vào từng port".
Tài liệu schema chính thức (ovs-vswitchd.conf.db) định nghĩa hai cột cốt lõi trên bảng Port:
tag: "For an access port, the port's implicitly tagged VLAN... Must be empty if this is a trunk port."trunks: "For a trunk... port, the 802.1Q VLAN or VLANs that this port trunks; if it is empty, then the port trunks all VLANs."
Nói cách khác: đặt tag=10 mà không đặt trunks → OVS tự hiểu đây là access port VLAN 10. Đặt trunks=10,20 mà không đặt tag → OVS tự hiểu đây là trunk port cho VLAN 10 và 20. Có một cột thứ ba, vlan_mode, quyết định chính xác 5 hành vi khác nhau (access, trunk, native-tagged, native-untagged, dot1q-tunnel) — tài liệu schema nói rõ khi cột này để trống, "a default mode is selected as follows: if tag contains a value, the port is an access port... Otherwise, the port is a trunk port", đúng như hai gạch đầu dòng trên. Đào sâu cả 5 giá trị, đặc biệt native-tagged/dot1q-tunnel dễ hiểu nhầm nhất, là chủ đề riêng của Bài 8 — ở đây chỉ cần nắm hai trường hợp cơ bản. (Riêng dot1q-tunnel còn có thêm một cột con riêng tên cvlans — customer VLAN — để giới hạn VLAN khách hàng nào được phép đi qua tunnel; chi tiết cách dùng cũng để dành cho Bài 8.)
Lab: gán VLAN vào đúng lab Bài 4 đã dựng. Nếu br0/ns1/ns2 từ Bài 4 vẫn còn, tiếp tục ngay — nếu không, dựng lại theo Bài 4 Mục 4 trước khi chạy tiếp phần này.
# Gán ns1 vào VLAN 10, ns2 vào VLAN 20 — hai VLAN khác nhau
sudo ovs-vsctl set port veth0-ovs tag=10
sudo ovs-vsctl set port veth1-ovs tag=20
# Ping lại — lần này phải TIMEOUT, vì hai port đã ở hai VLAN khác nhau
sudo ip netns exec ns1 ping -c 3 -W 1 10.0.0.2
# Output minh họa
PING 10.0.0.2 (10.0.0.2) 56(84) bytes of data.
--- 10.0.0.2 ping statistics ---
3 packets transmitted, 0 received, 100% packet loss, time 2041ms
(Output minh họa — cấu trúc đúng theo lệnh thật, số liệu cụ thể tùy máy.) Không có gì kỳ diệu ở đây — đây đúng là hành vi VLAN isolation bạn đã quen trên switch vật lý, chỉ khác nơi cấu hình. Đưa cả hai về cùng VLAN để xác nhận lại việc thông là do VLAN, không phải do lỗi gì khác:
sudo ovs-vsctl set port veth1-ovs tag=10
sudo ip netns exec ns1 ping -c 3 10.0.0.2 # thông lại
Thêm một trunk port để thấy cú pháp trunks= — chưa cần traffic thật đi qua nó (bài tập Bài 8 sẽ làm việc đó với hai OVS host thật), chỉ cần thấy nó được OVSDB ghi nhận đúng:
sudo ovs-vsctl add-port br0 uplink0 -- set interface uplink0 type=internal -- set port uplink0 trunks=10,20
4. Hai cách nhìn cùng một dữ liệu: ovs-vsctl show và ovsdb-client dump
💡 Hình dung như thế này:
ovs-vsctl showgiống một dashboard tóm tắt — dễ đọc, đủ dùng hằng ngày, nhưng đã được định dạng lại cho con người.ovsdb-client dumpgiống chạy thẳng một câuSELECT * FROM tablevào database gốc — thấy đúng từng cột, từng UUID, không bị lược bớt hay format lại. Khi dashboard nói một thứ mà bạn nghi ngờ, việc tiếp theo luôn là mở database thô ra đối chiếu — thói quen y hệt lúc bạn nghi ngờ output GUI của một hệ thống quản lý switch vật lý và mở thẳng running-config ra xem.
ovsdb-client là công cụ nói chuyện trực tiếp với ovsdb-server qua giao thức OVSDB — khác ovs-vsctl, vốn là một lớp tiện ích được viết sẵn để làm những thao tác thường gặp (thêm bridge, gán VLAN...) mà không cần biết cú pháp OVSDB thô. Man page chính thức mô tả lệnh dump: "Connects to server, retrieves all of the data in database, and prints it on stdout as a series of tables. If table is specified, only that table is retrieved."
sudo ovsdb-client dump Open_vSwitch Port
# Output minh họa
Port table
_uuid bond_fake_iface external_ids fake_bridge interfaces name ... tag trunks
------------------------------------ ---------------- ------------- ----------- -------------------- ---------- --- ----- ------
7f2a1e00-.... false {} false [c3d4e5f6-....] "br0" ... [] []
9a1b2c3d-.... false {} false [d4e5f6a7-....] "uplink0" ... [] [10, 20]
b2c3d4e5-.... false {} false [e5f6a7b8-....] "veth0-ovs" ... 10 []
c3d4e5f6-.... false {} false [f6a7b8c9-....] "veth1-ovs" ... 10 []
(Output minh họa — cấu trúc đúng theo lệnh thật, số liệu cụ thể tùy máy; số cột thật nhiều hơn, đã lược bớt để dễ đọc.) So sánh ngay với ovs-vsctl show chạy cùng lúc: show sẽ vẽ lại đúng dữ liệu này dưới dạng cây thư mục dễ nhìn (Bridge br0 → Port veth0-ovs → Interface veth0-ovs, kèm dòng tag: 10 nếu có) — cùng một database, hai cách trình bày. Cột interfaces trong bảng Port ở trên chứa UUID trỏ sang bảng Interface; theo chiều ngược lại, cột ports trong bảng Bridge — schema định nghĩa kiểu dữ liệu "set of Ports", mô tả ngắn gọn "Ports included in the bridge" — chứa UUID trỏ tới từng Port thuộc bridge đó. Đây chính là quan hệ 1-n giữa Bridge và Port, và (thường là) 1-1 giữa Port và Interface. Ngoại lệ duy nhất của quan hệ 1-1 Port–Interface là bond — một port trỏ tới nhiều interface cùng lúc, chủ đề Bài 10.
5. Transaction: gộp nhiều lệnh vào một atomic write
💡 Hình dung như thế này: một transaction gộp nhiều lệnh
ovs-vsctlgiống một giao dịch chuyển khoản ngân hàng giữa hai tài khoản — trừ tiền tài khoản A và cộng tiền tài khoản B phải xảy ra cùng lúc, trọn vẹn hoặc không xảy ra gì cả. Ngân hàng không bao giờ để lại trạng thái "đã trừ tiền A nhưng chưa kịp cộng B" dù có sự cố giữa chừng. Nối nhiều lệnhovs-vsctlbằng--hoạt động theo đúng nguyên tắc đó — hoặc cả chuỗi lệnh cùng thành công, hoặc OVSDB coi như chưa có gì thay đổi.
Toàn bộ lệnh ovs-vsctl trong bốn mục trên đều chạy rời từng câu — mỗi câu là một transaction riêng ghi vào OVSDB. Nhưng ovs-vsctl cho phép gộp nhiều lệnh thành một transaction duy nhất bằng cách nối chúng lại với --. Man page chính thức xác nhận: các lệnh nối kiểu này "can be implemented as a single atomic transaction against the database" — nghĩa là hoặc toàn bộ các lệnh cùng thành công, hoặc nếu bất kỳ lệnh nào giữa chừng lỗi, không lệnh nào trong cả chuỗi được ghi xuống, database giữ nguyên trạng thái trước đó. Khác hẳn chạy rời từng lệnh bash nối bằng && hay xuống dòng — kiểu đó nếu lệnh thứ ba lỗi, hai lệnh đầu vẫn đã ghi xong, để lại một trạng thái nửa vời.
# Dựng lại toàn bộ br0 + 2 port VLAN 10 trong đúng MỘT transaction
sudo ovs-vsctl \
-- --may-exist add-br br0 \
-- --may-exist add-port br0 veth0-ovs \
-- set port veth0-ovs tag=10 \
-- --may-exist add-port br0 veth1-ovs \
-- set port veth1-ovs tag=10
Đây chính xác là kiểu lệnh mà automation thực chiến dùng — Neutron, OVN (Module 4) không gọi ovs-vsctl rời rạc hàng chục lần cho một topology, mà build đúng một transaction dài như trên để đảm bảo topology hoặc được tạo trọn vẹn, hoặc không tạo gì cả, không bao giờ để lại trạng thái dở dang giữa chừng nếu có lỗi.
Nếu chỉ nhớ được một điều sau bài này, hãy nhớ: ovs-vsctl không "cấu hình" OVS theo nghĩa ra lệnh trực tiếp như CLI một switch vật lý — nó luôn đang ghi giao dịch vào một database, và mọi công cụ khác (ovs-vsctl show, ovsdb-client dump, hay chính ovs-vswitchd lúc khởi động lại) chỉ đang đọc lại đúng database đó theo những cách khác nhau.
Bài tập cuối bài
- Dựng hai VLAN access port mới trên
br0(VLAN 30 và VLAN 40, dùng hai internal port đặt tên tùy ý) và một trunk port thứ hai chỉ trunk đúng VLAN 30 (không phải "trunk tất cả"). Dùngovsdb-client dump Open_vSwitch Portđể tự xác nhận cộttrunkschỉ chứa[30], không phải rỗng. - Viết một script bash tạo toàn bộ topology của bài tập 1 (bridge nếu chưa có, 2 access port, 1 trunk port) chỉ bằng một lệnh
ovs-vsctltransaction duy nhất nối bằng--, dùng--may-exist/--if-existsđể script chạy lại được nhiều lần không lỗi. Chạy thử script hai lần liên tiếp, xác nhận lần thứ hai không báo lỗi gì.
Nguồn tham khảo
add-br"Creates a new bridge... Initially the bridge will have no ports",add-port"Creates on bridge a new port...",del-br"Deletes bridge and all of its ports", cờ--may-exist/--if-exists, cú phápadd-port bridge port [column[:key]=value]..., và transaction "can be implemented as a single atomic transaction against the database" — ovs-vsctl(8), man7.org.- Ví dụ chính thức tạo internal port VLAN access kèm gán IP (
add-port br0 vlan10 tag=10 -- set Interface vlan10 type=internalrồiip addr add) trong mục "Configuration Cookbook" — nguồn cho ví dụmgmt0ở Mục 2 — cùng nguồnovs-vsctl(8)ở trên, bản đầy đủ tại ovs-vsctl(8), openvswitch.org. - Cột
tag: "For an access port, the port's implicitly tagged VLAN... Must be empty if this is a trunk port"; cộttrunks: "the 802.1Q VLAN or VLANs that this port trunks; if it is empty, then the port trunks all VLANs"; 5 giá trịvlan_mode(access,trunk,native-tagged,native-untagged,dot1q-tunnel) và quy tắc suy ra mặc định "if tag contains a value, the port is an access port... Otherwise, the port is a trunk port"; cộtcvlansriêng chodot1q-tunnel; cộtportscủa bảng Bridge ("set of Ports... Ports included in the bridge") — ovs-vswitchd.conf.db(5), openvswitch.org. - Định nghĩa interface type
system("An ordinary network device... The empty string is a synonym for system"),internal("A simulated network device that sends and receives traffic"),tap("A TUN/TAP device managed by Open vSwitch") — cùng nguồnovs-vswitchd.conf.db(5)ở trên. - Tap device của VM không nên gán type
taptrong OVS: "users should not configure KVM 'tap' devices as type 'tap' (use type 'system', the default, instead)" — Common Configuration Issues FAQ, docs.openvswitch.org. - Ví dụ
tag=9cho access port vàvlan_mode=native-tagged— VLANs FAQ, docs.openvswitch.org. ovsdb-client dump: "Connects to server, retrieves all of the data in database, and prints it on stdout as a series of tables" — ovsdb-client(1), openvswitch.org.conf.dblà nơiovsdb-serverlưu bền cấu hình OVSDB xuống đĩa; trên góiopenvswitch-switchcủa Debian/Ubuntu, đường dẫn mặc định là/etc/openvswitch/conf.db(khác với default/usr/local/etc/openvswitch/conf.dbkhi build từ nguồn không chỉ định prefix) — ovsdb-server(1), Debian manpages, ovsdb-server(1), openvswitch.org.
Tiếp theo: Module 1, Bài 6 — ovs-ofctl: đọc flow table, khái niệm OpenFlow cơ bản (match/action).