הטמעה לדוגמה של איזון עומסים בשכבה 7 ב-HAProxy

ב-Google Distributed Cloud (GDC)‎ עם air gap יש מאזן עומסים מנוהל ומובנה בשכבה 4 (L4), אבל הרבה אפליקציות ארגוניות דורשות יכולות מתקדמות בשכבה 7 (L7), כמו ניתוב מבוסס-מארח, ניהול TLS מרכזי וחלוקת תנועה מורכבת. בעבר, זה נעשה באמצעות Ingress API, שנחשב עכשיו ל-feature-frozen בקהילת Kubernetes.

ארכיטקטורת העזר הזו מספקת פתרון לניהול עצמי של איזון עומסים בשכבה 7. באמצעות פריסת בקר הקוד הפתוח הפופולרי HAProxy באשכול GDC רגיל, הלקוחות יכולים לנתב בצורה חלקה תעבורת נתונים ב-L7 לסביבות היברידיות. בארכיטקטורה הזו נעשה שימוש בסיום TLS ‏(HTTPRoute) כדי לנתב תנועה על סמך ציון שם השרת (SNI) גם לפודים מובְנים בקונטיינרים וגם לאפליקציות שמתארחות במכונות וירטואליות חיצוניות.

ארכיטקטורה

דיאגרמת ארכיטקטורה של איזון עומסים בשכבה 7 של HAProxy ב-GDC עם air gap.

הרכיבים העיקריים של הפתרון כוללים:

  • לקוח: ישות שיוזמת בקשות HTTPS כדי ליצור אינטראקציה עם האפליקציות.
  • אשכול רגיל ב-GDC: ב-GDC יש דרך מובנית ליצור אשכולות Vanilla של Kubernetes. בפתרון הזה, האשכול יארח את מאזן העומסים L7 ואת הבקרים שלו, יחד עם עומסי העבודה והשירות ללא ממשק משתמש למכונות וירטואליות חיצוניות.
  • מאזן עומסים GDC L4: מאזן העומסים המובנה L4 משמש כנקודת הכניסה, ומפיץ את תעבורת הנתונים של TCP/443 ישירות לפודים של Kubernetes שמריצים את הבקרים.
  • בקרי Ingress: אופרטורים של HAProxy שפועלים באשכול הרגיל. הם עוקבים אחרי המשאבים של Ingress ומעדכנים באופן דינמי את שרתי ה-proxy הבסיסיים. בהטמעה הבאה נשתמש ב-HAProxy Ingress Controller.
  • ‫Ingress: משאבים סטנדרטיים ב-Kubernetes שמגדירים את יציאת ההאזנה הפיזית (443) ואת כללי הניתוב של המארח שמבוססים על SNI עם סיום TLS.
  • עומס עבודה בקונטיינרים (Pods): פריסה רגילה של Kubernetes שנחשפת באופן פנימי באמצעות Service רגיל של Kubernetes.
  • עומס עבודה מבוסס-VM (חיצוני): עומס עבודה שמתארח ב-VM חיצוני ברשת של הפרויקט, ונחשף לשרת ה-proxy באמצעות Service ללא ממשק משתמש של Kubernetes ונקודת קצה מותאמת אישית שמכילה את כתובת ה-IP הישירה של ה-VM.
  • ‫Harbor Registry: Container Registry פרטי שמשמש לאחסון ולהצגה של קובצי האימג' של שרת ה-proxy ושל האפליקציה בסביבה עם air gap.

באשכול הרגיל, יוצרים שלושה מרחבי שמות:

  • מרחב השמות load-balancer מארח את HAProxy Ingress Controller ואת עומס העבודה של מאזן העומסים של HAProxy:

    המשאבים של מרחב השמות של מאזן העומסים.

  • מרחב השמות hello-app מארח את Deployment, Service ו-Ingress בשביל עומס העבודה של מאגר ההדגמה:

    המשאבים של מרחב השמות hello-app.

  • מרחב השמות vm-app מארח שירות ללא ממשק משתמש שחושף את כתובת ה-IP החיצונית של המכונה הווירטואלית, EndpointSlice שמצביע על כתובת ה-IP החיצונית ו-Ingress:

    המשאבים של מרחב השמות vm-app.

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

