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

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

העורפים (back-end) של פרסום המודלים (Ollama, ‏ vLLM) נפרסים עם קבוצת המדריכים הנלווים Open Weight Models on GDC עם air gap. במדריך Body-based routing with Envoy Agent Router user guide מוסבר איך לנתב בקשות אליהם לפי שם המודל.

ארכיטקטורה

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

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

Envoy Gateway

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

נתב Envoy Agent

‫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 לפי הצורך.

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

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

  • סביבת GDC עם air gap בגרסה 1.16.2-hf1 ואילך זמינה עם אשכול רגיל שפועל ב-Kubernetes בגרסה v1.32.13-gke.400 ואילך.
  • נוצר אשכול רגיל עם מספיק משאבים. רכיבי השער פועלים רק במעבד (CPU). לשרתי הקצה של המודלים יש דרישות משלהם לגבי מאיץ (ראו את המדריכים בנושא מודלים של משקלים פתוחים ב-GDC עם פערים בבידוד).
  • מופע 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 User באשכול רגיל: התפקידים הקודמים בפרויקט לא מעניקים הרשאות בתוך האשכול. בנוסף, אדמין של IAM בפרויקט צריך לקשר את המשתמש אל StandardClusterRole cluster-admin עם StandardClusterRoleBinding במרחב השמות של הפרויקט בשרת של Management API. הקישור מועבר לאשכולות הרגילים של הפרויקט תוך שניות (status.clusters[].conditions מציג Propagated=True). נדרשות הרשאות ברמת האשכול כי במדריך הזה מותקנים Custom Resource Definitions‏ (CRD),‏ 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 של שרת Management API.
  • ‫USER: משתמש.
  • ‫PROJECT: project.

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

  • רשימת מאגרים
  • שליפת נתונים ממאגר
  • מאגר Push
  • קריאת ארטיפקט
  • ארטיפקט של רשימה
  • יצירת תג
  • תג רשימה

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

  • רשימת מאגרים
  • שליפת נתונים ממאגר
  • קריאת ארטיפקט
  • ארטיפקט של רשימה
  • תג רשימה

תחנת עבודה

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

דרישות

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

  • ‫crane: ניהול והעתקה של קובצי אימג' בקונטיינרים ושל ארטיפקטים של 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: אסימון האימות של חשבון הרובוט של Harbor למשיכת תמונות Kubernetes.

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

  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. יוצרים את קובץ הטעינה של סביבת הבסיס:

    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
    
  3. מוודאים שכל צומת מריץ את גרסת Kubernetes שנדרשת בקטע לפני שמתחילים במדריך הזה:

    kubectl get nodes -o custom-columns='NAME:.metadata.name,VERSION:.status.nodeInfo.kubeletVersion'
    

הכנות להעברת ארטיפקטים

  1. יוצרים קובץ תצורה של Docker עבור crane. חשבון רובוט משמש להעברת שכבות גדולות של תמונות כדי למנוע פסק זמן של אסימוני אימות כשמשתמשים בכלי העזר לפרטי כניסה (docker-credential-mhs) של Managed Harbor Service (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 image pull. ‫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 הנדרשים למאגר הארטיפקטים:

    ${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 (ערוץ רגיל) ואת Envoy Gateway Custom Resource Definitions (CRDs) מהתרשים המאכלס:

    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. מוודאים שהתמונות של בקר ה-Controller והעבודה ליצירת האישור שלו מגיעות מ-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‏ Pods. ‏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 הוא 'התקבל':

    kubectl get gatewayclass "${GDCS_GATEWAY_CLASS_NAME}"
    

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

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

אימות

האימות פורס את ההפעלה המהירה של Envoy Gateway (קצה עורפי של הד Echo מאחורי 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. שולחים בקשת בדיקה דרך השער באמצעות העברה ליציאה אחרת. ‫Envoy 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

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

מרחב שמות

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

    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 מהתרשים המוגדר מראש:

    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 ‏ (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 יהיה Available (זמין):

    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:

    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 יהיה זמין:

    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 כדי שיזהה את ההגדרות וההרשאות החדשות, ומחכים עד שהסטטוס שלו יהיה Available:

    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 (דוגמה מדומה של מקור תואם ל-OpenAI מאחורי AIGatewayRoute) במרחב השמות של נתב Envoy Agent, ומסיר אותה לאחר מכן.

  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. מוודאים ש-Envoy proxy‏ Pod של Gateway מפעיל את מעבד הצד החיצוני שהוזרק על ידי 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 של גוף הבקשה ומנתב אותו לשרת ה-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 controller אין גישה לשרת התוסף, או שהוא לא הופעל מחדש אחרי השדרוג ‫kubectl logs deployment/envoy-gateway --namespace="${GDCS_ENVOY_GATEWAY_NAMESPACE}"; מוודאים ש-service/ai-gateway-controller קיים ב-${GDCS_ENVOY_AGENT_ROUTER_NAMESPACE} ושהיציאה 1063 מופיעה; חוזרים על ההפעלה מחדש של הפריסה.
הפניה אל InferencePool באמצעות AIGatewayRoute לא מתקבלת ל-Envoy Gateway אין הרשאה לקרוא את inferencepools, או שחסרים CRD של InferencePool מוודאים את ClusterRoleBinding envoy-gateway-inferencepool-reader ואת inferencepools.inference.networking.k8s.io CRD, ובודקים את יומני הבקרה של forbidden.
בקשת השלמה של צ'אט מחזירה 413 או שהחיבור מאופס להנחיות גדולות מגבלת ברירת המחדל של מאגר הנתונים הזמני של Envoy ‏ (32KiB) קטנה מדי בשביל נתוני AI מצרפים ClientTrafficPolicy עם connection.bufferLimit (האימות משתמש ב-50Mi) אל Gateway.
הגבלת קצב של יצירת בקשות בשירות Pod היא CrashLoopBackOff אין גישה ל-Redis בכתובת ה-URL שהוגדרה ‫kubectl get service redis --namespace="${GDCS_REDIS_NAMESPACE}"; מתקנים את rateLimit.backend.redis.url בקובץ הערכים של השילוב ומשדרגים שוב.

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