วิธีแยก DNS ระหว่าง Kubernetes กับ OpenVPN sidecar
พอด 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 คือรายการโซน แต่ละรายการระบุเซิร์ฟเวอร์ที่ตอบได้. หนึ่งบล็อกครอบโซนของอุโมงค์ และบล็อก catch-all มาสุด ทำหน้าที่รับทุกอย่างที่ไม่มีบล็อกใดอ้างสิทธิ์. catch-all ต้องชี้ไปยัง upstream จริง ห้ามชี้กลับไปยังพอด: .:53 { forward . 10.100.0.10 } ใช้ resolver ของคลัสเตอร์ซ้ำ เพราะมันส่งชื่อสาธารณะไปยัง resolver ของโนดอยู่แล้ว. สsิ่งอื่นในพอดไม่ต้องรู้ว่าชื่ออยู่ในเครือข่ายใด.
.: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 ของโนดลงในบล็อก catch-all โดยอ่านจากโนด ไม่ใช่จากในพอด.
อย่าชี้บล็อก catch-all ไปยัง /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 ต่อพอด. การเปลี่ยนแปลงนั้นอยู่ระดับคลัสเตอร์: ต้องมีสิทธิ์ผู้ดูแลและเริ่ม deployment ที่ทุกคนพิ่งใหม่. จำกัดเพียงโซนเดียวนั้น ไม่ใช่ราก. ที่อยู่ของอุโมงค์ของงานหนึ่งจะกลายเป็น 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 ไว้ในตั๋ว เพราะนั่นคือสิ่งที่แยกปัญหาชื่อออกจากปัญหาการเส้นทาง.