מידע על kube-dns ל-GKE

אם אתם מפעילים אפליקציות באשכולות רגילים, kube-dns הוא ספק ה-DNS שמוגדר כברירת מחדל ועוזר לכם להפעיל גילוי שירותים ותקשורת. במאמר הזה מוסבר איך לנהל DNS באמצעות kube-dns, כולל הארכיטקטורה, ההגדרה ושיטות מומלצות לאופטימיזציה של פעולות ה-DNS בסביבת GKE.

המסמך הזה מיועד למפתחים, לאדמינים ולאדריכלים שאחראים על ניהול DNS ב-GKE. למידע על תפקידים ומשימות נפוצים ב- Google Cloud, אפשר לעיין במאמר תפקידים ומשימות נפוצים של משתמשי GKE.

לפני שמתחילים, חשוב להכיר את השירותים של Kubernetes ואת המושגים הכלליים של DNS.

הסבר על ארכיטקטורה של kube-dns

kube-dns פועל בתוך אשכול GKE כדי לאפשר פענוח DNS בין Pods לבין Services.

הדיאגרמה הבאה מציגה כיצד ה-Pods מקיימים אינטראקציה עם שירות kube-dns:

איור 1: דיאגרמה שמראה איך רכיבי Pod שולחים שאילתות DNS לשירות kube-dns, שמגובה על ידי רכיבי Pod של kube-dns. ה-Pods של kube-dns מטפלים בפענוח DNS פנימי ומעבירים שאילתות חיצוניות לשרתי DNS במעלה הזרם.

רכיבים עיקריים

kube-dns כולל את הרכיבים העיקריים הבאים:

  • kube-dns Pods: ה-Pods האלה מריצים את תוכנת השרת kube-dns. כמה רפליקות של ה-Pods האלה פועלות במרחב השמות kube-system, והן מספקות זמינות גבוהה ויתירות.
  • kube-dns שירות: שירות Kubernetes מסוג ClusterIP, שמקבץ את ה-Pods וחושף אותם כנקודת קצה יציבה אחת.kube-dnsClusterIP פועל כשרת DNS עבור האשכול, והפודים משתמשים בו כדי לשלוח שאילתות DNS. ‫kube-dns תומך בעד 1,000 נקודות קצה לכל שירות ללא ממשק משתמש.
  • kube-dns-autoscaler: ה-Pod הזה משנה את מספר העותקים המשוכפלים של kube-dns בהתאם לגודל האשכול, שכולל את מספר הצמתים וליבות ה-CPU. הגישה הזו עוזרת להבטיח ש-kube-dns יוכל להתמודד עם עומסים משתנים של שאילתות DNS.

פענוח DNS פנימי

כש-Pod צריך לפתור שם DNS בדומיין של האשכול, כמו myservice.my-namespace.svc.cluster.local, מתרחש התהליך הבא:

  1. הגדרת ה-DNS של ה-Pod:kubelet בכל צומת מגדיר את קובץ /etc/resolv.conf של ה-Pod. בקובץ הזה נעשה שימוש ב-kube-dns של שירות ClusterIP כשרת השמות.
  2. שאילתת DNS: ה-Pod שולח שאילתת DNS לkube-dns Service.
  3. פתרון שמות: kube-dns מקבל את השאילתה. הוא מחפש את כתובת ה-IP המתאימה ברשומות ה-DNS הפנימיות שלו ומגיב ל-Pod.
  4. תקשורת: ה-Pod משתמש בכתובת ה-IP שזוהתה כדי לתקשר עם שירות היעד.

פענוח DNS חיצוני

כש-Pod צריך לפתור שם DNS חיצוני, או שם שנמצא מחוץ לדומיין של האשכול, ‏kube-dns פועל כמפענח רקורסיבי. הוא מעביר את השאילתה לשרתי DNS במעלה הזרם שהוגדרו בקובץ ConfigMap. אפשר גם להגדיר פותרים בהתאמה אישית לדומיינים ספציפיים, שנקראים גם דומיינים מסוג stub. ההגדרה הזו מכוונת את kube-dns להעביר בקשות לדומיינים האלה לשרתי DNS ספציפיים במעלה הזרם.

הגדרת DNS של Pod

ב-GKE, סוכן kubelet בכל צומת מגדיר את הגדרות ה-DNS של ה-Pods שפועלים בצומת הזה.

הגדרת הקובץ /etc/resolv.conf

