はじめに
Ansibleを使ってUbuntu18.04にDjangoアプリをデプロイしてみました。
作成したプレイブックの動作確認にはEC2を使用しました。
構築するシステムの構成は下図の通りです。
CondaでDjangoの実行環境を管理し、DBにはCondaでインストールしたPostgreSQL、WebサーバはApache(+mod_wsgi)を使います。
Ansibleのインストール
pipでAnsibleをインストールします。また、ユーザ作成時に使用する password_hash
で必要となる passlib
もインストールします。
$ pip install ansible
$ pip install passlib # パスワードのハッシュ化に必要
site.yml
がシステムの構成を定義したプレイブックです。
通常は色々とファイルを分けるべきですが、今回はシンプルさ優先で一つのファイルに全て詰め込んでます。
example
がデプロイするDjangoのプロジェクト、 example/environments.yml
が実行環境を定義したConda環境の設定ファイルです。
site.yml
example
├── manage.py
├── example
│ ├── __init__.py
│ ├── urls.py
│ ├── asgi.py
│ ├── wsgi.py
│ └── settings.py
├── environments.yml
用意したDjangoプロジェクトはほとんど django-admin startproject example
で生成したままの状態ですが、
settings.py
のみ、以下の記事に記載した方法でデータベースへの接続情報を管理するように手を加えています。
hiroki-sawano.hatenablog.com
今回動作環境のために用意したConda環境には最低限のパッケージのみインストールすることにします。
name: my_env
channels:
- defaults
dependencies:
- django=3.0.3
- postgresql=11.2
- psycopg2=2.7.6.1
- python=3.7.3
作成したプレイブック
以下が作成したプレイブックです。各設定内容について後述します。
初プレイブックなので不備等あるかもしれません。
共通設定
1つのEC2インスタンスに対してコマンドを実行するだけなのでホストは全指定( hosts: all
)にします。
ユーザはUbuntu AMIのデフォルトユーザで( user: ubuntu
)、 root
権限へのエスカレーションを可能とします( become: yes
)。
- hosts: all
user: ubuntu
become: yes
変数
vars
に各種変数を指定します。用途は以下のコメントに記載の通りです。
vars:
user: "foo"
password: "bar"
pub_key: "<your-pub-key>"
conda_env_name: "my_env"
db_name: "my_db"
db_user: "my_db_user"
db_password: "my_db_password"
app_name: "example"
app_src: "example"
conda_yml_file: "example/environments.yml"
home: "/home/{{ user }}"
conda_prefix: "{{ home }}/miniconda3"
conda_env: "{{ conda_prefix }}/envs/{{ conda_env_name }}"
project_dir: "{{ home }}/{{ app_name }}"
project_package: "{{ project_dir }}/example"
environment
に環境変数を指定します。
PostgreSQLの PGDATA
とDjangoの DJANGO_SETTINGS_MODULE
のみ指定しておきます。
environment:
PGDATA: "{{ project_dir }}/postgres_data"
DJANGO_SETTINGS_MODULE: "example.settings"
タスク
以降に実装した各タスクと対応するコマンドイメージを記載します。
Ansibleの変数参照部分 {{...}}
はコマンド例では ${...}
と記述します。
aptパッケージのアップデート、アップグレード
aptを使います。
キャッシュのアップデートは1日( cache_valid_time: 86400
)です。
upgrade
は update_cache
と同じく boolean
かと思いきや文字列なのでクォーテーションで囲まないと警告が出ました。
- name: Update and upgrade apt packages
apt:
upgrade: "yes"
update_cache: yes
cache_valid_time: 86400
$ sudo apt update
$ sudo apt upgrade -y
timezoneでタイムゾーンを Asia/Tokyo
に設定します。
- name: Set timezone to Asia/Tokyo
timezone:
name: Asia/Tokyo
$ sudo timedatectl set-timezone Asia/Tokyo
ユーザの追加
userで {{ user }}
を作成し、 sudo
権限を与えます。
- name: Add user and add it to sudo
user:
name: "{{ user }}"
shell: /bin/bash
state: present
groups: sudo
append: yes
password: "{{ password | password_hash('sha512') }}"
$ sudo adduser ${user}
$ sudo gpasswd -a ${user} sudo
$ sudo su - ${user}
authorized_keyで .ssh/authorized_keys
を設定し、 {{ user }}
でSSH接続できるようにします。
- name: Set authorized key for newly created user
authorized_key:
user: "{{ user }}"
state: present
key: "{{ pub_key }}"
$ cd ~
$ mkdir .ssh
$ chmod 700 .ssh
$ touch .ssh/authorized_keys
$ chmod 600 .ssh/authorized_keys
$ echo ${pub_key} > ./.ssh/authorized_keys
Minicondaのインストール
get_urlでMinicondaのインストーラーをダウンロードします。
- name: Download Miniconda
get_url:
url: https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
dest: "{{ home }}/miniconda3.sh"
mode: '0755'
shellでMinicondaをインストールします。
スクリプトはバッチモード( -b
オプション)で起動することで対話を抑止し、続けて conda init
を別途実行しています。
- name: Install Miniconda
shell: "bash {{ home }}/miniconda3.sh -b -p {{ conda_prefix }}"
args:
creates: "{{ conda_prefix }}"
become: yes
become_user: "{{ user }}"
- name: Init conda
shell: "{{ conda_prefix }}/bin/conda init bash"
args:
executable: /bin/bash
become: yes
become_user: "{{ user }}"
$ wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh
$ bash Miniconda3-latest-Linux-x86_64.sh -p ${conda_prefix}
...
Do you wish the installer to initialize Miniconda3
by running conda init? [yes|no]
[no] >>> yes
Djangoプロジェクトのアップロード
fileでDjangoプロジェクトのインストール先ディレクトリを作成し、
copyでローカルのプロジェクトをアップロードします。
- name: Create project directory
file:
path: "{{ project_dir }}"
state: directory
owner: "{{ user }}"
group: "{{ user }}"
mode: '0755'
- name: Upload django project
copy:
src: "{{ app_src }}/"
dest: "{{ project_dir }}/"
owner: "{{ user }}"
group: "{{ user }}"
mode: '0644'
force: no
$ scp -i /path/to/private/key -r ${app_src} ${user}@${ansible_host}:${project_dir}
Conda環境の作成
copyでCondaの環境設定ファイルをアップロードし、shellでConda環境を作成します。
- name: Upload environments.yml
copy:
src: "{{ conda_yml_file }}"
dest: "{{ home }}/environments.yml"
owner: "{{ user }}"
group: "{{ user }}"
- name: Create conda environment
shell: "{{ conda_prefix }}/bin/conda env create -f {{ home }}/environments.yml -n {{ conda_env_name }}"
args:
executable: /bin/bash
creates: "{{ conda_env }}"
become: yes
become_user: "{{ user }}"
$ scp -i /path/to/private/key ${conda_yml_file} ${user}@${ansible_host}:${home}/environments.yml
...
$ conda env create -f ${home}/environments.yml -n ${conda_env_name}
機密情報を記述したJSONファイルの作成
copyで secrets.json
を作成します。
ファイルではなくテキストを直接指定する場合は src
ではなく content
を使用します。
SECRET_KEY
の部分は本来はランダムな値を生成すべきです。
- name: Create secrets.json
copy:
content: '{
"SECRET_KEY": "t43m32_02dr5popsxczu*_!qyo@d7g!+e*0*@3u#_%vil=)i(u",
"DB_NAME": "{{ db_name }}",
"DB_USER": "{{ db_user }}",
"DB_PASSWORD": "{{ db_password }}",
}'
dest: "{{ project_dir }}/secrets.json"
owner: "{{ user }}"
group: "{{ user }}"
$ echo '{"SECRET_KEY": ... }' > ${project_dir}/secrets.json
データベースクラスタの作成
shellで initdb
を呼び出し、データベースクラスタを作成します。
- name: Create database cluster
shell: "{{ conda_env }}/bin/initdb"
register: result
args:
executable: /bin/bash
creates: "{{ project_dir }}/postgres_data"
become: yes
become_user: "{{ user }}"
copyでPostgreSQLサービスを作成します。
直接PostgreSQLをサーバにインストールする場合は自動的にユニットファイルが作成されますが、
今回はConda環境のPostgreSQLを使いたいので自前で作成します。
- name: Create postgres service
copy:
content:
"[Unit]\n
Description=PostgreSQL database server\n
After=network.target\n\n
[Service]\n
Type=simple\n
User={{ user }}\n
ExecStart={{ conda_env }}/bin/postgres -D {{ project_dir }}/postgres_data\n
ExecReload=/bin/kill -HUP $MAINPID\n
KillMode=mixed\n
KillSignal=SIGINT\n
TimeoutSec=0\n\n
[Install]\n
WantedBy=multi-user.target\n"
dest: "/etc/systemd/system/postgres.service"
systemdでサービスを起動し、自動起動を有効化します。
- name: Start and enable postgres service
systemd:
state: started
name: postgres
enabled: yes
$ cat << EOF > /etc/systemd/system/postgres.service
[Unit]
Description=PostgreSQL database server
...
WantedBy=multi-user.target
EOF
$ sudo systemctl daemon-reload
$ sudo systemctl start postgres
$ sudo systemctl enable postgres
shellでConda環境をアクティベートした後、DBのマイグレーションや静的ファイルの収集などの作業を実施します。
Conda環境を有効にした状態で複数のコマンドを実行したいので、 with_items
を活用しています。
Adminユーザの名前、パスワードは本来変数化すべきです。
- name: Database settings & Collect static files
shell: ". {{ conda_prefix }}/etc/profile.d/conda.sh && conda activate {{ conda_env_name }} && {{ item }}"
args:
executable: /bin/bash
chdir: "{{ project_dir }}"
with_items:
- psql postgres -c "create role {{ db_user }} with login password '{{ db_password }}';"
- createdb {{ db_name }} -O {{ db_user }}
- python manage.py migrate
- python manage.py collectstatic --noinput
- echo "from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@example.com', 'admin')" | python manage.py shell
when: result is changed
become: yes
become_user: "{{ user }}"
$ conda activate ${conda_env_name}
$ cd ${project_dir}
$ psql postgres -c "create role ${db_user} with login password '${db_password}';"
$ createdb ${db_name} -O ${db_user}
$ python manage.py migrate
$ python manage.py collectstatic --noinput
$ echo "from django.contrib.auth.models import User; User.objects.create_superuser('admin', 'admin@example.com', 'admin')" | python manage.py shell
aptでApache2をインストールします。
- name: Install Apache
apt:
pkg:
- apache2
- apache2-dev
$ sudo apt-get install -y apache2 apache2-dev
mod_wsgiのインストール
unarchiveでmod_wsgiをダウンロード・展開します。
- name: Download mod_wsgi
unarchive:
src: https://github.com/GrahamDumpleton/mod_wsgi/archive/4.5.14.tar.gz
dest: "{{ home }}"
remote_src: yes
owner: "{{ user }}"
group: "{{ user }}"
shellで mod_wsgi
をインストールします。
- name: Make mod_wsgi
shell: "{{ item }}"
with_items:
- "./configure --with-python={{ conda_env }}/bin/python"
- "make"
args:
executable: /bin/bash
chdir: "{{ home }}/mod_wsgi-4.5.14"
creates: "/usr/lib/apache2/modules/mod_wsgi.so"
become: yes
become_user: "{{ user }}"
- name: Install mod_wsgi
shell: "make install"
args:
executable: /bin/bash
chdir: "{{ home }}/mod_wsgi-4.5.14"
creates: "/usr/lib/apache2/modules/mod_wsgi.so"
$ cd ~
$ wget https://github.com/GrahamDumpleton/mod_wsgi/archive/4.5.14.tar.gz
$ tar -zxvf 4.5.14.tar.gz
$ cd mod_wsgi-4.5.14/
$ ./configure --with-python=${conda_env}/bin/python
$ make
$ sudo make install
サイトの設定
copyでApacheのサイトを設定し、commandで有効化します。
最後にsystemdでApacheを再起動します。
- name: Create Apache site
copy:
content:
"LoadFile {{ conda_env }}/lib/libpython3.7m.so.1.0\n
LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so\n
WSGIPythonHome {{ conda_env }}\n\n
<VirtualHost *:80>\n
ServerName {{ ansible_host }}\n
WSGIDaemonProcess PROCESS_GROUP user={{ user }} group={{ user }} python-path={{ project_dir }}\n
WSGIProcessGroup PROCESS_GROUP\n
WSGIScriptAlias / {{ project_package }}/wsgi.py process-group=PROCESS_GROUP\n\n
<Directory {{ project_package }}/>\n
<Files wsgi.py>\n
Require all granted\n
</Files>\n
</Directory>\n\n
Alias /static/ {{ project_dir }}/static/\n
<Directory {{ project_dir }}/static/>\n
Require all granted\n
</Directory>\n
CustomLog {{ project_dir }}/access_log common\n
ErrorLog {{ project_dir }}/error_log\n
</VirtualHost>\n"
dest: "/etc/apache2/sites-available/{{ app_name }}.conf"
- name: Disable default site
command: a2dissite 000-default
- name: Enable django app site
command: a2ensite {{ app_name }}
- name: Restart Apache
systemd:
state: restarted
name: apache2
$ cat << EOF > /etc/apache2/sites-available/${app_name}.conf
LoadFile ${conda_env}/lib/libpython3.7m.so.1.0
LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.s
...
EOF
$ sudo a2dissite 000-default
$ sudo a2ensite ${app_name}
$ sudo systemctl restart apache2
動作確認
Ubuntu Server 18.04のAMIでインスタンスを立ち上げます。
IPアドレスは 13.231.182.215
です。
プレイブックの実行
起動したインスタンスには標準でPython3が搭載されているので、即プレイブックを実行できます。
IPアドレスを -i
オプションで直接指定します。
$ ansible-playbook --private-key /path/to/private/key -u ubuntu -i 13.231.182.215, site.yml
...
TASK [Install Apache] ************************************************************************************************
changed: [13.231.182.215]
TASK [Download mod_wsgi] *********************************************************************************************
changed: [13.231.182.215]
TASK [Make mod_wsgi] *************************************************************************************************
changed: [13.231.182.215] => (item=./configure --with-python=/home/foo/miniconda3/envs/my_env/bin/python)
changed: [13.231.182.215] => (item=make)
TASK [Install mod_wsgi] **********************************************************************************************
changed: [13.231.182.215]
TASK [Create Apache site] ********************************************************************************************
changed: [13.231.182.215]
TASK [Disable default site] ******************************************************************************************
changed: [13.231.182.215]
TASK [Enable django app site] ****************************************************************************************
changed: [13.231.182.215]
TASK [Restart Apache] ************************************************************************************************
changed: [13.231.182.215]
PLAY RECAP ***********************************************************************************************************
13.231.182.215 : ok=25 changed=24 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
何も実装していないのでAdminサイトにアクセスできることを確認することにします。
以下の通り http://13.231.182.215/admin
にアクセスすると、無事ログイン画面が表示されました。
さいごに
初めてAnsibleを触ってみましたが、思っていたよりも簡単に利用できて好印象でした。
ドキュメントも充実していて、今回試した範囲だとそれほど悩むこともなかったです。
対象ホストにPythonさえ入っていれば良いので導入の障壁も低いと思います。
今後本格的に利用を検討していきたいです。