akiyoko blog

akiyoko の IT技術系ブログです

IPA「情報セキュリティマネジメント試験」に一夜漬けで合格するためのたった二つの勉強法

f:id:akiyoko:20161117002302j:plain

こんにちは、akiyoko です。


先月の IPA(情報処理推進機構)の秋試験で、「情報セキュリティマネジメント試験」に 一夜漬けで合格 することができました!!


パチパチパチ!


先日合格発表があり、めでたく合格できたことを確認しました。

f:id:akiyoko:20161115005143p:plain


試験対策としては、試験前日に数時間やっただけです。
成績照会の結果を見てみると、午前が90点・午後が88点だったので(合格基準は午前・午後それぞれ 60点)余裕の合格と言ってもいいでしょう。



そもそも、「情報セキュリティマネジメント試験」(略号「SG」)は、2016年(平成28年)の春試験からスタートした新しい国家試験(情報処理技術者試験)です。


高度化する IT、多様化・複雑化するサイバー攻撃や脅威に対応できる情報セキュリティマネジメントを担う人材がますます求められているという時代背景に合わせて創設された、いわば「今必要な人材のための新しい資格」とも言えます。


共通キャリア・スキルフレームワーク(CCSF)レベル 2相当なので、他の試験で言うと「基本情報技術者試験」(FE)と同程度のレベルということになります。

f:id:akiyoko:20161115010733p:plain
(試験区分 「IPA 独立行政法人 情報処理推進機構:試験要綱 Ver3.0」より) *1


難易度としてはぶっちゃけ、IT業界で長く働いていればそんなに難しくない試験だと思いますが、セキュリティ関連の専門用語・専門知識が必要になるので、それらの分野が苦手な人はそれなりに勉強が必要になってきます。

私見としては、受験資格(条件)は特に無く *2、年に二回(4月と10月)実施されるため受験チャンスも多く、文章記述の問題が無く(全てマークシート方式)、試験時間も比較的短く(午前と午後で90分ずつ)、受験料もリーズナブル(5,700円)で、気軽に受験できる国家資格だと思います。

時間区分 午前 午後
試験時間 90分 90分
出題形式 多肢選択式(四肢択一) 多肢選択式
合格基準 60点(100点満点) 60点(100点満点)


こちらの公式ページから、サンプル問題(午前・午後)と平成28年度春期試験の問題、平成28年度春期試験の問題をチェックすることができますので、気になる方は一度チラ見してみてください。




さて、この「情報セキュリティマネジメント試験」の受験を勧める対象者としては、

  • 業務で個人情報を取り扱う全ての方
  • 業務部門・管理部門で情報管理を担当する全ての方
  • 外部委託先に対する情報セキュリティ評価・確認を行う全ての方
  • 情報セキュリティ管理の知識・スキルを身に付けたい全ての方
  • パス(ITパスポート試験)合格から、さらにステップアップしたい全ての方

と書いてありますが(*3)、実際に受験会場でざっと見回した限りでは、40代〜50代の管理職っぽい面々もちらほら見受けられました。予想以上に平均年齢が高い印象です。会社から「セキュリティの資格を取れ」と言われていたけど「情報処理安全確保支援士試験」(旧セキュリティスペシャリスト試験)までは手が出せなかった層にある程度のニーズがあるのかもしれません。





 

勉強法①:午前対策は「過去問よりも参考書」

私はその他の資格試験では一貫して「過去問絶対主義」だったのですが、この試験だけは勝手が違うようです。


何と、他の IPA試験だったらそのままの問題が何問か出るはずの過去問から、一問も出なかったのです。

その代わり、私のチェックした限りで、基本情報技術者試験の午前問題から4問、応用情報技術者試験の午前問題から2問、全く同じ問題が出題されていました。


今回買った参考書はこの本なのですが、振り返ってみると大当たりでした。まるっきり同じ過去問が出題されたその6問が練習問題として含まれていたので、この本一冊と心中してしまってもよいでしょう。

(私が買ったのは「2016年秋期版」でしたが、新しいものに差し替えました)


もちろん、全く同じ問題ではなくても似たような問題(例えば「rootkit」という用語を知っていれば答えられる、等)がいくつか出題されていたので、もちろん過去問もしっかりやっておくべきでしょう。


ということで午前対策は、過去問をひと通り、プラス参考書をざっと通し読みするだけ で大丈夫です。合計で 3〜4時間くらいでしょうか。




なお、参考書を Kindle で買うのか、紙の本で買うのかという議論がありますが、正直なところ、一長一短かと思います。今回は、試験対策本として初めて Kindle 版を買ってみたのですが、私はどちらかと言うと紙の本の方が合ってるかなぁという感じです。


<メリット>

  • 持ち運びに便利
  • ちょっとだけ安い
  • 通勤電車で読んでいても恥ずかしくない(!)
  • 直前に読んでいても「必死に勉強している感」を隠せる(!!)

<デメリット>

  • ページを行ったり来たりするのが面倒(特に、過去問の問題と解答・解説)


 

勉強法②:午後対策は「図を描きながらストーリーを理解」

午後は、6〜8ページほどの長文を読んで多肢選択式の設問に答えるタイプの問題が 3問も待ち構えています。


長文を読むのは結構つらいです。文章が長いので、読み終わる頃には前半部分の登場人物や所属なんかを忘れてしまっていることも。。

そこで、多少時間は掛かってしまいますが、下のような組織構造を表した図を描いて全体像をメモしながら状況を把握するのがよいでしょう。

f:id:akiyoko:20161116021359p:plain

コツとしては、問題が違ってもだいたい似たような構成になるはず(情報システム部があって情報セキュリティ責任者とリーダーがいる、等)なので、パターンに慣れることが重要です。


また、問題文にはお決まりの「起承転結」があるので、ストーリーの流れを図の状況と合わせて理解していきましょう。


例えば、こんな感じで問題文が書かれているはずです。

《 起 》 企業や組織の業務、および情報システム部についての紹介
《 承 》 事前のセキュリティ対策や企業や組織個別の事情など
《 転 》 セキュリティ事故発生!!
《 結 》 セキュリティ責任者による事態の収束とセキュリティ対策の評価・改善


どんなセキュリティ事故が起きても、必ず最後はハッピーエンドで終わります。


午後対策は、「組織図」と「ストーリーの流れ」に注目しながら過去問一回分を解けば十分 でしょう。



 

まとめ

午前対策としては、上で紹介した「情報処理教科書 情報セキュリティマネジメント 2016年秋期」を買って、数時間でざっとひと通り読み込んでおきましょう。頻出するセキュリティ用語・知識を網羅しておくことがキーポイントになりますが、この「情報セキュリティマネジメント試験」では他試験(基本情報技術者試験および応用情報技術者試験)の過去問がそのまま出ることが多いので、分からない問題はいっそのこと丸暗記してしまってもよいでしょう(あくまで一夜漬けとして)。


午後対策としては、「図を描きながらストーリーを理解」することを挙げました。問題文は長いですが、ストーリーの流れや組織図はパターン化されていますので、過去問で少し慣れてしまえば、あとは恐れるに足りません。


これであなたも来春には「情報セキュリティマネジメント試験」合格ですね!!

*1:2017年の春試験から、「情報セキュリティスペシャリスト試験」が「情報処理安全確保支援士試験」となり、登録制になるのがこれまでと大きく異なります。

*2:年齢や国籍等を含め受験する上での制限はありません。また、どの試験区分からでも受験いただけます。(「IPA 独立行政法人 情報処理推進機構:情報処理技術者試験:よくある質問」より)

*3:情報セキュリティマネジメント試験 より

「D3.jsで学ぶデータビジュアライゼーション」に参加してきました

会場

Twitter Japan
東京都中央区京橋3−1−1 東京スクエアガーデン19階


(参考)Twitter Japan に行ってきた! - 941::blog



Twitter

twitter.com


 

全体の感想など

今年の 6/28 に D3.js がバージョン 4系にメジャーバージョンアップしたとのことですが、バージョン 3 系がリリースされたのが 2012年12月だったので、およそ 3年半ぶりのメジャーバージョンアップとなったようです。そういえば、二年ほど前に個人的にいろいろ試していたバージョンも 3.3 or 3.4 でした。だいぶ息の長いバージョンになりましたね。サンプルやドキュメント、プラグインがまだ追い付いていないらしいので、現場ではまだしばらくはバージョン 3系が使われそうです。


<過去記事>
akiyoko.hatenablog.jp


後半の発表については、Python と D3.js を連携させるときのノウハウ(REST API を使うとか)だったり、どういうふうに役割分担させたか(例えば、データ検索と前処理は Python / NumPy とか)だったり、バックエンドに Python を使うことの明確なメリット(データ件数とレスポンスタイムの比較)だったりを期待していたので、少し思惑とは違った内容でした。


もちろん個人的にいくつか得るものがありましたし、参加者も多くて全体的な雰囲気も活気があったように見受けられ、データビジュアライゼーションはまだまだ人気分野だと感じました。

なお、今回は有料イベント(1,000円)でした。




 

「細かすぎて伝わらないD3 ver.4の話」

清水 正行氏

(参考)





 

「PythonとD3.jsで作るデータベースの活用を広げる可視化アプリケーション開発」

オーイシ ナオヤ氏


  • 生命科学関連データベースの利用促進のための Webサービス開発
  • AOE: http://aoe.dbcls.jp/
    • 遺伝子発現データベースの可視化サービス
  • 使っているもの
  • Tableau, Spotfire じゃダメなの?? 何で Webサービス?
  • SPARQL Query Filter
  • データが大きい場合や、独自のアルゴリズムで統計処理したものを可視化したい場合は、Python で事前に処理して D3.js に渡した方が軽快かも


 

参考本

サンプルが多くて初心者向け。

インタラクティブ・データビジュアライゼーション ―D3.jsによるデータの可視化

インタラクティブ・データビジュアライゼーション ―D3.jsによるデータの可視化

現場ですぐ使える時系列データ分析 ~データサイエンティストのための基礎知識~

現場ですぐ使える時系列データ分析 ~データサイエンティストのための基礎知識~

