شعار زيفيرنت

استخدم Karpenter لتسريع Amazon EMR على قياس EKS التلقائي

التاريخ:

Amazon EMR على Amazon EKS هو خيار نشر لـ أمازون EMR يسمح للمؤسسات بتشغيل Apache Spark خدمة أمازون مطاطا Kubernetes (أمازون EKS). مع EMR على EKS ، تعمل وظائف Spark في وقت تشغيل Amazon EMR لـ Apache Spark. هذا يزيد من أداء وظائف Spark الخاصة بك بحيث تعمل بشكل أسرع وبتكلفة أقل من Apache Spark مفتوحة المصدر. يمكنك أيضًا تشغيل تطبيقات Apache Spark المستندة إلى Amazon EMR مع أنواع أخرى من التطبيقات على نفس مجموعة EKS لتحسين استخدام الموارد وتبسيط إدارة البنية التحتية.

كاربنتر في AWS re: Invent 2021 لتوفير حل ديناميكي وعالي الأداء ومفتوح المصدر لتوسيع نطاق المجموعة تلقائيًا لـ Kubernetes. يوفر تلقائيًا عقدًا جديدة استجابةً للقرون غير المجدولة. وهي تراقب طلبات الموارد الإجمالية للقرون غير المجدولة وتتخذ قرارات لإطلاق عقد جديدة وإنهاء إيقافها لتقليل زمن الوصول المجدول بالإضافة إلى تكاليف البنية التحتية.

لتكوين Karpenter ، تقوم بإنشاء ملف الموفرون التي تحدد كيفية إدارة Karpenter للعقد المعلقة وتنتهي صلاحيتها. على الرغم من أنه يتم التعامل مع معظم حالات الاستخدام من خلال موفِّر واحد ، إلا أن الموفرين المتعددين مفيدون في حالات الاستخدام متعدد المستأجرين مثل عزل العقد للفوترة ، أو استخدام قيود العقد المختلفة (مثل عدم وجود وحدات معالجة رسومات لفريق) ، أو استخدام إعدادات مختلفة لإلغاء حق الوصول. تطلق Karpenter العقد مع الحد الأدنى من موارد الحوسبة لتلائم القرون غير القابلة للجدولة لتعبئة binpack بكفاءة. إنه يعمل جنبًا إلى جنب مع جدولة Kubernetes لربط البودات غير القابلة للجدولة بالعقد الجديدة التي يتم توفيرها. يوضح الرسم البياني التالي كيف يعمل.

يوضح هذا المنشور كيفية دمج Karpenter في EMR الخاص بك على بنية EKS لتحقيق إمكانات توسعة تلقائية أسرع ومراعية للقدرات لتسريع أعباء عمل البيانات الضخمة والتعلم الآلي (ML) مع تقليل التكاليف. نقوم بتشغيل نفس عبء العمل باستخدام كل من Cluster Autoscaler و Karpenter ، لمشاهدة بعض التحسينات التي نناقشها في القسم التالي.

تحسينات مقارنة بالمقياس التلقائي العنقودي

