הטמעה לדוגמה של AI Gateway עם נתב סוכן Envoy ב-GDC עם air gap

במאמר הזה מוסבר איך לפרוס שער AI בסביבות עם Air Gap ב-Google Distributed Cloud ‏(GDC). השער מורכב מ-Envoy Gateway, הטמעה של Kubernetes Gateway API שמבוססת על שרת proxy של Envoy, ומ-Envoy Agent Router (לשעבר Envoy AI Gateway), התוסף שהופך את Envoy Gateway לנקודת כניסה מאוחדת שתואמת ל-OpenAI לתעבורת נתונים של מודל שפה גדול (LLM). המדריך כולל הסברים על העברת קובצי האימג' של הקונטיינרים ותרשימי Helm ל-Harbor Registry המקומי, התקנת שני מישורי הבקרה באשכול רגיל, הכנת הקצה העורפי האופציונלי להגבלת קצב של יצירת בקשות שמבוססת על טוקנים ואימות ההתקנה באמצעות עומסי עבודה לדוגמה.

קצוות עורפיים לפרסום מודלים (Ollama, ‏ vLLM) נפרסים באמצעות קבוצת המדריכים הנלווים מודלים של משקלים פתוחים ב-GDC עם air gap. המדריך למשתמש בנושא ניתוב מבוסס-גוף באמצעות Envoy Agent Router מראה איך לנתב אליהם בקשות לפי שם המודל.

ארכיטקטורה

הפתרון פועל באשכול רגיל של GDC. תחנת עבודה של אדמין מפיצה את קובצי האימג' ואת תרשימי Helm למאגר Harbor של הפרויקט, ומתקינה את שני מישורי הבקרה: הבקר של Envoy Gateway, שמתכנת את מישור הנתונים של שרת ה-proxy של Envoy ממשאבי Gateway API‏ (GatewayClass, Gateway, HTTPRoute), והבקר של Envoy Agent Router, שמרחיב את מישור הנתונים הזה באמצעות מעבד חיצוני (ExtProc) לתנועת AI‏ (AIGatewayRoute, AIServiceBackend, InferencePool). לקוחות של אפליקציות שולחים בקשות תואמות ל-OpenAI לשרת ה-proxy של Envoy, שמנתב אותן לקצוות העורפיים של פרסום המודל או ל-InferencePool. מכונה אופציונלית של Redis מאחסנת את המונים של שירות הגבלת הקצב של Envoy, לצורך הגבלת קצב מבוססת-אסימון.

ארכיטקטורת עזר של AI Gateway עם Envoy Agent Router ב-GDC עם air gap.

Envoy Gateway

‫Envoy Gateway הוא פרויקט קוד פתוח שמבוסס על שרת proxy של Envoy, שמפשט את ההטמעה, השימוש והניהול של שרת proxy של Envoy כשער API של Kubernetes. הוא מטמיע ומרחיב את Kubernetes Gateway API, שמחליף את Ingress API: משאבי GatewayClass ו-Gateway מתארים את נקודות הכניסה, משאבי מסלול כמו HTTPRoute מתארים איך התנועה מותאמת ומועברת, והעיצוב מבוסס התפקידים מפריד בין תחומי האחריות של צוותי התשתית והאפליקציה. ל-Envoy Gateway יש ממשקי API משלו להרחבה, למשל EnvoyProxy (הגדרות של מישור הנתונים), Backend (נקודות קצה מחוץ לאשכול או כאלה שמוזכרות על ידי FQDN) ו-ClientTrafficPolicy (הגדרות חיבור כמו מגבלות מאגר נתונים).

Envoy Agent Router

