使用Ansible Playbook在生产环境中配置Nginx + Gunicorn + Django + Aurora (MySQL)
使用Django + MySQL开发的应用程序的生产环境将使用Ansible在Amazon EC2上进行配置。

以下两篇文章的续篇已于此次完成。
-
- 前々回: 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。

根据之前的文章所述,在操作系统启动时,为套接字生成系统服务 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に変更した