はじめに
初心者の入門編として、AWSを用いて、動的Webサイトを構築する。
システム概略
システムダイアグラムは以下の通りである。
使用したサービスは以下の通りです。
-
フロントエンド
- AWS EC2
- AWS Route 53
- AWS Elastic IP
- Let’s Encrpyt(無料のSSL認証書発行局)
-
お名前.com(日本最大級のドメインレジストラ、ドメイン取得サービスサイト)
ICANN(ドメイン、IP、ポートの割り振りや割り当ての管理・調整する組織)から代理で取得する
※AWSでもドメインは取得できるが有料。詳細はQiita記事を参照ください。
-
バックエンド
- AWS API Gateway
- AWS VPC
- AWS Lambda
- AWS WAF
- AWS S3
- Apatch
-
開発環境
- VS Code
- AWS Cloud9
- draw.io
事前準備
- AWSのアカウント登録
- VS Codeのインストール
- Pythonのインストール
- Draw.ioのインストール
システム構築
フロントエンドから開発して、次にバックエンドを開発し、最後にテストという流れで構築する。
フロントエンド構築
1. ドメインレジストラからドメインを取得する
お名前.comに登録して、任意のドメインを取得する。
例)
sample0123456789.com
2. EC2にドメインを割り当て、公開サーバーを作る
- AWS Route53を開く
- 「ホストゾーンの作成」をクリックする
- ドメイン名に取得したドメイン名(sample0123456789.com)を入力する
- 「ホストゾーンの作成」をクリックする
- 作成したホストゾーンのドメイン名をクリックして管理画面を表示する
- レコードに記載されているネームサーバー名を各ドメインサービスのドメインレジストラに登録する
例) ns-****.awsdns-22.org ※末尾の.は削除する
- AWS EC2を開く
- 「インスタンスを起動」をクリックする
- 名前、OS、インスタンスタイプ、キーペアを決める。
※Amazon Linux 2 Kernel 5.10 AMI 2.0.20230320.0 x86_64 HVM gp2にするのに注意
- ネットワーク設定で任意のサブネットを選択する
- インバウンドセキュリティグループのルールで、タイプ=HTTPSを選択する
-
- ルールの追加で、タイプ=HTTPを選択する
- ルールの追加で、タイプ=SSHを選択する
- 「インスタンスを起動」をクリックする
- インスタンス画面から「接続」をクリックして、SSHクライアントの下部のコマンドでSSH接続する
- EC2の管理画面の左側Elastic IPをクリックする
- Elastic IP アドレスを割り当てるをクリックする
- ネットワークボーダーグループに、EC2があるリージョンを選んで割り当てを押す
- Elastic IPの管理画面から、作成したElastic IP アドレスにチェックを入れ、右上のアクションからElastic IP アドレスの関連付けをクリックする
- Route53の作成したドメイン画面で、レコードを追加を押す
- レコード名を空白、レコードタイプはA、Elastic IPを値に入れて、レコード作成を押す
- EC2の管理画面でElastic IP アドレスが関連付けされたか確認する
- nslookup ドメイン名(sample0123456789.com)でドメインが名前解決可能か確認する
サーバー: aterm.me Address: 192.168.10.1 権限のない回答: 名前: sample0123456789.com Address: 54.165.***.**
これで任意のドメインを持つWebサーバーができた
3. Webサーバーソフトウェアで、Webページを公開する
- 下記のコマンドでApatch(httpd)をインストールして、起動する
$ sudo yum -y update $ sudo yum install emacs $ sudo yum install emacs-nox //上記でインストールできなかった場合 $ sudo yum -y install httpd $ httpd -v $ sudo systemctl start httpd && sudo systemctl enable httpd $ sudo systemctl is-enabled httpd
- ブラウザのURL欄にドメイン(httpで)を打ち込み、Apatchのテストページが表示されるか確認する
- ローカルで開発したHTML/cssなどをscpでサーバーに転送する
scp -i "mykeypair.pem" -r .\folder ec2-user@ec2-***********.compute-1.amazonaws.com:folder
- サーバーで公開フォルダへ移動する
sudo mv ./folder/* /var/www/html/
- ブラウザで閲覧可能か確認する
http://sample0123456789.com/index.html
4. HTTPSを有効化する
下記公式サイトを参照して行う。
HTTPSはSSL(Secure Sockets Layer)/TLS(Transport Layer Security)のプロトコルを使った暗号化通信を行い、セキュアな通信を可能にする。
- OpenSSLとmod_sslをインストールする
これでmod_ssl の設定ファイルが/etc/httpd/conf.d/ssl.confに、サーバーホスト用の自己署名 X.509 証明書とプライベートキーを生成するためのスクリプトが/etc/pki/tls/certs/make-dummy-certに作成された
$ sudo yum install openssl $ sudo yum install mod_ssl
- テスト用に自己署名のダミー証明書とキーを生成するためのスクリプトを実行する
/etc/pki/tls/certs/ ディレクトリに新しいファイル localhost.crt が生成される
cd /etc/pki/tls/certs sudo ./make-dummy-cert localhost.crt
- /etc/httpd/conf.d/ssl.conf ファイルを開き、次の行をコメントアウトする
sudo emacs /etc/httpd/conf.d/ssl.conf # SSLCertificateKeyFile /etc/pki/tls/private/localhost.key
- Apacheサーバーをリスタートする
sudo systemctl restart httpd
1. ブラウザでhttps://+ドメインで開く。
信頼されていない自己署名ホスト証明書を使用してサイトに接続しようとしているため、ブラウザには一連のセキュリティ警告が表示される
5. 認証局CA(Certificate Authority)の署名証明書を取得する
認証局CAとは、信頼できる証明書を発行する組織のことである。
- /etc/pki/tls/private/ に移動する
- 新しいプライベートキーを生成する
$ sudo openssl genrsa -out custom.key
- 新しいプライベートキーに、制限の厳しい所有権とアクセス権を設定する
$ sudo chown root:root custom.key $ sudo chmod 600 custom.key $ ls -al custom.key
- CSR(Certificate Signing Request:証明書署名要求) を作成する。
$ sudo openssl req -new -key custom.key -out csr.pem
- CAにCSRを送信する。今回はCAとして、無料の認証局であるIdenTrust 社のLet’s Encrpytを使う。
$ sudo amazon-linux-extras install epel $ sudo yum install certbot 一回、Apacheサーバーを止めて $ sudo systemctl stop httpd 最新版の証明書の作成 $ sudo certbot certonly --standalone -d example.com >>Enter email address >> (Enter 'c' to cancel):********@gmail.com >>(Y)es/(N)o: Y >>IMPORTANT NOTES: >> - Congratulations! Your certificate and chain have been saved at: >> /etc/letsencrypt/live/sample12094713568723.com/fullchain.pem >> Your key file has been saved at: >> /etc/letsencrypt/live/sample12094713568723.com/privkey.pem >> Your certificate will expire on 2023-04-07. To obtain a new or >> tweaked version of this certificate in the future, simply run >> certbot again. To non-interactively renew *all* of your >> certificates, run "certbot renew" >> - If you like Certbot, please consider supporting our work by: >> >> Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate >> Donating to EFF: https://eff.org/donate-le
- 正常に終了すると、/etc/letsencrypt/live 以下に最新版の証明書へのシンボリックリンクが作成されるので、/etc/pki/tls/certsにコピーしておく
$ sudo ls /etc/letsencrypt/live/example.com/ $ sudo cp -LR /etc/letsencrypt/live/example.com /etc/pki/tls/certs/ $ sudo cp /etc/pki/tls/certs/example.com/* /etc/pki/tls/certs/
- /etc/httpd/conf.d/ssl.confに以下のように設定する
# SSLCertificateFile /etc/pki/tls/certs/localhost.crt SSLCertificateFile /etc/pki/tls/certs/cert.pem # SSLCertificateKeyFile /etc/pki/tls/private/localhost.key SSLCertificateKeyFile /etc/pki/tls/certs/privkey.pem # SSLCertificateChainFile /etc/pki/tls/certs/server-chain.crt SSLCertificateChainFile /etc/pki/tls/certs/chain.pem
- Apatcheを再起動する
$ sudo systemctl start httpd
- ブラウザでhttps:// でアクセス可能か確認する。セキュリティ警告が表示されなかったら成功。
https://example.com/
- 証明書は 3 ヶ月で有効期限が切れるので、以下コマンドを定期実行して更新する。
crontab -eはviと同じ操作。$ sudo certbot renew --pre-hook "systemctl stop httpd" --post-hook "systemctl start httpd" $ crontab -e / sudo crontab -u root -e PATH=/sbin:/bin:/usr/sbin:/usr/bin * * 01 * * sudo certbot renew --pre-hook "sudo systemctl stop httpd" --post-hook "sudo systemctl start httpd" 00 04 01 * * certbot renew --pre-hook "sudo systemctl stop httpd" --post-hook "sudo systemctl start httpd" // 毎月1日の4:00に実行 //次の更新はいつなのか確認できる $ sudo certbot certificates //証明書の削除 $ sudo certbot revoke --cert-path /etc/letsencrypt/live/sample12094713568723.com/cert.pem
バックエンド構築
1. ローカルでwebアプリを開発する
今回はブラウザから渡された値をDynamoDBへ追加していくWebアプリを作る。
まずはDynamoDBを作成する。
- Amazon DynamoDB コンソールにログインする
- 「テーブルの作成」を押す
- テーブル名にHelloWorldDatabase、パーティションキーにIDと入力する
- テーブルの作成を押す
- HelloWorldDatabaseの設定画面で、追加情報を押し、Amazon リソースネーム (ARN)をコピーしておく
- 次に、以下のようにDynamoDBのテーブルへ名前と時間をJSON形式で送り、追記するPythonコードを書く。
# import the json utility package since we will be working with a JSON object
import json
# import the AWS SDK (for Python the package name is boto3)
import boto3
# import two packages to help us with dates and date formatting
from time import gmtime, strftime
# create a DynamoDB object using the AWS SDK
dynamodb = boto3.resource('dynamodb')
# use the DynamoDB object to select our table
table = dynamodb.Table('HelloWorldDatabase')
# store the current time in a human readable format in a variable
now = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
# extract values from the event object we got from the Lambda service and store in a variable
name = 'MyName'
def lambda_function(event):
# write name and time to the DynamoDB table using the object we instantiated and save response in a variable
response = table.put_item(
Item={
'ID': name,
'LatestGreetingTime':now
})
# return a properly formatted JSON object
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda, ' + name)
}
print(lambda_function(name))
ローカル環境で実行すると、DynamoDBへMynameと実行時の時間が追記される。
2. AWS Lambdaへ移管する
- AWS Lambda コンソールにログインする
- 関数の作成を押す
- 関数名をHelloWorldFunction、ランタイムにPython 3.9を選択する
- 関数の作成を押す
- 設定タブ->アクセス権限->実行ロールのURLを押す
- IAMのロール画面に遷移する
- 許可タブの許可を追加で、インラインポリシーの作成を押す
- JSONタブをクリックし、以下を入力する。
※YOUR-TABLE-ARNは、DynamoDBのリソースフィールドのテーブルの ARN を置き換える{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "dynamodb:PutItem", "dynamodb:DeleteItem", "dynamodb:GetItem", "dynamodb:Scan", "dynamodb:Query", "dynamodb:UpdateItem" ], "Resource": "YOUR-TABLE-ARN" } ] }
- 名前にHelloWorldDynamoPolicyを入れ、ポリシーの作成を押す
- Lambda関数のコンソール画面に戻る
- 関数のコードを以下に置き換える
# import the json utility package since we will be working with a JSON object import json # import the AWS SDK (for Python the package name is boto3) import boto3 # import two packages to help us with dates and date formatting from time import gmtime, strftime # create a DynamoDB object using the AWS SDK dynamodb = boto3.resource('dynamodb') # use the DynamoDB object to select our table table = dynamodb.Table('HelloWorldDatabase') # store the current time in a human readable format in a variable now = strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()) # define the handler function that the Lambda service will use as an entry point def lambda_handler(event, context): # extract values from the event object we got from the Lambda service and store in a variable name = event['firstName'] +' '+ event['lastName'] # write name and time to the DynamoDB table using the object we instantiated and save response in a variable response = table.put_item( Item={ 'ID': name, 'LatestGreetingTime':now }) # return a properly formatted JSON object return { 'statusCode': 200, 'body': json.dumps('Hello from Lambda, ' + name) }
- Deployを押す
- Testを押す
- イベント名にHelloWorldTestEventを入れる
- イベントJSONを以下に置き換える
{ "firstName": "Ada", "lastName": "Lovelace" }
- テストタブへ行き、右上のテストを押す
- 実行結果:成功が出て、""Hello from Lambda, Ada Lovelace""が出れば成功
3. AWS API gatewayでWebアプリのURLを作成する
- API Gateway Console にログインする
- REST APIで構築を押す
- プロトコルの選択でREST、新しいAPIの作成で新しいAPIを選択する
- API名をHelloWorldAPIを入れ、エンドポイントタイプにエッジ最適化を選択する
- API の作成を押す
- アクション->メソッドの作成->POST->横のチェックを押す
- 統合タイプにLambda 関数、Lambda 関数にarn:aws:lambda:us-east-1:*****:function:HelloWorldFunctionを入れて、保存を押す
- アクション->CORS を有効にするを押す
- POSTチェックボックスを選択して、CORS を有効にして既存の CORS ヘッダーを置換を押す
- アクション->APIをデプロイを押す
- デプロイされるステージで新しいステージを押し、ステージ名にdevを入れ、デプロイを押す
- URL の呼び出しのURLを控えておく
- リソース->POST->小さな青い稲妻のテストを押す
- 下記のJSONを入力し、テストを押す
{ "firstName":"Grace", "lastName":"Hopper" }
- bodyに ""Hello from Lambda, Grace Hopper"が返ってきていれば成功
Webサイトに実装する
1. WebページにWebアプリのURLを埋め込み、ブラウザで実行してテストする
- First NameとLast Nameを入力して、Call APIでLambdaを呼び出す
- するとDynamoDBのテーブル->テーブルアイテムの探索で、下部に呼び出した人の名前と時間が入力されていれば成功!
セキュリティレベルを上げる
AWS WAFを設定する
- API Gateway Console にログインする
- 作成したAPIGatewayをクリックする
- 画面左欄の「ステージ」をクリックする
- ステージ名(dev)をクリックする
- 設定タブのウェブアプリケーションファイアウォール (WAF)の ウェブ ACL を作成するを押す
- NameにHelloWorldWAFを入れ、Regionに作成したAPIGatewayと同じリージョンを選択する
- Add AWS resourcesで、Amazon API Gatewayから作成したAPIGatewayにチェックを入れ、Addを押す
- Nextを押す
- Add rulesからAdd managed rule groupを押す
- AWS managed rule groupsから「Core rule set」のAdd to web ACLにチェックを入れる
- Add rulesを押す
- Nextを押す
- Nextを押す
- Nextを押す
- Create web ACLを押す
- APIGatewayのステージ名(dev)の設定タブ画面に戻り、ウェブ ACLをありにして、作成したWAFを選択する
Apacheの設定
Apacheの設定は、管理者権限を持つ場合、/etc/httpd/conf/httpd.confで設定し、それ以外のユーザーは各ディレクトリに.htaccessを設置し、そのディレクトリ下位に適応する。
Apacheの/(root)ディレクトリは、/etc/httpdでパスの最後にスラッシュ/を付けてはいけない。
Webサーバーとして80番ポートで処理をします。
Listen 80
デフォルトの文字コードを「UTF-8」に設定します。
AddDefaultCharset UTF-8
設定ファイルの場所と形式を設定します。「/etc/httpd/conf.d」ディレクトリ配下にある .confファイルは
すべて設定ファイルになります。
IncludeOptional conf.d/*.conf
クライアントにファイルを送る時に sendfile を使うかどうかを設定します。onは使います。
EnableSendfile on
動的モジュールを使用したい場合は、/etc/httpd/conf.modules.dディレクトリに記述する。
Include conf.modules.d/*.conf
Apacheを他のアカウントやグループで動かしたい場合は、ここを変更する。
Group apache
User apache
このWebサーバーの管理者のメールアドレスを記述します。
ServerAdmin root@localhost
Webサーバーとして公開するホスト名を指定します。ホスト名は FQDN(Fully Qualified Domain Name) で記述します。
ServerName www.example.com:80
<Directory /> は、/(root)ディレクトリ配下に関しての設定しますよという意味です。AllowOverride none ← AllowOverride は 上位のディレクトリ(この場合は/(root)ディレクトリ)でした設定を下位のディレクトリで設定を上書きできるかどうか(オーバーライドできるかどうか)を設定します。
DocumentRoot "/var/www/html" は、Webサーバーのトップディレクトリです。例えば「/var/www/html」ディレクトリ配下に「index.html」ファイルを置いた場合、「http://xxx-xxxx-xxxx.vs.sakura.ne.jp/index.html」でアクセスすることができます。
/var/wwwディレクトリ配下へのアクセス制御に関する設定です。
<Directory "/var/www/html">
このディレクトリ(/var/www/html)配下に「.htaccess」ファイルを作成した場合、上書きするか
AllowOverride None
/var/wwwディレクト配下へのアクセスは全員が可能です。つまり、Webサイトを閲覧することができます。
Require all granted
シンボリックリンクをたどれるようにします。
Options Indexes FollowSymLinks
</Directory>
<IfModule dir_module>← IfModule は条件分岐です。「IfModule dir_module」は、「もし dir_module が組み込まれている場合」という意味です。
DirectoryIndex index.html ← DirectoryIndex とは一言で言うと、ディレクトリのインデックス一覧です。そのままの意味なのですが、ここでの意味は「もしindex.htmlファイルがあれば表示するよ。もしindex.htmlファイルがなければ、ディレクトリの一覧を表示するよ」という意味です。たまにサイトにアクセスしたときにリンクの設定が間違えていて、ファイルが全部見えてしまうことがありますが、その状態になるということです。(セキュリティ的によくありません)
</IfModule>
もしディレクトリに「.htaccess」ファイルや「.htpasswd」ファイルがあった場合、
ユーザーに表示するかどうかを設定しています。
もちろん「Require all denied」で表示させないように設定しています。
<Files ".ht*">
Require all denied
</Files>
【例】http://sample.ne.jpの場合、「/var/www/cgi-bin/」ディレクトリを「http://sample.ne.jp/cgi-bin」
に割り当てます。つまりどのディレクトリを「cgi-bin」に割り当てるのか設定します。
<IfModule alias_module>
ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"
</IfModule>
cgi-binディレクトリの動作について設定します。
<Directory "/var/www/cgi-bin">
AllowOverride None
Options None ← オプションは何もありません。
Require all granted
</Directory>
ReactのようなSPAでは、クライアント上でreact-routerにより仮想DOMに遷移するので、サーバの方でルーティングされず、404 Errorになる。以下の手順でURL直打ちでも特定のページが開くようにする必要がある。
- 対象のフォルダに以下の隠しファイルを作成する
Options -MultiViews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.html [QSA,L]
- /etc/httpd/conf/httpd.confで親フォルダで設定した内容を子フォルダで上書きできるようにする
<Directory "/var/www/html">
AllowOverride All <- ここをNoneからAllにする
</Directory>
最終的な構成図
EC2のEBS容量の増やし方
EBSの容量はEC2の起動中でも変更できる。ただ処理中のジョブを保証するものではないので、ボリュームのスナップショットを作成していく方がよい
- コンソールでEBSの容量を増やす
- 下記コマンドでEC2のファイルシステムのパーティションを変更する
sudo lsblk
sudo growpart /dev/nvme0n1 1
sudo xfs_growfs -d
sudo resize2fs /dev/nvme0n1p1
まとめ
AWSのサービスを用いて、動的Webサイトを構築した。
参考サイト