مثل كاربنتر ، Kubernetes الكتلة Autoscaler تم تصميم (CAS) لإضافة العقد عندما تأتي الطلبات لتشغيل البودات التي لا يمكن تلبيتها بالسعة الحالية. Cluster Autoscaler هو جزء من مشروع Kubernetes ، مع عمليات التنفيذ من قبل موفري السحابة الرئيسيين لـ Kubernetes. من خلال إلقاء نظرة جديدة على التوفير ، تقدم Karpenter التحسينات التالية:

  • لا يوجد حمل لإدارة مجموعة العقدة - نظرًا لأن لديك متطلبات موارد مختلفة لأحمال عمل Spark المختلفة جنبًا إلى جنب مع أعباء العمل الأخرى في مجموعة EKS الخاصة بك ، فأنت بحاجة إلى إنشاء مجموعات عقد منفصلة يمكنها تلبية متطلباتك ، مثل أحجام المثيلات ومناطق التوفر وخيارات الشراء. يمكن أن ينمو هذا بسرعة إلى عشرات ومئات من مجموعات العقد ، مما يضيف عبء إدارة إضافي. يدير Karpenter كل مثيل بشكل مباشر ، دون استخدام آليات تنسيق إضافية مثل مجموعات العقد ، مع اتباع نهج أقل مجموعة عن طريق استدعاء واجهة برمجة تطبيقات الأسطول EC2 مباشرة إلى عقد التزويد. يتيح ذلك لـ Karpenter استخدام أنواع مثيلات متنوعة ، ومناطق توافر ، وخيارات شراء ببساطة عن طريق إنشاء مزود واحد ، كما هو موضح في الشكل التالي.
  • عمليات إعادة المحاولة السريعة - إذا كان الأمازون الحوسبة المرنة السحابية سعة (Amazon EC2) غير متوفرة ، يمكن لـ Karpenter إعادة المحاولة في أجزاء من الثانية بدلاً من الدقائق. يمكن أن يكون هذا مفيدًا حقًا إذا كنت تستخدم مثيلات EC2 Spot ولا يمكنك الحصول على سعة لأنواع مثيلات معينة.
  • مصمم للتعامل مع المرونة الكاملة للسحابة - تتمتع Karpenter بالقدرة على معالجة مجموعة كاملة من أنواع المثيلات المتاحة من خلال AWS بكفاءة. لم يتم إنشاء Cluster Autoscaler في الأصل مع المرونة في التعامل مع مئات أنواع المثيلات ومناطق توافر الخدمات وخيارات الشراء. نوصي بأن تكون مرنًا قدر الإمكان لتمكين Karpenter من الحصول على السعة التي تحتاجها في الوقت المناسب.
  • يحسن الاستخدام الكلي للعقدة عن طريق binpacking - يقوم Karpenter بدفعات البودات المعلقة ثم حزمها بناءً على وحدة المعالجة المركزية والذاكرة ووحدات معالجة الرسومات المطلوبة ، مع مراعاة النفقات العامة للعقدة (على سبيل المثال ، مجموعة الموارد الخفية المطلوبة). بعد أن يتم حزم binpacks على نوع المثيل الأكثر كفاءة ، يأخذ Karpenter أنواع المثيلات الأخرى المشابهة أو الأكبر من الحزم الأكثر كفاءة ، ويمرر خيارات نوع المثيل إلى واجهة برمجة تطبيقات تسمى EC2 Fleet ، باتباع بعض أفضل الممارسات لتنويع المثيل لتحسين فرص الحصول على سعة الطلب.

أفضل الممارسات باستخدام Karpenter مع EMR على EKS

للحصول على أفضل الممارسات العامة مع Karpenter ، راجع أفضل ممارسات Karpenter. فيما يلي أشياء إضافية يجب مراعاتها مع EMR على EKS:

  • تجنب تكلفة نقل البيانات بين الألف إلى الياء إما عن طريق تكوين موفر Karpenter للتشغيل في منطقة توافر واحدة أو استخدام محدد العقدة or التقارب وعدم التقارب لجدولة السائق والمنفذين لنفس الوظيفة في منطقة توافر واحدة. انظر الكود التالي:
    nodeSelector: topology.kubernetes.io/zone: us-east-1a

  • تحسين التكلفة لأحمال عمل Spark باستخدام مثيلات EC2 Spot للمنفذين والمثيلات عند الطلب للسائق باستخدام محدد العقدة مع التسمية karpenter.sh/capacity-type في قوالب البود. نوصي باستخدام قوالب البودات لتحديد أقراص برنامج التشغيل لتعمل على مثيلات عند الطلب وأقراص المنفذ للتشغيل على مثيلات Spot. يسمح لك هذا بدمج مواصفات الموفر لأنك لا تحتاج إلى مواصفة اثنين لكل نوع مهمة. كما أنه يتبع أفضل ممارسة لاستخدام التخصيص المحدد في أنواع أحمال العمل وللحفاظ على مواصفات الموفر لدعم عدد أكبر من حالات الاستخدام.
  • عند استخدام مثيلات EC2 الموضعية ، قم بزيادة تنويع المثيل في تكوين الموفر للالتزام بـ أفضل الممارسات. لتحديد أنواع المثيلات المناسبة ، يمكنك استخدام ملحق محدد مثيل ec2، أداة CLI ومكتبة go التي توصي بأنواع المثيلات بناءً على معايير الموارد مثل وحدات المعالجة المركزية الافتراضية والذاكرة.

حل نظرة عامة

