The Cloud Camp Week#09 (K8S Part4: Resource/HealthCheck/Scale/Scheduler)

สัปดาห์นี้มีเรื่องวุ่นเกี่ยวกับ Wix ToolSet กลับมาที่เรื่อง K8S ของ Week นี้ครับ จะเน้นไปในส่วนของ Power of Pod ต่ออะไรที่ทำกับ Pod ได้ หัวข้อประมาณนี้ครับ

สำหรับใครที่เข้ามาจาก Google ลองดู Blog ก่อนหน้าได้ครับ

Resource Management

ในตัว Pod เราสามารถกำหนดการใช้งาน Resource พวก CPU / Memory / Local Ephemeral / GPU ได้จาก 2 ส่วน ได้แก่ requests / limits

  • requests - resource ขั้นต่ำที่ container นั้นๆต้องการ ปกติแล้ว จะดูตอน Pod Initialize และ Start จนเสร็จสิ้น
    NOTE: ถ้ากำหนดมากไป มันจอง Resource ไว้ ทำให้ Node ช้าๆลง
  • limits - resource สูงสุดที่ยอมให้ container นั้นๆ ใช้งานได้ ปกติแล้ว ต้องทำ Performance Test / Load Test กันก่อน แต่ถ้ารีบจริงๆ อาจจะเอาจำนวน Request และใช้ Magic Number เช่น 1.5 / 2 มาคูณทดเข้าไป

การกำหนดข้อมูล requests / limits จะให้ตัว kube-scheduler เอาไปเป็นเงื่อนไขในการเลือก Node ด้วย

โดย resource ที่กำหนดได้มีหลายตัว แต่ที่เรียนกันมี CPU / Memory //ตาม SI Model

  • CPU - กำหนดเป็น absolute number มี 2 แบบ
    - ตัวเลข - 1 (1 vCpu) / 0.5 (0.5 vCpu)
    - millicores แบบนี้นิยมทำ เพราะ K8S จะมีข้อจำกัดใส่ 0.005
    > ถ้าใช้ 1 vCpu จะเป็น 1000m
    > ถ้าใช้ 1/4 vCpu จะเป็น 250m
    > ถ้าใช้ 1/10 vCpu จะเป็น 100m
  • Memory - กำหนด absolute number จะเป็น
    - Bytes เช่น 128974848
    - Exponent เช่น 129e6
    - หน่วย E, P, T, G, M, K.
    - หน่วย Ei, Pi, Ti, Gi, Mi, Ki ได้เต็มเม็ดเต็มหน่วยตามฐาน 2
    เช่น 512Mi = 512*1024 เป็นต้น

    ปล. ตัวเต็ม Exabyte / Petabyte / Terabyte / Gigabyte / Megabyte / Kilobyte
- Sample YAML
apiVersion: v1
kind: Pod
metadata:
  name: eureka-managing-resource
spec:
  containers:
  - name: eureka-managing-resource
    image: netflixoss/eureka:1.3.1
    resources:
      requests:
        memory: "250Mi"
        cpu: "250m"
      limits:
        memory: "300Mi"
        cpu: "500m"

หลังจากกำหนด requests / limits แล้ว จะเป็นหน้าที่ของตัว kubelet ที่จะติดต่อ Container Runtime เพื่อคุม Resource ตาม requests / limits ที่กำหนดไว้

- Best Practice
  • ควรกำหนด requests / limits ให้กับแต่ละ pod จะได้สะท้อนภาพรวมของระบบได้ ปกติควรจะอยู่ประมาณ 70-80% ของ Node
    Note: อย่าลืมนอกจาก Node แล้ว มันยังมีพวก OS / Kernel ด้วยนะ ที่ใช้งาน Resource ด้วย
  • การกำหนด Limit ให้ดู Nature ของ App ด้วย ว่าเป็นแบบใช้ CPU เยอะ / ใช้ Memory เยอะ เวลาใช้งานบน Cloud จะได้เลือก Instance ที่ออกแบบมาตรงกับ APP เช่น ใช้ CPU เยอะ Compute Optimize
  • ปกติแล้ว จะ limits ที่ตัว memory ถ้าเลือกได้อย่างใดอย่างนึง เพราะ ตัว App ส่วนใหญ่มีโอกาสจะ memory leak ได้สูง แล้วถ้าเกิน Limit HW นานระดับนึงจะโดน Kill Pod ทิ้ง Pod Status จะเป็น OOMKilled (Out of Memory Killed)
    Note ถ้ามีเยอะๆ ตัว Node ตายได้นะ
  • ถ้า Pod Restart บ่อย ต้องเข้ามา Investigate หาสาเหตุ อาจจะเกิด Memoryleak แล้วทำให้ App ตาย ซึ่งถ้าเกิดบ่อยๆ ส่งผลกับรายได้ (Tech > Biz)
