Skip to content

Experimentar um grupo do central no Docker

Redundância explica como um grupo do central é construído, e Um grupo do central em servidores separados explica como implantá-lo. Esta página trata de vê-lo funcionar com os próprios olhos: numa única máquina, a partir de contêineres, em dez minutos e sem alugar um único servidor.

O banco é descartável. Nele se pode — e se deve — fazer o que não se faz numa instalação em serviço: matar processos em pleno voo e ver o que sai disso.

O que é necessário

  • Docker com compose — não há mais nada a instalar.
  • Uma chave de licença. Cada processo precisa de uma: os dois central e os três streamers.
  • As portas 8081 e 8082 livres na máquina local.

De que é feito o banco

Seis contêineres:

  • postgres — um banco para todo o grupo.
  • central-a e central-b — duas instâncias do plano de controle sobre esse banco, com consoles nas portas 8081 e 8082.
  • streamer-1 e streamer-2 — dois streamers divididos entre as instâncias: o primeiro vai ao central-a, o segundo ao central-b.
  • streamer-3 — um streamer ao qual foi dado um único endereço, central-b. Da segunda instância ele só pode saber pelo próprio central.

Dividir os streamers entre instâncias não é enfeite, é o sentido do banco. A imagem do «agora mesmo» é uma só para o grupo, e todo o parque tem de ser visível a partir de qualquer um dos dois endereços, embora as máquinas reportem a instâncias diferentes. O terceiro streamer é a situação de qualquer máquina de um parque existente no dia em que uma segunda instância é acrescentada à instalação: ninguém vai reescrever a configuração em mil máquinas.

Baixe o docker-compose.yaml para um diretório vazio. Abaixo estão as partes por causa das quais ele foi escrito; o resto é encanamento comum.

A primeira instância declara os quatro papéis, migrate incluído:

central:
  admin_keys:
    - lab-admin-key
  roles: [run, migrate, layouter, stats]
  advertise_url: http://central-a

A segunda é a mesma sem migrate: o esquema é aplicado por um único processo, ou dois lançam as migrações em disputa.

central:
  admin_keys:
    - lab-admin-key
  roles: [run, layouter, stats]
  advertise_url: http://central-b

Os papéis layouter e stats são declarados pelas duas, e isso não é partida dupla: um papel é uma declaração, e o executor é escolhido por um mandato.

A um streamer não se dá o endereço de uma instância, e sim a lista de endereços do grupo; a ordem é a ordem de preferência:

managed_by:
  name: streamer-1
  url:
    - http://central-a
    - http://central-b
  join_token: ${JOIN_TOKEN}

O segundo streamer tem a mesma lista em ordem inversa. Ao terceiro é dado um único endereço — aquele que o laboratório vai desligar depois:

managed_by:
  name: streamer-3
  url: http://central-b
  join_token: ${JOIN_TOKEN}

Os streamers rodam a mesma imagem que o central: um streamer de um cluster Catena é o mesmo binário do Catena, e precisa da mesma licença.

Subindo

O banco não se monta com um único docker compose up: a janela de adesão é aberta por um central vivo, e o token de adesão só existe depois que ele parte.

Primeiro o banco e as duas instâncias do plano de controle:

export CATENA_VERSION=latest
export LICENSE_KEY='sua chave'
docker compose up -d postgres central-a central-b

Espere o console abrir em http://127.0.0.1:8081 e entre como admin com a senha pass. No primeiro login o console pede para criar uma conta nominal — crie-a.

Abra Cluster → Streamers, clique em Permitir adesão e copie o token de adesão. Suba os streamers com ele:

JOIN_TOKEN=jt-... docker compose up -d streamer-1 streamer-2 streamer-3

As três máquinas aparecem na lista de streamers em segundos. Feche a janela de adesão depois — o parque está montado.

Crie alguns streams para que o cluster tenha o que alocar. A entrada syntetic é o gerador embutido; o banco não precisa de nenhuma fonte externa:

for i in 1 2 3 4 5 6; do
  curl -s -X PUT -H 'Authorization: Bearer lab-admin-key' \
    -H 'Content-Type: application/json' \
    -d '{"inputs":[{"syntetic":{}}]}' \
    "http://127.0.0.1:8081/central/api-v4/streams/configs/cam$i"
done

O grupo inteiro

Abra Cluster → Central.

Instâncias do central: papéis reivindicados e mandatos

A tabela tem duas linhas — o grupo inteiro — e qualquer instância a mostra igual: o registro está no banco comum.

  • Os papéis reivindicados são quase iguais nas duas: migrate só é declarado pelo central-a, todo o resto é declarado pelas duas máquinas.
  • Os mandatos detidos só estão preenchidos numa linha. Essa é a resposta a «quem manda aqui»: ninguém manda, há titulares de mandatos específicos.
  • A segunda linha diz nenhum — apenas atende pedidos. Uma instância assim não é reserva nem está dormindo: ela responde por completo ao console, à API e aos streamers; simplesmente não é ela quem faz o trabalho de fundo agora.

No seu banco os mandatos podem ficar de outro jeito — os dois numa máquina ou um em cada. Os mandatos são independentes e o arranjo deles depende de quem chegou primeiro; aqui não existe arranjo errado.

O parque é visível de qualquer endereço

Sem sair da instância, abra Cluster → Streamers.

