Kubernetes Services Overview
- Services in Kubernetes provide a way for applications to communicate with each other or with external clients. They allow for stable endpoints that remain constant, even if the underlying pods change.
Types of Kubernetes Services
- ClusterIP: The default service type, exposing the service within the Kubernetes cluster only. It's useful for internal communication between services or pods.
-
NodePort: Exposes the service on a static port on each node’s IP, enabling access from outside the cluster via
<NodeIP>:<NodePort>
. Often used in development for simple external access but can have security risks in production. - LoadBalancer: Integrates with cloud providers to automatically create an external load balancer, which routes traffic to your services. This type is most common for exposing services to the internet in production environments on managed Kubernetes clusters.
- ExternalName: Maps a service to an external DNS name by returning a CNAME record. Useful when pointing to external services outside of the Kubernetes environment, without complex configurations.
Prerequisites
Before we begin, ensure you have the following configuration for your KIND cluster:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: cka-cluster
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 30001 # Change this to match the NodePort
hostPort: 30001
listenAddress: "0.0.0.0"
protocol: tcp
- role: worker
- role: worker
This YAML file sets up a Kubernetes cluster named cka-cluster
with one control plane node and two worker nodes. The extraPortMappings
section maps port 30001 to allow external access to the NodePort service.
1. Create a Service named myapp
of type ClusterIP
- Goal: This creates an internal network endpoint accessible only within the Kubernetes cluster, allowing internal traffic routing to your application.
-
Details: The
ClusterIP
type will not be accessible outside the cluster, so it’s used here to facilitate communication between Pods and other resources internally. The Service will map port 80 to the target port 80 on the Pods.
Steps:
Create the KIND cluster (if you haven’t already):
kind create cluster --name kind-cka-cluster --config <kind.yml>
Switch context to your new KIND cluster (if required):
kubectl config use-context kind-kind-cka-cluster
Create the Service manifest:
Open a new YAML file using your preferred editor:
nano service.yml
Define the Service:
Copy the following YAML configuration into service.yml
:
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
type: ClusterIP
selector:
app: MyApp # Change this to match your pod labels. I copied this of the k8s docs
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
Apply the Service configuration:
kubectl apply -f service.yml
Verify the Service is running:
kubectl get services
This will create the myapp
Service, of type ClusterIP
, mapping port 80 on the Service to port 80 on the target Pods.
2. Create a Deployment named myapp
Goal: The Deployment manages Pod replicas for nginx (in this case, version
1.23.4-alpine
), ensuring they’re running and available.Details: By setting up a Deployment, you gain automatic scalability, rolling updates, and easier management. You expose the container’s port 80, which the Service will use to route traffic.
Steps:
Create the Deployment manifest:
Open a new YAML file using your preferred editor:
nano deployment.yml
Define the Deployment:
Copy the following YAML configuration into deployment.yml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: MyApp
template:
metadata:
labels:
app: MyApp
spec:
containers:
- name: nginx
image: nginx:1.23.4-alpine
ports:
- containerPort: 80
Apply the Deployment configuration:
Run the following command to create the Deployment:
kubectl apply -f deployment.yml
Verify the Deployment is running:
Check the status by running:
kubectl get deployments
3. Scale the Deployment to 2 replicas
Goal: This tests the load-balancing functionality of the Service, as it should distribute traffic across the two Pod instances.
Details: You can scale the Deployment either by updating the manifest to set
replicas: 2
or by using akubectl
command.
Steps:
Update the Deployment manifest: If you want to change the number of replicas in the YAML, edit the replicas
field:
replicas: 2 # Update to 2 for scaling
Alternatively, use kubectl
to scale the Deployment: Run the following command to scale the Deployment:
kubectl scale deployment myapp --replicas=2
Verify the scaling: Run the following command to check that the new Pods are created and ready:
kubectl get pods
4. Create a Temporary Pod Using busybox and Run wget
Against the Service IP
Goal: This verifies that the Service is reachable from within the cluster.
Details: You’ll launch a temporary
busybox
Pod and runwget
to check if it can access themyapp
Service over its ClusterIP. This step assumes you’re comfortable finding the Service’s ClusterIP (accessible viakubectl get svc
).
Important Labeling Warning: Ensure that the labels in the Service selector match the Deployment's matchLabels exactly. Misalignment will prevent the Service from routing traffic to the correct Pods.
Steps:
Find the Service IP:
Run the following command to retrieve the ClusterIP of myapp
:
kubectl get services
Example output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapp ClusterIP 10.96.87.231 <none> 80/TCP 10h
Create the Temporary Pod:
Use the following command to create a busybox
Pod and run wget
to test connectivity:
kubectl run busybox --image=busybox -it --rm --restart=Never -- wget myapp:80
Command Breakdown:
-
--image=busybox: Specifies
busybox
as the image to pull. - -it: Interactive terminal, which allows you to access the command line inside the Pod.
- --rm: Cleans up and removes the Pod after the command completes.
- --restart=Never: Ensures Kubernetes does not attempt to restart the Pod after it finishes.
-
wget myapp:80: Sends a request to the
myapp
Service at port 80, confirming connectivity.
Expected Output:
You should see output similar to the following upon a successful connection:
Connecting to myapp:80 (10.96.87.231:80)
saving to 'index.html'
index.html 100% |********************************| 615 0:00:00 ETA
'index.html' saved
pod "busybox" deleted
This output confirms that the Service is accessible within the cluster and can successfully handle requests from other Pods.
5. Run a wget
Command Against the Service from Outside the Cluster
-
Goal: This tests if external traffic can reach the Service (spoiler: it won’t, as it’s still a
ClusterIP
type). -
Steps: From an external machine or terminal, try reaching the Service IP. You won’t get a response because
ClusterIP
restricts access to within the cluster.
Find the Service IP:
Run the following command to retrieve the ClusterIP of myapp
:
kubectl get services
Example output:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
myapp ClusterIP 10.96.87.231 <none> 80/TCP 10h
Next, attempt to use wget
to access the Service:
wget myapp:80
Output:
--2024-10-27 06:49:46-- http://myapp/
Resolving myapp (myapp)... failed: nodename nor servname provided, or not known.
wget: unable to resolve host address ‘myapp’
6. Change the Service Type for External Access
- Goal: Now, we want to change the Service type so it’s accessible externally.
-
Details: Changing the Service type to
NodePort
orLoadBalancer
enables external traffic. UseNodePort
if you want to access it through a specific node’s IP and port, orLoadBalancer
if your cloud provider’s load balancer will handle it.
Rationale: Instead of executing commands directly in the terminal, we should leverage YAML files for defining our configurations. This approach not only facilitates source control but also ensures that our infrastructure as code (IaC) practices are consistent and reproducible.
Steps:
Remove the existing service configuration:
rm service.yml
Create a new service definition using your preferred text editor:
nano service.yml
Copy the following YAML configuration (adapted from the Kubernetes documentation) into service.yml
:
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
type: NodePort
selector:
app: MyApp
ports:
- port: 80
targetPort: 80
nodePort: 30001 # Optional: Define the node port if desired
Deploy the new service configuration:
kubectl apply -f service.yml
For more information, you can refer to the Kubernetes documentation.
7. Access the Service in Your Browser or Using curl
- Goal: This confirms external access to the Service once exposed.
-
Details: You can visit the nginx homepage in your browser or use
curl
to see the response from the service. -
Command:
curl http://localhost:30001
Discussion Points
-
Exposing Pods without a Deployment:
-
Can it be done? Yes, it’s possible to create a Service that directly exposes individual Pods by using
spec.selector
to match the Pods' labels. - Why (or why not)? However, Deployments provide benefits like auto-scaling, rolling updates, and ensuring a specified number of Pods are always running, which wouldn’t be handled automatically if only using a Service.
-
Can it be done? Yes, it’s possible to create a Service that directly exposes individual Pods by using
-
Choosing Service Types:
- ClusterIP: Use it for internal traffic only, where the Service should only be accessible within the cluster. Great for microservices that need to communicate with each other.
- NodePort: This makes the Service accessible on each Node's IP at a specific port, allowing limited external access. Often used for development or testing but not ideal for production as it doesn’t offer load balancing.
- LoadBalancer: Ideal for production in cloud environments, as it distributes incoming traffic across multiple nodes or Pods and abstracts the complexity of handling traffic.
- ExternalName: This type creates an alias for an external hostname, allowing Pods to access external services using Kubernetes DNS. Suitable for situations where in-cluster services need access to an external service, but it’s limited to simple DNS resolution.
Tags and Mentions
@piyushsachdeva
Day 8 video