‫Envoy Agent Router (לשעבר Envoy AI Gateway) הוא פרויקט בקוד פתוח שמשתמש ב-Envoy Gateway כדי לטפל בתנועת הבקשות מלקוחות של אפליקציות לשירותי AI גנרטיבי. הוא מספק שכבה מאוחדת לניתוב ולניהול של תעבורת נתונים ב-LLM באמצעות ניתוב שמבוסס על מודלים, אימות במעלה הזרם, הגבלת קצב של יצירת בקשות שמבוססת על אסימונים וניראות, והוא משתלב עם Gateway API Inference Extension (InferencePool, Endpoint Picker) לבחירת נקודות קצה שמבוססת על מדדים. הבקר עוקב אחרי המשאבים של aigateway.envoyproxy.io/v1beta1 ומוסיף מעבד חיצוני לצד שרת ה-proxy של Envoy. המעבד החיצוני מנתח את גוף הבקשה (לדוגמה, השדה model של בקשה להשלמת צ'אט של OpenAI), מגדיר כותרות ניתוב כמו x-ai-eg-model ומתרגם בין סכימות API במקרה הצורך.

לפני שמתחילים

לפני שמתחילים בפריסה, חשוב לוודא שהסביבה עומדת בכל הדרישות המוקדמות, ושכלי שורת הפקודה הנדרשים מוגדרים כמו שצריך. חשוב להגדיר את הכלים האלה בתחנת העבודה כדי לנהל את Container Registry, לתקשר עם אשכולות ולהפוך את תהליך הפריסה לאוטומטי.

  • סביבת GDC עם air gap בגרסה 1.16.2-hf1 ואילך זמינה עם אשכול רגיל שפועל בו Kubernetes בגרסה v1.32.13-gke.400 ואילך.
  • אשכול רגיל שנוצר עם מספיק משאבים. רכיבי השער פועלים רק ב-CPU. לקצוות העורפיים של פרסום המודל יש דרישות משלהם למאיצים (ראו את המדריכים בנושא מודלים של משקולות פתוחות ב-GDC עם air gap).
  • מכונה של Harbor זמינה ונגישה.
  • הוחלו הרשאות ה-IAM הנדרשות.
  • תחנת עבודה עם הקישוריות הנדרשת לסביבה ולאינטרנט

הגדרת הסביבה

הגדרת הסביבה כוללת את הזהויות וההרשאות, את תחנת העבודה ואת הגישה לסביבת GDC ולמערך.

ניהול זהויות והרשאות גישה

מוודאים שהחשבונות, התפקידים וההרשאות הנדרשים ב-IAM מוגדרים כמו שצריך.

תפקידי GDC User בפרויקט (RoleBinding במרחב השמות של הפרויקט, שניתנו על ידי אדמין IAM של הפרויקט):

  • צפייה במכונות של Harbor‏ (harbor-instance-viewer)
  • ‫Harbor Project Creator (harbor-project-creator, רק אם הפרויקט ב-Harbor עדיין לא קיים)
  • אדמין של אשכול רגיל (standard-cluster-admin, נדרש ל-gdcloud clusters get-credentials)

התפקיד משתמש GDC באשכול הרגיל: התפקידים הקודמים בפרויקט לא מעניקים הרשאות בתוך האשכול. בנוסף, אדמין של IAM בפרויקט צריך לקשר את המשתמש ל-StandardClusterRole cluster-admin באמצעות StandardClusterRoleBinding במרחב השמות של הפרויקט בשרת ה-API לניהול. הקישור מופץ לאשכולות הרגילים של הפרויקט תוך כמה שניות (status.clusters[].conditions מציג את Propagated=True). נדרשות הרשאות ברמת האשכול כי במדריך הזה מתקינים הגדרות של משאבים בהתאמה אישית, ClusterRole ו-GatewayClass.

cat <<EOF | kubectl --kubeconfig MANAGEMENT_API_SERVER apply -f -
apiVersion: iam.gdc.goog/v1
kind: StandardClusterRoleBinding
metadata:
  name: user-USER-cluster-admin
  namespace: PROJECT
spec:
  roleRef:
    apiGroup: iam.gdc.goog
    kind: StandardClusterRole
    name: cluster-admin
  subjects:
    - apiGroup: rbac.authorization.k8s.io
      kind: User
      name: USER
EOF

מחליפים את מה שכתוב בשדות הבאים:

  • ‫MANAGEMENT_API_SERVER: הנתיב לקובץ kubeconfig של שרת ה-API לניהול.
  • ‫USER: משתמש.
  • ‫PROJECT: פרויקט.

הרשאות לחשבון רובוט של Harbor crane (crane):

  • מאגר רשימות
  • מאגר שליפה
  • מאגר Push
  • קריאת ארטיפקט
  • רשימת פריטי מידע שנוצרו בתהליך הפיתוח (Artifact)
  • יצירת תג
  • תג רשימה

הרשאות של חשבון רובוט לשליפת תמונות של Harbor Kubernetes‏ (kubernetes-image-puller):

  • מאגר רשימות
  • מאגר שליפה
  • קריאת ארטיפקט
  • רשימת פריטי מידע שנוצרו בתהליך הפיתוח (Artifact)
  • תג רשימה

תחנת עבודה

אלא אם צוין אחרת, צריך להריץ את כל הפקודות במדריך הזה מתחנת עבודה עם הכלים הנדרשים וקישוריות לסביבה ולאינטרנט.

דרישות

צריך להתקין את הכלים הבאים בתחנת העבודה:

  • ‫crane: ניהול והעתקה של קובצי אימג' של קונטיינרים ופריטי מידע שנוצרו בתהליך הפיתוח (Artifact) של OCI בין מאגרים (מסמכי תיעוד).
  • ‫gdcloud: ממשק שורת הפקודה (CLI) לניהול משאבי GDC (מסמכי תיעוד).
  • ‫kubectl: ממשק שורת הפקודה (CLI) שמשמש לתקשורת עם אשכול Kubernetes ולניהול שלו.
  • ‫helm: מנהל חבילות ל-Kubernetes, גרסה 3.8 ואילך (תמיכה במרשם OCI) (מסמכי תיעוד).
  • ‫curl: כלי שורת פקודה להעברת נתונים באמצעות כתובות URL.
  • ‫jq: מעבד JSON קל וגמיש בשורת הפקודה.
  • ‫yq: מעבד YAML נייד של שורת הפקודה.

צריך להריץ את כל הפקודות במדריך הזה מתחנת העבודה, אלא אם צוין אחרת בשלב מסוים.

הגדרת תחנת העבודה

כדי להגדיר את סביבת העבודה, צריך את הפרטים הבאים על הסביבה:

  • ‫GDC_STANDARD_CLUSTER_NAME: השם של אשכול GDC רגיל.
  • ‫GDC_DOMAIN_SUFFIX: סיומת הדומיין של סביבת GDC (לדוגמה, gdc.example.com).
  • ‫GDC_ORG: השם של הארגון ב-GDC.
  • ‫GDC_PROJECT: השם של הפרויקט ב-GDC.
  • ‫GDC_ZONE: השם של אזור הפריסה של GDC.
  • ‫GDC_HARBOR_INSTANCE_NAME: השם של המכונה של Harbor בפרויקט.

  • ‫GDCS_HARBOR_PROJECT_NAME: השם של פרויקט Harbor שבו משתמשים בשביל קובץ האימג' (ברירת המחדל: solutions)

  • ‫GDCS_HARBOR_CRANE_ROBOT_NAME: השם של חשבון הרובוט של Harbor crane.

  • ‫GDCS_HARBOR_CRANE_ROBOT_TOKEN: אסימון האימות של חשבון הרובוט של Harbor crane.

  • ‫GDCS_HARBOR_K8S_ROBOT_NAME: השם של חשבון הרובוט של Harbor Kubernetes image pull.

  • ‫GDCS_HARBOR_K8S_ROBOT_TOKEN: אסימון האימות של חשבון הרובוט לשליפת קובצי אימג' של Kubernetes ב-Harbor.

אחרי שאוספים את הערכים של כל המשתנים הנדרשים, ממשיכים ליצירת קובץ משתני הסביבה. אחרי שהקובץ נוצר, אפשר לערוך אותו ידנית בכל שלב.

  1. יוצרים את תיקיות השורש של הפתרון ואת תיקיית הסודות:

    mkdir -p ${HOME}/gdcag-solutions/env.d
    mkdir -p ${HOME}/gdcag-solutions/secrets
    
    touch ${HOME}/gdcag-solutions/secrets/harbor_crane_robot_token
    touch ${HOME}/gdcag-solutions/secrets/harbor_k8s_robot_token
    
    chmod u=rwx,go= ${HOME}/gdcag-solutions/secrets
    chmod -R u=rw,go= ${HOME}/gdcag-solutions/secrets/*
    
  2. יוצרים את קובץ התצורה של סביבת הפלטפורמה:

    cat << 'EOF' > ${HOME}/gdcag-solutions/env.d/platform.sh && echo "Successfully created." || echo "Failed to create!"
    # Infrastructure (Platform Native)
    export GDC_STANDARD_CLUSTER_NAME="STANDARD_CLUSTER_NAME"
    export GDC_DOMAIN_SUFFIX="DOMAIN_SUFFIX"
    export GDC_ORG="ORG"
    export GDC_PROJECT="PROJECT"
    export GDC_ZONE="ZONE"
    export GDC_HARBOR_INSTANCE_NAME="HARBOR_INSTANCE_NAME"
    
    # Derived platform values
    export GDC_ZONAL_HOSTNAME="${GDC_ORG}.${GDC_ZONE}.${GDC_DOMAIN_SUFFIX}"
    export GDC_ZONAL_CONSOLE_URL="https://console.${GDC_ZONAL_HOSTNAME}"
    export GDC_HARBOR_HOST="${GDC_HARBOR_INSTANCE_NAME}-${GDC_PROJECT}.${GDC_ORG}.${GDC_ZONE}.${GDC_DOMAIN_SUFFIX}"
    EOF
    

    מחליפים את מה שכתוב בשדות הבאים:

    • ‫STANDARD_CLUSTER_NAME: שם האשכול הרגיל של GDC.
    • ‫DOMAIN_SUFFIX: סיומת הדומיין של GDC.
    • ‫ORG: ארגון ב-GDC.
    • ‫PROJECT: פרויקט ב-GDC.
    • ‫ZONE: אזור GDC.
    • ‫HARBOR_INSTANCE_NAME: השם של מכונת GDC Harbor.
  3. יוצרים את קובץ התצורה של סביבת המרשם:

    cat << 'EOF' > ${HOME}/gdcag-solutions/env.d/registry.sh && echo "Successfully created." || echo "Failed to create!"
    # GDC Solutions Registry & Secrets
    export GDCS_HARBOR_PROJECT_NAME="solutions"
    export GDCS_HARBOR_CRANE_ROBOT_NAME="HARBOR_CRANE_ROBOT_NAME"
    export GDCS_HARBOR_CRANE_ROBOT_TOKEN="$(cat ${GDCS_ROOT_HOME}/secrets/harbor_crane_robot_token)"
    export GDCS_HARBOR_K8S_ROBOT_NAME="HARBOR_K8S_ROBOT_NAME"
    export GDCS_HARBOR_K8S_ROBOT_TOKEN="$(cat ${GDCS_ROOT_HOME}/secrets/harbor_k8s_robot_token)"
    export GDCS_HARBOR_K8S_PULL_SECRET="gdcs-image-pull-secret"
    
    # Derived registry values
    export GDCS_HARBOR_PROJECT_URI="${GDC_HARBOR_HOST}/${GDCS_HARBOR_PROJECT_NAME}"
    export GDCS_HARBOR_CHART_OCI_URI="oci://${GDCS_HARBOR_PROJECT_URI}"
    EOF
    

    מחליפים את מה שכתוב בשדות הבאים:

    • HARBOR_CRANE_ROBOT_NAME: שם חשבון הרובוט של GDC Harbor.
    • HARBOR_K8S_ROBOT_NAME: שם חשבון הרובוט של GDC Harbor.
  4. מוסיפים את הטוקנים לקובצי הסודות:

    set +o history
    
    echo "CRANE_ROBOT_TOKEN" > ${HOME}/gdcag-solutions/secrets/harbor_crane_robot_token
    echo "KUBERNETES_ROBOT_TOKEN" > ${HOME}/gdcag-solutions/secrets/harbor_k8s_robot_token
    
    set -o history
    

    מחליפים את מה שכתוב בשדות הבאים:

    • ‫CRANE_ROBOT_TOKEN: אסימון של רובוט המנוף.
    • ‫KUBERNETES_ROBOT_TOKEN: אסימון רובוט של Kubernetes.
  5. יוצרים את קובץ טעינת הסביבה ברמה הבסיסית (root):

    cat << 'EOF' > ${HOME}/gdcag-solutions/env.sh && echo "Successfully created." || echo "Failed to create!"
    export GDCS_ROOT_HOME="${HOME}/gdcag-solutions"
    echo "GDCS_ROOT_HOME=${GDCS_ROOT_HOME}"
    
    # Sourced in dependency order
    source "${GDCS_ROOT_HOME}/env.d/platform.sh"
    source "${GDCS_ROOT_HOME}/env.d/registry.sh"
    EOF
    

הגדרת משתני הפתרון

  1. יוצרים את ספריית ההטמעה של הפתרון:

    mkdir -p ${HOME}/gdcag-solutions/ai-gateway/envoy/env.d
    
  2. יוצרים את קובץ התצורה של סביבת הפתרון:

    cat << 'EOF' > ${HOME}/gdcag-solutions/ai-gateway/envoy/env.d/envoy.sh && echo "Successfully created." || echo "Failed to create!"
    # Envoy Gateway
    export GDCS_ENVOY_GATEWAY_NAMESPACE="envoy-gateway-system"
    export GDCS_ENVOY_GATEWAY_VERSION="v1.8.5"
    export GDCS_ENVOY_PROXY_IMAGE_TAG="distroless-v1.38.4"
    export GDCS_ENVOY_RATELIMIT_IMAGE_TAG="8fe6ea42"
    export GDCS_GATEWAY_API_ECHO_IMAGE_TAG="v1.5.1"
    
    # Envoy Agent Router (formerly Envoy AI Gateway; the images and charts keep the ai-gateway names)
    export GDCS_ENVOY_AGENT_ROUTER_NAMESPACE="envoy-ai-gateway-system"
    export GDCS_ENVOY_AGENT_ROUTER_VERSION="v1.1.0"
    
    # Gateway API Inference Extension (InferencePool, Endpoint Picker)
    export GDCS_GATEWAY_API_INFERENCE_EXTENSION_VERSION="v1.5.0"
    
    # Redis (token-based rate limiting backend)
    export GDCS_REDIS_IMAGE_TAG="8.10.2-alpine3.23"
    export GDCS_REDIS_NAMESPACE="${GDCS_ENVOY_GATEWAY_NAMESPACE}"
    
    # Gateway class shared by the user guides
    export GDCS_GATEWAY_CLASS_NAME="envoy-ai-gateway"
    
    # Docker configuration directories for crane and Kubernetes
    export GDCS_HARBOR_CRANE_DOCKER_CONFIG="${GDCS_IMPLEMENTATION_HOME}/docker/crane"
    export GDCS_HARBOR_K8S_DOCKER_CONFIG="${GDCS_IMPLEMENTATION_HOME}/docker/k8s"
    EOF
    
  3. יוצרים את קובץ הטעינה של סביבת ההטמעה:

    cat << 'EOF' > ${HOME}/gdcag-solutions/ai-gateway/envoy/env.sh && echo "Successfully created." || echo "Failed to create!"
    source "${HOME}/gdcag-solutions/env.sh"
    
    export GDCS_IMPLEMENTATION_HOME="${HOME}/gdcag-solutions/ai-gateway/envoy"
    echo "GDCS_IMPLEMENTATION_HOME=${GDCS_IMPLEMENTATION_HOME}"
    
    # Sourced in dependency order
    source "${GDCS_IMPLEMENTATION_HOME}/env.d/envoy.sh"
    EOF
    
  4. עורכים ובודקים את קובצי הסביבה בעורך המועדף:

    ${EDITOR:-vi} ${HOME}/gdcag-solutions/env.d/platform.sh
    ${EDITOR:-vi} ${HOME}/gdcag-solutions/env.d/registry.sh
    ${EDITOR:-vi} ${HOME}/gdcag-solutions/ai-gateway/envoy/env.d/envoy.sh
    
  5. מפעילים את קובץ הסביבה:

    source ${HOME}/gdcag-solutions/ai-gateway/envoy/env.sh
    

    הפלט אמור להיראות כך:

    GDCS_ROOT_HOME=HOME_DIRECTORY_PATH/gdcag-solutions
    GDCS_IMPLEMENTATION_HOME=HOME_DIRECTORY_PATH/gdcag-solutions/ai-gateway/envoy
    

GDC

במדריך הזה אנחנו מניחים שתחנת העבודה שלכם מוגדרת כך שהיא נותנת אמון באישורי ה-TLS של סביבת GDC ושל מכונת Harbor.

  1. הגדרת gdcloud:

    gdcloud config set core/account "default-user"
    gdcloud config set core/organization_console_url "${GDC_ZONAL_CONSOLE_URL}"
    gdcloud config set core/project "${GDC_PROJECT}"
    gdcloud config set core/zone "${GDC_ZONE}"
    
  2. אימות בסביבת GDC:

    gdcloud auth login
    

אשכול

  1. אחזור פרטי הכניסה של האשכול:

    gdcloud clusters get-credentials "${GDC_STANDARD_CLUSTER_NAME}" \
    --project="${GDC_PROJECT}" \
    --standard \
    --zone="${GDC_ZONE}"
    
  2. מאמתים את הקישוריות לאשכול:

    kubectl get nodes -L node.cluster.private.gdc.goog/machine-class
    

הכנות לקראת העברת פריטי המידע שנוצר (Artifact)

  1. יוצרים קובץ תצורה של Docker ל-crane. חשבון רובוט משמש לדחיפת שכבות גדולות של קובצי אימג', כדי למנוע זמן קצוב לתפוגה של אסימון אימות כשמשתמשים בכלי העזר לאימות של Managed Harbor Service‏ (MHS)‏ (docker-credential-mhs) עם חשבון משתמש:

    set +o history
    
    export DOCKER_CONFIG="${GDCS_HARBOR_CRANE_DOCKER_CONFIG}"
    
    crane auth login "${GDC_HARBOR_HOST}" \
    --password="${GDCS_HARBOR_CRANE_ROBOT_TOKEN}" \
    --username="${GDCS_HARBOR_CRANE_ROBOT_NAME}"
    
    set -o history
    
  2. יוצרים קובץ תצורה של Docker ל-Kubernetes:

    set +o history
    
    export DOCKER_CONFIG="${GDCS_HARBOR_K8S_DOCKER_CONFIG}"
    
    crane auth login "${GDC_HARBOR_HOST}" \
    --password="${GDCS_HARBOR_K8S_ROBOT_TOKEN}" \
    --username="${GDCS_HARBOR_K8S_ROBOT_NAME}"
    
    set -o history
    
  3. נכנסים למרשם Harbor OCI עם helm באמצעות חשבון הרובוט לשליפת קובצי אימג' של Kubernetes. ל-helm יש פרטי כניסה משלו לרישום, והוא צריך אותם כדי לאחזר את התרשימים מ-Harbor:

    set +o history
    helm registry login "${GDC_HARBOR_HOST}" \
    --password="${GDCS_HARBOR_K8S_ROBOT_TOKEN}" \
    --username="${GDCS_HARBOR_K8S_ROBOT_NAME}"
    set -o history
    

    הפלט אמור להיראות כך:

    Login Succeeded
    
  4. יוצרים את הסקריפט seed_registry.sh:

    cat << 'EOF' > ${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh && echo "Successfully created." || echo "Failed to create!"
    #!/bin/bash
    
    # seed_registry.sh: Modular artifact migration for GDC Solutions
    
    # Requires the env.sh file to be sourced first.
    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    set -o nounset
    source "${SCRIPT_DIR}/env.sh"
    
    # Set the Docker config
    export DOCKER_CONFIG="${GDCS_HARBOR_CRANE_DOCKER_CONFIG}"
    eval "${SERIALIZED_IMAGES}"
    
    # Ensure GDCS_REGISTRY_IMAGES is set
    if [[ ${#GDCS_REGISTRY_IMAGES[@]} -eq 0 ]]; then
      echo "GDCS_REGISTRY_IMAGES must be set, exiting..."
      exit 1
    fi
    
    # Migrate the images
    for source_image in "${GDCS_REGISTRY_IMAGES[@]}"; do
      # Strip the registry host only when the first path segment is a host (contains a dot or a port)
      first_segment="${source_image%%/*}"
      if [[ "${first_segment}" == *.* || "${first_segment}" == *:* ]]; then
        image_path="${source_image#*/}"
      else
        image_path="${source_image}"
      fi
      destination_image="${GDCS_HARBOR_PROJECT_URI}/${image_path}"
      # Ensure the folder structure is created
      crane append \
        --new_layer=<(tar czf - -T /dev/null) \
        --new_tag="${destination_image%:*}:create" \
        --oci-empty-base 2> /dev/null || true
      # Copy the linux/amd64 platform only to avoid transferring multi-arch layers over air-gapped links
      crane copy --platform linux/amd64 "${source_image}" "${destination_image}" 2> /dev/null
    done
    echo "Migration complete: Images are available at ${GDCS_HARBOR_PROJECT_URI}"
    EOF
    chmod u+x "${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh"
    
  5. יוצרים את הסקריפט seed_charts.sh. תרשימי Helm שפורסמו כפריטי מידע שנוצרו באופן ידני ב-OCI מועתקים גם הם באמצעות crane, בלי בחירת פלטפורמה ובלי התג create שמשמש למאגרי קובצי אימג' של קונטיינרים:

    cat << 'EOF' > ${GDCS_IMPLEMENTATION_HOME}/seed_charts.sh && echo "Successfully created." || echo "Failed to create!"
    #!/bin/bash
    
    # seed_charts.sh: OCI Helm chart migration for GDC Solutions
    
    # Requires the env.sh file to be sourced first.
    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    set -o nounset
    source "${SCRIPT_DIR}/env.sh"
    
    # Set the Docker config
    export DOCKER_CONFIG="${GDCS_HARBOR_CRANE_DOCKER_CONFIG}"
    eval "${SERIALIZED_CHARTS}"
    
    # Ensure GDCS_REGISTRY_CHARTS is set
    if [[ ${#GDCS_REGISTRY_CHARTS[@]} -eq 0 ]]; then
      echo "GDCS_REGISTRY_CHARTS must be set, exiting..."
      exit 1
    fi
    
    # Migrate the charts (source format: REGISTRY_HOST/REPOSITORY:CHART_VERSION)
    for source_chart in "${GDCS_REGISTRY_CHARTS[@]}"; do
      chart_path="${source_chart#*/}"
      destination_chart="${GDCS_HARBOR_PROJECT_URI}/${chart_path}"
      crane copy "${source_chart}" "${destination_chart}" || { echo "Failed to copy ${source_chart} to ${destination_chart}"; exit 1; }
    done
    echo "Migration complete: Charts are available at ${GDCS_HARBOR_CHART_OCI_URI}"
    EOF
    chmod u+x "${GDCS_IMPLEMENTATION_HOME}/seed_charts.sh"
    

    מחליפים את מה שכתוב בשדות הבאים:

    • ‫REGISTRY_HOST: מארח המרשם.
    • REPOSITORY: מאגר.
    • ‫CHART_VERSION: גרסת התרשים.
  6. מגדירים את רשימת קובצי האימג' של הקונטיינרים שנדרשים לפתרון:

    declare -a GDCS_REGISTRY_IMAGES=(
      "docker.io/envoyproxy/gateway:${GDCS_ENVOY_GATEWAY_VERSION}"
      "docker.io/envoyproxy/envoy:${GDCS_ENVOY_PROXY_IMAGE_TAG}"
      "docker.io/envoyproxy/ratelimit:${GDCS_ENVOY_RATELIMIT_IMAGE_TAG}"
      "docker.io/envoyproxy/ai-gateway-controller:${GDCS_ENVOY_AGENT_ROUTER_VERSION}"
      "docker.io/envoyproxy/ai-gateway-extproc:${GDCS_ENVOY_AGENT_ROUTER_VERSION}"
      "docker.io/envoyproxy/ai-gateway-testupstream:${GDCS_ENVOY_AGENT_ROUTER_VERSION}"
      "docker.io/library/redis:${GDCS_REDIS_IMAGE_TAG}"
      "registry.k8s.io/gateway-api/echo-basic:${GDCS_GATEWAY_API_ECHO_IMAGE_TAG}"
    )
    export SERIALIZED_IMAGES=$(declare -p GDCS_REGISTRY_IMAGES)
    
  7. מזינים את קובצי האימג' הנדרשים של הקונטיינר ל-Artifact Registry:

    ${GDCS_IMPLEMENTATION_HOME}/seed_registry.sh
    
  1. מגדירים את רשימת תרשימי ה-Helm שנדרשים לפתרון:

    declare -a GDCS_REGISTRY_CHARTS=(
      "docker.io/envoyproxy/gateway-crds-helm:${GDCS_ENVOY_GATEWAY_VERSION}"
      "docker.io/envoyproxy/gateway-helm:${GDCS_ENVOY_GATEWAY_VERSION}"
      "docker.io/envoyproxy/ai-gateway-crds-helm:${GDCS_ENVOY_AGENT_ROUTER_VERSION}"
      "docker.io/envoyproxy/ai-gateway-helm:${GDCS_ENVOY_AGENT_ROUTER_VERSION}"
    )
    export SERIALIZED_CHARTS=$(declare -p GDCS_REGISTRY_CHARTS)
    
  2. מזינים את תרשימי Helm הנדרשים ל-Artifact Registry:

    ${GDCS_IMPLEMENTATION_HOME}/seed_charts.sh
    
  3. מוודאים שאפשר לקרוא את התרשימים בחזרה מ-Harbor:

    helm show chart "${GDCS_HARBOR_CHART_OCI_URI}/envoyproxy/gateway-helm" --version "${GDCS_ENVOY_GATEWAY_VERSION}" | grep -E '^(name|version):'
    helm show chart "${GDCS_HARBOR_CHART_OCI_URI}/envoyproxy/ai-gateway-helm" --version "${GDCS_ENVOY_AGENT_ROUTER_VERSION}" | grep -E '^(name|version):'
    

    הפלט אמור להיראות כך:

    name: gateway-helm
    version: v1.8.5
    name: ai-gateway-helm
    version: v1.1.0
    
  4. מורידים את המניפסטים של Gateway API Inference Extension. הם מתפרסמים כנכס של פריט תוכן, ולא כתרשים:

    mkdir -p "${GDCS_IMPLEMENTATION_HOME}/manifests"
    
    curl --fail --location --show-error --silent \
    --output "${GDCS_IMPLEMENTATION_HOME}/manifests/gateway-api-inference-extension-${GDCS_GATEWAY_API_INFERENCE_EXTENSION_VERSION}.yaml" \
    "https://github.com/kubernetes-sigs/gateway-api-inference-extension/releases/download/${GDCS_GATEWAY_API_INFERENCE_EXTENSION_VERSION}/manifests.yaml"
    
    grep --count '^kind: CustomResourceDefinition' "${GDCS_IMPLEMENTATION_HOME}/manifests/gateway-api-inference-extension-${GDCS_GATEWAY_API_INFERENCE_EXTENSION_VERSION}.yaml"
    

    הפלט אמור להיראות כך:

    4
    

Envoy Gateway

קודם מתקינים את Envoy Gateway ומאמתים אותו בנפרד באמצעות המדריך למתחילים של ה-upstream. לאחר מכן, משלבים את Envoy Agent Router, כפי שמוסבר בקטע הבא.

מרחב שמות

  1. יוצרים את מרחב השמות של Envoy Gateway. גם שרתי ה-proxy של Envoy Deployment בכל Gateway נוצרים במרחב השמות הזה:

    kubectl create namespace "${GDCS_ENVOY_GATEWAY_NAMESPACE}"
    
  2. מוסיפים את imagePullSecret:

    kubectl create secret docker-registry "${GDCS_HARBOR_K8S_PULL_SECRET}" \
    --dry-run=client \
    --from-file=.dockerconfigjson=${GDCS_HARBOR_K8S_DOCKER_CONFIG}/config.json \
    --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}" \
    --output=yaml | kubectl apply -f -
    

הגדרות של משאבים בהתאמה אישית

  1. מתקינים את Gateway API (ערוץ רגיל) ואת הגדרות המשאבים בהתאמה אישית (CRD) של Envoy Gateway מהתרשים שהוזן:

    helm template eg-crds "${GDCS_HARBOR_CHART_OCI_URI}/envoyproxy/gateway-crds-helm" \
    --set crds.gatewayAPI.channel=standard \
    --set crds.gatewayAPI.enabled=true \
    --set crds.envoyGateway.enabled=true \
    --version "${GDCS_ENVOY_GATEWAY_VERSION}" | kubectl apply --server-side --filename=-
    
  2. מוודאים שה-CRD רשומים:

    kubectl get crd | grep -E 'gateway.networking.k8s.io|gateway.envoyproxy.io'
    

שלט רחוק

  1. יוצרים את קובץ הערכים של Helm ל-Envoy Gateway. התמונות נשלפות מ-Harbor באמצעות סוד שליפת התמונה, crds.enabled=false כי ה-CRD הותקנו בנפרד:

    cat <<EOF > "${GDCS_IMPLEMENTATION_HOME}/envoy-gateway-values.yaml" && echo "Successfully created." || echo "Failed to create!"
    config:
      envoyGateway:
        extensionApis:
          enableBackend: true
          enableEnvoyPatchPolicy: true
        gateway:
          controllerName: gateway.envoyproxy.io/gatewayclass-controller
        logging:
          level:
            default: info
        provider:
          type: Kubernetes
    crds:
      enabled: false
    global:
      imagePullSecrets:
        - name: ${GDCS_HARBOR_K8S_PULL_SECRET}
      imageRegistry: ${GDCS_HARBOR_PROJECT_URI}
      images:
        envoyProxy:
          image: ${GDCS_HARBOR_PROJECT_URI}/envoyproxy/envoy:${GDCS_ENVOY_PROXY_IMAGE_TAG}
          pullSecrets:
            - name: ${GDCS_HARBOR_K8S_PULL_SECRET}
    EOF
    
  2. מתקינים את Envoy Gateway:

    helm upgrade --install eg "${GDCS_HARBOR_CHART_OCI_URI}/envoyproxy/gateway-helm" \
    --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}" \
    --values="${GDCS_IMPLEMENTATION_HOME}/envoy-gateway-values.yaml" \
    --version="${GDCS_ENVOY_GATEWAY_VERSION}"
    
  3. מחכים שהבקר של Envoy Gateway יהיה זמין:

    watch --color --interval 5 --no-title \
    "kubectl get deployment/envoy-gateway \
    --namespace=${GDCS_ENVOY_GATEWAY_NAMESPACE} | GREP_COLORS='mt=01;92' egrep --color=always -e '^' -e '1/1     1            1'"
    
  4. מוודאים שהתמונות של הבקר ומשימת יצירת האישורים שלו מגיעות מ-Harbor:

    kubectl get pods --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}" \
    --output=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].image}{"\n"}{end}'
    

