sábado, 24 de setembro de 2016

Cluster MidiaCenter com Nas4Free

Meu objetivo é fazer um log de como construí um cluster com NAS4FREE utilizando CARP+HAST+ZFS com Plex MediaCenter, CouchPotato e Sonarr. Um NAS com MediaCenter com  controles de falha completo(failover).
  1. Configurar o CARP
  2. Configurar o HAST
  3. Criando Unidades ZFS
  4. Failover (Complete Failover)
       Corrigindo Desligamento
       Montando discos AutoMágicamente
       Definindo hast roles no boot com Scarpd
  5. Instalado Plex, CouchPotato, Sonarr 
  6. Referências 

O NAS4FREE está instalado em um pendrive de 8GB com full install em ambos os nodes... Estou usando o FreeBSD 10.3.13 neste NAS4FREE. 
Vamos ao que interessa:


Common Address Redundancy Protocol (CARP)

 A primeira coisa à fazer é subir as interfaces CARP o cenário fica assim:

Temos duas placas de rede em cada nó, a rede 13 está falando por um cabo crossover para evitar que o tráfego do HAST passe pelo switch.
A segunda interface de rede tem o CARP configurado falando pela subrede 192.168.101.0/27  e irá responder pelo switch com ip 192.168.101.29 quando for o CARP MASTER.

Uma analogia do CARP ao universo Linux sería o HeartBeat.
O nó master fica com 192.168.101.1 e o nó backup(slave) com 192.168.101.2, o ip do cluster no CARP será o 29.

O CARP é muito simples, não tem nenhum pulo do gato até aqui... o NAS4FREE ainda tem uma GUI que torna tudo literalmente orientado a click, pelo menos até esta parte... ;)
Como o NAS4FREE persiste as configurações em um arquivo xml por enquanto vou seguir o manual e acertar o CARP pela GUI.

Network --> Interface Management --> Carp


O virtual IP address é o ip do CARP, no caso o 29 a configuração é igual em ambos os nós, com exceção do advskew que deve ser menor no node master. O menor advskew prevalece como master. No meu caso está 1 pro MASTER e 100 para o BACKUP.
A cultura do click é estranha pra mim, então só pra deixar claro na prática oque esta acontecendo é basicamente isso:
#kldload carp
#ifconfig re0 inet vhid 1 pass mypassword advskew 1 alias 192.168.101.29/27 up

Cada node, carregando o modulo do carp e subindo interfaces virtuais com advskew diferentes. Para ser permanente devería ser editado em rc.conf e o módulo no loader.conf. Este é só um comparativo simples do que está acontecendo com estes cliques na GUI.

So far, so good.... Após aplicar estes passos em cada node o CARP está configurado...

High Avaiable Storage (HAST)

O DRDB do universo linux...  Imagine "RAID1" pela rede...
A arquitetura do cluster para o HAST está ilustrada aqui:

Cenário é simples mas héterogeneo.
O NODE-0 com 4 discos e NODE-1 com apenas 2 discos. Cada nó possui um disco ada0 com 1,5TB de capacidade que será espelhado a nível de bloco com o HAST e outro HD de 500GB.
O motivo de não ter espelhado todos os discos é simples... $$$. HD's são caros no Brasil e esses são os discos que tenho pra resolver o caso.
Também pode estar querendo saber porque raios o ada1 no NODE-1 não é o ada1 no NODE-0... Bom isso é prq eu estava com preguiça de abrir a máquina pra inverter a posição do cabo sata ;-P

Segui a GUI em  : Services --> HAST --> Resources

lá criei dois resources... um com nome de midia e outro com nome share respectivamente ada0 e ada3.

Estou deixando o ada0 com nome de hast resource midia no NODE-0 será mapeado como ada0  em NODE-1 também.
O ada3 do NODE-0 será o ada1 no NODE-1.

A lógica dos resources é assim na implementação manual no arquivo /etc/hast.conf

resource midia {
   on  node0 {
       local ada0
       remote 13.13.13.2
    }
   on node1 {
       local ada0
       remote 13.13.13.1
    }
}
resource share{
    on node0 {
        local ada3
        remote 13.13.13.2
    }
   on node1{
        local ada1
        remote 13.13.13.1
    }
}


