Seph - do "com improviso" ao "produção"

Escolha do CEPH. Parte 1

Tínhamos cinco racks, dez switches ópticos, BGP configurado, algumas dezenas de SSDs e uma infinidade de discos SAS de todas as cores e tamanhos, além de proxmox e o desejo de armazenar toda a parte estática em nosso próprio armazenamento S3. Não que tudo isso fosse necessário para a virtualização, mas uma vez que comecei a usar opensource — devo levar minha paixão até o fim. A única coisa que me preocupava era o BGP. Não há nada mais impotente, irresponsável e imoral do que o roteamento interno pelo BGP. E eu sabia que em breve estaríamos mergulhados nisso.

Seph - do "com improviso" ao "produção"

A tarefa era banal — havia um CEPH que não estava funcionando muito bem. Precisava ser feito "bem".
O cluster que me foi entregue era heterogêneo, configurado às pressas e praticamente sem tuning. Era composto por dois grupos de nós diferentes, com uma única rede comum servindo tanto como rede de cluster quanto como rede pública. Os nós estavam recheados com quatro tipos de discos — dois tipos de SSD, organizados em duas regras de colocação separadas, e dois tipos de HDD de tamanhos diferentes, agrupados em um terceiro grupo. O problema com os tamanhos diferentes foi resolvido com diferentes pesos de OSD.

A própria configuração foi dividida em duas partes — tuning do sistema operacional e tuning do próprio CEPH e suas configurações.

Aprimoramento do OS

Rede

Alta latência afetava tanto a gravação quanto o balanceamento. Na gravação — porque o cliente não receberá uma resposta sobre a gravação bem-sucedida, até que as réplicas dos dados em outros grupos de colocação confirmem o sucesso. Como as regras de distribuição de réplicas no CRUSH map tinham uma réplica por host, a rede era usada o tempo todo.

Portanto, a primeira coisa que decidi foi ajustar levemente a rede atual, ao mesmo tempo tentando convencer a mudança para redes separadas.

Primeiro, ajuste as configurações das placas de rede. Comecei pelo ajuste das filas:

o que havia:

ethtool -l ens1f1

root@ceph01:~# ethtool -l ens1f1
Parâmetros de canal para ens1f1:
Máximos pré-definidos:
RX:     0
TX:     0
Outro:      1
Combinado:   63
Configurações de hardware atuais:
RX:     0
TX:     0
Outro:      1
Combinado:   1
root@ceph01:~# ethtool -g ens1f1
Parâmetros de anel para ens1f1:
Máximos pré-definidos:
RX:     4096
RX Mini:    0
RX Jumbo:   0
TX:     4096
Configurações de hardware atuais:
RX:     256
RX Mini:    0
RX Jumbo:   0
TX:     256
root@ceph01:~# ethtool -l ens1f1
Parâmetros de canal para ens1f1:
Máximos pré-definidos:
RX:     0
TX:     0
Outro:      1
Combinado:   63
Configurações de hardware atuais:
RX:     0
TX:     0
Outro:      1
Combinado:   1

É evidente que os parâmetros atuais estão longe dos máximos. Aumentei:

root@ceph01:~#ethtool -G ens1f0 rx 4096
root@ceph01:~#ethtool -G ens1f0 tx 4096
root@ceph01:~#ethtool -L ens1f0 combined 63

Guiado por um excelente artigo

https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data/

aumentei o comprimento da fila de envio txqueuelen de 1000 a 10 000

root@ceph01:~#ip link set ens1f0 txqueuelen 10000

E seguindo a documentação do próprio ceph

https://ceph.com/geen-categorie/ceph-loves-jumbo-frames/

aumentei MTU para 9000.

root@ceph01:~#ip link set dev ens1f0 mtu 9000

Adicionei em /etc/network/interfaces, para que tudo o que foi mencionado acima carregasse na inicialização

cat /etc/network/interfaces

root@ceph01:~# cat /etc/network/interfaces
auto lo
iface lo inet loopback

auto ens1f0
iface ens1f0 inet manual
post-up /sbin/ethtool -G ens1f0 rx 4096
post-up /sbin/ethtool -G ens1f0 tx 4096
post-up /sbin/ethtool -L ens1f0 combined 63
post-up /sbin/ip link set ens1f0 txqueuelen 10000
mtu 9000

auto ens1f1
iface ens1f1 inet manual
post-up /sbin/ethtool -G ens1f1 rx 4096
post-up /sbin/ethtool -G ens1f1 tx 4096
post-up /sbin/ethtool -L ens1f1 combined 63
post-up /sbin/ip link set ens1f1 txqueuelen 10000
mtu 9000

Depois disso, seguindo o mesmo artigo, comecei a ajustar cuidadosamente os parâmetros do núcleo 4.15. Considerando que os nós têm 128G de RAM, obtive um arquivo de configuração para sysctl

cat /etc/sysctl.d/50-ceph.conf

net.core.rmem_max = 56623104  
# Tamanho máximo do buffer de recepção para todas as conexões  54M

net.core.wmem_max = 56623104
# Tamanho máximo do buffer de transmissão para todas as conexões 54M

net.core.rmem_default = 56623104
# Tamanho do buffer de recepção padrão para todas as conexões. 54M

net.core.wmem_default = 56623104
# Tamanho do buffer de transmissão padrão para todas as conexões 54M  
# por socket