‫Envoy proxy ו-Gateway class

  1. יוצרים את המניפסט לתבנית מישור הנתונים של EnvoyProxy ול-GatewayClass. ה-EnvoyProxy מגדיר את קובץ האימג' של שרת ה-proxy, את הסוד למשיכת קובץ האימג' ואת בקשות המשאבים של שרתי ה-proxy מסוג Pod. ה-GatewayClass משותף למדריכים למשתמשים:

    cat <<EOF > "${GDCS_IMPLEMENTATION_HOME}/gateway-class.yaml" && echo "Successfully created." || echo "Failed to create!"
    apiVersion: gateway.envoyproxy.io/v1alpha1
    kind: EnvoyProxy
    metadata:
      name: ${GDCS_GATEWAY_CLASS_NAME}
      namespace: ${GDCS_ENVOY_GATEWAY_NAMESPACE}
    spec:
      provider:
        type: Kubernetes
        kubernetes:
          envoyDeployment:
            container:
              image: ${GDCS_HARBOR_PROJECT_URI}/envoyproxy/envoy:${GDCS_ENVOY_PROXY_IMAGE_TAG}
              resources:
                limits:
                  memory: 2Gi
                requests:
                  cpu: 250m
                  memory: 512Mi
            pod:
              imagePullSecrets:
                - name: ${GDCS_HARBOR_K8S_PULL_SECRET}
    ---
    apiVersion: gateway.networking.k8s.io/v1
    kind: GatewayClass
    metadata:
      name: ${GDCS_GATEWAY_CLASS_NAME}
    spec:
      controllerName: gateway.envoyproxy.io/gatewayclass-controller
      parametersRef:
        group: gateway.envoyproxy.io
        kind: EnvoyProxy
        name: ${GDCS_GATEWAY_CLASS_NAME}
        namespace: ${GDCS_ENVOY_GATEWAY_NAMESPACE}
    EOF
    
  2. מחילים את המניפסט על EnvoyProxy ועל GatewayClass:

    kubectl apply \
    --filename="${GDCS_IMPLEMENTATION_HOME}/gateway-class.yaml"
    
  3. מוודאים שהסטטוס של GatewayClass הוא Accepted (התקבל):

    kubectl get gatewayclass "${GDCS_GATEWAY_CLASS_NAME}"
    

    הפלט אמור להיראות כך:

    NAME               CONTROLLER                                      ACCEPTED   AGE
    envoy-ai-gateway   gateway.envoyproxy.io/gatewayclass-controller   True       5s
    