- เราจะรู้ได้ไงว่าควรตั้ง resources.requests เท่าไหร่ถึงพอ ?

อันนี้เขียนเพิ่มจากวันเสาร์ที่ได้ไปทำ Lab นะ

  • Step แรก ถ้ามีกำหนดไว้ให้ลอง Comment ออกก่อน แล้วลอง Deploy จากนั้นใช้ Tools อย่าง K9S มาดู memory / cpu หรือ ใช้ kubectl top pod / node
PS D:\2023TCC\Week9\Lab\2> kubectl top pod -n group-1
NAME                           CPU(cores)   MEMORY(bytes)   
eurakaping-7fc5b9d7cd-llhf6   1000m          500Mi
pinga-5f58b8967c-b7xw9         0m            7Mi
pingb-6d55d7f555-t2jpx         8m            7Mi
pingc-6b6854df5-4sl84          0m            7Mi
  • พอได้ Resource ที่มันใช้สำหรับ Start Container แล้ว ค่อยไปกำหนด resources.requests และเพิ่ม limit จาก load test หรือจะคูณ magic number (1.5 / 2.0 / 2.5)

Resource:
- Resource Management for Pods and Containers | Kubernetes
- Kubernetes size definitions: What's the difference of "Gi" and "G"? - Stack Overflow
- Kubectl Top Pod/Node | How to get & read resource utilization metrics of K8s? | SigNoz

Auto Scale

- Recap Scale

เงื่อนไขว่าจะอะไรมา Scale ตาม Load หรือ Reqest ที่เข้ามาหนักหน่วง (Intensive)

  • จับจาก Resource เช่น CPU / Memory / GPU / Connection (Pool / Response ที่ตอบช้าลง)
  • ช่วงเวลา Custom เช่น Business ช่วงต้องปิดงบไตรมาส

ภาพ App Service Plan ลอยมาเลย สำหรับรูปแบบการ scale มี 2 แบบ

  • Vertical Scaling (Scale UP/ Down) เป็นการขยาย/ลดขนาดของ Instance ให้มี Resource มากขึ้น เช่น CPU / RAM
    - Prod จัดการได้ง่าย เพราะตัว App เดิมๆ ที่อาจจะเป็น Stateful (เช่น พวก DB) ไม่ต้องมาปรับตัวอะไรมาก
    - Con single point of failure และมื่อถึงจุดที่ต้องการ Spec สูงๆ จะเริ่มแพงขึ้น เพราะหาเครื่องในท้องตลาดยาก
  • Horizontal Scaling (Scale Out / In) เป็นการเพิ่ม / ลดจำนวน Instance
    - Prod เพิ่ม Availability + fault-tolerance
    - Con Complexity เพิ่มต้องมาจัดการ Instance / Communication และเรียนรู้ Tools เข้ามาช่วย อย่าง K8S
    รวมถึงต้องคิดตั้งแต่ Design App ควรต้องเป็น Stateless ไม่ถืออะไร เดวจากลาไม่ต้องมาตามล่า รวมถึง Cost ที่เพิ่มขึ้น
- Pod Autoscale

สำหรับใน K8S จะมี 2 แบบ

  • Horizontal Pod Autoscaler (HPA) - เดี๋ยวมีอธิบายต่อไป
  • Vertical Pod Autoscaler (VPA) - อันนี้จะไป Workload เข้ามาปรับ requests / limits เหมาะกับงานพวก Stateful เช่น DB ที่ต้องการเพิ่ม CPU / RAM ทำ Scale Up/Down //ใน Course คุณโจ้จะไม่ได้อธิบายไว้
