SDN trong thực tế: Xây dựng mạng lập trình được với OpenFlow, OVS và Ryu

Networking tutorial - IT technology blog
Networking tutorial - IT technology blog

Tại sao mô hình mạng truyền thống đang dần lỗi thời

Quản lý mạng trước đây thường là một chu trình tẻ nhạt: SSH vào 50 switch khác nhau, thao tác trên 50 giao diện CLI riêng biệt và nhập cấu hình VLAN hoặc ACL một cách thủ công. Cách tiếp cận tập trung vào phần cứng này là một nút thắt cổ chai lớn đối với các trung tâm dữ liệu hiện đại. Khi một ứng dụng mới cần các chính sách đặc thù, việc chờ đợi ba ngày để thay đổi cấu hình thủ công không còn là một lựa chọn khả thi.

Tôi nhớ một “cơn ác mộng” cụ thể hồi đầu sự nghiệp liên quan đến cấu hình “split-brain”. Một switch trong cụm có thiết lập MTU 1500 trong khi các switch khác là 9000. Việc tìm ra lỗi sai lệch duy nhất đó đã tiêu tốn bốn giờ ngừng hoạt động (downtime). Software-Defined Networking (SDN) khắc phục điều này bằng cách tách biệt bộ não (Control Plane) khỏi cơ bắp (Data Plane).

Kể từ khi chuyển sang hạ tầng dựa trên SDN cách đây sáu tháng, quy trình làm việc của tôi đã chuyển từ nhập lệnh CLI thủ công sang viết logic lập trình. Trong môi trường production, thiết lập này vẫn hoạt động cực kỳ ổn định. Nó xử lý việc cô lập tenant tự động và điều phối lưu lượng (traffic engineering) động – những điều vốn dĩ bất khả thi với phần cứng tiêu chuẩn.

Bộ công cụ: OpenFlow, OVS và Ryu

Để xây dựng một môi trường SDN hoạt động tốt, chúng ta cần ba thành phần phối hợp với nhau. Hiểu rõ sự tương tác giữa chúng là điều tối quan trọng trước khi chúng ta viết bất kỳ dòng mã nào.

1. Data Plane: Open vSwitch (OVS)

Open vSwitch là một switch ảo đa lớp được thiết kế để tự động hóa quy mô lớn. Trong các môi trường như Proxmox hoặc KVM, OVS nằm bên trong hypervisor. Nó đảm nhận nhiệm vụ nặng nề là chuyển tiếp các gói tin giữa các máy ảo. Không giống như Linux bridge cơ bản, OVS hỗ trợ OpenFlow, nghĩa là một bộ điều khiển (controller) bên ngoài có thể quản lý bảng chuyển tiếp (forwarding table) của nó từ xa.

2. Giao thức: OpenFlow

OpenFlow là ngôn ngữ mà controller sử dụng để giao tiếp với switch. Thay vì switch tự đưa ra quyết định độc lập dựa trên địa chỉ MAC, nó sẽ hỏi controller: “Tôi thấy một gói tin với các header này; tôi nên làm gì?”. Sau đó, controller sẽ đẩy một “flow entry” vào bộ nhớ của switch. Điều này hướng dẫn switch chuyển tiếp, hủy (drop) hoặc sửa đổi các gói tin tương tự trong tương lai.

3. Control Plane: Ryu Controller

Ryu là một framework SDN dựa trên Python. Tôi thích Ryu hơn các lựa chọn nặng nề khác như ONOS vì nó nhẹ nhàng và thân thiện với lập trình viên. Nếu bạn biết Python cơ bản, bạn có thể viết logic cho mạng. Nó cung cấp một API sạch sẽ giúp việc tạo các quy tắc firewall tùy chỉnh hoặc bộ cân bằng tải (load balancer) trở nên đơn giản.

Triển khai thực tế

Đối với thiết lập này, hãy sử dụng môi trường Ubuntu 22.04 LTS sạch. Chúng ta sẽ sử dụng Mininet để mô phỏng mạng. Đây là tiêu chuẩn công nghiệp để tạo mẫu (prototyping) các cấu trúc liên kết (topology) SDN mà không cần đến một tủ rack chứa các switch vật lý.

Bước 1: Cài đặt

Bắt đầu bằng cách cập nhật hệ thống và cài đặt các công cụ mạng cốt lõi.

sudo apt update
sudo apt install -y openvswitch-switch mininet python3-pip

Bây giờ, hãy cài đặt framework Ryu. Mặc dù sử dụng môi trường ảo (virtual environment) thường tốt hơn, nhưng việc cài đặt trực tiếp vẫn hoạt động tốt cho một máy lab chuyên dụng.

pip3 install ryu

Bước 2: Xây dựng một Learning Switch

Hãy viết một script Python để khiến OVS hoạt động như một Learning Switch thông minh. Logic này đảm bảo switch ghi nhớ địa chỉ MAC nào nằm ở cổng (port) nào. Lưu file này với tên my_switch.py.

from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import CONFIG_DISPATCHER, MAIN_DISPATCHER
from ryu.controller.handler import set_ev_cls
from ryu.ofproto import ofproto_v1_3
from ryu.lib.packet import packet
from ryu.lib.packet import ethernet

