פריסת שער מעבר של TLS

במאמר הזה מוסבר איך לפרוס משאב של שער Kubernetes שמוגדר להעברת TLS, כדי לאפשר ניתוב מבוסס-SNI (ציון שם השרת) ב-Google Kubernetes Engine ‏(GKE). הגישה הזו שומרת על הצפנה מקצה לקצה ועל Mutual TLS ‏(mTLS) בין לקוחות לבין Pods של קצה עורפי, תומכת בפרוטוקולים מבוססי-TLS שאינם HTTP וחוסכת כתובות IP.

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

סקירה כללית

בנוסף למאזני עומסים של אפליקציות (ALB) מסוג HTTP ו-HTTPS בשכבה 7, בקר השער של GKE תומך במאזני עומסי רשת של שרתי proxy בשכבה 4 (מאזני עומסים של שרתי proxy מסוג TCP) שמוגדרים עם העברת TLS ללא שינוי.

במעבר TLS, מאזן העומסים לא מסיים או מפענח את סשן ה-TLS. במקום זאת, כשלקוח מתחיל לחיצת יד בפרוטוקול TLS, מאזן העומסים בודק את הודעת ה-ClientHello הלא מוצפנת כדי לקרוא את שם המארח של ציון שם השרת (SNI). על סמך שם המארח של SNI, מאזן העומסים מנתב את זרם ה-TCP הגולמי המוצפן לשירות Kubernetes המתאים בקצה העורפי, שבו קבוצות ה-Pod של האפליקציה מסיימות את ה-TLS ישירות.

כשמשתמשים ב-TLS passthrough עם Gateway API, אפשר:

  • שמירה על הצפנה מקצה לקצה ותמיכה בתאימות לארכיטקטורת אבטחה ללא אמון (Zero Trust): התנועה נשארת מוצפנת באופן מלא מהלקוח ל-Pod בקצה העורפי, בלי לתת למאזן העומסים גישה למפתחות פרטיים או לנתונים בטקסט פשוט.
  • שימוש באימות TLS בו-זמני (mTLS): אימות לקוחות ישירות בשכבת האפליקציה בקצה העורפי.
  • חיסכון בכתובות IPv4: ריבוב של כמה שירותי TLS נפרדים בדומיינים או בתת-דומיינים שונים, באמצעות כתובת IP ויציאה אחת של שער (כמו 443).
  • תמיכה בפרוטוקולים מבוססי-TLS שאינם HTTP: ניתוב תעבורת נתונים מוצפנת לעומסי עבודה עם שמירת מצב, למסדי נתונים, לשירותי gRPC בהתאמה אישית או לפרוטוקולים קנייניים שפועלים ב-TLS.

‫GatewayClasses נתמכים

ה-GatewayClasses הבאים של GKE תומכים במאזינים של TLS passthrough ובמשאבי TLSRoute:

  • gke-l7-regional-external-managed
  • gke-l7-regional-external-managed-mc
  • gke-l7-rilb
  • gke-l7-rilb-mc
  • gke-l7-cross-regional-internal-managed-mc

להשוואה מלאה של היכולות של GatewayClass, ראו היכולות של GatewayClass.

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

לפני שמתחילים, חשוב לוודא שביצעתם את המשימות הבאות:

  • מפעילים את Google Kubernetes Engine API.
  • הפעלת Google Kubernetes Engine API
  • כדי להשתמש ב-Google Cloud CLI למשימה הזו, מתקינים ואז מאתחלים את ה-CLI של gcloud. אם כבר התקנתם את ה-CLI של gcloud, הריצו את הפקודה gcloud components update כדי להוריד את הגרסה העדכנית. יכול להיות שגרסאות קודמות של ה-CLI של gcloud לא תומכות בהרצת הפקודות שבמסמך הזה.
  • מוודאים שיש לכם אשכול קיים במצב Autopilot או Standard, שפועל ב-GKE בגרסה 1.35 ואילך, וש-Gateway API מופעל. כדי ליצור אשכול, קראו את המאמר יצירת אשכול Autopilot. כדי להפעיל את Gateway API באשכול קיים, קראו את המאמר הפעלת Gateway API באשכול.
  • מוודאים שכלי שורת הפקודה OpenSSL מותקן. אתם משתמשים בכלי הזה כדי ליצור אישורי TLS עם חתימה עצמית לאפליקציות לדוגמה.

