22

I have a kubernetes cluster running with 2 minions. Currently I make my service accessible in 2 steps:

  1. Start replication controller & pod
  2. Get minion IP (using kubectl get minions) and set it as publicIPs for the Service.

What is the suggested practice for exposing service to the public? My approach seems wrong because I hard-code the IP-s of individual minion IP-s. It also seems to bypass load balancing capabilities of kubernetes services because clients would have to access services running on individual minions directly.

To set up the replication controller & pod I use:

id: frontend-controller
kind: ReplicationController
apiVersion: v1beta1
desiredState:
  replicas: 2
  replicaSelector:
    name: frontend-pod
  podTemplate:
    desiredState:
      manifest:
        version: v1beta1
        id: frontend-pod
        containers:
          - name: sinatra-docker-demo
            image: madisn/sinatra_docker_demo
            ports:
              - name: http-server
                containerPort: 4567
    labels:
      name: frontend-pod

To set up the service (after getting minion ip-s):

kind: Service
id: frontend-service
apiVersion: v1beta1
port: 8000
containerPort: http-server
selector:
  name: frontend-pod
labels:
  name: frontend
publicIPs: [10.245.1.3, 10.245.1.4]
Madis Nõmme
  • 1,264
  • 2
  • 15
  • 25
  • Where are you running your cluster? Some cloud providers, such as GCE and GKE allow you to create a service with an external load balancer. – Robert Bailey Apr 21 '15 at 17:10
  • I'm using vagrant provider locally (development) and EC2 in production so I'm looking for a solution that would work similarly independent of the provider. – Madis Nõmme Apr 21 '15 at 18:23
  • CreateExternalLoadbalancer is the abstraction you are looking for, but unfortunately it isn't implemented for Vagrant. – Robert Bailey Apr 21 '15 at 23:57

5 Answers5

8

As I mentioned in the comment above, the createExternalLoadBalancer is the appropriate abstraction that you are looking for, but unfortunately it isn't yet implemented for all cloud providers, and in particular for vagrant, which you are using locally.

One option would be to use the public IPs for all minions in your cluster for all of the services you want to be externalized. The traffic destined for the service will end up on one of the minions, where it will be intercepted by the kube-proxy process and redirected to a pod that matches the label selector for the service. This could result in an extra hop across the network (if you land on a node that doesn't have the pod running locally) but for applications that aren't extremely sensitive to network latency this will probably not be noticeable.

Robert Bailey
  • 17,866
  • 3
  • 50
  • 58
  • Do I understand correctly? With this multiple IP-s per service solution the *PublicIPs* array would be updated to include all the online pods for service. As the pods get taken offline/brought online by replication controller, some logic has to update Service's definition. Also DNS server would have to return these same (pod) IP-s when requested, basically using round-robin DNS to load balance. If that is true, where would you hook the code in kubernetes world to observe changes made to pods (to update service & dns)? – Madis Nõmme Apr 27 '15 at 07:27
  • 1
    There is a daemon running on each machine called the kube-proxy that watches the pods and maps a service to the set of pods that match the label selector for the service. The service represents a stable way (IP+port) to reach the pods even as the pods move around the cluster. If you use PublicIPs with a service, the kube-proxy will intercept requests to those IPs and redirect them to the pods that are part of the service. – Robert Bailey Apr 29 '15 at 06:48
  • If you want to do HTTP load balancing, you can also take a look at the [Kubernetes Ingress](http://kubernetes.io/v1.1/docs/user-guide/ingress.html) object. – Robert Bailey Jan 15 '16 at 19:31
  • The extra hop across the network happens if there is another pod at different node, regardless having a locally running pod. A NodePort service will randomly distribute requests to all pods. The hop seems to decrease request per second, since iptables routing could trigger high CPU usage of ksoftirqd. See [K8S service NodePort with low efficiency](http://stackoverflow.com/questions/36194263/k8s-service-nodeport-with-low-efficiency). – gosharplite Mar 29 '16 at 06:04
7

As Robert said in his reply this is something that is coming up, but unfortunately isn't available yet.

I am currently running a Kubernetes cluster on our datacenter network. I have 1 master and 3 minions all running on CentOS 7 virtuals (vcenter). The way I handled this was to create a dedicated "kube-proxy" server. I basically am just running the Kube-Proxy service (along with Flannel for networking) and then assigning "public" IP addresses to the network adapter attached to this server. When I say public I mean addresses on our local datacenter network. Then when I create a service that I would like to access outside of the cluster I just set the publicIPs value to one of the available IP addresses on the kube-proxy server. When someone or something attempts to connect to this service from outside the cluster it will hit the kube-proxy and then be redirected to the proper minion.

While this might seem like a work around, this is actually similar to what I would expect to be happening once they come up with a built in solution to this issue.

2

If you're running a cluster locally, a solution I used was to expose the service on your kubernetes nodes using the nodeport directive in your service definition and then round robin to every node in your cluster with HAproxy.

Here's what exposing the nodeport looks like:

apiVersion: v1
kind: Service
metadata:
  name: nginx-s
  labels:
    name: nginx-s
spec:
  type: NodePort
  ports:
    # must match the port your container is on in your replication controller
    - port: 80
      nodePort: 30000
  selector:
    name: nginx-s

Note: the value you specify must be within the configured range for node ports. (default: 30000-32767)

This exposes the service on the given nodeport on every node in your cluster. Then I set up a separate machine on the internal network running haproxy and a firewall that's reachable externally on the specified nodeport(s) you want to expose.

If you look at your nat table on one of your hosts, you can see what it's doing.

root@kube01:~# kubectl create -f nginx-s.yaml
You have exposed your service on an external port on all nodes in your
cluster.  If you want to expose this service to the external internet, you may
need to set up firewall rules for the service port(s) (tcp:30000) to serve traffic.

See http://releases.k8s.io/HEAD/docs/user-guide/services-firewalls.md for more details.
services/nginx-s
root@kube01:~# iptables -L -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
KUBE-PORTALS-CONTAINER  all  --  anywhere             anywhere             /* handle ClusterIPs; NOTE: this must be before the NodePort rules */
DOCKER     all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL
KUBE-NODEPORT-CONTAINER  all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL /* handle service NodePorts; NOTE: this must be the last rule in the chain */

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
KUBE-PORTALS-HOST  all  --  anywhere             anywhere             /* handle ClusterIPs; NOTE: this must be before the NodePort rules */
DOCKER     all  --  anywhere            !127.0.0.0/8          ADDRTYPE match dst-type LOCAL
KUBE-NODEPORT-HOST  all  --  anywhere             anywhere             ADDRTYPE match dst-type LOCAL /* handle service NodePorts; NOTE: this must be the last rule in the chain */

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.17.0.0/16        anywhere

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  anywhere             anywhere

Chain KUBE-NODEPORT-CONTAINER (1 references)
target     prot opt source               destination
REDIRECT   tcp  --  anywhere             anywhere             /* default/nginx-s: */ tcp dpt:30000 redir ports 42422

Chain KUBE-NODEPORT-HOST (1 references)
target     prot opt source               destination
DNAT       tcp  --  anywhere             anywhere             /* default/nginx-s: */ tcp dpt:30000 to:169.55.21.75:42422

Chain KUBE-PORTALS-CONTAINER (1 references)
target     prot opt source               destination
REDIRECT   tcp  --  anywhere             192.168.3.1          /* default/kubernetes: */ tcp dpt:https redir ports 51751
REDIRECT   tcp  --  anywhere             192.168.3.192        /* default/nginx-s: */ tcp dpt:http redir ports 42422

Chain KUBE-PORTALS-HOST (1 references)
target     prot opt source               destination
DNAT       tcp  --  anywhere             192.168.3.1          /* default/kubernetes: */ tcp dpt:https to:169.55.21.75:51751
DNAT       tcp  --  anywhere             192.168.3.192        /* default/nginx-s: */ tcp dpt:http to:169.55.21.75:42422
root@kube01:~#

Particularly this line

DNAT       tcp  --  anywhere             anywhere             /* default/nginx-s: */ tcp dpt:30000 to:169.55.21.75:42422

And finally, if you look at netstat, you can see kube-proxy is listening and waiting for that service on that port.

root@kube01:~# netstat -tupan | grep 42422
tcp6       0      0 :::42422                :::*                    LISTEN      20748/kube-proxy
root@kube01:~#

Kube-proxy will listen on a port for each service, and do network address translation into your virtual subnet that your containers reside in. (I think?) I used flannel.


For a two node cluster, that HAproxy configuration might look similiar to this:

listen sampleservice 0.0.0.0:80
    mode http
    stats enable
    balance roundrobin
    option httpclose
    option forwardfor
    server noname 10.120.216.196:30000 check
    server noname 10.155.236.122:30000 check
    option httpchk HEAD /index.html HTTP/1.0

And your service is now reachable on port 80 via HAproxy. If any of your nodes go down, the containers will be moved to another node thanks to replication controllers and HAproxy will only route to your nodes that are alive.

I'm curious what methods others have used though, that's just what I came up with. I don't usually post on stack overflow, so apologies if I'm not following conventions or proper formatting.

Carlin
  • 51
  • 3
1

This is for MrE. I did not have enough space in the comments area to post this answer so I had to create another answer. Hope this helps:

We have actually moved away from Kubernetes since posting this reply. If I remember correctly though all I really had to do was run the kube-proxy executable on a dedicated CentOS VM. Here is what I did:

First I removed Firewalld and put iptables in place. Kube-proxy relies on iptables to handle its NAT and redirections.

Second, you need to install flanneld so you can have a bridge adapter on the same network as the Docker services running on your minions.

Then what I did was assign multiple ip addresses to the local network adapter installed on the machine. These will be the ip addresses you can use when setting up a service. These will be the addresses available OUTSIDE your cluster.

Once that is all taken care of you can start the proxy service. It will connect to the Master, and grab an IP address for the flannel bridge network. Then it will sync up all the IPtables rules and you should be set. Every time a new service is added it will create the proxy rules and replicate those rules across all minions (and your proxy). As long as you specified an ip address available on your proxy server then that proxy server will forward all traffic for that ip address over to the proper minion.

Hope this is a little more clear. Remember though I have not been part of the Kubernetes project for about 6 months now so I am not sure what changed have been made since I left. They might even have a feature in place that handles this sort of thing. If not hopefully this helps you get it taken care of.

0

You can use Ingress resource to allow external connections from outside of a Kubernetes cluster to reach the cluster services.

Assuming that you already have a Pod deployed, you now need a Service resource, e.g.:

apiVersion: v1 kind: Service metadata: name: frontend-service labels: tier: frontend spec: type: ClusterIP selector: name: frontend-pod ports: - name: http protocol: TCP # the port that will be exposed by this service port: 8000 # port in a docker container; defaults to what "port" has set targetPort: 8000

And you need an Ingress resource: apiVersion: extensions/v1beta1 kind: Ingress metadata: name: frontend-ingress spec: rules: - host: foo.bar.com http: paths: - path: / backend: serviceName: frontend-service # the targetPort from service (the port inside a container) servicePort: 8000 In order to be able to use Ingress resources, you need some ingress controller deployed.

Now, providing that you know your Kubernetes master IP, you can access your application from outside of a Kubernetes cluster with: curl http://<master_ip>:80/ -H 'Host: foo.bar.com'


If you use some DNS server, you can add this record: foo.bar.com IN A <master_ip> or add this line to your /etc/hosts file: <master_ip> foo.bar.com and now you can just run: curl foo.bar.com


Notice, that this way you will always access foo.bar.com using port 80. If you want to use some other port, I recommend using a Service of type NodePort, only for that one not-80 port. It will make that port resolvable, no matter which Kubernetes VM IP you use (any master or any minion IP is fine). Example of such a Service: apiVersion: v1 kind: Service metadata: name: frontend-service-ssh labels: tier: frontend spec: type: NodePort selector: name: frontend-pod ports: - name: ssh targetPort: 22 port: 22 nodePort: 2222 protocol: TCP And if you have <master_ip> foo.bar.com in your /etc/hosts file, then you can access: foo.bar.com:2222

Ewa
  • 553
  • 7
  • 13
  • I've some questions about your solution. I'm new to kubernetes and trying to do something similar to that. But what I found was: 1. When I use the ingress, it binds to my node internal ip, not the external one 2. When I try to use NodePort, it doesn't allow me to do it with a port < 3000 Is any of this configurable? – Tiago Sep 07 '17 at 20:34
  • @Tiago, on 2: you can set this apiserver option to e.g. `--service-node-port-range="2000-32767"`. on 1: I'm not in this context right now, so I won't add anything for now. (Sorry for long time not answering, I was on vacation). – Ewa Oct 06 '17 at 14:27