本番運用しているブログサイトの Mezzanine を 4.1.0 から 4.2.2 にアップデートしてみた

Monotalk さんの以下の記事に触発されて、私が本番運用している某ブログサイトの Mezzanine を 4.1.0 から 4.2.2 にアップデートしてみました。


アップデートするのに結構苦労したように書いてあったので、念のため、Vagrant の開発環境と EC2 のバックアップイメージから起動したインスタンス上でのリハーサルをしてから臨みました。


結論から言うと、あまり苦労せずにアップデートできました。Monotalk さんは Mezzanine 3 系から運用していたような感じだったので、そこからの負債が溜まっていたのではないかと推測します。


 

アップデート手順

アップデートの手順はたったこれだけです。

$ pip install -U Mezzanine==4.2.2

$ python manage.py makemigrations
$ python manage.py migrate



アップデート前の本番環境のインストール済みライブラリ一覧(2016/11/2 時点)

$ pip list
awscli (1.11.10)
beautifulsoup4 (4.5.1)
bleach (1.4.3)
boto (2.41.0)
botocore (1.4.67)
chardet (2.3.0)
colorama (0.3.7)
Django (1.9.9)
django-appconf (1.0.2)
django-compressor (2.0)
django-contrib-comments (1.7.2)
django-ses (0.8.0)
docutils (0.12)
filebrowser-safe (0.4.5)
future (0.15.2)
futures (3.0.5)
grappelli-safe (0.4.4)
gunicorn (19.6.0)
html5lib (0.9999999)
jmespath (0.9.0)
Mezzanine (4.1.0)
MySQL-python (1.2.5)
oauthlib (1.1.2)
Pillow (3.3.1)
pip (8.1.2)
psycopg2 (2.6.2)
pyasn1 (0.1.9)
python-dateutil (2.5.3)
python-memcached (1.58)
pytz (2016.6.1)
rcssmin (1.0.6)
requests (2.11.1)
requests-oauthlib (0.6.2)
rjsmin (1.0.12)
rsa (3.4.2)
s3transfer (0.1.9)
setproctitle (1.1.10)
setuptools (24.0.2)
six (1.10.0)
tzlocal (1.2.2)
wheel (0.29.0)


アップデート後のインストール済みライブラリの一覧は、以下のようになりました。

$ pip list
awscli (1.11.10)
beautifulsoup4 (4.5.1)
bleach (1.4.3)
boto (2.41.0)
botocore (1.4.67)
chardet (2.3.0)
colorama (0.3.7)
Django (1.10.3)
django-appconf (1.0.2)
django-compressor (2.0)
django-contrib-comments (1.7.3)
django-ses (0.8.0)
docutils (0.12)
filebrowser-safe (0.4.6)
future (0.16.0)
futures (3.0.5)
grappelli-safe (0.4.5)
gunicorn (19.6.0)
html5lib (0.9999999)
jmespath (0.9.0)
Mezzanine (4.2.2)
MySQL-python (1.2.5)
oauthlib (2.0.0)
Pillow (3.4.2)
pip (8.1.2)
psycopg2 (2.6.2)
pyasn1 (0.1.9)
python-dateutil (2.5.3)
python-memcached (1.58)
pytz (2016.7)
rcssmin (1.0.6)
requests (2.11.1)
requests-oauthlib (0.7.0)
rjsmin (1.0.12)
rsa (3.4.2)
s3transfer (0.1.9)
setproctitle (1.1.10)
setuptools (24.0.2)
six (1.10.0)
tzlocal (1.3)
wheel (0.29.0)


Mezzanine 4.1.0 から 4.2.2 への一番の変更点は、Django のバージョンが 1.9 系から 1.10 系にアップデートされることでしょうか。

4.1.0 から 4.2.2 への全ての変更箇所は以下で確認することができます。
https://github.com/stephenmcd/mezzanine/compare/4.1.0...4.2.2


このアップデートでいくつかのバグ(*1)が本家で修正されたのに伴い、技術的な負債を少し解消することができました。



 

影響

4.2.2 へのアップデート後、ping_google コマンド実行時にエラーが出るようになってしまいました。

$ python manage.py ping_google
Traceback (most recent call last):
  File "manage.py", line 14, in <module>
    execute_from_command_line(sys.argv)
  File "/home/vagrant/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 367, in execute_from_command_line
    utility.execute()
  File "/home/vagrant/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 359, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/vagrant/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/django/core/management/base.py", line 294, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/vagrant/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/django/core/management/base.py", line 345, in execute
    output = self.handle(*args, **options)
  File "/home/vagrant/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/django/contrib/sitemaps/management/commands/ping_google.py", line 12, in handle
    ping_google(sitemap_url=options['sitemap_url'])
  File "/home/vagrant/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/django/contrib/sitemaps/__init__.py", line 36, in ping_google
    raise SitemapNotFound("You didn't provide a sitemap_url, and the sitemap URL couldn't be auto-detected.")
django.contrib.sitemaps.SitemapNotFound: You didn't provide a sitemap_url, and the sitemap URL couldn't be auto-detected.

いろいろ調査したのですが、まだ解決していません。。

Django 本体の差分にも原因があるのかなぁ??
https://fossies.org/diffs/Django/1.9.8_vs_1.10/django/contrib/sitemaps/__init__.py-diff.html



 

バックアップイメージでの検証

EC2 のバックアップイメージから起動したインスタンス上でのリハーサル手順をメモしておきます。

1. バックアップイメージからインスタンス起動

まずは、EC2 インスタンスの Create Image をおこない、AMI を作成します。

作成したイメージからインスタンスを起動し、新たな Elastic IP を付与します。


 

2. settings.py の修正

config/local_settings.py

ALLOWED_HOSTS = ['akiyoko.com']

を、

ALLOWED_HOSTS = ['52.199.xx.xx']

と修正します(新しい IP アドレスを「52.199.xx.xx」と想定)。


最後に、プロセスを再起動。

$ sudo supervisorctl restart all


なおこれを修正しないと、アクセス時に Bad Request (400) が発生してしまいます。

(参考)django - Bad request 400: nginx / gunicorn - Stack Overflow

 

3. Nginx の設定ファイル修正

server_name に指定しているドメインを、Elastic IP に変更します。
また、テスト検証では HTTPS は使用しないので、SSL の設定は取り除きます。


/etc/nginx/sites-enabled/akiyokoproject.conf

(変更前)

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

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_name 52.199.xx.xx;
    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 !~* ^(52.199.xx.xx)$) {
        return 444;
    }
    ・
    ・

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


最後に Nginx をリロード。

$ sudo service nginx reload


 

4. Mezzanine アップデート

$ workon akiyokoproject
(akiyokoproject)$ pip install -U Mezzanine==4.2.2
  ・
  ・
Successfully installed Mezzanine-4.2.2 django-1.10.3 django-contrib-comments-1.7.3 filebrowser-safe-0.4.6 future-0.16.0 grappelli-safe-0.4.5 oauthlib-2.0.0 pillow-3.4.2 pytz-2016.7 requests-oauthlib-0.7.0 tzlocal-1.3
(akiyokoproject)$ python manage.py makemigrations
(akiyokoproject)$ python manage.py migrate

Mezzanine 系のモデル変更はありませんでしたが、Django 系の auth_user のモデルが少し変更になったようです。

$ sudo supervisorctl restart all


 

5. テンプレートの更新

HTMLファイル確認用の templats ディレクトリに、テンプレートの更新差分(4.1.0 → 4.2.2)をマージします。

config/settings.py

