Как масштабировать кластер Kubernetes при ограничении затрат на GCP - PullRequest
2 голосов
/ 06 мая 2020

У нас есть кластер GKE, настроенный на облачной платформе Google.

У нас есть деятельность, которая требует «всплесков» вычислительной мощности.

Представьте, что мы обычно выполняем в среднем 100 вычислений в час, а затем внезапно нам нужно иметь возможность обработать 100000 менее чем за две минуты. Однако в большинстве случаев все находится в состоянии ожидания.

Мы не хотим платить за простаивающие серверы в 99% времени и хотим масштабировать кластеры в зависимости от фактического использования (сохранение данных не требуется, серверы могут быть удаленным впоследствии). Я просмотрел документацию, доступную на kubernetes, относительно автоматического масштабирования, для добавления дополнительных модулей с HPA и добавления дополнительных узлов с помощью кластерного автомасштабирования

Однако это не похоже любое из этих решений фактически снизило бы наши затраты или повысило бы производительность, потому что они, кажется, не масштабируются дальше плана GCP:

Представьте, что у нас есть план Google с 8 процессорами. Насколько я понимаю, если мы добавим больше узлов с автоматическим масштабированием кластера, мы просто вместо того, чтобы иметь, например, 2 узла, использующие 4 процессора каждый, у нас будет 4 узла, каждый из которых использует 2 процессора. Но общая доступная вычислительная мощность по-прежнему составит 8 ЦП. Те же рассуждения go для HPA с большим количеством модулей вместо большего количества узлов.

Если у нас есть план оплаты с 8 процессорами, но мы используем только 4 из них, я понимаю, что мы все равно получаем счет за 8, поэтому сокращение не является действительно полезно.

Нам нужно автоматическое масштабирование, чтобы временно изменить наш план платежей (представьте, с n1-standard-8 на n1-standard-16) и получить действительно новую вычислительную мощность.

Я могу Не верю, что мы единственные, у кого есть этот вариант использования, но я нигде не могу найти никакой документации по нему! Я что-то неправильно понял?

1 Ответ

2 голосов
/ 08 мая 2020

TL; DR:


Цены GKE:

  • От Стоимость GKE :

    Начиная с 6 июня 2020 года GKE будет взимать плату за управление кластером в размере 0,10 доллара США за кластер в час. К оплате за управление кластером применяются следующие условия:

    • Один зональный кластер на каждую учетную запись бесплатно .
    • Плата фиксированная. , независимо от размера и топологии кластера.
    • Биллинг рассчитывается на основе в секунду для каждого кластера. Общая сумма округляется до ближайшего цента в конце каждого месяца.
  • От Цены на рабочие узлы :

    GKE использует экземпляры Compute Engine для рабочих узлов в кластере . Вам будет выставлен счет за каждый из этих экземпляров в соответствии с ценами Compute Engine , до тех пор, пока узлы не будут удалены . Счета за ресурсы Compute Engine выставляются посекундно с минимальной стоимостью использования за одну минуту.

  • Вход, Cluster Autoscaler :

    автоматически изменяет размер пулов узлов кластера GKE в зависимости от требований ваших рабочих нагрузок. Когда потребность высока, средство автомасштабирования кластера добавляет узлы в пул узлов. Когда спрос невелик, автоматическое масштабирование кластера сокращается до минимального размера, который вы указали. Это может повысить доступность ваших рабочих нагрузок, когда вам это нужно, при одновременном контроле затрат.


  • Cluster Autoscaler не может масштабировать весь кластер до нуля, по крайней мере один узел всегда должен быть доступен в кластере для запуска системных модулей.
  • Поскольку у вас уже есть постоянная рабочая нагрузка, это не будет проблемой, мы создадим новый пул узлов :

    A пул узлов - это группа узлов в кластере, имеющих одинаковую конфигурацию. В каждом кластере есть как минимум один пул узлов по умолчанию , но при необходимости вы можете добавить и другие пулы узлов.

  • В этом примере я создам два узла пулы:

    • Пул узлов по умолчанию с фиксированным размером одного узла с небольшим размером экземпляра (имитирующий кластер, который у вас уже есть).
    • Второй пул узлов с большей вычислительной мощностью для выполнения заданий (я назову это пулом мощности).
      • Выберите тип машины с мощностью, необходимой для выполнения ваших заданий AI, для этого примера я создам n1-standard-8.
      • Этот пул мощности будет иметь автоматическое масштабирование, позволяющее макс. 4 узла, минимум 0 узлов.
      • Если вам нравится добавлять графические процессоры, вы можете проверить следующее: Гидравлическое масштабирование почти до нуля + графические процессоры .

