עבודה עם DPDK

בדף הזה מוסבר איך להשתמש בערכה לפיתוח מישור הנתונים (DPDK) במכונות U4C של Compute Engine.

מידע על DPDK over AF_XDP

Data Plane Development Kit (DPDK) היא מסגרת לפיתוח אפליקציות עתירות ביצועים שדורשות עיבוד מהיר של מנות נתונים, זמן אחזור נמוך וביצועים עקביים. ‫DPDK עוקף את מחסנית הרשת של ליבת לינוקס ופועל ישירות במרחב המשתמש. אפשר להריץ DPDK במופעי U4C באמצעות ארכיטקטורת AF_XDP.

‫DPDK מספק מנהל התקן (PMD) של מצב סקר AF_XDP, שהוא מכשיר וירטואלי (vdev) שמאפשר לאפליקציות DPDK לפעול על AF_XDP במצב העתקה או במצב העתקה אפס. מידע נוסף זמין במאמר בנושא AF_XDP Poll Mode Driver במסמכי התיעוד של DPDK. בניגוד לפריסות רגילות ב-Compute Engine, ב-DPDK over AF_XDP במופעי U4C לא צריך להגדיר VFIO,‏ UIO או dpdk-devbind.py.

השימוש ב-DPDK עם פתרון ULL כולל תמיכה בהכוונת תנועה. אפשר לעקוף את הגיבוב (hashing) של קנה מידה של קבלת נתונים (RSS) שמוגדר כברירת מחדל על ידי הפניית זרמי תנועה ספציפיים ישירות אל תור קבלה (RX) ייעודי. פתרון ULL תומך בהכוונת זרימה של 3 טאפלים (פרוטוקול, כתובת IP של היעד ויציאת היעד) לתעבורת נתונים של ULL unicast ו-multicast.

לפני שמתחילים

כדי לעבוד עם DPDK במכונות Compute Engine מסוג U4C, צריך לעמוד בדרישות הבאות.

יצירת מכונה של U4C

אם עדיין לא עשיתם זאת, צרו מכונת Bare Metal של U4C. איך יוצרים מכונות Compute Engine עם זמן אחזור נמוך במיוחד

התחברות למופע באמצעות SSH

אם עוד לא עשיתם זאת, מתחברים למופע באמצעות SSH.

מעבר למשתמש Root

הפקודות והסקריפטים בהליכים הבאים משנים הגדרות ברמת המערכת, פרמטרים של ליבת המערכת וממשקי רשת. כדי להריץ אותן בהצלחה, צריך להריץ אותן כמשתמש root. אפשר לעבור אל מעטפת root על ידי הפעלת הפקודה sudo su, או להוסיף את הפקודה sudo לפני הפעלת פקודות לפי הצורך.

התקנת DPDK במופע U4C

כדי להתקין את DPDK במופע U4C, פועלים לפי השלבים הבאים:

  1. מגדירים את יחסי התלות להתקנת DPDK:

    apt-get update && apt-get upgrade -yq
    apt-get install -yq build-essential ninja-build python3-pip \
        linux-headers-$(uname -r) pkg-config libnuma-dev
    pip install pyelftools meson
    
  2. מתקינים את DPDK.

    wget https://fast.dpdk.org/rel/dpdk-VERSION.tar.xz
    tar xvf dpdk-VERSION.tar.xz
    cd dpdk-VERSION

    מחליפים את VERSION בגרסת ה-DPDK שרוצים להתקין, למשל 26.07. במקרה הצורך, אפשר לעיין בדף הורדה של DPDK.

  3. כדי ליצור DPDK עם הדוגמאות:

    meson setup -Dexamples=all build
    ninja -C build install; ldconfig
    

הגדרת ממשקי רשת ל-AF_XDP

כדי להשתמש ב-AF_XDP ב-Google Virtual NIC ‏ (gVNIC), צריך לשנות את תכונות ברירת המחדל של מנהל ההתקן כדי להכין את ממשק הרשת.

אפשר לבצע את השלבים האלה באופן ידני או להשתמש בסקריפט אוטומטי להגדרה. בוחרים באחת מהכרטיסיות הבאות:

גלילה ידנית

