diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000..7df6d19d
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,88 @@
+stages:
+ - lint
+ - check
+ - test
+ - build
+ - deploy
+
+default:
+ tags:
+ - qr
+
+lint_pre_commit:
+ stage: lint
+ image: python:3.11-slim
+ before_script:
+ - apt-get update && apt-get install -y git curl
+ - pip install pre-commit
+ - curl -sS -L https://github.com/gitleaks/gitleaks/releases/download/v8.18.2/gitleaks_8.18.2_linux_x64.tar.gz | tar -xz -C /usr/local/bin gitleaks
+ script:
+ - pre-commit run --all-files
+ rules:
+ - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+ - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
+
+lint_code:
+ stage: check
+ image: python:3.10-slim
+ script:
+ - pip install flake8
+ - flake8 . --exclude=venv
+
+test_api:
+ stage: test
+ image: python:3.10-slim
+ script:
+ - pip install -r api/requirements.txt
+ - python api/test_main.py
+
+build_api:
+ stage: build
+ image: docker:29
+ services:
+ - docker:29-dind
+ variables:
+ DOCKER_TLS_CERTDIR: "/certs"
+ before_script:
+ - echo "$CI_JOB_TOKEN" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
+ script:
+ - docker build -t "$CI_REGISTRY_IMAGE/api:$CI_COMMIT_SHA" ./api
+ - docker push "$CI_REGISTRY_IMAGE/api:$CI_COMMIT_SHA"
+
+build_front:
+ stage: build
+ image: docker:29
+ services:
+ - docker:29-dind
+ variables:
+ DOCKER_TLS_CERTDIR: "/certs"
+ before_script:
+ - echo "$CI_JOB_TOKEN" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
+ script:
+ - >-
+ docker build
+ --build-arg NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
+ -t $CI_REGISTRY_IMAGE/front:$CI_COMMIT_SHA
+ ./front-end-nextjs
+ - docker push $CI_REGISTRY_IMAGE/front:$CI_COMMIT_SHA
+
+deploy:
+ stage: deploy
+ image:
+ name: alpine/k8s:1.30.0
+ entrypoint: [""]
+ before_script:
+ - kubectl create secret docker-registry gitlab-registry-secret
+ --docker-server=$CI_REGISTRY
+ --docker-username=$CI_DEPLOY_USER
+ --docker-password=$CI_DEPLOY_PASSWORD
+ --dry-run=client -o yaml | kubectl apply --validate=false -f -
+ script:
+ - kubectl cluster-info
+ - kubectl delete validatingwebhookconfiguration ingress-nginx-admission --ignore-not-found
+ - kubectl kustomize k8s/ | envsubst '$CI_REGISTRY_IMAGE $CI_COMMIT_SHA' | kubectl apply -f -
+ - kubectl rollout status deployment/ingress-nginx-controller -n ingress-nginx --timeout=120s
+ - kubectl rollout status deployment/fastapi --timeout=180s
+ - kubectl rollout status deployment/frontend --timeout=180s
+# rules:
+# - if: $CI_COMMIT_BRANCH == "main"
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 00000000..f39a4731
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,36 @@
+repos:
+ # Standard Git hygiene checks
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v4.6.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-yaml
+ args: [--allow-multiple-documents]
+ - id: check-added-large-files
+ args: ['--maxkb=10000']
+ - id: check-merge-conflict
+
+ # GitLab CI Schema Validation
+ - repo: https://github.com/python-jsonschema/check-jsonschema
+ rev: 0.28.0
+ hooks:
+ - id: check-jsonschema
+ name: "Validate .gitlab-ci.yml Schema"
+ files: ^\.gitlab-ci.*\.yml$
+ args:
+ - "--schemafile"
+ - "https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json"
+
+ # Secret Detection
+ - repo: https://github.com/gitleaks/gitleaks
+ rev: v8.18.2
+ hooks:
+ - id: gitleaks
+
+ # YAML Formatting
+ - repo: https://github.com/adrienverge/yamllint.git
+ rev: v1.35.0
+ hooks:
+ - id: yamllint
+ args: [-c, .yamllint.yml]
diff --git a/.yamllint.yml b/.yamllint.yml
new file mode 100644
index 00000000..e0b4e207
--- /dev/null
+++ b/.yamllint.yml
@@ -0,0 +1,10 @@
+extends: default
+
+rules:
+ line-length:
+ max: 160
+ level: warning
+ indentation:
+ spaces: 2
+ check-multi-line-strings: false
+ document-start: disable
diff --git a/api/.env.example b/api/.env.example
index 6083f141..5bc932fe 100644
--- a/api/.env.example
+++ b/api/.env.example
@@ -1,2 +1,2 @@
AWS_ACCESS_KEY=Your-AWS-Access-Key
-AWS_SECRET_KEY=Your-AWS-Secret-Access-Key
\ No newline at end of file
+AWS_SECRET_KEY=Your-AWS-Secret-Access-Key
diff --git a/api/.gitignore b/api/.gitignore
index 6769e21d..68bc17f9 100644
--- a/api/.gitignore
+++ b/api/.gitignore
@@ -157,4 +157,4 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
-#.idea/
\ No newline at end of file
+#.idea/
diff --git a/api/Dockerfile b/api/Dockerfile
new file mode 100644
index 00000000..b39985bc
--- /dev/null
+++ b/api/Dockerfile
@@ -0,0 +1,27 @@
+FROM python:3.11-slim AS builder
+WORKDIR /app
+
+RUN apt-get update && apt-get install -y \
+ gcc \
+ libpq-dev \
+ && rm -rf /var/lib/apt/lists/*
+
+COPY requirements.txt .
+RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
+
+FROM python:3.11-slim AS runner
+WORKDIR /app
+
+RUN apt-get update && apt-get install -y \
+ libpq5 \
+ && rm -rf /var/lib/apt/lists/*
+
+RUN groupadd -g 10001 appgroup && \
+ useradd -u 10001 -g appgroup -m -s /bin/bash appuser
+
+COPY --from=builder /install /usr/local
+COPY --chown=python:pythongr . .
+
+USER 10001
+EXPOSE 8000
+CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
diff --git a/api/main.py b/api/main.py
index d9d275b4..473909ff 100644
--- a/api/main.py
+++ b/api/main.py
@@ -1,5 +1,6 @@
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
+from prometheus_fastapi_instrumentator import Instrumentator
import qrcode
import boto3
import os
@@ -7,29 +8,48 @@
# Loading Environment variable (AWS Access Key and Secret Key)
from dotenv import load_dotenv
+
load_dotenv()
app = FastAPI()
+
+# Expose /metrics for Prometheus
+Instrumentator().instrument(app).expose(app)
+
+
+@app.get("/")
+def read_root():
+ return {"message": "API is running"}
+
+
# Allowing CORS for local testing
-origins = [
- "http://localhost:3000"
-]
+origins = ["*"]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
+ allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# AWS S3 Configuration
s3 = boto3.client(
- 's3',
- aws_access_key_id= os.getenv("AWS_ACCESS_KEY"),
- aws_secret_access_key= os.getenv("AWS_SECRET_KEY"))
+ "s3",
+ aws_access_key_id=os.getenv("AWS_ACCESS_KEY"),
+ aws_secret_access_key=os.getenv("AWS_SECRET_KEY"),
+)
+
+bucket_name = "YOUR_BUCKET_NAME" # Add your bucket name here
+
+
+@app.get("/healthz/startup")
+@app.get("/healthz/ready")
+@app.get("/healthz/liveness")
+async def health_check():
+ return {"status": "ok"}
-bucket_name = 'YOUR_BUCKET_NAME' # Add your bucket name here
@app.post("/generate-qr/")
async def generate_qr(url: str):
@@ -44,10 +64,10 @@ async def generate_qr(url: str):
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
-
+
# Save QR Code to BytesIO object
img_byte_arr = BytesIO()
- img.save(img_byte_arr, format='PNG')
+ img.save(img_byte_arr, format="PNG")
img_byte_arr.seek(0)
# Generate file name for S3
@@ -55,11 +75,16 @@ async def generate_qr(url: str):
try:
# Upload to S3
- s3.put_object(Bucket=bucket_name, Key=file_name, Body=img_byte_arr, ContentType='image/png', ACL='public-read')
-
+ s3.put_object(
+ Bucket=bucket_name,
+ Key=file_name,
+ Body=img_byte_arr,
+ ContentType="image/png",
+ ACL="public-read",
+ )
+
# Generate the S3 URL
s3_url = f"https://{bucket_name}.s3.amazonaws.com/{file_name}"
return {"qr_code_url": s3_url}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
-
\ No newline at end of file
diff --git a/api/requirements.txt b/api/requirements.txt
index 41984a1c..7fb1ac8e 100644
--- a/api/requirements.txt
+++ b/api/requirements.txt
@@ -1,3 +1,5 @@
+httpx<0.28.0
+prometheus-fastapi-instrumentator>=6.1.0
annotated-types==0.6.0
anyio==3.7.1
boto3==1.34.11
diff --git a/api/test_main.py b/api/test_main.py
index 78fc9741..b344d7e4 100644
--- a/api/test_main.py
+++ b/api/test_main.py
@@ -3,6 +3,7 @@
client = TestClient(app)
+
def test_generate_qr():
url = "http://example.com"
response = client.post("/generate-qr/", json={"url": url})
@@ -10,8 +11,9 @@ def test_generate_qr():
assert response.status_code == 200
assert "qr_code_url" in response.json()
+
def test_generate_qr_invalid_url():
url = "invalid-url"
response = client.post("/generate-qr/", json={"url": url})
- assert response.status_code == 422 # FastAPI validation error
\ No newline at end of file
+ assert response.status_code == 422 # FastAPI validation error
diff --git a/front-end-nextjs/Dockerfile b/front-end-nextjs/Dockerfile
new file mode 100644
index 00000000..6a5348bb
--- /dev/null
+++ b/front-end-nextjs/Dockerfile
@@ -0,0 +1,25 @@
+FROM node:20-alpine AS builder
+WORKDIR /app
+
+COPY package*.json ./
+RUN npm install
+
+COPY . .
+ARG NEXT_PUBLIC_API_URL
+ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
+RUN npm run build --if-present
+
+FROM node:20-alpine AS runner
+WORKDIR /app
+ENV NODE_ENV=production
+
+RUN addgroup -g 10001 appgroup && \
+ adduser -u 10001 -G appgroup -s /bin/sh -D appuser
+
+COPY --chown=10001:10001 package*.json ./
+COPY --chown=10001:10001 --from=builder /app/.next ./.next
+COPY --chown=10001:10001 --from=builder /app/public ./public
+COPY --chown=10001:10001 --from=builder /app/node_modules ./node_modules
+
+EXPOSE 3000
+CMD ["npm", "run", "start"]
diff --git a/front-end-nextjs/next.config.js b/front-end-nextjs/next.config.js
index 767719fc..a5089350 100644
--- a/front-end-nextjs/next.config.js
+++ b/front-end-nextjs/next.config.js
@@ -1,4 +1,7 @@
/** @type {import('next').NextConfig} */
-const nextConfig = {}
+const nextConfig = {
+ serverExternalPackages: ['prom-client'],
+};
-module.exports = nextConfig
+
+module.exports = nextConfig;
diff --git a/front-end-nextjs/package.json b/front-end-nextjs/package.json
index ecbbea37..b4f2568b 100644
--- a/front-end-nextjs/package.json
+++ b/front-end-nextjs/package.json
@@ -11,6 +11,7 @@
"dependencies": {
"axios": "^1.6.3",
"next": "14.0.4",
+ "prom-client": "^15.1.0",
"react": "^18",
"react-dom": "^18"
},
diff --git a/front-end-nextjs/public/next.svg b/front-end-nextjs/public/next.svg
index 5174b28c..5bb00d40 100644
--- a/front-end-nextjs/public/next.svg
+++ b/front-end-nextjs/public/next.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/front-end-nextjs/public/vercel.svg b/front-end-nextjs/public/vercel.svg
index d2f84222..1aeda7d6 100644
--- a/front-end-nextjs/public/vercel.svg
+++ b/front-end-nextjs/public/vercel.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
diff --git a/front-end-nextjs/src/app/metrics/route.js b/front-end-nextjs/src/app/metrics/route.js
new file mode 100644
index 00000000..e11dbcd2
--- /dev/null
+++ b/front-end-nextjs/src/app/metrics/route.js
@@ -0,0 +1,16 @@
+import { collectDefaultMetrics, register } from 'prom-client';
+
+export const dynamic = 'force-dynamic';
+
+// Initialize default node/runtime metrics
+if (!global._hasInitPrometheus) {
+ collectDefaultMetrics();
+ global._hasInitPrometheus = true;
+}
+
+export async function GET() {
+ const metrics = await register.metrics();
+ return new Response(metrics, {
+ headers: { 'Content-Type': register.contentType },
+ });
+}
diff --git a/front-end-nextjs/src/app/page.js b/front-end-nextjs/src/app/page.js
index db5b0dc6..5e436da0 100644
--- a/front-end-nextjs/src/app/page.js
+++ b/front-end-nextjs/src/app/page.js
@@ -10,7 +10,8 @@ export default function Home() {
const handleSubmit = async (e) => {
e.preventDefault();
try {
- const response = await axios.post(`http://localhost:8000/generate-qr/?url=${url}`);
+ const apiUrl = process.env.NEXT_PUBLIC_API_URL || '/api';
+ const response = await axios.post(`${apiUrl}/generate-qr/?url=${encodeURIComponent(url)}`);
setQrCodeUrl(response.data.qr_code_url);
} catch (error) {
console.error('Error generating QR Code:', error);
diff --git a/gitlab-ci-docker.yml b/gitlab-ci-docker.yml
new file mode 100644
index 00000000..a248095b
--- /dev/null
+++ b/gitlab-ci-docker.yml
@@ -0,0 +1,68 @@
+stages:
+ - check
+ - test
+ - build
+ - deploy
+
+default:
+ tags:
+ - qr
+
+lint_code:
+ stage: check
+ image: python:3.10-slim
+ script:
+ - pip install flake8
+ - flake8 . --exclude=venv
+
+test_api:
+ stage: test
+ image: python:3.10-slim
+ script:
+ - pip install -r api/requirements.txt
+ - python api/test_main.py
+
+build_api:
+ stage: build
+ image: docker:29
+ services:
+ - docker:29-dind
+ variables:
+ DOCKER_TLS_CERTDIR: "/certs"
+ before_script:
+ - echo "$CI_JOB_TOKEN" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
+ script:
+ - docker build -t "$CI_REGISTRY_IMAGE/api:$CI_COMMIT_SHA" ./api
+ - docker push "$CI_REGISTRY_IMAGE/api:$CI_COMMIT_SHA"
+
+build_front:
+ stage: build
+ image: docker:29
+ services:
+ - docker:29-dind
+ variables:
+ DOCKER_TLS_CERTDIR: "/certs"
+ before_script:
+ - echo "$CI_JOB_TOKEN" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
+ script:
+ - >-
+ docker build
+ --build-arg NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL # replace with cm
+ -t $CI_REGISTRY_IMAGE/front:$CI_COMMIT_SHA
+ ./front-end-nextjs
+ - docker push $CI_REGISTRY_IMAGE/front:$CI_COMMIT_SHA
+
+deploy:
+ stage: deploy
+ image: docker:29
+ variables:
+ DOCKER_HOST: "unix:///var/run/docker.sock"
+ before_script:
+ - echo "$CI_JOB_TOKEN" | docker login -u "$CI_REGISTRY_USER" --password-stdin "$CI_REGISTRY"
+ script:
+ - export CI_REGISTRY_IMAGE="$CI_REGISTRY_IMAGE"
+ - export IMAGE_TAG="$CI_COMMIT_SHA"
+ - docker compose pull
+ - docker compose up -d --remove-orphans
+ rules:
+ - if: $CI_COMMIT_BRANCH == "main"
diff --git a/k8s/configmap.yml b/k8s/configmap.yml
new file mode 100644
index 00000000..ae9748a4
--- /dev/null
+++ b/k8s/configmap.yml
@@ -0,0 +1,7 @@
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: cm
+data:
+ NEXT_PUBLIC_API_URL: "/api"
diff --git a/k8s/fastapi_deployment.yml b/k8s/fastapi_deployment.yml
new file mode 100644
index 00000000..985c74a8
--- /dev/null
+++ b/k8s/fastapi_deployment.yml
@@ -0,0 +1,76 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: fastapi
+spec:
+ replicas: 2
+ strategy:
+ type: RollingUpdate
+ rollingUpdate:
+ maxSurge: 25%
+ maxUnavailable: 0
+ selector:
+ matchLabels:
+ name: fastapi
+ template:
+ metadata:
+ annotations:
+ prometheus.io/scrape: "true"
+ prometheus.io/port: "8000"
+ prometheus.io/path: "/metrics"
+ labels:
+ name: fastapi
+ spec:
+ terminationGracePeriodSeconds: 30
+ securityContext:
+ runAsNonRoot: true
+ runAsUser: 10001
+ runAsGroup: 10001
+ fsGroup: 10001
+ topologySpreadConstraints:
+ - maxSkew: 1
+ topologyKey: kubernetes.io/hostname
+ whenUnsatisfiable: ScheduleAnyway
+ labelSelector:
+ matchLabels:
+ name: fastapi
+ imagePullSecrets:
+ - name: gitlab-registry-secret
+ containers:
+ - name: fastapi
+ image: "${CI_REGISTRY_IMAGE}/api:${CI_COMMIT_SHA}"
+ imagePullPolicy: IfNotPresent
+ ports:
+ - name: api
+ containerPort: 8000
+ resources:
+ requests:
+ cpu: "125m"
+ memory: "128Mi"
+ limits:
+ cpu: "250m"
+ memory: "256Mi"
+
+ startupProbe:
+ httpGet:
+ path: /healthz/startup
+ port: 8000
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ failureThreshold: 10
+ readinessProbe:
+ httpGet:
+ path: /healthz/ready
+ port: 8000
+ periodSeconds: 10
+ timeoutSeconds: 2
+ successThreshold: 1
+ failureThreshold: 3
+ livenessProbe:
+ httpGet:
+ path: /healthz/liveness
+ port: 8000
+ periodSeconds: 15
+ timeoutSeconds: 2
+ failureThreshold: 3
diff --git a/k8s/fastapi_service.yml b/k8s/fastapi_service.yml
new file mode 100644
index 00000000..cb195970
--- /dev/null
+++ b/k8s/fastapi_service.yml
@@ -0,0 +1,14 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: fastapi
+spec:
+ selector:
+ name: fastapi
+ type: ClusterIP
+ ports:
+ - name: api
+ port: 8000
+ targetPort: 8000
+ protocol: TCP
diff --git a/k8s/frontend_deployment.yml b/k8s/frontend_deployment.yml
new file mode 100644
index 00000000..cd85a1a0
--- /dev/null
+++ b/k8s/frontend_deployment.yml
@@ -0,0 +1,67 @@
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: frontend
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ name: frontend
+ template:
+ metadata:
+ annotations:
+ prometheus.io/scrape: "true"
+ prometheus.io/port: "3000"
+ prometheus.io/path: "/metrics"
+ labels:
+ name: frontend
+ spec:
+ terminationGracePeriodSeconds: 30
+ securityContext:
+ runAsNonRoot: true
+ runAsUser: 10001
+ runAsGroup: 10001
+ fsGroup: 10001
+ imagePullSecrets:
+ - name: gitlab-registry-secret
+ containers:
+ - name: frontend
+ image: "${CI_REGISTRY_IMAGE}/front:${CI_COMMIT_SHA}"
+ imagePullPolicy: IfNotPresent
+ envFrom:
+ - configMapRef:
+ name: cm
+ ports:
+ - name: front
+ containerPort: 3000
+ resources:
+ requests:
+ cpu: "50m"
+ memory: "64Mi"
+ limits:
+ cpu: "100m"
+ memory: "128Mi"
+
+ startupProbe:
+ httpGet:
+ path: /
+ port: 3000
+ initialDelaySeconds: 5
+ periodSeconds: 5
+ failureThreshold: 10
+ readinessProbe:
+ httpGet:
+ path: /
+ port: 3000
+ periodSeconds: 10
+ timeoutSeconds: 2
+ successThreshold: 1
+ failureThreshold: 3
+ livenessProbe:
+ httpGet:
+ path: /
+ port: 3000
+ periodSeconds: 15
+ timeoutSeconds: 2
+ failureThreshold: 3
diff --git a/k8s/frontend_service.yml b/k8s/frontend_service.yml
new file mode 100644
index 00000000..f8eea410
--- /dev/null
+++ b/k8s/frontend_service.yml
@@ -0,0 +1,14 @@
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: frontend
+spec:
+ selector:
+ name: frontend
+ type: ClusterIP
+ ports:
+ - name: api
+ port: 3000
+ targetPort: 3000
+ protocol: TCP
diff --git a/k8s/ingress.yml b/k8s/ingress.yml
new file mode 100644
index 00000000..7a0d0f7c
--- /dev/null
+++ b/k8s/ingress.yml
@@ -0,0 +1,36 @@
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: fastapi-ingress
+ annotations:
+ nginx.ingress.kubernetes.io/use-regex: "true"
+ nginx.ingress.kubernetes.io/rewrite-target: /$2
+spec:
+ ingressClassName: nginx
+ rules:
+ - http:
+ paths:
+ - path: /api(/|$)(.*)
+ pathType: ImplementationSpecific
+ backend:
+ service:
+ name: fastapi
+ port:
+ number: 8000
+---
+apiVersion: networking.k8s.io/v1
+kind: Ingress
+metadata:
+ name: frontend-ingress
+spec:
+ ingressClassName: nginx
+ rules:
+ - http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: frontend
+ port:
+ number: 3000
diff --git a/k8s/ingress_nginx.yml b/k8s/ingress_nginx.yml
new file mode 100644
index 00000000..b5ad43ba
--- /dev/null
+++ b/k8s/ingress_nginx.yml
@@ -0,0 +1,22 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: ingress-nginx-controller
+ namespace: ingress-nginx
+spec:
+ strategy:
+ type: RollingUpdate
+ rollingUpdate:
+ maxUnavailable: 1
+ maxSurge: 1
+ template:
+ spec:
+ hostNetwork: true
+ dnsPolicy: ClusterFirstWithHostNet
+ containers:
+ - name: controller
+ env:
+ - name: KUBERNETES_SERVICE_HOST
+ value: "192.168.1.10"
+ - name: KUBERNETES_SERVICE_PORT
+ value: "6443"
diff --git a/k8s/kustomization.yml b/k8s/kustomization.yml
new file mode 100644
index 00000000..ef493360
--- /dev/null
+++ b/k8s/kustomization.yml
@@ -0,0 +1,20 @@
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+
+resources:
+ - https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/baremetal/deploy.yaml
+ - configmap.yml
+ - fastapi_deployment.yml
+ - fastapi_service.yml
+ - frontend_deployment.yml
+ - frontend_service.yml
+ - ingress.yml
+ - monitoring/prometheus-configmap.yml
+ - monitoring/prometheus-rbac.yml
+ - monitoring/prometheus-deployment.yml
+ - monitoring/prometheus-service.yml
+ - monitoring/grafana-deployment.yml
+ - monitoring/grafana-service.yml
+
+patches:
+ - path: ingress_nginx.yml
diff --git a/k8s/monitoring/grafana-deployment.yml b/k8s/monitoring/grafana-deployment.yml
new file mode 100644
index 00000000..cb693cb6
--- /dev/null
+++ b/k8s/monitoring/grafana-deployment.yml
@@ -0,0 +1,23 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: grafana
+ namespace: default
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: grafana
+ template:
+ metadata:
+ labels:
+ app: grafana
+ spec:
+ containers:
+ - name: grafana
+ image: grafana/grafana:10.4.0
+ ports:
+ - containerPort: 3000
+ env:
+ - name: GF_SECURITY_ADMIN_PASSWORD
+ value: "admin"
diff --git a/k8s/monitoring/grafana-service.yml b/k8s/monitoring/grafana-service.yml
new file mode 100644
index 00000000..fbed945f
--- /dev/null
+++ b/k8s/monitoring/grafana-service.yml
@@ -0,0 +1,13 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: grafana-service
+ namespace: default
+spec:
+ type: NodePort
+ selector:
+ app: grafana
+ ports:
+ - port: 3000
+ targetPort: 3000
+ nodePort: 30001
diff --git a/k8s/monitoring/prometheus-configmap.yml b/k8s/monitoring/prometheus-configmap.yml
new file mode 100644
index 00000000..1f35bda4
--- /dev/null
+++ b/k8s/monitoring/prometheus-configmap.yml
@@ -0,0 +1,42 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: prometheus-config
+ namespace: default
+data:
+ prometheus.yml: |
+ global:
+ scrape_interval: 15s
+
+ scrape_configs:
+ - job_name: 'k8s-pod-metrics'
+ kubernetes_sd_configs:
+ - role: pod
+ relabel_configs:
+ - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
+ action: keep
+ regex: true
+ - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
+ action: replace
+ target_label: __metrics_path__
+ regex: (.+)
+ - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
+ action: replace
+ regex: ([^:]+)(?::\d+)?;(\d+)
+ replacement: $1:$2
+ target_label: __address__
+ - source_labels: [__meta_kubernetes_pod_name]
+ action: replace
+ target_label: pod
+ - source_labels: [pod]
+ regex: (fastapi-.*)
+ target_label: app_name
+ replacement: fastapi
+ - job_name: 'k8s-cadvisor'
+ scheme: https
+ metrics_path: /metrics/cadvisor
+ tls_config:
+ insecure_skip_verify: true
+ bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
+ kubernetes_sd_configs:
+ - role: node
diff --git a/k8s/monitoring/prometheus-deployment.yml b/k8s/monitoring/prometheus-deployment.yml
new file mode 100644
index 00000000..2f2315f9
--- /dev/null
+++ b/k8s/monitoring/prometheus-deployment.yml
@@ -0,0 +1,30 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: prometheus
+ namespace: default
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: prometheus
+ template:
+ metadata:
+ labels:
+ app: prometheus
+ spec:
+ serviceAccountName: prometheus
+ containers:
+ - name: prometheus
+ image: prom/prometheus:v2.51.0
+ args:
+ - "--config.file=/etc/prometheus/prometheus.yml"
+ ports:
+ - containerPort: 9090
+ volumeMounts:
+ - name: config-volume
+ mountPath: /etc/prometheus
+ volumes:
+ - name: config-volume
+ configMap:
+ name: prometheus-config
diff --git a/k8s/monitoring/prometheus-rbac.yml b/k8s/monitoring/prometheus-rbac.yml
new file mode 100644
index 00000000..227e15e7
--- /dev/null
+++ b/k8s/monitoring/prometheus-rbac.yml
@@ -0,0 +1,37 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: prometheus
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: prometheus
+rules:
+ - apiGroups: [""]
+ resources:
+ - nodes
+ - nodes/metrics
+ - services
+ - endpoints
+ - pods
+ verbs: ["get", "list", "watch"]
+ - apiGroups: [""]
+ resources:
+ - configmaps
+ verbs: ["get"]
+ - nonResourceURLs: ["/metrics"]
+ verbs: ["get"]
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: prometheus
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: prometheus
+subjects:
+ - kind: ServiceAccount
+ name: prometheus
+ namespace: default
diff --git a/k8s/monitoring/prometheus-service.yml b/k8s/monitoring/prometheus-service.yml
new file mode 100644
index 00000000..ecea7e73
--- /dev/null
+++ b/k8s/monitoring/prometheus-service.yml
@@ -0,0 +1,13 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: prometheus-service
+ namespace: default
+spec:
+ type: NodePort
+ selector:
+ app: prometheus
+ ports:
+ - port: 9090
+ targetPort: 9090
+ nodePort: 30090