All writing
Updated 4 min readNaren

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:

Kubernetes Cluster Node 1 Node 2 Service Types Pod A Pod B Pod C Pod D ClusterIP Internal Service NodePort Node IP:Port LoadBalancer External Load Balancer

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: ClusterIP

Apply 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: NodePort

Apply 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: LoadBalancer

Apply 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 name

Apply 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:

  • port is the port the Service itself exposes inside the cluster.
  • targetPort is 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.

Kubernetes Cluster host: app1.example.com path: /api External User Ingress Controller Service App1 Service API Pod App1 Pod API

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 update

Install the controller into its own ingress-nginx namespace:

helm install ingress-nginx ingress-nginx/ingress-nginx \
  --namespace ingress-nginx --create-namespace

Check that it is running:

kubectl get pods --namespace ingress-nginx
kubectl get service --namespace ingress-nginx

Find 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: 80

Apply 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: 80

Apply 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: 80

Apply 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-controller

To 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.com

Then 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.