פועלים לפי השלבים האלה לכל ממשק רשת שרוצים להגדיר.

  1. הפחתת מספר התורים של RX ו-TX: כברירת מחדל, gVNIC משתמש במספר המקסימלי הנתמך של תורי RX ו-TX, אבל צריך להפחית את המספר הזה בחצי כדי לוודא שיש מספיק תורי TX לתנועת ליבת רגילה.

    ethtool -L NIC_NAME rx NUM_SOCKETS \
    tx NUM_SOCKETS

    מחליפים את מה שכתוב בשדות הבאים:

    • NIC_NAME: השם של ממשק הרשת במערכת ההפעלה, למשל eth1.
    • NUM_SOCKETS: מספר שקעי AF_XDP שצריך להגדיר. הערך הזה לא יכול להיות גדול יותר ממחצית הערך של התורים המקסימליים לממשק. במקרים של מופעי U4C, בדרך כלל מדובר ב-8 (חצי מ-16 התורים שמוגדרים כברירת מחדל). כדי לבדוק את מספר התורים המקסימלי, מריצים את הפקודה ethtool -l NIC_NAME.
  2. השבתה של GRO ו-LRO בחומרה: מכיוון ש-gVNIC לא תומך ב-XDP מרובה מאגרים, צריך להשבית את Large Receive Offload ‏ (LRO) ואת Generic Receive Offload ‏ (GRO) בחומרה:

    ethtool -K NIC_NAME rx-gro-hw off
    ethtool -K NIC_NAME lro off
  3. הקטנת האורך של מאגר הנתונים הזמני של RX: כברירת מחדל, מנהלי התקנים חדשים יותר מפרסמים מאגרי נתונים זמניים של 4KB ‏ (4,096 בייט) בממשק הרשת עבור RX, אבל XDP דורש אורך של מאגר נתונים זמני של 2048:

    ethtool -G NIC_NAME rx-buf-len 2048

Script

לחלופין, אפשר להריץ את סקריפט Bash הבא לכל ממשק רשת שרוצים להגדיר. הסקריפט מכין אוטומטית ממשק רשת נתון ל-XDP על ידי הקטנת מספר התורים, השבתת העברות והתאמת אורך מאגר ה-RX:

#!/bin/bash
# Usage example: NUM_SOCKETS=8 prep_xdp.sh eth0

DEV=$1
NUM_SOCKETS=${NUM_SOCKETS=1}

# Reduce RX/TX queue counts to the number of AF_XDP sockets
ethtool -L $DEV rx $NUM_SOCKETS tx $NUM_SOCKETS

# Disable LRO/HW-gro
OFFLOAD=$(ethtool -k $DEV | \
grep "rx-gro-hw\|large-receive-offload" | \
grep -v fixed | cut -d ":" -f 1)
ethtool -K $DEV ${OFFLOAD} off

# Reduce RX buffer length to 2048
ethtool -G $DEV rx-buf-len 2048

הפעלת אפליקציית DPDK

כדי להשתמש ב-AF_XDP PMD, צריך לכלול את הדגל --vdev בארגומנטים של שכבת הפשטת הסביבה (EAL) של אפליקציית DPDK.

פקודת הדוגמה הבאה כוללת כמה פרמטרים מרכזיים. מידע מפורט על ההגדרה והפרמטרים זמין במאמר AF_XDP Poll Mode Driver במסמכי ה-DPDK.

DPDK_APPLICATION -a PCIE_BDF \
  --vdev=net_af_xdp,iface=NIC_NAME,queue_count=NUM_SOCKETS,start_queue=START_QUEUE,xdp_prog=XDP_PROG \
  -- APPLICATION_ARGS

מחליפים את מה שכתוב בשדות הבאים:

  • DPDK_APPLICATION: קובץ ה-DPDK הבינארי של האפליקציה להרצה.
  • PCIE_BDF: כתובת ה-PCI של ממשק הרשת. אפשר למצוא את הערך הזה על ידי הפעלת ethtool -i NIC_NAME ובדיקת הערך של bus-info, כמו 0000:00:04.0.
  • NIC_NAME: השם של ממשק הרשת במערכת ההפעלה, למשל eth1.
  • NUM_SOCKETS: מספר שקעי AF_XDP שייפתחו. כל שקע מצורף לצמד תורים יחיד. הערך הזה צריך להיות זהה למספר התורים שהגדרתם בממשק.
  • START_QUEUE: אינדקס התור ההתחלתי עבור שקעי AF_XDP.
  • XDP_PROG: תוכנית XDP מותאמת אישית להרצה על מנות שהתקבלו. אם לא מציינים תוכנית, DPDK משתמש בתוכנית XDP שמוגדרת כברירת מחדל ומסופקת על ידי libxdp.
  • APPLICATION_ARGS: ארגומנטים ספציפיים לאפליקציית DPDK.

שימוש בתכונות של מנהל ההתקן

בקטע הזה מפורט מידע על השימוש בהפניית תנועה ובחותמות זמן של RX.

הכוונה של רצף פעולות

