ขีดจำกัดของ CPU และการควบคุมปริมาณเชิงรุกใน Kubernetes

บันทึก. แปล: ประวัติศาสตร์ที่เปิดหูเปิดตาของ Omio ซึ่งเป็นผู้รวบรวมการเดินทางในยุโรป นำผู้อ่านตั้งแต่ทฤษฎีพื้นฐานไปจนถึงความซับซ้อนในทางปฏิบัติที่น่าทึ่งของการกำหนดค่า Kubernetes ความคุ้นเคยกับกรณีดังกล่าวไม่เพียงแต่ช่วยเปิดโลกทัศน์ของคุณให้กว้างขึ้นเท่านั้น แต่ยังป้องกันปัญหาที่ไม่สำคัญอีกด้วย

ขีดจำกัดของ CPU และการควบคุมปริมาณเชิงรุกใน Kubernetes

คุณเคยประสบปัญหาแอปพลิเคชันค้าง หยุดตอบสนองต่อการตรวจสุขภาพ และคุณไม่สามารถทราบสาเหตุได้หรือไม่ คำอธิบายที่เป็นไปได้ประการหนึ่งเกี่ยวข้องกับขีดจำกัดโควต้าทรัพยากร CPU นี่คือสิ่งที่เราจะพูดถึงในบทความนี้

TL; DR:
เราขอแนะนำอย่างยิ่งให้ปิดใช้การจำกัด CPU ใน Kubernetes (หรือปิดใช้โควต้า CFS ใน Kubelet) หากคุณใช้เคอร์เนล Linux เวอร์ชันที่มีข้อบกพร่องโควต้า CFS ในแกนกลาง มี จริงจังและ เป็นที่รู้จักกันดี จุดบกพร่องที่นำไปสู่การควบคุมปริมาณและความล่าช้ามากเกินไป
.

ในโอมิโอะ โครงสร้างพื้นฐานทั้งหมดได้รับการจัดการโดย Kubernetes. ปริมาณงานแบบมีสถานะและไร้สถานะทั้งหมดของเราทำงานบน Kubernetes โดยเฉพาะ (เราใช้ Google Kubernetes Engine) ในช่วงหกเดือนที่ผ่านมา เราเริ่มสังเกตเห็นการชะลอตัวแบบสุ่ม แอปพลิเคชันค้างหรือหยุดตอบสนองต่อการตรวจสอบสภาพ ขาดการเชื่อมต่อกับเครือข่าย ฯลฯ พฤติกรรมนี้ทำให้เรางงงวยมาเป็นเวลานาน และในที่สุด เราก็ตัดสินใจที่จะแก้ไขปัญหานี้อย่างจริงจัง

สรุปบทความ:

  • คำไม่กี่คำเกี่ยวกับคอนเทนเนอร์และ Kubernetes
  • วิธีการใช้งานคำขอและขีดจำกัดของ CPU
  • ขีดจำกัดของ CPU ทำงานอย่างไรในสภาพแวดล้อมแบบมัลติคอร์
  • วิธีติดตามการควบคุมปริมาณ CPU
  • การแก้ปัญหาและความแตกต่าง

คำไม่กี่คำเกี่ยวกับคอนเทนเนอร์และ Kubernetes

Kubernetes ถือเป็นมาตรฐานสมัยใหม่ในโลกโครงสร้างพื้นฐาน หน้าที่หลักคือการจัดการคอนเทนเนอร์

ตู้คอนเทนเนอร์