@@ -273,7 +275,7 @@ TEMPLATES = [
     {
         "BACKEND": "django.template.backends.django.DjangoTemplates",
         "DIRS": [
-            os.path.join(PROJECT_ROOT, "custom/templates"),
+            #os.path.join(PROJECT_ROOT, "custom/templates"),
             os.path.join(PROJECT_ROOT, "templates")
         ],
         "APP_DIRS": True,

上記のように修正して、一時的にカスタムテンプレートを外してから、

$ python manage.py collecttemplates

を実行して、テンプレートを上書きします。

なお、上書きするときにいちいちオーバーライドするかどうか聞かれるので、
~/.virtualenvs/akiyokoproject/lib/python2.7/site-packages/mezzanine/core/management/commands/collecttemplates.py

               self.stdout.write("Template exists%s.\n" % prev)
                #confirm = input("Overwrite?  (yes/no/abort): ")
                #while confirm not in ("yes", "no", "abort"):
                #    confirm = input(
                #        "Please enter either 'yes', 'no' or 'abort': ")
                #if confirm == "abort":
                #    self.stdout.write("Aborted\n")
                #    break  # exit templates copying loop
                #elif confirm == "no":
                #    self.stdout.write("[Skipped]\n")
                #    copy = False
            if copy:
                try:
                    os.makedirs(os.path.dirname(dest))
                except OSError:
                    pass
                shutil.copy2(path, dest)
                template_src[name] = app
                count += 1
        if verbosity >= 1:
            s = "s" if count != 1 else ""
            self.stdout.write("\nCopied %s template%s\n" % (count, s))

とコメントアウトすると、手間が省けます。


custom アプリケーションで独自に修正したテンプレートの一覧は以下の通りなので、

custom/__init__.py
custom/static/css/custom.css
custom/static/img/slide-1.jpg
custom/static/img/slide-2.jpg
custom/templates/base.html
custom/templates/blog/blog_post_detail.html
custom/templates/blog/blog_post_list.html
custom/templates/generic/includes/comment.html
custom/templates/generic/includes/comments.html
custom/templates/includes/form_fields.html
custom/templates/index.html
custom/templates/pages/menus/dropdown.html
custom/templates/twitter/tweets.html
custom/templatetags/__init__.py
custom/templatetags/add_attributes.py

本家テンプレートの更新差分(4.1.0 → 4.2.2)と重複したファイル

  • blog/blog_post_detail.html

については手動でマージをおこない、その他の

  • includes/editable_loader.html
  • pages/form.html
  • pages/menus/admin.html

については custom アプリケーションの templates ディレクトリ以下のものに上書きしました。

Mezzanine の本番設定(その4:Mezzanine の運用設定)〜AWS 環境構築から運用設定まで〜

こんにちは、akiyoko です。

Mezzanine は、まだまだ日本での認知度はイマイチですが、知る人ぞ知る Python製の WordPress風フルスタックCMSフレームワークです。

akiyoko.hatenablog.jp


今年の 7月に、Mezzanine を使った某ブログサイト(将来的に ECサイトを増設予定)の本番運用を開始しました。*1 その備忘録として、AWS の初期設定から Mezzanine テーマのカスタマイズ、Mezzanine の本番デプロイ、ちょっとした運用設定までの記録をまとめておくことにしました。

全ての記録を一つの記事にすると長くなり過ぎるので、テーマごとに、

の 4本に記事を分割することにしました。

今回はラスト 4本目、「その4:Mezzanine の運用設定」について説明します。


Mezzanine の運用設定として実施した内容としては、

  • Mezzanine 本体のカスタム設定
  • Mezzanine サイトのページ設定
  • バックアップの設定
  • sitemap の定期更新
  • Google Search Console の設定
  • CloudWatch の監視設定

となります。



【目次】



 

1. Mezzanine 本体のカスタム設定

config/settings.py の設定を変更して、Mezzanine 本体のカスタム設定をおこないます。

本来この設定変更は本番デプロイ時(fab all コマンド実行時)かその直後におこなうべきですが、運用に関わる設定なのでここに記しておきます。

1.1. ブログ記事のフィーチャー・イメージ設定

ブログ記事にフィーチャー・イメージを付けるかどうかの設定です。
なお、画像は必須ではありません。

--- a/config/settings.py
+++ b/config/settings.py
@@ -79,7 +79,7 @@ from django.utils.translation import ugettext_lazy as _

 # Setting to turn on featured images for blog posts. Defaults to False.
 #
-# BLOG_USE_FEATURED_IMAGE = True
+BLOG_USE_FEATURED_IMAGE = True

 # If True, the django-modeltranslation will be added to the
 # INSTALLED_APPS setting.

 

1.2. レーティング設定

レーティング機能をオフにします。

--- a/config/settings.py
+++ b/config/settings.py
@@ -85,6 +85,9 @@ BLOG_USE_FEATURED_IMAGE = True
 # INSTALLED_APPS setting.
 USE_MODELTRANSLATION = False

+# Comment settings
+COMMENTS_USE_RATINGS = False
+

 ########################
 # MAIN DJANGO SETTINGS #


 

2. Mezzanine サイトのページ設定

Django Admin(/admin/)にログインして、Mezzanine サイトの各種設定を行います。

 

2.1. Settings

[Site]>[Settings]から Mezzanine サイト全体の設定を行います。

項目 内容 設定例
Comments > Accounts required for commenting コメントするのにアカウントが必要か チェックしない
Comments > Auto-approve comments コメントを自動承認するか チェック
Comments > Show removed comments 削除済みのコメントを表示するか チェックしない
Comments > Show unapproved comments 未承認のコメントを表示するか チェック
Twitter > Twitter OAuth access token Twitter 連携で必要 Twitter 側の設定 ① を参照
Twitter > Twitter OAuth access token secret 同上 Twitter 側の設定 ② を参照
Twitter > Twitter OAuth consumer key 同上 Twitter 側の設定 ③ を参照
Twitter > Twitter OAuth consumer secret 同上 Twitter 側の設定 ④ を参照
Twitter > Default Number of Tweets Tweet の表示数 5
Twitter > Default Twitter Query Twitter 検索のキー aki_yok (*2
Twitter > Default Twitter Query Type Twitter 検索キーの種類 User
Miscellaneous > Akismet API Key Akismet 連携で必要 Akismet 側の設定を参照
Miscellaneous > Blog posts per page ブログ一覧の表示数 6
Miscellaneous > Google Analytics ID Google Analytics 連携で必要 Google Analytics 側の設定を参照
Miscellaneous > Max paging links ブログ一覧のページ表示数 10
Miscellaneous > Accounts required for rating レーティングするのにアカウントが必要か Yes
Miscellaneous > Rich Text filter level Rich Text ページのフィルタリングレベル No filtering (*3

f:id:akiyoko:20161029182952p:plain



Twitter 側の設定

f:id:akiyoko:20161030115540p:plain


(参考)Twitter REST APIの使い方


Akismet 側の設定

f:id:akiyoko:20161030115703p:plain


Google Analytics 側の設定

f:id:akiyoko:20161030122212p:plain


 

2.2. Groups

ブログ投稿しかできない「akiyoko」ユーザを作成するため、まず、ブログ投稿関連のパーミッションのみを許可した「Blog user」という権限グループを作成します。


以下のパーミッションを選択します。

  • blog | Blog Category | Can add Blog Category
  • blog | Blog Category | Can change Blog Category
  • blog | Blog Category | Can delete Blog Category
  • blog | Blog post | Can add Blog post
  • blog | Blog post | Can change Blog post
  • blog | Blog post | Can delete Blog post
  • galleries | Gallery | Can add Gallery
  • galleries | Gallery | Can change Gallery
  • galleries | Gallery | Can delete Gallery
  • galleries | Image | Can add Image
  • galleries | Image | Can change Image
  • galleries | Image | Can delete Image

f:id:akiyoko:20161030124441p:plain


 

2.3. Users

「akiyoko」ユーザを作成して、「Blog user」権限グループを付与します。

f:id:akiyoko:20161030124507p:plain


ここで、新しく追加したユーザで Django Admin サイトにログインしようとすると「You don't have permission to access the admin for this site.」というエラーが出てログインできない問題が発生(2016年7月時点)。 *4

関連するデータを見てみると、

mysql> select * from core_sitepermission;
+----+---------+
| id | user_id |
+----+---------+
|  1 |       2 |
+----+---------+
1 row in set (0.00 sec)

mysql> select * from core_sitepermission_sites;
Empty set (0.00 sec)

となっていたのですが、本事象が発生しなかった開発環境(Vagrant)では、

mysql> select * from core_sitepermission;
+----+---------+
| id | user_id |
+----+---------+
|  1 |       2 |
+----+---------+
1 row in set (0.00 sec)

mysql> select * from core_sitepermission_sites;
+----+-------------------+---------+
| id | sitepermission_id | site_id |
+----+-------------------+---------+
|  1 |                 1 |       1 |
+----+-------------------+---------+
1 row in set (0.00 sec)

となっていて、結局原因はよく分からなかったのですが、

mysql> insert core_sitepermission_sites(id,sitepermission_id,site_id) values(1,1,1);

と実行して core_sitepermission_sites レコードを作成してやると、事象は発生しなくなりました。


 

2.4. Contact

お問い合わせページの作成方法。こんな感じのページです。

f:id:akiyoko:20161030130048p:plain

[Content]>[Pages]から[Add Form]を選択して、Form ページを作成。

項目 内容
Title Contact
Content Please fill out the form below and submit for your inquiry.
Button text Submit
Email > Send email to user チェックしない
Email > From address no-reply@akiyoko.com
Email > Send email to others xxx@gmail.com, xxx@gmail.com
Email > Subject [akiyoko.com] New contact
Email > Message 問い合わせフォームからコンタクトがありました。対応をお願いします。
Fields > Name Single line text
Fields > Email Email
Fields > Message Multi line text


f:id:akiyoko:20161029183251p:plain


実際にフォームへの問い合わせがあれば、[Email > Send email to others]に設定したメールアドレス宛に、以下のような内容のメールが届きます。

問い合わせフォームからコンタクトがありました。対応をお願いします。

Name:
xxx

Email:
xxx@xxx.com

Message:
メッセージの内容


http://akiyoko.com


 

3. バックアップの設定

Mezzanine サイトでアップロードした画像ファイルは、

/home/webapp/mezzanine/akiyokoproject/static/media/
└── uploads
    └── blog
        └── test.png

というパスに配置されるので、以下のように media ディレクトリ以下を圧縮してバックアップします。

$ tar czvf <出力ファイル名> <対象ファイル or 対象ディレクトリ>

$ tar czvf /tmp/media_backup_`date +%y%m%d%H%M`.tar.gz /home/webapp/mezzanine/akiyokoproject/static/media

圧縮したファイル名は「media_backup_1610301815.tar.gz」などとなります。


また、MySQL のバックアップは以下のようにして実行します。

$ mysqldump --single-transaction -u root -p akiyokoproject > /tmp/mysql_backup_`date +%y%m%d%H%M`.dump

こちらは今のところ圧縮しない方針です(ファイルがもっと大きくなれば圧縮した方がよさそうですが)。


以下、バックアップファイルを cron で毎日深夜に S3 にアップロードするまでの手順です。

 

3.1. S3 バケットの準備

バックアップ用バケット(バケット名は「akiyokoproject」とします)を作成し、有効期限ルールを設定します。


バケットの Properties の[Lifecycle]から、[Add rule]を選択。
f:id:akiyoko:20161030201246p:plain

バケット内の「media/」ディレクトリ以下のオブジェクトの有効期限を10日間(有効期限を過ぎたものは削除)に設定します。
f:id:akiyoko:20161030201305p:plain

f:id:akiyoko:20161030201323p:plain

f:id:akiyoko:20161030201340p:plain

「db/」ディレクトリ以下のオブジェクトの有効期限についても同様に設定します。


(参考)Amazon S3でオブジェクトの有効期限を設定できるようになりました | Developers.IO


 

3.2. AWS CLI のインストール

AWS CLI を pip でインストールします。

(akiyokoproject)$ pip install awscli

(akiyokoproject)$ pip list | grep awscli
awscli (1.11.10)


 

3.3. 実行シェルの作成

media ディレクトリ以下のファイルと MySQL のバックアップを行う、シェルを作成します。

/home/webapp/mezzanine/akiyokoproject/scripts/backup.sh

#!/bin/sh

MEDIA_BACKUP_FILE=media_backup_`date +%y%m%d%H%M`.tar.gz
DB_BACKUP_FILE=mysql_backup_`date +%y%m%d%H%M`.dump
DB_PASS=TODO(FIX ME)

# Create backup file
tar czvf /tmp/$MEDIA_BACKUP_FILE /home/webapp/mezzanine/akiyokoproject/static/media
mysqldump --single-transaction -u root -p$DB_PASS akiyokoproject > /tmp/$DB_BACKUP_FILE

# Copy file to S3
. /home/webapp/.virtualenvs/akiyokoproject/bin/activate
aws s3 cp /tmp/$MEDIA_BACKUP_FILE s3://akiyokoproject/media/
aws s3 cp /tmp/$DB_BACKUP_FILE s3://akiyokoproject/db/

# Delete old files
find /tmp -name 'media_backup_*.tar.gz' -mtime +10 -exec rm {} \;
find /tmp -name 'mysql_backup_*.dump' -mtime +10 -exec rm {} \;

(mysqldump のパスワード「TODO(FIX ME)」の部分は、適宜設定してください。)

$ sudo chmod 755 /home/webapp/mezzanine/akiyokoproject/scripts/backup.sh


(参考)

 

(2017/3/6 追記)

f:id:akiyoko:20170307012941p:plain:w450
「/tmp」以下のゴミファイルが溜まり過ぎて、「/dev/xvda1」がディスクフルになってしまうという不具合が発生してしまったので、

# Delete old files
find /tmp -name 'media_backup_*.tar.gz' -mtime +10 -exec rm {} \;
find /tmp -name 'mysql_backup_*.dump' -mtime +10 -exec rm {} \;

10日前より古いファイルを削除する処理を加えました。

Traceback (most recent call last):
  File "/usr/lib/python2.7/runpy.py", line 162, in _run_module_as_main
    "__main__", fname, loader, pkg_name)
  File "/usr/lib/python2.7/runpy.py", line 72, in _run_code
    exec code in run_globals
  File "/usr/local/lib/python2.7/dist-packages/virtualenvwrapper/hook_loader.py", line 223, in <module>
    main()
  File "/usr/local/lib/python2.7/dist-packages/virtualenvwrapper/hook_loader.py", line 145, in main
    output.close()
IOError: [Errno 28] No space left on device
-bash: cannot create temp file for here-document: No space left on device

$ df
Filesystem     1K-blocks    Used Available Use% Mounted on
udev              502984      12    502972   1% /dev
tmpfs             101636     352    101284   1% /run
/dev/xvda1       8115168 7679972         0 100% /
none                   4       0         4   0% /sys/fs/cgroup
none                5120       0      5120   0% /run/lock
none              508160       0    508160   0% /run/shm
none              102400       0    102400   0% /run/user


なお、CloudWatch のデフォルトではディスク使用量の監視が利用できないようで、ディスク使用量の監視をするには、

あたりの対応が必要そうです。面倒なので一旦保留ということで。。



 

3.4. cron ファイルの編集

デプロイ時に Fabric スクリプトを実行すると、「/etc/cron.d/」配下に「akiyokoproject」という cron ファイルが用意されるので、このファイルに定期バックアップルールを追記します。

$ ls -al /etc/cron.d/
total 16
drwxr-xr-x  2 root root   4096 Oct 30 19:52 .
drwxr-xr-x 92 root root   4096 Aug 11 21:59 ..
-rw-------  1 root webapp  306 Oct 30 19:52 akiyokoproject
-rw-r--r--  1 root root    102 Feb  9  2013 .placeholder

/etc/cron.d/akiyokoproject

# Poll Twitter every 5 minutes
# Comment-out if you don't use Mezzanine's Twitter app
*/5 * * * * webapp /home/webapp/.virtualenvs/akiyokoproject/bin/python /home/webapp/mezzanine/akiyokoproject/manage.py poll_twitter

(poll_twitter を5分ごとに実行する設定が既に書き込まれています。)



毎日深夜 0時5分に定期バックアップのためのシェルが実行されるような設定を、末尾に追加します。

# Daily backup
5 0 * * * webapp /home/webapp/mezzanine/akiyokoproject/scripts/backup.sh


(参考)cron設定をもっとわかりやすく説明! - こまけぇこたぁいんだよ


 

3.5. cron 実行ログの出力先を変更

Ubuntu はデフォルトで、cron の実行ログが「/var/log/syslog」に出力されるようになっているのですが、「/var/log/cron.log」に出力されるように rsyslog の設定を変更しておきます。


/etc/rsyslog.d/50-default.conf

  ・
  ・
(略)

# First some standard log files.  Log by facility.
#
auth,authpriv.*                 /var/log/auth.log
*.*;auth,authpriv.none          -/var/log/syslog
#cron.*                         /var/log/cron.log
#daemon.*                       -/var/log/daemon.log
kern.*                          -/var/log/kern.log
#lpr.*                          -/var/log/lpr.log
mail.*                          -/var/log/mail.log
#user.*                         -/var/log/user.log

(略)
  ・
  ・

となっているのを、「cron.*」のコメントを外して、

cron.*                         /var/log/cron.log

と修正します。

最後に、

$ sudo service rsyslog restart

を実行して、rsyslog を再起動します。
これで、/var/log/cron.log にログが出力されるようになりました。


(参考)ubuntu14.04 cronログ出力設定 - Qiita



 

4. sitemap の定期更新

Mezzanine には、Google Search Console の設定に必要な「sitemap.xml」を Google に送信するためのコマンドが用意されています。 *5

(akiyokoproject)$ python manage.py ping_google
System check identified some issues:

WARNINGS:
?: (1_8.W001) The standalone TEMPLATE_* settings were deprecated in Django 1.8 and the TEMPLATES dictionary takes precedence. You must put the values of the following settings into your default TEMPLATES dict: TEMPLATE_DIRS.

WARNING が出ているのは無視するとして、何も反応が無いのが気になります。。


先述の cronファイルの末尾に、以下のルールを追加します。

/etc/cron.d/akiyokoproject

# Hourly ping google
0 * * * * webapp /home/webapp/.virtualenvs/akiyokoproject/bin/python /home/webapp/mezzanine/akiyokoproject/manage.py ping_google


 

5. Google Search Console の設定

ここでは細かく書きませんが、Google Search Console の各種設定を行い、SEO 対策をしておきます。


(参考)


 

6. CloudWatch の監視設定

CloudWatch で以下の監視を実施し、異常を検知した場合に管理者用のメールアドレスに通知をするように設定をおこないます。

  • EC2 インスタンスの CPU使用率監視
  • EC2 インスタンスの死活監視

なお、CloudWatch の監視については、無料枠内で最低限の監視ができればよいという方針で運用しています。


(参考)


 

6.1. 監視通知用の SNS 作成

SNS の通知先に、監視用のメールアドレスを指定します。


まず、[Topics]から[Create new topic]を選択してトピックを作成します。

f:id:akiyoko:20161102004922p:plain

トピック名は「akiyokoproject-notification」としました。 *6

f:id:akiyoko:20161102004949p:plain

作成されたトピックの ARN をクリックして、[Create subscription]をクリックします(あるいは[Subscription]から[Create subscription]をクリックしてから ARN を直接指定します)。

f:id:akiyoko:20161102005016p:plain
f:id:akiyoko:20161102005041p:plain

Protocol Email
Endpoint 管理者のメールアドレス

を設定して、[Create subscription]をクリックします。

f:id:akiyoko:20161102005103p:plain

確認メールが届くので、メール本文の「Confirm subscription」をクリックします。

f:id:akiyoko:20161102005128p:plain

f:id:akiyoko:20161102005141p:plain

「Subscription ID」が「PendingConfirmation」から ARN に変われば、設定完了となります。

f:id:akiyoko:20161102005156p:plain


 

6.2. メトリクスを設定

[Metrics]から[EC2]をクリックします。
f:id:akiyoko:20161102011342p:plain
[Per-Instance Metrics]をクリック。
f:id:akiyoko:20161102011417p:plain


「CPUUtilization」(CPU使用率)および「StatusCheckFailed」(インスタンスステータス異常あるいはシステムステータス異常)を選択します。
f:id:akiyoko:20161102011447p:plain

(参考)【小ネタ】CloudWatchの「StatusCheckFailed_Instance」と「StatusCheckFailed_System」について | Developers.IO



続けて、作成したメトリクスをダッシュボードに保存します。


[Actions]から、[Add to dashboard]を選択します。
f:id:akiyoko:20161102082216p:plain

ダッシュボード名を入力して、[Add to dashboard]ボタンをクリック。
f:id:akiyoko:20161102082310p:plain

[Save dashboard]ボタンをクリックして完了です。
f:id:akiyoko:20161102082349p:plain




 

6.3. アラームを設定

  • EC2 インスタンスの平均 CPU 使用率が 70% を超える状態が 10分以上継続
  • EC2 インスタンスのインスタンスステータス異常あるいはシステムステータス異常が 10分以上継続

を閾値として、アラームを作成します。 *7


まずは、「CPUUtilization」(CPU使用率)のアラームから。


「Graphed metrics」タブから、「CPUUtilization」の右側のベルアイコンをクリックします。
f:id:akiyoko:20161102011732p:plain

以下のようにアラームの設定をします。
f:id:akiyoko:20161102013453p:plain


(参考)E メールを送信する CPU 使用率アラームの作成 - Amazon CloudWatch



引き続き、「StatusCheckFailed」(インスタンスステータス異常あるいはシステムステータス異常)の右側のベルアイコンをクリックして、アラームを以下のように設定します。
f:id:akiyoko:20161102012644p:plain


 

TODO

その他の運用設定として、

  • logrotate の設定が不完全(application.log とか)

について、今後対応していく予定です。



 

まとめ

今回は、「Mezzanine の本番設定」と銘打った連続記事の最後を締めくくる「Mezzanine の運用設定」として、

  • Mezzanine 本体のカスタム設定
  • Mezzanine サイトのページ設定
  • バックアップの設定
  • sitemap の定期更新
  • Google Search Console の設定
  • CloudWatch の監視設定

を実施した内容を記載しました。


Mezzanine の本番サイトを運用し始めて 3ヶ月ほど経ちますが、やりたいこと、やらなければいけないことがまだまだ盛り沢山です。

今後も定期的に、Mezzanine の記事をアップしていこうと思います。



 

参考本

運用についてはまだまだ勉強不足です。。

Amazon Web Services 定番業務システム12パターン 設計ガイド

Amazon Web Services 定番業務システム12パターン 設計ガイド

Amazon Web Services パターン別構築・運用ガイド  一番大切な知識と技術が身につく

Amazon Web Services パターン別構築・運用ガイド  一番大切な知識と技術が身につく

  • 作者: NRIネットコム株式会社,佐々木拓郎,林晋一郎,小西秀和,佐藤瞬
  • 出版社/メーカー: SBクリエイティブ
  • 発売日: 2015/03/25
  • メディア: Kindle版
  • この商品を含むブログを見る

Amazon Web Servicesクラウドサーバ構築ガイド コストを削減する導入・実装・運用ノウハウ

Amazon Web Servicesクラウドサーバ構築ガイド コストを削減する導入・実装・運用ノウハウ

*1:Mezzanine を採用したのは、個人の趣味です。

*2:「@」は付けない

*3:「No filtering」に設定すると、Rich Text ページ内のタグが無効化されなくなる

*4:(関連)https://groups.google.com/forum/#!topic/mezzanine-users/RSoPDeDrnaw

*5:正確には Django の機能です。(参考)django/ping_google.py at 1.9.7 · django/django · GitHub

*6:管理者のメールアドレスに通知する系の SNS は、「akiyokoproject-notification」を使い回す予定です。

*7:10分以上継続というのは、5分間の監視インターバルを 2回連続で異常が検知された場合を指す

Mezzanine の本番設定(その3:Mezzanine の本番デプロイ)〜AWS 環境構築から運用設定まで〜

こんにちは、akiyoko です。

Mezzanine は、知る人ぞ知る Python製の WordPress風フルスタックCMSフレームワークです。

akiyoko.hatenablog.jp


今年の 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. 概要

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 ベースのシステム上のあらゆるシステムレベルの依存ライブラリをセットアップしたり、初回デプロイ・継続的デプロイのためのプロジェクトレベルの仮想環境を管理したり、他にもたくさんのことをしたりするために利用できる組み立て可能なコマンド群が含まれています。


Deployment — Mezzanine 4.2.3 documentation


 
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)


Deployment — Mezzanine 4.2.3 documentation


データベースとして PostgreSQL がインストールされますが、PostgreSQL と MySQL はどっちがいいの?という議論(*3)は置いておいて、私の場合は単純に「操作や運用に慣れている」という理由から、今回はデータベースを PostgreSQL から MySQL に入れ替えることにしました(実際には、Fabric スクリプトを実行して PostgreSQL を一旦インストールした後に手動で削除し、Ansible で MySQL をインストールしました)。


(参考)


 
最終的なサーバとコンポーネント、プロジェクトの構成は以下のようになりました。

サーバ:

  • 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

(参考)



最後に、秘密鍵を書き換えて(*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


(参考)Nginx で Basic 認証 - Qiita



 

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 のインストールは不要です。


(参考)



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

の記事が、一番参考になりました。

その他、

も参考に。




 

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

(参考)


 

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パソコンベストムック)


MySQL 本なら。

MySQL徹底入門 第3版 ~5.5新機能対応~

MySQL徹底入門 第3版 ~5.5新機能対応~

  • 作者: 遠藤俊裕,坂井恵,館山聖司,鶴長鎮一,とみたまさひろ,班石悦夫,松信嘉範
  • 出版社/メーカー: 翔泳社
  • 発売日: 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 がいくつか出ましたが、特に影響なさそうなので無視しました。

Mezzanine の本番設定(その2:Mezzanine テーマのカスタマイズ)〜AWS 環境構築から運用設定まで〜

こんにちは、akiyoko です。

Mezzanine は Python製の WordPress風フルスタックCMSフレームワークです。

akiyoko.hatenablog.jp


今年の 7月に、Mezzanine を使った某ブログサイト(将来的に ECサイトを増設予定)の本番運用を開始しました。*1 その備忘録として、AWS の初期設定から Mezzanine の本番デプロイ、ちょっとした運用設定までの記録をまとめておくことにしました。

全ての記録を一つの記事にすると長くなり過ぎるので、テーマごとに、

の 4本に記事を分割することにしました。

今回はその 2本目、「その2:Mezzanine テーマのカスタマイズ」について説明します。


Mezzanine テーマのカスタマイズとして実施した内容としては、

  • Bootstrap テンプレートの選定
  • Bootstrap テンプレートの設定
  • 各種テンプレートファイルの修正

となります。



 

1. Bootstrap テンプレートの選定

Mezzanine は、テンプレートのCSS が Twitter Bootstrap の Version 3 に対応しています。

Twitter Bootstrap integration


Overview — Mezzanine 4.2.3 documentation


そこで、Mezzanine テーマをカスタマイズするための第一歩として、まずは Bootstrap テンプレートの選定を行いました。


結論から書くと、いろいろ探した中で、「WrapBootstrap」というサイトから「The Project - Multipurpose Template」という有料テンプレートを購入することにしました。価格は、1サイト分の利用ライセンスで $16 でした。


The Project - Multipurpose Template
wrapbootstrap.com


f:id:akiyoko:20161018081943p:plain

デモページもあります。
htmlcoder.me




他にも、

f:id:akiyoko:20161018082028p:plain

f:id:akiyoko:20161018082054p:plain

f:id:akiyoko:20161018082121p:plain

f:id:akiyoko:20161018082153p:plain
などを候補としてチェックしていました。


最終的にこの「The Project」テンプレートを採用することにしたのは、

  • 有料テンプレート($4 〜 $49)の中でも比較的安め($16)
  • 人気がある(購入数ランキングでトップ10)
  • テンプレートが多い(コーポレートやポートフォリオ、ブログ、ECサイトなど 30種類近くのテーマで 200ページ以上)
  • ちょくちょく更新されている

といった理由からです。

特に、必須要件である「ブログ」と「ECサイト」以外にも数多くのテンプレートファイルが含まれていて、今後もいろいろな使い勝手があるんじゃないかと思ったのが大きな決め手になりました。あと、使ってる人が多そう(購入者が多い)、サポートがきちんとしてるっぽい(更新頻度が多い)というのも安心ですよね。


なお、「The Project」の現時点の最新のバージョンは Version 1.3 で、Bootstrap 3.3 系と互換性があるとのこと(Bootstrap: Compatible with 3.3.x)。





 

2. Bootstrap テンプレートの設定

購入した Bootstrap テンプレート「The Project」を、Mac 上に構築した PyCharm プロジェクト(あるいは Ubuntu 上に構築した Mezzanine プロジェクト)に実際に当てていきます。


<前提>

  • Mac 上に構築した PyCharm プロジェクト名は「akiyokoproject」
    • プロジェクトのパスは「~/PycharmProjects/akiyokoproject」
  • ダウンロードした「products%2FWB0F82581.zip」をダブルクリックして解凍済み


 

2.1. Bootstrap テンプレートの static ファイルを配置

Mac 上に構築した PyCharm プロジェクトに、Bootstrap テンプレートの static ファイルを配置します。

以下、Mac での手順です。

### Bootstrap の js, css 等の static ファイルをコピーするための前準備
$ cd ~/Downloads/the_project_v.1.3/
### HFS Plus の拡張属性(Extended Attributes)を除去
$ xattr -cr .
$ find . -type d -exec chmod 775 {} \;
$ find . -type f -exec chmod 664 {} \;

### custom ディレクトリを作成
$ cd ~/PycharmProjects/akiyokoproject/
$ mkdir -p custom/{templates,static}
$ touch custom/__init__.py

### 「The Project」テンプレートの html/template 配下のファイルをコピー
$ cp -a ~/Downloads/the_project_v.1.3/html/template/bootstrap custom/static/
$ cp -a ~/Downloads/the_project_v.1.3/html/template/css custom/static/
$ cp -a ~/Downloads/the_project_v.1.3/html/template/fonts custom/static/
$ cp -a ~/Downloads/the_project_v.1.3/html/template/images custom/static/
$ cp -a ~/Downloads/the_project_v.1.3/html/template/js custom/static/
$ cp -a ~/Downloads/the_project_v.1.3/html/template/plugins custom/static/
$ cp -a ~/Downloads/the_project_v.1.3/html/template/videos custom/static/


 

2.2. collecttemplates コマンドを修正

Mezzanin のデフォルトテンプレートを custom アプリケーションにコピーしてくれる便利なコマンドが、Mezzanin には用意されています。

### テンプレートを全コピーしてくれるコマンド
$ python manage.py collecttemplates

(-a オプションを付けると、admin のテンプレートも対象に入れる)

しかしながら、コマンドを実行すると、

  (略)
  File "/home/webapp/.virtualenvs/akiyokoproject/local/lib/python2.7/site-packages/mezzanine/core/management/commands/collecttemplates.py", line 42, in handle
    to_dir = settings.TEMPLATE_DIRS[0]
IndexError: list index out of range

という、Django 1.9 ならではのエラーが出たので(2016年7月時点)、
python manage.py collecttemplates fails with error IndexError: list index out of range · Issue #1512 · stephenmcd/mezzanine · GitHub
に従って暫定対応しました。 *2

diff --git a/config/settings.py b/config/settings.py
index bfd3958..5371af6 100644
--- a/config/settings.py
+++ b/config/settings.py
@@ -220,6 +220,7 @@ TEMPLATES = [
         },
     },
 ]
