Workshop RHEL 8 Beta: Xây dựng ứng dụng web chạy được

RHEL 8 Beta cung cấp cho các nhà phát triển nhiều tính năng mới, danh sách các tính năng này có thể mất nhiều trang, tuy nhiên, việc học những điều mới trong thực tế luôn tốt hơn, vì vậy dưới đây chúng tôi tổ chức một hội thảo về cách thực sự tạo cơ sở hạ tầng ứng dụng dựa trên Red Hat Enterprise Linux 8 Beta.

Workshop RHEL 8 Beta: Xây dựng ứng dụng web chạy được

Hãy lấy Python, một ngôn ngữ lập trình phổ biến trong số các nhà phát triển, làm cơ sở, sự kết hợp giữa Django và PostgreSQL, một sự kết hợp khá phổ biến để tạo ứng dụng và định cấu hình RHEL 8 Beta để hoạt động với chúng. Sau đó, chúng tôi sẽ thêm một vài thành phần nữa (chưa được phân loại).

Môi trường thử nghiệm sẽ thay đổi vì thật thú vị khi khám phá các khả năng tự động hóa, làm việc với các vùng chứa và môi trường thử nghiệm với nhiều máy chủ. Để bắt đầu với một dự án mới, bạn có thể bắt đầu bằng cách tạo một nguyên mẫu nhỏ, đơn giản bằng tay để bạn có thể thấy chính xác những gì cần xảy ra và cách nó tương tác, sau đó chuyển sang tự động hóa và tạo các cấu hình phức tạp hơn. Hôm nay chúng ta đang nói về việc tạo ra một nguyên mẫu như vậy.

Hãy bắt đầu bằng việc triển khai image RHEL 8 Beta VM. Bạn có thể cài đặt máy ảo từ đầu hoặc sử dụng hình ảnh khách KVM có sẵn với đăng ký Beta của bạn. Khi sử dụng hình ảnh khách, bạn sẽ cần định cấu hình đĩa CD ảo chứa siêu dữ liệu và dữ liệu người dùng để khởi tạo đám mây (cloud-init). Bạn không cần phải làm gì đặc biệt với cấu trúc đĩa hoặc các gói có sẵn; bất kỳ cấu hình nào cũng được.

Chúng ta hãy xem xét kỹ hơn toàn bộ quá trình.

Cài đặt Django

Với phiên bản mới nhất của Django, bạn sẽ cần một môi trường ảo (virtualenv) với Python 3.5 trở lên. Trong ghi chú Beta, bạn có thể thấy rằng Python 3.6 đã có sẵn, hãy kiểm tra xem trường hợp này có thực sự xảy ra không:

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

Red Hat tích cực sử dụng Python làm bộ công cụ hệ thống trong RHEL, vậy tại sao lại dẫn đến kết quả này?

Thực tế là nhiều nhà phát triển Python vẫn đang dự tính chuyển đổi từ Python 2 sang Python 2, trong khi bản thân Python 3 đang được phát triển tích cực và ngày càng có nhiều phiên bản mới liên tục xuất hiện. Do đó, để đáp ứng nhu cầu về các công cụ hệ thống ổn định đồng thời cung cấp cho người dùng quyền truy cập vào nhiều phiên bản Python mới khác nhau, Python hệ thống đã được chuyển sang gói mới và cung cấp khả năng cài đặt cả Python 2.7 và 3.6. Thông tin thêm về những thay đổi và lý do chúng được thực hiện có thể được tìm thấy trong ấn phẩm ở Blog của Langdon White (Langdon trắng).

Vì vậy, để Python hoạt động, bạn chỉ cần cài đặt hai gói, trong đó có python3-pip làm phần phụ thuộc.

sudo yum install python36 python3-virtualenv

Tại sao không sử dụng lệnh gọi mô-đun trực tiếp như Langdon gợi ý và cài đặt pip3? Hãy ghi nhớ quá trình tự động hóa sắp tới, được biết rằng Ansible sẽ yêu cầu cài đặt pip để chạy, vì mô-đun pip không hỗ trợ virtualenvs với tệp thực thi pip tùy chỉnh.

Với trình thông dịch python3 đang hoạt động theo ý của bạn, bạn có thể tiếp tục quá trình cài đặt Django và có một hệ thống hoạt động cùng với các thành phần khác của chúng tôi. Có nhiều tùy chọn triển khai có sẵn trên Internet. Có một phiên bản được trình bày ở đây nhưng người dùng có thể sử dụng quy trình của riêng họ.

Theo mặc định, chúng tôi sẽ cài đặt các phiên bản PostgreSQL và Nginx có sẵn trong RHEL 8 bằng Yum.

sudo yum install nginx postgresql-server

PostgreSQL sẽ yêu cầu psycopg2, nhưng nó chỉ cần khả dụng trong môi trường virtualenv, vì vậy chúng tôi sẽ cài đặt nó bằng pip3 cùng với Django và Gunicorn. Nhưng trước tiên chúng ta cần thiết lập virtualenv.

Luôn có rất nhiều tranh luận về chủ đề chọn nơi thích hợp để cài đặt các dự án Django, nhưng khi nghi ngờ, bạn luôn có thể chuyển sang Tiêu chuẩn phân cấp hệ thống tập tin Linux. Cụ thể, FHS cho biết rằng /srv được sử dụng để: “lưu trữ dữ liệu dành riêng cho máy chủ — dữ liệu mà hệ thống tạo ra, chẳng hạn như dữ liệu và tập lệnh máy chủ web, dữ liệu được lưu trữ trên máy chủ FTP và kho lưu trữ hệ thống kiểm soát”. -2.3 năm 2004)."

Đây chính xác là trường hợp của chúng tôi, vì vậy chúng tôi đưa mọi thứ chúng tôi cần vào /srv, thuộc sở hữu của người dùng ứng dụng của chúng tôi (người dùng đám mây).

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

Thiết lập PostgreSQL và Django thật dễ dàng: tạo cơ sở dữ liệu, tạo người dùng, định cấu hình quyền. Một điều cần lưu ý khi cài đặt PostgreSQL lần đầu là tập lệnh postgresql-setup được cài đặt cùng với gói postgresql-server. Tập lệnh này giúp bạn thực hiện các tác vụ cơ bản liên quan đến quản trị cụm cơ sở dữ liệu, chẳng hạn như khởi tạo cụm hoặc quá trình nâng cấp. Để định cấu hình phiên bản PostgreSQL mới trên hệ thống RHEL, chúng ta cần chạy lệnh:

sudo /usr/bin/postgresql-setup -initdb

Sau đó, bạn có thể khởi động PostgreSQL bằng systemd, tạo cơ sở dữ liệu và thiết lập dự án ở Django. Hãy nhớ khởi động lại PostgreSQL sau khi thực hiện các thay đổi đối với tệp cấu hình xác thực ứng dụng khách (thường là pg_hba.conf) để định cấu hình lưu trữ mật khẩu cho người dùng ứng dụng. Nếu bạn gặp những khó khăn khác, hãy đảm bảo thay đổi cài đặt IPv4 và IPv6 trong tệp pg_hba.conf.

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

Trong tệp /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

Trong tệp /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 }}',
   }
}

Sau khi định cấu hình tệp settings.py trong dự án và thiết lập cấu hình cơ sở dữ liệu, bạn có thể khởi động máy chủ phát triển để đảm bảo mọi thứ đều hoạt động. Sau khi khởi động máy chủ phát triển, bạn nên tạo người dùng quản trị để kiểm tra kết nối tới cơ sở dữ liệu.

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

WSGI? Đợi đã?

Máy chủ phát triển rất hữu ích cho việc thử nghiệm, nhưng để chạy ứng dụng, bạn phải định cấu hình máy chủ và proxy thích hợp cho Giao diện cổng máy chủ Web (WSGI). Có một số kết hợp phổ biến, ví dụ: Apache HTTPD với uWSGI hoặc Nginx với Gunicorn.

Công việc của Giao diện cổng máy chủ web là chuyển tiếp các yêu cầu từ máy chủ web tới khung web Python. WSGI là tàn tích của quá khứ khủng khiếp khi các công cụ CGI còn tồn tại và ngày nay WSGI là tiêu chuẩn trên thực tế, bất kể máy chủ web hay khung Python được sử dụng. Nhưng mặc dù được sử dụng rộng rãi nhưng vẫn có nhiều sắc thái khi làm việc với các framework này và có nhiều sự lựa chọn. Trong trường hợp này, chúng tôi sẽ cố gắng thiết lập sự tương tác giữa Gunicorn và Nginx thông qua một ổ cắm.

Vì cả hai thành phần này đều được cài đặt trên cùng một máy chủ, hãy thử sử dụng ổ cắm UNIX thay vì ổ cắm mạng. Vì giao tiếp cần có ổ cắm trong mọi trường hợp, hãy thử thực hiện thêm một bước nữa và định cấu hình kích hoạt ổ cắm cho Gunicorn thông qua systemd.

