SiamCafe · Blog
Multus CNI Testing Strategy QA
บทความ

Multus CNI Testing Strategy QA

เผยแพร่ 28 พฤษภาคม 2569

Multus CNI Testing Strategy QA คืออะไร

Multus CNI Testing Strategy QA

Multus CNI เป็น meta-plugin สำหรับ Kubernetes ที่ช่วยให้ pods มี network interfaces หลายตัวพร้อมกัน นอกเหนือจาก default cluster network ปกติ รองรับ SR-IOV, Macvlan, IPVLAN, Bridge และ CNI plugins อื่นๆ Testing Strategy QA คือกลยุทธ์การทดสอบเพื่อรับรองคุณภาพของระบบ ครอบคลุม unit tests, integration tests, end-to-end tests และ performance tests การรวมสองแนวคิดนี้ช่วยให้ multi-network Kubernetes deployments มีความน่าเชื่อถือสูง ตรวจจับปัญหา networking ก่อน production

Multus CNI Architecture

# multus_arch.py — Multus CNI architecture overview
import json

class MultusArchitecture:
    CONCEPTS = {
        "multus": {
            "name": "Multus CNI",
            "description": "Meta-plugin ที่ chain CNI plugins หลายตัว — pod ได้หลาย network interfaces",
            "use_cases": "Telco/5G (user plane + control plane), NFV, storage networks, monitoring networks",
        },
        "net_attach_def": {
            "name": "NetworkAttachmentDefinition (NAD)",
            "description": "CRD ที่กำหนด additional networks — ระบุ CNI plugin + config",
            "example": "NAD สำหรับ Macvlan network ที่ connect กับ physical NIC",
        },
        "default_network": {
            "name": "Default Network (eth0)",
            "description": "Cluster network ปกติ (Calico, Flannel, Cilium) — ทุก pod มี",
        },
        "additional_networks": {
            "name": "Additional Networks (net1, net2...)",
            "description": "Networks เพิ่มเติมจาก Multus — SR-IOV, Macvlan, Bridge",
        },
    }

    NAD_EXAMPLE = """
# NetworkAttachmentDefinition — Macvlan network
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: macvlan-net
  namespace: default
spec:
  config: |
    {
      "cniVersion": "0.3.1",
      "type": "macvlan",
      "master": "eth0",
      "mode": "bridge",
      "ipam": {
        "type": "host-local",
        "subnet": "192.168.1.0/24",
        "rangeStart": "192.168.1.200",
        "rangeEnd": "192.168.1.250",
        "gateway": "192.168.1.1"
      }
    }
---
# Pod with multiple networks
apiVersion: v1
kind: Pod
metadata:
  name: multi-net-pod
  annotations:
    k8s.v1.cni.cncf.io/networks: macvlan-net
spec:
  containers:
    - name: app
      image: nginx
      # Pod จะมี: eth0 (cluster) + net1 (macvlan)
"""

    def show_concepts(self):
        print("=== Multus CNI Concepts ===\n")
        for key, concept in self.CONCEPTS.items():
            print(f"[{concept['name']}]")
            print(f"  {concept['description']}")
            print()

    def show_nad(self):
        print("=== NAD Example ===")
        print(self.NAD_EXAMPLE[:500])

arch = MultusArchitecture()
arch.show_concepts()
arch.show_nad()

Testing Strategy Overview

# testing_strategy.py — Testing strategy for Multus CNI
import json

class TestingStrategy:
    PYRAMID = {
        "unit": {
            "name": "Unit Tests",
            "percentage": "40%",
            "scope": "CNI config validation, IPAM logic, NAD schema",
            "tools": "pytest, Go test (สำหรับ CNI plugins)",
            "speed": "เร็วมาก (< 1 วินาที)",
        },
        "integration": {
            "name": "Integration Tests",
            "percentage": "30%",
            "scope": "CNI plugin chain, network connectivity ระหว่าง pods, IPAM allocation",
            "tools": "Kind/Minikube + pytest, Go integration tests",
            "speed": "ปานกลาง (10-60 วินาที)",
        },
        "e2e": {
            "name": "End-to-End Tests",
            "percentage": "20%",
            "scope": "Full cluster tests — pod creation, multi-network connectivity, failover",
            "tools": "Ginkgo + Gomega, Robot Framework, custom scripts",
            "speed": "ช้า (1-10 นาที)",
        },
        "performance": {
            "name": "Performance Tests",
            "percentage": "10%",
            "scope": "Throughput, latency, pod startup time, network bandwidth",
            "tools": "iperf3, netperf, custom benchmarks",
            "speed": "ช้ามาก (10-60 นาที)",
        },
    }

    def show_pyramid(self):
        print("=== Test Pyramid ===\n")
        for key, level in self.PYRAMID.items():
            print(f"[{level['name']}] ({level['percentage']})")
            print(f"  Scope: {level['scope']}")
            print(f"  Tools: {level['tools']}")
            print(f"  Speed: {level['speed']}")
            print()

