สรุปเรื่อง Kubernetes Services บน GKE หลังศึกษาและทดลองอย่างเอาจริงเอาจัง

Kubernetes เป็นระบบจัดการ Container ที่เป็นที่นิยมสูงมากในตอนนี้ ส่วนตัวผมแล้วก็พยายามเรียนรู้และใช้งานอยู่จริงเช่นกัน ในเรื่องของการ Deploy พวกนี้ผมถือว่าไม่มีปัญหาอะไร มีตัวอย่างให้ดูกันบนอินเทอร์เน็ตมากมาย จนกระทั่งมาถึงจุดที่ต้องการจะส่ง Traffic ออกมานอก Cluster เมื่อนั้นก็เริ่มเกิดความสับสนว่าตกลงมันต้องทำแบบไหนกันแน่ ซึ่งปัญหาทั้งหมดทั้งปวงมันเกิดจากผมไม่เข้าใจเรื่อง Service อย่างถ่องแท้นั่นเอง จะเนื่องจาก Official Doc เขียนไม่เคลียร์ หรือเป็นเพราะผมพื้นฐานไม่ดีพอที่จะอ่านมันแล้วเข้าใจก็สุดแล้วแต่ สุดท้ายเมื่อไม่นานมานี้ผมก็เลยเปิดการทดลองใช้งาน Service อย่างเอาจริงเอาจัง หลังจากใช้มั่วแบบงูๆ ปลาๆ มานาน เอาให้รู้กันไปว่าตกลงอะไรมันทำอะไรกันแน่ แต่จะประเภทมันต่างกันอย่างไร Configuration แต่ละตัวที่ใช้อยู่ประจำมันส่งผลยังไงกันแน่ จึงได้มาเขียนสรุปเรื่อง Services ชนิดต่างๆ บน Kubernetes ไปจนถึงการเอาไปใช้งานจริงว่า เมื่อไหร่ ควรจะใช้แบบไหนกันแน่ เผื่อจะช่วยล่นเวลาคนที่กำลังสับสนอยู่เช่นกัน

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

ก่อนจะเริ่มกัน ผมขอขอบคุณบทความจาก https://medium.com/@metaphorical/internal-and-external-connectivity-in-kubernetes-space-a25cba822089 และโพสนี้ https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0 ที่ช่วยวาดรูปสวยๆ ให้ดูพร้อมคำอธิบาย ช่วยให้เห็นภาพมากขึ้น แม้สุดท้ายจะไม่มากพอที่จะทำให้ผมเข้าใจจนเอาไปประยุกต์ใช้งานได้อยู่ดี แต่ก็อยากให้ทุกคนเข้าไปอ่านกันก่อนที่จะเริ่มต้น เราจะได้มีพื้นฐานที่ตรงกัน และจะได้ดูภาพประกอบด้วย ผมไม่สามารถวาดภาพสวยๆ แบบนั้นได้เลย 555


Service คืออะไร?

เรื่องมันมีอยู่ว่า เรามี Pod อันเดียวกันกระจัดกระจายเต็มไปหมดบน Node ที่วางอยู่หลายๆ ตัว แถว Node แต่ละตัวก็ถือ Pod ของคนละแอปกันอยู่ด้วย คำถามคือ เราจะสามารถ Route Traffic เข้าไปหา Pod ให้ถูกต้องได้อย่างไร วิธีการก็คือ เราก็ต้องสร้าง Router ขึ้นมานั่นเอง Router ทุกๆ ตัวถูก Config เหมือนกันบนทุกๆ Node ทำให้รู้ว่า ถ้ามี Traffic แบบนี้วิ่งผ่าน จะติดต่อไปหา Node ไหน แล้วพอถึง Node ปลายทางแล้ว จะต้องติดเข้าไปหาที่ Pod ไหน Container ใด ที่ Port ใด นี่คือหน้าที่ของ Service ครับ มันคือ Router นั่นเอง ซึ่งจากที่ศึกษาดูความจริงมันก็คือ Deployment Pod ดีๆ นี่เอง เพียงแต่เขาแยกประเภทออกมาเพราะมันมีหน้าที่เฉพาะเจาะจงของมัน มีความสำคัญ และถูกใช้งานเยอะมากจริงๆ

ซึ่งจะมองดูแล้วการที่ Service สามารถทำ Routing ไปหา Pod ที่ถูกต้องให้เราได้นั้น มันก็มีความเป็น Load Balance ไปในตัวด้วยนั่นเอง นี่เป็นคุณสมบัติที่น่าสนใจมากว่า การสร้าง Service คือการสร้าง Internal Load Balancer นั่นเอง

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


