Kubernetes Administration: kubectl, RBAC, Namespaces, and Resource Quotas
Kubernetes administration is the day-to-day management of a cluster through its API: driving it with kubectl, controlling who can do what with RBAC, dividing it into namespaces, and bounding consumption with ResourceQuotas and LimitRanges. Everything an administrator does - creating objects, granting access, inspecting failures - is ultimately an API request to the kube-apiserver, so understanding that single entry point unlocks the whole topic. KCNA tests it mostly at recognition depth, with some scenarios that combine two facts: which RBAC object grants permissions cluster-wide, why a permission on pods does not cover kubectl logs, what kubectl apply does differently from kubectl create, which component is broken when Pods stay Pending, and what a ResourceQuota limits versus a LimitRange. This lesson covers kubectl and its path to the API server, the cluster components through an administrator's eyes, declarative versus imperative management, the four RBAC objects with their subjects, API groups, verbs, and subresources, and the namespace-level guardrails that keep shared clusters fair.
On this page7 sections
- kubectl: the client for the Kubernetes API
- The cluster components through an administrator's eyes
- Declarative kubectl apply vs imperative commands
- RBAC: Roles, ClusterRoles, and their bindings
- Subjects, ServiceAccounts, verbs, and resources
- Namespaces as scope and isolation boundaries
- ResourceQuotas and LimitRanges: keeping shared clusters fair
- Explain how kubectl communicates with the kube-apiserver, how logs and exec reach the kubelet, and what a kubeconfig context holds
- Localise a failure to the component that owns it, and explain why etcd runs with an odd number of members
- Distinguish declarative kubectl apply from imperative commands and explain how imperative changes drift from the manifests
- Identify the four RBAC objects, read a binding's roleRef and subjects, and match Roles and ClusterRoles to the correct binding
- Read an RBAC rule - API group, resource, subresource, and verbs - and test it with kubectl auth can-i
- Explain what namespaces, ResourceQuotas, and LimitRanges bound, and what they leave untouched
kubectl: the client for the Kubernetes API
kubectl is the command-line client for the Kubernetes API. Every command you type - kubectl get pods, kubectl apply, kubectl delete - is translated into an HTTPS request to the kube-apiserver. kubectl never connects to nodes, the kubelet, or etcd directly; the API server authenticates the request, checks authorization (RBAC), validates it, and acts on the cluster state. This single-entry-point design is why access control, auditing, and validation can all live in one place.
kubectl finds the cluster through a kubeconfig file, by default at ~/.kube/config. A kubeconfig holds three kinds of entries: clusters (API server addresses and certificate data), users (credentials), and contexts, which pair a cluster with a user and optionally a default namespace. Switching contexts with kubectl config use-context is how one workstation manages several clusters. Two details matter: the command writes current-context into the kubeconfig file itself, so every terminal using that file switches too (a per-shell choice needs the --context flag or a separate file via KUBECONFIG); and because a context bundles cluster, user, and default namespace, switching it changes all three at once.
The everyday verbs are worth recognizing on sight: kubectl get lists objects, kubectl describe shows detailed state plus recent events (the first stop when debugging), kubectl logs prints a container's output, kubectl exec runs a command inside a running container, and kubectl explain documents the fields of any resource type. Flags follow a consistent pattern: -n selects a namespace, -A spans all namespaces, -l filters by label selector, and -o yaml or -o wide change the output format. Knowing what each verb returns is the skill to build: events live in describe, container output in logs, field documentation in explain, and permission answers in auth can-i.
logs and exec take a longer path. The API server still authenticates and authorizes them, but it does not hold container output, so it opens a connection to the kubelet API on the Pod's node (TCP port 10250 by default) and relays the stream back. That explains two distinct failures. If the control plane cannot reach one node's kubelet (a firewall or security group blocking 10250), kubectl get pods still works - that data comes from the API server - but kubectl logs and exec for Pods on that node time out with a dial error naming the node's IP and port 10250. If instead the user lacks permission, the API server answers immediately with Forbidden, for every node alike. The error class tells you whether to look at the network path or at RBAC.
The cluster components through an administrator's eyes
You have met the components as architecture; an administrator meets them as failure domains. Each component owns one job, so each has a characteristic symptom when it is unhealthy, and exam questions often describe the symptom and ask for the component.
| Component | Admin-lens job | Typical symptom when unhealthy |
|---|---|---|
| kube-apiserver | Serves the API; the door every request passes through | kubectl commands fail entirely; nothing can be changed |
| etcd | Persists all cluster state | API reads and writes fail or the cluster loses its memory of desired state |
| kube-scheduler | Assigns new Pods to nodes | New Pods stay Pending with no node assigned |
| kube-controller-manager | Runs the reconciliation controllers | Desired state stops being enforced; failed Pods are not replaced |
| kubelet | Node agent that starts and monitors Pods on its node | The node goes NotReady; its Pods stop being managed |
| kube-proxy | Programs Service routing rules on each node | Pods run but Service traffic does not reach them |
Localising a failure means following the chain of handoffs. Creating a Deployment is a write to the API server; the Deployment controller (in kube-controller-manager) then creates a ReplicaSet, and the ReplicaSet controller creates Pods; the scheduler binds each Pod to a node; that node's kubelet starts the containers; and each node's kube-proxy programs Service routing. So the place the chain stops names the broken component. A Deployment that exists with no ReplicaSet or Pods at all points at the controller manager - nothing created them. Pods that exist but stay Pending with no node assigned point at the scheduler (or at a genuine lack of fitting nodes, which kubectl describe pod events reveal). Pods that are bound but never start point at that node's kubelet or container runtime. And when a Service's ClusterIP fails only from one node while Pod IPs answer directly, the Pods and the Service object are fine - the routing rules on that node are not, which points at that node's kube-proxy.
etcd and quorum. Cluster state lives in etcd, so backing up etcd (a snapshot) is the canonical way to back up a cluster. etcd uses the Raft consensus protocol and accepts writes only while a majority of members - the quorum, floor(n/2)+1 - can agree. That arithmetic is the real reason for odd member counts:
| Members | Quorum (majority) | Failures tolerated |
|---|---|---|
| 1 | 1 | 0 |
| 3 | 2 | 1 |
| 4 | 3 | 1 |
| 5 | 3 | 2 |
A fourth member adds no fault tolerance over three (both survive one loss) while adding a machine that must take part in every write, so even counts cost more for nothing. Odd counts are not about breaking tied leader elections - Raft resolves split votes with randomized election timeouts. When quorum is lost, etcd stops accepting writes, so the API server can no longer change cluster state.
Finally, in managed clusters (EKS, AKS, GKE) the provider runs the control plane, so an administrator's hands-on scope narrows to worker nodes, workloads, and the API - one reason the exam favors conceptual questions over operational ones.
A quick health picture comes from commands like kubectl get nodes, kubectl get pods -n kube-system, and kubectl describe node NODE_NAME - reading conditions and events rather than logging into machines.
Declarative kubectl apply vs imperative commands
There are two styles of driving the API, and KCNA expects you to tell them apart. Imperative commands state an action: kubectl create deployment web --image=nginx, kubectl run tmp --image=busybox, kubectl scale deployment web --replicas=5, kubectl expose deployment web --port=80. They are quick for experiments, but the resulting configuration exists only in the live cluster - there is no file to review, version, or reapply.
Declarative management states a destination: you keep manifests in files and run kubectl apply -f manifest.yaml (or -f a whole directory). Apply creates the object if it is missing and updates it if it drifted, so the same command is safe to run repeatedly - it is idempotent. Because the manifests are plain files, they can live in Git, go through code review, and be applied by automation; this is the foundation of the GitOps pattern of cluster management.
| Imperative (create, run, scale, expose) | Declarative (apply -f) | |
|---|---|---|
| You specify | An action to perform now | The desired end state |
| Repeat the command | Errors or duplicates (already exists) | Converges; safe to re-run |
| Source of truth | The live cluster only | Versioned manifest files |
| Best for | Experiments, quick debugging | Production, teams, automation |
When manifests in a repository are the source of truth, kubectl apply is what keeps the cluster matching them, and it succeeds where kubectl create fails with an already-exists error because it computes and patches the difference instead of insisting on creating something new.
Mixing the two styles causes drift. Client-side kubectl apply records the configuration it applied in a kubectl.kubernetes.io/last-applied-configuration annotation on the object (kubectl apply view-last-applied prints it), and compares it with the manifest and the live object on the next run. An imperative change such as kubectl scale --replicas=8 or kubectl edit alters only the live object. If the manifest sets that field - say replicas: 3 - the next kubectl apply sets it back to 3, silently undoing the manual change. The durable fix is to change the manifest, or to remove the field from the manifest when something else (such as a HorizontalPodAutoscaler) should own it. kubectl diff -f previews what an apply would change.
Imperative commands still have one declarative trick worth knowing: --dry-run=client -o yaml prints the manifest an imperative command would create, which is a fast way to scaffold a file you then manage with apply.
RBAC: Roles, ClusterRoles, and their bindings
Role-Based Access Control (RBAC) is how Kubernetes decides who may do what. It is built from exactly four API objects, in two pairs. A Role is a named list of permissions that applies within a single namespace. A ClusterRole is the same idea without the namespace boundary: it can grant access across all namespaces, or to cluster-scoped resources such as nodes and PersistentVolumes that no namespaced Role could ever cover. Neither object names any people - roles define what can be done, not who can do it.
Bindings supply the who. A RoleBinding grants the permissions of a role to a set of subjects within one namespace; a ClusterRoleBinding grants a ClusterRole's permissions across the entire cluster. One combination surprises people and is beloved by exam writers: a RoleBinding may reference a ClusterRole, which grants that ClusterRole's permissions only inside the binding's namespace. This lets administrators define a permission set once (say, a generic developer ClusterRole) and bind it per namespace, instead of copying identical Roles everywhere. The reverse limit matters just as much: because a RoleBinding is namespaced, a ClusterRole bound through RoleBindings only ever reaches namespaced resources - however many namespaces receive a RoleBinding, it never grants access to cluster-scoped resources such as Nodes or PersistentVolumes. Those need a ClusterRoleBinding.
Every binding has the same anatomy: a roleRef that names exactly one Role or ClusterRole (and cannot be changed after creation - you delete and recreate the binding), and a subjects list of User, Group, or ServiceAccount entries. Roles and ClusterRoles only ever appear in roleRef; they are never subjects.
| Object | Scope | What it does |
|---|---|---|
| Role | One namespace | Defines permissions on namespaced resources |
| ClusterRole | Cluster-wide | Defines permissions cluster-wide or on cluster-scoped resources |
| RoleBinding | One namespace | Grants a Role or ClusterRole to subjects in that namespace |
| ClusterRoleBinding | Cluster-wide | Grants a ClusterRole to subjects across all namespaces |
Two properties define RBAC's character. It is additive and allow-only: there are no deny rules, permissions accumulate across bindings, and anything not explicitly granted is forbidden. And it defaults closed, which makes least privilege the natural practice: start from nothing and grant only the verbs and resources a subject genuinely needs.
No deny rules has a practical consequence: you cannot carve an exception out of a broad grant. If a team holds a ClusterRole through a ClusterRoleBinding (all namespaces) and must lose access to one namespace, nothing can be added to subtract it. The fix is to remove the ClusterRoleBinding and grant the same ClusterRole through a RoleBinding in each namespace the team should keep - and to automate creating that binding for new namespaces, since a per-namespace grant no longer covers them automatically.
Subjects, ServiceAccounts, verbs, and resources
Every RBAC rule answers three questions: who (the subject), may do what (the verbs), to which objects (the resources). Subjects come in three kinds: Users and Groups, which represent humans and are not Kubernetes API objects (they come from certificates or an external identity provider - there is no kubectl create user), and ServiceAccounts, which are API objects and represent workloads. Every namespace has a default ServiceAccount, every Pod runs as some ServiceAccount, and a Pod that needs to call the API should get its own ServiceAccount bound to a narrowly scoped Role.
Verbs are the actions the API server recognizes: get, list, and watch for reading; create, update, patch, and delete for writing. Resources are the object types the verbs act on - pods, deployments, services, secrets - and every rule names the API group they belong to. apiGroups: [""] (an empty string) is the core group: Pods, Services, ConfigMaps, Secrets, ServiceAccounts, Namespaces, and Nodes. Deployments, ReplicaSets, StatefulSets, and DaemonSets live in the apps group, Jobs and CronJobs in batch, and Roles and bindings in rbac.authorization.k8s.io. A rule listing deployments under the core group matches nothing, because no core resource has that name. A Role that lets its holder read Pods in the dev namespace looks like this:
apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: dev name: pod-reader rules: - apiGroups: [""] resources: ["pods"] verbs: ["get", "list", "watch"]
Subresources are authorized separately. Some operations act on a sub-path of an object, written resource/subresource in a rule. kubectl logs is a get on pods/log; kubectl exec needs create on pods/exec (and kubectl port-forward needs create on pods/portforward). A rule on pods does not cover any of them, so the pod-reader Role above lets its holder list Pods but answers Forbidden to kubectl logs until pods/log is added to its resources. Scaling a Deployment likewise touches deployments/scale.
A RoleBinding then attaches the Role to a subject - a user, a group, or a ServiceAccount. Inside the API, a ServiceAccount authenticates as the username system:serviceaccount:<namespace>:<name> and is also a member of the groups system:serviceaccounts (every ServiceAccount) and system:serviceaccounts:<namespace> (every ServiceAccount in that namespace), so bindings to those groups apply to it too.
To check the outcome, administrators use kubectl auth can-i, for example kubectl auth can-i list pods -n dev. With --as it impersonates another identity, and the exact string matters. --as=system:serviceaccount:build:ci-bot tests the ServiceAccount ci-bot from the build namespace, including its group memberships; -n prod separately sets the namespace whose permissions are being tested, so the two namespaces can differ. --as=ci-bot without the prefix impersonates an ordinary user named ci-bot, so the ServiceAccount's bindings and groups are never evaluated and the answer describes a different identity. Remember the human/workload split: ServiceAccounts for Pods, users and groups for people.
Namespaces as scope and isolation boundaries
For an administrator, a namespace is the unit of tenancy: the boundary at which access is granted, quotas are enforced, and blast radius is contained. Object names must be unique only within their namespace, so multiple teams can use the same conventional names side by side. Policy objects attach at the same seam - a RoleBinding grants access within its namespace, and a ResourceQuota caps consumption within its namespace - so putting each team or environment in its own namespace gives each a private naming scope with tailored permissions and limits.
Not everything lives inside a namespace. Namespaced resources include Pods, Deployments, Services, ConfigMaps, Secrets, Roles, and RoleBindings. Cluster-scoped resources include Nodes, PersistentVolumes, ClusterRoles, ClusterRoleBindings, and namespaces themselves. You can ask the API which is which: kubectl api-resources --namespaced=true and --namespaced=false. This split is exactly why ClusterRoles exist - no namespaced Role could grant access to a Node.
Day to day, you select a namespace per command with -n, span all of them with -A, or set a default in your kubeconfig context with kubectl config set-context --current --namespace=dev. Deleting a namespace deletes every namespaced object in it - Pods, Deployments, Secrets, ServiceAccounts, Roles, RoleBindings - which makes namespaces convenient for disposable environments and dangerous to delete casually. Cluster-scoped objects are not in the namespace, so they survive even when they refer to it: a ClusterRoleBinding whose subject is a ServiceAccount in the deleted namespace stays behind and must be cleaned up separately (and would grant its access again to a same-named ServiceAccount if the namespace were recreated).
Know the limits of the boundary. Namespaces isolate names, access, and quota accounting - they do not isolate the network. With no NetworkPolicy in place, any Pod can reach any Pod in any namespace directly by its IP (or through a Service's cross-namespace DNS name); restricting that requires NetworkPolicies. Nor do namespaces isolate nodes: Pods from every namespace are scheduled onto the same machines unless scheduling rules (node selectors, affinity, taints and tolerations) keep them apart, and they compete for the same node capacity - which is precisely the problem quotas address next.
Tip. Administration questions pair concepts with their scope and then add a twist. Expect to match RBAC objects to their reach (Role and RoleBinding to one namespace, ClusterRole and ClusterRoleBinding to the whole cluster, and a ClusterRole through a RoleBinding to one namespace's namespaced resources only), kubectl apply to declarative and create, run, and scale to imperative, and ResourceQuota to namespace totals versus LimitRange to per-container bounds. Harder scenarios combine two facts: how subresources are authorized separately from their parent resource, how etcd quorum size limits fault tolerance, which node-level component explains a failure seen on only one node, how impersonation tests permissions for users and ServiceAccounts, and how to grant broadly when RBAC has no deny rules. kubectl only ever talks to the kube-apiserver.
- Every kubectl command is a request to the kube-apiserver; for logs and exec the API server relays to the kubelet on the Pod's node (port 10250), so a blocked path to one kubelet breaks only those commands.
- kubectl config use-context rewrites current-context in the kubeconfig file and switches cluster, user, and namespace together.
- kubectl apply is declarative and idempotent; an imperative scale or edit is reverted by the next apply of a manifest that sets that field.
- Localise failures along the chain: no ReplicaSet or Pods means the controller manager, Pending Pods the scheduler, a ClusterIP failing on one node that node's kube-proxy.
- etcd needs a majority (floor(n/2)+1): 3 and 4 members both tolerate one failure, 5 tolerate two - hence odd counts. Back up etcd to back up the cluster.
- A Role grants permissions in one namespace; a ClusterRole via a ClusterRoleBinding reaches every namespace and cluster-scoped resources, but via RoleBindings never reaches cluster-scoped ones like Nodes.
- RBAC is additive and allow-only: no deny rules, so excluding one namespace means replacing a ClusterRoleBinding with per-namespace RoleBindings.
- Rules name API groups ("" is core; Deployments are in apps) and subresources separately: logs needs get on pods/log, exec needs create on pods/exec.
- Test a ServiceAccount with --as=system:serviceaccount:<namespace>:<name>; a bare name impersonates a user instead.
- A ResourceQuota caps a namespace's totals at admission and is not retroactive; a LimitRange sets per-container defaults, minimums, and maximums.
- Namespaces scope names, access, and quotas, but not the network or the nodes; deleting one does not remove cluster-scoped objects such as ClusterRoleBindings that reference it.
Frequently asked questions
What is the difference between a Role and a ClusterRole?
A Role defines permissions within a single namespace and can only cover namespaced resources there. A ClusterRole is not bound to a namespace: it can grant permissions across all namespaces or on cluster-scoped resources such as nodes and PersistentVolumes. A ClusterRole can also be referenced by a RoleBinding, which applies its permissions inside just that binding's namespace - a common pattern for reusing one permission set across many namespaces.
What is the difference between kubectl apply and kubectl create?
kubectl create is imperative: it makes a new object and fails if the object already exists. kubectl apply is declarative: it reads a manifest describing desired state, creates the object if it is missing, and patches it if it exists, so the same command can be run repeatedly and safely. Apply is the standard for production because the manifest files become a reviewable, version-controlled source of truth.
What is a ServiceAccount in Kubernetes?
A ServiceAccount is the identity a workload uses when it calls the Kubernetes API. Unlike users and groups, which represent humans and are managed outside Kubernetes, ServiceAccounts are real API objects that live in a namespace. Every namespace has a default ServiceAccount, every Pod runs as one, and a Pod that needs API access should get a dedicated ServiceAccount bound to a narrowly scoped Role via a RoleBinding.
What is the difference between a ResourceQuota and a LimitRange?
A ResourceQuota caps the total resource consumption of a whole namespace - the sum of CPU and memory requests and limits, and counts of objects like Pods or Services. A LimitRange constrains individual containers or Pods in that namespace: it sets default requests and limits for containers that omit them, and minimum and maximum bounds for those that declare them. Quota is the namespace budget; LimitRange is the per-container guardrail.
Does kubectl talk directly to etcd or to the nodes?
No. kubectl only communicates with the kube-apiserver over HTTPS. The API server authenticates and authorizes the request, then reads or writes cluster state in etcd itself; it is the only component with direct access to etcd. Commands that need data from a node, such as kubectl logs and kubectl exec, still go to the API server, which then connects to the kubelet API on the Pod's node (port 10250 by default) and relays the stream back - so a blocked control-plane-to-kubelet path breaks logs and exec for that node while kubectl get keeps working.
Do namespaces isolate network traffic between teams?
Not by default. Namespaces isolate names, RBAC scope, and quota accounting, but with no NetworkPolicy any Pod can reach a Pod in another namespace directly by IP; restricting that requires NetworkPolicies. They also do not partition the underlying machines - Pods from all namespaces share the same nodes unless scheduling rules such as node affinity or taints separate them, which is why ResourceQuotas exist to keep consumption fair.
Why does kubectl logs return Forbidden when I can list Pods?
Because kubectl logs is authorized as a get on the pods/log subresource, which RBAC checks separately from the pods resource. A Role whose rule lists only pods with get, list, and watch lets you see Pods but not read their logs; adding pods/log to the rule's resources fixes it. kubectl exec works the same way through the pods/exec subresource, which needs the create verb.
Why should etcd have an odd number of members?
etcd only accepts writes while a majority of members, floor(n/2)+1, agree. Three members need two and tolerate one failure; four need three and still tolerate only one; five need three and tolerate two. An even count therefore adds cost and another machine in every write without adding fault tolerance. The reason is this majority arithmetic, not tied leader elections, which Raft resolves with randomized timeouts.
Source
This lesson covers the "Kubernetes Fundamentals" domain of the official KCNA exam guide. Vendors revise their guides — check the source for the current version.
- KCNA certification page and curriculum — The Linux Foundation
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.