Kubernetes — Service + Ingress
A ClusterIP Service that exposes your Deployment pods internally, paired with an Nginx Ingress resource that routes external HTTPS traffic to it — with automatic TLS certificate provisioning via cert-manager and Let's Encrypt.
Overview
In Kubernetes, a Service gives your pods a stable network identity. Pods come and go
during rolling updates or scaling events, and their IP addresses change every time. A Service provides
a single, stable virtual IP (called a ClusterIP) and DNS name that other workloads inside the cluster
can use to reach your application — regardless of which pod actually handles the request. The Service
uses label selectors to identify the pods it should route traffic to, exactly like the Deployment's
selector.matchLabels.
An Ingress is an API object that defines how external HTTP and HTTPS traffic reaches
Services inside the cluster. Rather than exposing each Service with its own cloud load balancer (which
is expensive), the Ingress routes traffic from a single entry point — the Nginx Ingress Controller —
to the correct Service based on hostname and path rules. This file combines both resources using YAML's
multi-document separator ---, so you can apply them both with a single
kubectl apply -f service-ingress.yaml command.
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/cloud/deploy.yaml
and refer to the cert-manager documentation for installation steps.
Full YAML Copy-paste ready
apiVersion: v1
kind: Service
metadata:
name: my-app
namespace: default
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app
namespace: default
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- example.com
secretName: my-app-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
Key sections explained
The --- multi-document separator
YAML supports multiple documents in a single file, separated by --- (three dashes on their
own line). Kubernetes uses this to allow you to group related resources together in one file — in this
case the Service and the Ingress that work together to expose the same application. When you run
kubectl apply -f service-ingress.yaml, both resources are created or updated in the same
operation. There is no limit to how many documents you can include; large teams often have a single
file per application that contains its Deployment, Service, Ingress, ConfigMap, and HPA all in one place.
ClusterIP vs NodePort vs LoadBalancer
Kubernetes Services come in three main types. ClusterIP (the default, used here) creates an internal virtual IP that is only reachable from within the cluster. It is the right choice when another Kubernetes resource — like an Ingress Controller — will handle the external-facing traffic. NodePort exposes the Service on a static port on every node's external IP, which is useful for testing but not recommended for production because it bypasses the Ingress layer. LoadBalancer provisions an external cloud load balancer (on AWS, GCP, Azure, etc.) and is appropriate when you need a dedicated IP per Service — but at significant cost. For most applications, the ClusterIP + Ingress pattern gives you one load balancer shared across all your services, which is both cheaper and easier to manage.
How Ingress routes traffic to the Service
The Ingress rules section maps hostnames and paths to backend Services. In this manifest,
any request for example.com matching path prefix / is forwarded to the
my-app Service on port 80. The Service then load-balances the request across all healthy
pods matching app: my-app. The pathType: Prefix means the rule matches
/, /api, /static/logo.png, and any other path — it is a
catch-all. Use pathType: Exact if you need a specific path to match without sub-paths.
Multiple rules can be added in the same Ingress to route different hostnames or path prefixes to
different Services, enabling you to host many microservices behind a single entry point. For example,
you could route /api to an API service and / to a frontend service.
cert-manager annotation for automatic TLS
The annotation cert-manager.io/cluster-issuer: letsencrypt-prod instructs cert-manager
to watch this Ingress resource and automatically obtain a TLS certificate from Let's Encrypt for the
listed domains. cert-manager uses the HTTP-01 or DNS-01 ACME challenge to prove domain ownership,
then stores the resulting certificate in a Kubernetes Secret named by spec.tls[].secretName
— in this case my-app-tls. The Nginx Ingress Controller reads that Secret and serves
HTTPS traffic using the certificate. cert-manager also handles renewal automatically, so you never
need to think about certificate expiry.
The nginx.ingress.kubernetes.io/ssl-redirect: "true" annotation tells the Nginx controller
to issue a 308 redirect for any HTTP request to the same URL over HTTPS. Without this, users who visit
the plain HTTP URL would not be redirected and might interact with the site insecurely. Note that the
value must be a string ("true") not a boolean, because Kubernetes annotations are always
strings.
Tips & variations
Route multiple paths to different services
Add additional path entries under http.paths to route different URL prefixes to different
backend services — for example, /api to an API service and / to a frontend:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
Use a staging issuer for testing
Let's Encrypt has strict rate limits on production certificate issuance. While testing your cert-manager
setup, replace letsencrypt-prod with letsencrypt-staging in the annotation.
The staging issuer uses an untrusted root CA, so browsers will show a warning — but it lets you iterate
without hitting rate limits. Switch back to letsencrypt-prod when you are confident
everything works.
Manage with Helm
The Helm values.yaml example shows
how to manage the Ingress configuration — including TLS hosts, path rules, and annotations — through
a values.yaml file, making it easy to vary settings across environments without editing
the raw manifest.