לפני שפורסים את הפתרון הזה, צריך לוודא שהתנאים המוקדמים הבאים מתקיימים:

  • התוכנות הנדרשות: helm, ‏ docker, ‏ kubectl
  • התחברות ל-CLI והגדרה מקומית: מורידים את gdcloud CLI ממסוף GDC ומגדירים את הסביבה באופן מקומי:

    export USER_NAME="USER_NAME"
    export PROJECT_ID="PROJECT_ID"
    export ZONE="ZONE"
    export ORG_NAME="ORG_NAME"
    export GDC_URL="GDC_URL"
    
    gdcloud components install gdcloud-k8s-auth-plugin
    
    gdcloud config set core/organization_console_url \
      https://console.$ORG_NAME.$ZONE.$GDC_URL
    gdcloud config set core/zone $ZONE
    gdcloud config set core/project ${PROJECT_ID}
    
    gdcloud auth login # use --login-config-cert option in case of TLS error
    
  • הגדרת הפרויקט: יוצרים פרויקט בסביבת GDC עם air gap כדי לאחסן את המשאבים:

    gdcloud projects create $PROJECT_ID
    
  • תפקידי IAM: כדי לנהל משאבים ב-Kubernetes, מקצים למשתמש את התפקידים אדמין של אשכול ואדמין רגיל של אשכול. כדי להעביר קובצי אימג'ים, מקצים למשתמש את התפקיד אדמין של מכונה ב-Harbor:

    # Grant standard cluster and cluster admin roles
    gdcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member="user:${USER_NAME}" \
      --role=cluster-admin
    
    gdcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member="user:${USER_NAME}" \
      --role=standard-cluster-admin
    
    # Grant Harbor instance admin role
    gdcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member="user:${USER_NAME}" \
      --role=harbor-instance-admin
    

יצירת אשכול רגיל

בקטע הזה מוסבר איך להגדיר אשכול Kubernetes רגיל בסביבת GDC עם air gap. אשכול רגיל מספק בסיס גמיש וחזק לפריסת עומסי עבודה שונים, כולל HAProxy Ingress Controller והאפליקציות בהתאמה אישית. כדי לוודא שהאשכול מוגדר כמו שצריך ונגיש לפריסות הבאות, צריך לבצע את השלבים הבאים.

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

    gdcloud compute machine-types list
    
  2. בוחרים סוג מכונה שמתאים לצומתי העובדים של האשכול. במדריך הזה מומלץ להשתמש בסוג מכונה עם לפחות 4 מעבדי vCPU.

    export MACHINE_TYPE="MACHINE_TYPE"
    
  3. מורידים את קובץ ה-kubeconfig של שרת ה-API לניהול ומגדירים כינוי:

    export CLUSTER_NAME="CLUSTER_NAME"
    
    KUBECONFIG=kubeconfig-admin.yaml gdcloud clusters \
      get-credentials ${ORG_NAME}-admin
    
    alias km="kubectl --kubeconfig kubeconfig-admin.yaml"
    
  4. יוצרים אשכול רגיל עם שני צומתי עובד:

    km create -f - <<EOF
    apiVersion: cluster.gdc.goog/v1
    kind: Cluster
    metadata:
      name: ${CLUSTER_NAME}
      namespace: ${PROJECT_ID}
    spec:
      nodePools:
      - machineTypeName: ${MACHINE_TYPE}
        nodeCount: 2
        name: ${CLUSTER_NAME}-node-pool
    EOF
    

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

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

    km get clusters/${CLUSTER_NAME} \
      -n ${PROJECT_ID} \
      --watch
    

    כשהאשכול מוכן, הפלט אמור להציג את המצב Running, כך:

    NAME         STATE     K8S VERSION
    my-cluster   Running   1.30.12-gke.300
    
  5. אחרי שהאשכול מוכן, מאחזרים את פרטי הכניסה שלו:

    KUBECONFIG=kubeconfig-${CLUSTER_NAME}.yaml gdcloud clusters \
      get-credentials ${CLUSTER_NAME} \
      --standard \
      --project ${PROJECT_ID} \
      --zone ${ZONE}
    
  6. כדי לקצר את הפקודות של kubectl בהמשך המדריך, יוצרים שם חלופי. הכינוי הזה ישמש לאינטראקציה עם האשכול הרגיל:

    alias kk="kubectl --kubeconfig kubeconfig-${CLUSTER_NAME}.yaml"
    
  7. יוצרים מרחבי שמות לבקר, לאפליקציה בקונטיינר לדוגמה hello-app ולאפליקציה לדוגמה שמבוססת על VM:

    kk create namespace load-balancer
    kk create namespace hello-app
    kk create namespace vm-app
    

