By: Scott Coulton, Microsoft Developer Advocate, CNCF Ambassador, and Docker Captain
When you start your Kubernetes journey, the first thing you need to learn is how to deploy an application. There are multiple resource types that you can use to do this, including Pods, deployments, and Services. Navigating through these resources might feel overwhelming, but understanding these resources is essential to building scalable and resilient applications. In this blog post, we’ll begin with an overview of Kubernetes, explore its fundamental resources, and explain what each resource does, when to use it, and why it matters.
The course covers key Kubernetes concepts, including pods, services, and deployments. Once you become proficient in these important areas, you will learn about Kubernetes architecture, building clusters, and deploying and managing applications. In the last part of the course, you explore threat modeling and real-world security practices. By completing the course, you can effectively use Kubernetes in your projects.
A pod is the lowest unit of an application in Kubernetes. Now, before we move on, we need to get one thing straight — and that is a pod is not equal to a container in the Docker world. A pod can be made up of multiple containers. If you have come from a pure Docker background, this can be hard to wrap your head around. If a pod can have more than one container, how does it work? There are some limits we need to be aware of. A pod has the following:
A single IP address
Share localhost
A shared IPC space
A shared network port range
Shared volumes
The containers in a pod talk to each other via local host, whereas pod-to-pod communication is done via services. Pods are a great way for you to deploy an application, but there is some limitation to the pod resource type. A pod is a single entity, and if it fails, it cannot restart itself; this won’t suit most use cases, as we want our applications to be highly available. Don’t worry, Kubernetes has this issue solved, and we will look at how to tackle high availability further on in the post.
If we want to have connectivity to our pods, we will need to create a service. In Kubernetes, a service is a network abstraction over a set of pods. This allows for the traffic to be load-balanced for failures. A service allows Kubernetes to set a single DNS record for the pods. As we mentioned earlier, each pod has a separate IP address. With the service resource type, you would usually define a selector like the example below:
In addition to this, kube-proxy also creates a virtual IP in the cluster to access the service. This virtual IP then routes to the pod IPs. If the pod IPs change or new pods are deployed, the service resource type will track the change and update the internal routing on your behalf.
Modern Kubernetes Services offer more than just exposing pods.
They provide advanced routing and networking features that help applications scale and perform reliably.
Service types:
Use ClusterIP for internal communication, NodePort for exposing apps on each node,LoadBalancer for cloud-managed load balancers, and ExternalName for aliasing external resources.
Headless Services:
Set clusterIP: None to directly expose pod DNS names — ideal for stateful apps like databases.
internalTrafficPolicy:
Restrict traffic to node-local endpoints to reduce latency and cross-node traffic.
SessionAffinity:
Enable sticky sessions for stateful web applications.
EndpointSlice:
A modern service discovery mechanism replacing legacy Endpoints, improving scalability and performance.
These options give you fine-grained control over service behavior and networking performance.
Now in the pod section of the post, we discovered that pods are mortal, and if they die, that is the end of them. What if you want to have three versions of the same pod running for availability?
Enter the replication controller.
The main responsibility for the replication controller is to prevent against failure, and it sits above the pod resource type and controls it. Let’s look at an example. I want to deploy 4 of my pod x, this time I would create a replica set. A replica set has a defined number of pods that needs to be running, in this case 4. If one of the pods fails or dies, the replication controller will start a new pod for me and again, I will have 4 of pod x running. So, this functionality looks after the issue we mentioned earlier about pods being mortal. Now, a replica set sounds like we have everything we need, right? What about the lifecycle of the pods? Say we want a new version, how do we upgrade the pods without downtime? A replication controller does not look after this.
Ingress was once the default for HTTP routing, but the Gateway API is now the future of Kubernetes networking.
It’s richer, more extensible, and better suited for modern, multi-team environments.
Ingress: Still great for simple host/path-based routing.
Gateway API: Provides HTTPRoute, TCPRoute, and GRPCRoute, with traffic splitting, delegation, and cross-namespace routing.
Example HTTPRoute:
apiVersion: gateway.networking.k8s.io/v1beta1kind: HTTPRoutemetadata:name: my-app-routespec:parentRefs:- name: my-gatewayrules:- matches:- path:type: PathPrefixvalue: "/api"backendRefs:- name: my-serviceport: 80
This richer model is quickly becoming the standard for Kubernetes networking.
So, we have learnt about pods, services, replica sets, and, now the last piece of the puzzle: deployments.
The deployment resource type sits above a replica set and can manipulate them. Why would we want to manipulate a replica set? Replica sets are all or nothing. If you need to do an upgrade, you need to replace the replica set. This action will cause downtime to your application.
One of the main benefits of Kubernetes is high availability. Deployments give us the functionality to do upgrades without downtime. As you do in a replica set, you specify the number of pods you would like to run. Once you trigger an update a deployment will do a rolling upgrade on the pods, all while making sure the upgrade is successful on the pod before moving to the next one. This is awesome!
So, this sounds like we have what we need to deploy and upgrade our application on Kubernetes. But what happens if we rollout a new version of our application and something goes wrong? Deployments have us covered there as well, as we can just as easily rollback a deployment. There is one caveat to this: if you are using a pvc (persistent volume claim) and have written something to the claim. That will not be rolled back. The other thing to remember is that deployments control replica sets and replica sets control pods; this means that when you use a deployment resource type, you can’t forget that you still need a service to access it.
In a production environment the recommendation would be to use deployments for our applications within Kubernetes.
In saying that, I still think it’s important knowledge to have in your tool box to understand how deployments work and the other resource types that it controls.
A simple deployment isn’t enough for production.
Use deployment strategies to ensure smooth updates and reduce risks.
RollingUpdate: Default strategy. Tune maxSurge and maxUnavailable for controlled rollouts.
Blue/Green Deployments: Deploy a new version alongside the old one and switch traffic after validation.
Canary Deployments: Gradually shift traffic to a new version while monitoring metrics.
Horizontal Pod Autoscaler (HPA v2): Scale based on CPU, memory, or custom metrics for adaptive performance.
These strategies enhance uptime, safety, and deployment reliability in production.
Your deployment is only reliable if pods remain healthy and available.
Use Kubernetes primitives to keep them that way:
Probes: Add liveness, readiness, and startup probes to detect issues early.
PodDisruptionBudget (PDB): Limit how many pods can be disrupted during maintenance.
unhealthyPodEvictionPolicy: Prevent healthy pods from being evicted before unhealthy ones.
Topology Spread Constraints: Spread pods across zones and nodes to avoid single points of failure.
These features ensure your workloads remain resilient and production-ready.
Deployments are the most common workload type, but they’re not the only option.
Choosing the right one ensures reliability and proper lifecycle management.
StatefulSet: For apps needing stable network IDs or persistent volumes.
DaemonSet: For workloads that must run on every node (e.g., log collectors, monitoring agents).
Job / CronJob: For one-time or scheduled batch processing tasks.
Use a decision matrix to choose the best workload type for your application’s requirements.
Running workloads is only half the story — once your apps are live, you’ll need to debug, observe, and optimize them.
Ephemeral containers: Attach a temporary debugging container with kubectl debug.
Resource requests and limits: Prevent OOM kills and throttling by tuning properly.
QoS classes: Understand how Kubernetes prioritizes pods under resource pressure.
Diagnostics: Use kubectl describe, logs, and events to troubleshoot issues quickly.
These tools and practices help you manage real-world production clusters with confidence.
About: Scott Coulton
Scott Coulton is a Developer Advocate, CNCF Ambassador, and Docker Captain with 10 years of experience as a software engineer in the managed services and hosting space. He has extensive experience in architecture and rolling out distributed compute systems and network solutions for national and multinational companies with a wide variety of technologies, including Azure, Kubernetes, Puppet, Docker, Cisco, VMware, Microsoft, and Linux. His design strengths are in cloud computing, automation, and security space.