strategy = TestingStrategy()
strategy.show_pyramid()

Test Implementation

Multus CNI Testing Strategy QA
# tests.py — Multus CNI test implementations
import json

class MultusTests:
    UNIT_TESTS = """
# test_nad_validation.py — Unit tests for NAD config
import pytest
import json

class TestNADValidation:
    def test_valid_macvlan_config(self):
        config = {
            "cniVersion": "0.3.1",
            "type": "macvlan",
            "master": "eth0",
            "mode": "bridge",
            "ipam": {
                "type": "host-local",
                "subnet": "192.168.1.0/24",
            }
        }
        assert validate_cni_config(config) == True
    
    def test_missing_cni_version(self):
        config = {"type": "macvlan", "master": "eth0"}
        with pytest.raises(ValueError, match="cniVersion required"):
            validate_cni_config(config)
    
    def test_invalid_subnet(self):
        config = {
            "cniVersion": "0.3.1",
            "type": "macvlan",
            "ipam": {"type": "host-local", "subnet": "invalid"}
        }
        with pytest.raises(ValueError, match="Invalid subnet"):
            validate_cni_config(config)
    
    def test_ipam_range_overlap(self):
        config = {
            "cniVersion": "0.3.1",
            "type": "macvlan",
            "ipam": {
                "type": "host-local",
                "subnet": "192.168.1.0/24",
                "rangeStart": "192.168.1.250",
                "rangeEnd": "192.168.1.200",  # End before Start
            }
        }
        with pytest.raises(ValueError, match="Invalid range"):
            validate_cni_config(config)

def validate_cni_config(config):
    if "cniVersion" not in config:
        raise ValueError("cniVersion required")
    # ... validation logic
    return True
"""

    INTEGRATION_TESTS = """
# test_multus_integration.py — Integration tests
import subprocess
import pytest
import time
import json

class TestMultusIntegration:
    @pytest.fixture(autouse=True)
    def setup(self):
        # Create test namespace
        subprocess.run(["kubectl", "create", "ns", "multus-test"], check=False)
        yield
        subprocess.run(["kubectl", "delete", "ns", "multus-test", "--grace-period=0", "--force"])
    
    def test_pod_gets_additional_interface(self):
        '''Pod with Multus annotation gets extra network interface'''
        # Create NAD
        subprocess.run(["kubectl", "apply", "-f", "test-nad.yaml", "-n", "multus-test"], check=True)
        
        # Create pod with annotation
        subprocess.run(["kubectl", "apply", "-f", "test-pod.yaml", "-n", "multus-test"], check=True)
        
        # Wait for pod ready
        subprocess.run(["kubectl", "wait", "--for=condition=Ready", 
                        "pod/test-pod", "-n", "multus-test", "--timeout=60s"], check=True)
        
        # Verify interfaces
        result = subprocess.run(
            ["kubectl", "exec", "-n", "multus-test", "test-pod", "--", "ip", "addr"],
            capture_output=True, text=True
        )
        
        assert "eth0" in result.stdout  # Default network
        assert "net1" in result.stdout  # Multus additional network
    
    def test_cross_pod_connectivity(self):
        '''Two pods on same additional network can communicate'''
        # Create 2 pods on same macvlan network
        subprocess.run(["kubectl", "apply", "-f", "test-2pods.yaml", "-n", "multus-test"], check=True)
        time.sleep(10)
        
        # Get pod2 IP on net1
        result = subprocess.run(
            ["kubectl", "exec", "-n", "multus-test", "pod-a", "--",
             "ping", "-c", "3", "-W", "2", "192.168.1.201"],
            capture_output=True, text=True
        )
        assert result.returncode == 0
    
    def test_network_isolation(self):
        '''Pods on different additional networks cannot communicate'''
        # pod-a on macvlan-net-a, pod-b on macvlan-net-b
        # ping should fail
        result = subprocess.run(
            ["kubectl", "exec", "-n", "multus-test", "pod-a", "--",
             "ping", "-c", "2", "-W", "2", "10.0.2.100"],
            capture_output=True, text=True
        )
        assert result.returncode != 0  # Should fail — isolated networks
"""

    def show_unit(self):
        print("=== Unit Tests ===")
        print(self.UNIT_TESTS[:600])

    def show_integration(self):
        print(f"\n=== Integration Tests ===")
        print(self.INTEGRATION_TESTS[:600])