Os três streamers no ar

As três máquinas estão no ar, embora a esta instância apenas uma delas reporte. As outras mandam sua sincronização ao vizinho e são vistas aqui porque a imagem do «agora mesmo» é uma só para o grupo: uma instância sem o mandato stats chega a ela pela rede, justamente por aquele endereço anunciado.

Abra a mesma página em http://127.0.0.1:8082 e verá o mesmo. É essa a verificação para a qual os streamers estão divididos entre instâncias.

Matamos o titular do mandato

Agora aquilo por que o banco é descartável. Mate a instância que tem os mandatos — nas capturas acima é o central-b:

docker compose stop central-b

Volte a Cluster → Central na instância viva e espere alguns segundos.

Os mandatos se mudaram, a linha do vizinho apagou

Três coisas mudaram:

  • A linha da instância matada não sumiu, ela está marcada: «em silêncio — o processo provavelmente morreu». O grupo lembra sua composição, não apenas os vivos.
  • Os mandatos se mudaram para a instância viva. Ninguém os entregou: um mandato vive numa conexão própria com o PostgreSQL e é liberado com a morte dessa sessão, e o vizinho vem buscar o liberado por conta própria.
  • O número da época cresceu. Ele cresce a cada troca de titular e viaja com cada escrita: uma instância que acorda já sem ser a titular recebe uma recusa na escrita, em vez de estragar a alocação depois do fato.

A mudança leva segundos. Não é preciso confirmá-la à mão, nem há com o quê.

O streamer que conhecia um único endereço

Abra Cluster → Streamers — ainda na instância viva.

Os três streamers no ar a partir da instância viva

As três máquinas estão no ar, e a terceira é aquela cuja configuração nomeia apenas o central-b que você acabou de desligar. Ela não conhecia o segundo endereço pela configuração e mesmo assim passou a ele após uma requisição malsucedida: o central nem chegou a dá-la por perdida.

O endereço ela aprendeu do próprio central. Cada resposta à sincronização traz a composição do grupo — os endereços das instâncias vivas que atendem streamers —, e o streamer os acrescenta aos que tem na sua configuração. O aprendido fica guardado em disco junto ao resto do estado da máquina, por isso sobrevive a um reinício:

docker compose cp streamer-3:/var/lib/catena/group.json group.json && cat group.json
{
  "announced": [
    "http://central-a"
  ]
}

No log do streamer esse momento é marcado pela linha the central group roster changed — nela vêm primeiro os endereços da configuração e depois os aprendidos. A troca em si é a linha switching to the next central address.

Confira também o reinício: docker compose restart streamer-3 com o central-b ainda desligado sobe os streams do cache sem esperar a rede e, após uma requisição malsucedida ao central-b, leva a máquina ao central-a — o endereço é lido do disco, embora a configuração dela não saiba nada de uma segunda instância.

O que o anúncio não substitui é a primeira resposta. Um streamer que nunca sincronizou e perdeu seu único endereço não sabe nada do grupo: um minuto e meio depois consta como offline, e o layouter move seus streams para outras máquinas. Por isso as máquinas com as quais um parque é levantado recebem os dois endereços, e o anúncio cobre todo o resto — acrescentar instâncias a um parque que já funciona.

O ar não percebeu

Abra Streams.

Todos os streams seguem no ar

Os seis streams seguem. Os que vivem nos streamers que reportavam à instância matada não notaram a mudança: uma troca de endereço de gestão não é um evento para o ar.

Devolvemos a instância

docker compose start central-b

A linha acende de novo. Os mandatos não voltam — e isso é uma decisão, não algo por terminar: não há nada a ganhar tirando-os, o titular está trabalhando, e uma troca de titular custa uma nova encarnação da imagem do «agora mesmo».

Os streamers, por sua vez, voltam. Uma vez a cada cinco minutos o streamer testa o primeiro endereço da sua lista com uma requisição de sincronização comum: o central-b respondeu, então streamer-2 e streamer-3 voltam a ir a ele, porque a ordem da lista é a preferência do operador, não uma dica de partida. Espere cinco minutos e olhe o log:

docker compose logs --since 6m streamer-2 | grep -i 'preferred'

Um retorno bem-sucedido é marcado pela linha trying the preferred central address again sem nada depois; se o endereço continuasse morto, ela seria seguida por the preferred address is still down, e o streamer ficaria onde estava.

Dá para devolver o arranjo original dos mandatos com um reinício — é para isso que o banco é descartável —, mas em produção não é preciso: o grupo funciona em qualquer arranjo.

Retiramos o banco

docker compose down -v

A opção -v retira também os volumes: o banco, o estado dos streamers, a identidade deles no cluster e os endereços do grupo que aprenderam. Sem ela a próxima subida herda um estado meio alheio.

O que este banco não mostra

  • Uma falha do banco de dados. Aqui ele é único e sem réplica: mate-o e a gestão para por completo enquanto o ar segue. É verdade, mas a redundância do banco é tarefa do próprio banco.
  • A rede. Todos os contêineres estão numa mesma rede do docker, sem perdas nem latência entre eles.
  • A carga. Seis streams sintéticos não dizem nada sobre como o grupo se comporta num parque real.
  • O ponto de entrada do operador. No banco há dois, um por instância; em produção é preciso um único endereço com comutação.