使用Ansible Playbook在生产环境中配置Nginx + Gunicorn + Django + Aurora (MySQL)

使用Django + MySQL开发的应用程序的生产环境将使用Ansible在Amazon EC2上进行配置。

django-wsgi-full.jpg

以下两篇文章的续篇已于此次完成。

    • 前々回: DjangoをAmazon Linux 2にAnsibleで構成する、Aurora (MySQL) をCloudFormationでUTF-8に構成する、そして接続するまでが地味に大変だったこと

前回: Nginx + Gunicorn + Django + Aurora (MySQL) の構成を図で説明してみる

环境

    • OS: Amazon Linux 2(Amazon EC2)

 

    • アプリケーションフレームワーク: Django 2.2.6

 

    • アプリケーションサーバ: Gunicorn 19.9

 

    • Web サーバ: Nginx 1.12 (Amazon Extras)

 

    • データベース: Amazon Aurora Serverless(MySQL 5.6.10 互換)

 

    構成ツール: Ansible 2.8.5、AWS CloudFormation

※ 我们将不安装虚拟环境(如venv),而是专门为Django应用程序使用EC2。
※ Nginx和Gunicorn将使用同一实例上的UNIX域套接字进行连接。
※ 在本地的CentOS 7上也可以实现相同的配置。

绝策任务书

为了在其他Playbook中重复使用,Python3和Nginx将被分成三个角色,如下所示,以便更方便地再利用。

├── inventories/
├── roles/
│   ├── django/
│   ├── nginx/
│   └── python3/

使用Python3滚

Python3 的安装与上一篇文章相同,需要安装 python3、python3-devel、python3-libs、python3-pip 的 RPM 包。通过 yum 安装 python3-devel,其他依赖项将自动安装。

│   ├── python3/
│   │   └── tasks/
│   │       └── main.yml
- name: Install Python3 packages
  yum:
    name:
      - python3-devel
    state: present

Nginx角色

在这个角色中,将安装Nginx软件包,并进行其他Playbook中使用的通用基本配置。

如果操作系统是Amazon Linux 2,安装Nginx需要稍作调整。参考:考虑在Ansible上如何安装Nginx在Amazon Linux 2的EC2上(即使使用旧版的Jinja2也可以)。

最近流行的是从 /etc/nginx/sites-enabled/ 中包含虚拟网站的配置文件,因此为了与之相适应,我们将进行一个小小的改动。

│   ├── nginx/
│   │   ├── files/
│   │   │   └── etc/
│   │   │       └── nginx/
│   │   │           └── conf.d/
│   │   │               └── sites-enabled.conf
│   │   └── tasks/
│   │       └── main.yml

安装Nginx

使用amazon-linux-extras命令激活nginx1.12仓库,然后使用yum安装nginx软件包。

- name: Enable amzn2extra-nginx1.12 repository
  shell: amazon-linux-extras enable nginx1.12
  changed_when: false

- name: Install Nginx packages from amazon-linux-extras
  yum:
    name: nginx
    state: present

虚拟网站设置文件的部署/包含目录

接下来,我们将创建一个名为sites-available/的目录来部署虚拟主机的配置文件,并创建一个名为sites-enabled/的目录来放置相关的包含文件。

- name: Create sites-available directory
  file:
    path: "/etc/nginx/sites-available"
    state: directory

- name: Create sites-enabled directory
  file:
    path: "/etc/nginx/sites-enabled"
    state: directory