יצירה ושילוב של Harbor Registry

‫Harbor הוא מאגר של קובצי אימג' של קונטיינרים עם תמיכה מובנית ב-GDC עם air gap. בקטע הזה נסביר איך לשלב את Harbor Registry עם אשכול רגיל, כולל הגדרת פרטי כניסה וסודות כדי לאפשר משיכה ודחיפה מאובטחות של קובצי אימג'.

  1. יוצרים מכונה של Harbor בפרויקט.
  2. יוצרים פרויקט Harbor במכונה של Harbor.
  3. מגדירים את משתני הסביבה:

    export HARBOR_INSTANCE_NAME="HARBOR_INSTANCE_NAME"
    export HARBOR_INSTANCE_URL="HARBOR_INSTANCE_URL"
    export HARBOR_PROJECT="HARBOR_PROJECT"
    export IMAGE_PULL_SECRET_NAME="harbor-secret"
    
  4. נכנסים למכונה של Harbor באמצעות חשבון רובוט:

    docker --config=./docker login ${HARBOR_INSTANCE_URL}
    
  5. יוצרים את הסודות באשכול הרגיל:

    kk create secret docker-registry ${IMAGE_PULL_SECRET_NAME} \
      --from-file=.dockerconfigjson=./docker/config.json \
      -n load-balancer
    
    kk create secret docker-registry ${IMAGE_PULL_SECRET_NAME} \
      --from-file=.dockerconfigjson=./docker/config.json \
      -n hello-app
    

פריסת אפליקציית הדגמה בקונטיינר

בקטע הזה מוסבר איך לפרוס אפליקציה לדוגמה בקונטיינר (hello-app) באשכול Kubernetes של GDC עם air gap. תיצרו את המשאבים הנדרשים של Kubernetes Deployment ו-Service כדי להריץ את hello-app ולחשוף אותו באופן פנימי בתוך האשכול, כהכנה לגישה באמצעות מאזן העומסים L7.

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

    docker pull gcr.io/google-samples/hello-app:1.0 \
      --platform linux/amd64
    docker tag gcr.io/google-samples/hello-app:1.0 \
      ${HARBOR_INSTANCE_URL}/${HARBOR_PROJECT}/hello-app:1.0
    docker --config=./docker push \
      ${HARBOR_INSTANCE_URL}/${HARBOR_PROJECT}/hello-app:1.0
    
  2. פורסים את המניפסט הבא באשכול הרגיל:

    cat << EOF > hello-app.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: hello-app
      namespace: hello-app
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: hello-app
      template:
        metadata:
          labels:
            app: hello-app
        spec:
          containers:
          - name: hello-server
            image: ${HARBOR_INSTANCE_URL}/${HARBOR_PROJECT}/hello-app:1.0
            ports:
            - containerPort: 8080
          imagePullSecrets:
          - name: ${IMAGE_PULL_SECRET_NAME}
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: hello-app
      namespace: hello-app
    spec:
      type: ClusterIP
      selector:
        app: hello-app
      ports:
        - protocol: TCP
          port: 80
          targetPort: 8080
    EOF
    
    kk apply -f hello-app.yaml
    

