9.7 KiB
Exposing containers
-
We can connect to our pods using their IP address
-
Then we need to figure out a lot of thigns:
-
how do we look up the IP address of the pod(s)?
-
how do we connect from outside the cluster?
-
how do we load balance traffic?
-
what if a pod fails?
-
-
Kubernetes has a resource type named Service
-
Services address all these questions!
Services in a nutshell
-
Services give us a stable endpoint to connect to a pod or a group of pods
-
An easy way to create a service is to use
kubectl expose -
If we have a deployment named
my-little-deploy, we can run:kubectl expose deployment my-little-deploy --port=80... and this will create a service with the same name (
my-little-deploy) -
Services are automatically added to an internal DNS zone
(in the example above, our code can now connect to http://my-little-deploy/)
Advantages of services
-
We don't need to look up the IP address of the pod(s)
(we resolve the IP address of the service using DNS)
-
There are multiple service types; some of them allow external traffic
(e.g.
LoadBalancerandNodePort) -
Services provide load balancing
(for both internal and external traffic)
-
Service addresses are independent from pods' addresses
(when a pod fails, the service seamlessly sends traffic to its replacement)
Many kinds and flavors of service
-
There are different types of services:
ClusterIP,NodePort,LoadBalancer,ExternalName -
There are also headless services
-
Services can also have optional external IPs
-
There is also another resource type called Ingress
(specifically for HTTP services)
-
Wow, that's a lot! Let's start with the basics ...
ClusterIP
-
It's the default service type
-
A virtual IP address is allocated for the service
(in an internal, private range; e.g. 10.96.0.0/12)
-
This IP address is reachable only from within the cluster (nodes and pods)
-
Our code can connect to the service using the original port number
-
Perfect for internal communication, within the cluster
LoadBalancer
-
An external load balancer is allocated for the service
(typically a cloud load balancer, e.g. ELB on AWS, GLB on GCE ...)
-
This is available only when the underlying infrastructure provides some kind of "load balancer as a service"
-
Each service of that type will typically cost a little bit of money
(e.g. a few cents per hour on AWS or GCE)
-
Ideally, traffic would flow directly from the load balancer to the pods
-
In practice, it will often flow through a
NodePortfirst
NodePort
-
A port number is allocated for the service
(by default, in the 30000-32767 range)
-
That port is made available on all our nodes and anybody can connect to it
(we can connect to any node on that port to reach the service)
-
Our code needs to be changed to connect to that new port number
-
Under the hood:
kube-proxysets up a bunch ofiptablesrules on our nodes -
Sometimes, it's the only available option for external traffic
(e.g. most clusters deployed with kubeadm or on-premises)
Running containers with open ports
-
Since
pingdoesn't have anything to connect to, we'll have to run something else -
We could use the
nginxofficial image, but ...... we wouldn't be able to tell the backends from each other!
-
We are going to use
jpetazzo/httpenv, a tiny HTTP server written in Go -
jpetazzo/httpenvlistens on port 8888 -
It serves its environment variables in JSON format
-
The environment variables will include
HOSTNAME, which will be the pod name(and therefore, will be different on each backend)
Creating a deployment for our HTTP server
-
We could do
kubectl run httpenv --image=jpetazzo/httpenv... -
But since
kubectl runis being deprecated, let's see how to usekubectl createinstead
.exercise[
- In another window, watch the pods (to see when they are created):
kubectl get pods -w
-
Create a deployment for this very lightweight HTTP server:
kubectl create deployment httpenv --image=jpetazzo/httpenv -
Scale it to 10 replicas:
kubectl scale deployment httpenv --replicas=10
]
Exposing our deployment
- We'll create a default
ClusterIPservice
.exercise[
-
Expose the HTTP port of our server:
kubectl expose deployment httpenv --port 8888 -
Look up which IP address was allocated:
kubectl get service
]
Services are layer 4 constructs
-
You can assign IP addresses to services, but they are still layer 4
(i.e. a service is not an IP address; it's an IP address + protocol + port)
-
This is caused by the current implementation of
kube-proxy(it relies on mechanisms that don't support layer 3)
-
As a result: you have to indicate the port number for your service
(with some exceptions, like
ExternalNameor headless services, covered later)
Testing our service
- We will now send a few HTTP requests to our pods
.exercise[
- Let's obtain the IP address that was allocated for our service, programmatically:
IP=$(kubectl get svc httpenv -o go-template --template '{{ .spec.clusterIP }}')
-
Send a few requests:
curl http://$IP:8888/ -
Too much output? Filter it with
jq:curl -s http://$IP:8888/ | jq .HOSTNAME
]
--
Try it a few times! Our requests are load balanced across multiple pods.
class: extra-details
ExternalName
-
Services of type
ExternalNameare quite different -
No load balancer (internal or external) is created
-
Only a DNS entry gets added to the DNS managed by Kubernetes
-
That DNS entry will just be a
CNAMEto a provided record
Example:
kubectl create service externalname k8s --external-name kubernetes.io
Creates a CNAME k8s pointing to kubernetes.io
class: extra-details
External IPs
-
We can add an External IP to a service, e.g.:
kubectl expose deploy my-little-deploy --port=80 --external-ip=1.2.3.4 -
1.2.3.4should be the address of one of our nodes(it could also be a virtual address, service address, or VIP, shared by multiple nodes)
-
Connections to
1.2.3.4:80will be sent to our service -
External IPs will also show up on services of type
LoadBalancer(they will be added automatically by the process provisioning the load balancer)
class: extra-details
Headless services
-
Sometimes, we want to access our scaled services directly:
-
if we want to save a tiny little bit of latency (typically less than 1ms)
-
if we need to connect over arbitrary ports (instead of a few fixed ones)
-
if we need to communicate over another protocol than UDP or TCP
-
if we want to decide how to balance the requests client-side
-
...
-
-
In that case, we can use a "headless service"
class: extra-details
Creating a headless services
-
A headless service is obtained by setting the
clusterIPfield toNone(Either with
--cluster-ip=None, or by providing a custom YAML) -
As a result, the service doesn't have a virtual IP address
-
Since there is no virtual IP address, there is no load balancer either
-
CoreDNS will return the pods' IP addresses as multiple
Arecords -
This gives us an easy way to discover all the replicas for a deployment
class: extra-details
Services and endpoints
-
A service has a number of "endpoints"
-
Each endpoint is a host + port where the service is available
-
The endpoints are maintained and updated automatically by Kubernetes
.exercise[
- Check the endpoints that Kubernetes has associated with our
httpenvservice:kubectl describe service httpenv
]
In the output, there will be a line starting with Endpoints:.
That line will list a bunch of addresses in host:port format.
class: extra-details
Viewing endpoint details
-
When we have many endpoints, our display commands truncate the list
kubectl get endpoints -
If we want to see the full list, we can use one of the following commands:
kubectl describe endpoints httpenv kubectl get endpoints httpenv -o yaml -
These commands will show us a list of IP addresses
-
These IP addresses should match the addresses of the corresponding pods:
kubectl get pods -l app=httpenv -o wide
class: extra-details
endpoints not endpoint
endpointsis the only resource that cannot be singular
$ kubectl get endpoint
error: the server doesn't have a resource type "endpoint"
-
This is because the type itself is plural (unlike every other resource)
-
There is no
endpointobject:type Endpoints struct -
The type doesn't represent a single endpoint, but a list of endpoints
class: extra-details
The DNS zone
-
In the
kube-systemnamespace, there should be a service namedkube-dns -
This is the internal DNS server that can resolve service names
-
The default domain name for the service we created is
default.svc.cluster.local
.exercise[
-
Get the IP address of the internal DNS server:
IP=$(kubectl -n kube-system get svc kube-dns -o jsonpath={.spec.clusterIP}) -
Resolve the cluster IP for the
httpenvservice:host httpenv.default.svc.cluster.local $IP
]
class: extra-details
Ingress
-
Ingresses are another type (kind) of resource
-
They are specifically for HTTP services
(not TCP or UDP)
-
They can also handle TLS certificates, URL rewriting ...
-
They require an Ingress Controller to function