WARNING! 
Estou usando os IP's da rede crossover para o HAST pra evitar que o sync dos discos fique passando pelo switch.

PS: Se na hora de aplicar os resources tiver um erro ao criar o resource faça um wipe no disco antes de adiciona-los ao HAST.
Eu tenho um script que faz esse wipe:

Wipe disk
#!/bin/bash
echo "What disk do you want"
echo "to wipe? For example - ada1 :"
read disk
echo "OK, in 10 seconds I will destroy all data on $disk!"
echo "Press CTRL+C to abort!"
sleep 10

diskinfo ${disk} | while read disk sectorsize size sectors other
do
    # Delete MBR, GPT Primary, ZFS(L0L1)/other partition table.
    /bin/dd if=/dev/zero of=/dev/${disk} bs=${sectorsize} count=8192
    # Delete GEOM metadata, GPT Secondary(L2L3).
    /bin/dd if=/dev/zero of=/dev/${disk} bs=${sectorsize} oseek=$(expr $sectors - 8192) count=8192
done

Ok... agora tenho os discos configurados nos dois nós vou acertar o hast pela linha de comando.

NODE-0 MASTER
hastctl create midia
hastctl create share
service hastd onestart
hastctl role primary all

 
NODE-1 BACKUP(SLAVE)
hastctl create midia
hastctl create share
service hastd onestart
hastctl role secondary all
O status deles agora dever ser complete
PUTS! :/ o resource midia não esta subindo... matei tudo...
hastctl role init all
service hastd forcestop


Vou subir o hastd agora em modo ativo pra ver os logs do porque não foi pra frente no resource midia de 1,5TB

subindo hastd no-detach
$hastd -d
Logs reclamado dos resources com tamanhos diferentes


Agora entendi... ele nao quer syncar porque esta falando que meu disco de 1.5TB é alguns bytes maior que o outro... puts...

Vou recriar o device forçando o tamanho certo:
$hastctl create -m 1465137527k midia
Recriei o device de novo com o tamanho forçado nos dois nós...

Agora subi o hastd de novo e vamos ver o status...
$hastctl status

Opa... maravilha... agora esta tudo sincado...

Só um conferes do dump deles...
$hastctl dump -c /etc/hast.conf
Beleuza... tudo certo...

ZFS

Agora vamos formatar... precisamos de um filesystem... Eu optei por ZFS... Simplismente porque ZFS é muito massa... hehehehe
Uma observação importante é que a formatação seja feita apenas no MASTER... o HAST trabalha à nível de bloco... então só precisa ser feito num lado, o lado primario do HAST. Os devices vão ter spawn em /dev/hast, então vão poder ser montados sempre sómente quando o nó for master com hast role primário.

Vamos seguir essa formatação pela GUI do NAS4FREE...
Mas antes... vamos importar os devices na GUI.
Importei os discos em:

Disks --> Management --> HDD Management --> Import Disks

Com os hasts devices mapeados na GUI vou formatar com zfs:
Disks --> Management --> HDD Format ... etc...

Depois só criar os virtual devices e os pools.  Tenho tudo ok pela GUI.

Failover Complete

Agora chegou a hora de arregaçar as mangas e botar a mão na massa... Eu quero um FAILOVER COMPLETO.  Quero desligar uma e ter meus serviços migrando e iniciando sózinhos sem nenhuma intervenção humana!
Meus testes de Failover em checklist devem funcionar nos seguintes cenários:
----------------------------------------------------------------------------------

IF        NODE-0 == DOWN
THEN NODE-1 == BECOMES MASTER


IF        NODE-0 == UP AGAIN
THEN NODE-1 == STIIL MASTER (net.inet.carp.preempt=0)
AND   NODE-0 == BECOMES SLAVE


IF        NODE-1 == DOWN
THEN NODE-0 == BECOMES MASTER AGAIN
IF        NODE-1 == UP
THEN NODE-1 == BACK TO SLAVE

4º  Quando os dois forem ligados ao mesmo tempo o advskew deve agir 
IF        NODE-0 == DOWN
AND   NODE-1 == DOWN
IF        NODE-0 == UP
AND   NODE-1 == UP
THEN NODE-0 == SE TORNA MASTER POR advksew hast primario pelo scarpd
THEN NODE-1 == SE TORNA SLAVE POR advskew hast secundario pelo scarpd
----------------------------------------------------------------------------------
 Agora que as configurações do CARP e HAST  já "estão ok". Vou começar meu checklist de failover DESLIGADNO o NODE-0 e nessa teoria o NODE-1 deverá se tornar o MASTER.
#shutdown -p now

OPS! :(
Simplismente não quer desliga... Depois de vários testes descobri que  quando estou com as unidades ZFS HAST montadas o NAS4FREE não consegue desmontar e não desliga nunca... Fiz um teste pra ver se o watchdog entrava em ação, mas a máquina ficou na sequencia de shutdown por 7 horas seguidas e não desligou...
Essa falha de não desligar já entrou pro failover, já que vou ter de tratar o desligamento e a inicialização então depois de um tempo estudando porque não desliga, resolvi fazer algumas modificações no Nas4free...

Corrigindo o Desligamento do NAS4FREE com CARP+HAST+ZFS


Para corrigir o desligamento com ZFS + HAST preciso caçar o coelho na toca vendo oque o devd deve fazer quando a interface CARP mudar pro state DOWN. Mas como não desmonta os discos hasts, então não consegue deixar nunca a interface DOWN. E o problema de desligar começa a ficar grande...

Quando cria a interface carp no NAs4Free ele cria a configuração para a interface CARP em /etc/devd, nesse diretório deve heistir um arquivo carp_1@SUAcarpiface.conf.

notify 30 {

        match "system" "CARP";
        match "subsystem" "1@re0";
        match "type" "MASTER";
        action "/usr/local/sbin/carp-hast-switch master";
};
notify 30 {
        match "system" "CARP";
        match "subsystem" "1@re0";
        match "type" "BACKUP";
        action "/usr/local/sbin/carp-hast-switch slave";
};


Bom fica claro aí que a ação que ele deve chamar é o script carp-hast-switch com o case master ou slave.
Até aí nada demais...
Mas se formos depurar o script carp-hast-switch vamos descobrir que oque ele faz nada mais é que mudar a interface carp de BACKUP para MASTER e virar os discos hasts secundários para primarios...
Mas quem cuida de fato do HAST é o hastswitch... O hastswitch é chamado pelo carp-hast-switch para lidar com as unidades HAST detectando quem são qual o filesystem e qual tomada de acção. Tudo claro trazendo sempres os dados do /conf/config.xml que é onde o NAS4FREE guarda as configurações da GUI.

Ta ok... Detectei que no hastswitch no case shutdown ele não estava exportando os pools do zfs depois de desmontar então resolvi alterar no daemon do zfs deixando  a função zfs_stop_main do arquivo /etc/rc.d/zfs assim:

zfs_stop_main()

{
        zfs unshare -a
        zfs unmount -a
        resources=`hastctl dump -c /etc/hast.conf|grep resource|awk '{print $2}'`
        for activepools in $resources
        do
                zpool export $activepools
        done

}

Ps: Não era extremamente necessário modificar o daemon do zfs, podia ser tudo modificado no script hastswitch mas já que modifiquei lá resolvi documentar...
Modifiquei também a função shutdown acrescentando o stop do daemon zfs lá... E assim ficou sanado a primeira parte desse failover... O shutdown...

shutdown)

        # state to backup
        /usr/local/bin/xml sel -t \
                -m "//vinterfaces/carp" \
                        -v "if" \
                        -n \
                -b \
            ${configxml_file} | /usr/local/bin/xml unesc | \
        while read _if; do
                if [ "${PREEMPT}" = "0" -a  -n "$_if" ]; then
                    /usr/bin/logger -p $log -t $name "Change ${_if} state to BACKUP."
                    /sbin/ifconfig "${_if}" state backup 2>/dev/null
                    /bin/sleep 1
                elif [ "${PREEMPT}" != "0" -a  -n "$_if" ]; then
                    /usr/bin/logger -p $log -t $name "Change ${_if} state to INIT."
                    /sbin/ifconfig "${_if}" down 2>/dev/null
                    /bin/sleep 1
                fi
        done
        /bin/sleep 5
        /etc/rc.d/zfs stop
        ;;