ואז מוודאים שהפריסה והשירות נמצאים שם

kk get svc,deploy -n hello-app

פריסת אפליקציית הדגמה ב-VM

בקטע הזה מוסבר איך לפרוס אפליקציית הדגמה במכונה וירטואלית (VM) מחוץ לאשכול Kubernetes. כדי לדמות אפליקציה חיצונית שמאזן העומסים יכול לחשוף, תגדירו שרת HTTP ב-VM. כך תדגימו את היכולת של מאזן העומסים לנהל את התנועה למשאבים בתוך האשכול ומחוצה לו.

קודם כל, יוצרים מכונה וירטואלית לאפליקציית ההדגמה:

  1. פותחים את מסוף GDC בדפדפן האינטרנט.
  2. בוחרים את אותו פרויקט שבו יצרתם את אשכול Kubernetes הרגיל.
  3. פותחים את התפריט ולוחצים על Virtual machines.
  4. לוחצים על Create Instance.
  5. נותנים למכונה הווירטואלית את השם vm-workload. תמונה של 2 vCPU מספיקה לדוגמה.
  6. לקובץ האימג' של דיסק האתחול, בוחרים בהפצה של Ubuntu 22.04, שכוללת את Python מותקן מראש.
  7. לוחצים על יצירה.
  8. מחכים כמה דקות עד שהמכונה הווירטואלית תהיה מוכנה.
  9. יוצרים חיבור SSH למכונה הווירטואלית:
    1. במסוף GDC, לוחצים על המכונה הווירטואלית.
    2. לוחצים על Connect with SSH.

אחרי שמתחברים למסוף ה-SSH, מריצים את הפקודה הבאה:

mkdir ~/simple-server
cd ~/simple-server
echo 'Welcome to my VM!' > index.html
python3 -m http.server --bind 0.0.0.0 8080 &

כדי לנתב תנועה למכונה וירטואלית, יוצרים שירות ללא ממשק משתמש (ללא בוררים). הכתובת הזו תמופה באופן ידני לכתובת ה-IP הפנימית של המכונה הווירטואלית באמצעות משאב EndpointSlice.

kk apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
  name: vm-app-svc
  namespace: vm-app
spec:
  ports:
  - protocol: TCP
    port: 443
    targetPort: 443
EOF

מריצים את הפקודה הבאה כדי לקבל את כתובת ה-IP של המכונה הווירטואלית vm-workload:

gdcloud compute instances list --project ${PROJECT_ID} \
  | grep workload-vm | awk '{print $3}'

הפלט יהיה כתובת ה-IP של המכונה הווירטואלית שתידרש כדי להגדיר את המשאב EndpointSlice.

יוצרים את המשאב EndpointSlice שיתחבר לשירות ללא בורר של אפליקציית ה-VM, ומציינים את כתובת ה-IP של ה-VM שאליה צריך לנתב את התנועה.

apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: vm-app-endpoints
  namespace: vm-app
  labels:
    kubernetes.io/service-name: vm-app-svc
addressType: IPv4
ports:
  - port: 8080
endpoints:
  - addresses:
    - "VM_IP"
    conditions:
      ready: true

יצירת אישורים בחתימה עצמית