אימות

האימות פורס את ההתחלה המהירה של Envoy Gateway (קצה עורפי של הד מאחורי HTTPRoute) במרחב השמות של Envoy Gateway, ומסיר אותו לאחר מכן.

  1. יוצרים את המניפסט לעומס העבודה של המדריך למתחילים:

    cat <<EOF > "${GDCS_IMPLEMENTATION_HOME}/eg-quickstart.yaml" && echo "Successfully created." || echo "Failed to create!"
    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: eg-quickstart
    spec:
      gatewayClassName: ${GDCS_GATEWAY_CLASS_NAME}
      listeners:
        - name: http
          protocol: HTTP
          port: 80
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: eg-quickstart-backend
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: eg-quickstart-backend
      labels:
        app: eg-quickstart-backend
    spec:
      ports:
        - name: http
          port: 3000
          targetPort: 3000
      selector:
        app: eg-quickstart-backend
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: eg-quickstart-backend
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: eg-quickstart-backend
      template:
        metadata:
          labels:
            app: eg-quickstart-backend
        spec:
          serviceAccountName: eg-quickstart-backend
          containers:
            - image: ${GDCS_HARBOR_PROJECT_URI}/gateway-api/echo-basic:${GDCS_GATEWAY_API_ECHO_IMAGE_TAG}
              imagePullPolicy: IfNotPresent
              name: backend
              ports:
                - containerPort: 3000
              env:
                - name: POD_NAME
                  valueFrom:
                    fieldRef:
                      fieldPath: metadata.name
                - name: NAMESPACE
                  valueFrom:
                    fieldRef:
                      fieldPath: metadata.namespace
          imagePullSecrets:
            - name: ${GDCS_HARBOR_K8S_PULL_SECRET}
    ---
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: eg-quickstart-backend
    spec:
      parentRefs:
        - name: eg-quickstart
      hostnames:
        - "www.example.com"
      rules:
        - backendRefs:
            - group: ""
              kind: Service
              name: eg-quickstart-backend
              port: 3000
              weight: 1
          matches:
            - path:
                type: PathPrefix
                value: /
    EOF
    
  2. מחילים את המניפסט על עומס העבודה של המדריך למתחילים:

    kubectl apply \
    --filename="${GDCS_IMPLEMENTATION_HOME}/eg-quickstart.yaml" \
    --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}"
    
  3. ממתינים לתכנות של Gateway:

    watch --color --interval 5 --no-title \
    "kubectl get gateway/eg-quickstart \
    --namespace=${GDCS_ENVOY_GATEWAY_NAMESPACE} | GREP_COLORS='mt=01;92' egrep --color=always -e '^' -e 'True'"
    
  4. מחכים שהקצה העורפי Deployment יהיה זמין:

    watch --color --interval 5 --no-title \
    "kubectl get deployment/eg-quickstart-backend \
    --namespace=${GDCS_ENVOY_GATEWAY_NAMESPACE} | GREP_COLORS='mt=01;92' egrep --color=always -e '^' -e '1/1     1            1'"
    
  5. שולחים בקשת בדיקה דרך השער באמצעות העברה ליציאה אחרת. ‫Service של Gateway נמצא לפי התוויות של השער שבבעלותו:

    export ENVOY_SERVICE=$(kubectl get service --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}" --selector="gateway.envoyproxy.io/owning-gateway-namespace=${GDCS_ENVOY_GATEWAY_NAMESPACE},gateway.envoyproxy.io/owning-gateway-name=eg-quickstart" --output=jsonpath='{.items[0].metadata.name}')
    echo "ENVOY_SERVICE=${ENVOY_SERVICE}"
    
    kubectl port-forward "service/${ENVOY_SERVICE}" \
    --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}" 8888:80 &
    PF_PID=$!
    
    sleep 2
    
    curl --header "Host: www.example.com" \
    --no-progress-meter \
    --show-error \
    http://127.0.0.1:8888/get | jq
    
    kill -9 ${PF_PID}
    

    הפלט אמור להיראות כך:

    {
      "path": "/get",
      "host": "www.example.com",
      "method": "GET",
      ...
      "namespace": "envoy-gateway-system",
      "pod": "eg-quickstart-backend-...",
      ...
    }
    
  6. מסירים את עומס העבודה של המדריך למתחילים:

    kubectl delete \
    --filename="${GDCS_IMPLEMENTATION_HOME}/eg-quickstart.yaml" \
    --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}"
    

