Tag: infrastructure-as-code

Ansible + Windows: o básico, NTLM, DSC e mais.

Ansible + Windows: o básico, NTLM, DSC e mais.

Eu passei os últimos 6 anos trabalhando muito pouco quase nada com Windows. Meu coração está com o linux, mas eu não sou dado a fanatismos, e agora me encontro novamente trabalhando em um ambiente multiplataforma, com uso forte de Windows. Hora de aprender novos truques e tirar a poeira das minhas adormecidas Windows skills.

Parte disso está sendo testar como está o suporte de Windows no Ansible. Puppet, Salt (e acho que Chef também) tem um suporte mais avançado e/ou maduro a Windows, mas o Ansible tem avançado bastante recentemente. Nem tudo está muito bem documentado e difundido ainda, e nessa jornada eu descobri algumas jóias um pouco escondidas. Então, let’s blog.

Mantendo a filosofia agentless, o Ansible acessa o Windows remotamente usando recursos de acesso remoto do Powershell (Powershell Remoting). Para quem não sabe (eu não sabia), é possível abrir um console ou rodar comandos remotamente usando os comandos Enter-PSession e Invoke-Command no Powershell. Assim meio que parecido com um ssh, mas com uma sintaxe esquisita :D. Esse acesso remoto pode ser implementado de mais de uma maneira, usando RPC, WMI ou WS-Management, este último provido pelo serviço Windows Remote Management (WinRM). E é o WinRM que o Ansible utiliza.

Para isso tudo funcionar, algumas coisas precisam ser preparadas. Há scripts prontos para ajudar, mas vejamos uma lista:

  • Instalar o ansible, dãã.
  • Instalar a pywinrm na máquina que vai rodar o ansible
  • Habilitar o Powershell Remoting no Windows, se já não estiver (o procedimento depende da versão do Windows)
  • Habilitar o listener SSL no serviço WinRM do Windows (e gerar um certificado auto-assinado ou utilizar um “quente”).
  • Configurar o ansible para conectar usando o winrm e se autenticar …
    • … usando Kerberos …
    • … ou usando NTLM.
    • Nota: Pelo que investiguei eu aaacho que talvez seja possível se autenticar com base no certificado SSL do cliente (na ansible control machine), mas eu não testei nem achei nenhum documento sobre isso relacionado ao ansible.

Antes de botar na massa, só mais um comentário. O cenário normal e suportado é rodar o ansible em uma estação linux/unix. Para rodar o ansible a partir de uma estação Windows, a coisa complica. Cygwin não serve, Bash for Ubuntu no Windows 10 talvez, e eu já testei com sucesso uma solução usando docker. Mas isso é o assunto de outro post 😀

Mãos à obra

Leia a documentação oficial sobre Windows Support. Sério, leia, não é longa. Depois volte aqui e veja o meu resumo e o que funcionou pra mim.

(é, eu sei que você não vai ler, mas eu avisei)

Na máquina que roda o ansible (a control machine):

pip install "pywinrm>=0.1.1

Eu geralmente prefiro instalar o pacote da distro quando disponível, mas a versão pode acabar sendo antiga e não suportar alguma coisa, por exemplo a autenticação NTLM que eu explico abaixo. Teste.

No Windows, rode este script mencionado na documentação. Ele tem alguns parâmetros que você pode ou não precisar. Veja a seção Windows System Prep na documentação. Eu li e entendi o script. Ele gera um certificado auto assinado, configura um listener SSL no WinRM na porta 5986 (5985 é a padrão sem SSL), e libera essa porta no firewall para as redes privadas.

Dependendo da versão do Windows, o trabalho pode ser maior:

PowerShell 3.0 or higher is needed for most provided Ansible modules for Windows, and is also required to run the above setup script. Note that PowerShell 3.0 is only supported on Windows 7 SP1, Windows Server 2008 SP1, and later releases of Windows.

Eles sugerem um script para fazer o upgrade do powershell, veja na doc.

No meu ambiente, estou fazendo essa preparação do windows já na imagem base de Windows que estou gerando (go packer!). Daí quando a máquina nasce já está pronta para acesso via ansible.

Configurando

