ผมเคยเจอ pipeline ที่ทุกคนในทีมภูมิใจมาก ทุก helm upgrade มี --atomic ติดไว้หมด เหตุผลคือ "deploy พังแล้วมันย้อนให้เอง ไม่ต้องตื่นมาแก้" ฟังดูดีใช่ไหม ผมก็เห็นด้วยอยู่หลายเดือน จนวันหนึ่งมีคนถามว่า "ไหนดู log ของ pod ที่ทำให้ deploy เมื่อวานล้มหน่อย" แล้วทุกคนก็เงียบ เพราะ pod นั้นถูกลบไปพร้อม rollback แล้ว เหลือแค่บรรทัดใน CI ที่เขียนว่า UPGRADE FAILED: timed out waiting for the condition
นี่คือ hot take ของผม: --atomic เป็นเครื่องมือที่ดี แต่การใส่มันเป็น default ทุก release โดยไม่คิดคือนิสัยที่ควรเลิก ไม่ใช่เพราะมันแย่ แต่เพราะมันทำให้เรา "รู้สึกปลอดภัย" มากกว่าที่ความจริงเป็น
บทความนี้เป็นความเห็นเชิงแนวปฏิบัติ ไม่ได้ตำหนิ tool หรือใคร ตัวอย่างทั้งหมดเป็นของสมมติ ไม่ได้มาจากระบบของบริษัทไหน
สรุปสั้นๆ ว่า --atomic ทำอะไร
helm upgrade --atomic ทำสองอย่างรวมกัน อย่างแรกคือรอให้ resource พร้อม (เหมือนใส่ --wait) อย่างที่สองคือถ้ารอไม่ผ่านหรือ timeout ก็สั่ง rollback ไปยัง revision ก่อนหน้าให้เอง
helm upgrade my-app ./chart \
--install \
--atomic \
--timeout 5m \
-f values-prod.yaml
ข้อมูลที่ควรรู้ไว้ก่อนอ่านต่อ: ใน Helm 4 flag นี้ถูกเปลี่ยนชื่อเป็น --rollback-on-failure (จาก changelog ของ Helm เป็น breaking change ที่ลงไว้ตั้งแต่ช่วงสิงหาคม 2025) และมีการคืน --atomic กลับมาแบบ deprecated ให้บางคำสั่งเพื่อความเข้ากันได้ ดังนั้นถ้าทีมคุณยังเขียน pipeline ด้วย --atomic อยู่ แล้ววันหนึ่งอัปเกรด Helm client เป็นเวอร์ชัน 4 ให้ไล่เช็คทุกที่ที่เรียกใช้ อย่าเดาว่ามันจะทำงานเหมือนเดิม
เรื่องเวลาก็ควรจดไว้ด้วย: บทความ migration ที่ผมอ่านระบุว่า Helm 3 จะได้ security patch ถึงวันที่ 11 พฤศจิกายน 2026 ซึ่งก็คือสัปดาห์นี้เลย ผมยืนยันจากแหล่งเดียว ถ้าจะใช้วางแผนจริงให้ไปเช็คประกาศของ Helm โดยตรงอีกที
เหตุผลที่ 1: rollback ไม่ย้อนข้อมูล
ประโยค "ย้อนให้เอง" ฟังเหมือนมีปุ่ม undo ของทั้งระบบ แต่ helm rollback ย้อนแค่ manifest ที่ Helm จัดการ คือ Deployment, ConfigMap, Service และของที่อยู่ใน release นั้น มันไม่ย้อน schema ของ database ที่ pre-upgrade hook เพิ่งรัน migration ไปแล้ว ไม่ย้อนข้อความที่ consumer เพิ่งอ่านไปจาก queue และไม่ย้อน object ที่ถูกสร้างนอก release
ลองนึกภาพ สมมติ chart มี hook รัน migration เพิ่ม column ใหม่ แล้ว pod เวอร์ชันใหม่ crash เพราะ config ผิด --atomic เห็นว่า timeout ก็ rollback กลับไป image เก่า ซึ่งตอนนี้เจอ schema ที่หน้าตาไม่เหมือนที่มันคาดไว้ ถ้าโชคดี column ที่เพิ่มเป็น nullable ทุกอย่างรอด ถ้าโชคร้าย migration ที่ลบหรือเปลี่ยนชนิดข้อมูลไปแล้วกลับมาไม่ได้ เราจะได้ระบบที่ "rollback สำเร็จ" ตาม Helm แต่พังจริงในแง่ข้อมูล
ข้อสรุปของผม: ถ้า release มี hook ที่แตะข้อมูล อย่าปล่อยให้ tool ตัดสินใจ rollback แทนคน อย่างน้อยต้องมีกติกาเรื่อง migration แบบ backward compatible (expand แล้วค่อย contract) ก่อนจะวางใจ rollback อัตโนมัติ
เหตุผลที่ 2: มันซ่อนหลักฐานตอนที่เราต้องการมันที่สุด
กลับไปที่เรื่อง pod ที่ถูกลบ ตอน deploy ล้ม สิ่งที่มีค่าที่สุดคือสถานะของ pod ที่กำลังพัง: kubectl describe pod ที่บอกว่า probe ล้มเพราะอะไร events ที่เห็น ImagePullBackOff หรือ OOMKilled และ log ของ container ตัวล่าสุดก่อนตาย
พอ --atomic rollback เสร็จ pod ชุดใหม่ถูกลบ ของพวกนี้หายไปพร้อมกัน เหลือแค่ข้อความ error กว้างๆ จาก Helm ที่บอกแค่ว่า "รอไม่ทัน" ไม่ได้บอกว่าทำไม ผมไม่ได้เป็นคนแรกที่พูดเรื่องนี้ มีบทความของชุมชนที่ตั้งชื่อประมาณว่า atomic rollback ทิ้งเราไว้ในที่มืด และผมเห็นด้วยกับประเด็นนั้นทุกตัวอักษร
วิธีแก้ที่ไม่ต้องเลิกใช้ --atomic คือเก็บหลักฐานก่อนที่ rollback จะเกิด แต่ปัญหาคือ --atomic ไม่มีช่วงให้เรา "แทรก" ได้ มันรอแล้ว rollback ใน command เดียว ถ้าอยากเก็บหลักฐานจริงๆ ก็ต้องแยกเป็นสองขั้นตอนใน pipeline ซึ่งเราจะพูดถึงด้านล่าง
เหตุผลที่ 3: timeout ที่เดามั่วคือสาเหตุของ rollback ที่ไม่จำเป็น
--atomic ผูกกับ --timeout (ค่า default ของ Helm คือ 5 นาที) ลองดูสถานการณ์ที่เจอบ่อย:
- image ใหญ่และ node ใหม่ต้อง pull ครั้งแรก
- cluster กำลัง scale node เพราะ pod ใหม่ยังไม่มีที่ลง
- readiness probe ตั้ง
initialDelaySecondsยาวเพราะแอปต้อง warm cache
ทั้งสามเรื่องไม่ได้แปลว่า deploy พัง แปลว่า "ช้ากว่าที่เราคาด" แต่ --atomic แยกไม่ออก มันเห็นแค่ว่าเกินเวลา แล้ว rollback ทันที ผลคือเรามี deploy ที่ใกล้จะสำเร็จ ถูกย้อนกลับ แล้วต้องมานั่งรันใหม่ ถ้า timeout ของเราตั้งตามความรู้สึก flag นี้จะกลายเป็นตัวสุ่มที่ทำให้ pipeline แดงโดยไม่มีเหตุผลจริง
ข้อนี้อาจเจอหนักขึ้นในช่วงที่เราเปลี่ยนไป Helm 4 เพราะ --wait ใน Helm 4 เป็น strategy flag มีโหมดให้เลือก (ตามบทความ migration ที่ผมอ่าน --wait เปล่าๆ ใช้ strategy แบบ watcher ที่อาศัย kstatus ตรวจความพร้อม ส่วนถ้าไม่ใส่เลยจะเป็นโหมดที่ไม่รอ resource ปกติ และมี legacy ไว้ย้อนพฤติกรรมเก่า) การที่นิยามของคำว่า "พร้อม" เปลี่ยน แปลว่าเวลาที่ใช้รอก็อาจเปลี่ยนตาม ควรทดสอบ timeout ใหม่บน staging ก่อน อย่าเชื่อว่าตัวเลขเดิมยังใช้ได้
เหตุผลที่ 4: rollback ที่ไม่มีใครรู้ คือ deploy ที่คนเข้าใจผิดว่าผ่าน
อันนี้เรื่องคนล้วนๆ เมื่อ pipeline แดง คนรู้ว่าต้องทำอะไรต่อ แต่ถ้า rollback เกิดแล้ว pipeline แจ้ง fail ใน job เดียว ไม่มีใครดู ระบบก็กลับมาที่เวอร์ชันเก่าอย่างเงียบๆ ฟีเจอร์ที่ทีม product รอไม่ขึ้น คนส่วนใหญ่จะคิดว่า "deploy แล้วนี่" เพราะ merge ไปแล้ว
ผมเลยมองว่า rollback ทุกครั้งต้องเป็น event ที่มีคนเห็น: notify เข้า channel ของทีม หรือเปิด ticket อัตโนมัติ ไม่ใช่แค่ exit code ไม่เท่ากับศูนย์ใน log ที่ไม่มีใครเปิด
แล้วควรทำยังไงแทน
ผมไม่ได้บอกให้เลิกใช้ --atomic ผมบอกให้ใช้ให้ถูกที่ นี่คือกติกาที่ผมใช้กับทีมในตัวอย่างสมมตินี้
แยก release ตามความเสี่ยง release ที่ stateless และไม่มี hook ที่แตะข้อมูล (frontend, API ที่ไม่มี migration ในรอบนั้น) ใช้ --atomic ได้สบายใจ ส่วน release ที่มี migration ให้ใช้ --wait กับ timeout ที่คำนวณจากข้อมูลจริง แล้วให้คนตัดสินใจ rollback เอง
เก็บหลักฐานก่อนย้อน ถ้าไม่ใช้ --atomic ให้เขียน step ใน pipeline แบบนี้:
set +e
helm upgrade my-app ./chart --install --wait --timeout 8m -f values-prod.yaml
rc=$?
set -e
if [ $rc -ne 0 ]; then
# เก็บหลักฐานก่อน rollback
kubectl -n my-ns get events --sort-by=.lastTimestamp | tail -n 50 > evidence-events.txt
kubectl -n my-ns describe pods -l app.kubernetes.io/instance=my-app > evidence-pods.txt
kubectl -n my-ns logs -l app.kubernetes.io/instance=my-app --tail=200 --all-containers > evidence-logs.txt || true
helm rollback my-app --wait --timeout 5m -n my-ns
# แจ้งทีม แล้ว fail pipeline ให้แดงชัดๆ
exit 1
fi
สคริปต์นี้เป็นโครงตัวอย่าง ปรับ namespace, label และการ upload artifact ให้เข้ากับ CI ของคุณเอง ประเด็นสำคัญคือ "เก็บก่อน ย้อนทีหลัง"
ตั้ง timeout จากข้อมูล ไม่ใช่ความรู้สึก ดูเวลา rollout จริงของ release ย้อนหลังสัก 10 ครั้ง เอาค่าสูงสุดมาบวกส่วนเผื่อ แล้วใช้ตัวเลขนั้น อย่าปล่อย default ไว้เฉยๆ
ตรวจเรื่อง Helm 4 ก่อนอัปเกรด client ไล่ grep หา --atomic และ --wait ใน pipeline ทั้งหมด ทดสอบบน staging ว่าพฤติกรรมตรงกับที่คิดไหม โดยเฉพาะเรื่อง wait strategy และ server-side apply (จากบทความที่อ่าน Helm 4 ใช้ server-side apply เป็น default กับ install ใหม่ ส่วน release ที่ย้ายมาจะคงวิธีเดิมไว้จนกว่าจะสั่งเปลี่ยน)
ข้อโต้แย้งที่ผมรู้ว่าจะโดน
"ถ้าไม่มี --atomic แล้วตี 3 deploy พัง ใครจะมาย้อน" ข้อนี้แฟร์มาก ตอบตรงๆ คือถ้าทีมคุณไม่มี on-call ที่ตอบเรื่อง deploy ได้ --atomic ก็คุ้มกว่าการปล่อยระบบค้างครึ่งๆ กลางๆ ผมไม่ได้บอกว่ามันผิด ผมบอกว่ามันมีต้นทุน และต้นทุนนั้นควรถูกเลือกโดยตั้งใจ ไม่ใช่ติดมากับ template ที่ copy กันมา
อีกข้อคือ "เก็บหลักฐานเองก็ซับซ้อนกว่า" ใช่ ซับซ้อนกว่าแน่ๆ แต่ความซับซ้อนนั้นจ่ายครั้งเดียวใน pipeline template ส่วนความลับของ "ทำไมมันล้ม" ที่หายไปทุกครั้ง ต้องจ่ายซ้ำทุกครั้งที่เกิดเรื่อง
สรุปสำหรับคนรีบ
--atomic เหมาะกับ release ที่ stateless และมี timeout ที่ผ่านการวัดแล้ว ไม่เหมาะเป็น default ของทุกอย่าง เพราะ rollback ไม่ย้อนข้อมูล มันลบหลักฐานของ pod ที่พัง timeout ที่เดาเอาทำให้ย้อนโดยไม่จำเป็น และ rollback เงียบๆ ทำให้คนเข้าใจผิดว่าของขึ้นแล้ว ถ้าคุณกำลังจะย้ายไป Helm 4 ถือโอกาสนี้ทบทวนทั้งชื่อ flag และความหมายของ --wait ไปพร้อมกัน
เห็นต่างยังไงมาเถียงกันได้ครับ ผมอยากรู้ว่าทีมไหนใช้กติกาต่างจากนี้แล้วเวิร์ก
Sources: Helm 4 changelog (helm.sh/docs/changelog), บทความ migrate Helm 3 to Helm 4 (computingforgeeks.com)

Top comments (0)