בקטע הזה נסביר איך ליצור אישורי TLS וסודות של Kubernetes כדי לאבטח את התקשורת של האפליקציות שמבוססות על קונטיינרים ועל מכונות וירטואליות. לשם הנוחות, במדריך הזה נעשה שימוש באישורים בחתימה עצמית, אבל בסביבות ייצור צריך להשתמש באישורים ברמת ייצור, כמו שמתואר בקטע אופציונלי: שימוש באישורים שמוכנים לייצור. בוחרים שמות דומיין לדוגמה לאפליקציות האלה. כשיוצרים חיבורים מאובטחים, אפשר להבטיח את תקינות הנתונים ואת הסודיות של הלקוחות שניגשים לאפליקציה דרך HAProxy Ingress Controller.

לאפליקציה בקונטיינר, אנחנו יוצרים אישור בחתימה עצמית ושומרים אותו כסוד במרחב השמות של מאזן העומסים. הוא ישמש ל-TLS כשנשלחת בקשה ל-k8s-app.example.com

openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout tls-containerized.key \
  -out tls-containerized.crt \
  -subj "/CN=k8s-app.example.com" \
  -days 365

kk create secret tls tls-containerized \
  --namespace load-balancer \
  --key tls-containerized.key \
  --cert tls-containerized.crt

kk create secret tls tls-containerized \
  --namespace hello-app \
  --key tls-containerized.key \
  --cert tls-containerized.crt

באפליקציית VM, אישור דומה עם חתימה עצמית מונפק ונשמר. הוא ישמש ל-TLS כשנשלחת בקשה ל-vm-app.example.com

openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout tls-vm.key \
  -out tls-vm.crt \
  -subj "/CN=vm-app.example.com" \
  -days 365

kk create secret tls tls-vm \
  --namespace load-balancer \
  --key tls-vm.key \
  --cert tls-vm.crt

kk create secret tls tls-vm \
  --namespace vm-app \
  --key tls-vm.key \
  --cert tls-vm.crt

פריסת HAProxy

התקנת בקר HAProxy Ingress ו-L4 LB

export HAPROXY_VERSION=3.1.14

# pull the HAProxy Ingress Controller image and push it to Harbor
docker pull haproxytech/kubernetes-ingress:${HAPROXY_VERSION} \
  --platform linux/amd64
docker tag haproxytech/kubernetes-ingress:${HAPROXY_VERSION} \
  ${HARBOR_INSTANCE_URL}/${HARBOR_PROJECT}/haproxy-ingress:${HAPROXY_VERSION}
docker --config=./docker push \
  ${HARBOR_INSTANCE_URL}/${HARBOR_PROJECT}/haproxy-ingress:${HAPROXY_VERSION}

# Get Helm repo
helm repo add haproxytech https://haproxytech.github.io/helm-charts
helm repo update

# Install the Ingress Controller with helm
helm upgrade --install haproxy-kubernetes-ingress \
  haproxytech/kubernetes-ingress \
  --kubeconfig kubeconfig-${CLUSTER_NAME}.yaml \
  --namespace load-balancer \
  --set controller.image.repository=${HARBOR_INSTANCE_URL}/${HARBOR_PROJECT}/haproxy-ingress \
  --set controller.image.tag=${HAPROXY_VERSION} \
  --set controller.existingImagePullSecret=${IMAGE_PULL_SECRET_NAME} \
  --set controller.service.type=LoadBalancer \
  --set-json \
  controller.service.annotations='{"networking.gke.io/load-balancer-type": "internal"}'

בקר ה-Ingress של HAProxy מקבל כתובת IP וירטואלית ייחודית לגישת לקוח באמצעות שירות מסוג LoadBalancer. השירות הזה מגדיר מאזן עומסים מנוהל לחלוטין בשכבה 4. כדי לפשט את המדריך, יוצרים מאזן עומסים פנימי על ידי הגדרת ההערה load-balancer-type ל-internal. אם לא תכללו את ההערה הזו, ייווצר מאזן עומסים חיצוני. הפריסה של Kubernetes שולפת באופן מאובטח קובצי אימג' מ-Harbor באמצעות הסוד שסופק (${IMAGE_PULL_SECRET_NAME}), שמכיל את פרטי הכניסה של חשבון הרובוט של Harbor.