Envoy Agent Router

‫Envoy Agent Router מותקן במרחב שמות משלו, ואז Envoy Gateway מוגדר מחדש כדי לקרוא לו כשרת התוספים שלו.

מרחב שמות

  1. יוצרים את מרחב השמות של הבקר של Envoy Agent Router:

    kubectl create namespace "${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE}"
    
  2. מוסיפים את imagePullSecret:

    kubectl create secret docker-registry "${GDCS_HARBOR_K8S_PULL_SECRET}" \
    --dry-run=client \
    --from-file=.dockerconfigjson=${GDCS_HARBOR_K8S_DOCKER_CONFIG}/config.json \
    --namespace="${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE}" \
    --output=yaml | kubectl apply -f -
    

הגדרות של משאבים בהתאמה אישית

  1. מתקינים את ה-CRD של Envoy Agent Router מהתרשים שהוזן:

    helm template aieg-crds "${GDCS_HARBOR_CHART_OCI_URI}/envoyproxy/ai-gateway-crds-helm" \
    --version "${GDCS_ENVOY_AGENT_ROUTER_VERSION}" | kubectl apply --server-side --filename=-
    
  2. מתקינים את ה-CRD של Gateway API Inference Extension (InferencePool, InferenceObjective) מהמניפסטים שהורדתם:

    kubectl apply --server-side \
    --filename="${GDCS_IMPLEMENTATION_HOME}/manifests/gateway-api-inference-extension-${GDCS_GATEWAY_API_INFERENCE_EXTENSION_VERSION}.yaml"
    
  3. מוודאים שה-CRD רשומים:

    kubectl get crd | grep -E 'aigateway.envoyproxy.io|inference.networking'
    

    הפלט אמור להיראות כך:

    aigatewayroutes.aigateway.envoyproxy.io                 ...
    aiservicebackends.aigateway.envoyproxy.io               ...
    backendsecuritypolicies.aigateway.envoyproxy.io         ...
    gatewayconfigs.aigateway.envoyproxy.io                  ...
    inferencemodelrewrites.inference.networking.x-k8s.io    ...
    inferenceobjectives.inference.networking.x-k8s.io       ...
    inferencepoolimports.inference.networking.x-k8s.io      ...
    inferencepools.inference.networking.k8s.io              ...
    mcproutes.aigateway.envoyproxy.io                       ...
    quotapolicies.aigateway.envoyproxy.io                   ...
    

Redis

הגבלת קצב של יצירת בקשות שמבוססת על אסימונים נאכפת על ידי שירות הגבלת הקצב של Envoy, שמאחסן את המונים שלו ב-Redis. במדריך הזה נפרס רפליקה אחת של Redis בלי עקביות. אפשר להשתמש בשירות Redis קיים במקום זאת, על ידי שינוי הערך של rateLimit.backend.redis.url בקטע הבא.

  1. יוצרים את המניפסט ל-Redis Deployment:

    cat <<EOF > "${GDCS_IMPLEMENTATION_HOME}/redis.yaml" && echo "Successfully created." || echo "Failed to create!"
    apiVersion: v1
    kind: Service
    metadata:
      name: redis
      labels:
        app: redis
    spec:
      ports:
        - name: redis
          port: 6379
      selector:
        app: redis
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: redis
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: redis
      template:
        metadata:
          labels:
            app: redis
        spec:
          containers:
            - image: ${GDCS_HARBOR_PROJECT_URI}/library/redis:${GDCS_REDIS_IMAGE_TAG}
              imagePullPolicy: IfNotPresent
              name: redis
              ports:
                - name: redis
                  containerPort: 6379
              resources:
                limits:
                  memory: 512Mi
                requests:
                  cpu: 100m
                  memory: 128Mi
          imagePullSecrets:
            - name: ${GDCS_HARBOR_K8S_PULL_SECRET}
          restartPolicy: Always
    EOF
    
  2. מחילים את המניפסט על Redis Deployment:

    kubectl apply \
    --filename="${GDCS_IMPLEMENTATION_HOME}/redis.yaml" \
    --namespace="${GDCS_REDIS_NAMESPACE}"
    
  3. מחכים עד שה-Redis Deployment יהיה זמין:

    watch --color --interval 5 --no-title \
    "kubectl get deployment/redis \
    --namespace=${GDCS_REDIS_NAMESPACE} | GREP_COLORS='mt=01;92' egrep --color=always -e '^' -e '1/1     1            1'"
    

