Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

docs(examples): Kubernetes with Meilisearch

Abierto
#45 0 comentarios 0 reacciones 1 asignado Ver en GitHub

@aRustyDev ya está trabajando en esto.

Desde el 5/1/2026.

Evaluación

Este issue todavía no se ha evaluado.

Descripción

documentation

Kubernetes with Meilisearch

Deploy mdbook-htmx with Meilisearch for server-side full-text search.

Overview

This deployment includes:

  • Meilisearch: Fast, typo-tolerant search engine
  • Documentation Server: NGINX serving mdbook-htmx output
  • Indexer Job: CronJob to keep search index updated

Architecture

                    ┌─────────────┐
                    │   Ingress   │
                    └──────┬──────┘
                           │
            ┌──────────────┼──────────────┐
            │              │              │
     ┌──────▼──────┐┌──────▼──────┐       │
     │    Docs     ││   Search    │       │
     │   Service   ││   Service   │       │
     └──────┬──────┘└──────┬──────┘       │
            │              │              │
     ┌──────▼──────┐┌──────▼──────┐       │
     │    Docs     ││ Meilisearch │       │
     │ Deployment  ││  StatefulSet│       │
     └─────────────┘└──────┬──────┘       │
                           │              │
                    ┌──────▼──────┐       │
                    │     PVC     │       │
                    │ (search idx)│       │
                    └─────────────┘       │
                           │              │
                    ┌──────▼──────┐       │
                    │  Indexer    │◄──────┘
                    │   CronJob   │
                    └─────────────┘

Meilisearch Deployment

meilisearch-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: meilisearch
  namespace: docs
  labels:
    app.kubernetes.io/name: meilisearch
    app.kubernetes.io/component: search
spec:
  serviceName: meilisearch
  replicas: 1
  selector:
    matchLabels:
      app: meilisearch
  template:
    metadata:
      labels:
        app: meilisearch
    spec:
      containers:
        - name: meilisearch
          image: getmeili/meilisearch:v1.6
          ports:
            - containerPort: 7700
              name: http
          env:
            - name: MEILI_ENV
              value: production
            - name: MEILI_MASTER_KEY
              valueFrom:
                secretKeyRef:
                  name: meilisearch-secrets
                  key: master-key
            - name: MEILI_NO_ANALYTICS
              value: "true"
            - name: MEILI_DB_PATH
              value: /meili_data/data.ms
          resources:
            requests:
              cpu: 100m
              memory: 256Mi
            limits:
              cpu: 500m
              memory: 512Mi
          volumeMounts:
            - name: data
              mountPath: /meili_data
          livenessProbe:
            httpGet:
              path: /health
              port: 7700
            initialDelaySeconds: 10
            periodSeconds: 30
          readinessProbe:
            httpGet:
              path: /health
              port: 7700
            initialDelaySeconds: 5
            periodSeconds: 10
          securityContext:
            allowPrivilegeEscalation: false
            runAsNonRoot: true
            runAsUser: 1000
      securityContext:
        fsGroup: 1000
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        storageClassName: standard
        resources:
          requests:
            storage: 5Gi
meilisearch-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: meilisearch
  namespace: docs
spec:
  type: ClusterIP
  ports:
    - port: 7700
      targetPort: http
      protocol: TCP
      name: http
  selector:
    app: meilisearch
meilisearch-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: meilisearch-secrets
  namespace: docs
type: Opaque
stringData:
  master-key: "your-master-key-here"  # Generate with: openssl rand -base64 32

Search Indexer

indexer-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: indexer-scripts
  namespace: docs
data:
  index.py: |
    #!/usr/bin/env python3
    """Index documentation content to Meilisearch."""
    import json
    import os
    import hashlib
    from pathlib import Path
    import meilisearch

    MEILI_HOST = os.environ.get('MEILI_HOST', 'http://meilisearch:7700')
    MEILI_KEY = os.environ['MEILI_MASTER_KEY']
    DOCS_PATH = os.environ.get('DOCS_PATH', '/docs')
    INDEX_NAME = os.environ.get('INDEX_NAME', 'docs')

    def main():
        client = meilisearch.Client(MEILI_HOST, MEILI_KEY)

        # Load search index from mdbook-htmx output
        search_index_path = Path(DOCS_PATH) / 'search-index.json'

        if not search_index_path.exists():
            print(f"Search index not found at {search_index_path}")
            return

        with open(search_index_path) as f:
            index_data = json.load(f)

        # Transform to Meilisearch format
        documents = []
        for page in index_data.get('pages', []):
            doc = {
                'id': hashlib.md5(page['path'].encode()).hexdigest(),
                'path': page['path'],
                'title': page['title'],
                'content': page.get('content', ''),
                'headings': page.get('headings', []),
                'scope': page.get('scope', 'all'),
                'tags': page.get('tags', []),
            }
            documents.append(doc)

        # Create or update index
        index = client.index(INDEX_NAME)

        # Configure searchable and filterable attributes
        index.update_settings({
            'searchableAttributes': [
                'title',
                'headings',
                'content',
                'tags',
            ],
            'filterableAttributes': [
                'scope',
                'tags',
            ],
            'sortableAttributes': [
                'title',
            ],
            'rankingRules': [
                'words',
                'typo',
                'proximity',
                'attribute',
                'sort',
                'exactness',
            ],
        })

        # Add documents
        task = index.add_documents(documents)
        print(f"Indexing task: {task.task_uid}")

        # Wait for completion
        client.wait_for_task(task.task_uid)
        print(f"Indexed {len(documents)} documents")

    if __name__ == '__main__':
        main()