כש-GKE יוצר Pod, סוכן kubelet משנה את הקובץ /etc/resolv.conf של ה-Pod. הקובץ הזה מגדיר את שרת ה-DNS לפתרון שמות ומציין דומיינים לחיפוש. כברירת מחדל, kubelet מגדיר את ה-Pod להשתמש בשירות ה-DNS הפנימי של האשכול, kube-dns, כשרת השמות שלו. הוא גם מאכלס את דומייני החיפוש בקובץ. דומייני החיפוש האלה מאפשרים לכם להשתמש בשמות לא מלאים בשאילתות DNS. לדוגמה, אם הפוד מבצע שאילתה לגבי myservice,‏ Kubernetes מנסה קודם לפתור את myservice.default.svc.cluster.local, אחר כך את myservice.svc.cluster.local ואז את הדומיינים האחרים מהרשימה search.

בדוגמה הבאה מוצגת הגדרת ברירת מחדל של /etc/resolv.conf:

nameserver 10.0.0.10
search default.svc.cluster.local svc.cluster.local cluster.local c.my-project-id.internal google.internal
options ndots:5

הקובץ הזה כולל את הערכים הבאים:

  • nameserver: מגדיר את ClusterIP של שירות kube-dns.
  • search: מגדיר את דומייני החיפוש שמצורפים לשמות לא מלאים במהלך חיפושי DNS.
  • options ndots:5: הגדרת ערך הסף שלפיו GKE קובע אם שם הוא שם מוגדר במלואו. שם נחשב מוגדר במלואו אם יש בו חמש נקודות או יותר.

הגדרת ה-DNS של פודים שהוגדרו עם ההגדרה hostNetwork: true עוברת בירושה מהמארח, והם לא שולחים שאילתות ישירות אל kube-dns.

התאם אישית את kube-dns

kube-dns מספק פענוח DNS חזק כברירת מחדל. אתם יכולים להתאים את ההתנהגות שלו לצרכים ספציפיים, כמו שיפור היעילות של הרזולוציה או שימוש בשרתי DNS מועדפים. כדי להגדיר גם דומיינים מסוג stub וגם שרתי שמות במעלה הזרם, צריך לשנות את kube-dns ConfigMap במרחב השמות kube-system.

שינוי של kube-dns ConfigMap

כדי לשנות את kube-dns ConfigMap, מבצעים את הפעולות הבאות:

  1. פותחים את ConfigMap לעריכה:

    kubectl edit configmap kube-dns -n kube-system
    
  2. בקטע data, מוסיפים את השדות stubDomains ו-upstreamNameservers:

    apiVersion: v1
    kind: ConfigMap
    metadata:
      labels:
        addonmanager.kubernetes.io/mode: EnsureExists
      name: kube-dns
      namespace: kube-system
    data:
      stubDomains: |
        {
          "example.com": [
            "8.8.8.8",
            "8.8.4.4"
          ],
          "internal": [ # Required if your upstream nameservers can't resolve GKE internal domains
            "169.254.169.254" # IP of the metadata server
          ]
        }
      upstreamNameservers: |
        [
          "8.8.8.8", # Google Public DNS
          "1.1.1.1" # Cloudflare DNS
        ]
    
  3. שומרים את ה-ConfigMap. kube-dns טוען מחדש את ההגדרה באופן אוטומטי.

דומיינים מסוג Stub

דומיינים מסוג Stub מאפשרים להגדיר פותרים מותאמים אישית של DNS לדומיינים ספציפיים. כש-Pod שולח שאילתה לשם בדומיין stub, ‏ kube-dns מעביר את השאילתה למפענח ה-DNS שצוין במקום להשתמש במנגנון ברירת המחדל שלו לפתרון.

אתם כוללים קטע stubDomains בkube-dns ConfigMap.

בסעיף הזה מצוינים הדומיין ושרתי השמות המתאימים של ה-upstream. ‫kube-dns ואז מעביר שאילתות לשמות בדומיין הזה לשרתים המיועדים. לדוגמה, אפשר לנתב את כל שאילתות ה-DNS עבור internal.mycompany.com אל 192.168.0.10, ולהוסיף את "internal.mycompany.com": ["192.168.0.10"] אל stubDomains.

כשמגדירים מפענח DNS מותאם אישית לדומיין stub, כמו example.com, kube-dns מעביר את כל בקשות תרגום השם של הדומיין הזה, כולל תת-דומיינים כמו *.example.com, לשרתים שצוינו.

שרתי שמות במעלה הזרם

