RHEL 8 Beta 研讨会:构建工作 Web 应用程序

RHEL 8 Beta 为开发人员提供了许多新功能,这些功能的列表可能需要几页纸,但是,在实践中学习新东西总是更好,因此下面我们提供了一个关于实际创建基于 Red Hat Enterprise Linux 8 Beta 的应用程序基础架构的研讨会。

RHEL 8 Beta 研讨会:构建工作 Web 应用程序

让我们以 Python(一种在开发人员中流行的编程语言)为基础,结合 Django 和 PostgreSQL(一种用于创建应用程序的相当常见的组合),并配置 RHEL 8 Beta 来使用它们。 然后我们将添加更多(未分类)成分。

测试环境将会改变,因为探索自动化的可能性、使用容器以及尝试具有多个服务器的环境是很有趣的。 要开始新项目,您可以首先手动创建一个小型、简单的原型,这样您就可以准确地看到需要发生什么以及它如何交互,然后继续自动化并创建更复杂的配置。 今天我们谈论的是这样一个原型的创建。

我们首先部署 RHEL 8 Beta VM 映像。 您可以从头开始安装虚拟机,或使用 Beta 订阅提供的 KVM 来宾映像。 使用来宾映像时,您需要配置一个虚拟 CD,其中包含用于云初始化 (cloud-init) 的元数据和用户数据。 您不需要对磁盘结构或可用软件包进行任何特殊操作;任何配置都可以。

让我们仔细看看整个过程。

安装Django

使用最新版本的 Django,您将需要一个包含 Python 3.5 或更高版本的虚拟环境 (virtualenv)。 在 Beta 注释中,您可以看到 Python 3.6 可用,让我们检查一下是否确实如此:

[cloud-user@8beta1 ~]$ python
-bash: python: command not found
[cloud-user@8beta1 ~]$ python3
-bash: python3: command not found

Red Hat在RHEL中积极使用Python作为系统工具包,那么为什么会出现这样的结果呢?

事实是,许多Python开发者仍在考虑从Python 2过渡到Python 2,而Python 3本身正在积极开发中,并且越来越多的新版本不断出现。 因此,为了满足对稳定系统工具的需求,同时让用户能够访问各种新版本的Python,系统Python被移入新的包中,并提供了安装Python 2.7和3.6的能力。 有关这些更改及其原因的更多信息,请参阅以下出版物: 兰登·怀特的博客 (兰登·怀特)。

因此,要使用 Python,您只需要安装两个包,其中包含 python3-pip 作为依赖项。

sudo yum install python36 python3-virtualenv

为什么不按照 Langdon 建议使用直接模块调用并安装 pip3? 请记住即将到来的自动化,众所周知,Ansible 将需要安装 pip 才能运行,因为 pip 模块不支持具有自定义 pip 可执行文件的 virtualenvs。

有了可用的 python3 解释器,您就可以继续 Django 安装过程,并拥有一个工作系统以及我们的其他组件。 互联网上有许多可用的实施选项。 这里提供了一种版本,但用户可以使用自己的流程。

默认情况下,我们将使用 Yum 安装 RHEL 8 中可用的 PostgreSQL 和 Nginx 版本。

sudo yum install nginx postgresql-server

PostgreSQL 将需要 psycopg2,但它只需要在 virtualenv 环境中可用,因此我们将使用 pip3 以及 Django 和 Gunicorn 来安装它。 但首先我们需要设置 virtualenv。

关于选择正确的位置来安装 Django 项目的话题总是存在很多争论,但是当有疑问时,您可以随时转向 Linux 文件系统层次结构标准。 具体来说,FHS 表示 /srv 用于:“存储特定于主机的数据——系统生成的数据,例如 Web 服务器数据和脚本、存储在 FTP 服务器上的数据以及控制系统存储库。”版本(出现在 FHS 中) 2.3 年为-2004)。”

这正是我们的情况,因此我们将所需的所有内容放入 /srv 中,该文件由我们的应用程序用户(云用户)拥有。

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

设置 PostgreSQL 和 Django 很简单:创建数据库、创建用户、配置权限。 最初安装 PostgreSQL 时要记住的一件事是与 postgresql-server 包一起安装的 postgresql-setup 脚本。 此脚本可帮助您执行与数据库集群管理相关的基本任务,例如集群初始化或升级过程。 要在 RHEL 系统上配置新的 PostgreSQL 实例,我们需要运行以下命令:

sudo /usr/bin/postgresql-setup -initdb

然后,您可以使用 systemd 启动 PostgreSQL、创建数据库并在 Django 中设置项目。 请记住在更改客户端身份验证配置文件(通常是 pg_hba.conf)以配置应用程序用户的密码存储后重新启动 PostgreSQL。 如果遇到其他困难,请确保更改 pg_hba.conf 文件中的 IPv4 和 IPv6 设置。

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

在文件 /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

在文件 /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 }}',
   }
}

在项目中配置settings.py文件并设置数据库配置后,您可以启动开发服务器以确保一切正常。 启动开发服务器后,最好创建一个管理员用户以测试与数据库的连接。

./manage.py runserver 0.0.0.0:8000
./manage.py createsuperuser

WSGI? 威?

开发服务器对于测试很有用,但要运行应用程序,您必须为 Web 服务器网关接口 (WSGI) 配置适当的服务器和代理。 有几种常见的组合,例如 Apache HTTPD 与 uWSGI 或 Nginx 与 Gunicorn。

Web 服务器网关接口的工作是将来自 Web 服务器的请求转发到 Python Web 框架。 WSGI 是 CGI 引擎出现时可怕的过去的遗迹,而如今 WSGI 已成为事实上的标准,无论使用什么 Web 服务器或 Python 框架。 但是,尽管它被广泛使用,但在使用这些框架时仍然存在许多细微差别和许多选择。 在这种情况下,我们将尝试通过套接字在 Gunicorn 和 Nginx 之间建立交互。

由于这两个组件都安装在同一台服务器上,因此我们尝试使用 UNIX 套接字而不是网络套接字。 由于通信在任何情况下都需要套接字,因此我们尝试再采取一步,通过 systemd 为 Gunicorn 配置套接字激活。

创建套接字激活服务的过程非常简单。 首先,创建一个单元文件,其中包含指向将创建 UNIX 套接字的点的 ListenStream 指令,然后创建一个服务单元文件,其中 Requires 指令将指向套接字单元文件。 然后,在服务单元文件中,剩下的就是从虚拟环境中调用 Gunicorn 并为 UNIX 套接字和 Django 应用程序创建 WSGI 绑定。

以下是一些可以用作基础的单元文件示例。 首先我们设置套接字。

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

现在您需要配置 Gunicorn 守护进程。

[Unit]
Description=Gunicorn daemon
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

对于 Nginx,创建代理配置文件并设置一个目录来存储静态内容(如果您使用的是代理配置文件)是一件简单的事情。 在 RHEL 中,Nginx 配置文件位于 /etc/nginx/conf.d 中。 您可以将以下示例复制到文件 /etc/nginx/conf.d/default.conf 中并启动服务。 确保设置 server_name 与您的主机名匹配。

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;
   }
}

使用 systemd 启动 Gunicorn 套接字和 Nginx,然后就可以开始测试了。

网关错误?

如果您在浏览器中输入该地址,您很可能会收到 502 Bad Gateway 错误。 它可能是由于错误配置的 UNIX 套接字权限引起的,也可能是由于与 SELinux 中的访问控制相关的更复杂的问题引起的。

在 nginx 错误日志中你可以看到这样一行:

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"

如果我们直接测试 Gunicorn,我们将得到一个空答案。

curl —unix-socket /run/gunicorn.sock 8beta1.example.com

让我们弄清楚为什么会发生这种情况。 如果您打开日志,您很可能会发现问题与 SELinux 有关。 由于我们正在运行一个尚未为其创建策略的守护进程,因此它被标记为 init_t。 让我们在实践中检验这个理论。

sudo setenforce 0

这一切可能会引起批评和血泪,但这只是调试原型。 让我们禁用该检查,以确保这就是问题所在,然后我们将所有内容放回原处。

通过在浏览器中刷新页面或重新运行我们的curl命令,您可以看到Django测试页面。

因此,在确保一切正常并且不再存在权限问题后,我们再次启用 SELinux。

sudo setenforce 1

