TL; DR:
- Создание небольшого постоянного пула узлов
- Создание мощного пула узлов, который можно масштабировать до нуля (и прекратить биллинг), пока не используется.
- Используемые инструменты:
Цены 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}
Поскольку у нас нет рабочей нагрузки, выполняемой в автоматически масштабируемом пуле узлов, он показывает 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
Здесь мы видим, что все узлы теперь запущены и работают (таким образом, выставлен счет по секундам)
- Теперь все задания выполняются, через несколько минут задания завершают свои задачи:
$ 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
нет другого узла, кроме постоянного постоянного.)
GKE:
Заключительные мысли:
При уменьшении масштаба автоматическое масштабирование кластера учитывает планирование и Правила выселения установлены для капсул. Эти ограничения могут предотвратить удаление узла автомасштабированием. Удаление узла можно предотвратить, если он содержит модуль с любым из следующих условий: PodDisruptionBudget приложения также может предотвратить автомасштабирование; если удаление узлов приведет к превышению бюджета, кластер не масштабируется. 5 минут до завершения sh уменьшения масштаба резервного узла, обеспечивая ОГРОМНОЕ улучшение вашего биллинга.
вытесняемые виртуальные машины - это Compute Engine экземпляры виртуальных машин , срок службы которых составляет не более 24 часов и не дает никаких гарантий доступности. Вытесняемые виртуальные машины на ниже , чем стандартные виртуальные машины Compute Engine, и предлагают те же типы машин и опции.
Я знаю, что вы все еще рассматриваете лучшую архитектуру для ваше приложение.
Использование APP Engine и IA Platform также являются оптимальными решениями, но, поскольку вы в настоящее время выполняете свою рабочую нагрузку на GKE, я хотел бы показать вам пример по запросу.
Если у вас есть вопросы, дайте мне знать в комментариях.