שלט רחוק

  1. יוצרים את קובץ הערכים של Helm ל-Envoy Agent Router:

    cat <<EOF > "${GDCS_IMPLEMENTATION_HOME}/envoy-agent-router-values.yaml" && echo "Successfully created." || echo "Failed to create!"
    controller:
      image:
        repository: ${GDCS_HARBOR_PROJECT_URI}/envoyproxy/ai-gateway-controller
      imagePullSecrets:
        - name: ${GDCS_HARBOR_K8S_PULL_SECRET}
    envoyGateway:
      namespace: ${GDCS_ENVOY_GATEWAY_NAMESPACE}
    extProc:
      image:
        repository: ${GDCS_HARBOR_PROJECT_URI}/envoyproxy/ai-gateway-extproc
      imagePullSecrets:
        - name: ${GDCS_HARBOR_K8S_PULL_SECRET}
    global:
      imagePullSecrets:
        - name: ${GDCS_HARBOR_K8S_PULL_SECRET}
    EOF
    
  2. התקנת Envoy Agent Router:

    helm upgrade --install aieg "${GDCS_HARBOR_CHART_OCI_URI}/envoyproxy/ai-gateway-helm" \
    --namespace="${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE}" \
    --values="${GDCS_IMPLEMENTATION_HOME}/envoy-agent-router-values.yaml" \
    --version="${GDCS_ENVOY_AGENT_ROUTER_VERSION}"
    
  3. מחכים עד שהבקר של Envoy Agent Router יהיה זמין:

    watch --color --interval 5 --no-title \
    "kubectl get deployment/ai-gateway-controller \
    --namespace=${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE} | GREP_COLORS='mt=01;92' egrep --color=always -e '^' -e '1/1     1            1'"
    

שילוב של Envoy Gateway

צריך להגדיר מחדש את Envoy Gateway כדי לקרוא לבקר Envoy Agent Router כשרת התוספים שלו, כדי לקבל משאבי InferencePool כקצוות עורפיים ולהשתמש בשירות הגבלת קצב של יצירת בקשות שמגובה ב-Redis.

  1. יוצרים את קובץ הערכים של Helm לשילוב:

    cat <<EOF > "${GDCS_IMPLEMENTATION_HOME}/envoy-gateway-agent-router-values.yaml" && echo "Successfully created." || echo "Failed to create!"
    config:
      envoyGateway:
        extensionManager:
          backendResources:
            - group: inference.networking.k8s.io
              kind: InferencePool
              version: v1
          hooks:
            xdsTranslator:
              post:
                - Translation
                - Cluster
                - Route
              translation:
                cluster:
                  includeAll: true
                listener:
                  includeAll: true
                route:
                  includeAll: true
                secret:
                  includeAll: true
          service:
            fqdn:
              hostname: ai-gateway-controller.${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE}.svc.cluster.local
              port: 1063
        rateLimit:
          backend:
            redis:
              url: redis.${GDCS_REDIS_NAMESPACE}.svc.cluster.local:6379
            type: Redis
    EOF
    
  2. משדרגים את Envoy Gateway באמצעות שני קובצי הערכים:

    helm upgrade --install eg "${GDCS_HARBOR_CHART_OCI_URI}/envoyproxy/gateway-helm" \
    --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}" \
    --values="${GDCS_IMPLEMENTATION_HOME}/envoy-gateway-values.yaml" \
    --values="${GDCS_IMPLEMENTATION_HOME}/envoy-gateway-agent-router-values.yaml" \
    --version="${GDCS_ENVOY_GATEWAY_VERSION}"
    
  3. יוצרים את המניפסט של ClusterRole שמאפשר לבקר של Envoy Gateway לעקוב אחרי משאבי InferencePool. התרשים לא מעניק את ההרשאה הזו. ClusterRoleBinding נפרד שורד שדרוגים של התרשים, בניגוד לתיקון של ClusterRole שמנוהל על ידי התרשים:

    cat <<EOF > "${GDCS_IMPLEMENTATION_HOME}/envoy-gateway-inferencepool-rbac.yaml" && echo "Successfully created." || echo "Failed to create!"
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: envoy-gateway-inferencepool-reader
    rules:
      - apiGroups:
          - inference.networking.k8s.io
        resources:
          - inferencepools
        verbs:
          - get
          - list
          - watch
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: envoy-gateway-inferencepool-reader
    roleRef:
      apiGroup: rbac.authorization.k8s.io
      kind: ClusterRole
      name: envoy-gateway-inferencepool-reader
    subjects:
      - kind: ServiceAccount
        name: envoy-gateway
        namespace: ${GDCS_ENVOY_GATEWAY_NAMESPACE}
    EOF
    
  4. מחילים את המניפסט על ClusterRole:

    kubectl apply \
    --filename="${GDCS_IMPLEMENTATION_HOME}/envoy-gateway-inferencepool-rbac.yaml"
    
  5. מפעילים מחדש את בקר Envoy Gateway כדי שיקבל את ההגדרות וההרשאות החדשות, וממתינים עד שהוא יהיה זמין:

    kubectl rollout restart deployment/envoy-gateway \
    --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}"
    
    kubectl rollout status deployment/envoy-gateway \
    --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}" \
    --timeout=5m
    
  6. מוודאים ששירות הגבלת קצב הבקשות נפרס ושהוא זמין:

    watch --color --interval 5 --no-title \
    "kubectl get deployment/envoy-ratelimit \
    --namespace=${GDCS_ENVOY_GATEWAY_NAMESPACE} | GREP_COLORS='mt=01;92' egrep --color=always -e '^' -e '1/1     1            1'"
    

אימות