Precisamos passar para o ansible alguns parâmetros para ele saber como conectar nas máquinas windows. Eu faço isso associando elas a um grupo “windows” no inventário e aí criando este arquivo de vars:

$ cat group_vars/windows 
---
ansible_port: 5986
ansible_connection: winrm
ansible_winrm_server_cert_validation: ignore

Com tudo que já falamos, acho que é bastante auto-explicativo. Claro que é necessário ter as credenciais também. Dá para fornecê-las iterativamente, se for seu caso, com os parâmetros normais -u e –ask-pass, ou manter em outro arquivo de variáveis criptografado com ansible vault:

$ ansible-vault edit group_vars/ansiblelab 
Vault password: 
# opens in my $EDITOR:
---
ansible_user: myuser
ansible_password: mypassword

Bom, com isso, já dá para acessar a máquina utilizando o usuário da base local do Windows. Agora vamos ao AD…

Autenticando Windows com o Active Directory

Para autenticar no Windows, usando uma conta do AD (ao invés de uma conta local do servidor), o procedimento normal descrito na documentação do Ansible é utilizar Kerberos, tendo inclusive que colocar a control machine no domínio.

Arrrghh, que dor só de ouvir. E imagina se eu tiver mais de um domínio não relacionado (sem relação de confiança entre eles), vou ter que ter uma control machine para cada domínio?

Felizmente, não é bem assim. Para começar, é algo meio recente, mas dá para utilizar NTLM, que me parece uma alternativa muito mais simples e flexível.

Autenticando com NTLM

Basta adicionar essa opção aqui junto daquelas outras lá em cima:

ansible_winrm_transport: ntlm

Além disso, é preciso informar o domínio ao passar o usuário:

ansible_user: myuser@ANSIBLELAB.LOCAL
ansible_password: mypassword

Pronto, só isso! Note que eu usei o formato user@FULL.DNS.DOMAIN em vez do mais comum SHORTDOMAIN\user. Esse foi o jeito de funcionar da mesma com ntlm ou Kerberos, meu próximo teste.

Autenticando com Kerberos

Eu não sei se meu ambiente de teste (só nele eu testei com Kerberos) não representou bem a realidade, mas eu não precisei adicionar a control machine no domínio. No entanto, tem vários detalhes importantes:

  •  Obviamente, a control machine precisa enxergar perfeitamente o domínio DNS associado ao AD em questão.
  • O relógio precisa estar sincronizado, isso é básico para qualquer ambiente Kerberos.
  • O registro reverso no DNS precisa estar correto. Isso me mordeu, demorei a me dar conta. Confesso que agora não tenho certeza se só os registros dos servidores windows ou da control machine também. Eu acertei todos eles.
  • O inventário precisa utilizar o nome do servidor Windows, e não o IP.

O método NTLM não precisa dessas coisas, mas cabe notar que são todas boas práticas a serem seguidas de qualquer forma.

Pacotes:

yum -y install python-devel krb5-devel gcc krb5-libs krb5-workstation
pip install kerberos requests_kerberos

Eu criei um domínio de teste, chamado ANSIBLELAB (ansiblelab.local), promovendo a Domain Controller o meu host de teste ansiblelab-w02. Não vou entrar em detalhes aqui sobre como fazer isso no Windows.  Mas a configuração do Kerberos na minha control machine ficou assim:

# cat /etc/krb5.conf 
includedir /var/lib/sss/pubconf/krb5.include.d/ 
[logging] 
 default = FILE:/var/log/krb5libs.log 
 kdc = FILE:/var/log/krb5kdc.log 
 admin_server = FILE:/var/log/kadmind.log 
 
[libdefaults] 
 dns_lookup_realm = false 
 ticket_lifetime = 24h 
 renew_lifetime = 7d 
 forwardable = true 
 rdns = false 
 default_realm = ANSIBLELAB.LOCAL 
 default_ccache_name = KEYRING:persistent:%{uid} 
 
[realms] 
ANSIBLELAB.LOCAL = { 
  kdc = ansiblelab-w02.ansiblelab.local 
  kdc = ansiblelab-w02.ansiblelab.local 
} 
 
[domain_realm] 
.ansiblelab.local = ANSIBLELAB.LOCAL

