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