ในอดีต เราต้องสร้างอาร์ติแฟกต์ เช่น Java JARs/WARs, Python Eggs หรือโปรแกรมปฏิบัติการเพื่อให้ทำงานบนเซิร์ฟเวอร์ อย่างไรก็ตาม เพื่อให้ทำงานได้ จำเป็นต้องมีการทำงานเพิ่มเติม เช่น การติดตั้งสภาพแวดล้อมรันไทม์ (Java/Python) วางไฟล์ที่จำเป็นในตำแหน่งที่ถูกต้อง ตรวจสอบให้แน่ใจว่าเข้ากันได้กับระบบปฏิบัติการเวอร์ชันเฉพาะ เป็นต้น กล่าวอีกนัยหนึ่ง จะต้องให้ความสนใจอย่างระมัดระวังต่อการจัดการการกำหนดค่า (ซึ่งมักเป็นสาเหตุให้เกิดความขัดแย้งระหว่างนักพัฒนาและผู้ดูแลระบบ)

คอนเทนเนอร์เปลี่ยนทุกอย่าง ตอนนี้สิ่งประดิษฐ์เป็นอิมเมจคอนเทนเนอร์ มันสามารถแสดงเป็นไฟล์ปฏิบัติการแบบขยายที่มีไม่เพียงแต่โปรแกรมเท่านั้น แต่ยังรวมถึงสภาพแวดล้อมการดำเนินการเต็มรูปแบบ (Java/Python/...) เช่นเดียวกับไฟล์/แพ็คเกจที่จำเป็น ที่ติดตั้งไว้ล่วงหน้าและพร้อมที่จะ วิ่ง. คุณสามารถปรับใช้และรันคอนเทนเนอร์บนเซิร์ฟเวอร์ที่แตกต่างกันได้โดยไม่ต้องมีขั้นตอนเพิ่มเติม

นอกจากนี้ คอนเทนเนอร์ยังทำงานในสภาพแวดล้อมแซนด์บ็อกซ์ของตัวเองอีกด้วย พวกเขามีอะแดปเตอร์เครือข่ายเสมือนของตัวเอง ระบบไฟล์ของตัวเองที่มีการเข้าถึงที่จำกัด ลำดับชั้นของกระบวนการของตัวเอง ข้อ จำกัด ของตัวเองบน CPU และหน่วยความจำ ฯลฯ ทั้งหมดนี้เกิดขึ้นได้ด้วยระบบย่อยพิเศษของเคอร์เนล Linux - เนมสเปซ

Kubernetes

ตามที่ระบุไว้ก่อนหน้านี้ Kubernetes เป็นผู้ควบคุมคอนเทนเนอร์ มันทำงานดังนี้: คุณให้เครื่องจักรจำนวนหนึ่งกับมัน แล้วพูดว่า: “เฮ้ Kubernetes มาเปิดใช้คอนเทนเนอร์ของฉันสิบอินสแตนซ์ที่มีโปรเซสเซอร์ 2 ตัวและหน่วยความจำ 3 GB ต่อตัวแล้วปล่อยให้พวกมันทำงานต่อไป!” Kubernetes จะจัดการส่วนที่เหลือเอง มันจะค้นหาความจุฟรี เปิดใช้คอนเทนเนอร์และรีสตาร์ทหากจำเป็น ออกการอัปเดตเมื่อเปลี่ยนเวอร์ชัน ฯลฯ โดยพื้นฐานแล้ว Kubernetes ช่วยให้คุณสามารถแยกส่วนประกอบฮาร์ดแวร์ออกไป และสร้างระบบที่หลากหลายที่เหมาะสำหรับการปรับใช้และรันแอปพลิเคชัน

ขีดจำกัดของ CPU และการควบคุมปริมาณเชิงรุกใน Kubernetes
Kubernetes จากมุมมองของคนธรรมดา

คำขอและขีดจำกัดใน Kubernetes คืออะไร

โอเค เราได้ครอบคลุมคอนเทนเนอร์และ Kubernetes แล้ว เรายังทราบด้วยว่าตู้คอนเทนเนอร์หลายตู้สามารถอยู่ในเครื่องเดียวกันได้