+TEMPLATE_DIRS = [TEMPLATES[0]['DIRS'][0]]

 if DJANGO_VERSION < (1, 9):
     del TEMPLATES[0]["OPTIONS"]["builtins"]


 

2.3. デフォルトテンプレートを templates 配下にコピー

一旦、Mezzanine デフォルトテンプレートを templates/ に全コピーしておきます。

$ python manage.py collecttemplates


 

2.4. custom アプリケーションを INSTALLED_APPS に登録

最後に、custom アプリケーションを settings.py の INSTALLED_APPS に登録します。

$ vi config/settings.py

---
TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [
            os.path.join(PROJECT_ROOT, "custom/templates"),
            os.path.join(PROJECT_ROOT, "templates")
        ],
        "APP_DIRS": True,
    ・
    ・

INSTALLED_APPS = (
    ・
    ・
    # "mezzanine.accounts",
    # "mezzanine.mobile",
    "custom",
)
---

ちなみに、「INSTALLED_APPS」を修正しないと、「DEBUG = False」の場合は static ファイルをちゃんと認識してくれるものの、「DEBUG = True」の場合には認識してくれなかったので NG。つまり、「INSTALLED_APPS」への修正も必要だし、「TEMPLATES.DIRS」の修正もテンプレートファイル参照の優先度を付けるために必要となります。


