บันทึก. แปล: บทความนี้เขียนโดย Galo Navarro ซึ่งดำรงตำแหน่งหัวหน้าวิศวกรซอฟต์แวร์ของบริษัท Adevinta ในยุโรป เป็น "การสืบสวน" ที่น่าสนใจและให้ความรู้ในด้านการดำเนินงานด้านโครงสร้างพื้นฐาน ชื่อดั้งเดิมของมันถูกขยายออกไปเล็กน้อยในการแปลด้วยเหตุผลที่ผู้เขียนอธิบายไว้ตอนเริ่มต้น
หมายเหตุจากผู้เขียน: ดูเหมือนกระทู้นี้ครับ
เมื่อสองสามสัปดาห์ที่ผ่านมา ทีมของฉันกำลังย้ายไมโครเซอร์วิสเดียวไปยังแพลตฟอร์มหลักที่รวม CI/CD, รันไทม์ที่ใช้ Kubernetes, ตัววัด และคุณสมบัติอื่นๆ การย้ายดังกล่าวมีลักษณะเป็นการทดลอง: เราวางแผนที่จะใช้เป็นพื้นฐานและโอนบริการเพิ่มเติมประมาณ 150 รายการในอีกไม่กี่เดือนข้างหน้า พวกเขาทั้งหมดมีหน้าที่รับผิดชอบในการดำเนินงานของแพลตฟอร์มออนไลน์ที่ใหญ่ที่สุดบางแห่งในสเปน (Infojobs, Fotocasa ฯลฯ)
หลังจากที่เราปรับใช้แอปพลิเคชันกับ Kubernetes และเปลี่ยนเส้นทางการรับส่งข้อมูลไปยังแอปพลิเคชันนั้น ความประหลาดใจที่น่าตกใจก็รอเราอยู่ ล่าช้า (แฝง) คำขอใน Kubernetes สูงกว่าใน EC10 ถึง 2 เท่า โดยทั่วไปมีความจำเป็นต้องค้นหาวิธีแก้ไขปัญหานี้หรือละทิ้งการย้ายไมโครเซอร์วิส (และอาจเป็นทั้งโครงการ)
เหตุใดเวลาแฝงใน Kubernetes จึงสูงกว่าใน EC2 มาก
เพื่อค้นหาปัญหาคอขวด เราได้รวบรวมตัวชี้วัดตามเส้นทางคำขอทั้งหมด สถาปัตยกรรมของเรานั้นเรียบง่าย: พร็อกซีเกตเวย์ API (Zuul) ร้องขอไปยังอินสแตนซ์ไมโครเซอร์วิสใน EC2 หรือ Kubernetes ใน Kubernetes เราใช้ NGINX Ingress Controller และแบ็กเอนด์ก็เป็นออบเจ็กต์ธรรมดาๆ
EC2
+---------------+
| +---------+ |
| | | |
+-------> BACKEND | |
| | | | |
| | +---------+ |
| +---------------+
+------+ |
Public | | |
-------> ZUUL +--+
traffic | | | Kubernetes
+------+ | +-----------------------------+
| | +-------+ +---------+ |
| | | | xx | | |
+-------> NGINX +------> BACKEND | |
| | | xx | | |
| +-------+ +---------+ |
+-----------------------------+
ดูเหมือนว่าปัญหาจะเกี่ยวข้องกับเวลาแฝงเริ่มต้นในแบ็กเอนด์ (ฉันทำเครื่องหมายพื้นที่ปัญหาบนกราฟเป็น "xx") บน EC2 การตอบสนองของแอปพลิเคชันใช้เวลาประมาณ 20 มิลลิวินาที ใน Kubernetes เวลาแฝงเพิ่มขึ้นเป็น 100-200 ms
เราไล่ผู้ต้องสงสัยที่เกี่ยวข้องกับการเปลี่ยนแปลงรันไทม์ออกอย่างรวดเร็ว เวอร์ชัน JVM ยังคงเหมือนเดิม ปัญหาเกี่ยวกับคอนเทนเนอร์ไม่ได้เกี่ยวข้องอะไรด้วย เนื่องจากแอปพลิเคชันทำงานได้สำเร็จแล้วในคอนเทนเนอร์บน EC2 กำลังโหลด? แต่เราสังเกตเห็นเวลาแฝงที่สูงแม้ที่ 1 คำขอต่อวินาที การหยุดเก็บขยะก็อาจละเลยได้เช่นกัน
ผู้ดูแลระบบ Kubernetes คนหนึ่งของเราสงสัยว่าแอปพลิเคชันมีการพึ่งพาภายนอกหรือไม่ เนื่องจากการสืบค้น DNS เคยทำให้เกิดปัญหาที่คล้ายกันในอดีต
สมมติฐานที่ 1: การจำแนกชื่อ DNS
สำหรับแต่ละคำขอ แอปพลิเคชันของเราจะเข้าถึงอินสแตนซ์ AWS Elasticsearch หนึ่งถึงสามครั้งในโดเมนที่คล้ายกัน elastic.spain.adevinta.com
. ภายในภาชนะของเรา
แบบสอบถาม DNS จากคอนเทนเนอร์:
[root@be-851c76f696-alf8z /]# while true; do dig "elastic.spain.adevinta.com" | grep time; sleep 2; done
;; Query time: 22 msec
;; Query time: 22 msec
;; Query time: 29 msec
;; Query time: 21 msec
;; Query time: 28 msec
;; Query time: 43 msec
;; Query time: 39 msec
คำขอที่คล้ายกันจากหนึ่งในอินสแตนซ์ EC2 ที่แอปพลิเคชันทำงานอยู่:
bash-4.4# while true; do dig "elastic.spain.adevinta.com" | grep time; sleep 2; done
;; Query time: 77 msec
;; Query time: 0 msec
;; Query time: 0 msec
;; Query time: 0 msec
;; Query time: 0 msec
เมื่อพิจารณาว่าการค้นหาใช้เวลาประมาณ 30 มิลลิวินาที เห็นได้ชัดว่าการแก้ไข DNS เมื่อเข้าถึง Elasticsearch มีส่วนทำให้เวลาแฝงเพิ่มขึ้นอย่างแน่นอน
อย่างไรก็ตาม นี่เป็นเรื่องแปลกด้วยเหตุผลสองประการ:
- เรามีแอปพลิเคชัน Kubernetes มากมายที่โต้ตอบกับทรัพยากร AWS โดยไม่ได้รับผลกระทบจากเวลาแฝงที่สูง ไม่ว่าจะด้วยเหตุผลใดก็ตาม เกี่ยวข้องกับกรณีนี้โดยเฉพาะ
- เรารู้ว่า JVM ทำการแคช DNS ในหน่วยความจำ ในภาพของเรา ค่า TTL จะถูกเขียนไว้
$JAVA_HOME/jre/lib/security/java.security
และตั้งเป็น 10 วินาที:networkaddress.cache.ttl = 10
. กล่าวอีกนัยหนึ่ง JVM ควรแคชการสืบค้น DNS ทั้งหมดเป็นเวลา 10 วินาที
เพื่อยืนยันสมมติฐานแรก เราจึงตัดสินใจหยุดการโทร DNS ชั่วคราวและดูว่าปัญหาหายไปหรือไม่ อันดับแรก เราตัดสินใจกำหนดค่าแอปพลิเคชันใหม่เพื่อให้สื่อสารโดยตรงกับ Elasticsearch ด้วยที่อยู่ IP แทนที่จะสื่อสารผ่านชื่อโดเมน สิ่งนี้จะต้องมีการเปลี่ยนแปลงรหัสและการใช้งานใหม่ ดังนั้นเราจึงแมปโดเมนกับที่อยู่ IP ของมัน /etc/hosts
:
34.55.5.111 elastic.spain.adevinta.com
ตอนนี้คอนเทนเนอร์ได้รับ IP เกือบจะในทันที ซึ่งส่งผลให้มีการปรับปรุงบางอย่าง แต่เราเข้าใกล้ระดับเวลาในการตอบสนองที่คาดไว้เพียงเล็กน้อยเท่านั้น แม้ว่าการแก้ไข DNS จะใช้เวลานาน แต่เหตุผลที่แท้จริงยังคงหลบเลี่ยงเราอยู่
การวินิจฉัยผ่านเครือข่าย
เราตัดสินใจวิเคราะห์การรับส่งข้อมูลจากคอนเทนเนอร์โดยใช้ tcpdump
เพื่อดูว่าเกิดอะไรขึ้นบนเครือข่าย:
[root@be-851c76f696-alf8z /]# tcpdump -leni any -w capture.pcap
จากนั้นเราได้ส่งคำขอหลายรายการและดาวน์โหลดการจับภาพ (kubectl cp my-service:/capture.pcap capture.pcap
) เพื่อการวิเคราะห์เพิ่มเติมใน
ไม่มีอะไรน่าสงสัยเกี่ยวกับการสืบค้น DNS (ยกเว้นสิ่งเล็กๆ น้อยๆ สิ่งหนึ่งที่ฉันจะพูดถึงในภายหลัง) แต่มีความแตกต่างบางประการในวิธีที่บริการของเราจัดการคำขอแต่ละรายการ ด้านล่างนี้คือภาพหน้าจอของการจับภาพที่แสดงคำขอที่ได้รับการยอมรับก่อนที่การตอบกลับจะเริ่มต้น:
หมายเลขพัสดุจะแสดงอยู่ในคอลัมน์แรก เพื่อความชัดเจน ฉันได้กำหนดรหัสสีให้กับสตรีม TCP ต่างๆ
สตรีมสีเขียวที่เริ่มต้นด้วยแพ็กเก็ต 328 แสดงให้เห็นว่าไคลเอนต์ (172.17.22.150) สร้างการเชื่อมต่อ TCP กับคอนเทนเนอร์ (172.17.36.147) อย่างไร หลังจากการจับมือครั้งแรก (328-330) ก็นำพัสดุ 331 มาด้วย HTTP GET /v1/..
— คำขอเข้ามาใช้บริการของเรา กระบวนการทั้งหมดใช้เวลา 1 มิลลิวินาที
สตรีมสีเทา (จากแพ็กเก็ต 339) แสดงว่าบริการของเราส่งคำขอ HTTP ไปยังอินสแตนซ์ Elasticsearch (ไม่มีการแฮนด์เชค TCP เนื่องจากใช้การเชื่อมต่อที่มีอยู่) ใช้เวลา 18 มิลลิวินาที
จนถึงตอนนี้ทุกอย่างเรียบร้อยดี และเวลาโดยประมาณสอดคล้องกับความล่าช้าที่คาดไว้ (20-30 มิลลิวินาที เมื่อวัดจากไคลเอนต์)
อย่างไรก็ตาม ส่วนสีน้ำเงินใช้เวลา 86ms เกิดอะไรขึ้นในนั้น? ด้วยแพ็กเก็ต 333 บริการของเราส่งคำขอ HTTP GET ไปที่ /latest/meta-data/iam/security-credentials
และหลังจากนั้น จะมีการร้องขอ GET อีกรายการหนึ่งไปยังการเชื่อมต่อ TCP เดียวกัน /latest/meta-data/iam/security-credentials/arn:..
.
เราพบว่าสิ่งนี้เกิดขึ้นซ้ำกับทุกคำขอตลอดการติดตาม การแก้ไข DNS นั้นช้ากว่าเล็กน้อยในคอนเทนเนอร์ของเรา (คำอธิบายสำหรับปรากฏการณ์นี้ค่อนข้างน่าสนใจ แต่ฉันจะบันทึกไว้สำหรับบทความแยกต่างหาก) ปรากฎว่าสาเหตุของความล่าช้าเป็นเวลานานคือการเรียกบริการ AWS Instance Metadata ในแต่ละคำขอ
สมมติฐานที่ 2: การเรียก AWS โดยไม่จำเป็น
ปลายทางทั้งสองเป็นของ
/ # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
arn:aws:iam::<account_id>:role/some_role
คำขอที่สองขอสิทธิ์ชั่วคราวจากปลายทางที่สองสำหรับอินสแตนซ์นี้:
/ # curl http://169.254.169.254/latest/meta-data/iam/security-credentials/arn:aws:iam::<account_id>:role/some_role`
{
"Code" : "Success",
"LastUpdated" : "2012-04-26T16:39:16Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "ASIAIOSFODNN7EXAMPLE",
"SecretAccessKey" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Token" : "token",
"Expiration" : "2017-05-17T15:09:54Z"
}
ลูกค้าสามารถใช้งานได้ในช่วงเวลาสั้นๆ และต้องได้รับใบรับรองใหม่เป็นระยะๆ (ก่อนที่จะได้รับ) Expiration
). โมเดลนั้นเรียบง่าย: AWS หมุนเวียนคีย์ชั่วคราวบ่อยครั้งด้วยเหตุผลด้านความปลอดภัย แต่ไคลเอนต์สามารถแคชคีย์เหล่านั้นได้ไม่กี่นาทีเพื่อชดเชยการลดประสิทธิภาพการทำงานที่เกี่ยวข้องกับการได้รับใบรับรองใหม่
AWS Java SDK ควรเข้ามารับผิดชอบในการจัดการกระบวนการนี้ แต่สิ่งนี้ไม่เกิดขึ้นด้วยเหตุผลบางประการ
หลังจากค้นหาปัญหาบน GitHub เราก็พบปัญหา
AWS SDK อัปเดตใบรับรองเมื่อมีเงื่อนไขข้อใดข้อหนึ่งต่อไปนี้เกิดขึ้น:
- วันหมดอายุ (
Expiration
) ล้มลงEXPIRATION_THRESHOLD
ฮาร์ดโค้ดนานถึง 15 นาที - เวลาผ่านไปนับตั้งแต่ความพยายามครั้งล่าสุดในการต่ออายุใบรับรองมากกว่า
REFRESH_THRESHOLD
ฮาร์ดโค้ดเป็นเวลา 60 นาที
หากต้องการดูวันหมดอายุที่แท้จริงของใบรับรองที่เราได้รับ เราได้รันคำสั่ง cURL ข้างต้นจากทั้งคอนเทนเนอร์และอินสแตนซ์ EC2 ระยะเวลาที่ถูกต้องของใบรับรองที่ได้รับจากคอนเทนเนอร์นั้นสั้นกว่ามาก: 15 นาทีพอดี
ตอนนี้ทุกอย่างชัดเจนแล้ว: สำหรับคำขอแรก บริการของเราได้รับใบรับรองชั่วคราว เนื่องจากไม่ถูกต้องนานกว่า 15 นาที AWS SDK จึงตัดสินใจอัปเดตตามคำขอครั้งต่อไป และสิ่งนี้ก็เกิดขึ้นกับทุกคำขอ
เหตุใดระยะเวลาที่มีผลบังคับใช้ของใบรับรองจึงสั้นลง
ข้อมูลเมตาของอินสแตนซ์ AWS ได้รับการออกแบบมาเพื่อทำงานกับอินสแตนซ์ EC2 ไม่ใช่ Kubernetes ในทางกลับกัน เราไม่ต้องการเปลี่ยนอินเทอร์เฟซของแอปพลิเคชัน สำหรับสิ่งนี้เราใช้
KIAM เป็นผู้จัดหาใบรับรองระยะสั้นให้กับพ็อด ซึ่งสมเหตุสมผลเมื่อพิจารณาว่าอายุการใช้งานเฉลี่ยของพ็อดนั้นสั้นกว่าอินสแตนซ์ EC2 ระยะเวลาที่มีผลบังคับใช้เริ่มต้นสำหรับใบรับรอง
ดังนั้นหากคุณวางค่าเริ่มต้นทั้งสองทับซ้อนกันก็จะเกิดปัญหาขึ้น ใบรับรองแต่ละใบที่มอบให้กับแอปพลิเคชันจะหมดอายุหลังจากผ่านไป 15 นาที อย่างไรก็ตาม AWS Java SDK บังคับให้ต่ออายุใบรับรองใดๆ ที่ยังเหลือเวลาน้อยกว่า 15 นาทีก่อนวันหมดอายุ
ด้วยเหตุนี้ ใบรับรองชั่วคราวจึงถูกบังคับให้ต่ออายุพร้อมกับคำขอแต่ละรายการ ซึ่งทำให้เกิดการเรียก AWS API สองครั้ง และส่งผลให้เวลาแฝงเพิ่มขึ้นอย่างมาก เราพบใน AWS Java SDK
การแก้ปัญหากลายเป็นเรื่องง่าย เราเพียงแค่กำหนดค่า KIAM ใหม่เพื่อขอใบรับรองที่มีระยะเวลาใช้งานได้นานขึ้น เมื่อสิ่งนี้เกิดขึ้น คำขอเริ่มไหลโดยไม่ต้องมีส่วนร่วมของบริการ AWS Metadata และเวลาแฝงลดลงถึงระดับที่ต่ำกว่าใน EC2 อีกด้วย
ผลการวิจัย
จากประสบการณ์ของเราในการย้ายข้อมูล แหล่งที่มาของปัญหาที่พบบ่อยที่สุดประการหนึ่งไม่ใช่จุดบกพร่องใน Kubernetes หรือองค์ประกอบอื่นๆ ของแพลตฟอร์ม นอกจากนี้ยังไม่ได้แก้ไขข้อบกพร่องพื้นฐานใดๆ ในไมโครเซอร์วิสที่เรากำลังย้าย ปัญหามักเกิดขึ้นเพียงเพราะเรานำองค์ประกอบที่แตกต่างกันมารวมกัน
เราผสมผสานระบบที่ซับซ้อนซึ่งไม่เคยมีปฏิสัมพันธ์กันมาก่อนเข้าด้วยกัน โดยคาดหวังว่าเมื่อรวมกันแล้วจะกลายเป็นระบบเดียวที่ใหญ่กว่า อนิจจา ยิ่งมีองค์ประกอบมาก ยิ่งมีโอกาสเกิดข้อผิดพลาดมากขึ้น เอนโทรปีก็จะยิ่งสูงขึ้น
ในกรณีของเรา เวลาแฝงที่สูงไม่ได้เป็นผลมาจากจุดบกพร่องหรือการตัดสินใจที่ไม่ดีใน Kubernetes, KIAM, AWS Java SDK หรือไมโครเซอร์วิสของเรา เป็นผลมาจากการรวมการตั้งค่าเริ่มต้นสองรายการเข้าด้วยกัน: รายการหนึ่งใน KIAM และอีกรายการใน AWS Java SDK เมื่อพิจารณาแยกกัน พารามิเตอร์ทั้งสองก็สมเหตุสมผล: นโยบายการต่ออายุใบรับรองที่ใช้งานอยู่ใน AWS Java SDK และระยะเวลาใช้งานสั้นของใบรับรองใน KAIM แต่เมื่อคุณรวมเข้าด้วยกัน ผลลัพธ์ก็คาดเดาไม่ได้ โซลูชันที่เป็นอิสระและตรรกะสองรายการไม่จำเป็นต้องสมเหตุสมผลเมื่อนำมารวมกัน
ปล.จากผู้แปล
คุณสามารถเรียนรู้เพิ่มเติมเกี่ยวกับสถาปัตยกรรมของยูทิลิตี้ KIAM สำหรับการผสานรวม AWS IAM กับ Kubernetes ได้ที่
อ่านในบล็อกของเราด้วย:
- «
3 เรื่องราวของความล้มเหลวของ Kubernetes ในการผลิต: การต่อต้านความสัมพันธ์, การปิดระบบอย่างสง่างาม, เว็บฮุค "; - «
ลำดับความสำคัญของพ็อดใน Kubernetes ทำให้เกิดการหยุดทำงานที่ Grafana Labs ได้อย่างไร "; - «
6 ข้อบกพร่องของระบบความบันเทิงในการทำงานของ Kubernetes [และวิธีแก้ปัญหา] "; - «
6 เรื่องราวเชิงปฏิบัติจากชีวิตประจำวัน SRE ของเรา '
ที่มา: will.com