איך לפצל DNS בין Kubernetes ל-sidecar של OpenVPN
פוד sidecar צריך שני עולמות שמות. הצביעו resolver אחד של loopback אל האשכול והמנהרה, והפסיקו להסתמך על resolv.conf שערכתם.
פודים שמריצים OpenVPN sidecar נכשלים בפתרון שמות בדפוס מוכר: שמות האשכול נתקעים, שמות מרוחקים עונים עד שהפוד מופעל מחדש, וקובץ ה-resolver שערכתם חוזר אחורה. שום דבר ברצף הזה אינו תקלת מנהרה. לפוד יש קובץ resolver אחד ושני עולמות שמות, והקובץ יכול לפרט שרתים לפי סדר אבל אינו יכול לבטא אזורים. התיקון העמיד הוא resolver שרץ בתוך הפוד ובעל הפיצול, כך שהקובץ נשאר פשוט ולאף מדיניות אין סיבה לשכתוב אותו מחדש.
למה העריכה שעשיתם ממשיכה להיעלם
הקובץ שייך ל-kubelet, לא לכם. תחת dnsPolicy: ClusterFirst ה-kubelet יוצר אותו כדי להצביע על ה-DNS של האשכול, ויוצר אותו מחדש בכל פעם שקונטיינר מתחיל. כל מה שתשנו דרך kubectl exec חי בשכבה הניתנת לכתיבה של הקונטיינר, ולכן הפעלה מחדש, פינוי או rollout זורקים אותו. מפרט הפוד עדיין נושא את אותה מדיניות, ולכן אותו קובץ חוזר.
הגדרת dnsPolicy: None עוצרת את היצירה מחדש וגם מסירה את פתרון השמות באשכול. אתם מרוויחים קובץ שאתם שולטים בו ומאבדים את השמות שהייתם צריכים. החלופה הזו היא כל הבעיה: ל-resolv.conf אין מושג של אזורים, רק של שרתים לנסות לפי סדר, ולכן הוא לא יכול לשלוח את cluster.local לכתובת אחת ואזור פרטי לאחרת.
הריצו resolver אחד בפוד, על loopback
הצביעו את הפוד לשרת שמות אחד על loopback, ואז תנו לקונטיינר CoreDNS שחולק את מרחב השמות של הרשת של הפוד לענות בפורט 53. הגדירו את המדיניות ל-None, תנו לפוד את הכתובת האחת הזו, והוסיפו את קונטיינר ה-DNS לצד ה-VPN והיישום:
spec:
dnsPolicy: None
dnsConfig:
nameservers:
- 127.0.0.1
options:
- name: ndots
value: "5"
containers:
- name: dns
image: coredns/coredns
args: ["-conf", "/etc/coredns/Corefile"]
volumeMounts:
- {name: corefile, mountPath: /etc/coredns, readOnly: true}
- name: vpn
image: your-openvpn-client-image
- name: app
image: your-app-image
volumes:
- name: corefile
configMap:
name: split-dns-corefileשלושת הקונטיינרים חולקים מרחב שמות רשת אחד, לכן 127.0.0.1:53 מקונטיינר היישום מגיע ל-CoreDNS בלי שירות, בלי port-forward ובלי עקיפה דרך רשת האשכול. הרכבת ה-Corefile מ-ConfigMap שומרת עליו כקלט מגורסן במקום קובץ שנערך במקום. ה-resolv.conf שנוצר מחזיק אז שרת שמות אחד ושום דבר אחר, וזה מצב שאפשר לנמק לגביו.
שני פרטים נושכים כאן. התמונה הרשמית קושרת את פורט 53 כ-root, וכמה מדיניות אשכול מחייבות אתכם לכתוב זאת במפורש. כל קונטיינר בפוד תלוי כעת בכך שהקונטיינר האחד הזה פועל, לכן הוסיפו readiness probe מול שם לא מזיק ותנו ליישום לסבול כשלי אתחול.
תנו לכל אזור פרטי את ה-upstream שלו
Corefile הוא רשימה של אזורים, כל אחד מציין את השרת שיכול לענות לו. בלוק אחד מכסה את אזור המנהרה, והבלוק שתופס הכל בא אחרון ולוקח כל מה ששום בלוק מפורש לא תבע. הבלוק שתופס הכל חייב לכוון ל-upstream אמיתי, לעולם לא חזרה לפוד: .:53 { forward . 10.100.0.10 } משתמש מחדש ב-resolver של האשכול משום שהוא כבר מעביר שמות ציבוריים ל-resolver-ים של הצומת עצמו. שום דבר אחר בפוד לא צריך לדעת לאיזו רשת שם שייך.
.:53 {
errors
cache 30
forward . 10.100.0.10
}
svc.acme.internal:53 {
errors
cache 30
forward . 192.168.240.1
}קראו את כתובת האשכול משירות kube-dns ב-kube-system במקום להעתיק אחת ממאמר, משום שה-CIDR של השירותים שונים. כתובת המנהרה מגיעה מערך dhcp-option DNS ששרת ה-OpenVPN דוחף, או משרת הסמכות של אותו אזור. אם ה-resolver של האשכול שלכם אינו עונה לשמות ציבוריים, שימו את כתובת ה-resolver של הצומת בבלוק שתופס הכל, נקראת על הצומת ולא בתוך הפוד.
אל תכוונו את הבלוק שתופס הכל אל /etc/resolv.conf, שהוא שורה אחת מפתה ומלכודת בדיוק בהגדרה הזו. תחת dnsPolicy: None הקובץ של הפוד עצמו מכיל את שרת השמות היחיד 127.0.0.1, לכן היעד הזה הוא CoreDNS עצמו וכל שאילתה שלא מתאימה חוזרת רקורסיבית עד שפיקת הזמן. בלוק בשם svc.acme.internal מתאים לשם הזה ולכל שם מתחתיו, מה שמכסה את db.svc.acme.internal ומוציא את svc.acme.internal.example.net. הנקודה האחרונה היא קוסמטית, משום ששמות אזורים מנורמלים לצורה מלאה; גבול הסיומת הוא שקובע היכן שאילתה נוחתת.
החליטו אם ההעברה שייכת ל-resolver של האשכול
אם כל עומס עבודה במרחב השמות זקוק לאזור המנהרה, בלוק העברה ב-ConfigMap של CoreDNS תחת kube-system זול יותר מ-sidecar לכל פוד. השינוי הזה הוא בהיקף האשכול: הוא דורש הרשאות מנהל והפעלה מחדש של פריסה שכולם תלויים בה. הגבילו אותו לאותו אזור אחד, לעולם לא לשורש. כתובת המנהרה של עומס עבודה בודד הופכת אז ל-resolver של כל פוד עבור האזור, וביום שהמנהרה נופלת כל האשכול מאבד את השם.
ה-sidecar שומר על רדיוס הפיצוץ בתוך פוד אחד, וזו ברירת המחדל הטובה יותר כשרק מספר שירותים צריכים את הרשת המרוחקת. מה שתבחרו, כתבו איזה resolver מחזיק באזור, משום שהאדם הבא שינפה timeout ישאל.
שמות שנפתרים כשהחיבורים עדיין נכשלים
פתרון השמות הוא אחד משני הדברים שהמנהרה חייבת לשאת; השני הוא מסלול החזרה, ורשת הפוד מסתירה אותו היטב. אם שרת ה-OpenVPN עצמו רץ כפוד, כללים שתוסיפו בקונטיינר הזה אינם שולטים בתעבורה שיוצאת דרך הצומת. הכשל הזה צר: רק פודים על הצומת שהריץ גם את השרת קיבלו תשובות, וכל השאר פג.
הרצת השרת עם hostNetwork: true מחזירה אותו למרחב השמות של הצומת, שבו כלל FORWARD ותרגום מקור פועלים. תרגמו את מאגר הלקוחות ביציאה, אחרת לאף צומת מרוחק אין מסלול חזרה לכתובת מנהרה כמו 10.8.0.6. בדקו את שני הכיוונים לפני שתיגעו שוב ב-Corefile. מנהרה שנושאת את השאילתה ולא את התשובה מייצרת בדיוק אותו timeout כמו resolver שלעולם לא קיבל את השאילתה.
אמתו מפוד חדש, לא מהסשן שתיקנתם
שאלו במפורש על שני האזורים, ואז חזרו על השאילתה לאחר שהפוד הוחלף. תצורה שקיימת רק בקונטיינר פועל אינה תצורה:
kubectl exec -it deploy/app -- dig @127.0.0.1 kubernetes.default.svc.cluster.local +short
kubectl exec -it deploy/app -- dig @127.0.0.1 db.svc.acme.internal +short
kubectl delete pod -l app=your-app
kubectl wait --for=condition=Ready pod -l app=your-app --timeout=90s
kubectl exec -it deploy/app -- dig @127.0.0.1 db.svc.acme.internal +shortאם הזוג הראשון עונה והזוג שלאחר ההחלפה לא, ה-Corefile תקין ומפרט הפוד לא. אם שניהם עונים והיישום עדיין לא מצליח להתחבר, הפסיקו לנפות DNS ובדקו את מסלול ה-TCP עם nc -vz db.svc.acme.internal 5432 מתוך הקונטיינר. שמרו את פלט ה-dig בכרטיס, משום שהוא הראיה שמפרידה בין בעיית שמות לבעיית ניתוב.