我不会在这里谈论audit2allow或使用sepolgen创建基于警报的策略,因为目前没有实际的Django应用程序,因此没有Gunicorn可能想要访问的内容以及应该拒绝访问的内容的完整映射。 因此,有必要保持 SELinux 运行以保护系统,同时允许应用程序运行并在审核日志中留下消息,以便可以根据它们创建实际的策略。

指定许可域

并不是每个人都听说过 SELinux 中允许的域,但它们并不是什么新鲜事。 许多人甚至在没有意识到的情况下与他们合作。 当基于审核消息创建策略时,创建的策略代表已解析的域。 让我们尝试创建一个简单的许可策略。

要为 Gunicorn 创建特定的允许域,您需要某种策略,并且还需要标记适当的文件。 此外,还需要工具来制定新政策。

sudo yum install selinux-policy-devel

允许的域机制是识别问题的一个很好的工具,特别是当涉及自定义应用程序或未创建策略的应用程序时。 在这种情况下,Gunicorn 允许的域策略将尽可能简单 - 声明一个主类型 (gunicorn_t),声明一个我们将用于标记多个可执行文件的类型 (gunicorn_exec_t),然后为系统设置一个转换以正确标记正在运行的进程。 最后一行将策略设置为在加载时默认启用。

古尼康.te:

policy_module(gunicorn, 1.0)

type gunicorn_t;
type gunicorn_exec_t;
init_daemon_domain(gunicorn_t, gunicorn_exec_t)
permissive gunicorn_t;

您可以编译此策略文件并将其添加到您的系统中。

make -f /usr/share/selinux/devel/Makefile
sudo semodule -i gunicorn.pp

sudo semanage permissive -a gunicorn_t
sudo semodule -l | grep permissive

让我们检查一下 SELinux 是否阻止了除了我们未知的守护进程正在访问的内容之外的其他内容。

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

SELinux 阻止 Nginx 将数据写入 Gunicorn 使用的 UNIX 套接字。 通常,在这种情况下,政策会开始改变,但未来还会面临其他挑战。 您还可以将域设置从限制域更改为权限域。 现在让我们将 httpd_t 移至权限域。 这将为 Nginx 提供必要的访问权限,我们可以继续进一步的调试工作。

sudo semanage permissive -a httpd_t

因此,一旦您成功地保护了 SELinux(您确实不应该将 SELinux 项目置于受限模式)并且加载了权限域,您需要弄清楚究竟需要将哪些内容标记为 Gunicorn_exec_t 才能使一切正常工作再次。 让我们尝试访问该网站以查看有关访问限制的新消息。

sudo ausearch -m AVC -c gunicorn

您会看到很多包含“comm =“gunicorn””的消息,这些消息对 /srv/djangoapp 中的文件执行各种操作,因此这显然是值得标记的命令之一。

但除此之外,还会出现这样的消息:

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

如果您查看gunicorn服务的状态或运行ps命令,您将看不到任何正在运行的进程。 看起来 Gunicorn 正在尝试访问 virtualenv 环境中的 Python 解释器,可能是为了运行工作脚本。 现在让我们标记这两个可执行文件,看看是否可以打开 Django 测试页面。

chcon -t gunicorn_exec_t /srv/djangoapp/django/bin/gunicorn /srv/djangoapp/django/bin/python3.6

在选择新标签之前,需要重新启动gunicorn 服务。 您可以立即重新启动它,也可以停止服务并让套接字在您在浏览器中打开站点时启动它。 使用 ps 验证进程是否已收到正确的标签。

ps -efZ | grep gunicorn

不要忘记稍后创建一个正常的 SELinux 策略!

如果您现在查看 AVC 消息,最后一条消息包含与应用程序相关的所有内容的 permissive=1,以及针对系统其余部分的 permissive=0。 如果您了解实际应用程序需要什么样的访问,您可以快速找到解决此类问题的最佳方法。 但在此之前,最好保持系统安全并对 Django 项目进行清晰、可用的审核。

sudo ausearch -m AVC

事实证明!

一个可用的 Django 项目已经出现,其前端基于 Nginx 和 Gunicorn WSGI。 我们从 RHEL 3 Beta 存储库配置了 Python 10 和 PostgreSQL 8。 现在,您可以继续创建(或简单部署)Django 应用程序,或探索 RHEL 8 Beta 中的其他可用工具,以自动化配置过程、提高性能,甚至容器化此配置。

来源: habr.com

添加评论