tests = MultusTests()
tests.show_unit()
tests.show_integration()

E2E & Performance Tests

# e2e_perf.py — E2E and performance tests
import json
import random

class E2EPerformanceTests:
    E2E_SCENARIOS = {
        "pod_lifecycle": {
            "name": "Pod Lifecycle with Multi-Network",
            "steps": ["Create NAD", "Create Pod", "Verify interfaces", "Delete Pod", "Verify IP released"],
            "timeout": "120s",
        },
        "rolling_update": {
            "name": "Rolling Update Preserves Network",
            "steps": ["Deploy with Multus", "Rolling update image", "Verify no connectivity loss"],
            "timeout": "300s",
        },
        "node_failure": {
            "name": "Node Failure Recovery",
            "steps": ["Pod on node A", "Drain node A", "Pod rescheduled to B", "Verify new interfaces"],
            "timeout": "600s",
        },
        "scale_test": {
            "name": "Scale Test — 100 Pods",
            "steps": ["Create 100 pods with Multus", "Verify all get IPs", "Verify connectivity", "Cleanup"],
            "timeout": "600s",
        },
    }

    PERF_BENCHMARKS = """
# perf_test.py — Network performance benchmarks
import subprocess
import json
import time

class NetworkPerfTest:
    def __init__(self, namespace="perf-test"):
        self.ns = namespace
    
    def bandwidth_test(self, interface="net1"):
        '''iperf3 bandwidth test on additional network'''
        # Start iperf3 server pod
        subprocess.run(["kubectl", "exec", "-n", self.ns, "iperf-server",
                       "--", "iperf3", "-s", "-D", "--bind", "0.0.0.0"])
        
        # Run client
        result = subprocess.run(
            ["kubectl", "exec", "-n", self.ns, "iperf-client", "--",
             "iperf3", "-c", "SERVER_IP", "-t", "30", "-J"],
            capture_output=True, text=True
        )
        
        data = json.loads(result.stdout)
        return {
            "bandwidth_gbps": data["end"]["sum_sent"]["bits_per_second"] / 1e9,
            "retransmits": data["end"]["sum_sent"]["retransmits"],
        }
    
    def latency_test(self, target_ip, count=100):
        '''Ping latency test'''
        result = subprocess.run(
            ["kubectl", "exec", "-n", self.ns, "ping-pod", "--",
             "ping", "-c", str(count), "-i", "0.1", target_ip],
            capture_output=True, text=True
        )
        # Parse avg latency
        return {"avg_latency_ms": 0.5}  # parsed from output
    
    def pod_startup_time(self, count=10):
        '''Measure pod startup time with Multus'''
        times = []
        for i in range(count):
            start = time.time()
            subprocess.run(["kubectl", "run", f"timing-{i}", 
                          "--image=alpine", "--restart=Never",
                          f"--overrides={{...multus annotation...}}"], check=True)
            subprocess.run(["kubectl", "wait", f"--for=condition=Ready",
                          f"pod/timing-{i}", "--timeout=120s"], check=True)
            times.append(time.time() - start)
        
        return {
            "avg_startup_s": sum(times) / len(times),
            "p99_startup_s": sorted(times)[int(len(times) * 0.99)],
        }
"""

    def show_scenarios(self):
        print("=== E2E Test Scenarios ===\n")
        for key, scenario in self.E2E_SCENARIOS.items():
            print(f"[{scenario['name']}] Timeout: {scenario['timeout']}")
            for step in scenario["steps"][:3]:
                print(f"  → {step}")
            print()

    def show_benchmarks(self):
        print("=== Performance Benchmarks ===")
        print(self.PERF_BENCHMARKS[:500])

    def sample_results(self):
        print(f"\n=== Sample Performance Results ===")
        print(f"  Default network (eth0):")
        print(f"    Bandwidth: {random.uniform(8, 10):.1f} Gbps")
        print(f"    Latency: {random.uniform(0.1, 0.5):.2f} ms")
        print(f"  Multus macvlan (net1):")
        print(f"    Bandwidth: {random.uniform(7, 9.5):.1f} Gbps")
        print(f"    Latency: {random.uniform(0.05, 0.3):.2f} ms")
        print(f"  Multus SR-IOV (net1):")
        print(f"    Bandwidth: {random.uniform(9, 10):.1f} Gbps")
        print(f"    Latency: {random.uniform(0.02, 0.1):.2f} ms")
        print(f"  Pod startup (with Multus): {random.uniform(3, 8):.1f}s avg")

