17

For local development I have a working minikube. There we have different services deployed. Now I want to connect the Frontend with the Backend.

The Frontend is a angular application and lives in its own service. The Backend is a node.js application also using a separate service and uses DNS to connect to other internal services like mongodb.

Now I want to communicate from the Frontend with the Backend. DNS is not working because the Frontend does not know how to resolve the named route. The problem is to tell the frontend which backend URL and port it should use to send requests to?

The only working state was approached when I first started the Backend service with type NodePort and copied the url and port to the Frontends target URL. I think this is very unclean to me. Is there another approach to get the url for backend requests into the Frontend?

I know when we deploy a service on a production system with type="LoadBalancer" that the service is exposed by an external IP and I can access the service then from there. And that the external IP will be permanent at pod updates and so on. The problem I also see is that the backend IP needs to be injected into the docker container by an additional commit.

Edit(1): The backend service

apiVersion: v1
kind: Service
metadata:
  name: backend
  labels:
    app: some-app
    tier: backend
spec:
  type: NodePort
  ports:
  - port: 3000
  selector:
    app: some-app
    tier: backend

Edit(2): I also get this response when I request from the client with a fqn:

OPTIONS http://backend.default.svc.cluster.local:3000/signup/ net::ERR_NAME_NOT_RESOLVED
Lunero
  • 666
  • 1
  • 8
  • 17
  • did you expose you backend with a service ? if yes you should have defined the ports in the service definition and you should get DNS resolution for say http://backend:80/ – Sebastien Goasguen Jul 18 '17 at 11:31
  • @SebastienGoasguen I have added the backend service definition. So I have defined port 3000 for the backend and it is also not reachable. I think the angular application does not know how to resolve the dns name 'backend'. – Lunero Jul 18 '17 at 11:51
  • can you try with fqn like this `backend.default.svc.cluster.local`. you may need to replace default with your name space. – sfgroups Jul 18 '17 at 12:27
  • 1
    @sfgroups The fqn solution does sadly also not work. Maybe a dumb question. But the url has to be prepended by a protocol like http? – Lunero Jul 18 '17 at 13:24
  • yes, to call the rest service we need to use the protocol in front. most likely you pods are not associated to this service. can you double the selector for pod and service? – sfgroups Jul 18 '17 at 14:41
  • Thanks. I have looked into the selectors of the deployment and service they have the label 'app: some-app' in common. I do not understand how the client in a Webbrowser will resolve the name 'backend' and send it then to the correct backend IP. – Lunero Jul 18 '17 at 15:05
  • 5
    your last comment is exactly the point: the frontend (the angular app) runs in a browser, and the browser is very probably not inside the k8s cluster. So using a service name for calling the backend can not work. – slintes Jul 19 '17 at 07:39
  • @slintes If we give internal IP address instead of service.namespace.svc.cluster.local, then it works. How is it possible? – Prakash Mar 09 '21 at 05:00
  • @Prakash If the IP is reachable by the frontend (which lives in the browser) then it is correct. If you pass in the service fqn then the frontend will not be able to resolve this because it does not have access to this information in the browser as slintes mentioned. If the browser has access to this fqn information then you have created a security issue I guess. – Lunero May 19 '21 at 20:54

4 Answers4

13

First I will try to address your specific questions

The only working state was approached when I first started the Backend service with type NodePort and copied the url and port to the Frontends target URL. I think this is very unclean to me. Is there another approach to get the url for backend requests into the Frontend?

You have couple of options here 1) As you said, use type="LoadBalancer". OR 2) Proxy all your backend calls through your front end server