net.ipv4.tcp_rmem = 4096 87380 56623104
# Variável vetorial (mínimo, padrão, máximo) do arquivo tcp_rmem
# contém 3 números inteiros que definem o tamanho do buffer de recepção dos sockets TCP.
# Mínimo: cada socket TCP tem direito a usar essa memória no 
# momento da sua criação. A possibilidade de uso desse buffer 
# é garantida mesmo ao atingir o limite de restrição (pressão de memória moderada).
# O tamanho do buffer mínimo padrão é de 8 KB (8192).
# Valor padrão: quantidade de memória permitida para o buffer 
# de transmissão do socket TCP padrão. Este valor é aplicado em lugar
# do parâmetro /proc/sys/net/core/rmem_default, usado por outros protocolos.
# O valor do buffer usado por padrão geralmente (normalmente) 
# é de 87830 bytes. Isso determina o tamanho da janela 65535 com 
# o valor padrão definido de tcp_adv_win_scale e tcp_app_win = 0, 
# um pouco menor do que o valor padrão definido de tcp_app_win.
# Máximo: tamanho máximo do buffer que pode ser automaticamente
# alocado para recepção do socket TCP. Este valor não anula o máximo, 
# definido no arquivo /proc/sys/net/core/rmem_max. Em uma alocação "estática"
# de memória usando SO_RCVBUF, este parâmetro não tem efeito.

net.ipv4.tcp_wmem = 4096 65536 56623104
net.core.somaxconn = 5000    
# Número máximo de sockets abertos aguardando conexões.

net.ipv4.tcp_timestamps=1
# Permite o uso de timestamps, de acordo com a RFC 1323.

net.ipv4.tcp_sack=1
# Permitir confirmações seletivas no protocolo TCP

net.core.netdev_max_backlog=5000 (padrão 1000)
# número máximo de pacotes na fila para processamento, se 
# a interface recebe pacotes mais rápido do que o kernel pode processá-los.

net.ipv4.tcp_max_tw_buckets=262144
# Número máximo de sockets em estado TIME-WAIT simultaneamente.
# Ao exceder esse limite, o socket "extra" é destruído e uma
# mensagem é escrita no log do sistema.

net.ipv4.tcp_tw_reuse=1
#Permitindo a reutilização de sockets TIME-WAIT em casos,
# onde o protocolo considera isso seguro.

net.core.optmem_max=4194304
#Aumentar o tamanho máximo do buffer geral ALLOCATABLE
# medido em unidades de página (4096 bytes)

net.ipv4.tcp_low_latency=1
# Permite que a pilha TCP/IP priorize baixa latência
# em vez de maior largura de banda.

net.ipv4.tcp_adv_win_scale=1
# Esta variável influencia o cálculo do volume de memória no buffer do socket,
# dedicado ao tamanho da janela TCP e ao buffer da aplicação.
# Se o valor de tcp_adv_win_scale for negativo, a seguinte expressão é usada para o cálculo:
# Bytes - bytes2 elevado a -tcp_adv_win_scale
# Onde bytes é o tamanho da janela em bytes. Se o valor de tcp_adv_win_scale for positivo, a seguinte expressão é usada:
# Bytes - bytes2 elevado a tcp_adv_win_scale
# A variável aceita um valor inteiro. O valor padrão é 2, 
# ou seja, ¼ do volume definido pela variável
# tcp_rmem é alocado para o buffer da aplicação.

net.ipv4.tcp_slow_start_after_idle=0
# mecanismo de reinício da inicialização lenta que redefinirá o valor da janela 
# de sobrecarga, se a conexão não tiver sido utilizada por um período de tempo determinado.
# É melhor desativar o SSR no servidor para melhorar o desempenho 
# de conexões de longa duração.

net.ipv4.tcp_no_metrics_save=1
#Não salvar os resultados das medições da conexão TCP em cache ao encerrá-la.

net.ipv4.tcp_syncookies=0
# Desativar o mecanismo de envio de syncookie

net.ipv4.tcp_ecn=0
# Explicit Congestion Notification (Notificação de Congestionamento Explícita) em 
# conexões TCP. Usado para notificar a ocorrência de um "congestionamento" 
# no caminho para um determinado host ou rede. Pode ser usado para avisar
# o host remetente sobre a necessidade de reduzir a taxa de transmissão de pacotes através
# de um roteador ou firewall específico.

net.ipv4.conf.all.send_redirects=0
# desativa a emissão de ICMP Redirect … para outros hosts. Esta opção deve ser habilitada
# se o host atuar como um roteador de qualquer tipo.
# Não temos roteamento.

net.ipv4.ip_forward=0
#Desativar o encaminhamento. Não somos um gateway, Docker nas máquinas não está ativo,
# isso não é necessário.

net.ipv4.icmp_echo_ignore_broadcasts=1
#Não responder a solicitações ICMP ECHO transmitidas por pacotes de broadcast

net.ipv4.tcp_fin_timeout=10
# Define o tempo em que o socket permanecerá no estado FIN-WAIT-2 após seu
# fechamento pela parte local. Padrão 60

net.core.netdev_budget=600 # (padrão 300)
# Se a execução de interrupções de software não for duradoura o suficiente,
# a taxa de crescimento dos dados de entrada pode exceder a capacidade do kernel 
# de esvaziar o buffer. Como resultado, os buffers NIC ficarão sobrecarregados e o tráfego será perdido.
# Às vezes, é necessário aumentar o tempo de execução do SoftIRQs
# (interrupções de software) com a CPU. Isso é controlado pelo netdev_budget. 
# O valor padrão é 300. O parâmetro fará com que o processo SoftIRQ processe
# 300 pacotes do NIC antes de liberar a CPU

net.ipv4.tcp_fastopen=3
# TFO TCP Fast Open
# se tanto o cliente quanto o servidor suportam TFO, que é reportado por um
# sinalizador especial no pacote TCP. No nosso caso, é apenas um placebo, apenas
# parece bom)

Comrede de luster foi alocada em interfaces de rede separadas de 10Gbps em uma rede plana. Cada máquina tinha placas de rede de dois portos instaladas mellanox 10/25 Gbps, conectados a dois switches de 10Gbps separados. A agregação foi realizada com OSPF, uma vez que o vínculo com lacp, por algum motivo, mostrou uma largura de banda total máxima de 16 Gbps, enquanto o ospf utilizou com sucesso ambas as dez conexões em cada máquina. Planos futuros incluíam utilizar ROCE nesses mellanoxs para reduzir a latência. Como configuramos esta parte da rede:

  1. Como as máquinas têm endereços IP externos em BGP, o software necessário é (na verdade, no momento da escrita deste artigo, era frr=6.0-1 ) já estava instalado.
  2. No total, as máquinas tinham duas interfaces de rede, somando 4 portas. Uma placa de rede com dois portos estava voltada para a fábrica, onde foi configurado o BGP, enquanto a outra, com dois portos, estava voltada para dois switches diferentes e tinha OSPF configurado.

