TL;DR: here’s a diagram to help you debug your deployments in Kubernetes (and you can download it in the PDF version here).
When you wish to deploy an application in Kubernetes, you usually define three components:
Here’s a quick visual recap.
In Kubernetes your applications are exposed through two layers of load balancers: internal and external.
The internal load balancer is called Service, whereas the external one is called Ingress.
Pods are not deployed directly. Instead, the Deployment creates the Pods and whatches over them.
Assuming you wish to deploy a simple Hello World application, the YAML for such application should look similar to this:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
labels:
track: canary
spec:
selector:
matchLabels:
any-name: my-app
template:
metadata:
labels:
any-name: my-app
spec:
containers:
- name: cont1
image: learnk8s/app:1.0.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- port: 80
targetPort: 8080
selector:
name: app
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- http:
paths:
- backend:
serviceName: app
servicePort: 80
path: /
The definition is quite long, and it’s easy to overlook how the components relate to each other.
For example:
Before focusing on the debugging, let’s recap how the three components link to each other.
Let’s start with Deployment and Service.
The surprising news is that Service and Deployment aren’t connected at all.
Instead, the Service points to the Pods directly and skips the Deployment altogether.
So what you should pay attention to is how the Pods and the Service are related to each other.
You should remember three things:
targetPort
should match the containerPort
of the container inside the Podport
can be any number. Multiple Services can use the same port because they have different IP addresses assigned.The following diagram summarises the how to connect the ports:
Consider the following Pod exposed by a Service.
When you create a Pod, you should define the port containerPort for each container in your Pods.
When you create a Service, you can define a port and a targetPort. But which one should you connect to the container?
targetPort and containerPort should always match.
If your container exposes port 3000, then the targetPort should match that number.
If you look at the YAML, the labels and ports
/targetPort
should match:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
labels:
track: canary
spec:
selector:
matchLabels:
any-name: my-app
template:
metadata:
labels:
any-name: my-app
spec:
containers:
- name: cont1
image: learnk8s/app:1.0.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- port: 80
targetPort: 8080
selector:
any-name: my-app
What about the track: canary
label at the top of the Deployment?
Should that match too?
That label belongs to the deployment, and it’s not used by the Service’s selector to route traffic.
In other words, you can safely remove it or assign it a different value.
And what about the matchLabels
selector?
It always has to match the Pod labels and it’s used by the Deployment to track the Pods.
Assuming that you made the correct change, how do you test it?
You can check if the Pods have the right label with the following command:
kubectl get pods --show-labels
Or if you have Pods belonging to several applications:
kubectl get pods --selector any-name=my-app --show-labels
Where any-name=my-app
is the label any-name: my-app
.
Still having issues?
You can also connect to the Pod!
You can use the port-forward
command in kubectl to connect to the Service and test the connection.
kubectl port-forward service/<service name> 3000:80
Where:
service/<service name>
is the name of the service — in the current YAML is “my-service”port
fieldIf you can connect, the setup is correct.
If you can’t, you most likely misplaced a label or the port doesn’t match.
The next step in exposing your app is to configure the Ingress.
The Ingress has to know how to retrieve the Service to then retrieve the Pods and route traffic to them.
The Ingress retrieves the right Service by name and port exposed.
Two things should match in the Ingress and Service:
servicePort
of the Ingress should match the port
of the ServiceserviceName
of the Ingress should match the name
of the ServiceThe following diagram summarises how to connect the ports:
You already know that the Service expose a port.
The Ingress has a field called servicePort.
The Service port and the Ingress servicePort should always match.
If you decide to assign port 80 to the service, you should change servicePort to 80 too.
In practice, you should look at these lines:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- port: 80
targetPort: 8080
selector:
any-name: my-app
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: my-ingress
spec:
rules:
- http:
paths:
- backend:
serviceName: my-service
servicePort: 80
path: /
How do you test that the Ingress works?
You can use the same strategy as before with kubectl port-forward
, but instead of connecting to a service, you should connect to the Ingress controller.
First, retrieve the Pod name for the Ingress controller with:
kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS
kube-system coredns-5644d7b6d9-jn7cq 1/1 Running
kube-system etcd-minikube 1/1 Running
kube-system kube-apiserver-minikube 1/1 Running
kube-system kube-controller-manager-minikube 1/1 Running
kube-system kube-proxy-zvf2h 1/1 Running
kube-system kube-scheduler-minikube 1/1 Running
kube-system nginx-ingress-controller-6fc5bcc 1/1 Running
Identify the Ingress Pod (which might be in a different Namespace) and describe it to retrieve the port:
kubectl describe pod nginx-ingress-controller-6fc5bcc \
--namespace kube-system \
| grep Ports
Ports: 80/TCP, 443/TCP, 18080/TCP
Finally, connect to the Pod:
kubectl port-forward nginx-ingress-controller-6fc5bcc 3000:80 --namespace kube-system
At this point, every time you visit port 3000 on your computer, the request is forwarded to port 80 on the Ingress controller Pod.
If you visit http://localhost:3000, you should find the app serving a web page.
Here’s a quick recap on what ports and labels should match:
targetPort
should match the containerPort
of the container inside the PodservicePort
of the Ingress should match the port
in the ServiceserviceName
in the IngressKnowing how to structure your YAML definition is only part of the story.
What happens when something goes wrong?
Perhaps the Pod doesn’t start, or it’s crashing.
It’s essential to have a well defined mental model of how Kubernetes works before diving into debugging a broken deployment.
Since there are three components in every deployment, you should debug all of them in order, starting from the bottom.
If the Pods is Ready, you should investigate if the Service can distribute traffic to the Pods.
Finally, you should examine the connection between the Service and the Ingress.
Most of the time, the issue is in the Pod itself.
You should make sure that your Pods are Running and Ready.
How do you check that?
kubectl get pods
NAME READY STATUS RESTARTS AGE
app1 0/1 ImagePullBackOff 0 47h
app2 0/1 Error 0 47h
app3-76f9fcd46b-xbv4k 1/1 Running 1 47h
In the above session, the last Pod is Running and Ready — however, the first two Pods are neither Running nor Ready.
How do you investigate on what went wrong?
There are four useful commands to troubleshoot Pods:
kubectl logs <pod name>
is helpful to retrieve the logs of the containers of the Podkubectl describe pod <pod name>
is useful to retrieve a list of events associated with the Podkubectl get pod <pod name>
is useful to extract the YAML definition of the Pod as stored in Kuberneteskubectl exec -ti <pod name> bash
is useful to run an interactive command within one of the containers of the PodWhich one should you use?
There isn’t a one-size-fits-all.
Instead, you should use a combination of them.
Pods can have startup and runtime errors.
Startup errors include:
Runtime errors include:
Some errors are more common than others.
The following is a list of the most common error and how you can fix them.
This error appears when Kubernetes isn’t able to retrieve the image for one of the containers of the Pod.
There are three common culprits:
The first two cases can be solved by correcting the image name and tag.
For the last, you should add the credentials to your private registry in a Secret and reference it in your Pods.
The official documentation has an example about how you could to that.
If the container can’t start, then Kubernetes shows the CrashLoopBackOff message as a status.
Usually, a container can’t start when:
You should try and retrieve the logs from that container to investigate why it failed.
If you can’t see the logs because your container is restarting too quickly, you can use the following command:
kubectl logs <pod-name> --previous
Which prints the error messages from the previous container.
The error appears when the container is unable to start.
That’s even before the application inside the container starts.
The issue is usually due to misconfiguration such as:
You should use kubectl describe pod <pod-name>
to collect and analyse the error.
When you create a Pod, the Pod stays in the Pending state.
Why?
Assuming that your scheduler component is running fine, here are the causes:
Your best option is to inspect the Events section in the kubectl describe
command:
kubectl describe pod <pod name>
For errors that are created as a result of ResourceQuotas, you can inspect the logs of the cluster with:
kubectl get events --sort-by=.metadata.creationTimestamp
If a Pod is Running but not Ready it means that the Readiness probe is failing.
When the Readiness probe is failing, the Pod isn’t attached to the Service, and no traffic is forwarded to that instance.
A failing Readiness probe is an application-specific error, so you should inspect the Events section in kubectl describe
to identify the error.
If your Pods are Running and Ready, but you’re still unable to receive a response from your app, you should check if the Service is configured correctly.
Services are designed to route the traffic to Pods based on their labels.
So the first thing that you should check is how many Pods are targeted by the Service.
You can do so by checking the Endpoints in the Service:
kubectl describe service <service-name> | grep Endpoints
An endpoint is a pair of <ip address:port>
, and there should be at least one — when the Service targets (at least) a Pod.
If the “Endpoints” section is empty, there are two explanations:
selector
labels of the ServiceIf you see a list of endpoints, but still can’t access your application, then the targetPort
in your service is the likely culprit.
How do you test the Service?
Regardless of the type of Service, you can use kubectl port-forward
to connect to it:
kubectl port-forward service/<service-name> 3000:80
Where:
<service-name>
is the name of the Service3000
is the port that you wish to open on your computer80
is the port exposed by the ServiceIf you’ve reached this section, then:
But you still can’t see a response from your app.
It means that most likely, the Ingress is misconfigured.
Since the Ingress controller being used is a third-party component in the cluster, there are different debugging techniques depending on the type of Ingress controller.
But before diving into Ingress specific tools, there’s something straightforward that you could check.
The Ingress uses the serviceName
and servicePort
to connect to the Service.
You should check that those are correctly configured.
You can inspect that the Ingress is correctly configured with:
kubectl describe ingress <ingress-name>
If the Backend column is empty, then there must be an error in the configuration.
If you can see the endpoints in the Backend column, but still can’t access the application, the issue is likely to be:
You can isolate infrastructure issues from Ingress by connecting to the Ingress Pod directly.
First, retrieve the Pod for your Ingress controller (which could be located in a different namespace):
kubectl get pods --all-namespaces
NAMESPACE NAME READY STATUS
kube-system coredns-5644d7b6d9-jn7cq 1/1 Running
kube-system etcd-minikube 1/1 Running
kube-system kube-apiserver-minikube 1/1 Running
kube-system kube-controller-manager-minikube 1/1 Running
kube-system kube-proxy-zvf2h 1/1 Running
kube-system kube-scheduler-minikube 1/1 Running
kube-system nginx-ingress-controller-6fc5bcc 1/1 Running
Describe it to retrieve the port:
kubectl describe pod nginx-ingress-controller-6fc5bcc
--namespace kube-system \
| grep Ports
Finally, connect to the Pod:
kubectl port-forward nginx-ingress-controller-6fc5bcc 3000:80 --namespace kube-system
At this point, every time you visit port 3000 on your computer, the request is forwarded to port 80 on the Pod.
Does it works now?
If you still can’t get the Ingress controller to work, you should start debugging it.
There are many different versions of Ingress controllers.
Popular options include Nginx, HAProxy, Traefik, etc.
You should consult the documentation of your Ingress controller to find a troubleshooting guide.
Since Ingress Nginx is the most popular Ingress controller, we included a few tips for it in the next section.
The Ingress-nginx project has an official plugin for Kubectl.
You can use kubectl ingress-nginx
to:
The three commands that you should try are:
kubectl ingress-nginx lint
, which checks the nginx.conf
kubectl ingress-nginx backend
, to inspect the backend (similar to kubectl describe ingress <ingress-name>
)kubectl ingress-nginx logs
, to check the logsPlease notice that you might need to specify the correct namespace for your Ingress controller with
--namespace <name>
.
Troubleshooting in Kubernetes can be a daunting task if you don’t know where to start.
You should always remember to approach the problem bottom-up: start with the Pods and move up the stack with Service and Ingress.
The same debugging techniques that you learnt in this article can be applied to other objects such as:
#Kubernetes #DevOps