I know when we deploy a service on a production system with type="LoadBalancer" that the service is exposed by an external IP and I can access the service then from there. And that the external IP will be permanent at pod updates and so on. The problem I also see is that the backend IP needs to be injected into the docker container by an additional commit.

  1. Make it a 12-factor app (or 1 step closer to a 12-factor app :)) by moving the config out from your code to platform (let's say to k8s configmap or an external KV registry like consul/eureka)
  2. Even if it's left in code, as you said, the external IP will be referable and it's not going to change unless you do so. I don't see why you need another deployment

Proxy all your backend calls through your front end server

If you are routing (or willing to route) all your microservices/backend call thru the server side of your front end and if are deploying both your front end and backend in the same k8s cluster in the same namespace, then you can use KubeDNS add-on (If it is not available in your k8s cluster yet, you can check with the k8s admin) to resolve the backend service name to it's IP. From your front end server, Your backend service will always be resolvable by it's name.

Since you have kubeDNS in your k8s cluster, and both frontend and backend services resides in same k8s cluster and same namespace, we can make use of k8s' inbuilt service discovery mechanism. Backend service and frontend service will be discoverable each other by it's name. That means, you can simply use the DNS name "backend" to reach your backend service from your frontend pods. So, just proxy all the backend request through your front end nginx to your upstream backend service. In the frontend nginx pods, backend service's IP will resolvable for the domain name "backend". This will save you the CORS headache too. This setup is portable, meaning, it doesn't matter whether you are deploying in dev or stage or prod, name "backend" will always resolve to the corresponding backend.

A potential pitfall of this approach is, your backend may not be able to scale independent of frontend; Which is not a big deal in my humble opinion; In a k8s environment, it is just a matter of spinning up more pods if needed.

Just curious- What is serving your front end (which server technology is delivering your index.html to user's browser)? is it static servers like nginx or apache httpd or are you using nodejs here?

so-random-dude
  • 15,277
  • 10
  • 68
  • 113
  • Sorry I did not mention currently there is a nginx service in front of it which handles the Backend and Frontend. So my Backend, Frontend and nginx are in the same namespace. And Kubedns is also defined. – Lunero Jul 18 '17 at 18:56
  • No worries. So, it is very easy for you to implement, proxy all the backend request through your front end nginx to your upstream backend service. In the frontend nginx, backend service's IP will resolvable for the domain name "backend". This will save you the CORS headache too. This setup is portable, meaning, it doesn't matter whether you are deploying in dev or stage or prod, name "backend" will always resolve to the corresponding backend. – so-random-dude Jul 18 '17 at 22:54
  • Thanks that helped a lot. In combination with the answer of Marc Sluiter I got it to work. Thanks in advance. – Lunero Jul 19 '17 at 12:11
  • 1
    @so-random-dude Thank you for the answers, it is very clear and concise. Just one thing I don't understand, what do you mean when you said: "This will save you the CORS headache too"? because from my understanding, even though the two services are in the same cluster, they still have very different clusterIP, one might be 10.271.28.9 and the other is 10.110.8.6. So there will still be problems with CORS. – Hansen W Oct 15 '18 at 08:08
  • @HansenW, Not if you are proxying all the API/service calls through your front-end pod – so-random-dude Oct 15 '18 at 14:49
  • @so-random-dude, yeah, you are right, it makes sense now, I was stupid. Do you happen to know what would be the best practice if I were to use an ingress? Do i go client -> ingress -> nginx for static serving -> backend, or can I serve frontend with the ingress controller? – Hansen W Oct 15 '18 at 23:50
  • @Lunero, so after all, how to set up front end app to route the traffic through nginx? – orkenstein Jun 10 '19 at 15:48
  • 1
    @orkenstein We defined a reverse proxy for the route /api/ which is proxied to the backend. You only need to call /api/. – Lunero Jun 11 '19 at 11:38
  • @Lunero, thanks for the tip! Could you, please, clarify, where reverse proxy should be defined? The whole topic is new to me – orkenstein Jun 11 '19 at 13:29
  • @orkenstein This is no topic related to the original question. But here you might understand what I meant https://stackoverflow.com/questions/5009324/node-js-nginx-what-now?rq=1. – Lunero Jun 11 '19 at 15:22
  • @Lunero, I ended up setting up proxy like this: https://stackoverflow.com/questions/57074795/nginx-proxy-on-kubernetes – orkenstein Jul 17 '19 at 17:39
  • I still cant get it, how am I supposed to set my routes in my frontend to be available for backend. Could You explain a bit more? I use react on my frontend and java + spring-boot on my backend. However I am still unable to get requests from my frontend to backend and when I invoke a curl from the frontend pod everything works. I think that this is similar reason, however I dont understand it very well. Could You help? – Hubert Bratek Jul 25 '19 at 16:51
3

We use a different approach than in the answer of so-random-dude (which a nice solution): we let the backend server serve the frontend files. We have separated docker images for both, but use 1 pod only. The frontend runs as init container and copies the files to a emptydir volume. The backend also mounts that volume and serves it on / (all backend resources are served on other paths). This way the frontend and the backend are served on the same host.

You can get the current host (which also is the backend host now) in the Angular code with window.location.protocol + '//' + window.location.host.

During development on the local dev machine we run the frontend and backend on its own separated servers. So we have a little helper function for getting the correct backend url in all cases:

public getBackendUrl(): string {
  return this.getBackendUrlInternal(window.location.protocol, window.location.host);
}

private getBackendUrlInternal(protocol: string, host: string): string {
  if (host === 'localhost:3000') {
    // running in local dev server, connect to local dev backend
    return 'http://localhost:8585';
  } else {
    // running in docker compose or on k8s, backend is on same host and port as we are
    return protocol + '//' + host;
  }
}

(There 2 methods because we have some tests for the 2nd one)

slintes
  • 740
  • 5
  • 9
  • Techanically ur solution is correct but operationally seems not. U r defying the purpose kuernates pod. What if i want to scale only backened or frontend only. U are coupling tightly backend and frontend. Ur solution is ok in case requirment demands design to be tightly coupled – Qasim Jul 16 '22 at 22:07
2

I would sugest using Kubernetes own way of flexible tarffic ingestion with Ingress/IngressController. Havin ingress controller deployed in your cluster, you can easily create Ingress definition telling the controller to expose service under particular URL. All you need to do then is to point that name in DNS to the Ingress Controllers loadbalancer (in most cloud setups that will be with a CNAME to the LB fqdn).

https://kubernetes.io/docs/concepts/services-networking/ingress/ https://github.com/kubernetes/ingress

Radek 'Goblin' Pieczonka
  • 21,554
  • 7
  • 52
  • 48
2

This is how i have configured Ingress to connect frontend app to backend using service names.

Bankend (Spring Boot App) Note the name attribute of service

  • Service config
apiVersion: v1
kind: Service
metadata:
  name: student-app-api
spec:
  selector:
    app: student-app-api
  ports:
    - port: 8080
      protocol: TCP
      targetPort: 8080

Frontend Frontend does not need to know about the backend server.

lets assume frontend needs to call getAllStudents API of backend. Frontend can call API like this Sample React code

const get = id => {
  return http.get(`api/students/`);
};

//this will send request to own server, and we will redirect that request to backend using Ingress config like below

Ingress

apiVersion: networking.k8s.io/v1beta1 # for versions before 1.14 use extensions/v1beta1
kind: Ingress
metadata:
  name: student-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
  - http:
      paths:
        - path: /?(.*)
          backend:
            serviceName: student-app-client-service
            servicePort: 80
        - path: /api/?(.*)   //Redirect all request to backend (backend service name)
          backend:
            serviceName: student-app-api
            servicePort: 8080
Niraj Sonawane
  • 10,225
  • 10
  • 75
  • 104