אימות ההתקנה של בקר HAProxy Ingress

בודקים שה-Pods של HAProxy Ingress Controller פועלים ומוכנים:

kk get pods -n load-balancer

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

NAME                                          READY   STATUS      RESTARTS   AGE
haproxy-kubernetes-ingress-78dc9c8676-f8fcb   1/1     Running     0          35s
haproxy-kubernetes-ingress-78dc9c8676-lfnr2   1/1     Running     0          65s
haproxy-kubernetes-ingress-crdjob-3-tgj2h     0/1     Completed   0          65s

מוודאים שהשירות של HAProxy Ingress Controller נוצר ומוגדר:

kk get services -n load-balancer

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

NAME                         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                                                                  AGE
haproxy-kubernetes-ingress   LoadBalancer   10.252.27.46   10.252.4.17   80:32023/TCP,443:31103/TCP,443:31103/UDP,1024:30146/TCP,6060:30718/TCP   10m

הגדרת משאבי Ingress לאפליקציות לדוגמה

יוצרים את משאב ה-Ingress שיחבר את HAProxy לשירות של האפליקציה בקונטיינר.

cat << EOF > hello-app-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-app-ingress
  namespace: hello-app
  annotations:
    haproxy.org/ssl-redirect: "true"
    haproxy.org/ssl-redirect-port: "443"
    haproxy.org/ssl-redirect-code: "308"
spec:
  ingressClassName: haproxy
  tls:
  - hosts:
    - "k8s-app.example.com"
    secretName: tls-containerized
  rules:
  - host: "k8s-app.example.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: hello-app
            port:
              number: 80
EOF

kk apply -f hello-app-ingress.yaml

יוצרים את משאב ה-Ingress שיתחבר לשירות ללא בורר של אפליקציית ה-VM, ומציינים את כתובת ה-IP של ה-VM שאליה צריך לנתב את התנועה.

cat << EOF > vm-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: vm-app-ingress
  namespace: vm-app
  annotations:
    haproxy.org/ssl-redirect: "true"
    haproxy.org/ssl-redirect-port: "443"
    haproxy.org/ssl-redirect-code: "308"
spec:
  ingressClassName: haproxy
  tls:
  - hosts:
    - "vm-app.example.com"
    secretName: tls-vm
  rules:
  - host: "vm-app.example.com"
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: vm-app-svc
            port:
              number: 443
EOF

kk apply -f vm-ingress.yaml

אחזור של כתובת ה-IP של מאזן העומסים

מריצים את הפקודה כדי לקבל את כתובת ה-IP של מאזן העומסים.

kk get services/haproxy-kubernetes-ingress \
  -n load-balancer \
  -o jsonpath='{.status.loadBalancer.ingress[0].ip}'

המידע הזה נדרש כדי לאמת את הגישה לאפליקציות. הוא יכונה LOAD_BALANCER_IP.

יצירת מכונה וירטואלית של לקוח

כדי ליצור מכונה וירטואלית של לקוח:

  1. פותחים את מסוף GDC בדפדפן האינטרנט.
  2. פותחים את התפריט ולוחצים על Virtual machines.
  3. לוחצים על Create Instance.
  4. יוצרים מכונה וירטואלית בשם client, בוחרים סוג מכונה קטן ובוחרים ב-Rocky Linux או ב-Ubuntu, שכוללות את curl מותקן מראש.
  5. לוחצים על יצירה.
  6. מחכים כמה דקות עד שהמכונה הווירטואלית תהיה מוכנה.
  7. אחרי שהמכונה הווירטואלית מוכנה, יוצרים חיבור SSH למכונה הווירטואלית:
    1. במסוף GDC, לוחצים על המכונה הווירטואלית.
    2. לוחצים על Connect with SSH.

אימות הגישה והניתוב

