Verified9 commandsAI-assisted

Networking & Config

.md

Verified against kubectl v1.34.0 (client), flags verified via `kubectl <cmd> --help` run locally, 2026-08-29 · official docs

Services, ingress, configmaps, secrets, RBAC, and switching between clusters/namespaces via kubeconfig contexts — the surface area that's less "what's the state of my workload" and more "how does traffic reach it, and who's allowed to touch it."

Exposing a deployment as a service#

kubectl expose deployment my-deployment --port=80 --target-port=8080
kubectl expose pod my-pod --port=443 --name=my-frontend
kubectl expose deployment my-deployment --port=80 --type=LoadBalancer   # provision a cloud load balancer, if the cluster supports it
kubectl get services
kubectl get svc my-service -o wide

--port is what the Service listens on; --target-port is the container port traffic gets forwarded to — they're allowed to differ (e.g. exposing 443 externally while the container listens on 8443).

Service typeReachable from
ClusterIP (default)Only inside the cluster
NodePortAny cluster node's IP, on a fixed high port (30000-32767 by default)
LoadBalancerThe internet, via a cloud provider's provisioned load balancer (needs cloud-controller-manager support)
ExternalNameAn external DNS name — the Service becomes a CNAME, no proxying involved at all

Ingress#

kubectl create ingress simple --rule="app.example.com/*=my-service:80"
kubectl create ingress simple --class=nginx --rule="app.example.com/*=my-service:80,tls=my-tls-secret"
kubectl get ingress
kubectl describe ingress simple

--class selects which ingress controller handles the resource (e.g. nginx, alb) — a cluster with no matching IngressClass installed will accept the resource but never actually route traffic for it, which is a common "why isn't this working" trap: the resource looks fine (kubectl get ingress shows it), but nothing is actually listening for it.

ConfigMaps#

kubectl create configmap my-config --from-literal=LOG_LEVEL=info --from-literal=ENV=production
kubectl create configmap my-config --from-file=path/to/config.yaml
kubectl create configmap my-config --from-env-file=.env               # bulk-load an entire .env-style file
kubectl get configmap my-config -o yaml

Secrets 🔒#

kubectl create secret generic my-secret --from-literal=DB_PASSWORD=hunter2
kubectl create secret generic my-secret --from-file=ssh-privatekey=path/to/id_rsa
kubectl create secret docker-registry my-registry-secret --docker-server=registry.example.com --docker-username=me --docker-password=pw
kubectl get secret my-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d   # decode a value for inspection

Warning

Secret values in get -o yaml/json are base64-encoded, not encrypted — base64 is an encoding, not security. Anyone with get secrets RBAC access can decode it in one command. Rely on RBAC (and, for anything sensitive, an external secrets manager or encryption-at-rest with a real KMS-backed provider) for actual protection, not the encoding itself.

Kubeconfig contexts — switching clusters and namespaces#

kubectl config get-contexts                     # list all contexts
kubectl config current-context                  # which one is active
kubectl config use-context my-cluster            # switch active context
kubectl config set-context --current --namespace=my-namespace   # default namespace for the current context
kubectl config view --minify                       # show only the active context's config, not every cluster/user/context in the file
kubectl config delete-context old-cluster
Diagram

Caution

Switching context switches which cluster and credentials kubectl talks to — every command after that is silently pointed at a different cluster. A common, genuinely damaging incident-response mistake is running a delete against the wrong cluster because the active context wasn't checked first, especially when dev/staging/prod contexts are named similarly. kubectl config current-context before anything destructive is cheap insurance — many teams also set a colored/labeled shell prompt (via kube-ps1 or similar) specifically so the active context is always visible, not just checkable on demand.

Port-forwarding to a pod or service#

kubectl port-forward pod/my-pod 8080:80
kubectl port-forward deployment/my-deployment 8080:80
kubectl port-forward service/my-service 8443:https   # target a service's named port
kubectl port-forward pod/my-pod 8080:80 --address=0.0.0.0   # bind on all interfaces, not just localhost — useful from a remote box, riskier on a shared one

RBAC — roles, bindings, and service accounts#

kubectl create serviceaccount my-app-sa
kubectl get serviceaccounts

kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods
kubectl create rolebinding my-app-binding --role=pod-reader --serviceaccount=my-namespace:my-app-sa

kubectl create clusterrolebinding my-app-cluster-binding --clusterrole=view --user=jane@example.com

kubectl get roles,rolebindings                    # namespaced RBAC objects, current namespace
kubectl get clusterroles,clusterrolebindings        # cluster-scoped RBAC objects

A Role/RoleBinding pair grants permissions within one namespace; ClusterRole/ClusterRoleBinding grants cluster-wide (or, if bound via a namespaced RoleBinding, a ClusterRole's rules scoped to just that namespace — useful for reusing a built-in role like view/edit/admin without redefining it per namespace).

kubectl auth can-i create pods --namespace=my-namespace                       # am I allowed to?
kubectl auth can-i list pods --as=system:serviceaccount:my-namespace:my-app-sa   # check as a specific service account
kubectl auth can-i '*' '*' --as=system:serviceaccount:kube-system:my-operator    # a quick "is this effectively cluster-admin" sanity check

Tip

auth can-i is the fastest way to debug a Forbidden error — it evaluates the same RBAC rules the API server would, without you having to trace through Role/RoleBinding YAML by hand across every namespace a permission might be granted in.

Real-world scenario: least-privilege service account for a CI deployer#

A CI pipeline needs to deploy into one namespace and nothing else — over-scoping this to cluster-admin (a common shortcut) is a real blast-radius risk if the CI token ever leaks:

kubectl create serviceaccount ci-deployer -n staging
kubectl create role deployer --verb=get,list,watch,create,update,patch,delete \
  --resource=deployments,services,configmaps,secrets -n staging
kubectl create rolebinding ci-deployer-binding --role=deployer \
  --serviceaccount=staging:ci-deployer -n staging

# Verify the scope is actually what was intended before wiring it into CI:
kubectl auth can-i delete deployments --as=system:serviceaccount:staging:ci-deployer -n staging   # yes
kubectl auth can-i delete deployments --as=system:serviceaccount:staging:ci-deployer -n production   # should be no

Verifying with auth can-i in both the intended namespace and an unintended one is the step that actually confirms the RBAC scoping worked, rather than trusting the create role/create rolebinding commands succeeded without error — a typo in --serviceaccount=<namespace>:<name> (wrong namespace) is an easy mistake that still exits 0.

Common pitfalls#

  • Not checking current-context before a destructive command — see the CAUTION above.
  • Assuming a Secret is "secure" because it's not plaintext — see the WARNING above.
  • An Ingress with no matching IngressClass installed — the resource is accepted but silently does nothing.
  • Scoping a CI service account too broadly "to save time" — see the real-world scenario above for the actual verification step that catches this before it ships.

When to reach for something else#

For managing RBAC/network policy at scale across many namespaces, hand-running create role/create rolebinding per namespace doesn't scale — most real platforms template these via Helm/Kustomize or a policy-as-code tool (see this site's Security & Compliance section for OPA/Conftest, which can enforce RBAC conventions cluster-wide rather than relying on each team getting the imperative commands right by hand).