OK! :) Agora que a maquina desliga consigo observar que o  NODE-1  realmente se tornou MASTER na interface CARP e os discos HAST estão aparecendo como primarios, porém ele não esta importando os pools...

Montando os discos automágicamente:


Quando o BACKUP se torna MASTER, você agora precisa que estes pools também estejam importados devidamente na GUI do nas4free então agora podemos trazer as configurações do zfs para este nó que ainda estava sem estes pools mapeados da seguinte forma:

Disks --> ZFS --> Configuration --> Synchronize

Aqui temos o problema do ovo e da galinha... O hastswitch start deve importar os pools, dos discos zfs... Porém ele se baseia no config.xml da gui pra fazer isso, então é provável que quando tente importar os pools aqui pela GUI não tenham pools sendo listandos porque não foram importados...

Então vamos resolver o problema do ovo...
$zpool import -f midia
$zfs import -f share

Agora os pools estão importados eles devem aparecer na GUI para serem sincronizados.

Eu acabei fazendo uma mudança significativa no hastswitch start para lidar com pools zfs como esse pedaço inteiro:

start)

        # import ZFS pool
        hast_pools=$(xml sel -t -m "//disk" --if "type='HAST'" --if "fstype='zfs'" -v name -n -b ${configxml_file})
        for pools in $hast_pools
        do
                /usr/bin/logger -p $log -t $name "Import ZFS pool $pools."
                /sbin/zpool import -f "$pools"
                if [ $? -ne 0 ]
                then
                        zfs mount $pools
                fi
        done 


Estou deixando todos os scripts que modifiquei neste repositório do github.

Bom nesse momento com hastswitch modificado no start e shutdown eu posso fazer o segundo teste do Failover que será desligar os nós alternadamente, pra ver os discos sendo montados e desmontados de um pra outro enquanto eles se tornam MASTER e BACKUP(slave)...

Fiz várias vezes este failover de alternar entre elas, que seria o failover 1º,2º e 3º e tudo funciona muito bem...

Vou agora pro teste 4º, onde desligo as duas ao mesmo tempo e ligo de novo... Como não estou usando preempt isso pode dar um slpit brain... Mesmo com advskew diferentes...
Bom, oque aconteceu foi que o advskew agiu certo na interface CARP tornando uma master a outra SLAVE, mas... os discos HAST não formam montandos porque o devd só entra em ação quando o LINK  sai de UP pra DOWN... mas nesse caso o devd não tomou partida durante a inicialização da maquina e nao rodou o script em action, com isso os discos ficarm sem role tanto no MASTER como no BACKUP e assim não monta os hasts...

Forçando uma ação pro HAST na inicialização (scarpd):


Eu não achei ninguem falando sobre esse caso... Então resolvi escrever um daemon que rode depois do HAST que verifique quem é MASTER e quem é SLAVE e defina o role pro resource do hast. Batizei esse serviço de scarpd.

#!/bin/sh
#
#
# Detectd whos is master nad whois slave and set hast initial roles
# this is failover util in case both nodes are shutdown and need to start at same time
# by: Igor Brandao
# PROVIDE: scarpd
# REQUIRE: NETWORKING syslogd hastd
# RCVAR: scarpd

. /etc/rc.subr

name="scarpd"
desc="Detects whos master and whos slave"
rcvar="${name}_enable"
load_rc_config "${name}"

