Documentation
¶
Overview ¶
Package kubernetes executa cada passo de um workflow como um POD proprio.
A dinamica e a da secao 2 do plano, e e o que separa este motor de um worker monolitico: o pod sobe com a imagem EXATA do passo, roda um comando, reporta e morre. Um passo de dbt sobe a imagem de dbt com 1Gi; o fetcher em Go ao lado sobe uma imagem de 10 MB com 32Mi. Numa imagem unica os dois pagariam o maior dos dois — em bytes de pull, em memoria reservada e em superficie.
O cliente e escrito sobre a stdlib, sem client-go. A biblioteca oficial traz centenas de dependencias e dezenas de MB para o que aqui sao quatro chamadas REST: criar pod, ler status, ler log, apagar pod. A mesma escolha ja foi feita para o React (bundle vendorizado) e para o CSS (Tailwind standalone): o custo de uma dependencia grande so se paga quando se usa uma fracao grande dela.
Index ¶
- func NomeDoPod(t execution.TaskExec) string
- type API
- type Cliente
- func (c *Cliente) ApagarPod(ctx context.Context, nome string) error
- func (c *Cliente) CriarPod(ctx context.Context, p Pod) (Pod, error)
- func (c *Cliente) LerPod(ctx context.Context, nome string) (Pod, error)
- func (c *Cliente) Logs(ctx context.Context, nome string, seguir bool) (io.ReadCloser, error)
- func (c *Cliente) Namespace() string
- type Condicao
- type Container
- type ErrForaDoCluster
- type Executor
- type FonteEnv
- type FontePVC
- type FonteVar
- type Metadata
- type MontagemDeVolume
- type Opcoes
- type Pod
- type PodSpec
- type PodStatus
- type Recursos
- type RefChave
- type RefLocal
- type StatusContainer
- type Toleracao
- type Var
- type Volume
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NomeDoPod ¶
NomeDoPod produz um nome valido e ESTAVEL para a mesma tentativa.
Estavel importa: se o processo morrer entre criar o pod e registrar isso, a tentativa seguinte encontra o pod existente (409 AlreadyExists) em vez de subir um segundo pod rodando o mesmo dbt em paralelo com o primeiro.
O sufixo de hash resolve a colisao que o corte de 63 caracteres criaria entre dois nodes de nome longo e prefixo comum.
Types ¶
type API ¶
type API interface {
CriarPod(ctx context.Context, p Pod) (Pod, error)
LerPod(ctx context.Context, nome string) (Pod, error)
Logs(ctx context.Context, nome string, seguir bool) (io.ReadCloser, error)
ApagarPod(ctx context.Context, nome string) error
}
API e o que o executor precisa do servidor. Interface no consumidor: e ela que permite testar o ciclo de vida inteiro do pod contra um servidor falso.
type Cliente ¶
type Cliente struct {
// contains filtered or unexported fields
}
Cliente fala com o servidor de API.
type Condicao ¶
type Condicao struct {
Type string `json:"type"`
Status string `json:"status"`
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
}
Condicao carrega o PodScheduled, onde o scheduler explica por que nao coube.
type Container ¶
type Container struct {
Name string `json:"name"`
Image string `json:"image"`
Command []string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
Env []Var `json:"env,omitempty"`
EnvFrom []FonteEnv `json:"envFrom,omitempty"`
Resources *Recursos `json:"resources,omitempty"`
WorkingDir string `json:"workingDir,omitempty"`
VolumeMounts []MontagemDeVolume `json:"volumeMounts,omitempty"`
}
type ErrForaDoCluster ¶
type ErrForaDoCluster struct{ Motivo string }
ErrForaDoCluster e devolvido quando nao ha service account montada.
func (ErrForaDoCluster) Error ¶
func (e ErrForaDoCluster) Error() string
type Executor ¶
type Executor struct {
// Intervalo de sondagem do status. Sondar e nao observar (watch) e
// deliberado: um watch exige reconexao, resync e tratamento de eventos
// perdidos para ganhar segundos numa task que dura minutos.
Intervalo time.Duration
// contains filtered or unexported fields
}
Executor roda cada passo como um pod.
O ciclo e sempre o mesmo: cria o pod, espera sair de Pending, acompanha o log enquanto roda, le o codigo de saida e apaga. Nenhum estado vive aqui alem dos pods em voo — se o processo reiniciar, os pods continuam rodando e o dispatcher os reencontra pelo nome deterministico.
func NewExecutor ¶
type FonteVar ¶
type FonteVar struct {
SecretKeyRef *RefChave `json:"secretKeyRef,omitempty"`
}
FonteVar aponta uma variavel para uma chave de um Secret. O valor nunca passa pelo motor: quem le e o kubelet, na hora de subir o container.
type MontagemDeVolume ¶
type Opcoes ¶
type Opcoes struct {
Namespace string
ServiceAccount string
PullSecrets []string
NodeSelector map[string]string
Tolerations []Toleracao
EnvFromSecrets []string
EnvFromConfigMaps []string
// CredencialPVC e CredencialPath montam um volume onde o SDK guarda a
// credencial rotacionada entre execucoes.
//
// Com os dois definidos, TODO pod de passo ganha o volume e a env
// BREVIS_CREDENTIAL_DIR apontando para o mount. Sem eles nada muda -- e e
// assim que a feature continua sendo atalho, e nao requisito.
//
// A credencial no volume vai cifrada; a chave e um Secret comum, que entra
// por EnvFromSecrets. O motor nao a ve nem precisa dela.
CredencialPVC string
CredencialPath string
// SecretsPermitidos sao os Secrets que um YAML pode citar em `secrets:`.
//
// Existe porque `secrets:` inverte quem escolhe. EnvFromSecrets vem do
// ambiente do scheduler: a INSTALACAO decide. `secrets:` esta no arquivo,
// e o arquivo e escrito por outra pessoa -- sem esta lista, um workflow
// poderia montar qualquer Secret do namespace, inclusive o do banco do
// proprio Brevis, e rodar um comando arbitrario com ele em maos.
//
// Vazia nega tudo. Negar por padrao custa uma variavel na instalacao;
// permitir por padrao custa o inverso, e o inverso e irreversivel.
//
// A divisao final e essa: a instalacao diz QUAIS segredos existem para
// workflows, o YAML diz QUAL passo recebe cada um.
SecretsPermitidos []string
Labels map[string]string
Shell []string
// EsperaParaIniciar e quanto um pod pode ficar sem comecar antes de o passo
// desistir. Existe porque `Pending` nao e erro para o Kubernetes: um pod que
// nao cabe em no nenhum fica ali para sempre, e sem este limite a etapa
// espera junto — sem log, sem falha, sem retry. Aconteceu em dev com um
// request de CPU maior que o livre no pool.
EsperaParaIniciar time.Duration
// ManterPodEmFalha deixa o pod para inspecao quando o passo falha. O de
// sucesso e sempre apagado: milhares de pods Completed poluem o namespace e
// nao dizem nada que o historico do Brevis nao diga melhor.
ManterPodEmFalha bool
}
Opcoes parametriza como os pods sao criados. Sao decisoes da INSTALACAO — credenciais, pool de nos, conta de servico —, nao do autor do workflow: um YAML de pipeline nao deve poder escolher a service account com que roda.
type Pod ¶
type Pod struct {
APIVersion string `json:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty"`
Metadata Metadata `json:"metadata"`
Spec PodSpec `json:"spec,omitempty"`
// Ponteiro porque `omitempty` nao omite struct vazia: sem ele, todo pod
// criado enviaria `"status":{}` ao servidor — inofensivo, mas e ruido num
// objeto que se le para depurar.
Status *PodStatus `json:"status,omitempty"`
}
Pod e o subconjunto do objeto que este motor usa. Escrever as structs a mao, em vez de importar as do client-go, mantem a arvore de dependencias pequena e deixa visivel exatamente o que se envia ao servidor de API.
func MontarPod ¶
MontarPod traduz uma task no objeto que vai para o servidor de API.
Funcao pura: recebe task e opcoes, devolve o objeto. E o que permite testar o spec inteiro — imagem, comando, recursos, rotulos — sem cluster nenhum.
func (Pod) Motivo ¶
Motivo e o `reason` do status (DeadlineExceeded, OOMKilled, Evicted) — a diferenca entre "o codigo falhou" e "o cluster matou o processo".
func (Pod) MotivoDeEspera ¶
MotivoDeEspera explica por que o container ainda nao rodou.
E a informacao mais util quando um passo "nao faz nada": ImagePullBackOff e CreateContainerConfigError sao problemas de configuracao que, sem isto, apareceriam apenas como um pod parado ate o timeout.
type PodSpec ¶
type PodSpec struct {
RestartPolicy string `json:"restartPolicy,omitempty"`
ServiceAccountName string `json:"serviceAccountName,omitempty"`
ImagePullSecrets []RefLocal `json:"imagePullSecrets,omitempty"`
NodeSelector map[string]string `json:"nodeSelector,omitempty"`
Tolerations []Toleracao `json:"tolerations,omitempty"`
ActiveDeadlineSeconds *int64 `json:"activeDeadlineSeconds,omitempty"`
Volumes []Volume `json:"volumes,omitempty"`
Containers []Container `json:"containers"`
}
type PodStatus ¶
type PodStatus struct {
Phase string `json:"phase,omitempty"`
Conditions []Condicao `json:"conditions,omitempty"`
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
ContainerStatuses []StatusContainer `json:"containerStatuses,omitempty"`
}
type StatusContainer ¶
type StatusContainer struct {
Name string `json:"name"`
State struct {
Waiting *struct {
Reason string `json:"reason"`
Message string `json:"message"`
} `json:"waiting,omitempty"`
Running *struct {
StartedAt string `json:"startedAt"`
} `json:"running,omitempty"`
Terminated *struct {
ExitCode int `json:"exitCode"`
Reason string `json:"reason"`
Message string `json:"message"`
} `json:"terminated,omitempty"`
} `json:"state"`
}
type Volume ¶
type Volume struct {
Name string `json:"name"`
PVC *FontePVC `json:"persistentVolumeClaim,omitempty"`
}
Volume e um PersistentVolumeClaim montado no pod.
So PVC, e nao a uniao de tudo que o Kubernetes aceita: o motor monta volume para um proposito -- guardar a credencial rotacionada entre execucoes -- e um campo que existe para um proposito nao deve aceitar dez formas.