يقدم هذا المنشور مثالاً على كيفية إعداد كل من Cluster Autoscaler و Karpenter في مجموعة EKS ومقارنة تحسينات القياس التلقائي عن طريق تشغيل عينة EMR على حمل عمل EKS.

يوضح الرسم البياني التالي بنية هذا الحل.

نستخدم دعم قرار مجلس أداء معالجة المعاملات (TPC-DS) ، وهو معيار دعم القرار لتشغيل ثلاثة استعلامات Spark SQL بالتسلسل (q70-v2.4 ، q82-v2.4 ، q64-v2.4) برقم ثابت 50 منفذًا ، مقابل 17.7 مليار سجل ، ما يقرب من 924 جيجا بايت بيانات مضغوطة بتنسيق ملف باركيه. لمزيد من التفاصيل حول TPC-DS ، راجع eks-spark-standard GitHub repo.

نرسل نفس الوظيفة مع مواصفات مختلفة لبرنامج Spark ومنفذها لتقليد الوظائف المختلفة فقط لمراقبة سلوك التحجيم التلقائي و binpacking. نوصيك بتحديد الحجم المناسب لمنفذي Spark استنادًا إلى خصائص عبء العمل لأحمال العمل الإنتاجية.

الكود التالي هو مثال على تكوين Spark الذي ينتج عنه طلبات مواصفات pod لـ 4 vCPU و 15 غيغابايت:

--conf spark.executor.instances=50 --conf spark.driver.cores=4 --conf spark.driver.memory=10g --conf spark.driver.memoryOverhead=5g --conf spark.executor.cores=4 --conf spark.executor.memory=10g --conf spark.executor.memoryOverhead=5g

نستخدم قوالب البود لجدولة برامج تشغيل Spark على المثيلات عند الطلب والمنفذين في مثيلات EC2 Spot (والتي يمكن أن توفر ما يصل إلى 90٪ من أسعار المثيلات عند الطلب). إن المرونة الكامنة في Spark تجعل السائق يطلق منفذين جددًا ليحلوا محل المنفذين الذين يفشلون بسبب الانقطاعات الموضعية. انظر الكود التالي:

apiVersion: v1
kind: Pod
spec: nodeSelector: karpenter.sh/capacity-type: spot containers: - name: spark-kubernetes-executor apiVersion: v1
kind: Pod
spec: nodeSelector: karpenter.sh/capacity-type: on-demand containers: - name: spark-kubernetes-driver

المتطلبات الأساسية المسبقة

نحن نستخدم ملف سحابة AWS 9 IDE لتشغيل جميع التعليمات في جميع أنحاء هذا المنشور.

لإنشاء IDE الخاص بك ، قم بتشغيل الأوامر التالية بتنسيق أوس كلاودشيل. المنطقة الافتراضية هي us-east-1، ولكن يمكنك تغييره إذا لزم الأمر.

# clone the repo
git clone https://github.com/black-mirror-1/karpenter-for-emr-on-eks.git
cd karpenter-for-emr-on-eks
./setup/create-cloud9-ide.sh

انتقل إلى AWS Cloud9 IDE باستخدام عنوان URL من إخراج البرنامج النصي.

قم بتثبيت الأدوات على AWS Cloud9 IDE

قم بتثبيت الأدوات التالية المطلوبة في بيئة AWS Cloud9 من خلال تشغيل البرنامج النصي:

قم بتشغيل الإرشادات التالية في بيئة AWS Cloud9 وليس CloudShell.

  1. استنساخ مستودع GitHub:
    cd ~/environment
    git clone https://github.com/black-mirror-1/karpenter-for-emr-on-eks.git
    cd ~/environment/karpenter-for-emr-on-eks

  2. قم بإعداد متغيرات البيئة المطلوبة. لا تتردد في تعديل الكود التالي وفقًا لاحتياجاتك:
    # Install envsubst (from GNU gettext utilities) and bash-completion
    sudo yum -y install jq gettext bash-completion moreutils # Setup env variables required
    export EKSCLUSTER_NAME=aws-blog
    export EKS_VERSION="1.23"
    # get the link to the same version as EKS from here https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html
    export KUBECTL_URL="https://s3.us-west-2.amazonaws.com/amazon-eks/1.23.7/2022-06-29/bin/linux/amd64/kubectl"
    export HELM_VERSION="v3.9.4"
    export KARPENTER_VERSION="v0.18.1"
    # get the most recent matching version of the Cluster Autoscaler from here https://github.com/kubernetes/autoscaler/releases
    export CAS_VERSION="v1.23.1"

  3. قم بتثبيت أدوات AWS Cloud9 CLI:
    cd ~/environment/karpenter-for-emr-on-eks
    ./setup/c9-install-tools.sh

