こんにちは、akiyoko です。
Mezzanine は、知る人ぞ知る Python製の WordPress風フルスタックCMSフレームワークです。
今年の 7月に、Mezzanine を使った某ブログサイト(将来的に ECサイトを増設予定)の本番運用を開始しました。*1 その備忘録として、AWS の初期設定から Mezzanine の本番デプロイ、ちょっとした運用設定までの記録をまとめておくことにしました。
全ての記録を一つの記事にすると長くなり過ぎるので、テーマごとに、
の 4本に記事を分割することにしました。
今回はその 3本目、「その3:Mezzanine の本番デプロイ」について説明します。
Mezzanine の本番デプロイとして実施した内容としては、
- 本番サーバ(Ubuntu)を起動
- Fabric スクリプトを実行
- Fabric スクリプト実行後の各種設定
- SSL の設定
- URL の転送設定
- BASIC認証の設定
- MySQL のインストール
- メールの設定
- ログの設定
- タイムゾーンを Asia/Tokyo に変更
- sitemap.xml / robot.txt の設定
となります。
【目次】
- 1. 概要
- 2. Ubuntu インスタンスを起動
- 3. Fabric スクリプトを実行
- 4. SSL の設定
- 5. URL の転送設定
- 6. BASIC認証の設定
- 7. MySQL のインストール
- 8. メールの設定
- 9. ログの設定
- 10. タイムゾーンを Asia/Tokyo に変更
- 11. sitemap.xml / robot.txt の設定
- 12. デプロイ後の本番環境確認
- まとめ
- 参考本
1. 概要
Mezzanine には何と、コマンド一発で リバースプロキシや DB などを含めた全部入りの本番環境が構築できる Fabric スクリプト が含まれています。
Fabric
Each Mezzanine project comes bundled with utilities for deploying production Mezzanine sites, using Fabric. The provided fabfile.py contains composable commands that can be used to set up all the system-level requirements on a new Debian based system, manage each of the project-level virtual environments for initial and continuous deployments, and much more.
(akiyoko 翻訳)
それぞれの Mezzanine プロジェクトには、本番 Mezzanine サイトをデプロイするための Fabric を使った便利ツールがバンドルされています。提供されている fabfile.py には、新しい Debian ベースのシステム上のあらゆるシステムレベルの依存ライブラリをセットアップしたり、初回デプロイ・継続的デプロイのためのプロジェクトレベルの仮想環境を管理したり、他にもたくさんのことをしたりするために利用できる組み立て可能なコマンド群が含まれています。
Mezzanine の Fabric スクリプト (*2) を実行するには、Fabric を pip でインストールした後、
$ fab secure
してから、
$ fab all
するだけです。簡単ですよね!
一度試してみれば分かりますが、もう至れり尽くせりです。
例えば、HTTPS のためのオレオレ証明書を作成して所定の位置に配置し、Nginx の設定までしてくれるという心尽くしに「ありがとう!」と思わず声が漏れてしまいます。
Mezzanine デフォルトの Fabric スクリプトを実行して構築した本番環境は、以下のコンポーネントで構成されます。
- NGINX - public facing web server
- gunicorn - internal HTTP application server
- PostgreSQL - database server
- memcached - in-memory caching server
- supervisord - process control and monitor
- virtualenv - isolated Python environments for each project
- git or mercurial - version control systems (optional)
データベースとして PostgreSQL がインストールされますが、PostgreSQL と MySQL はどっちがいいの?という議論(*3)は置いておいて、私の場合は単純に「操作や運用に慣れている」という理由から、今回はデータベースを PostgreSQL から MySQL に入れ替えることにしました(実際には、Fabric スクリプトを実行して PostgreSQL を一旦インストールした後に手動で削除し、Ansible で MySQL をインストールしました)。
(参考)
- Deploy Mezzanine to Ubuntu 14 server on DigitalOcean with Fabric | PerezProgramming
- http://bitofpixels.com/blog/deploying-mezzanine-to-digital-ocean-using-the-included-fabfile/
- Deploying Mezzanine: Fabric Git Vagrant Joy | BScientific
- Setting up Django with Nginx, Gunicorn, virtualenv, supervisor and PostgreSQL - Michał Karzyński (*4)
最終的なサーバとコンポーネント、プロジェクトの構成は以下のようになりました。
サーバ:
- Ubuntu 14.04.4 LTS
コンポーネント:
Nginx | 1.4.6 |
Gunicorn | 19.6.0 |
MySQL | 5.5.49 |
Supervisor | 3.0b2 |
Python | 2.7.6 |
Django | 1.9.7 |
Mezzanine | 4.1.0 (*5) |
上記のバージョンは、いずれも 2016年7月時点のものです。
プロジェクト:
プロジェクト名 | akiyokoproject |
プロジェクトの配置場所 | /home/webapp/mezzanine/akiyokoproject |
プロジェクトのオーナ(Ubuntu ユーザ名) | webapp |
virtualenv の配置場所 | /home/webapp/.virtualenvs/akiyokoproject |
デプロイ先のドメイン名 | akiyoko.com (*6) |
以降、Fabric スクリプトの実行を含めた、Mezzanine の本番デプロイの手順について説明します。
2. Ubuntu インスタンスを起動
Fabric スクリプトを実行する前に、スクリプトを流し込むサーバを立ち上げる必要があります。サーバには、AWS EC2 で起動した Ubuntu インスタンスを利用します。
また初期設定として、Fabric スクリプトを流し込むための SSHユーザの設定などを行います。
2.1. EC2インスタンスを起動
AWS EC2インスタンスを立ち上げます。その際、IAMロール「ec2-prod」(*7)を付けておきます。
具体的な起動方法については、以下の過去記事を参照してください。
<過去記事>
akiyoko.hatenablog.jp
以下、インスタンスの IPアドレスを「52.xxx.xxx.xxx」とします。
2.2. webapp ユーザを作成
以下のコマンドで Ubuntu にアクセスします。
(サーバにアクセスするための SSH秘密鍵が「~/.ssh/aws_p1.pem」に配置されているという前提です。)
$ ssh -i ~/.ssh/aws_p1.pem ubuntu@52.xxx.xxx.xxx
Fabric スクリプトの各コマンドを実行する SSHユーザ「webapp」を作成します。
$ sudo adduser --gecos '' webapp
(ここでは、パスワードは「pass」など簡単なもので OK。)
(参考)What do the `--disabled-login` and `--gecos` options of `adduser` command stand for? - Ask Ubuntu
次に、webapp ユーザがパスワード無しでログインできるように設定します。
$ sudo visudo -f /etc/sudoers.d/90-cloud-init-users
を実行して、以下の一行を末尾に追加します(control + option + x で保存)。
webapp ALL=(ALL) NOPASSWD:ALL
Ubuntu サーバを再起動後、ubuntu ユーザのものと同じ SSH鍵でログインできるように設定します。 *8
$ sudo service sudo restart $ sudo su - webapp $ mkdir -m 700 ~/.ssh $ sudo cp -a /home/ubuntu/.ssh/authorized_keys ~/.ssh/ $ sudo chown webapp. ~/.ssh/authorized_keys $ sudo chmod 600 ~/.ssh/authorized_keys
最後に、Mac 上から
$ ssh -i ~/.ssh/aws_p1.pem webapp@52.xxx.xxx.xxx
でサーバにログインできることが確認できれば OK。
3. Fabric スクリプトを実行
3.1. 本番デプロイ用の Mezzanine プロジェクトを作成
Mac上で、Fabric スクリプトを実行するための、本番デプロイ用の Mezzanine プロジェクトを作成します。
### virualenv が Mac にインストール済みという前提 $ virtualenv ~/.venvs/mezzanine_deploy $ source ~/.venvs/mezzanine_deploy/bin/activate ### Fabric を実行するために必要なコンポーネントを pip install (mezzanine_deploy)$ pip install future fabric mezzanine Mezzanine (4.1.0) Fabric (1.11.1) paramiko (1.17.0) ### 適当なディレクトリにプロジェクトを作成 (mezzanine_deploy)$ cd ~/dev (mezzanine_deploy)$ mkdir mezzanine_deploy_akiyokoproject_for_aws && cd $_ (mezzanine_deploy)$ mezzanine-project config .
本番デプロイ用に、config/local_settings.py を以下のように編集します。 *9
・ ・ ################### # DEPLOY SETTINGS # ################### # Domains for public site ALLOWED_HOSTS = [".akiyoko.com"] # These settings are used by the default fabfile.py provided. # Check fabfile.py for defaults. FABRIC = { "SSH_USER": "webapp", # VPS SSH username and will be an application owner "SSH_KEY_PATH": "~/.ssh/aws_p1.pem", "HOSTS": ["52.xxx.xxx.xxx"], # The IP address of your VPS "DOMAINS": ["akiyoko.com"], # Edit domains in ALLOWED_HOSTS #"LIVE_HOSTNAME": "akiyoko.com", # Host for public site. #"VIRTUALENV_HOME": "/home/ubuntu/.virtualenvs", # Absolute remote path for virtualenvs "PROJECT_NAME": "akiyokoproject", # Unique identifier for project "REQUIREMENTS_PATH": "requirements.txt", # Project's pip requirements "GUNICORN_PORT": 8000, # Port gunicorn will listen on "LOCALE": "en_US.UTF-8", # Should end with ".UTF-8" "DB_PASS": "dbpass", # Live database password "ADMIN_PASS": "adminpass", # Live admin user password "SECRET_KEY": SECRET_KEY, "NEVERCACHE_KEY": NEVERCACHE_KEY, }
(「ADMIN_PASS」および「ADMIN_PASS」の値はサンプルですので、コピペしないようにご注意ください。)
ちなみに、
"DEPLOY_TOOL": "git",
と設定すると、
Fatal error: local() encountered an error (return code 128) while executing 'git push -f ssh://ubuntu@52.xxx.xxx.xxx/home/ubuntu/git/akiyokoproject.git master'
というエラーになったので、今回は設定していません。
3.2. fabfile.py を修正
2016年7月時点では、fab all 実行時に、
$ /home/webapp/mezzanine/akiyokoproject/bin/python /home/webapp/mezzanine/akiyokoproject/manage.py syncdb --noinput -> [52.xxx.xxx.xxx] out: Unknown command: 'syncdb' [52.xxx.xxx.xxx] out: Type 'manage.py help' for usage. [52.xxx.xxx.xxx] out: Fatal error: run() received nonzero return code 1 while executing!
というエラーが出たので、fabfile.py を以下のように修正しました。 *10
fabfile.py
@@ -435,6 +435,7 @@ def install(): """ # Install system requirements sudo("apt-get update -y -q") + sudo("apt-get upgrade -y -q") apt("nginx libjpeg-dev python-dev python-setuptools git-core " "postgresql libpq-dev memcached supervisor python-pip") run("mkdir -p /home/%s/logs" % env.user) @@ -635,7 +636,7 @@ def deploy(): rsync_upload() with project(): manage("collectstatic -v 0 --noinput") - manage("syncdb --noinput") + manage("makemigrations --noinput") manage("migrate --noinput") for name in get_templates(): upload_template_and_reload(name)
原因は、fabfile.py 内で使用されている syncdb コマンドが Django 1.9(Django 1.7 以降)に対応していないからです。
(参考)Deploy Mezzanine to Ubuntu 14 server on DigitalOcean with Fabric | PerezProgramming
なお、今回のように AWS + Ubuntu でサーバを構築する場合は、fab secure の内容はほぼ網羅できているのでスキップした(fab secure コマンドは実行しなかった)のですが、それだと「apt-get upgrade -y -q」が唯一実行できていないので、fabfile.py の install() に追加しています。
3.3. デプロイ実行
先述のように、今回は AWS + Ubuntu でサーバを構築したので、
(mezzanine_deploy)$ fab secure
は今回実行しません。
以下のコマンドのみを実行します。
(mezzanine_deploy)$ fab all
正常に Fabric スクリプトが流れ終わったら、デプロイ後の後処理を実施していきます。
4. SSL の設定
ここではすでに、正規の SSL証明書を取得しているものとします。
取得方法については、以下の過去記事を参考にしてください。
<過去記事>
akiyoko.hatenablog.jp
まずは、「GoGetSSL」の管理ページから、www_akiyoko_com.crt(SSL証明書)と www_akiyoko_com.ca-bundle(中間証明書)をダウンロードしておきます。
クライアント(Mac)からサーバに SSL証明書と中間証明書を転送します。
$ scp -i ~/.ssh/aws_p1.pem ~/Downloads/www_akiyoko_com.crt ubuntu@52.xxx.xxx.xxx:/tmp/ $ scp -i ~/.ssh/aws_p1.pem ~/Downloads/www_akiyoko_com.ca-bundle ubuntu@52.xxx.xxx.xxx:/tmp/
次に、Ubuntu 上で、SSL証明書を(Fabric スクリプトが作成してくれたデフォルトのものから)入れ替えます。
$ sudo mv /etc/nginx/conf/akiyokoproject.crt /etc/nginx/conf/akiyokoproject.crt.orig $ sudo mv /etc/nginx/conf/akiyokoproject.key /etc/nginx/conf/akiyokoproject.key.orig $ sudo cp /tmp/www_akiyoko_com.crt /etc/nginx/conf/akiyokoproject.crt $ sudo chmod 644 /etc/nginx/conf/akiyokoproject.crt ### 中間証明書を連結する $ cat /tmp/www_akiyoko_com.ca-bundle | sudo tee -a /etc/nginx/conf/akiyokoproject.crt
なお、Android 実機(Nexus 5)から HTTPS で閲覧したときに証明書エラーが出たので、以下を参考に、SSL証明書に中間証明書を連結しました。 *11
(参考)
- http://techracho.bpsinc.jp/baba/2011_07_07/3533
- Blog: SSL中間証明書を改良してAndroidに対応させた – x86-64.jp - くりす研
- SSL証明書と中間CA証明書、そしてブラウザ(PC、スマホ)の挙動 - Qiita
最後に、秘密鍵を書き換えて(*12)、Nginx をリロードします。
$ sudo vi /etc/nginx/conf/akiyokoproject.key (秘密鍵をコピペ) $ ls -al /etc/nginx/conf total 24 drwxr-xr-x 2 root root 4096 Jul 7 15:29 . drwxr-xr-x 6 root root 4096 Jul 7 15:14 .. -rw-r--r-- 1 root root 1964 Jul 7 15:29 akiyokoproject.crt -rw-r--r-- 1 root root 1111 Jul 7 15:14 akiyokoproject.crt.orig -rw-r--r-- 1 root root 1704 Jul 7 15:29 akiyokoproject.key -rw-r--r-- 1 root root 1704 Jul 7 15:14 akiyokoproject.key.orig ### Nginx をリロード $ sudo service nginx reload
5. URL の転送設定
ここで、
http://akiyoko.com/foo/bar ⇒ https://akiyoko.com/foo/bar
http://www.akiyoko.com/foo/bar ⇒ https://akiyoko.com/foo/bar
https://www.akiyoko.com/foo/bar ⇒ https://akiyoko.com/foo/bar
のように URL転送(要するに全部 https の wwwなしドメインに転送)したいので、Nginx の設定を修正して 80 番ポートアクセス時の転送先を変更します。
/etc/nginx/sites-enabled/akiyokoproject.conf
server {
#listen 80;
listen 443 ssl;
・
・
server {
listen 80;
server_name akiyoko.com;
return 301 https://akiyoko.com$request_uri;
}
server {
listen 80;
listen 443 ssl;
server_name www.akiyoko.com;
return 301 https://akiyoko.com$request_uri;
}
なお、以下 ↓ のように設定しても動作しましたが、上記の方がベターかと。
server {
#listen 80;
listen 443 ssl;
・
・
server {
listen 80;
server_name akiyoko.com;
return 301 https://akiyoko.com$request_uri;
}
server {
server_name www.akiyoko.com;
return 301 https://akiyoko.com$request_uri;
}
一旦、Nginx をリロードします。
$ sudo service nginx reload
6. BASIC認証の設定
(デプロイ時はまだリリース前だったので)ここで一旦、BASIC認証をかけておきます。もちろん、サイト公開時には設定を外しておくのを忘れずに。
$ sudo apt-get -y install apache2-utils $ sudo htpasswd -c /etc/nginx/.htpasswd test (パスワードを入力)
Nginx の設定を変更します。
/etc/nginx/sites-enabled/akiyokoproject.conf
server { #listen 80; listen 443 ssl; server_name akiyoko.com; ・ ・ location / { auth_basic "Restricted"; auth_basic_user_file /etc/nginx/.htpasswd; proxy_redirect off; proxy_set_header Host $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-Protocol $scheme; proxy_pass http://akiyokoproject; }}
Nginx をリロードして作業完了です。
$ sudo service nginx reload
7. MySQL のインストール
先述の通り今回は、Fabric スクリプトを実行してインストールされる PostgreSQL から MySQL にデータベースを入れ替えました。実際の作業としては、インストールされた PostgreSQL を手動で削除し、Ansible スクリプトを実行して MySQL をインストールしました。
以下、その手順です。
7.1. PostgreSQL のアンインストール
まず、手動で PostgreSQL を削除します。
$ sudo apt-get -y --purge remove postgresql\* $ sudo rm -rf /etc/postgresql/ $ sudo rm -rf /etc/postgresql-common/ $ sudo rm -rf /var/lib/postgresql/ $ sudo userdel -r postgres $ sudo groupdel postgres
7.2. MySQL のインストール
次に、MySQL を Ansible を使って Mac からインストールします。
今回は、Ansible Galaxy を使ってインストールをおこないました。
<過去記事>
akiyoko.hatenablog.jp
Mac 上の適当なディレクトリにプロジェクトを作成します。
$ cd ~/dev/ansible-mysql
hosts
[db-servers] 52.xxx.xxx.xxx
site.yml
- hosts: db-servers user: ubuntu vars_files: - vars/database.yml roles: - { role: ANXS.mysql }
vars/database.yml
mysql_current_root_password: '' mysql_root_password: dbpass mysql_databases: - name: akiyokoproject mysql_users: - name: akiyokoproject pass: akiyokoprojectpass priv: "akiyokoproject.*:ALL"
(「mysql_root_password」および「pass」の値はサンプルですので、コピペしないようにご注意ください。)
Ansible を実行します。
$ ansible-playbook -i hosts site.yml --sudo --private-key ~/.ssh/aws_p1.pem -vv
7.3. MySQL 用の設定
データベースを MySQL に変更したのに伴い、Ubuntu サーバの各種設定を変更します。
MySQL 用のプラグインをインストールします。
$ sudo apt-get -y install libmysqlclient-dev $ pip install MySQL-python
settings.py のデータベース設定を修正します。
config/local_settings.py
・ ・ DATABASES = { "default": { # Ends with "postgresql_psycopg2", "mysql", "sqlite3" or "oracle". "ENGINE": "django.db.backends.mysql", # DB name or path to database file if using sqlite3. "NAME": "akiyokoproject", # Not used with sqlite3. "USER": "akiyokoproject", # Not used with sqlite3. "PASSWORD": "akiyokoprojectpass", # Set to empty string for localhost. Not used with sqlite3. "HOST": "127.0.0.1", # Set to empty string for default. Not used with sqlite3. "PORT": "", } } ・ ・
データベースのマイグレーションを実行します。
$ python manage.py migrate $ python manage.py createsuperuser username (leave blank to use 'webapp'): admin Email address: admin@akiyoko.com Password: Password (again):
とりあえずデータベースをバックアップしておきます。
$ mkdir ~/db_backup $ mysqldump --single-transaction -u root -p akiyokoproject > ~/db_backup/akiyokoproject_init.dump ### タイムスタンプをバックアップファイル名に含める場合 $ mysqldump --single-transaction -u root -p akiyokoproject > ~/db_backup/akiyokoproject_backup_`date +%y%m%d%H%M`.dump ### リストアするときのコマンド ### mysql -u root -p akiyokoproject < ~/db_backup/akiyokoproject_init.dump
(参考)MySQLのデータベースをmysqldumpでバックアップ/復元する方法 | WEB ARCH LABO
最後に gunicorn を再起動して完了です。
(akiyokoproject)$ python manage.py collectstatic $ sudo supervisorctl restart all
ここで万が一(バックアップした AMI から起動した直後など?)、virtualenvwrapper を使って workon しようとした際に、
$ workon akiyokoproject workon: command not found
というエラーが出てしまう場合は、一旦 Ubuntu からログアウトして、ubuntuユーザで接続し直してから、
$ sudo su - webapp
として .bashrc を読み込み直すとよさそうです(source ~/.bashrc としてもよかったのかな??)。
8. メールの設定
前提として、AWS Management Console で SES の設定が完了し、起動した EC2インスタンスに SES の権限を持った IAMロール(今回は「ec2-prod」がそれに相当)が付与されているものとします。
<過去記事>
akiyoko.hatenablog.jp
なお、Amazon SES を利用するため、postfix などの MTA のインストールは不要です。
(参考)
- https://www.ianlewis.org/jp/django-amazon-ses
- Getting E-Mail right with Django and SES | Eventual Consistency
- https://www.webforefront.com/django/setupdjangoemail.html#djangoemailses
Ubuntuサーバ上で、django-ses をインストールします。
(akiyokoproject)$ pip install django-ses
config/local_settings.py の末尾に以下を追加します。
################ # AWS SETTINGS # ################ #AWS_ACCESS_KEY_ID = "AKIxxxxxxxxxxxxxxxxx" #AWS_SECRET_ACCESS_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ################## # EMAIL SETTINGS # ################## EMAIL_BACKEND = "django_ses.SESBackend" AWS_SES_REGION_NAME = "us-west-2" AWS_SES_REGION_ENDPOINT = "email.us-west-2.amazonaws.com" #EMAIL_HOST_USER = "admin@akiyoko.com" #EMAIL_USE_TLS = True #EMAIL_HOST = "email-smtp.us-west-2.amazonaws.com" #DEFAULT_FROM_EMAIL = SERVER_EMAIL = u"no-reply <no-reply@akiyoko.com>"
上記は、SES を「Oregon」リージョンで開設した場合の設定になります。
なお、コメントアウト部分はあってもなくても OK なのですが、「EMAIL SETTINGS」に設定している 3つは最低限必要となります。 *13
あと、「AWS_SES_REGION_ENDPOINT」は、普段(?)のホスト名とは違う(「email.」で始まる)ので注意が必要かと。
(参考)Customizing Mezzanine | ROSS LAIRD
の記事が、一番参考になりました。
その他、
- https://groups.google.com/forum/#!topic/mezzanine-users/WMjxxZoslwo
- GitHub - django-ses/django-ses: A Django email backend for Amazon's Simple Email Service
も参考に。
9. ログの設定
Fabric スクリプト実行後の状態ではアプリケーションの各種ログが出力されないようになっているので、ここで、Mezzanine のログ設定を変更します。
まず、ログを出力するディレクトリを掘っておきます。
なお、gunicorn のプロセスは webapp ユーザで起動されるので、ディレクトリのオーナーを webapp.webapp にしておきます。
$ sudo mkdir /var/log/akiyokoproject $ sudo chown webapp. /var/log/akiyokoproject
config/local_settings.py の末尾に、以下の設定を追加します。
################ # LOG SETTINGS # ################ LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'DEBUG', 'class': 'logging.FileHandler', 'filename': '/var/log/akiyokoproject/application.log', }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'DEBUG', 'propagate': True, }, }, }
(上記は、DEBUGレベル以上のログを「/var/log/akiyokoproject/application.log」に出力する設定になります。)
gunicorn を再起動して設定を反映させます。
$ sudo supervisorctl restart all
10. タイムゾーンを Asia/Tokyo に変更
これはちょっと「やっちまった系」です。
本番稼働して何日か経ってからふと思い立ってタイムゾーンを「Asia/Tokyo」に変更したのですが、本来は、Fabric スクリプトを実行する前にやっておくべきだったかもしれません。。
10.1. Ubuntuサーバのタイムゾーンを変更
$ timedatectl Local time: Mon 2016-07-25 13:02:02 UTC Universal time: Mon 2016-07-25 13:02:02 UTC Timezone: Etc/UTC (UTC, +0000) NTP enabled: yes NTP synchronized: no RTC in local TZ: no DST active: n/a $ sudo timedatectl set-timezone Asia/Tokyo $ timedatectl Local time: Mon 2016-07-25 22:02:44 JST Universal time: Mon 2016-07-25 13:02:44 UTC Timezone: Asia/Tokyo (JST, +0900) NTP enabled: yes NTP synchronized: no RTC in local TZ: no DST active: n/a $ date Mon Jul 25 22:05:10 JST 2016
(参考)
- タイムゾーンの設定方法をメモ(RHEL6, RHEL7, Ubuntu編) | Siguniang's Blog
- Ubuntu 14.04 LTS : システムのタイムゾーンを設定する : Server World
10.2. settings.py の修正
settings.py の TIME_ZONE を「Asia/Tokyo」に変更して、プロセスを再起動します。
config/settings.py
@@ -101,7 +101,7 @@ ALLOWED_HOSTS = [] # timezone as the operating system. # If running in a Windows environment this must be set to the same as your # system time zone. -TIME_ZONE = 'UTC' +TIME_ZONE = 'Asia/Tokyo' # If you set this to True, Django will use timezone-aware datetimes. USE_TZ = True
10.3. MySQL のタイムゾーン変更
本番運用を開始した後に Ubuntu のタイムゾーンを変更したからか(十中八九それが原因だと思いますが・・)、Django Admin で [Content] > [Blog posts] に移動しようとすると、「Sorry, an error occurred.」と画面に出て、以下のエラーログが出るようになってしまいました。
・ ・ ValueError: Database returned an invalid value in QuerySet.datetimes(). Are time zone definitions for your database and pytz installed? Exception while resolving variable 'get_admin_url' in template 'includes/editable_toolbar.html'. Traceback (most recent call last): File "/home/webapp/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/django/template/base.py", line 905, in _resolve_lookup (bit, current)) # missing attribute VariableDoesNotExist: Failed lookup for key [get_admin_url] in u'None'
先に、MySQL の設定を確認しておきます。
$ mysql -u root -p mysql> show variables like '%time_zone%'; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | JST | | time_zone | SYSTEM | +------------------+--------+ 2 rows in set (0.00 sec) mysql> select count(*) from `mysql`.`time_zone_name`; +----------+ | count(*) | +----------+ | 0 | +----------+ 1 row in set (0.00 sec)
対策として、タイムゾーンデータをインポートしました(ちなみに、最後の「mysql」はテーブル名)。 *14
$ mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql Enter password: Warning: Unable to load '/usr/share/zoneinfo/iso3166.tab' as time zone. Skipping it. Warning: Unable to load '/usr/share/zoneinfo/leap-seconds.list' as time zone. Skipping it. Warning: Unable to load '/usr/share/zoneinfo/zone.tab' as time zone. Skipping it. ### ↓のコマンドを実行すると、MySQLを再起動しなくてもOK $ mysql -u root -p -e "flush tables;" mysql
(参考)mysql - Database returned an invalid value in QuerySet.dates() - Stack Overflow
なお、「MySQLでタイムゾーンを設定する - Qiita」に書かれているように、「/etc/my.cnf」のタイムゾーンを「default-time-zone = 'Asia/Tokyo'」などと追加しなくてもよさそうです(詳細は不明)。
最後に、MySQL の設定を再び確認。
$ mysql -u root -p mysql> show variables like '%time_zone%'; +------------------+--------+ | Variable_name | Value | +------------------+--------+ | system_time_zone | JST | | time_zone | SYSTEM | +------------------+--------+ 2 rows in set (0.00 sec) mysql> select count(*) from `mysql`.`time_zone_name`; +----------+ | count(*) | +----------+ | 1808 | +----------+ 1 row in set (0.00 sec)
(「/etc/my.cnf」のタイムゾーンを変更すると、time_zone の値は「Asia/Tokyo」になりますが)time_zone は「SYSTEM」のままです。
これで解決しました!!
11. sitemap.xml / robot.txt の設定
これは公開直前におこなう作業ですが、Google Search Console の設定に先駆けて、sitemap.xml および robot.txt の設定をしておきます。
これまたありがたいことに、Mezzanine には、sitemap.xml および robot.txt を提供してくれる仕組みがすでに整っています。
11.1. sitemap.xml の設定
sitemap.xml については、Mezzanine にデフォルトで「django.contrib.sitemaps」がインストールされているので、「/sitemap.xml」にアクセスするとコンテンツに応じた sitemap.xml が自動的に生成されて表示されます。
しかしながら Mezzanine 4.1.0 では、「/sitemap.xml」にアクセスすると以下のエラーが出てしまいます。
Internal Server Error: /sitemap.xml Traceback (most recent call last): File "/home/webapp/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 149, in get_response response = self.process_exception_by_middleware(e, request) (中略) File "/home/webapp/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/mezzanine/core/sitemaps.py", line 27, in items return list(Displayable.objects.url_map(in_sitemap=True).values()) File "/home/webapp/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/mezzanine/core/managers.py", line 366, in url_map home = self.model(title=_("Home")) File "/home/webapp/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/django/db/models/base.py", line 416, in __init__ val = field.get_default() File "/home/webapp/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 905, in get_default if isinstance(field_default, self.remote_field.model): TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types Exception while resolving variable 'get_admin_url' in template 'includes/editable_toolbar.html'. Traceback (most recent call last): File "/home/webapp/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/django/template/base.py", line 905, in _resolve_lookup (bit, current)) # missing attribute VariableDoesNotExist: Failed lookup for key [get_admin_url] in u'None'
この不具合は本家で修正されていたのですが、残念ながら、私が本番デプロイした Mezzanine 4.1.0 にはまだマージされていませんでした(4.2.0 から適用されています)。
Fix dummy homepage object in sitemap. Closes #1516. · stephenmcd/mezzanine@94d5729 · GitHub
そこで以下の手順で、Mezzanine 4.1.0 にパッチを充てました。
まず、GitHub 上で stephenmcd/mezzanine を clone しておきます。
その後、Mac 上で以下のコマンドを実行します。
### ローカルに clone $ git clone https://github.com/akiyoko/mezzanine.git ### tag 4.1.0 でチェックアウト(ブランチ名は「release」) $ git checkout -b release refs/tags/4.1.0 ### パッチを充てる $ git cherry-pick 94d57294bcc1e934fdd723546be7ea15bb9dcd1a ### リモートリポジトリに push $ git push origin release
本番環境にて、akiyoko リポジトリから release タグ指定で Mezzanine をインストールし直します。
$ workon akiyokoproject $ pip install -U git+https://github.com/akiyoko/mezzanine.git@release#egg=Mezzanine
これで解決しました。
(参考)
11.2. robot.txt の設定
Fabric スクリプトを実行することで配置される robots.txt は、Google 検索エンジン bot のクローリングを全てのページで拒否するような設定になっているため、本番サイトの公開前に、以下のように「/admin/」以外をクローリング許可するような設定に書き換えます。
static/robots.txt
User-agent: * Disallow:
となっているのを、
User-agent: * Disallow: /admin/ Sitemap: https://akiyoko.com/sitemap.xml
に修正します。
(なお、「static/robots.txt」は、.gitignore に「/static」が含まれているため Git管理下にはなりません。)
12. デプロイ後の本番環境確認
最後に、デプロイ後の本番環境の各種設定を確認します。
まずは、tree をインストールします。
$ sudo apt-get -y install tree
12.1. ディレクトリ構成
$ find . -name "*.pyc" -exec rm {} \; $ tree ~/ -L 4 /home/webapp/ ├── db_backup │ └── akiyokoproject_init.dump ├── logs │ ├── akiyokoproject_error.log │ ├── akiyokoproject_error_nginx.log │ └── akiyokoproject_supervisor └── mezzanine ├── akiyokoproject │ ├── config │ │ ├── __init__.py │ │ ├── local_settings.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py │ ├── custom │ │ ├── __init__.py │ │ ├── static │ │ ├── templates │ │ └── templatetags │ ├── deploy │ │ ├── crontab.template │ │ ├── gunicorn.conf.py.template │ │ ├── local_settings.py.template │ │ ├── nginx.conf.template │ │ └── supervisor.conf.template │ ├── fabfile.py │ ├── gunicorn.conf.py │ ├── gunicorn.pid │ ├── gunicorn.sock │ ├── __init__.py │ ├── last.db │ ├── manage.py │ ├── requirements.txt │ ├── static │ │ ├── admin │ │ ├── bootstrap │ │ ├── CACHE │ │ ├── css │ │ ├── filebrowser │ │ ├── fonts │ │ ├── grappelli │ │ ├── images │ │ ├── img │ │ ├── js │ │ ├── media │ │ ├── mezzanine │ │ ├── plugins │ │ ├── robots.txt │ │ ├── test │ │ └── videos │ └── templates │ ├── base.html │ ├── blog │ ├── email │ ├── errors │ ├── generic │ ├── includes │ ├── index.html │ ├── pages │ ├── search_results.html │ └── twitter └── akiyokoproject.tar
12.2. 各種ログ
Nginx: /var/log/nginx
$ sudo ls -al /var/log/nginx/ total 8 drwxr-x--- 2 www-data adm 4096 Jun 25 07:06 . drwxrwxr-x 11 root syslog 4096 Jun 25 07:06 .. -rw-r--r-- 1 root root 0 Jun 25 07:06 access.log -rw-r--r-- 1 root root 0 Jun 25 07:06 error.log
Supervisor: /var/log/supervisor
$ ls -al /var/log/supervisor total 12 drwxr-xr-x 2 root root 4096 Jun 25 07:07 . drwxrwxr-x 11 root syslog 4096 Jun 25 07:06 .. -rw------- 1 root root 0 Jun 25 07:07 gunicorn_akiyokoproject-stderr---supervisor-VP9pQh.log -rw-r--r-- 1 root root 760 Jun 25 07:07 supervisord.log
アプリケーションログ: /var/log/akiyokoproject/
$ ls -al /var/log/akiyokoproject/ total 42520 drwxr-xr-x 2 webapp webapp 4096 Aug 11 20:11 . drwxrwxr-x 12 root syslog 4096 Oct 25 06:39 .. -rw-r--r-- 1 webapp webapp 43527221 Oct 25 21:30 application.log
エラー系ログ: /home/webapp/logs/
$ ls -al /home/webapp/logs/ total 3808 drwxrwxr-x 2 webapp webapp 4096 Aug 11 20:11 . drwxr-xr-x 8 webapp webapp 4096 Oct 22 14:38 .. -rw-r--r-- 1 webapp webapp 23980 Aug 11 20:04 akiyokoproject_error.log -rw-r--r-- 1 www-data root 3851602 Oct 25 21:58 akiyokoproject_error_nginx.log -rw-r--r-- 1 root root 5985 Aug 11 20:04 akiyokoproject_supervisor
12.3. Mezzanine の各種設定
config/local_settings.py
from __future__ import unicode_literals SECRET_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" NEVERCACHE_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ALLOWED_HOSTS = ['akiyoko.com'] DATABASES = { "default": { # Ends with "postgresql_psycopg2", "mysql", "sqlite3" or "oracle". "ENGINE": "django.db.backends.mysql", # DB name or path to database file if using sqlite3. "NAME": "akiyokoproject", # Not used with sqlite3. "USER": "akiyokoproject", # Not used with sqlite3. "PASSWORD": "akiyokoprojectpass", # Set to empty string for localhost. Not used with sqlite3. "HOST": "127.0.0.1", # Set to empty string for default. Not used with sqlite3. "PORT": "", } } SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTOCOL", "https") CACHE_MIDDLEWARE_SECONDS = 60 CACHE_MIDDLEWARE_KEY_PREFIX = "akiyokoproject" CACHES = { "default": { "BACKEND": "django.core.cache.backends.memcached.MemcachedCache", "LOCATION": "127.0.0.1:11211", } } SESSION_ENGINE = "django.contrib.sessions.backends.cache" ################ # AWS SETTINGS # ################ #AWS_ACCESS_KEY_ID = "AKIxxxxxxxxxxxxxxxxx" #AWS_SECRET_ACCESS_KEY = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ################## # EMAIL SETTINGS # ################## EMAIL_BACKEND = "django_ses.SESBackend" AWS_SES_REGION_NAME = "us-west-2" AWS_SES_REGION_ENDPOINT = "email.us-west-2.amazonaws.com" #EMAIL_HOST_USER = "admin@akiyoko.com" #EMAIL_USE_TLS = True #EMAIL_HOST = "email-smtp.us-west-2.amazonaws.com" #DEFAULT_FROM_EMAIL = SERVER_EMAIL = u"no-reply <no-reply@akiyoko.com>" ################ # LOG SETTINGS # ################ if not DEBUG: LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'file': { 'level': 'DEBUG', 'class': 'logging.FileHandler', 'filename': '/var/log/akiyokoproject/application.log', }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'DEBUG', 'propagate': True, }, }, }
12.4. pip でインストールしたパッケージリスト
(akiyokoproject)$ pip list beautifulsoup4 (4.4.1) bleach (1.4.3) chardet (2.3.0) Django (1.9.7) django-appconf (1.0.2) django-compressor (2.0) django-contrib-comments (1.7.1) filebrowser-safe (0.4.3) future (0.15.2) grappelli-safe (0.4.2) gunicorn (19.6.0) html5lib (0.9999999) Mezzanine (4.1.0) MySQL-python (1.2.5) oauthlib (1.1.2) Pillow (3.3.0) pip (8.1.2) psycopg2 (2.6.2) python-memcached (1.58) pytz (2016.4) rcssmin (1.0.6) requests (2.10.0) requests-oauthlib (0.6.1) rjsmin (1.0.12) setproctitle (1.1.10) setuptools (24.0.2) six (1.10.0) tzlocal (1.2.2) wheel (0.29.0)
(2016年7月時点のもの)
11.5. Supervisor の設定
以下の 2つの Supervisor 設定ファイルは、Fabric スクリプト実行時に自動生成されたものです。
/etc/supervisor/conf.d/akiyokoproject.conf
[program:gunicorn_akiyokoproject] command=/home/webapp/.virtualenvs/akiyokoproject/bin/gunicorn -c gunicorn.conf.py -p gunicorn.pid config.wsgi:application directory=/home/webapp/mezzanine/akiyokoproject user=webapp autostart=true stdout_logfile = /home/webapp/logs/akiyokoproject_supervisor autorestart=true redirect_stderr=true environment=LANG="en_US.UTF-8",LC_ALL="en_US.UTF-8",LC_LANG="en_US.UTF-8"
/home/webapp/mezzanine/akiyokoproject/gunicorn.conf.py
from __future__ import unicode_literals import multiprocessing bind = "unix:/home/webapp/mezzanine/akiyokoproject/gunicorn.sock" workers = multiprocessing.cpu_count() * 2 + 1 errorlog = "/home/webapp/logs/akiyokoproject_error.log" loglevel = "error" proc_name = "akiyokoproject"
11.6. Nginx の設定
$ ls -al /etc/nginx/ total 80 drwxr-xr-x 6 root root 4096 Jul 8 01:03 . drwxr-xr-x 92 root root 4096 Aug 11 21:59 .. drwxr-xr-x 2 root root 4096 Jul 10 15:41 conf drwxr-xr-x 2 root root 4096 Jun 3 00:16 conf.d (中略) -rw-r--r-- 1 root root 1601 Mar 5 2014 nginx.conf (中略) drwxr-xr-x 2 root root 4096 Jul 8 00:14 sites-available drwxr-xr-x 2 root root 4096 Jul 8 00:15 sites-enabled (後略) $ ls -al /etc/nginx/conf.d/ total 8 drwxr-xr-x 2 root root 4096 Jun 2 15:16 . drwxr-xr-x 6 root root 4096 Jun 18 04:29 .. $ ls -al /etc/nginx/sites-available/ total 12 drwxr-xr-x 2 root root 4096 Jul 8 00:14 . drwxr-xr-x 6 root root 4096 Jul 8 01:03 .. -rw-r--r-- 1 root root 2593 Mar 5 2014 default $ ls -al /etc/nginx/sites-enabled/ total 12 drwxr-xr-x 2 root root 4096 Jun 18 04:30 . drwxr-xr-x 6 root root 4096 Jun 18 04:29 .. -rw-rw-r-- 1 webapp webapp 2226 Jun 18 04:30 akiyokoproject.conf lrwxrwxrwx 1 root root 34 Jun 18 04:29 default -> /etc/nginx/sites-available/default
/etc/nginx/nginx.conf
user www-data; worker_processes 4; pid /run/nginx.pid; events { worker_connections 768; # multi_accept on; } http { ## # Basic Settings ## sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; # server_tokens off; # server_names_hash_bucket_size 64; # server_name_in_redirect off; include /etc/nginx/mime.types; default_type application/octet-stream; ## # Logging Settings ## access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; ## # Gzip Settings ## gzip on; gzip_disable "msie6"; # gzip_vary on; # gzip_proxied any; # gzip_comp_level 6; # gzip_buffers 16 8k; # gzip_http_version 1.1; # gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript; ## # nginx-naxsi config ## # Uncomment it if you installed nginx-naxsi ## #include /etc/nginx/naxsi_core.rules; ## # nginx-passenger config ## # Uncomment it if you installed nginx-passenger ## #passenger_root /usr; #passenger_ruby /usr/bin/ruby; ## # Virtual Host Configs ## include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } (後略)
/etc/nginx/sites-available/default
# You may add here your # server { # ... # } # statements for each of your virtual hosts to this file ## # You should look at the following URL's in order to grasp a solid understanding # of Nginx configuration files in order to fully unleash the power of Nginx. # http://wiki.nginx.org/Pitfalls # http://wiki.nginx.org/QuickStart # http://wiki.nginx.org/Configuration # # Generally, you will want to move this file somewhere, and start with a clean # file but keep this around for reference. Or just disable in sites-enabled. # # Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. ## server { listen 80 default_server; listen [::]:80 default_server ipv6only=on; root /usr/share/nginx/html; index index.html index.htm; # Make site accessible from http://localhost/ server_name localhost; location / { # First attempt to serve request as file, then # as directory, then fall back to displaying a 404. try_files $uri $uri/ =404; # Uncomment to enable naxsi on this location # include /etc/nginx/naxsi.rules } (中略) } (後略)
/etc/nginx/sites-enabled/akiyokoproject.conf
upstream akiyokoproject {
server unix:/home/webapp/mezzanine/akiyokoproject/gunicorn.sock fail_timeout=0;
}
server {
listen 80;
listen 443 ssl;
server_name akiyoko.com;
client_max_body_size 10M;
keepalive_timeout 15;
error_log /home/webapp/logs/akiyokoproject_error_nginx.log info;
ssl_certificate conf/akiyokoproject.crt;
ssl_certificate_key conf/akiyokoproject.key;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA;
ssl_prefer_server_ciphers on;
# Deny illegal Host headers
if ($host !~* ^(akiyoko.com)$) {
return 444;
}
location / {
proxy_redirect off;
proxy_set_header Host $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-Protocol $scheme;
proxy_pass http://akiyokoproject;
}
location /static/ {
root /home/webapp/mezzanine/akiyokoproject;
access_log off;
log_not_found off;
expires 30d;
}
location /robots.txt {
root /home/webapp/mezzanine/akiyokoproject/static;
access_log off;
log_not_found off;
}
location /favicon.ico {
root /home/webapp/mezzanine/akiyokoproject/static/img;
access_log off;
log_not_found off;
}
}
まとめ
今回、Mezzanine の本番デプロイとして、
- 本番サーバ(Ubuntu)を起動
- Fabric スクリプトを実行
- Fabric スクリプト実行後の各種設定
- SSL の設定
- URL の転送設定
- BASIC認証の設定
- MySQL のインストール
- メールの設定
- ログの設定
- タイムゾーンを Asia/Tokyo に変更
- sitemap.xml / robot.txt の設定
を実施した内容を記載しました。
次回は、Mezzanine 本番設定の第四弾として、「その4:Mezzanine の運用設定」について記載します。
参考本
CentOS の本ならたくさんあるのに、Ubuntu の本はあまり無いんですよねぇ。
上級者向けではないですが、こんな感じでしょうか。
絶対つまずかない Linuxサーバー構築ガイド(日経BPパソコンベストムック)
- 作者: 日経Linux
- 出版社/メーカー: 日経BP社
- 発売日: 2016/08/08
- メディア: 単行本
- この商品を含むブログを見る
MySQL 本なら。
- 作者: 遠藤俊裕,坂井恵,館山聖司,鶴長鎮一,とみたまさひろ,班石悦夫,松信嘉範
- 出版社/メーカー: 翔泳社
- 発売日: 2011/08/26
- メディア: 大型本
- 購入: 9人 クリック: 82回
- この商品を含むブログ (9件) を見る
*1:Mezzanine を採用したのは、個人の趣味です。
*2:Fabric スクリプトの実体は、https://github.com/stephenmcd/mezzanine/blob/4.1.0/mezzanine/project_template/fabfile.py です。
*3:参考:PostgreSQLとMySQLはどちらかに明確な優位性がありますか? - QA@IT
*4:Fabric じゃないけど一応
*5:現時点の最新は 4.2.2 ですが、2016年7月当時は 4.1.0 でした。
*6:実際に運用しているドメインは akiyoko.com ではありませんので悪しからず。
*7:「完全な管理者アクセス権限(AdministratorAccess)」ポリシーを付けた EC2インスタンス用 Role です。
*8:ちょっとした裏技(?)ですが、ubuntu ユーザの authorized_keys ファイルをコピーすれば簡単です。
*9:Git 管理下に置きたくない各種設定は、local_settings.py で管理します。
*10:Mezzanine 4.2.0 から修正されたようです。 https://github.com/stephenmcd/mezzanine/commit/898e330a26500d473663f9602165e40d313677d1
*11:「GoGetSSL」からダウンロードした中間証明書に「^M」が入ってたけど、気にしなくて OK!
*12:秘密鍵は GoGetSSL からメールで送られます。
*13:「ec2-prod」ロールを EC2 に付与しているため、「AWS_ACCESS_KEY_ID」および「AWS_SECRET_ACCESS_KEY」が不要になっています。もし IAMロールをインスタンス起動時に付与しない場合は、6つが最低限必要となります。
*14:以下のように Warning がいくつか出ましたが、特に影響なさそうなので無視しました。