O RHEL 8 Beta oferece aos desenvolvedores muitas novas oportunidades, o que poderia ocupar páginas, no entanto, sempre é melhor aprender algo novo na prática. Portanto, temos o prazer de apresentar um workshop prático sobre a criação real de infraestrutura de aplicativos com base no Red Hat Enterprise Linux 8 Beta.

Usaremos o Python, uma linguagem de programação popular entre os desenvolvedores, junto com a combinação Django e PostgreSQL, que é bastante comum para a criação de aplicativos, e configuraremos o RHEL 8 Beta para trabalhar com eles. Depois, adicionaremos mais alguns (não secretos) ingredientes.
O ambiente de testes mudará, pois é interessante explorar as possibilidades de automação, trabalhar com contêineres e experimentar ambientes com vários servidores. Para iniciar um novo projeto, podemos começar a criar um pequeno protótipo simples manualmente – assim, podemos ver o que deve acontecer e como a interação ocorre, e depois avançar para a automação e a criação de configurações mais complexas. Hoje falaremos sobre a criação desse protótipo.
Começaremos com a implantação da imagem da máquina virtual RHEL 8 Beta VM. Você pode instalar a máquina virtual do zero ou usar a imagem convidada KVM, disponível com a assinatura Beta. Ao usar a imagem convidada, precisaremos configurar um CD virtual que conterá os metadados e os dados do usuário para a inicialização em nuvem (cloud-init). Não é necessário fazer nada especial com a estrutura do disco ou os pacotes disponíveis; qualquer configuração será suficiente.
Vamos dar uma olhada em todo o processo em mais detalhes.
Instalação do Django
Com a versão mais recente do Django, precisaremos de um ambiente virtual (virtualenv) com Python 3.5 ou versão mais recente. Nas notas da Beta, pode-se ver que o Python 3.6 está disponível; vamos verificar se isso é verdade:
[cloud-user@8beta1 ~]$ python
-bash: python: comando não encontrado
[cloud-user@8beta1 ~]$ python3
-bash: python3: comando não encontrado
A Red Hat utiliza Python como ferramenta de sistema no RHEL, então por que estamos obtendo esse resultado?
O fato é que muitos desenvolvedores que utilizam Python ainda estão considerando a transição do Python 2 para o Python 3, sendo que o Python 3 está em desenvolvimento ativo e novas versões continuam a ser lançadas. Portanto, para atender à demanda por ferramentas de sistema estáveis e, ao mesmo tempo, oferecer aos usuários acesso a várias versões novas do Python, o Python do sistema foi movido para um novo pacote, permitindo a instalação tanto do Python 2.7 quanto do 3.6. Informações mais detalhadas sobre as mudanças e sobre por que isso foi feito podem ser encontradas na publicação no (Langdon White).
Assim, para obter um Python funcional, é necessário instalar apenas dois pacotes, sendo que o python3-pip será instalado como uma dependência.
sudo yum install python36 python3-virtualenv
Por que não devemos usar chamadas diretas ao módulo, como sugerido por Langdon, e não instalar o pip3? Tendo em mente a automação futura, é sabido que o Ansible requer o pip instalado, pois o módulo pip não suporta ambientes virtuais (virtualenvs) com um executável pip personalizado.
Com um interpretador python3 funcionando, podemos continuar o processo de instalação do Django e obter um sistema funcional junto com nossos outros componentes. Existem muitas opções de implementação disponíveis online. Esta é uma versão, mas os usuários podem utilizar seus próprios processos.
As versões do PostgreSQL e do Nginx disponíveis no RHEL 8 por padrão serão instaladas usando o Yum.
sudo yum install nginx postgresql-server
Para o PostgreSQL, precisaremos do psycopg2, mas é necessário que ele esteja disponível apenas no ambiente virtual, portanto, iremos instalá-lo com pip3 junto com Django e Gunicorn. Mas primeiro precisamos configurar o virtualenv.
Sempre há muitas discussões sobre a escolha correta do local de instalação dos projetos Django, mas quando há dúvidas, pode-se sempre consultar o padrão Linux Filesystem Hierarchy Standard. Em particular, o FHS afirma que /srv é usado para: "armazenar dados específicos do nó, ou seja, dados fornecidos pelo sistema, como dados e scripts de servidores web, dados armazenados em servidores FTP, bem como repositórios de sistemas de controle de versão (os quais apareceram no FHS-2.3 em 2004)".
Este é exatamente o nosso caso, então colocamos tudo que é necessário em /srv, cuja propriedade é do nosso usuário de aplicativo (cloud-user).
sudo mkdir /srv/djangoapp
sudo chown cloud-user:cloud-user /srv/djangoapp
cd /srv/djangoapp
virtualenv django
source django/bin/activate
pip3 install django gunicorn psycopg2
./django-admin startproject djangoapp /srv/djangoapp
Configurar o PostgreSQL e o Django não é complicado: criamos um banco de dados, criamos um usuário e configuramos as permissões. Há um ponto que você deve lembrar ao instalar o PostgreSQL – é o script postgresql-setup, que é instalado juntamente com o pacote postgresql-server. Este script ajuda a executar tarefas básicas de administração do cluster de bancos de dados, como inicializar o cluster ou o processo de atualização. Para configurar uma nova instância do PostgreSQL no sistema RHEL, precisamos executar o comando:
sudo /usr/bin/postgresql-setup -initdb
Depois disso, podemos iniciar o PostgreSQL usando o systemd, criar o banco de dados e configurar o projeto no Django. Não se esqueça de reiniciar o PostgreSQL após fazer alterações no arquivo de configuração de autenticação do cliente (geralmente pg_hba.conf) para configurar o armazenamento da senha para o usuário da aplicação. Se você encontrar outras dificuldades, verifique se as configurações de IPv4 e IPv6 no arquivo pg_hba.conf foram alteradas.
systemctl enable --now postgresql
sudo -u postgres psql
postgres=# create database djangoapp;
postgres=# create user djangouser with password 'qwer4321';
postgres=# alter role djangouser set client_encoding to 'utf8';
postgres=# alter role djangouser set default_transaction_isolation to 'read committed';
postgres=# alter role djangouser set timezone to 'utc';
postgres=# grant all on DATABASE djangoapp to djangouser;
postgres=# q
No arquivo /var/lib/pgsql/data/pg_hba.conf:
# IPv4 local connections:
host all all 0.0.0.0/0 md5
# IPv6 local connections:
host all all ::1/128 md5
No arquivo /srv/djangoapp/settings.py:
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': '{{ db_name }}',
'USER': '{{ db_user }}',
'PASSWORD': '{{ db_password }}',
'HOST': '{{ db_host }}',
}
}
Após configurar o arquivo settings.py no projeto e a configuração do banco de dados, você pode iniciar o servidor de desenvolvimento para garantir que tudo esteja funcionando. Após iniciar o servidor de desenvolvimento, é uma boa ideia criar um usuário admin para testar a conexão com o banco de dados.
./manage.py runserver 0.0.0.0:8000
./manage.py createsuperuser
WSGI? O que é isso?
O servidor de desenvolvimento é útil para testes, mas para executar a aplicação, é necessário configurar o servidor e o proxy adequados para o Web Server Gateway Interface (WSGI). Existem várias combinações comuns, como Apache HTTPD com uWSGI ou Nginx com Gunicorn.
A função do Web Server Gateway Interface é redirecionar solicitações de servidor web para o framework web Python. WSGI é um tipo de herança de um passado problemático, quando os mecanismos CGI estavam em uso, e hoje o WSGI é de fato um padrão, independentemente do servidor web ou framework Python utilizado. Mas apesar de sua ampla adoção, existem muitos detalhes a ser considerados ao trabalhar com esses frameworks e muitas opções a serem escolhidas. Nesse caso, vamos tentar estabelecer a interação entre Gunicorn e Nginx através de um socket.
Como ambos esses componentes estão instalados no mesmo servidor, vamos tentar usar um socket UNIX em vez de um socket de rede. Como para comunicações sempre é necessário um socket, vamos dar mais um passo e configurar a ativação do socket para Gunicorn através do systemd.
O processo de criação de serviços ativados por socket (socket activated services) é bastante simples. Primeiro cria-se um arquivo de unidade que contém a diretiva ListenStream, indicando o ponto onde o socket UNIX será criado, e em seguida, um arquivo de unidade para o serviço, onde a diretiva Requires apontará para o arquivo de unidade do socket. Depois, no arquivo de unidade do serviço, basta chamar o Gunicorn a partir do ambiente virtual e criar a ligação WSGI para o socket UNIX e para o aplicativo Django.
Aqui estão alguns exemplos de arquivos de unidade que podem ser usados como base. Primeiro, configuramos o socket.
[Unit]
Description=Socket WSGI do Gunicorn
[Socket]
ListenStream=\/run\/gunicorn.sock
[Install]
WantedBy=sockets.target
Agora é necessário configurar o daemon Gunicorn.
[Unit]
Description=Daemon Gunicorn
Requires=gunicorn.socket
After=network.target
[Service]
User=cloud-user
Group=cloud-user
WorkingDirectory=\/srv\/djangoapp
ExecStart=\/srv\/djangoapp\/django\/bin\/gunicorn
—access-logfile -
—workers 3
—bind unix:gunicorn.sock djangoapp.wsgi
[Install]
WantedBy=multi-user.target
Para o Nginx, basta criar os arquivos de configuração do proxy e configurar o diretório para armazenar o conteúdo estático, se você estiver usando. No RHEL, os arquivos de configuração do Nginx estão em \/etc\/nginx\/conf.d. Você pode copiar o seguinte exemplo para o arquivo \/etc\/nginx\/conf.d\/default.conf e iniciar o serviço. Certifique-se de que o server_name está de acordo com o nome do seu host.
server {
listen 80;
server_name 8beta1.example.com;
location = \/favicon.ico { access_log off; log_not_found off; }
location \/static\/ {
root \/srv\/djangoapp;
}
location \/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http:\/\/unix:\/run\/gunicorn.sock;
}
}
Ative o socket Gunicorn e o Nginx usando o systemd e você pode começar a testar.
Erro Bad Gateway?
Se você inserir o endereço no navegador, provavelmente verá o erro 502 Bad Gateway. Isso pode ser causado por permissões incorretamente configuradas para o soquete UNIX ou por problemas mais complexos relacionados ao gerenciamento de acesso no SELinux.
No registro de erros do nginx, você pode encontrar uma linha semelhante a esta:
2018/12/18 15:38:03 [crit] 12734#0: *3 connect() to unix:/run/gunicorn.sock failed (13: Permission denied) while connecting to upstream, client: 192.168.122.1, server: 8beta1.example.com, request: "GET / HTTP/1.1", upstream: "http://unix:/run/gunicorn.sock:/", host: "8beta1.example.com"
Se testarmos o Gunicorn diretamente, obteremos uma resposta vazia.
curl --unix-socket /run/gunicorn.sock 8beta1.example.com
Vamos entender por que isso está acontecendo. Se abrirmos o log, provavelmente veremos que o problema está relacionado ao SELinux. Como temos um daemon em execução para o qual não foi criada uma política, ele é marcado como init_t. Vamos testar essa teoria na prática.
sudo setenforce 0
Tudo isso pode causar críticas e lágrimas, mas é apenas uma depuração de protótipo. Vamos desligar a verificação apenas para garantir que o problema está realmente nisso, e depois voltaremos tudo ao normal.
Atualizando a página no navegador ou reiniciando nosso comando curl, podemos ver a página de teste do Django.
Assim, garantindo que tudo está funcionando e não há mais problemas de permissões, reativamos o SELinux.
sudo setenforce 1
Não haverá uma explicação sobre audit2allow e a criação de políticas com base em avisos usando sepolgen, pois, neste momento, não há um aplicativo Django real, então não temos um mapa completo do que o Gunicorn pode querer acessar e o que esse acesso deve ser negado. Portanto, é necessário manter o SELinux em funcionamento para proteger o sistema, ao mesmo tempo permitindo que o aplicativo seja iniciado e registre mensagens no log de auditoria, para que possamos, posteriormente, criar uma política real com base nelas.
Indicação de domínios permitidos (permissive domains)
Sobre domínios permitidos no SELinux, nem todos ouviram, mas não há nada de novo neles. Muitos até trabalharam com eles sem perceber. Quando uma política é criada com base em mensagens de auditoria, a política criada representa um domínio permitido. Vamos tentar criar uma política permissiva simples.
Para criar um domínio permitido específico para o Gunicorn, é necessária uma política, além de ser preciso marcar os arquivos correspondentes. Além disso, são necessárias ferramentas para compilar novas políticas.
sudo yum install selinux-policy-devel
O mecanismo de domínios permitidos é uma excelente ferramenta para identificar problemas, especialmente quando se trata de um aplicativo personalizado ou de aplicativos que são fornecidos sem políticas já criadas. Neste caso, a política de domínio permitido para Gunicorn será a mais simples possível — declararemos o tipo principal (gunicorn_t), declararemos o tipo que usaremos para marcar vários arquivos executáveis (gunicorn_exec_t), e então configuraremos a transição (transition) para system, a fim de marcar corretamente os processos em execução. A última linha define a política como permitida por padrão no momento do carregamento.
gunicorn.te:
policy_module(gunicorn, 1.0)
type gunicorn_t;
type gunicorn_exec_t;
init_daemon_domain(gunicorn_t, gunicorn_exec_t)
permissive gunicorn_t;
É possível compilar este arquivo de políticas e adicioná-lo ao sistema.
make -f /usr/share/selinux/devel/Makefile
sudo semodule -i gunicorn.pp
sudo semanage permissive -a gunicorn_t
sudo semodule -l | grep permissive
Vamos verificar se o SELinux está bloqueando algo além do que nosso daemon desconhecido está acessando.
sudo ausearch -m AVC
type=AVC msg=audit(1545315977.237:1273): avc: denied { write } for pid=19400 comm="nginx" name="gunicorn.sock" dev="tmpfs" ino=52977 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=sock_file permissive=0
O SELinux não permite que o Nginx escreva dados no socket UNIX usado pelo Gunicorn. Normalmente, nessas situações, as políticas começam a ser alteradas, mas ainda restam outras tarefas pela frente. Também é possível mudar as configurações do domínio, transformando-o de um domínio restrito para um domínio permitido. Agora, vamos mover httpd_t para o domínio permitido. Isso fornecerá ao Nginx o acesso necessário, e poderemos continuar com o trabalho de depuração.
sudo semanage permissive -a httpd_t
Portanto, quando conseguimos manter a proteção do SELinux (na verdade, não se deve deixar um projeto com o SELinux em modo restrito) e os domínios permitidos estão carregados, é necessário descobrir o que exatamente precisa ser marcado como gunicorn_exec_t para que tudo funcione corretamente novamente. Vamos tentar acessar o site para ver novas mensagens sobre restrições de acesso.
sudo ausearch -m AVC -c gunicorn
É possível ver várias mensagens contendo ‘comm=«gunicorn»’, que executam várias ações sobre arquivos em /srv/djangoapp, portanto, é evidente que este é um dos comandos que vale a pena marcar.
Mas além disso, aparece uma mensagem do seguinte tipo:
type=AVC msg=audit(1545320700.070:1542): avc: denied { execute } for pid=20704 comm="(gunicorn)" name="python3.6" dev="vda3" ino=8515706 scontext=system_u:system_r:init_t:s0 tcontext=unconfined_u:object_r:var_t:s0 tclass=file permissive=0
Se você olhar o status do serviço gunicorn ou executar o comando ps, não aparecerão processos em execução. Parece que o gunicorn está tentando acessar o interpretador Python em nosso ambiente virtualenv, possivelmente para executar scripts de trabalho (workers). Portanto, agora vamos marcar esses dois arquivos executáveis e verificar se conseguimos abrir nossa página de teste Django.
chcon -t gunicorn_exec_t /srv/djangoapp/django/bin/gunicorn /srv/djangoapp/django/bin/python3.6
Será necessário reiniciar o serviço gunicorn para que a nova etiqueta possa ser escolhida. Ele pode ser reiniciado imediatamente ou parar o serviço e deixar o socket iniciá-lo ao abrir o site no navegador. Certifique-se de que os processos receberam as etiquetas corretas usando ps.
ps -efZ | grep gunicorn
Não se esqueça de criar uma política SELinux adequada depois!
Se observar as mensagens AVC agora, a última mensagem contém permissive=1 para tudo que se refere ao aplicativo, e permissive=0 para todo o resto do sistema. Compreendendo qual acesso é necessário para o aplicativo real, você pode encontrar mais rapidamente a maneira ideal de resolver problemas como esse. Mas até lá, é melhor que o sistema esteja protegido e que você obtenha uma auditoria clara e utilizável sobre o projeto Django.
sudo ausearch -m AVC
Conseguimos!
Um projeto Django funcional foi criado com frontend em Nginx e Gunicorn WSGI. Nós configuramos Python 3 e PostgreSQL 10 a partir dos repositórios RHEL 8 Beta. Agora podemos seguir em frente e criar (ou simplesmente implantar) aplicativos Django ou explorar outras ferramentas disponíveis no RHEL 8 Beta para automatizar o processo de configuração, melhorar o desempenho ou até mesmo containerizar essa configuração.
Fonte: habr.com