הדרישות של GKE Gateway Controller

  • אפשר להשתמש ב-Gateway API רק באשכולות מותאמים ל-VPC.
  • אם אתם משתמשים ב-GatewayClasses אזוריים או חוצי-אזורים, אתם צריכים להפעיל רשת משנה של שרתי proxy בלבד.
  • התוסף HttpLoadBalancing צריך להיות מופעל באשכול.
  • אם אתם משתמשים ב-Istio, אתם צריכים לשדרג את Istio לאחת מהגרסאות הבאות:
    • ‫1.15.2 ואילך
    • ‫1.14.5 ואילך
    • ‫1.13.9 ואילך.
  • אם אתם משתמשים ב-VPC משותף, בפרויקט המארח אתם צריכים להקצות את התפקיד Compute Network User לחשבון השירות של GKE בפרויקט השירות.

הגבלות ומגבלות

כשפורסים שערים של מעבר TLS ומשאבי TLSRoute, חשוב לשים לב למגבלות הבאות:

  • כשמגדירים את protocol: TLS במאזין, צריך להגדיר במפורש את השדה tls.mode לערך Passthrough. ‫GKE לא תומך במצב ברירת המחדל Terminate למאזינים של TLS.
  • במאזין TLS, אל תגדירו את השדה certificateRefs. אימות אישור לקוח ברמת השער (השדה spec.tls.frontendValidation) לא חל על מאזינים מסוג TLS. כדי להשתמש ב-mTLS, צריך לאמת את אישורי הלקוח בקצה העורפי.
  • מאזין TLS יכול לשתף יציאה רק עם מאזיני TLS אחרים. אי אפשר להגדיר מאזינים מסוג HTTP או HTTPS באותה יציאה כמו מאזין מסוג TLS.
  • בשדה TLSRoute.spec.hostnames, צריך לציין רשימה של 1 עד 1,024 שמות מארחים של SNI (שם מארח מדויק או קידומת של תו כללי לחיפוש, כמו *.example.com). כל רשומה יכולה להכיל עד 253 תווים ולא יכולה להיות כתובת IP.
  • בשדה TLSRoute.spec.rules אפשר להגדיר כלל אחד לכל היותר.
  • כל כלל תומך בערך אחד בדיוק של backendRef. בקצוות עורפיים של שרתי proxy בשכבה 4 אי אפשר לפצל את התנועה לפי משקל בין כמה קצוות עורפיים.
  • ב-GatewayClasses של אשכול יחיד יש תמיכה רק בקצוות עורפיים של שירותים, וב-GatewayClasses של כמה אשכולות יש תמיכה רק בקצוות עורפיים של ServiceImport.
  • בשדה TLSRoute.spec.rules.backendRefs, מגדירים את השדה port ליציאה שהוצהרה על ידי משאב היעד Service או ServiceImport, ולא ליציאת הקונטיינר.
  • שירות ה-Kubernetes לקצה העורפי (כולל שירותים שיוצאו כ-ServiceImport) שאליו מפנה משאב TLSRoute צריך להשתמש בערך TCP לפרוטוקול היציאה שלו.
  • אי אפשר לצרף משאב BackendTLSPolicy, שרת proxy לאימות זהויות (IAP) או מדיניות אבטחה של Google Cloud Armor לקצה עורפי של TLSRoute, כי הבקר דוחה הגדרות מהסוג הזה.
  • כשמצרפים משאב GCPBackendPolicy לשירות או ל-ServiceImport שמשאב TLSRoute מפנה אליו, צריך להגדיר את השדה spec.protocol לערך TCP (אם משמיטים את השדה spec.protocol, ברירת המחדל היא הערך HTTP, שרלוונטי רק לקצוות עורפיים של HTTPRoute). אפשר לצרף ל-Service או ל-ServiceImport מדיניות HTTP אחת ומדיניות TCP אחת בו-זמנית. אם כמה כללי מדיניות עם אותו ערך spec.protocol מטרגטים את אותו Service או ServiceImport, המדיניות הישנה ביותר מקבלת קדימות וכללי המדיניות החדשים יותר לא מצורפים.
  • משאב GCPBackendPolicy עם השדה spec.protocol שמוגדר כ-TCP תומך רק בשדות הבאים בשדה spec.default (כשמטרגטים משאב ServiceImport, אפשר להגדיר את שלושת השדות האחרונים לכל אשכול, אזור או אזור גיאוגרפי בשדה spec.default.scopes במקום זאת):

    • timeoutSec
    • connectionDraining
    • logging.enabled וגם logging.sampleRate
    • ‫sessionAffinity.type (הערכים התקינים הם CLIENT_IP או NONE)
    • capacityScalerPercent
    • balancingMode: CONNECTION
    • maxConnectionsPerEndpoint

    אי אפשר ליצור מדיניות TCP שכוללת שדות אחרים, או מדיניות HTTP שכוללת שדות שרלוונטיים רק ל-TCP (balancingMode: CONNECTION ו-maxConnectionsPerEndpoint). שרת ה-API של Kubernetes דוחה את המדיניות בזמן היצירה. מידע נוסף זמין במאמר הגדרת משאבי Gateway באמצעות כללי מדיניות.

  • אם גם משאב HTTPRoute וגם משאב TLSRoute מפנים לאותו Service או ServiceImport, צירוף של משאב HealthCheckPolicy לאותו יעד מחליף את בדיקות התקינות הנפרדות שמוגדרות כברירת מחדל, ומחיל את אותה הגדרה של בדיקת תקינות על הקצוות העורפיים של שני המסלולים. כדי להגדיר בדיקות תקינות באופן עצמאי (למשל בדיקות תקינות של HTTP לקצוות עורפיים של HTTPRoute ובדיקות תקינות של SSL או TCP לקצוות עורפיים של TLSRoute), צריך ליצור שירות נפרד לכל נתיב ולצרף משאב HealthCheckPolicy לכל שירות. שני השירותים יכולים לבחור את אותם Pods.

  • תכונות של שכבה 7 (כמו שכתוב של כתובות URL, ניתוב מבוסס-נתיב וטרנספורמציות של כותרות HTTP) לא חלות על מאזני עומסים של שרתי proxy ל-TCP בשכבה 4.

