Services and Ingress: exposing your applications
Give a changing set of Pods one stable address with a Service, choose the right Service type, and route outside traffic by host and path with Ingress.
The last post ran applications with Deployments, ReplicaSets, Jobs and CronJobs. Those applications now run inside the cluster, which raises two questions: how do they talk to each other, and how do users outside the cluster reach them?
This post answers both. Services give your Pods stable network endpoints, and Ingress manages outside access to your applications with routing rules.
Services
Pods are temporary. They get created, destroyed and rescheduled, so their IP addresses change all the time. Anything that connected straight to a Pod IP would keep breaking.
A Service is a stable way to expose a group of Pods. It gives your application one consistent IP address and DNS name, which other applications and outside clients use to connect, whichever Pods happen to be running behind it. In effect, a Service is a load balancer and a fixed entry point for your application.
That has three benefits. Applications no longer need to know the changing IPs of individual Pods. Traffic is spread across all the healthy Pods automatically. And because Kubernetes gives every Service a DNS name, applications can find each other by name.
Service types
Kubernetes has several Service types, each for a different kind of access:
ClusterIP, for traffic inside the cluster
ClusterIP is the default type. It exposes the Service on an internal IP address that can only be reached from inside the cluster. Use it for communication between the parts of your application, such as a frontend talking to a backend, or a backend connecting to a database.
# my-backend-service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-backend-service
spec:
selector:
app: backend # Selects Pods with the label 'app: backend'
ports:
- protocol: TCP
port: 80 # Port the Service itself exposes (internal to cluster)
targetPort: 8080 # Port the application in the Pod is listening on
type: ClusterIPApply it with kubectl apply -f my-backend-service.yaml.
NodePort, on every node
NodePort builds on ClusterIP. It also exposes the Service on a static port on every node's IP address in the cluster. By default the port comes from the range 30000-32767, and outside traffic reaches the application at the node's IP address and that port.
It is useful in development and test environments, and in on-premises setups where no cloud load balancer is available.
# my-frontend-service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-frontend-service
spec:
selector:
app: frontend
ports:
- protocol: TCP
port: 80 # Service's internal port
targetPort: 8080 # Pod's application port
nodePort: 30080 # Optional: specific port on the Node (30000-32767)
type: NodePortApply it with kubectl apply -f my-frontend-service.yaml, then open http://<any-node-ip>:30080.
LoadBalancer, through a cloud load balancer
LoadBalancer is for exposing applications to the internet in cloud environments such as AWS, GCP and Azure. Kubernetes provisions an external load balancer from the cloud provider, with a public IP address, and that load balancer routes traffic to the cluster's nodes. It is the standard way to expose public web applications and APIs on a cloud-hosted cluster.
# my-webapp-service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-webapp-service
spec:
selector:
app: webapp
ports:
- protocol: TCP
port: 80 # Service's internal port
targetPort: 8080 # Pod's application port
type: LoadBalancerApply it with kubectl apply -f my-webapp-service.yaml, and get its external IP with
kubectl get services my-webapp-service.
ExternalName, an alias for something outside
ExternalName does not send traffic to Pods at all. It maps the Service to an external DNS name. When a client inside the cluster looks up the Service's name, Kubernetes answers with a CNAME record that points at the external name. Use it when in-cluster applications need to reach something outside the cluster, such as a managed database or a third-party API.
# my-external-db.yaml
apiVersion: v1
kind: Service
metadata:
name: my-external-db
spec:
type: ExternalName
externalName: my.database.example.com # The external DNS nameApply it with kubectl apply -f my-external-db.yaml. Any Pod that connects to my-external-db
is sent to my.database.example.com.
port, targetPort and nodePort
Three port fields come up in almost every Service:
portis the port the Service itself exposes inside the cluster.targetPortis the port the application inside the Pod listens on.nodePort, on NodePort and LoadBalancer Services, is the static port opened on every node's IP address for outside access.
Ingress
A LoadBalancer Service works well for one application. When you want several applications behind a single public IP address, told apart by hostname or URL path, you want Ingress.
Ingress is a Kubernetes API object that defines rules for routing outside HTTP and HTTPS traffic to Services inside the cluster. It gathers your routing rules in one place and exposes many Services through a single entry point.
Ingress can route by host, so app1.example.com goes to service-app1 and app2.example.com
goes to service-app2. It can route by path, so example.com/api goes to service-api and
example.com/web goes to service-web. It can terminate TLS, decrypting HTTPS traffic before
passing it on to your Services. And it saves money, because you no longer need a separate cloud
load balancer for every Service.
The Ingress controller
An Ingress resource defines how traffic should be routed. An Ingress controller is the software that carries out those rules. It runs inside your cluster, watches for Ingress resources, and configures itself, for example an Nginx proxy, to route traffic to match. Without an Ingress controller, your Ingress rules do nothing.
Popular Ingress controllers include Nginx, Traefik and the controllers each cloud provides.
Try it: the Nginx Ingress controller
This section installs the Nginx Ingress controller and exposes a sample application through it.
Step 1: Install the controller
Helm makes the installation easy. Add the controller's Helm repository:
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo updateInstall the controller into its own ingress-nginx namespace:
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespaceCheck that it is running:
kubectl get pods --namespace ingress-nginx
kubectl get service --namespace ingress-nginxFind the ingress-nginx-controller Service and note its EXTERNAL-IP, which on some clouds is
a hostname rather than an IP address. That is the address you point your domain at.
Step 2: Deploy a sample application
The sample is a simple httpd web server. Save the Deployment as app-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
labels:
app: demo-app
spec:
replicas: 2
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: demo-app
image: httpd:latest
ports:
- containerPort: 80Apply it with kubectl apply -f app-deployment.yaml. Then save the Service as
app-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: demo-app-service
spec:
selector:
app: demo-app
ports:
- protocol: TCP
port: 80
targetPort: 80Apply it with kubectl apply -f app-service.yaml.
Step 3: Define the Ingress
This Ingress routes traffic for demo.example.com to demo-app-service:
# demo-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demo-ingress
spec:
ingressClassName: nginx # Tell Kubernetes to use the Nginx Ingress Controller
rules:
- host: demo.example.com # Your desired hostname
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demo-app-service
port:
number: 80Apply it with kubectl apply -f demo-ingress.yaml.
Step 4: Test it
Get the Ingress controller's external address:
kubectl get service -n ingress-nginx ingress-nginx-controllerTo test with a made-up domain like demo.example.com, map it to that EXTERNAL-IP in your hosts
file. That is /etc/hosts on Linux and macOS, and C:\Windows\System32\drivers\etc\hosts on
Windows. Add a line like this:
<EXTERNAL-IP-OF-INGRESS-CONTROLLER> demo.example.comThen open http://demo.example.com in your browser. You should see the "It works!" page from the
httpd server.
The next post locks this traffic down with network policies, which control which Pods may talk to each other: Network policies.