e2e = E2EPerformanceTests()
e2e.show_scenarios()
e2e.sample_results()

CI/CD Integration

# cicd.py — CI/CD for Multus CNI testing
import json

class CICDPipeline:
    GITHUB_ACTIONS = """
# .github/workflows/multus-tests.yml
name: Multus CNI Tests
on:
  push:
    branches: [main]
    paths: ['network/**', 'tests/**']
  pull_request:
    paths: ['network/**', 'tests/**']

jobs:
  unit-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install pytest pyyaml jsonschema
      - run: pytest tests/unit/ -v

  integration-tests:
    needs: unit-tests
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Create Kind cluster with Multus
        run: |
          kind create cluster --config tests/kind-config.yaml
          kubectl apply -f https://raw.githubusercontent.com/k8snetworkplumbingwg/multus-cni/master/deployments/multus-daemonset.yml
          kubectl wait --for=condition=Ready -n kube-system ds/kube-multus-ds --timeout=120s
      
      - name: Run integration tests
        run: pytest tests/integration/ -v --timeout=300
      
      - name: Cleanup
        if: always()
        run: kind delete cluster

  e2e-tests:
    needs: integration-tests
    runs-on: self-hosted  # Needs real hardware for SR-IOV
    steps:
      - uses: actions/checkout@v4
      - name: Run E2E tests
        run: pytest tests/e2e/ -v --timeout=600
"""

    def show_pipeline(self):
        print("=== CI/CD Pipeline ===")
        print(self.GITHUB_ACTIONS[:600])

    def test_matrix(self):
        print(f"\n=== Test Matrix ===")
        matrix = [
            {"cni": "Macvlan", "unit": "Pass", "integration": "Pass", "e2e": "Pass"},
            {"cni": "Bridge", "unit": "Pass", "integration": "Pass", "e2e": "Pass"},
            {"cni": "IPVLAN", "unit": "Pass", "integration": "Pass", "e2e": "Skip"},
            {"cni": "SR-IOV", "unit": "Pass", "integration": "Skip", "e2e": "Pass"},
        ]
        print(f"  {'CNI Plugin':<12} {'Unit':>6} {'Integration':>12} {'E2E':>6}")
        for m in matrix:
            print(f"  {m['cni']:<12} {m['unit']:>6} {m['integration']:>12} {m['e2e']:>6}")

cicd = CICDPipeline()
cicd.show_pipeline()
cicd.test_matrix()

FAQ - คำถามที่พบบ่อย

Q: Multus CNI จำเป็นเมื่อไหร่?

A: จำเป็นเมื่อ pod ต้องการ network interfaces มากกว่า 1: Telco/5G: แยก user plane กับ control plane, Storage network: แยก data path กับ management, NFV: multiple VLANs per pod, High-performance: SR-IOV สำหรับ line-rate networking ถ้า pod ใช้แค่ 1 network → ไม่ต้องใช้ Multus

Q: Test Multus ยากไหม?

A: Unit tests: ง่าย — validate config, schema Integration: ปานกลาง — ต้อง Kubernetes cluster + Multus installed E2E: ยาก — SR-IOV ต้อง hardware จริง (NIC ที่รองรับ) ใช้ Kind cluster สำหรับ Macvlan/Bridge tests (ไม่ต้อง hardware พิเศษ) SR-IOV tests: ต้อง self-hosted runners + physical NICs

Q: Performance ของ Multus เป็นอย่างไร?

A: Macvlan: ใกล้เคียง bare metal (~95% throughput), latency ต่ำ Bridge: ดี แต่มี overhead จาก software bridge SR-IOV: ดีที่สุด — bypass kernel, near line-rate (99%+) Pod startup: เพิ่ม 1-3 วินาที ต่อ additional interface Multus overhead เอง: น้อยมาก (< 100ms) — ส่วนใหญ่ขึ้นกับ CNI plugin ที่ใช้

Q: Troubleshoot Multus ยังไง?

A: 1) kubectl describe pod — ดู events สำหรับ network errors 2) kubectl logs -n kube-system ds/kube-multus-ds — ดู Multus logs 3) kubectl exec pod -- ip addr — ตรวจสอบ interfaces 4) kubectl get net-attach-def — ตรวจสอบ NAD configs 5) journalctl -u kubelet — ดู CNI plugin errors ที่ node level