indexer-cronjob.yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: docs-indexer
  namespace: docs
spec:
  schedule: "0 */6 * * *"  # Every 6 hours
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 3
  jobTemplate:
    spec:
      template:
        spec:
          containers:
            - name: indexer
              image: python:3.11-slim
              command: ["/bin/sh", "-c"]
              args:
                - |
                  pip install meilisearch
                  python /scripts/index.py
              env:
                - name: MEILI_HOST
                  value: "http://meilisearch:7700"
                - name: MEILI_MASTER_KEY
                  valueFrom:
                    secretKeyRef:
                      name: meilisearch-secrets
                      key: master-key
                - name: DOCS_PATH
                  value: "/docs"
              volumeMounts:
                - name: scripts
                  mountPath: /scripts
                - name: docs
                  mountPath: /docs
                  readOnly: true
          volumes:
            - name: scripts
              configMap:
                name: indexer-scripts
            - name: docs
              configMap:
                name: docs-content
          restartPolicy: OnFailure

Docs Server with Search Proxy

docs-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: docs
  namespace: docs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: docs
  template:
    metadata:
      labels:
        app: docs
    spec:
      containers:
        - name: nginx
          image: nginx:alpine
          ports:
            - containerPort: 80
          volumeMounts:
            - name: nginx-config
              mountPath: /etc/nginx/nginx.conf
              subPath: nginx.conf
            - name: docs-content
              mountPath: /usr/share/nginx/html
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 128Mi
      volumes:
        - name: nginx-config
          configMap:
            name: nginx-search-config
        - name: docs-content
          configMap:
            name: docs-content
nginx-search-config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-search-config
  namespace: docs
data:
  nginx.conf: |
    events {
        worker_connections 1024;
    }

    http {
        include /etc/nginx/mime.types;
        default_type application/octet-stream;

        # Meilisearch upstream
        upstream meilisearch {
            server meilisearch:7700;
            keepalive 32;
        }

        server {
            listen 80;

            # Static documentation
            location / {
                root /usr/share/nginx/html;
                try_files $uri $uri/ /index.html;
            }

            # Proxy search requests to Meilisearch
            location /search {
                # Extract API key from header or use default search key
                set $search_key "";
                if ($http_authorization ~* "Bearer (.+)") {
                    set $search_key $1;
                }

                proxy_pass http://meilisearch/indexes/docs/search;
                proxy_http_version 1.1;
                proxy_set_header Connection "";
                proxy_set_header Host $host;
                proxy_set_header X-Real-IP $remote_addr;

                # Add Meilisearch search API key
                proxy_set_header Authorization "Bearer ${SEARCH_API_KEY}";

                # CORS headers
                add_header Access-Control-Allow-Origin * always;
                add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
                add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;

                if ($request_method = OPTIONS) {
                    return 204;
                }
            }

            # Health check
            location /healthz {
                return 200 "OK\n";
            }
        }
    }

book.toml Configuration

[output.htmx.search]
enabled = true
mode = "server"
endpoint = "/search"
min_chars = 2
debounce_ms = 300

# Scope filtering
[output.htmx.search.filters]
scope = true
tags = true

Search API Keys

Create a search-only API key:

# Create search-only key (no write access)
curl -X POST 'http://meilisearch:7700/keys' \
  -H 'Authorization: Bearer YOUR_MASTER_KEY' \
  -H 'Content-Type: application/json' \
  --data-binary '{
    "description": "Search-only key for docs",
    "actions": ["search"],
    "indexes": ["docs"],
    "expiresAt": null
  }'

Store the search key in a secret:

apiVersion: v1
kind: Secret
metadata:
  name: meilisearch-search-key
  namespace: docs
stringData:
  search-key: "your-search-api-key"

Ingress Configuration

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: docs
  namespace: docs
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/configuration-snippet: |
      # Rate limit search endpoint
      limit_req zone=search burst=10 nodelay;
spec:
  tls:
    - hosts:
        - docs.example.com
      secretName: docs-tls
  rules:
    - host: docs.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: docs
                port:
                  number: 80

Monitoring Meilisearch

Prometheus metrics

Meilisearch exposes metrics at /metrics:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: meilisearch
  namespace: docs
spec:
  selector:
    matchLabels:
      app: meilisearch
  endpoints:
    - port: http
      path: /metrics
      interval: 30s
Key Metrics
  • meilisearch_http_requests_total
  • meilisearch_http_response_time_seconds
  • meilisearch_index_docs_count
  • meilisearch_db_size_bytes

Resource Sizing

Load Meilisearch CPU Meilisearch Memory Docs Size
Light (<100 pages) 100m 256Mi 1Gi
Medium (100-1000 pages) 250m 512Mi 5Gi
Heavy (1000+ pages) 500m 1Gi 10Gi

Next Steps

Lenguaje dominante
Rust
Estrellas
0
Forks
1
Métricas de merge de PR
Sin PR fusionados en 30 d

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de aRustyDev/mdbook-htmx

Todos los issues de aRustyDev/mdbook-htmx

Issues similares

Más issues de Rust

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.