Cluster IP

https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0

Cluster IP เป็น Service Default ที่ Kubernetes จะสร้างให้ถ้าเราไม่ได้ใส่ Type ของ Service ลงไป หน้าที่ของมันก็คือ การสร้าง Virtual IP พร้อมกับ DNS ที่เป็นชื่อของ Service ขึ้นมาบน Cluster ของเรา แล้วก็แมพมันเข้ากับ Pod ที่เราต้องการที่ถูกกำหนดลงใน Selector ทำให้ใครก็ตามที่ต้องการจะใช้งาน Pod ดังกล่าว สามารถต่อผ่านทาง Service นี้ได้ ไม่ว่าจะอยู่บน namespace ใดของ Cluster ก็ตาม

โดยวิธีการเรียกใช้งานที่ยอดนิยมผ่านทาง curl โดยการใช้ URL แบบ <SERVICE_NAME>:<PORT> ในกรณีที่ใช้งานอยู่บน Namespace เดียวกัน และถ้าต้องการเรียกมาจากต่าง Namespace ทำได้โดยการใช้ URL ว่า <SERVICE_NAME>.<NAMESPACE>:<PORT> ด้วยเหตุนี้ทำให้เราสามารถจัดกลุ่มและวาง Deployment ที่อยู่เบื้องหลังไว้คนละ Namespace แล้วยังสามารถเรียกมาเชื่อมต่อเข้าหากันได้ ช่วยให้จัดการบริหารได้ง่ายขึ้นอย่างมาก ไม่ใช่ว่า สั่ง get svc แล้วทุกอย่างโชว์ขึ้นมาเต็มจอจนหาไม่เจอว่าจะเอาอะไร

ต่อมาเรามาดูตัวอย่างของ Cluster IP กัน (ผมจะไม่พูดถึงเรื่อง Deployment นะครับ)

ผมขอบรรยายดังนี้

Cluster IP Service นี้ มีชื่อว่า cluster-ip-demo วางอยู่บน namespace ชื่อ namespace-demo เราจึงได้ dns มาชื่อว่า cluster-ip-demo.namespace-demo นั่นเอง

ไปกันต่อที่ selector อันนี้คือสิ่งที่จะไปแมพกับ deployment ซึ่ง Service นี้จะไปเรียกใช้งานต่อจาก Pods ทั้งหลายที่มี label ตรงกับที่กำหนดใน selector นั่นเอง โดยเราจะทำการใช้งาน 1 service ต่อ 1 ประเภทของ label (ขอหยุดอธิบายตรงนี้ก่อน ก่อนจะพาสับสนไปกันใหญ่)

ต่อมาไปที่ชวนปวดหัวที่สุด ก็คือ ports สำหรับ Service นี้ ทำการ Forward เพียง Port เดียว

โดยตัว Port ของ Service หรือ Port ขาเข้า ใช้เลข 8080 เป็น TCP ย้ำอีกครั้งก็คือ ถ้าเราจะเรียกใช้งาน Service นี้ คือใช้ผ่านตัวแปร port นั่นเอง ซึ่งตอนนี้คือ tcp:8080 ดังนั้นเวลาเรียกใช้งานผ่าน curl ก็จะเป็นประมาณนี้ https://cluster-ip-demo.namespace-demo:8080

พอเข้ามาทาง 8080 แล้ว มันจะส่งต่อไปที่ targetPort ก็คือ 80 ซึ่งตรงนี้จะตรงกับ spec ของ Pods ของเรานั่นเอง ซึ่งนั่นหมายความว่า Pods ของเรา จะต้องเปิด containerPort 80 เอาไว้นั่นเอง

อ่อ port 8080 เนี่ย มันสามารถซ้ำกันได้ใน cluster นะครับ เพราะว่ามันอิงไปตาม domain ให้จินตนาการว่า การสร้าง Cluster IP เป็นการสร้าง Load Balancer ขึ้นมาให้เราใหม่ทั้งชิ้น มี domain name ให้ใช้อย่างสวยงาม ดังนั้นเราไม่ต้องไปกลัวว่า port จะไปชนกับชาวบ้านเลย ดังนั้นตั้งชื่อตั้งเลขให้มันเป็น standard ที่จะใช้กันได้ตามสบาย

