רשתות עם מחסנית כפולה של IPv4/‏IPv6

רשתות עם מחסנית כפולה ב-Google Distributed Cloud מאפשרות לאשכולות לקבל תנועה ממכשירים חיצוניים שמשתמשים ב-IPv4 או ב-IPv6. Pod וצמתים מקבלים גם כתובות IPv4 וגם כתובות IPv6, ולשירות Kubernetes יכולה להיות כתובת IPv4, כתובת IPv6 או שתיהן.

באשכולות עם מחסנית כפולה תמיד משתמשים במצב שטוח ל-IPv6. ב-IPv4, אשכולות עם שני סוגי כתובות IP משתמשים במצב אי כברירת מחדל, אבל אפשר להגדיר מצב שטוח במקום זאת. כדי ליצור אשכול עם שני סוגי כתובות IP, צריך להפעיל את שני סוגי כתובות ה-IP ברשת הבסיסית. אי אפשר להפעיל אשכול עם מחסנית כפולה ברשת עם מחסנית אחת של IPv4 או IPv6.

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

אם צומתי האשכול מריצים את RedHat Enterprise Linux ומופעל בהם SELinux, בכל צומת:

  • ב/etc/firewalld/firewalld.conf, מגדירים את IPv6_rpfilter=no.

  • מריצים את systemctl restart firewalld.

אם צומתי האשכול משתמשים בהגדרה אוטומטית של כתובת ללא שמירת מצב (SLAAC) להגדרת רשת IPv6, עיינו במאמר הגדרת IPv6 של צומת לרשתות עם מחסנית כפולה כדי להגדיר את הצמתים לקבלת הודעות על נתבים (RAs).

סקירה כללית על יצירת אשכול עם מחסנית כפולה

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

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

בקובץ התצורה, צריך לכלול מניפסטים של:

  • משאב של מרחב שמות
  • משאב של אשכול
  • משאב אחד או יותר של NodePool
  • משאב אחד או יותר מסוג ClusterCIDRConfig

ממלאים את המניפסט של מרחב השמות ואת המניפסטים של NodePool כמו באשכול עם מחסנית אחת.

במניפסט של האשכול, בקטע clusterNetwork.services.cidrBlocks, מציינים גם טווח CIDR של IPv4 וגם טווח CIDR של IPv6. זהו הקריטריון להפעלת אשכול עם מחסנית כפולה. כלומר, אם תספקו טווחי CIDR של שירות גם ל-IPv4 וגם ל-IPv6, לאשכול תהיה רשת עם מחסנית כפולה.

במניפסט של האשכול, בקטע clusterNetwork.pods.cidrBlocks, מציינים טווח CIDR של IPv4, אבל לא מציינים טווח CIDR של IPv6. טווחי ה-CIDR של IPv6 לפודים מצוינים במניפסטים של ClusterCIDRConfig.

אם אתם משתמשים באיזון עומסים מקובץ, צריך לספק גם כתובות IPv4 וגם כתובות IPv6 בקטע loadBalancer.addressPools במניפסט של האשכול.

המשאבים של ClusterCIDRConfig מיועדים לציון טווחי CIDR של IPv4 ו-IPv6 לפודים. אפשר להשתמש במשאב ClusterCIDRConfig אחד כדי לציין טווחי CIDR שחלים על כל האשכול. כלומר, כתובות ה-IPv4 של Pod לכל הצמתים נלקחות מטווח CIDR יחיד, וכתובות ה-IPv6 של Pod לכל הצמתים נלקחות מטווח CIDR יחיד. אפשר גם להשתמש בכמה משאבי ClusterCIDRConfig כדי לציין טווחי CIDR שחלים על מאגר צמתים מסוים או על צומת מסוים.

נגישות לכתובות IP של Pod

אשכול עם מחסנית כפולה משתמש במצב שטוח לרשת IPv6. הדוגמה שמופיעה במסמך הזה היא לאשכול שמשתמש ברשת סטטית במצב שטוח ל-IPv6. כלומר, האשכול לא מוגדר לשימוש ב-Border Gateway Protocol ‏ (BGP).

באשכול שמשתמש ברשת סטטית במצב שטוח, צריך לציין כתובות IP של צומת ושל Pod שכולן חלק מאותו רשת משנה. ההגדרה הזו מאפשרת ללקוחות מחוץ לאשכול, אבל באותו דומיין של שכבה 2 כמו צומתי האשכול, לשלוח חבילות ישירות לכתובות ה-IP של ה-Pod.

