In the previous post, we created our first kubernetes pod, but one important part is missing, it is not possible to access that application, we’ll solve this with a service now.
In kubernetes, even thou a pod is similar to a docker container, it doesn’t have a public or private IP address.
Of course if you check the underlying container implementation you’ll be able to access the pod, but that way you’ll loose all the benefits of kubernetes.
The basic idea is that a pod can be in any node, and the pods for a deployment can be in many nodes, and kubernetes will make that transparent for us using a service.
To make things clearer, and show only what is important, I’ve cleaned up the YAML for the deployment we’ve created in the previous post and we’ll start from there.
apiVersion: apps/v1 kind: Deployment metadata: labels: run: rails-sample name: rails-sample spec: replicas: 1 selector: matchLabels: run: rails-sample template: metadata: labels: run: rails-sample spec: containers: - args: - rails - s - -b - 0.0.0.0 - -p - "3000" env: - name: DATABASE_HOST value: 192.168.0.15 - name: DATABASE_USERNAME value: root - name: DATABASE_PASSWORD value: password image: urubatan/urubatan_rails_docker_sample:1.0.0 name: rails-sample ports: - containerPort: 3000 protocol: TCP
Assuming that yaml file is called “deployment.yaml” we can go back to the same state we were before with a “kubectl apply -f deployment.yaml”
And now to access the application, we’ll do the same as before and start with the command line, then go to the yaml, but right now, we already have one point to rememter, the “key” that kubernetes will use to match the service to the pods, is anything that we’ve added in the “labels” section, right now it is “run: rails-sample” but we can use any label we create, and the labels can be mostly any key/value pair.
To create a simple service to access our pods from that deployment, we can simply run: kubectl expose deployment.apps/rails-sample
And now, running a “kubectl get svc” you’ll see something similar to:
$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 18d rails-sample ClusterIP 10.152.183.16 <none> 3000/TCP 43s
From there, we can see that the services have a name, a type (we’ll see more on that later) a cluster IP, an external IP and ports.
The cluster IP is the service IP inside the kubernetes cluster, since we are running it locally now, we can use it to access the service, so right now, in my machine, if you go to http://10.152.183.16:3000 you’ll be able to access the rails sample app.
Only some types of services have external IPs.
The service type, can be any of:
- ClusterIP: Exposes the Service on a cluster-internal IP. Choosing this value makes the Service only reachable from within the cluster. This is the default ServiceType.
- NodePort: Exposes the Service on each Node’s IP at a static port (the NodePort). A ClusterIP Service, to which the NodePort Service routes, is automatically created. You’ll be able to contact the NodePort Service, from outside the cluster, by requesting :.
- LoadBalancer: Exposes the Service externally using a cloud provider’s load balancer. NodePort and ClusterIP Services, to which the external load balancer routes, are automatically created.
- ExternalName: Maps the Service to the contents of the externalName field (e.g. foo.bar.example.com), by returning a CNAME record
Now lets do the same as before, and take a look at the YAML that represents this service, we’ll get the yaml with the command: kubectl get service/rails-sample -o=yaml, and the result will be:
apiVersion: v1 kind: Service metadata: labels: run: rails-sample name: rails-sample spec: ports: - port: 3000 protocol: TCP targetPort: 3000 selector: run: rails-sample type: ClusterIP
As before I’ve cleaned up the output a little bit, removing some state from the yaml.
What is really important for us right now, is what is in the “spec” portion.
- type is where we specify exactly what we want, I usually set ClusterIP for anything that doesn’t need to be externally accessible and LoadBalancer for what needs to have external access, so lets change it to LoadBalancer in this case.
- selector is where we tell what pods this service will map to, as mentioned before, the label is matched to the service, so be careful to not set the same label to different pod types, because that can create a really confusing experience 😀
- ports is where we tell kubernetes what we’ll be exposing from the target pods, we can have different “port” and “targetPort” for each entry, where port is the externally accessible port, and targetPort is the pod port mapped to it.
If you saved that yaml as for example, service.yaml, changed the service type to LoadBalancer and then executed “kubectl apply -f service.yaml”, now you should have something similar to:
$ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 18d rails-sample LoadBalancer 10.152.183.16 192.168.0.150 3000:31865/TCP 16m
And with the external IP for our new load balancer, we can access the service from outside your machine.
If you are running this on AWS or Google Cloud, you’ll have their load balancer name in that field.
If you are running this locally with microk8s and you do not have an external IP listed, it is probably because you didn’t enable the “metallb” addon.
So I think it is all that you need to start playing with services, come back here in a couple of days and we’ll see how to increase the availability and performance of our services with automatic horizontal scaling.
And as always, please let me know if you have any question in the comment section bellow, if you liked or not this post and if you want to see any other type of content around here.