然而,在默认的配置文件中,包含路径被设置为conf.d/,因此sites-enabled/目录中的文件不会被包含进来。

    include /etc/nginx/conf.d/*.conf;

为了将sites-enabled/目录包含到包含路径中,而不更改默认的配置文件,请将以下文件部署到conf.d/目录中。

include /etc/nginx/sites-enabled/*;

将文件部署到conf.d/目录是一个任务。

- name: Copy Nginx configuration files into nginx/conf.d/
  copy:
    dest: "/etc/nginx/conf.d/"
    src:  "{{ role_path }}/files/etc/nginx/conf.d/"

最后将Nginx服务启动。

- name: Start and enable nginx service
  systemd:
    enabled: yes
    name: nginx.service
    state: started

当部署虚拟站点的设置文件进行部署时,请将文件部署到“sites-available/”中,并在“sites-enabled/”中创建符号链接,这样它就会被默认设置文件包含进来。

Django角色

Django的角色包括对Gunicorn和UNIX域套接字进行配置,这使得它变得稍复杂。请先阅读上一篇文章来了解配置和设置。

files/にはデプロイする Django プロジェクトのファイルをコピーしておきます。ただしsettings.pyは別途テンプレートでデプロイするのでここには含めません。

templates/には前回の記事で説明した各種設定ファイルとユニットファイルのテンプレートを用意します。

│   ├── django/
│   │   ├── files/
│   │   │   └── opt/
│   │   │       └── django/
│   │   │           └── mysite/
│   │   │               ├── myapp/
│   │   │               │   ├── __init__.py
│   │   │               │   ├── admin.py
│   │   │               │   ├── apps.py
│   │   │               │   ├── models.py
│   │   │               │   ├── tests.py
│   │   │               │   ├── urls.py
│   │   │               │   └── views.py
│   │   │               ├── mysite/
│   │   │               │   ├── __init__.py
│   │   │               │   ├── urls.py
│   │   │               │   └── wsgi.py
│   │   │               └── manage.py
│   │   ├── handlers/
│   │   │   └── main.yml
│   │   ├── tasks/
│   │   │   └── main.yml
│   │   └── templates/
│   │       ├── gunicorn.conf.j2
│   │       ├── gunicorn.service.j2
│   │       ├── gunicorn.socket.j2
│   │       ├── nginx.conf.j2
│   │       └── settings.py.j2

变量文件

由于需要在多个设置文件和单位文件中调整设置值,因此我们将共享的设置值统一定义在 vars 文件的变量中。

├── inventories/
│   ├── development/
│   │   ├── group_vars/
│   │   │   ├── all.yml
    • django_project_base_dir: Django プロジェクトをデプロイするディレクトリで、settings.pyのBASE_DIRと同じになります。どこでもいいのですが、本稼働環境へのデプロイなので/opt/の下にしました。

 

    • gunicorn_run_dir: Gunicorn の UNIX ドメインソケットと PID ファイルを置くディレクトリです。変更することはないのでハードコーディングでもいいのですが、複数の設定ファイル/ユニットファイルの中で明示的に同じ設定値にするために定義します。

 

    • service_user/service_group: Gunicorn サービスの実行ユーザーとグループです。任意ですが、ここでは Nginx のユーザーを使用します。

 

    データベースの各接続パラメータは前々回の記事と同様です。
# Django / Gunicorn
django_project_name:      "mysite"
django_project_base_dir:  "/opt/django/{{ django_project_name }}"

service_user:       "nginx"
service_group:      "nginx"
gunicorn_run_dir:   "/run/gunicorn"

# DB (Aurora Serverless)
db_server_addres:   "my-main-db.cluster-xxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com"
db_default_schema:  "MySchema"
db_user:            "myuser"
db_password:        "myp@ssword"

安装RPM/pip软件包的任务

与前一篇文章相似,我们将安装RPM和pip包。下面是为了与Aurora的MySQL接近的版本,正在安装MySQL 5.7的客户端,但也可以更改为MySQL 8.0或MariaDB。

- name: Install MySQL repository
  yum:
    name:
      - https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
    state: present

- name: Install rpm packages required by mysqlclient
  yum:
    # 'mysql*'にマッチするリポジトリを全部無効化して
    # 'mysql57-community'を有効化し、5.7だけをインストール対象にする
    disablerepo:  "mysql*"
    enablerepo:   "mysql57-community"
    name:
      - gcc
      - mysql-community-devel
      - mysql-community-client
    state: present

- name: Install Django packages with pip3
  pip:
    executable: pip3
    name:
      - django
      - gunicorn
      - mysqlclient
    state: present

部署项目文件的任务

创建部署目录并将复制到files/的Django项目文件部署。

- name: Create directory for Django project
  file:
    group:  "{{ service_group }}"
    mode:   0755
    owner:  "{{ service_user }}"
    path:   "{{ django_project_base_dir }}"
    state:  directory

- name: Deploy Django project files
  synchronize:
    checksum: yes
    delete:   yes
    dest:   "{{ django_project_base_dir }}/"
    recursive:  yes
    src:    "{{ role_path }}/files{{ django_project_base_dir }}/"

- name: Change owner of Django project files
  file:
    group:  "{{ service_group }}"
    owner:  "{{ service_user}}"
    path:   "{{ django_project_base_dir}}"
    recurse:  yes
    state:  directory

从模板生成settings.py的任务

用模板生成并部署没有包含在项目文件中的settings.py文件。

- name: Deploy settings.py for Django project
  template:
    path:   "{{ django_project_base_dir }}/{{ django_project_name }}/settings.py"
    group:  "{{ service_group }}"
    owner:  "{{ service_user }}"
    src:    "{{ role_path }}/templates/settings.py.j2"

以下是应用在settings.py模板中的vars变量定义的部分。除了下面的部分,其他部分将根据项目的设置进行创建。

ROOT_URLCONF = '{{ django_project_name }}.urls'

WSGI_APPLICATION = '{{ django_project_name }}.wsgi.application'

# Database
DATABASES = {
    'default': {
        'ENGINE':   'django.db.backends.mysql',
        'NAME':     '{{ db_default_schema }}',
        'USER':     '{{ db_user }}',
        'PASSWORD': '{{ db_password }}',
        'HOST':     '{{ db_server_addres }}',
        'PORT':     '3306',
        'OPTIONS': {
            'charset': 'utf8',
            'sql_mode': 'TRADITIONAL,NO_AUTO_VALUE_ON_ZERO,ONLY_FULL_GROUP_BY',
        },
    }
}

从模板生成systemd-tmpfiles文件的任务 systemd-tmpfiles de

根据 Gunicorn 官方文档的配置示例进行参考,文档链接如下:Deploying Gunicorn (Systemd): http://docs.gunicorn.org/en/latest/deploy.html#systemd。

gunicorn-settings.png

根据之前的文章所述,在操作系统启动时,为套接字生成系统服务 systemd-tmpfiles 的目录,请使用模板生成配置文件 gunicorn.conf 并进行部署。

- name: Deploy tmpfiles.d/gunicorn.conf from template
  template:
    path: "/etc/tmpfiles.d/gunicorn.conf"
    group: root
    owner: root
    src:  "{{ role_path }}/templates/gunicorn.conf.j2"
  notify: "systemd-tmpfiles changed"
#Type Path                    Mode  UID                GID                  Age Argument
d     {{ gunicorn_run_dir }}  0755  {{ service_user }} {{ service_group }}  -

在部署gunicorn.conf时,通过处理程序运行systemd-tmpfiles命令以创建目录。

- name: Create systemd-tmpfiles
  command: /usr/bin/systemd-tmpfiles --create
  listen: "systemd-tmpfiles changed"

使用模板生成套接字单元文件的任务

从模板生成并部署UNIX域套接字的单元文件gunicorn.socket。由于Gunicorn服务在启动时由systemd启动,所以不需要配置套接字启动。

- name: Deploy gunicorn.socket unit file from template
  template:
    path: "/etc/systemd/system/gunicorn.socket"
    group: root
    owner: root
    src:  "{{ role_path }}/templates/gunicorn.socket.j2"
  notify: "gunicorn.socket unitfile changed"

以下是单位文件的模板。
虽然 Gunicorn 官方文档中没有ExecStartPre,但我试着添加了它,以确保在套接字启动之前生成目录。

[Unit]
Description=gunicorn socket

[Socket]
ListenStream={{ gunicorn_run_dir }}/socket
ExecStartPre=/usr/bin/systemd-tmpfiles --create

[Install]
WantedBy=sockets.target

在部署gunicorn.conf之后,为了立即生成目录,我们还需要在处理程序中执行systemd-tmpfiles命令。

- name: Create systemd-tmpfiles
  command: /usr/bin/systemd-tmpfiles --create
  listen: "systemd-tmpfiles changed"

从模板生成并启动Gunicorn服务的单元文件任务

使用模板生成并部署Gunicorn服务的单位文件gunicorn.service。

- name: Deploy gunicorn.service unit file from template
  template:
    path: "/etc/systemd/system/gunicorn.service"
    group: root
    owner: root
    src:  "{{ role_path }}/templates/gunicorn.service.j2"
  notify: "gunicorn.service unitfile changed"

- name: Start and enable gunicorn.service
  systemd:
    enabled: yes
    name: "gunicorn.service"
    state: started

以下是单位文件的模板。
由于Requires=gunicorn.socket的设置,套接字会被systemd自动启动。
由于该套接字的目录由systemd-tmpfiles生成,所以我尝试从Gunicorn官方文档的设置示例中删除了RuntimeDirectory=gunicorn。

[Unit]
Description=gunicorn daemon
Requires=gunicorn.socket
After=network.target

[Service]
PIDFile={{ gunicorn_run_dir }}/pid
User={{ service_user }}
Group={{ service_group }}
WorkingDirectory={{ django_project_base_dir }}
ExecStart=/usr/local/bin/gunicorn --pid {{ gunicorn_run_dir }}/pid   \
          --bind unix:{{ gunicorn_run_dir }}/socket {{ django_project_name }}.wsgi
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID
PrivateTmp=true

[Install]
WantedBy=multi-user.target

当单元文件发生更改时,将使用处理程序重新启动服务。

- name: Restart gunicorn.service
  systemd:
    daemon_reload: yes
    name: "gunicorn.service"
    state: restarted
  listen: "gunicorn.service unitfile changed"

从模板生成Nginx虚拟主机配置文件的任务

使用模板生成连接到Gunicorn的虚拟主机的配置文件,并将其部署到sites-available/目录下。然后在sites-enabled/目录中创建符号链接并启用该配置文件。

- name: Deploy Nginx virtual site configuration from template
  template:
    path:   "/etc/nginx/sites-available/nginx.{{ django_project_name }}.conf"
    src:    "{{ role_path }}/templates/nginx.conf.j2"
  notify:   "nginx configuration changed"

- name: Enable Nginx virtual site
  file:
    dest:   "/etc/nginx/sites-enabled/nginx.{{ django_project_name }}.conf"
    src:    "/etc/nginx/sites-available/nginx.{{ django_project_name }}.conf"
    state:  link
  notify:   "nginx configuration changed"

将反向代理的传输目标proxy_pass设置为gunicorn套接字。

server {
    listen       80;
    server_name  {{ ansible_host }};
    server_tokens off;

    location / {
        proxy_pass http://unix:{{ gunicorn_run_dir }}/socket;
        proxy_set_header Host               $host;
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host   $host;
        proxy_set_header X-Forwarded-Server $host;
        proxy_set_header X-Real-IP          $remote_addr;
    }

    location /static {
        alias {{ django_project_base_dir }}/static;
    }
}

当设置文件被更改时,使用一个处理程序重新加载Nginx。

- name: Reload nginx.service
  systemd:
    name: "nginx.service"
    state: reloaded
  listen: "nginx configuration changed"

可以使用以上的Playbook来实现上图的结构。

请提供更多上下文信息。

Gunicorn (Systemd) 部署:元上的 Gunicorn 官方文档设置

Ansible Playbook 的最佳实践
与 Playbooks 的工作 » 最佳实践: https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html

所有文章的列表

    • 1/4: DjangoをAmazon Linux 2にAnsibleで構成する、Aurora (MySQL) をCloudFormationでUTF-8に構成する、そして接続するまでが地味に大変だったこと

2/4: Nginx + Gunicorn + Django + Aurora (MySQL) の構成を図で説明してみる

3/4: Nginx + Gunicorn + Django + Aurora (MySQL) の本番環境をAnsible Playbookで構成する
4/4: NginxとGunicornの接続をソケットからHTTPに変更した

bannerAds