Search code examples
javareactjsspring-bootgoogle-cloud-platformgoogle-kubernetes-engine

ERR_NAME_NOT_RESOLVED error when React app execute REST API call to Spring Boot app which deployed into GKE


I faced with problem of resolving my backend IP address from frontend service. Both deployed into GKE. There are two deployment files - for the backend and frontend. The frontend app calls the backend app API. On the local kubernetes everything is fine, but when I deploy to GKE there are problems with calling the API from the front. There is an error in the browser console:

error in console

I tried to pass the service name of the backend to the front deployment file in different ways:

  • imageupload-service:9595

  • imageupload-service.default.svc.cluster.local:9595

  • imageupload-service.default.svc:9595

but no luck.

Also I tried to execute command curl http://imageupload-service:9595/api/v1/user-profile from inside of my frontend pod to check availability of backend thought DNS and it returned correct response.

Here is my deployment files and content of App.js file where call to backend executed:

backend-deployment.yml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: imageupload-deployment
  namespace: default
  labels:
    app: imageupload-deployment
spec:
  progressDeadlineSeconds: 600
  replicas: 2
  selector:
    matchLabels:
      app: imageupload-deployment
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: imageupload-deployment
    spec:
      containers:
        - image:
          imagePullPolicy: Always
          name: imageupload
          env:
            - name: SPRING_DATASOURCE_URL
              value: "jdbc:postgresql://postgres-service:5432/postgres?currentSchema=imageupload"
            - name: SPRING_DATASOURCE_USER
              value: postgres
            - name: SPRING_JPA_HIBERNATE_DDL-AUTO
              value: none
            - name: SPRING_SQL_INIT_MODE
              value: NEVER
            - name: SPRING_DATASOURCE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: postgres-password-secret
                  key: postgres-password
          resources:
            limits:
              memory: "1024Mi"
              cpu: "500m"
            requests:
              memory: "256Mi"
              cpu: "200m"
      restartPolicy: Always
      terminationGracePeriodSeconds: 30

---

apiVersion: v1
kind: Service
metadata:
  name: imageupload-service
  namespace: default
  labels:
    app: imageupload-service
spec:
  selector:
    app: imageupload-deployment
  ports:
    - name: imageupload-service-port
      protocol: TCP
      port: 9595
  type: LoadBalancer
  loadBalancerIP: ""

---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: imageupload-deployment-hpa
  namespace: default
  labels:
    app: imageupload-service
spec:
  maxReplicas: 2
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: imageupload-deployment
  targetCPUUtilizationPercentage: 70

frontend-deployment.yml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: imageupload-front-deployment
  namespace: default
  labels:
    app: imageupload-front-deployment
spec:
  selector:
    matchLabels:
      app: imageupload-front-deployment
  replicas: 2
  template:
    metadata:
      labels:
        app: imageupload-front-deployment
    spec:
      containers:
        - name: imageupload-front
          image:
          env:
            - name: REACT_APP_API_URL
              value: http://imageupload-service:9595
          imagePullPolicy: Always
          resources:
            limits:
              cpu: "500m"
              memory: "1024Mi"
            requests:
              cpu: "200m"
              memory: "256Mi"

---
apiVersion: v1
kind: Service
metadata:
  name: imageupload-front-service
  namespace: default
  labels:
    app: imageupload-front-service
spec:
  selector:
    app: imageupload-front-deployment
  ports:
    - name: imageupload-front-service-port
      protocol: TCP
      port: 8080
  type: LoadBalancer
  loadBalancerIP: ""
---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: imageupload-front-deployment-hpa
  namespace: default
  labels:
    app: imageupload-front-service
spec:
  maxReplicas: 3
  minReplicas: 1
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: imageupload-front-deployment
  targetCPUUtilizationPercentage: 70

Content of App.js file:

import logo from './logo.svg';
import './App.css';
import axios from "axios";
import React, {useState, useEffect, useCallback} from "react";
import {useDropzone} from 'react-dropzone'

const apiUrl = process.env.REACT_APP_API_URL || "http://localhost:9595";
console.log("API URL:", apiUrl);

const UserProfiles = () => {

    const [userProfiles, setUserProfiles] = useState([]);

    const fetchUserProfiles = () => {
        axios.get(`${apiUrl}/api/v1/user-profile`).then(res => {
            console.log(res);
            setUserProfiles(res.data);
        })
            .catch(err => {
                console.error("Axios request failed:", err);
            });
    }

    useEffect(() => {
        fetchUserProfiles();
    }, []);

    return userProfiles.map((userProfile, index) => {
        return (
            <div key={userProfile.userProfileImageLink}>
                {userProfile.userProfileId ?
                    <img key={userProfile.userProfileImageLink}
                         src={`${apiUrl}/api/v1/user-profile/${userProfile.userProfileId}/image/download`}/> : null}
                <br/>
                <br/>
                <h1>{userProfile.username}</h1>
                <p>{userProfile.userProfileId}</p>
                <Dropzone {...userProfile} updateImageEvent={fetchUserProfiles}/>
                <br/>
            </div>
        )
    })
}

function Dropzone({userProfileId, updateImageEvent}) {
    const onDrop = useCallback(acceptedFiles => {
        const file = acceptedFiles[0];
        console.log(file);
        const formData = new FormData();
        formData.append("file", file);
        axios.post(`${apiUrl}/api/v1/user-profile/${userProfileId}/image/upload`,
            formData,
            {
                headers: {
                    "Content-Type": "multipart/form-data"
                }
            }).then(() => {
            console.log("file uploaded successfully");
            updateImageEvent();
        }).catch(err => {
            console.log(err);
        });
    }, [])
    const {getRootProps, getInputProps, isDragActive} = useDropzone({onDrop})

    return (
        <div {...getRootProps()}>
            <input {...getInputProps()} />
            {
                isDragActive ?
                    <p>Drop the image here ...</p> :
                    <p>Drag 'n' drop profile image, or click to select profile image</p>
            }
        </div>
    )
}

function App() {
    return (
        <div className="App">
            <UserProfiles/>
        </div>
    );
}

export default App;

I expected response in JSON format like:

{
    "userProfileId": "bb5b9dd8-f6fc-4bd4-8050-e9aa947e80cb",
    "username": "John Doe",
    "imageLink": ""
  }

Looking for help to understand what is wrong here and what am I doing wrong and why is this error happening? I have no ideas...


Solution

  • It's a common mistake with modern website deployment. Ask yourself: where is running my frontend?

    If your answer is "on GKE", it's wrong. GKE serves the web files (JS, HTML, CSS), but the code run on the browser.

    Therefore, the mention

    Also I tried to execute command curl http://imageupload-service:9595/api/v1/user-profile from inside of my frontend pod to check availability of backend thought DNS and it returned correct response.

    is correct FROM INSIDE.

    But your browser is OUTSIDE. All this URL are only known by your cluster, not on the internet

    imageupload-service:9595

    imageupload-service.default.svc.cluster.local:9595

    imageupload-service.default.svc:9595

    You must use the public URL of your ingress gateway (an IP or a domain name) to access your cluster FROM OUTSIDE.