The network service is an application that receives requests and provides a solution. Clients access the service by a network connection. They don't have to know the architecture of the service or how it runs. The only thing that clients have to verify is whether the end point of the service is contactable, and then follow its usage policy to solve problems. The Kubernetes service has similar ideas. It is not necessary to understand every pod before reaching their functionalities. For components outside the Kubernetes system, they just access the Kubernetes service with an exposed network port to communicate with running pods. It is not necessary to be aware of the containers' IPs and ports. Therefore, we can fulfill a zero downtime update for our container programs without struggling:
The preceding image shows the basic structure of the service and realizes the following concepts:
In this recipe, you will learn how to create services along with your pods.
Prior to applying services, it is important to verify whether all your nodes in the system are running kube-proxy
. Daemon kube-proxy
works as a network proxy in node. It helps to reflect service settings like IPs or ports on each node. To check whether kube-proxy
is enabled or not, you can inspect the status of the daemon or search running processes on the node with a specific name:
// check the status of service kube-proxy # service kube-proxy status
or
// Check processes on each node, and focus on kube-proxy // grep "kube-proxy" or "hyperkube proxy" # ps aux | grep "kube-proxy"
For demonstration in later sections, you can also install a private network environment on the master node. The daemons related to the network settings are flanneld
and kube-proxy
. It is easier for you to do the operation and verification on a single machine. Otherwise, please verify Kubernetes services on a node, which by default, has an internal network ready.
We can define and create a new Kubernetes service through the CLI or a configuration file. Here we are going to explain how to deploy the services by command. The subcommands expose
and describe
are utilized in the following commands for various scenarios. The version of Kubernetes we used in this recipe is 1.1.3. For file-format creation, please go to Working with configuration files recipe in Chapter 3, Playing with Containers for a detailed discussion.
When creating services, there are two configurations with which we have to take care: one is the label, the other is the port. As the following image indicates, the service and pod have their own key-value pair labels and ports. Be assured to use correct tags for these settings:
To create a service like this one, push the following command:
# kubectl expose pod <POD_NAME> --labels="Name=Amy-log-service" --selector="App=LogParser,Owner=Amy" --port=8080 --target-port=80
The --labels
tag expose
in the subcommand is for labeling the services with key-value pairs. It is used to mark the services. For defining the selector of the service, use tag --selector
. Without setting the selector for service, the selector would be the same as the labels of resource. In the preceding image, the selector would have an addition label: Version=1.0.
To expose the service port, we send out a port number with the tag --port
in the subcommand expose
. The service will take the container port number as its exposed port if no specific number is assigned. On the other hand, the tag --target-port
points out the container port for service. While the target port is different from the exposed port of the container, users will get an empty response. At the same time, if we only assign the service port, the target port will copy it. Taking the previous image as an example, the traffic will be directed to container port 8080
supposing we don't use the tag --target-port
, which brings out a refused connection error.
You can attach a service to a pod, a replication controller and an endpoint outside the Kubernetes system, or even another service. We will show you these, one by one, in the next pages. The service creation is in the format: kubectl expose RESOURCE_TYPE RESOURCE_NAME [TAGS]
or kubectl expose -f CONFIGURATION_FILE [TAGS]
. Simply put, the resource types pod, replication controller and service are supported by the subcommand expose
. So is the configuration file which follows the type limitation.
The pods shielded by the service need to contain labels, because the service takes this as a necessary condition based on the selector:
// Create a pod, and add labels to it for the selector of service. # kubectl run nginx-pod --image=nginx --port=80 --restart="Never" --labels="app=nginx" pod "nginx-pod" created # kubectl expose pod nginx-pod --port=8000 --target-port=80 --name="service-pod" service "service-pod" exposed
Resource type |
Abbreviated alias |
---|---|
Componentstatuses |
|
Events |
|
Endpoints |
|
Horizontalpodautoscaler |
|
Limitranges |
|
Nodes |
|
Namespaces |
|
Pods |
|
Persistentvolumes |
|
Persistentvolumesclaims |
|
Resourcequotas |
|
Replicationcontrollers |
|
Services |
|
Ingress |
|
// "svc" is the abbreviation of service # kubectl get svc service-pod NAME CLUSTER_IP EXTERNAL_IP PORT(S) SELECTOR AGE service-pod 192.168.195.195 <none> 8000/TCP app=nginx 11s
As you see in these commands, we open a service with port 8000
exposed. The reason why we specify the container port is so that the service doesn't take 8000
as the container port, by default. To verify whether the service is workable or not, go ahead with the following command in an internal network environment (which has been installed with the Kubernetes cluster CIDR).
// accessing by services CLUSTER_IP and PORT # curl 192.168.195.195:8000
A replication controller is the ideal resource type for a service. For pods supervised by the replication controller, the Kubernetes system has a controller manager to look over the lifecycle of them. It is also helpful for updating the version or state of program by binding existing services to another replication controller:
// Create a replication controller with container port 80 exposed # kubectl run nginx-rc --image=nginx --port=80 --replicas=2 replicationcontroller "nginx-rc" created # kubectl expose rc nginx-rc --name="service-rc" --external-ip="<USER_SPECIFIED_IP>" service "service-rc" exposed
In this case, we can provide the service with another IP address, which doesn't need to be inside the cluster network. The tag --external-ip
of the subcommand expose
can realize this static IP requirement. Be aware that the user-specified IP address could be contacted, for example, with the master node public IP:
// EXTERNAL_IP has Value shown on # kubectl get svc service-rc NAME CLUSTER_IP EXTERNAL_IP PORT(S) SELECTOR AGE service-rc 192.168.126.5 <USER_SPECIFIED_IP> 80/TCP run=nginx-rc 4s
Now, you can verify the service by 192.168.126.5:80
or <USER_SPECIFIED_IP>:80
:
// Take a look of service in details # kubectl describe svc service-rc Name: service-rc Namespace: default Labels: run=nginx-rc Selector: run=nginx-rc Type: ClusterIP IP: 192.168.126.5 Port: <unnamed> 80/TCP Endpoints: 192.168.45.3:80,192.168.47.2:80 Session Affinity: None No events.
You will find that the label and selector of a service is the default of the replication controller. In addition, there are multiple endpoints, which are replicas of the replication controller, available for dealing with requests from the service.
First, you should have an endpoint with an IP address. For example, we can generate an individual container in an instance, where it is located outside our Kubernetes system but is still contactable:
// Create an nginx server on another instance with IP address <FOREIGN_IP> # docker run -d -p 80:80 nginx 2a17909eca39a543ca46213839fc5f47c4b5c78083f0b067b2df334013f62002 # docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 2a17909eca39 nginx "nginx -g 'daemon off" 21 seconds ago Up 20 seconds 0.0.0.0:80->80/tcp, 443/tcp goofy_brown
Then, in the master, we can create a Kubernetes endpoint by using the configuration file. The endpoint is named service-foreign-ep
. We could configure multiple IP addresses and ports in the template:
# cat nginx-ep.json { "kind": "Endpoints", "apiVersion": "v1", "metadata": { "name": "service-foreign-ep" }, "subsets": [ { "addresses": [ { "ip": "<FOREIGN_IP>" } ], "ports": [ { "port": 80 } ] } ] } # kubectl create -f nginx-ep.json endpoints "service-foreign-ep" created # kubectl get ep service-foreign-ep NAME ENDPOINTS AGE service-foreign-ep <FOREIGN_IP>:80 16s
As mentioned in the previous section, we can start a service for a resource-configured template with the subcommand expose
. However, the CLI is unable to support exposing an endpoint in the file format:
// Give it a try! # kubectl expose -f nginx-ep.json error: invalid resource provided: Endpoints, only a replication controller, service or pod is accepted
Therefore, we create the service through a configuration file:
# cat service-ep.json { "kind": "Service", "apiVersion": "v1", "metadata": { "name": "service-foreign-ep" }, "spec": { "ports": [ { "protocol": "TCP", "port": 80, "targetPort" : 80 } ] } }
The most important thing of all is that there is no selector defined in the template. This is quite reasonable since the endpoints are not in the Kubernetes system. The relationship between endpoints and service is built up by resource name. As you can see, the name of the service must be identical to the name of the endpoint:
# kubectl create -f service-ep.json service "service-foreign-ep" created // Check the details in service # kubectl describe svc service-foreign-ep Name: service-ep Namespace: default Labels: <none> Selector: <none> Type: ClusterIP IP: 192.168.234.21 Port: <unnamed> 80/TCP Endpoints: <FOREIGN_IP>:80 Session Affinity: None No events.
Finally, the no-selector service is created for the external endpoint. Verify the result with <FOREIGN_IP>:80
.
Through the subcommand expose
, we can also copy the settings of one service to another:
// Check the service we created for replication controller in previous section # kubectl describe svc service-rc Name: service-rc Namespace: default Labels: run=nginx-rc Selector: run=nginx-rc Type: ClusterIP IP: 192.168.126.5 Port: <unnamed> 80/TCP Endpoints: 192.168.45.3:80,192.168.47.2:80 Session Affinity: None No events. //Create a new service with different name and service port # kubectl expose svc service-rc --port=8080 --target-port=80 --name=service-2nd --session-affinity="ClientIP" service "service-2nd" exposed
The new service named service-2nd
is reset with service port 8080
and session affinity is enabled:
# kubectl describe svc service-2nd Name: service-2nd Namespace: default Labels: run=nginx-rc Selector: run=nginx-rc Type: ClusterIP IP: 192.168.129.65 Port: <unnamed> 8080/TCP Endpoints: 192.168.45.3:80,192.168.47.2:80 Session Affinity: ClientIP No events.
Currently, the ClientIP
is the only valued setting for the tag --session-affinity
. While session affinity to the ClientIP
is enabled, instead of round robin, the request of which endpoint the service should be sent to would be decided by the ClientIP
. For example, if the requests from the client in the CIDR range 192.168.45.0/24
are sent to service service-2nd
, they will be transferred to the endpoint 192.168.45.3:80
.
There are three types of service: ClusterIP, NodePort and LoadBalancer:
By default, every service is created as a ClusterIP type. The service in the ClusterIP type would be assigned an internal IP address randomly. For the NodePort type, it covers the ClusterIP's feature, and also allows the user to expose services on each node with the same port. The LoadBalancer is on the top of the other two types. The LoadBalancer service would be exposed internally and on the node. Besides this, if your cloud provider supports external load balancing servers, you can bind the load balancer IP to the service and this will become another exposing point.
Next, we are going to show you how to create a NodePort
service. The tag --type
in the subcommand expose
helps to define the service type:
// Create a service with type NodePort, attaching to the replication controller we created before # kubectl expose rc nginx-rc --name=service-nodeport --type="NodePort" service "service-nodeport" exposed # kubectl describe svc service-nodeport Name: service-nodeport Namespace: default Labels: run=nginx-rc Selector: run=nginx-rc Type: NodePort IP: 192.168.57.90 Port: <unnamed> 80/TCP NodePort: <unnamed> 31841/TCP Endpoints: 192.168.45.3:80,192.168.47.2:80 Session Affinity: None No events.
In the preceding case, the network port 31841
exposed on a node is randomly assigned by the system; the default port range is 30000
to 32767
. Notice that the port is exposed on every node in the system, so it is fine to access the service through <NODE_IP>:31841
, for example, through the domain name of a node, like kube-node1:31841
.
The main actors in the Kubernetes system that perform the service environment are flanneld and kube-proxy. Daemon flanneld builds up a cluster network by allocating a subnet lease out of a preconfigured address space, and storing the network configuration in etcd, while kube-proxy directs the endpoints of services and pods.
To get the best use of services, the following recipes are suggested to be read as well: