Scheduling & Affinity - Pod Placement Control

Tổng quan

Kubernetes scheduler quyết định pod placement. Affinity rules và constraints control pod scheduling behavior.

Scheduler

Scheduling Process

  1. Filtering nodes
  2. Scoring nodes
  3. Selecting best node
  4. Binding pod to node

Scheduling Policies

  • Resource requirements
  • Node affinity
  • Pod affinity/anti-affinity
  • Taints và tolerations

Node Affinity

apiVersion: v1
kind: Pod
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/arch
            operator: In
            values:
            - amd64

Pod Affinity

apiVersion: v1
kind: Pod
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - cache
        topologyKey: kubernetes.io/hostname

Taints và Tolerations

Taints

kubectl taint nodes node1 key=value:NoSchedule

Tolerations

apiVersion: v1
kind: Pod
spec:
  tolerations:
  - key: "key"
    operator: "Equal"
    value: "value"
    effect: "NoSchedule"

Node Selectors

apiVersion: v1
kind: Pod
spec:
  nodeSelector:
    disktype: ssd

Priority Classes

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000
globalDefault: false
description: "High priority class"

Best Practices

  • Use affinity cho performance optimization
  • Implement anti-affinity cho high availability
  • Reserve nodes với taints
  • Set resource requests accurately
  • Use priority classes appropriately

Advanced Scheduling Scenarios

Custom Schedulers

Bạn có thể triển khai một custom scheduler nếu các chiến lược lập lịch mặc định của Kubernetes không đáp ứng được nhu cầu của bạn. Custom scheduler sẽ lắng nghe các Pod mới có schedulerName được chỉ định và quyết định node nào sẽ chạy Pod đó.

Ví dụ cấu hình Pod sử dụng custom scheduler:

apiVersion: v1
kind: Pod
metadata:
  name: my-pod-with-custom-scheduler
spec:
  schedulerName: my-custom-scheduler
  containers:
  - name: my-container
    image: nginx

Describing a Custom Scheduler in Java (Conceptual)

Một custom scheduler trong Java sẽ cần sử dụng Fabric8 Kubernetes Client để: 1. Lắng nghe các Pod có schedulerName khớp với tên của nó. 2. Áp dụng các thuật toán lập lịch tùy chỉnh (ví dụ: dựa trên tải của node, chi phí, hoặc các yếu tố kinh doanh). 3. Gửi yêu cầu Binding đến Kubernetes API Server để gán Pod cho một Node.

import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.BindingBuilder;
import io.fabric8.kubernetes.client.Watcher;
import io.fabric8.kubernetes.client.WatcherException;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class CustomScheduler {

    private static final String CUSTOM_SCHEDULER_NAME = "my-custom-scheduler";
    private KubernetesClient client;
    private ScheduledExecutorService executorService;

    public CustomScheduler() {
        this.client = new DefaultKubernetesClient();
        this.executorService = Executors.newSingleThreadScheduledExecutor();
    }

    public void start() {
        System.out.println("Starting custom scheduler: " + CUSTOM_SCHEDULER_NAME);
        client.pods().watch(new Watcher<Pod>() {
            @Override
            public void eventReceived(Action action, Pod pod) {
                if (action == Action.ADDED && pod.getSpec().getSchedulerName() != null &&
                    pod.getSpec().getSchedulerName().equals(CUSTOM_SCHEDULER_NAME) &&
                    pod.getSpec().getNodeName() == null) {

                    System.out.println("Found unscheduled pod: " + pod.getMetadata().getName());
                    executorService.schedule(() -> schedulePod(pod), 1, TimeUnit.SECONDS);
                }
            }

            @Override
            public void onClose(WatcherException cause) {
                System.err.println("Watcher closed: " + cause.getMessage());
            }
        });
    }

    private void schedulePod(Pod pod) {
        // Simple scheduling logic: pick the first available node
        // In a real scheduler, this would involve complex algorithms
        String targetNode = client.nodes().list().getItems().stream()
                .filter(node -> !node.getSpec().getUnschedulable())
                .map(node -> node.getMetadata().getName())
                .findFirst()
                .orElse(null);

        if (targetNode != null) {
            System.out.println("Attempting to schedule pod " + pod.getMetadata().getName() + " to node " + targetNode);
            try {
                client.pods().inNamespace(pod.getMetadata().getNamespace())
                        .withName(pod.getMetadata().getName())
                        .bind(new BindingBuilder()
                                .withNewMetadata()
                                    .withName(pod.getMetadata().getName())
                                .endMetadata()
                                .withNewTarget()
                                    .withApiVersion("v1")
                                    .withKind("Node")
                                    .withName(targetNode)
                                .endTarget()
                                .build());
                System.out.println("Pod " + pod.getMetadata().getName() + " scheduled to " + targetNode);
            } catch (Exception e) {
                System.err.println("Failed to bind pod " + pod.getMetadata().getName() + ": " + e.getMessage());
            }
        } else {
            System.out.println("No suitable node found for pod " + pod.getMetadata().getName());
        }
    }

    public static void main(String[] args) {
        CustomScheduler scheduler = new CustomScheduler();
        scheduler.start();
        // Keep the main thread alive
        try { Thread.sleep(Long.MAX_VALUE); } catch (InterruptedException e) { /* ignore */ }
    }
}

Nội dung đã được mở rộng với advanced scheduling scenarios và custom scheduler development, cùng các ví dụ Java.