Kubernetes offers fine-grained control over where Pods run and how many resources they consume. The scheduler selects an optimal node for each new Pod based on resource requests, affinity and anti-affinity rules, taints and tolerations, node selectors, and topology spread constraints. Taints applied to nodes repel Pods that lack matching tolerations, with effects NoSchedule for hard rejection, PreferNoSchedule for soft preference, and NoExecute for evicting already-running Pods. Node affinity constrains scheduling via node labels, with requiredDuringSchedulingIgnoredDuringExecution as a hard requirement and preferredDuringSchedulingIgnoredDuringExecution as a soft preference weighted numerically. Pod anti-affinity keeps replicas of the same workload spread across nodes or zones, while topology spread constraints generalize this idea by distributing Pods across failure domains defined by a topologyKey such as topology.kubernetes.io/zone, capped by a maxSkew value.
Scaling happens at two levels. The Horizontal Pod Autoscaler adjusts the replica count of a Deployment or ReplicaSet based on observed CPU, memory, or custom metrics, while the Vertical Pod Autoscaler tunes the CPU and memory requests and limits of individual containers using Off, Auto, or Initial modes. Because VPA may restart Pods to apply changes, it should not be used on the same CPU/memory metrics as HPA. The Cluster Autoscaler operates one level higher, adding or removing worker nodes when Pods cannot be scheduled or when nodes are underutilized. Resource requests and limits are central to all of this: requests drive scheduling decisions and must fit on a node, while limits drive runtime enforcement, with exceeded CPU limits causing throttling and exceeded memory limits triggering OOMKill. CPU is measured in millicores, where 1000m equals one vCPU.
Each Pod's combined settings give it a Quality of Service class: Guaranteed when every container's requests equal its limits, Burstable when some are set, and BestEffort when none are, with kubelet eviction preferring to kill BestEffort Pods first under node pressure. PriorityClass assigns a numeric priority to Pods so the scheduler can preempt lower-priority Pods when the cluster is full, with built-in classes such as system-node-critical and system-cluster-critical for system components. PodDisruptionBudgets complement replica counts by capping how many Pods of an application can be simultaneously unavailable during voluntary disruptions such as node drains, using either minAvailable or maxUnavailable. Throughout all of this, labels and selectors are the connective tissue: equality-based selectors like app=nginx and set-based selectors like env in (prod, staging) let Services, Deployments, ReplicaSets, and other controllers target the right Pods, while annotations carry non-identifying metadata that selectors cannot use but external tools can read.