ทราฟฟิกขาออกใน Kubernetes และพร็อกซี: sidecar, egress และ NO_PROXY
บทความ
- พื้นฐาน: ทำไมในคลัสเตอร์ทุกอย่างต่างออกไป
- เจาะลึก: สามระดับที่พร็อกซีอาศัยอยู่
- ตัวแปรสภาพแวดล้อมใน manifest และบทบาทของ no_proxy
- อะไรบ้างที่ไม่รับตัวแปรสภาพแวดล้อม
- ความลับ: เก็บเครดิตพร็อกซีใน secret ไม่ใช่ใน manifest
- แนวทาง sidecar: ใช้เมื่อไหร่ที่คุ้ม และให้อะไร
- Egress gateway: ที่อยู่ขาออกที่เสถียรสำหรับคลัสเตอร์
- การวินิจฉัยทราฟฟิกขาออกในคลัสเตอร์
- ข้อผิดพลาดทั่วไปที่ควรหลีกเลี่ยง
- เครื่องมือและแหล่งข้อมูล
- เคสและผลลัพธ์
- Faq: คำถามที่วิศวกรมักถาม
- สรุป: รวบรวมเช็คลิสต์การดีพลอย
วิศวกรที่คุ้นเคยกับเซิร์ฟเวอร์ทั่วไปพอเข้ามาใน Kubernetes ก็มักทำสิ่งที่เคยทำมาตลอด: export HTTP_PROXY เข้าสภาพแวดล้อม รีสตาร์ทโปรเซส แล้วรอให้ทราฟฟิกขาออกทั้งหมดวิ่งผ่านพร็อกซี บางครั้งก็ได้ผล แต่บ่อยครั้งกว่ามากที่มันทำให้คลัสเตอร์พังไปครึ่งหนึ่ง ส่วนอีกครึ่งยังคงวิ่งออกอินเทอร์เน็ตตรง ๆ ข้ามพร็อกซีไป แล้วความสนุกก็เริ่มต้น: ทำไมบางพอดรับตัวแปรไป บางพอดไม่รับ ทำไมเซอร์วิสหากันไม่เจอ และทำไม healthcheck จู่ ๆ ก็วิ่งผ่านพร็อกซีที่ไม่ได้อยู่ในรายการที่เชื่อถือได้
บทความนี้คือการวิเคราะห์อย่างละเอียดว่าทราฟฟิกขาออกในคลัสเตอร์ทำงานจริงอย่างไร และพร็อกซีควรถูกฝังตรงไหนอย่างถูกต้อง เราจะไม่ทบทวนการตั้งค่าตัวแปรสภาพแวดล้อมพื้นฐาน เพราะคุณรู้อยู่แล้ว บทสนทนาจะเจาะลึกถึงลักษณะเฉพาะของ Kubernetes ที่วิธีการเดิม ๆ ให้ผลลัพธ์ไม่คาดคิด ตัวอย่างทั้งหมดอ้างอิงจาก manifest จริง และในบทบาทของโครงสร้างพื้นฐานพร็อกซีคือ Proxeon (proxeon.net)
พื้นฐาน: ทำไมในคลัสเตอร์ทุกอย่างต่างออกไป
เริ่มจากรากฐาน บนเซิร์ฟเวอร์ทั่วไป แนวคิดเรื่องทราฟฟิกขาออกนั้นตรงไปตรงมา: มีอินเทอร์เฟซเครือข่ายตัวเดียว มีตารางเส้นทาง มีตัวแปรสภาพแวดล้อมของระบบ และเกือบทุกอย่างที่คุณรันจะสืบทอดค่าจากเชลล์แม่ พอ export ตัวแปรเข้า profile โปรเซสของผู้ใช้ทุกตัวก็เห็นมัน
ใน Kubernetes ไม่มีจุดรวมนั้น ตรงนี้มี พอด ซึ่งเป็นหน่วยการดีพลอยที่เล็กที่สุด ข้างในมีคอนเทนเนอร์หนึ่งตัวหรือมากกว่าอาศัยอยู่ พอดมีเนมสเปซเครือข่ายของตัวเอง มี IP ของตัวเอง มีชุดตัวแปรสภาพแวดล้อมของตัวเองที่กำหนดโดย manifest ไม่มีเชลล์ที่โปรเซสจะสืบทอดอะไรได้ ตัวแปรสภาพแวดล้อมจะปรากฏในคอนเทนเนอร์ก็ต่อเมื่อคุณประกาศมันอย่างชัดเจนในสเปกของพอดหรือในอิมเมจเท่านั้น
ทราฟฟิกขาออกของพอดคืออะไร
เมื่อคอนเทนเนอร์เรียกไปยังที่อยู่อาศัยภายนอก แพ็กเก็ตจะเดินทางเป็นระยะทางยาว เริ่มจากออกจากเนมสเปซเครือข่ายของคอนเทนเนอร์ผ่านอินเทอร์เฟซเสมือน จากนั้นเข้าสู่สแต็กเครือข่ายของโหนด ซึ่ง CNI plugin ซึ่งเป็นคอมโพเนนต์ที่ดูแลเครือข่ายคลัสเตอร์จะเข้ามาดูแล ต่อจากนั้นกฎ iptables หรือ eBPF กลไก SNAT (การแทนที่ที่อยู่ต้นทาง) จะเข้ามาทำงาน แล้วแพ็กเก็ตจึงออกผ่านอินเทอร์เฟซเครือข่ายของโหนดสู่โลกภายนอก
ประเด็นสำคัญคือ จากมุมมองของเซอร์วิสภายนอก คำขอมาถึงไม่ใช่จากที่อยู่ของพอด แต่มาจากที่อยู่ของโหนดคลัสเตอร์ พอดถูกซ่อนอยู่หลังการแปลงที่อยู่ นี่คือสิ่งแรกที่ทำให้มือใหม่ประหลาดใจเมื่อพยายามตั้งค่าการเข้าถึงตามรายการ IP ที่อนุญาต พวกเขาเพิ่มที่อยู่ของพอดเข้าไปในรายการ แต่ทราฟฟิกกลับมาจากที่อยู่อื่นโดยสิ้นเชิง
ทราฟฟิกขาออกสองประเภท
สิ่งสำคัญตั้งแต่เริ่มต้นคือต้องแยกกระแสสองแบบที่ต่างกันโดยสิ้นเชิง:
- ตะวันออก-ตะวันตก - ทราฟฟิกระหว่างเซอร์วิสภายในคลัสเตอร์ พอดหนึ่งเรียกอีกพอดผ่านเซอร์วิส, ClusterIP หรือชื่อ DNS อย่าง my-service.namespace.svc.cluster.local
- เหนือ-ใต้ - ทราฟฟิกออกสู่ภายนอก ไปยัง API ภายนอก ฐานข้อมูล เซอร์วิสพันธมิตร ที่เก็บออบเจ็กต์
พร็อกซีมักจำเป็นเฉพาะกับทราฟฟิกเหนือ-ใต้เท่านั้น และข้อผิดพลาดที่พบบ่อยและเจ็บปวดที่สุดคือ การตั้งค่าพร็อกซีที่ไม่ถูกต้องเผลอจับทราฟฟิกตะวันออก-ตะวันตกด้วย ทำให้การสื่อสารภายในขาดสะบั้น นั่นคือเหตุผลที่รายการยกเว้นตรงนี้สำคัญยิ่งกว่าที่ไหน ๆ แต่เดี๋ยวค่อยว่ากัน
เจาะลึก: สามระดับที่พร็อกซีอาศัยอยู่
มีอยู่สามระดับสถาปัตยกรรมเท่านั้นที่คุณจะฝังพร็อกซีเข้าไปในเส้นทางขาออกของพอดได้ แต่ละระดับแก้ปัญหาแตกต่างกัน แต่ละระดับมีราคาที่ต้องจ่าย มาดูทั้งสามอย่างตรงไปตรงมา พร้อมข้อดีข้อเสีย
ระดับ 1: ตัวคอนเทนเนอร์เอง
นี่คือกรณีที่แอปพลิเคชันในคอนเทนเนอร์รู้จักพร็อกซีเอง มันอ่านตัวแปรสภาพแวดล้อม HTTP_PROXY และ HTTPS_PROXY หรือมีพร็อกซีในคอนฟิกของตัวเอง แล้วส่งคำขอ HTTP ผ่านมันไป ตรรกะพร็อกซีถูกฝังอยู่ในไลบรารีไคลเอนต์ของแอปพลิเคชันเอง
ข้อดี ง่ายที่สุดในการนำไปใช้ตอนเริ่มต้น ไม่ต้องเพิ่มอะไรในคลัสเตอร์ ไม่มีคอมโพเนนต์เพิ่มเติม ควบคุมได้ในระดับพอดเจาะจง: คุณรู้แน่นอนว่าแอปไหนวิ่งไปไหน
ข้อเสีย ทุกแอปต้องอ่านตัวแปรเหล่านี้ได้ ซึ่งไม่ใช่ทุกตัวทำได้ การตั้งค่ากระจายไปทั่วหลายสิบ manifest การอัปเดตที่อยู่พร็อกซีหมายถึงต้องไล่แก้ทุกดีพลอยเมนต์ ง่ายมากที่จะลืมเซอร์วิสหนึ่ง แล้วมันก็วิ่งตรงออกไป ไม่มีนโยบายรวมศูนย์
ระดับ 2: คอนเทนเนอร์ sidecar
ตรงนี้จะรันคอนเทนเนอร์ตัวที่สองชื่อ sidecar เคียงข้างคอนเทนเนอร์หลักในพอดเดียวกัน มันดักจับทราฟฟิกขาออกแล้วส่งผ่านพร็อกซี แอปพลิเคชันอาจไม่รู้เลยว่ามีพร็อกซีอยู่: มันส่งคำขอตามปกติ และ sidecar ก็พร็อกซีให้อย่างแนบเนียน นี่คือวิธีที่ service mesh อย่าง Istio และ Linkerd ทำงาน และด้วยหลักการเดียวกัน เราสามารถวางพร็อกซีเอเจนต์แบบเบาในเครื่องได้
ข้อดี โปร่งใสต่อแอปพลิเคชัน นโยบายรวมศูนย์ผ่านเทมเพลตพอด เพิ่มความสามารถในการสังเกตการณ์เหนือการพร็อกซี: เมตริก การติดตาม การลองใหม่ ไทม์เอาต์ sidecar แยกตัวอยู่ในพอดและใช้ชีวิตอยู่ร่วมกับพอด
ข้อเสีย มีโอเวอร์เฮด: ตอนนี้ทุกพอดมีสองคอนเทนเนอร์ ซึ่งหมายถึงใช้หน่วยความจำและ CPU มากขึ้น การดีบักซับซ้อนขึ้นเพราะมีข้อต่อเพิ่มในเชน ลำดับการเริ่มคอนเทนเนอร์สำคัญ: ถ้าแอปพลิเคชันเริ่มก่อน sidecar คำขอแรก ๆ อาจล้มเหลว ใน Kubernetes 1.28+ แก้ปัญหานี้ได้ด้วย native sidecar containers ในฐานะ init container ที่มีนโยบาย restartPolicy Always
ระดับ 3: egress gateway ของคลัสเตอร์
นี่คือโหนดหรือพอดเฉพาะที่ทราฟฟิกขาออกทั้งหมดของคลัสเตอร์ถูกบังคับให้วิ่งผ่าน แพ็กเก็ตถูกกำหนดเส้นทางไปยัง egress gateway ด้วยเครื่องมือของ CNI แล้วตัวมันเองจึงคุยกับพร็อกซีภายนอก หรือทำหน้าที่เป็นจุดออกที่มี IP ขาออกที่เสถียรเอง
ข้อดี จุดควบคุมรวมศูนย์สำหรับทั้งคลัสเตอร์ ที่อยู่ขาออกที่เสถียรและคาดเดาได้ ซึ่งใส่ในรายการที่อนุญาตของพันธมิตรภายนอกได้สะดวก เปลี่ยนนโยบายที่เดียว แอปพลิเคชันไม่รู้เรื่องเลย
ข้อเสีย egress gateway กลายเป็นจุดวิกฤต: ล่มก็หยุดทั้งทราฟฟิกเหนือ-ใต้ ต้องมีสำรองและการมอนิเตอร์ การตั้งค่าซับซ้อนกว่าและต้องได้รับการสนับสนุนจาก CNI ความละเอียดต่ำกว่า: กำหนดนโยบายต่างกันให้แอปต่างกันยากขึ้นหากไม่มีกฎเพิ่มเติม
จะเลือกระดับไหนดี
แนวปฏิบัติจากประสบการณ์: โปรเจกต์เล็กที่มีเซอร์วิสไม่กี่ตัวซึ่งต้องการพร็อกซีภายนอก - ระดับคอนเทนเนอร์ คลัสเตอร์ขนาดกลางที่มีเซอร์วิสหลายสิบตัวและต้องการนโยบายรวมศูนย์ - sidecar โครงสร้างพื้นฐานขนาดใหญ่ที่พันธมิตรภายนอกต้องการ IP ขาออกคงที่และการตรวจสอบทราฟฟิกทั้งหมด - egress gateway บ่อยครั้งมีการผสมระดับ: egress gateway สำหรับการควบคุมพื้นฐาน บวกตัวแปรสภาพแวดล้อมสำหรับปรับแต่งพอดเฉพาะผ่าน Proxeon
ตัวแปรสภาพแวดล้อมใน manifest และบทบาทของ NO_PROXY
มาถึงรายละเอียดที่ถูกมองข้ามมากที่สุด ตัวแปร HTTP_PROXY, HTTPS_PROXY และ NO_PROXY ใน manifest กำหนดผ่านบล็อก env ในสเปกของคอนเทนเนอร์ ชื่อแบบดั้งเดิมมักถูกทำซ้ำเป็นตัวพิมพ์เล็กเพราะไลบรารีบางตัวอ่านเฉพาะรูปแบบตัวพิมพ์เล็ก
apiVersion: apps/v1
kind: Deployment
metadata:
name: worker
spec:
replicas: 2
selector:
matchLabels:
app: worker
template:
metadata:
labels:
app: worker
spec:
containers:
- name: app
image: registry.example.com/worker:1.4.0
env:
- name: HTTP_PROXY
value: "http://gate.proxeon.net:8080"
- name: HTTPS_PROXY
value: "http://gate.proxeon.net:8080"
- name: NO_PROXY
value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.svc.cluster.local,.cluster.local,kubernetes.default"
- name: http_proxy
value: "http://gate.proxeon.net:8080"
- name: https_proxy
value: "http://gate.proxeon.net:8080"
- name: no_proxy
value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.svc.cluster.local,.cluster.local,kubernetes.default"ทำไมคลัสเตอร์พังถ้า NO_PROXY ไม่ถูกต้อง
นี่คือแก่นของปัญหา ทันทีที่คุณประกาศ HTTP_PROXY ไคลเอนต์ HTTP ของแอปพลิเคชันจะส่งคำขอทั้งหมดผ่านพร็อกซี รวมถึงการเรียกไปยังเซอร์วิสข้างเคียงในคลัสเตอร์ แต่เซอร์วิสภายในเข้าถึงได้เฉพาะในเครือข่ายคลัสเตอร์เท่านั้น พร็อกซีภายนอกไปไม่ถึงมันทางกายภาพ ผลลัพธ์: คำขอไปยัง orders.default.svc.cluster.local วิ่งไปที่พร็อกซีภายนอก ซึ่งพยายาม resolve ชื่อนี้ ไม่ได้ แล้วคืนข้อผิดพลาด การสื่อสารภายในพังทันที
NO_PROXY คือรายการยกเว้น ที่อยู่และโดเมนที่ต้องข้ามพร็อกซีและส่งตรง บนเซิร์ฟเวอร์ทั่วไปมักใส่ localhost กับซับเน็ตภายในไม่กี่ตัว ใน Kubernetes รายการนี้กลายเป็นสิ่งสำคัญอย่างยิ่ง เพราะต้องครอบคลุมโครงสร้างภายในทั้งหมดของคลัสเตอร์ พลาดซับฟิกซ์หนึ่งตัว แล้วทราฟฟิกระหว่างเซอร์วิสบางส่วนก็วิ่งผ่านพร็อกซีภายนอกทันที
สิ่งที่ NO_PROXY ของคลัสเตอร์ต้องมี
- localhost และ 127.0.0.1 - การเรียกภายในพอดเอง
- ช่วง Pod CIDR - ซับเน็ตที่แจกที่อยู่ให้พอด เช่น 10.0.0.0/8 หรือช่วงที่เจาะจงของคลัสเตอร์คุณ
- ช่วง Service CIDR - ซับเน็ตของ ClusterIP เซอร์วิส มักเป็น 10.96.0.0/12
- ซับฟิกซ์ DNS ของคลัสเตอร์ - .svc, .svc.cluster.local, .cluster.local ซึ่งครอบคลุมชื่อ DNS ภายในทั้งหมดของเซอร์วิส
- kubernetes.default - ชื่อของ API server ที่แอปและเอเจนต์เรียกหา
- เมทาดาทาของคลาวด์ - ที่อยู่ 169.254.169.254 ถ้าคุณอยู่ในสภาพแวดล้อมคลาวด์ เพื่อให้การเรียกเมทาดาทาไม่วิ่งผ่านพร็อกซี
รายละเอียดไวยากรณ์ของ NO_PROXY
ตรงนี้มีความไม่ชัดเจนซ่อนอยู่เต็มไปหมด ซึ่งแม้แต่วิศวกรมากประสบการณ์ก็ยังสะดุด
ประการแรก ไลบรารีต่าง ๆ ตีความรายการต่างกัน บางตัวถือว่า .cluster.local ที่มีจุดนำหน้าหมายถึงซับฟิกซ์และจะตรงกับซับโดเมนทั้งหมด อีกบางตัวต้องใช้รูปแบบ cluster.local โดยไม่มีจุด แนวปฏิบัติ: ระบุทั้งสองแบบ ทั้งมีจุดและไม่มี เพื่อครอบคลุมไคลเอนต์ให้มากที่สุด
ประการที่สอง การรองรับสัญกรณ์ CIDR ไม่เป็นสากล ไลบรารี Go เข้าใจ 10.0.0.0/8 แต่ไคลเอนต์เวอร์ชันเก่าบางตัวในภาษาอื่นไม่เข้าใจ ต้องใช้ที่อยู่แต่ละตัวหรือช่วงในรูปแบบอื่น ตรวจสอบพฤติกรรมของสแต็กคุณเอง
ประการที่สาม พอร์ต ถ้ารายการใน NO_PROXY ระบุโดยไม่มีพอร์ต ปกติจะครอบคลุมทุกพอร์ตของโฮสต์นั้น แต่ไคลเอนต์บางตัวจับคู่กับพอร์ตแบบตรงตัว เมื่อเซอร์วิสภายในใช้พอร์ตที่ไม่มาตรฐาน เรื่องนี้สำคัญต้องตรวจสอบ
ข้อสังเกตจากประสบการณ์: เก้าในสิบของเหตุการณ์การสื่อสารภายในพังหลังนำพร็อกซีมาใช้ คือ NO_PROXY ที่ไม่ครบถ้วน จัดทำรายการมาตรฐานสำหรับคลัสเตอร์คุณสักครั้ง เก็บไว้ใน ConfigMap กลาง แล้วนำกลับมาใช้ใหม่ในทุกดีพลอยเมนต์ ช่วยประหยัดเวลาดีบักได้หลายสิบชั่วโมง
อะไรบ้างที่ไม่รับตัวแปรสภาพแวดล้อม
ความเชื่อแบบง่าย ๆ ที่ว่า “ใส่ HTTP_PROXY แล้ว ทราฟฟิกทั้งหมดก็วิ่งผ่านพร็อกซี” ในคลัสเตอร์นั้นผิดสองต่อ มีคอมโพเนนต์ทั้งกลุ่มที่เพิกเฉยต่อตัวแปรเหล่านี้ ต้องรู้ไว้ล่วงหน้า
kubelet และคอมโพเนนต์ระบบ
ตัวแปรสภาพแวดล้อมของคอนเทนเนอร์มองเห็นได้เฉพาะโปรเซสในคอนเทนเนอร์นั้น kubelet ซึ่งเป็นเอเจนต์ของโหนดที่ดึงอิมเมจ เริ่มคอนเทนเนอร์ และคุยกับ API server อาศัยอยู่ในระดับโหนด ไม่ใช่พอด มันไม่อ่าน env จาก manifest ถ้าคุณต้องการให้ kubelet ดึงอิมเมจผ่านพร็อกซี ให้ตั้งค่าที่ระดับ systemd service ของ kubelet หรือคอนฟิกของ container runtime ไม่ใช่ในสเปกของพอด
# /etc/systemd/system/kubelet.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://gate.proxeon.net:8080"
Environment="HTTPS_PROXY=http://gate.proxeon.net:8080"
Environment="NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,.svc,.cluster.local"เช่นเดียวกันกับ container runtime อย่าง containerd หรือ CRI-O การดึงอิมเมจวิ่งผ่านบริบทเครือข่ายของมันเอง การตั้งค่าพร็อกซีสำหรับ registry อิมเมจกำหนดในคอนฟิกของ runtime ซึ่งเป็นเรื่องแยกจากพอด
อิมเมจที่มีคอนฟิกของตัวเอง
อิมเมจยอดนิยมหลายตัวมีค่าตั้งเครือข่ายในตัวที่เขียนทับตัวแปรสภาพแวดล้อม ตัวอย่างเช่น ตัวจัดการแพ็กเกจ เว็บเซิร์ฟเวอร์ หรือเครื่องมือพร็อกซีในอิมเมจอาจอ่านไฟล์คอนฟิกของตัวเองแทนสภาพแวดล้อม ถ้าแอปในคอนเทนเนอร์ใช้ไฟล์ตั้งค่าที่ระบุพารามิเตอร์การเชื่อมต่อชัดเจน ตัวแปร env ของคุณจะไม่ถูกมองเห็นเลย
อีกหมวดหมู่แยกต่างหากคือแอปที่ไคลเอนต์เครือข่ายเริ่มทำงานก่อนอ่านสภาพแวดล้อม หรือแคชค่าเมื่อเริ่มต้น การเปลี่ยนตัวแปรโดยไม่รีสตาร์ทโปรเซสจะไม่มีผล
SDK และรันไทม์ภาษาต่าง ๆ
นี่คือกลุ่มที่ร้ายกาจที่สุด การเคารพ HTTP_PROXY เป็นข้อตกลง ไม่ใช่มาตรฐาน บางตัวทำตาม บางตัวไม่ทำ
- Go http.Client มาตรฐานผ่าน ProxyFromEnvironment เคารพตัวแปร แต่ถ้าโค้ดสร้าง transport ที่ตั้ง Proxy เป็น nil ตัวแปรจะถูกละเลย
- Python ไลบรารี requests อ่านสภาพแวดล้อมโดยค่าเริ่มต้น แต่ซ็อกเก็ตระดับต่ำ ไคลเอนต์ gRPC บางตัว และไลบรารีแบบอะซิงโครนัส ไม่ทำ
- Java JVM ใช้พร็อพเพอร์ตีของระบบเองคือ http.proxyHost และ https.proxyHost และโดยค่าเริ่มต้นไม่อ่านตัวแปรสภาพแวดล้อมเลย ต้องส่งผ่าน JAVA_TOOL_OPTIONS
- Node.js โมดูล http ในตัวไม่เคารพตัวแปรสภาพแวดล้อม ต้องใช้เอเจนต์จากภายนอกที่อ่านมัน
- gRPC บางอิมพลีเมนเทชันอ่านตัวแปรพิเศษชื่อ grpc_proxy ไม่ใช่ตัวแปรมาตรฐาน
ข้อสรุปง่าย ๆ: อย่าสันนิษฐานว่าแค่ประกาศตัวแปรแล้วทราฟฟิกทั้งหมดจะวิ่งผ่านมัน ต้องตรวจสอบแต่ละรันไทม์แยกกัน ตัวอย่างเช่นสำหรับ JVM manifest จะมีลักษณะดังนี้:
env:
- name: JAVA_TOOL_OPTIONS
value: "-Dhttp.proxyHost=gate.proxeon.net -Dhttp.proxyPort=8080 -Dhttps.proxyHost=gate.proxeon.net -Dhttps.proxyPort=8080 -Dhttp.nonProxyHosts=localhost|127.0.0.1|*.svc|*.cluster.local"สังเกตว่าตัวคั่นข้อยกเว้นใน JVM คือเครื่องหมายขีดตั้ง ไม่ใช่เครื่องหมายจุลภาค และแพตเทิร์นใช้เครื่องหมายดาว นี่เป็นอีกกับดักสำหรับคนที่คัดลอก NO_PROXY มาอย่างกลไก
ความลับ: เก็บเครดิตพร็อกซีใน Secret ไม่ใช่ใน manifest
ถ้าพร็อกซีของคุณต้องการการยืนยันตัวตน ก็เกิดคำถามว่าจะเก็บล็อกอินกับรหัสผ่านไว้ที่ไหน ใจมันอยากจะเขียนลงใน URL ใน env ตรง ๆ แต่ทำแบบนั้นไม่ได้ และนี่คือเหตุผล
Manifest ของดีพลอยเมนต์เกือบทั้งหมดอยู่ในระบบควบคุมเวอร์ชัน รหัสผ่านแบบเปิดเผยใน env คือการรั่วไหลเครดิตเข้าสู่ประวัติ Git ที่ทุกคนที่มีสิทธิ์เข้าถึงรีโปเห็นได้ อีกทั้งค่าของ env มองเห็นได้โดยใครก็ตามที่รัน kubectl describe pod นี่เป็นการละเมิดหลักการสิทธิ์น้อยที่สุดโดยตรง
เส้นทางที่ถูกต้องคือออบเจ็กต์ Secret ซึ่งเก็บข้อมูลอ่อนไหวแยกต่างหาก พร้อมความสามารถในการจำกัดการเข้าถึงผ่าน RBAC และเปิดการเข้ารหัสในที่จัดเก็บ etcd
apiVersion: v1
kind: Secret
metadata:
name: proxeon-credentials
type: Opaque
stringData:
proxy-user: my_account
proxy-pass: s3cr3t_token_valueจากนั้นเชื่อมค่าจาก secret เข้าสู่ตัวแปรสภาพแวดล้อมของคอนเทนเนอร์ผ่าน secretKeyRef และประกอบ URL ของพร็อกซีเองเพื่อไม่ให้เครดิตโผล่ใน manifest
env:
- name: PROXY_USER
valueFrom:
secretKeyRef:
name: proxeon-credentials
key: proxy-user
- name: PROXY_PASS
valueFrom:
secretKeyRef:
name: proxeon-credentials
key: proxy-pass
- name: HTTPS_PROXY
value: "http://$(PROXY_USER):$(PROXY_PASS)@gate.proxeon.net:8080"
- name: NO_PROXY
value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.cluster.local"Kubernetes แทนค่าได้จากตัวแปรที่ประกาศไว้ก่อนหน้าผ่านไวยากรณ์ที่มีเครื่องหมายดอลลาร์และวงเล็บ ช่วยให้ประกอบ URL ที่มีเครดิตในรันไทม์โดยไม่ต้องฝังรหัสผ่านลงในตัวอักษรของ manifest โดยตรง ระวังไว้: ค่าที่ประกอบเป็น HTTPS_PROXY ยังคงมองเห็นได้ใน env ของคอนเทนเนอร์ที่รันอยู่ ดังนั้นควรจำกัดเพิ่มว่าใครสามารถ exec และ describe พอดเหล่านี้ได้
แนวปฏิบัติที่ดีกับ secret
- เปิดการเข้ารหัส etcd at rest ไม่เช่นนั้น secret จะถูกเก็บแค่ base64 ซึ่งไม่ใช่การป้องกัน
- จำกัดการเข้าถึง secret ผ่าน RBAC: พอดควรเห็นเฉพาะ secret ของตัวเอง
- หมุนเวียนเครดิตพร็อกซีเป็นประจำ และทำให้การรีสตาร์ทพอดหลังหมุนเวียนเป็นอัตโนมัติ
- พิจารณาตัวจัดการความลับภายนอกที่เชื่อมผ่าน CSI driver เพื่อไม่ให้เครดิตเข้าไปอยู่ใน etcd เลย
- ห้ามล็อก URL พร็อกซีที่ประกอบแล้วเด็ดขาด รหัสผ่านจะรั่วเข้าสู่ระบบเก็บล็อก
แนวทาง sidecar: ใช้เมื่อไหร่ที่คุ้ม และให้อะไร
เราได้กล่าวถึง sidecar ในฐานะหนึ่งในสามระดับไปแล้ว ตอนนี้มาดูมันในฐานะกลยุทธ์อิสระให้ละเอียดขึ้น เพราะนี่คือเครื่องมือที่ยืดหยุ่นที่สุด ถ้าคุณพร้อมจ่ายด้วยทรัพยากร
เมื่อไหร่ sidecar คุ้มค่า
- แอปอ่าน HTTP_PROXY ไม่ได้ และแก้โค้ดมันไม่ได้หรือแพงเกินไป
- ต้องการนโยบายพร็อกซีรวมศูนย์โดยไม่ต้องแก้ทุกแอป
- ต้องการดักจับทราฟฟิกแบบโปร่งใส รวมถึงโปรโตคอลที่ไม่ใช่ HTTP
- ต้องการความสามารถในการสังเกตการณ์: เมตริกการเชื่อมต่อขาออก การติดตาม การตรวจสอบ
- ต้องการนโยบายลองใหม่ ไทม์เอาต์ การตัดวงจรในระดับการเชื่อมต่อ
sidecar ให้อะไรนอกเหนือการพร็อกซี
คุณค่าจริงซ่อนอยู่ตรงนี้ sidecar ไม่ใช่แค่ตัวเปลี่ยนเส้นทางแพ็กเก็ต ถ้าตั้งถูก มันกลายเป็นจุดควบคุมและสังเกตการณ์ทราฟฟิกขาออกทั้งหมดของพอด
- เมตริก มีคำขอออกภายนอกกี่ครั้ง ไปโฮสต์ไหน ด้วยความหน่วงเท่าไร มีข้อผิดพลาดกี่ครั้ง ถ้าไม่มี sidecar ข้อมูลเหล่านี้ต้องเก็บในแต่ละแอปแยกกัน
- การติดตาม เทรซแบบกระจายของการเรียกขาออกที่เชื่อมโยงกับคำขอขาเข้า
- นโยบายความน่าเชื่อถือ การลองใหม่ของคำขอ idempotent โดยอัตโนมัติ ไทม์เอาต์ การจำกัดจำนวนการเชื่อมต่อพร้อมกัน
- TLS รวมศูนย์ sidecar สามารถสิ้นสุดและตั้งการเชื่อมต่อที่ปลอดภัยได้จากส่วนกลาง
- การตรวจสอบและความสอดคล้อง บันทึกครบถ้วนว่าแอปวิ่งไปไหนบ้าง ซึ่งขาดไม่ได้สำหรับการปฏิบัติตามข้อกำหนด
ตัวอย่างพอดที่มี sidecar พร็อกซี
ด้านล่างเป็นเทมเพลตแบบย่อที่คอนเทนเนอร์หลักส่งคำขอ HTTP ขาออกไปยัง sidecar ในเครื่อง และ sidecar พร็อกซีต่อไปยัง Proxeon สำหรับแอปแล้วที่อยู่พร็อกซีคือ localhost ซึ่งตัดทราฟฟิกระหว่างเซอร์วิสจากการพร็อกซีภายนอกโดยอัตโนมัติ ถ้าแอปเรียกเพื่อนบ้านโดยตรง
apiVersion: v1
kind: Pod
metadata:
name: app-with-proxy-sidecar
labels:
app: billing
spec:
containers:
- name: app
image: registry.example.com/billing:2.1.0
env:
- name: HTTP_PROXY
value: "http://127.0.0.1:3128"
- name: HTTPS_PROXY
value: "http://127.0.0.1:3128"
- name: NO_PROXY
value: "localhost,127.0.0.1,10.0.0.0/8,.svc,.cluster.local"
- name: egress-proxy
image: registry.example.com/egress-agent:1.0.0
ports:
- containerPort: 3128
env:
- name: UPSTREAM_PROXY
value: "http://gate.proxeon.net:8080"
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128MiNative sidecar และลำดับการเริ่ม
ปัญหาคลาสสิกของ sidecar คือการแข่งขันตอนเริ่ม ถ้าคอนเทนเนอร์หลักเริ่มและส่งคำขอขาออกแรกก่อนที่ sidecar จะพร้อมรับการเชื่อมต่อ คำขอจะล้มเหลว ตั้งแต่ Kubernetes 1.28 เป็นต้นมา มีการรองรับ native sidecar container: ประกาศในบล็อก initContainers ด้วย restartPolicy Always และเริ่มทำงานและอยู่ต่อจนกว่าคอนเทนเนอร์หลักจะเริ่ม ช่วยแก้ปัญหาการแข่งขันได้อย่างสะอาด ไม่ต้องใช้วิธีลัดอย่างการหน่วงหรือลองใหม่ตอนเริ่ม
spec:
initContainers:
- name: egress-proxy
image: registry.example.com/egress-agent:1.0.0
restartPolicy: Always
ports:
- containerPort: 3128Egress gateway: ที่อยู่ขาออกที่เสถียรสำหรับคลัสเตอร์
ระดับที่สามสมควรได้พูดถึงแยก เพราะมันแก้ปัญหาที่คนมักหันมาหาพร็อกซีในคลัสเตอร์จริง ๆ: IP ขาออกที่คาดเดาได้
ทำไมต้องมี IP ขาออกคงที่
พันธมิตรภายนอก เกตเวย์ชำระเงิน ผู้ให้ข้อมูล มักทำงานด้วยรายการที่อยู่ที่อนุญาต พวกเขาบอกว่า: เรารับคำขอจาก IP เหล่านี้เท่านั้น ในคลัสเตอร์ทั่วไป ที่อยู่ขาออกของพอดคือที่อยู่ของโหนดที่พอดบังเอิญอยู่ตอนนั้น โหนดมีหลายตัว ปรับขนาด ทดแทน เพิ่มตอน autoscaling ที่อยู่คาดเดาไม่ได้ พันธมิตรไม่สามารถใส่พูลโหนดทั้งหมดที่เปลี่ยนไปมาได้
Egress gateway แก้ปัญหานี้: ทราฟฟิกขาออกทั้งหมดถูกรวบรวมไว้ที่จุดเดียวที่มีที่อยู่คงที่ พันธมิตรใส่ IP ที่เสถียรหนึ่งหรือสองตัวลงในรายการที่อนุญาต แล้วทุกอย่างก็ทำงาน เมื่อใช้ Proxeon เป็นจุดขาออก คุณจะได้ที่อยู่ภายนอกที่เสถียร ซึ่งให้พันธมิตรครั้งเดียวจบ
ทราฟฟิกถูกกำหนดเส้นทางไป Egress gateway อย่างไร
กลไกขึ้นอยู่กับ CNI บาง CNI plugin รองรับออบเจ็กต์ egress policy ที่คุณอธิบายว่า: ทราฟฟิกจากพอดที่มีเลเบลนี้ ออกภายนอก ต้องออกผ่านโหนดหรือที่อยู่นี้ plugin จะตั้งกฎ SNAT และการกำหนดเส้นทางที่สอดคล้องกันโดยอัตโนมัติ ในเชิงแนวคิดมีลักษณะแบบนี้:
apiVersion: policy.example.io/v1
kind: EgressPolicy
metadata:
name: billing-egress
spec:
selector:
matchLabels:
egress: proxeon
egressIP: 203.0.113.10
destinationCIDRs:
- 0.0.0.0/0ไวยากรณ์ที่แน่นอนแตกต่างกันในแต่ละ CNI ควรตรวจสอบกับเอกสารของ plugin คุณ แนวคิดไม่เปลี่ยน: ติดเลเบลพอดที่ทราฟฟิกจะวิ่งผ่านเกตเวย์ และกำหนดที่อยู่ขาออกคงที่
ความน่าเชื่อถือของ egress gateway
เนื่องจากเกตเวย์เป็นจุดเดียว จึงเป็นจุดล้มเหลวเดียวด้วย กฎการอยู่รอด:
- มีสำรอง: โหนดเกตเวย์อย่างน้อยสองตัวพร้อมการสลับอัตโนมัติ
- มอนิเตอร์ความพร้อมใช้งานของทางออกด้วยคำขอสังเคราะห์ออกภายนอกทุกนาที
- จับตาปริมาณงาน: ทราฟฟิกเหนือ-ใต้ทั้งหมดไหลผ่านเกตเวย์ คอขวดจะส่งผลทั้งระบบ
- แยกนโยบาย: ทราฟฟิกวิกฤตและทราฟฟิกพื้นหลังควรแยกเส้นทาง เพื่อให้โหลดพื้นหลังไม่รบกวนงานสำคัญ
การวินิจฉัยทราฟฟิกขาออกในคลัสเตอร์
เมื่ออะไรไม่ไปอย่างที่ควร ซึ่งมันจะเกิด ต้องมีคลังการตรวจสอบ รวบรวมชุดคำสั่งและแนวทางปฏิบัติ
ขั้นตอนที่ 1: หา IP ขาออกจริงจากพอด
สิ่งแรกที่ตรวจสอบ: ที่อยู่ที่พอดมองเห็นจากภายนอก รันคอนเทนเนอร์ดีบักแบบชั่วคราวหรือ exec เข้าไปในพอดที่มี แล้วเรียกไปยังเซอร์วิสที่คืนที่อยู่สาธารณะของคุณ
kubectl run nettest --rm -it --image=registry.example.com/nettools:1.0 --restart=Never -- sh
# ภายในคอนเทนเนอร์
curl -s https://api.ipify.org
curl -s -x http://gate.proxeon.net:8080 https://api.ipify.orgคำขอแรกแสดงที่อยู่โดยไม่ผ่านพร็อกซี คำขอที่สองผ่านพร็อกซีอย่างชัดเจน ถ้าทั้งสองเหมือนกันในขณะที่คุณคาดว่าต่างกัน แสดงว่าพร็อกซีไม่ได้ถูกนำมาใช้ ถ้าคำขอที่สองคืนที่อยู่ที่คาดหวังของ Proxeon แสดงว่าพร็อกซีทำงาน และปัญหาอยู่ที่คอนฟิกของแอป
ขั้นตอนที่ 2: ตรวจสอบว่าแอปมองเห็นตัวแปรหรือไม่
kubectl exec deploy/worker -c app -- env | grep -i proxyถ้าผลลัพธ์ว่างเปล่า ตัวแปรไม่ได้ไปถึงคอนเทนเนอร์ ตรวจสอบ manifest และพอดถูกสร้างใหม่หลังการเปลี่ยนแปลงหรือไม่ จำไว้ว่า: การเปลี่ยน env ต้องสร้างพอดใหม่ ไม่สามารถรับค่าระหว่างรันได้ทันที
ขั้นตอนที่ 3: ตรวจสอบการ resolve DNS จากภายใน
หลายข้อผิดพลาดถูกกลบด้วยปัญหาพร็อกซี แต่จริง ๆ แล้วเป็น DNS ตรวจสอบการแก้ชื่อภายในและภายนอก:
kubectl exec deploy/worker -c app -- nslookup orders.default.svc.cluster.local
kubectl exec deploy/worker -c app -- nslookup api.external-partner.comถ้าชื่อภายใน resolve ไม่ได้ ปัญหาอยู่ที่ CoreDNS หรือคำขอวิ่งไปพร็อกซีภายนอกเพราะขาดซับฟิกซ์ใน NO_PROXY นี่คือการยืนยันตรงว่ารายการยกเว้นไม่ครบ
ขั้นตอนที่ 4: ดูความล้มเหลวที่ไหน
- ล็อกของแอป ค้นหาข้อผิดพลาดการเชื่อมต่อ ไทม์เอาต์ การปฏิเสธการยืนยันตัวตนพร็อกซี (มักเป็นรหัส 407)
- ล็อกของ sidecar ถ้ามี จะเห็นว่ามันรับคำขออะไรและส่งต่อไปไหน
- เมตริกของ egress gateway การเพิ่มขึ้นของความล้มเหลวและความหน่วงที่เกตเวย์
- เหตุการณ์ของพอด kubectl describe pod จะแสดงปัญหาเริ่มต้น รวมถึงการแข่งขันของ sidecar
- NetworkPolicy ตรวจสอบว่านโยบายเครือข่ายปิดกั้นทราฟฟิกขาออกหรือไม่ ซึ่งเป็นสาเหตุที่พบบ่อยของความล้มเหลวแบบเงียบ
ลายเซ็นของปัญหาที่พบบ่อย
- รหัส 407 จากพร็อกซี - เครดิตผิดหรือไม่มี ตรวจสอบ Secret
- ไทม์เอาต์เมื่อเรียกเซอร์วิสภายในหลังเปิดพร็อกซี - NO_PROXY ไม่ครบ
- IP ขาออกต่างกันในคำขอซ้ำ ๆ - ทราฟฟิกไม่ผ่าน egress gateway ใช้ SNAT ปกติของโหนด
- คำขอแรกหลังเริ่มล้ม แล้วค่อยทำงานได้ - การแข่งขันของ sidecar เปลี่ยนไปใช้ native sidecar
- แอป Java เพิกเฉยต่อพร็อกซี - ลืม JAVA_TOOL_OPTIONS และพร็อพเพอร์ตีของระบบ
ข้อผิดพลาดทั่วไปที่ควรหลีกเลี่ยง
รวบรวมกับดักที่คนเหยียบกันบ่อยที่สุดไว้ที่เดียว ตรวจสอบตัวเองด้วยรายการนี้ก่อน ไม่ใช่หลังเกิดเหตุการณ์
NO_PROXY ไม่ครบ
แชมป์อันดับหนึ่ง ลืม Service CIDR ลืมซับฟิกซ์ .svc ไม่นึกถึงที่อยู่เมทาดาทาของคลาวด์ แล้วก็ได้ความล้มเหลวลึกลับเป็นทอด ๆ ให้ยึดรายการมาตรฐานที่ครบถ้วนสำหรับคลัสเตอร์คุณเสมอ
รหัสผ่านพร็อกซีแบบเปิดเผยใน manifest
รั่วไหลเข้าสู่ Git และผลลัพธ์ของ describe ใช้ Secret เท่านั้น และจำกัดการเข้าถึงเท่านั้น
สันนิษฐานว่าทุกรันไทม์เคารพตัวแปร
JVM, Node.js, ไคลเอนต์ gRPC บางตัวเพิกเฉยต่อ HTTP_PROXY ตรวจสอบทุกสแต็กแยกกันและตั้งค่าด้วยวิธีเฉพาะของมัน
มองข้าม kubelet และ runtime
การดึงอิมเมจผ่านพร็อกซีตั้งค่าที่ระดับโหนด ไม่ใช่พอด อย่าแปลกใจว่าทำไมอิมเมจดึงไม่ได้ ถ้าตั้งแค่ env ใน manifest
การแข่งขันตอนเริ่ม sidecar
แอปเริ่มก่อนพร็อกซีและทำคำขอแรก ๆ ล้ม เหทางแก้คือ native sidecar containers
Egress gateway ที่ไม่มีสำรอง
จุดล้มเหลวเดียวที่ไม่มีการทำซ้ำจะทำให้ทราฟฟิกขาออกทั้งหมดล่ม ให้มีสำรองและมอนิเตอร์
ผสมรูปแบบ CIDR
ระบุ 10.0.0.0/8 ให้ไคลเอนต์ที่ไม่เข้าใจ CIDR ตรวจสอบว่ารูปแบบใดที่ไลบรารีคุณรับได้ และมีทางเลือกให้
ไม่สร้างพอดใหม่หลังเปลี่ยน env
เปลี่ยนตัวแปรในดีพลอยเมนต์ แต่พอดเก่ายังทำงานกับค่าเดิมจนกว่าจะรีสตาร์ท ทำให้แน่ใจว่ามีการโรลเอาต์
ลืมตัวพิมพ์เล็กของตัวแปร
ไลบรารีบางตัวอ่านเฉพาะ http_proxy ตัวพิมพ์เล็ก ทำซ้ำชื่อในทั้งสองแบบ
เครื่องมือและแหล่งข้อมูล
สิ่งที่ควรมีไว้ใกล้มือสำหรับงานทราฟฟิกขาออกในคลัสเตอร์
ด้านการวินิจฉัย
- kubectl exec และ kubectl debug - พื้นฐานสำหรับตรวจสอบจากภายในพอด
- คอนเทนเนอร์ดีบักชั่วคราว - ช่วยเชื่อมชุดเครื่องมือเครือข่ายเข้ากับพอดที่ทำงานอยู่โดยไม่ต้องสร้างอิมเมจใหม่
- อิมเมจที่มีเครื่องมือเครือข่าย - curl, dig, nslookup, traceroute รวมในคอนเทนเนอร์เดียวเพื่อการตรวจสอบที่รวดเร็ว
- เซอร์วิสคืน IP สาธารณะ - ตรวจสอบที่อยู่ขาออกจริง
ด้านโครงสร้างพื้นฐาน
- ConfigMap ที่มี NO_PROXY มาตรฐาน - แหล่งความจริงเดียวสำหรับทุกดีพลอยเมนต์
- Secret และตัวจัดการความลับภายนอก - สำหรับเครดิตพร็อกซี
- Service mesh - ถ้าต้องการ sidecar พร้อมการสังเกตการณ์ในตัว
- Egress policy ของ CNI - สำหรับกำหนดเส้นทางไปเกตเวย์
- Proxeon (proxeon.net) - โครงสร้างพื้นฐานพร็อกซีสำหรับที่อยู่ขาออกเสถียรและการเข้าถึงที่ยืนยันตัวตน
การมอนิเตอร์
- เมตริกการเชื่อมต่อขาออกจาก sidecar หรือ egress gateway
- การตรวจสอบสังเคราะห์ความพร้อมใช้งานของที่อยู่ภายนอกจากในคลัสเตอร์
- การแจ้งเตือนเมื่อรหัส 407 และไทม์เอาต์ของคำขอขาออกเพิ่มขึ้น
- แดชบอร์ดที่แสดงการกระจายทราฟฟิกขาออกตามโฮสต์ปลายทาง
เคสและผลลัพธ์
มาดูสามสถานการณ์ทั่วไปที่แสดงว่าการเลือกระดับส่งผลต่อผลลัพธ์อย่างไร
เคส 1: การเชื่อมต่อระบบชำระเงินกับรายการ IP ที่อนุญาต
ทีมเชื่อมต่อเกตเวย์ชำระเงินภายนอกที่รับคำขอเฉพาะจากที่อยู่ที่ตกลงไว้ ตอนแรกใช้ตัวแปรสภาพแวดล้อมในระดับคอนเทนเนอร์ แล้วก็เจอปัญหา: ตอน autoscaling พอดกระจายไปยังโหนดใหม่ที่มีที่อยู่ใหม่ แต่ IP ขาออกก็ยังคงเป็นที่อยู่ของโหนด ไม่ใช่พร็อกซี คำขอบางส่วนเริ่มถูกปฏิเสธ
ทางแก้: ย้ายเซอร์วิสชำระเงินไปใช้ egress ผ่าน Proxeon ที่มีที่อยู่ขาออกคงที่ พันธมิตรใส่ IP เดียวลงในรายการที่อนุญาต การปฏิเสธที่เกิดจากที่อยู่ไม่รู้จักหายไป ผลพลอยได้คือบันทึกคำขอทั้งหมดไปยังเกตเวย์ชำระเงินจากส่วนกลางเพื่อการตรวจสอบ
เคส 2: การสื่อสารระหว่างเซอร์วิสขาดหลังนำพร็อกซีมาใช้
บริษัทเพิ่ม HTTP_PROXY เข้าทุกดีพลอยเมนต์ในคราวเดียวผ่านเทมเพลตกลาง ไม่กี่นาทีต่อมา ข้อผิดพลาดก็ถาโถม: เซอร์วิสหากันไม่เจอ การเรียกภายในวิ่งไปพร็อกซีภายนอก ซึ่ง resolve ไม่ได้
การวินิจฉัยใช้เวลา จนเมื่อตรวจสอบการ resolve จากในพอดและเห็นว่าชื่อ .svc.cluster.local วิ่งไปพร็อกซี สาเหตุคือ NO_PROXY มีแค่ localhost จึงจัดทำรายการมาตรฐานที่ครบถ้วนพร้อม Pod CIDR, Service CIDR และซับฟิกซ์ DNS ทั้งหมด เก็บใน ConfigMap แล้วเชื่อมเข้ากับทุกพอด การสื่อสารภายในกลับมาทำงาน ตั้งแต่นั้น NO_PROXY มาตรฐานก็เป็นส่วนบังคับของเทมเพลตดีพลอยเมนต์
เคส 3: การสังเกตการณ์ทราฟฟิกขาออกผ่าน sidecar
ข้อกำหนดด้านความปลอดภัย: ต้องรู้ว่าแอปแต่ละตัววิ่งออกไปไหนบ้าง พร้อมบันทึกครบถ้วน แอปเขียนด้วยภาษาต่างกัน บางตัวอ่าน HTTP_PROXY ไม่ได้ การแก้โค้ดเซอร์วิสหลายสิบตัวแพงเกินไป
นำ sidecar พร็อกซีเข้าใส่ในเทมเพลตพอด แอปส่งทราฟฟิกขาออกไปยังเอเจนต์ในเครื่อง ซึ่งพร็อกซีผ่าน Proxeon และเขียนเมตริก: โฮสต์ปลายทาง ความหน่วง รหัสตอบกลับ เกิดแดชบอร์ดการเรียกขาออกแยกตามเซอร์วิส เพิ่มเติมคือตั้งไทม์เอาต์และลองใหม่ที่ระดับ sidecar ลดจำนวนความล้มเหลวแบบทอด ๆ เมื่อ API ภายนอกไม่พร้อมใช้งานชั่วคราว ราคาที่จ่ายคือทรัพยากรเพิ่มให้ sidecar แต่ผลตอบแทนด้านการสังเกตการณ์และความน่าเชื่อถือก็คุ้มค่า
FAQ: คำถามที่วิศวกรมักถาม
ทำไมทราฟฟิกไปพอดข้างเคียงถึงวิ่งผ่านพร็อกซีภายนอก ทั้งที่เห็นชัดว่าเป็นที่อยู่ภายใน?
เพราะไคลเอนต์ HTTP ไม่รู้ว่าที่อยู่นั้นเป็นภายใน มันเห็นชื่อหรือที่อยู่ และถ้าไม่อยู่ใน NO_PROXY ก็ส่งคำขอไปพร็อกซีตามกฎ ไคลเอนต์ไม่แยกตะวันออก-ตะวันตกกับเหนือ-ใต้เอง รายการยกเว้นต่างหากที่ทำให้ ใส่ซับฟิกซ์และซับเน็ตภายในลงใน NO_PROXY
ต้องตั้งพร็อกซีสำหรับดึงอิมเมจด้วยไหม?
ต้อง แต่ไม่ใช่ผ่าน env ของพอด การดึงอิมเมจทำโดย container runtime และ kubelet ในระดับโหนด พร็อกซีสำหรับ registry ตั้งค่าในคอนฟิกของ runtime หรือ systemd unit ของ kubelet ตัวแปรสภาพแวดล้อมของคอนเทนเนอร์ไม่มีผลเลย
อะไรสำคัญกว่ากันสำหรับ IP ขาออกที่เสถียร sidecar หรือ egress gateway?
Egress gateway sidecar พร็อกซีทราฟฟิกของพอดเดี่ยว แต่ที่อยู่ขาออกยังถูกกำหนดโดยที่ที่การเชื่อมต่อไป สำหรับที่อยู่คงที่ที่พันธมิตรภายนอกจะรับ ต้องมี egress gateway หรือออกผ่านจุดภายนอกที่มีที่อยู่ถาวร เช่นผ่าน Proxeon
ทำไมแอป Java ถึงเพิกเฉยต่อ HTTP_PROXY?
JVM ไม่ได้อ่านตัวแปรสภาพแวดล้อมสำหรับพร็อกซีด้วยเหตุผลทางประวัติศาสตร์ มันใช้พร็อพเพอร์ตีของระบบเองคือ http.proxyHost, https.proxyHost และข้อยกเว้น http.nonProxyHosts ส่งผ่าน JAVA_TOOL_OPTIONS และจำไว้ว่าตัวคั่นข้อยกเว้นใน JVM คือเครื่องหมายขีดตั้ง และแพตเทิร์นใช้เครื่องหมายดาว ไม่ใช่ CIDR
จะเก็บรหัสผ่านพร็อกซีอย่างปลอดภัยได้อย่างไร?
ในออบเจ็กต์ Secret ที่จำกัดการเข้าถึงด้วย RBAC และเปิดการเข้ารหัส etcd เชื่อมค่าผ่าน secretKeyRef อย่าเขียนรหัสผ่านแบบเปิดเผยใน manifest ไม่เช่นนั้นจะรั่วเข้าสู่ Git และผลลัพธ์ describe สำหรับข้อกำหนดที่สูงขึ้น ใช้ตัวจัดการความลับภายนอกผ่าน CSI
เปลี่ยนตัวแปรสภาพแวดล้อมแล้ว พฤติกรรมไม่เปลี่ยน ทำไม?
ตัวแปรสภาพแวดล้อมถูกตรึงเมื่อคอนเทนเนอร์เริ่ม จนกว่าพอดจะถูกสร้างใหม่ มันจะทำงานกับค่าเดิม อัปเดตดีพลอยเมนต์ให้มีการโรลเอาต์พอดด้วย ตรวจสอบด้วยว่าแอปอ่านสภาพแวดล้อมจริงหรือแคชค่าเมื่อเริ่มต้น
จะรู้ได้อย่างไรว่า NO_PROXY รูปแบบไหนที่ไลบรารีของฉันต้องการ?
เชิงประจักษ์: ตั้งพร็อกซี เรียกชื่อภายในจากในพอด แล้วดูว่าคำขอวิ่งไปพร็อกซีหรือตรง ถ้าวิ่งไปพร็อกซี รูปแบบไม่ถูกจดจำ ลองทั้งแบบมีจุดนำหน้าและไม่มี เพิ่มที่อยู่แต่ละตัวแทน CIDR ตรวจสอบตัวพิมพ์ของชื่อตัวแปร เอกสารของไคลเอนต์เจาะจงคือแนวทางที่ดีที่สุด
ควรใช้ service mesh เพื่อการพร็อกซีเสมอไหม?
ไม่ Service mesh เป็นเครื่องมือทรงพลังที่มีการสังเกตการณ์และนโยบาย แต่มีโอเวอร์เฮดและความซับซ้อนในการดำเนินงานที่ชัดเจน ถ้างานคือแค่ส่งทราฟฟิกขาออกผ่านพร็อกซี เอเจนต์ sidecar แบบเบาหรือ egress gateway จะถูกกว่า นำ mesh มาใช้เมื่อคุณต้องการความสามารถทั้งหมดจริง ๆ
จะจัดการกับทราฟฟิกที่ไม่ใช่ HTTP อย่างไร?
ตัวแปร HTTP_PROXY ใช้ได้เฉพาะกับไคลเอนต์ HTTP และ HTTPS ที่เคารพมัน สำหรับการเชื่อมต่อ TCP ทั่วไป ต้องมีการดักจับแบบโปร่งใสที่ระดับ sidecar หรือ egress gateway พร้อมกฎกำหนดเส้นทางที่เหมาะสม ตรงนี้ตัวแปรสภาพแวดล้อมทำอะไรไม่ได้
กำหนดนโยบายพร็อกซีต่างกันสำหรับพอดต่างกันได้ไหม?
ได้ ในระดับคอนเทนเนอร์ ผ่าน env ต่างกันในดีพลอยเมนต์ต่างกัน ในระดับ egress ผ่าน selector ของเลเบลใน egress policy ส่งพอดที่ติดเลเบลไปยังทางออกที่ต้องการ การผสมผสานให้ความยืดหยุ่น: ทางออกพื้นฐานผ่านเกตเวย์บวกการตั้งค่าเฉพาะสำหรับเซอร์วิสบางตัว
สรุป: รวบรวมเช็คลิสต์การดีพลอย
เราเดินทางจากทำไมการตั้งค่า “เหมือนบนเซิร์ฟเวอร์ทั่วไป” ถึงไม่ทำงานในคลัสเตอร์ ไปจนถึงสามระดับการฝังพร็อกซี รายละเอียดของ NO_PROXY, Secret, sidecar, egress gateway และการวินิจฉัย ข้อสรุปหลักคือ: ใน Kubernetes ทราฟฟิกขาออกไม่ใช่แค่ตัวแปรตัวเดียว แต่เป็นระบบเป็นชั้น ๆ ที่สำคัญที่สุดไม่ใช่วิธีเปิดพร็อกซี แต่เป็นวิธีไม่ทำให้การสื่อสารภายในพังด้วยมัน
มารวบรวมเช็คลิสต์การดีพลอยฉบับสมบูรณ์ที่ควรมีไว้ตรงหน้าทุกครั้งที่นำไปใช้
เช็คลิสต์การดีพลอยพร็อกซีในคลัสเตอร์
- เลือกระดับแล้ว เลือกคอนเทนเนอร์ sidecar หรือ egress gateway อย่างมีสติ ตามงานเฉพาะ
- จัดทำ NO_PROXY ที่ครบถ้วน