לדוגמה, נניח שצומתי האשכול ומכונות אחרות נמצאים באותו דומיין של שכבה 2. זו דרך אחת שבה אפשר לציין טווחי כתובות:

מטרהטווחמספר הכתובות
כל הדומיין של שכבה 2fd12::/108220
קבוצת Podfd12::1:0/112216
צמתיםfd12::2:0/112216
מכונות אחרותfd12::3:0/112216
אח"מיםfd12::4:0/112216

בדוגמה שלמעלה, אלה הנקודות העיקריות שצריך להבין:

  • כל הכתובות של הצמתים, ה-Pods והמכונות נמצאות בטווח הגדול: fd12::/108.

  • כתובות ה-IP של הפודים נמצאות בקבוצת משנה של הטווח הגדול.

  • כתובות ה-IP של הצמתים נמצאות בקבוצת משנה אחרת של הטווח הגדול.

  • כתובות ה-IP של מכונות אחרות נמצאות בקבוצת משנה אחרת של הטווח הגדול.

  • כל טווחי קבוצות המשנה נבדלים זה מזה.

בדוגמה הקודמת, לכל מכונה בדומיין של שכבה 2, כולל צומתי האשכול, צריך להיות כלל העברה לטווח הגדול. לדוגמה:

inet fd12::/108 scope global eth0

הגדרת IPv6 של צומת לרשתות עם סטאק כפול

כשמפעילים רשתות בסטאק כפול, Google Distributed Cloud מגדיר באופן אוטומטי כל צומת כדי לאפשר העברת חבילות IPv6 על ידי הגדרת הפרמטר net.ipv6.conf.all.forwarding sysctl ל-1. הגדרת הליבה הזו נדרשת לשכבת הרשת של האשכול כדי לנתב תנועת IPv6 של Pods ו-Services בין צמתים.

שיקולים לגבי פרסום נתבים

להפעלת net.ipv6.conf.all.forwarding יש תופעת לוואי רגילה של ליבת Linux: ממשקי הרשת מפסיקים לקבל הודעות נתב (RA) של IPv6 כברירת מחדל. ליבת Linux מניחה שמערכת שמופעלת בה העברת חבילות פועלת כנתב, ונתבים לא מעבדים הודעות RA מנתבים אחרים כברירת מחדל.

השינוי הזה יכול לשבש צמתים שמסתמכים על הגדרה אוטומטית של כתובות בלי שמירת מצב (SLAAC) כדי לקבל כתובות IPv6, קידומות או נתיבי ברירת מחדל מהרשת, מה שעלול להוביל לאובדן הקישוריות של IPv6 ברמת הצומת.

ההגדרה הנדרשת לצמתים שתלויים ב-SLAAC

אם הצמתים משתמשים ב-SLAAC להגדרת רשת IPv6, צריך להגדיר ידנית את הליבה (kernel) כך שתקבל RAs כשהעברת חבילות IPv6 מופעלת.

כדי לאשר RAs בממשק כשהעברת IPv6 מופעלת, מגדירים באופן קבוע את הפרמטר net.ipv6.conf.INTERFACE_NAME.accept_ra sysctl ל-2 בכל צומת שמושפע:

  1. מוסיפים את ההגדרה sysctl לקובץ תצורה ב-/etc/sysctl.d/, למשל /etc/sysctl.d/60-ipv6-ra.conf:

    net.ipv6.conf.INTERFACE_NAME.accept_ra=2
    

    מחליפים את INTERFACE_NAME בשם של ממשק הרשת, למשל eth0.

  2. מחילים את הגדרת sysctl:

    sudo sysctl --system
    

הגדרת net.ipv6.conf.INTERFACE_NAME.accept_ra לערך 2 מבטיחה שהצומת יוכל לנתב חבילות IPv6 לאשכול, תוך המשך קבלת הגדרות הרשת שלו באמצעות RAs.

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

כשיוצרים אשכול עם מחסנית כפולה, יש כמה אפשרויות. לדוגמה, יכולים להיות טווחי CIDR ברמת האשכול או טווחי CIDR שחלים על מאגרי צמתים מסוימים. אפשר לשלב רשת שטוחה של IPv6 עם רשת במצב אי של IPv4. או שגם רשתות IPv4 וגם רשתות IPv6 יכולות להיות שטוחות. אתם יכולים להשתמש באיזון עומסים בחבילה או באיזון עומסים ידני.

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

  • רשת IPv4 במצב אי (island)
  • רשת IPv6 במצב שטוח
  • טווח CIDR של IPv4 ל-Pods בכל האשכול
  • טווח CIDR של IPv6 ל-Pods בכל האשכול
  • טווח CIDR של IPv4 לכל האשכול בשביל שירותים
  • טווח CIDR של IPv6 ברמת האשכול לשירותים
  • מאגר כתובות IPv4 לשימוש בשירותים מסוג LoadBalancer
  • מאגר כתובות IPv6 לשימוש בשירותים מסוג LoadBalancer
  • איזון עומסים בחבילה

