Este tópico descreve como executar jobs do Spark no ACK usando recursos de Elastic Container Instance (ECI). Configure estratégias de agendamento adequadas para criar pods ECI sob demanda e pagar apenas pelos recursos consumidos. Essa abordagem reduz custos com ociosidade e torna a execução de jobs do Spark mais econômica.
Pré-requisitos
Você precisa de:
ack-virtual-node implantado no cluster (expõe a capacidade do ECI como nós virtuais).
Como funciona
Ao implantar o ack-virtual-node, cada nó ECI recebe um taint virtual-kubelet.io/provider=alibabacloud:NoSchedule. Sem uma toleration correspondente, o agendador ignora os nós ECI e aloca todos os pods no ECS. Para direcionar pods do Spark ao ECI, adicione tolerations e regras de afinidade de nó à especificação do SparkApplication.
O ECI executa cada contêiner em uma sandbox virtual leve e isola totalmente os pods. No modelo de pagamento conforme o uso, você paga apenas pela CPU e memória que cada pod consome durante a execução, sem custos por capacidade ociosa do nó.
Posicionamento de driver versus executor
Pods de driver e executor apresentam falhas diferentes, o que afeta o posicionamento:
Se um executor falhar, o Spark o substitui automaticamente. Executors são stateless e tolerantes a falhas.
Se o driver falhar, todo o job falha e reinicia do início. Mantenha o driver estável.
Mantenha o driver em nós ECS confiáveis e direcione os executors para instâncias ECI ou preemptíveis de menor custo quando o preço for um fator crítico.
Características do ECI para cargas de trabalho do Spark:
|
Característica |
Valor |
|
Escala |
Mais de 50.000 pods em um cluster ACK Serverless; sem configuração extra |
|
Velocidade de provisionamento |
Milhares de pods em segundos |
|
Faturamento |
Pagamento conforme o uso; instâncias preemptíveis disponíveis para redução adicional de custos |
Escolha uma estratégia de agendamento
Três estratégias cobrem cenários comuns de implantação:
|
Estratégia |
Quando usar |
Posicionamento do driver |
Posicionamento do executor |
|
Apenas ECS |
Cargas de trabalho previsíveis e constantes |
ECS |
ECS |
|
Apenas ECI |
Jobs em lote ou com picos que exigem elasticidade total |
ECI |
ECI |
|
ECS prioritário com fallback para ECI |
Carga normal no ECS; expansão automática para ECI durante picos |
ECS (preferencial) |
ECS (preferencial), ECI (overflow) |
Para controle refinado, como limitar pods por tipo de recurso, use uma ResourcePolicy.
Agende jobs do Spark em nós ECS e ECI
Todos os exemplos usam SparkApplication (CRD sparkoperator.k8s.io/v1beta2) com estas configurações base:
|
Campo |
Valor |
|
Imagem |
|
|
|
|
|
|
|
|
|
|
Apenas ECS
Nenhuma toleration ou afinidade é necessária. O taint padrão do ECI impede que o agendador coloque pods nesses nós.
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
name: spark-pi-ecs-only
namespace: default
spec:
type: Scala
mode: cluster
image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/spark:3.5.2
mainClass: org.apache.spark.examples.SparkPi
mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.5.2.jar
arguments:
- "5000"
sparkVersion: 3.5.2
driver:
cores: 1
coreLimit: 1200m
memory: 512m
serviceAccount: spark-operator-spark
executor:
instances: 2
cores: 2
memory: 4g
Comportamento esperado: Todos os pods de driver e executor são agendados em nós ECS. Se a capacidade do ECS for insuficiente, os pods permanecem em Pending até que haja capacidade disponível.
Apenas ECI
Adicione uma toleration para o taint padrão do ECI e uma regra de afinidade requiredDuringSchedulingIgnoredDuringExecution para fixar os pods nos nós ECI. Aplique ambas nas especificações do driver e do executor.
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
name: spark-pi-eci-only
namespace: default
spec:
type: Scala
mode: cluster
image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/spark:3.5.2
mainClass: org.apache.spark.examples.SparkPi
mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.5.2.jar
arguments:
- "5000"
sparkVersion: 3.5.2
driver:
cores: 1
coreLimit: 1200m
memory: 512m
serviceAccount: spark-operator-spark
affinity:
nodeAffinity:
# Pin driver to ECI nodes only.
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: type
operator: In
values:
- virtual-kubelet
tolerations:
# Tolerate the default ECI taint.
- key: virtual-kubelet.io/provider
operator: Equal
value: alibabacloud
effect: NoSchedule
executor:
instances: 2
cores: 2
memory: 4g
affinity:
nodeAffinity:
# Pin executors to ECI nodes only.
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: type
operator: In
values:
- virtual-kubelet
tolerations:
# Tolerate the default ECI taint.
- key: virtual-kubelet.io/provider
operator: Equal
value: alibabacloud
effect: NoSchedule
Comportamento esperado: Todos os pods são agendados em nós ECI e o job é executado em modo totalmente serverless. Se a capacidade regional do ECI for limitada, os pods permanecem em Pending até que os recursos estejam disponíveis.
ECS prioritário com fallback para ECI
Use a afinidade preferredDuringSchedulingIgnoredDuringExecution para favorecer o ECS e permitir overflow para o ECI. Adicione a toleration do ECI para que o agendador possa colocar pods no ECI quando o ECS estiver cheio.
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
name: spark-pi-ecs-first
namespace: default
spec:
type: Scala
mode: cluster
image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/spark:3.5.2
mainClass: org.apache.spark.examples.SparkPi
mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.5.2.jar
arguments:
- "5000"
sparkVersion: 3.5.2
driver:
cores: 1
coreLimit: 1200m
memory: 512m
serviceAccount: spark-operator-spark
affinity:
nodeAffinity:
# Prefer ECS nodes; fall back to ECI if ECS capacity is exhausted.
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: type
operator: NotIn
values:
- virtual-kubelet
tolerations:
# Allow scheduling to ECI nodes when needed.
- key: virtual-kubelet.io/provider
operator: Equal
value: alibabacloud
effect: NoSchedule
executor:
instances: 2
cores: 2
memory: 4g
affinity:
nodeAffinity:
# Prefer ECS nodes; fall back to ECI if ECS capacity is exhausted.
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: type
operator: NotIn
values:
- virtual-kubelet
tolerations:
# Allow scheduling to ECI nodes when needed.
- key: virtual-kubelet.io/provider
operator: Equal
value: alibabacloud
effect: NoSchedule
Comportamento esperado: Os pods são alocados no ECS quando há capacidade disponível. Quando o ECS está cheio, os pods excedentes são agendados no ECI. Isso evita filas de jobs durante picos sem reservar permanentemente recursos do ECI.
Consulte Configurar alocação de recursos com base em instâncias ECS e instâncias de contêiner elásticas para obter detalhes sobre taints, tolerations e afinidade de nó.
Configure o agendamento de recursos baseado em prioridade
Use uma ResourcePolicy para definir unidades de agendamento com limites de pods por unidade. O agendador do ACK preenche as unidades em ordem de prioridade durante o scale-out e remove pods na ordem inversa durante o scale-in. Consulte Configurar agendamento de recursos baseado em prioridade.
-
Crie o arquivo
resourcepolicy.yamlpara pods do Spark Operator no namespacedefault(máximo de 2 em ECS AMD64, depois 3 em ECI):apiVersion: scheduling.alibabacloud.com/v1alpha1 kind: ResourcePolicy metadata: name: sparkapplication-resource-policy namespace: default # Applies only to pods in this namespace. spec: ignorePreviousPod: true ignoreTerminatingPod: false matchLabelKeys: - sparkoperator.k8s.io/submission-id # Group pods by Spark job submission ID. preemptPolicy: AfterAllUnits # Attempt preemption only after all units are exhausted. selector: sparkoperator.k8s.io/launched-by-spark-operator: "true" strategy: prefer units: - max: 2 # Up to 2 pods on AMD64 ECS nodes (first priority). resource: ecs nodeSelector: kubernetes.io/arch: amd64 - max: 3 # Up to 3 pods on ECI (second priority). resource: eci -
Aplique a ResourcePolicy:
kubectl apply -f resourcepolicy.yaml -
Crie o arquivo
spark-pi.yaml. Este SparkApplication solicita 1 driver e 5 executors, todos tolerando o taint do ECI para que a ResourcePolicy possa distribuí-los entre ambos os tipos de unidade.apiVersion: sparkoperator.k8s.io/v1beta2 kind: SparkApplication metadata: name: spark-pi namespace: default spec: type: Scala mode: cluster image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/spark:3.5.2 mainClass: org.apache.spark.examples.SparkPi mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.5.2.jar arguments: - "5000" sparkVersion: 3.5.2 driver: cores: 1 coreLimit: 1200m memory: 512m serviceAccount: spark-operator-spark tolerations: - key: virtual-kubelet.io/provider # Allow the driver to be scheduled to ECI if needed. operator: Equal value: alibabacloud effect: NoSchedule executor: instances: 5 cores: 1 coreLimit: 1200m memory: 512m tolerations: - key: virtual-kubelet.io/provider # Allow executors to be scheduled to ECI. operator: Equal value: alibabacloud effect: NoSchedule -
Envie o job do Spark:
kubectl apply -f spark-pi.yaml -
Verifique os resultados do agendamento:
kubectl get pods -o wide -l sparkoperator.k8s.io/app-name=spark-piSaída esperada:
NAME READY STATUS RESTARTS AGE IP NODE spark-pi-34c0998f9f832e61-exec-1 1/1 Running 0 28s 192.XXX.XX.34 cn-beijing.192.XXX.XX.250 spark-pi-34c0998f9f832e61-exec-2 1/1 Running 0 28s 192.XXX.XX.87 virtual-kubelet-cn-beijing-i spark-pi-34c0998f9f832e61-exec-3 1/1 Running 0 28s 192.XXX.XX.88 virtual-kubelet-cn-beijing-i spark-pi-34c0998f9f832e61-exec-4 1/1 Running 0 28s 192.XXX.XX.86 virtual-kubelet-cn-beijing-i spark-pi-34c0998f9f832e61-exec-5 0/1 Pending 0 28s <none> <none> spark-pi-driver 1/1 Running 0 34s 192.XXX.XX.37 cn-beijing.192.XXX.XXX.250Resultado: O driver e o exec-1 são alocados na unidade ECS AMD64 (máximo de 2 pods). Os exec-2, exec-3 e exec-4 são alocados na unidade ECI (máximo de 3 pods). O exec-5 permanece em Pending porque ambas as unidades atingiram seu limite de pods.
Acelere o pull de imagens com ImageCache
Baixar uma imagem grande do Spark a cada inicialização de pod adiciona latência. O ImageCache do ECI pré-armazena imagens na infraestrutura subjacente e reduz a inicialização do pod de cerca de 100 segundos para quase instantânea em caso de acerto no cache. Consulte Usar ImageCache para acelerar a criação de instâncias de contêiner elásticas.
Compare a inicialização com e sem cache de imagem
Sem cache, o comando kubectl describe pod spark-pi-driver após enviar um SparkApplication mostra:
Events:
...
Warning ImageCacheMissed 24m EciService [eci.imagecache]Missed image cache.
Normal ImageCacheAutoCreated 24m EciService [eci.imagecache]Image cache imc-2zeXXXXXXXXXXXXXXXXX is auto created
Normal Pulling 24m kubelet Pulling image "registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/spark:3.5.2"
Normal Pulled 23m kubelet Successfully pulled image "registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/spark:3.5.2" in 1m41.289s (1m41.289s including waiting)
...
A imagem levou cerca de 100 segundos para ser baixada. O ECI criou automaticamente um cache para execuções futuras.
Com acerto no cache, os eventos mostram:
Events:
...
Normal SuccessfulHitImageCache 23s EciService [eci.imagecache]Successfully hit image cache imc-2zeXXXXXXXXXXXXXXXXX, eci will be scheduled with this image cache.
Normal Pulled 4s kubelet Container image "registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/spark:3.5.2" already present on machine
...
Nenhum download de imagem foi necessário.
Especifique um ID de cache de imagem
Adicione a anotação k8s.aliyun.com/eci-image-snapshot-id nas especificações do driver e do executor para fixar um cache de imagem específico:
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
name: spark-pi-eci-only
namespace: default
spec:
type: Scala
mode: cluster
image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/spark:3.5.2
mainClass: org.apache.spark.examples.SparkPi
mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.5.2.jar
arguments:
- "5000"
sparkVersion: 3.5.2
driver:
annotations:
k8s.aliyun.com/eci-image-snapshot-id: imc-2zeXXXXXXXXXXXXXXXXX # Image cache ID.
cores: 1
coreLimit: 1200m
memory: 512m
serviceAccount: spark-operator-spark
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: type
operator: In
values:
- virtual-kubelet
tolerations:
- key: virtual-kubelet.io/provider
operator: Equal
value: alibabacloud
effect: NoSchedule
executor:
annotations:
k8s.aliyun.com/eci-image-snapshot-id: imc-2zeXXXXXXXXXXXXXXXXX # Image cache ID.
instances: 2
cores: 2
memory: 4g
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: type
operator: In
values:
- virtual-kubelet
tolerations:
- key: virtual-kubelet.io/provider
operator: Equal
value: alibabacloud
effect: NoSchedule
Ative a criação e correspondência automática de cache de imagem
Para permitir que o ECI gerencie a criação e a correspondência de cache automaticamente — sem um ID de cache — defina a anotação k8s.aliyun.com/eci-image-cache como "true" no driver e no executor:
apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
name: spark-pi-eci-only
namespace: default
spec:
type: Scala
mode: cluster
image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/spark:3.5.2
mainClass: org.apache.spark.examples.SparkPi
mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.5.2.jar
arguments:
- "5000"
sparkVersion: 3.5.2
driver:
annotations:
k8s.aliyun.com/eci-image-cache: "true" # Enable automatic image cache creation and matching.
cores: 1
coreLimit: 1200m
memory: 512m
serviceAccount: spark-operator-spark
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: type
operator: In
values:
- virtual-kubelet
tolerations:
- key: virtual-kubelet.io/provider
operator: Equal
value: alibabacloud
effect: NoSchedule
executor:
annotations:
k8s.aliyun.com/eci-image-cache: "true" # Enable automatic image cache creation and matching.
instances: 2
cores: 2
memory: 4g
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: type
operator: In
values:
- virtual-kubelet
tolerations:
- key: virtual-kubelet.io/provider
operator: Equal
value: alibabacloud
effect: NoSchedule
Na primeira execução, o ECI cria um cache automaticamente. Execuções subsequentes correspondem ao cache e ignoram o download da imagem.
Aplicação em produção
Use ECS prioritário com fallback para ECI em jobs de produção. Mantém cargas sensíveis à latência em nós ECS persistentes, enquanto o ECI absorve tráfego de pico sem provisionar nós extras antecipadamente.
Execute o driver no ECS e os executors no ECI. Uma falha no driver reinicia todo o job. Mantenha o driver em nós ECS estáveis e direcione executors stateless para instâncias ECI ou preemptíveis de menor custo.
Ative o cache automático de imagens para jobs recorrentes. A primeira execução cria o cache; as execuções seguintes ignoram o download da imagem e reduzem a inicialização de cerca de 100 segundos para quase instantânea.
Use ResourcePolicy quando a distribuição de pods entre tipos de nó precisar ser precisa. O campo
maxpor unidade limita os pods, útil para restringir gastos com ECI por envio de job.