->> Horizontal Pod Autoscaler (HPA)

HPA ใช้ Autoscale สำหรับ Deployment / StatefulSet โดยดูจาก metric CPU Utilization / Memory ซึ่งข้อมูล metrics พวกนี้ มาจาก 2 แหล่ง

  • K8S Metric-Servier
  • Custom metrics support เช่น Prometheus Adapter

เมื่อ metrics ถึง Threshold ที่กำหนดสิ่งที่ HPA ทำ มันจะทำการเพิ่ม Pod ขึ้นมา ถ้า Metric ลดลงแล้วตัว HPA จะรอสักพัก แล้วลดจำนวน POD ลงให้เหมาะสม

DeamonSet ใชัตัว Horizontal Pod Autoscaler ไม่ได้นะ

ใน K8S จะมี Object อีกตัว HorizontalPodAutoscaler เข้ามาช่วยจัดการเรื่องนี้ ตัว Yaml Pattern ประมาณนี้

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  creationTimestamp: null
  name: php-apache
spec:
  maxReplicas: 10  #จำนวน Pod สูงสุดที่ยอมให้ Scale
  minReplicas: 1   #จำนวน Pod ต่ำสุดที่ต้องมี
  #หา Deployment โดยจับจาก apiVersion / kind และ metadata.name
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: php-apache
  targetCPUUtilizationPercentage: 50   #จับจากภาพรวม เลยแนะนำให้ใช้อันนี้ 
status:
  currentReplicas: 0
  desiredReplicas: 0S
apiVersion: apps/v1
kind: Deployment
metadata:
  name: php-apache
  labels:
    run: php-apache
spec:
  selector:
    matchLabels:
      run: php-apache
  template:
    metadata:
      labels:
        run: php-apache
    spec:
      containers:
      - name: php-apache
        image: registry.k8s.io/hpa-example
        ports:
        - containerPort: 80
        resources:
          limits:
            cpu: 500m
          requests:
            cpu: 200m
---
apiVersion: v1
kind: Service
metadata:
  name: php-apache
  labels:
    run: php-apache
spec:
  ports:
  - port: 80
  selector:
    run: php-apache

ซึ่งตัว HorizontalPodAutoscaler จะเข้าไปคุมตัว ReplicaSet อีกที โดยการ Scale Out / In ใน K8S มีสูตรพื้นฐานมาให้

desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]

desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]

โดยที่
currentMetricValue   = load ปัจจุบัน
desiredMetricValue   = ค่าที่ต้องการให้ตัว HorizontalPodAutoscaler รักษาไว้
currentReplicas      = จำนวน pod ตอนนี้

ตัวอย่าง เช่น 
currentMetricValue   = 249
desiredMetricValue   = 50
currentReplicas      = 1

ดังนั้นจำนวน Pod ที่ HPA ต้อง Scale 1*(305/50) = 4.98 >> Celi(4.98) >> 5
ดู Config maxReplicas = 10 
สรุปได้ HPA ต้อง Scale Pod จาก 1 > 5 (Scale Out)
==========================================
กลับกันถ้า 
currentReplicas      = 0
desiredMetricValue   = 50
currentReplicas      = 5

ดังนั้นจำนวน Pod ที่ HPA ต้อง Scale 5*(0/50) = 0 >> Celi(0) >> 0 แต่ต้องไปดู Config minReplicas
สรุปได้ HPA ต้อง Scale Pod จาก 5 > 1 (Scale In)

แต่ถ้ามีสูตรที่ดีกว่าก็ Custom ได้ และเพื่อป้องกัน Scale มันเหวี่ยง ตัว K8S จะมี tolerance เอาหน่วงรอสักพัก (Default 0.1)

  • LoadTest1-Scale-Out
  • LoadTest2-Scale-In
  • LoadTest3-Scale-In
->> Command
#Create HPA
kubectl autoscale deployment <<YOUR_DEPLOYMENT_NAME>> --cpu-percent=50 --min=1 --max=3 -n group-1 --dry-run=client -oyaml > pinga-hpa.yaml