במאמר וריאציות של שימוש ב-ClusterCIDRConfig תוכלו לקרוא דוגמאות נוספות להגדרות אישיות.

מילוי קובץ תצורה

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

בקובץ התצורה, במניפסט Cluster:

  • בשביל clusterNetwork.pods.cidrBlocks, צריך לציין טווח CIDR יחיד של IPv4.

  • בשביל clusterNetwork.services.cidrBlocks, צריך לציין שני טווחי CIDR: אחד ל-IPv4 ואחד ל-IPv6.

  • בשביל loadBalancer.addressPools, צריך לספק שני טווחי כתובות: אחד ל-IPv4 ואחד ל-IPv6. כשיוצרים שירות מסוג LoadBalancer, כתובות ה-IP החיצוניות של השירות נבחרות מהטווחים האלה.

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

apiVersion: baremetal.cluster.gke.io/v1
kind: Cluster
metadata:
  name: "dual-stack"
  namespace: "cluster-dual-stack"

spec:
  clusterNetwork:
    pods:
      cidrBlocks:
      - "192.168.0.0/16"
    services
      cidrBlocks:
       - "172.16.0.0/20"
       - "fd12::5:0/116"
...
  loadBalancer:
    mode: "bundled"
    ...
    addressPools:
    - name: "pool-1"
      addresses:
       - "10.2.0.212-10.2.0.221"
       - "fd12::4:101-fd12::4:110"

באותו קובץ תצורה, כוללים מניפסט של ClusterCIDRConfig.

  • מגדירים את ipv4.cidr לאותו טווח CIDR שציינתם במניפסט של Cluster. זו דרישה אם IPv4 נמצא במצב אי.

  • מגדירים את namespace לאותו ערך שציינתם במניפסט של Cluster.

  • מגדירים את ipv6.cidr לטווח CIDR של IPv6 ל-Pods.

  • לכל טווח CIDR, צריך לציין ערך ל-perNodeMaskSize כדי לקבוע כמה כתובות Pod יוקצו לכל צומת. מספר כתובות ה-IPv4 שמוקצות לכל צומת צריך להיות זהה למספר כתובות ה-IPv6 שמוקצות לכל צומת. צריך להגדיר את הערכים של perNodeMaskSize בהתאם. לדוגמה, אם רוצים 28 כתובות לכל צומת, מגדירים את הערכים של perNodeMaskSize באופן הבא:

    • ipv4.perNodeMaskSize: 24 # (32 - 8 = 24)
    • ipv6.perNodeMaskSize: 120 # (128 - 8 = 120)

דוגמה למניפסט של ClusterCIDRConfig:

apiVersion: baremetal.cluster.gke.io/v1alpha1
kind: ClusterCIDRConfig
metadata:
  name: "cluster-wide-ranges"
  namespace: "cluster-dual-stack"  # Must be the same as the Cluster namespace.
spec:
  ipv4:
    cidr: "192.168.0.0/16"  #  For island mode, must be the same as the Cluster CIDR.
    perNodeMaskSize: 24
  ipv6:
    cidr: "fd12::1:0/112"
    perNodeMaskSize: 120

בדוגמה הקודמת:

  • טווח ה-CIDR של Pod ב-IPv4 כולל 2(32-16) = 216 כתובות. גודל המסכה לכל צומת הוא 24, כך שמספר הכתובות שמוקצות לכל צומת הוא 2(32-24) = 28.

  • טווח ה-CIDR של Pod ב-IPv6 כולל 2(128-112) = 216 כתובות. גודל המסכה לכל צומת הוא 120, כך שמספר הכתובות שמוקצות לכל צומת הוא 2(128-120) = 28.

קובץ תצורה לדוגמה

סיום יצירת האשכול

מסיימים ליצור את האשכול כמו שמתואר במסמך ליצירת אשכול.

הצגת צמתים ו-Pods של אשכולות

מציגים את צומתי האשכול:

kubectl --kubeconfig CLUSTER_KUBECONFIG get nodes --output yaml

מחליפים את CLUSTER_KUBECONFIG בנתיב של קובץ ה-kubeconfig של האשכול.

בפלט, אפשר לראות את כתובות ה-IPv4 וה-IPv6 של כל צומת. אפשר גם לראות את טווחי הכתובות של IPv4 ו-IPv6 לפודים בצומת. לדוגמה:

- apiVersion: v1
  kind: Node
  ...
  spec:
    podCIDR: 192.168.1.0/24
    podCIDRs:
    - 192.168.1.0/24
    - fd12::1:100/120
    providerID: baremetal://10.2.0.5
  status:
    addresses:
    - address: 10.2.0.5
      type: InternalIP
    - address: fd12::2:5
      type: InternalIP

מציגים את ה-Pods באשכול:

kubectl --kubeconfig CLUSTER_KUBECONFIG get pods --all-namespaces

בוחרים Pod אחד ומפרטים את הפרטים שלו. לדוגמה:

kubectl --kubeconfig CLUSTER_KUBECONFIG get pod gke-metrics-agent-b9qrv \
  --namespace kube-system \
  -- output yaml

בפלט אפשר לראות את כתובות ה-IPv4 וה-IPv6 של ה-Pod. לדוגמה:

apiVersion: v1
kind: Pod
metadata:
  ...
  name: gke-metrics-agent-b9qrv
  namespace: kube-system
...
status:
  ...
  podIPs:
  - ip: 192.168.1.146
  - ip: fd12::1:11a

וריאציות של שימוש ב-ClusterCIDRConfig

בדוגמה הקודמת השתמשנו באובייקט ClusterCIDRConfig כדי לציין טווחי CIDR של Pod ברמת האשכול. כלומר, טווח CIDR אחד של IPv4 משמש לכל ה-Pods באשכול. טווח CIDR אחד של IPv6 משמש לכל ה-Pods באשכול.

במצבים מסוימים, יכול להיות שלא תרצו להשתמש בטווח CIDR אחד לכל ה-Pods באשכול. לדוגמה, יכול להיות שתרצו לציין טווח CIDR נפרד לכל מאגר צמתים, או טווח CIDR נפרד לכל צומת. למידע נוסף על ClusterCIDRConfig ודוגמאות לשימוש בו, קראו את המאמר הסבר על המשאב המותאם אישית ClusterCIDRConfig.

לדוגמה, בקובץ ה-ClusterCIDRConfig הבא מצוין טווח CIDR למאגר צמתים בשם "workers".

apiVersion: baremetal.cluster.gke.io/v1alpha1
kind: ClusterCIDRConfig
metadata:
  name: "worker-pool-ccc"
  namespace: "cluster-dual-stack"
spec:
  ipv4:
    cidr: "192.168.0.0/16"
    perNodeMaskSize: 24
  ipv6:
    cidr: "fd12::1:0/112"
    perNodeMaskSize: 120
  nodeSelector:
    matchLabels:
      baremetal.cluster.gke.io/node-pool: "workers"

הפקודה הבאה, ClusterCIDRConfig, מציינת טווח CIDR לצומת יחיד עם כתובת ה-IP‏ 10.2.0.5:

apiVersion: baremetal.cluster.gke.io/v1alpha1
kind: ClusterCIDRConfig
metadata:
  name: "range-node1"
  namespace: "cluster-dual-stack"
spec:
  ipv4:
    cidr: "192.168.1.0/24"
    perNodeMaskSize: 24
  ipv6:
    cidr: "fd12::1:0/120"
    perNodeMaskSize: 120
  nodeSelector:
    matchLabels:
      baremetal.cluster.gke.io/k8s-ip: "10.2.0.5"

יצירת שירות מסוג ClusterIP עם מחסנית כפולה

הנה מניפסט לפריסה:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: "my-deployment"
spec:
  selector:
    matchLabels:
      app: "try-dual-stack"
  replicas: 3
  template:
    metadata:
      labels:
        app: "try-dual-stack"
    spec:
      containers:
      - name: "hello"
        image: "us-docker.pkg.dev/google-samples/containers/gke/hello-app:2.0"

שומרים את המניפסט בקובץ בשם my-deployment.yaml ויוצרים את הפריסה:

kubectl --kubeconfig CLUSTER_KUBECONFIG apply -f my-deployment.yaml

מחליפים את CLUSTER_KUBECONFIG בנתיב של קובץ ה-kubeconfig של האשכול.

זהו מניפסט של שירות מסוג ClusterIP:

apiVersion: v1
kind: Service
metadata:
  name: "my-service"
spec:
  selector:
    app: "try-dual-stack"
  type: "ClusterIP"
  ipFamilyPolicy: "RequireDualStack"
  ipFamilies:
  - "IPv6"
  - "IPv4"
  ports:
  - port: 80
    targetPort: 8080

