พลาดไป XNUMX ข้อเมื่อปรับใช้แอปพลิเคชันแรกบน Kubernetes

พลาดไป XNUMX ข้อเมื่อปรับใช้แอปพลิเคชันแรกบน Kubernetesล้มเหลวโดย Aris Dreamer

หลายคนคิดว่ามันเพียงพอแล้วที่จะโอนแอปพลิเคชันไปยัง Kubernetes (ไม่ว่าจะใช้ Helm หรือด้วยตนเอง) - และจะมีความสุข แต่ไม่ใช่ทุกอย่างจะง่ายนัก

ทีม Mail.ru โซลูชั่นคลาวด์ แปลบทความโดยวิศวกร DevOps Julian Gindy เขาบอกถึงข้อผิดพลาดที่บริษัทของเขาต้องเผชิญในระหว่างกระบวนการย้ายข้อมูล เพื่อที่คุณจะได้ไม่ไปเหยียบจุดเดียวกัน

ขั้นตอนที่หนึ่ง: ตั้งค่าคำขอและขีดจำกัดของ Pod

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

คำขอพ็อด เป็นค่าหลักที่ตัวกำหนดตารางเวลาใช้เพื่อวางพ็อดอย่างเหมาะสมที่สุด

ของ เอกสาร Kubernetes: ขั้นตอนการกรองกำหนดชุดของโหนดที่สามารถกำหนดเวลาพ็อดได้ ตัวอย่างเช่น ตัวกรอง PodFitsResources จะตรวจสอบเพื่อดูว่าโหนดมีทรัพยากรเพียงพอที่จะตอบสนองคำขอทรัพยากรเฉพาะจากพ็อดหรือไม่

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

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

ขีด จำกัด ของพ็อด เป็นขีดจำกัดที่ชัดเจนสำหรับพ็อด ซึ่งแสดงถึงจำนวนทรัพยากรสูงสุดที่คลัสเตอร์จะจัดสรรให้กับคอนเทนเนอร์

อีกครั้งจาก เอกสารราชการ: หากคอนเทนเนอร์มีหน่วยความจำจำกัดที่ 4 GiB คูเบเล็ต (และรันไทม์ของคอนเทนเนอร์) จะบังคับใช้ รันไทม์ป้องกันไม่ให้คอนเทนเนอร์ใช้ทรัพยากรเกินขีดจำกัดที่ระบุ ตัวอย่างเช่น เมื่อกระบวนการในคอนเทนเนอร์พยายามใช้หน่วยความจำมากกว่าจำนวนที่อนุญาต เคอร์เนลของระบบจะยุติกระบวนการด้วยข้อผิดพลาด "หน่วยความจำไม่เพียงพอ" (OOM)

คอนเทนเนอร์สามารถใช้ทรัพยากรมากกว่าที่คำขอทรัพยากรระบุเสมอ แต่จะไม่สามารถใช้เกินขีดจำกัด ค่านี้ยากที่จะตั้งค่าอย่างถูกต้อง แต่มีความสำคัญมาก

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

น่าเสียดายที่ฉันไม่สามารถให้คำแนะนำเฉพาะเจาะจงเกี่ยวกับค่าที่จะตั้งค่า แต่เราปฏิบัติตามกฎต่อไปนี้:

  1. โดยใช้เครื่องมือทดสอบโหลด เราจำลองระดับการรับส่งข้อมูลพื้นฐานและสังเกตการใช้ทรัพยากรพ็อด (หน่วยความจำและตัวประมวลผล)
  2. ตั้งค่าคำขอพ็อดเป็นค่าต่ำโดยพลการ (โดยจำกัดทรัพยากรประมาณ 5 เท่าของค่าคำขอ) และสังเกต เมื่อคำขออยู่ในระดับต่ำเกินไป กระบวนการไม่สามารถเริ่มต้นได้ ซึ่งมักทำให้เกิดข้อผิดพลาดรันไทม์ Go ที่เป็นความลับ

ฉันทราบว่าการจำกัดทรัพยากรที่สูงขึ้นทำให้การจัดกำหนดการยากขึ้น เนื่องจากพ็อดต้องการโหนดเป้าหมายที่มีทรัพยากรเพียงพอ

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

ขั้นตอนที่สอง: ตั้งค่าการทดสอบความสดและความพร้อม

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

ความเป็นอยู่ แสดงว่าคอนเทนเนอร์กำลังทำงานอยู่ หากล้มเหลว kubelet จะฆ่าคอนเทนเนอร์และเปิดใช้งานนโยบายการรีสตาร์ทสำหรับคอนเทนเนอร์นั้น หากคอนเทนเนอร์ไม่ได้ติดตั้ง Liveness Probe สถานะเริ่มต้นจะสำเร็จตามที่ระบุไว้ใน เอกสาร Kubernetes.

