> ## Documentation Index
> Fetch the complete documentation index at: https://ayakaleaf-pro.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# التوسع الأفقي

> شغّل عدة نسخ متماثلة من Ayakaleaf Pro خلف موازن أحمال مع جلسات لاصقة (sticky sessions).

تستخدم النسخة المتماثلة الواحدة وحدة المعالجة المركزية لعقدة واحدة في كل عملية ترجمة: على عقد الاختبار لدينا ذات 2 vCPU، استغرقت ست عمليات ترجمة متزامنة ثلاثة أضعاف الوقت الذي تستغرقه عملية واحدة، بينما بقيت العقد الأخرى خاملة. أما النسخ المتماثلة المتعددة فتوزّع المستخدمين، وعمليات الترجمة الخاصة بهم، على العقد.

يتبع هذا تصميم [التوسع الأفقي](https://ayakaleaf-pro.mintlify.site/ar/on-premises/maintenance/horizontal-scaling.md) الخاص بـ Overleaf، بعد نقله إلى Kubernetes:

| المتطلب | هنا |
| - | - |
| MongoDB وRedis مشتركان | `10-mongo.yaml` و`20-redis.yaml` من التثبيت |
| أسرار مشتركة | الـ Secret ‏`overleaf-secrets` من [الخطوة 2](https://ayakaleaf-pro.mintlify.site/ar/on-premises/installation/using-kubernetes/2.-create-the-namespace-and-secrets.md) يحتوي عليها بالفعل |
| ملفات المشاريع وسجلّها في **S3** (وليس NFS) | أي خدمة S3، راجع [S3](https://ayakaleaf-pro.mintlify.site/ar/on-premises/configuration/kubernetes/s3.md)؛ ويُستخدم Garage هناك مثالًا داخل الكتلة |
| قرص محلي لكل نسخة متماثلة لعمليات الترجمة والرفع | `hostPath` ‏`/srv/overleaf-ha` على كل عقدة |
| توجيه دائم، وWebSockets، ومهلات 10 دقائق، ورفع ملفات حتى 50 MB | HAProxy مع نموذج الإعداد الخاص بـ Overleaf |
| git-bridge كنسخة مفردة (singleton) بجوار نسخة متماثلة واحدة | git-bridge مع `overleaf-0` بوصفه الشقيق (sibling) الخاص به |

> [!WARNING]
>
> خطّط لـ S3 منذ البداية. تحتاج النسخة التي تحتفظ بالفعل بالملفات على وحدة التخزين الخاصة بها إلى [الترحيل إلى S3](https://ayakaleaf-pro.mintlify.site/ar/on-premises/maintenance/s3-migration.md) أولًا. يحلّ هذا الإعداد محل الـ Deployment من الخطوة 5: احذفه (`kubectl delete -f 40-overleaf.yaml`) قبل تطبيق الـ StatefulSet.

**1. تخزين S3**

أعدّ الحاويات (buckets) الثلاث والمستخدمَين الاثنين كما هو موضح في [S3](https://ayakaleaf-pro.mintlify.site/ar/on-premises/configuration/kubernetes/s3.md)، باستخدام خدمة S3 لديك أو باستخدام Garage داخل الكتلة، وضع المفاتيح الأربعة في الـ Secret ‏`overleaf-s3`. يستخدم الإعداد أدناه مثال Garage من تلك الصفحة.

**2. الإعداد**

يحلّ هذا الـ ConfigMap محل `30-config.yaml`. وهو يضيف S3 وgit-bridge ويوجّه `OVERLEAF_SITE_URL` إلى موازن الأحمال:

```yaml title="60-config.yaml" wrap theme={null}
apiVersion: v1
kind: ConfigMap
metadata:
  name: overleaf-env
  namespace: overleaf
data:
  OVERLEAF_APP_NAME: Ayakaleaf Pro
  OVERLEAF_SITE_URL: http://192.0.2.10:30081      # the load balancer (HAProxy) URL
  OVERLEAF_ADMIN_EMAIL: admin@example.com
  # Emails: add the sender and the SMTP server together, see Email delivery
  OVERLEAF_MONGO_URL: mongodb://mongo-0.mongo:27017,mongo-1.mongo:27017,mongo-2.mongo:27017/sharelatex?replicaSet=overleaf
  OVERLEAF_REDIS_HOST: redis
  REDIS_HOST: redis
  ENABLED_LINKED_FILE_TYPES: project_file,project_output_file,url
  ENABLE_CONVERSIONS: "true"
  EMAIL_CONFIRMATION_DISABLED: "true"
  OVERLEAF_TRUSTED_PROXY_IPS: loopback,10.244.0.0/16   # HAProxy runs in the pod CIDR
  # Sandboxed compiles, node paths: each replica owns /srv/overleaf-ha on its node
  SANDBOXED_COMPILES: "true"
  SANDBOXED_COMPILES_SIBLING_CONTAINERS: "true"
  DOCKER_RUNNER: "true"
  SANDBOXED_COMPILES_HOST_DIR_COMPILES: /srv/overleaf-ha/data/compiles
  SANDBOXED_COMPILES_HOST_DIR_OUTPUT: /srv/overleaf-ha/data/output
  SANDBOXED_COMPILES_HOST_DIR_CACHE: /srv/overleaf-ha/data/cache
  ALL_TEX_LIVE_DOCKER_IMAGES: ghcr.io/ayaka-notes/texlive-full:2026.1
  ALL_TEX_LIVE_DOCKER_IMAGE_NAMES: TeX Live 2026
  TEX_LIVE_DOCKER_IMAGE: ghcr.io/ayaka-notes/texlive-full:2026.1
  # S3 (required for more than one replica); keys are in the Secret overleaf-s3
  OVERLEAF_FILESTORE_BACKEND: s3
  OVERLEAF_FILESTORE_TEMPLATE_FILES_BUCKET_NAME: overleaf-template-files
  OVERLEAF_FILESTORE_S3_ENDPOINT: http://garage:3900
  OVERLEAF_FILESTORE_S3_PATH_STYLE: "true"
  OVERLEAF_FILESTORE_S3_REGION: garage
  OVERLEAF_HISTORY_BACKEND: s3
  OVERLEAF_HISTORY_PROJECT_BLOBS_BUCKET: overleaf-project-blobs
  OVERLEAF_HISTORY_CHUNKS_BUCKET: overleaf-chunks
  OVERLEAF_HISTORY_S3_ENDPOINT: http://garage:3900
  OVERLEAF_HISTORY_S3_PATH_STYLE: "true"
  OVERLEAF_HISTORY_S3_REGION: garage
  # Git integration: one git-bridge, its sibling is overleaf-0
  GIT_BRIDGE_ENABLED: "true"
  GIT_BRIDGE_HOST: 10.96.100.80      # the git-bridge Service's fixed ClusterIP, see 75-git-bridge.yaml
  GIT_BRIDGE_PORT: "8000"
```

**3. النسخ المتماثلة**

```yaml title="70-overleaf.yaml" wrap theme={null}
apiVersion: v1
kind: Service
metadata:
  name: overleaf-hl
  namespace: overleaf
spec:
  clusterIP: None            # stable DNS per replica: overleaf-0.overleaf-hl, ...
  selector:
    app: overleaf-ha
  ports:
    - {name: http, port: 80}
    - {name: web, port: 3000}       # git-bridge API (sibling only)
    - {name: history, port: 3100}   # history-v1, used by git-bridge via V1_HISTORY_URL
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: overleaf
  namespace: overleaf
spec:
  serviceName: overleaf-hl
  replicas: 3
  # OrderedReady: overleaf-0 runs the migrations alone before the next replica starts
  podManagementPolicy: OrderedReady
  selector:
    matchLabels:
      app: overleaf-ha
  template:
    metadata:
      labels:
        app: overleaf-ha
    spec:
      terminationGracePeriodSeconds: 60
      # Keep the environment to the ConfigMap and Secrets: no REDIS_PORT=tcp://..., GIT_BRIDGE_PORT=tcp://...
      enableServiceLinks: false
      affinity:
        podAntiAffinity:            # one replica per node: each uses that node's Docker and disk
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: overleaf-ha
              topologyKey: kubernetes.io/hostname
      containers:
        - name: sharelatex
          image: ghcr.io/ayaka-notes/overleaf-pro:6.3.2
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            # Each replica hands out history URLs with its own stable name, so git-bridge can reach them
            - name: V1_HISTORY_URL
              value: http://$(POD_NAME).overleaf-hl.overleaf.svc.cluster.local:3100/api
          envFrom:
            - configMapRef: {name: overleaf-env}
            - secretRef: {name: overleaf-secrets}
            - secretRef: {name: overleaf-s3}
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet: {path: /status, port: 80}
            periodSeconds: 10
          resources:
            requests: {cpu: 500m, memory: 1536Mi}
            limits: {memory: 3Gi}
          volumeMounts:
            - {name: local, mountPath: /var/lib/overleaf}
            - {name: docker-sock, mountPath: /var/run/docker.sock}
      volumes:
        # Local disk for ephemeral files (compiles, output, uploads); the HOST_DIR_* paths are under it
        - name: local
          hostPath: {path: /srv/overleaf-ha, type: DirectoryOrCreate}
        - name: docker-sock
          hostPath: {path: /var/run/docker.sock, type: Socket}
```

* **StatefulSet، وليس Deployment.** تحصل كل نسخة متماثلة على اسم ثابت (`overleaf-0`، `overleaf-1`، ...) يشير إليه ملف تعريف الارتباط الخاص بموازن الأحمال وgit-bridge، ويبدأ `OrderedReady` تشغيل `overleaf-0` وحده أولًا، بحيث لا تشغّل عمليات ترحيل قاعدة البيانات إلا نسخة متماثلة واحدة.
* **نسخة متماثلة واحدة لكل عقدة.** تبدأ كل نسخة متماثلة عمليات الترجمة الخاصة بها عبر Docker الخاص بعقدتها وتحتفظ بها في `/srv/overleaf-ha` الخاص بعقدتها.
* **`V1_HISTORY_URL` لكل نسخة متماثلة.** تقدّم كل نسخة متماثلة روابط السجل باسمها الخاص، حتى يتمكن git-bridge من الوصول إليها.

**4. git-bridge**

```yaml title="75-git-bridge.yaml" wrap theme={null}
apiVersion: v1
kind: Service
metadata:
  name: git-bridge
  namespace: overleaf
spec:
  # Fixed ClusterIP (inside the service CIDR): the sharelatex image's nginx proxies /git/ with
  # `resolver 127.0.0.11` (Docker's DNS), which does not exist in Kubernetes, so GIT_BRIDGE_HOST is this IP
  clusterIP: 10.96.100.80
  selector:
    app: git-bridge
  ports:
    - {name: http, port: 8000}
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: git-bridge
  namespace: overleaf
spec:
  serviceName: git-bridge
  replicas: 1
  selector:
    matchLabels:
      app: git-bridge
  template:
    metadata:
      labels:
        app: git-bridge
    spec:
      # Service "git-bridge" would inject GIT_BRIDGE_PORT=tcp://..., which git-bridge reads as its port
      enableServiceLinks: false
      containers:
        - name: git-bridge
          image: ghcr.io/ayaka-notes/overleaf-pro/git-bridge:6.3.2   # same version as the sharelatex image
          command: ["/server-pro-start.sh"]
          securityContext:
            runAsUser: 0
          env:
            - {name: GIT_BRIDGE_API_BASE_URL, value: "http://overleaf-0.overleaf-hl.overleaf.svc.cluster.local:3000/api/v0/"}
            - {name: GIT_BRIDGE_OAUTH2_SERVER, value: "http://overleaf-0.overleaf-hl.overleaf.svc.cluster.local"}
            - {name: GIT_BRIDGE_POSTBACK_BASE_URL, value: "http://git-bridge.overleaf.svc.cluster.local:8000"}
            - {name: GIT_BRIDGE_ROOT_DIR, value: /data/git-bridge}
          ports:
            - containerPort: 8000
          resources:
            requests: {cpu: 100m, memory: 256Mi}
            limits: {memory: 768Mi}
          volumeMounts:
            - {name: data, mountPath: /data/git-bridge}
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: [ReadWriteOnce]
        resources:
          requests:
            storage: 5Gi
```

راجع [التكامل مع Git](https://ayakaleaf-pro.mintlify.site/ar/on-premises/configuration/kubernetes/integrations/git-integration.md) لمعرفة سبب كون `GIT_BRIDGE_HOST` عنوان IP ثابتًا.

**5. موازن الأحمال**

إعداد HAProxy هو نموذج Overleaf مع خمسة تغييرات: توجيه `/project/<id>/mcp` إلى `overleaf-0` مثل `/git/` (راجع [خادم MCP](https://ayakaleaf-pro.mintlify.site/ar/on-premises/configuration/kubernetes/integrations/mcp-server.md#several-replicas))، وHTTP عادي على المنفذ `8080` (تعمل الصورة بمستخدم غير الجذر، ولا يمكنه الارتباط بالمنفذ 80 في ظل containerd)، والعثور على الخوادم عبر DNS الخاص بالكتلة (تتغير عناوين IP الخاصة بالـ Pods عند كل إعادة تشغيل)، وDaemonSet بحيث تستجيب كل عقدة، و`balance leastconn` بدلًا من `roundrobin` الافتراضي (راجع قسم الأسئلة والأجوبة أدناه).

```yaml title="80-haproxy.yaml" wrap theme={null}
apiVersion: v1
kind: ConfigMap
metadata:
  name: haproxy-config
  namespace: overleaf
data:
  haproxy.cfg: |
    global
      log stdout format raw local0 info

    resolvers kubedns
      parse-resolv-conf
      hold valid 10s

    defaults
      mode                    http
      # New sessions go to the replica with the fewest open connections (editors keep a WebSocket open)
      balance                 leastconn
      option                  httpchk HEAD /status
      http-check              expect status 200
      default-server          check resolvers kubedns init-addr none
      log                     global
      option                  httplog
      # Reroute to a different backend if the sticky one is down
      option                  redispatch 1
      retries                 3
      # Sticky session for 24h of inactivity -- compile output is deleted after 24h
      cookie                  server-pro-ha insert maxidle 24h
      timeout queue           1m
      timeout connect         15s
      timeout client          1m
      # Allow slow compiles -- hard-coded limit in clsi is 10min
      timeout server          10m
      # Disconnect the editor after 23h
      timeout tunnel          23h
      # Lower than NGINX_KEEPALIVE_TIMEOUT (65s) in the sharelatex container
      timeout http-keep-alive 60s

    listen overleaf
      bind :8080
      option forwardfor
      # When TLS ends in front of HAProxy, also pass:  http-request set-header X-Forwarded-Proto https
      # git requests go to the git-bridge sibling
      use-server overleaf-0 if { path_beg /git/ }
      # MCP clients send no cookie: keep compile, log and PDF download on one replica
      use-server overleaf-0 if { path_reg ^/project/[0-9a-f]{24}/mcp(/|$) }
      http-response add-header X-Served-By %s
      stats enable
      stats uri /haproxy
      server overleaf-0 overleaf-0.overleaf-hl.overleaf.svc.cluster.local:80 cookie overleaf-0
      server overleaf-1 overleaf-1.overleaf-hl.overleaf.svc.cluster.local:80 cookie overleaf-1
      server overleaf-2 overleaf-2.overleaf-hl.overleaf.svc.cluster.local:80 cookie overleaf-2
---
apiVersion: apps/v1
# DaemonSet: one HAProxy per node, so the NodePort answers on every node with externalTrafficPolicy
# Local. The cookie holds the replica name, so stickiness works whichever HAProxy a request hits.
kind: DaemonSet
metadata:
  name: haproxy
  namespace: overleaf
spec:
  selector:
    matchLabels:
      app: haproxy
  template:
    metadata:
      labels:
        app: haproxy
      annotations:
        config-hash: "1"   # bump to restart after editing the ConfigMap
    spec:
      containers:
        - name: haproxy
          image: haproxy:lts-alpine
          ports:
            - containerPort: 8080
          resources:
            requests: {cpu: 50m, memory: 32Mi}
            limits: {memory: 256Mi}
          volumeMounts:
            - {name: config, mountPath: /usr/local/etc/haproxy}
      volumes:
        - name: config
          configMap:
            name: haproxy-config
---
apiVersion: v1
kind: Service
metadata:
  name: haproxy
  namespace: overleaf
spec:
  type: NodePort
  externalTrafficPolicy: Local    # keep the client IP (no SNAT); only nodes running HAProxy answer
  selector:
    app: haproxy
  ports:
    - {name: http, port: 80, targetPort: 8080, nodePort: 30081}
```

```bash theme={null}
kubectl delete -f 40-overleaf.yaml --ignore-not-found
kubectl apply -f 60-config.yaml -f 70-overleaf.yaml -f 75-git-bridge.yaml -f 80-haproxy.yaml
kubectl -n overleaf rollout status statefulset/overleaf --timeout=20m
curl -sI http://192.0.2.10:30081/login | grep -i x-served-by
```

يحافظ `externalTrafficPolicy: Local` على عناوين IP الخاصة بالمستخدمين في `X-Forwarded-For`.

**6. TLS**

أنهِ HTTPS في HAProxy كما هو موضح في [وكيل TLS](https://ayakaleaf-pro.mintlify.site/ar/on-premises/configuration/kubernetes/tls-proxy.md)، ثم اضبط `OVERLEAF_SITE_URL` على عنوان `https://` و`OVERLEAF_SECURE_COOKIE: "true"` في `60-config.yaml`.

### ما الذي يمكن توقّعه

اختبرنا ثلاث نسخ متماثلة على ثلاث عقد لكل منها 2 vCPU و4 GB، مع MongoDB وRedis وGarage وgit-bridge وHAProxy على العقد نفسها:

* 12 مستخدمًا يترجمون مستندًا من 600 صفحة في الوقت نفسه: ترجم الـ 12 جميعهم مستنداتهم وجلبوا ملفات PDF الخاصة بهم، بواقع 4 مستخدمين لكل نسخة متماثلة؛ واستغرقت الجولة الواحدة من 8 إلى 18 ثانية. احتفظت كل عقدة بما لا يقل عن 1.3 GB من الذاكرة المتاحة.
* يبقى كل مستخدم على نسخة متماثلة واحدة (ملف تعريف الارتباط `server-pro-ha`)، بما في ذلك تنزيل ملف PDF الذي يجب أن يأتي من النسخة المتماثلة التي ترجمته. وينطبق الأمر نفسه عبر HTTPS، بما في ذلك WebSocket الخاص بالمحرر.
* عندما تتوقف نسخة متماثلة، يتلقى مستخدموها أخطاء لمدة 15 إلى 20 ثانية تقريبًا، إلى أن يعلّمها HAProxy على أنها متوقفة؛ ثم ينتقلون إلى نسخة متماثلة أخرى ويبقون مسجلين دخولهم، لأن الجلسات موجودة في Redis.

### أسئلة وأجوبة

> [accordion] لماذا `balance leastconn`؟
>
> **الإجابة:** مع نموذج Overleaf (‏`roundrobin`، وHAProxy واحد لكل عقدة)، توزّع 12 مستخدمًا جديدًا بواقع 8 و3 و1 على النسخ المتماثلة الثلاث في اختبارنا، وجعلت النسخة المتماثلة الأكثر انشغالًا أبطأ المستخدمين ينتظرون 29 ثانية. ومع `leastconn`، الذي يفضّل النسخة المتماثلة ذات أقل عدد من الاتصالات المفتوحة (يبقي كل محرر WebSocket مفتوحًا)، أعطى الاختبار نفسه 4 و4 و4 و18 ثانية. لم يتغير الالتصاق (stickiness): فهو يأتي من ملف تعريف الارتباط.

> [accordion] هل يمكنني استخدام متحكم الـ Ingress لديّ بدلًا من HAProxy؟
>
> **الإجابة:** نعم، إذا كان يوجّه حسب ملف تعريف ارتباط إلى نسخة متماثلة ثابتة، ويدعم WebSockets، ويسمح بأزمنة طلبات تصل إلى 10 دقائق ورفع ملفات حتى 50 MB، ويُبقي مهلة keep-alive لديه أقل من 65 ثانية، ويرسل `X-Forwarded-For` و`X-Forwarded-Proto`، ويرسل `/git/` و`/project/<id>/mcp` إلى `overleaf-0`. وجّه إلى الأسماء الخاصة بالنسخ المتماثلة (`overleaf-N.overleaf-hl`)، وليس إلى Service يوازن كل طلب.

> [accordion] كيف أضيف نسخة متماثلة رابعة؟
>
> **الإجابة:** أضف عقدة تحتوي على Docker وصور TeX Live، واضبط `replicas: 4` في `70-overleaf.yaml`، وأضف سطر `server overleaf-3 ...` إلى إعداد HAProxy (وزِد قيمة `config-hash` حتى يُعاد تشغيل HAProxy).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.