Mais detalhes sobre a configuração do OSPF: O principal objetivo é agregar duas conexões e ter tolerância a falhas.
duas interfaces de rede configuradas em duas redes planas simples — 10.10.10.0/24 e 10.10.20.0/24

1: ens1f0:  mtu 9000 qdisc mq state UP group default qlen 1000
    inet 10.10.10.2/24 brd 10.10.10.255 scope global ens1f0

2: ens1f1:  mtu 9000 qdisc mq state UP group default qlen 1000
    inet 10.10.20.2/24 brd 10.10.20.255 scope global ens1f1

pelas quais as máquinas se veem.

DISCO

O próximo passo foi otimizar o desempenho dos discos. Para SSD, mudei o planejador para noop, para HDD — deadline. Grosso modo, o NOOP funciona com o princípio de "quem chega primeiro, leva a melhor", que em inglês é conhecido como "FIFO (First In, First Out)". As solicitações entram em fila à medida que chegam. O DEADLINE é mais focado na leitura, além de o processo da fila obter acesso praticamente monopolizado ao disco no momento da operação. Para nosso sistema, isso se adequa perfeitamente — pois apenas um processo (OSD daemon) trabalha com cada disco.
(Os interessados em se aprofundar no planejador de I/O podem ler sobre ele aqui:
http://www.admin-magazine.com/HPC/Articles/Linux-I-O-Schedulers

Para quem prefere ler em russo: https://www.opennet.ru/base/sys/linux_shedulers.txt.html)

As recomendações para tuning do Linux também sugerem aumentar o nr_request

nr_requests
O valor de nr_requests determina a quantidade de solicitações de I/O que são armazenadas em buffer antes que o escalonador de I/O envie/receptione dados para o dispositivo de bloco. Se você estiver usando um cartão RAID/dispositivo de bloco que pode lidar com uma fila maior do que o que o escalonador de I/O está configurado, aumentar o valor de nr_requests pode ajudar a melhorar o desempenho e reduzir a carga do servidor quando grandes quantidades de I/O ocorrem no servidor. Se você estiver usando Deadline ou CFQ como escalonador, recomenda-se definir o valor de nr_request para 2 vezes o valor da profundidade da fila.

NO! Os próprios cidadãos, desenvolvedores do CEPH, nos convencem de que seu sistema de prioridades funciona melhor.

Seph - do "com improviso" ao "produção"

WBThrottle e/ou nr_requests

WBThrottle e/ou nr_requests
O armazenamento de arquivos utiliza operações de entrada/saída bufferizadas para gravação; isso traz uma série de benefícios se o log do armazenamento de arquivos estiver em um meio mais rápido. As solicitações dos clientes são notificadas assim que os dados são gravados no log, e então são despejados no disco de dados em um momento posterior, utilizando a funcionalidade padrão do Linux. Isso permite que discos OSD de platô forneçam latência de gravação semelhante à de um SSD ao gravar em pacotes pequenos. Essa gravação atrasada também permite que o próprio núcleo reorganize as solicitações de operações de entrada/saída para o disco, com a esperança de mesclá-las ou permitir que as cabeças existentes do disco escolham um caminho mais otimizado sobre seus pratos. O efeito final é que você pode extrair um pouco mais de operações de entrada/saída de cada disco do que seria possível com operações de entrada/saída diretas ou síncronas.

No entanto, surge um certo problema se o volume de gravações chegando a este cluster Ceph ultrapassar todas as capacidades dos discos subjacentes. Nesse cenário, o número total de operações de entrada/saída em espera para gravação no disco pode crescer descontroladamente, resultando em filas de operações de entrada/saída ocupando todo o disco e equipes Ceph. As solicitações de leitura afetam especialmente mal, pois ficam presas entre as solicitações de gravação, que podem levar vários segundos para serem despejadas no disco principal.

Para resolver esse problema, o Ceph possui um mecanismo embutido de throttling de gravação assíncrona chamado WBThrottle. Ele foi projetado para limitar o volume total de operações de entrada/saída de gravação assíncrona que podem ser enfileiradas e iniciar seu processo de descarregamento antes que isso aconteça naturalmente, por meio do próprio núcleo. Infelizmente, os testes demonstram que os valores padrão ainda podem não reduzir o comportamento existente a um nível que possa diminuir o impacto na latência das operações de leitura. Ajustes podem alterar esse comportamento e reduzir as filas de gravação gerais, possibilitando um impacto menos significativo. No entanto, existe um certo compromisso: ao diminuir o total máximo de gravações permitidas na fila, você pode reduzir a capacidade do núcleo de maximizar sua eficiência na ordenação dos pedidos recebidos. Vale a pena refletir sobre o que é mais necessário para seu caso específico de uso, cargas de trabalho e ajustar de acordo com isso.

Para gerenciar a profundidade dessa fila de gravação assíncrona, você pode reduzir o número total máximo de operações de entrada/saída pendentes, aplicando as configurações do WBThrottle, ou diminuindo o valor máximo para operações pendentes ao nível do bloco do seu núcleo. Ambos podem efetivamente gerenciar o mesmo comportamento e suas preferências serão a base para a implementação dessa configuração.
Também deve-se observar que o sistema de prioridades de operações existente no Ceph é mais eficiente para solicitações mais curtas no nível do disco. Ao reduzir a fila geral para este disco, a principal localização na fila é movida para o Ceph, onde ele tem mais controle sobre o que prioridade tem a operação de entrada/saída. Vamos considerar o seguinte exemplo:

echo 8 > /sys/block/sda/queue/nr_requests

http://onreader.mdl.ru/MasteringCeph/content/Ch09.html#030202

COMUM

E mais algumas configurações do núcleo que podem tornar sua máquina suave e sedosa, extraindo um pouco mais de desempenho do hardware

cat /etc/sysctl.d/60-ceph2.conf

 kernel.pid_max = 4194303
#Discos em cada máquina são 25, portanto, calculamos que haveria muitos processos
kernel.threads-max=2097152
# Threads, é claro, também.
vm.max_map_count=524288
# Aumentamos o número de áreas de mapeamento de memória do processo. 
# Como indicado na documentação sobre variáveis do kernel 
# As áreas de mapeamento de memória são usadas como um efeito colateral da chamada
# malloc, diretamente através de mmap, mprotect e madvise, bem como durante o carregamento
# de bibliotecas compartilhadas.
fs.aio-max-nr=50000000
# Ajustamos os parâmetros de entrada e saída
# O kernel Linux fornece uma função de entrada e saída assíncrona não bloqueante (AIO),
# que permite ao processo iniciar várias operações de entrada e saída
# simultaneamente, sem esperar pela conclusão de nenhuma delas. 
# Isso ajuda a melhorar o desempenho das aplicações, 
# que podem sobrepor o processamento e a entrada e saída.
# O parâmetro aio-max-nr define o número máximo de 
# solicitações simultâneas permitidas.
vm.min_free_kbytes=1048576
# Tamanho mínimo da memória livre que deve ser mantido.
# Definido em 1Gb, o que é mais do que suficiente para o funcionamento do sistema operacional, 
# e evita que o OOM Killer ative para processos OSD. Embora haja bastante
# memória disponível, o que é um bom sinal
vm.swappiness=10
# Dizemos para usar swap se 10% da memória está livre.
# Em máquinas com 128G de RAM, 10% é 12 Gigas. Mais do que suficiente para trabalhar.
# O parâmetro padrão de 60% fazia a sistema ficar lenta, acessando o swap,
# quando ainda havia muita memória livre
vm.vfs_cache_pressure=1000
# Aumentamos do padrão de 100. Fazemos o kernel descarregar
# páginas de memória não utilizadas do cache com mais frequência.
vm.zone_reclaim_mode=0
# Permite definir abordagens mais ou menos agressivas para
# recuperação de memória quando a zona está acabando. 
# Se definido como zero, não ocorre recuperação de zona.
# Para servidores de arquivos ou cargas de trabalho
# é vantajoso que os dados estejam em cache, deixar zone_reclaim_mode
# desligado, já que o efeito de caching, 
# provavelmente será mais importante do que a localização dos dados.
vm.dirty_ratio=20
# Percentagem da memória RAM que pode ser alocada para "páginas sujas"
# Calculado com base em uma estimativa: 
# No sistema, são 128 gigas de memória.
# Aproximadamente 20 discos SSD, nos quais as configurações do CEPH indicam 
# alocar 3G de RAM para caching.
# Aproximadamente 40 discos HDD, para os quais esse parâmetro é 1G
# 20% de 128 é 25.6 gigas. Portanto, em caso de máxima utilização da memória,
# para o sistema restarão 2.4G de memória. O que deve ser suficiente para sobreviver e esperar
# a chegada da cavalaria - ou seja, a chegada do DevOps que consertará tudo.
vm.dirty_background_ratio=3
# porcentagem da memória do sistema que pode ser preenchida com dirty pages até que,
# processos em segundo plano pdflush/flush/kdmflush as gravem no disco
fs.file-max=524288
# Portanto, provavelmente teremos muitos mais arquivos abertos do que os indicados por padrão. 

Imersão em CEPH

Configurações detalhadas que gostaria de explorar mais:

cat /etc/ceph/ceph.conf

osd:
    journal_aio: true               # Três parâmetros que incluem 
    journal_block_align: true       # I/O direto
    journal_dio: true               # para o journal
    journal_max_write_bytes: 1073714824 # Vamos estender um pouco o tamanho máximo
                                        # da operação de gravação no journal
    journal_max_write_entries: 10000    # E o número de gravações simultâneas
    journal_queue_max_bytes: 10485760000 
    journal_queue_max_ops: 50000
    rocksdb_separate_wal_dir: true      # Decidimos fazer um WAL separado
                                        # Até tentamos obter isso de forma adequada
                                        # NVMe
    bluestore_block_db_create: true     # E um dispositivo separado para o journal
    bluestore_block_db_size: '5368709120 #5G'
    bluestore_block_wal_create: true
    bluestore_block_wal_size: '1073741824   #1G' 
    bluestore_cache_size_hdd: '3221225472   # 3G' 
                                            # Grande volume de RAM permite 
                                            # armazenar volumes consideráveis
    bluestore_cache_size_ssd: '9663676416   # 9G' 

    keyring: /var/lib/ceph/osd/ceph-$id/keyring
    osd_client_message_size_cap: '1073741824 #1G'
    osd_disk_thread_ioprio_class: idle
    osd_disk_thread_ioprio_priority: 7
    osd_disk_threads: 2 # número de threads do demônio por disco
    osd_failsafe_full_ratio: 0.95
    osd_heartbeat_grace: 5
    osd_heartbeat_interval: 3
    osd_map_dedup: true
    osd_max_backfills: 2 # número de operações de preenchimento simultâneas em um OSD.
    osd_max_write_size: 256
    osd_mon_heartbeat_interval: 5
    osd_op_threads: 16
    osd_op_num_threads_per_shard: 1
    osd_op_num_threads_per_shard_hdd: 2
    osd_op_num_threads_per_shard_ssd: 2
    osd_pool_default_min_size: 1     # Características de ganância. O espaço se tornou muito
    osd_pool_default_size: 2         # escasso, portanto, a solução temporária
                                     # foi reduzir o número de
                                     # réplicas de dados
    osd_recovery_delay_start: 10.000000
    osd_recovery_max_active: 2
    osd_recovery_max_chunk: 1048576
    osd_recovery_max_single_start: 3
    osd_recovery_op_priority: 1
    osd_recovery_priority: 1            # parâmetros ajustáveis conforme necessário
    osd_recovery_sleep: 2
    osd_scrub_chunk_max: 4

Alguns parâmetros testados no QA na versão 12.2.12 estão ausentes na versão ceph 12.2.2, por exemplo osd_recovery_threads. Portanto, o plano incluía uma atualização para 12.2.12 em produção. A prática mostrou a compatibilidade em um cluster das versões 12.2.2 e 12.2.12, permitindo realizar uma atualização gradual.

Cluster de teste

Naturalmente, para os testes, era necessário ter a mesma versão que na produção, mas, no momento em que comecei a trabalhar com o cluster, apenas uma versão mais nova estava disponível no repositório. Ao verificar que as diferenças na versão menor não eram muito grandes (1393 linhas nos configs contra 1436 na nova versão), decidimos começar a testar a nova (de qualquer forma, teríamos que atualizar, então por que continuar com a versão antiga)

A única coisa que tentamos manter da versão antiga foi o pacote ceph-deploy, já que parte das utilitários (e parte da equipe) estava adaptada à sua sintaxe. A nova versão diferia bastante, mas não afetou o funcionamento do cluster em si, e mantivemos a versão 1.5.39

Como a equipe ceph-disk claramente afirma que está obsoleta e recomenda que usemos a equipe ceph-volume, começamos a criar OSD exatamente com esse comando, sem perder tempo com métodos ultrapassados.

O plano era criar um espelho de dois discos SSD, nos quais armazenaríamos os logs dos OSD que, por sua vez, estariam em SASs de disco giratório. Assim, nos protegeríamos contra problemas de dados em caso de falha do disco de log.

Começamos a criar o cluster com base na documentação

cat /etc/ceph/ceph.conf

root@ceph01-qa:~# cat /etc/ceph/ceph.conf # configuração previamente preparada
[client]
rbd_cache = true
rbd_cache_max_dirty = 50331648
rbd_cache_max_dirty_age = 2
rbd_cache_size = 67108864
rbd_cache_target_dirty = 33554432
rbd_cache_writethrough_until_flush = true
rbd_concurrent_management_ops = 10
rbd_default_format = 2
[global]
auth_client_required = cephx
auth_cluster_required = cephx
auth_service_required = cephx
cluster network = 10.10.10.0/24
debug_asok = 0/0
debug_auth = 0/0
debug_buffer = 0/0
debug_client = 0/0
debug_context = 0/0
debug_crush = 0/0
debug_filer = 0/0
debug_filestore = 0/0
debug_finisher = 0/0
debug_heartbeatmap = 0/0
debug_journal = 0/0
debug_journaler = 0/0
debug_lockdep = 0/0
debug_mon = 0/0
debug_monc = 0/0
debug_ms = 0/0
debug_objclass = 0/0
debug_objectcatcher = 0/0
debug_objecter = 0/0
debug_optracker = 0/0
debug_osd = 0/0
debug_paxos = 0/0
debug_perfcounter = 0/0
debug_rados = 0/0
debug_rbd = 0/0
debug_rgw = 0/0
debug_throttle = 0/0
debug_timer = 0/0
debug_tp = 0/0
fsid = d0000000d-4000-4b00-b00b-0123qwe123qwf9
mon_host = ceph01-q, ceph02-q, ceph03-q
mon_initial_members = ceph01-q, ceph02-q, ceph03-q
public network = 8.8.8.8/28 # endereço alterado, naturalmente ))
rgw_dns_name = s3-qa.mycompany.ru # e este endereço também
rgw_host = s3-qa.mycompany.ru # e este também
[mon]
mon allow pool delete = true
mon_max_pg_per_osd = 300 # mais de trezentos grupos de colocação
                          # para disco não foram decididos
                     # embora o parâmetro, naturalmente, dependa do número de pools,
                     # seus tamanhos e a quantidade de OSD. Ter poucos, mas saudáveis, PGs
                        # também não é a melhor escolha - a precisão do balanceamento sofre
mon_osd_backfillfull_ratio = 0.9
mon_osd_down_out_interval = 5
mon_osd_full_ratio = 0.95 # por enquanto, para discos SSD,
                          # o espaço para seus logs
                          # é o mesmo dispositivo que para OSD
                          # decidimos que 5% do disco (que é de 1.2Tb)
                          # deve ser mais do que suficiente, e corresponde ao parâmetro
                          # bluestore_block_db_size mais a variabilidade para grandes 
                          # grupos de colocação
mon_osd_nearfull_ratio = 0.9
mon_pg_warn_max_per_osd = 520
[osd]
bluestore_block_db_create = true
bluestore_block_db_size = 5368709120 #5G
bluestore_block_wal_create = true
bluestore_block_wal_size = 1073741824 #1G
bluestore_cache_size_hdd = 3221225472 # 3G
bluestore_cache_size_ssd = 9663676416 # 9G
journal_aio = true
journal_block_align = true
journal_dio = true
journal_max_write_bytes = 1073714824
journal_max_write_entries = 10000
journal_queue_max_bytes = 10485760000
journal_queue_max_ops = 50000
keyring = /var/lib/ceph/osd/ceph-$id/keyring
osd_client_message_size_cap = 1073741824 #1G
osd_disk_thread_ioprio_class = idle
osd_disk_thread_ioprio_priority = 7
osd_disk_threads = 2
osd_failsafe_full_ratio = 0.95
osd_heartbeat_grace = 5
osd_heartbeat_interval = 3
osd_map_dedup = true
osd_max_backfills = 4
osd_max_write_size = 256
osd_mon_heartbeat_interval = 5
osd_op_num_threads_per_shard = 1
osd_op_num_threads_per_shard_hdd = 2
osd_op_num_threads_per_shard_ssd = 2
osd_op_threads = 16
osd_pool_default_min_size = 1
osd_pool_default_size = 2
osd_recovery_delay_start = 10.0
osd_recovery_max_active = 1
osd_recovery_max_chunk = 1048576
osd_recovery_max_single_start = 3
osd_recovery_op_priority = 1
osd_recovery_priority = 1
osd_recovery_sleep = 2
osd_scrub_chunk_max = 4
osd_scrub_chunk_min = 2
osd_scrub_sleep = 0.1
rocksdb_separate_wal_dir = true

# создаем мониторы
root@ceph01-qa:~#ceph-deploy mon create ceph01-q
# генерируем ключи для аутентификации нод в кластере
root@ceph01-qa:~#ceph-deploy gatherkeys ceph01-q
# Это если поштучно. Если у нас несколько машин доступны - те, которые описаны в конфиге в секции 
# mon_initial_members = ceph01-q, ceph02-q, ceph03-q
# можно запустить эти две команды в виде одной
root@ceph01-qa:~#ceph-deploy mon create-initial
# Положим ключи в указанные в конфиге места
root@ceph01-qa:~#cat ceph.bootstrap-osd.keyring > /var/lib/ceph/bootstrap-osd/ceph.keyring 
root@ceph01-qa:~#cat ceph.bootstrap-mgr.keyring > /var/lib/ceph/bootstrap-mgr/ceph.keyring 
root@ceph01-qa:~#cat ceph.bootstrap-rgw.keyring > /var/lib/ceph/bootstrap-rgw/ceph.keyring
# создадим ключ для управления кластером
root@ceph01-qa:~#ceph-deploy admin ceph01-q
# и менеджер, плагинами управлять
root@ceph01-qa:~#ceph-deploy mgr create ceph01-q

A primeira que encontrei ao trabalhar com esta versão do ceph-deploy em um cluster da versão 12.2.12 foi um erro ao tentar criar um OSD com db em RAID de software —

root@ceph01-qa:~#ceph-volume lvm create --bluestore --data /dev/sde --block.db /dev/md0
blkid não conseguiu detectar um PARTUUID para o dispositivo: /dev/md1

De fato, blkid não mostra PARTUUID, precisei criar as partições manualmente:

root@ceph01-qa:~#parted /dev/md0 mklabel GPT 
# haverá muitas partições, 
# sem GPT não será possível criá-las
# o tamanho da partição foi indicado na configuração acima = bluestore_block_db_size: '5368709120 #5G'
# tenho 20 discos para OSD, está cansativo criar partições manualmente
# por isso fiz um loop
root@ceph01-qa:~#for i in {1..20}; do echo -e "nnnn+5Gnw" | fdisk /dev/md0; done

Parece que tudo está pronto, vamos tentar criar o OSD novamente e recebemos o seguinte erro (que, a propósito, não ocorreu em produção)

ao criar um OSD do tipo bluestore sem especificar um caminho para o WAL, mas com a referência ao db

root@ceph01-qa:~#ceph-volume lvm create --bluestore --data /dev/sde --block.db /dev/md0
 stderr: 2019-04-12 10:39:27.211242 7eff461b6e00 -1 bluestore(/var/lib/ceph/osd/ceph-0/) _read_fsid uuid não analisável
 stderr: 2019-04-12 10:39:27.213185 7eff461b6e00 -1 bdev(0x55824c273680 /var/lib/ceph/osd/ceph-0//block.wal) open abriu e obteve: (22) Argumento inválido
 stderr: 2019-04-12 10:39:27.213201 7eff461b6e00 -1 bluestore(/var/lib/ceph/osd/ceph-0/) _open_db add dispositivo de bloco(/var/lib/ceph/osd/ceph-0//block.wal) retornou: (22) Argumento inválido
 stderr: 2019-04-12 10:39:27.999039 7eff461b6e00 -1 bluestore(/var/lib/ceph/osd/ceph-0/) mkfs falhou, (22) Argumento inválido
 stderr: 2019-04-12 10:39:27.999057 7eff461b6e00 -1 OSD::mkfs: ObjectStore::mkfs falhou com erro (22) Argumento inválido
 stderr: 2019-04-12 10:39:27.999141 7eff461b6e00 -1 ** ERRO: erro ao criar armazenamento de objetos vazio em /var/lib/ceph/osd/ceph-0/: (22) Argumento inválido

Contudo, se em mesmo espelho (ou em outro lugar, à escolha) eu criar mais uma partição para o WAL e especificá-la ao criar o OSD — tudo funcionará bem (exceto pela criação de um WAL separado, que você talvez não quisesse).

Mas, como ainda está nos planos distantes mover o WAL para NVMe, a prática não foi em vão.

root@ceph01-qa:~#ceph-volume lvm create --bluestore --data /dev/sdf --block.wal /dev/md0p2 --block.db /dev/md1p2

Criamos monitores, gerenciadores e OSD. Agora queremos agrupá-los de diferentes maneiras, pois planejamos ter discos de tipos variados — pools rápidos em SSD e grandes, mas lentos em discos SAS.

Vamos considerar que há 20 discos nos servidores, a primeira dezena é um tipo, a segunda — outro.
O mapa inicial, padrão, é assim:

ceph osd tree

root@ceph01-q:~# ceph osd tree
ID CLASSE PESO TIPO NOME STATUS REAJUSTAR PRI-AF
-1 14.54799 raiz padrão
-3 9.09200 host ceph01-q
0 ssd 1.00000 osd.0 ativo 1.00000 1.00000
1 ssd 1.00000 osd.1 ativo 1.00000 1.00000
2 ssd 1.00000 osd.2 ativo 1.00000 1.00000
3 ssd 1.00000 osd.3 ativo 1.00000 1.00000
4 hdd 1.00000 osd.4 ativo 1.00000 1.00000
5 hdd 0.27299 osd.5 ativo 1.00000 1.00000
6 hdd 0.27299 osd.6 ativo 1.00000 1.00000
7 hdd 0.27299 osd.7 ativo 1.00000 1.00000
8 hdd 0.27299 osd.8 up 1.00000 1.00000
9 hdd 0.27299 osd.9 up 1.00000 1.00000
10 hdd 0.27299 osd.10 up 1.00000 1.00000
11 hdd 0.27299 osd.11 up 1.00000 1.00000
12 hdd 0.27299 osd.12 up 1.00000 1.00000
13 hdd 0.27299 osd.13 up 1.00000 1.00000
14 hdd 0.27299 osd.14 up 1.00000 1.00000
15 hdd 0.27299 osd.15 up 1.00000 1.00000
16 hdd 0.27299 osd.16 up 1.00000 1.00000
17 hdd 0.27299 osd.17 up 1.00000 1.00000
18 hdd 0.27299 osd.18 up 1.00000 1.00000
19 hdd 0.27299 osd.19 up 1.00000 1.00000
-5 5.45599 host ceph02-q
20 ssd 0.27299 osd.20 up 1.00000 1.00000
21 ssd 0.27299 osd.21 up 1.00000 1.00000
22 ssd 0.27299 osd.22 up 1.00000 1.00000
23 ssd 0.27299 osd.23 up 1.00000 1.00000
24 hdd 0.27299 osd.24 up 1.00000 1.00000
25 hdd 0.27299 osd.25 up 1.00000 1.00000
26 hdd 0.27299 osd.26 up 1.00000 1.00000
27 hdd 0.27299 osd.27 up 1.00000 1.00000
28 hdd 0.27299 osd.28 up 1.00000 1.00000
29 hdd 0.27299 osd.29 up 1.00000 1.00000
30 hdd 0.27299 osd.30 up 1.00000 1.00000
31 hdd 0.27299 osd.31 up 1.00000 1.00000
32 hdd 0.27299 osd.32 up 1.00000 1.00000
33 hdd 0.27299 osd.33 up 1.00000 1.00000
34 hdd 0.27299 osd.34 up 1.00000 1.00000
35 hdd 0.27299 osd.35 up 1.00000 1.00000
36 hdd 0.27299 osd.36 up 1.00000 1.00000
37 hdd 0.27299 osd.37 up 1.00000 1.00000
38 hdd 0.27299 osd.38 up 1.00000 1.00000
39 hdd 0.27299 osd.39 up 1.00000 1.00000
-7 6.08690 host ceph03-q
40 ssd 0.27299 osd.40 up 1.00000 1.00000
41 ssd 0.27299 osd.41 up 1.00000 1.00000
42 ssd 0.27299 osd.42 up 1.00000 1.00000
43 ssd 0.27299 osd.43 up 1.00000 1.00000
44 hdd 0.27299 osd.44 up 1.00000 1.00000
45 hdd 0.27299 osd.45 up 1.00000 1.00000
46 hdd 0.27299 osd.46 up 1.00000 1.00000
47 hdd 0.27299 osd.47 up 1.00000 1.00000
48 hdd 0.27299 osd.48 up 1.00000 1.00000
49 hdd 0.27299 osd.49 up 1.00000 1.00000
50 hdd 0.27299 osd.50 up 1.00000 1.00000
51 hdd 0.27299 osd.51 up 1.00000 1.00000
52 hdd 0.27299 osd.52 up 1.00000 1.00000
53 hdd 0.27299 osd.53 up 1.00000 1.00000
54 hdd 0.27299 osd.54 up 1.00000 1.00000
55 hdd 0.27299 osd.55 up 1.00000 1.00000
56 hdd 0.27299 osd.56 up 1.00000 1.00000
57 hdd 0.27299 osd.57 up 1.00000 1.00000
58 hdd 0.27299 osd.58 up 1.00000 1.00000
59 hdd 0.89999 osd.59 up 1.00000 1.00000

Vamos criar nossos próprios racks e servidores com blackjack e o resto:

root@ceph01-q:~#ceph osd crush add-bucket rack01 root #criado novo root
root@ceph01-q:~#ceph osd crush add-bucket ceph01-q host #criado novo host
root@ceph01-q:~#ceph osd crush move ceph01-q root=rack01 #moved server para outro rack
root@ceph01-q:~#osd crush add 28 1.0 host=ceph02-q # Adicionamos OSD ao servidor

# Se criamos de forma errada, podemos remover
root@ceph01-q:~# ceph osd crush remove osd.4
root@ceph01-q:~# ceph osd crush remove rack01

Problemas que encontramos em produção cluster ao tentar criar um novo host e movê-lo para um rack existente — o comando ceph osd crush move ceph01-host root=rack01 ficou travado, e os monitores começaram a cair um por um. A interrupção do comando com CTRL+C trouxe o cluster de volta à vida.

A pesquisa revelou o seguinte problema: https://tracker.ceph.com/issues/23386

A solução foi despejar o crushmap e remover a seção rule replicated_ruleset

root@ceph01-prod:~#ceph osd getcrushmap -o crushmap.row #Despejando o mapa em estado bruto
root@ceph01-prod:~#crushtool -d crushmap.row -o crushmap.txt #convertendo para legível
root@ceph01-prod:~#vim  crushmap.txt #editando, removendo rule replicated_ruleset
root@ceph01-prod:~#crushtool -c crushmap.txt  -o new_crushmap.row #compilando novamente
root@ceph01-prod:~#ceph osd setcrushmap -i  new_crushmap.row #carregando no cluster

Atenção: essa operação pode causar um reequilíbrio do placement group entre OSD. Isso ocorreu connosco, mas de forma muito pequena.

A estranheza que encontramos no cluster de testes foi que, após a reinicialização do servidor, os OSD esqueciam que haviam sido movidos para novos servidores e racks, retornando ao root default.
No final, ao construir o esquema final, onde criamos um root separado para os discos SSD e outro para os discos de spindle, distribuímos todos os OSD pelos racks e simplesmente removemos o default root. Após a reinicialização, os OSD permaneceram em seus lugares.
Depois, ao vasculhar a documentação, encontramos um parâmetro que controla esse comportamento. Ele será abordado na segunda parte.

Como fizemos vários grupos por tipos de discos.

Para começar, criamos dois roots — para SSD e para HDD.

root@ceph01-q:~#ceph osd crush add-bucket ssd-root root
root@ceph01-q:~#ceph osd crush add-bucket hdd-root root

Uma vez que os servidores físicos estão em racks diferentes — para facilitar, criamos os racks e neles colocamos os servidores.

# Стойки:
root@ceph01-q:~#ceph osd crush add-bucket ssd-rack01 rack
root@ceph01-q:~#ceph osd crush add-bucket ssd-rack02 rack
root@ceph01-q:~#ceph osd crush add-bucket ssd-rack03 rack

root@ceph01-q:~#ceph osd crush add-bucket hdd-rack01 rack
root@ceph01-q:~#ceph osd crush add-bucket hdd-rack01 rack
root@ceph01-q:~#ceph osd crush add-bucket hdd-rack01 rack

# Сервера
root@ceph01-q:~#ceph osd crush add-bucket ssd-ceph01-q host
root@ceph01-q:~#ceph osd crush add-bucket ssd-ceph02-q host
root@ceph01-q:~#ceph osd crush add-bucket ssd-ceph03-q host

root@ceph01-q:~#ceph osd crush add-bucket hdd-ceph01-q host
root@ceph01-q:~#ceph osd crush add-bucket hdd-ceph02-q host
root@ceph01-q:~#ceph osd crush add-bucket hdd-ceph02-q host

e distribuímos os discos por seus tipos em diferentes servidores.

root@ceph01-q:~# Os discos de 0 a 3 são SSD, estão no ceph01-q, colocamos eles no servidor 
root@ceph01-q:~#  ssd-ceph01-q
root@ceph01-q:~#ceph osd crush add 0 1 host=ssd-ceph01-q
root@ceph01-q:~#ceph osd crush add 1 1 host=ssd-ceph01-q
root@ceph01-q:~#ceph osd crush add 2 1 host=ssd-ceph01-q
root@ceph01-q:~#ceph osd crush add 3 1 host=ssd-ceph01-q
root-ceph01-q:~# similar com os outros servidores

Distribuindo os discos entre os roots ssd-root e hdd-root, deixamos o root-default vazio, então podemos removê-lo.

root-ceph01-q:~#ceph osd crush remove default

Em seguida, precisamos criar regras de distribuição que iremos vincular aos pools que estamos criando — nas regras, especificaremos em quais roots os dados do nosso pool podem ser armazenados e o nível de unicidade das réplicas — por exemplo, as réplicas devem estar obrigatoriamente em servidores diferentes, ou em racks diferentes (podemos até estar em roots diferentes, se tivermos essa distribuição).

Antes de escolher o tipo, é melhor consultar a documentação:
http://docs.ceph.com/docs/jewel/rados/operations/crush-map/#crushmaprules

root-ceph01-q:~#ceph osd crush rule create-simple rule-ssd ssd-root host firstn
root-ceph01-q:~#ceph osd crush rule create-simple rule-hdd hdd-root host firstn
root@ceph01-q:~# Especificamos duas regras, onde os dados são replicados 
root@ceph01-q:~# entre hosts — ou seja, a réplica deve estar em outro host,
root@ceph01-q:~# mesmo que estejam no mesmo rack.
root@ceph01-q:~# Em produção, se possível, é melhor distribuir os hosts
root@ceph01-q:~# pelos racks e indicar que as réplicas sejam distribuídas pelos racks:
root@ceph01-q:~# ##ceph osd crush rule create-simple rule-ssd ssd-root rack firstn

Vamos criar os pools onde desejamos armazenar as imagens de disco da nossa virtualização — PROXMOX:

    root-ceph01-q:~# #ceph osd pool create {NAME} {pg_num} {pgp_num}
    root-ceph01-q:~# ceph osd pool create ssd_pool 1024 1024 
    root-ceph01-q:~# ceph osd pool create hdd_pool 1024 1024

E definimos quais regras de colocação esses pools devem seguir

 root-ceph01-q:~# ceph osd crush rule ls # verificamos a lista de regras
    root-ceph01-q:~# ceph osd crush rule dump rule-ssd | grep rule_id # escolhemos a ID necessária
    root-ceph01-q:~# ceph osd pool set ssd_pool crush_rule 2

A escolha do número de grupos de colocação deve ser feita com uma visão prévia sobre seu cluster — quantos OSDs haverá aproximadamente, qual a quantidade de dados (em porcentagem do total) estará no pool, e quanto de dados no total.

No total, é aconselhável não ter mais de 300 grupos de colocação por disco, e será mais fácil balancear com grupos de colocação menores — ou seja, se todo o seu pool ocupa 10 TB e tem 10 PGs, balancear Transferindo blocos de terabytes (pg) será problemático — é mais fácil e uniforme despejar areia com pequenos grãos em baldes.

Mas deve-se lembrar que quanto maior o número de PGs — mais recursos são usados para calcular sua localização — a memória e a CPU começam a ser consumidas.

Um entendimento aproximado pode ser fornecido por uma calculadora, disponibilizada pelos desenvolvedores da documentação CEPH.

Lista de materiais:

https://blog.packagecloud.io/eng/2017/02/06/monitoring-tuning-linux-networking-stack-sending-data
http://www.admin-magazine.com/HPC/Articles/Linux-I-O-Schedulers
http://onreader.mdl.ru/MasteringCeph/content/Ch09.html#030202
https://tracker.ceph.com/issues/23386
https://ceph.com/pgcalc/

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster