1.はじめに
WealthDevでは、以下のような技術を使用しています。
- フロントエンド:HTML、CSS、JavaScript(Chart.js)
- サーバサイド:Python(Django)、Nginx、Gunicorn、AWS(EC2、Elastic IP、 Lambda、API Gateway、DynamoDB、Route53)、Google Cloud Platform(Compute Engine、Google Analytics)
こちらのページでは、WealthDevの構築に用いた技術スタックを解説することを通じて、 Webアプリ開発工程の全体像をご紹介すると共に、開発者の知識の棚卸を図ります。
2.Webアプリケーションの構成
アプリケーション動作フロー
WealthDevの動作フローは以下の①~⑦から構成されます
(⑤と⑥はリスク許容度診断実行時のみ)。
- ① DNSサーバに問い合わせを行いAmazon Route 53のネームサーバを取得
- ② Amazon Route 53のネームサーバに問い合わせを行いEC2インスタンスのIPアドレスを取得
- ③ EC2インスタンスにリクエストを送信
- ④ NginxがHTTPリスクエストを受け付けGunicornに送信
- ⑤ Gunicorn上で動作するDjangoにてAmazon API Gatewayを呼び出し
- ⑥ AWS Lambdaの処理を実行しAmazon DynamoDBのデータと合わせて結果を返却
- ⑦ Djangoで生成されたレスポンスをGunicornからNginxが受け取りブラウザに返却
図1. アプリケーション全体像
追記:2021年5月31日より、サーバ運営コスト削減の目的で、AWC EC2(t2.micro)からGoogle Cloud Platformの Compute Engine(f1-micro)にインスタンスを変更しました。 AWS EC2との使用感の際はほとんどなく、EC2インスタンスがVMインスタンスに、Elastic IPが外部IPに変わった程度の印象です。
インスタンスのスペック比較(2021年6月4日時点)
下記に示した比較の通り、f1-microは期限の定めなく使用可能である一方で、
メモリが0.6GBと非常に少量であり、データ分析等を伴うアプリケーションでは工夫が必要です。
実際に、移管後にはメモリの不足からデータ更新のコード実行出来なくなったため、
不要なインポートを削除したり、データの呼び出しや加工を関数に切り出したりといった工夫を行いました。
再追記:2022年3月29日より、過去のGCPの無料枠変更を受けてGoogle Cloud PlatformのCompute Engine(e2-micro)にインスタンスタイプを変更しました。
e2-microのメモリは1.0Gなので、f1-microと比較するとメモリ制約は大きく緩和されました。
なお、GCPのインスタンス変更は、VMインスタンスを停止して編集からマシンタイプを変更することで、簡単に出来ました。
名前解決
今回使用しているドメインは「お名前.com」
より取得しました。DNSにはAmazon Route 53を使用しました。
Amazon Route 53によるDNSでは、最初にAmazon Route 53においてホストゾーンを作成し、取得したドメインを登録します。
続いて「お名前.com」にてネームサーバをAmazon Route 53に変更します。
上記の設定後にブラウザからドメインにアクセスすると、「お名前.com」のサーバを経由して
Amazon Route 53のネームサーバにIPアドレスの問い合わせが発生します。
最終的には、Amazon Route 53のネームサーバで名前解決されたIPアドレスを元にEC2インスタンスに
リクエストが届くことで、ブラウザにWebページが表示されます。
図2.「お名前.com」とAmazon Route 53を組み合わせた名前解決のイメージ図
サーバの構築
WealthDevは、PythonのWebアプリケーション・フレームワークであるdjangoを使用して作成しています。
サーバにはAWS EC2インスタンス(t2.micro)を使用しています。
インスタンスにGunicornとNginxをインストールし、Gunicorn上でdjangoを動作させています。
このうちNginxがWebサーバ、Gunicornがアプリケーションサーバの役割を担っています。
ユーザがブラウザからアクセスすると、NginxがHTTP通信を受け取り、Gunicornにリクエストを送ります(リバースプロキシ)。
Gunicornは受け取ったリクエストを元にdjangoのアプリケーションを実行、結果をNginxに返却します。
NginxはGunicornから受け取った結果をユーザに返し、処理が完了します。
なお、実行に際しては、あらかじめElasctic IPを確保してEC2インスタンスに紐づけておくことで、
サーバを停止してもIPアドレスが変わらないようにしています。
図3. サーバ動作フローのイメージ図
Web APIの構築
WealthDevでは、リスク許容度の計算ロジック部分をWeb APIとして実装しています。
Web APIはAWS上に構成されており、Amazon API Gatewayを通じてHTTPリクエストを受け取り、AWS Lambda上にある
Pythonコードの計算を実行します。計算結果を元にAmazon DynamoDBに登録されている情報を抽出し、
計算結果と併せてHTTPレスポンスを返します。
このような実装は、サーバ構築を伴わないことから「サーバレスアプリケーション」と呼ばれています(参考:西谷,2018)。
図4.Web APIイメージ図
サーバ構築に使用したアプリケーション
Nginx
Nginxは、Apacheに次いで頻繁に用いられているフリーのWebサーバー用のソフトウェアです。 WealthDevでは、ユーザのHTTPリクエストを受け付けてGunicornに連携する役割と、 Gunicornのレスポンスをユーザに返す役割を担う、Webサーバとしての働きをしています。
Gunicorn
Gunicornは、Python WSGI HTTPサーバです。 WealthDevでは、djangoを動作させる役割を担っており、Webサーバ経由で受け取った リスクエストを元にdjangoを動作させ、結果をWebサーバに返すアプリケーションサーバとしての働きをしています。 なお、Gunicorn単体でもWebサーバとしての役割も果たすことは可能ですが、負荷分散やセキュリティの観点から、 公式においてはNginxのようなプロキシサーバの背後で動作させる(リバースプロキシ)ことが推奨されています。
3.デプロイまでのプロセス
djangoアプリケーションの準備
最初に、ローカルでデプロイしたいdjangoアプリケーションを作成します。 djangoを用いたWebアプリケーション開発の詳細は、書籍や動画のような情報源が多数存在するので、 そちらをご参照ください。 以下に特に参考にした情報源を列挙します。
書籍
1つ目の書籍は、djangoを用いたアプリケーションの構築についての基本的な話題がまとまっており、
最初の1冊として重宝しました(現在でも辞書的に参照しています)。
2つ目の書籍には、ログイン周りの実装やSSL認証の取得、
NginxとGunicornを用いてアプリケーションをサーバにデプロイするまでのプロセス、
アプリケーション高速化や堅牢性の向上といったより実践的な話題が載っており、
実際にアプリケーションをデプロイするに当たって非常に参考になりました。
Udemy
UdemyはDjango以外にも、AWSやGCP、Linuxといった講座もあり、非常に有用でした。
上記に挙げた講座はいずれも入門者向けであり、最初に全体像を掴むために活用しました。
加えて、1つ目の講座には、NginxとGunicornを用いてEC2インスタンスにデプロイするまでのプロセスが載っており、
今回のデプロイに参考になりました。
インスタンスの準備
次に、AWSアカウントを作成し、EC2インスタンスを作成します。本アプリケーションではAWS無料枠でも利用可能な
Ubuntuのt2.microを使用しました。
なお、デプロイを予定している場合、Elastic IPを取得してEC2インスタンスと紐付けておくと、
インスタンス停止時にパブリックIPアドレスが変わらないので便利です。
作成したEC2を操作する場合、SSHが実行可能なターミナルを通じて行うのが一般的です。
SSHを実行する際には、インスタンス作成時のプライベートキーファイル(例.Ubuntu18.pem)を準備します。
SSHが機能するにはキーが公開されていないことが必要なので、適宜以下のようなコマンドによって権限を変更する必要があります。
$ chmod 400 Ubuntu18.pem
なお、WSLを使用してSSHを実行する場合には、pemファイルをmnt配下のディレクトリにおいてchmod 400を実行しても、
権限変更が通らない点には注意が必要です。
接続においては、以下のようなパブリックDNSを使用します。
また、作成したdjangoプロジェクトのようなローカルデータを転送する場合には、以下のようにSCPを使用することも可能です。
$ ssh -i "Ubuntu18.pem" ubuntu@ec2-18-218-207-238.us-east-2.compute.amazonaws.com
$ scp -i "Ubuntu18.pem" -r "移動元パス" ubuntu@ec2-18-224-211-216.us-east-2.compute.amazonaws.com:"移動先パス"
無事に接続出来たら、必要に応じてPythonの仮想環境の構築やdjangoアプリケーションで使用するライブラリ等をインストールし、 Python実行環境を構築します。 Pythonの実行環境が準備出来たら、一度runserverを使用して動作確認をしておくことをおすすめします。 動作確認をする際には、EC2のセキュリティでローカルホストが使用出来るように、 設定したセキュリティグループからポートを開けておく必要があります。
DNSの設定
ドメインを使用する場合には「お名前.com」のような事業者からドメインを取得します。 取得したドメインは、Amazon Route 53のようなDNSサービスを用いて、適宜上記で準備したEC2インスタンスに紐付けます。 WealthDevにおけるDNSについては、既に上記に記載したため、こちらをご参照ください。
settings.pyの変更
djangoで作成したアプリケーションをデプロイする際には、settings.pyを修正する必要があります。 具体的には、以下の2か所を修正します。
$ DEBUG = False
$ ALLOWED_HOSTS = ["xxx.com", "11.22.33.44"]
DEBUGをFalseにすることで、エラー発生時に詳細なデバッグ情報が表示されず、 エラーコードのみが表示されるようになります。 また、接続を許可するドメインとEC2のIPアドレスを設定しておく必要があります。
アプリケーションサーバ構築
次に、EC2インスタンスにサーバを構築するために、サーバアプリケーションをインストールします。
インストールが完了したら、それぞれの設定ファイルを作成してdjangoとの紐付けを行う必要があります
(上述の書籍等に加えてこちらも参考になりました)。
最初にアプリケーションサーバを構築します。
アプリケーションサーバにはGunicornを使用しています。以下のようにして、Gunicornをインストールします。
(venv) $ pip3 install gunicorn
インストールが完了したら、以下の手順でGunicornで起動するサービスをsystemdに登録します。
- ① systemdが自動起動するサービスのUnit定義ファイルを作成
- ② systemdが待ち受けるソケットのUnit定義ファイルを作成
- ③ systemdにサービスを登録
$ sudo vim /etc/systemd/system/XXX.service
# XXX.service
[Unit]
Description=gunicorn daemon
Requires=XXX.socket
After=network.target
[Service]
User=(ユーザ名)
Group=www-data
WorkingDirectory=(djangoアプリのディレクトリのパスを指定)
ExecStart=/home/(ユーザ名)/venv/bin/gunicorn --access-logfile - --workers 3 --bind unix:/run/XXX.sock config.wsgi:application
[Install]
WantedBy=multi-user.target
同様に②についても、以下のようにXXX(サービス名).socketファイルを作成します。
$ sudo vim /etc/systemd/system/XXX.socket
# XXX.socket
[Unit]
Description=gunicorn socket
[Socket]
ListenStream=/run/XXX.sock
[Install]
WantedBy=sockets.target
ファイルの作成が完了したら、systemdにサービス管理登録を行います。
$ sudo systemctl enable XXX.socket
$ sudo systemctl enable XXX.service
$ sudo systemctl start XXX
Webサーバ構築
次にWebサーバを構築します。 WebサーバにはNginxを使用しています。 最初に、以下のようにして、Nginxをインストールします。
$ sudo apt install -y nginx
インストールが完了したら、以下の手順でNginxの設定ファイルを作成します。
$ sudo vim /etc/nginx/sites-available/XXX
# XXX
server {
server_name XXX.example.com;
client_max_body_size 20M;
location = /favicon.ico {access_log off; log_not_found off;}
location /static/ {
root (staticファイルのパス);
}
location /media/ {
root (mediaファイルのパス);
}
location / {
include proxy_params;
proxy_pass http://unix:/run/XXX.sock;
}
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
}
}
なお、Nginxの設定ファイルにstaticやmediaのパスを記載しないと、 cssや画像が適切に反映されないので注意が必要です。 設定ファイルの作成が完了したら、以下のようにしてシンボリックリンクを作成します。
$ sudo ln -s /etc/nginx/sites-available/XXX /etc/nginx/sites-enabled/
デプロイ
上記全ての設定が完了したら、NginxとGunicornを再起動します。 これでアプリケーションがデプロイされ、 ブラウザでドメインを入力するとdjangoで作成したサイトが表示されるようになります。 なお、アプリケーションを変更した場合には、反映のために適宜以下のようなコマンドを使用して NginxやGunicornを再起動する必要があります。
$ service nginx restart
$ systemctl restart gunicorn.service
4.アプリケーションの高度化
SSL認証とHTTPS
HTTPS(Hypertext Transfer Protocol Secure)は、従来よく用いられてきたHTTPプロトコルに
SSL認証によるセキュリティ強化を行ったものです(SSLは、現在はTLSに名称が変わっていますが、
SSL認証の名称が広く浸透していることから以下ではSSL認証で表記を統一します)。
近年では、多くのサイトがHTTPSに対応しており、本サイトもHTTPSに対応しております。
サイトがHTTPSに対応しているかどうかは、URL横の鍵マークの有無によって確認出来ます。
図5. HTTPSに対応したURLの表示例
WebサイトをHTTPSに対応させるためには、SSLサーバ証明書を取得する必要があります。
SSL(Secure Sockets Layer)とは、ユーザとWebサーバの通信を暗号化するための仕組みです。
本サイトではcertbotからSSLサーバ証明書を取得しました。
取得の手順はこちらに詳細に説明されています。
HTTPSで利用されるSSL/TSLでは、以下のような仕組みにより、Webサイトの安全性が確保されます。
暗号化通信
HTTPにおける通信は暗号化されていないため、比較的容易に通信データを見ることが出来ます。 このような問題点に対して、SSLは秘密鍵を用いた「ハンドシェイク」と呼ばれる手法により、 ブラウザとサーバーの通信を暗号化することで、情報の漏洩を防止します。
改ざん防止
例えば、ホームページに記載されているメールアドレスやSNSのような情報が書き換えられると、 ユーザの個人情報が流出する危険性があります。 一方で、SSLには、通信の途中でデータが書き換えられていないかを検出する仕組みがあり(メッセージダイジェスト)、 改ざんを検知すると、データを破棄するとともにデータの再送が要求されます。
なりすまし防止
一般に、URLも含めてホームページを完全にコピーし、別のサーバーに設置することは、技術的には可能です。 一方で、SSL通信に必要となる秘密鍵はコピー出来ないため、 SSL通信の段階で上記のような「なりすまし」を検出することが出来ます。
なお、運用に使用するインスタンスを変更したことに伴い、SSH認証を変更したい場合には、
移行元のサーバで以下のコードを実行してSSH認証を破棄してから、
移行先のサーバで再度SSH認証を取得すると簡単でした。
$ sudo certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem
レスポンシブ対応
レスポンシブ対応とは、異なる端末の画面サイズに応じて表示が調整されるような対応を指します。
近年、スマホやタブレットの普及に伴って、多くのサイトがレスポンシブ対応を行っており、
WealthDevもレスポンシブ対応を行っております。
レスポンシブ対応にはいくつか方法がありますが、WealthDevでは、メディアクエリを用いて
CSSの読み込みを変更する方法を採用しております。
以下にデスクトップとモバイルでの表示のされ方の違いについて例示します。
図6a. デスクトップの表示イメージ
図6b. モバイルの表示イメージ
メディアクエリを用いる方法では、headタグ内におけるcssファイルの読み込みを、 以下のようにアクセスしたメディア幅に応じて変化させます。
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<link rel="stylesheet" href="{% static 'css/desktop.css' %}" media="(min-width: 701px)">
<link rel="stylesheet" href="{% static 'css/mobile.css' %}" media="(max-width: 700px)">
上記のうち、デスクトップとモバイルで共通する装飾をstyle.cssに、デスクトップ固有の装飾を desktop.cssに、モバイル固有の装飾をmobile.cssに記述しています。
Google Analyticsの導入
WealthDevでは、Google Analyticsを用いたアクセスログ解析を行っています。 Google Analyticsを導入すると、アクセスのあったユーザ数やページビュー、 タイトルごとのアクセス数、アクセス地域といった様々な情報をモニタリング出来るようになります。
図7. Google Analyticsの結果一例(画像出所:公式ホームページ)
Google Analyticsの導入は、取得したドメインをアナリティクスに登録し、 HTMLのheadタグ内にアナリティクスに記載されている以下のようなJavaScriptコードを追記することで、 簡単に実現できます。
<script async src="https://www.googletagmanager.com/gtag/js?id=XXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() { dataLayer.push(arguments); }
gtag('js', new Date());
gtag('config', 'XXXXXXXXXX');
</script>
ファイル定期更新の自動化
Webアプリケーションは、コンテンツの定期的な更新作業を伴うようなものも珍しくありません。 WealthDevでは、日次の分析結果の更新をcronを用いて自動化しています。 cronを使用する場合には、以下のようにcronで実行する処理を登録しておく必要があります。
$ crontab -e
# crontab
0 6 * * * /update.sh
0 0 1 * * sudo /usr/bin/certbot renew -q --renew-hook "/bin/systemctl reload nginx"
crontabには、処理を繰り返す分、時間、日(月始めから)、月、週(週初めから)、 実行するコマンドを登録します。*(アスタリスク)は毎回実行を意味します。 上記のcrontabの一行目では、実行するコマンドをスクリプト(update.sh)に集約して、 日次(AM6:00)でスクリプトの実行をすることを登録しています。 また、crontabの二行目では、上記で取得したSSL認証の定期更新を毎月0時に実行するように自動化しています。