kubernetes DNS does not work in react app

2/3/2020

I'm trying to deploy my simple web application using Kubernetes. I completed making the Kubernetes cluster and successfully exposed my react app using ingress. But it seems that the domain URL of backend service received from manifest file's "env" field does not work.

Following is the manifest file of react application.

kind: Service
apiVersion: v1
metadata:
  name: recofashion-client
  labels:
    app: recofashion-client
spec:
  type: NodePort
  selector:
    app: recofashion-client
  ports:
    - name: http
      port: 80
      targetPort: 3000

---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: recofashion-client
  labels:
    name: recofashion-client
spec:
  replicas: 2
  selector:
    matchLabels:
      app: recofashion-client
  template:
    metadata:
      labels:
        app: recofashion-client
    spec:
      containers:
      - name: web
        image: recofashion/client-runtime
        imagePullPolicy: Always
        ports:
        - containerPort: 3000
        env:
        - name: NODE_ENV
          value: production
        - name: REACT_APP_API_V1_ENDPOINT
          value: http://recofashion-api/api/v1

And I think there is no problem in k8s DNS itself. I tried to send request using curl in my "recofashion-client" pod, and it seems to work as I intended.

curl http://recofashion-api/api/v1/user/me
{"timestamp":"2020-02-03T06:55:20.748+0000","status":403,"error":"Forbidden","message":"Access Denied","path":"/api/v1/user/me"}

But when I try to send request in the browser, it fails like this:

image

And I'm receiving the external environment variables in react app from k8s like this:

const response = await getWithAuth(`${process.env.REACT_APP_API_V1_ENDPOINT}/user/me`)

So what's the problem??? I searched internet a lot, but I couldn't get any appopriate answer...

++ the manifest file of ingress

kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: ingress
spec:
  rules:
  - http:
      paths:
      - path: /*
        backend:
          serviceName: recofashion-client
          servicePort: 80
-- geunyoung
kubernetes

3 Answers

2/5/2020

Based on all informations provided I managed to reproduce your scenario using GKE.

TL;DR:

  • Yes, the correct service-type for your api-service is ClusterIP
  • The correct service-type for your api-client for outside access is LoadBalancer, doc here.
  • Your ENV REACT_APP_API_V1_ENDPOINT must point to Api Service address, not to deploy or pod address. (i.e: value: http://recofashion-api-svc)

  • You cannot use cluster DNS externally.

Reproduction

Since I don't have your react app, I'm using an echo-app to simulate the two parts of the communication. This way I'm manually reproducing what your application would do by itself.

  • First between internet and recofashion-client
  • Second between recofashion-client and recofashion-api.

recofashion-client - FrontEnd:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: recofashion-client
  labels:
    name: recofashion-client
spec:
  replicas: 2
  selector:
    matchLabels:
      app: recofashion-client
  template:
    metadata:
      labels:
        app: recofashion-client
    spec:
      containers:
      - name: web
        image: mendhak/http-https-echo
        ports:
        - name: http
          containerPort: 80
        env:
        - name: NODE_ENV
          value: production
        - name: REACT_APP_API_V1_ENDPOINT
          value: http://recofashion-api-svc
---
apiVersion: v1
kind: Service
metadata:
  name: recofashion-cli-svc
  labels:
    app: recofashion-client
spec:
  type: LoadBalancer
  selector:
    app: recofashion-client
  ports:
    - name: http
      port: 3000
      targetPort: 80

recofashion-api - backend API:

kind: Deployment
apiVersion: apps/v1
metadata:
  name: recofashion-api
  labels:
    name: recofashion-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: recofashion-api
  template:
    metadata:
      labels:
        app: recofashion-api
    spec:
      containers:
      - name: api-web
        image: mendhak/http-https-echo
        imagePullPolicy: Always
        ports:
        - containerPort: 80
        env:
        - name: NODE_ENV
          value: production
---
kind: Service
apiVersion: v1
metadata:
  name: recofashion-api-svc
  labels:
    app: recofashion-api
spec:
  selector:
    app: recofashion-api
  ports:
    - name: http
      port: 80
      targetPort: 80

Note: I kept your Ingress intact.

Now to the terminal:

$ kubectl get nodes
NAME                                       STATUS   ROLES    AGE   VERSION
gke-cluster-1-default-pool-e0523823-06jt   Ready    <none>   2d    v1.15.7-gke.23
gke-cluster-1-default-pool-e0523823-vklh   Ready    <none>   2d    v1.15.7-gke.23

$ kubectl apply -f recofashion-full.yaml 
deployment.apps/recofashion-client created
service/recofashion-cli-svc created
deployment.apps/recofashion-api created
service/recofashion-api-svc created
ingress.extensions/reco-ingress created

$ kubectl get all 
NAME                                      READY   STATUS    RESTARTS   AGE
pod/recofashion-api-784b4d9897-9256q      1/1     Running   0          12m
pod/recofashion-api-784b4d9897-ljkfs      1/1     Running   0          12m
pod/recofashion-client-75579c8499-wd5vj   1/1     Running   0          12m
pod/recofashion-client-75579c8499-x766s   1/1     Running   0          12m

NAME                          TYPE           CLUSTER-IP   EXTERNAL-IP     PORT(S)          AGE
service/kubernetes            ClusterIP      10.0.0.1     <none>          443/TCP          2d
service/recofashion-api-svc   ClusterIP      10.0.4.73    <none>          80/TCP           12m
service/recofashion-cli-svc   LoadBalancer   10.0.3.133   35.239.58.188   3000:31814/TCP   12m

NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/recofashion-api      2/2     2            2           13m
deployment.apps/recofashion-client   2/2     2            2           13m

NAME                                            DESIRED   CURRENT   READY   AGE
replicaset.apps/recofashion-api-784b4d9897      2         2         2       13m
replicaset.apps/recofashion-client-75579c8499   2         2         2       13m

$ curl http://35.239.58.188:3000  
{
  "path": "/",
  "headers": {
    "host": "35.239.58.188:3000",
    "user-agent": "curl/7.66.0",
  },
  "method": "GET",
  "body": "",
  "hostname": "35.239.58.188",
  "ip": "::ffff:10.8.1.1",
  "protocol": "http",
  "os": {
    "hostname": "recofashion-client-75579c8499-x766s"
  }
}

Client Access from browser from outside network

So far no problems, we are able to curl from outside to the recofashion-client.

Now let's connect inside recofashion-client and try to connect to recofashion-api using the ENV created during deploy:

❯ kubectl exec recofashion-client-75579c8499-x766s -it sh
/app # apk update && apk add curl
OK: 10 MiB in 20 packages

/app # env
REACT_APP_API_V1_ENDPOINT=http://recofashion-api-svc
NODE_ENV=production

/app # curl $REACT_APP_API_V1_ENDPOINT
{
  "path": "/",
  "headers": {
    "host": "recofashion-api-svc",
    "user-agent": "curl/7.61.1",
    "accept": "*/*"
  },
  "method": "GET",
  "body": "",
  "hostname": "recofashion-api-svc",
  "ip": "::ffff:10.8.1.21",
  "protocol": "http",
  "os": {
    "hostname": "recofashion-api-784b4d9897-9256q"
  }
}