start_cmd="${name}_start"
stop_cmd="${name}_stop"
carps=`/usr/local/bin/xml sel -t -m "//vinterfaces/carp" -v if -n -b /conf/config.xml`
hast_set_state(){
if [ ! -e /var/run/hastctl ]; then
   sleep 2
   if [ ! -e /var/run/hastctl ]; then
        # hastd is not started
        return 0
    fi
fi

# get carp interfaces
carps=`/usr/local/bin/xml sel -t -m "//vinterfaces/carp" -v if -n -b /conf/config.xml`
STATE="unknown"
if [ -n "${carps}" ]; then
        for if in ${carps}; do
                STATE=`/sbin/ifconfig ${if} | grep  "carp:" | awk '{ print tolower($2) }'`
        done
fi
if [ $STATE == master ]
then
        hastctl role primary all
        /usr/local/sbin/hastswitch start

elif [ $STATE == backup ]
then
        hastctl role secondary all
        /usr/local/sbin/hastswitch stop
fi
}


scarpd_start()
{
        hast_set_state
}

scarpd_stop()
{
        hastctl role init all
load_rc_config $name
run_rc_command "$1"

O scarpd também esta no meu github. Você deve chamar ele em rc.conf com scarpd_enable="YES"
Bom nessa etapa... O Failover já é meu amigo... e tudo funciona perfeitamente, agora vem a diversão...

Instalando CouchPotato+Plex+Sonarr


Realmente não tem segredo nas próximas etapas, eu poderia criar jails, e até mesmo usar o finch para estes serviços, mas fui curto e grosso... intalei tudo direto:

1º Mudar o repositorio do /etc/pkg/FreeBSD.conf pra latest.

# url: "pkg+http://pkg.FreeBSD.org/${ABI}/quarterly",
  url: "pkg+http://pkg.FreeBSD.org/${ABI}/latest",


2º Instalando pacotes:
pkg install plexmediaserver sonarr python py27-sqlite3 fpc-libcurl docbook-xml git-lite

3º Instalando o couchpotato
git clone https://github.com/CouchPotato/CouchPotatoServer.git
ln -s /usr/local/bin/python /usr/bin/python
cp CouchPotatoServer/init/freebsd /usr/local/etc/rc.d/couchpotato&&chmod +x /usr/local/etc/rc.d/couchpotato


4º Linkar tudo em disco HAST
Não da pra equecer que por default sem jail as configuraçöes desses aplicativos ficam em /usr/local/ então linkar pra um disco hast os diretorios de configuração pode ser uma boa ideia...
mv /usr/local/couchpotato /mnt/hast/pool
ln -s  /usr/local/couchpotato /mn/t/hast/pool/couchpotato
mv /usr/local/sonarr /mnt/hast/pool
ln -s /mnt/hast/pool/sonarr /usr/local/sonarr
mv plexdata /mnt/hast/pool
ln -s /mnt/pool/hast/plexdata /usr/local/plexdata

 
5º Adcionar todos eles no rc.conf


Blz... aqui ja está tudo rodando, Cluster,couch,potato,sonarr etc... Uma última observação é que os serviços devem ser sempre colocados com no hastswitch start e stop... assim o failover dos serviços sonarr, coucpotato e plex ficam funcionando também...
Enjoy :)


Bibliografias e Leituras úteis:

https://www.freebsd.org/doc/en_US.ISO8859-1/books/handbook/carp.html
https://www.freebsd.org/doc/handbook/carp.html
https://www.freebsd.org/doc/handbook/disks-hast.html
https://www.freebsd.org/cgi/man.cgi?query=devd.conf&sektion=5&apropos=0&manpath=FreeBSD+10.3-RELEASE+and+Ports
https://wiki.freebsd.org/HAST
https://forums.freebsd.org/threads/29639/
https://forums.freebsd.org/threads/38397/
http://forums.nas4free.org/viewtopic.php?t=1271
https://forums.freebsd.org/threads/49492/
http://blog.uni.cl/2015/06/freebsd-stockage-zfs-haute-disponibilite-avec-hast/
https://www.freebsd.org/doc/handbook/zfs-zpool.html
http://forums.nas4free.org/viewtopic.php?f=80&t=2333
https://lists.freebsd.org/pipermail/freebsd-fs/2013-October/018480.html
https://www.bsdinfo.com.br/artigos/FreeBSD_e_Alta_Disponibilidade_com_HAST+CARP+ZFS.pdf