הגדרת רשת משנה לשרתי proxy בלבד

שער אזורי דורש רשת משנה מסוג proxy-only (purpose=REGIONAL_MANAGED_PROXY) בכל אזור שבו פורסים אותו. תת-רשת אחת של שרתי proxy בלבד משרתת את כל מאזני העומסים האזוריים שמבוססים על Envoy באותו אזור. אם באזור שלכם עדיין אין רשת משנה כזו, תוכלו לקרוא איך להגדיר רשת משנה מסוג proxy-only.

שמירת כתובת IP סטטית

לפני שפורסים את השער, צריך להקצות כתובת IP סטטית אזורית כדי שכתובת השער לא תשתנה, וכדי שתוכלו לפתור את שמות המארחים לדוגמה. הוראות ליצירת כתובת IP סטטית מופיעות במאמר יצירת שער HTTP(S)‎ חיצוני אזורי.

פריסת שער עם מאזינים להעברת TLS

השער בקטע הזה מגדיר שני מאזינים של TLS שמשתפים את היציאה 443. כל מאזין תואם לשם מארח SNI אחר ומעביר חיבורים במצב מעבר למשאבי TLSRoute מצורפים.

  1. שומרים את המניפסט הבא בתור store-gateway.yaml:

    kind: Gateway
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: store-tls
    spec:
      gatewayClassName: gke-l7-regional-external-managed
      listeners:
      # Accept TLS connections for store.example.com without decrypting them.
      - name: store
        protocol: TLS
        port: 443
        hostname: "store.example.com"
        tls:
          mode: Passthrough
        allowedRoutes:
          kinds:
          - kind: TLSRoute
      # A second listener on the same port, selected by a different SNI name.
      - name: store-german
        protocol: TLS
        port: 443
        hostname: "store-de.example.com"
        tls:
          mode: Passthrough
        allowedRoutes:
          kinds:
          - kind: TLSRoute
      addresses:
      - type: NamedAddress
        value: IP_ADDRESS_NAME
    

    מחליפים את IP_ADDRESS_NAME בשם של כתובת ה-IP הסטטית ששמרתם.

    שימו לב שלבלוק tls אין שדה certificateRefs. במצב Passthrough, אישור ה-TLS נמצא ב-Pods של הקצה העורפי ולא במאזן העומסים.

  2. מחילים את המניפסט של Gateway:

    kubectl apply -f store-gateway.yaml
    
  3. מוודאים שהשער מתוכנת:

    kubectl get gateway store-tls
    

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

    NAME        CLASS                              ADDRESS         PROGRAMMED   AGE
    store-tls   gke-l7-regional-external-managed   35.118.32.224   True         53s
    

שימוש בשער פנימי

כדי שתעבורת הנתונים תישאר בתוך רשת ה-VPC, משנים את הערך של השדה gatewayClassName ל-gke-l7-rilb ומסירים את השדה addresses מהמניפסט של השער. לאחר מכן, GKE יוצר מאזן עומסי רשת פנימי אזורי לשרת proxy ומקצה כתובת IP פנימית מרשת המשנה של האשכול. שאר השלבים בדף הזה לא השתנו. שולחים את בקשות הבדיקה ממכונה של Compute Engine באותה רשת VPC, כי אי אפשר להגיע לכתובת ה-IP הפנימית מהאינטרנט.

פריסת אפליקציות TLS לדוגמה

בקטע הזה פורסים שני פריסות NGINX (store-v1 ו-store-german) שמסיימות את ה-TLS ביציאה 8443. כל פריסה מציגה אישור TLS אחר ומדווחת על שם המארח של ה-SNI שהיא קיבלה, וכך אתם יכולים לוודא שהניתוב התבצע על סמך SNI.

  1. יוצרים אישור TLS בחתימה עצמית ומפתח פרטי לכל שם מארח:

    openssl req -x509 -newkey rsa:2048 -nodes -sha256 -days 365 \
        -keyout store.key -out store.crt \
        -subj '/CN=store.example.com' \
        -addext 'subjectAltName=DNS:store.example.com'
    openssl req -x509 -newkey rsa:2048 -nodes -sha256 -days 365 \
        -keyout store-de.key -out store-de.crt \
        -subj '/CN=store-de.example.com' \
        -addext 'subjectAltName=DNS:store-de.example.com'
    

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

  2. כדי לאחסן כל אישור ומפתח ב-Kubernetes Secret, מריצים את הפקודות הבאות:

    kubectl create secret tls store-cert \
        --cert=store.crt --key=store.key
    kubectl create secret tls store-de-cert \
        --cert=store-de.crt --key=store-de.key
    
  3. שומרים את המניפסט הבא בתור store-tls-app.yaml:

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: store-nginx
    data:
      default.conf: |
        server {
          listen 8443 ssl;
          ssl_certificate     /etc/nginx/ssl/tls.crt;
          ssl_certificate_key /etc/nginx/ssl/tls.key;
          default_type text/plain;
          # Report which Pod answered and which SNI name it received.
          return 200 "backend: $hostname\nsni: $ssl_server_name\n";
        }
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: store-v1
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: store-v1
      template:
        metadata:
          labels:
            app: store-v1
        spec:
          containers:
          - name: nginx
            image: nginx:1.27
            ports:
            - containerPort: 8443
            volumeMounts:
            - name: config
              mountPath: /etc/nginx/conf.d
            - name: cert
              mountPath: /etc/nginx/ssl
              readOnly: true
          volumes:
          - name: config
            configMap:
              name: store-nginx
          - name: cert
            secret:
              secretName: store-cert
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: store-v1
    spec:
      selector:
        app: store-v1
      ports:
      - port: 443
        targetPort: 8443
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: store-german
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: store-german
      template:
        metadata:
          labels:
            app: store-german
        spec:
          containers:
          - name: nginx
            image: nginx:1.27
            ports:
            - containerPort: 8443
            volumeMounts:
            - name: config
              mountPath: /etc/nginx/conf.d
            - name: cert
              mountPath: /etc/nginx/ssl
              readOnly: true
          volumes:
          - name: config
            configMap:
              name: store-nginx
          - name: cert
            secret:
              secretName: store-de-cert
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: store-german
    spec:
      selector:
        app: store-german
      ports:
      - port: 443
        targetPort: 8443
    

    בשירותים לא צריך שדה appProtocol. ‫GKE מעביר את התנועה של TLSRoute לקצה העורפי כ-TCP, כך שלשדה אין השפעה.

  4. מחילים את המניפסט על האשכול:

    kubectl apply -f store-tls-app.yaml
    
  5. מוודאים שה-Pods פועלים:

    kubectl get pods -l 'app in (store-v1,store-german)'
    

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

    NAME                            READY   STATUS    RESTARTS   AGE
    store-german-7d9f8c5b47-2xqkd   1/1     Running   0          41s
    store-german-7d9f8c5b47-h6tzn   1/1     Running   0          41s
    store-v1-6c4b9d8f65-kp8vr       1/1     Running   0          41s
    store-v1-6c4b9d8f65-wm2ls       1/1     Running   0          41s
    

פריסת משאבי TLSRoute לניתוב מבוסס-SNI