ความจริงมันก็คล้ายกับเวลาเรา Forward Port ในเร้าเตอร์เนี่ยแหละ สถาการณ์ตอนนี้ก็คือ ถ้ามี Traffic เข้ามาจาก cluster-ip-demo.namespace-demo:8080 ให้ส่งต่อไปที่ Pod ใน selector โดยต่อเข้าไปที่ Port ภายในคือ 80 นั่นเอง ซึ่งไอ้ network ภายในก็ไปจัดการกันเองในฝั่ง application ละ เพียงแต่ว่า เจ้า Cluster IP นี้ มันเป็น domain ที่เราเอาไว้ใช้งานภายใน Cluster มันไม่สามารถเรียกใช้งานจากข้างนอกได้ ด้วยเหตุนี้ เราจึงสามารถตั้งชื่อ DNS สวยๆ ให้กับมันได้นั่นเอง 🙂


NodePort

https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0

มาต่อกันที่ NodePort เจ้านี่แหละคือการ Forward Port บนเร้าเตอร์ตามบ้านอย่างแท้จริงเลย เพราะมันคือการเปิด Port ให้สามารถเข้าใช้งานได้จากภายนอก โดยการแมพ external port เข้ากับ internal port นั่นเอง แต่ฝั่ง internal แทนที่จะเป็น IP:PORT แต่มันดันเป็น selector ของ Pods เท่านั้นเอง (พอดๆ พอตๆ เต็มไปหมด เขียนเองยังสับสน)

ย่อหน้านี้คือสิ่งที่ผมอยากเขียนที่สุดในบทความนี้ ความต่างของ NodePort กับ Cluster IP ก็คือ
– NodePort ใช้สำหรับ External Use แต่ Cluster IP ใช้สำหรับ Internal Use
– External Port ของ NodePort ต้องไม่ซ้ำกันใน Cluster เพราะมันคือ Port ของ Cluster ของเรา ใช้ร่วมกันทั้ง Cluster ดังนั้นต้องห้ามซ้ำ
– เราสามารถใช้ NodePort แล้วเข้าใช้งานผ่านจากทาง Ingress หรือทาง External IP ได้ แต่เราทำแบบนี้กับ Cluster IP ไม่ได้ Cluster IP ใช้กับ Ingress ไม่ได้ ผมลองมาแล้วกับมือบน GKE เล่นเอามึนติบอยู่นาน

อย่างไรก็ดี สุดท้ายแล้วไม่ว่าจะ NodePort หรือ Cluster IP เราก็สามารถเรียกใช้งานได้ด้วย name.namespace:port ได้ทั้งคู่ ที่เล่าตรงนี้ให้ฟังเพื่อที่ว่าจะได้เอาไว้ Debug Service ได้ สามารถ curl เช็คความถูกต้องได้โดยง่าย แต่ถ้าจะใช้ภายใน การใช้ Cluster IP นั้นเหมาะสมกว่า เพราะไม่ต้องเป็นกังวลเรื่องการ Expose port ออกไปข้างนอก ในทางตรงกันข้าม สำหรับ Frontend Service ที่ต้องการต่อออกไปข้างนอก ให้ใช้ NodePort

เอาล่ะ มาดูหน้าตา YAML กันว่ามันเป็นยังไง

สังเกตว่าหน้าตามันก็เหมือนกับ Cluster IP เนี่ยแหละ แต่มี NodePort เพิ่มขึ้นมาใน ports ซึ่งมัน Port ที่เราจะ Expose ออกไปข้างนอก Cluster นั่นเอง ซึ่งเวลาใช้งานจริง NodePort ตรงนี้ไม่ต้องเขียนก็ได้ เพราะ ​Ingress มันแมพเข้ากับ Service อยู่แล้ว กล่าวก็คือ เราตั้งให้ Ingress เข้ามาทาง Port 8080 ของ node-port-demo ได้เลย


Load Balancer

https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0

ไอ้เจ้าเนี่ยแหละที่ทำผมงงหนัก เพราะผมพยายามตบตีกับการพยายามทำ Load Balancer แต่ความจริงไม่ต้องไปยุ่งก็ได้ถ้าเราใช้ของ GKE ที่มันบอก Load Balancer เนี่ย มันคือ External Load Balancer ของ Cloud Provider ครับ

