I have working on Microservices with Node JS and React (course from udemy) ,
but unfortunately I'm stuck with kubernetes problem
i have created posts-srv service,
posts-srv.yaml
apiVersion: v1
kind: Service
metadata:
name: posts-srv
spec:
type: NodePort
selector:
app: posts
ports:
- name: posts
protocol: TCP
port: 4000
targetPort: 4000
posts.js (index.js)
const { randomBytes } = require('crypto');
const express = require('express')
const cors = require('cors')
const axios = require('axios')
const app = express();
app.use(express.json())
app.use(cors())
const posts = {};
app.get('/test',(req,res)=>
{
res.status(200).json({
success: true
})
})
app.get('/posts',(req,res)=>
{
res.send(posts)
})
app.post('/posts', async (req,res)=>
{
console.log("app.post('/posts',(req,res)=> ")
const id = randomBytes(4).toString('hex');
const {title } = req.body;
posts[id] = {
id,title
};
await axios.post('htt://localhost:4005/events',{
type:'PostCreated',
data:{
id,title
}
});
res.status(201).send(posts[id])
})
app.post('/events',(req,res)=>
{
console.log("received event",req.body.type);
res.send({});
})
app.listen(4000,()=>{
console.log("server started for posts on 4000");
})
and the docker file is
from node:alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY ./ ./
CMD ["npm","start"]
then i used the command kubectl apply -f post-srv.yaml and service was successfully created but that is not accessible to my computer using browser
service details
Name: posts-srv
Namespace: default
Labels: <none>
Annotations: <none>
Selector: app=posts
Type: NodePort
IP Families: <none>
IP: 10.108.229.174
IPs: <none>
Port: posts 4001/TCP
TargetPort: 4001/TCP
NodePort: posts 30095/TCP
Endpoints: <none>
External Traffic Policy: Cluster
I have accessed through the localhost:30095
but getting same error, please suggest some solutions
Related
I'm trying to learn about multipath in Kubernetes Ingress. First of all, I'm using minikube for this tutorial, I created a simple Web API using node js.
NodeJS Code
In this nodeJS, I created a simple Web API, with routing and controller
server.js
const express = require ('express');
const routes = require('./routes/tea'); // import the routes
const app = express();
app.use(express.json());
app.use('/', routes); //to use the routes
const listener = app.listen(process.env.PORT || 3000, () => {
console.log('Your app is listening on port ' + listener.address().port)
})
routes/tea.js
const express = require('express');
const router = express.Router();
const teaController = require('../controllers/tea');
router.get('/tea', teaController.getAllTea);
router.post('/tea', teaController.newTea);
router.delete('/tea', teaController.deleteAllTea);
router.get('/tea/:name', teaController.getOneTea);
router.post('/tea/:name', teaController.newComment);
router.delete('/tea/:name', teaController.deleteOneTea);
module.exports = router;
controllers/tea.js
const os = require('os');
//GET '/tea'
const getAllTea = (req, res, next) => {
res.json({message: "GET all tea, " + os.hostname() });
};
//POST '/tea'
const newTea = (req, res, next) => {
res.json({message: "POST new tea, " + os.hostname()});
};
//DELETE '/tea'
const deleteAllTea = (req, res, next) => {
res.json({message: "DELETE all tea, " + os.hostname()});
};
//GET '/tea/:name'
const getOneTea = (req, res, next) => {
res.json({message: "GET 1 tea, os: " + os.hostname() + ", name: " + req.params.name});
};
//POST '/tea/:name'
const newComment = (req, res, next) => {
res.json({message: "POST 1 tea comment, os: " + os.hostname() + ", name: " + req.params.name});
};
//DELETE '/tea/:name'
const deleteOneTea = (req, res, next) => {
res.json({message: "DELETE 1 tea, os: " + os.hostname() + ", name: " + req.params.name});
};
//export controller functions
module.exports = {
getAllTea,
newTea,
deleteAllTea,
getOneTea,
newComment,
deleteOneTea
};
Dockerfile
After that I created a docker image using this Dockerfile
FROM node:18.9.1-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD [ "node", "server.js" ]
Kubernetes Manifest
And then, I created replicaset and service for this docker image
foo-replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: foo
spec:
selector:
matchLabels:
app: foo
replicas: 3
template:
metadata:
labels:
app: foo
spec:
containers:
- name: foo
image: emriti/tea-app:1.0.0
ports:
- name: http
containerPort: 3000
protocol: TCP
foo-svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: foo-nodeport
spec:
type: NodePort
ports:
- port: 3000
targetPort: 3000
nodePort: 31234
selector:
app: foo
all-ingress.yaml
Ingress for both Foo and Bar backend
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: foobar
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- host: foobar.com
http:
paths:
- path: /foo
pathType: Prefix
backend:
service:
name: foo-nodeport
port:
number: 3000
- path: /bar
pathType: Prefix
backend:
service:
name: bar-nodeport
port:
number: 3000
Additional setup
I also did these:
add 127.0.0.1 foobar.com to /etc/hosts
running minikube tunnel
After that I run curl foobar.com/foo/tea and I get this error:
curl : Cannot GET /
At line:1 char:1
+ curl foobar.com/foo
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
I'm wondering if maybe someone has experienced a similar problem that I did and maybe already had the answer for that. Secondly how to debug the ingress if I meet similar issues?
The codes and manifest could be accessed on this repo
Thank you!
Your problem is here:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
You haven't defined any capture groups in your rules, so this directive is rewriting all requests to /. If a client asks for /foo/tea/darjeeling, you request /. If a client requests /foo/this/does/not/exist, you request /.
To make this work, you need to add appropriate capture groups to your rules:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: foobar
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
rules:
- host: foobar.com
http:
paths:
- path: /foo/(.*)
pathType: Prefix
backend:
service:
name: foo-nodeport
port:
number: 3000
- path: /bar/(.*)
pathType: Prefix
backend:
service:
name: bar-nodeport
port:
number: 3000
I suspect you would have found this much easier to debug if your application were to log incoming requests. You might want to look into that. I figured this by adding a sidecar proxy to your pods that would log requests, which made the problem relatively obvious (I could see that every request was for / regardless of what URL the client was using).
There's more documentation about path rewriting when using the Nginx ingress service here.
Unrelated to your question:
You probably want to update your manifests to create Deployment resources instead of ReplicaSets.
For the way you've configured things here you don't need NodePort services.
I'm trying to run in kubernetes mern application (https://github.com/ibrahima92/fullstack-typescript-mern-todo/). I have a client and a server container, and I need to replace the path to the url client in the backend, so I defined variables in the backend code, but they don't replace the values of the variables from the manifest files. There are variables inside the container, but the backend does not use them.
I tried such options 1. ${FRONT_URL}, ${process.env.FRONT_URL}, process.env.FRONT_URL. If I directly insert the URL of the service with the port number in backend code then everything works. How to correctly define variables in a container?
I need replace http://localhost:${PORT} to url of service from K8S and
the same thing need to do with ${MONGO_URL}
import express, { Express } from 'express'
import mongoose from 'mongoose'
import cors from 'cors'
import todoRoutes from './routes'
const app: Express = express()
const PORT: string | number = process.env.PORT || 4000
app.use(cors())
app.use(todoRoutes)
const uri: string = `mongodb://${MONGO_URL}?retryWrites=true&w=majority`
const options = { useNewUrlParser: true, useUnifiedTopology: true }
mongoose.set('useFindAndModify', false)
mongoose
.connect(uri, options)
.then(() =>
app.listen(PORT, () =>
console.log(`Server running on http://localhost:${PORT}`)
)
)
.catch((error) => {
throw error
})
Manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-server-app-deploy
spec:
replicas: 1
selector:
matchLabels:
app: todo-server-app
template:
metadata:
labels:
app: todo-server-app
spec:
containers:
- image: repo/todo-server-app:24
name: container1
ports:
- containerPort: 4000
env:
- name: FRONT_URL
value: a1ecab155236d4c7fba8b0c6a1b6ad2b-549550669.us-east-1.elb.amazonaws.com:80
- name: MONGO_URL
value: todo-mongo-service:27017
imagePullPolicy: IfNotPresent
You can create a config map, giving your container run time variables,
Or alternatively, build your own docker image using the ENV
You can also acheive that using kustomization.
kustomization.yml
secretGenerator:
- name: my-secret
behavior: create
env: .env
Deployment.yml
envFrom:
- secretRef:
name: my-secret
Apologies in advance for such a long question, I just want to make sure I cover everything...
I have a react application that is supposed to connect to a socket being run in a service that I have deployed to kubernetes. The service runs and works fine. I am able to make requests without any issue but I cannot connect to the websocket running in the same service.
I am able to connect to the websocket when I run the service locally and use the locahost uri.
My express service's server.ts file looks like:
import "dotenv/config";
import * as packageJson from "./package.json"
import service from "./lib/service";
const io = require("socket.io");
const PORT = process.env.PORT;
const server = service.listen(PORT, () => {
console.info(`Server up and running on ${PORT}...`);
console.info(`Environment = ${process.env.NODE_ENV}...`);
console.info(`Service Version = ${packageJson.version}...`);
});
export const socket = io(server, {
cors: {
origin: process.env.ACCESS_CONTROL_ALLOW_ORIGIN,
methods: ["GET", "POST"]
}
});
socket.on('connection', function(skt) {
console.log('User Socket Connected');
socket.on("disconnect", () => console.log(`${skt.id} User disconnected.`));
});
export default service;
When I run this, PORT is set to 8088, and access-control-allow-origin is set to *. And note that I'm using a rabbitmq cluster that is deployed to Kubernetes, it is the same uri for the rabbit connection when I run locally. Rabbitmq is NOT running on my local machine, so I know it's not an issue with my rabbit deployment, it has to be something I'm doing wrong in connecting to the socket.
When I run the service locally, I'm able to connect in the react application with the following:
const io = require("socket.io-client");
const socket = io("ws://localhost:8088", { path: "/socket.io" });
And I see the "User Socket Connected" message and it all works as I expect.
When I deploy the service to Kubernetes though, I'm having some issues figuring out how to connect to the socket.
My Kubernetes Service:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 8088
selector:
app: my-service
My deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-service
spec:
replicas: 2
selector:
matchLabels:
app: my-service
template:
metadata:
labels:
app: my-service
spec:
containers:
- name: project
image: my-private-registry.com
ports:
- containerPort: 8088
imagePullSecrets:
- name: mySecret
And finally, my ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-service-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/enable-cors: "true" // Just added this to see if it helped
nginx.ingress.kubernetes.io/cors-allow-origin: "*" // Just added this to see if it helped
nginx.ingress.kubernetes.io/cors-allow-methods: PUT, GET, POST, OPTIONS, DELETE, PATCH // Just added this to see if it helped
spec:
tls:
- hosts:
- my.host.com
secretName: my-service-tls
rules:
- host: "my.host.com"
http:
paths:
- pathType: Prefix
path: "/project"
backend:
service:
name: my-service
port:
number: 80
I can connect to the service fine and get data, post data, etc. but I cannot connect to the websocket, I get either 404 or cors errors.
Since the service is running on my.host.com/project, I assume that the socket is at the same uri. So I try to connect with:
const socket = io("ws://my.host.com", { path: "/project/socket.io" });
and also using wss://
const socket = io("wss://my.host.com", { path: "/project/socket.io" });
and I have an error being logged in the console:
socket.on("connect_error", (err) => {
console.log(`connect_error due to ${err.message}`);
});
both result in
polling-xhr.js?d33e:198 GET https://my.host.com/project/?EIO=4&transport=polling&t=NjWQ8Tc 404
websocket.ts?25e3:14 connect_error due to xhr poll error
I have tried all of the following and none of them work:
const socket = io("ws://my.host.com", { path: "/socket.io" });
const socket = io("wss://my.host.com", { path: "/socket.io" });
const socket = io("ws://my.host.com", { path: "/project" });
const socket = io("wss://my.host.com", { path: "/project" });
const socket = io("ws://my.host.com", { path: "/" });
const socket = io("wss://my.host.com", { path: "/" });
const socket = io("ws://my.host.com");
const socket = io("wss://my.host.com");
Again, this works when the service is run locally, so I must have missed something and any help would be extremely appreciated.
Is there a way to go on the Kubernetes pod and find where rabbit is being broadcast to?
In case somebody stumbles on this in the future and wants to know how to fix it, it turns out it was a really dumb mistake on my part..
In:
export const socket = io(server, {
cors: {
origin: process.env.ACCESS_CONTROL_ALLOW_ORIGIN,
methods: ["GET", "POST"]
},
});
I just needed to add path: "/project/socket.io" to the socket options, which makes sense.
And then if anybody happens to run into the issue that followed, I was getting a 400 error on the post to the websocket polling so I set transports: [ "websocket" ] in my socket.io-client options and that seemed to fix it. The socket is now working and I can finally move on!
Trying to create a simple websocket server on minikube with ingress.
While connecting with the ws://192.168.99.119/ getting the below error.
WebSocket connection to 'ws://192.168.99.119/' failed: Error during WebSocket handshake: Unexpected response code: 502
Please help me to fix this issue. Below provided the details of the implementation.
Websocket Server implementation
import * as express from 'express';
import * as http from 'http';
import * as WebSocket from 'ws';
interface ExtWebSocket extends WebSocket {
id: string; // your custom property
isAlive: boolean
}
const PORT = parseInt(process.env.NODE_PORT) || 8010;
const app = express();
//initialize a simple http server
const server = http.createServer(app);
//initialize the WebSocket server instance
const wss = new WebSocket.Server({ server });
wss.on('connection', (ws: ExtWebSocket) => {
//connection is up, let's add a simple simple event
ws.on('message', (message: string) => {
//log the received message and send it back to the client
console.log('received: %s', message);
const broadcastRegex = /^broadcast\:/;
ws.isAlive = true;
ws.on('pong', () => {
ws.isAlive = true;
});
if (broadcastRegex.test(message)) {
message = message.replace(broadcastRegex, '');
//send back the message to the other clients
wss.clients
.forEach(client => {
if (client != ws) {
client.send(`Hello, broadcast message -> ${message}`);
}
});
} else {
ws.send(`Hello, you sent -> ${message}`);
}
});
//send immediatly a feedback to the incoming connection
ws.send('Hi there, I am a WebSocket server');
});
setInterval(() => {
wss.clients.forEach((ws: ExtWebSocket) => {
if (!ws.isAlive) return ws.terminate();
ws.isAlive = false;
ws.ping(null, false);
});
}, 10000);
//start our server
server.listen(PORT, () => {
console.log(`Server started on port ${JSON.stringify(server.address())}`);
});
Dockerfile
FROM node:alpine
WORKDIR /app
COPY package*.json /app/
RUN npm install
COPY ./ /app/
RUN npm run build
CMD ["node","./dist/server.js"]
EXPOSE 8010
Websocket service yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: socketserver
spec:
replicas: 1
selector:
matchLabels:
app: socketserver
template:
metadata:
labels:
app: socketserver
spec:
containers:
- name: socketserver
image: kireeti123/socketserver:1.0.2
resources:
limits:
memory: "128Mi"
cpu: "500m"
ports:
- containerPort: 8010
name: wsport
---
apiVersion: v1
kind: Service
metadata:
name: socketserver-svc
spec:
selector:
app: socketserver
ports:
- port: 8010
targetPort: 80
---
Ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: demo-ingress
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/websocket-services: socketserver-svc
nginx.org/websocket-services: socketserver-svc
spec:
rules:
- http:
paths:
- path: /
backend:
serviceName: socketserver-svc
servicePort: 80
Why do you have targetPort set to 80 while your Pod exposes port 8010 ? It won't work.
Switch port numbers in your Service definition so it looks like in the example below:
apiVersion: v1
kind: Service
metadata:
name: socketserver-svc
spec:
selector:
app: socketserver
ports:
- port: 80
targetPort: 8010
I hope it helps.
I have a kubernetes cluster and an express server serving a SPA. Currently if I hit the http version of my website I do not redirect to https, but I would like to.
This is what I've tried -
import express from "express";
const PORT = 3000;
const path = require("path");
const app = express();
const router = express.Router();
const forceHttps = function(req, res, next) {
const xfp =
req.headers["X-Forwarded-Proto"] || req.headers["x-forwarded-proto"];
if (xfp === "http") {
console.log("host name");
console.log(req.hostname);
console.log(req.url);
const redirectTo = `https:\/\/${req.hostname}${req.url}`;
res.redirect(301, redirectTo);
} else {
next();
}
};
app.get("/*", forceHttps);
// root (/) should always serve our server rendered page
// other static resources should be served as they are
const root = path.resolve(__dirname, "..", "build");
app.use(express.static(root, { maxAge: "30d" }));
app.get("/*", function(req, res, next) {
if (
req.method === "GET" &&
req.accepts("html") &&
!req.is("json") &&
!req.path.includes(".")
) {
res.sendFile("index.html", { root });
} else {
next();
}
});
// tell the app to use the above rules
app.use(router);
app.listen(PORT, error => {
console.log(`listening on ${PORT} from the server`);
if (error) {
console.log(error);
}
});
This is what my kubernetes config looks like
apiVersion: v1
kind: Service
metadata:
name: <NAME>
labels:
app: <APP>
annotations:
# Note that the backend talks over HTTP.
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: <CERT>
# Only run SSL on the port named "https" below.
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
spec:
type: LoadBalancer
selector:
app: <APP>
ports:
- port: 443
targetPort: 3000
protocol: TCP
name: https
- port: 80
targetPort: 3000
protocol: TCP
name: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: <DEPLOYMENT_NAME>
labels:
app: <APP>
spec:
replicas: 1
selector:
matchLabels:
app: <APP>
template:
metadata:
labels:
app: <APP>
spec:
containers:
- name: <CONTAINER_NAME>
image: DOCKER_IMAGE_NAME
imagePullPolicy: Always
env:
- name: VERSION_INFO
value: "1.0"
- name: BUILD_DATE
value: "1.0"
ports:
- containerPort: 3000
I successfully hit the redirect...but the browser does not actually redirect. How do I get it to redirect from http to https?
Relatedly, from googling around I keep seeing that people are using an Ingress AND a Load Balancer - why would I need both?
When you LoadBalancer type service in EKS it will either create a classic or a network load balancer.None of these support http to https redirection. You need an application load balancer(ALB) which supports http to https redirection.In EKS to use ALB you need to use AWS ALB ingress controller. Once you have ingress controller setup you can use annotation to redirect http to https
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
namespace: default
name: ingress
annotations:
kubernetes.io/ingress.class: alb
alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-west-2:xxxx:certificate/xxxxxx
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS":443}]'
alb.ingress.kubernetes.io/actions.ssl-redirect: '{"Type": "redirect", "RedirectConfig": { "Protocol": "HTTPS", "Port": "443", "StatusCode": "HTTP_301"}}