משאב TLSRoute מתחבר למאזין של Gateway ומנתב חיבורים שתואמים לשמות המארחים שלו לשירות קצה עורפי יחיד.

  1. שומרים את המניפסט הבא בתור store-tls-routes.yaml:

    kind: TLSRoute
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: store
    spec:
      parentRefs:
      - kind: Gateway
        name: store-tls
        sectionName: store
      hostnames:
      - "store.example.com"
      rules:
      - backendRefs:
        - name: store-v1
          port: 443
    ---
    kind: TLSRoute
    apiVersion: gateway.networking.k8s.io/v1
    metadata:
      name: store-german
    spec:
      parentRefs:
      - kind: Gateway
        name: store-tls
        sectionName: store-german
      hostnames:
      - "store-de.example.com"
      rules:
      - backendRefs:
        - name: store-german
          port: 443
    

    השדה sectionName בוחר את המאזין המטורגט ב-Gateway. השדה port מציין את יציאת השירות (443), ולא את יציאת הקונטיינר.

  2. מחילים את המניפסט של TLSRoute:

    kubectl apply -f store-tls-routes.yaml
    
  3. מוודאים שבקר השער קיבל את הנתיב:

    kubectl get tlsroute store -o yaml
    

    השדה status דומה לשדה הבא:

    status:
      parents:
      - controllerName: networking.gke.io/gateway
        parentRef:
          name: store-tls
          sectionName: store
        conditions:
        - type: ResolvedRefs
          status: "True"
          reason: ResolvedRefs
        - type: Accepted
          status: "True"
          reason: Accepted
        - type: Reconciled
          status: "True"
          reason: ReconciliationSucceeded
    

    התנאי Reconciled הוא תנאי סטטוס ספציפי ל-GKE, שמדווח אם בקר ה-Gateway יצר את משאבי ה-Google Cloud הבסיסיים.

  4. מוודאים שהמאזין של השער קיבל את הנתיב:

    kubectl get gateway store-tls -o yaml
    

    כל רשומה בשדה status.listeners דומה לזו:

    - name: store
      supportedKinds:
      - group: gateway.networking.k8s.io
        kind: TLSRoute
      attachedRoutes: 1
      conditions:
      - type: ResolvedRefs
        status: "True"
        reason: ResolvedRefs
      - type: Accepted
        status: "True"
        reason: Accepted
      - type: Programmed
        status: "True"
        reason: Programmed
    

אימות ניתוב תעבורת הנתונים

בדיקות התקינות של GKE בקצה עורפי של מעבר TLS מתבצעות על ידי השלמת לחיצת יד בפרוטוקול TLS. יכול להיות שיעברו כמה דקות עד שה-Pods יעברו את בדיקת התקינות הראשונית.

  1. מאחזרים את כתובת ה-IP של השער:

    kubectl get gateway store-tls -o jsonpath='{.status.addresses[0].value}'
    
  2. שולחים בקשה לכל שם מארח, ומחליפים את GATEWAY_IP בכתובת ה-IP מהשלב הקודם:

    curl --resolve store.example.com:443:GATEWAY_IP \
        --cacert store.crt https://store.example.com
    curl --resolve store-de.example.com:443:GATEWAY_IP \
        --cacert store-de.crt https://store-de.example.com
    

    כל תשובה מציינת את שם ה-Pod שענה ומהדהדת את שם המארח של ה-SNI שהגיע אליו:

    backend: store-v1-6c4b9d8f65-kp8vr
    sni: store.example.com
    backend: store-german-7d9f8c5b47-2xqkd
    sni: store-de.example.com
    

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

  3. מוודאים שהניתוב מתבצע במהלך לחיצת היד בפרוטוקול TLS, לפני שהלקוח שולח בקשת HTTP:

    openssl s_client -connect GATEWAY_IP:443 \
        -servername store-de.example.com </dev/null 2>/dev/null \
        | openssl x509 -noout -subject
    

    בפלט מוצג האישור של הקצה העורפי של store-german:

    subject=CN=store-de.example.com
    

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

  4. שליחת בקשה עם שם מארח SNI שלא תואם לאף מאזין:

    curl --resolve other.example.com:443:GATEWAY_IP \
        https://other.example.com
    

    החיבור נסגר כי אין מאזין ביציאה 443 שתואם לשם המארח other.example.com של ה-SNI.

הגדרת קיבולת החיבורים לקצה העורפי

בשירותי GKE שמוגדרים במשאבי TLSRoute, בקר השער של GKE מגדיר שירות לקצה העורפי של TCP באמצעות מצב האיזון CONNECTION, ומגדיר את ערך ברירת המחדל של השדה maxConnectionsPerEndpoint ל-100,000,000 (1e8).