אתם יכולים להגדיר את kube-dns כך שישתמש בשרתי שמות בהתאמה אישית במעלה הזרם כדי לפתור שמות של דומיינים חיצוניים. ההגדרה הזו מורה ל-kube-dns להעביר את כל בקשות ה-DNS, מלבד הבקשות לדומיין הפנימי של האשכול (*.cluster.local), לשרתי ה-upstream המיועדים. יכול להיות ששרתי ה-DNS המותאמים אישית שלכם לא יוכלו לפתור דומיינים פנימיים כמו metadata.internal ו-*.google.internal. אם מפעילים את איחוד הזהויות של עומסי עבודה ל-GKE או שיש עומסי עבודה שתלויים בדומיינים האלה, צריך להוסיף דומיין stub ל-internal ב-ConfigMap. משתמשים ב-169.254.169.254, כתובת ה-IP של שרת המטא-נתונים, כמפענח ה-DNS של דומיין ה-stub הזה.

ניהול פריסה מותאמת אישית של kube-dns

ב-GKE רגיל, התהליך kube-dns פועל כפריסה. פריסה kube-dns בהתאמה אישית מאפשרת לכם, כאדמינים של האשכול, לשלוט בפריסה ולהתאים אותה לצרכים שלכם, במקום להשתמש בפריסה שמוגדרת כברירת מחדל ב-GKE.

סיבות לפריסה בהתאמה אישית

כדאי לשקול פריסה מותאמת אישית של kube-dns מהסיבות הבאות:

  • הקצאת משאבים: כוונון עדין של משאבי המעבד והזיכרון עבור kube-dnsPods כדי לבצע אופטימיזציה של הביצועים באשכולות עם נפח תנועה גבוה של DNS.
  • גרסת התמונה: אפשר להשתמש בגרסה ספציפית של התמונה kube-dns או לעבור לספק DNS חלופי כמו CoreDNS.
  • הגדרה מתקדמת: התאמה אישית של רמות הרישום ביומן, מדיניות האבטחה והתנהגות של שמירת DNS במטמון.

התאמה אוטומטית לעומס (autoscaling) לפריסות בהתאמה אישית

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

כשמנהלים Deployment (פריסה) בהתאמה אישית, האחריות על כל הרכיבים שלה מוטלת על המשתמש, למשל שמירה על עדכניות של תמונת ה-מידרוג אוטומטי. שימוש ברכיבים לא עדכניים עלול לגרום לירידה בביצועים או לכשלים ב-DNS.

הוראות מפורטות להגדרה ולניהול של פריסה משלכם של kube-dns kube-dns מופיעות במאמר הגדרה של פריסת kube-dns בהתאמה אישית.

פתרון בעיות

למידע על פתרון בעיות ב-kube-dns, אפשר לעיין בדפים הבאים:

אופטימיזציה של פענוח DNS

בקטע הזה מתוארות בעיות נפוצות ושיטות מומלצות לניהול DNS ב-GKE.

המגבלה של dnsConfig דומיינים לחיפוש ב-Pod

ב-Kubernetes, מספר הדומיינים של חיפוש DNS מוגבל ל-32. אם מנסים להגדיר יותר מ-32 דומיינים לחיפוש ב-dnsConfig של Pod,‏ kube-apiserver לא ייצור את ה-Pod ותופיע שגיאה דומה לזו:

The Pod "dns-example" is invalid: spec.dnsConfig.searches: Invalid value: []string{"ns1.svc.cluster-domain.example", "my.dns.search.suffix1", "ns2.svc.cluster-domain.example", "my.dns.search.suffix2", "ns3.svc.cluster-domain.example", "my.dns.search.suffix3", "ns4.svc.cluster-domain.example", "my.dns.search.suffix4", "ns5.svc.cluster-domain.example", "my.dns.search.suffix5", "ns6.svc.cluster-domain.example", "my.dns.search.suffix6", "ns7.svc.cluster-domain.example", "my.dns.search.suffix7", "ns8.svc.cluster-domain.example", "my.dns.search.suffix8", "ns9.svc.cluster-domain.example", "my.dns.search.suffix9", "ns10.svc.cluster-domain.example", "my.dns.search.suffix10", "ns11.svc.cluster-domain.example", "my.dns.search.suffix11", "ns12.svc.cluster-domain.example", "my.dns.search.suffix12", "ns13.svc.cluster-domain.example", "my.dns.search.suffix13", "ns14.svc.cluster-domain.example", "my.dns.search.suffix14", "ns15.svc.cluster-domain.example", "my.dns.search.suffix15", "ns16.svc.cluster-domain.example", "my.dns.search.suffix16", "my.dns.search.suffix17"}: must not have more than 32 search paths.

