Fundamentals, core concepts, and getting started
Quick Answer
Pods are like individual rooms in an apartment building, each running one or more containers. When a pod fails, the system tries to restart it automatically.
Detailed Answer
Imagine you're organizing a dinner party where each guest has their own designated space (pod) to set up dishes and utensils (containers). Just as if someone accidentally spills water on their space, Kubernetes detects this failure and tries to clean up by restarting the pod in another location within the apartment building (cluster). The pod ensures that even if one instance of a container fails, its functionality can be quickly restored without affecting other guests (services) or the overall party atmosphere.
Pods in Kubernetes are logical groupings of containers that make up an application. Each pod is responsible for running one or more related containers, such as a web server and database. When you create a pod, Kubernetes ensures that all the containers within it share resources like network namespaces and IPC, allowing them to communicate easily.
Kubernetes uses a stateful mechanism where pods are managed by controllers (like Deployment Controllers). If a pod fails, the controller notices this via periodic heartbeats. It then triggers a new pod creation in another node of the cluster, ensuring high availability and fault tolerance. The process involves several components: etcd stores the desired state, API Server manages CRUD operations, Scheduler places pods on nodes based on resource requirements, and Controller Managers monitor and maintain the state.
At scale, engineers must configure pod restart policies (liveness and readiness probes), define tolerations and anti-affinity rules to manage failures across different nodes. They also use monitoring tools like Prometheus to track pod health and ensure that failure scenarios are logged and can be reviewed post-incident. Common issues include network connectivity problems, insufficient resources, or misconfigured liveness/readiness checks leading to unnecessary restarts.
At scale, engineers must configure pod restart policies (liveness and readiness probes), define tolerations and anti-affinity rules to manage failures across different nodes. They also use monitoring tools like Prometheus to track pod health and make sure failure scenarios are logged and can be reviewed post-incident. Common issues include network connectivity problems, insufficient resources, or misconfigured liveness/readiness checks leading to unnecessary restarts.
A non-obvious issue is when a container in a pod writes its state to shared volumes but fails before it has time to clean up. This can lead to partial data loss if the pod is restarted. Additionally, resource starvation (e.g., CPU or memory limits) can cause containers to crash, which may not be immediately obvious without proper monitoring and logging.
Code Example
# Create a pod running nginx
kubectl run payments-web --image=nginx:1.25-alpine --port=80
# Check pod status and events
kubectl get pod payments-web -o wide
# View pod logs
kubectl logs payments-web
# Describe pod to see events, conditions, and restart reasons
kubectl describe pod payments-web
# Check why a pod failed (exit code and last state)
kubectl get pod payments-web -o jsonpath="{.status.containerStatuses[0].lastState}"
# Delete and recreate a failed pod
kubectl delete pod payments-web
kubectl run payments-web --image=nginx:1.25-alpine --port=80Interview Tip
A junior engineer typically says 'Kubernetes just restarts the pod automatically,' which is true but skips the actual mechanics an interviewer wants to hear. For a senior role, explain the full loop: the kubelet reports container health via liveness/readiness probes, a controller (ReplicaSet/Deployment) notices the pod is gone from its desired-count via the API server's watch mechanism, and schedules a brand-new pod — potentially on a different node — rather than 'restarting' the old one in place. The gotcha worth mentioning unprompted: a pod that writes state to a container's local filesystem loses that state on restart unless it's backed by a PersistentVolume, and misconfigured liveness probes are one of the most common causes of unnecessary restart storms in production.
💬 Comments
Quick Answer
Services act like a virtual load balancer that routes traffic to the correct set of pods based on labels and selectors. This ensures high availability and fault tolerance in an application.
Detailed Answer
Imagine you're running a coffee shop with multiple baristas (pods) serving customers, but the location is always changing due to renovations. To manage this smoothly, you use a system where each table (pod) has a unique label indicating its current location and type of service it offers (e.g., espresso or latte). The manager (service) maintains a list of all tables and routes customers to the appropriate barista based on their order. This way, even if one barista moves to another spot temporarily, the routing system ensures that no customer is left without service.
Kubernetes services provide a stable network identity for applications by abstracting away the underlying pod IP addresses. They route traffic using labels and selectors, ensuring that requests are directed to healthy pods within a deployment or across multiple deployments.
Services use a combination of load balancing algorithms (like round-robin, least connections) and iptables rules to distribute incoming traffic. The Service object defines the selector criteria (labels on pods), type (ClusterIP, NodePort, LoadBalancer), and port mappings. When a request comes in, the API Server routes it through the service proxy to the correct set of backend pods.
At scale, engineers need to configure service types based on availability requirements and network topology. For example, using ExternalName services for external DNS entries or LoadBalancer services to expose applications publicly. Monitoring tools like Prometheus track service health, latency, and throughput to ensure that traffic is routed efficiently. Common issues include misconfigured selectors leading to unexpected routing behavior, or load imbalance due to incorrect distribution policies.
At scale, engineers need to configure service types based on availability requirements and network topology. For example, using ExternalName services for external DNS entries or LoadBalancer services to expose applications publicly. Monitoring tools like Prometheus track service health, latency, and throughput to make sure traffic is routed efficiently. Common issues include misconfigured selectors leading to unexpected routing behavior, or load imbalance due to incorrect distribution policies.
A critical gotcha is when a service selector changes but the corresponding pod labels haven't been updated (or vice versa). This can result in traffic being sent to pods that no longer meet the criteria. Another issue is stale DNS entries for services, especially in multi-zone or multi-region clusters, which might cause delays and routing issues.
Code Example
# Create a ClusterIP service for payments-api pods kubectl expose deployment payments-api --port=8080 --target-port=8080 --type=ClusterIP # Check service endpoints (should list pod IPs) kubectl get endpoints payments-api # Verify service is routing to healthy pods kubectl get svc payments-api -o wide # Test service DNS resolution from inside the cluster kubectl run debug --rm -it --image=busybox -- nslookup payments-api.payments.svc.cluster.local # Check if selectors match pod labels kubectl get pods -l app=payments-api --show-labels
Interview Tip
A junior engineer typically describes a Service as 'a load balancer for pods' and stops there. For a senior role, the interviewer wants you to explain that a Service is really a stable virtual IP backed by kube-proxy's iptables (or IPVS) rules, continuously reconciled against the live set of pod IPs matching its label selector via Endpoints objects — there's no single process 'routing' traffic, it's distributed packet-rewriting rules on every node. The gotcha that separates strong candidates: a Service with a selector that no longer matches any pod's labels silently has zero endpoints, so requests don't error loudly, they just time out — always check `kubectl get endpoints` first when a Service seems to be swallowing traffic.
💬 Comments
Quick Answer
Kubernetes uses PersistentVolume (PV) and PersistentVolumeClaim (PVC) to manage storage for pods. PVCs are requests by applications for specific storage classes, while PVs provide the actual storage resources.
Detailed Answer
Think of a library where books are stored on shelves (pods), but each book has its own unique code (volume name). When you borrow a book (create a pod), you request it using your ID card (PersistentVolumeClaim) which matches with the librarian’s record. The librarian then finds the correct shelf (PersistentVolume) and places the book there, ensuring that every time you come back to borrow it, it's always in the same place.
Kubernetes uses PersistentVolumes (PVs) and PersistentVolumeClaims (PVCs) for managing storage. PVs are pre-provisioned storage resources with a specific provisioner, while PVCs represent application requests that match available PVs. This abstraction allows applications to request persistent storage without knowing the underlying details.
When you create a PVC, Kubernetes schedules it based on its access modes (read/write/readonly) and requested capacity. The Scheduler chooses an appropriate PV that matches these requirements and binds them together using the VolumeAttachment controller. During runtime, PersistentVolumeClaims can be mounted as volumes in pods, allowing applications to read from or write to this data.
At scale, engineers must choose between different storage classes for performance, durability, and cost considerations. They also need to monitor storage usage and lifecycle events (PV deletion) to prevent data loss. Common issues include resource contention when multiple PVCs compete for limited storage resources, or misconfigured access modes leading to permission errors.
At scale, engineers must choose between different storage classes for performance, durability, and cost considerations. They also need to monitor storage usage and lifecycle events (PV deletion) to prevent data loss. Common issues include resource contention when multiple PVCs compete for limited storage resources, or misconfigured access modes leading to permission errors.
A key gotcha is the difference between temporary (ephemeral) storage (built-in pods) and persistent storage (PV/PVC). Ephemeral storage can be lost if a pod is deleted, while persistent storage remains even after a pod restarts. Another issue is when PVCs request resources that are not available or cannot be provisioned due to network constraints or storage class limitations.
Code Example
# Create a PersistentVolumeClaim for a database
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: payments-db-data # PVC name referenced by the pod
namespace: payments
spec:
accessModes:
- ReadWriteOnce # Single node read-write access
resources:
requests:
storage: 50Gi # Request 50GB of storage
storageClassName: gp3 # AWS EBS gp3 StorageClass
# Check PVC status (should be Bound)
kubectl get pvc payments-db-data -n payments
# Check the PersistentVolume that was dynamically provisioned
kubectl get pv | grep payments-db-data
# Describe PVC to see events and binding details
kubectl describe pvc payments-db-data -n paymentsInterview Tip
A junior engineer typically explains PV/PVC as 'a way to request storage' without covering the binding lifecycle. For a senior role, the interviewer wants you to explain that a PVC is a request against a StorageClass, which can provision a PV dynamically (via a CSI driver) or bind to a pre-existing static PV matching its access mode and capacity — and that this binding is one-to-one and sticky, a bound PV can't be claimed by a second PVC even if it has spare capacity. The gotcha worth raising: deleting a pod does not delete its PVC or the underlying data, but the reverse assumption — that ephemeral emptyDir volumes survive a pod restart — is false and a common source of 'we lost our data' incidents.
💬 Comments
Quick Answer
RBAC defines who can perform specific actions on resources within a namespace, so only authorized users have access and preventing unauthorized modifications.
Detailed Answer
Imagine you're managing a company where different departments have different levels of access to sensitive information. For example, HR has access to employee records, while IT controls the network infrastructure. RBAC in Kubernetes is like setting up these rules: defining roles based on job functions (e.g., editor, viewer) and then assigning permissions to specific users or groups (like department heads). This ensures that only authorized personnel can make changes or view sensitive data.
Role-Based Access Control (RBAC) in Kubernetes allows administrators to define roles with specific permissions and bind these roles to users or groups. This mechanism restricts what actions a user can perform, such as creating, reading, updating, or deleting resources within a namespace.
Kubernetes RBAC uses Role and ClusterRole objects that map to subjects (users/groups). These roles have associated policies defining allowed actions on various resource types. RoleBindings and ClusterRoleBindings link these roles to specific users or groups. When a user makes an API request, the Authorization component checks if they have the required permissions based on their role bindings.
At scale, engineers need to configure RBAC policies carefully to balance security and usability. They use namespace-specific roles for better isolation between teams and projects. Monitoring tools like Open Policy Agent can enforce compliance with these policies by checking requests against defined rules. Common issues include overly permissive policies leading to accidental or malicious modifications, or complex permission hierarchies that are difficult to manage.
At scale, engineers need to configure RBAC policies carefully to balance security and usability. They use namespace-specific roles for better isolation between teams and projects. Monitoring tools like Open Policy Agent can enforce compliance with these policies by checking requests against defined rules. Common issues include overly permissive policies leading to accidental or malicious modifications, or complex permission hierarchies that are difficult to manage.
A critical gotcha is the difference between namespace-scoped and cluster-wide roles. Namespace-scoped RBAC applies only within a single namespace, while ClusterRole can be used across all namespaces in the cluster. Misconfiguring role bindings at the wrong scope can lead to unintended access control issues.
Code Example
# Create a Role that allows reading pods in the payments namespace apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: pod-reader # Role name namespace: payments # Scoped to payments namespace rules: - apiGroups: [""] # Core API group resources: ["pods", "pods/log"] # Can read pods and their logs verbs: ["get", "list", "watch"] # Read-only operations # Bind the role to the dev-team group apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: dev-pod-reader namespace: payments subjects: - kind: Group name: dev-team # Kubernetes group apiGroup: rbac.authorization.k8s.io roleRef: kind: Role name: pod-reader # References the Role above apiGroup: rbac.authorization.k8s.io # Test what a user can do kubectl auth can-i list pods -n payments --as-group=dev-team
Interview Tip
A junior engineer typically says RBAC 'controls who can do what' without explaining the object model. For a senior role, the interviewer wants you to name the four pieces and how they compose: Roles/ClusterRoles define permission sets (verbs on resources), and RoleBindings/ClusterRoleBindings attach those permission sets to subjects (users, groups, or service accounts) — a Role scoped to a namespace can only ever be bound within that namespace, while a ClusterRole can be bound either cluster-wide or namespace-scoped. The gotcha that trips people up: binding a ClusterRole via a RoleBinding grants those permissions only within that one namespace, not cluster-wide — a common source of both over-permissioning (someone assumes namespace scope when it's cluster-wide) and confusing 'permission denied' errors (someone assumes cluster scope when it's namespace-limited).
💬 Comments
Quick Answer
Helm charts are like blueprints that package application configurations and dependencies, making it easy to deploy complex applications in Kubernetes with consistent results.
Detailed Answer
Imagine you're building a house. You have all the materials (containers) but need a blueprint (chart) to organize them into the correct structure (deployments). Helm charts provide this blueprint for Kubernetes, allowing developers and operators to package up an application's configuration, dependencies, and resources into reusable templates that can be deployed consistently across different environments.
Helm is a package manager for Kubernetes that helps in deploying complex applications by providing templated manifests. These charts contain metadata, values, and multiple YAML files defining the desired state of your application, including deployments, services, and configurations.
A Helm chart is structured with a Chart.yaml file containing metadata about the package, a values.yaml for configurable parameters, and various templates (e.g., templates/deployment.yaml) that define Kubernetes resources. When you install a chart, Helm processes these templates using the provided values to generate the final manifests sent to the API Server. This process includes rendering placeholders in the template files with actual data from the values file.
At scale, engineers use Helm to manage and automate deployments across multiple clusters. They leverage Helm repositories (like Chartmuseum) for versioning and sharing charts. Monitoring tools like Prometheus can track Helm release statuses and detect issues during upgrades or rollbacks. Common challenges include managing dependencies between different charts, ensuring proper resource allocation, and handling secret management securely.
At scale, engineers use Helm to manage and automate deployments across multiple clusters. They use Helm repositories (like Chartmuseum) for versioning and sharing charts. Monitoring tools like Prometheus can track Helm release statuses and detect issues during upgrades or rollbacks. Common challenges include managing dependencies between different charts, ensuring proper resource allocation, and handling secret management securely.
A critical gotcha is the complexity of dependency resolution when multiple charts have overlapping resources (like services or deployments). Making sure all dependencies are correctly resolved can be challenging, especially in large-scale environments. Another issue is handling secrets and sensitive data within charts without compromising security.
Code Example
# Create a new Helm chart helm create payments-api # Install a chart into the payments namespace helm install payments-api ./payments-api \\ --namespace payments \\ --set replicaCount=3 \\ --set image.repository=registry.company.com/payments-api \\ --set image.tag=2.8.4 # List installed releases helm list -n payments # Upgrade a release with new values helm upgrade payments-api ./payments-api \\ --namespace payments \\ --set image.tag=2.9.0 # Rollback to previous version if upgrade fails helm rollback payments-api 1 -n payments # View release history helm history payments-api -n payments
Interview Tip
A junior engineer typically describes Helm as 'a package manager for Kubernetes YAML' and leaves it there. For a senior role, the interviewer wants you to explain what a release actually is: Helm renders a chart's templates against a values file into plain Kubernetes manifests, applies them, and stores a record of that exact rendered state (as a Secret by default) so `helm upgrade`/`helm rollback` can diff against a known previous state rather than just re-applying YAML blindly. The gotcha worth mentioning: `helm rollback` reverts the release's tracked resources to a prior revision, but it does not revert anything a chart's hooks did outside normal reconciliation (like a migration Job that already ran), so rollbacks of stateful changes need manual verification, not blind trust in the command.
💬 Comments
Quick Answer
A pod is a group of containers that share the same resources, like network interfaces. If one container in a pod fails, it can affect all others in the same pod.
Detailed Answer
Imagine you're organizing a team for a project where each person needs to collaborate on documents and share information. In Kubernetes, a pod is similar — it's a group of containers that are closely related and share resources like network interfaces, storage volumes, and logs. Each container in the pod works together as a single unit, much like how your team members need to communicate and collaborate effectively.
Kubernetes pods are designed this way because they help ensure consistency and availability. For example, if you have two containers running different services but both require access to shared storage or network resources, Kubernetes will group them into the same pod for convenience and efficiency. If a container within a pod fails, it can affect all other containers in that pod due to their interdependent nature.
Internally, when a container within a pod exits with an error code or is forcibly terminated by Kubernetes (perhaps due to resource constraints), the entire pod will be marked as failed. This triggers a series of events: first, the Pod's controller watches for these failures and decides how to handle them (e.g., restarting containers). Then, if the failure persists, it might lead to the entire pod being recreated with new containers.
At scale in production, engineers monitor pod events closely using tools like Prometheus and Grafana. They pay attention to metrics such as pod restarts, container health checks, and overall service availability. Ensuring high reliability often involves setting up redundancy (using multiple pods) or configuring automatic failover mechanisms that can quickly replace unresponsive containers.
A non-obvious gotcha is that if a pod's network interface fails, it might affect not just the containers within that pod but also neighboring pods. This is because Kubernetes relies heavily on network communication between pods to coordinate and manage tasks.
Code Example
# Example of creating a simple pod with two containers
apiVersion: v1
kind: Pod
metadata:
name: payments-api-pod
description: A pod containing the payments API service and its database connection.
spec:
containers:
- name: payments-api
image: payments-api-service:v2
- name: db-connection
image: db-connection-service:v1Interview Tip
A junior engineer typically explains that pods restart when they fail, but for a senior role, the interviewer is actually looking for understanding of the pod lifecycle states (Pending, Running, Succeeded, Failed, Unknown), how kubelet manages container health through probes, and what happens to in-flight requests when a pod becomes unresponsive. Explain how liveness probes trigger restarts, how readiness probes remove pods from service endpoints, and why the distinction matters for zero-downtime deployments. Mentioning pod disruption budgets and graceful shutdown with preStop hooks shows production awareness.
◈ Architecture Diagram
┌──────────┐
│ k8s Pod │
└─────┬────┘
│
┌───┴───┐
│Container A│
│(payments-api)│
└─────┬────┘
│
┌───┴───┐
│Container B│
│(db-connection)│
└──────────┘💬 Comments
Quick Answer
Kubernetes uses a virtual network for pods, allowing them to communicate as if they were on the same local network. This means services running in different pods can easily interoperate without needing complex networking configurations.
Detailed Answer
Think of your home Wi-Fi network where all devices can talk to each other seamlessly. In Kubernetes, this is similar — pods are like devices on a virtual local area network (LAN). Each pod gets its own IP address and can communicate with any other pod or external services as if they were directly connected via the same router.
Kubernetes achieves this through a mechanism called Service Discovery. When a new pod is created, Kubernetes assigns it an IP address within the cluster's virtual network. Pods can then use these addresses to contact each other, just like how you might ping your neighbor’s Wi-Fi-enabled device from yours. This internal communication is crucial for services that need to interact with one another, such as microservices architecture where different components depend on each other.
Internally, Kubernetes uses a combination of tools and configurations to manage this network. The API Server handles the creation and deletion of pods, and the Kubelet makes sure containers are running correctly within those pods. The CNI (Container Network Interface) plugin manages IP address assignment and routing within the cluster. When you create a Service object in Kubernetes, it essentially creates a load balancer that routes traffic to the correct set of pods based on their labels.
At scale, engineers need to make sure network policies are properly configured to control how pods can communicate with each other and external services. Misconfigurations here can lead to security issues or performance bottlenecks. For instance, overly permissive rules might expose internal services to the internet without proper safeguards.
A non-obvious gotcha is that network traffic between pods often doesn't use standard DNS names but instead uses IP addresses directly due to latency and performance considerations. This means you need to carefully manage your network policies to ensure security while maintaining efficiency.
Code Example
# Example of creating a simple Service for pod communication
apiVersion: v1
kind: Service
metadata:
name: user-auth-service
description: A service that provides authentication and authorisation for the application.
spec:
selector:
app: user-auth
ports:
- protocol: TCP
port: 80
targetPort: 8080Interview Tip
A junior engineer typically says pods communicate over the cluster network, but for a senior role, the interviewer is actually looking for understanding of the flat network model where every pod gets a unique IP, how CNI plugins like Calico or Cilium implement this, how Services abstract pod IPs behind a stable ClusterIP using iptables or eBPF rules, and how DNS resolution via CoreDNS enables service discovery by name. Mentioning NetworkPolicy for traffic control and the difference between east-west and north-south traffic shows networking depth.
◈ Architecture Diagram
┌──────────┐
│ k8s Pod │
└─────┬────┘
│
┌───┴───┐
│Container A│
│(user-auth)│
└─────┬────┘
│
┌───┴───┐
│Container B│
│(payments-api)│
└──────────┘
┌──────────┐ ┌──────────┐
│ k8s API │───▶│ Kubelet │───▶│ Container │
└──────────┘ └──────┬────┘ └──────────┘
│
┌────────────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ CNI │ │Kubernetes│
└──────────┘ └──────────┘💬 Comments
Quick Answer
Kubernetes provides Persistent Volumes (PVs) to store data that needs to persist across pod restarts. This means stateful applications can maintain their state even when containers are replaced.
Detailed Answer
Imagine you're setting up a file-sharing system where users need to save documents and access them later. Just like how you might use an external hard drive or cloud storage service, Kubernetes uses Persistent Volumes (PVs) to provide persistent storage for applications that need it. This is crucial for stateful applications because they often have data that must be retained across container restarts.
Kubernetes manages this through a mechanism called Persistent Volume Claims (PVCs). A PVC acts like a request from an application for storage, and Kubernetes will allocate the appropriate PV to satisfy this claim. Unlike temporary (ephemeral) volumes which are tied directly to pods, persistent volumes can persist even if the underlying pod is recreated or deleted.
Internally, when you create a Persistent Volume in Kubernetes, it's essentially a block of storage that can be dynamically provisioned and attached to pods. The volume type could range from local disks on worker nodes to network-attached storage like NFS, Ceph, or AWS EBS. When a pod is created with a PVC, Kubernetes will automatically attach the appropriate PV and mount it as a filesystem within the container.
At scale in production, engineers need to carefully manage how storage is provisioned and accessed. They must consider factors such as performance, durability, and cost when choosing storage classes and backing providers. Monitoring tools like Prometheus can help track storage usage and health metrics for better decision-making.
A non-obvious gotcha is that persistent volumes have limited mobility within the cluster. If a pod needs to be moved (due to maintenance or scaling), Kubernetes must make sure its PV remains attached, which could introduce complex orchestration issues. Additionally, improper handling of storage can lead to data loss if not managed correctly.
Code Example
# Example of creating a Persistent Volume
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
description: A persistent volume for storing user documents.
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /mnt/dataInterview Tip
A junior engineer typically mentions volumes, but for a senior role, the interviewer is actually looking for understanding of ephemeral vs persistent storage, how PVCs decouple storage requests from provisioning, how StorageClasses enable dynamic provisioning, and why StatefulSets need stable storage identities. Explain the CSI driver model, how reclaim policies (Retain vs Delete) affect data after pod deletion, and why you should always test backup and restore procedures for production persistent volumes.
◈ Architecture Diagram
┌──────────┐
│ k8s Pod │
└─────┬────┘
│
┌───┴───┐
│Container A│
│(user-documents)│
└─────┬────┘
▼
┌─────────────────────┐
│ k8s Persistent Volume (PV) 5Gi, ReadWriteOnce, HostPath /mnt/data│
└─────────────────────┘💬 Comments
Quick Answer
The scheduler decides which node a new pod should be placed on based on resource availability, policies, and constraints.
Detailed Answer
Imagine your kitchen is a server with various appliances (CPU cores) and storage space for ingredients. When you start preparing a meal (running a pod), you need to decide where the best place is to lay out all the ingredients and cookware (node). The scheduler in Kubernetes plays this role, but on a much larger scale.
In Kubernetes, the scheduler's main responsibility is to choose which node a new pod should run on. It does this by considering several factors: available resources like CPU and memory, network proximity for fast communication with other pods, custom policies defined by engineers, and even hardware features of the nodes themselves (like GPUs).
Here’s how it works step-by-step: The scheduler first queries the API server to get information about all available nodes. Then, it looks at the pod's specifications, including resource requests and limits, as well as any node selector or affinity rules that might dictate where the pod should run. After evaluating these factors, the scheduler proposes a node for each pod, and the API server confirms this by updating the pod’s status.
At scale, you need to ensure your pods are distributed across nodes in a way that maximizes resource use while minimizing network latency between services. For instance, you might want to place database-related pods close to application pods to reduce data transfer times over the network. Engineers also monitor metrics like node utilizations and response times to make sure no single node is overloaded.
A non-obvious gotcha is that if your scheduler makes a poor decision due to outdated information or misconfigured policies, it can lead to inefficient resource use. For example, placing all CPU-intensive pods on one node while leaving others idle can cause bottlenecks and slow down the entire system.
Code Example
# Example kubectl command to list nodes
# This shows available resources for each node
kubectl get nodes -o wide
# Example YAML manifest with a pod that has specific resource requests
apiVersion: v1
kind: Pod
metadata:
name: webserver-pod
spec:
containers:
- name: webserver-container
image: nginx
resources:
limits:
cpu: "2"
memory: "4Gi"
requests:
cpu: "1"
memory: "2Gi"Interview Tip
A junior engineer typically says the scheduler picks a node with enough resources, but for a senior role, the interviewer is actually looking for understanding of the two-phase scheduling process: filtering (eliminating ineligible nodes) and scoring (ranking remaining nodes). Explain how resource requests affect scheduling decisions, how taints and tolerations control where pods can run, how affinity and anti-affinity spread workloads, and how topology spread constraints ensure zone-level high availability.
◈ Architecture Diagram
┌────────┐ ┌───────┬───────┐ └────────┘
│ kubectl │───▶│ API Server│───▶│ etcd │
└────────┘ └───────┴───────┘ └────────┘
↑
│
▼
┌────────┐ ┌──────────┐ ┌──────────┐
│Scheduler│──▶│Controller│─┐ │ Nodes │
└────────┘ └──────────┘ └─────────────┘💬 Comments
Quick Answer
Network policies control inbound and outbound traffic to specific pods, enhancing security by defining which services can communicate with each other.
Detailed Answer
Imagine you have a building where multiple tenants (pods) live. The door (network policy) controls who can enter or leave the building based on pre-defined rules. In Kubernetes, network policies are used to define these rules for pods, ensuring only necessary traffic is allowed in and out.
In Kubernetes, network policies are implemented using plugins like Calico or kube-router. They allow you to specify which services (pods) can communicate with each other based on various criteria such as pod labels, IP ranges, and protocols.
Here’s a step-by-step breakdown: First, the network policy specifies which pods should be allowed to communicate. Then, when a packet of data is sent from one pod to another, the plugin checks if it matches any policies. If there's no matching rule or an explicit deny statement, the traffic is blocked.
In production environments, this is crucial for maintaining security. For example, you might want to make sure only certain services can communicate with a database service to prevent unauthorized access. Network policies also help in reducing attack surface by defaulting to non-communication unless explicitly allowed.
At scale, network policies need to be carefully designed to balance security requirements and performance considerations. For instance, overly restrictive policies can lead to increased latency due to frequent policy checks. Monitoring tools like the Kubernetes dashboard or third-party solutions (e.g., Falco) are used to track traffic patterns and ensure policies are being enforced correctly.
A non-obvious gotcha is that network policies should be applied with care; overly permissive rules can allow more traffic than necessary, leading to potential security vulnerabilities. For example, allowing all pods to communicate over port 80 might expose your services to unnecessary risks.
Code Example
# Example YAML manifest for a simple network policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: webserver-to-database-policy
spec:
podSelector:
matchLabels:
app: database
ingress:
- from:
- podSelector:
matchLabels:
app: webserverInterview Tip
A junior engineer typically says NetworkPolicy restricts traffic, but for a senior role, the interviewer is actually looking for the default-open security model of Kubernetes and how to implement zero-trust networking. Explain that without any NetworkPolicy, all pods can talk to all pods. Then describe how a deny-all default policy followed by explicit allow rules creates a whitelist model. Mention that NetworkPolicy requires a CNI plugin that supports it (Calico, Cilium), and that policies are namespace-scoped and additive.
◈ Architecture Diagram
┌──────────┐ ┌───────┬───────┐ └──────────┘ │ Pod A │───▶│ API Server│───▶│ Network Policy Plugin │───▶│ Pod B │ └──────────┘ └───────┴───────┘ └──────────┘
💬 Comments
Quick Answer
Storage classes define the type of storage to use for a PersistentVolumeClaim, so volumes are provisioned with specific characteristics like performance or cost.
Detailed Answer
Imagine you have a filing cabinet (PersistentVolume) where you store important documents. Now, depending on how valuable and sensitive those documents are, you might choose different types of cabinets—maybe one for quick access to frequently used files and another for storing less critical paperwork. In Kubernetes, storage classes serve this role by defining the type of storage that should be provisioned when a PersistentVolumeClaim (PVC) is made.
Storage classes in Kubernetes allow administrators to define policies about how volumes are created. For example, you might want some PVCs to be backed by fast SSD drives for performance-critical applications, while others could use cheaper but slower HDD storage. This flexibility helps in optimizing costs and performance based on the workload requirements.
The process works as follows: When a user requests a PersistentVolume via a PersistentVolumeClaim (PVC), the system checks which StorageClass is associated with that request. The StorageClass provides details like volume capacity, access modes, and dynamic provisioning options. If dynamic provisioning is enabled, the cluster automatically creates the necessary PersistentVolume based on the defined parameters.
In production environments, storage classes are critical for ensuring high availability and performance. For instance, using a high-performance class for databases can improve query response times significantly. Monitoring tools like Prometheus or built-in Kubernetes observability features help in tracking volume usage patterns and making sure storage is being used efficiently.
A non-obvious gotcha is understanding how different StorageClasses interact with data persistence across node failures. For example, if a high-performance SSD-based class is configured without proper backup strategies, losing a node could result in data loss unless snapshots or other recovery mechanisms are in place.
Code Example
# Example YAML manifest for a storage class apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-storage provisioner: kubernetes.io/aws-ebs parameters: type: io1 iopsPerGB: "5000" throughputMode: "provisioned" throughputProvisionedValue: "100MiBps"
Interview Tip
A junior engineer typically says StorageClass defines the type of storage, but for a senior role, the interviewer is actually looking for understanding of dynamic provisioning: how a PVC referencing a StorageClass triggers the CSI driver to create a real volume automatically. Explain the difference between gp3 and io2 StorageClasses on AWS, how volume binding modes (Immediate vs WaitForFirstConsumer) affect scheduling, and why the default StorageClass matters for teams that forget to specify one.
◈ Architecture Diagram
┌──────────┐ ┌───────┬───────┐ └──────────┘ │ PVC A │───▶│ API Server│───▶│ Storage Class Definition │───▶│ PV A │ └──────────┘ └───────┴───────┘ └──────────┘
💬 Comments
Quick Answer
A Service provides a stable network endpoint (DNS name and virtual IP) that routes traffic to a set of Pods selected by labels. It solves the problem that Pods are ephemeral — they get new IP addresses every time they restart — so clients need a stable way to reach them.
Detailed Answer
Imagine you're calling a customer support line. You don't call a specific agent's personal phone number — you call the main support number, and the phone system routes you to whichever agent is available. If one agent goes on break, the system automatically sends your call to another. The main number never changes even though the agents behind it constantly rotate.
A Kubernetes Service works the same way. Pods come and go — they crash, get rescheduled, scale up and down — and each time they get a brand new IP address. If your frontend tried to connect directly to a backend Pod IP, it would break every time that Pod restarted. A Service gives you a stable virtual IP (called a ClusterIP) and a DNS name (like payments-api.production.svc.cluster.local) that never changes. Behind the scenes, the Service watches for Pods that match its label selector, and maintains a list of healthy Pod IPs called Endpoints.
When traffic hits the Service IP, kube-proxy on each node handles the routing. In the default iptables mode, kube-proxy writes iptables rules that randomly distribute connections across all healthy backend Pods. In IPVS mode (used at scale), it uses the Linux kernel's IPVS load balancer for better performance with thousands of backend Pods. The key insight is that the Service IP is virtual — no network interface actually has this IP. It only exists as iptables/IPVS rules that intercept packets and DNAT (destination NAT) them to a real Pod IP.
There are four Service types, each building on the previous: ClusterIP (default — only reachable inside the cluster), NodePort (exposes the Service on a static port on every node's IP), LoadBalancer (provisions a cloud load balancer that points to the NodePorts), and ExternalName (a CNAME alias to an external DNS name, no proxying). In production, most internal services use ClusterIP, while services that need external access use LoadBalancer or an Ingress controller.
A subtle but important gotcha: Services use label selectors to find Pods, and the Endpoints controller only includes Pods that are both Running AND pass their readiness probe. This means if your readiness probe is misconfigured — say it checks a dependency that's temporarily down — the Service will remove all your Pods from its Endpoints, causing a complete outage even though the Pods are actually running. Always make readiness probes check only whether your application can handle requests, not whether its dependencies are healthy.
Code Example
# Expose the payments-api deployment as a ClusterIP Service
kubectl expose deployment payments-api \
--port=80 \
--target-port=8080 \
--name=payments-api
# Verify the Service got a ClusterIP and found the right Pods
kubectl get svc payments-api -o wide
# Check which Pod IPs the Service is routing to
kubectl get endpoints payments-api
# Test the Service DNS from another Pod
kubectl run debug --rm -it --image=busybox -- \
wget -qO- http://payments-api.default.svc.cluster.local/health
# Full Service manifest with health-aware routing
apiVersion: v1
kind: Service
metadata:
name: payments-api
namespace: production
spec:
type: ClusterIP # Only reachable inside the cluster
selector:
app: payments # Routes to Pods with this label
tier: backend
ports:
- name: http
port: 80 # Port the Service listens on
targetPort: 8080 # Port the container listens on
protocol: TCP
sessionAffinity: None # Random distribution (default)Interview Tip
A junior engineer typically says 'a Service load balances traffic to Pods.' For a senior role, you should explain the mechanism: kube-proxy watches the Service and Endpoints objects, then programs iptables/IPVS rules on every node. Mention that the ClusterIP is virtual — it only exists as NAT rules, not on any interface. The killer insight is explaining the relationship between readiness probes and Endpoints: a Pod that fails its readiness probe is removed from the Service's Endpoints, which is the foundation of zero-downtime deployments. If you can articulate this chain, you prove operational understanding.
◈ Architecture Diagram
┌──────────┐
│ Client │
└────┬─────┘
│ payments-api.production.svc.cluster.local
▼
┌──────────┐ ClusterIP: 10.96.45.12
│ Service │ (virtual — only iptables rules)
└────┬─────┘
│ kube-proxy distributes via iptables/IPVS
│
┌────┴──────────────────────────┐
│ Endpoints │
├───────────┬───────┬───────────┤
▼ ▼ ▼ │
┌────────┐ ┌────────┐ ┌────────┐
│Pod │ │Pod │ │Pod │
│10.244 │ │10.244 │ │10.244 │
│.1.5 │ │.2.3 │ │.3.8 │
└────────┘ └────────┘ └────────┘
Ready ✓ Ready ✓ Ready ✓💬 Comments
Quick Answer
A Deployment is a controller that manages a set of identical Pods through a ReplicaSet. It handles rolling updates, rollbacks, and scaling by creating new ReplicaSets and gradually shifting traffic from old Pods to new ones while maintaining availability.
Detailed Answer
Think of a Deployment like a restaurant manager responsible for keeping the right number of cooks on shift. If the manager needs 5 cooks and one calls in sick, they immediately hire a replacement. If the restaurant gets busier, the manager adds more cooks. And when the kitchen switches to a new menu (application update), the manager doesn't fire everyone and start fresh — they gradually train new cooks on the new menu while the experienced ones keep serving customers, so there's never a gap in service.
A Deployment is a declarative way to tell Kubernetes: 'I want 3 copies of my application running at all times, using this container image, with these resource limits.' The Deployment controller, running inside the kube-controller-manager, continuously watches the actual state and adjusts it to match your desired state. Under the hood, a Deployment doesn't manage Pods directly — it creates a ReplicaSet, which is the controller that actually creates and monitors the Pods. The Deployment manages the ReplicaSet.
The real power of Deployments is their update strategy. When you change the container image (say from v1.2 to v1.3), the Deployment creates a NEW ReplicaSet for v1.3 and gradually scales it up while scaling the OLD ReplicaSet down. With the default RollingUpdate strategy and settings of maxUnavailable=25% and maxSurge=25%, if you have 4 replicas, Kubernetes will first create 1 new Pod (surge), then kill 1 old Pod (unavailable), and continue this dance until all Pods are running v1.3. At every step, at least 3 Pods are serving traffic. The old ReplicaSet is kept around (scaled to 0 replicas) so you can instantly roll back.
Kubernetes records the history of these rollouts. Each change creates a new revision, and by default Kubernetes keeps the last 10. If v1.3 has a bug, you can run kubectl rollout undo and the Deployment immediately scales the old ReplicaSet back up — much faster than deploying a new version because the old ReplicaSet and its Pod template already exist.
A critical gotcha: Deployments rely on readiness probes to decide when a new Pod is 'ready' to receive traffic. If you don't configure a readiness probe, Kubernetes considers a Pod ready the moment its container starts — even if your application needs 30 seconds to load configuration, warm caches, or connect to a database. This means during a rolling update, traffic gets sent to Pods that aren't actually ready, causing errors. Always configure readiness probes in production Deployments.
Code Example
# Create a deployment with 3 replicas
kubectl create deployment user-auth-service \
--image=user-auth:1.2.0 \
--replicas=3
# Full Deployment manifest with production-ready settings
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-auth-service
namespace: production
spec:
replicas: 3 # Run 3 identical Pods
revisionHistoryLimit: 5 # Keep 5 old ReplicaSets for rollback
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # At most 1 Pod down during update
maxSurge: 1 # At most 1 extra Pod during update
selector:
matchLabels:
app: user-auth
template:
metadata:
labels:
app: user-auth
spec:
containers:
- name: api
image: user-auth:1.2.0
ports:
- containerPort: 8080
readinessProbe: # CRITICAL — gates traffic during rollout
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
resources:
requests:
cpu: 250m # Guaranteed CPU
memory: 256Mi
limits:
memory: 512Mi # Hard ceiling — OOMKilled if exceeded
# Update the image — triggers rolling update
kubectl set image deployment/user-auth-service \
api=user-auth:1.3.0
# Watch the rollout progress
kubectl rollout status deployment/user-auth-service
# Something wrong? Instant rollback to previous version
kubectl rollout undo deployment/user-auth-service
# Check rollout history
kubectl rollout history deployment/user-auth-serviceInterview Tip
A junior engineer typically explains that a Deployment 'manages Pods.' For a senior role, you should explain the three-level hierarchy: Deployment → ReplicaSet → Pods. Explain that the Deployment doesn't touch Pods directly — it creates and manages ReplicaSets, and each rollout creates a new ReplicaSet. The critical production insight is the relationship between readiness probes and rolling updates: without a readiness probe, Kubernetes will send traffic to Pods that aren't ready, causing user-facing errors during deployments. If you can explain maxUnavailable, maxSurge, and why old ReplicaSets are kept at 0 replicas for fast rollback, you prove operational maturity.
◈ Architecture Diagram
┌──────────────── Deployment ─────────────────┐ │ user-auth-service (replicas: 3) │ │ │ │ ┌─── ReplicaSet v2 (current) ────────────┐ │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │ │ │Pod v2 │ │Pod v2 │ │Pod v2 │ │ │ │ │ │ Ready │ │ Ready │ │ Ready │ │ │ │ │ └────────┘ └────────┘ └────────┘ │ │ │ └────────────────────────────────────────┘ │ │ │ │ ┌─── ReplicaSet v1 (scaled to 0) ────────┐ │ │ │ (kept for rollback — no running Pods) │ │ │ └────────────────────────────────────────┘ │ └──────────────────────────────────────────────┘ Rolling Update (v2 → v3): ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐ │v2 ✓ │ │v2 ✓ │ │v2 ✓ │ │v3 new │ ← surge └───────┘ └───────┘ └───────┘ └───────┘ ┌───────┐ ┌───────┐ ┌───────┐ │v2 ✓ │ │v2 ✓ │ │v3 ✓ │ ← v2 pod removed └───────┘ └───────┘ └───────┘
💬 Comments
Quick Answer
Namespaces are virtual clusters within a physical Kubernetes cluster that provide scope for resource names, access control boundaries, and resource quota enforcement. They let multiple teams or environments share a cluster without stepping on each other.
Detailed Answer
Think of Namespaces like floors in an office building. Each floor houses a different department — engineering, marketing, finance. Each floor has its own conference rooms (resources) that can share the same names without conflict (Room A on floor 3 is different from Room A on floor 5). The building security (RBAC) can give each department access only to their floor. And the building manager can set limits on how much electricity (CPU) and water (memory) each floor can use.
In Kubernetes, a Namespace is a logical partition of the cluster. Resources like Pods, Services, Deployments, and ConfigMaps are 'namespaced' — they live inside a Namespace, and their names only need to be unique within that Namespace. You can have a Service called 'payments-api' in the 'staging' namespace AND a different Service called 'payments-api' in the 'production' namespace without conflict. Each Namespace is completely isolated from the other in terms of naming.
Kubernetes creates four Namespaces by default: 'default' (where resources go if you don't specify a namespace), 'kube-system' (for Kubernetes system components like CoreDNS, kube-proxy, and the metrics server), 'kube-public' (readable by all users, used for cluster discovery), and 'kube-node-lease' (holds Lease objects for node heartbeat efficiency). In production, you should never deploy your applications into 'default' or 'kube-system'.
Namespaces become powerful when combined with other Kubernetes features. ResourceQuotas let you limit the total CPU, memory, and number of objects a Namespace can consume — so one team can't monopolize the cluster. LimitRanges set default and maximum resource requests per Pod. RBAC policies can restrict users to specific Namespaces — a developer might have full access to 'staging' but read-only access to 'production'. NetworkPolicies can restrict traffic between Namespaces, creating network isolation.
A common misconception is that Namespaces provide security isolation. They don't — at least not by themselves. A Pod in one Namespace can freely communicate with a Pod in another Namespace over the network (via the full DNS name: service-name.namespace.svc.cluster.local) unless you explicitly create NetworkPolicies to block it. Namespaces are a soft boundary for organization, not a hard boundary for security. For true multi-tenant isolation, you need Namespaces plus NetworkPolicies plus RBAC plus resource quotas working together.
Code Example
# List all namespaces in the cluster
kubectl get namespaces
# Create a namespace for the payments team
kubectl create namespace payments-team
# Deploy into a specific namespace
kubectl create deployment checkout-api \
--image=checkout:3.1 \
--namespace=payments-team
# Set a ResourceQuota to prevent resource hogging
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-quota
namespace: payments-team
spec:
hard:
requests.cpu: "4" # Team can request up to 4 CPU cores total
requests.memory: 8Gi # Team can request up to 8 GiB RAM total
limits.cpu: "8" # Hard ceiling at 8 cores
limits.memory: 16Gi
pods: "20" # Max 20 Pods in this namespace
services: "10" # Max 10 Services
# Cross-namespace service discovery
# From within payments-team namespace, reach a service in auth namespace:
# curl http://user-auth.auth-team.svc.cluster.local:8080/verify
# Switch your kubectl context to always use a namespace
kubectl config set-context --current --namespace=payments-team
# View resources across ALL namespaces
kubectl get pods --all-namespacesInterview Tip
A junior engineer typically says 'Namespaces separate environments like dev and prod.' For a senior role, you should explain that Namespaces are an organizational boundary, NOT a security boundary by default. The critical insight is the four-pillar multi-tenancy model: Namespaces + RBAC + NetworkPolicies + ResourceQuotas. Without all four, you have naming isolation but not real isolation. Mention cross-namespace DNS (service.namespace.svc.cluster.local) to show you understand that network traffic flows freely across Namespaces unless NetworkPolicies block it. This proves you've operated multi-team clusters, not just single-team dev environments.
◈ Architecture Diagram
┌──────────── Kubernetes Cluster ─────────────┐ │ │ │ ┌──── payments-team ns ───────────────────┐ │ │ │ ┌──────────┐ ┌──────────┐ Quota: │ │ │ │ │checkout │ │payments │ CPU: 4 │ │ │ │ │-api │ │-worker │ Mem: 8Gi │ │ │ │ └──────────┘ └──────────┘ Pods: 20 │ │ │ └─────────────────────────────────────────┘ │ │ ▲ NetworkPolicy can block/allow │ │ │ │ cross-namespace traffic ▼ │ │ ┌──── auth-team ns ───────────────────────┐ │ │ │ ┌──────────┐ ┌──────────┐ Quota: │ │ │ │ │user-auth │ │session │ CPU: 2 │ │ │ │ │ │ │-store │ Mem: 4Gi │ │ │ │ └──────────┘ └──────────┘ │ │ │ └─────────────────────────────────────────┘ │ │ │ │ ┌──── kube-system ns (system components) ─┐ │ │ │ CoreDNS │ kube-proxy │ metrics-server │ │ │ └─────────────────────────────────────────┘ │ └──────────────────────────────────────────────┘
💬 Comments
Quick Answer
kubectl is the command-line tool for interacting with the Kubernetes API server. It translates human-readable commands into API requests. The essential commands are get, describe, apply, logs, exec, and delete — together they cover 90% of daily operations.
Detailed Answer
Think of kubectl like a remote control for your Kubernetes cluster. Just as a TV remote sends infrared signals to the TV to change channels, kubectl sends HTTP requests to the Kubernetes API server to create, read, update, and delete resources. You could technically do everything kubectl does by sending raw REST API calls with curl, but kubectl makes it convenient by handling authentication, serialization, and output formatting.
When you run a command like kubectl get pods, here's what happens internally: kubectl reads your kubeconfig file (usually at ~/.kube/config) to find the API server address and your authentication credentials (certificates, tokens, or cloud provider auth). It then sends an HTTPS GET request to the API server at a path like /api/v1/namespaces/default/pods. The API server authenticates your request, checks your RBAC permissions, and returns the data. Kubectl then formats the response into the table you see in your terminal.
The most critical commands form a daily workflow: kubectl get lists resources (pods, services, deployments) — add -o wide for more detail or -o yaml for the full spec. kubectl describe gives a detailed view of a single resource including its events, which is your first stop when debugging. kubectl apply -f creates or updates resources from a YAML file — this is the declarative approach used in production. kubectl logs streams container logs for debugging. kubectl exec -it pod -- /bin/sh gives you a shell inside a running container, like SSH into a server. kubectl delete removes resources.
Beyond the basics, several advanced commands are invaluable: kubectl port-forward tunnels a local port to a Pod for local debugging. kubectl top pods shows real-time CPU/memory usage (requires metrics-server). kubectl rollout status monitors deployment progress. And kubectl diff -f file.yaml shows what would change before you apply — essential in production.
A productivity gotcha: kubectl is verbose by default. In your daily workflow, set up aliases (alias k=kubectl), enable shell completion (source <(kubectl completion zsh)), and use kubectl config set-context to set a default namespace so you don't have to type -n namespace on every command. For the CKA/CKAD exams, these shortcuts save critical minutes.
Code Example
# Check cluster connectivity and version kubectl cluster-info kubectl version --short # List Pods in current namespace with extra details kubectl get pods -o wide # List ALL resources across ALL namespaces kubectl get all --all-namespaces # Describe a Pod — shows events, conditions, and probe status kubectl describe pod payments-api-7d5f8b6c4-x2k9m # Apply a manifest (declarative — creates or updates) kubectl apply -f deployment.yaml # View real-time logs of a running Pod kubectl logs -f payments-api-7d5f8b6c4-x2k9m # View logs of a specific container in a multi-container Pod kubectl logs payments-api-7d5f8b6c4-x2k9m -c log-shipper # Get a shell inside a running container kubectl exec -it payments-api-7d5f8b6c4-x2k9m -- /bin/sh # Port-forward for local debugging (localhost:8080 → Pod:8080) kubectl port-forward pod/payments-api-7d5f8b6c4-x2k9m 8080:8080 # Check resource usage (requires metrics-server) kubectl top pods --sort-by=memory kubectl top nodes # Preview changes before applying kubectl diff -f updated-deployment.yaml # Delete a resource kubectl delete pod payments-api-7d5f8b6c4-x2k9m # Essential aliases for productivity # alias k=kubectl # alias kgp='kubectl get pods' # alias kgs='kubectl get svc' # alias kdp='kubectl describe pod' # alias kl='kubectl logs -f'
Interview Tip
A junior engineer typically lists kubectl commands they've memorized. For a senior role, show understanding of what happens beneath the command — that kubectl is an API client sending REST requests to the API server, authenticated via kubeconfig. Explain the request flow: kubeconfig → HTTPS to API server → authentication → RBAC authorization → admission controllers → etcd. If asked about debugging, demonstrate a systematic approach: get pods (check status) → describe pod (check events) → logs (check application output) → exec (inspect the container). This workflow shows operational maturity, not just command memorization.
◈ Architecture Diagram
┌──────────┐ HTTPS/REST ┌──────────┐
│ │──────────────────►│ │
│ kubectl │ GET /api/v1/pods │API Server│
│ │◄──────────────────│ │
└────┬─────┘ JSON response └────┬─────┘
│ │
│ reads │ reads/writes
▼ ▼
┌──────────┐ ┌──────────┐
│~/.kube/ │ │ etcd │
│ config │ │ (store) │
└──────────┘ └──────────┘
Request Flow:
kubectl → Auth → RBAC → Admission → etcd
│
┌─────────────┤
▼ ▼
┌──────────┐ ┌──────────┐
│Scheduler │ │Controller│
│ │ │ Manager │
└──────────┘ └──────────┘💬 Comments
Quick Answer
kubectl is the command-line tool for interacting with the Kubernetes API server. It reads credentials from ~/.kube/config, sends HTTPS REST requests to the API server, and formats the JSON responses into human-readable output.
Detailed Answer
Think of kubectl like a TV remote control. The remote doesn't contain the TV's brain — it just sends signals (infrared or bluetooth) to the TV telling it what to do: change the channel, adjust the volume, open settings. Similarly, kubectl doesn't contain Kubernetes logic — it just sends HTTP requests to the API server, which is the actual brain of the cluster. You could do everything kubectl does by sending raw curl commands, but kubectl makes it convenient.
When you run a command like kubectl get pods, here's the full chain of events: First, kubectl reads your kubeconfig file (usually at ~/.kube/config) to find three things — the API server's URL (like https://my-cluster.region.eks.amazonaws.com), your authentication credentials (certificates, bearer tokens, or a cloud provider auth plugin), and which namespace to use by default. Then it constructs an HTTPS request — for get pods, that's a GET to /api/v1/namespaces/default/pods — and sends it to the API server.
On the server side, the API server processes the request through a pipeline: Authentication (who are you? — checking certificates or tokens), Authorization (are you allowed to list pods? — checking RBAC policies), Admission Controllers (should this request be modified or rejected? — checking policies like PodSecurityAdmission), and finally the request reaches etcd (the key-value store that holds all cluster state). The response flows back through the same chain, kubectl receives the JSON, and formats it into the table or YAML you see in your terminal.
The most important kubectl commands for daily operations form a natural debugging workflow: kubectl get lists resources with summaries, kubectl describe gives deep detail including events (your first stop when something is wrong), kubectl logs shows container stdout/stderr output, kubectl exec gives you a shell inside a container for live debugging, and kubectl apply -f creates or updates resources from YAML files (the declarative approach used in production). Beyond these, kubectl port-forward tunnels traffic for local testing, kubectl top shows live CPU/memory usage, and kubectl rollout manages deployment updates.
A productivity tip that separates experienced operators from beginners: set up shell aliases and completions. Typing kubectl get pods -n production dozens of times a day is wasteful. With alias k=kubectl, shell tab-completion, and a default namespace set via kubectl config set-context, you can operate 3x faster. For CKA/CKAD exams, this time savings is the difference between finishing and running out of time.
Code Example
# Check cluster connectivity and version kubectl cluster-info kubectl version --short # List Pods with extra detail (node, IP) kubectl get pods -o wide # List ALL resource types across ALL namespaces kubectl get all --all-namespaces # Describe a Pod — shows events, conditions, probe status kubectl describe pod payments-api-7d5f8b6c4-x2k9m # Apply a manifest declaratively (creates or updates) kubectl apply -f deployment.yaml # Stream real-time logs from a running Pod kubectl logs -f payments-api-7d5f8b6c4-x2k9m # Logs from a specific container in a multi-container Pod kubectl logs payments-api-7d5f8b6c4-x2k9m -c log-shipper # Get an interactive shell inside a container kubectl exec -it payments-api-7d5f8b6c4-x2k9m -- /bin/sh # Port-forward for local debugging kubectl port-forward pod/payments-api-7d5f8b6c4-x2k9m 8080:8080 # Check resource usage (requires metrics-server) kubectl top pods --sort-by=memory kubectl top nodes # Preview what would change before applying kubectl diff -f updated-deployment.yaml # Essential aliases — add to your ~/.zshrc or ~/.bashrc # alias k=kubectl # alias kgp='kubectl get pods' # alias kgs='kubectl get svc' # alias kdp='kubectl describe pod' # alias kl='kubectl logs -f' # source <(kubectl completion zsh) # tab completion
Interview Tip
A junior engineer typically lists kubectl commands they've memorized. For a senior role, show understanding of what happens beneath the surface — that kubectl is an HTTP client sending REST requests to the API server, authenticated via kubeconfig. Describe the request flow: kubeconfig → HTTPS to API server → Authentication → RBAC Authorization → Admission Controllers → etcd. When asked about debugging, demonstrate a systematic approach rather than random commands: check pod status (get), read events (describe), check app output (logs), inspect live container (exec). This workflow shows operational maturity and is exactly what SREs do during real incidents.
◈ Architecture Diagram
┌──────────┐ ┌──────────┐
│ kubectl │───── HTTPS ──────►│ API │
│ │ GET /api/v1/pods │ Server │
│ │◄── JSON response ─│ │
└────┬─────┘ └────┬─────┘
│ │
│ reads │ auth → RBAC
▼ │ → admission
┌──────────┐ ▼
│~/.kube/ │ ┌──────────┐
│ config │ │ etcd │
│ │ │ (state) │
│ - server │ └──────────┘
│ - cert │
│ - ns │ ┌──────────────────┐
└──────────┘ │ Controllers │
├────────┬─────────┤
│Scheduler│CtrlMgr │
└────────┴─────────┘💬 Comments
Quick Answer
ConfigMaps store non-sensitive configuration data (feature flags, database hostnames, config files) as key-value pairs. Secrets store sensitive data (passwords, API keys, TLS certificates) with base64 encoding. Both inject configuration into Pods without baking it into container images.
Detailed Answer
Think of this like moving into a new apartment. The landlord gives you two things: a welcome packet (ConfigMap) with the WiFi name, trash pickup schedule, and building rules — information that's useful but not sensitive, and you'd stick it on the fridge for anyone to read. They also give you a sealed envelope (Secret) with the front door keypad code, mailbox key number, and garage PIN — information you'd keep in a locked drawer because if someone else got it, they could access your stuff.
ConfigMaps hold non-sensitive configuration that your application needs: database hostnames, feature flag values, logging levels, or even entire configuration files like nginx.conf. They're stored in etcd as plain text, visible to anyone with RBAC read access, and show up in full when you run kubectl describe configmap. You inject them into Pods either as environment variables (good for simple key-value pairs) or as mounted files in a volume (good for entire config files that your application reads from disk).
Secrets are structurally almost identical to ConfigMaps — they're also key-value pairs stored in etcd. The critical differences: Secret values are base64 encoded (NOT encrypted — base64 is just encoding, anyone can decode it with echo <value> | base64 -d), they're excluded from kubectl get output by default to prevent accidental exposure in terminal logs, and Kubernetes can be configured to encrypt them at rest in etcd using an EncryptionConfiguration. When mounted into a Pod, Secrets are stored in a tmpfs (in-memory filesystem) so they're never written to the node's physical disk.
Both ConfigMaps and Secrets can be updated without restarting Pods — but only when mounted as volumes. Kubernetes automatically propagates changes to mounted volumes within about 60 seconds (the kubelet sync period). However, environment variables from ConfigMaps or Secrets are set at Pod startup and NEVER updated — you must restart the Pod to pick up changes. This is a common source of confusion in production.
The biggest security gotcha with Secrets: by default, they are NOT encrypted in etcd — they're only base64 encoded, which provides zero security. Anyone with etcd access can read all your Secrets in plain text. For production clusters, you must either enable etcd encryption at rest (using an EncryptionConfiguration), or better yet, use an external secrets manager (AWS Secrets Manager, HashiCorp Vault, or the External Secrets Operator) that stores the actual secret values outside the cluster and injects them at runtime.
Code Example
# Create a ConfigMap from literal values
kubectl create configmap app-config \
--from-literal=DB_HOST=postgres.production.svc \
--from-literal=LOG_LEVEL=info \
--from-literal=FEATURE_DARK_MODE=true
# Create a ConfigMap from a config file
kubectl create configmap nginx-conf \
--from-file=nginx.conf=/path/to/nginx.conf
# Create a Secret from literal values
kubectl create secret generic db-credentials \
--from-literal=DB_USER=payments_svc \
--from-literal=DB_PASS=xK9#mP2vL8qR
# Pod using both ConfigMap and Secret
apiVersion: v1
kind: Pod
metadata:
name: payments-api
spec:
containers:
- name: api
image: payments-api:3.1
env:
- name: DB_HOST # From ConfigMap as env var
valueFrom:
configMapKeyRef:
name: app-config
key: DB_HOST
- name: DB_PASSWORD # From Secret as env var
valueFrom:
secretKeyRef:
name: db-credentials
key: DB_PASS
volumeMounts:
- name: config-vol # Mount entire ConfigMap as files
mountPath: /etc/app-config
readOnly: true
volumes:
- name: config-vol
configMap:
name: app-config
# Decode a Secret value (base64 is NOT encryption)
kubectl get secret db-credentials -o jsonpath='{.data.DB_PASS}' | base64 -dInterview Tip
A junior engineer typically says 'ConfigMaps are for config, Secrets are for passwords.' For a senior role, you should explain that Secrets are NOT encrypted by default — they're only base64 encoded, which is NOT security. The critical insight is: without etcd encryption at rest or an external secrets manager (Vault, AWS Secrets Manager, External Secrets Operator), your 'Secrets' are stored in plain text in etcd. Also explain the update propagation difference: volume-mounted ConfigMaps auto-update within ~60 seconds, but environment variables are frozen at Pod startup. This nuance shows you've actually debugged config update issues in production.
◈ Architecture Diagram
┌──────────────── Pod ────────────────────┐
│ │
│ ┌──────────────────────────────────┐ │
│ │ Container │ │
│ │ │ │
│ │ env: │ │
│ │ DB_HOST=postgres.prod.svc │ │
│ │ DB_PASS=xK9#mP2vL8qR │ │
│ │ │ │
│ │ /etc/app-config/ │ │
│ │ DB_HOST LOG_LEVEL FEATURE │ │
│ └──────────────────────────────────┘ │
│ ▲ ▲ │
│ │ env vars │ volume │
└───────┼────────────────────┼────────────┘
│ │
┌──────┴─────┐ ┌────────┴────────┐
│ Secret │ │ ConfigMap │
│ db-creds │ │ app-config │
│ │ │ │
│ base64 │ │ plain text │
│ (tmpfs) │ │ (auto-updates) │
└────────────┘ └─────────────────┘
│
▼
┌────────────┐
│ etcd │ ← NOT encrypted by default!
└────────────┘💬 Comments
Quick Answer
A ReplicaSet ensures a specified number of identical Pod replicas are running at any time. A Deployment is a higher-level controller that manages ReplicaSets and adds rolling updates, rollback history, and declarative update strategies. You almost never create ReplicaSets directly — you use Deployments.
Detailed Answer
Think of it like a factory floor. A ReplicaSet is a shift supervisor whose only job is headcount — make sure exactly 5 workers are on the assembly line at all times. If one leaves, immediately bring in a replacement. If there are too many, send some home. That's all the supervisor does: count and maintain.
A Deployment is the factory manager above the supervisor. The manager handles everything the supervisor does (maintaining headcount) but also manages shift changes (rolling updates): when the factory switches from building Product v1 to Product v2, the manager coordinates bringing in v2-trained workers while gradually sending v1 workers home, ensuring the production line never stops. If the new Product v2 has defects, the manager can instantly recall the v1 workers (rollback). The manager keeps records of every shift change (revision history) so they can go back to any previous version.
Technically, a ReplicaSet has one job: maintain the desired count of Pods matching a label selector. If a Pod dies (node failure, OOMKill, manual deletion), the ReplicaSet controller notices within seconds (via the API server's watch mechanism) and creates a replacement. If you scale from 3 to 5 replicas, it creates 2 new Pods. If you scale from 5 to 3, it terminates 2 Pods. But a ReplicaSet has no concept of updates — if you change the Pod template (new image version), existing Pods are NOT affected. Only newly created Pods use the updated template. You'd have to manually delete the old Pods to trigger replacements with the new template.
A Deployment solves this by managing ReplicaSets. When you update the Pod template in a Deployment (say, change the image from v1 to v2), it creates a brand new ReplicaSet for v2 and orchestrates the transition: scaling up v2 while scaling down v1 according to the strategy (RollingUpdate or Recreate). The old v1 ReplicaSet isn't deleted — it's kept at 0 replicas so kubectl rollout undo can scale it back up instantly. This is why kubectl get replicaset often shows multiple ReplicaSets for one Deployment — the old ones are rollback snapshots.
The key takeaway: you should almost never create a ReplicaSet directly. Always use a Deployment instead. The only exception is if you're building a custom controller that needs fine-grained control over Pod lifecycle — and even then, you should strongly consider using a Deployment with a custom strategy. In CKA/CKAD exams, if a question asks you to create a ReplicaSet, do it, but in any real-world answer or production discussion, always recommend Deployments.
Code Example
# ReplicaSet — you rarely create these directly
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: payments-api-v1
spec:
replicas: 3 # Maintain exactly 3 Pods
selector:
matchLabels:
app: payments-api
version: v1
template:
metadata:
labels:
app: payments-api
version: v1
spec:
containers:
- name: api
image: payments-api:1.0 # Changing this does NOT update running Pods
# Deployment — what you should actually use
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
spec:
replicas: 3
strategy:
type: RollingUpdate # Gradually replace old with new
selector:
matchLabels:
app: payments-api
template:
metadata:
labels:
app: payments-api
spec:
containers:
- name: api
image: payments-api:1.0 # Changing this triggers rolling update
# See the ReplicaSets a Deployment manages
kubectl get replicaset -l app=payments-api
# NAME DESIRED CURRENT READY
# payments-api-7d5f8b6c4 3 3 3 ← current
# payments-api-6b4a9c1e2 0 0 0 ← old (for rollback)Interview Tip
A junior engineer typically says both 'keep Pods running' without explaining the difference. For a senior role, articulate the exact boundary: a ReplicaSet only maintains Pod count with a fixed template — changing the template doesn't affect running Pods. A Deployment wraps ReplicaSets to add versioned updates: each template change creates a new ReplicaSet, and the Deployment orchestrates the transition. The proof that you understand this: explain why `kubectl get rs` shows multiple ReplicaSets for one Deployment (old revisions at 0 replicas). In practice, the only reason to know ReplicaSet internals is debugging — when a Deployment rollout stalls, you inspect the ReplicaSets to see which one is stuck and why.
◈ Architecture Diagram
Deployment ┌─────────────────────────────────────┐ │ manages ReplicaSets + rollouts │ │ │ │ ┌── RS v2 (current) ────────────┐ │ │ │ replicas: 3 │ │ │ │ ┌──────┐ ┌──────┐ ┌──────┐ │ │ │ │ │Pod │ │Pod │ │Pod │ │ │ │ │ │v2 │ │v2 │ │v2 │ │ │ │ │ └──────┘ └──────┘ └──────┘ │ │ │ └──────────────────────────────┘ │ │ │ │ ┌── RS v1 (rollback) ───────────┐ │ │ │ replicas: 0 (no Pods) │ │ │ │ template preserved for undo │ │ │ └──────────────────────────────┘ │ └─────────────────────────────────────┘ ReplicaSet alone (no Deployment): ┌── RS ────────────────────────────┐ │ replicas: 3 │ │ ┌──────┐ ┌──────┐ ┌──────┐ │ │ │Pod │ │Pod │ │Pod │ │ │ └──────┘ └──────┘ └──────┘ │ │ ✗ no rolling updates │ │ ✗ no rollback history │ └──────────────────────────────────┘
💬 Comments
Quick Answer
Labels are key-value pairs attached to Kubernetes objects for identification and grouping. Selectors are queries that filter objects by their labels. Together they form the core mechanism that connects Services to Pods, Deployments to ReplicaSets, and NetworkPolicies to targets — virtually every relationship in Kubernetes is label-based.
Detailed Answer
Think of labels like the colored sticky tags you'd put on boxes when moving to a new house. You might tag boxes with 'kitchen' or 'bedroom' to indicate which room they go to, 'fragile' to indicate handling, and 'priority: high' for boxes to unpack first. When the movers arrive, you tell them 'bring me all boxes tagged kitchen AND priority high' — that's a selector. The tags (labels) are on the boxes, and the query (selector) finds the right ones.
In Kubernetes, labels are the glue that connects everything together. A Label is simply a key-value pair like app=payments-api, tier=backend, env=production, or version=v2.1. You attach them to any Kubernetes object: Pods, Services, Deployments, Nodes, even Namespaces. Labels carry no meaning to Kubernetes itself — it doesn't know or care that 'env=production' means something important to your team. They're purely for your organizational use.
Selectors are what make labels powerful. When you create a Service with selector: app=payments-api, the Service watches for ALL Pods with that label and routes traffic to them. When a Deployment has matchLabels: app=payments-api, it manages all ReplicaSets with that label. When a NetworkPolicy has podSelector: tier=backend, it applies firewall rules to all Pods with that label. This loose coupling is a deliberate design choice — it means you can change which Pods a Service routes to simply by changing labels, without modifying the Service itself.
There are two types of selectors: equality-based (app=payments, tier!=frontend) and set-based (env in (production, staging), version notin (deprecated)). Set-based selectors are more powerful and used in newer API resources like Deployments and Jobs, while older resources like Services only support equality-based selectors. You can also use labels with kubectl: kubectl get pods -l app=payments,tier=backend instantly filters to just the Pods you care about across any namespace.
The most common mistake with labels is inconsistency. If one team labels their Pods app=payments-api and another labels theirs application=payments-api, the Service selector won't find the second team's Pods. Production clusters should enforce a labeling standard: at minimum, app (application name), env (environment), version (release version), and team (owning team). Some organizations use admission webhooks to reject any Pod that's missing required labels. Without consistent labeling, operating a cluster at scale becomes chaotic — you can't query, monitor, or apply policies to resources you can't reliably select.
Code Example
# Add labels when creating a Pod
kubectl run payments-api --image=payments:2.1 \
--labels="app=payments-api,tier=backend,env=production"
# Add or update a label on an existing resource
kubectl label pod payments-api version=v2.1
# Remove a label (note the minus sign)
kubectl label pod payments-api version-
# List Pods filtered by label selector
kubectl get pods -l app=payments-api,tier=backend
# Set-based selector: Pods in production OR staging
kubectl get pods -l 'env in (production, staging)'
# Service using labels to find its backend Pods
apiVersion: v1
kind: Service
metadata:
name: payments-api
spec:
selector: # Routes to Pods matching BOTH labels
app: payments-api
tier: backend
ports:
- port: 80
targetPort: 8080
# Deployment managing Pods via labels
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
labels: # Labels on the Deployment itself
app: payments-api
spec:
selector:
matchLabels: # Must match template labels
app: payments-api
template:
metadata:
labels: # Labels on the Pods it creates
app: payments-api
tier: backend
version: v2.1
spec:
containers:
- name: api
image: payments-api:2.1
# Show all labels on all Pods
kubectl get pods --show-labelsInterview Tip
A junior engineer typically describes labels as 'metadata tags for organization.' For a senior role, you should explain that labels are the primary relationship mechanism in Kubernetes — they're how Services find Pods, how Deployments manage ReplicaSets, how NetworkPolicies target workloads, and how node affinity works. The production insight is about label governance: without an enforced labeling convention across teams, you lose the ability to query, monitor, and apply policies at scale. Mention that some organizations use OPA Gatekeeper or Kyverno admission webhooks to enforce required labels on all workloads. This shows you've thought about platform-level concerns, not just individual deployments.
◈ Architecture Diagram
Labels connect everything:
┌──────────┐ selector: ┌──────────┐
│ Service │ app=payments ──►│ Pod │
│ │ │ app: │
│payments │ │ payments │
│-api │ │ tier: │
└──────────┘ │ backend │
└──────────┘
┌──────────┐ matchLabels: ┌──────────┐
│Deployment│ app=payments ──►│ReplicaSet│
└──────────┘ └─────┬────┘
│
matchLabels:
app=payments
│
┌────────┼────────┐
▼ ▼ ▼
┌──────┐┌──────┐┌──────┐
│ Pod ││ Pod ││ Pod │
│app: ││app: ││app: │
│pay.. ││pay.. ││pay.. │
└──────┘└──────┘└──────┘
┌──────────┐ podSelector: ┌──────────┐
│ Network │ tier=backend ──►│ Pod │
│ Policy │ │ tier: │
└──────────┘ │ backend │
└──────────┘💬 Comments
Quick Answer
A DaemonSet ensures that exactly one copy of a Pod runs on every node (or a selected subset of nodes) in the cluster. Use it for node-level infrastructure like log collectors, monitoring agents, and network plugins that must run on every machine.
Detailed Answer
Think of a DaemonSet like the janitorial staff in an office building. You don't want 3 janitors all cleaning the same floor — you want exactly one janitor per floor, and whenever a new floor is added to the building, a janitor is automatically assigned to it. If a janitor leaves, a replacement is immediately placed on that specific floor. The key difference from a regular team (Deployment) is that the distribution is per-floor, not a pool of workers that get assigned randomly.
A DaemonSet guarantees that a copy of a specific Pod runs on every node (or every node matching a nodeSelector). When a new node joins the cluster, the DaemonSet controller automatically schedules a Pod on it. When a node is removed, the Pod on that node is garbage collected. You never have to manually think about placement — the DaemonSet handles it.
The most common DaemonSet use cases are all infrastructure-level concerns: log collection agents (Fluentd or Fluent Bit that reads /var/log from every node and ships to Elasticsearch), monitoring agents (Prometheus node-exporter that exposes node-level CPU, memory, and disk metrics), network plugins (Calico or Cilium that set up the Pod network on each node), and storage drivers (CSI node plugins that enable Pods on each node to mount cloud volumes). These are all cases where you need exactly one agent per node — not more, not fewer.
The DaemonSet controller bypasses the normal scheduler by default. While a Deployment creates Pods and the scheduler decides which node they land on, DaemonSet Pods are pre-assigned to specific nodes by the DaemonSet controller setting the nodeName field. This means they run even on nodes that are tainted or unschedulable (like control plane nodes) if you add the right tolerations. This is intentional — you usually want your monitoring agent running on control plane nodes too.
A common question is 'why not just use a Deployment with replicas equal to the node count?' Three reasons: first, a DaemonSet automatically adapts when nodes are added or removed — a Deployment with a fixed replica count would need manual adjustment. Second, a DaemonSet guarantees one Pod per node — a Deployment might schedule two Pods on the same node and zero on another. Third, DaemonSets support rolling updates with maxUnavailable per node, so you can update the log collector on every node without losing log coverage anywhere.
Code Example
# DaemonSet for log collection on every node
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: log-collector
namespace: monitoring
spec:
selector:
matchLabels:
app: log-collector
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # Update one node at a time
template:
metadata:
labels:
app: log-collector
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
effect: NoSchedule # Also run on control plane nodes
containers:
- name: fluentbit
image: fluent/fluent-bit:2.2
resources:
requests:
cpu: 100m # Lightweight — runs on every node
memory: 128Mi
limits:
memory: 256Mi
volumeMounts:
- name: varlog # Read logs from the node
mountPath: /var/log
readOnly: true
- name: containers # Read container logs
mountPath: /var/lib/docker/containers
readOnly: true
volumes:
- name: varlog
hostPath:
path: /var/log # Node's actual log directory
- name: containers
hostPath:
path: /var/lib/docker/containers
# Check DaemonSet status — DESIRED should equal node count
kubectl get daemonset -n monitoring
# NAME DESIRED CURRENT READY NODE SELECTOR
# log-collector 5 5 5 <none>
# Run DaemonSet only on nodes with SSD storage
# Add nodeSelector:
# disk: ssdInterview Tip
A junior engineer typically says 'a DaemonSet runs a Pod on every node.' For a senior role, explain WHY this is different from a Deployment: guaranteed per-node placement (not pooled scheduling), automatic adaptation to cluster scaling, and the ability to tolerate taints so infrastructure agents run even on control plane nodes. The key production insight is that DaemonSets are for node-level concerns, not application workloads. If someone suggests using a DaemonSet for an API server, that's a red flag. Mention common real-world DaemonSets by name (Fluent Bit, Prometheus node-exporter, Calico/Cilium, CSI node drivers) to show you've operated production clusters with real observability and networking stacks.
◈ Architecture Diagram
Deployment (pooled): DaemonSet (per-node): ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ │Node 1│ │Node 2│ │Node 1│ │Node 2│ │ │ │ │ │ │ │ │ │ Pod │ │ Pod │ │ Pod │ │ Pod │ │ Pod │ │ │ │ │ │ │ └──────┘ └──────┘ └──────┘ └──────┘ ┌──────┐ ┌──────┐ │Node 3│ ← 0 Pods! │Node 3│ │ │ Scheduler chose │ │ │ │ nodes 1 & 2 │ Pod │ ← guaranteed └──────┘ └──────┘ Deployment: 3 replicas DaemonSet: 1 per node placed randomly placed everywhere New node added: ┌──────┐ ┌──────┐ │Node 4│ ← nothing happens │Node 4│ ← Pod auto │ │ (still 3 replicas) │ Pod │ created └──────┘ └──────┘
💬 Comments
Quick Answer
Volumes attach storage to Pods but are tied to the Pod lifecycle. PersistentVolumes (PVs) are cluster-level storage resources that exist independently of Pods. PersistentVolumeClaims (PVCs) are requests for storage that bind to PVs — this three-layer abstraction separates storage provisioning (admin) from storage consumption (developer).
Detailed Answer
Think of it like renting storage space. A basic Volume is like a temporary locker at the gym — it exists while you're working out (Pod is running) and gets emptied when you leave (Pod is deleted). An emptyDir volume is exactly this: scratch space that dies with the Pod. But what if you need a permanent storage unit?
That's where PersistentVolumes and PersistentVolumeClaims come in. A PersistentVolume (PV) is like a storage unit in a warehouse that exists independently — the warehouse manager (cluster admin) provisions it, and it's available whether or not anyone is currently using it. It might be backed by an AWS EBS disk, a Google Persistent Disk, or an NFS share. The PV has a specific size, access mode (ReadWriteOnce, ReadOnlyMany, ReadWriteMany), and reclaim policy (what happens when nobody needs it anymore).
A PersistentVolumeClaim (PVC) is a tenant's request: 'I need 10Gi of fast SSD storage with read-write access.' Kubernetes matches this claim against available PVs and binds them together. The developer creating the PVC doesn't need to know whether the storage is EBS, GCE PD, or NFS — they just ask for what they need. This separation of concerns is the entire point: admins manage physical storage, developers request logical storage.
In modern clusters, you rarely create PVs manually. Instead, you use StorageClasses, which enable dynamic provisioning. A StorageClass defines a 'recipe' for creating storage: the provisioner (like ebs.csi.aws.com), the disk type (gp3, io2), and the reclaim policy. When a PVC references a StorageClass, Kubernetes automatically creates a matching PV on-demand. This is how EKS, GKE, and AKS work — you create a PVC, and within seconds a cloud disk is provisioned and attached to your node.
The most critical gotcha with persistent storage: by default, a PV's reclaimPolicy is 'Delete', meaning when you delete the PVC, the underlying cloud disk is ALSO deleted — along with all your data. For any database or stateful application, you must set reclaimPolicy to 'Retain'. Additionally, ReadWriteOnce volumes can only be attached to a single node at a time. If your Pod gets rescheduled to a different node, it must wait for the volume to detach from the old node and attach to the new one — this can take 1-3 minutes on AWS EBS, during which your application is down. This is why StatefulSets (not Deployments) are used for databases.
Code Example
# StorageClass for dynamic provisioning on AWS
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast-ssd
provisioner: ebs.csi.aws.com # AWS EBS CSI driver
parameters:
type: gp3 # General purpose SSD
encrypted: "true" # Encrypt at rest
reclaimPolicy: Retain # Keep disk when PVC deleted
volumeBindingMode: WaitForFirstConsumer # Don't provision until Pod scheduled
# PersistentVolumeClaim — developer requests storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
namespace: production
spec:
accessModes:
- ReadWriteOnce # Single node read-write
storageClassName: fast-ssd # Use the StorageClass above
resources:
requests:
storage: 50Gi # Request 50 GiB
# Pod using the PVC
apiVersion: v1
kind: Pod
metadata:
name: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
volumeMounts:
- name: data # Mount the PVC at the data directory
mountPath: /var/lib/postgresql/data
volumes:
- name: data
persistentVolumeClaim:
claimName: postgres-data # Reference the PVC by name
# Check PV/PVC binding status
kubectl get pv,pvc -n production
# NAME CAPACITY ACCESS MODES STATUS CLAIM
# pv/pvc-abc123 50Gi RWO Bound production/postgres-dataInterview Tip
A junior engineer typically describes PV/PVC without explaining why the abstraction exists. For a senior role, articulate the separation of concerns: admins manage StorageClasses and physical storage, developers request storage via PVCs without caring about the backend. The production-critical insights are: (1) reclaimPolicy must be Retain for any data you care about — Delete is the default and destroys data, (2) ReadWriteOnce volumes cause downtime during Pod rescheduling because EBS/PD detach+attach takes 1-3 minutes, and (3) WaitForFirstConsumer binding mode prevents the volume from being provisioned in a different AZ than the Pod. Mention these three gotchas and you prove real operational experience with stateful workloads.
◈ Architecture Diagram
Developer Admin / Cloud
┌──────────┐ ┌──────────────┐
│ PVC │ │ StorageClass │
│ │ binds to │ fast-ssd │
│ 50Gi SSD │───────────────►│ gp3 / EBS │
│ RWO │ │ Retain │
└────┬─────┘ └──────┬───────┘
│ │
│ mounts │ provisions
▼ ▼
┌──────────┐ ┌──────────────┐
│ Pod │ │ PV │
│ │ │ 50Gi EBS │
│ postgres │◄───────────────│ vol-abc123 │
│ :5432 │ attached │ │
└──────────┘ └──────────────┘
Lifecycle:
PVC created → StorageClass provisions PV
→ PV bound to PVC → Pod mounts PVC
→ Pod deleted → PVC still exists
→ PVC deleted → reclaimPolicy decides:
Delete: PV + disk destroyed ✗
Retain: PV + disk preserved ✓💬 Comments
Quick Answer
A liveness probe detects when a container is stuck or deadlocked and restarts it. A readiness probe detects when a container can't serve traffic yet and removes it from Service endpoints. Without probes, Kubernetes has no way to know if your application is actually healthy — it only knows if the process is running.
Detailed Answer
Imagine you manage a call center. A liveness check is like peeking into an agent's booth to see if they're alive and conscious — if they've fainted at their desk (deadlock, infinite loop), you need to wake them up or replace them. A readiness check is different: the agent might be alive and well but currently in training, updating their system, or on a personal call — they're not ready to take customer calls yet. You wouldn't route a customer to them until they signal they're available.
In Kubernetes, the kubelet on each node performs these health checks on every container. A liveness probe answers 'is this container stuck and needs a restart?' If the liveness probe fails (returns non-200 HTTP, exit code non-zero, or TCP connection refused) for the configured number of consecutive failures (failureThreshold), the kubelet kills and restarts the container. This handles scenarios where your application is running but broken — a deadlocked Java thread, an unresponsive event loop in Node.js, or a corrupted internal state.
A readiness probe answers 'can this container handle traffic right now?' If the readiness probe fails, the kubelet tells the Endpoints controller to remove this Pod's IP from all Service endpoints. Traffic stops flowing to this Pod, but it is NOT restarted. This is crucial during startup (your Spring Boot app needs 30 seconds to load), during dependency outages (your cache is temporarily down so you can't serve requests), and during graceful shutdown. When the readiness probe passes again, the Pod is added back to Service endpoints.
There's also a third probe type: startupProbe (added in Kubernetes 1.18). It runs only during container startup and disables the liveness and readiness probes until it succeeds. This is designed for slow-starting applications — a Java app that needs 2 minutes to load. Without a startup probe, you'd have to set the liveness probe's initialDelaySeconds very high, which means Kubernetes would be slow to detect a crash after startup. With a startup probe, you get both: patience during startup and fast failure detection afterward.
The probes support three mechanisms: httpGet (hit an endpoint, check for 200-399 status), exec (run a command inside the container, check for exit code 0), and tcpSocket (try to open a TCP connection to a port). For web applications, httpGet is almost always the right choice. The most dangerous mistake is making a readiness probe check downstream dependencies: if your readiness probe checks 'can I reach the database?' and the database has a brief hiccup, ALL your Pods fail their readiness probes simultaneously, the Service routes to zero Pods, and you have a complete outage — even though your application could have served cached responses or returned a useful error message.
Code Example
# Production Pod with all three probe types
apiVersion: v1
kind: Pod
metadata:
name: payments-api
spec:
containers:
- name: api
image: payments-api:3.1
ports:
- containerPort: 8080
startupProbe: # Runs ONLY during startup
httpGet:
path: /healthz # Lightweight health endpoint
port: 8080
failureThreshold: 30 # 30 × 10s = 5 min max startup time
periodSeconds: 10
livenessProbe: # Am I stuck? Restart me.
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0 # Starts after startupProbe succeeds
periodSeconds: 15 # Check every 15 seconds
failureThreshold: 3 # 3 failures = restart
timeoutSeconds: 5 # Timeout per check
readinessProbe: # Can I serve traffic? Route to me.
httpGet:
path: /ready # Separate endpoint from liveness!
port: 8080
periodSeconds: 10 # Check every 10 seconds
failureThreshold: 3 # 3 failures = remove from Service
successThreshold: 1 # 1 success = add back to Service
# Common MISTAKE — readiness checks a dependency:
# readinessProbe:
# exec:
# command: ["curl", "http://database:5432"] ← DON'T DO THIS
# If database hiccups, ALL Pods removed from Service = full outage
# CORRECT — readiness checks only YOUR application:
# GET /ready → checks: can I accept HTTP requests?
# - web server listening? yes
# - app initialized? yes
# - return 200Interview Tip
A junior engineer typically says 'liveness restarts the container, readiness removes it from the Service.' For a senior role, you should mention three things: (1) the difference between the /healthz endpoint (liveness — am I stuck?) and /ready endpoint (readiness — can I serve?), (2) why readiness probes must NEVER check downstream dependencies (it causes cascading outages), and (3) when to use startupProbe for slow-starting applications instead of setting a huge initialDelaySeconds on the liveness probe. If you can explain a scenario where misconfigured readiness probes caused a production outage, you prove real incident experience. This question is a favorite in SRE interviews because probe misconfiguration is one of the top causes of Kubernetes incidents.
◈ Architecture Diagram
kubelet checks every container:
┌─────────┐ startupProbe
│ kubelet │─────────────────────────┐
└────┬────┘ │
│ ▼
│ ┌───────────────────┐
│ │ Container starting │
│ │ │
│ │ startupProbe runs │
│ │ until success or │
│ │ failureThreshold │
│ └─────────┬─────────┘
│ │ success
│ ▼
│ livenessProbe ┌─────────────────┐
├───────────────────►│ Container ready │
│ every 15s │ │
│ │ readinessProbe │
│ readinessProbe │ every 10s │
├───────────────────►│ │
│ every 10s └────────┬────────┘
│ │
│ ┌────────┴────────┐
│ │ │
│ readiness readiness
│ passes fails
│ │ │
│ ▼ ▼
│ ┌──────────┐ ┌──────────┐
│ │ Added to │ │ Removed │
│ │ Service │ │ from Svc │
│ │ Endpoints│ │ Endpoints│
│ └──────────┘ └──────────┘
│
│ liveness fails 3x
▼
Container restarted💬 Comments
Quick Answer
The control plane is the brain of the cluster: the API Server is the single point of communication, etcd stores all cluster state, the Scheduler assigns Pods to nodes, and the Controller Manager runs reconciliation loops that maintain desired state. On worker nodes, the kubelet manages Pods and kube-proxy handles networking.
Detailed Answer
Think of a Kubernetes cluster like an airport. The control plane is the airport operations center — the people and systems that coordinate everything. The API server is the main radio tower: every communication between pilots (kubectl), ground crew (kubelets), and air traffic control (controllers) goes through it. Nobody talks directly to anyone else. Etcd is the flight database — every flight plan, gate assignment, and schedule is recorded there, and if this database goes down, the airport can't function. The Scheduler is the gate assignment officer who decides which arriving plane goes to which gate based on gate size, availability, and terminal capacity. The Controller Manager is the operations team that constantly walks the airport comparing the schedule to reality: 'Gate 3 should have a plane — it doesn't — redirect one there.'
The API Server (kube-apiserver) is the only component that talks to etcd. When you run kubectl get pods, kubectl sends an HTTPS request to the API server, which authenticates you, checks your RBAC permissions, retrieves the data from etcd, and returns it. When you create a Deployment, the API server validates the manifest, stores it in etcd, and notifies watching controllers via its built-in watch mechanism. Every component in the cluster — scheduler, controllers, kubelets — communicates exclusively through the API server.
Etcd is a distributed key-value store that holds the entire state of the cluster: every Pod, Service, Secret, ConfigMap, and node registration. It uses the Raft consensus algorithm to maintain consistency across multiple replicas (production clusters run 3 or 5 etcd members). Etcd is the most critical component — if etcd data is lost and there's no backup, the cluster is unrecoverable. This is why etcd backup is a non-negotiable operational requirement.
The Scheduler (kube-scheduler) watches for newly created Pods that have no node assigned. For each unscheduled Pod, it runs a two-phase process: filtering (which nodes CAN run this Pod — enough CPU? right architecture? matching tolerations?) and scoring (which node is BEST — least loaded? closest to existing Pods? has the image cached?). The winning node is written to the Pod's spec.nodeName field, and the kubelet on that node picks it up.
The Controller Manager (kube-controller-manager) runs dozens of control loops, each responsible for one type of resource. The Deployment controller watches Deployments and manages ReplicaSets. The ReplicaSet controller watches ReplicaSets and manages Pods. The Node controller monitors node heartbeats and marks nodes as unhealthy. The Endpoints controller updates Service endpoints when Pods change. Each controller follows the same pattern: observe current state → compare to desired state → take action to converge. This reconciliation loop is the fundamental operating principle of Kubernetes.
On each worker node, the kubelet is the agent that actually runs Pods. It watches the API server for Pods assigned to its node, pulls container images, starts containers via the container runtime (containerd), and reports status back. Kube-proxy runs on every node and maintains network rules (iptables or IPVS) that implement Service routing. A common misconception is that kube-proxy proxies traffic — in iptables mode, it doesn't. It just programs the kernel's packet filtering rules and gets out of the way.
Code Example
# Check control plane component health kubectl get componentstatuses # View all control plane Pods (they run as static Pods on master nodes) kubectl get pods -n kube-system # Check API server endpoint kubectl cluster-info # Kubernetes control plane is running at https://10.0.0.1:6443 # View etcd members (if you have access to the master node) ETCDCTL_API=3 etcdctl member list \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key # Take an etcd backup (CRITICAL for disaster recovery) ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot.db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key # Check node status and kubelet version kubectl get nodes -o wide # View kubelet logs on a node (SSH required) journalctl -u kubelet -f # Check kube-proxy mode (iptables vs IPVS) kubectl get configmap kube-proxy -n kube-system -o yaml | grep mode
Interview Tip
A junior engineer typically lists the components. For a senior role, you should explain the communication pattern: EVERYTHING goes through the API server — no component talks directly to another. The scheduler doesn't tell the kubelet to run a Pod; it writes nodeName to the Pod object in the API server, and the kubelet watches for Pods assigned to it. This watch-based architecture is what makes Kubernetes scalable and reliable. The critical operational insights are: (1) etcd is the single point of failure — back it up or lose the cluster, (2) the scheduler uses filtering then scoring — understanding this explains why Pods sometimes land on unexpected nodes, and (3) controllers are reconciliation loops, not event handlers — they converge toward desired state even after partial failures. This proves you can troubleshoot scheduling problems and cluster failures, not just deploy apps.
◈ Architecture Diagram
┌──────────── Control Plane ──────────────┐
│ │
│ ┌──────────┐ watches ┌────────┐ │
│ │Scheduler │◄─────────────►│ API │ │
│ │ │ │ Server │ │
│ └──────────┘ ┌─────────►│ │ │
│ │ │ only │ │
│ ┌──────────┐ │ │component│ │
│ │Controller│────┘ │that │ │
│ │ Manager │ watches │talks to │ │
│ └──────────┘ │ etcd │ │
│ └───┬────┘ │
│ │ │
│ ┌─────▼─────┐ │
│ │ etcd │ │
│ │ (cluster │ │
│ │ state) │ │
│ └───────────┘ │
└─────────────────────────────────────────┘
│
API server
watches/updates
│
┌────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐┌──────────┐ ┌──────────┐
│ Node 1 ││ Node 2 │ │ Node 3 │
│ ││ │ │ │
│ kubelet ││ kubelet │ │ kubelet │
│ kube- ││ kube- │ │ kube- │
│ proxy ││ proxy │ │ proxy │
│ ││ │ │ │
│ Pod Pod ││ Pod Pod │ │ Pod Pod │
└──────────┘└──────────┘ └──────────┘💬 Comments
Quick Answer
The control plane consists of the API Server (REST frontend that validates all requests), etcd (distributed key-value store holding all cluster state), Scheduler (assigns Pods to nodes based on constraints), and Controller Manager (runs reconciliation loops that drive actual state toward desired state).
Detailed Answer
Think of the control plane like the management floor of a large warehouse. The API Server is the front desk receptionist who handles every single request coming in — whether it's a customer placing an order, a supervisor checking inventory, or a new employee asking for directions. Every interaction with the warehouse goes through this one desk, no exceptions. etcd is the filing cabinet behind the desk that holds the single source of truth: every order, every employee record, every inventory count. The Scheduler is the floor manager who decides which aisle worker handles which incoming package based on who has capacity. The Controller Manager is the quality inspector who constantly walks the floor comparing what should be happening (orders to fulfill) with what is actually happening (packages on shelves), and files corrective actions when they don't match.
The API Server (kube-apiserver) is the only component that talks directly to etcd. Every kubectl command, every internal component communication, and every webhook goes through the API Server as HTTPS REST calls. It performs authentication (who are you?), authorization via RBAC (are you allowed to do this?), admission control (should this request be modified or rejected?), and validation (is this YAML well-formed?) before persisting anything to etcd. It also serves the watch API, which lets other components subscribe to changes in real-time rather than polling — this is how the entire system stays reactive.
etcd is a distributed, strongly-consistent key-value store built on the Raft consensus algorithm. It stores every object in the cluster: every Pod spec, every Service definition, every Secret, every ConfigMap. In production, etcd runs as a 3 or 5 node cluster (always odd numbers for quorum) and is often the first component to cause cluster-wide outages when it becomes unhealthy. etcd performance directly determines API Server response time — slow disk I/O on etcd nodes is the number one silent killer of Kubernetes clusters. Production teams typically dedicate SSD-backed nodes exclusively for etcd and monitor fsync latency religiously.
The Scheduler (kube-scheduler) watches the API Server for newly created Pods that have no node assigned (spec.nodeName is empty). For each unscheduled Pod, it runs a two-phase algorithm: filtering (eliminate nodes that don't meet hard requirements like resource requests, nodeSelector, taints/tolerations, and affinity rules) and scoring (rank remaining nodes by soft preferences like spreading Pods across failure domains, preferring nodes with the image already cached, or balancing resource utilization). The highest-scoring node wins, and the Scheduler writes the node assignment back to the API Server.
The Controller Manager (kube-controller-manager) is actually dozens of separate control loops compiled into a single binary for simplicity. Each controller watches a specific resource type and reconciles actual state with desired state. The ReplicaSet controller ensures the right number of Pods exist. The Deployment controller manages ReplicaSets during rollouts. The Node controller detects when nodes go offline. The Endpoint controller populates Service endpoints. The Job controller manages batch workloads. If the Controller Manager crashes, no reconciliation happens — Pods keep running but nothing self-heals until it's back.
A critical production gotcha: many teams monitor Pod health but forget to monitor control plane health. If the API Server is overloaded (common with too many custom controllers or misconfigured HPA polling intervals), the entire cluster becomes unresponsive — you can't deploy, can't scale, can't even see what's broken. Production clusters should have dedicated monitoring for API Server request latency (apiserver_request_duration_seconds), etcd fsync duration (etcd_disk_wal_fsync_duration_seconds), and scheduler queue depth (scheduler_pending_pods).
Code Example
# Check control plane component health kubectl get componentstatuses kubectl get --raw='/healthz?verbose' # View control plane Pods (self-hosted clusters like kubeadm) kubectl get pods -n kube-system -l tier=control-plane # NAME READY STATUS # etcd-master-01 1/1 Running # kube-apiserver-master-01 1/1 Running # kube-controller-manager-master-01 1/1 Running # kube-scheduler-master-01 1/1 Running # Check API Server response time (latency issues?) kubectl get --raw='/metrics' | grep apiserver_request_duration # Verify etcd cluster health kubectl exec -n kube-system etcd-master-01 -- etcdctl \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ endpoint health # Check scheduler is making decisions kubectl get events --field-selector reason=Scheduled -A # View controller-manager logs for reconciliation errors kubectl logs -n kube-system kube-controller-manager-master-01 \ --tail=50 | grep -i error # Monitor API Server audit logs for troubleshooting # (configured via --audit-policy-file on API Server) kubectl logs -n kube-system kube-apiserver-master-01 \ | grep payments-api
Interview Tip
A junior engineer typically lists the four components by name without explaining how they interact. Interviewers at production-focused companies want you to explain the data flow: everything goes through the API Server, nothing talks to etcd directly except the API Server, and controllers use the watch mechanism (not polling) to react to changes. The killer detail is explaining that the Controller Manager runs dozens of independent reconciliation loops — each one is a separate goroutine watching its resource type. If you can name specific controllers (ReplicaSet controller, Endpoint controller, Node controller) and explain what each reconciles, you demonstrate that you've debugged real cluster issues by checking controller logs. Mention etcd's fsync latency as the top production concern and you'll stand out from candidates who only know the textbook definitions.
◈ Architecture Diagram
┌─────────────── Control Plane ──────────────────────────┐
│ │
│ ┌────────────────────────────────────────┐ │
│ │ kube-apiserver │ │
│ │ REST frontend + auth + admission │ │
│ └──────────┬────────────┬────────────────┘ │
│ │ │ │
│ watch │ │ read/write │
│ │ ▼ │
│ │ ┌──────────────┐ │
│ │ │ etcd │ │
│ │ │ key-value │ │
│ │ │ cluster state│ │
│ │ └──────────────┘ │
│ │ │
│ ┌────────┴──────────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────────────────┐ │
│ │kube-scheduler│ │ kube-controller-manager │ │
│ │ │ │ │ │
│ │ filter → │ │ ReplicaSet controller │ │
│ │ score → │ │ Deployment controller │ │
│ │ bind Pod to │ │ Node controller │ │
│ │ best node │ │ Endpoint controller │ │
│ └──────────────┘ └──────────────────────────┘ │
│ │
└────────────────────────────────────────────────────────┘
│
│ API Server communicates with
▼
┌─────── Worker Nodes ──────┐
│ kubelet │ kube-proxy │
└───────────────────────────┘💬 Comments
Quick Answer
Each worker node runs kubelet (agent that starts and monitors Pods), kube-proxy (manages networking rules for Service routing), and a container runtime (containerd or CRI-O that actually runs containers). The kubelet communicates with the API Server via HTTPS to receive Pod assignments and report node status.
Detailed Answer
Think of a worker node like a restaurant kitchen station. The kubelet is the line cook who receives tickets (Pod specs) from the head chef (API Server) and actually prepares the dishes (starts containers). The container runtime (containerd or CRI-O) is the set of pots, pans, and ovens that the cook uses — the actual tools that do the cooking. And kube-proxy is the waiter who knows which table ordered which dish, making sure the right plate gets to the right customer via the correct route. Without any one of these three, the kitchen cannot function.
The kubelet is the primary node agent. It registers the node with the API Server, then watches (via the API Server's watch mechanism) for PodSpecs assigned to its node. When a new Pod is scheduled to its node, the kubelet calls the container runtime through the Container Runtime Interface (CRI) to pull images and start containers. It then continuously monitors container health by executing liveness and readiness probes at configured intervals. If a liveness probe fails, the kubelet restarts the container. It reports Pod status and node conditions (memory pressure, disk pressure, PID pressure, network unavailable) back to the API Server every 10 seconds by default (the node-status-update-frequency). If the API Server doesn't receive heartbeats for 40 seconds (the node-monitor-grace-period), the node is marked NotReady.
The container runtime is the software that actually creates and runs containers using Linux kernel features like namespaces (isolation) and cgroups (resource limits). Kubernetes removed direct Docker support in v1.24 — it now requires a CRI-compatible runtime. The two production choices are containerd (lightweight, used by EKS, GKE, and most managed platforms) and CRI-O (purpose-built for Kubernetes, used by OpenShift). The kubelet communicates with the runtime via a Unix socket using the gRPC-based CRI protocol. The runtime handles image pulling, container lifecycle, and log management.
kube-proxy runs on every node and implements the Service abstraction. When you create a Service, kube-proxy watches the API Server for Service and Endpoint objects, then programs the node's networking stack to route traffic correctly. In the default iptables mode, it creates iptables rules that perform DNAT (destination NAT) to translate the virtual Service IP to a real Pod IP, with random selection for load balancing. In IPVS mode (better for clusters with thousands of Services), it uses the Linux kernel's IPVS load balancer which supports multiple algorithms (round-robin, least-connections, source-hash). kube-proxy does NOT proxy traffic through itself — it only configures networking rules; actual packets flow directly from source to destination Pod.
The communication between worker nodes and the control plane is strictly one-directional in terms of initiation: the kubelet always initiates connections to the API Server, never the reverse. This is a security design — worker nodes can be in untrusted networks and the API Server never needs to push data to them. The kubelet establishes a persistent watch connection to the API Server, which means it receives updates the instant they happen (Pod scheduled, Pod deleted, ConfigMap changed) without polling. For features like kubectl exec and kubectl logs, the API Server does establish a reverse connection to the kubelet's HTTPS endpoint (port 10250), which is why kubelet has its own TLS certificate.
A common production gotcha: if the container runtime's socket becomes unresponsive (containerd hung, disk full preventing image pulls), the kubelet cannot start new Pods or report accurate status. The node might show Ready because the kubelet process itself is fine, but Pods scheduled there will be stuck in ContainerCreating forever. Monitoring containerd/CRI-O process health separately from kubelet health is essential for catching this early.
Code Example
# Check node status and conditions kubectl get nodes -o wide kubectl describe node worker-node-01 # Look for: Conditions section (MemoryPressure, DiskPressure, PIDPressure) # View kubelet logs on the node (SSH required) sudo journalctl -u kubelet --since "10 minutes ago" | tail -50 # Check kubelet's view of Pods on this node kubectl get pods --field-selector spec.nodeName=worker-node-01 -A # Verify container runtime is healthy sudo crictl info # Runtime status sudo crictl ps # Running containers sudo crictl pods # Running Pod sandboxes # Check kube-proxy is programming iptables rules sudo iptables -t nat -L KUBE-SERVICES | head -20 # View kube-proxy mode and configuration kubectl get configmap kube-proxy -n kube-system -o yaml # Check if kubelet can reach the API Server kubectl get --raw='/api/v1/nodes/worker-node-01/proxy/healthz' # Debug networking by checking kube-proxy logs kubectl logs -n kube-system -l k8s-app=kube-proxy \ --tail=30 # Check node resource capacity vs allocatable kubectl describe node worker-node-01 | grep -A5 'Capacity\|Allocatable' # Capacity: # cpu: 8 # memory: 32Gi # Allocatable: ← what's available for Pods (after system reserved) # cpu: 7600m # memory: 30Gi
Interview Tip
A junior engineer typically names kubelet and kube-proxy without explaining their interaction model. Interviewers want you to articulate three key details: (1) kubelet initiates all communication to the API Server — it's a pull model via watch, not push; (2) kube-proxy doesn't proxy traffic through userspace — it only programs iptables/IPVS rules, so actual data flows kernel-to-kernel without extra hops; (3) the container runtime communicates with kubelet via the CRI gRPC protocol over a Unix socket. If asked about Docker removal, explain that Docker used dockershim as a CRI adapter, adding an unnecessary layer — containerd is what Docker used internally anyway, so removing Docker just eliminated the middleman. Mention the node heartbeat mechanism (Lease objects in kube-node-lease namespace) to show you understand how the control plane detects node failures.
◈ Architecture Diagram
┌────────────── Worker Node ──────────────────────────────┐
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ kubelet │ │
│ │ • Registers node with API Server │ │
│ │ • Watches for Pod assignments │ │
│ │ • Executes liveness/readiness probes │ │
│ │ • Reports node status every 10s │ │
│ └────────┬──────────────────────┬─────────────────┘ │
│ │ CRI (gRPC) │ HTTPS │
│ ▼ │ │
│ ┌─────────────────┐ │ │
│ │ Container Runtime│ │ │
│ │ (containerd) │ │ │
│ │ │ │ │
│ │ ┌────┐ ┌────┐ │ │ │
│ │ │Pod │ │Pod │ │ │ │
│ │ │ A │ │ B │ │ │ │
│ │ └────┘ └────┘ │ │ │
│ └──────────────────┘ │ │
│ │ │
│ ┌─────────────────┐ │ │
│ │ kube-proxy │ │ │
│ │ │ │ │
│ │ iptables/IPVS │ │ │
│ │ rules for │ │ │
│ │ Service routing │ │ │
│ └────────┬────────┘ │ │
│ │ │ │
└───────────┼──────────────────────┼──────────────────────┘
│ │
│ watch Services │ watch PodSpecs
│ & Endpoints │ report status
▼ ▼
┌──────────────────────────────────┐
│ kube-apiserver │
│ (Control Plane) │
└──────────────────────────────────┘💬 Comments
Quick Answer
kubectl sends the YAML to the API Server via HTTPS, which validates it, runs admission controllers, and stores it in etcd. The Deployment controller creates a ReplicaSet, the Scheduler assigns Pods to nodes, kubelets start containers, and kube-proxy programs routing rules so traffic can reach the new Pods.
Detailed Answer
Think of this like ordering furniture from an online store. You submit your order (kubectl apply) to the website (API Server), which checks your payment and address are valid. The order goes into the warehouse system (etcd). The logistics team (Scheduler) figures out which delivery truck and warehouse has capacity. The warehouse worker (kubelet) picks, packs, and ships your item. Finally, the GPS routing system (kube-proxy) updates so future deliveries know the new delivery route. Each step is handled by a different team, and they coordinate through the central order system, not by talking directly to each other.
Step 1 — kubectl reads your YAML file, validates it client-side (basic schema checking), then sends an HTTPS POST (for create) or PUT/PATCH (for update) request to the API Server endpoint. For a Deployment, that's POST to /apis/apps/v1/namespaces/default/deployments. The request includes your authentication credentials from ~/.kube/config.
Step 2 — The API Server processes the request through its pipeline: Authentication (verifies your identity via certificate, token, or OIDC), Authorization (checks RBAC policies — do you have 'create deployments' permission in this namespace?), Mutating Admission Webhooks (can modify the request — for example, injecting sidecar containers or setting default resource limits), Object Schema Validation (is the YAML structurally valid?), Validating Admission Webhooks (can reject but not modify — for example, enforcing that all Pods must have resource limits), and finally Persistence (writes the Deployment object to etcd). The API Server responds with the created object.
Step 3 — The Deployment controller (inside kube-controller-manager) receives a watch notification that a new Deployment exists. It creates a ReplicaSet object with the Pod template from the Deployment spec. The ReplicaSet controller then receives its own watch notification and creates the specified number of Pod objects (with spec.nodeName empty — they're unscheduled).
Step 4 — The Scheduler receives watch notifications about the new unscheduled Pods. For each Pod, it runs filtering (which nodes meet the Pod's requirements: enough CPU/memory, correct nodeSelector, tolerate any taints?) and scoring (which eligible node is the best fit: most balanced resources, already has the image cached, spreading across zones?). It then updates each Pod's spec.nodeName field via the API Server.
Step 5 — The kubelet on each assigned node receives a watch notification about the new Pod. It calls the container runtime (containerd) via CRI to pull the container image (if not cached) and start the containers. It then begins executing probes (startup probe first if configured, then liveness and readiness probes).
Step 6 — Once a Pod's containers are running and the readiness probe passes, the Endpoint controller adds the Pod's IP to the Service's Endpoints object. kube-proxy on every node picks up this change and updates iptables/IPVS rules so traffic to the Service ClusterIP gets routed to the new Pod.
A production-critical insight: this entire chain is asynchronous and event-driven. kubectl apply returns immediately after Step 2 — it doesn't wait for Pods to actually run. This is why kubectl rollout status exists: it watches the Deployment until all Pods are Ready. In CI/CD pipelines, always follow kubectl apply with a rollout status check, or you'll report deploys as successful before they actually complete (or fail).
Code Example
# The full lifecycle from apply to running Pod # Step 1: Apply the Deployment kubectl apply -f payments-api-deployment.yaml # deployment.apps/payments-api created # Step 2: Verify API Server accepted it kubectl get deployment payments-api # NAME READY UP-TO-DATE AVAILABLE # payments-api 0/3 3 0 ← Pods not ready yet # Step 3: Watch the ReplicaSet get created kubectl get replicaset -l app=payments-api # NAME DESIRED CURRENT READY # payments-api-7d5f8b6c4 3 3 0 # Step 4: See Scheduler assign Pods to nodes kubectl get events --field-selector reason=Scheduled # payments-api-7d5f8b6c4-x2k9m Scheduled Successfully assigned # default/payments-api-7d5f8b6c4-x2k9m to worker-02 # Step 5: Watch Pods transition through states kubectl get pods -w -l app=payments-api # NAME STATUS # payments-api-7d5f8b6c4-x2k9m Pending → scheduled # payments-api-7d5f8b6c4-x2k9m ContainerCreating → pulling image # payments-api-7d5f8b6c4-x2k9m Running → containers started # Step 6: Confirm readiness and endpoint registration kubectl get endpoints payments-api # NAME ENDPOINTS # payments-api 10.244.1.5:8080,10.244.2.3:8080,10.244.3.8:8080 # CRITICAL: Wait for rollout to actually complete in CI/CD kubectl rollout status deployment/payments-api --timeout=300s # deployment "payments-api" successfully rolled out # View the full event timeline for debugging kubectl describe deployment payments-api | grep -A20 Events
Interview Tip
A junior engineer typically says 'kubectl sends YAML to the cluster and Pods start.' Interviewers want you to walk through each component's role in sequence, showing you understand the asynchronous, event-driven architecture. The key insight is that no component directly calls another — everything communicates through the API Server's watch mechanism. Emphasize that kubectl apply returns after the API Server accepts the object, NOT after Pods are running. This is why CI/CD pipelines that only run `kubectl apply` without checking rollout status will falsely report success even when deployments fail. Mention admission webhooks (mutating and validating) because they show you understand real production clusters where tools like OPA Gatekeeper, Istio sidecar injection, and LimitRanger modify requests before they reach etcd.
◈ Architecture Diagram
┌──────────┐
│ kubectl │
│ apply -f │
└────┬─────┘
│ HTTPS POST
▼
┌──────────────────────────────────────────────┐
│ API Server Pipeline │
│ │
│ Auth → RBAC → Mutating Webhooks → Validate │
│ → Validating Webhooks → Persist │
└──────────────────┬───────────────────────────┘
│ write
▼
┌────────┐
│ etcd │
└────┬───┘
│ watch notifications
┌──────────┼──────────┐
▼ ▼ ▼
┌────────────┐┌─────────┐┌──────────┐
│ Deployment ││Scheduler││ Endpoint │
│ Controller ││ ││Controller│
│ ││ filter → ││ │
│creates ││ score → ││ adds Pod │
│ReplicaSet ││ bind ││ IPs to │
│ ││ ││ Service │
└─────┬──────┘└────┬────┘└──────────┘
│ │
│ creates │ assigns node
│ Pods │
▼ ▼
┌─────────────────────────┐
│ kubelet │
│ pull image → start │
│ container → run probes │
└─────────────────────────┘
│
▼ Pod Ready
┌─────────────────────────┐
│ kube-proxy │
│ updates iptables/IPVS │
│ → traffic can flow │
└─────────────────────────┘💬 Comments
Quick Answer
A node shows NotReady when the kubelet stops sending heartbeats to the API Server for longer than the grace period (default 40s). Common causes include kubelet process crash, disk pressure, memory pressure, PID exhaustion, container runtime failure, or network connectivity loss between the node and control plane.
Detailed Answer
Think of a node going NotReady like an employee who stops responding to their walkie-talkie. The manager (control plane) sends check-ins every few seconds, and the employee (kubelet) is supposed to respond. If 40 seconds pass with no response, the manager assumes something is wrong and marks the employee as unavailable on the schedule. The employee might be fine but just have a dead radio battery (network issue), or they might have actually collapsed (kubelet crash), or they might be overwhelmed handling an emergency and can't respond (resource pressure).
The kubelet sends node status updates to the API Server at a configurable interval (default: 10 seconds via node-status-update-frequency). In modern Kubernetes (1.17+), it also maintains a Lease object in the kube-node-lease namespace that acts as a lightweight heartbeat — this reduces etcd load compared to updating the full Node object. The Node controller in the kube-controller-manager monitors these heartbeats. If a node hasn't updated in 40 seconds (node-monitor-grace-period), the controller sets the node's Ready condition to Unknown. After 5 more minutes (pod-eviction-timeout), it begins evicting Pods from that node by marking them for deletion.
Disk Pressure triggers when available disk space drops below a threshold (default: 15% free or 10% free depending on the eviction signal). The kubelet starts evicting Pods with the highest disk usage — first ephemeral storage consumers, then by priority. This commonly happens when container logs fill up the disk (applications logging at DEBUG level without rotation), or when images and unused containers accumulate (lack of garbage collection). Memory Pressure triggers when available memory drops below 100Mi (default). The kubelet evicts Pods in QoS order: BestEffort first (no resource requests), then Burstable (partial requests), and Guaranteed last (requests equal limits). PID Pressure means the node is running out of process IDs — each container can spawn many processes, and the default Linux PID limit (32768) can be exhausted by applications that fork excessively.
Container runtime issues are sneaky because the kubelet process itself may be healthy and reporting heartbeats, but the runtime socket is unresponsive. Pods on the node will be stuck in ContainerCreating or Unknown state. You'll see the kubelet reporting errors like 'PLEG is not healthy' (PLEG = Pod Lifecycle Event Generator — the component that syncs container runtime state with kubelet's internal state). If PLEG hasn't completed a full relist within 3 minutes, kubelet marks itself NotReady. This is often caused by a hung containerd process, a node with hundreds of Pods causing slow container listing, or an exhausted inotify watch limit.
Network connectivity loss between the node and the API Server causes the most confusing NotReady scenarios. The node is actually perfectly healthy — all Pods are running fine, serving traffic locally — but the control plane can't reach it. After the grace period, the control plane marks the node NotReady and starts the eviction timer. If your cloud provider's load balancer health checks are tied to node condition, the load balancer removes the node from its target group, causing real traffic loss even though the applications were healthy. This is why production clusters use node-level monitoring (such as node_exporter with Prometheus) independent of Kubernetes to detect whether a node is actually down versus just disconnected from the API Server.
The most dangerous gotcha: when a node goes NotReady due to a network partition, the control plane eventually evicts its Pods and schedules replacements elsewhere. But the original Pods may still be running on the partitioned node (they don't know they're evicted). If the network heals, you can briefly have duplicate instances of stateful applications — split-brain. This is why StatefulSets have strict Pod identity guarantees and why storage systems use fencing to prevent dual-writes.
Code Example
# Check which nodes are NotReady kubectl get nodes # NAME STATUS ROLES AGE VERSION # worker-01 Ready <none> 45d v1.28.3 # worker-02 NotReady <none> 45d v1.28.3 ← problem! # Get detailed node conditions kubectl describe node worker-02 | grep -A15 Conditions # Type Status Reason # MemoryPressure False KubeletHasSufficientMemory # DiskPressure True KubeletHasDiskPressure ← disk full! # PIDPressure False KubeletHasSufficientPID # Ready False KubeletNotReady # Check node events for clues kubectl get events --field-selector involvedObject.name=worker-02 # SSH into the node for direct diagnosis ssh worker-02 # Is kubelet running? sudo systemctl status kubelet sudo journalctl -u kubelet --since "5 minutes ago" | grep -i error # Check disk pressure df -h / # Root filesystem df -h /var/lib/containerd # Container images and layers du -sh /var/log/pods/* # Pod log sizes # Check memory pressure free -h # Look for: available < 100Mi # Check container runtime health sudo systemctl status containerd sudo crictl info | grep -i status # Check PLEG health (Pod Lifecycle Event Generator) sudo journalctl -u kubelet | grep -i 'PLEG is not healthy' # Check network connectivity to API Server curl -k https://<api-server-ip>:6443/healthz # Check node lease (lightweight heartbeat) kubectl get lease -n kube-node-lease worker-02 -o yaml # renewTime shows last successful heartbeat
Interview Tip
A junior engineer typically says 'the node is down' when they see NotReady. Interviewers want you to demonstrate a systematic diagnosis: first check if the kubelet is actually running (systemctl status kubelet), then check node conditions (describe node), then look at specific pressure conditions. The sophisticated answer includes explaining that NotReady can mean the node is perfectly healthy but just lost network connectivity to the API Server — a network partition. Mention PLEG (Pod Lifecycle Event Generator) as a common cause of kubelet marking itself unhealthy when the container runtime is slow to respond. If you can explain the eviction timeline (40s grace → Unknown → 5min → Pod eviction) and mention the split-brain risk for stateful workloads during network partitions, you'll demonstrate incident-response-level understanding that separates operators from textbook learners.
◈ Architecture Diagram
┌────── Node Health State Machine ──────────────────┐ │ │ │ Normal Operation: │ │ kubelet → heartbeat every 10s → API Server │ │ │ │ ┌───────┐ 40s no heartbeat ┌──────────┐ │ │ │ Ready │ ──────────────────────► │ NotReady │ │ │ └───────┘ └─────┬────┘ │ │ ▲ │ │ │ │ heartbeat resumes │ 5 min │ │ │ ▼ │ │ │ ┌──────────────┐ │ │ └────── node recovers ──── │ Pod Eviction │ │ │ └──────────────┘ │ │ │ │ Common Causes: │ │ ┌─────────────────────────────────────────────┐ │ │ │ 1. kubelet crashed → systemctl restart │ │ │ │ 2. DiskPressure → clean logs/images │ │ │ │ 3. MemoryPressure → evict Pods/add RAM│ │ │ │ 4. PIDPressure → check fork bombs │ │ │ │ 5. containerd hung → restart runtime │ │ │ │ 6. Network partition → check routes/fw │ │ │ │ 7. PLEG not healthy → too many Pods │ │ │ └─────────────────────────────────────────────┘ │ └────────────────────────────────────────────────────┘
💬 Comments
Quick Answer
ClusterIP exposes the Service on an internal-only virtual IP reachable only within the cluster. NodePort opens a static port (30000-32767) on every node's IP, allowing external access. LoadBalancer provisions a cloud provider's load balancer that routes external traffic through NodePorts to Pods.
Detailed Answer
Think of these three Service types like three levels of restaurant accessibility. ClusterIP is like an employee cafeteria inside a corporate office — only people who already work in the building (other Pods in the cluster) can eat there. NodePort is like putting a food window on the side of the building — anyone on the street who knows the building address and the window number can order food, but they have to know the exact window. LoadBalancer is like having a dedicated restaurant entrance with a host stand, a sign on the street, and an automated system that seats you at the next available table — the full public-facing experience with professional traffic management.
ClusterIP is the default Service type. When you create a Service without specifying a type, you get a ClusterIP. Kubernetes assigns it a virtual IP from the Service CIDR range (typically 10.96.0.0/12). This IP exists only as iptables/IPVS rules — no network interface has this IP. Other Pods reach the Service by its DNS name (payments-api.production.svc.cluster.local) which resolves to the ClusterIP. kube-proxy ensures traffic to this IP gets distributed across healthy backend Pods. Use ClusterIP for all internal communication: microservice-to-microservice, app-to-database, worker-to-queue.
NodePort builds on ClusterIP. When you create a NodePort Service, Kubernetes does everything ClusterIP does AND additionally opens a specific port (from range 30000-32767, unless you pick one explicitly) on every node in the cluster. Traffic hitting any node's IP on that port gets forwarded to the Service, which then routes it to a backend Pod. This means you can access the Service from outside the cluster by hitting http://<any-node-ip>:<node-port>. The downside: you're exposing a high-numbered port that clients need to know about, you have no TLS termination or path-based routing, and you're bypassing any external load balancing — if a node goes down, clients hitting that specific node get errors.
LoadBalancer builds on NodePort. When you create a LoadBalancer Service, Kubernetes does everything NodePort does AND additionally requests a load balancer from your cloud provider (AWS NLB/ALB, GCP Network LB, Azure Load Balancer). The cloud provider provisions an external IP or DNS name, configures health checks against the NodePorts, and distributes traffic across healthy nodes. From there, NodePort routing takes over and delivers traffic to Pods. This is the simplest way to expose a Service to the internet, but it has costs: each LoadBalancer Service gets its own cloud load balancer (at $15-20/month on AWS), which adds up fast if you have dozens of services. This cost explosion is why most production teams use an Ingress controller (a single LoadBalancer Service in front of an nginx or Envoy reverse proxy that handles routing for all services based on hostnames and paths).
There's also a fourth type that's rarely discussed: ExternalName. It creates a CNAME DNS record pointing to an external DNS name — no proxy, no ClusterIP, just DNS aliasing. Use it to give an in-cluster DNS name to an external service (like an RDS database endpoint) so your application code uses consistent service discovery regardless of whether the dependency is internal or external.
The production gotcha: when using NodePort or LoadBalancer, be aware of externalTrafficPolicy. The default (Cluster) means any node can handle the traffic, even if it doesn't run the target Pod — it'll forward to another node, adding a hop and losing the client's source IP. Setting externalTrafficPolicy: Local means the node only handles traffic if it has a local Pod, preserving source IP and reducing latency, but creating uneven load distribution if Pods aren't evenly spread across nodes.
Code Example
# ──── ClusterIP (internal only) ────
apiVersion: v1
kind: Service
metadata:
name: payments-api
namespace: production
spec:
type: ClusterIP # Default — internal only
selector:
app: payments-api
ports:
- port: 80 # Service port
targetPort: 8080 # Container port
# Test from inside the cluster
kubectl run debug --rm -it --image=busybox -- \
wget -qO- http://payments-api.production.svc.cluster.local/health
# ──── NodePort (external via node IP) ────
apiVersion: v1
kind: Service
metadata:
name: payments-api-nodeport
spec:
type: NodePort
selector:
app: payments-api
ports:
- port: 80
targetPort: 8080
nodePort: 30080 # Accessible on every node at :30080
# Access from outside: curl http://<node-ip>:30080/health
# ──── LoadBalancer (cloud LB provisioned) ────
apiVersion: v1
kind: Service
metadata:
name: payments-api-public
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: nlb # AWS NLB
spec:
type: LoadBalancer
externalTrafficPolicy: Local # Preserve client source IP
selector:
app: payments-api
ports:
- port: 443
targetPort: 8080
# Check the external IP/hostname assigned
kubectl get svc payments-api-public
# NAME TYPE EXTERNAL-IP
# payments-api-public LoadBalancer a1b2c3.elb.amazonaws.com
# Compare all three types side-by-side
kubectl get svc -n production
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S)
# payments-api ClusterIP 10.96.45.12 <none> 80/TCP
# payments-api-np NodePort 10.96.45.13 <none> 80:30080/TCP
# payments-api-public LoadBalancer 10.96.45.14 a1b2.elb.aws.com 443:31234/TCPInterview Tip
A junior engineer typically defines each type in isolation. Interviewers want you to explain the layering: LoadBalancer builds on NodePort, which builds on ClusterIP. Each type includes everything the previous type does plus more. The production-savvy answer includes mentioning the cost problem: each LoadBalancer Service creates a separate cloud load balancer ($15-20/month each), so production teams use an Ingress controller (one LoadBalancer + nginx/Envoy routing rules) instead of giving every Service its own LoadBalancer. Also mention externalTrafficPolicy — explaining that 'Local' preserves source IP but can cause uneven distribution shows you've configured production-grade services, not just followed tutorials. If you mention that ClusterIP is virtual (exists only in iptables rules, not on any NIC), you prove you understand the networking internals.
◈ Architecture Diagram
┌─── ClusterIP ────────────────────────────────────┐ │ Internal only — other Pods can reach it │ │ │ │ Pod A → 10.96.45.12 (virtual) → Pod B │ │ (iptables DNAT) │ └───────────────────────────────────────────────────┘ ┌─── NodePort ─────────────────────────────────────┐ │ ClusterIP + port on every node │ │ │ │ External → <NodeIP>:30080 → ClusterIP → Pod │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ Node A │ │ Node B │ │ Node C │ │ │ │ :30080 │ │ :30080 │ │ :30080 │ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ └──────────────┼──────────────┘ │ │ ▼ │ │ Service ClusterIP → Pods │ └───────────────────────────────────────────────────┘ ┌─── LoadBalancer ─────────────────────────────────┐ │ NodePort + cloud load balancer │ │ │ │ Internet → Cloud LB → NodePort → ClusterIP → Pod │ │ (AWS NLB) │ │ │ │ ┌──────────────────┐ │ │ │ Cloud LB │ │ │ │ a1b2.elb.aws │ │ │ └────────┬─────────┘ │ │ │ health checks │ │ ┌─────┼─────┐ │ │ ▼ ▼ ▼ │ │ Node A Node B Node C (:30080) │ │ │ │ │ │ │ └─────┼─────┘ │ │ ▼ │ │ Target Pods │ └───────────────────────────────────────────────────┘
💬 Comments
Quick Answer
Check the Service selector matches Pod labels, verify Endpoints exist with Pod IPs, confirm Pods are passing readiness probes, validate no NetworkPolicy is blocking traffic, and test DNS resolution and kube-proxy rules on the node.
Detailed Answer
Think of debugging an unreachable Service like troubleshooting a broken phone extension in an office. If you dial extension 5001 and nobody answers, you check: Is extension 5001 actually programmed in the phone system (does the Service exist)? Is it routed to the right desk (do selectors match labels)? Is anyone sitting at that desk (are Pods running)? Is the person at the desk taking calls (readiness probe passing)? Is the phone line physically connected (NetworkPolicy allowing traffic)? And is the internal phone directory working (DNS resolution)? You work from the outermost layer inward.
Step 1 — Verify the Service exists and has the correct configuration. Run kubectl get svc <name> to confirm the Service exists, check its type, and note the ClusterIP and port. Then run kubectl describe svc <name> to see the Selector field. A common mistake is typos in selectors: if the Service selects app: payment-api (singular) but Pods have the label app: payments-api (plural), the Service will never find any Pods.
Step 2 — Check Endpoints. Run kubectl get endpoints <service-name>. If the Endpoints list is empty, the Service found no matching Pods. This means either no Pods with matching labels exist, or Pods exist but aren't passing their readiness probe. Run kubectl get pods -l <selector-labels> to check if Pods with the right labels exist. If Pods exist but Endpoints are empty, check their readiness: kubectl describe pod <pod> and look at the Conditions section — Ready must be True. If the readiness probe is failing, the Endpoint controller removes the Pod from the Service's Endpoints, even though the Pod is Running.
Step 3 — Test DNS resolution. From a debug Pod, run nslookup <service-name>.<namespace>.svc.cluster.local. If DNS doesn't resolve, CoreDNS might be unhealthy — check kubectl get pods -n kube-system -l k8s-app=kube-dns. If DNS resolves but the connection times out, the issue is in the routing layer. Common DNS pitfalls: the client Pod uses a shortened service name (payments-api) which only works if the client is in the same namespace, or the ndots:5 setting in /etc/resolv.conf causes excessive DNS queries before finding the right suffix.
Step 4 — Check NetworkPolicies. If your cluster uses NetworkPolicies (Calico, Cilium, or any CNI that supports them), a policy might be blocking traffic. Run kubectl get networkpolicies -n <namespace> to see if any policies exist. NetworkPolicies are deny-by-default when present: if ANY NetworkPolicy selects a Pod, all traffic not explicitly allowed by some policy is blocked. A common mistake: adding a NetworkPolicy that allows ingress from the frontend namespace but forgetting to allow ingress from the monitoring namespace, breaking health checks.
Step 5 — Verify kube-proxy and node networking. If all of the above checks pass, the issue might be at the node networking level. Check if kube-proxy is running: kubectl get pods -n kube-system -l k8s-app=kube-proxy. On the node, verify iptables rules exist for your Service: sudo iptables -t nat -L KUBE-SERVICES | grep <service-clusterip>. If rules are missing, kube-proxy may not be syncing — check its logs for errors.
The production gotcha that causes the most subtle outages: readiness probes that depend on external services. If your payments-api readiness probe checks whether it can reach the database, and the database has a brief hiccup, ALL Pods simultaneously fail their readiness probes. The Endpoint controller removes all Pods from the Service. Now you have a complete outage — not because your application crashed, but because the readiness probe is too aggressive. Readiness probes should check 'can I handle requests?' not 'are all my dependencies healthy?'
Code Example
# Step 1: Check Service exists and has correct config kubectl get svc payments-api -n production kubectl describe svc payments-api -n production # Look at: Selector, Endpoints, Port/TargetPort # Step 2: Check Endpoints — are any Pod IPs listed? kubectl get endpoints payments-api -n production # NAME ENDPOINTS AGE # payments-api 10.244.1.5:8080,10.244.2.3:8080 5d # If EMPTY → selector doesn't match labels or Pods not Ready # Verify Pods with matching labels exist kubectl get pods -n production -l app=payments-api --show-labels # Check if Pods are Ready (readiness probe passing) kubectl get pods -n production -l app=payments-api # READY column should show 1/1, not 0/1 # Step 3: Test DNS resolution from inside the cluster kubectl run dns-debug --rm -it --image=busybox:1.36 -- sh nslookup payments-api.production.svc.cluster.local wget -qO- --timeout=5 http://payments-api.production.svc.cluster.local:80/health # Step 4: Check NetworkPolicies that might block traffic kubectl get networkpolicies -n production kubectl describe networkpolicy -n production # Step 5: Check kube-proxy is running and syncing kubectl get pods -n kube-system -l k8s-app=kube-proxy kubectl logs -n kube-system -l k8s-app=kube-proxy --tail=20 # Direct connectivity test (bypass Service, hit Pod directly) kubectl get pod -n production -l app=payments-api -o wide # If direct Pod IP works but Service doesn't → kube-proxy issue kubectl run debug --rm -it --image=busybox -- \ wget -qO- http://10.244.1.5:8080/health
Interview Tip
A junior engineer typically says 'restart the Service' or 'check if Pods are running.' Interviewers want to see a layered debugging methodology: Service exists → selector matches → Endpoints populated → DNS resolves → kube-proxy routes → NetworkPolicy allows. The key insight that impresses interviewers: the relationship between readiness probes and Endpoints. If a Pod is Running but not Ready (readiness probe failing), the Endpoint controller removes it from the Service's Endpoints list, making the Service unreachable even though Pods appear healthy in `kubectl get pods`. Demonstrate that you understand this by explaining that 'READY 0/1' in the pod listing means the Pod exists but receives no traffic. Also mention the common label mismatch trap — a single character difference between Service selector and Pod labels causes an empty Endpoints list with zero error messages, making it invisible unless you explicitly check.
◈ Architecture Diagram
┌─── Service Reachability Debug Path ───────────────┐ │ │ │ Client Pod │ │ │ │ │ │ ① DNS Lookup │ │ ▼ │ │ ┌──────────┐ resolves? ┌──────────────┐ │ │ │ CoreDNS │───── yes ───────►│ ClusterIP │ │ │ └──────────┘ │ 10.96.45.12 │ │ │ │ no └──────┬───────┘ │ │ ▼ │ │ │ FIX: check CoreDNS Pods │ │ │ ② kube-proxy │ │ iptables rules? │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Endpoints │ │ │ │ (Pod IPs) │ │ │ └───────┬─────────┘ │ │ │ │ │ ③ Endpoints empty? │ │ ┌──────┴──────┐ │ │ │ │ │ │ Label mismatch Readiness │ │ (selector ≠ probe │ │ pod labels) failing │ │ │ │ Quick checks: │ │ • kubectl get endpoints <svc> → IPs listed? │ │ • kubectl get pods -l <selector> → READY 1/1? │ │ • kubectl get networkpolicy → blocking traffic? │ └────────────────────────────────────────────────────┘
💬 Comments
Quick Answer
During a rolling update, the Deployment creates a new ReplicaSet and gradually scales it up while scaling the old one down, controlled by maxSurge and maxUnavailable. Pods must pass readiness probes before old Pods are terminated. To rollback, run kubectl rollout undo which scales the previous ReplicaSet back up.
Detailed Answer
Think of a rolling update like replacing light bulbs in a theater while the show is still running. You can't turn off all the lights at once (that's a Recreate strategy — total blackout). Instead, you unscrew one old bulb, screw in a new one, verify it lights up (readiness probe), and only then move to the next one. At every moment during the replacement, the audience still has enough light to see the show. If a new bulb turns out to be defective (bad deployment), you stop the replacement and screw the old bulbs back in (rollback).
When you update a Deployment (change the container image, environment variables, or any field in the Pod template), the Deployment controller creates a brand new ReplicaSet with the updated Pod template. It then orchestrates a carefully choreographed transition between the old and new ReplicaSets. The two parameters that control this dance are maxSurge (how many extra Pods above the desired count are allowed during the update — controls speed) and maxUnavailable (how many Pods can be offline simultaneously — controls safety margin). With replicas=4, maxSurge=1, maxUnavailable=1: Kubernetes can have up to 5 Pods running (4+1 surge) and at minimum 3 Pods available (4-1 unavailable) at any point.
The update proceeds in cycles. In each cycle: (1) the new ReplicaSet is scaled up by maxSurge number of Pods, (2) Kubernetes waits for those new Pods to pass their readiness probe (this is the critical gate — without a readiness probe, Kubernetes considers Pods ready immediately, potentially sending traffic to uninitialized applications), (3) once new Pods are Ready, the old ReplicaSet is scaled down by maxUnavailable Pods. This cycle repeats until all old Pods are terminated and all new Pods are running. The entire process is recorded as a new revision in the Deployment's rollout history.
Rollback is elegantly simple because Kubernetes keeps old ReplicaSets around (scaled to 0 replicas). When you run kubectl rollout undo deployment/payments-api, the Deployment controller doesn't create anything new — it simply scales up the previous ReplicaSet (which still has the old Pod template with the known-good image) and scales down the current one. This means rollback is typically faster than the original deployment because: the old image may still be cached on nodes (no pull needed), and the old ReplicaSet already exists (no creation delay). You can also rollback to a specific revision with kubectl rollout undo --to-revision=3.
By default, Kubernetes keeps the last 10 old ReplicaSets (controlled by revisionHistoryLimit). Setting this too low (like 1) means you can only undo one step back. Setting it too high wastes API Server memory with stale ReplicaSet objects. For most teams, 5 revisions is the sweet spot.
The most critical production gotcha: if you don't have a readiness probe, Kubernetes considers Pods ready the instant the container process starts — even if your Spring Boot app needs 45 seconds to initialize. During a rolling update, traffic gets routed to these half-started Pods, causing 500 errors for real users. The second gotcha: if your readiness probe never passes (bug in health endpoint, wrong port, misconfigured path), the rollout hangs forever — new Pods stay in a NotReady state, old Pods never get terminated, and the Deployment reports 'waiting for rollout to finish'. Use progressDeadlineSeconds (default 600s) to automatically mark a stuck rollout as Failed after a timeout.
Code Example
# Current state: payments-api running v2.1.0 with 4 replicas
kubectl get deployment payments-api
# NAME READY UP-TO-DATE AVAILABLE AGE
# payments-api 4/4 4 4 30d
# Trigger rolling update to v2.2.0
kubectl set image deployment/payments-api \
api=registry.company.io/payments-api:2.2.0
# Watch the rollout in real time
kubectl rollout status deployment/payments-api
# Waiting for deployment "payments-api" rollout to finish:
# 1 out of 4 new replicas have been updated...
# 2 out of 4 new replicas have been updated...
# 4 out of 4 new replicas have been updated...
# deployment "payments-api" successfully rolled out
# See both ReplicaSets during the transition
kubectl get replicasets -l app=payments-api
# NAME DESIRED CURRENT READY
# payments-api-7d5f8b6c4 4 4 4 ← new (v2.2.0)
# payments-api-6b4a9c1e2 0 0 0 ← old (v2.1.0)
# v2.2.0 has a bug! Rollback immediately
kubectl rollout undo deployment/payments-api
# deployment.apps/payments-api rolled back
# Confirm rollback succeeded
kubectl rollout status deployment/payments-api
kubectl describe deployment payments-api | grep Image
# Image: registry.company.io/payments-api:2.1.0 ← back to previous
# View full rollout history
kubectl rollout history deployment/payments-api
# REVISION CHANGE-CAUSE
# 1 initial deploy
# 2 image update to v2.2.0
# 3 rollback to revision 1
# Rollback to a specific revision
kubectl rollout undo deployment/payments-api --to-revision=1
# Deployment spec for controlled rollouts
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 1 extra Pod allowed during update
maxUnavailable: 0 # Never drop below desired count (safest)
progressDeadlineSeconds: 300 # Fail rollout if stuck for 5 min
minReadySeconds: 30 # Wait 30s after Ready before continuingInterview Tip
A junior engineer typically says 'Kubernetes replaces old Pods with new ones.' Interviewers want you to explain the mechanism precisely: new ReplicaSet created, scaled up in cycles, readiness probe gates progression, old ReplicaSet scaled down, old ReplicaSets retained at 0 for rollback. The killer detail is explaining maxUnavailable=0 with maxSurge=1 as the safest configuration — it means you never have fewer than the desired number of healthy Pods, at the cost of needing extra node capacity for the surge Pods. Explain that rollback doesn't create new Pods — it scales up an existing ReplicaSet that already has the old template, which is why rollbacks are faster than deployments. If you mention progressDeadlineSeconds as the timeout that prevents infinite hangs when readiness probes never pass, you show you've dealt with stuck rollouts in production.
◈ Architecture Diagram
┌─── Rolling Update Timeline (replicas=4, maxSurge=1) ───┐ │ │ │ Step 0: Stable on v2.1.0 │ │ ┌──────┐┌──────┐┌──────┐┌──────┐ │ │ │v2.1.0││v2.1.0││v2.1.0││v2.1.0│ Available: 4 │ │ └──────┘└──────┘└──────┘└──────┘ │ │ │ │ Step 1: Create 1 new Pod (surge) │ │ ┌──────┐┌──────┐┌──────┐┌──────┐┌──────┐ │ │ │v2.1.0││v2.1.0││v2.1.0││v2.1.0││v2.2.0│ Total: 5 │ │ └──────┘└──────┘└──────┘└──────┘└──┬───┘ │ │ │ │ │ wait for readiness probe │ │ ▼ │ │ Step 2: New Pod Ready → terminate 1 old │ │ ┌──────┐┌──────┐┌──────┐┌──────┐ │ │ │v2.1.0││v2.1.0││v2.1.0││v2.2.0│ Available: 4 │ │ └──────┘└──────┘└──────┘└──────┘ │ │ │ │ ... repeat until ... │ │ │ │ Step Final: All replaced │ │ ┌──────┐┌──────┐┌──────┐┌──────┐ │ │ │v2.2.0││v2.2.0││v2.2.0││v2.2.0│ Available: 4 │ │ └──────┘└──────┘└──────┘└──────┘ │ │ │ │ Rollback: scale old RS back up (instant, no new pull) │ │ ┌──────┐┌──────┐┌──────┐┌──────┐ │ │ │v2.1.0││v2.1.0││v2.1.0││v2.1.0│ ← old RS restored │ │ └──────┘└──────┘└──────┘└──────┘ │ └──────────────────────────────────────────────────────────┘
💬 Comments