מכיוון שבקר ה-Gateway לא יכול לקבוע באופן אוטומטי את מגבלת החיבורים בו-זמנית של קונטיינר, אתם יכולים להשתמש במשאב GCPBackendPolicy כדי לשנות את ערך ברירת המחדל של השדה maxConnectionsPerEndpoint (מ-1 ל-1,000,000,000). כדי לקבוע את מגבלת החיבורים האופטימלית לפודים של ה-backend, צריך לבדוק את הדברים הבאים:

  • מגבלות תוכנה: בודקים את הגדרות השרת או האפליקציה, כמו ההוראה MaxRequestWorkers של Apache, ההגדרות worker_processes * worker_connections של NGINX, המאפיין maxConnections של Tomcat או מגבלות מאגר החיבורים של מסד הנתונים.
  • בדיקת עומסים: מגדילים בהדרגה את החיבורים בו-זמנית ל-Pod מבודד, תוך מעקב אחרי המעבד (CPU), הזיכרון, זמן האחזור והמגבלות של השקע או של תיאור הקובץ.

החלת GCPBackendPolicy בהתאמה אישית

כדי לשנות את ערך ברירת המחדל של השדה maxConnectionsPerEndpoint בשירות לקצה העורפי של TLSRoute, צריך לצרף משאב GCPBackendPolicy עם השדה spec.protocol שמוגדר ל-TCP והשדה balancingMode שמוגדר ל-CONNECTION:

  1. שומרים את המניפסט הבא בתור backend-policy.yaml:

    apiVersion: networking.gke.io/v1
    kind: GCPBackendPolicy
    metadata:
      name: store-v1-backend-policy
    spec:
      protocol: TCP
      default:
        balancingMode: CONNECTION
        maxConnectionsPerEndpoint: 1000
      targetRef:
        group: ""
        kind: Service
        name: store-v1
    
  2. מחילים את המדיניות:

    kubectl apply -f backend-policy.yaml
    

מידע נוסף על הגדרת מדיניות לקצה העורפי זמין במאמר הגדרת משאבי Gateway באמצעות מדיניות.

הגדרת בדיקות תקינות למעבר TLS

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

טיפול בהיעדר תוספי SNI מבדיקות

כשמגדירים בדיקות תקינות לקצוות עורפיים של מעבר TLS, צריך להביא בחשבון את האופן שבו בדיקות התקינות של ה-prober פועלות עם נקודות קצה של TLS. Google Cloud

אם האפליקציות בקצה העורפי דורשות שם מארח תקין של SNI כדי להשלים לחיצת יד בפרוטוקול TLS, בדיקות התקינות של SSL שמוגדרות כברירת מחדל ייכשלו. כדי לפתור את הבעיה, אפשר להשתמש באחת מהשיטות הבאות:

  • הגדרת אישורי גיבוי: מגדירים את שרת בק-אנד או ה-Pod כך שיציגו אישור TLS כברירת מחדל כשהחיבור ל-TLS מגיע בלי תוסף SNI.
  • שימוש בבדיקות תקינות של TCP עם HealthCheckPolicy: מצמידים משאב HealthCheckPolicy (כשהשדה config.type מוגדר ל-TCP) לשירות כדי לבדוק את הקישוריות של יציאת ה-TCP הגולמית ולהימנע מלחיצת היד בפרוטוקול TLS.

החלת HealthCheckPolicy בהתאמה אישית

כדי להגדיר פרמטרים של בדיקת תקינות בהתאמה אישית או לעבור לבדיקת תקינות של TCP, צריך להשתמש במשאב HealthCheckPolicy:

  1. שומרים את המניפסט הבא בתור health-check-policy.yaml:

    apiVersion: networking.gke.io/v1
    kind: HealthCheckPolicy
    metadata:
      name: store-health-check
    spec:
      default:
        checkIntervalSec: 10
        timeoutSec: 5
        healthyThreshold: 2
        unhealthyThreshold: 3
        config:
          type: TCP
          tcpHealthCheck:
            port: 8443
      targetRef:
        group: ""
        kind: Service
        name: store-service
    
  2. מחילים את המדיניות:

    kubectl apply -f health-check-policy.yaml
    

מידע נוסף על הגדרת בדיקות תקינות זמין במאמר הגדרת משאבי Gateway באמצעות כללי מדיניות.

הגדרות מתקדמות

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

התאמה של שם מארח עם תווים כלליים לחיפוש

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

שומרים את המניפסט הבא כדי להגדיר TLSRoute עם תו כללי לחיפוש:

