Networking & Config
.mdVerified 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 type | Reachable from |
|---|---|
ClusterIP (default) | Only inside the cluster |
NodePort | Any cluster node's IP, on a fixed high port (30000-32767 by default) |
LoadBalancer | The internet, via a cloud provider's provisioned load balancer (needs cloud-controller-manager support) |
ExternalName | An 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 yamlSecrets 🔒#
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 inspectionWarning
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-clusterCaution
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 oneRBAC — 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 objectsA 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 checkTip
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 noVerifying 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-contextbefore 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).