#General Command
kubectl get hpa

ตัวอย่าง การใช้

# Linux 
watch kubectl get hpa;

#Power Shell
while (1) {kubectl get hpa; write-host "--`n"; sleep 5}

#Power Shell Full Command.
kubectl run -i --tty load-generator --rm --image=busybox:1.28 --restart=Never -- /bin/sh -c "while sleep 0.01; do wget -q -O- http://php-apache; done"
while (1) {kubectl get hpa; write-host "--`n"; sleep 5}
while (1) {kubectl get pod; write-host "--`n"; sleep 5}
while (1) {kubectl get deployment php-apache; write-host "--`n"; sleep 5}
->> เปิด metric-server
minikube addons enable metrics-server
  • ตรวจสอบ pod metrics-server มีสถานะ running ไหมจากคำสั่ง
kubectl get pod,svc,ep -n kube-system
->> Best Practice
  • ตัว HPA ถ้าทำให้ Smooth อาจจะต้องมาปรับ Tune Property ที่มันมีมาให้ก่อน เพื่อให้ได้ behavior ที่ต้องการ
  • ใน Deployment ค่า replicas ให้เอาออก แล้วให้ตัว HPA มันคุมเอง
  • ที่ HPA targetCPUUtilizationPercentage ปกติจำกำหนดที่ 70% เหลือ 30% ให้ Node แต่ถ้า App ที่ขึ้นมัน Critical มากกว่านั้นค่าจาก 70% ลง
  • App monolithic (lift & ship) จะใช้ VPA + Deployment เสริม Availability
  • ถ้าต้องการ Scale เป็นช่วงเวลาที่แน่นอนใช้ tkestack/cron-hpa: Cron Horizontal Pod Autoscaler(CronHPA) (github.com)

Resource: Horizontal Pod Autoscaling | Kubernetes

- Cluster Auto Scaler

เอามาแก้ปัญหากรณีที่ Scale Pod จนเต็มแล้ว ถ้ามี Request ที่ต้องการ Scale เพิ่ม ตอนนี้จะเหลือเพิ่ม เครื่อง หรือ เพิ่ม Resource ให้เครื่องแล้ว สำหรับ Tools ทำ Horizontal Scale มีตัว

Cluster คือ กลุ่มของ K8S Machine (Node) ไม่ว่าจะเป็น VM / Bare Metal Server เป็นต้น ที่เอา Resource ใช้สร้าง Pod ต่างๆ โดยมีสิ่งที่ Cluster Auto Scaler จัดการ ดังนี้

  • ดู metric ภาพรวมของ Node ว่าถึงจุดอันตราย หรือย้ง เพื่อตัดสินใจ Scale (เพิ่ม/ลด Node) โดยที่จุดอันตราย ดูจาก Load
    - แบบ HPA จับจาก currentMetric > มากกว่า ratio ที่กำหนด
    - event ว่ามี pod pending เยอะๆ
  • เมื่อถึงจุดที่ต้อง Scale Out [ต้องเพิ่ม Machine/Node]
    - กรณี Cloud จะให้ตัว Cloud Controller ส่ง Request ไปขอเพิ่ม Instance
    - กรณี On-Premises เช่น vSphere เพิ่ม Instance / VM ขึ้นมา
    Note: ในกรณีที่ Request ลดลง จะ Scale in [ลด Machine /Node ลง]
  • แล้วอะไรที่ต้องระวังตอน scale ?
    - การออกแบบ IP / Subnet ต้องเผื่อให้เพียงพอกับการ Scale Node ด้วย
  • Cloud Cost Optimization Guide - การเพิ่ม Node นั้นเสียเงิน เราสามารถแบ่งกลุ่ม Node ได้
    - Subnet Scale แบบ On-Demand สร้าง Dedicate Node ใหม่
    - Subnet Scale จาก Spot Instance ใช้สำหรับ Load ที่มาในรูปแบบ Spike สั้นๆ เวลาน้อย เพราะ Note: Spot Instance เป็น Node ที่ราคาถูกมาก เอา Resource เหลือจาก Cloud มาใช้งาน แต่มีโอกาศยึดคืนได้จากตัว Cloud Provider ได้เหมือนกัน