ทำไมต้องมีมันล่ะ ความจริงก็เข้าทาง Node IP ก็ได้ไม่ใช่หรอ? ลองจินตนาการดูว่า ตอนนี้คุณมี Node หลายตัว แล้วจะสร้างทางเข้าทางเดียว จะทำยังไง ให้ DNS A Record เอาดีไหม มันทำแบบนั้นไม่ได้นะ เพราะว่า Node ของคุณมันขึ้นๆ ลงๆ อยู่ตลอดเวลา คุณจะมั่นใจได้ไงว่า Node ที่ชี้อยู่จะไม่หายไป ทาง Cloud Provider เนี่ย เขาก็ได้จัดเตรียม Service พิเศษขึ้นมา สร้างเครื่องขึ้นมาให้เราขึ้นมาหลายๆ เครื่อง แล้วแมพให้เข้าถึงได้จาก IP Address เดียว ทีนี้เราก็สามารถตั้ง Domain Name ให้ชี้มาที่ IP นี้ได้เลย ประมาณนั้น

ทีนี้สำหรับ GKE เนี่ย เราไม่ต้องไปยุ่งกับตัวนี้เลย เราสามารถกระโดดข้ามไปใช้งาน Ingress เพื่อเชื่อมต่อเข้ากับ NodePort ได้เลย


Ingress

https://medium.com/google-cloud/kubernetes-nodeport-vs-loadbalancer-vs-ingress-when-should-i-use-what-922f010849e0

มาถึงตัวสุดท้ายแล้ว นั่นก็คือ Ingress นั่นเอง คุณมาได้ไกลมากแล้ว 555

อธิบายแบบสั้นที่สุดเลยก็คือ Ingress คล้ายๆ กับการทำ Proxy Pass ใน nginx นั่นเอง คิดซะว่า nginx ของเราเป็น instance 1 ตัว ที่เราอยากจะให้มันมีหลายๆ IP จากนั้นเราก็ทำการ Forward Traffic เข้ามาให้กับ Service ข้างใน Cluster นั่นเอง

แต่ทีนี้มันจะมาติดไอ้ตรงว่าต้อง Config Static IP ยังไง แล้วจะทำ HTTPS ยังไงเนี่ยล่ะ อันนี้ Challenge ตัวจริง ซึ่งในโพสนี้ผมขอเล่าเฉพาะเรื่อง Static IP ก่อน ส่วน HTTPS นี้ซับซ้อน จะขอเล่าในโพสต่อไป

อ่อ! ในตัวอย่างนี้เราจะใช้ Load Balancer ของ GCE กันนะครับ ดังนั้นตอนตั้ง Cluster ให้ Enable Option นี้เอาไว้ด้วย ซึ่งมันเปิดไว้เป็น default อยู่แล้ว ถ้าใครไม่ได้เปิดเอาไว้ ก็ไปหาวิธีการเปิดขึ้นมาใช้งานกันเองเด้อ

แล้วก็ก่อนจะตั้ง Ingress ผมแนะนำให้คุณเริ่มสร้าง Static IP ก่อนเลย เพราะไม่งั้น IP เราจะเปลี่ยนไปมา ทำให้ไม่สามารถตั้ง DNS ได้ วิธีการนั้นแสนง่าย เพียงรันคำสั่งนี้

แก้ demo-static-ip ก่อนเอาไปรันจริงนะครับ

มันเป็นชื่อ static ip ที่จะใช้ใน cluster แต่ในตัวอย่างผมจะใช้ชื่อนี้ต่อไป สร้างเสร็จแล้วไปเปิดดูตรง External IP บน GCP ก็จะเห็น IP ของเราโผล่ขึ้นมา ให้ชี้ A Record มาหาได้เลย

ทีนี้เรามาดู YAML ของ Ingress กัน

ส่วนใหญ่น่าจะพออ่านเข้าใจได้อยู่แล้ว แต่อยากให้สังเกตที่ kubernetes.io/ingress.global-static-ip-name อันนี้ให้ตั้งเป็นชื่อ static ip ที่เราได้มาก่อนหน้านี้นั่นเอง