またこの変更により、

$ python manage.py collecttemplates

を実行すると、Mezzanine のデフォルトテンプレートを custom 配下にコピーしてくれるようになります。

なので例えば、以下のコマンドは「cp -a ~/.virtualenvs/akiyokoproject/lib/python2.7/site-packages/mezzanine/core/templates/base.html custom/templates/」と同等になります。

$ python manage.py collecttemplates -t base.html

 

3. 各機能のテンプレートファイル修正

3.1. base.html のヘッダ修正

Bootstrap テンプレートの各種 static ファイルを読み込んでいるのが、base.html です。

なのでまず最初に、base.html の head 部分を
http://bitofpixels.com/blog/mezzatheming-creating-mezzanine-themes-part-1-basehtml/
に沿って修正しました。

diff --git a/custom/templates/base.html b/custom/templates/base.html
index 122e702..9d4149e 100644
--- a/custom/templates/base.html
+++ b/custom/templates/base.html
@@ -15,26 +15,71 @@
 <link rel="alternate" type="application/atom+xml" title="Atom" href="{% url "blog_post_feed" "atom" %}">
 {% endifinstalled %}

+<!-- Web Fonts -->
+<link href='//fonts.googleapis.com/css?family=Roboto:400,300,300italic,400italic,500,500italic,700,700italic' rel='stylesheet' type='text/css'>
+<link href='//fonts.googleapis.com/css?family=Raleway:700,400,300' rel='stylesheet' type='text/css'>
+<link href='//fonts.googleapis.com/css?family=Pacifico' rel='stylesheet' type='text/css'>
+<link href='//fonts.googleapis.com/css?family=PT+Serif' rel='stylesheet' type='text/css'>
+
 {% compress css %}