כדי לבדוק את הניתוב, מריצים פקודות curl מה-VM של הלקוח. אפשר להתחבר לשתי האפליקציות באמצעות שמות המארחים שהוגדרו להן, עם כתובת ה-IP של מאזן העומסים.

אם מעבירים את הדגל --resolve ב-curl, אפשר לאלץ את שמות הדומיינים לזהות את כתובת ה-IP של מאזן העומסים L4 ב-GDC עם air gap. שימו לב שאנחנו מעבירים את הדגל -k כדי לתת אמון באישורים בחתימה עצמית.

בודקים את האפליקציה בקונטיינר של Kubernetes:

curl -k --resolve k8s-app.example.com:443:$LOAD_BALANCER_IP https://k8s-app.example.com -v

בודקים את האפליקציה של ה-VM החיצוני:

curl -k --resolve vm-app.example.com:443:$LOAD_BALANCER_IP https://vm-app.example.com -v

אם מגדירים אותו נכון, בקר ה-Ingress יפעל בצורה חלקה כסיום TLS ויעביר את התנועה ליעד.

אופציונלי: שימוש באישורים שמוכנים לייצור

בקטע הזה מוסבר איך להשתמש בשירות ה-CA של GDC עם air gap כדי ליצור רשות אישורי בסיס (CA) פרטית, להנפיק אישורים חתומים לעומסי העבודה ולעדכן באופן מאובטח את המכונות הווירטואליות של הלקוח ואת האשכול הרגיל של GDC עם air gap.

בקטע הזה מוסבר איך להשתמש בשירות ה-CA של GDC עם air gap כדי ליצור רשות אישורי בסיס (CA) פרטית ולהנפיק אישורים תקפים לאפליקציות שלכם. אם תתקינו את ה-CA הבסיסי הזה ב-VM של הלקוח, תוכלו לוודא שסיום ה-TLS פועל בצורה חלקה עם אישורים מהימנים, בלי לעקוף את אזהרות ה-SSL (לדוגמה, באמצעות curl -k).

מתן ההרשאות הנדרשות וקבלת פרטי כניסה

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

  1. מקצים את התפקידים certificate-authority-service-admin ו-certificate-requester:

    gdcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member=user:${USER_NAME} \
      --role=certificate-authority-service-admin
    
    gdcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member=user:${USER_NAME} \
      --role=certificate-requester
    
  2. מקבלים את פרטי הכניסה של שרת ה-API לניהול:

    gdcloud clusters get-credentials ${ORG_NAME}-admin
    

יצירת רשות אישורים בסיסית (root CA)

אתם תיצרו רשות אישורים בשרת ה-API לניהול, במרחב השמות של הפרויקט.

  1. מחילים את המשאב CertificateAuthority:

    km apply -f - <<EOF
    apiVersion: pki.security.gdc.goog/v1
    kind: CertificateAuthority
    metadata:
      name: my-root-ca
      namespace: ${PROJECT_ID}
    spec:
      caProfile:
        commonName: "My Root CA"
        duration: 87600h # 10 years
        keyAlgorithm: RSA_2048
        maxChainLength: 1
      caType: ROOT
      keyLocation: HSM
      rotationPolicy:
        cronTime: 0 0 1 1 *
    EOF
    
    km -n ${PROJECT_ID} get \
    certificateauthority.pki.security.gdc.goog/my-root-ca -ojson \
    | jq -r '
    .status.conditions[] | select( .type as $id | "Ready" | index($id)) .status'
    

הנפקה ופריסה של אישורים

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

יוצרים בקשות לשני הדומיינים:

km apply -f - <<EOF
apiVersion: pki.security.gdc.goog/v1
kind: CertificateRequest
metadata:
  name: tls-containerized-req
  namespace: ${PROJECT_ID}
spec:
  certificateAuthorityRef:
    name: my-root-ca
    namespace: ${PROJECT_ID}
  certificateConfig:
    subjectConfig:
      commonName: "k8s-app.example.com"
      dnsNames:
      - "k8s-app.example.com"
  signedCertificateSecret: tls-containerized-signed