ต่อมาลงมาที่ rules อันนี้ก็คือบอกว่า ถ้าเข้ามาแล้วจะให้ใช้ไปที่ Service ไหน ซึ่ง Service ที่ว่านี้ จะต้องเป็น NodePort นะครับ ผมทำให้มันต่อเข้ากับตัวอย่างของ NodePort ของบนนั่นเอง โดยมี Parameters ที่สำคัญดังนี้

  • host อันนี้ก็คือชื่อ domain name ที่จะเข้ามานั่นเอง มันเหมือนกับการทำ virtual host บน nginx ไม่มีผิด
  • http บอกว่ามันเป็น service ประเภท http จะได้ expose port 80 (และ 443 ถ้ามา TLS) ออกไปให้ใช้งาน
  • paths อันนี้เป็น rule ซ้อนเข้าไปอีกว่า ถ้าเข้ามาที่นี้แล้วให้วิ่งไปที่ service ใด เหมาะสำหรับการทำ micro-service อย่างยิ่ง
    • path ตัวนี้บอกละว่า ถ้าเข้ามาทาง / ใดๆ ก็ตาม ให้ใช้ rule นี้
    • backend บอกว่า ให้วิ่งไปที่ที่ไหน
    • serviceName ใส่ชื่อ Service ที่เราต้องการลงไป ขอย้ำอีกครั้งว่าต้องเป็น NodePort ใช้ Cluster IP กับ Ingress ไม่ได้นะครับ
    • servicePort ใส่ Port ของ Service ลงไป

เท่านี้แหละครับที่ต้องรู้ ทีนี้เราจะสร้างกี่ host กี่ path ก็ลอกๆ ออกมาใช้งานได้เลย

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

หลังจากเซทอะไรถูกต้องหมดแล้วเนี่ย ถ้าไปดูใน GKE Console บนเว็บ เราควรจะเห็นเครื่องหมายถูกสีเขียวขึ้นตรง Ingress ของเรา ถ้าไม่ล่ะก็ ก็ดูว่ามันติดที่อะไร เดี๋ยวนี้ Console บน GKE ดีขึ้นมากแล้ว ไม่งั้นผมก็คงไม่รอดมาถึงจุดนี้เช่นกัน 555 ถ้ามันเขียวแล้วแล้วก็ลองเปิดผ่านทาง Domain Name ที่ตั้งเอาไว้ได้เลย ถ้าไม่ขึ้น ก็รอสักพัก ถ้าเกิน 10 นาทีแล้วยังไม่ได้ ทีนี้น่าจะต้องมีปัญหาขึ้นแน่นอน

อ่อ ให้ระวังเรื่องโปรแกรมของเราที่ / จะต้อง Return HTTP Status 200 นะครับ เจ้าตัว Load Balancer มันจะเช็คว่าโปรแกรมของเราเป็นหรือตายด้วย Condition นี้ จุดนี้ก็เล่นเอาผมตกม้าตายอยู่นานเหมือนกัน จริงๆ มันมีทางเปลี่ยนแหละแต่ผมทำแล้วยังไม่สำเร็จ เพราะขี้เกียจรอมันแก้ Config นานๆ เลยลองไม่สุด

อีกเรื่องก็คือ ไอ้ Load Balancer นี่มันคิดเงินแยกนะครับ ไปเช็คราคาดูด้วย


ทั้งหมดทั้งมวลในเเรื่อง Service ของ GKE ที่ควรจะต้องรู้ก็ประมาณนี้ละครับ รู้เท่านี้ก็สามารถเอาไปใช้งานจริงได้ประมาณหนึ่งละ ผมว่าที่มันยากมันอยู่ที่เราไม่รู้วิธีการ Debug ที่ถูกต้อง ซึ่งมันก็คือการเข้าไป curl เข้าหา Service ซะเลยเนี่ยแหละ ส่วนอีกจุดก็คือเพราะว่า Load Balancer มันมีหน่วงเวลาในการ Config ทีนี้เซทถูกเซทผิด ใช้ Config อันไหนอยู่กันแน่ก็เลยสับสนไปกันหมด แต่หลังจากจบโพสนี้แล้วก็หวังว่าทุกคนก็น่าจะเข้าใจเห็นภาพกันมากขึ้นว่า ที่จริงแล้วเราจะต้องเลือกใช้ Service ตัวไหนกันแน่ แล้วจะ Expose Traffic ออกมาอย่างถูกท่า จะต้องทำอย่างไร ซึ่งก็น่าจะเพียงพอแล้วล่ะสำหรับโพสนี้ ขออภัยที่ไม่ได้เขียนลงรายละเอียดมากนัก เพราะไม่งั้นคงจะต้องอ่านกันยาวจัดเกินไป

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

สำหรับบทความนี้ก็ขอจบแต่เพียงเท่านี้ สวัสดีครับ ขอให้สนุกกับการทำ Cluster 🙂