إشعار
يتطلب الوصول إلى هذه الصفحة تخويلاً. يمكنك محاولة تسجيل الدخول أو تغيير الدلائل.
يتطلب الوصول إلى هذه الصفحة تخويلاً. يمكنك محاولة تغيير الدلائل.
في هذا المقال، نقوم بتكوين ونشر عنقود Valkey على خدمة Azure Kubernetes Service (AKS)، بما في ذلك إنشاء خريطة تكوين عنقود Valkey، ووحدات عنقود رئيسية ونسخ لضمان التكرار وتكرار المناطق، وميزانية تعطيل الوحدات (PDB) لضمان توفر عالي.
إشعار
تحتوي هذه المقالة على إشارات إلى مصطلحي السيد (الأساسي) والتابع (النسخة المقلدة)، وهما مصطلحان لم تعد مايكروسوفت تستخدمها. عند إزالة المصطلح من برنامج Valkey، سنقوم بإزالته من هذه المقالة.
الاتصال بنظام مجموعة AKS
إشعار
إذا كنت تستخدم Terraform، تأكد من استبدال النقاط المؤقتة في الكود بالمخرجات الفعلية من أوامر Terraform عند نشر البنية التحتية.
قم بتكوين
kubectlللاتصال بمجموعة AKS الخاصة بك باستخدامaz aks get-credentialsالأمر .az aks get-credentials --resource-group $MY_RESOURCE_GROUP_NAME --name $MY_CLUSTER_NAME --overwrite-existing --output table
إنشاء مساحة اسم
إنشاء مساحة اسم لمجموعة Valkey باستخدام
kubectl create namespaceالأمر .kubectl create namespace valkey --dry-run=client --output yaml | kubectl apply -f -مثال على الإخراج:
namespace/valkey created
إنشاء أسرار
إنشاء كلمة مرور عشوائية لمجموعة Valkey باستخدام openssl وتخزينها في مخزن مفاتيح Azure باستخدام
az keyvault secret setالأمر .export SECRET=$(openssl rand -base64 32) echo requirepass $SECRET > /tmp/valkey-password-file.conf echo primaryauth $SECRET >> /tmp/valkey-password-file.conf az keyvault secret set --vault-name $MY_KEYVAULT_NAME --name valkey-password-file --file /tmp/valkey-password-file.conf --output none rm /tmp/valkey-password-file.confقم بتعيين النهج للسماح للهوية المعينة من قبل المستخدم بالحصول على السر باستخدام
az keyvault set-policyالأمر .az keyvault set-policy --name $MY_KEYVAULT_NAME --object-id $userAssignedObjectID --secret-permissions get --output table
احصل على تفاصيل الهوية المعينة من قبل المستخدم
احصل على معرف الهوية ومعرف الكائن الذي تم إنشاؤه بواسطة إضافة Azure Key Vault Secrets Provider باستخدام
az aks showالأمر.export userAssignedIdentityID=$(az aks show --resource-group $MY_RESOURCE_GROUP_NAME --name $MY_CLUSTER_NAME --query addonProfiles.azureKeyvaultSecretsProvider.identity.clientId --output tsv) export userAssignedObjectID=$(az aks show --resource-group $MY_RESOURCE_GROUP_NAME --name $MY_CLUSTER_NAME --query addonProfiles.azureKeyvaultSecretsProvider.identity.objectId --output tsv)
تكوين الوصول إلى كلمة مرور Valkey المخزنة في Azure Key Vault
أنشئ موردا
SecretProviderClassللوصول إلى كلمة مرور Valkey المخزنةkubectl applyفي مخزن المفاتيح باستخدام الأمر .export TENANT_ID=$(az account show --query tenantId --output tsv) kubectl apply -f - <<EOF --- apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: valkey-password namespace: valkey spec: provider: azure parameters: usePodIdentity: "false" useVMManagedIdentity: "true" userAssignedIdentityID: "${userAssignedIdentityID}" keyvaultName: ${MY_KEYVAULT_NAME} # the name of the AKV instance objects: | array: - | objectName: valkey-password-file objectAlias: valkey-password-file.conf objectType: secret tenantId: "${TENANT_ID}" # the tenant ID of the AKV instance EOF
إنشاء ملف تكوين Valkey
ConfigMapإنشاء مورد لتخزين ملف تكوين Valkey.kubectl apply -f - <<EOF apiVersion: v1 kind: ConfigMap metadata: name: valkey-cluster namespace: valkey data: valkey.conf: |+ cluster-enabled yes cluster-node-timeout 15000 cluster-config-file /data/nodes.conf appendonly yes protected-mode yes dir /data port 6379 include /etc/valkey-password/valkey-password-file.conf EOFمثال على الإخراج:
configmap/valkey-cluster created
إنشاء حجيرات نظام المجموعة الأساسية Valkey
StatefulSetإنشاء مورد لهدفspec.affinityهو الاحتفاظ بجميع الانتخابات التمهيدية في المنطقة 1 والمنطقة 2، ويفضل أن تكون في عقد مختلفة، باستخدامkubectl applyالأمر .kubectl apply -f - <<EOF --- apiVersion: apps/v1 kind: StatefulSet metadata: name: valkey-masters namespace: valkey spec: serviceName: "valkey-masters" replicas: 3 selector: matchLabels: app: valkey template: metadata: labels: app: valkey appCluster: valkey-masters spec: terminationGracePeriodSeconds: 20 affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: agentpool operator: In values: - valkey - key: topology.kubernetes.io/zone operator: In values: - ${MY_LOCATION}-1 - matchExpressions: - key: agentpool operator: In values: - valkey - key: topology.kubernetes.io/zone operator: In values: - ${MY_LOCATION}-2 podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - valkey topologyKey: topology.kubernetes.io/zone - weight: 90 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - valkey topologyKey: kubernetes.io/hostname containers: - name: role-master-checker image: "${MY_ACR_REGISTRY}.azurecr.io/valkey:latest" command: - "/bin/bash" - "-c" args: [ "while true; do role=\$(valkey-cli --pass \$(cat /etc/valkey-password/valkey-password-file.conf | awk '{print \$2; exit}') role | awk '{print \$1; exit}'); if [ \"\$role\" = \"slave\" ]; then valkey-cli --pass \$(cat /etc/valkey-password/valkey-password-file.conf | awk '{print \$2; exit}') cluster failover; fi; sleep 30; done" ] volumeMounts: - name: valkey-password mountPath: /etc/valkey-password readOnly: true - name: valkey image: "${MY_ACR_REGISTRY}.azurecr.io/valkey:latest" env: - name: VALKEY_PASSWORD_FILE value: "/etc/valkey-password/valkey-password-file.conf" - name: MY_POD_IP valueFrom: fieldRef: fieldPath: status.podIP command: - "valkey-server" args: - "/conf/valkey.conf" - "--cluster-announce-ip" - "\$(MY_POD_IP)" resources: requests: cpu: "100m" memory: "100Mi" ports: - name: valkey containerPort: 6379 protocol: "TCP" - name: cluster containerPort: 16379 protocol: "TCP" volumeMounts: - name: conf mountPath: /conf readOnly: false - name: data mountPath: /data readOnly: false - name: valkey-password mountPath: /etc/valkey-password readOnly: true volumes: - name: valkey-password csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: valkey-password - name: conf configMap: name: valkey-cluster defaultMode: 0755 volumeClaimTemplates: - metadata: name: data spec: accessModes: [ "ReadWriteOnce" ] storageClassName: managed-csi-premium resources: requests: storage: 20Gi EOFمثال على الإخراج:
statefulset.apps/valkey-masters created
إنشاء حجيرات مجموعة النسخة المتماثلة Valkey
أنشئ موردا ثانيا
StatefulSetلنسخ Valkey بهدفspec.affinityإبقاء جميع النسخ في المنطقة 3، ويفضل أن تكون في عقد مختلفة، باستخدامkubectl applyالأمر.kubectl apply -f - <<EOF --- apiVersion: apps/v1 kind: StatefulSet metadata: name: valkey-replicas namespace: valkey spec: serviceName: "valkey-replicas" replicas: 3 selector: matchLabels: app: valkey template: metadata: labels: app: valkey appCluster: valkey-replicas spec: terminationGracePeriodSeconds: 20 affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: agentpool operator: In values: - valkey - key: topology.kubernetes.io/zone operator: In values: - ${MY_LOCATION}-3 podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 90 podAffinityTerm: labelSelector: matchExpressions: - key: app operator: In values: - valkey topologyKey: kubernetes.io/hostname containers: - name: valkey image: "${MY_ACR_REGISTRY}.azurecr.io/valkey:latest" env: - name: VALKEY_PASSWORD_FILE value: "/etc/valkey-password/valkey-password-file.conf" - name: MY_POD_IP valueFrom: fieldRef: fieldPath: status.podIP command: - "valkey-server" args: - "/conf/valkey.conf" - "--cluster-announce-ip" - "\$(MY_POD_IP)" resources: requests: cpu: "100m" memory: "100Mi" ports: - name: valkey containerPort: 6379 protocol: "TCP" - name: cluster containerPort: 16379 protocol: "TCP" volumeMounts: - name: conf mountPath: /conf readOnly: false - name: data mountPath: /data readOnly: false - name: valkey-password mountPath: /etc/valkey-password readOnly: true volumes: - name: valkey-password csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: valkey-password - name: conf configMap: name: valkey-cluster defaultMode: 0755 volumeClaimTemplates: - metadata: name: data spec: accessModes: [ "ReadWriteOnce" ] storageClassName: managed-csi-premium resources: requests: storage: 20Gi EOFمثال على الإخراج:
statefulset.apps/valkey-replicas created
التحقق من توزيع الجراب والعقدة
تحقق من
master-Nتشغيل العقد والمناطقreplica-Nالمختلفة باستخدامkubectl get nodesالأوامر و.kubectl get podskubectl get pods -n valkey -o wide kubectl get node -o custom-columns=Name:.metadata.name,Zone:".metadata.labels.topology\.kubernetes\.io/zone"مثال على الإخراج:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES valkey-masters-0 1/1 Running 0 2m55s 10.224.0.4 aks-valkey-18693609-vmss000004 <none> <none> valkey-masters-1 1/1 Running 0 2m31s 10.224.0.137 aks-valkey-18693609-vmss000000 <none> <none> valkey-masters-2 1/1 Running 0 2m7s 10.224.0.222 aks-valkey-18693609-vmss000001 <none> <none> valkey-replicas-0 1/1 Running 0 88s 10.224.0.237 aks-valkey-18693609-vmss000005 <none> <none> valkey-replicas-1 1/1 Running 0 70s 10.224.0.18 aks-valkey-18693609-vmss000002 <none> <none> valkey-replicas-2 1/1 Running 0 48s 10.224.0.242 aks-valkey-18693609-vmss000005 <none> <none> Name Zone aks-nodepool1-17621399-vmss000000 centralus-1 aks-nodepool1-17621399-vmss000001 centralus-2 aks-nodepool1-17621399-vmss000003 centralus-3 aks-valkey-18693609-vmss000000 centralus-1 aks-valkey-18693609-vmss000001 centralus-2 aks-valkey-18693609-vmss000002 centralus-3 aks-valkey-18693609-vmss000003 centralus-1 aks-valkey-18693609-vmss000004 centralus-2 aks-valkey-18693609-vmss000005 centralus-3انتظر لمدة ثلاث دقائق تقريبا حتى يتم تشغيل جميع
master-Nالكبسولاتreplica-Nقبل الانتقال إلى الخطوة التالية.
إنشاء خدمات بدون عنوان
أنشئ ثلاثة موارد بدون
Serviceرؤوس (الأولى للعنقود بأكمله، والثانية للأساسيات، والثالث للنسخ المقلدة) لاستخدامها للحصول على عناوين IP لوحدات Valkey باستخدام الأمرkubectl apply.kubectl apply -f - <<EOF apiVersion: v1 kind: Service metadata: name: valkey-cluster namespace: valkey spec: clusterIP: None ports: - name: valkey-port port: 6379 protocol: TCP targetPort: 6379 selector: app: valkey sessionAffinity: None type: ClusterIP EOF kubectl apply -f - <<EOF apiVersion: v1 kind: Service metadata: name: valkey-masters namespace: valkey spec: clusterIP: None ports: - name: valkey-port port: 6379 protocol: TCP targetPort: 6379 selector: app: valkey appCluster: valkey-masters sessionAffinity: None type: ClusterIP EOF kubectl apply -f - <<EOF apiVersion: v1 kind: Service metadata: name: valkey-replicas namespace: valkey spec: clusterIP: None ports: - name: valkey-port port: 6379 protocol: TCP targetPort: 6379 selector: app: valkey appCluster: valkey-replicas sessionAffinity: None type: ClusterIP EOFمثال على الإخراج:
service/valkey-cluster created service/valkey-masters created service/valkey-replicas created
إنشاء موازنة تعطيل الجراب (PDB)
إنشاء موازنة تعطيل الجراب (PDB) لضمان عدم توفر جراب واحد على الأكثر أثناء الاضطرابات الطوعية، مثل الترقيات أو الصيانة. يساعد هذا في الحفاظ على استقرار وتوافر تطبيق Valkey داخل مجموعة Kubernetes.
kubectl apply -f - <<EOF apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: valkey namespace: valkey spec: maxUnavailable: 1 selector: matchLabels: app: valkey EOFمثال على الإخراج:
poddisruptionbudget.policy/valkey created
تشغيل نظام مجموعة Valkey
في مجموعة Valkey، يعد تخصيص الفتحة جزءا أساسيا من كيفية توزيع البيانات عبر العقد. تقسم مجموعات Valkey (على غرار مجموعات Redis) مساحة المفتاح إلى 16,384 فتحة تجزئة ، والتي يتم توزيعها بالتساوي عبر العقد الأساسية في نظام المجموعة.
في الأقسام التالية، أنشأنا عنقود فالكي بثلاث عقد مع ثلاث نسخ مقلدة، لضمان ما يلي:
- تغطية كاملة للفتحات (جميع الفتحات البالغ عددها 16,384).
- قابلية وصول عالية عبر النسخ المتماثل.
- تعيين الدور المناسب والتحقق من صحة نظام المجموعة.
تهيئة نظام مجموعة Valkey مع العقد الأساسية في المنطقة 1 و 2 وتكوين توزيع الفتحة
قم بإنشاء نظام المجموعة بثلاث عقد أساسية في المنطقة 1 و2 وقم بتوزيع جميع فتحات التجزئة البالغ عددها 16,384 بالتساوي عبرها باستخدام الأمر
kubectl exec.kubectl exec -it -n valkey valkey-masters-0 -- valkey-cli --cluster create --cluster-yes --cluster-replicas 0 \ valkey-masters-0.valkey-masters.valkey.svc.cluster.local:6379 \ valkey-masters-1.valkey-masters.valkey.svc.cluster.local:6379 \ valkey-masters-2.valkey-masters.valkey.svc.cluster.local:6379 \ --pass ${SECRET}مثال على الإخراج:
>>> Performing hash slots allocation on 3 nodes... Master[0] -> Slots 0 - 5460 Master[1] -> Slots 5461 - 10922 Master[2] -> Slots 10923 - 16383 M: ee6ac1d00d3f016b6f46c7ce11199bc1a7809a35 valkey-masters-0.valkey-masters.valkey.svc.cluster.local:6379 slots:[0-5460] (5461 slots) master M: fd1fb98db83976478e05edd3d2a02f9a13badd80 valkey-masters-1.valkey-masters.valkey.svc.cluster.local:6379 slots:[5461-10922] (5462 slots) master M: ea47bf57ae7080ef03164a4d48b662c7b4c8770e valkey-masters-2.valkey-masters.valkey.svc.cluster.local:6379 slots:[10923-16383] (5461 slots) master >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join ... >>> Performing Cluster Check (using node valkey-masters-0.valkey-masters.valkey.svc.cluster.local:6379) M: ee6ac1d00d3f016b6f46c7ce11199bc1a7809a35 valkey-masters-0.valkey-masters.valkey.svc.cluster.local:6379 slots:[0-5460] (5461 slots) master M: ea47bf57ae7080ef03164a4d48b662c7b4c8770e 10.224.0.176:6379 slots:[10923-16383] (5461 slots) master M: fd1fb98db83976478e05edd3d2a02f9a13badd80 10.224.0.247:6379 slots:[5461-10922] (5462 slots) master [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
إضافة النسخ المتماثلة Valkey في المنطقة 3 لتمكين قابلية الوصول العالية
أضف نسخة متماثلة واحدة لكل عقدة أساسية في منطقة مختلفة لضمان التسامح مع الخطأ وإمكانية تجاوز الفشل التلقائي باستخدام الأوامر التالية
kubectl exec.kubectl exec -ti -n valkey valkey-masters-0 -- valkey-cli --cluster add-node \ valkey-replicas-0.valkey-replicas.valkey.svc.cluster.local:6379 \ valkey-masters-0.valkey-masters.valkey.svc.cluster.local:6379 --cluster-slave \ --pass ${SECRET} kubectl exec -ti -n valkey valkey-masters-0 -- valkey-cli --cluster add-node \ valkey-replicas-1.valkey-replicas.valkey.svc.cluster.local:6379 \ valkey-masters-1.valkey-masters.valkey.svc.cluster.local:6379 --cluster-slave \ --pass ${SECRET} kubectl exec -ti -n valkey valkey-masters-0 -- valkey-cli --cluster add-node \ valkey-replicas-2.valkey-replicas.valkey.svc.cluster.local:6379 \ valkey-masters-2.valkey-masters.valkey.svc.cluster.local:6379 --cluster-slave \ --pass ${SECRET}مثال على الإخراج:
>>> Adding node valkey-replicas-0.valkey-replicas.valkey.svc.cluster.local:6379 to cluster valkey-masters-0.valkey-masters.valkey.svc.cluster.local:6379 >>> Performing Cluster Check (using node valkey-masters-0.valkey-masters.valkey.svc.cluster.local:6379) M: ee6ac1d00d3f016b6f46c7ce11199bc1a7809a35 valkey-masters-0.valkey-masters.valkey.svc.cluster.local:6379 slots:[0-5460] (5461 slots) master M: ea47bf57ae7080ef03164a4d48b662c7b4c8770e 10.224.0.176:6379 slots:[10923-16383] (5461 slots) master M: fd1fb98db83976478e05edd3d2a02f9a13badd80 10.224.0.247:6379 slots:[5461-10922] (5462 slots) master [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. Automatically selected master valkey-masters-0.valkey-masters.valkey.svc.cluster.local:6379 >>> Send CLUSTER MEET to node valkey-replicas-0.valkey-replicas.valkey.svc.cluster.local:6379 to make it join the cluster. Waiting for the cluster to join >>> Configure node as replica of valkey-masters-0.valkey-masters.valkey.svc.cluster.local:6379. [OK] New node added correctly. >>> Adding node valkey-replicas-1.valkey-replicas.valkey.svc.cluster.local:6379 to cluster valkey-masters-1.valkey-masters.valkey.svc.cluster.local:6379 >>> Performing Cluster Check (using node valkey-masters-1.valkey-masters.valkey.svc.cluster.local:6379) M: fd1fb98db83976478e05edd3d2a02f9a13badd80 valkey-masters-1.valkey-masters.valkey.svc.cluster.local:6379 slots:[5461-10922] (5462 slots) master S: 0ebceb60cbcc31da9040159440a1f4856b992907 10.224.0.224:6379 slots: (0 slots) slave replicates ee6ac1d00d3f016b6f46c7ce11199bc1a7809a35 M: ea47bf57ae7080ef03164a4d48b662c7b4c8770e 10.224.0.176:6379 slots:[10923-16383] (5461 slots) master M: ee6ac1d00d3f016b6f46c7ce11199bc1a7809a35 10.224.0.14:6379 slots:[0-5460] (5461 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. Automatically selected master valkey-masters-1.valkey-masters.valkey.svc.cluster.local:6379 >>> Send CLUSTER MEET to node valkey-replicas-1.valkey-replicas.valkey.svc.cluster.local:6379 to make it join the cluster. Waiting for the cluster to join >>> Configure node as replica of valkey-masters-1.valkey-masters.valkey.svc.cluster.local:6379. [OK] New node added correctly. >>> Adding node valkey-replicas-2.valkey-replicas.valkey.svc.cluster.local:6379 to cluster valkey-masters-2.valkey-masters.valkey.svc.cluster.local:6379 >>> Performing Cluster Check (using node valkey-masters-2.valkey-masters.valkey.svc.cluster.local:6379) M: ea47bf57ae7080ef03164a4d48b662c7b4c8770e valkey-masters-2.valkey-masters.valkey.svc.cluster.local:6379 slots:[10923-16383] (5461 slots) master S: 0ebceb60cbcc31da9040159440a1f4856b992907 10.224.0.224:6379 slots: (0 slots) slave replicates ee6ac1d00d3f016b6f46c7ce11199bc1a7809a35 S: fa44edff683e2e01ee5c87233f9f3bc35c205dce 10.224.0.103:6379 slots: (0 slots) slave replicates fd1fb98db83976478e05edd3d2a02f9a13badd80 M: ee6ac1d00d3f016b6f46c7ce11199bc1a7809a35 10.224.0.14:6379 slots:[0-5460] (5461 slots) master 1 additional replica(s) M: fd1fb98db83976478e05edd3d2a02f9a13badd80 10.224.0.247:6379 slots:[5461-10922] (5462 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. Automatically selected master valkey-masters-2.valkey-masters.valkey.svc.cluster.local:6379 >>> Send CLUSTER MEET to node valkey-replicas-2.valkey-replicas.valkey.svc.cluster.local:6379 to make it join the cluster. Waiting for the cluster to join >>> Configure node as replica of valkey-masters-2.valkey-masters.valkey.svc.cluster.local:6379. [OK] New node added correctly.
التحقق من أدوار الجرابات وحالة النسخ المتماثل
تأكد من تكوين كل نسخة أساسية ونسخة متماثلة بشكل صحيح وإنشاء علاقات النسخ المتماثل باستخدام الأوامر التالية
kubectl exec.for x in $(seq 0 2); do echo "valkey-masters-$x"; kubectl exec -n valkey valkey-masters-$x -- valkey-cli --pass ${SECRET} role; echo; done for x in $(seq 0 2); do echo "valkey-replicas-$x"; kubectl exec -n valkey valkey-replicas-$x -- valkey-cli --pass ${SECRET} role; echo; doneمثال على الإخراج:
valkey-masters-0 master 84 10.224.0.224 6379 84 valkey-masters-1 master 84 10.224.0.103 6379 84 valkey-masters-2 master 70 10.224.0.200 6379 70 valkey-replicas-0 slave 10.224.0.14 6379 connected 98 valkey-replicas-1 slave 10.224.0.247 6379 connected 98 valkey-replicas-2 slave 10.224.0.176 6379 connected 84
الخطوات التالية
لمعرفة المزيد حول نشر البرامج مفتوحة المصدر على Azure Kubernetes Service (AKS)، راجع المقالات التالية:
- نشر قاعدة بيانات PostgreSQL عالية التوفر على AKS
- إنشاء ونشر البنية الأساسية لبرنامج ربط العمليات التجارية للبيانات والتعلم الآلي باستخدام Flyte على AKS
المساهمون
تحتفظ Microsoft بهذه المقالة. كتبه المساهمون التاليون في الأصل:
- نيللي كيبوي | مهندس خدمة
- Saverio Proto | مهندس تجربة العملاء الرئيسي
- Don High | مهندس العملاء الرئيسي
- LaBrina المحبة | مهندس الخدمة الرئيسي
- كين كيلتي | الوحدة النمطية للنظام الأساسي الموثوق به
- راسل دي بينا | الوحدة النمطية للنظام الأساسي الموثوق به
- كولن ميكسون | إدارة المنتجات
- كيتان شودا | مهندس عملاء أول
- نافي خرادي | مهندس تجربة العملاء
- إيرين شيفر | مطور المحتوى 2