apiVersion: gateway.networking.k8s.io/v1
kind: TLSRoute
metadata:
  name: wildcard-tls-route
spec:
  parentRefs:
  - name: store-tls
    sectionName: store
  hostnames:
  - "*.example.com"
  rules:
  - backendRefs:
    - name: store-v1
      port: 443

כללים לפתרון התנגשויות וסדר עדיפויות

כשכמה משאבי TLSRoute מצורפים לאותו מאזין של Gateway וטוענים לשמות מארחים חופפים או זהים, בקר ה-GKE Gateway פותר את ההתנגשויות באמצעות אותם כללים של ספציפיות של שם מארח וחותמת זמן של יצירה כמו בנתיבי Gateway אחרים:

  • התאמות מדויקות של שמות מארחים (כמו store.example.com) מקבלות עדיפות על פני התאמות של תווים כלליים לחיפוש (כמו *.example.com), והתאמות ארוכות יותר של תווים כלליים לחיפוש מקבלות עדיפות על פני התאמות רחבות יותר של תווים כלליים לחיפוש (כמו *.com).
  • אם שני משאבי TLSRoute או יותר מצהירים על אותו שם מארח בדיוק, המסלול עם חותמת הזמן הישנה ביותר של היצירה (metadata.creationTimestamp) מקבל עדיפות. אם חותמות הזמן של היצירה זהות, המזהה הקטן יותר של <namespace>/<name> מבחינה לקסיקוגרפית מכריע את השוויון. בקר השער לא מקבל מסלולים מתנגשים, שמדווחים על מצב Accepted: False עם הסיבה Conflicted.

מידע נוסף על קדימות של נתיבים זמין במאמר מיזוג, קדימות ואימות של נתיבים.

ניתוב בין מרחבי שמות באמצעות ReferenceGrant

בסביבות עם כמה דיירים, צוות פלטפורמה מרכזי יכול לנהל את השער במרחב שמות של תשתית (כמו gateway-infra), וצוותי אפליקציות יכולים לנהל את המשאבים של TLSRoute ו-Service במרחבי שמות של מפתחים (כמו store-ns).

  1. במאזין של השער, מאפשרים מסלולים מכל מרחבי השמות (או ממרחבי שמות נבחרים):

    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: shared-tls-gateway
      namespace: gateway-infra
    spec:
      gatewayClassName: gke-l7-regional-external-managed
      listeners:
      - name: tls-passthrough
        protocol: TLS
        port: 443
        tls:
          mode: Passthrough
        allowedRoutes:
          namespaces:
            from: All
          kinds:
          - kind: TLSRoute
    
  2. במרחב השמות של האפליקציה (store-ns), יוצרים משאב ReferenceGrant שמאפשר לשער במרחב השמות gateway-infra להפנות למשאב TLSRoute או למשאב Service:

    apiVersion: gateway.networking.k8s.io/v1beta1
    kind: ReferenceGrant
    metadata:
      name: allow-gateway-infra
      namespace: store-ns
    spec:
      from:
      - group: gateway.networking.k8s.io
        kind: Gateway
        namespace: gateway-infra
      to:
      - group: ""
        kind: Service
    
  3. במרחב השמות של האפליקציה (store-ns), יוצרים את המשאב TLSRoute שמפנה לשער המשותף:

    apiVersion: gateway.networking.k8s.io/v1
    kind: TLSRoute
    metadata:
      name: store-tls-route
      namespace: store-ns
    spec:
      parentRefs:
      - name: shared-tls-gateway
        namespace: gateway-infra
        sectionName: tls-passthrough
      hostnames:
      - "store.example.com"
      rules:
      - backendRefs:
        - name: store-service
          port: 443
    

שער מעבר TLS עם כמה אשכולות

אתם יכולים לפרוס שערים מרובי אשכולות עם מעבר TLS בכמה אשכולות GKE ב-fleet:

  1. משתמשים ב-GatewayClass נתמך עם כמה אשכולות (gke-l7-regional-external-managed-mc,‏ gke-l7-rilb-mc או gke-l7-cross-regional-internal-managed-mc).
  2. מייצאים את השירות לקצה העורפי לכל ה-fleet באמצעות המשאב ServiceExport.
  3. במשאב TLSRoute, מפנים לקצה העורפי של כמה אשכולות באמצעות משאב ServiceImport:

    apiVersion: gateway.networking.k8s.io/v1
    kind: TLSRoute
    metadata:
      name: mc-store-tls-route
    spec:
      parentRefs:
      - name: mc-tls-gateway
        sectionName: tls-passthrough
      hostnames:
      - "store.example.com"
      rules:
      - backendRefs:
        - group: net.gke.io
          kind: ServiceImport
          name: store-service
          port: 443
    