האימות פורס את הדוגמה הבסיסית של Envoy Agent Router (דמה של OpenAI-compatible upstream מאחורי AIGatewayRoute) במרחב השמות של Envoy Agent Router, ואז מסיר אותו.

  1. יוצרים את המניפסט לעומס העבודה של האימות:

    cat <<EOF > "${GDCS_IMPLEMENTATION_HOME}/aieg-basic.yaml" && echo "Successfully created." || echo "Failed to create!"
    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: aieg-basic
    spec:
      gatewayClassName: ${GDCS_GATEWAY_CLASS_NAME}
      listeners:
        - name: http
          protocol: HTTP
          port: 80
    ---
    apiVersion: gateway.envoyproxy.io/v1alpha1
    kind: ClientTrafficPolicy
    metadata:
      name: aieg-basic-buffer-limit
    spec:
      targetRefs:
        - group: gateway.networking.k8s.io
          kind: Gateway
          name: aieg-basic
      connection:
        bufferLimit: 50Mi
    ---
    apiVersion: aigateway.envoyproxy.io/v1beta1
    kind: AIGatewayRoute
    metadata:
      name: aieg-basic
    spec:
      parentRefs:
        - name: aieg-basic
          kind: Gateway
          group: gateway.networking.k8s.io
      rules:
        - matches:
            - headers:
                - type: Exact
                  name: x-ai-eg-model
                  value: some-cool-self-hosted-model
          backendRefs:
            - name: aieg-basic-testupstream
    ---
    apiVersion: aigateway.envoyproxy.io/v1beta1
    kind: AIServiceBackend
    metadata:
      name: aieg-basic-testupstream
    spec:
      schema:
        name: OpenAI
      backendRef:
        name: aieg-basic-testupstream
        kind: Backend
        group: gateway.envoyproxy.io
    ---
    apiVersion: gateway.envoyproxy.io/v1alpha1
    kind: Backend
    metadata:
      name: aieg-basic-testupstream
    spec:
      endpoints:
        - fqdn:
            hostname: aieg-basic-testupstream.${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE}.svc.cluster.local
            port: 80
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: aieg-basic-testupstream
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: aieg-basic-testupstream
      template:
        metadata:
          labels:
            app: aieg-basic-testupstream
        spec:
          containers:
            - name: testupstream
              image: ${GDCS_HARBOR_PROJECT_URI}/envoyproxy/ai-gateway-testupstream:${GDCS_ENVOY_AGENT_ROUTER_VERSION}
              imagePullPolicy: IfNotPresent
              ports:
                - containerPort: 8080
              env:
                - name: TESTUPSTREAM_ID
                  value: test
              readinessProbe:
                httpGet:
                  path: /health
                  port: 8080
                initialDelaySeconds: 5
                periodSeconds: 10
              livenessProbe:
                httpGet:
                  path: /health
                  port: 8080
                initialDelaySeconds: 10
                periodSeconds: 20
          imagePullSecrets:
            - name: ${GDCS_HARBOR_K8S_PULL_SECRET}
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: aieg-basic-testupstream
    spec:
      selector:
        app: aieg-basic-testupstream
      ports:
        - protocol: TCP
          port: 80
          targetPort: 8080
      type: ClusterIP
    EOF
    
  2. מחילים את המניפסט על עומס העבודה לאימות:

    kubectl apply \
    --filename="${GDCS_IMPLEMENTATION_HOME}/aieg-basic.yaml" \
    --namespace="${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE}"
    
  3. ממתינים לתכנות של Gateway:

    watch --color --interval 5 --no-title \
    "kubectl get gateway/aieg-basic \
    --namespace=${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE} | GREP_COLORS='mt=01;92' egrep --color=always -e '^' -e 'True'"
    
  4. מחכים שה-mock upstream Deployment יהיה זמין:

    watch --color --interval 5 --no-title \
    "kubectl get deployment/aieg-basic-testupstream \
    --namespace=${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE} | GREP_COLORS='mt=01;92' egrep --color=always -e '^' -e '1/1     1            1'"
    
  5. מוודאים ששרת ה-proxy של Envoy Pod של Gateway מריץ את המעבד החיצוני של ה-sidecar שהוחדר על ידי Envoy Agent Router:

    kubectl get pods --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}" \
    --selector="gateway.envoyproxy.io/owning-gateway-name=aieg-basic" \
    --output=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[*].name}{"\n"}{end}'
    

    הפלט אמור להיראות כך:

    envoy-envoy-ai-gateway-system-aieg-basic-...   envoy shutdown-manager ai-gateway-extproc
    
  6. שולחים בקשה להשלמת צ'אט דרך השער באמצעות העברה ליציאה אחרת. המעבד החיצוני קורא את השדה model בגוף הבקשה ומנתב אותו ל-mock upstream:

    export ENVOY_SERVICE=$(kubectl get service --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}" --selector="gateway.envoyproxy.io/owning-gateway-namespace=${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE},gateway.envoyproxy.io/owning-gateway-name=aieg-basic" --output=jsonpath='{.items[0].metadata.name}')
    echo "ENVOY_SERVICE=${ENVOY_SERVICE}"
    
    kubectl port-forward "service/${ENVOY_SERVICE}" \
    --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}" 8888:80 &
    PF_PID=$!
    
    sleep 2
    
    curl http://127.0.0.1:8888/v1/chat/completions \
    --data '{"model": "some-cool-self-hosted-model", "messages": [{"role": "user", "content": "Say this is a test."}]}' \
    --header "Content-Type: application/json" \
    --no-progress-meter \
    --show-error | jq
    
    kill -9 ${PF_PID}
    

    הפלט אמור להיראות כך:

    {
      "choices": [
        {
          "index": 0,
          "message": {
            "role": "assistant",
            "content": "..."
          },
          "finish_reason": "stop"
        }
      ],
      "usage": {
        ...
      }
    }
    
  7. מסירים את עומס העבודה של האימות:

    kubectl delete \
    --filename="${GDCS_IMPLEMENTATION_HOME}/aieg-basic.yaml" \
    --namespace="${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE}"
    

תפעול

משימות ליום השני במישורי הבקרה של השער.

שדרוג

  • מפיצים את התמונות והתרשימים של הגרסאות החדשות (מעדכנים את משתני הגרסה ב-env.sh, מריצים מחדש את seed_registry.sh ואת seed_charts.sh), מחילים את תרשימי ה-CRD החדשים באמצעות kubectl apply --server-side, ואז מריצים את אותן פקודות helm upgrade --install עם ה---version החדש. בודקים בטבלת התאימות של Envoy Agent Router את הגרסאות של Envoy Gateway ו-Gateway API שנתמכות בגרסת היעד. Envoy Agent Router 1.1.0 מבוסס על Envoy Gateway 1.8.

הסרה

  • קודם מוחקים את המשאבים Gateway,‏ AIGatewayRoute ו-InferencePool של המדריכים למשתמש, ואז את helm uninstall aieg --namespace="${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE}",‏ helm uninstall eg --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}", ‏ ClusterRole envoy-gateway-inferencepool-reader, ‏ GatewayClass, ‏ Redis Deployment ולבסוף את ה-CRD.

פתרון בעיות

תיאור הבעיה סיבה אפשרית פעולה
‫helm show chart או helm upgrade נכשלים עם unauthorized או FetchReference ל-helm אין פרטי כניסה ל-Harbor (הוא לא משתמש בהגדרות Docker של crane) מריצים שוב את השלב helm registry login עם חשבון הרובוט לשליפת תמונות של Kubernetes.
‫Envoy Gateway או Envoy Agent Router Pods נשארים ב-ImagePullBackOff התמונה לא נשלחה, או שחסר סוד שליפת התמונה במרחב השמות בודקים את kubectl describe pod, משווים את נתיב התמונה ל-crane ls "${GDCS_HARBOR_PROJECT_URI}/envoyproxy/gateway" ומוודאים שהסוד קיים ב-${GDCS_ENVOY_GATEWAY_NAMESPACE} וב-${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE}.
Gateway נשאר Programmed=False אחרי השילוב הבקר של Envoy Gateway לא יכול להגיע לשרת התוסף, או שלא הופעל מחדש אחרי השדרוג kubectl logs deployment/envoy-gateway --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}"; מוודאים ש-service/ai-gateway-controller קיים ב-${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE} ושיציאה 1063 מופיעה ברשימה; חוזרים על ההפעלה מחדש של ההשקה.
AIGatewayRoute שמפנה אל InferencePool לא מתקבל ל-Envoy Gateway אין הרשאה לקרוא את inferencepools, או שחסרים ה-CRD של InferencePool צריך לאמת את ClusterRoleBinding envoy-gateway-inferencepool-reader ואת inferencepools.inference.networking.k8s.io CRD; לבדוק את יומני הבקר של forbidden.
בקשה להשלמת צ'אט מחזירה את השגיאה 413 או שהחיבור מתאפס בהנחיות גדולות מגבלת ברירת המחדל של מאגר הנתונים הזמני של Envoy‏ (32KiB) קטנה מדי למטענים ייעודיים (payloads) של AI מצרפים ClientTrafficPolicy עם connection.bufferLimit (האימות משתמש ב-50Mi) אל Gateway.
שירות הגבלת הקצב של יצירת הבקשות Pod הוא CrashLoopBackOff אין גישה ל-Redis בכתובת ה-URL שהוגדרה kubectl get service redis --namespace="${GDCS_REDIS_NAMESPACE}"; צריך לתקן את rateLimit.backend.redis.url בקובץ ערכי השילוב ולשדרג שוב.

חומרים נוספים