class MySwitch(app_manager.RyuApp):
    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]

    def __init__(self, *args, **kwargs):
        super(MySwitch, self).__init__(*args, **kwargs)
        self.mac_to_port = {}

    @set_ev_cls(ofp_event.EventOFPSwitchFeatures, CONFIG_DISPATCHER)
    def switch_features_handler(self, ev):
        datapath = ev.msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser

        # Cài đặt flow entry cho trường hợp table-miss
        match = parser.OFPMatch()
        actions = [parser.OFPActionOutput(ofproto.OFPP_CONTROLLER,
                                          ofproto.OFPCML_NO_BUFFER)]
        self.add_flow(datapath, 0, match, actions)

    def add_flow(self, datapath, priority, match, actions):
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        inst = [parser.OFPInstructionActions(ofproto.OFPIT_APPLY_ACTIONS, actions)]
        mod = parser.OFPFlowMod(datapath=datapath, priority=priority,
                                match=match, instructions=inst)
        datapath.send_msg(mod)

    @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER)
    def _packet_in_handler(self, ev):
        msg = ev.msg
        datapath = msg.datapath
        ofproto = datapath.ofproto
        parser = datapath.ofproto_parser
        in_port = msg.match['in_port']

        pkt = packet.Packet(msg.data)
        eth = pkt.get_protocols(ethernet.ethernet)[0]
        dst = eth.dst
        src = eth.src

        dpid = datapath.id
        self.mac_to_port.setdefault(dpid, {})

        # Học địa chỉ MAC để tránh flooding trong lần tiếp theo
        self.mac_to_port[dpid][src] = in_port

        if dst in self.mac_to_port[dpid]:
            out_port = self.mac_to_port[dpid][dst]
        else:
            out_port = ofproto.OFPP_FLOOD

        actions = [parser.OFPActionOutput(out_port)]

        if out_port != ofproto.OFPP_FLOOD:
            match = parser.OFPMatch(in_port=in_port, eth_dst=dst)
            self.add_flow(datapath, 1, match, actions)

        data = None
        if msg.buffer_id == ofproto.OFP_NO_BUFFER:
            data = msg.data

        out = parser.OFPPacketOut(datapath=datapath, buffer_id=msg.buffer_id,
                                  in_port=in_port, actions=actions, data=data)
        datapath.send_msg(out)

Bước 3: Kiểm tra mạng

Mở hai cửa sổ terminal. Ở cửa sổ đầu tiên, khởi chạy Ryu controller với script của bạn:

ryu-manager my_switch.py

Trong cửa sổ terminal thứ hai, sử dụng Mininet để tạo một cấu trúc mạng gồm một switch và ba host. Chúng ta sẽ trỏ nó đến Ryu controller cục bộ:

sudo mn --topo single,3 --mac --controller remote,ip=127.0.0.1,port=6633 --switch ovsk,protocols=OpenFlow13

Khi giao diện CLI của Mininet (mininet>) đã sẵn sàng, hãy chạy thử nghiệm kết nối:

mininet> pingall

Lần ping đầu tiên thường có độ trễ cao hơn (khoảng 50-100ms) do controller phải xử lý địa chỉ MAC chưa biết. Các lần ping sau đó sẽ giảm xuống mức dưới 1 mili giây. Điều này xảy ra vì OVS hiện đang xử lý lưu lượng trong kernel, bỏ qua hoàn toàn controller.

Tìm hiểu cơ chế bên dưới

Điểm hay nhất của SDN là khả năng quan sát (visibility). Để thấy chính xác cách switch đưa ra quyết định, hãy mở terminal thứ ba và chạy:

sudo ovs-ofctl -O OpenFlow13 dump-flows s1

Bạn sẽ thấy một quy tắc như priority=1,in_port=1,eth_dst=00:00:00:00:00:02 actions=output:2. Đây chính là chỉ dẫn chính xác mà Ryu controller đã gửi đến phần cứng switch. Không giống như các switch “hộp đen” truyền thống, bạn có thể thấy mọi quy tắc quyết định số phận của một gói tin.

Bài học triển khai thực tế

Chuyển từ môi trường lab sang một cụm production đòi hỏi sự tập trung vào tính sẵn sàng cao (high availability). Khi lần đầu triển khai OVS với Ryu trong môi trường staging, tôi nhận ra rằng controller là một điểm yếu duy nhất (single point of failure). Nếu controller gặp sự cố, các switch của bạn sẽ trở nên “ngu ngơ” và ngừng chuyển tiếp lưu lượng.

Để giải quyết vấn đề này, hiện tại tôi sử dụng một cụm Ryu gồm 3 node đứng sau một bộ cân bằng tải. Tôi cũng cấu hình OVS sử dụng nhiều IP của controller. Điều này đảm bảo rằng ngay cả khi cập nhật phần mềm, data plane vẫn hoạt động. Trong hơn sáu tháng vận hành, chúng tôi chưa gặp phải một sự cố toàn mạng nào do logic SDN gây ra.

Lời kết

Xây dựng SDN giúp chuyển đổi sự phức tạp từ các cuốn hướng dẫn phần cứng dày cộm sang mã Python có thể quản lý được. Sự thay đổi này cho phép bạn tạo mẫu các tính năng như firewall tùy chỉnh hoặc phân tách mạng tự động (automated network slicing) trong vài giờ thay vì vài tuần. Mặc dù bạn phải học logic dựa trên luồng (flow-based logic), nhưng sự linh hoạt mà nó mang lại hoàn toàn xứng đáng. Nếu bạn muốn tự động hóa hạ tầng của mình, ba công cụ này là nơi tốt nhất để bắt đầu.

Share: