พูดถึงการใช้งานเบื้องต้นบน Kubernetes


ข้อจำกัดของ Docker นั้นมีอยู่ว่าสามารถทำงานได้บนแค่ machine หนึ่งเครื่องเท่านั้น ดังนั้นการทำ scaling ด้วย Docker จึงต้องใช้ vertical scaling ในการอัปเกรด; แต่ในปัจจุบันการทำ scaling แบบ horizontal เป็นสิ่งที่ทำได้ง่ายกว่าและเป็นตัวเลือกที่ดีกว่า และจุดนั้น Docker ยังไม่สามารถตอบโจทย์นี้ได้; เลยมี Kubernetes เกิดขึ้นมา ซึ่งตัวมันเองคือ Container Orchestration ที่สามารถทำให้ Containers ที่อยู่คนละเครื่องสามารถเชื่อมต่อกันได้ และก็ยังมีความสามารถมากกว่าเช่น ถ้าหากว่า Container ในระบบเกิดปัญหาหรือพังขึ้นมา Kubernetes จะสามารถที่จะรีสตาร์ท container ใหม่เพื่อให้มันสามารถกลับมาทำงานได้อีกครั้ง หรืออาจจะเป็นเวลาที่ต้องการอัปเดตเวอร์ชันเอง Kubernetes ก็สามารถเปลี่ยนเวอร์ชันใหม่ได้โดยที่ไม่จำเป็นต้องชัตดาวน์ระบบเพื่อลงตัวใหม่ แต่สามารถอัปเดตไปในขณะที่เวอร์ชันเก่ายังทำงานอยู่ โดยการสร้าง Container ใหม่ขึ้นมา เมื่อพร้อมแล้วก็เปลี่ยน traffic มาหาตัวใหม่ แล้วลบตัวเก่าทิ้ง

Machine หลาย ๆ ตัวที่ถูกควบคุมโดย kubernetes เพื่อใช้ในการจัดการวาง contatainers นั้นจะเรียกรวมกันว่าเป็น Kubernetes Cluster; โดยจะพูดอธิบายถึงส่วนประกอบคร่าว ๆ ของ Kubernetes อย่าง Pod, Deployment, Statefulset, DaemonSet, Service, ConfigMap และ Secret ไว้ดังนี้

Pod

Pod คือก้อนที่รวม Containers ไว้ด้วยกันหลายตัว; ตามที่เกริ่นตัวอย่างไว้ก่อนหน้าเกี่ยวกับเรื่องที่ Kubernetes สามารถรีสตาร์ทการทำงานของ Containers ได้เมื่อเกิดปัญหา; ซึ่งนั่นก็คือ Containers ที่อยู่ใน Pod; Containers ใน Pod นั้นสามารถที่จะกำหนด limitation ของ cpu กับ memory และก็เงื่อนไขบางอย่างในการที่จะถูกรีสตาร์ทได้ในกรณีที่การทำงานบางอย่างเกิดปัญหา ถ้าหากเป็นไปตามเงื่อนไขแล้ว Container นั้นจะถูกทำการรีสตาร์ทเพื่อทำงานใหม่อีกครั้ง; Pod ต่าง ๆ นี้จะถูก kubernetes กระจายไปตาม machine เครื่องต่าง ๆ ใน Kubernetes Cluster ดังนั้นโดยปรกติเราจะให้ 1 pod มี 1 container (ใน microservices นั้น service หนึ่งตัวก็คือ 1 container) เพื่อให้โปรแกรมของเรากระจายไปใน cluster ได้อย่างทั่วถึง ไม่ให้ไปรวมกันที่ machine เครื่องใดเครื่องนึง; ทีนี้จะพูดถึงกฎการทำงานของ containers ที่อยู่ใน Pod กัน; โดย Containers นั้นจะสามารถตั้งค่าต่าง ๆ ได้ดังนี้

  • livenessProbe เงื่อนไขที่เอาไว้สำหรับเช็คว่า Container นั้นควรจะถูกรีสตาร์ทการทำงานใหม่เมื่อไร ถ้าตรงตามเงื่อนไขก็จะถูกรีสตาร์ทโดยอัตโนมัติ
  • readinessProbe เงื่อนไขที่เอาไว้บอกว่า Container ตัวนี้พร้อมรับทราฟฟิกจากภายนอกแล้ว หากยังไม่ตรงตามเงื่อนไขจะไม่จ่ายทราฟฟิกให้
  • startupProbe ในบางกรณี Container นั้นอาจจะมีขั้นตอนในการทำ initialization ที่นาน และถ้าหากว่าเกิดไปทำ livenessProbe ก่อน อาจจะทำให้ Container ตัวนั้นถูกรีสตาร์ททั้ง ๆ ที่ยังไม่ได้ทำอะไรเลยได้; จึงจะต้องมี startupProbe ที่เอาไว้สร้างเงื่อนไขว่า Container นั้นพร้อมทำ livenessProbe หรือยัง ถ้าหากว่ายัง ก็จะไม่เช็ค livenessProbe ให้; Probes พวกนี้จะถูกสามารถกำหนดความถี่ในการเช็คได้; livenessProbe และ readinessProbe นั้นจะมีการเช็คตลอด ตราบใดที่ Container ยังทำงานอยู่

Deployment

เมื่อกล่าวถึง Pod ไปแล้ว ถ้าตาม microservices แล้ว เราอาจจะมองว่า pod ก็คือ service ตัวนึง; และสิ่งที่มักกล่าวตามมาก็คือการทำให้ service นั้นรองรับโหลดที่มีจำนวนมากได้; โดย deployment นั้นจะสามารถกำหนดจำนวนของ pod ได้ (หรือก็คือทำตัว copy ขึ้นมา); เนื่องจาก services ของแอปพลิเคชันนั้นมีหลายตัว pods ก็จะมีหลายตามไปด้วย และบาง services นั้นคงจะไม่ได้ต้องการจำนวนของ pod ที่เท่ากันจึงต้องมีการกำหนด metadata label ไว้ว่า pod ไหนเป็นของ service อะไร; โดน deployment นั้นจะเลือก pods ที่ตัวเองต้องรับผิดชอบโดยดูจาก label; คีย์เวิร์ดต่อไปก็คือ replicas ซึ่งจะเป็นตัวเลขที่บอกว่า deployment นั้นควรจะต้องควบคุมให้จำนวน pod ของตัวเองนั้นมีเท่าไหร่; หากว่ามี pod ที่พังลงไป ซึ่งอาจจะพังเองหรือเกิดจาก livenessProbe หรือ machine เครื่องที่ pod นั้นอยู่พัง deployment ก็จะทำการสร้าง pod ใหม่และรักษาจำนวนให้มีเท่ากับที่กำหนดใน replicas เสมอ

StatefulSet

pods ที่ถูกสร้างจาก deployment นั้นมีชื่อตรงกับที่กำหนดบน metadata ของ pod แต่ว่าจะมีห้อยเลข hash ที่ไม่สามารถคาดเดาได้ตามมาด้วย; StatefulSet นั้นสามารถควบคุมจำนวนของ pod ได้เช่นเดียวกัน Deployment; เพียงแต่ว่า pod ที่ถูกสร้างโดย StatefulSet นั้นจะมีชื่อที่ค่อนข้างแน่นอนตามจำนวนของ replicas โดยที่เลขที่ต่อข้างหลังนั้นจะไม่เป็นเลข hash แต่จะเป็นจำนวนเต็มที่เริ่มนับจาก 0 ไป; ยกตัวอย่างเช่น pod ที่ชื่อ mysql นั้นโดย StatefulSet และมีการกำหนด replicas เป็น 3; pods ที่ถูกสร้างก็จะมีชื่อตามนี้ mysql-0, mysql-1, mysql-2; จะเห็นว่าเป็นเลขที่ค่อนข้างง่ายต่อการจำ; และ StatefulSet นั้นจะมีการสร้างหน่วยความจำ (Volume จะพูดถึงทีหลัง) ไว้สำหรับ pod แต่ละตัวด้วย โดยที่ pod นั้นจะได้ volume ที่ต้องใช้ไปตัวละ 1 volume; เมื่อมี pod หายไป StatefulSet ก็จะสร้าง pod ใหม่มาทำหน้าที่แทน pod เก่าและให้ดูแล volume ที่ pod เก่าดูแลไว้ต่อด้วย; StatefulSet นั้นมีประโยชน์สำหรับ Pod ประเภท database ที่จะต้องมีการรักษาข้อมูลต่าง ๆ ไว้เพื่อให้ไม่เกิดปัญหาข้อมูลผิดพลาดภายหลัง เพราะ pod ตัวนึงนั้นจะมี volume ของตัวเองไปเลย

DaemonSet

DaemonSet นั้นก็สามารถควบคุมจำนวนของ pod ได้เช่นกัน เพียงแต่ว่าเราไม่สามารถกำหนด replicas ได้โดยตรง; จำนวน replicas ของ pod ที่ถูกควบคุมโดย DaemonSet จะมีค่าเท่ากับจำนวน machines ที่อยู่บน Kubernetes Cluster; และ pod ที่ถูกสร้างนั้นก็จะกระจายไปอยู่ใน machine เครื่องละ 1 pod ด้วย; ต่างจาก Deployment และ StatefulSet ตรงที่ไม่ได้กำหนดตำแหน่ง machine ที่แน่นอน

Service

