Giới thiệu và Kiến trúcIntroduction and Architecture
Thuộc bộ tài liệu kiến thức theo Kubernetes Roadmap.
Tổng quan
Kubernetes (K8s) là một nền tảng mã nguồn mở để tự động hóa việc triển khai, scale và vận hành các ứng dụng container hóa trên cả một cụm máy. Nó được thiết kế ban đầu tại Google — kế thừa một hệ thống nội bộ tên Borg — được mã nguồn mở năm 2014, và hiện là dự án hàng đầu của Cloud Native Computing Foundation (CNCF). Tên gọi bắt nguồn từ tiếng Hy Lạp nghĩa là “người lái tàu”; “K8s” là cách viết tắt (chữ K, tám chữ cái, rồi s).
Để hiểu vì sao Kubernetes tồn tại, hãy nhìn theo hành trình đóng gói ứng dụng. Bare-metal chạy một app trên một server — lãng phí và cấp phát chậm. Máy ảo (VM) nhồi nhiều OS cô lập lên cùng một host, nhưng mỗi VM mang theo cả một guest OS đầy đủ: nặng, boot chậm, ship lớn. Container (Docker, 2013) dùng chung kernel của host và chỉ đóng gói app cùng các dependency của nó — một container image nhỏ, khởi động trong vài mili-giây, và chạy y hệt nhau trên laptop, trong CI, và trên production. Container đã giải quyết bài toán đóng gói. Nhưng khi bạn có hàng trăm container trải trên hàng chục node, một lớp vấn đề mới xuất hiện: mỗi container nên chạy trên node nào? Chuyện gì xảy ra khi một container crash, hay cả một node chết? Làm sao rollout phiên bản mới không downtime, rollback khi nó lỗi, scale replica theo tải, để các container tìm và nói chuyện với nhau, gắn storage và cấu hình? Đó là orchestration, và đây chính là khoảng trống mà Kubernetes lấp đầy.
Giải pháp thay thế cho orchestrator là một đống script tự chế dễ vỡ: logic đặt container, health check, vòng lặp restart, script rolling-deploy, các mánh service-discovery. Kubernetes biến từng mối lo vận hành đó thành một primitive khai báo (declarative) qua API. Bạn mô tả điểm đến; nền tảng tự tìm đường và giữ bạn ở đó.
Một mô hình tư duy
Kubernetes được hiểu tốt nhất như một cỗ máy control-loop. Mọi thứ đều là object lưu trong một database (etcd); mỗi object có một spec (điều bạn muốn) và một status (điều đang thực sự có). Các controller liên tục theo dõi object và làm cho status khớp spec. kubectl apply không làm gì theo kiểu mệnh lệnh — nó ghi lại ý định của bạn, và controller biến nó thành hiện thực. Thấm được đúng một ý này thì phần lớn hành vi của Kubernetes — self-healing, rollout, autoscaling, GitOps — hết còn là phép màu và trở nên hiển nhiên.
Kiến thức nền tảng
Mô hình khai báo và desired state
Automation truyền thống mang tính mệnh lệnh (imperative): bạn phát ra các lệnh (“khởi động container này”, “thêm một replica”, “restart process kia”). Nếu một lệnh bị bỏ lỡ, nếu một máy reboot, hoặc một bước lỗi giữa chừng, hệ thống trôi lệch (drift) và bạn phải suy luận nó đã đến trạng thái này bằng cách nào để sửa.
Kubernetes mang tính khai báo (declarative): bạn nộp trạng thái cuối mong muốn (“tôi muốn 3 replica của image web:1.4.2 sau một load balancer”), và việc của hệ thống là làm cho thực tế khớp — và giữ cho nó luôn khớp. Đây là khác biệt giữa một danh sách việc cần làm và một bộ điều nhiệt (thermostat). Thermostat không bắn một lệnh “làm nóng” một lần rồi thôi; nó liên tục so sánh nhiệt độ phòng (thực tế) với nhiệt độ mục tiêu (mong muốn) và hành động cho tới khi khớp, rồi tiếp tục theo dõi.
Thiết kế này là level-triggered, không phải edge-triggered. Controller không phản ứng với từng sự kiện riêng lẻ (“một pod vừa bị xóa”); chúng liên tục suy ra lại hành động đúng từ trạng thái hiện tại (“muốn 3, thực tế 2, vậy tạo thêm một”). Đó là lý do Kubernetes phục hồi sạch sẽ sau sự kiện bị bỏ lỡ, controller restart, và lỗi cục bộ: nó không bao giờ phụ thuộc vào việc đã thấy một sự kiện cụ thể — nó luôn đọc lại thế giới và hội tụ.
Kiến trúc cụm (cluster)
Một cụm Kubernetes chia thành hai mặt phẳng: control plane (bộ não — ra quyết định toàn cục và lưu trạng thái) và các worker node (cơ bắp — chạy container của bạn). Trong production chúng chạy trên các máy tách biệt; các dịch vụ managed (EKS, GKE, AKS) chạy control plane hộ bạn.
- Control Planekube-apiserveretcdkho trạng tháikube-schedulerkube-controller-manager(cloud-controller-manager)
- (mọi traffic đi qua API)NodesNodekubelet, kube-proxy, containerdNodekubelet, kube-proxy, containerdNodekubelet, kube-proxy, containerd
Các thành phần control plane:
- kube-apiserver — cửa trước và là thành phần duy nhất mà mọi thứ khác nói chuyện. Mọi thao tác đọc và ghi — từ
kubectl, từ controller, từ kubelet — đều đi qua REST API này. Nó xử lý authentication (bạn là ai?), authorization (RBAC — bạn có được phép?), và admission control (các webhook validating/mutating có thể từ chối hoặc viết lại request). Nó là thành phần duy nhất nói chuyện trực tiếp với etcd. Nó stateless và scale ngang được. - etcd — một key-value store phân tán, nhất quán mạnh (consensus Raft) chứa toàn bộ trạng thái cụm: mọi object, spec và status của chúng. etcd chính là nguồn sự thật — mất nó là mất cụm, nên hãy backup và chạy số lượng thành viên lẻ (3 hoặc 5) để có quorum.
- kube-scheduler — theo dõi các Pod mới tạo chưa được gán node và chọn một node cho từng Pod, dựa trên resource request, node affinity/anti-affinity, Pod affinity/anti-affinity, taint/toleration, và topology spread constraint. Nó chỉ quyết định ở đâu; nó không bao giờ tự chạy gì.
- kube-controller-manager — một binary duy nhất chạy nhiều control loop có sẵn: các controller cho Deployment, ReplicaSet, Node, Job, EndpointSlice, namespace, và nhiều thứ khác. Mỗi loop đưa trạng thái thực tế về phía trạng thái mong muốn.
- cloud-controller-manager — đường nối giữa Kubernetes và một cloud provider. Nó chạy các loop cấp phát cloud load balancer cho Service loại
LoadBalancer, quản lý vòng đời node theo instance API của cloud, và cấu hình route và volume. Tách riêng phần này giúp phần lõi Kubernetes giữ tính cloud-agnostic.
Các thành phần node (trên mọi worker):
- kubelet — agent của node. Nó theo dõi API server để bắt các Pod được schedule vào node của nó, bảo container runtime pull image và chạy container, mount volume, và liên tục báo cáo status của Pod và node về. Nó chạy các probe liveness/readiness/startup. kubelet chỉ quản lý các container do Kubernetes tạo — không quản lý container tùy tiện.
- kube-proxy — duy trì các rule mạng (iptables, IPVS, hoặc eBPF) khiến virtual IP của Service hoạt động: traffic tới ClusterIP của một Service được load-balance qua các Pod backend của nó. Một số CNI plugin (Cilium) có thể thay thế hoàn toàn kube-proxy.
- Container runtime — phần mềm thực sự chạy container, giao tiếp qua CRI (Container Runtime Interface). Các bản triển khai phổ biến là containerd và CRI-O. Shim tích hợp của Docker (
dockershim) đã bị gỡ ở Kubernetes v1.24 — image build bằng Docker vẫn chạy bình thường (chúng là image OCI), nhưng runtime dưới kubelet giờ là containerd/CRI-O. - CNI plugin — Calico, Cilium, Flannel, và các plugin khác triển khai mạng cho Pod theo spec CNI (Container Network Interface), cấp cho mỗi Pod một IP có thể route được để các Pod liên lạc phẳng với nhau qua các node.
Mô hình object/resource
Mọi thứ bạn quản lý trong Kubernetes là một object — một bản ghi bền vững về ý định. Mọi object đều chia sẻ cùng bốn field cấp cao nhất:
| Field | Ý nghĩa |
|---|---|
| apiVersion | Nhóm API và version nào định nghĩa kind này — ví dụ v1 (core), apps/v1 (Deployment), batch/v1 (Job), networking.k8s.io/v1 (Ingress) |
| kind | Loại object — Pod, Deployment, Service, ConfigMap, … |
| metadata | Danh tính và tổ chức — name, namespace, labels, annotations, uid |
| spec | Trạng thái mong muốn bạn khai báo — hình dạng tùy theo kind |
| status | Trạng thái quan sát được, do hệ thống điền và cập nhật liên tục — bạn không viết nó |
API được nhóm và đánh version. Nhóm (group) đặt namespace cho các kind liên quan (apps, batch, networking.k8s.io); version tốt nghiệp qua alpha → beta → stable (v1). Các version API bị deprecated cuối cùng sẽ bị gỡ, đây là mối lo lặp lại khi upgrade — một manifest dùng apiVersion đã bị gỡ sẽ bị từ chối sau khi upgrade.
Label và selector là chất keo loose-coupling của toàn hệ thống. Label là một cặp key: value tùy ý trên metadata của object (app: web, tier: backend). Selector khớp các object theo label của chúng. Một Service tìm Pod của nó bằng selector; một Deployment sở hữu các Pod có label khớp selector của nó. Không có gì được nối bằng tham chiếu cứng — tất cả khớp qua label, và đó là lý do hệ thống dễ ghép nối đến vậy. Annotation trông tương tự (key: value) nhưng dùng cho metadata không định danh mà công cụ đọc (thông tin build, config-hash, cấu hình ingress controller); bạn không bao giờ select theo annotation.
kubectl và kubeconfig
kubectl là CLI chính — một client mỏng phủ lên API server. Nó đọc thông tin kết nối từ file kubeconfig (mặc định ~/.kube/config), gói ba danh sách cùng một lựa chọn hiện tại:
- clusters — URL của API server và CA certificate của chúng.
- users — credential (client cert, token, hoặc một exec plugin lấy token từ cloud).
- contexts — một bộ ba có tên
(cluster, user, namespace). current-context là cái đang hoạt động.
Chuyển context là cách bạn di chuyển giữa các cluster hay namespace mặc định mà không phải gõ lại flag:
kubectl config get-contexts # liệt kê context, * là hiện tại
kubectl config use-context prod-eks # đổi cluster
kubectl config set-context --current --namespace=payments # đổi ns mặc định
Các công cụ như kubectx/kubens làm việc này còn nhanh hơn. Hãy coi kubeconfig như một credential — nó cấp quyền truy cập cluster.
Namespace
Một namespace là một cluster ảo bên trong một cluster vật lý — một phạm vi cho tên và một ranh giới cho policy. Tên object phải duy nhất trong một namespace, không phải trên toàn cluster, nên web ở dev và web ở prod cùng tồn tại. Namespace là nơi bạn gắn RBAC (ai được làm gì ở đây), ResourceQuota (namespace này được dùng bao nhiêu CPU/memory/object), và LimitRange (request/limit mặc định và tối đa cho mỗi Pod). Mỗi cluster có sẵn default, kube-system (add-on của control plane), kube-public, và kube-node-lease. Lưu ý không phải mọi thứ đều có namespace: Node, PersistentVolume, bản thân Namespace, và ClusterRole là cluster-scoped.
Khái niệm chính
Một request kubectl apply chảy qua hệ thống như thế nào
Theo dõi một lệnh từ đầu đến cuối sẽ nối mọi thành phần lại với nhau. Giả sử bạn chạy kubectl apply -f deployment.yaml cho một Deployment 3 replica:
- kubectl đọc YAML, lấy endpoint API và credential từ kubeconfig, và gửi một HTTP
PATCH/POSTtới kube-apiserver. - API server xác thực người gọi, chạy authorization (RBAC: user này có được tạo Deployment ở đây không?), rồi admission control — mutating webhook có thể chèn giá trị mặc định (ví dụ một sidecar), validating webhook có thể từ chối object (ví dụ “không cho image
:latest”). Object được kiểm tra theo schema của nó. - API server lưu object Deployment vào etcd. Tới đây phần mệnh lệnh đã xong — chưa có gì được “chạy” cả. Object chỉ đơn giản tồn tại với một
specvà mộtstatusrỗng. - Deployment controller (trong kube-controller-manager) đang theo dõi các Deployment. Nó thấy một cái mới với 0 ReplicaSet và tạo một object ReplicaSet (một lần ghi nữa vào API/etcd).
- ReplicaSet controller thấy một ReplicaSet muốn 3 replica và hiện có 0, nên nó tạo 3 object Pod — vẫn chỉ là bản ghi trong etcd, mỗi cái chưa gán node (
nodeNamerỗng). - scheduler đang theo dõi các Pod chưa được schedule. Với mỗi Pod, nó lọc và chấm điểm các node rồi ghi
nodeNameđược chọn lại lên Pod (một binding). - Trên mỗi node được chọn, kubelet thấy một Pod giờ đã gán vào mình, gọi container runtime (qua CRI) pull image và khởi động container, thiết lập mạng (qua CNI), mount volume, và bắt đầu chạy probe.
- Khi container lên, kubelet cập nhật status của từng Pod qua API server vào etcd. ReplicaSet và Deployment controller quan sát các status và cập nhật status của chính mình —
status.readyReplicasleo lên 3, và Deployment báoAvailable.
Để ý: không thành phần nào ra lệnh xuống theo một chuỗi. Mỗi cái theo dõi API, thấy khoảng cách giữa mong muốn và thực tế, và hành động. Đó là mô hình reconciliation đang vận hành.
Ví dụ: một manifest tối thiểu
Object hữu ích nhỏ nhất là một Pod đơn lẻ, nhưng trong thực tế bạn gần như luôn khai báo một Deployment (nó quản lý Pod hộ bạn). Đây là một Deployment tối thiểu, dễ đọc, cùng Service để expose nó:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec:
replicas: 3 # desired state: luôn giữ 3 Pod chạy
selector:
matchLabels:
app: web # Deployment này sở hữu Pod có app=web
template: # Pod template được dập ra cho mỗi replica
metadata:
labels:
app: web # PHẢI khớp selector ở trên
spec:
containers:
- name: web
image: nginx:1.27 # tag bất biến — không dùng :latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web # route tới Pod có label app=web
ports:
- port: 80
targetPort: 80
template.metadata.labels của Deployment phải được spec.selector của nó khớp — chính liên kết đó khiến Deployment sở hữu các Pod ấy. selector của Service khớp cùng label một cách độc lập để tìm endpoint. Đổi image thành nginx:1.28 và apply lại: Deployment controller tạo một ReplicaSet mới và thực hiện rolling update, tất cả từ một chỉnh sửa khai báo duy nhất đó.
Các lệnh kubectl cốt lõi
# Apply / kiểm tra
kubectl apply -f app.yaml # tạo hoặc cập nhật từ manifest
kubectl get pods -o wide # liệt kê Pod, kèm node và IP
kubectl get deploy,svc # nhiều kind cùng lúc
kubectl describe pod web-6d4c... # events + config — vì sao Pending/CrashLooping?
kubectl get pod web-6d4c... -o yaml # object đầy đủ, gồm cả status
# Debug
kubectl logs -f deploy/web # stream log
kubectl logs web-6d4c... --previous # log của container crash lần trước
kubectl exec -it web-6d4c... -- sh # shell vào một container đang chạy
kubectl get events --sort-by=.lastTimestamp
# Quản lý
kubectl rollout status deploy/web
kubectl rollout undo deploy/web # rollback về ReplicaSet trước
kubectl scale deploy/web --replicas=5
kubectl delete -f app.yaml
# Context / namespace
kubectl config use-context prod
kubectl get pods -n kube-system
kubectl api-resources # mọi kind, apiVersion, và tên viết tắt
kubectl describe và kubectl get events là điểm dừng đầu tiên để tìm vì sao thứ gì đó không hoạt động — câu trả lời hầu như luôn nằm trong events (lỗi pull image, schedule thất bại, probe thất bại, OOM kill).
Dùng imperative so với declarative
kubectl hỗ trợ cả lệnh imperative (kubectl run, kubectl create deployment, kubectl scale, kubectl edit) lẫn declarative (kubectl apply -f). Imperative tiện cho khám phá và debug, nhưng apply declarative trên các file lưu trong Git mới là cách tiếp cận cấp production: manifest là nguồn sự thật, thay đổi review được, và cluster có thể dựng lại từ chúng. Đây là nền tảng mà GitOps xây dựng trên đó.
Best Practices
- Tư duy declarative, không phải imperative. Mô tả desired state trong manifest và để controller hội tụ; tránh các thao tác imperative một lần trong production. Nếu không tái tạo được cluster từ file, bạn không thực sự biết trạng thái của nó.
- Lưu mọi manifest trong Git và apply chúng, không edit trực tiếp.
kubectl apply/GitOps thay vìkubectl edit. Repository là nguồn sự thật; cluster nên phản chiếu một trạng thái đã được review và commit. - Hiểu hình dạng object API (
apiVersion/kind/metadata/spec/status) trước khi với tới công cụ. Helm và Kustomize chỉ sinh ra các object này — bạn không debug được thứ chúng tạo nếu các primitive vẫn là bí ẩn. - Ghim
apiVersioncó chủ đích và theo dõi deprecation. API bị gỡ qua các bản phát hành; dùng công cụ nhưplutohoặc để ý cảnh báo deprecation củakubectlđể một lần upgrade không từ chối manifest của bạn. - Không bao giờ dùng tag image
latest. Triển khai tag bất biến, có version (hoặc digest) để rollout, rollback, và audit tái lập được và có ý nghĩa. - Dùng namespace để phân vùng team và environment, và gắn RBAC, ResourceQuota, LimitRange cho mỗi namespace để một sai sót ở một namespace không thể ngốn cả cluster.
- Áp dụng RBAC tối thiểu quyền ngay từ ngày đầu. Cấp các Role hẹp gắn với ServiceAccount/user cụ thể; đừng phát
cluster-adminmột cách bừa bãi. - Coi kubeconfig như một secret và dùng context tường minh. Đặt tên context rõ ràng (
prod-eks,staging), xác nhận context hiện tại trước các lệnh phá hủy, và tách credential prod khỏi dev. - Đặt label nhất quán khắp nơi. Áp dụng bộ label khuyến nghị
app.kubernetes.io/*; selector, dashboard, và phân bổ chi phí đều phụ thuộc vào một sơ đồ label sạch, dễ đoán. - Dùng annotation cho metadata, label cho selection. Đừng nhồi label với dữ liệu bạn không bao giờ select (build SHA, URL) — chúng thuộc về annotation.
- Bảo vệ và backup etcd. Nó là toàn bộ trạng thái cluster; chạy quorum số lẻ, mã hóa secret at-rest, và chụp (và test-restore) snapshot — trên cluster managed đây là việc của provider, nhưng hãy biết ranh giới ở đâu.
- Ưu tiên control plane managed (EKS/GKE/AKS) trừ khi có lý do không. Tự chạy và vá API server cùng etcd là công việc thật, liên tục; để provider lo và tập trung vào workload.
- Học cách đọc events trước tiên. Khi thứ gì đó kẹt,
kubectl describevàkubectl get eventshầu như luôn chứa lý do (ImagePullBackOff, FailedScheduling, OOMKilled) — với tới chúng trước khi đoán mò. - Giữ tách bạch trong đầu control plane và workload. Scheduler quyết định ở đâu, kubelet chạy, API server là cửa trước duy nhất — biết thành phần nào sở hữu một hành vi giúp troubleshoot nhanh hơn nhiều.
Tài liệu tham khảo
- Kubernetes documentation
- What is Kubernetes?
- Kubernetes components
- Kubernetes API concepts
- Understanding Kubernetes objects
- Nodes · Control plane / node communication
- The kubelet · Container Runtime Interface (CRI)
- Namespaces
- Labels and selectors
- Organizing cluster access using kubeconfig files
- kubectl overview & cheat sheet
- etcd documentation
- CNCF landscape
- Sách: Kubernetes Up & Running (Hightower, Burns, Beda — O’Reilly)
- roadmap.sh — Kubernetes
Part of the Kubernetes Roadmap knowledge base.
Overview
Kubernetes (K8s) is an open-source platform for automating the deployment, scaling, and operation of containerized applications across a fleet of machines. It was originally designed at Google — descended from an internal system called Borg — open-sourced in 2014, and is now the flagship project of the Cloud Native Computing Foundation (CNCF). The name comes from the Greek word for “helmsman”; “K8s” is a numeronym (K, eight letters, s).
To see why Kubernetes exists, follow the arc of application packaging. Bare-metal ran one app per server — wasteful and slow to provision. Virtual machines packed several isolated OSes onto one host, but each VM carries a full guest OS: heavy, slow to boot, big to ship. Containers (Docker, 2013) share the host kernel and package only the app plus its dependencies — a container image is small, starts in milliseconds, and runs identically on a laptop, in CI, and in production. Containers solved packaging. But once you have hundreds of containers spread across dozens of nodes, a new class of problems appears: which node should each container run on? What happens when a container crashes, or a whole node dies? How do you roll out a new version without downtime, roll it back if it breaks, scale replicas with load, let containers find and talk to each other, and attach storage and configuration? This is orchestration, and it is exactly the gap Kubernetes fills.
The alternative to an orchestrator is a pile of brittle bespoke scripts: placement logic, health checks, restart loops, rolling-deploy shell scripts, service-discovery hacks. Kubernetes turns every one of those operational concerns into a declarative API primitive. You describe the destination; the platform figures out the route and keeps you there.
A mental model
Kubernetes is best understood as a control-loop machine. Everything is an object stored in a database (etcd); every object has a spec (what you want) and a status (what actually is). Controllers continuously watch objects and work to make status match spec. kubectl apply does not do anything imperatively — it records your intent, and controllers make it real. Internalize this one idea and most of Kubernetes’ behavior — self-healing, rollouts, autoscaling, GitOps — stops being magic and becomes obvious.
Fundamentals
The declarative model and desired state
Traditional automation is imperative: you issue commands (“start this container”, “add a replica”, “restart that process”). If a command is missed, if a machine reboots, or if a step half-fails, the system drifts and you must reason about how it got here to fix it.
Kubernetes is declarative: you submit the desired end state (“I want 3 replicas of image web:1.4.2 behind a load balancer”), and the system’s job is to make reality match — and to keep it matching. This is the difference between a to-do list and a thermostat. A thermostat doesn’t fire a one-shot “heat up” command; it continuously compares the room temperature (actual) to the target (desired) and acts until they agree, then keeps watching.
This design is level-triggered, not edge-triggered. Controllers don’t react to individual events (“a pod was deleted”); they repeatedly re-derive the correct action from current state (“desired 3, actual 2, therefore create one”). That is why Kubernetes recovers cleanly from missed events, controller restarts, and partial failures: it never depends on having seen a particular event — it always re-reads the world and converges.
Cluster architecture
A Kubernetes cluster is split into two planes: the control plane (the brain — makes global decisions and stores state) and the worker nodes (the muscle — run your containers). In production these run on separate machines; managed offerings (EKS, GKE, AKS) run the control plane for you.
- Control Planekube-apiserveretcdstate storekube-schedulerkube-controller-manager(cloud-controller-manager)
- (all traffic via the API)NodesNodekubelet, kube-proxy, containerdNodekubelet, kube-proxy, containerdNodekubelet, kube-proxy, containerd
Control plane components:
- kube-apiserver — the front door and the only component anything else talks to. Every read and write — from
kubectl, from controllers, from kubelets — goes through this REST API. It handles authentication (who are you?), authorization (RBAC — are you allowed?), and admission control (validating/mutating webhooks that can reject or rewrite requests). It is the only component that talks to etcd directly. It is stateless and horizontally scalable. - etcd — a distributed, strongly consistent key-value store (Raft consensus) that holds the entire cluster state: every object, its spec, and its status. etcd is the source of truth — lose it and you lose the cluster, so back it up and run an odd number of members (3 or 5) for quorum.
- kube-scheduler — watches for newly created Pods with no assigned node and picks one for each, based on resource requests, node affinity/anti-affinity, Pod affinity/anti-affinity, taints/tolerations, and topology spread constraints. It only decides where; it never runs anything itself.
- kube-controller-manager — a single binary running many built-in control loops: the Deployment, ReplicaSet, Node, Job, EndpointSlice, and namespace controllers, among others. Each loop drives actual state toward desired state.
- cloud-controller-manager — the seam between Kubernetes and a cloud provider. It runs the loops that provision cloud load balancers for
LoadBalancerServices, manage node lifecycle against the cloud’s instance API, and configure routes and volumes. Splitting this out lets Kubernetes core stay cloud-agnostic.
Node components (on every worker):
- kubelet — the node’s agent. It watches the API server for Pods scheduled to its node, tells the container runtime to pull images and run containers, mounts volumes, and continuously reports Pod and node status back. It runs the liveness/readiness/startup probes. The kubelet manages containers created by Kubernetes only — not arbitrary containers.
- kube-proxy — maintains the network rules (iptables, IPVS, or eBPF) that make Service virtual IPs work: traffic to a Service’s ClusterIP is load-balanced across its backend Pods. Some CNI plugins (Cilium) can replace kube-proxy entirely.
- Container runtime — the software that actually runs containers, spoken to over the CRI (Container Runtime Interface). The common implementations are containerd and CRI-O. Docker’s built-in shim (
dockershim) was removed in Kubernetes v1.24 — Docker-built images still run fine (they’re OCI images), but the runtime under the kubelet is now containerd/CRI-O. - CNI plugin — Calico, Cilium, Flannel, and others implement Pod networking per the CNI (Container Network Interface) spec, giving every Pod a routable IP so Pods can reach each other flat across nodes.
The objects/resources model
Everything you manage in Kubernetes is an object — a persisted record of intent. Every object shares the same four top-level fields:
| Field | Meaning |
|---|---|
| apiVersion | Which API group and version defines this kind — e.g. v1 (core), apps/v1 (Deployment), batch/v1 (Job), networking.k8s.io/v1 (Ingress) |
| kind | The type of object — Pod, Deployment, Service, ConfigMap, … |
| metadata | Identity and organization — name, namespace, labels, annotations, uid |
| spec | The desired state you declare — the shape depends on the kind |
| status | The observed state, filled in and continuously updated by the system — you don’t write it |
The API is grouped and versioned. Groups namespace related kinds (apps, batch, networking.k8s.io); versions graduate through alpha → beta → stable (v1). Deprecated API versions are eventually removed, which is a recurring upgrade concern — a manifest using a removed apiVersion will be rejected after an upgrade.
Labels and selectors are the loose-coupling glue of the whole system. A label is an arbitrary key: value on an object’s metadata (app: web, tier: backend). A selector matches objects by their labels. A Service finds its Pods by selector; a Deployment owns the Pods whose labels match its selector. Nothing is wired by hard reference — it’s all matched by labels, which is what makes the system so composable. Annotations look similar (key: value) but are for non-identifying metadata that tools read (build info, config-hash, ingress controller settings); you never select on annotations.
kubectl and kubeconfig
kubectl is the primary CLI — a thin client over the API server. It reads its connection details from a kubeconfig file (default ~/.kube/config), which bundles three lists plus a current selection:
- clusters — API server URLs and their CA certificates.
- users — credentials (client cert, token, or an exec plugin that fetches a cloud token).
- contexts — a named triple of
(cluster, user, namespace). The current-context is the one active now.
Switching context is how you move between clusters or default namespaces without retyping flags:
kubectl config get-contexts # list contexts, * marks current
kubectl config use-context prod-eks # switch clusters
kubectl config set-context --current --namespace=payments # change default ns
Tools like kubectx/kubens make this even faster. Treat kubeconfig as a credential — it grants cluster access.
Namespaces
A namespace is a virtual cluster inside a physical cluster — a scope for names and a boundary for policy. Object names must be unique within a namespace, not across the whole cluster, so web in dev and web in prod coexist. Namespaces are where you attach RBAC (who can do what here), ResourceQuota (how much CPU/memory/objects this namespace may use), and LimitRange (default and max requests/limits per Pod). Every cluster ships with default, kube-system (control-plane add-ons), kube-public, and kube-node-lease. Note that not everything is namespaced: Nodes, PersistentVolumes, Namespaces themselves, and ClusterRoles are cluster-scoped.
Key Concepts
How a kubectl apply request flows through the system
Following one command end-to-end ties every component together. Suppose you run kubectl apply -f deployment.yaml for a 3-replica Deployment:
- kubectl reads the YAML, resolves the API endpoint and credentials from kubeconfig, and sends an HTTP
PATCH/POSTto the kube-apiserver. - The API server authenticates the caller, runs authorization (RBAC: may this user create Deployments here?), then admission control — mutating webhooks may inject defaults (e.g. a sidecar), validating webhooks may reject the object (e.g. “no
:latestimages”). The object is validated against its schema. - The API server persists the Deployment object to etcd. At this point the imperative part is over — nothing has been “run” yet. The object simply exists with a
specand an emptystatus. - The Deployment controller (in kube-controller-manager) is watching Deployments. It sees a new one with 0 ReplicaSets and creates a ReplicaSet object (another write to the API/etcd).
- The ReplicaSet controller sees a ReplicaSet wanting 3 replicas and 0 existing, so it creates 3 Pod objects — still just records in etcd, each with no assigned node (
nodeNameempty). - The scheduler is watching for unscheduled Pods. For each, it filters and scores nodes and writes the chosen
nodeNameback onto the Pod (a binding). - On each chosen node, the kubelet sees a Pod now bound to it, calls the container runtime (via CRI) to pull images and start containers, sets up networking (via CNI), mounts volumes, and starts running probes.
- As containers come up, the kubelet updates each Pod’s status back through the API server into etcd. The ReplicaSet and Deployment controllers observe the statuses and update their own —
status.readyReplicasclimbs to 3, and the Deployment reportsAvailable.
Notice: no component gave orders down a chain. Each watched the API, saw a gap between desired and actual, and acted. That is the reconciliation model in motion.
Example: a minimal manifest
The smallest useful object is a single Pod, but in practice you almost always declare a Deployment (which manages Pods for you). Here is a minimal, readable Deployment plus the Service that exposes it:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec:
replicas: 3 # desired state: keep 3 Pods running
selector:
matchLabels:
app: web # this Deployment owns Pods with app=web
template: # the Pod template stamped out for each replica
metadata:
labels:
app: web # MUST match the selector above
spec:
containers:
- name: web
image: nginx:1.27 # immutable tag — never :latest
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web # route to Pods labelled app=web
ports:
- port: 80
targetPort: 80
The Deployment’s template.metadata.labels must be matched by its spec.selector — that link is what makes the Deployment own those Pods. The Service’s selector independently matches the same label to find endpoints. Change the image to nginx:1.28 and re-apply: the Deployment controller creates a new ReplicaSet and performs a rolling update, all from that one declarative edit.
Core kubectl commands
# Apply / inspect
kubectl apply -f app.yaml # create or update from a manifest
kubectl get pods -o wide # list Pods, with node and IP
kubectl get deploy,svc # multiple kinds at once
kubectl describe pod web-6d4c... # events + config — why is it Pending/CrashLooping?
kubectl get pod web-6d4c... -o yaml # full object incl. status
# Debug
kubectl logs -f deploy/web # stream logs
kubectl logs web-6d4c... --previous # logs from the last crashed container
kubectl exec -it web-6d4c... -- sh # shell into a running container
kubectl get events --sort-by=.lastTimestamp
# Manage
kubectl rollout status deploy/web
kubectl rollout undo deploy/web # roll back to previous ReplicaSet
kubectl scale deploy/web --replicas=5
kubectl delete -f app.yaml
# Context / namespace
kubectl config use-context prod
kubectl get pods -n kube-system
kubectl api-resources # every kind, its apiVersion, and short name
kubectl describe and kubectl get events are your first stop for why something isn’t working — the answer is almost always in the events (image pull failures, failed scheduling, probe failures, OOM kills).
Imperative vs declarative usage
kubectl supports imperative commands (kubectl run, kubectl create deployment, kubectl scale, kubectl edit) and declarative ones (kubectl apply -f). Imperative is convenient for exploration and debugging, but declarative apply against files stored in Git is the production-grade approach: the manifests are your source of truth, changes are reviewable, and the cluster can be rebuilt from them. This is the foundation GitOps builds on.
Best Practices
- Think declaratively, not imperatively. Describe desired state in manifests and let controllers converge; avoid one-off imperative mutations in production. If you can’t reproduce your cluster from files, you don’t really know its state.
- Store all manifests in Git and apply them, don’t edit live.
kubectl apply/GitOps overkubectl edit. The repository is the source of truth; the cluster should reflect a reviewed, committed state. - Understand the API object shape (
apiVersion/kind/metadata/spec/status) before reaching for tools. Helm and Kustomize just generate these objects — you can’t debug what they produce if the primitives are a mystery. - Pin
apiVersiondeliberately and track deprecations. APIs get removed across releases; run tools likeplutoor heedkubectldeprecation warnings so an upgrade doesn’t reject your manifests. - Never use the
latestimage tag. Deploy immutable, versioned tags (or digests) so rollouts, rollbacks, and audits are reproducible and meaningful. - Use namespaces to partition teams and environments, and attach RBAC, ResourceQuota, and LimitRange to each so a mistake in one namespace can’t consume the cluster.
- Apply least-privilege RBAC from day one. Grant narrow Roles bound to specific ServiceAccounts/users; never hand out
cluster-admincasually. - Treat kubeconfig as a secret and use explicit contexts. Name contexts clearly (
prod-eks,staging), confirm the current context before destructive commands, and keep prod credentials separate from dev. - Label everything consistently. Adopt the recommended
app.kubernetes.io/*labels; selectors, dashboards, and cost allocation all depend on a clean, predictable labeling scheme. - Use annotations for metadata, labels for selection. Don’t overload labels with data you’ll never select on (build SHAs, URLs) — that belongs in annotations.
- Protect and back up etcd. It is the entire cluster state; run an odd-sized quorum, encrypt secrets at rest, and take (and test-restore) snapshots — on managed clusters this is the provider’s job, but know where the line is.
- Prefer managed control planes (EKS/GKE/AKS) unless you have a reason not to. Running and patching the API server and etcd yourself is real, ongoing work; let the provider own it and focus on workloads.
- Learn to read events first. When something is stuck,
kubectl describeandkubectl get eventsalmost always contain the reason (ImagePullBackOff, FailedScheduling, OOMKilled) — reach for them before guessing. - Keep the control plane and workloads mentally separate. The scheduler decides where, the kubelet runs, the API server is the only front door — knowing which component owns a behavior makes troubleshooting far faster.
References
- Kubernetes documentation
- What is Kubernetes?
- Kubernetes components
- Kubernetes API concepts
- Understanding Kubernetes objects
- Nodes · Control plane / node communication
- The kubelet · Container Runtime Interface (CRI)
- Namespaces
- Labels and selectors
- Organizing cluster access using kubeconfig files
- kubectl overview & cheat sheet
- etcd documentation
- CNCF landscape
- Book: Kubernetes Up & Running (Hightower, Burns, Beda — O’Reilly)
- roadmap.sh — Kubernetes