בהקשר של התרגיל הזה, אלה הנקודות העיקריות שצריך להבין לגבי המניפסט של השירות שלמעלה:

  • השדה ipFamilyPolicy מוגדר לערך RequireDualStack. המשמעות היא שכתובות IPv6 ו-IPv4 של ClusterIP מוקצות לשירות.

  • בשדה ipFamilies, משפחת IPv6 מופיעה ראשונה ומשפחת IPv4 מופיעה שנייה. כלומר, spec.ClusterIP של השירות תהיה כתובת IPv6 שנבחרה מתוך clusterNetwork.services.cidrBlocks במניפסט של האשכול.

שומרים את המניפסט בקובץ בשם my-cip-service.yaml ויוצרים את השירות:

kubectl --kubeconfig CLUSTER_KUBECONFIG apply -f my-cip-service.yaml

פרטים על השירות:

kubectl --kubeconfig CLUSTER_KUBECONFIG get service my-service --output yaml

בפלט אפשר לראות את כתובות ה-IP של האשכול של השירות. לדוגמה:

apiVersion: v1
kind: Service
metadata:
  name: my-service
  …
spec:
  clusterIP: fd12::5:9af
  clusterIPs:
  - fd12::5:9af
  - 172.16.12.197

בצומת של אשכול, קוראים לשירות:

curl IPV4_CLUSTER_IP
curl IPV6_CLUSTER_IP

בפלט מוצגת ההודעה "Hello world":

Hello, world!
Version: 2.0.0
Hostname: my-deployment-xxx

יצירת שירות מסוג LoadBalancer עם מחסנית כפולה

זהו מניפסט של שירות מסוג LoadBalancer:

apiVersion: v1
kind: Service
metadata:
  name: "my-lb-service"
spec:
  selector:
    app: "try-dual-stack"
  type: "LoadBalancer"
  ipFamilyPolicy: "RequireDualStack"
  ipFamilies:
  - "IPv6"
  - "IPv4"
  ports:
  - port: 80
    targetPort: 8080

שומרים את המניפסט בקובץ בשם my-lb-service.yaml ויוצרים את השירות:

kubectl --kubeconfig CLUSTER_KUBECONFIG apply -f my-lb-service.yaml

זוכרים שבמניפסט של האשכול ציינתם טווח של כתובות IPv6 וטווח של כתובות IPv4 לשימוש בשירותים מסוג LoadBalancer?

  loadBalancer:
    mode: "bundled"
    ...
    addressPools:
    - name: "pool-1"
      addresses:
      - "10.2.0.112-10.2.0.221"
      - "fd12::4:101-fd12::4:110"

לשירות תוקצה כתובת IPv4 חיצונית שנבחרה מטווח ה-IPv4 וכתובת IPv6 חיצונית שנבחרה מטווח ה-IPv6.

פרטי הרשימה של השירות:

kubectl --kubeconfig CLUSTER_KUBECONFIG get service my-lb-service --output yaml

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

apiVersion: v1
kind: Service
metadata:
  name: my-lb-service
...
status:
  loadBalancer:
    ingress:
    - ip: 10.2.0.213
    - ip: fd12::4:101

ערכים אפשריים של ipFamilyPolicy

כשיוצרים שירות עם מחסנית כפולה, אפשר להגדיר את ipFamilyPolicy לאחד מהערכים הבאים:

  • ‫SingleStack: הבקר מקצה כתובת IP של אשכול לשירות, שנבחרה מהטווח הראשון שצוין במניפסט של האשכול בקטע clusterNetwork.services.cidrBlocks.

  • ‫PreferDualStack: הבקר מקצה כתובות IP של אשכול IPv4 ו-IPv6 לשירות, שנבחרו מהטווחים שצוינו במניפסט של האשכול בקטע clusterNetwork.services.cidrBlocks. אם האשכול הוא לא אשכול עם מחסנית כפולה, ההתנהגות זהה לזו של SingleStack.

  • ‫RequireDualStack: הבקר מקצה כתובות IP של אשכול IPv4 ו-IPv6 לשירות, שנבחרו מהטווחים שצוינו במניפסט של האשכול בקטע clusterNetwork.services.cidrBlocks. הוא מגדיר את הערך של spec.clusterIP על סמך משפחת הכתובות הראשונה שצוינה במניפסט של השירות בקטע ipFamilies.

מידע נוסף

מידע נוסף על יצירת שירותים עם שני סוגי כתובות IP זמין במאמר אפשרויות לשני סוגי כתובות IP בשירותים חדשים.