-<link rel="stylesheet" href="{% static "css/bootstrap.css" %}">
-<link rel="stylesheet" href="{% static "css/mezzanine.css" %}">
-<link rel="stylesheet" href="{% static "css/bootstrap-theme.css" %}">
-{% if LANGUAGE_BIDI %}
-<link rel="stylesheet" href="{% static "css/bootstrap-rtl.css" %}">
-{% endif %}
-{% ifinstalled cartridge.shop %}
-<link rel="stylesheet" href="{% static "css/cartridge.css" %}">
-{% if LANGUAGE_BIDI %}
-<link rel="stylesheet" href="{% static "css/cartridge.rtl.css" %}">
-{% endif %}
-{% endifinstalled %}
+<!-- Bootstrap core CSS -->
+<link href="{% static "bootstrap/css/bootstrap.css" %}" rel="stylesheet">
+<!-- Font Awesome CSS -->
+<link href="{% static "fonts/font-awesome/css/font-awesome.css" %}" rel="stylesheet">
+<!-- Fontello CSS -->
+<link href="{% static "fonts/fontello/css/fontello.css" %}" rel="stylesheet">
+<!-- Plugins -->
+<link href="{% static "plugins/magnific-popup/magnific-popup.css" %}" rel="stylesheet">
+<link href="{% static "plugins/rs-plugin/css/settings.css" %}" rel="stylesheet">
+<link href="{% static "css/animations.css" %}" rel="stylesheet">
+<link href="{% static "plugins/owl-carousel/owl.carousel.css" %}" rel="stylesheet">
+<link href="{% static "plugins/owl-carousel/owl.transitions.css" %}" rel="stylesheet">
+<link href="{% static "plugins/hover/hover-min.css" %}" rel="stylesheet">
+<!-- The Project's core CSS file -->
+<link href="{% static "css/style.css" %}" rel="stylesheet" >
+<!-- The Project's Typography CSS file, includes used fonts -->
+<!-- Used font for body: Roboto -->
+<!-- Used font for headings: Raleway -->
+<link href="{% static "css/typography-default.css" %}" rel="stylesheet" >
+<!-- Color Scheme (In order to change the color scheme, replace the blue.css with the color scheme that you prefer)-->
+<link href="{% static "css/skins/purple.css" %}" rel="stylesheet">
+<!-- Custom css -->
+<link href="{% static "css/custom.css" %}" rel="stylesheet">
 {% block extra_css %}{% endblock %}
 {% endcompress %}

 {% compress js %}
-<script src="{% static "mezzanine/js/"|add:settings.JQUERY_FILENAME %}"></script>
-<script src="{% static "js/bootstrap.js" %}"></script>
-<script src="{% static "js/bootstrap-extras.js" %}"></script>
+<!-- Jquery and Bootstap core js files -->
+<script type="text/javascript" src="{% static "plugins/jquery.min.js" %}"></script>
+<script type="text/javascript" src="{% static "bootstrap/js/bootstrap.min.js" %}"></script>
+<!-- Modernizr javascript -->
+<script type="text/javascript" src="{% static "plugins/modernizr.js" %}"></script>
+<!-- jQuery Revolution Slider  -->
+<script type="text/javascript" src="{% static "plugins/rs-plugin/js/jquery.themepunch.tools.min.js" %}"></script>
+<script type="text/javascript" src="{% static "plugins/rs-plugin/js/jquery.themepunch.revolution.min.js" %}"></script>
+<!-- Isotope javascript -->
+<script type="text/javascript" src="{% static "plugins/isotope/isotope.pkgd.min.js" %}"></script>
+<!-- Magnific Popup javascript -->
+<script type="text/javascript" src="{% static "plugins/magnific-popup/jquery.magnific-popup.min.js" %}"></script>
+<!-- Appear javascript -->
+<script type="text/javascript" src="{% static "plugins/waypoints/jquery.waypoints.min.js" %}"></script>
+<!-- Count To javascript -->
+<script type="text/javascript" src="{% static "plugins/jquery.countTo.js" %}"></script>
+<!-- Parallax javascript -->
+<script src="{% static "plugins/jquery.parallax-1.1.3.js" %}"></script>
+<!-- Contact form -->
+<script src="{% static "plugins/jquery.validate.js" %}"></script>
+<!-- Background Video -->
+<script src="{% static "plugins/vide/jquery.vide.js" %}"></script>
+<!-- Owl carousel javascript -->
+<script type="text/javascript" src="{% static "plugins/owl-carousel/owl.carousel.js" %}"></script>
+<!-- SmoothScroll javascript -->
+<script type="text/javascript" src="{% static "plugins/jquery.browser.js" %}"></script>
+<script type="text/javascript" src="{% static "plugins/SmoothScroll.js" %}"></script>
+<!-- Initialization of Plugins -->
+<script type="text/javascript" src="{% static "js/template.js" %}"></script>
+<!-- Custom Scripts -->
+<script type="text/javascript" src="{% static "js/custom.js" %}"></script>
 {% block extra_js %}{% endblock %}
 {% endcompress %}


なお、
http://bitofpixels.com/blog/mezzatheming-creating-mezzanine-themes-part-1-basehtml/
の「Updating static asset locations」に記載されている通り、staticファイルのパス指定は「{% static %}」で囲まないと、ファイルアクセス時に

UncompressableFileError: 'bootstrap/css/bootstrap.css' isn't accessible via COMPRESS_URL ('/static/') and can't be compressed

というエラーが出てしまうので注意です。




 

3.2. 各機能のテンプレート修正

どの機能がどのテンプレートファイルに対応するかというのを理解するのが、Mezzanine のテンプレートをカスタマイズする上で非常に重要なポイントになります。

日本語の記事では、Monotalk さんのブログが分かりやすいかと思います(日本語の記事はとても少ないので貴重です!)。
mezzanineのテーマを作成してみました。 | Monotalk


以下、(他にも修正が必要なファイルがあるかもしれませんが)私が実際に修正したファイルの一覧です。


トップページ

  • custom/static/img/slide-1.jpg
  • custom/static/img/slide-2.jpg
  • custom/templates/base.html
  • custom/templates/index.html

ドロップダウンメニュー

  • custom/templates/base.html
  • custom/templates/pages/menus/dropdown.html

ブログ

  • custom/static/css/custom.css
  • custom/templates/blog/blog_post_detail.html
  • custom/templates/blog/blog_post_list.html
  • custom/templates/generic/includes/comment.html
  • custom/templates/generic/includes/comments.html

Twitter 連携

  • custom/static/css/custom.css
  • custom/templates/twitter/tweets.html

  

3.3. 入力フォームの form-control 問題対応

少し細かいですが、Form ページに使用されるテンプレート(custom/templates/includes/form_fields.html)内の入力フィールドタグに、Bootstrap 標準の「form-control」という CSS クラスが無いために、Bootstrap のスタイルがうまく当たらないという問題が発覚しました。


参考サイト


上記のサイトを参考にして、custom 配下に templatetags/__init__.py および templatetags/add_attributes.py を追加し、custom/templates/includes/form_fields.html を修正しました。

custom/
    static/
    templates/
    templatetags/
        __init__.py
        add_attributes.py
    __init__.py


custom/templatetags/add_attributes.py

from django import template
register = template.Library()


@register.filter(name='add_attributes')
def add_attributes(field, css):
    attrs = {}
    definition = css.split(',')

    for d in definition:
        if ':' not in d:
            attrs['class'] = d
        else:
            t, v = d.split(':')
            attrs[t] = v

    return field.as_widget(attrs=attrs)


custom/templates/includes/form_fields.html