אפשר להשתמש ב-XDP כדי להפנות מנות נתונים של אפליקציות לקבוצת משנה ספציפית של תורים, ולהשאיר את התורים הנותרים לתנועה של ליבת מערכת ההפעלה. בקטעים הבאים מתוארות שתי גישות שבהן אפשר להשתמש לתכנות של כללי זרימה.

הפניית תנועה לפני תחילת התוכנית (מומלץ)

מאחר שתכנות של כללי זרימה תוך כדי תנועה באמצעות קריאות ל-ioctl לא נתמך על ידי AF_XDP PMD ודורש שינוי של אפליקציית DPDK, אנחנו ממליצים לתכנת מראש את כללי הזרימה באמצעות ethtool לפני הפעלת האפליקציה.

לדוגמה, כדי לתכנת כלל זרימה שמנתב תנועת UDP ב-IPv4 לתור 0:

ethtool -N NIC_NAME flow-type udp4 \
  dst-ip DST_IP dst-port DST_PORT action 0 loc 0

מחליפים את מה שכתוב בשדות הבאים:

  • NIC_NAME: השם של ממשק הרשת במערכת ההפעלה, למשל eth1.
  • DST_IP: כתובת ה-IP של היעד של תעבורת הנתונים שרוצים לנתב.
  • DST_PORT: יציאת היעד של תעבורת הנתונים להפניה.

שינוי כללי התוכנית תוך כדי תנועה

ה-PMD של AF_XDP לא תומך בתכנות זרימה תוך כדי תנועה. כדי להשתמש בשיטה הזו, צריך לשנות את אפליקציית DPDK כדי לשלוח שיחות ethtool ioctl באופן ידני.

מומלץ להימנע מהגישה הזו אלא אם האפליקציה מטפלת במספר גדול של חיבורים זמניים. gVNIC תומך בעד 20,000 כללים להפניית זרימה של 3 טאפלים. אם אתם צריכים פחות כללים ואתם יודעים מראש מהן כתובות ה-IP והיציאות של היעד, עדיף לתכנת מראש את הכללים.

אם אתם צריכים לתכנת כללים באופן דינמי, תוכלו להיעזר בדוגמה הבאה של קוד C:

הרחבה לצפייה בקוד לדוגמה בשפת C

struct flow_rule_info {
  uint32_t src_ip;
  uint32_t dst_ip;
  uint16_t src_port;
  uint16_t dst_port;
  uint32_t target_queue;
  uint32_t rule_id;
}

int add_flow_rule(const char *ifname,
                  struct flow_rule_info *rule_info,
                  bool is_5tuple) {
  struct ethtool_rxnfc cmd;
  struct ifreq ifr;
  int fd;

  fd = socket(AF_INET, SOCK_DGRAM, 0);
  if (fd < 0) {
    fprintf(stderr, "Failed to open socket: %s", strerror(fd));
    return -1;
  }

  memset(&cmd, 0, sizeof(cmd));
  memset(&ifr, 0, sizeof(ifr));

  cmd.cmd = ETHTOOL_SRXCLSRLINS;
  cmd.fs.flow_type = UDP_V4_FLOW;

  cmd.fs.h_u.udp_ip4_spec.ip4dst = rule_info->dst_ip;
  cmd.fs.h_u.udp_ip4_spec.pdst = htons(rule_info->dst_port);
  cmd.fs.m_u.udp_ip4_spec.ip4dst = 0xFFFFFFFF;
  cmd.fs.m_u.udp_ip4_spec.pdst = 0xFFFF;

  if (is_5tuple) {
    cmd.fs.h_u.udp_ip4_spec.ip4src = rule_info->src_ip;
    cmd.fs.h_u.udp_ip4_spec.psrc = htons(rule_info->src_port);
    cmd.fs.m_u.udp_ip4_spec.ip4src = 0xFFFFFFFF;
    cmd.fs.m_u.udp_ip4_spec.psrc = 0xFFFF;
  }

  cmd.fs.ring_cookie = rule_info->target_queue;
  cmd.fs.location = rule_info->rule_id;

  strncpy(ifr.ifr_name, ifname, IFNAMSIZ - 1);
  ifr.ifr_data = (void *)&cmd;

  int ret = ioctl(fd, SIOCETHTOOL, &ifr);
  if (ret)
    fprintf(stderr, "Failed to send ioctl: %s\n", strerror(errno));
  close(fd);

  return ret;
}