- Addon Resizer

Addon Resizer - เป็น Tools ที่เอามาทำ vertically scales เพิ่ม Resource ให้ Container ตามจำนวน Node ถ้า Node เพิ่ม Container จะได้ Resource เพิ่ม

The Addon scale up/down the container resource configuration of specific singleton Kubernetes addons based on the size of the Kubernetes cluster

ปิดท้ายเรื่อง Scale ทั้ง HPA / VPA / Cluster Auto Scaler / Addon Resizer เป็นการแก้ปัญหาฝั่ง Infra Structure ถ้าต้องการคุม Cost + Performance ต้องมา Optimized Application ด้วย

Resource: kubernetes/autoscaler: Autoscaling components for Kubernetes (github.com)

Health Check

- YAML Pattern
....
spec:
  containers:
  - name: testcontainer
    image: nginx
    <<ProbeType>>:                          # readinessProbe/livenessProbe/startupProbe
      <<ProbeProtocal>>:                    # httpGet / tcpSocket / exec 
        <<Config for Each Probe Protocal>>: # 
      <<Common Probe Config>>               # initialDelaySeconds / periodSeconds ...
- Probe Type

Readiness Probe - ตรวจว่า Pod พร้อมไหม ที่จะรับ Traffic ในกรณีที่รับ Load เยอะไป สามารถให้ Pod Return false ตอน Check ได้

Update เพราะหลังจากลลองโจทย์ มันได้ 0/1 รอตาม Readiness Probe ได้ 1/1 และค้างยาวเลย 555
สรุปตัว App ต้องแก้ด้วย เช่น ถ้าเรามี WebAPI รับ Load ได้ 40 Job ถ้ามีตัว 40 เข้ามา return false กลับไป บอก K8S กระจายไปตัวอื่นๆ

gist: Sample Simulate Readiness (github.com)

Liveness Probe - เมื่อไหร่ Pod ของ Restart หรือ Start สิ่งใหม่ //น่าจะเกี่ยวกับพวก ReplicaSet

  • เคสแรก ตรวจสถานะ Pod ถ้า False ติดกัน 3 รอบ ให้ K8S Restart Pod ที่ทำหลายรอบ เพราะว่า อาจจะเรื่อง Network หลุด เลยตรวจหลายๆรอบให้แน่ใจก่อน
  • เคสสอง จะสัมพันธ์กับ Readiness Probe จากตัวอย่าง WebAPI Job 41 มาจะเข้าไม่ได้และ แต่ใน Pod นั้น 40 Job เดิมยังคงทำงานจนจบ แล้ว ถึง Restart ได้ เพื่อ Start สิ่งใหม่ อาจจะขึ้น Ver ใหม่

Startup Probe - สำหรับ Application ที่มีตัว Warm Start >> App เวลาที่เริ่มจนมันพร้อมใช้งาน Ready นานพอสมควร เช่น ต้องรอมากกว่า 30 วินาทีขึ้นไป
Note ไม่ควรใช้ Liveness ที่เพิ่ม Interval ในการรอ (long liveness interval) เพื่อตรวจสอบ กรณีที่ App Start ช้า สูตรเผื่อเวลา

สูตร initialDelaySeconds + (failureThreshold × periodSeconds)

  • กำหนด End Point เดียวกัน Liveness
    ปล. NET ระวังเคส Cold Start
#startupProbe 
# สูตร initialDelaySeconds + (failureThreshold × periodSeconds)
# จะได้  0 + (30 x 10) = 300 Sec ประมาณ 5 นาที
startupProbe:
httpGet:
 path: /healthcheck
 port: 80
failureThreshold: 30
periodSeconds: 10

livenessProbe:
httpGet:
 path: /healthcheck
 port: 80
failureThreshold: 1
periodSeconds: 1
- Probe Order
ลำดับ Probe
Start ->-------------------------------------------------->--- Ready 
              Startup Probe => Readiness Probe
                                => Liveness Probe