Порчи и допуски:

  • В пуле мощности будут выполняться только задания, связанные с рабочей нагрузкой AI, для этого используйте селектор узлов в модулях заданий, чтобы убедиться, что они работают в узлах пула мощности.
  • Установите правило анти-сродства , чтобы убедиться, что два ваших модуля обучения не могут быть запланированным на том же узле (оптимизируя соотношение цены и производительности, это необязательно в зависимости от вашей рабочей нагрузки).
  • Добавьте taint в пул мощности, чтобы избежать других рабочих нагрузок (и системных ресурсы) для планирования в пуле с автоматическим масштабированием.
  • Добавьте допуски в задания AI чтобы позволить им работать на этих узлах.

Воспроизведение:

  • Создайте кластер с постоянным пулом по умолчанию:
PROJECT_ID="YOUR_PROJECT_ID"  
GCP_ZONE="CLUSTER_ZONE"  
GKE_CLUSTER_NAME="CLUSTER_NAME"  
AUTOSCALE_POOL="power-pool"  

gcloud container clusters create ${GKE_CLUSTER_NAME} \
--machine-type="n1-standard-1" \
--num-nodes=1 \
--zone=${GCP_ZONE} \
--project=${PROJECT_ID}
  • Создание пула с автоматическим масштабированием:
gcloud container node-pools create ${GKE_BURST_POOL} \
--cluster=${GKE_CLUSTER_NAME} \
--machine-type=n1-standard-8 \
--node-labels=load=on-demand \
--node-taints=reserved-pool=true:NoSchedule \
--enable-autoscaling \
--min-nodes=0 \
--max-nodes=4 \
--zone=${GCP_ZONE} \
--project=${PROJECT_ID}
  • Примечание о параметрах:

    • --node-labels=load=on-demand: добавьте метку к узлам в пуле мощности, чтобы можно было выбирать их в нашем задании AI с помощью селектора узлов .
    • --node-taints=reserved-pool=true:NoSchedule: добавьте taint к узлам, чтобы предотвратить случайное планирование любой другой рабочей нагрузки в этом пуле узлов.
  • Здесь вы можете увидеть два созданных нами пула: stati c пул с 1 узлом и автоматически масштабируемый пул с 0–4 узлами.

enter image description here

Поскольку у нас нет рабочей нагрузки, выполняемой в автоматически масштабируемом пуле узлов, он показывает 0 работающих узлов (и с бесплатно, пока нет исполняемого узла).

  • Теперь мы создадим задание, которое создаст 4 параллельных модуля, работающих в течение 5 минут.
    • Это задание будет иметь следующие параметры, чтобы отличать его от обычных модулей:
    • parallelism: 4: использовать все 4 узла для повышения производительности
    • nodeSelector.load: on-demand: назначать узлы с этой меткой.
    • podAntiAffinity: чтобы объявить, что мы не хотим, чтобы два модуля с одинаковой меткой app: greedy-job работали на одном узле (необязательно).
    • tolerations: чтобы соответствовать допустимости заражения, которое мы прикрепили к узлам, поэтому эти поды могут быть запланированы на этих узлах.
apiVersion: batch/v1  
kind: Job  
metadata:  
  name: greedy-job  