توفير البنية التحتية

قمنا بإعداد الموارد التالية باستخدام النص البرمجي للبنية التحتية للتوفير:

  1. قم بإنشاء EMR على EKS والبنية التحتية Karpenter:
    cd ~/environment/karpenter-for-emr-on-eks
    ./setup/create-eks-emr-infra.sh

  2. تحقق من صحة الإعداد:
    # Should have results that are running
    kubectl get nodes
    kubectl get pods -n karpenter
    kubectl get po -n kube-system -l app.kubernetes.io/instance=cluster-autoscaler
    kubectl get po -n prometheus

فهم تكوينات كاربنتر

نظرًا لأن عبء العمل النموذجي يحتوي على مواصفات السائق والمنفذ بأحجام مختلفة ، فقد حددنا مثيلات من عائلات c5 و c5a و c5d و c5ad و c6a و m4 و m5 و m5a و m5d و m5ad و m6a ذات الأحجام 2xlarge و 4xlarge ، 8xlarge و 9 xlarge لعبء العمل لدينا باستخدام محدد مثيل amazon-ec2 CLI. باستخدام CAS ، نحتاج إلى إنشاء إجمالي 12 مجموعة عقدة ، كما هو موضح في eksctl-config.yaml، ولكن يمكنك تحديد نفس القيود في Karpenter باستخدام مزود واحد ، كما هو موضح في الكود التالي:

apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata: name: default
spec: provider: launchTemplate: {EKSCLUSTER_NAME}-karpenter-launchtemplate subnetSelector: karpenter.sh/discovery: {EKSCLUSTER_NAME} labels: app: kspark requirements: - key: "karpenter.sh/capacity-type" operator: In values: ["on-demand","spot"] - key: "kubernetes.io/arch" operator: In values: ["amd64"] - key: karpenter.k8s.aws/instance-family operator: In values: [c5, c5a, c5d, c5ad, m5, c6a] - key: karpenter.k8s.aws/instance-size operator: In values: [2xlarge, 4xlarge, 8xlarge, 9xlarge] - key: "topology.kubernetes.io/zone" operator: In values: ["{AWS_REGION}a"] limits: resources: cpu: "2000" ttlSecondsAfterEmpty: 30

لقد قمنا بإعداد كلاً من أجهزة القياس التلقائية لتصغير العقد الفارغة لمدة 30 ثانية باستخدام ttlSecondsAfterEmpty في كاربنتر و --scale-down-unneeded-time في CAS.

سيحاول Karpenter حسب التصميم تحقيق التعبئة الأكثر كفاءة للقرون على عقدة بناءً على وحدة المعالجة المركزية والذاكرة ووحدات معالجة الرسومات المطلوبة.

قم بتشغيل نموذج حمل العمل

