ภาพรวมสั้นๆ ของปัญหา
วันหนึ่งระหว่างการอัปเดตคลัสเตอร์ k8s ตามแผน เราพบว่า POD เกือบทั้งหมดของเรา (ประมาณ 500 จาก 1,000) ในโหนดใหม่ไม่สามารถเริ่มทำงานได้ และนาทีก็กลายเป็นชั่วโมงอย่างรวดเร็ว เราพยายามค้นหาสาเหตุที่แท้จริง แต่หลังจากผ่านไปสามชั่วโมง PODS ยังคงอยู่ในสถานะ ContainerCreating
โชคดีที่นี่ไม่ใช่สภาพแวดล้อมการผลิตและหน้าต่างการบำรุงรักษาถูกกำหนดไว้ในช่วงสุดสัปดาห์ เรามีเวลาตรวจสอบปัญหาโดยไม่มีแรงกดดันใดๆ
คุณควรเริ่มค้นหาสาเหตุที่แท้จริงจากที่ไหน คุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับวิธีแก้ปัญหาที่เราพบหรือไม่ เตรียมตัวให้พร้อมและสนุกไปกับมัน!
รายละเอียดเพิ่มเติมเกี่ยวกับปัญหา
ปัญหาคือเรามีภาพ Docker จำนวนมากที่ต้องดึงและเริ่มต้นบนแต่ละโหนดในคลัสเตอร์ในเวลาเดียวกัน เนื่องจากการดึงภาพ Docker พร้อมกันหลายรายการบนโหนดเดียวอาจทำให้มีการใช้งานดิสก์สูงและเวลาเริ่มต้นระบบแบบเย็นยาวนานขึ้น
บางครั้งกระบวนการ CD อาจใช้เวลานานถึง 3 ชั่วโมงในการดึงภาพออกมา อย่างไรก็ตาม ในครั้งนี้ กระบวนการดังกล่าวเกิดการหยุดชะงัก เนื่องจากปริมาณ PODS ในระหว่างการอัปเกรด EKS (แบบอินไลน์ เมื่อเราแทนที่โหนดทั้งหมดในคลัสเตอร์) สูงเกินไป
แอปทั้งหมดของเราอยู่ใน k8s (ตาม EKS ) เพื่อประหยัดต้นทุนสำหรับสภาพแวดล้อม DEV เราจึงใช้อินสแตนซ์จุด
เราใช้อิมเมจ AmazonLinux2 สำหรับโหนด
เรามี ฟีเจอร์สาขา (FB) จำนวนมากในสภาพแวดล้อมการพัฒนาที่ปรับใช้กับคลัสเตอร์ Kubernetes ของเราอย่างต่อเนื่อง FB แต่ละรายการมีชุดแอปพลิเคชันของตัวเอง และแอปพลิเคชันแต่ละรายการมีชุดการอ้างอิงของตัวเอง (ภายในอิมเมจ)
ในโครงการของเรา มีแอปเกือบ 200 แอป และจำนวนนี้ยังคงเพิ่มขึ้นเรื่อยๆ แอปแต่ละตัวจะใช้หนึ่งใน 7 อิมเมจพื้นฐานของ Docker ที่มีขนาดประมาณ 2 GB ขนาดรวมสูงสุดของอิมเมจที่เก็บถาวร (ใน ECR ) อยู่ที่ประมาณ 3 GB
รูปภาพทั้งหมดถูกเก็บไว้ใน Amazon Elastic Container Registry (ECR)
เราใช้ประเภทปริมาณ gp3 EBS เริ่มต้นสำหรับโหนด
ปัญหาที่ต้องเผชิญ
ระยะเวลาการเริ่มต้นแบบเย็นที่ขยายออก: การเริ่มพ็อดใหม่ด้วยภาพใหม่นั้นอาจใช้เวลานานกว่า 1 ชั่วโมง โดยเฉพาะอย่างยิ่งเมื่อมีการดึงภาพหลายภาพพร้อมกันบนโหนดเดียว
ข้อผิดพลาด ErrImagePull: เกิด
ErrImagePull
บ่อยครั้ง หรือติดอยู่ในสถานะContainerCreating
บ่งชี้ถึงปัญหาในการดึงภาพการใช้ดิสก์สูง: การใช้ดิสก์ยังคงอยู่ที่เกือบ 100% ในระหว่างกระบวนการดึงภาพ ซึ่งเป็นผลมาจากการใช้ดิสก์ I/O จำนวนมากที่จำเป็นสำหรับการคลายการบีบอัด (เช่น “unpigz”)
ปัญหาชุด DaemonSet ของระบบ: ชุด DaemonSet ของระบบบางชุด (เช่น
aws-node
หรือebs-csi-node
) ถูกย้ายไปยังสถานะ "ไม่พร้อม" เนื่องจากแรงกดดันของดิสก์ ซึ่งส่งผลกระทบต่อความพร้อมของโหนดไม่มีแคชรูปภาพบนโหนด: เนื่องจากเราใช้อินสแตนซ์จุด เราจึงไม่สามารถใช้ดิสก์ภายในเครื่องเพื่อแคชรูปภาพได้
ส่งผลให้การปรับใช้บนสาขาฟีเจอร์ต่างๆ หยุดชะงักเป็นจำนวนมาก โดยเฉพาะอย่างยิ่ง เนื่องจาก FB ที่แตกต่างกันมีชุดรูปภาพพื้นฐานที่แตกต่างกัน
หลังจากตรวจสอบอย่างรวดเร็ว เราพบว่าปัญหาหลักคือแรงกดดันของดิสก์บนโหนดโดยกระบวนการ unpigz
กระบวนการนี้รับผิดชอบในการคลายการบีบอัดอิมเมจของ Docker เราไม่ได้เปลี่ยนการตั้งค่าเริ่มต้นสำหรับประเภทโวลุ่ม EBS ของ gp3 เนื่องจากไม่เหมาะกับกรณีของเรา
Hotfix สำหรับการกู้คืนคลัสเตอร์
ในขั้นตอนแรกเราตัดสินใจที่จะลดจำนวน POD บนโหนด
- เราย้ายโหนดใหม่ไปยังสถานะ “Cordon”
- ถอด PODS ที่ติดอยู่ทั้งหมดออกเพื่อลดแรงกดของดิสก์
- รัน POD ทีละตัวเพื่ออุ่นเครื่องโหนด
- หลังจากนั้นเราจะย้ายโหนดที่อุ่นเครื่องแล้วไปสู่สถานะปกติ (“unCordon”)
- ลบโหนดทั้งหมดในสถานะค้างอยู่
- PODS ทั้งหมดเริ่มทำงานได้สำเร็จโดยใช้แคชภาพ Docker
การออกแบบ CI/CD ดั้งเดิม
แนวคิดหลักของโซลูชันนี้คือการวอร์มอัปโหนดก่อนที่กระบวนการ CD จะเริ่มต้นโดยใช้ส่วนที่ใหญ่ที่สุดของอิมเมจ Docker (เลเยอร์การพึ่งพา JS) ซึ่งใช้เป็นอิมเมจรูทสำหรับแอปทั้งหมดของเรา เรามีอิมเมจรูทอย่างน้อย 7 ประเภทที่มีการพึ่งพา JS ซึ่งเกี่ยวข้องกับประเภทของแอป ดังนั้น มาวิเคราะห์การออกแบบ CI/CD ดั้งเดิมกัน
ใน CI/CD pipeline ของเรา เรามี 3 เสาหลัก:
ท่อ CI/CD ดั้งเดิม:
ในขั้นตอน
Init
it: เตรียมสภาพแวดล้อม/ตัวแปร กำหนดชุดของอิมเมจที่จะสร้างใหม่ ฯลฯ...ในขั้นตอน
Build
: เราสร้างภาพและส่งไปยัง ECRในขั้นตอน
Deploy
: เราจะปรับใช้รูปภาพไปยัง k8s (อัปเดตการปรับใช้ เป็นต้น...)
รายละเอียดเพิ่มเติมเกี่ยวกับการออกแบบ CICD ดั้งเดิม:
- สาขาคุณลักษณะ (FB) ของเราแยกออกจากสาขา
main
ในกระบวนการ CI เราจะวิเคราะห์ชุดรูปภาพที่มีการเปลี่ยนแปลงใน FB และสร้างใหม่เสมอ สาขาmain
จะเสถียรเสมอ เนื่องจากคำจำกัดความควรมีรูปภาพพื้นฐานเวอร์ชันล่าสุดอยู่เสมอ - เราสร้างภาพ Docker ของ JS ที่ต้องพึ่งพา (สำหรับแต่ละสภาพแวดล้อม) แยกกันและส่งไปยัง ECR เพื่อนำกลับมาใช้ใหม่เป็นภาพราก (ฐาน) ใน Dockerfile เรามีภาพ Docker ที่ต้องพึ่งพา JS ประมาณ 5–10 ประเภท
- FB จะถูกปรับใช้กับคลัสเตอร์ k8s ในเนมสเปซที่แยกจากกัน แต่สำหรับโหนดทั่วไปสำหรับ FB FB สามารถมีแอพได้ ~200 แอพ โดยมีขนาดภาพสูงสุด 3 GB
- เรามีระบบปรับขนาดคลัสเตอร์อัตโนมัติ ซึ่งปรับขนาดโหนดในคลัสเตอร์ตามโหลดหรือ PODS ที่รอดำเนินการด้วย nodeSelector และค่าความคลาดเคลื่อนที่สอดคล้องกัน
- เราใช้จุดอินสแตนซ์สำหรับโหนด
การดำเนินการตามกระบวนการวอร์มอัพ
มีข้อกำหนดสำหรับกระบวนการอุ่นเครื่อง
บังคับ:
- การแก้ไขปัญหา : จัดการและแก้ไขปัญหา
ContainerCreating
- ปรับปรุงประสิทธิภาพ : ลดเวลาในการเริ่มต้นระบบอย่างมากด้วยการใช้อิมเมจฐานที่อุ่นไว้ล่วงหน้า (การอ้างอิง JS)
ดีใจที่มีการปรับปรุง:
- ความยืดหยุ่น : ช่วยให้สามารถเปลี่ยนแปลงประเภทโหนดและอายุการใช้งานได้อย่างง่ายดาย (เช่น SLA ที่สูงหรือระยะเวลาการใช้งานที่ขยายออกไป)
- ความโปร่งใส : ให้มาตรวัดที่ชัดเจนเกี่ยวกับการใช้งานและประสิทธิภาพ
- ประสิทธิภาพด้านต้นทุน : ประหยัดต้นทุนด้วยการลบ VNG ทันทีหลังจากลบสาขาฟีเจอร์ที่เกี่ยวข้อง
- การแยกตัว : แนวทางนี้ทำให้แน่ใจว่าสภาพแวดล้อมอื่น ๆ จะไม่ได้รับผลกระทบ
สารละลาย
หลังจากวิเคราะห์ข้อกำหนดและข้อจำกัดแล้ว เราตัดสินใจที่จะใช้กระบวนการวอร์มอัพที่จะอุ่นโหนดด้วยอิมเมจแคช JS พื้นฐาน กระบวนการนี้จะถูกเรียกใช้ก่อนที่กระบวนการ CD จะเริ่มต้น เพื่อให้แน่ใจว่าโหนดพร้อมสำหรับการใช้งาน FB และเรามีโอกาสสูงสุดที่จะเข้าถึงแคช
การปรับปรุงนี้เราแบ่งออกเป็นขั้นตอนใหญ่ๆ ดังนี้:
สร้าง ชุดโหนด (Virtual Node Group) ต่อแต่ละ FB
เพิ่ม รูปภาพพื้นฐานลงในสคริปต์ cloud-init สำหรับโหนดใหม่
เพิ่ม ขั้นตอนก่อนการปรับใช้เพื่อรัน DaemonSet พร้อมกับส่วน
initContainers
เพื่อดาวน์โหลดภาพ Docker ที่จำเป็นไปยังโหนดก่อนที่กระบวนการ CD จะเริ่มต้น
CI/CD Pipeline ที่อัปเดตจะมีลักษณะดังนี้:
CI/CD Pipeline ที่อัปเดต:
- ขั้นตอน การเริ่มต้น
1.1.(ขั้นตอนใหม่) การเริ่มใช้งาน : หากเป็นการเริ่มต้น FB ครั้งแรก ให้สร้างชุดส่วนบุคคลใหม่ของอินสแตนซ์โหนด (ในเงื่อนไขของเราคือ Virtual Node Group หรือ VNG) และดาวน์โหลดอิมเมจพื้นฐานของ JS ทั้งหมด (5–10 อิมเมจ) จากสาขาหลัก การดำเนินการนี้ถือว่ายุติธรรม เพราะเราได้แยก FB ออกจากสาขาหลัก จุดสำคัญคือ นี่ไม่ใช่การดำเนินการบล็อก - ขั้นตอน การสร้าง
- ขั้น ตอนก่อนการปรับใช้ ดาวน์โหลดอิมเมจฐาน JS ที่สดใหม่พร้อมแท็ก FB เฉพาะจาก ECR
3.1.(ขั้นตอนใหม่) จุดสำคัญ : เป็นการดำเนินการบล็อค เนื่องจากเราควรลดแรงกดดันของดิสก์ ทีละรายการ เราจะดาวน์โหลดอิมเมจพื้นฐานสำหรับแต่ละโหนดที่เกี่ยวข้อง
อย่างไรก็ตาม ขอขอบคุณสำหรับขั้นตอน “ การปรับใช้ init” เรามีภาพ Docker พื้นฐานจากสาขาหลักแล้ว ซึ่งทำให้มีโอกาสสูงที่จะเข้าถึงแคชในการเริ่มต้นครั้งแรก - **การใช้งาน
**ไม่มีการเปลี่ยนแปลงในขั้นตอนนี้ แต่ด้วยขั้นตอนก่อนหน้านี้ เรามีเลเยอร์ภาพ Docker ขนาดใหญ่ทั้งหมดบนโหนดที่จำเป็นแล้ว
ขั้นตอนการใช้งาน Init
สร้างชุดโหนดใหม่สำหรับแต่ละ FB ผ่านการเรียก API (ไปยังระบบปรับขนาดอัตโนมัติของบุคคลที่สาม) จากไปป์ไลน์ CI ของเรา
ปัญหาที่ได้รับการแก้ไข:
การแยก : FB แต่ละอันมีชุดโหนดของตัวเอง เพื่อให้แน่ใจว่าสภาพแวดล้อมจะไม่ได้รับผลกระทบจาก FB อื่นๆ
ความยืดหยุ่น : เราสามารถเปลี่ยนประเภทโหนดและอายุการใช้งานได้อย่างง่ายดาย
ประสิทธิภาพด้านต้นทุน : เราสามารถลบโหนดได้ทันทีหลังจากลบ FB
ความโปร่งใส : เราสามารถติดตามการใช้งานและประสิทธิภาพของโหนดได้อย่างง่ายดาย (แต่ละโหนดมีแท็กที่เกี่ยวข้องกับ FB)
การใช้งาน Spot Instance อย่างมีประสิทธิผล : Spot Instance เริ่มต้นด้วยอิมเมจพื้นฐานที่กำหนดไว้แล้ว ซึ่งหมายความว่า หลังจากที่โหนด Spot เริ่มทำงาน ก็มีอิมเมจพื้นฐานอยู่บนโหนดแล้ว (จากสาขาหลัก)
ดาวน์โหลดภาพฐาน JS ทั้งหมดจากสาขาหลักไปยังโหนดใหม่ ผ่านสคริปต์ cloud-init
ในขณะที่กำลังดาวน์โหลดรูปภาพในพื้นหลัง กระบวนการ CD สามารถสร้างรูปภาพใหม่ต่อไปได้โดยไม่มีปัญหาใดๆ นอกจากนี้ โหนดถัดไป (ซึ่งจะสร้างขึ้นโดยระบบปรับขนาดอัตโนมัติ) จากกลุ่มนี้จะถูกสร้างขึ้นโดยใช้ข้อมูล cloud-init
ที่อัปเดตแล้ว ซึ่งมีคำแนะนำในการดาวน์โหลดรูปภาพก่อนเริ่มต้นอยู่แล้ว
ปัญหาที่ได้รับการแก้ไข:
การแก้ไขปัญหา : แรงกดดันของดิสก์หายไปแล้ว เนื่องจากเราอัปเดตสคริปต์
cloud-init
โดยเพิ่มการดาวน์โหลดอิมเมจฐานจากสาขาหลัก วิธีนี้ช่วยให้เราเข้าถึงแคชได้เมื่อเริ่มต้น FB ครั้งแรกการใช้งานอินสแตนซ์จุดอย่างมีประสิทธิภาพ : อินสแตนซ์จุดจะเริ่มต้นด้วยข้อมูลการเริ่มต้น
cloud-init
ที่อัปเดต ซึ่งหมายความว่า หลังจากโหนดจุดเริ่มทำงานแล้ว จะมีอิมเมจพื้นฐานบนโหนด (จากสาขาหลัก) อยู่แล้วประสิทธิภาพการทำงานที่ได้รับการปรับปรุง : กระบวนการ CD สามารถสร้างภาพใหม่ต่อไปได้โดยไม่มีปัญหาใดๆ
การดำเนินการนี้เพิ่มเวลา ~17 วินาที (การเรียก API) ให้กับไปป์ไลน์ CI/CD ของเรา
การดำเนินการนี้จะสมเหตุสมผลเฉพาะในครั้งแรกที่เราเริ่ม FB เท่านั้น ในครั้งถัดไป เราจะปรับใช้แอปของเรากับโหนดที่มีอยู่แล้ว ซึ่งมีอิมเมจพื้นฐานที่เราได้ส่งมอบไปแล้วในการปรับใช้ครั้งก่อน
ขั้นตอนก่อนการใช้งาน
เราจำเป็นต้องทำขั้นตอนนี้เนื่องจากรูปภาพ FB แตกต่างจากรูปภาพสาขาหลัก เราจำเป็นต้องดาวน์โหลดรูปภาพฐาน FB ไปยังโหนดก่อนที่กระบวนการ CD จะเริ่มต้น ซึ่งจะช่วยลดเวลาในการเริ่มต้นระบบแบบเย็นที่ยาวนานและการใช้ดิสก์สูงที่อาจเกิดขึ้นได้เมื่อดึงรูปภาพขนาดใหญ่หลายภาพพร้อมกัน
วัตถุประสงค์ของขั้นตอนก่อนการปรับใช้
ป้องกันแรงกดดันของดิสก์ : ดาวน์โหลดรูปภาพขนาดใหญ่ที่สุดของ Docker ตามลำดับ หลังจากขั้นตอน init-deploy แล้ว เราก็มีรูปภาพพื้นฐานบนโหนดแล้ว ซึ่งหมายความว่าเรามีโอกาสสูงที่จะเกิดแคชที่ได้รับผลกระทบ
ปรับปรุงประสิทธิภาพการใช้งาน : ช่วยให้แน่ใจว่าโหนดได้รับการอุ่นเครื่องล่วงหน้าด้วยอิมเมจ Docker ที่จำเป็น ซึ่งจะนำไปสู่เวลาในการเริ่มต้น POD ที่เร็วขึ้น (เกือบจะทันที)
เพิ่มเสถียรภาพ : ลดโอกาสที่จะพบข้อผิดพลาด
ErrImagePull
/ContainerCreating
และตรวจสอบให้แน่ใจว่าชุดเดมอนของระบบยังคงอยู่ในสถานะ "พร้อม"
ในขั้นตอนนี้ เราจะเพิ่มเวลาให้กับกระบวนการซีดี 10–15 นาที
รายละเอียดขั้นตอนก่อนการใช้งาน:
- ในซีดี เราสร้าง DaemonSet ด้วยส่วน
initContainers
- ส่วน
initContainers
จะถูกดำเนินการก่อนที่คอนเทนเนอร์หลักจะเริ่มต้น เพื่อให้แน่ใจว่ามีการดาวน์โหลดรูปภาพที่จำเป็นก่อนที่คอนเทนเนอร์หลักจะเริ่มต้น - ในซีดี เราจะตรวจสอบสถานะของ daemonSet อย่างต่อเนื่อง หาก daemonSet อยู่ในสถานะ "พร้อม" เราจะดำเนินการปรับใช้ มิฉะนั้น เราจะรอให้ daemonSet พร้อม
การเปรียบเทียบ
การเปรียบเทียบขั้นตอนเดิมและขั้นตอนที่อัปเดตกับกระบวนการอุ่นล่วงหน้า
ขั้นตอน | ขั้นตอนการใช้งาน Init | ขั้นตอนก่อนการใช้งาน | การใช้งาน | เวลารวม | ต่าง |
---|---|---|---|---|---|
โดยไม่ต้องอุ่นเครื่อง | 0 | 0 | 11นาที 21วินาที | 11นาที 21วินาที | 0 |
ด้วยการอุ่นเครื่องล่วงหน้า | 8 วินาที | 58 วินาที | 25 วินาที | 1นาที 31วินาที | -9นาที50วินาที |
สิ่งสำคัญคือเวลา "Deploy" เปลี่ยนไป (จากคำสั่ง Apply แรกไปจนถึงสถานะ Running ของพ็อด) จาก 11 นาที 21 วินาที เป็น 25 วินาที เวลารวมเปลี่ยนจาก 11 นาที 21 วินาที เป็น 1 นาที 31 วินาที
ประเด็นสำคัญคือ หากไม่มีอิมเมจฐานจากสาขาหลัก เวลา "ปรับใช้" จะเท่ากับเวลาเดิมหรืออาจมากกว่าเล็กน้อย แต่ถึงอย่างไร เราก็ได้แก้ไขปัญหาความดันดิสก์และเวลาเริ่มต้นระบบแบบเย็นแล้ว
บทสรุป
ปัญหาหลัก ContainerCreating
ได้รับการแก้ไขด้วยกระบวนการวอร์มอัพ ผลที่ได้คือ เราลดเวลาการเริ่มระบบแบบเย็นของ POD ได้อย่างมาก
แรงกดดันของดิสก์หายไปแล้ว เนื่องจากเรามีอิมเมจพื้นฐานบนโหนดแล้ว daemonSet ของระบบอยู่ในสถานะ "พร้อม" และ "มีสุขภาพดี" (เนื่องจากไม่มีแรงกดดันของดิสก์) และเราไม่พบข้อผิดพลาด ErrImagePull
ใดๆ ที่เกี่ยวข้องกับปัญหานี้
วิธีแก้ปัญหาและลิงค์ที่เป็นไปได้
- ใช้อินสแตนซ์ ตามความต้องการ สำหรับโหนดแทนอิน สแตนซ์จุด
เราไม่สามารถใช้วิธีนี้ได้ เนื่องจากเกินขอบเขตงบประมาณสำหรับสภาพแวดล้อมที่ไม่เกี่ยวข้องกับการผลิต - ใช้ประเภทไดรฟ์ข้อมูล Amazon EBS gp3 (หรือดีกว่า) พร้อม IOPS ที่เพิ่มขึ้น
เราไม่สามารถใช้วิธีนี้ได้ เนื่องจากฟีเจอร์นี้ยังเกินขอบเขตงบประมาณสำหรับสภาพแวดล้อมที่ไม่ใช่การผลิตอีกด้วย นอกจากนี้ AWS ยังมี ข้อจำกัด ของ IOPS สำหรับบัญชีของคุณในแต่ละภูมิภาคอีกด้วย - ลดเวลาเริ่มต้นคอนเทนเนอร์บน Amazon EKS ด้วยปริมาณข้อมูลของ Bottlerocket
จริงๆ แล้วเราไม่สามารถเคลื่อนตัวในลักษณะนี้ได้ เพราะมันมีผลกระทบต่อการผลิตและสภาพแวดล้อมอื่นๆ มากเกินไป แต่นี่ก็เป็นวิธีแก้ปัญหาที่ดีเช่นกัน - การแก้ไขปัญหา Kubernetes Cluster Autoscaler ใช้เวลา 1 ชั่วโมงในการปรับขนาดพ็อด 600 พ็อดขึ้น
ป.ล.: ฉันอยากจะแสดงความชื่นชมต่อทีมงานด้านเทคนิคที่ยอดเยี่ยมของ Justt ( https://www.linkedin.com/company/justt-ai ) สำหรับการทำงานอย่างไม่รู้จักเหน็ดเหนื่อยและแนวทางที่สร้างสรรค์อย่างแท้จริงในการแก้ไขปัญหาต่างๆ ที่พวกเขาเผชิญ โดยเฉพาะอย่างยิ่ง ขอแสดงความชื่นชมต่อ Ronny Sharaby หัวหน้าที่ยอดเยี่ยมซึ่งรับผิดชอบต่องานอันยอดเยี่ยมที่ทีมงานกำลังทำอยู่ ฉันตั้งตารอที่จะเห็นตัวอย่างที่ยอดเยี่ยมมากขึ้นเรื่อยๆ ว่าความคิดสร้างสรรค์ของคุณส่งผลต่อผลิตภัณฑ์ของ Justt อย่างไร