spec:  
  parallelism: 4  
  template:  
    metadata:  
      name: greedy-job  
      labels: 
        app: greedy-app  
    spec:  
      containers:  
      - name: busybox  
        image: busybox  
        args:  
        - sleep  
        - "300"  
      nodeSelector: 
        load: on-demand 
      affinity:  
        podAntiAffinity:  
          requiredDuringSchedulingIgnoredDuringExecution:  
          - labelSelector:  
              matchExpressions:  
              - key: app  
                operator: In  
                values:  
                - greedy-app  
            topologyKey: "kubernetes.io/hostname"  
      tolerations:  
      - key: reserved-pool  
        operator: Equal  
        value: "true"  
        effect: NoSchedule  
      restartPolicy: OnFailure
  • Теперь, когда наш кластер находится в режиме ожидания, мы будем использовать только что созданное задание yaml (я назову его greedyjob.yaml). Это задание будет запускать четыре процесса, которые будут выполняться параллельно и завершатся примерно через 5 минут.
$ kubectl get nodes
NAME                                                  STATUS   ROLES    AGE   VERSION
gke-autoscale-to-zero-cl-default-pool-9f6d80d3-x9lb   Ready    <none>   42m   v1.14.10-gke.27

$ kubectl get pods
No resources found in default namespace.

$ kubectl apply -f greedyjob.yaml 
job.batch/greedy-job created

$ kubectl get pods
NAME               READY   STATUS    RESTARTS   AGE
greedy-job-2xbvx   0/1     Pending   0          11s
greedy-job-72j8r   0/1     Pending   0          11s
greedy-job-9dfdt   0/1     Pending   0          11s
greedy-job-wqct9   0/1     Pending   0          11s
  • Наше задание было применено, но ожидает выполнения, давайте посмотрим, что происходит в этих модулях:
$ kubectl describe pod greedy-job-2xbvx
...
Events:
  Type     Reason            Age                From                Message
  ----     ------            ----               ----                -------
  Warning  FailedScheduling  28s (x2 over 28s)  default-scheduler   0/1 nodes are available: 1 node(s) didn't match node selector.
  Normal   TriggeredScaleUp  23s                cluster-autoscaler  pod triggered scale-up: [{https://content.googleapis.com/compute/v1/projects/owilliam/zones/us-central1-b/instanceGroups/gke-autoscale-to-zero-clus-power-pool-564148fd-grp 0->1 (max: 4)}]
  • Модуль не может быть запланирован на текущем узле из-за правил, которые мы определили, это запускает процедуру масштабирования в нашем пуле мощности. Это очень динамичный c процесс, через 90 секунд первый узел запускается:
$ kubectl get pods
NAME               READY   STATUS              RESTARTS   AGE
greedy-job-2xbvx   0/1     Pending             0          93s
greedy-job-72j8r   0/1     ContainerCreating   0          93s
greedy-job-9dfdt   0/1     Pending             0          93s
greedy-job-wqct9   0/1     Pending             0          93s

$ kubectl nodes
NAME                                                  STATUS   ROLES    AGE   VERSION
gke-autoscale-to-zero-cl-default-pool-9f6d80d3-x9lb   Ready    <none>   44m   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-qxkw   Ready    <none>   11s   v1.14.10-gke.27
  • Поскольку мы установили правила анти-сродства подов, второй под может ' t быть запланированным на узле, который был запущен и запускает следующее масштабирование, посмотрите события на втором модуле:
$ k describe pod greedy-job-2xbvx
...
Events:
  Type     Reason            Age                  From                Message
  ----     ------            ----                 ----                -------
  Normal   TriggeredScaleUp  2m45s                cluster-autoscaler  pod triggered scale-up: [{https://content.googleapis.com/compute/v1/projects/owilliam/zones/us-central1-b/instanceGroups/gke-autoscale-to-zero-clus-power-pool-564148fd-grp 0->1 (max: 4)}]
  Warning  FailedScheduling  93s (x3 over 2m50s)  default-scheduler   0/1 nodes are available: 1 node(s) didn't match node selector.
  Warning  FailedScheduling  79s (x3 over 83s)    default-scheduler   0/2 nodes are available: 1 node(s) didn't match node selector, 1 node(s) had taints that the pod didn't tolerate.
  Normal   TriggeredScaleUp  62s                  cluster-autoscaler  pod triggered scale-up: [{https://content.googleapis.com/compute/v1/projects/owilliam/zones/us-central1-b/instanceGroups/gke-autoscale-to-zero-clus-power-pool-564148fd-grp 1->2 (max: 4)}]
  Warning  FailedScheduling  3s (x3 over 68s)     default-scheduler   0/2 nodes are available: 1 node(s) didn't match node selector, 1 node(s) didn't match pod affinity/anti-affinity, 1 node(s) didn't satisfy existing pods anti-affinity rules.
  • Тот же процесс повторяется до тех пор, пока не будут выполнены все требования удовлетворены:
$ kubectl get pods
NAME               READY   STATUS    RESTARTS   AGE
greedy-job-2xbvx   0/1     Pending   0          3m39s
greedy-job-72j8r   1/1     Running   0          3m39s
greedy-job-9dfdt   0/1     Pending   0          3m39s
greedy-job-wqct9   1/1     Running   0          3m39s

$ kubectl get nodes
NAME                                                  STATUS   ROLES    AGE     VERSION
gke-autoscale-to-zero-cl-default-pool-9f6d80d3-x9lb   Ready    <none>   46m     v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-qxkw   Ready    <none>   2m16s   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-sf6q   Ready    <none>   28s     v1.14.10-gke.27

$ kubectl get pods
NAME               READY   STATUS    RESTARTS   AGE
greedy-job-2xbvx   0/1     Pending   0          5m19s
greedy-job-72j8r   1/1     Running   0          5m19s
greedy-job-9dfdt   1/1     Running   0          5m19s
greedy-job-wqct9   1/1     Running   0          5m19s

$ kubectl get nodes
NAME                                                  STATUS   ROLES    AGE     VERSION
gke-autoscale-to-zero-cl-default-pool-9f6d80d3-x9lb   Ready    <none>   48m     v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-39m2   Ready    <none>   63s     v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-qxkw   Ready    <none>   4m8s    v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-sf6q   Ready    <none>   2m20s   v1.14.10-gke.27

$ kubectl get pods
NAME               READY   STATUS    RESTARTS   AGE
greedy-job-2xbvx   1/1     Running   0          6m12s
greedy-job-72j8r   1/1     Running   0          6m12s
greedy-job-9dfdt   1/1     Running   0          6m12s
greedy-job-wqct9   1/1     Running   0          6m12s

$ kubectl get nodes
NAME                                                  STATUS   ROLES    AGE     VERSION
gke-autoscale-to-zero-cl-default-pool-9f6d80d3-x9lb   Ready    <none>   48m     v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-39m2   Ready    <none>   113s    v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-ggxv   Ready    <none>   26s     v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-qxkw   Ready    <none>   4m58s   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-sf6q   Ready    <none>   3m10s   v1.14.10-gke.27

enter image description here enter image description here Здесь мы видим, что все узлы теперь запущены и работают (таким образом, выставлен счет по секундам)

  • Теперь все задания выполняются, через несколько минут задания завершают свои задачи:
$ kubectl get pods
NAME               READY   STATUS      RESTARTS   AGE
greedy-job-2xbvx   1/1     Running     0          7m22s
greedy-job-72j8r   0/1     Completed   0          7m22s
greedy-job-9dfdt   1/1     Running     0          7m22s
greedy-job-wqct9   1/1     Running     0          7m22s

$ kubectl get pods
NAME               READY   STATUS      RESTARTS   AGE
greedy-job-2xbvx   0/1     Completed   0          11m
greedy-job-72j8r   0/1     Completed   0          11m
greedy-job-9dfdt   0/1     Completed   0          11m
greedy-job-wqct9   0/1     Completed   0          11m
  • После завершения задачи autoscaler начинает уменьшение размера кластера.
  • Вы можете узнать больше о правилах для этого процесса здесь: GKE Cluster AutoScaler
$ while true; do kubectl get nodes ; sleep 60; done
NAME                                                  STATUS   ROLES    AGE     VERSION
gke-autoscale-to-zero-cl-default-pool-9f6d80d3-x9lb   Ready    <none>   54m     v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-39m2   Ready    <none>   7m26s   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-ggxv   Ready    <none>   5m59s   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-qxkw   Ready    <none>   10m     v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-sf6q   Ready    <none>   8m43s   v1.14.10-gke.27

NAME                                                  STATUS     ROLES    AGE   VERSION
gke-autoscale-to-zero-cl-default-pool-9f6d80d3-x9lb   Ready      <none>   62m   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-39m2   Ready      <none>   15m   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-ggxv   Ready      <none>   14m   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-qxkw   Ready      <none>   18m   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-sf6q   NotReady   <none>   16m   v1.14.10-gke.27
  • Once conditions выполнены, автомасштабирование помечает узел как NotReady и начинает их удаление:
NAME                                                  STATUS     ROLES    AGE   VERSION
gke-autoscale-to-zero-cl-default-pool-9f6d80d3-x9lb   Ready      <none>   64m   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-39m2   NotReady   <none>   17m   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-ggxv   NotReady   <none>   16m   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-qxkw   Ready      <none>   20m   v1.14.10-gke.27

NAME                                                  STATUS     ROLES    AGE   VERSION
gke-autoscale-to-zero-cl-default-pool-9f6d80d3-x9lb   Ready      <none>   65m   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-39m2   NotReady   <none>   18m   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-ggxv   NotReady   <none>   17m   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-qxkw   NotReady   <none>   21m   v1.14.10-gke.27

NAME                                                  STATUS     ROLES    AGE   VERSION
gke-autoscale-to-zero-cl-default-pool-9f6d80d3-x9lb   Ready      <none>   66m   v1.14.10-gke.27
gke-autoscale-to-zero-clus-power-pool-564148fd-ggxv   NotReady   <none>   18m   v1.14.10-gke.27

NAME                                                  STATUS   ROLES    AGE   VERSION
gke-autoscale-to-zero-cl-default-pool-9f6d80d3-x9lb   Ready    <none>   67m   v1.14.10-gke.27
* 125 7 *
  • Вот подтверждение того, что узлы были удалены из GKE и из виртуальных машин (помните, что каждый узел представляет собой виртуальную машину, выставленную как Compute Engine):

Compute Engine: ( обратите внимание, что gke-cluster-1-default-pool из другого кластера, я добавил его на снимок экрана, чтобы показать вам, что в кластере gke-autoscale-to-zero нет другого узла, кроме постоянного постоянного.) enter image description here

GKE: enter image description here


Заключительные мысли:

При уменьшении масштаба автоматическое масштабирование кластера учитывает планирование и Правила выселения установлены для капсул. Эти ограничения могут предотвратить удаление узла автомасштабированием. Удаление узла можно предотвратить, если он содержит модуль с любым из следующих условий: PodDisruptionBudget приложения также может предотвратить автомасштабирование; если удаление узлов приведет к превышению бюджета, кластер не масштабируется. 5 минут до завершения sh уменьшения масштаба резервного узла, обеспечивая ОГРОМНОЕ улучшение вашего биллинга.

вытесняемые виртуальные машины - это Compute Engine экземпляры виртуальных машин , срок службы которых составляет не более 24 часов и не дает никаких гарантий доступности. Вытесняемые виртуальные машины на ниже , чем стандартные виртуальные машины Compute Engine, и предлагают те же типы машин и опции.

Я знаю, что вы все еще рассматриваете лучшую архитектуру для ваше приложение.

Использование APP Engine и IA Platform также являются оптимальными решениями, но, поскольку вы в настоящее время выполняете свою рабочую нагрузку на GKE, я хотел бы показать вам пример по запросу.

Если у вас есть вопросы, дайте мне знать в комментариях.

...