diff --git a/custom/templates/includes/form_fields.html b/custom/templates/includes/form_fields.html
index f3d8697..f924e87 100644
--- a/custom/templates/includes/form_fields.html
+++ b/custom/templates/includes/form_fields.html
@@ -1,4 +1,4 @@
-{% load mezzanine_tags %}
+{% load mezzanine_tags add_attributes %}

 {% nevercache %}
 <input type="hidden" name="referrer" value="{{ request.META.HTTP_REFERER }}">
@@ -12,7 +12,7 @@
 <div class="form-group input_{{ field.id_for_label }} {{ field.field.type }}
     {% if field.errors %} has-error{% endif %}">
     {% if field.label %}<label class="control-label" for="{{ field.auto_id }}">{{ field.label }}</label>{% endif %}
-    {{ field }}
+    {{ field|add_attributes:"form-control" }}
     {% if field.errors %}
     <p class="help-block">
         {% for e in field.errors %}

 

まとめ

今回、Mezzanine テーマのカスタマイズとして、

  • Bootstrap テンプレートの選定
  • Bootstrap テンプレートの設定
  • 各種テンプレートファイルの修正

を実施した内容を記載しました。

次回は、Mezzanine 本番設定の第三弾として、「その3:Mezzanine の本番デプロイ」について記載します。


 

参考本

Twitter Bootstrap 3 関連の日本語の本は、数えるほどしかありません。
もし本格的に勉強したいのであれば、是非。

UIまで手の回らないプログラマのためのBootstrap 3実用ガイド

UIまで手の回らないプログラマのためのBootstrap 3実用ガイド

Bootstrapファーストガイド―CSS設計の手間を大幅に削減!

Bootstrapファーストガイド―CSS設計の手間を大幅に削減!

*1:Mezzanine を採用したのは、個人の趣味です。

*2:Mezzanine 4.2.0 から修正されたようです。 https://github.com/stephenmcd/mezzanine/commit/a84f38c38bdaee0b21c9e9209cec5148ba0ae108

Mezzanine の本番設定(その1:AWS 環境構築)〜AWS 環境構築から運用設定まで〜

こんにちは、akiyoko です。

Mezzanine は Python製の WordPress風フルスタックCMSフレームワークですが、個人的にブログサイト(将来的には ECサイトを増設予定)を本番運用するために、昨年12月頃から調査をしてきました。

akiyoko.hatenablog.jp


それ以来いろいろと企画やら調整を地道に進めてきましたが、この 7月にようやく本番運用に漕ぎ着けることができました。

それを記念して(?)AWS の初期設定から Mezzanine の本番デプロイ、ちょっとした運用設定までの記録を一旦ここでまとめておきたいと思います。

かなり長くなるので、

の 4本に記事を分けました。

今回はその 1本目、「その1:AWS 環境構築」について説明します。


AWS 環境構築として実施した内容としては、

  • IAM 設定(ルートアカウント・admin ユーザの設定)
  • VPC 設定(1ネットワーク+ 1サブネット)
  • EC2 設定(Ubuntu 14.04 LTS インスタンスの起動)
  • Amazon SES 設定(ドメイン認証、送信制限の解除申請)

となります。



 

1. IAM の初期設定

基本方針として、AWS ルートアカウントは原則使用せず、AdministratorAccess 権限を持たせた管理者ユーザー「admin」を新たに作成して利用することとします。

なお、ルートアカウントは、MFA(二段階認証)でセキュリティを強化しておきます。


(参考)


 

1. 1. ルートアカウントに MFA 導入

1.1.1. スマホに仮想MFAアプリケーションをインストール

まず、Android に

というアプリをインストールします。


なお、仮想MFA アプリケーションの一覧はこちらです。
私は Android ユーザなので Android を使っていますが、その他のスマホを使っている場合は適宜別のアプリを検討してください。

f:id:akiyoko:20160829014611p:plain

Android Google AuthenticatorAuthy 2 段階認証
iPhone Google Authenticator
Windows Phone Authenticator
Blackberry Google Authenticator

IAM - Multi-factor Authentication より)



(参考)AWS MFAの設定 - Qiita


 

1.1.2. AWS Management Console での設定

[IAM] > [Dashboard] から、[Security Status] > [Activate MFA on your root account] > [Manage MFA] を選択します。

f:id:akiyoko:20160829014934p:plain

[A virtual MFA device](仮想 MFA デバイス)を選択。

f:id:akiyoko:20160829015326p:plain