השגיאה הזו מוחזרת מ-kube-apiserver בתגובה לניסיון ליצור Pod. כדי לפתור את הבעיה, צריך להסיר מנתיבי החיפוש הנוספים את ההגדרה.

מגבלת nameservers בשרת במעלה הזרם עבור kube-dns

kube-dns מגביל את מספר הערכים של upstreamNameservers לשלושה. אם תגדירו יותר משלושה, Cloud Logging יציג שגיאה דומה לזו:

Invalid configuration: upstreamNameserver cannot have more than three entries (value was &TypeMeta{Kind:,APIVersion:,}), ignoring update

בתרחיש הזה, kube-dns מתעלם מההגדרה upstreamNameservers וממשיך להשתמש בהגדרה הקודמת התקינה. כדי לפתור את הבעיה, צריך להסיר את upstreamNameservers הנוסף מ-kube-dns ConfigMap.

הגדלת הקיבולת kube-dns

באשכולות רגילים, אפשר להשתמש בערך נמוך יותר עבור nodesPerReplica כדי ליצור יותר תרמילי kube-dns Pod כשצמתים באשכול עוברים שינוי גודל. מומלץ מאוד להגדיר ערך מפורש לשדה max כדי לוודא שהמכונה הווירטואלית (VM) של מישור הבקרה של GKE לא תהיה עמוסה מדי בגלל המספר הגדול של פודים kube-dns שעוקבים אחרי Kubernetes API.

אפשר להגדיר את הערך של השדה max למספר הצמתים באשכול. אם באשכול יש יותר מ-500 צמתים, צריך להגדיר את הערך של השדה max ל-500.

אפשר לשנות את מספר העותקים של kube-dns על ידי עריכת kube-dns-autoscaler ConfigMap.

kubectl edit configmap kube-dns-autoscaler --namespace=kube-system

הפלט אמור להיראות כך:

linear: '{"coresPerReplica":256, "nodesPerReplica":16,"preventSinglePointFailure":true}'

מספר העותקים המשוכפלים של kube-dns מחושב באמצעות הנוסחה הבאה:

replicas = max( ceil( cores * 1/coresPerReplica ) , ceil( nodes * 1/nodesPerReplica ) )
kube-dns

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

linear: '{"coresPerReplica":256, "nodesPerReplica":8,"max": 15,"preventSinglePointFailure":true}'

ההגדרה הזו יוצרת Pod אחד לכל שמונה צמתים באשכול.kube-dns ב-cluster עם 24 צמתים יש 3 עותקים וב-cluster עם 40 צמתים יש 5 עותקים. אם גודל האשכול עולה על 120 צמתים, מספר העותקים של kube-dns לא יעלה על 15, שהוא הערך של השדה max.

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

הפלט של kube-dns-autoscaler ConfigMap עם השדה min שמוגדר אמור להיראות כך:

linear: '{"coresPerReplica":256, "nodesPerReplica":8,"max": 15,"min": 5,"preventSinglePointFailure":true}'

שיפור הזמנים של חיפושי DNS

יש כמה גורמים שיכולים לגרום לזמן אחזור ארוך בחיפושי DNS או לכשלים ברזולוציית DNS עם ספק kube-dns שמוגדר כברירת מחדל. יכול להיות שבאפליקציות יופיעו השגיאות האלה בתור שגיאות getaddrinfo EAI_AGAIN, שמצביעות על כשל זמני בפתרון שמות. הסיבות האפשריות:

  • חיפושי DNS תכופים בעומס העבודה.
  • צפיפות גבוהה של Pods לכל צומת.
  • הפעלת kube-dns במכונות וירטואליות מסוג Spot או במכונות וירטואליות שניתן לקטוע את הפעולה שלהן, מה שעלול להוביל למחיקה לא צפויה של צמתים.
  • נפח שאילתות גבוה שחורג מהקיבולת של מופע dnsmasq ב-Pod‏ kube-dns. ב-GKE בגרסה 1.31 ואילך, יש מגבלה של 200 חיבורי TCP בו-זמניים למופע kube-dns יחיד. ב-GKE בגרסה 1.30 ומגרסאות קודמות, יש מגבלה של 20 חיבורי TCP בו-זמניים למופע kube-dns יחיד.