Liveness probes ควรมีราคาถูก เช่น ไม่ใช้ทรัพยากรมาก เนื่องจากทำงานบ่อยและควรแจ้งให้ Kubernetes ทราบว่าแอปพลิเคชันกำลังทำงานอยู่

หากคุณตั้งค่าตัวเลือกให้ทำงานทุกวินาที สิ่งนี้จะเพิ่ม 1 คำขอต่อวินาที ดังนั้นโปรดทราบว่าจะต้องใช้ทรัพยากรเพิ่มเติมในการประมวลผลการรับส่งข้อมูลนี้

ที่บริษัทของเรา การทดสอบ Liveness จะทดสอบส่วนประกอบหลักของแอปพลิเคชัน แม้ว่าข้อมูล (เช่น จากฐานข้อมูลระยะไกลหรือแคช) จะไม่พร้อมใช้งานอย่างสมบูรณ์

เราได้ตั้งค่าจุดสิ้นสุด "สุขภาพ" ในแอปพลิเคชันที่ส่งคืนรหัสตอบกลับ 200 รหัส นี่เป็นตัวบ่งชี้ว่ากระบวนการกำลังทำงานและสามารถจัดการคำขอได้ (แต่ยังไม่ใช่การรับส่งข้อมูล)

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

โพรบความพร้อมใช้ทรัพยากรมากขึ้น เนื่องจากต้องเข้าถึงแบ็กเอนด์ในลักษณะที่แสดงว่าแอปพลิเคชันพร้อมที่จะรับคำขอ

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

หากคุณตัดสินใจที่จะสอบถามฐานข้อมูลเพื่อทดสอบความพร้อมของแอปพลิเคชันของคุณ ตรวจสอบให้แน่ใจว่าฐานข้อมูลมีราคาถูกที่สุดเท่าที่จะเป็นไปได้ ลองใช้แบบสอบถามนี้:

SELECT small_item FROM table LIMIT 1

นี่คือตัวอย่างวิธีที่เรากำหนดค่าสองค่านี้ใน Kubernetes:

livenessProbe: 
 httpGet:   
   path: /api/liveness    
   port: http 
readinessProbe:  
 httpGet:    
   path: /api/readiness    
   port: http  periodSeconds: 2

คุณสามารถเพิ่มตัวเลือกการกำหนดค่าเพิ่มเติม:

  • initialDelaySeconds - จะผ่านไปกี่วินาทีระหว่างการเปิดตัวคอนเทนเนอร์และการเริ่มต้นของการเปิดตัวโพรบ
  • periodSeconds — ช่วงเวลารอระหว่างการทดสอบตัวอย่าง
  • timeoutSeconds — จำนวนวินาทีหลังจากนั้นถือว่าพ็อดฉุกเฉิน หมดเวลาปกติ
  • failureThreshold คือจำนวนการทดสอบที่ล้มเหลวก่อนที่จะส่งสัญญาณรีสตาร์ทไปยังพ็อด
  • successThreshold คือจำนวนการทดลองที่สำเร็จก่อนที่พ็อดจะเปลี่ยนเป็นสถานะพร้อม (หลังจากล้มเหลวเมื่อพ็อดเริ่มทำงานหรือกู้คืน)

ขั้นตอนที่สาม: การตั้งค่านโยบายเครือข่ายเริ่มต้นของ Pod

Kubernetes มีภูมิประเทศเครือข่ายแบบ "แบน" โดยค่าเริ่มต้น พ็อดทั้งหมดจะสื่อสารระหว่างกันโดยตรง ในบางกรณีสิ่งนี้ไม่เป็นที่ต้องการ

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

ตัวอย่างเช่น ต่อไปนี้เป็นนโยบายง่ายๆ ที่ปฏิเสธการรับส่งข้อมูลขาเข้าทั้งหมดสำหรับเนมสเปซเฉพาะ:

---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:  
 name: default-deny-ingress
spec:  
 podSelector: {}  
 policyTypes:  
   - Ingress

การแสดงภาพการกำหนดค่านี้:

พลาดไป XNUMX ข้อเมื่อปรับใช้แอปพลิเคชันแรกบน Kubernetes
(https://miro.medium.com/max/875/1*-eiVw43azgzYzyN1th7cZg.gif)
รายละเอียดเพิ่มเติม ที่นี่.

ขั้นตอนที่สี่: พฤติกรรมที่กำหนดเองด้วย Hooks และ Init Containers

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

ความยากลำบากโดยเฉพาะเกิดขึ้นกับ Nginx. เราสังเกตเห็นว่าเมื่อปรับใช้ Pod เหล่านี้ตามลำดับ การเชื่อมต่อที่ใช้งานอยู่จะถูกขัดจังหวะก่อนที่จะสำเร็จ

หลังจากการค้นคว้าอย่างถี่ถ้วนบนอินเทอร์เน็ต ปรากฎว่า Kubernetes ไม่รอให้การเชื่อมต่อของ Nginx หมดลงก่อนที่จะปิดพ็อด ด้วยความช่วยเหลือของ pre-stop hook เราได้ใช้ฟังก์ชันต่อไปนี้และกำจัดการหยุดทำงานโดยสิ้นเชิง:

lifecycle: 
 preStop:
   exec:
     command: ["/usr/local/bin/nginx-killer.sh"]

แต่ nginx-killer.sh:

#!/bin/bash
sleep 3
PID=$(cat /run/nginx.pid)
nginx -s quit
while [ -d /proc/$PID ]; do
   echo "Waiting while shutting down nginx..."
   sleep 10
done

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

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

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

ขั้นตอนที่ห้า: การกำหนดค่าเคอร์เนล

ในที่สุดเรามาพูดถึงเทคนิคขั้นสูงเพิ่มเติม

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

อย่างไรก็ตาม Kubernetes อนุญาตให้คุณเรียกใช้คอนเทนเนอร์ที่ได้รับสิทธิ์ซึ่งเปลี่ยนเฉพาะพารามิเตอร์เคอร์เนลสำหรับพ็อดหนึ่งๆ นี่คือสิ่งที่เราใช้ในการเปลี่ยนจำนวนสูงสุดของการเชื่อมต่อที่เปิดอยู่:

initContainers:
  - name: sysctl
     image: alpine:3.10
     securityContext:
         privileged: true
      command: ['sh', '-c', "sysctl -w net.core.somaxconn=32768"]

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

ในข้อสรุป

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

ตลอดการย้ายข้อมูลไปยัง Kubernetes สิ่งสำคัญคือต้องทำตาม "รอบการทดสอบโหลด": รันแอปพลิเคชัน ทดสอบภายใต้โหลด สังเกตเมตริกและพฤติกรรมการปรับสเกล ปรับการกำหนดค่าตามข้อมูลนี้ จากนั้นทำซ้ำรอบนี้อีกครั้ง

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

ถามตัวเองด้วยคำถามเหล่านี้เสมอ:

  1. แอปพลิเคชันใช้ทรัพยากรเท่าใดและจำนวนนี้จะเปลี่ยนแปลงอย่างไร
  2. ข้อกำหนดในการปรับขนาดที่แท้จริงคืออะไร แอปจะรองรับทราฟฟิกโดยเฉลี่ยเท่าใด แล้วการจราจรสูงสุดล่ะ?
  3. บริการจะต้องขยายขนาดบ่อยแค่ไหน? พ็อดใหม่ต้องเปิดใช้งานเร็วแค่ไหนจึงจะได้รับการเข้าชม
  4. พ็อดปิดตัวลงอย่างสง่างามแค่ไหน? จำเป็นหรือไม่? เป็นไปได้หรือไม่ที่จะปรับใช้โดยไม่หยุดทำงาน
  5. วิธีลดความเสี่ยงด้านความปลอดภัยและจำกัดความเสียหายจากพ็อดที่ถูกบุกรุก บริการใดมีสิทธิ์หรือการเข้าถึงที่พวกเขาไม่ต้องการหรือไม่

Kubernetes นำเสนอแพลตฟอร์มที่น่าทึ่งซึ่งช่วยให้คุณใช้แนวทางปฏิบัติที่ดีที่สุดในการปรับใช้บริการหลายพันรายการในคลัสเตอร์ อย่างไรก็ตาม แอปพลิเคชันทั้งหมดจะแตกต่างกัน บางครั้งการใช้งานต้องทำงานเพิ่มขึ้นเล็กน้อย

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

มีอะไรให้อ่านอีก:

  1. แนวทางปฏิบัติที่ดีที่สุดและแนวทางปฏิบัติที่ดีที่สุดสำหรับการเรียกใช้คอนเทนเนอร์และ Kubernetes ในสภาพแวดล้อมการผลิต.
  2. เครื่องมือที่มีประโยชน์มากกว่า 90 รายการสำหรับ Kubernetes: การปรับใช้ การจัดการ การตรวจสอบ ความปลอดภัย และอื่นๆ.
  3. ช่องของเราเกี่ยวกับ Kubernetes ใน Telegram.

ที่มา: will.com

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