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

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

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

ארכיטקטורה

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

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

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

ב-cluster הרגיל, יוצרים שלושה מרחבי שמות:

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

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

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

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

  • מרחב השמות vm-app מארח שירות ללא ראש (headless) שחושף את כתובת ה-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, צריך לתת למשתמש את התפקידים Cluster Admin ו-Standard Cluster Admin. כדי לדחוף תמונות, צריך לתת לו את התפקיד Harbor Instance Admin:

    # 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 שמבוססת על קונטיינר ואפליקציית ההדגמה שמבוססת על מכונה וירטואלית:

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

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

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

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

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

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

כדי להפנות תנועה למכונה וירטואלית, יוצרים Service ללא ראש (headless) (ללא סלקטורים). הכתובת הזו תמופה ידנית לכתובת ה-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 שיתחבר לשירות ללא בורר של אפליקציות למכונות וירטואליות, ומגדירים את כתובת ה-IP של המכונה הווירטואלית שאליה התנועה תנותב.

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

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"}'

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

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

בודקים שהפודים של 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 שיתחבר לשירות של המכונה הווירטואלית ללא בורר אפליקציות, ויקצה את כתובת ה-IP של המכונה הווירטואלית שאליה התנועה תנותב.

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.

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

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

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

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

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

אם מעבירים את הדגל --resolve ב-curl, אפשר לכפות על שמות הדומיינים להפנות לכתובת ה-IP של מאזן העומסים L4 עם בידוד פיזי ב-GDC. שימו לב שאנחנו מעבירים את הדגל -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 terminator ומעביר את התנועה ליעד.

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

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

בקטע הזה מוסבר איך להשתמש ב-CA Service של GDC עם air gap כדי ליצור רשות אישורי בסיס (CA) פרטית ולהנפיק אישורים תקפים לאפליקציות שלכם. התקנת רשות האישורים הבסיסית הזו במכונה הווירטואלית של הלקוח מאפשרת לוודא שסיום ה-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
    

יצירת רשות האישורים (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'
    

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

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

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 ולהדביק אותו בקובץ במכונה הווירטואלית של הלקוח).

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

אם מכונת client VM היא 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

אם מכונת client VM היא 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.