[Don't show me this dialog again] を選択して次へ。

f:id:akiyoko:20160829015853p:plain


以下の QRコードを 1.1.1. でインストールした「Google 認証システム」アプリでスキャンして、アプリをルートアカウントと紐付けます。

f:id:akiyoko:20160829020107p:plain


これで、AWS ルートアカウントでのログイン時にアプリで払い出されるワンタイムトークンが必要になるため、不正ログインに対するセキュリティが向上します。

 

1.2. パスワードポリシーの変更

[Dashboard] から、[Security Status] > [Manage Password Policy] をクリックします。

f:id:akiyoko:20160829020329p:plain


ここで、パスワードポリシーを

  • パスワードの最小長:8
  • 英大文字、英小文字、数字の組み合わせ
  • ユーザーにパスワードの変更を許可

とします。

有効期間、再利用禁止などのポリシーは今回設定していませんが、ニーズがあり次第、適宜設定することとします。

f:id:akiyoko:20160829020557p:plain


 

1.3. IAMユーザ、IAMグループの管理

 

1.3.1. IAMユーザ作成

管理者ユーザ「admin」を作成します。

f:id:akiyoko:20160829022915p:plain
f:id:akiyoko:20160829022936p:plain

アクセスキーを CSV(credentials.csv)でダウンロードしておきます。
f:id:akiyoko:20160829023157p:plain

User Name,Access Key Id,Secret Access Key
"admin",AKIxxxxxxxxxxxxxxxxx,xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx


次に、作成したユーザの [Security Credentials](認証情報)タブから、自動作成パスワードを払い出して設定します。

[Manage Password]をクリック。
f:id:akiyoko:20160829023522p:plain

「Assign an auto-generated password」をチェックして、[Apply]をクリック。
f:id:akiyoko:20160829023547p:plain

念のため、パスワードを CSV(admin_pass_credentials.csv)でダウンロードしておきます。
f:id:akiyoko:20160829023702p:plain

User Name,Password,Direct Signin Link
"admin",xxxxxxxxxxxx,https://53xxxxxxxxxx.signin.aws.amazon.com/console


なお、adminユーザには二段階認証は設定しません。 *1


 

1.3.2. IAMグループ作成

adminユーザのための管理者グループ「admin」を作成します。

f:id:akiyoko:20160829023957p:plain

完全な管理者アクセス権限(AdministratorAccess)ポリシーをアタッチします。

f:id:akiyoko:20160829024245p:plain
f:id:akiyoko:20160829024306p:plain

次に、admin グループに admin ユーザを追加します。

作成したグループを選択して、「Add Users to Group」をクリック。
f:id:akiyoko:20160829024412p:plain

追加したいユーザ(ここでは「admin」)を選択して、「Add Users」をクリック。
f:id:akiyoko:20160829024528p:plain
f:id:akiyoko:20160829024556p:plain


 

1.3.3. admin で再ログイン

以下、adminユーザで操作します。

一旦ログアウトし、

https://{AWS Account ID}.signin.aws.amazon.com/console

から、admin ユーザでログインします。なお、「AWS Account ID」は、AWSアカウントに紐づく 12桁の数字で、My Account ページの[Accout Settings]からも確認できます。


ログインしたら、リージョンを「Tokyo」にしておきます。
f:id:akiyoko:20160829024729p:plain


 

2. VPC の初期設定

以前に書いた 「AWS体験ハンズオン ~セキュア&スケーラブルウェブサービス構築~」に参加してきました - akiyoko blog で実現したような、

  • 別アベイラビリティゾーンに Webサーバを冗長構成して、ELB を使ってロードバランシング
  • プライベートなサブネットに RDS を配置(且つ、別アベイラビリティゾーンにレプリケーション)

は行わず、最小限の構成にしています。


今回構築する構成の最終形は、このようになります。
f:id:akiyoko:20160911110053p:plain


(参考)0から始めるAWS入門①:VPC編 - Qiita

 

2.1. VPC の作成

CIDR が「10.0.0.0/16」の VPC を作成します。

[VPC] > [Your VPCs] から、「Create VPC」ボタンをクリック。

f:id:akiyoko:20160830001821p:plain

以下を設定します。

Name tag P1
CIDR block 10.0.0.0/16
Tenancy Default

f:id:akiyoko:20160830001850p:plain


【注意】
このままの設定では、あとでインスタンスを起動して sudo を実行した際に、下記のエラーが出るので(コマンド自体は実行されているっぽい)、DNS Hostnames の設定を「Yes」に変更します。(すでに EC2インスタンスを起動していた場合には、再起動が必要です。)

$ sudo ls
sudo: unable to resolve host ip-10-0-0-80

f:id:akiyoko:20160830001913p:plain
f:id:akiyoko:20160830001930p:plain

(参考)amazon web services - AWS error - sudo: unable to resolve host ip-10-0-xx-xx - Stack Overflow


 

2.2. Subnet の作成

CIDR が「10.0.0.0/24」のサブネットを作成します。

[Subnets] から、「Create Subnet」ボタンをクリック。

f:id:akiyoko:20160830001951p:plain

以下を設定します。

Name tag P1 Public #1
VPC P1
Availability Zone ap-notrtheast-1a
CIDR block 10.0.0.0/24

f:id:akiyoko:20160830002016p:plain


 

2.3. Internet Gateway の作成

インターネットゲートウェイを作成します。

[Internet Gateways] から、「Create Internet Gateway」ボタンをクリック。

f:id:akiyoko:20160830002038p:plain

名前を「P1 Public #1」に設定。
f:id:akiyoko:20160830002101p:plain

作成した Internet Gateway を選択して「Attach to VPC」ボタンをクリックし、作成した VPC を選択して、Internet Gateway を VPC に紐付けます。

f:id:akiyoko:20160830002124p:plain
f:id:akiyoko:20160830002146p:plain

[Route Tables] から、作成した VPC の Route Table を選択して、Routes を編集します。

f:id:akiyoko:20160830002204p:plain

以下を設定します。

Destination 0.0.0.0./0
Target 作成した Internet Gateway を選択

f:id:akiyoko:20160830002223p:plain
f:id:akiyoko:20160830002254p:plain


これで VPC の設定は完了です。


3. EC2 インスタンスの起動

3.1. SSH鍵の作成

まず、SSH鍵を作成します。

[EC2] > [Key Pairs] から、「Create Key Pair」をクリック。

f:id:akiyoko:20160910223728p:plain

キーペア名は、「aws_p1」としました。
f:id:akiyoko:20160910223833p:plain

作成すると、自動的に鍵がダウンロードされます。
f:id:akiyoko:20160910223853p:plain


ダウンロードした SSH鍵を .ssh 以下に配置します。
(以下の操作は Mac を想定しています。)

$ mv ~/Downloads/aws_p1.pem ~/.ssh/
$ chmod 700 ~/.ssh
$ chmod 600 ~/.ssh/aws_p1.pem
$ ls -l@ ~/.ssh/aws_p1.pem
-rw-------@ 1 akiyoko  staff  1696  6  5 17:35 /Users/akiyoko/.ssh/aws_p1.pem
    com.apple.metadata:kMDItemWhereFroms     199
    com.apple.quarantine      68
$ xattr -d com.apple.metadata:kMDItemWhereFroms ~/.ssh/aws_p1.pem
$ xattr -d com.apple.quarantine ~/.ssh/aws_p1.pem
$ ls -l@ ~/.ssh/aws_p1.pem
-rw-------  1 akiyoko  staff  1696  6  5 17:35 /Users/akiyoko/.ssh/aws_p1.pem

ここで、ダウンロードした際によく分かんない attribute(Extended Attributes と言うものらしい)が付いてしまっているので、削除しています。

(参考)EA (Extended Attributes) の消し方


 

3.2. EC2インスタンス用の Security Group 作成

EC2インスタンスに付与する Security Group を作成します。

f:id:akiyoko:20160910223918p:plain

以下を設定します。

Security group name P1 Webserver #1
Description P1 Webserver #1
VPC P1
Type Protocol Port Range Source
SSH TCP 22 My IP
HTTP TCP 80 My IP
HTTPS TCP 443 My IP

f:id:akiyoko:20160910223941p:plain


 

3.3. EC2 インスタンス用の IAM Role の作成

EC2インスタンス作成時に IAM Role を設定して起動するため、先に作成しておきます。

f:id:akiyoko:20160910224102p:plain

IAM Role の名前は「ec2-prod」(本番 EC2インスタンス用の Role という意味)としておきます。
f:id:akiyoko:20160910224124p:plain

「Role Type」は「Amazon EC2」を選択。
f:id:akiyoko:20160910224151p:plain

完全な管理者アクセス権限(AdministratorAccess)ポリシーを選択します。
f:id:akiyoko:20160910224226p:plain

「Create Policy」をクリックして、IAM Role の作成は完了です。
f:id:akiyoko:20160910224252p:plain


 

3.4. EC2インスタンスの起動

EC2インスタンスを起動します。

f:id:akiyoko:20160910224329p:plain

「Ubuntu Server 14.04 LTS」を選択。
f:id:akiyoko:20160910224351p:plain

インスタンスタイプは(必要に応じていつでもスケールアップできるので、最初は)「t2.micro」でよいでしょう。
f:id:akiyoko:20160910224416p:plain

以下を設定します。

Network P1
Subnet P1 Public #1
IAM role ec2-prod
Enable termination protection 「Protect against accidental termination」にチェック

f:id:akiyoko:20160910224442p:plain

f:id:akiyoko:20160910224508p:plain

インスタンス名を「P1 Webserver #1」とします。
f:id:akiyoko:20160910224525p:plain

3.2. で作成した Security Group を設定します。
f:id:akiyoko:20160910224552p:plain

f:id:akiyoko:20160910224616p:plain

3.1. で作成した SSH鍵を設定して、インスタンスを起動します。
f:id:akiyoko:20160910224643p:plain


 

3.5. Elastic IP の紐付け

最後に、起動した EC2インスタンスに Elastic IP を紐付けます。

[Elastic IPs]>[Allocate New Address]をクリック。
f:id:akiyoko:20160910224724p:plain

「Yes, Allocate」をクリックして、Elastic IP を払い出します。
f:id:akiyoko:20160910224751p:plain

払い出した Elastic IP を選択し、[Actions]>[Associate Address]をクリック。
f:id:akiyoko:20160910224815p:plain

3.4. で起動したインスタンスを選択して「Associate」をクリックし、Elastic IP を紐付けます。
f:id:akiyoko:20160910224835p:plain


f:id:akiyoko:20160910224945p:plain


ここで念のため、疎通確認しておきます。

$ ssh -i ~/.ssh/aws_p1.pem ubuntu@52.xx.xx.xx



 

4. SES の設定

最後に、メールの設定です。
なおここで、AWS 外のレジストラ(ここでは お名前.com)で独自ドメイン「akixxxx.com」を管理していることを前提しています。


まず最初に、Amazon SES は Tokyo リージョンでは対応していないため *2、「Oregon」リージョンに移動します。

f:id:akiyoko:20160910225033p:plain


 

4.1. ドメイン認証

[Domain] > [Verify a New Domain]でドメインを認証します。
f:id:akiyoko:20160910225132p:plain

Domain に「akixxxx.com」を指定します。
f:id:akiyoko:20160910225203p:plain

送信用の TXTレコードと受信用の MXレコードが表示されます。
f:id:akiyoko:20160910225231p:plain


 

4.2. 送信用 TXTレコードと受信用 MXレコード設定

レジストラ(お名前.com)上でドメインNavi にログインし、上記の送信用の TXTレコードと受信用の MXレコードを設定します。


[ドメイン設定] > [ネームサーバーの設定]>[DNS関連機能の設定]から、該当ドメインをチェックして「次へ進む」をクリック。
f:id:akiyoko:20160910225320p:plain

[DNSレコード設定を利用する]>[設定する]をクリック。
f:id:akiyoko:20160910225355p:plain

以下を設定します。

ホスト名 TYPE TTL VALUE 優先 状態
_amazonses.akixxxx.com TXT 3600 (AWS Management Console の Domain Verification Record の値を貼り付け) 有効
akixxxx.com MX 3600 (AWS Management Console の Email Receiving Record の値を貼り付け) 10 有効

なお、MXレコードの優先度は「10」にします(そのまま貼り付けるとダメ。「10」と「inbound-smtp.us-west-2.amazonaws.com」に分割する)。

f:id:akiyoko:20160910225430p:plain

f:id:akiyoko:20160910225513p:plain

f:id:akiyoko:20160910225552p:plain



2時間ほどすると、「Amazon SES Domain Verification SUCCESS」というメールが来ます。
f:id:akiyoko:20160910225630p:plain

ドメインのステータスが「verified」になっていることが確認できます。
f:id:akiyoko:20160910225710p:plain


 

4.3. SES 送信制限の解除申請

ステータスが「verified」になったら、「Request a Sending Limit Increase」の申請をします。

f:id:akiyoko:20160910225743p:plain


申請内容は以下の通り。

Regarding(内容) Service Limit Increase(サービス制限の増加)
Limit Type(制限タイプ) SES Sending Limits(SES送信制限)
Region US West (Oregon)
Limit Desired Daily Sending Quota(希望する一日あたりの送信クォータ)
New limit value 10000
Mail Type(メールの種類) System Notifications(システム通知)
Website URL (メール送信を使用するシステムのURLを指定)
My email-sending complies with the AWS Service Terms and AUP(私は AWS サービス利用規約と AUP に準拠してメールを送信します) Yes
I only send to recipients who have specifically requested my mail(私は明確にリクエストされた受信者にのみメールを送信します) Yes
I have a process to handle bounces and complaints(バウンスや苦情を処理するプロセスがあります) Yes
Use Case Description(申請理由の説明) アカウント登録やパスワード変更など、システムを利用するユーザへの通知に利用します。メール送信は基本的にシステムからの自動送信となり、ユーザ自身が登録したメールアドレス宛に送信するため、バウンスはほとんど発生しません。予想されるユーザ数は当面のところ、○○程度と見込んでいます。(あくまでサンプルですので、状況に合わせて適宜書き直してください。)
Support Language 日本語
Contact method Web(しか選択できません)

f:id:akiyoko:20160910225819p:plain

f:id:akiyoko:20160910225857p:plain


6時間くらい後に承認メールが届きました。
f:id:akiyoko:20160910230541p:plain

Congratulations! After reviewing your case, we have increased your sending quota to 50,000 messages per day and your maximum send rate to 14 messages per second in AWS Region US West (Oregon). Your account has also been moved out of the sandbox, so you no longer need to verify recipient addresses.

AWS Management Console 上はこのようになっています。
f:id:akiyoko:20160910230618p:plain


(参考)Amazon SESによるメール送信環境の構築と実践 | Developers.IO


まとめ

今回、AWS 環境構築として、

  • IAM 設定(ルートアカウント・admin ユーザの設定)
  • VPC 設定(1ネットワーク+ 1サブネット)
  • EC2 設定(Ubuntu 14.04 LTS インスタンスの起動)
  • Amazon SES 設定(ドメイン認証、送信制限の解除申請)

を実施した内容を記載しました。

次回は、Mezzanine 本番設定の第二弾として、「その2:Mezzanine テーマのカスタマイズ」について記載します。


 

参考本

AWS 関連で比較的新しくて良さげな本を紹介します。

*1:というか、1台のスマホにつき紐付けられるユーザは 1つだけなのかな??

*2:「米国西部(オレゴン)」のほか、「米国東部(バージニア北部)」「欧州(アイルランド)」でも SES を利用できますが、今回は日本から物理的に一番近そうな米国西部を選択しました。(参考)https://docs.aws.amazon.com/ja_jp/ses/latest/DeveloperGuide/regions.html 東京リージョンに対応しました。(参考)Amazon SES 東京リージョン対応のお知らせ | Amazon Web Services ブログ