E nas minhas variáveis do ansible, eu setei para a WinRM usar kerberos:

ansible_winrm_transport: kerberos

Para que o ansible possa se autenticar via Kerberos, eu preciso rodar o kinit (para obter o TGT). Eu criei um usuário configurado como admin das máquinas windows, para testar.

kinit -U myuser@ANSIBLELAB.LOCAL

Ele pede a senha, e pronto, depois é só conectar com o ansible informando apenas o usuário e domínio (igual ao mostrado acima com o NTLM), não precisa mais colocar a senha.

Testando

Para começar, aquele teste com módulo ping do ansible, sempre útil para testar a conectividade básica antes de mais nada. Só que no caso do windows, vocês deve usar o módulo win_ping:

ansible_win_ping

Se por acaso der um erro tal como “ssl: HTTPSConnectionPool(host=’w2′, port=5986): Max retries exceeded with url: /wsman (Caused by ConnectTimeoutError …”, então o listener SSL não foi habilitado na porta 5986 (veja lá em cima onde eu cito o script powershell que habilita isso).

Agora vamos rodar um playbook simples. Eu resolvi testar subir um virtual host no IIS, copiando os arquivos do site em um zip para o servidor. Veja aqui o playbook:

Destacando as principais tarefas nele:

  • Instala o IIS (win_feature, linha 8)
  • Cria o Website/VirtualHost (win_iis_website, linha 25)
  • Abre a porta do novo website no firewall (win_firewall_rule, linha 31)
  • Copia um arquivo zip com o conteúdo do site para um diretório de staging (win_copy, linha 45)
  • Descompacta o arquivo zip no diretório do site (win_unzip, linha 49)

Algumas outras tarefas menos interessantes estão ali também, para criar os diretórios envolvidos e para subir um arquivo index.html para o site default, em geral usando o win_file.

A execução fica assim:

ansible_iis2

 

 

Essa não foi a minha primeira execução, então algumas tarefas no início já estavam feitas (como a instalação do IIS), e ele não precisou fazer nada, marcando apenas como OK. O resto não tinha sido feito ainda e então ele executou e marcou como changed.

Esses dois warnings aparecem devido a algum bug inócuo introduzido recentemente. Nothing to worry about, eu achei dito na lista no Google Groups.

Mas tem um outro bug mais chato escondido aí. Ao executar novamente o playbook, a task do firewall me deu erro:

ansible_iis_error

The rule exists but has different values. Arrgh. A vida não é um moranguinho, definitivamente. Há um issue aberto. Uma “solução” é setar force=true neste módulo, mas daí ele executa toda vez, marcando sempre como changed. Indesejável, para dizer o mínimo.

Módulos para Windows, e DSC

A lista de módulos para Windows já é grandinha, e tem crescido a cada versão. Vimos alguns no exemplo acima. A maioria é específico, com prefixo win_, mas alguns poucos funcionam tanto para linux quanto para windows, como o módulo script, que pode fazer upload e executar scripts powershell, bash, python, whatever. Tirado da doc:

Note:: There are a few other Ansible modules that don’t start with “win” that also function with Windows, including “fetch”, “slurp”, “raw”, and “setup” (which is how fact gathering works).

Além disso, existem outros ainda não oficiais, e os mais interessantes que eu achei foram os que linkei abaixo relativos ao DSC. Eu não testei eles ainda, mas é preciso comentar sobre eles pois o potencial é gigante.

O que é DSC? Desired State Configuration (DSC) é um conjunto de extensões ao Powershell para uma gerência de configuração de forma declarativa, nativa do Windows. Existe já um caminhão de resources DSC para realizar as mais variadas tarefas. E estão no GitHub. Tem módulo Puppet para  DSC.

A partir do Powershell 5.0, é possível utilizar o DSC de forma mais isolada e pontual. O que permitiu um tal de trondhindenes criar um módulo ansible para executar DSC, win_dsc. Mas isso não é o melhor. Ele criou um gerador automático de módulos DSC pra ansible, com o qual ele conseguiu converter todo aquele caminhão módulos DSC já prontos, para módulos DSC ansible.

São 176 (!) módulos. Eu ainda não sei se eles funcionam muito bem (testarei, não precisei deles ainda), mas caso positivo, o suporte do Ansible a Windows está catapultado a outro patamar.

Considerações finais e próximos passos

É preciso lembrar que nem tudo é uma beleza. O ansible não é tão maduro quanto caras mais antigos como Puppet ou Chef, e o suporte dele a Windows é especialmente novo. Haverão pedras no caminho. Eu não fiz muita coisa séria ainda e já encontrei algumas, como o bug do win_firewall_rule acima. Sinceramente, acho que qualquer destas alternativas ainda vai dar uma dor de cabeça extra ao lidar com Windows. Mas o cenário está evoluindo e o ansible é uma alternativa muito interessante nele.

Coisas que estou testando e que podem virar posts logo logo:

  • Instalar o novo Docker for Windows com containers nativos do Windows 2016, usando Ansible.
  • Examinar as alternativas (i.e. gambiarras) disponíveis para rodar o Ansible a partir de uma estação Windows.
  • Examinar alguns problemas que tive executando alguns módulos no Windows Server Core.

Outras ideias interessantes a serem exploradas:

  • O Active Directory tem um recurso de geração e distribuição automática de certificados (Auto Enrollment) que eu acredito ser uma boa solução para evitar os certificados auto-assinados. A geração “em massa” de certificados auto-assinados é uma nojeira, e um risco de segurança. Com certificados de uma CA confiada a segurança iria ficar muito mais séria.
  • Dado o passo acima, testar a autenticação via certificado SSL do cliente fica mais fácil. Pelo que vi superficialmente, acho que o serviço WinRM suporta isso e a pywinrm também.
  • Obter as credenciais de acesso ao Windows via HashiCorp Vault, ou talvez armazená-las no Ansible Tower ou quem sabe do Rundeck.
  • Testar aqueles módulos DSC mencionados acima, ver se funcionam bem.

Se alguém lendo isso tiver alguma experiência com os itens acima, please compartilhe 😀

Criando pacotes facilmente com o FPM

Criando pacotes facilmente com o FPM

Uma tarefa bastante comum nessa vida de sysadmin linux é criar um pacote deb/rpm/whatever a partir do zero. Pode ser um software que não tem ainda empacotamento para a nossa plataforma, pode ser um software próprio nosso, um script ou um conjunto de scripts.

(Observação: quando o problema é reempacotar/recompilar algo que já existe como pacote, a história é outra. Daí eu sempre tento usar, e ajustar se necessário, o empacotamento que já existe. Em distros baseadas em debian, ao menos, isso costuma ser bem fácil.)

Isso vira uma tarefa mais comum ainda quando usamos ferramentas como Puppet, Ansible, etc., onde utilizar um pacote para instalação de um software qualquer fica muito mais natural, prático e “limpo”, do que ter que rodar configure/make/make-install ou ficar descompactando tgzs/zip através delas.

Eu já fiz pacotes deb from scratch e até já mergulhei também alguma longínqua vez  nos meandros de pacotes RPM. Funciona, e não é lá muito difícil, mas esses processos são meio overkill quando o que eu preciso é pouco mais do que um tar/zip versionado com algumas dependências simples, e quiçá um pre/post install script. Ainda mais se for para consumo interno apenas.

Quem é das antigas vai lembrar do checkinstall. Muito usei ele. E me incomodei com ele. Mas hoje em dia para esse tipo de empacotamento leve, eu só uso o FPM:

https://github.com/jordansissel/fpm

Vantagens do FPM:

  • É fácil, mas muuuuito fácil.
  • Suporta pacotes DEB e RPM, entre outros formatos de saída.
  • … e de entrada também (Python pip -> deb|rpm em um comando, exemplo mais abaixo)
  • Tem um monte de opções para especificar dependências, pre/post scripts, e mais.
  • Mais moderno, mais recursos, mais bem mantido e menos bugs que o checkinstall.

Instalando

Um pouco ironicamente, o FPM é instalado via (ruby) gem, e não por um pacote. Ele precisa do pacote ruby-dev, gcc e make (ruby-devel no Fedora/CentOS/RHEL):

apt-get install ruby-dev make gcc
gem install fpm

Criando pacotes

Vamos começar  por um caso super simples, converter um tar em um pacote. É um caso real: em um projeto para um cliente da Propus era desejado converter o tar.xz da distribuição binária do RabbitMQ em um pacote RPM. Baixado o tar.xz, basta:

# fpm -s tar -t rpm -C rabbitmq_server-3.6.2 --prefix /opt/rabbitmq \
   -n rabbitmq-custom -v 3.6.2 rabbitmq-server-generic-unix-3.6.2.tar.xz

no value for epoch is set, defaulting to nil {:level=>:warn}
no value for epoch is set, defaulting to nil {:level=>:warn}
Created package {:path=>"rabbitmq-custom-3.6.2-1.x86_64.rpm"}

Temos aí:

  • -s: o formato de origem (tar). Ele cuida da descompactação xz/gz automaticamente, não é preciso especificar nada.
  • -t: o formato de destino (rpm).
  • -C: (chdir) diretório daonde serão coletados os arquivos para o pacote. Nesse caso, rabbitmq_server-3.6.2 é o dir raiz presente no tar, mas eu só quero no pacote o que está dentro dele.
  • –prefix: o prefixo dos arquivos dentro do pacote.
  • -n: o nome do pacote.
  • -v: a versão.
  • o argumento final é a origem propriamente dita (o arquivo tar.xz)

Python

Com o FPM também é incrivelmente fácil gerar um pacote deb/rpm a partir de um pacote do PyPI (Python Package Index, aqueles instalados com o pip):

fpm -s python -t deb rt
fpm -s python -t deb -v 0.1.6 businesstime

Pronto, gerados pacotes deb da biblioteca rt (API wrapper do RequestTracker) e da versão 0.1.6 da biblioteca businesstime. Interessante que ele converte até as dependências do pacote no PyPI:

dpkg -I python-rt_1.0.9_all.deb |grep Depends
 Depends: python-requests, python-six

Makefile

Mas o que eu mais uso, disparado, é o FPM junto com um make install. Geralmente eu tenho um Makefile para qualquer projetinho que eu queria gerar um pacote. Já tenho um modelo pronto, que segue abaixo.

Nele eu defino vários metadados como descrição, versão, autor, arquitetura, etc (linhas 22 a 28) que serão passados ao FPM (linha 33). Também já tenho nele um exemplo de chamada para pacote RPM e DEB, definindo dependências e scripts de pós-instalação (a partir das linhas 62 e 79).

O vital no caso do make install é usar a variável $(DESTDIR) como prefixo de todos os caminhos utilizados no install. Ao rodar o make install para usar com o FPM, defino esse DESTDIR para um diretório temporário TMPINSTALLDIR (ver linha 73), e o mesmo diretório é passado ao FPM como o parâmetro -C. Os argumentos finais do comando fpm são os (sub)diretórios que foram criados pelo install dentro do TMPINSTALLDIR. No caso do exemplo, etc, usr e var (ver linha 83).

Eu geralmente tenho ainda um target publish no Makefile, que já faz o upload do pacote para o repositório APT privado, configurado usando o reprepro. Mas isso seria assunto para outro post :D.

Concluindo

Existem muitas formas de gerar pacotes DEB e RPM, mas usar o FPM é disparado a mais fácil, prática e flexível que eu conheço. Empacote!

service blog start

Well, it’s about time to kick this blog alive.

So this is a blog about automation in the context of system administration. Automation is such a broad term. For example, monitoring is for me a little bit automation. It automates away the need to regularly check things by hand, which is paramount, by the way.

Shell scripting, ssh multiplexers, libraries and tools for automating non-interactive deployments or both text and visual interactive applications are all great tools to have under any sysadmin’s belt, and I’ve been using then for many years. They are still very useful, but recently I added the ultimate automation tool to my belt: a configuration management software. And that’s a role new game, called Infrastructure as Code.

Infrastructure as code is not just about making you type less, tough. It’s about consistency, repeatability, executable documentation, versioning, peer reviewing, testing. Automation is cool, but realizing the benefits from all these things together is what really excites me.

There are quite a few config management tools out there, and I decided to go with Puppet. So far I’m very happy with it, and it will certainly be a frequent topic here.

Update: my friend foscarini pointed out another quite interesting config management alternative.