/app # nslookup recofashion-api-svc
Name:      recofashion-api-svc
Address 1: 10.0.4.73 recofashion-api-svc.default.svc.cluster.local

When we use the api-service name in the ENV value, it resolves the DNS because the service is the responsible for directing the load to the PODs.

  • Follow this steps and you can be sure that your K8s configuration won't be an issue.

Edit:

-- willrof
Source: StackOverflow

2/4/2020

Ok, so you're not binding your ingress to any host name. So in order to access your service from the outside, you need to use the IP address associated to that ingress.
Using the service name won't have any affect from outside the cluster.

As the documentation says :

An optional host. In this example, no host is specified, so the rule applies to all inbound HTTP traffic through the IP address specified. If a host is provided (for example, foo.bar.com), the rules apply to that host.

In summary, if you have this configuration :

kubectl get ingress ingress # btw, this is a very bad name for your ingress !

NAME           HOSTS     ADDRESS           PORTS     AGE
ingress        *         107.178.254.228   80        59s

You should be able to access your service with this url : http://107.178.254.228/

You can find more info about how Ingress works (and this example in particulary) in the official documentation

After your clarification about the API, here is some precision :

As @saiteja pakalapati said, you can't access your API from outside your cluster using a ClusterIp. While using a NodePort can be a solution, you can just keep using your already existing Ingress. Example below :

kind: Ingress
apiVersion: extensions/v1beta1
metadata:
  name: ingress
spec:
  rules:
  - http:
      paths:
      - path: /*
        backend:
          serviceName: recofashion-client
          servicePort: 80
      - path: /api
        backend:
          serviceName: recofashion-api
          servicePort: 80 # change with whatever port you're using

Now, in your code, in order to call the API, you'll need to use the ingress IP (as you're already doing with the client) and the path to your api : http://107.178.254.228/api

-- Marc ABOUCHACRA
Source: StackOverflow

2/3/2020

You are trying to access the micro service that is restricted to cluster

recofashion-api can be resolved within the kubernetes cluster only not from outside

this line of code will be run in the browser who is accessing your site

const response = await getWithAuth(`${process.env.REACT_APP_API_V1_ENDPOINT}/user/me`)

so the browser cant resolve recofashion-api and your get method is failing

-- saiteja pakalapati
Source: StackOverflow