Readiness / Liveness พอๆกัน 
  • ตรวจ Startup Probe ก่อนแต่ต้อง Tune Config Readiness Probe / Liveness Probe ไม่งั้นจะมีเคส App Restart แบบแปลกๆ หรือ Traffic ไม่เข้า
Ref: มีน้องแซมที่ใช้ kube ประจำแนะนำเพิ่มครับ >> What Are Startup, Liveness, and Readiness in Kubernetes Probes (loft.sh)

Keyword อื่นๆ

  • Readiness Gate รอให้ deployment ที่เกี่ยวข้องกัน พร้อมใช้งานก่อน พวก microservice
    Ref: Pod readiness

เวลาขยับ Ver ของ K8S ตรวจพวกนี้ด้วย

- Define Probe

มี 3 แบบ

  • httpGet - curl ตาม path + port
  • tcpSocket - check port
    ทำมา Support พวก Queue / Socket ที่ httpGet ทำไม่ได้
    หรือ App ไม่มี HealthCheck Path
  • exec - Run Command แล้วตรวจ Exit Code หรือ จาก Cat ต้องดูคำ ต้องไปดู Spec เพิ่ม
- Configure Probes
  • initialDelaySeconds - รอกี่วินาที ถึงเริ่ม Check (Default = 0 / Min = 0)
  • periodSeconds - รอบถัดไปในการ Check อีกรอบ ถ้าน้อยจะเป็น ddos (Default = 10 / Min = 1)
  • timeoutSeconds - Trigger Check ไปแล้วให้รอผลลัพธ์เป็นเวลาเท่าไหร่ (Default = 1 / Min = 1)
    ถ้าหลุด มันจะเกิด
    - Readiness Fail งดรับ Traffic
    - liveness จะ Count
  • successThreshold - Check สำเร็จติดกัน กี่รอบถึงบอกว่าสำเร็จได้ (Default = 1 / Min = 1)
    - Readiness กำหนดเป็น 1
    - Liveness กำหนดเป็น 1
    - startup
    ** ระวังกำหนดตรงนี้ ถ้าพลาดจะเป็น CrashLoopBackOff
  • failureThreshold - Check Fail ติดกัน กี่รอบ ถึงบอก Fail (Default = 3 / Min = 1) ควรใช้ใน
    - Liveness - กี่รอบ Restart Container
    - Readiness - กี่รอบบอก Pod UnReady
- Sample YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: inv-api-deployment
spec:
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: inv-api
    spec:
      containers:
      - name: inv-api
        image: pingkung/inv-api:8.8.8
        # กรณีดูจาก Command
        # startupProbe:
        #   exec:
        #     command:
        #     - cat
        #     - /tmp/healthy
        startupProbe:
          httpGet:
            path: /healthcheck
            port: 80
          failureThreshold: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 10456
          successThreshold: 2
        livenessProbe:
          httpGet:
            path: /healthcheck
            port: 10456
          initialDelaySeconds: 0
          periodSeconds: 10
          timeoutSeconds: 1
          failureThreshold: 3

Scheduler

- nodeName

nodeName (ใส่ชื่อ node ลงไปตรงๆ) ตัวอย่าง เช่น ต้องการให้ลง node ชื่อ "kube-01"

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - name: nginx
    image: nginx
  nodeName: kube-01
- nodeSelector

nodeSelector กำหนดเงื่อนไขเลือก node อาจจะดู label ของ node เช่น

  • disktype > hdd / ssd
  • workload > general / compute

ถ้าใส่ไปหลายๆ label มันจะเอาตัวที่เจอก่อนเป็นตัวแรก

ตัวอย่าง เช่น

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    workloadtype: general 
- Taint and Toleration

Taint - Lock Node ทำ Policy ในการ Deploy Pod

  • Pattern
key=value:Effect
  • Effect
    - NoSchedule - ไม่ให้ขึ้น Pod ใหม่ //Pod ของเดิมยังอยู่ได้
    - PreferNoSchedule - ถ้าไม่มีที่อื่นแล้วมาที่นี้ก็ได้
    - NoExecute - ขอคืนพื้นที่ ถ้า Pod ที่ Run มี PriorityClass ที่ต่ำกว่า System อยู่จะโดน Kill ทันที
  • Sample Command
