Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/santivediap/fullstackapp-microservices-kubernetes
https://github.com/santivediap/fullstackapp-microservices-kubernetes
gke kubernetes kubernetes-deployment minikube nodejs react
Last synced: 3 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/santivediap/fullstackapp-microservices-kubernetes
- Owner: santivediap
- Created: 2024-09-03T14:57:08.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2024-09-17T09:19:22.000Z (4 months ago)
- Last Synced: 2024-10-02T04:22:39.466Z (4 months ago)
- Topics: gke, kubernetes, kubernetes-deployment, minikube, nodejs, react
- Language: JavaScript
- Homepage:
- Size: 1.16 MB
- Stars: 2
- Watchers: 1
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Deploy fullstack app on Kubernetes
## ❗️ Notes
First of all, I want to clarify that I'm not sure if this works on Minikube. I tried it with Docker Desktop with no results. Maybe works on VirtualBox (I didn't try)
Also, I don't know if this works the same way in EKS (Amazon Elastic Kubernetes Service), AKS (Azure Kubernetes Service) or any other provider. It should, but just in case, I let you know it.
I only tested sucessfully on GKE (Google Kubernetes Engine)## 📘 App overview
- Frontend created with React
- Backend created with NodeJS + ExpressClient communicates with server trough a reverse-proxy, wich is configured in the NGINX config file
## 🔨 Creating the server
### Creating index.js
So we have a simple backend that listens in port 80 by default with three endpoints
❗️ I did not specify how to create a backend from scratch, but you can learn how to [here](https://medium.com/@dhwajgupta27/build-a-node-js-server-in-5-minutes-quick-and-easy-server-setup-6eb594e8b26)
``` javascript
const express = require('express');
require('dotenv').config()const app = express();
app.use(express.json({ extended: false }));app.get("/", (req, res) => {
res.status(200).json({
msg: "Route / in Backend"
})
})app.get("/api/test", (req, res) => {
res.status(200).json({
msg: "Route /api/test in backend"
})
})app.get("/api/hello", (req, res) => {
res.status(200).json({
msg: "Route /api/hello in backend"
})
})const PORT = process.env.PORT || 80;
app.listen(PORT, () => console.log(`Server started port ${PORT}`));
```### Creating Dockerfile
❗️❗️❗️ I specified the platform's image because my computer doesn't have that architecture. If your computer has that architecture you can just write ```FROM node:18-alpine3.20```
``` docker
FROM --platform=linux/amd64 node:18-alpine3.20# Set the working directory
WORKDIR /app# Copy the package.json and package-lock.json files
COPY package*.json ./# Install the dependencies
RUN npm install# Copy the rest of the code
COPY . .# Expose the port that the app listens on
EXPOSE 80# Define the command to run the app
CMD ["npm", "start"]
```## 🔨 Creating the client
### Creating App.jsx
We basically have a form with one input where we write the endpoint in our backend that we want to make a HTTP GET request
❗️ I did not specify how to create a frontend from scratch, but you can learn how to [here](https://vitejs.dev/guide/)
``` javascript
import { useState } from 'react'
import './App.css'
import axios from "axios"function App() {
const [input, setInput] = useState("")
const [result, setResult] = useState("")const changeInput = async (e) => {
e.preventDefault()setInput(e.target.value)
}const submitForm = async (e) => {
e.preventDefault()try {
// Did this so client will request to /api/test instead of //api/test (for example) in backend when proxy is set on NGINX
const charIndex = input.indexOf("/", input.indexOf("/") + 1)
const backendRequest = await axios.get(input.substring(0, charIndex) + input.substring(charIndex + 1))
setResult(backendRequest.data.msg)
} catch(err) {
console.error(err);
setResult("No matching route in backend")
}
}return (
Kubernetes - Demo
Search backend route
{ result }
)
}export default App
```
### Creating NGINX config file
``` conf
# The identifier Backend is internal to nginx, and used to name this specific upstream
upstream Backend {
# backend is the internal DNS name used by the backend Service inside Kubernetes
server backend;
}
server {
listen 80;location / {
# This would be the directory where your React app's static files are stored at
root /usr/share/nginx/html;
try_files $uri /index.html;
}location /backend {
# The following statement will proxy traffic to the upstream named Backend
proxy_pass http://Backend/;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-NginX-Proxy true;
proxy_ssl_session_reuse off;
proxy_set_header Host $http_host;
proxy_cache_bypass $http_upgrade;
proxy_redirect off;
}
}
```### Creating Dockerfile
❗️❗️❗️ I specified the platform's image because my computer doesn't have that architecture. If your computer has that architecture you can just write ```FROM node:18-alpine3.20```
``` docker
# Use the official Node.js runtime as the base image
FROM --platform=linux/amd64 node:18-alpine3.20 AS build# Set the working directory in the container
WORKDIR /app# Copy package.json and package-lock.json to the working directory
COPY package*.json ./# Install dependencies
RUN npm install# Copy the entire application code to the container
COPY . .# Build the React app for production
RUN npm run build# Use Nginx as the production server
FROM --platform=linux/amd64 nginx:alpine# Copy the built React app to Nginx's web server directory
COPY --from=build /app/dist /usr/share/nginx/html
COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf# Expose port 80 for the Nginx server
EXPOSE 80
```## 🤩 Creating and pushing our Docker images to DockerHub
Perfect! __We now have our microservices ready__ to make their respective images!
Run the following command to build a Docker image
```sh
docker build -t (dockerhub-username)/(repository-name):(tag) .
```Then run the following command to push your image to DockerHub
```sh
docker push (dockerhub-username)/(repository-name):(tag)
```## 🤓 Creating the manifests
Great! You have your images pushed in DockerHub!
But we have to do one last thing before dealing with Kubernetes: __Creating the necessary manifests__
### Creating our deployments
> Let's create our backend deployment
> Inside `metadata` property we have:
> - `name:` Used to give a name to our deployments. It's just declarative (in deployments)
> - `labels:` Used to make our deployments easier to find when we have to use them in Services. You can write whatever you want. In this case we will be using `app: backend-app`> Inside `spec` property we define the specifications of our deployment, such as the number of `replicas` we want, the `template` for the Pods used to create the `replicas`, and the Pods we are going to use in `selector`
>
> We define our labels within the `template` property so we can find our Pod template on the `matchLabels` property. That's how Kubernetes understands wich template has to use for making replicas#### backend-deployment.yaml
``` yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-deploy
labels:
app: backend-app
spec:
replicas: 3
selector:
matchLabels:
app: backend-pod
template:
metadata:
name: backend-pod
labels:
app: backend-pod
spec:
containers:
- name: backend-container
image: your-backend-image
ports:
- containerPort: 80
```> Now we create the frontend deployment (It works the same way as the backend one)
#### frontend-deployment.yaml
``` yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend-deploy
labels:
app: frontend-app
spec:
replicas: 3
selector:
matchLabels:
app: frontend-pod
template:
metadata:
name: frontend-pod
labels:
app: frontend-pod
spec:
containers:
- name: frontend-container
image: your-frontend-image
ports:
- containerPort: 80
```### Creating our Services
> Let's create our backend service
> This part is __IMPORTANT__ ❗️❗️
>
> As specified in the comment within `metadata` property, __the name of our Service is the DNS__ we wrote on the nginx config file
>
> This is literally the part where we enable the communication between both microservices> In the `spec` property we define the specifications of our Service. We are not defining the `type` property, so the default value will be `ClusterIP`
>
> In the `ports` property, we set `targetPort` and `port` to 80 to enable the communication between our backend containers and our backend service
>
> Also, the NGINX web server listens on 80, thats why we expose our backend on that port#### backend-service.yaml
``` yaml
apiVersion: v1
kind: Service
metadata:
name: backend # DNS name to communicate with frontend
spec:
selector:
app: backend-pod
ports:
- port: 80
targetPort: 80
```> Now we can create the frontend service
> It works almost the same way as the backend. The only difference is that here we define the property `type` to LoadBalancer
>
> `LoadBalancer` distributes the incoming traffic across the Pods. It also provides external network access to them
>
> Kubernetes knows wich Pods have that external network access by the `selector` property. We set there our backend Pod labels#### frontend-service.yaml
``` yaml
apiVersion: v1
kind: Service
metadata:
name: frontend
spec:
type: LoadBalancer
selector:
app: frontend-pod
ports:
- port: 80
targetPort: 80
```## 🖥️ Deploying our app on Kubernetes
We now have the necessary resources to deploy our fullstack app on Kubernetes! 🥳
> First of all, let's verify everything is okay running `kubectl get all`. It should appear this
![kubernetes-first-preview.png](/assets/kubernetes-first-preview.png "kubernetes-first-preview.png")
> Now, to create our backend deployment, let's run `kubectl apply -f backend-deployment.yaml`
>
> The output should be `deployment.apps/backend-deploy created`
>
> Run `kubectl get all` and you should see this![kubernetes-backend-deploy.png](/assets/kubernetes-backend-deploy.png "kubernetes-backend-deploy.png")
> Great! Let's create our backend service running `kubectl apply -f backend-service.yaml`
>
> The output should be `service/backend created`
>
> Run `kubectl get all` to see the changes![kubernetes-backend-service.png](/assets/kubernetes-backend-service.png "kubernetes-backend-service.png")
> We have our backend running perfectly! Now let's create our frontend deployment running `kubectl apply -f frontend-deployment.yaml`
>
> The output should be `deployment.apps/frontend-deploy created`![kubernetes-frontend-deploy.png](/assets/kubernetes-frontend-deploy.png "kubernetes-frontend-deploy.png")
> Perfect! We are in the last step! Create our frontend service running `kubectl apply -f frontend-service.yaml`
>
> The output should be `service/frontend created`![kubernetes-frontend-service.png](/assets/kubernetes-frontend-service.png "kubernetes-frontend-service.png")
> PERFECT! We have our __microservices running without problems__, and we also have our __external IP to connect__ to our frontend. Let's try it on our browser!
![frontend-preview.png](/assets/frontend-preview.png "frontend-preview.png")
> It works perfectly! Now, to __test the communication__ between client-server we are going to write something in the input *(/backend, for example)*
![backend-test.png](/assets/backend-test.png "backend-test.png")
> Great! Our client can communicate with our server without any problems 🥳
>
> Let's try the other endpoints![backend-second-test.png](/assets/backend-second-test.png "backend-second-test.png")
![backend-third-test.png](/assets/backend-third-test.png "backend-third-test.png")
> __CONGRATULATIONS!__ 🥳
>
> You have deployed your fullstack app in Kubernetes with microservices! Remember that having your app deployed with microservices allows you scale or edit your app easily, in a more flexible way 😛
>
> I hope you find this helpful! If you want to add useful info or good practices, feel free to make a pull request 😁
>
> __HAPPY CODING!__ 🤩PD: I accidentally set another image on my deployment while taking the pictures so you will see the pods are named different. Don't worry, It should work fine