Connect React Client running on local PC to Kubernetes Cluster on Azure - reactjs

I have 2 deployments + services running on Azure: react client and nodejs auth.
I have registered a public IP on Azure which I added to my windows host file (= myexample.com).
Typing the URL in the browser, the client opens and requests go to auth service as expected.
Now I want to run the client locally (with npm start) but connect to auth service still running on Azure.
I removed the client from the cloud deployment (= the deployment+the service) and use the domain (=myexample.cloud) as the base URL in my axios client in my React client. To confirm, on Azure my ingress-nginx-controller of type Load_Balancer shows the aforementioned public IP as its external IP plus ports 80:30819/TCP,443:31077/TCP.
When I ran the Client locally, it shows the correct request URL (http://myexample.cloud/api/users/signin) but I get a 403 Forbidden answer.
What am I missing? I should be able to connect to my cloud service by using the public IP? There error is caused by my client because Azure is not putting road blocks in place. I mean it is a pubic IP, correct?
Update 1
Just to clarify, the 403 Forbidden is not caused by me trying to sign in with incorrect credentials. I have another api/users/health-ckeck route that is giving me the same error
My cloud ingress deployment. I have also tried to remove the client part (last 7 lines) to no effect.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-service
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
kubernetes.io/ingress.class: nginx
spec:
rules:
- host: myexample.cloud
http:
paths:
- path: /api/users/?(.*)
pathType: Prefix
backend:
service:
name: auth-srv
port:
number: 3000
- path: /
pathType: Prefix
backend:
service:
name: client-srv
port:
number: 3000
my client cloud deployment+service that worked when client was running in cloud
apiVersion: apps/v1
kind: Deployment
metadata:
name: client
spec:
replicas: 1
selector:
matchLabels:
app: client
template:
metadata:
labels:
app: client
spec:
containers:
- name: client
image: client
---
apiVersion: v1
kind: Service
metadata:
name: client
spec:
selector:
app: client
ports:
- name: client
protocol: TCP
port: 3000
targetPort: 3000
my auth deployment + service
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth
spec:
replicas: 1
selector:
matchLabels:
app: auth
template:
metadata:
labels:
app: auth
spec:
containers:
- name: auth
image: auth
apiVersion: v1
kind: Service
metadata:
name: auth
spec:
selector:
app: auth
ports:
- name: auth
protocol: TCP
port: 3000
targetPort: 3000

The problem was actually CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin'
but my browser did not tell me.
After switching from Chrome to Firefox, the problem became apperant.
I had to add annotations to my ingress controller as described here: express + socket.io + kubernetes Access-Control-Allow-Origin' header

Related

Frontend can't resolve backend name within k8s cluster

I'm trying to deploy a simple Angular/Express app on GKE and the http requests from the frontend can't find the name of the express app.
Here's an example of one get requests. I changed the request from 'localhost' to 'express' because that is the name of the clusterIP service setup in the cluster. Also, I'm able to curl this url from the angular pod and get json returned as expected.
getPups(){
this.http.get<{message:string, pups: any}>("http://express:3000/pups")
.pipe(map((pupData)=>{
return pupData.pups.map(pup=>{
return{
name: pup.name,
breed: pup.breed,
quote: pup.quote,
id: pup._id,
imagePath: pup.imagePath,
rates: pup.rates
}
});
}))
.subscribe((transformedPups)=>{
this.pups = transformedPups
this.pupsUpdated.next([...this.pups])
});
}
Here's the angular deployment.
apiVersion: apps/v1
kind: Deployment
metadata:
name: puprate-deployment
spec:
replicas: 1
selector:
matchLabels:
component: web
template:
metadata:
labels:
component: web
spec:
containers:
- name: puprate
image: brandonjones085/puprate
ports:
- containerPort: 4200
---
apiVersion: v1
kind: Service
metadata:
name: puprate-cluster-ip-service
spec:
type: ClusterIP
selector:
component: web
ports:
- port: 4200
targetPort: 4200
And the express deployment.
apiVersion: apps/v1
kind: Deployment
metadata:
name: express
spec:
replicas: 3
selector:
matchLabels:
component: server
template:
metadata:
labels:
component: server
spec:
containers:
- name: server
image: brandonjones085/puprate-express
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: express
spec:
type: ClusterIP
selector:
component: server
ports:
- port: 3000
targetPort: 3000
Your frontend app is making the call from outside your cluster, and therefor needs a way to reach it. Because you are serving http, the best way to set that up will be with an ingress.
First, make sure you have an ingress controller set up in your cluster ( e.g. nginx ingress controller) https://kubernetes.github.io/ingress-nginx/deploy/#gce-gke
Then, set up your express with a service (from your question, I see you already have that set up on port 3000, that's good, though in the service I recommend to change the port to 80 - though not critical)
With that, set up your ingress:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: express
spec:
rules:
- host: <a domain you own>
http:
paths:
# NOTICE!! have you express app listen for that path, or set up nginx rewrite rules (I recommend the former, it's much easier to understand)
- path: /api
backend:
serviceName: express
servicePort: 3000 # or 80 if you decide to change that
Do the same for your web deployment, so you can serve your frontend directly:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: web
spec:
rules:
- host: <a domain you own>
http:
paths:
- path: /
backend:
serviceName: web
servicePort: 4200 # or 80 if you decide to change that
Notice that both ingresses are using the same host but different paths - that's important for what's coming next
in your angular app, change that:
this.http.get<{message:string, pups: any}>("http://express:3000/pups")
to that:
this.http.get<{message:string, pups: any}>("/api/pups")
Browsers will parse that to <domain in your address bar>/api/pups
Since you are using GKE, when you set up the ingress controller you will generate a load balancer in the google cloud - make sure that has a DNS entry that directs there.
I'm assuming you already own a domain, but if you don't yet, just add the ip you got to your personal hosts file until you get one like so:
<ip of load balancer> <domain you want>
# for example
45.210.10.15 awesome-domain.com
So now, use the browser to go to the domain you own - you should get the frontend served to you - and since you are calling your api with an address that starts with /, your api call will go to the same host, and redirected by your ingress to your express app this time, instead of the frontend server.
Angular is running in your browser, not in the pod inside the cluster.
The requests will originate therefore externally and the URL must point to the Ingress or LoadBalancer of your backend service.

LoadBalancer in Pending State

I tried to deploy a reactapp service on kubernetes but the loadbalancer keeps coming in pending state.
I am on free trial status on GCP. Could this be the reason??
GKE sets up the kubernetes nodes for you. The load balancer should work straight out. Compare your yaml with the one below. And check for its status running kubectl get svc. Are you getting a ClusterIP IP? What happens if you change the type to NodePort, is you app reachable through the nodes IPS?
apiVersion: v1
kind: Service
metadata:
name: example-service
namespace: default
labels:
app: example-service
spec:
type: LoadBalancer
ports:
- name: "port"
port: 8000
targetPort: 8000
selector:
app: example-service

Kubernetes(minikube) + React Frontend + .netcore api + Cluster IP service + ingress + net::ERR_NAME_NOT_RESOLVED

Not able to resolve an API hosted as a ClusterIP service on Minikube when calling from the React JS frontend.
The basic architecture of my application is as follows
React --> .NET core API
Both these components are hosted as ClusterIP services. I have created an ingress service with http paths pointing to React component and the .NET core API.
However when I try calling it from the browser, react application renders, but the call to the API fails with
net::ERR_NAME_NOT_RESOLVED
Below are the .yml files for
1. React application
apiVersion: v1
kind: Service
metadata:
name: frontend-clusterip
spec:
type: ClusterIP
ports:
- port: 59000
targetPort: 3000
selector:
app: frontend
2. .NET core API
apiVersion: v1
kind: Service
metadata:
name: backend-svc-nodeport
spec:
type: ClusterIP
selector:
app: backend-svc
ports:
- port: 5901
targetPort: 59001
3. ingress service
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-service
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- http:
paths:
- path: /?(.*)
backend:
serviceName: frontend-clusterip
servicePort: 59000
- path: /api/?(.*)
backend:
serviceName: backend-svc-nodeport
servicePort: 5901
4. frontend deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
selector:
matchLabels:
app: frontend
replicas: 1
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: frontend
image: upendra409/tasks_tasks.frontend
ports:
- containerPort: 3000
env:
- name: "REACT_APP_ENVIRONMENT"
value: "Kubernetes"
- name: "REACT_APP_BACKEND"
value: "http://backend-svc-nodeport"
- name: "REACT_APP_BACKENDPORT"
value: "5901"
This is the error I get in the browser:
xhr.js:166 GET
http://backend-svc-nodeport:5901/api/tasks net::ERR_NAME_NOT_RESOLVED
I installed curl in the frontend container to get in the frontend pod to try to connect the backend API using the above URL, but the command doesn't work
C:\test\tasks [develop ≡ +1 ~6 -0 !]> kubectl exec -it frontend-655776bc6d-nlj7z --curl http://backend-svc-nodeport:5901/api/tasks
Error: unknown flag: --curl
You are getting this error from local machine because ClusterIP service is wrong type for accessing from outside of the cluster. As mentioned in kubernetes documentation ClusterIP is only reachable from within the cluster.
Publishing Services (ServiceTypes)
For some parts of your application (for example, frontends) you may
want to expose a Service onto an external IP address, that’s outside
of your cluster.
Kubernetes ServiceTypes allow you to specify what kind of Service
you want. The default is ClusterIP.
Type values and their behaviors are:
ClusterIP: Exposes the Service on a cluster-internal IP. Choosing this value makes the Service only reachable from within the
cluster. This is the default ServiceType.
NodePort:
Exposes the Service on each Node’s IP at a static port (the
NodePort). A ClusterIP Service, to which the NodePort
Service routes, is automatically created. You’ll be able to contact
the NodePort Service, from outside the cluster, by requesting
<NodeIP>:<NodePort>.
LoadBalancer:
Exposes the Service externally using a cloud provider’s load balancer.
NodePort and ClusterIP Services, to which the external load
balancer routes, are automatically created.
ExternalName:
Maps the Service to the contents of the externalName field (e.g.
foo.bar.example.com), by returning a CNAME record
with its value. No proxying of any kind is set up.
Note: You need CoreDNS version 1.7 or higher to use the ExternalName type.
I suggest using NodePort or LoadBalancer service type instead.
Refer to above documentation links for examples.

Unable to connect to api server of react js app using kubernetes?

I am basically trying to run a react js app which is mainly composed of 3 services namely postgres db, API server and UI frontend(served using nginx).Currently the app works as expected in the development mode using docker-compose but when i tried to run this in the production using kubernetes,I was not able to access the api server of the app(CONNECTION REFUSED).
Since I want to run in this in production using kubernetes, I created yaml files for each of the services and then tried running them using kubectl apply.I have also tried this with and without using the persistent volume for the api server.But none of this helped.
Docker-compose file(This works and i am able to connect to api server at port 8000)
version: "3"
services:
pg_db:
image: postgres
networks:
- wootzinternal
ports:
- 5432
environment:
- POSTGRES_PASSWORD=password
- POSTGRES_USER=postgres
- POSTGRES_DB=wootz
volumes:
- wootz-db:/var/lib/postgresql/data
apiserver:
image: wootz-backend
volumes:
- ./api:/usr/src/app
- /usr/src/app/node_modules
build:
context: ./api
dockerfile: Dockerfile
networks:
- wootzinternal
depends_on:
- pg_db
ports:
- '8000:8000'
ui:
image: wootz-frontend
volumes:
- ./client:/usr/src/app
- /usr/src/app/build
- /usr/src/app/node_modules
build:
context: ./client
dockerfile: Dockerfile
networks:
- wootzinternal
ports:
- '80:3000'
volumes:
wootz-db:
networks:
wootzinternal:
driver: bridge
My api server yaml for running in kubernetes(This doesn't work and I cant connect to the api server at port 8000)
apiVersion: v1
kind: Service
metadata:
name: apiserver
labels:
app: apiserver
spec:
ports:
- name: apiport
port: 8000
targetPort: 8000
selector:
app: apiserver
tier: backend
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: apiserver
labels:
app: apiserver
spec:
selector:
matchLabels:
app: apiserver
tier: backend
strategy:
type: Recreate
template:
metadata:
labels:
app: apiserver
tier: backend
spec:
containers:
- image: suji165475/devops-sample:corspackedapi
name: apiserver
env:
- name: POSTGRES_DB_USER
value: postgres
- name: POSTGRES_DB_PASSWORD
value: password
- name: POSTGRES_DB_HOST
value: postgres
- name: POSTGRES_DB_PORT
value: "5432"
ports:
- containerPort: 8000
name: myport
What changes should I make to my api server yaml(kubernetes). so that I can access it on port 8000. Currently I am getting a connection refused error.
The default service on Kubernetes is ClusterIP that is used to have service inside the cluster but not having that exposed to outside.
That is your service using the LoadBalancer type:
apiVersion: v1
kind: Service
metadata:
name: apiserver
labels:
app: apiserver
spec:
type: LoadBalancer
ports:
- name: apiport
port: 8000
targetPort: 8000
selector:
app: apiserver
tier: backend
With that, you can see how the service expects to have an external IP address by running kubectl describe service apiserver
In case you want to have more control of how your requests are routed to that service you can add an Ingress in front of that same service.
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
labels:
app: apiserver
name: apiserver
spec:
rules:
- host: apiserver.example.com
http:
paths:
- backend:
serviceName: apiserver
servicePort: 8000
path: /*
Your service in only exposed over the internal kubernetes network.
This is because if you do not specify a spec.serviceType, the default is ClusterIP.
To expose your application you can follow at least 3 ways:
LoadBalancer: you can specify a spec.serviceType: LoadBalancer. A Load Balancer service expose your application on the (public) network. This work great if your cluster is a cloud service (gke, digital ocean, aks, azure, ...), the cloud will take care of providing you the public ip and routing the network traffic to all your nodes. Usually this is not the best method because a cloud Load balancer has a cost (depends on the cloud) and if you need to expose a lot of services the situation could become difficult to be maintained.
NodePort: you can specify a spec.serviceType: NodePort. Exposes the Service on each Node’s IP at a static port (the NodePort).
You’ll be able to contact the service, from outside the cluster, by requesting <NodeIP>:<NodePort>.
Ingress: Ingress exposes HTTP and HTTPS routes from outside the cluster to services within the cluster. Traffic routing is controlled by rules defined on the Ingress resource. This is the most common scenario for simple http/https application. It allow you to easily manage ssl termination and routing.
You need to deploy an ingress controller to make this work, like a simple nginx. All the main cloud can do this for you with a simple setting when you create the cluster
Read here for more information about services
Read here for more information about ingress

React App communicating with Go backend api

I have a frontend React application and a Go backend service as the API for the frontend. Both are Kubernetes services in the same namespace. How can I communicate with the Go backend service without having to use an external IP? I got it to work with an external ip, but, I can't get the fqdn to resolve correctly like it should. The frontend service is built from the nginx:1.15.2-alpine docker image. How can I get the frontend React app to communicate with the backend Go server?
Frontend service.yaml:
apiVersion: v1
kind: Service
metadata:
name: ui
namespace: client
labels:
app: ui
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: http
- name: https
port: 443
targetPort: https
selector:
app: ui
Frontend deployment.yaml:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: ui
namespace: client
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 2
template:
metadata:
labels:
app: ui
spec:
containers:
- name: ui
image: #######
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
Backend service.yaml:
apiVersion: v1
kind: Service
metadata:
name: api
namespace: client
labels:
app: api
spec:
type: NodePort
ports:
- port: 8001
protocol: TCP
targetPort: http
name: http
selector:
app: api
Backend deployment.yaml:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: api
namespace: client
labels:
name: api
spec:
replicas: 1
revisionHistoryLimit: 2
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: ####
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8001
The React application does not run in Kubernetes. Maybe you have a dev server running in Kubernetes, but it just serves up HTML and Javascript files to a browser running outside the cluster. The application in the browser has no idea about this "Kubernetes" thing and can't resolve the Kubernetes-internal ...svc.cluster.local hostnames; it needs a way to talk back to the cluster.
Since you have the backend configured as a NodePort type service, you can look up the backend's externally visible port, then configure the backend URL in the served browser application to be that port number on some node in your cluster. This is a little bit messy and manual.
A better path is to configure an ingress so that, for example, https://.../ serves your browser application and https://.../api goes to your back-end. Then the backend URL can just be the bare path /api, and it will be interpreted with the same host name and scheme as the UI.
There are many issues with the yamls. First, in service yamls, the targetPort should be port numbers(integers) and not string. So, the updated config will be,
apiVersion: v1
kind: Service
metadata:
name: ui
namespace: client
labels:
app: ui
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
selector:
app: ui
and
apiVersion: v1
kind: Service
metadata:
name: api
namespace: client
labels:
app: api
spec:
type: NodePort
ports:
- port: 8001
protocol: TCP
targetPort: 8001
name: http
selector:
app: api
After changing the targetPort in service yamls, I created a pod to do nslookup and it works as expected.
kubectl apply -f https://k8s.io/examples/admin/dns/busybox.yaml
kubectl exec -ti busybox -- nslookup api.client
produces the output
Defaulting container name to busybox.
Use 'kubectl describe pod/busybox -n default' to see all of the containers in this pod.
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: api.client
Address 1: 10.101.84.21 api.client.svc.cluster.local

Resources