Quá trình tạo dịch vụ kích hoạt socket khá đơn giản. Đầu tiên, một tệp đơn vị được tạo có chứa lệnh ListenStream trỏ đến điểm mà ổ cắm UNIX sẽ được tạo, sau đó là tệp đơn vị cho dịch vụ trong đó lệnh Yêu cầu sẽ trỏ đến tệp đơn vị ổ cắm. Sau đó, trong tệp đơn vị dịch vụ, tất cả những gì còn lại là gọi Gunicorn từ môi trường ảo và tạo liên kết WSGI cho ổ cắm UNIX và ứng dụng Django.

Dưới đây là một số ví dụ về tệp đơn vị mà bạn có thể sử dụng làm cơ sở. Đầu tiên chúng ta thiết lập ổ cắm.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Bây giờ bạn cần cấu hình daemon 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

Đối với Nginx, việc tạo các tệp cấu hình proxy và thiết lập một thư mục để lưu trữ nội dung tĩnh nếu bạn đang sử dụng chỉ là một vấn đề đơn giản. Trong RHEL, các tệp cấu hình Nginx được đặt trong /etc/nginx/conf.d. Bạn có thể sao chép ví dụ sau vào tệp /etc/nginx/conf.d/default.conf và khởi động dịch vụ. Đảm bảo đặt server_name khớp với tên máy chủ của bạn.

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

Khởi động ổ cắm Gunicorn và Nginx bằng systemd và bạn đã sẵn sàng bắt đầu thử nghiệm.

Lỗi cổng xấu?

Nếu nhập địa chỉ vào trình duyệt, rất có thể bạn sẽ gặp lỗi 502 Bad Gateway. Nguyên nhân có thể là do quyền của ổ cắm UNIX được cấu hình không chính xác hoặc có thể do các vấn đề phức tạp hơn liên quan đến kiểm soát truy cập trong SELinux.

Trong nhật ký lỗi nginx, bạn có thể thấy một dòng như thế này:

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"

Nếu chúng tôi kiểm tra trực tiếp Gunicorn, chúng tôi sẽ nhận được câu trả lời trống rỗng.

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

Hãy tìm hiểu tại sao điều này xảy ra. Nếu bạn mở nhật ký, rất có thể bạn sẽ thấy vấn đề liên quan đến SELinux. Vì chúng ta đang chạy một daemon chưa có chính sách nào được tạo nên nó được đánh dấu là init_t. Hãy kiểm tra lý thuyết này trong thực tế.

sudo setenforce 0

Tất cả điều này có thể gây ra những lời chỉ trích và chảy nước mắt, nhưng đây chỉ là việc gỡ lỗi nguyên mẫu. Hãy vô hiệu hóa kiểm tra chỉ để đảm bảo rằng đây là sự cố, sau đó chúng tôi sẽ đưa mọi thứ trở lại vị trí của nó.

Bằng cách làm mới trang trong trình duyệt hoặc chạy lại lệnh cuộn tròn của chúng tôi, bạn có thể xem trang thử nghiệm Django.

Vì vậy, sau khi đảm bảo rằng mọi thứ đều hoạt động và không còn vấn đề gì về quyền, chúng tôi kích hoạt lại SELinux.

sudo setenforce 1

Tôi sẽ không nói về Audit2allow hoặc tạo chính sách dựa trên cảnh báo với sepolgen ở đây, vì hiện tại không có ứng dụng Django thực sự nào, nên không có bản đồ đầy đủ về những gì Gunicorn có thể muốn truy cập và những gì nó nên từ chối quyền truy cập. Do đó, cần phải duy trì SELinux chạy để bảo vệ hệ thống, đồng thời cho phép ứng dụng chạy và để lại thông báo trong nhật ký kiểm tra để sau đó có thể tạo chính sách thực tế từ chúng.

Chỉ định miền cho phép

Không phải ai cũng từng nghe nói về các miền được phép trong SELinux, nhưng chúng không có gì mới. Nhiều người thậm chí đã làm việc với họ mà không hề nhận ra điều đó. Khi một chính sách được tạo dựa trên thông báo kiểm tra, chính sách được tạo sẽ đại diện cho miền đã được giải quyết. Hãy thử tạo một chính sách cấp phép đơn giản.

Để tạo một miền cụ thể được phép cho Gunicorn, bạn cần một số loại chính sách và bạn cũng cần đánh dấu các tệp thích hợp. Ngoài ra, cần có các công cụ để tập hợp các chính sách mới.

sudo yum install selinux-policy-devel