สามารถเปรียบเทียบกับอพาร์ทเมนต์ส่วนกลางได้ มีผู้เช่าพื้นที่กว้างขวาง (เครื่องจักร/ยูนิต) แก่ผู้เช่า (ตู้คอนเทนเนอร์) หลายราย Kubernetes ทำหน้าที่เป็นนายหน้า คำถามเกิดขึ้นจะป้องกันไม่ให้ผู้เช่าขัดแย้งกันได้อย่างไร? จะเกิดอะไรขึ้นถ้าหนึ่งในนั้นตัดสินใจยืมห้องน้ำครึ่งวัน?

นี่คือจุดที่คำขอและขีดจำกัดเข้ามามีบทบาท ซีพียู ขอร้อง จำเป็นสำหรับวัตถุประสงค์ในการวางแผนเท่านั้น นี่คือสิ่งที่คล้ายกับ "รายการสินค้าที่ต้องการ" ของคอนเทนเนอร์ และใช้เพื่อเลือกโหนดที่เหมาะสมที่สุด ในขณะเดียวกันก็ซีพียู ลิมิตสวิตช์ สามารถเทียบได้กับสัญญาเช่า - ทันทีที่เราเลือกยูนิตสำหรับตู้คอนเทนเนอร์ ไม่ได้ ก้าวข้ามขีดจำกัดที่ตั้งไว้ และนี่คือที่มาของปัญหา...

วิธีนำคำขอและขีดจำกัดไปใช้ใน Kubernetes

Kubernetes ใช้กลไกการควบคุมปริมาณ (ข้ามรอบสัญญาณนาฬิกา) ที่สร้างไว้ในเคอร์เนลเพื่อใช้ขีดจำกัดของ CPU หากแอปพลิเคชันเกินขีดจำกัด การควบคุมปริมาณจะถูกเปิดใช้งาน (เช่น ได้รับรอบ CPU น้อยลง) คำขอและขีดจำกัดของหน่วยความจำได้รับการจัดระเบียบแตกต่างกัน ดังนั้นจึงตรวจพบได้ง่ายกว่า ในการดำเนินการนี้ เพียงตรวจสอบสถานะการรีสตาร์ทครั้งล่าสุดของพ็อด: ไม่ว่าจะเป็น "OOMKilled" หรือไม่ การควบคุมปริมาณ CPU ไม่ใช่เรื่องง่าย เนื่องจาก K8 ทำให้การวัดใช้งานได้ตามการใช้งานเท่านั้น ไม่ใช่ตามกลุ่ม c

คำขอซีพียู

ขีดจำกัดของ CPU และการควบคุมปริมาณเชิงรุกใน Kubernetes
วิธีดำเนินการคำขอ CPU

เพื่อความง่าย ลองดูที่กระบวนการโดยใช้เครื่องที่มี CPU 4 คอร์เป็นตัวอย่าง