พอแยก services ออกเป็น pods แล้ว; pods แต่ละตัวก็ควรจะเชื่อมต่อกันได้เพื่อให้สามารถส่งข้อมูลหากันได้ด้วย; โดยปรกติแล้ว pods ที่ถูกนั้นจะมี IP address ที่เอาไว้ติดต่อกันภายใน Cluster อยู่แล้ว; แต่มีข้อจำกัดอยู่ที่ว่า หาก pod นึงถูกรีสตาร์ทหรือสร้างขึ้นใหม่ IP address นั้นก็จะเปลี่ยนไปด้วย ทำให้เราไม่สามารถรู้ได้ว่าเมื่อไรที่ IP address จะเปลี่ยน; จึงมี Service ขึ้นมา Service นี้ไม่ได้มาจาก microservices แต่เป็น Resource ประเภทนึงของ Kubernetes; Service นั้นจะเลือก pod ผ่าน metadata label เหมือนกัน; เมื่อเลือกแล้ว pods อื่น ๆ ที่ต้องการติดต่อกับ pods ที่ถูกเลือกโดย Service นั้นก็จะสามารถเรียกได้ผ่านชื่อของ Service เลย โดยที่ Service จะมีการทำ Load Balancer ว่าจะส่งไปให้ pods ตัวไหนอีกที; ตัวอย่างการใช้เช่นมี Deployment ควบคุมจำนวน pod ที่มี label ว่า nginx ให้มีอยู่ 3 pods แล้วใช้ Service ชื่อ nginx-service เลือก pods พวกนี้ผ่าน label nginx; pods อื่นที่จะเชื่อมต่อกับ pod nginx นี้ก็อาจจะสามารถเชื่อมต่อได้ผ่าน url ชื่อ nginx-service แล้ว Service นั้นจะคอยดูแลทราฟฟิกและ load balance ไปยัง pod nginx อีกที; ประเภทของ Service นั้นจะมีอยู่ 4 แบบคือ ClusterIP ซึ่งเป็นค่าตั้งต้น, NodePort จะเชื่อมต่อผ่าน machine IP address, LoadBalancer ที่ใช้กับ cloud provider และ ExternalName; Service นั้นสามารถสร้างเป็น Headless Service ได้ด้วย ซึ่งจะเอาไว้ใช้กับ pods ที่ถูกสร้างผ่าน StatefulSet; โดย headless services นั้นจะไม่ได้ทำ load balance ให้ แต่เนื่องจากว่า pods ของ StatefulSet มีชื่อที่แน่นอน เราจึงสามารถเข้าถึงได้ผ่านชื่อของ pod แล้วตามด้วยชื่อของ service เช่น mysql-0.headlessservicename

ConfigMap

ConfigMap มีไว้สำหรับเก็บค่า environments ที่เอาไว้ใช้กับ containers ใน pods; หรืออาจจะเอาไว้เก็บไฟล์ด้วยก็ได้ โดยที่ configmaps นั้นจะเก็บค่าในรูปแบบของ key-value ส่วนในกรณีของไฟล์ key จะเป็นชื่อไฟล์ และ content ข้างในของไฟล์จะเป็น value แทน และสามารถเอาไปใช้ใน volume ของ containers ใน pods ได้ด้วย

Secret

เหมือนกันกับ ConfigMap แต่ Secret นั้นมีไว้เก็บค่าที่ sensitive โดยที่ค่าที่เข้าไปใน Secret จะต้องถูกเข้ารหัสด้วย base64 ก่อน และเมื่อถูกเอาไปใช้โดย containers ก็จะถอดรหัสกลับมาด้วย base64 คืน; Secret นั้นมี type พิเศษที่เอาไว้เก็บไฟล์สำหรับ TLS และ docker/config.json ที่เอาไว้ใช้ดึง image จาก private docker registry ได้

PersistentVolume

PersistentVolume นั้นมีไว้สำหรับสร้างหน่วย Volume ที่เอาไว้ให้ containers สำหรับเก็บบันทึกข้อมูลไม่ให้หายไปเมื่อ pod ถูกลบ (StatefulSet นั้นจะมีการตั้งค่าให้สร้าง Volume เองอยู่แล้ว); Volume นี้จะเชื่อมต่อกับเครื่อง machine ที่รันอยู่ใน cluster ได้ (hostPath) หรือเชื่อมต่อกับ NFS server และ Cloud providers ตัวอื่น ๆ ได้ รูปแบบในการเข้าถึง volume นั้นจะมีอยู่ทั้งหมดสามแบบที่ใช้กันหลัก ๆ

  • ReadWriteOnce ให้ pods ที่ทำงานอยู่บนเครื่องเดียวกันอ่านและเขียนได้ ยกเว้น pods ที่ทำงานอยู่เครื่องอื่นจะเข้าถึงไม่ได้
  • ReadOnlyMany ทุก pods จะเข้าถึงข้อมูลได้แต่แบบอ่านเท่านั้น
  • ReadWriteMany pods ทุกตัวอ่านและเขียนข้อมูลพร้อมกันได้ ไม่มีการจำกัดว่าจะต้องทำงานอยู่เครื่องเดียวกัน

PersistentVolumeClaim

PersistentVolumeClaim จะถูกเอาไว้ให้ใช้เชื่อมต่อกับ persistentVolumes อีกที โดยที่จะบอกแค่โหมดการเข้าถึงและขนาดความจำที่ต้องการใช้เท่านั้น ซึ่ง pods จะไม่ได้เชื่อมต่อกับ PersistentVolume ตรง ๆ แต่จะเข้าผ่าน PersistenVolumeClaim อีกที; เพื่อที่จะให้สามารถแบ่งการดูแลหน้าที่การจัดการ Volume ได้ เป็นส่วนของแอดมิน (PersistentVolume) และส่วนของนักพัฒนา (PersistentVolumeClaim)