วันที่ 27 เมษายน ในงานสัมมนา
ตามประเพณีเรามีความยินดีที่จะนำเสนอ
มาวิเคราะห์หัวข้อรายงานทีละคำแล้วเริ่มจากจุดสิ้นสุด
Kubernetes
สมมติว่าเรามีคอนเทนเนอร์ Docker บนโฮสต์ของเรา เพื่ออะไร? เพื่อให้มั่นใจถึงความสามารถในการทำซ้ำและการแยกส่วน ซึ่งช่วยให้ปรับใช้ได้ง่ายและดี CI/CD เรามียานพาหนะพร้อมตู้คอนเทนเนอร์มากมาย
Kubernetes ให้อะไรในกรณีนี้?
- เราหยุดคิดถึงเครื่องจักรเหล่านี้และเริ่มทำงานกับ "คลาวด์" คลัสเตอร์ของคอนเทนเนอร์ หรือฝัก (กลุ่มภาชนะ)
- ยิ่งไปกว่านั้น เราไม่ได้คิดถึงพ็อดเดี่ยวๆ ด้วยซ้ำ แต่ต้องจัดการมากกว่านั้นоกลุ่มใหญ่ เช่น ดั้งเดิมระดับสูง ให้เราบอกว่ามีเทมเพลตสำหรับการรันเวิร์กโหลดบางอย่าง และนี่คือจำนวนอินสแตนซ์ที่ต้องการในการรัน หากเราเปลี่ยนเทมเพลตในภายหลัง อินสแตนซ์ทั้งหมดก็จะเปลี่ยนไป
- ด้วย API ที่ประกาศ แทนที่จะดำเนินการตามลำดับคำสั่งเฉพาะ เราจะอธิบาย "โครงสร้างของโลก" (ใน YAML) ซึ่งสร้างขึ้นโดย Kubernetes และอีกครั้ง: เมื่อคำอธิบายเปลี่ยนแปลง การแสดงผลจริงก็จะเปลี่ยนไปด้วย
การจัดการทรัพยากร
ซีพียู
ให้เราเรียกใช้ nginx, php-fpm และ mysql บนเซิร์ฟเวอร์ บริการเหล่านี้จะมีกระบวนการทำงานเพิ่มมากขึ้น ซึ่งแต่ละกระบวนการต้องใช้ทรัพยากรในการประมวลผล:
(ตัวเลขในสไลด์คือ “นกแก้ว” ความต้องการเชิงนามธรรมของแต่ละกระบวนการเพื่อพลังในการคำนวณ)
เพื่อให้ทำงานได้ง่ายขึ้น มีเหตุผลที่จะรวมกระบวนการออกเป็นกลุ่ม (เช่น กระบวนการ nginx ทั้งหมดเป็นกลุ่มเดียว “nginx”) วิธีที่ง่ายและชัดเจนในการทำเช่นนี้คือการใส่แต่ละกลุ่มไว้ในคอนเทนเนอร์:
หากต้องการดำเนินการต่อ คุณต้องจำไว้ว่าคอนเทนเนอร์คืออะไร (ใน Linux) การปรากฏตัวของพวกมันเกิดขึ้นได้เนื่องจากคุณสมบัติหลักสามประการในเคอร์เนลซึ่งมีการใช้งานเมื่อนานมาแล้ว:
ในบริบทของรายงาน เราสนใจเพียงเท่านั้น กลุ่ม cgroupsเนื่องจากกลุ่มควบคุมเป็นส่วนหนึ่งของฟังก์ชันการทำงานของคอนเทนเนอร์ (นักเทียบท่า ฯลฯ) ที่ใช้การจัดการทรัพยากร กระบวนการที่รวมกันเป็นกลุ่มตามที่เราต้องการคือกลุ่มควบคุม
กลับไปที่ข้อกำหนดของ CPU สำหรับกระบวนการเหล่านี้ และตอนนี้สำหรับกลุ่มของกระบวนการ:
(ขอย้ำว่าตัวเลขทั้งหมดเป็นการแสดงออกถึงความต้องการทรัพยากรเชิงนามธรรม)
ในเวลาเดียวกัน CPU เองก็มีทรัพยากรที่มีจำกัด (ในตัวอย่างนี้คือ 1000)ซึ่งทุกคนอาจขาด (ผลรวมความต้องการของทุกกลุ่มคือ 150+850+460=1460) จะเกิดอะไรขึ้นในกรณีนี้?
เคอร์เนลเริ่มกระจายทรัพยากรและดำเนินการอย่าง "ยุติธรรม" โดยให้ทรัพยากรในแต่ละกลุ่มเท่ากัน แต่ในกรณีแรก มีมากกว่าที่จำเป็น (333>150) ดังนั้นส่วนที่เกิน (333-150=183) จึงยังคงเป็นสำรอง ซึ่งมีการกระจายเท่าๆ กันระหว่างคอนเทนเนอร์อื่นสองคอนเทนเนอร์:
ผลลัพธ์ก็คือ คอนเทนเนอร์แรกมีทรัพยากรเพียงพอ คอนเทนเนอร์ตัวที่สองมีทรัพยากรไม่เพียงพอ คอนเทนเนอร์ตัวที่สามมีทรัพยากรไม่เพียงพอ นี่คือผลของการกระทำ ตัวกำหนดเวลา "ซื่อสัตย์" ใน Linux -
มาดูกรณีการขาดทรัพยากรในคอนเทนเนอร์ที่สอง (php-fpm) ทรัพยากรคอนเทนเนอร์ทั้งหมดมีการกระจายระหว่างกระบวนการเท่าๆ กัน เป็นผลให้กระบวนการหลักทำงานได้ดี แต่พนักงานทุกคนช้าลง โดยได้รับสิ่งที่พวกเขาต้องการน้อยกว่าครึ่งหนึ่ง:
นี่คือวิธีการทำงานของตัวกำหนดเวลา CFS เราจะเรียกตุ้มน้ำหนักที่เรากำหนดให้กับคอนเทนเนอร์เพิ่มเติม คำขอ. เหตุใดจึงเป็นเช่นนี้ - ดูเพิ่มเติม
ลองดูสถานการณ์ทั้งหมดจากอีกด้านหนึ่ง ดังที่คุณทราบ ถนนทุกสายมุ่งสู่กรุงโรม และในกรณีของคอมพิวเตอร์ มุ่งสู่ CPU CPU ตัวเดียว งานมากมาย คุณต้องมีสัญญาณไฟจราจร วิธีที่ง่ายที่สุดในการจัดการทรัพยากรคือ "สัญญาณไฟจราจร": พวกเขาให้กระบวนการหนึ่งมีเวลาเข้าถึง CPU แบบคงที่ จากนั้นให้กระบวนการถัดไป ฯลฯ
วิธีการนี้เรียกว่าโควต้าที่ยาก (จำกัดยาก). จำไว้ง่ายๆ เหมือนกับว่า ขีดจำกัด. อย่างไรก็ตาม หากคุณกระจายขีดจำกัดไปยังคอนเทนเนอร์ทั้งหมด ปัญหาก็เกิดขึ้น: mysql ขับไปตามถนนและเมื่อถึงจุดหนึ่ง ความต้องการ CPU สิ้นสุดลง แต่กระบวนการอื่น ๆ ทั้งหมดถูกบังคับให้รอจนกว่า CPU ไม่ได้ใช้งาน.
กลับไปที่เคอร์เนล Linux และการโต้ตอบกับ CPU - ภาพรวมมีดังนี้:
cgroup มีการตั้งค่าสองแบบ - โดยพื้นฐานแล้วนี่คือ "การบิด" ง่ายๆ สองแบบที่ให้คุณกำหนดได้:
- น้ำหนักสำหรับคอนเทนเนอร์ (คำขอ) คือ ยอดแชร์;
- เปอร์เซ็นต์ของเวลา CPU ทั้งหมดสำหรับงานคอนเทนเนอร์ (ขีดจำกัด) คือ ส่วนแบ่ง.
จะวัดซีพียูได้อย่างไร?
มีหลายวิธี:
- อะไร นกแก้วไม่มีใครรู้ - คุณต้องเจรจาทุกครั้ง
- ดอกเบี้ย ชัดเจนกว่าแต่สัมพันธ์กัน: 50% ของเซิร์ฟเวอร์ที่มี 4 คอร์และ 20 คอร์นั้นแตกต่างอย่างสิ้นเชิง
- คุณสามารถใช้สิ่งที่กล่าวไปแล้วได้ น้ำหนักซึ่ง Linux รู้ แต่ก็มีความสัมพันธ์กันเช่นกัน
- ตัวเลือกที่เหมาะสมที่สุดคือการวัดทรัพยากรคอมพิวเตอร์ใน วินาที. เหล่านั้น. เป็นวินาทีของเวลาโปรเซสเซอร์สัมพันธ์กับวินาทีของเรียลไทม์: ให้เวลาโปรเซสเซอร์ 1 วินาทีต่อ 1 วินาทีจริง - นี่คือหนึ่งคอร์ CPU ทั้งหมด
เพื่อให้พูดได้ง่ายขึ้น พวกเขาเริ่มวัดโดยตรง เมล็ดพืชซึ่งหมายความว่ามีเวลา CPU เท่ากันเมื่อเทียบกับของจริง เนื่องจาก Linux เข้าใจน้ำหนัก แต่เวลา/คอร์ของ CPU ไม่มากนัก จึงจำเป็นต้องมีกลไกในการแปลจากที่หนึ่งไปยังอีกที่หนึ่ง
ลองพิจารณาตัวอย่างง่ายๆ กับเซิร์ฟเวอร์ที่มีคอร์ CPU 3 คอร์ โดยที่พ็อดสามตัวจะได้รับน้ำหนัก (500, 1000 และ 1500) ซึ่งสามารถแปลงเป็นส่วนที่สอดคล้องกันของคอร์ที่จัดสรรให้พวกเขาได้อย่างง่ายดาย (0,5, 1 และ 1,5)
หากคุณใช้เซิร์ฟเวอร์ตัวที่สอง ซึ่งจะมีคอร์เป็นสองเท่า (6) และวางพ็อดเดียวกันไว้ที่นั่น การกระจายของคอร์สามารถคำนวณได้อย่างง่ายดายโดยการคูณด้วย 2 (1, 2 และ 3 ตามลำดับ) แต่ช่วงเวลาสำคัญเกิดขึ้นเมื่อพ็อดที่สี่ปรากฏบนเซิร์ฟเวอร์นี้ ซึ่งมีน้ำหนักเพื่อความสะดวกคือ 3000 โดยจะใช้ทรัพยากร CPU บางส่วน (ครึ่งหนึ่งของคอร์) และสำหรับพ็อดที่เหลือจะถูกคำนวณใหม่ (ลดลงครึ่งหนึ่ง):
ทรัพยากร Kubernetes และ CPU
ใน Kubernetes ทรัพยากร CPU มักจะวัดเป็น มิลลิแดร็กซ์, เช่น. 0,001 แกนถือเป็นน้ำหนักฐาน (สิ่งเดียวกันในคำศัพท์เฉพาะของ Linux/cgroups เรียกว่าการแชร์ CPU แม้ว่าถ้าเจาะจงกว่านั้นคือ 1000 มิลลิคอร์ = 1024 การแชร์ CPU) K8 ช่วยให้มั่นใจได้ว่าจะไม่วางพ็อดบนเซิร์ฟเวอร์เกินกว่าทรัพยากร CPU สำหรับผลรวมของน้ำหนักของพ็อดทั้งหมด
สิ่งนี้เกิดขึ้นได้อย่างไร? เมื่อคุณเพิ่มเซิร์ฟเวอร์ลงในคลัสเตอร์ Kubernetes ระบบจะรายงานว่ามีคอร์ CPU กี่คอร์ที่พร้อมใช้งาน และเมื่อสร้างพ็อดใหม่ ตัวกำหนดเวลาของ Kubernetes จะรู้ว่าพ็อดนี้ต้องใช้คอร์จำนวนเท่าใด ดังนั้นพ็อดจะถูกกำหนดให้กับเซิร์ฟเวอร์ที่มีคอร์เพียงพอ
จะเกิดอะไรขึ้นถ้า ไม่ ระบุคำขอแล้ว (เช่น พ็อดไม่มีจำนวนคอร์ที่กำหนดไว้ตามที่ต้องการ) มาดูกันว่าโดยทั่วไปแล้ว Kubernetes นับทรัพยากรอย่างไร
สำหรับพ็อด คุณสามารถระบุทั้งคำขอ (ตัวกำหนดเวลา CFS) และขีดจำกัด (จำสัญญาณไฟจราจรได้ไหม):
- หากระบุให้เท่ากัน พ็อดจะถูกกำหนดคลาส QoS รับประกัน. รับประกันจำนวนแกนประมวลผลที่พร้อมใช้งานเสมอนี้
- หากคำขอน้อยกว่าขีดจำกัด - คลาส QoS ระเบิดได้. เหล่านั้น. ตัวอย่างเช่น เราคาดหวังว่าพ็อดจะใช้ 1 คอร์เสมอ แต่ค่านี้ไม่ใช่ข้อจำกัด: บางครั้ง พ็อดสามารถใช้งานได้มากขึ้น (เมื่อเซิร์ฟเวอร์มีทรัพยากรว่างสำหรับสิ่งนี้)
- นอกจากนี้ยังมีคลาส QoS สุดความพยายาม — รวมถึงพ็อดที่ไม่ได้ระบุคำขอด้วย ทรัพยากรจะถูกมอบให้เป็นลำดับสุดท้าย
หน่วยความจำ
ด้วยหน่วยความจำสถานการณ์จะคล้ายกัน แต่แตกต่างกันเล็กน้อย - ท้ายที่สุดแล้วลักษณะของทรัพยากรเหล่านี้ก็แตกต่างกัน โดยทั่วไปการเปรียบเทียบจะเป็นดังนี้:
มาดูกันว่าคำขอถูกนำไปใช้ในหน่วยความจำอย่างไร ปล่อยให้พ็อดอยู่บนเซิร์ฟเวอร์ โดยเปลี่ยนการใช้หน่วยความจำ จนกว่าหนึ่งในพ็อดจะใหญ่มากจนหน่วยความจำไม่เพียงพอ ในกรณีนี้ OOM killer จะปรากฏขึ้นและฆ่ากระบวนการที่ใหญ่ที่สุด:
สิ่งนี้ไม่เหมาะกับเราเสมอไป ดังนั้นจึงเป็นไปได้ที่จะควบคุมว่ากระบวนการใดที่สำคัญสำหรับเราและไม่ควรถูกฆ่า เมื่อต้องการทำเช่นนี้ ให้ใช้พารามิเตอร์ oom_score_adj.
กลับไปที่คลาส QoS ของ CPU และวาดความคล้ายคลึงกับค่า oom_score_adj ที่กำหนดลำดับความสำคัญการใช้หน่วยความจำสำหรับพ็อด:
- ค่า oom_score_adj ต่ำสุดสำหรับพ็อด - -998 - หมายความว่าพ็อดดังกล่าวควรถูกฆ่าเป็นลำดับสุดท้าย รับประกัน.
- สูงสุด - 1000 - คือ สุดความพยายามพ็อดดังกล่าวจะถูกฆ่าก่อน
- เพื่อคำนวณค่าที่เหลือ (ระเบิดได้) มีสูตรอยู่ ซึ่งมีสาระสำคัญอยู่ที่ความจริงที่ว่า ยิ่งพ็อดร้องขอทรัพยากรมากเท่าไร โอกาสที่พ็อดจะถูกฆ่าก็จะน้อยลงเท่านั้น
"บิด" ครั้งที่สอง - จำกัด_in_bytes - สำหรับข้อจำกัด ทุกอย่างง่ายขึ้น: เราเพียงกำหนดจำนวนหน่วยความจำสูงสุดที่ออกและที่นี่ (ไม่เหมือนกับ CPU) ไม่มีคำถามว่าจะวัดได้อย่างไร (หน่วยความจำ)
เบ็ดเสร็จ
แต่ละพ็อดใน Kubernetes จะได้รับ requests
и limits
- ทั้งพารามิเตอร์สำหรับ CPU และหน่วยความจำ:
- ตามคำขอ ตัวกำหนดตารางเวลา Kubernetes จะทำงานซึ่งกระจายพ็อดระหว่างเซิร์ฟเวอร์
- ขึ้นอยู่กับพารามิเตอร์ทั้งหมด คลาส QoS ของพ็อดจะถูกกำหนด
- น้ำหนักสัมพัทธ์คำนวณตามคำขอของ CPU
- ตัวกำหนดตารางเวลา CFS ได้รับการกำหนดค่าตามคำขอของ CPU
- OOM killer ได้รับการกำหนดค่าตามคำขอหน่วยความจำ
- “สัญญาณไฟจราจร” ได้รับการกำหนดค่าตามขีดจำกัดของ CPU
- ขึ้นอยู่กับขีดจำกัดของหน่วยความจำ ขีดจำกัดจะถูกกำหนดค่าสำหรับ cgroup
โดยทั่วไป รูปภาพนี้จะตอบทุกคำถามว่าส่วนหลักของการจัดการทรัพยากรเกิดขึ้นใน Kubernetes อย่างไร
การปรับขนาดอัตโนมัติ
ตัวปรับขนาดคลัสเตอร์อัตโนมัติ K8s
ลองจินตนาการว่าทั้งคลัสเตอร์ถูกครอบครองแล้ว และจำเป็นต้องสร้างพ็อดใหม่ แม้ว่าพ็อดจะไม่ปรากฏขึ้น แต่มันก็ค้างอยู่ในสถานะ กำลังดำเนินการ. เพื่อให้ปรากฏขึ้นเราสามารถเชื่อมต่อเซิร์ฟเวอร์ใหม่เข้ากับคลัสเตอร์หรือ... ติดตั้งคลัสเตอร์-autoscaler ซึ่งจะทำเพื่อเรา: สั่งซื้อเครื่องเสมือนจากผู้ให้บริการคลาวด์ (โดยใช้คำขอ API) และเชื่อมต่อกับคลัสเตอร์ หลังจากนั้นก็จะมีการเพิ่มพ็อด
นี่คือการปรับขนาดอัตโนมัติของคลัสเตอร์ Kubernetes ซึ่งใช้งานได้ดี (จากประสบการณ์ของเรา) อย่างไรก็ตาม เช่นเดียวกับที่อื่นๆ มีความแตกต่างบางประการที่นี่...
ตราบใดที่เราเพิ่มขนาดคลัสเตอร์ ทุกอย่างก็ดี แต่จะเกิดอะไรขึ้นเมื่อคลัสเตอร์ เริ่มปลดปล่อยตัวเอง? ปัญหาคือการย้ายพ็อด (เพื่อเพิ่มพื้นที่ว่าง) เป็นเรื่องยากทางเทคนิคและมีราคาแพงในแง่ของทรัพยากร Kubernetes ใช้แนวทางที่แตกต่างไปจากเดิมอย่างสิ้นเชิง
พิจารณาคลัสเตอร์ของเซิร์ฟเวอร์ 3 เครื่องที่มีการปรับใช้ มี 6 พ็อด: ขณะนี้มี 2 พ็อดสำหรับแต่ละเซิร์ฟเวอร์ ด้วยเหตุผลบางอย่าง เราจึงต้องการปิดเซิร์ฟเวอร์ตัวใดตัวหนึ่ง ในการทำเช่นนี้เราจะใช้คำสั่ง kubectl drain
, ที่:
- จะห้ามไม่ให้ส่งพ็อดใหม่ไปยังเซิร์ฟเวอร์นี้
- จะลบพ็อดที่มีอยู่บนเซิร์ฟเวอร์
เนื่องจาก Kubernetes มีหน้าที่รับผิดชอบในการรักษาจำนวนพ็อด (6) จึงทำได้ง่ายๆ จะสร้างใหม่ เหล่านั้นบนโหนดอื่น แต่ไม่ใช่ในโหนดที่ถูกปิดใช้งาน เนื่องจากถูกทำเครื่องหมายว่าไม่พร้อมใช้งานสำหรับการโฮสต์พ็อดใหม่ นี่เป็นกลไกพื้นฐานของ Kubernetes
อย่างไรก็ตามมีความแตกต่างกันนิดหน่อยที่นี่เช่นกัน ในสถานการณ์ที่คล้ายกัน สำหรับ StatefulSet (แทน Deployment) การดำเนินการจะแตกต่างออกไป ตอนนี้เรามีแอปพลิเคชัน stateful อยู่แล้ว - ตัวอย่างเช่น พ็อดสามตัวที่มี MongoDB ซึ่งหนึ่งในนั้นมีปัญหาบางอย่าง (ข้อมูลเสียหายหรือมีข้อผิดพลาดอื่นที่ทำให้พ็อดไม่สามารถเริ่มทำงานได้อย่างถูกต้อง) และเราตัดสินใจปิดการใช้งานเซิร์ฟเวอร์เดียวอีกครั้ง อะไรจะเกิดขึ้น?
MongoDB สามารถ die เนื่องจากต้องการองค์ประชุม: สำหรับคลัสเตอร์ที่มีการติดตั้งสามรายการ อย่างน้อยสองรายการต้องทำงาน อย่างไรก็ตามสิ่งนี้ ไม่ได้เกิดขึ้น - ขอบคุณ พ็อดDisruptionBudget. พารามิเตอร์นี้กำหนดจำนวนพ็อดการทำงานขั้นต่ำที่ต้องการ เมื่อรู้ว่าหนึ่งในพ็อด MongoDB ไม่ทำงานอีกต่อไป และเห็นว่า PodDisruptionBudget ได้รับการตั้งค่าสำหรับ MongoDB minAvailable: 2
Kubernetes จะไม่อนุญาตให้คุณลบพ็อด
ประเด็นสำคัญ: เพื่อให้การเคลื่อนไหว (และในความเป็นจริง คือการสร้างใหม่) ของพ็อดทำงานอย่างถูกต้องเมื่อมีการเผยแพร่คลัสเตอร์ จำเป็นต้องกำหนดค่า PodDisruptionBudget
การปรับขนาดแนวนอน
ลองพิจารณาสถานการณ์อื่น มีแอปพลิเคชันที่ทำงานเป็น Deployment ใน Kubernetes ปริมาณการใช้งานของผู้ใช้มาถึงพ็อด (เช่น มีสามรายการ) และเราจะวัดตัวบ่งชี้บางอย่างในพ็อดเหล่านั้น (เช่น โหลด CPU) เมื่อโหลดเพิ่มขึ้น เราจะบันทึกตามกำหนดเวลาและเพิ่มจำนวนพ็อดเพื่อกระจายคำขอ
ทุกวันนี้ใน Kubernetes สิ่งนี้ไม่จำเป็นต้องทำด้วยตนเอง: การเพิ่ม/ลดจำนวนพ็อดอัตโนมัติได้รับการกำหนดค่าขึ้นอยู่กับค่าของตัวบ่งชี้โหลดที่วัดได้
คำถามหลักที่นี่คือ: ต้องวัดอะไรกันแน่ и วิธีการตีความ ค่าที่ได้รับ (สำหรับการตัดสินใจเปลี่ยนจำนวนพ็อด) คุณสามารถวัดได้มากมาย:
วิธีดำเนินการในทางเทคนิค - รวบรวมเมตริก ฯลฯ — ฉันพูดโดยละเอียดในรายงานเกี่ยวกับ
มี
แทนการสรุป
รายงานมีความต่อเนื่อง: เกี่ยวกับการปรับขนาดตามแนวตั้งและวิธีการเลือกทรัพยากรที่เหมาะสม ฉันจะพูดถึงเรื่องนี้ในวิดีโอต่อๆ ไป
วิดีโอและสไลด์
วิดีโอจากการแสดง (44 นาที):
การนำเสนอรายงาน:
PS
รายงานอื่นๆ เกี่ยวกับ Kubernetes ในบล็อกของเรา:
- «
การขยายและการเสริม Kubernetes » (Andrey Polovov; 8 เมษายน 2019 บน Saint HighLoad++); - «
ฐานข้อมูลและ Kubernetes » (Dmitry Stolyarov; 8 พฤศจิกายน 2018 บน HighLoad++); - «
การตรวจสอบและ Kubernetes » (Dmitry Stolyarov; 28 พฤษภาคม 2018 ที่ RootConf); - «
แนวทางปฏิบัติที่ดีที่สุดของ CI/CD กับ Kubernetes และ GitLab » (Dmitry Stolyarov; 7 พฤศจิกายน 2017 บน HighLoad++); - «
ประสบการณ์ของเรากับ Kubernetes ในโครงการขนาดเล็ก » (Dmitry Stolyarov; 6 มิถุนายน 2017 ที่ RootConf).
ที่มา: will.com