#Check Node Taints
#-PowerShell
kubectl describe node node01 | select-string Taints*
#-Linux
kubectl describe node node01 | grep Taints*

#===================================
#Add Taint with No Schedule Effect
kubectl taint nodes node01 disktype=ssd:NoSchedule

#ตอน describe node ที่ในส่วนของ Taints จะมีตัวที่เราเพิ่งเพิ่มเข้าไป
#ถ้า deploy yaml แล้วตอนนี้ ยังไม่ได้กำหนด Toleration จะไม่ได้ เพราะกำหนด Effect NoSchedule

#Remove taint add - after key=value:Effect
kubectl taint nodes node01 disktype=ssd:NoSchedule-

Toleration บัตรผ่านการใช้ Taint

apiVersion: v1
kind: Pod
metadata:
  name: nginx-noschedule
spec:
  containers:
  - name: nginx-noschedule
    image: nginx
  #ใส่บัตรผ่าน เพื่อใช้ node01 disktype=ssd:NoSchedule
  tolerations:
  - key: "disktype"
    operator: "Equal"
    value: "ssd"
    effect: "NoSchedule"
apiVersion: v1
kind: Pod
metadata:
  name: nginx-noschedule
spec:
  containers:
  - name: nginx-noschedule
    image: nginx
  #ใส่บัตรผ่าน เพื่อใช้ node01 disktype=ssd:NoExecute
  tolerations:  
  - key: "disktype"
    operator: "Equal"
    value: "ssd"
    effect: "NoExecute"

ต้องกำหนดบัตรผ่าน (กุญแจให้ครบชุด ถึงใช้งานได้)

อ๋อ ถ้า node กำหนด taints ไว้หลายชุด ต้องใส่ให้ครบด้วยนะ

  • Query ดู Taints ใน node
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

- Node Affinity

เอาไว้กำหนดเงื่อนไขที่ซ้ำซ้อนขึ้น เพราะตัว Node Affinity มี Expression ให้ใช้งาน

  • Set Label
#List Label
kubectl get nodes --show-labels

#Add a label for each node format [key]=[value]
kubectl label nodes node01 workloadtype=general
kubectl label nodes node02 workloadtype=compute
kubectl label nodes node03 workloadtype=memory
  • Create Expression
        - matchExpressions:
          - key: workloadtype
            #operator ที่เป็นไปได้ In / NotIn / Exists / DoesNotExist / Lt / Gt
            operator: In
            values:
            - general
            - compute

Deployment Type

  • requiredDuringSchedulingIgnoredDuringExecution - ตรง Spec เท่านั้นถึง Deploy ได้
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: workloadtype
            operator: In
            values:
            - compute            
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  • preferredDuringSchedulingIgnoredDuringExecution - ถ้าไม่มีตรง Spec ยอมได้ ถ้ามีหลาย node มี weight (ค่า 0-100) เอามาให้ K8S คำนวณตัดสินใจเลือก Node
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80
        preference:
          matchExpressions:
          - key: workloadtype
            operator: In
            values:
            - general
      - weight: 20
        preference:
          matchExpressions:
          - key: workloadtype
            operator: In
            values:
            - compute
            - memory
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent

Note: IgnoredDuringExecution - เอาไว้ใช้เบรก มาแก้ Runtime เช่น การเปลี่ยน Label

- Pod Topology Spread Constraints

Pod Topology Spread Constraints = Policy ในการกระจาย Pod เพื่อเพิ่ม Availability กำหนดที่ Spec ของ Deployment จริงๆ ถ้าเทียบกับ Cloud มันพวก Availability Zone / Region

  • Config Pattern
pod.spec:
  # Configure a topology spread constraint
  topologySpreadConstraints:
    - maxSkew: <integer>
      minDomains: <integer> # optional; beta since v1.25
      topologyKey: <string>
      whenUnsatisfiable: <string>
      labelSelector: <object>
      matchLabelKeys: <list> # optional; beta since v1.27
      nodeAffinityPolicy: [Honor|Ignore] # optional; beta since v1.26
      nodeTaintsPolicy: [Honor|Ignore] # optional; beta since v1.26
  ### other Pod fields go here
  • ตัวอย่าง ตอนตรวจ List ว่า 5 pod กระจายไป 5 node อย่างละ 2 pod
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: pingd
  name: pingd
  namespace: group-1