K8 ใช้กลไกกลุ่มควบคุม (cgroups) เพื่อควบคุมการจัดสรรทรัพยากร (หน่วยความจำและโปรเซสเซอร์) มีโมเดลแบบลำดับชั้น: รายการย่อยสืบทอดขีดจำกัดของกลุ่มหลัก รายละเอียดการแจกจ่ายจะถูกจัดเก็บไว้ในระบบไฟล์เสมือน (/sys/fs/cgroup). ในกรณีของโปรเซสเซอร์นี่คือ /sys/fs/cgroup/cpu,cpuacct/*.

K8s ใช้ไฟล์ cpu.share เพื่อจัดสรรทรัพยากรโปรเซสเซอร์ ในกรณีของเรา root cgroup ได้รับการแชร์ทรัพยากร CPU 4096 ครั้ง - 100% ของกำลังประมวลผลที่มีอยู่ (1 คอร์ = 1024 ซึ่งเป็นค่าคงที่) กลุ่มรากจะกระจายทรัพยากรตามสัดส่วนโดยขึ้นอยู่กับส่วนแบ่งของผู้สืบทอดที่ลงทะเบียน cpu.shareและพวกเขาก็ทำเช่นเดียวกันกับลูกหลานของพวกเขา ฯลฯ บนโหนด Kubernetes ทั่วไป cgroup รูทมีลูกสามคน: system.slice, user.slice и kubepods. สองกลุ่มย่อยแรกใช้เพื่อกระจายทรัพยากรระหว่างโหลดระบบที่สำคัญและโปรแกรมผู้ใช้ภายนอก K8 อันสุดท้าย - kubepods — สร้างโดย Kubernetes เพื่อกระจายทรัพยากรระหว่างพ็อด

แผนภาพด้านบนแสดงให้เห็นว่ากลุ่มย่อยที่หนึ่งและสองได้รับแต่ละกลุ่ม 1024 แบ่งปันโดยจัดสรรกลุ่มย่อย kuberpod 4096 หุ้น เป็นไปได้อย่างไร: กลุ่มรูทสามารถเข้าถึงได้เท่านั้น 4096 หุ้นและผลรวมของหุ้นของลูกหลานของเธอเกินจำนวนนี้อย่างมีนัยสำคัญ (6144)? ประเด็นก็คือค่านี้สมเหตุสมผล ดังนั้น Linux scheduler (CFS) จึงใช้เพื่อจัดสรรทรัพยากร CPU ตามสัดส่วน ในกรณีของเรา สองกลุ่มแรกจะได้รับ 680 หุ้นจริง (16,6% ของ 4096) และ kubepod รับส่วนที่เหลือ 2736 หุ้น ในกรณีที่ระบบหยุดทำงาน สองกลุ่มแรกจะไม่ใช้ทรัพยากรที่จัดสรร

โชคดีที่ตัวกำหนดเวลามีกลไกเพื่อหลีกเลี่ยงการสิ้นเปลืองทรัพยากร CPU ที่ไม่ได้ใช้ โดยจะถ่ายโอนความจุ “ที่ไม่ได้ใช้งาน” ไปยังพูลส่วนกลาง ซึ่งจะถูกกระจายไปยังกลุ่มที่ต้องการพลังการประมวลผลเพิ่มเติม (การถ่ายโอนจะเกิดขึ้นเป็นชุดเพื่อหลีกเลี่ยงการสูญเสียการปัดเศษ) วิธีการที่คล้ายกันนี้ใช้กับทายาทของทายาททั้งหมด

กลไกนี้ช่วยให้มั่นใจได้ถึงการกระจายพลังของโปรเซสเซอร์อย่างยุติธรรม และรับประกันว่าจะไม่มีใครประมวลผล "ขโมย" ทรัพยากรจากผู้อื่น

ขีด จำกัด ของ CPU

แม้ว่าการกำหนดค่าขีดจำกัดและคำขอใน K8 จะดูคล้ายกัน แต่การใช้งานก็แตกต่างอย่างสิ้นเชิง: สิ่งนี้ ทำให้เข้าใจผิดมากที่สุด และส่วนที่เป็นเอกสารน้อยที่สุด

K8s มีส่วนร่วม กลไกโควต้า CFS เพื่อดำเนินการตามขีดจำกัด การตั้งค่าของพวกเขาระบุไว้ในไฟล์ cfs_period_us и cfs_quota_us ในไดเร็กทอรี cgroup (ไฟล์ก็อยู่ที่นั่นด้วย cpu.share).

แตกต่าง cpu.shareโควต้าจะขึ้นอยู่กับ ช่วงเวลาและไม่ขึ้นอยู่กับพลังของโปรเซสเซอร์ที่มีอยู่ cfs_period_us ระบุระยะเวลาของช่วงเวลา (ยุค) - จะเป็น 100000 μs (100 ms) เสมอ มีตัวเลือกในการเปลี่ยนค่านี้ใน K8s แต่ตอนนี้ใช้ได้เฉพาะในอัลฟ่าเท่านั้น ตัวกำหนดเวลาใช้ยุคเพื่อรีสตาร์ทโควต้าที่ใช้แล้ว ไฟล์ที่สอง cfs_quota_usระบุเวลาที่มีอยู่ (โควต้า) ในแต่ละยุค โปรดทราบว่ามีการระบุเป็นไมโครวินาทีด้วย โควต้าอาจเกินความยาวของยุค กล่าวอีกนัยหนึ่ง อาจมากกว่า 100 มิลลิวินาที

ลองดูสองสถานการณ์บนเครื่อง 16 คอร์ (คอมพิวเตอร์ประเภททั่วไปที่เรามีที่ Omio):

ขีดจำกัดของ CPU และการควบคุมปริมาณเชิงรุกใน Kubernetes
สถานการณ์ที่ 1: 2 เธรดและขีดจำกัด 200 ms ไม่มีการควบคุมปริมาณ

ขีดจำกัดของ CPU และการควบคุมปริมาณเชิงรุกใน Kubernetes
สถานการณ์ที่ 2: 10 เธรดและขีดจำกัด 200 มิลลิวินาที การควบคุมปริมาณเริ่มต้นหลังจาก 20 ms การเข้าถึงทรัพยากรตัวประมวลผลจะกลับมาทำงานต่อหลังจากผ่านไปอีก 80 ms

สมมติว่าคุณตั้งค่าขีดจำกัด CPU เป็น 2 เมล็ด; Kubernetes จะแปลค่านี้เป็น 200 ms ซึ่งหมายความว่าคอนเทนเนอร์สามารถใช้เวลา CPU สูงสุด 200ms โดยไม่ต้องควบคุมปริมาณ

และนี่คือจุดเริ่มต้นของความสนุก ตามที่กล่าวไว้ข้างต้น โควต้าที่มีอยู่คือ 200 ms หากคุณทำงานคู่กัน สิบ เธรดบนเครื่อง 12 คอร์ (ดูภาพประกอบสำหรับสถานการณ์ที่ 2) ในขณะที่พ็อดอื่นๆ ทั้งหมดไม่ได้ใช้งาน โควต้าจะหมดในเวลาเพียง 20 มิลลิวินาที (เนื่องจาก 10 * 20 มิลลิวินาที = 200 มิลลิวินาที) และเธรดทั้งหมดของพ็อดนี้จะหยุดทำงาน » (เค้น) ในอีก 80 มิลลิวินาทีข้างหน้า ที่กล่าวไปแล้ว ข้อผิดพลาดของกำหนดการเนื่องจากเกิดการควบคุมปริมาณมากเกินไปและคอนเทนเนอร์ไม่สามารถตอบสนองโควต้าที่มีอยู่ได้

จะประเมินการควบคุมปริมาณในพ็อดได้อย่างไร

เพียงเข้าสู่ระบบพ็อดและดำเนินการ cat /sys/fs/cgroup/cpu/cpu.stat.

  • nr_periods — จำนวนงวดตัวกำหนดตารางเวลาทั้งหมด
  • nr_throttled — จำนวนช่วงเวลาที่จำกัดในองค์ประกอบ nr_periods;
  • throttled_time — เวลาควบคุมปริมาณสะสมในหน่วยนาโนวินาที

ขีดจำกัดของ CPU และการควบคุมปริมาณเชิงรุกใน Kubernetes

เกิดอะไรขึ้นจริงๆ?

เป็นผลให้เราได้รับการควบคุมปริมาณสูงในทุกแอปพลิเคชัน บางครั้งเขาก็เข้า หนึ่งครั้งครึ่ง แกร่งเกินคาด!

สิ่งนี้นำไปสู่ข้อผิดพลาดต่างๆ - ความล้มเหลวในการตรวจสอบความพร้อม, การค้างของคอนเทนเนอร์, การเชื่อมต่อเครือข่ายขาด, การหมดเวลาภายในการโทรบริการ ซึ่งท้ายที่สุดแล้วส่งผลให้เวลาแฝงเพิ่มขึ้นและอัตราข้อผิดพลาดสูงขึ้น

การตัดสินใจและผลที่ตามมา

ทุกอย่างเรียบง่ายที่นี่ เราละทิ้งขีดจำกัดของ CPU และเริ่มอัปเดตเคอร์เนลระบบปฏิบัติการในคลัสเตอร์ให้เป็นเวอร์ชันล่าสุด ซึ่งข้อบกพร่องได้รับการแก้ไขแล้ว จำนวนข้อผิดพลาด (HTTP 5xx) ในบริการของเราลดลงอย่างมากทันที:

ข้อผิดพลาด HTTP 5xx

ขีดจำกัดของ CPU และการควบคุมปริมาณเชิงรุกใน Kubernetes
ข้อผิดพลาด HTTP 5xx สำหรับบริการที่สำคัญรายการหนึ่ง

เวลาตอบสนอง p95

ขีดจำกัดของ CPU และการควบคุมปริมาณเชิงรุกใน Kubernetes
เวลาแฝงของคำขอบริการที่สำคัญ เปอร์เซ็นไทล์ที่ 95

ต้นทุนการดำเนินงาน

ขีดจำกัดของ CPU และการควบคุมปริมาณเชิงรุกใน Kubernetes
จำนวนชั่วโมงที่ใช้อินสแตนซ์

จับคืออะไร?

ตามที่ระบุไว้ในตอนต้นของบทความ:

สามารถเปรียบเทียบได้กับอพาร์ทเมนต์ส่วนกลาง... Kubernetes ทำหน้าที่เป็นนายหน้า แต่จะป้องกันไม่ให้ผู้เช่าขัดแย้งกันได้อย่างไร? จะเกิดอะไรขึ้นถ้าหนึ่งในนั้นตัดสินใจยืมห้องน้ำครึ่งวัน?

นี่คือสิ่งที่จับได้ คอนเทนเนอร์ที่ไม่ระมัดระวังเพียงตัวเดียวอาจกินทรัพยากร CPU ที่มีอยู่ทั้งหมดบนเครื่องได้ หากคุณมีสแต็กแอปพลิเคชันอัจฉริยะ (เช่น JVM, Go, Node VM ได้รับการกำหนดค่าอย่างเหมาะสม) ก็ไม่ใช่ปัญหา: คุณสามารถทำงานในสภาวะดังกล่าวได้เป็นเวลานาน แต่หากแอปพลิเคชันได้รับการปรับให้เหมาะสมไม่ดีหรือไม่ได้รับการปรับให้เหมาะสมเลย (FROM java:latest) สถานการณ์อาจไม่สามารถควบคุมได้ ที่ Omio เรามี Dockerfiles พื้นฐานแบบอัตโนมัติพร้อมการตั้งค่าเริ่มต้นที่เพียงพอสำหรับสแต็กภาษาหลัก ดังนั้นจึงไม่มีปัญหานี้

เราขอแนะนำให้ติดตามเมตริก ใช้ (การใช้งาน ความอิ่มตัว และข้อผิดพลาด) ความล่าช้าของ API และอัตราข้อผิดพลาด ตรวจสอบให้แน่ใจว่าผลลัพธ์เป็นไปตามความคาดหวัง

การอ้างอิง

นี่คือเรื่องราวของเรา เนื้อหาต่อไปนี้ช่วยให้เข้าใจสิ่งที่เกิดขึ้นได้อย่างมาก:

รายงานข้อผิดพลาดของ Kubernetes:

คุณเคยพบปัญหาที่คล้ายกันในการปฏิบัติงานของคุณหรือมีประสบการณ์ที่เกี่ยวข้องกับการควบคุมปริมาณในสภาพแวดล้อมการผลิตแบบคอนเทนเนอร์หรือไม่? แบ่งปันเรื่องราวของคุณในความคิดเห็น!

ปล.จากผู้แปล

อ่านเพิ่มเติมในบล็อกของเรา:

ที่มา: will.com

เพิ่มความคิดเห็น