refactor: Split monolithic container into separate API and frontend deployments
ARCHITECTURE CHANGES:
- API service: Node.js server on port 3001 (Dockerfile.api)
- Frontend service: Nginx serving React on port 80 (Dockerfile.frontend)
- Each service has its own deployment, service, and replicas
- Ingress routes / to frontend and /api/ to API
KUBERNETES MANIFESTS:
- api-deployment.yaml: 2 replicas of Node.js API server
- api-service.yaml: ClusterIP service for API
- frontend-deployment.yaml: 2 replicas of Nginx frontend
- frontend-service.yaml: ClusterIP service for frontend
- Updated ingress.yaml: Routes traffic based on paths
- Updated kustomization.yaml: References new deployments
DOCKER IMAGES:
- Dockerfile.api: Minimal Node.js image for API (~200MB)
- Dockerfile.frontend: Nginx + React build (~50MB)
- Separate builds in workflow for independent versioning
NGINX CONFIGURATION:
- Removed API proxy (separate service now)
- Simplified config for static file serving only
BENEFITS:
- Independent scaling (can scale frontend/API separately)
- Smaller images with minimal base images
- API errors don't affect frontend availability
- Easier to update one service without affecting the other
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
7ae458f768
commit
0493a7ef70
@ -39,12 +39,18 @@ jobs:
|
|||||||
- name: Login to Container Registry
|
- name: Login to Container Registry
|
||||||
run: echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" -u "$REGISTRY_USER" --password-stdin
|
run: echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" -u "$REGISTRY_USER" --password-stdin
|
||||||
|
|
||||||
- name: Build and Push Docker image
|
- name: Build and Push API image
|
||||||
run: |
|
run: |
|
||||||
docker build -t "$REGISTRY_URL/sortifal/pfee:$IMAGE_TAG" -t "$REGISTRY_URL/sortifal/pfee:latest" .
|
docker build -f Dockerfile.api -t "$REGISTRY_URL/sortifal/pfee:$IMAGE_TAG" -t "$REGISTRY_URL/sortifal/pfee:latest" .
|
||||||
docker push "$REGISTRY_URL/sortifal/pfee:$IMAGE_TAG"
|
docker push "$REGISTRY_URL/sortifal/pfee:$IMAGE_TAG"
|
||||||
docker push "$REGISTRY_URL/sortifal/pfee:latest"
|
docker push "$REGISTRY_URL/sortifal/pfee:latest"
|
||||||
|
|
||||||
|
- name: Build and Push Frontend image
|
||||||
|
run: |
|
||||||
|
docker build -f Dockerfile.frontend -t "$REGISTRY_URL/sortifal/pfee-frontend:$IMAGE_TAG" -t "$REGISTRY_URL/sortifal/pfee-frontend:latest" .
|
||||||
|
docker push "$REGISTRY_URL/sortifal/pfee-frontend:$IMAGE_TAG"
|
||||||
|
docker push "$REGISTRY_URL/sortifal/pfee-frontend:latest"
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy to Kubernetes
|
name: Deploy to Kubernetes
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -84,9 +90,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd k8s
|
cd k8s
|
||||||
kustomize edit set image gitea.vidoks.fr/sortifal/pfee="$REGISTRY_URL/sortifal/pfee:$IMAGE_TAG"
|
kustomize edit set image gitea.vidoks.fr/sortifal/pfee="$REGISTRY_URL/sortifal/pfee:$IMAGE_TAG"
|
||||||
|
kustomize edit set image gitea.vidoks.fr/sortifal/pfee-frontend="$REGISTRY_URL/sortifal/pfee-frontend:$IMAGE_TAG"
|
||||||
kubectl apply -k .
|
kubectl apply -k .
|
||||||
|
|
||||||
- name: Verify deployment
|
- name: Verify deployment
|
||||||
run: |
|
run: |
|
||||||
kubectl rollout status deployment/sqdc-dashboard -n sqdc-dashboard --timeout=5m
|
kubectl rollout status deployment/sqdc-api -n sqdc-dashboard --timeout=5m
|
||||||
|
kubectl rollout status deployment/sqdc-frontend -n sqdc-dashboard --timeout=5m
|
||||||
kubectl get pods,svc,ingress -n sqdc-dashboard
|
kubectl get pods,svc,ingress -n sqdc-dashboard
|
||||||
|
|||||||
24
dashboard-sqdc/Dockerfile.api
Normal file
24
dashboard-sqdc/Dockerfile.api
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# API Server Dockerfile
|
||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install only production dependencies
|
||||||
|
RUN npm ci --only=production
|
||||||
|
|
||||||
|
# Copy database and server files
|
||||||
|
COPY database ./database
|
||||||
|
COPY server.js .
|
||||||
|
|
||||||
|
# Expose API port
|
||||||
|
EXPOSE 3001
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD node -e "require('http').get('http://localhost:3001/api/categories', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"
|
||||||
|
|
||||||
|
# Start the API server
|
||||||
|
CMD ["node", "server.js"]
|
||||||
35
dashboard-sqdc/Dockerfile.frontend
Normal file
35
dashboard-sqdc/Dockerfile.frontend
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Multi-stage build for React frontend with Nginx
|
||||||
|
FROM node:18-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
# Install all dependencies (including dev) needed for build
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Stage 2: Production image with Nginx
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Copy custom nginx configuration
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
# Copy built application from builder stage
|
||||||
|
COPY --from=builder /app/build /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1
|
||||||
|
|
||||||
|
# Start nginx
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
@ -1,30 +1,25 @@
|
|||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: sqdc-dashboard
|
name: sqdc-api
|
||||||
namespace: sqdc-dashboard
|
namespace: sqdc-dashboard
|
||||||
labels:
|
labels:
|
||||||
app: sqdc-dashboard
|
app: sqdc-api
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 2
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: sqdc-dashboard
|
app: sqdc-api
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: sqdc-dashboard
|
app: sqdc-api
|
||||||
spec:
|
spec:
|
||||||
imagePullSecrets:
|
|
||||||
- name: registry-credentials
|
|
||||||
containers:
|
containers:
|
||||||
- name: dashboard
|
- name: api
|
||||||
image: gitea.vidoks.fr/sortifal/pfee:latest
|
image: gitea.vidoks.fr/sortifal/pfee:latest
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
|
||||||
name: http
|
|
||||||
protocol: TCP
|
|
||||||
- containerPort: 3001
|
- containerPort: 3001
|
||||||
name: api
|
name: api
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
@ -33,23 +28,23 @@ spec:
|
|||||||
value: "production"
|
value: "production"
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
limits:
|
||||||
memory: "256Mi"
|
memory: "256Mi"
|
||||||
cpu: "250m"
|
cpu: "250m"
|
||||||
limits:
|
|
||||||
memory: "512Mi"
|
|
||||||
cpu: "500m"
|
|
||||||
livenessProbe:
|
livenessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /api/categories
|
||||||
port: 80
|
port: 3001
|
||||||
initialDelaySeconds: 30
|
initialDelaySeconds: 30
|
||||||
periodSeconds: 10
|
periodSeconds: 10
|
||||||
timeoutSeconds: 5
|
timeoutSeconds: 5
|
||||||
failureThreshold: 3
|
failureThreshold: 3
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /
|
path: /api/categories
|
||||||
port: 80
|
port: 3001
|
||||||
initialDelaySeconds: 10
|
initialDelaySeconds: 10
|
||||||
periodSeconds: 5
|
periodSeconds: 5
|
||||||
timeoutSeconds: 3
|
timeoutSeconds: 3
|
||||||
16
dashboard-sqdc/k8s/api-service.yaml
Normal file
16
dashboard-sqdc/k8s/api-service.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: sqdc-api
|
||||||
|
namespace: sqdc-dashboard
|
||||||
|
labels:
|
||||||
|
app: sqdc-api
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- port: 3001
|
||||||
|
targetPort: 3001
|
||||||
|
protocol: TCP
|
||||||
|
name: api
|
||||||
|
selector:
|
||||||
|
app: sqdc-api
|
||||||
48
dashboard-sqdc/k8s/frontend-deployment.yaml
Normal file
48
dashboard-sqdc/k8s/frontend-deployment.yaml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: sqdc-frontend
|
||||||
|
namespace: sqdc-dashboard
|
||||||
|
labels:
|
||||||
|
app: sqdc-frontend
|
||||||
|
spec:
|
||||||
|
replicas: 2
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: sqdc-frontend
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sqdc-frontend
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: frontend
|
||||||
|
image: gitea.vidoks.fr/sortifal/pfee-frontend:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
|
name: http
|
||||||
|
protocol: TCP
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "50m"
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 80
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /
|
||||||
|
port: 80
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 5
|
||||||
|
timeoutSeconds: 3
|
||||||
|
failureThreshold: 2
|
||||||
16
dashboard-sqdc/k8s/frontend-service.yaml
Normal file
16
dashboard-sqdc/k8s/frontend-service.yaml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: sqdc-frontend
|
||||||
|
namespace: sqdc-dashboard
|
||||||
|
labels:
|
||||||
|
app: sqdc-frontend
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: 80
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
|
selector:
|
||||||
|
app: sqdc-frontend
|
||||||
@ -4,7 +4,6 @@ metadata:
|
|||||||
name: sqdc-dashboard-ingress
|
name: sqdc-dashboard-ingress
|
||||||
namespace: sqdc-dashboard
|
namespace: sqdc-dashboard
|
||||||
annotations:
|
annotations:
|
||||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
|
||||||
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||||
spec:
|
spec:
|
||||||
ingressClassName: nginx
|
ingressClassName: nginx
|
||||||
@ -16,6 +15,13 @@ spec:
|
|||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: sqdc-dashboard-service
|
name: sqdc-frontend
|
||||||
port:
|
port:
|
||||||
number: 80
|
number: 80
|
||||||
|
- path: /api/
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: sqdc-api
|
||||||
|
port:
|
||||||
|
number: 3001
|
||||||
|
|||||||
@ -5,18 +5,21 @@ namespace: sqdc-dashboard
|
|||||||
|
|
||||||
resources:
|
resources:
|
||||||
- namespace.yaml
|
- namespace.yaml
|
||||||
- deployment.yaml
|
- api-deployment.yaml
|
||||||
- service.yaml
|
- api-service.yaml
|
||||||
|
- frontend-deployment.yaml
|
||||||
|
- frontend-service.yaml
|
||||||
- ingress.yaml
|
- ingress.yaml
|
||||||
- configmap.yaml
|
- configmap.yaml
|
||||||
# Note: PVC removed - using emptyDir for temporary storage
|
|
||||||
|
|
||||||
commonLabels:
|
commonLabels:
|
||||||
app: sqdc-dashboard
|
|
||||||
managed-by: kustomize
|
managed-by: kustomize
|
||||||
|
|
||||||
# Image tag will be set via kustomize edit or --kustomize-replace during deployment
|
# Image tags will be set via kustomize edit during deployment
|
||||||
images:
|
images:
|
||||||
- name: gitea.vidoks.fr/sortifal/pfee
|
- name: gitea.vidoks.fr/sortifal/pfee
|
||||||
newTag: latest
|
newTag: latest
|
||||||
newName: gitea.vidoks.fr/sortifal/pfee
|
newName: gitea.vidoks.fr/sortifal/pfee
|
||||||
|
- name: gitea.vidoks.fr/sortifal/pfee-frontend
|
||||||
|
newTag: latest
|
||||||
|
newName: gitea.vidoks.fr/sortifal/pfee-frontend
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: sqdc-dashboard-service
|
|
||||||
namespace: sqdc-dashboard
|
|
||||||
labels:
|
|
||||||
app: sqdc-dashboard
|
|
||||||
spec:
|
|
||||||
type: ClusterIP
|
|
||||||
selector:
|
|
||||||
app: sqdc-dashboard
|
|
||||||
ports:
|
|
||||||
- name: http
|
|
||||||
port: 80
|
|
||||||
targetPort: 80
|
|
||||||
protocol: TCP
|
|
||||||
- name: api
|
|
||||||
port: 3001
|
|
||||||
targetPort: 3001
|
|
||||||
protocol: TCP
|
|
||||||
@ -10,19 +10,6 @@ server {
|
|||||||
gzip_min_length 1024;
|
gzip_min_length 1024;
|
||||||
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json;
|
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json;
|
||||||
|
|
||||||
# API proxy
|
|
||||||
location /api/ {
|
|
||||||
proxy_pass http://127.0.0.1:3001/;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_cache_bypass $http_upgrade;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Static assets with caching
|
# Static assets with caching
|
||||||
location /static/ {
|
location /static/ {
|
||||||
expires 1y;
|
expires 1y;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user