spec:
  #Max 2 Per Node 5 Node = 10
  replicas: 10
  selector:
    matchLabels:
      app: pingd
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: pingd
    spec:
      topologySpreadConstraints:
      #eveny node 2 pods
      - maxSkew: 2
        #topologyKey is the key of node labels. Nodes that have a label with this key and identical values are considered to be in the same topology
        #topologyKey: topology.kubernetes.io/region=proenhn
        topologyKey: node-name
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: pingd
      containers:
      - image: nginx
        name: nginx
        resources: {}
        #warmup 10 seconds
        #initialDelaySeconds + failureThreshold × periodSeconds
        startupProbe:
          httpGet:
            path: /
            port: 80
            scheme: HTTP
          initialDelaySeconds: 10
        #Restart every 1 minute
        livenessProbe:
          httpGet:
            path: /
            port: 10456
          initialDelaySeconds: 1
          periodSeconds: 60
          failureThreshold: 1
        #Traffic every 16 seconds
        readinessProbe:
          httpGet:
            path: /
            port: 80
          successThreshold: 1
          periodSeconds: 16
          failureThreshold: 4
      # affinity:
      #   nodeAffinity:
      #     requiredDuringSchedulingIgnoredDuringExecution:
      #       nodeSelectorTerms:
      #       - matchExpressions:
      #         - key: node-name
      #           operator: In
      #           values:
      #           - node-01
      #           - node-02
      #           - node-03
      #           - node-04
      #           - node-05
      tolerations:
        - key: "node"
          operator: "Equal"
          value: "no1"
          effect: "NoSchedule"
        - key: "node"
          operator: "Equal"
          value: "no2"
          effect: "NoSchedule"
        #เอา node3 ขึ้นไม่ได้ เพราะเจอ Taint 2 ตัวของ node นั้น 
        #[map[effect:NoSchedule key:disktype value:ssd] map[effect:NoSchedule key:node value:no3]]
        - key: "node"
          operator: "Equal"
          value: "no3"
          effect: "NoSchedule"
        - key: "node"
          operator: "Equal"
          value: "no4"
          effect: "NoSchedule"
        - key: "node"
          operator: "Equal"
          value: "no5"
          effect: "NoSchedule"
status: {}
  • ตอน describe pod มันขึ้นตัว Topology Spread Constraints ด้วยนะ
Topology Spread Constraints:  node-name:DoNotSchedule when max skew 2 is exceeded for selector app=pingd

Resource: Pod Topology Spread Constraints | Kubernetes

DeScheduler

จัดการ Pod เพื่อคุม Cost/Performance ตาม Policy โดยการ

  • ลบ Pod ที่ไม่จำเป็น - Scheduler มันสร้างเกิน
  • หรือ อยู่ผิดที่ผิดทางออกไป - Pod กระจาย Node มากไปทั้งๆที่มันสามารถย้ายมารวม Node เดียวกันได้ เป็นต้น

Resource: kubernetes-sigs/descheduler: Descheduler for Kubernetes (github.com)

นอกจากนี้ตัว K8S มีของ Tools ตัวให้เอามาใช้งานใน Kubernetes SIGs (github.com) เช่น External DNS

ภาพรวมของ Week นี้น่าจะได้ประมาณนี้

ส่วนวันเสาร์ที่มาทำ Lab ที่ KX ได้รู้หลายเรื่องเลย

  • ตอนเช้า งงว่าเราจะรู้ได้ไงว่าต้องกำหนด resource.request เท่าไหร่ กับเทสยังไง เลยไปไล่นั่งอ่านจาก Eureka REST operations · Netflix/eureka Wiki (github.com) พอรู้วิธี ที่คุณโจ้เฉลยแล้ว อ๋อเลย
  • ตอนบ่าย งงเรื่อง HealthCheck ไม่รู้ว่าทำถูกไหม ฮ่าๆ

Reference


Discover more from naiwaen@DebuggingSoft

Subscribe to get the latest posts sent to your email.