כדי לשפר את זמני החיפוש ב-DNS:

  • מומלץ להימנע מהרצת רכיבי מערכת קריטיים כמו kube-dns במכונות וירטואליות מסוג Spot או במכונות וירטואליות שניתנות להפסקת פעולה. יוצרים לפחות מאגר צמתים אחד עם מכונות וירטואליות רגילות, ללא מכונות וירטואליות מסוג Spot או מכונות וירטואליות זמניות. כדי לוודא שעומסי עבודה קריטיים מתוזמנים בצמתים האמינים האלה, אפשר להשתמש ב-taints וב-tolerations.
  • מפעילים את NodeLocal DNSCache. ‫NodeLocal DNSCache שומר במטמון את תשובות ה-DNS ישירות בכל צומת, וכך מקטין את זמן האחזור ואת העומס על שירות kube-dns. אם מפעילים את NodeLocal DNSCache ומשתמשים במדיניות רשת עם כללי ברירת מחדל של דחייה, צריך להוסיף מדיניות שתאפשר לעומסי עבודה לשלוח שאילתות DNS לקבוצות ה-Pod של node-local-dns.
  • הרחבת המינוי kube-dns.
  • צריך לוודא שהאפליקציה משתמשת בפונקציות שמבוססות על dns.resolve* ולא בפונקציות שמבוססות על dns.lookup, כי dns.lookup היא סינכרונית.
  • משתמשים בשמות דומיין שמוגדרים במלואם (FQDN), לדוגמה, https://google.com./ במקום https://google.com/.

יכול להיות שיהיו כשלים בפענוח DNS במהלך שדרוגים של אשכולות GKE, בגלל שדרוגים מקבילים של רכיבי מישור הבקרה, כולל kube-dns. בדרך כלל, הכשלים האלה משפיעים על אחוז קטן של צמתים. חשוב לבדוק היטב את השדרוגים של האשכולות בסביבה שאינה סביבת ייצור לפני שמחילים אותם על אשכולות בסביבת ייצור.

מוודאים שאפשר לגלות את השירות

kube-dns יוצר רק רשומות DNS לשירותים שיש להם נקודות קצה. אם לשירות אין נקודות קצה, kube-dns לא יוצר רשומות DNS עבור השירות הזה.

ניהול פערים ב-TTL של DNS

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

ב-GKE, הבעיה הזו נפתרת בגרסאות ספציפיות של מישור הבקרה, כמו 1.21.14-gke.9100 ואילך או 1.22.15-gke.2100 ואילך. בגרסאות האלה מוגדר ערך TTL מקסימלי של 30 שניות לכל תגובת DNS עם ערך TTL גבוה יותר. ההתנהגות הזו דומה ל-NodeLocal DNSCache.

הצגת המדדים של kube-dns

אפשר לאחזר מדדים לגבי שאילתות DNS באשכול ישירות מ-Pods‏ kube-dns.

  1. איתור ה-Pods במרחב השמות kube-system:kube-dns

    kubectl get pods -n kube-system --selector=k8s-app=kube-dns
    

    הפלט אמור להיראות כך:

    NAME                        READY     STATUS    RESTARTS   AGE
    kube-dns-548976df6c-98fkd   4/4       Running   0          48m
    kube-dns-548976df6c-x4xsh   4/4       Running   0          47m
    
  2. בוחרים אחד מה-Pods ומגדירים העברת פורטים כדי לגשת למדדים מתוך ה-Pod הזה:

    • ‫Port 10055 חושף kube-dns מדדים.
    • ‫Port 10054 חושף dnsmasq מדדים.

    מחליפים את POD_NAME בשם של ה-Pod שבחרתם.

    POD_NAME="kube-dns-548976df6c-98fkd" # Replace with your pod name
    kubectl port-forward pod/${POD_NAME} -n kube-system 10055:10055 10054:10054
    

    הפלט אמור להיראות כך:

    Forwarding from 127.0.0.1:10054 -> 10054
    Forwarding from 127.0.0.1:10055 -> 10055
    
  3. בסשן חדש של מסוף, משתמשים בפקודה curl כדי לגשת לנקודות הקצה של המדדים.

    # Get kube-dns metrics
    curl http://127.0.0.1:10055/metrics
    
    # Get dnsmasq metrics
    curl http://127.0.0.1:10054/metrics
    

    הפלט ייראה בערך כך:

    kubedns_dnsmasq_errors 0
    kubedns_dnsmasq_evictions 0
    kubedns_dnsmasq_hits 3.67351e+06
    kubedns_dnsmasq_insertions 254114
    kubedns_dnsmasq_max_size 1000
    kubedns_dnsmasq_misses 3.278166e+06
    

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