כמה כתובות IP בשערים עם כמה אשכולות

כשפורסים שערים עם כמה אשכולות באזורים שונים (למשל כשמשתמשים ב-GatewayClass‏ gke-l7-cross-regional-internal-managed-mc), אפשר להגדיר כמה כתובות IP בשדה spec.addresses כדי לחשוף כתובת VIP פנימית באזורים שונים.

כשמגדירים כמה כתובות IP בשער אחד, בקר השער של GKE מתרגם כל כתובת IP לכלל העברה נפרד. במקביל, הבקר מפנה לשרת proxy משותף לחלוקת העומס ומשתמש שוב באותה הגדרת ניתוב בסיסית. הבקר לא מכפיל את משאבי ה-TLSRoute הבסיסיים במספר כתובות ה-IP, וכך מונע שכפול של מסלולים וניצול של המכסה.

בדוגמה הבאה מוצג שער מרובה אשכולות שמוגדר עם כתובות IP זמניות בשני אזורים:

kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
  name: internal-cross-region-tls-gateway
spec:
  gatewayClassName: gke-l7-cross-regional-internal-managed-mc
  addresses:
  - type: networking.gke.io/ephemeral-ipv4-address/us-west1
    value: "us-west1"
  - type: networking.gke.io/ephemeral-ipv4-address/us-east1
    value: "us-east1"
  listeners:
  - name: tls-passthrough
    protocol: TLS
    port: 443
    tls:
      mode: Passthrough
    allowedRoutes:
      kinds:
      - kind: TLSRoute

מידע נוסף על פריסת שערים פנימיים בכמה אשכולות זמין במאמר פריסת שער פנימי בכמה אשכולות.

איחוד חיבורי HTTP

לפי RFC 7540 (סעיף 9.1.1), דפדפני אינטרנט ולקוחות HTTP/2 יכולים לעשות שימוש חוזר בחיבור TLS קיים לכמה בקשות, אם מתקיימים שני התנאים הבאים:

  • שמות המארחים של היעד מפוענחים לאותה כתובת IP.
  • אישור ה-TLS שהשרת מציג הוא סמכותי לשני שמות המארחים. לדוגמה, אישור יכול לכסות כמה שמות מארחים כשמשתמשים באישורי תו כללי לחיפוש כמו *.example.com או בשמות חלופיים של בעלים (SAN) בכמה דומיינים.

בגלל שמאזן עומסים של שרת proxy ל-TCP בשכבה 4 לא בודק את כותרות בקשות ה-HTTP אחרי לחיצת היד הראשונית בפרוטוקול TLS, מאזן העומסים מעביר את הבקשות הממוזגות דרך החיבור הקיים ל-Pod של הקצה העורפי שהותאם בהתחלה.

כדי למנוע בעיות שקשורות לאיחוד חיבורים:

  • השתמשו באישורי TLS נפרדים שלא חופפים: אל תשתפו אישורי תווים כלליים לחיפוש או זהויות SAN נפוצות בין שירותים שונים לקצה העורפי, שמועברים בריבוב מאחורי אותה כתובת IP של שער.
  • החזרת קוד השגיאה HTTP 421 Misdirected Request: מגדירים את שרתי ה-HTTPS לקצה העורפי כך שיבדקו את הכותרת :authority (או Host) של ה-HTTP. אם המארח המבוקש לא תואם לדומיין הצפוי של השרת, מחזירים קוד סטטוס HTTP 421 Misdirected Request, שמבקש מהלקוח לפתוח חיבור חדש.
  • הפעלת מסגרת HTTP/2 ORIGIN: מאמצים את התמיכה במסגרת RFC 8336 ORIGIN בשרתי המקור ומצהירים במפורש אילו שמות מארחים תקפים לשימוש חוזר בחיבור.

הסרת המשאבים

כדי להימנע מצבירת חיובים בחשבון Google Cloud על המשאבים שבהם השתמשתם בדף הזה, מוחקים את המשאבים:

  1. מוחקים את המשאבים של TLSRoute:

    kubectl delete tlsroute store store-german
    
  2. מוחקים את המשאב Gateway:

    kubectl delete gateway store-tls
    
  3. מוחקים את המשאבים לדוגמה של Deployment,‏ Service ו-Secret:

    kubectl delete -f store-tls-app.yaml
    kubectl delete secret store-cert store-de-cert
    

המאמרים הבאים