Cơ chế miền được phép là một công cụ tuyệt vời để xác định sự cố, đặc biệt khi nói đến một ứng dụng tùy chỉnh hoặc các ứng dụng được gửi đi mà không có chính sách nào được tạo sẵn. Trong trường hợp này, chính sách miền được phép cho Gunicorn sẽ đơn giản nhất có thể - khai báo loại chính (gunicorn_t), khai báo loại chúng tôi sẽ sử dụng để đánh dấu nhiều tệp thực thi (gunicorn_exec_t), sau đó thiết lập chuyển đổi để hệ thống đánh dấu chính xác các tiến trình đang chạy. Dòng cuối cùng đặt chính sách được bật theo mặc định tại thời điểm nó được tải.

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;

Bạn có thể biên dịch tệp chính sách này và thêm nó vào hệ thống của mình.

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

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

Hãy kiểm tra xem liệu SELinux có đang chặn thứ gì khác ngoài thứ mà daemon không xác định của chúng ta đang truy cập hay không.

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 ngăn Nginx ghi dữ liệu vào ổ cắm UNIX được Gunicorn sử dụng. Thông thường, trong những trường hợp như vậy, các chính sách bắt đầu thay đổi nhưng vẫn còn những thách thức khác ở phía trước. Bạn cũng có thể thay đổi cài đặt miền từ miền hạn chế sang miền được phép. Bây giờ hãy chuyển httpd_t sang miền quyền. Điều này sẽ cung cấp cho Nginx quyền truy cập cần thiết và chúng ta có thể tiếp tục công việc gỡ lỗi tiếp theo.

sudo semanage permissive -a httpd_t

Vì vậy, khi bạn đã quản lý để bảo vệ SELinux (bạn thực sự không nên để dự án SELinux ở chế độ hạn chế) và các miền cấp phép đã được tải, bạn cần tìm ra chính xác những gì cần được đánh dấu là gunicorn_exec_t để mọi thứ hoạt động bình thường lại. Hãy thử truy cập trang web để xem các thông báo mới về hạn chế truy cập.

sudo ausearch -m AVC -c gunicorn

Bạn sẽ thấy nhiều thông báo chứa 'comm="gunicorn"' thực hiện nhiều tác vụ khác nhau trên các tệp trong /srv/djangoapp, vì vậy đây rõ ràng là một trong những lệnh đáng được gắn cờ.

Nhưng ngoài ra, một thông báo như thế này xuất hiện:

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

Nếu nhìn vào trạng thái của dịch vụ gunicorn hoặc chạy lệnh ps, bạn sẽ không thấy bất kỳ tiến trình nào đang chạy. Có vẻ như gunicorn đang cố truy cập trình thông dịch Python trong môi trường virtualenv của chúng tôi, có thể để chạy các tập lệnh công nhân. Vì vậy, bây giờ hãy đánh dấu hai tệp thực thi này và kiểm tra xem chúng tôi có thể mở trang thử nghiệm Django của mình không.

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

Dịch vụ gunicorn sẽ cần được khởi động lại trước khi có thể chọn thẻ mới. Bạn có thể khởi động lại ngay lập tức hoặc dừng dịch vụ và để ổ cắm khởi động khi bạn mở trang web trong trình duyệt. Xác minh rằng các quy trình đã nhận được nhãn chính xác bằng ps.

ps -efZ | grep gunicorn

Đừng quên tạo chính sách SELinux bình thường sau này!

Nếu bạn xem các thông báo AVC bây giờ, thông báo cuối cùng chứa permissive=1 cho mọi thứ liên quan đến ứng dụng và permissive=0 cho phần còn lại của hệ thống. Nếu bạn hiểu loại quyền truy cập mà một ứng dụng thực cần, bạn có thể nhanh chóng tìm ra cách tốt nhất để giải quyết những vấn đề đó. Nhưng cho đến lúc đó, tốt nhất bạn nên giữ an toàn cho hệ thống và thực hiện kiểm tra rõ ràng, có thể sử dụng được đối với dự án Django.

sudo ausearch -m AVC

Đã xảy ra!

Một dự án Django đang hoạt động đã xuất hiện với giao diện người dùng dựa trên Nginx và Gunicorn WSGI. Chúng tôi đã định cấu hình Python 3 và PostgreSQL 10 từ kho RHEL 8 Beta. Giờ đây, bạn có thể tiếp tục và tạo (hoặc đơn giản là triển khai) các ứng dụng Django hoặc khám phá các công cụ có sẵn khác trong RHEL 8 Beta để tự động hóa quy trình cấu hình, cải thiện hiệu suất hoặc thậm chí chứa cấu hình này.

Nguồn: www.habr.com

Thêm một lời nhận xét