Learn how to access your Kubernetes applications using Services
Tags: tutorial, kubernetes, showdev, beginners
Published: 2019-12-13 12:38:56

Welcome to another installment of the "Kubernetes in a Nutshell" blog series 👋 In this part we will dive into Kubernetes Services. You will learn about:

- Kubernetes Services types for internal and external communication
- Techniques for service discovery within the cluster
- How to access external services etc.

![](https://media.giphy.com/media/bcKmIWkUMCjVm/giphy.gif)

As always, the code (and `YAML`!) is available [on GitHub](https://github.com/abhirockzz/kubernetes-in-a-nutshell/blob/master/services)

> Happy to get your feedback via [Twitter](https://twitter.com/abhi_tweeter) or just drop a comment 🙏🏻


Kubernetes `Pod`s are ephemeral i.e. they do not retain their properties across restarts or re-schedules. This applies to container storage (volume), identity (`Pod` name), and even IP addresses. This poses challenges in terms of application access. Higher level abstractions like `Deployment`s control several `Pod`s and treat them as stateless entities - how do clients access these groups of `Pod`s? Does the client need to be aware of co-ordinates of every `Pod` underneath a `Deployment`? Also, you cannot count on a `Pod` getting the same IP after a restart - how will a client application continue to access the `Pod`?

Enter Kubernetes Services!

## Kubernetes Service

A `Service` is a higher level component that provides access to a bunch of `Pod`s. It decouples the client application from the specifics of a `Deployment` (or a set of `Pod`s in general) to enable predictable and stable access.

Kubernetes defines the following types of Services:

- `ClusterIP` — for access only within the Kubernetes cluster
- `NodePort` — access using IP and port of the Kubernetes Node itself
- `LoadBalancer` — an external load balancer (generally cloud provider specific) is used e.g. an [Azure Load Balancer](https://docs.microsoft.com/azure/load-balancer/load-balancer-overview?WT.mc_id=devto-blog-abhishgu) in [AKS](https://docs.microsoft.com/azure/aks/?WT.mc_id=devto-blog-abhishgu)
- `ExternalName` — maps a `Service` to an external DNS name

One can classify access patterns into two broad categories:

- External access
- Internal access

## External Access

You can use either `NodePort` or `LoadBalancer` service if you want external clients to access your apps inside the Kubernetes cluster.

### NodePort

`NodePort` is exactly what it sounds like - makes it possible to access the app within the cluster using the IP of the Node (on which the `Pod` has been scheduled) and a random port assigned by Kubernetes e.g. for a HTTP endpoint, you would use `http://<node_ip>:<port>`

Here is an example:

```
apiVersion: v1
kind: Service
metadata:
  name: kin-nodeport-service
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 8080
  selector:
    app: kin-service-app
```

Although `NodePort` is conceptually quite simple, here are a few points you should note

- the random port allocation is restricted to range — `30000–32767`
- the port is the *same* for every node in the cluster
- it is possible to specify a static port number, but the `Service` creation might fail for reasons like port allocation, invalid port, etc.

### LoadBalancer

When running in a cloud provider, a `LoadBalancer` service type triggers the provisioning of an external load balancer which distributes traffic amongst the backing `Pod`s.

To see this in action, let's deploy an application on [Azure Kubernetes Service](https://docs.microsoft.com/azure/aks/?WT.mc_id=devto-blog-abhishgu) and expose it using a `LoadBalancer` service. 

> Using a multi-node (at least two) cloud based Kubernetes cluster makes it easy to demonstrate this concept. Feel free to use any other cloud provider (such a `GKE` to try out this scenario)

If you want to try this out using [Azure](https://azure.microsoft.com/), here are a few pre-requisites you should complete before going through the tutorials in this post:

- Get a [free Microsoft Azure account](https://docs.microsoft.com/azure/aks/?WT.mc_id=devto-blog-abhishgu)!
- [Install Azure CLI tool](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu)
- [Install `kubectl`](https://kubernetes.io/docs/tasks/tools/install-kubectl/) to access your Kubernetes cluster
- [Setup a two-node Kubernetes cluster on Azure](https://docs.microsoft.com/en-in/azure/aks/kubernetes-walkthrough) using the CLI

Once you've finished setting up the cluster, make sure you configure `kubectl` to connect to it using the [`az aks get-credentials`](https://docs.microsoft.com/cli/azure/aks?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu#az-aks-get-credentials) command - this downloads credentials and configures the Kubernetes CLI to use them.

```
az aks get-credentials --name <AKS-cluster-name> --resource-group <AKS-resource-group>
```

You should be all set now. Let's start by creating the `Service` along as well as the sample application.

To keep things simple, the YAML file is being referenced directly from the [GitHub repo](https://github.com/abhirockzz/kubernetes-in-a-nutshell), but you can also download the file to your local machine and use it in the same way.

```
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/loadbalancer/service.yaml

kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/app/app.yaml
```

> the [sample application](https://github.com/abhirockzz/kubernetes-in-a-nutshell/tree/master/services/app) is really simple [Go](https://golang.org/) program

To confirm that the `Service` has been created

```
kubectl get svc/kin-lb-service
```

You should get back a response similar to below

```
NAME             TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
kin-lb-service   LoadBalancer   10.0.149.217   <pending>        80:31789/TCP   1m
```

The `pending` status for `EXTERNAL-IP` is temporary - it's because AKS is provisioning an [Azure Load Balancer](https://docs.microsoft.com/azure/load-balancer/load-balancer-overview?WT.mc_id=devto-blog-abhishgu) behind the scenes.

After some time, you should see `EXTERNAL-IP` populated with the public IP of the Load Balancer

```
NAME             TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
kin-lb-service   LoadBalancer   10.0.149.217   242.42.420.42   80:31789/TCP   2m
```

Check that the application is deployed as well - you should see two `Pod`s in `Running` state

```
kubectl get pod -l=app=kin-service-app

NAME                              READY   STATUS    RESTARTS   AGE
kin-service-app-775f989dd-gr4jk   1/1     Running   0          5m
kin-service-app-775f989dd-rmq6r   1/1     Running   0          5m
```

You can access the application using the load balancer IP as such:

```
curl http://242.42.420.42/
```

> Note that the IP will be different in your case. Also, the load balancer port is `80` as per `spec.ports.port` attribute in the `Service` manifest.

You should see a response similar to:

```
Hello from Pod IP 10.244.0.151 on Node aks-agentpool-37379363-0
```

This output shows:

- the IP of the `Pod`, and,
- the name of the `Node` on which the Pod is present

If you try accessing the application again (`curl http://242.42.420.42/`), you will most likely be load balanced to another instance `Pod` which may be on a different `Node` and you might see a response such as:

```
Hello from Pod IP 10.244.1.139 on Node aks-agentpool-37379363-1
```

> You can scale your application (in and out) and continue to access it using the Load Balancer IP.

If you want to get the Azure Load Balancer details using the CLI, please use the following commands:

```
export AZURE_RESOURCE_GROUP=[enter AKS resource group]
export  AKS_CLUSTER_NAME=[enter AKS cluster name]
```

Get the AKS cluster infra resource group name using the [`az aks show`](https://docs.microsoft.com/cli/azure/aks?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu) command

```
INFRA_RG=$(az aks show --resource-group $AZURE_RESOURCE_GROUP --name $AKS_CLUSTER_NAME --query nodeResourceGroup -o tsv)
```

Use it to list the load balancers with the [`az network lb list`](https://docs.microsoft.com/cli/azure/network/lb?view=azure-cli-latest&WT.mc_id=devto-blog-abhishgu#az-network-lb-list) command (you will get back a JSON response with the load balancer information)

```
az network lb list -g $INFRA_RG
```

## Internal access with `ClusterIP`

`ClusterIP` service type and can be used communication within the cluster - just specify `ClusterIP` in the `spec.type` and you should be good to go!

> `ClusterIP` is the default service type

Even for intra-cluster communication with `ClusterIP` `Service` type, there has to be a way for application `A` to call application `B` (via the `Service`). There are two ways of service discovery for apps within the cluster:

- Environment variables
- `DNS`

### Environment variables

Each `Pod` is populated with a set of environment variables specific to a `Service`. Let's see this in action! Create a `ClusterIP` service as follows, and confirm that it has been created

```
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/clusterip/service.yaml

kubectl get svc/kin-cip-service
```

Remember the application we had deployed to explore `LoadBalancer` `Service` type? Well, delete and re-create it again (I'll explain why this was done, in a moment)

```
kubectl delete -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/app/app.yaml

kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/app/app.yaml
```

Once the app is in `Running` status (check `kubectl get pod -l=app=kin-service-app`), [`exec`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#exec) (execute a command directly inside the `Pod`) into the `Pod` to check its environment variables

```
kubectl exec <enter-pod-name> -- env | grep KIN_CIP
```

You will see results similar to below:

```
KIN_CIP_SERVICE_PORT_9090_TCP_ADDR=10.0.44.29
KIN_CIP_SERVICE_SERVICE_PORT=9090
KIN_CIP_SERVICE_PORT_9090_TCP_PROTO=tcp
KIN_CIP_SERVICE_PORT_9090_TCP=tcp://10.0.44.29:9090
KIN_CIP_SERVICE_PORT=tcp://10.0.44.29:9090
KIN_CIP_SERVICE_SERVICE_HOST=10.0.44.29
KIN_CIP_SERVICE_PORT_9090_TCP_PORT=9090
```

Notice the format of the environment variables names? They include the name of the `ClusterIP` `Service` itself (i.e. `kin-cip-service`) with `-` replaced by `_` and the rest being upper-cased. This is a pre-defined format and can be used to communicate with another application given you know the name of the `Service` which backs it.

> There is a caveat: a `Pod` is seeded with the environment variables only if the `Service` was created before it! That's the reason why we had to recreate the application to see the effect.

Let's access this application from another `Pod` using the environment variables. Just run another `Pod` with `curl`

```
kubectl run --rm --generator=run-pod/v1 curl --image=radial/busyboxplus:curl -i --tty
```

> you should see a command prompt soon

Confirm that this `Pod` has the environment variables as well (use `env | grep KIN_CIP`) and then simply `curl` the application endpoint using the environment variables

```
curl http://$KIN_CIP_SERVICE_SERVICE_HOST:$KIN_CIP_SERVICE_SERVICE_PORT
```

You should see the same response as in the case of `LoadBalancer` example, i.e.

```
Hello from Pod IP 10.244.0.153 on Node aks-agentpool-37379363-0
```

Try it a few more times to confirm that the load is getting balanced among the individual Pods! So we derived the environment variables based on the `Service` name i.e. `kin-cip-service` was converted to `KIN_CIP_SERVICE` and the rest of the parts were added - `_SERVICE_HOST` and `_SERVICE_PORT` for the host and port respectively.

### DNS

Kubernetes has a built-in DNS server (e.g. `CoreDNS`) which maintains DNS records for each `Service`. Just like environment variables, DNS technique provides a consistent naming scheme based on which you can access applications given you know their `Service` name (and other info like `namespace` if needed).

> The good thing is that this technique does not depend on the order of `Serivce` and `Pod` creation as was the case with environment variables

You can try it right away:

Run the `curl` `Pod` again 

```
kubectl run --rm --generator=run-pod/v1 curl --image=radial/busyboxplus:curl -i --tty
```

To access the application:

```
curl http://kin-cip-service.default.svc.cluster.local:9090
```

Everything should work the same way! The FQDN format is `<service-name>.<namespace>.<cluster-domain-suffix>`. In our case, it maps to:

- `service-name` - `kin-cip-service`
- `namespace` - `default`
- `cluster-domain-suffix` - `svc.cluster.local`

> For applications in the same `namespace` you can actually skip most of this and just use the service name!

## Mapping external services

There are cases where you are required to refer to external services from applications inside your Kubernetes cluster. You can do this in a couple of ways

- In a static manner using `Endpoints` resource
- Using the `ExternalName` service type

### Static `Endpoints`

It's a good time to reveal that Kubernetes creates an `Endpoints` resource for every `Service` you create (if you use a `selector` which is mostly the case except for few scenarios). It's the `Endpoints` object which actually captures the IPs of the backing `Pod`s. You can look at existing ones using `kubectl get endpoints`.

In case you want to access an external service, you need to:

- create a `Service` without any `selector` - Kubernetes will NOT create an `Endpoints` object
- manually create the `Endpoints` resource corresponding to your `Service` (with the same name) and IP/port of the service you want to access.

This gives you the same benefits i.e. you can keep the `Service` constant and update the actual entity behind the scenes if needed. As an example, we will abstract access to a public HTTP endpoint `http://demo.nats.io:8222/` using this technique.

Start by creating the `Service`

```
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/external/static/service.yaml
```

Here is what the `Service` looks like. Notice that we are mapping `port` `8080` to the actual (target) port we want to access `8222`

```
kind: Service
apiVersion: v1
metadata:
  name: demo-nats-public-service
spec:
  type: ClusterIP
  ports:
    - port: 8080
      targetPort: 8222
```

Let's look at the `Endpoints` resource. 

```
kind: Endpoints
apiVersion: v1
metadata:
  name: demo-nats-public-service
subsets:
  - addresses:
      - ip: 107.170.221.32
    ports:
      - port: 8222
```

Notice the following:

- name of the resource (`demo-nats-public-service`) is the same as the `Service`
- we've used the `subsets` attribute to specify the `ip` and the `port` (we found the `ip` backing `demo.nats.io` by simply using `ping demo.nats.io`)

Create it using:

```
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/external/static/endpoints.yaml
```

That's it! Let's see if this works. Simply run a `curl` `Pod`

```
kubectl run --rm --generator=run-pod/v1 curl --image=radial/busyboxplus:curl -i --tty
```

Now, all we need to use is the name of our `Service` i.e. `demo-nats-public-service` (along with the port `8080`) and it will do the trick.

```
curl http://demo-nats-public-service:8080
```

You should see the following response (this is the same as if you had browsed to `http://demo.nats.io:8222`)

```
<html lang="en">
   <head>
    <link rel="shortcut icon" href="http://nats.io/img/favicon.ico">
    <style type="text/css">
      body { font-family: "Century Gothic", CenturyGothic, AppleGothic, sans-serif; font-size: 22; }
      a { margin-left: 32px; }
    </style>
  </head>
  <body>
    <img src="http://nats.io/img/logo.png" alt="NATS">
    <br/>
	<a href=/varz>varz</a><br/>
	<a href=/connz>connz</a><br/>
	<a href=/routez>routez</a><br/>
	<a href=/gatewayz>gatewayz</a><br/>
	<a href=/leafz>leafz</a><br/>
	<a href=/subsz>subsz</a><br/>
    <br/>
    <a href=https://docs.nats.io/nats-server/configuration/monitoring.html>help</a>
  </body>
</html>
```

### `ExternalName`

`ExternalName` is another `Service` type which can be used to map a `Service` to a DNS name. Note that this is a DNS name and not an IP/port combination as was the case with the above strategy (using manually created `Endpoints`).

In the `Serivce` manifest:

- don't include a `selector`
- use `externalName` attribute to specify DNS name e.g. `test.example.com`
- using IP addresses is not allowed

Here is an example:

```
apiVersion: v1
kind: Service
metadata:
  name: demo-nats-public-service2
spec:
  type: ExternalName
  externalName: demo.nats.io
```

We are creating a `Service` with the name `demo-nats-public-service2` which maps to DNS name `demo.nats.io` using the `spec.type` which is `ExternalName`.

It works the same way i.e. you need to use the `Service` name to access the external entity. The only difference (compared to the manual `Endpoints` approach) is that you'll need to know the port as well. To try this:

Create the `Service`

```
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/external/external-name/service.yaml
```

Simply run a `curl` `Pod`

```
kubectl run --rm --generator=run-pod/v1 curl --image=radial/busyboxplus:curl -i --tty
```

Now, all we need to use is the name of our `Service` i.e. `demo-nats-public-service2` (along with port `8222`)

```
curl http://demo-nats-public-service2:8222
```

You should see the same response as the previous scenario

## `Headless` Service

You can get load balancing among a group of `Pod`s using `LoadBalancer`, `NodePort` and `ClusterIP` Service types. A `Headless` `Service` allows access to individual `Pod`s - there is no proxying involved in this case. This is useful in many scenarios e.g. 

- consider a peer-to-peer system where individual instances (Pods) will need to access each other. 
- a master-follower style service where follower instances need to be aware of the master Pod

In order to create a `Headless` `Service`, you need to explicitly specify `None` as a value for `.spec.clusterIP`. Here is an example:

```
apiVersion: v1
kind: Service
metadata:
  name: kin-hl-service
spec:
  clusterIP: None
  ports:
    - port: 9090
      targetPort: 8080
  selector:
    app: kin-service-app
```

The way you use a Headless Service is different compared to other types. A DNS lookup against the Service (e.g. `<service-name>.<namespace>.svc.cliuster.local`) returns multiple IPs corresponding to different `Pods` (as compared to a single virtual IP in case of other Service types). Let's see this in action

Create the `Service`

```
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/headless/service.yaml
```

Run the `curl` `Pod`

```
kubectl run --rm --generator=run-pod/v1 curl --image=radial/busyboxplus:curl -i --tty
```

Check the backing `Pod` IPs

```
nslookup kin-hl-service.default.svc.cluster.local
```

You will get a response similar to the below

```
[ root@curl:/ ]$ nslookup kin-hl-service
Server:    10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local

Name:      kin-hl-service
Address 1: 10.244.0.153 10-244-0-153.kin-hl-service.default.svc.cluster.local
Address 2: 10.244.1.141 10-244-1-141.kin-hl-service.default.svc.cluster.local
```

`10-244-0-153.kin-hl-service.default.svc.cluster.local` and `10.244.1.141 10-244-1-141.kin-hl-service.default.svc.cluster.local` correspond to co-ordinates of the individual `Pod`s - this is not possible with a traditional `Service` type. You can now use this to access specific `Pod` e.g.

```
curl http://10-244-0-153.kin-hl-service.default.svc.cluster.local:8080

//response
Hello from Pod IP 10.244.0.153 on Node aks-agentpool-37379363-0

curl http://10-244-1-141.kin-hl-service.default.svc.cluster.local:8080

//response
Hello from Pod IP 10.244.1.141 on Node aks-agentpool-37379363-1
```

## Ingress

We did cover the basics of Kubernetes `Service`, but I do want to highlight `Ingress` which deserves a separate post altogether. `Ingress` is not a `Service` type (such as `ClusterIP` etc.) - think of as an abstraction on top of a `Service`. Just like `Service`s front end a bunch of `Pod`s, an `Ingress` can be configured to work with several backing `Service`s and forward the requests as per rules which you can define.

The intelligence provided by an `Ingress` is actually implemented in the form of an `Ingress` Controller. For example, [Minikube comes with an NGINX based Ingress Controller](https://kubernetes.io/docs/tasks/access-application-cluster/ingress-minikube/). The controller is responsible for providing access to the appropriate backing `Service` after evaluation of the `Ingress` rules.

That's it for this part of the "Kubernetes in a Nutshell" series. Stay tuned for more!

> Friendly reminder if you are interested in learning Kubernetes and Containers using [Azure](https://azure.microsoft.com/services/kubernetes-service/?WT.mc_id=devto-blog-abhishgu)! Simply [create a **free** account](https://azure.microsoft.com/en-us/free/?WT.mc_id=devto-blog-abhishgu) and get going! A good starting point is to use the [quickstarts, tutorials and code samples](https://docs.microsoft.com/azure/aks/?WT.mc_id=devto-blog-abhishgu) in the documentation to familiarize yourself with the service. I also highly recommend checking out the [50 days Kubernetes Learning Path](https://azure.microsoft.com/resources/kubernetes-learning-path/?WT.mc_id=devto-blog-abhishgu). Advanced users might want to refer to [Kubernetes best practices](https://docs.microsoft.com/azure/aks/best-practices?WT.mc_id=devto-blog-abhishgu) or watch some of the [videos](https://azure.microsoft.com/resources/videos/index/?services=kubernetes-service&WT.mc_id=devto-blog-abhishgu) for demos, top features and technical sessions.


I really hope you enjoyed and learned something from this article 🙌 Please like and follow if you did!

![](https://media.giphy.com/media/tF5j8LJPo94ys/giphy.gif)