لتشغيل نموذج حمل العمل ، أكمل الخطوات التالية:

  1. لنستعرض ملف واجهة سطر الأوامر AWS (AWS CLI) لإرسال عينة من الوظائف:
    aws emr-containers start-job-run --virtual-cluster-id $VIRTUAL_CLUSTER_ID --name karpenter-benchmark-${CORES}vcpu-${MEMORY}gb --execution-role-arn $EMR_ROLE_ARN --release-label emr-6.5.0-latest --job-driver '{ "sparkSubmitJobDriver": { "entryPoint": "local:///usr/lib/spark/examples/jars/eks-spark-benchmark-assembly-1.0.jar", "entryPointArguments":["s3://blogpost-sparkoneks-us-east-1/blog/BLOG_TPCDS-TEST-3T-partitioned","s3://'$S3BUCKET'/EMRONEKS_TPCDS-TEST-3T-RESULT-KA","/opt/tpcds-kit/tools","parquet","3000","1","false","q70-v2.4,q82-v2.4,q64-v2.4","true"], "sparkSubmitParameters": "--class com.amazonaws.eks.tpcds.BenchmarkSQL --conf spark.executor.instances=50 --conf spark.driver.cores='$CORES' --conf spark.driver.memory='$EXEC_MEMORY'g --conf spark.executor.cores='$CORES' --conf spark.executor.memory='$EXEC_MEMORY'g"}}' --configuration-overrides '{ "applicationConfiguration": [ { "classification": "spark-defaults", "properties": { "spark.kubernetes.node.selector.app": "kspark", "spark.kubernetes.node.selector.topology.kubernetes.io/zone": "'${AWS_REGION}'a", "spark.kubernetes.container.image": "'$ECR_URL'/eks-spark-benchmark:emr6.5", "spark.kubernetes.driver.podTemplateFile": "s3://'$S3BUCKET'/pod-template/karpenter-driver-pod-template.yaml", "spark.kubernetes.executor.podTemplateFile": "s3://'$S3BUCKET'/pod-template/karpenter-executor-pod-template.yaml", "spark.network.timeout": "2000s", "spark.executor.heartbeatInterval": "300s", "spark.kubernetes.executor.limit.cores": "'$CORES'", "spark.executor.memoryOverhead": "'$MEMORY_OVERHEAD'G", "spark.driver.memoryOverhead": "'$MEMORY_OVERHEAD'G", "spark.kubernetes.executor.podNamePrefix": "karpenter-'$CORES'vcpu-'$MEMORY'gb", "spark.executor.defaultJavaOptions": "-verbose:gc -XX:+UseG1GC", "spark.driver.defaultJavaOptions": "-verbose:gc -XX:+UseG1GC", "spark.ui.prometheus.enabled":"true", "spark.executor.processTreeMetrics.enabled":"true", "spark.kubernetes.driver.annotation.prometheus.io/scrape":"true", "spark.kubernetes.driver.annotation.prometheus.io/path":"/metrics/executors/prometheus/", "spark.kubernetes.driver.annotation.prometheus.io/port":"4040", "spark.kubernetes.driver.service.annotation.prometheus.io/scrape":"true", "spark.kubernetes.driver.service.annotation.prometheus.io/path":"/metrics/driver/prometheus/", "spark.kubernetes.driver.service.annotation.prometheus.io/port":"4040", "spark.metrics.conf.*.sink.prometheusServlet.class":"org.apache.spark.metrics.sink.PrometheusServlet", "spark.metrics.conf.*.sink.prometheusServlet.path":"/metrics/driver/prometheus/", "spark.metrics.conf.master.sink.prometheusServlet.path":"/metrics/master/prometheus/", "spark.metrics.conf.applications.sink.prometheusServlet.path":"/metrics/applications/prometheus/" }} ]}'

  2. قم بإرسال أربع وظائف مع مختلف وحدات المعالجة المركزية الافتراضية الخاصة بالسائق والمنفذ وأحجام الذاكرة على كاربنتر:
    # the arguments are vcpus and memory
    export EMRCLUSTER_NAME=${EKSCLUSTER_NAME}-emr
    ./sample-workloads/emr6.5-tpcds-karpenter.sh 4 7
    ./sample-workloads/emr6.5-tpcds-karpenter.sh 8 15
    ./sample-workloads/emr6.5-tpcds-karpenter.sh 4 15
    ./sample-workloads/emr6.5-tpcds-karpenter.sh 8 31 

  3. لمراقبة حالة القياس التلقائي للبودات في الوقت الفعلي ، افتح محطة طرفية جديدة في Cloud9 IDE وقم بتشغيل الأمر التالي (لا يتم إرجاع أي شيء في البداية):
    watch -n1 "kubectl get pod -n emr-karpenter"

  4. راقب مثيل EC2 وحالة القياس التلقائي للعقدة في علامة تبويب المحطة الطرفية الثانية عن طريق تشغيل الأمر التالي (حسب التصميم ، جداول Karpenter في منطقة توافر الخدمات أ):
    watch -n1 "kubectl get node --label-columns=node.kubernetes.io/instance-type,karpenter.sh/capacity-type,topology.kubernetes.io/zone,app -l app=kspark"

قارن مع Cluster Autoscaler (اختياري)