---
apiVersion: pki.security.gdc.goog/v1
kind: CertificateRequest
metadata:
  name: tls-vm-req
  namespace: ${PROJECT_ID}
spec:
  certificateAuthorityRef:
    name: my-root-ca
    namespace: ${PROJECT_ID}
  certificateConfig:
    subjectConfig:
      commonName: "vm-app.example.com"
      dnsNames:
      - "vm-app.example.com"
  signedCertificateSecret: tls-vm-signed
EOF

מחכים כמה רגעים עד שהאישורים יונפקו. כדי לוודא שהם מוכנים, בודקים אם התנאי Ready הוא True:

km get certificaterequests -n ${PROJECT_ID}

עדכון האשכול הרגיל

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

kk delete secret tls-containerized -n load-balancer
kk delete secret tls-vm -n load-balancer

kk delete secret tls-containerized -n hello-app
kk delete secret tls-vm -n vm-app

עכשיו מחלצים את האישורים החתומים משרת ה-API לניהול ויוצרים את הסודות החדשים באשכול הרגיל.

km get secret -n ${PROJECT_ID} tls-containerized-signed \
  -o jsonpath='{.data.tls\.crt}' \
  | base64 -d > tls-containerized.crt

km get secret -n ${PROJECT_ID} tls-containerized-signed \
  -o jsonpath='{.data.tls\.key}' \
  | base64 -d > tls-containerized.key

kk create secret tls tls-containerized \
  --namespace load-balancer \
  --key tls-containerized.key \
  --cert tls-containerized.crt

kk create secret tls tls-containerized \
  --namespace hello-app \
  --key tls-containerized.key \
  --cert tls-containerized.crt

km get secret -n ${PROJECT_ID} tls-vm-signed \
  -o jsonpath='{.data.tls\.crt}' \
  | base64 -d > tls-vm.crt

km get secret -n ${PROJECT_ID} tls-vm-signed \
  -o jsonpath='{.data.tls\.key}' \
  | base64 -d > tls-vm.key

kk create secret tls tls-vm \
  --namespace load-balancer \
  --key tls-vm.key \
  --cert tls-vm.crt

kk create secret tls tls-vm \
  --namespace vm-app \
  --key tls-vm.key \
  --cert tls-vm.crt

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

הגדרת אמון הלקוח

כדי לאמת את ההגדרה, צריך להגדיר ל-VM של הלקוח לתת אמון ב-CA הבסיסי החדש.

מחלצים את אישור ה-CA הבסיסי לקובץ:

km get secret -n ${PROJECT_ID} my-root-ca-secret \
  -o jsonpath='{.data.tls\.crt}' \
  | base64 -d > my-root-ca.crt

מעבירים את האישור למכונה הווירטואלית של הלקוח. (אפשר להעתיק את התוכן של my-root-ca.crt ולהדביק אותו בקובץ ב-VM של הלקוח).

ב-VM של הלקוח, מעדכנים את מאגר האישורים.

אם המכונה הווירטואלית של client היא Ubuntu:

sudo cp my-root-ca.crt /usr/local/share/ca-certificates/
sudo chmod 644 /usr/local/share/ca-certificates/my-root-ca.crt
sudo update-ca-certificates

אם ה-VM של client הוא Rocky Linux:

sudo cp my-root-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust

גישה מאומתת

עכשיו אתם יכולים לגשת לאפליקציות באמצעות curl בלי הדגל -k. החיבור יהיה מהימן לחלוטין.

בודקים את האפליקציה בקונטיינר של k8s:

curl -v --resolve k8s-app.example.com:443:LOAD_BALANDER_IP https://k8s-app.example.com

בודקים את אפליקציית המכונה הווירטואלית:

curl -v --resolve vm.example.com:443:LOAD_BALANDER_IP https://vm-app.example.com

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