int try_add_xdp_flow_rule(int port, struct flow_rule_info *rule_info) {
  char dev_name[RTE_ETH_NAME_MAX_LEN];
  char *af_xdp_driver = "net_af_xdp";
  struct rte_eth_dev_info dev_info;
  int err;

  if (!rte_eth_dev_is_valid_port(port)) return -1;

  err = rte_eth_dev_info_get(port, &dev_info);
  if (err) {
    fprintf(stderr, "Error getting info for port %d: %s\n", port,
            strerror(err));
    return retval;
  }

  if (strncmp(dev_info.driver_name, af_xdp_driver,
              strlen(af_xdp_driver)) != 0) {
    fprintf(stderr, "Not an AF_XDP vdev!\n");
    return -EINVAL;
  }

  err = rte_eth_dev_get_name_by_port(port, dev_name);
  if (retval) {
    fprintf(stderr, "Failed to get dev_name: %s\n", strerror(err));
    return retval;
  }

  /* replace ens4 with correct ifname; must be passed in as application argument */
  err = add_flow_rule("ens4", rule_info));
  if (err)
    fprintf("Failed to add flow rule: %s\n", strerror(err));
  return err;
}

/* From somewhere in the application: */
int application_func(...) {
  ...

  struct flow_rule_info *rule_info {
    .src_ip = 0x0a000001,
    .dst_ip = 0x0a000002,
    .src_port = 0x1110,
    .uint16_t dst_port = 0x1011,
    .uint32_t target_queue = 0,
    .uint32_t rule_id = 1,
  };
  try_add_xdp_flow_rule(xdp_port_id, &rule_info, /*is_5tuple=*/false);
  ...
}

תכנות RSS

כשמשתמשים בהפניית תנועה, RSS יכול לעזור בבידוד טוב יותר של התנועה. בדומה לניתוב תנועה, אפשר להגדיר את RSS באמצעות ethtool. מומלץ להגדיר RSS לפני שמריצים את האפליקציה.

בדוגמה הבאה אפשר לראות איך מגדירים את RSS לעבודה עם AF_XDP:

# Example: Kernel queues 0-3, XDP queues 4-7
NUM_SOCKETS=4 bash prep_xdp.sh eth0

# Program flow rules
ethtool -N eth0 flow-type udp4 dst-ip DST_IP dst-port DST_PORT_0 action 4 loc 0
ethtool -N eth0 flow-type udp4 dst-ip DST_IP dst-port DST_PORT_1 action 5 loc 1
ethtool -N eth0 flow-type udp4 dst-ip DST_IP dst-port DST_PORT_2 action 6 loc 2
ethtool -N eth0 flow-type udp4 dst-ip DST_IP dst-port DST_PORT_3 action 7 loc 3
# If there are more ports that the application polls on, flow rules can be added in a round-robin fashion in a script.

# Program RSS
ethtool -X eth0 start 0 equal 4

# Run the application
./path/to/application -a 0000:00:03.0 --vdev net_af_xdp,iface=eth0,start_queue=4,queue_count=4 -- APPLICATION_ARGS

RX timestamping

למרות שחותמות זמן של RX לא נתמכות במאגר DPDK של upstream עבור AF_XDP PMD, אפשר להשתמש בחותמות זמן של RX על ידי ביצוע הפעולות הבאות:

  1. מחילים את התיקונים הנדרשים על עץ המקור של DPDK, ומקמפלים מחדש את המקור של DPDK ואת האפליקציה. התיקונים האלה מוסיפים תמיכה ב-rte_eth_read_clock ובחותמות זמן של RX בהודעות mbufs שהתקבלו.

  2. משתמשים בתוכנת XDP שמעמיסה את חותמת הזמן למטא-נתונים.

  3. כשמפעילים את אפליקציית ה-DPDK, צריך לספק את הפרמטרים הנוספים הבאים של AF_XDP vdev:

    • xdp_meta_rx_ts_offset: היסט הבייטים מתחילת המטא-נתונים של XDP, שבו נמצא ערך חותמת הזמן של RX ב-64 ביט.
    • xdp_meta_valid_hint_offset: (אופציונלי) היסט הבייטים שכולל שדה דגל של בייט אחד [0x0A] שמציין אם חותמת הזמן תקפה.
    • xdp_meta_rx_ts_valid_mask: (אופציונלי) מסכת הביטים שמשמשת לחילוץ הביטים של הדגל התקף.

      אם ctx הוא תחילת המטא-נתונים, הערך של (ctx->data_meta + xdp_meta_valid_hint_offset) & xdp_meta_rx_ts_valid_mask מתאר אם חותמת הזמן תקפה.

המאמרים הבאים

  • כדי לסנכרן את השעון של המופע עם השעון של כרטיס ה-NIC הפיזי של שרת המארח, אפשר לעיין במאמר בנושא הגדרת זמן מדויק.