لقد قمنا بإعداد Cluster Autoscaler أثناء خطوة إعداد البنية التحتية بالتكوين التالي:

  • إطلاق عقد EC2 في منطقة توافر الخدمات ب
  • تحتوي على 12 مجموعة عقدة (6 لكل مجموعة عند الطلب وفي الموقع)
  • تصغير العقد غير الضرورية بعد 30 ثانية باستخدام --scale-down-unneeded-time
  • استخدم أقل نفايات المتوسع على CAS ، والتي يمكنها تحديد مجموعة العقدة التي ستحتوي على أقل وحدة معالجة مركزية خاملة لتحقيق كفاءة binpacking
  1. قم بإرسال أربع وظائف مع مختلف وحدات المعالجة المركزية الافتراضية (vCPUs) للسائق والمنفذ وأحجام الذاكرة على CAS:
    # the arguments are vcpus and memory
    ./sample-workloads/emr6.5-tpcds-ca.sh 4 7
    ./sample-workloads/emr6.5-tpcds-ca.sh 8 15
    ./sample-workloads/emr6.5-tpcds-ca.sh 4 15
    ./sample-workloads/emr6.5-tpcds-ca.sh 8 31

  2. لمراقبة حالة القياس التلقائي للبودات في الوقت الفعلي ، افتح محطة طرفية جديدة في Cloud9 IDE وقم بتشغيل الأمر التالي (لا يتم إرجاع أي شيء في البداية):
    watch -n1 "kubectl get pod -n emr-ca"

  3. راقب مثيل EC2 وحالة القياس التلقائي للعقدة في علامة تبويب المحطة الطرفية الثانية عن طريق تشغيل الأمر التالي (حسب التصميم ، جداول CAS في منطقة توافر الخدمات ب):
    watch -n1 "kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone,app -l app=caspark"

الملاحظات

الوقت من إنشاء الكبسولة إلى الجدولة في المتوسط ​​أقل مع Karpenter منه في CAS ، كما هو موضح في الشكل التالي ؛ يمكنك أن ترى فرقًا ملحوظًا عند تشغيل أحمال عمل كبيرة الحجم.

كما هو موضح في الأشكال التالية ، مع اكتمال الوظائف ، تمكن كاربنتر من تقليص العقد غير المطلوبة في غضون ثوانٍ. في المقابل ، تستغرق CAS دقائق ، لأنها ترسل إشارة إلى مجموعات العقد ، مضيفة زمن انتقال إضافي. وهذا بدوره يساعد على تقليل التكاليف الإجمالية عن طريق تقليل عدد الثواني التي يتم تشغيل مثيلات EC2 غير الضرورية فيها.

تنظيف

لتنظيف بيئتك ، احذف جميع الموارد التي تم إنشاؤها بترتيب عكسي عن طريق تشغيل البرنامج النصي للتنظيف:

export EKSCLUSTER_NAME=aws-blog
cd ~/environment/karpenter-for-emr-on-eks
./setup/cleanup.sh

وفي الختام

في هذا المنشور ، أوضحنا لك كيفية استخدام Karpenter لتبسيط توفير عقدة EKS ، وتسريع التحجيم التلقائي لـ EMR على أحمال عمل EKS. نحن نشجعك على تجربة Karpenter وتقديم أي ملاحظات عن طريق إنشاء GitHub قضية.

لمزيد من القراءة


حول المؤلف

تشانغبين جونج هو مهندس حلول رئيسي في Amazon Web Services. يتعامل مع العملاء لإنشاء حلول مبتكرة تعالج مشاكل عمل العملاء وتسريع اعتماد خدمات AWS. يستمتع Changbin بالقراءة والجري والسفر في أوقات فراغه.

سانديب بالافالاسا هي شركة Sr. Specialist Containers SA في Amazon Web Services. وهو رائد في مجال تكنولوجيا البرمجيات يتمتع بخبرة تزيد عن 12 عامًا في بناء أنظمة برمجيات موزعة على نطاق واسع. بدأت حياته المهنية بالتركيز على المراقبة وإمكانية الملاحظة ولديه خلفية قوية في الهندسة السحابية. يحب العمل على الأنظمة الموزعة وهو متحمس للحديث عن تصميم هندسة الخدمات المصغرة. تتركز اهتماماته الحالية في مجالات خدمات الحاويات والتقنيات التي لا تحتاج إلى خادم.

بقعة_صورة

أحدث المعلومات الاستخباراتية

بقعة_صورة