勉強がてら、自分で制作したWebアプリケーションを公開してみようと思うので、学んだ知識を備忘録として記しておこうと思います。
AWS EC2
AWS EC2とは、仮想サーバ"サービス"の名称
仮想サーバの容量やスペックの拡張性が高いことが特徴
仮想サーバの起動
◾️OSを選択
◾️CPU/メモリのハードウェアスペックを選択
仮想サーバのハードウェアスペックの選択とは、物理サーバのリソースを仮想サーバへどのぐらい引き渡すかということ
(例)
物理サーバ : CPU 30コア メモリ 50GB
仮想サーバA : CPU 10コア メモリ 15GB
仮想サーバB : CPU 5コア メモリ 10GB
仮想サーバC : CPU 8コア メモリ 12GB
オーバーコミットによって、仮想サーバ達の合計のリソースが物理サーバのリソースを超えることもある(前職で管理していたシステムもオーバーコミットしていた)
※オーバーコミットとは、全ての仮想マシンが同時に最大リソースを使用することはすごく稀なので、現状の仮想マシン達のリソースが物理サーバのリソースを超えていなければ大丈夫ということ(ハイパーバイザーが動的にリソースを管理する)
◾️キーペアを選択
1. ユーザー側で「公開鍵/秘密鍵」を作成
2. 公開鍵をサーバーへ登録
3. そのサーバーは接続があった時、公開鍵によって暗号化したデータを送信する
4. 送信されたデータを秘密鍵によって復号化し、応答
5. 応答内容が正しく復号化できていた場合は、接続を許可する
<SSHについて少々>
SSH接続は下記条件でできる
・接続されるサーバ側:SSHサーバ(OpenSSH Server)がインストールされている
・接続するサーバ側 :SSHクライアント(OpenSSH Client)がインストールされている
SSH接続で公開鍵暗号方式を使用したい場合は、OpenSSH Serverの設定ファイル(sshd_config)で公開鍵認証を有効にする
他にもパスワード認証や多要素認証があり、設定ファイルによって有効か無効かを設定する(ちなみにパスワード認証と公開鍵認証どちらも有効の場合、"どちらか"が成功すれば接続できるみたい)
〜〜〜簡単な流れ〜〜〜
1. クライアントがSSH接続要求をする
(デフォではポート22番に接続要求を送るが、ポート番号を指定することもできる)
2. 接続先サーバのSSHデーモンが接続要求を受け取る
(デフォではSSHデーモンはポート22番で待機している)
3. 接続先サーバはSSHサーバの設定ファイルを確認し、有効な認証方法に基づいてクライアントに認証要求をする
4. クライアント側で認証タスクを実行し、認証が成功すればSSH接続ができる
◾️ネットワークを設定
送信先が同じネットワーク部だったら、ローカル
違うならインターネットゲートウェイ
<ネットワークについて少々>
⚪︎ビット(bit)とは、コンピュータが扱う最小単位(0か1)
"Binary digIT"(2進数の数字)の略
1ビット:2通り(0, 1)
2ビット:4通り(00, 01, 10, 11)
3ビット:8通り(000, 001, 010, 011, 100, 101, 110, 111)
8ビット = 1バイト:256通り(2^8)
IPアドレスは、8ビット × 4で構成されているため、各数字が0~255の範囲になる (8ビットは256通りあるから) 0~255,0~255,0~255,0~255
⚪︎10.0.0.0/16 の意味
10.0.0.0これは10進数で表現されているが、2進数で表現すると10000000.00000000.00000000.00000000こんな感じ
/16は、先頭から16ビットはネットワーク部、それ以降はホスト部を意味する
⚪︎ネットワーク部とホスト部とは
192.168.1.0/24の場合、ネットワーク部は192.168.1である
同じネットワーク内の機器は全て192.168.1が割り当てられ、ホスト部(192.168.1."0"~192.168.1."255")によって機器を識別する
⚪︎サブネットとは
同じネットワーク内の機器のグループ
(例) VPC:10.0.0.0/16
パブリックサブネット1:10.0.1.0/24
パブリックサブネット2:10.0.2.0/24
プライベートサブネット1:10.0.3.0/24
プライベートサブネット2:10.0.4.0/24
◾️セキュリティグループを選択
外部からのアクセスは、インターネットゲートウェイで弾くのではなく、VPC内の個々の機器のファイアウォールによって弾く(プライベートサブネットはインターネットゲートウェイと繋がっていないので直接アクセスすることはできない)
デフォルトでVPC内の機器のポートは全て閉じているので、セキュリティグループによって必要なポートは開けてあげないとアクセスすることができない
◾️ストレージを設定する
アプリケーションの配置
◾️gitをインストール
sudo yum install git -y
-yオプションによって、インストール中にされる確認を全てYesに設定できる
◾️/var/wwwディレクトリを作成
Webサーバ(Apache/Nginx)のドキュメントルートがデフォルトで/var/www/
(ドキュメントルートは変更できる)
◾️git cloneによって、アプリケーションを配置
Railsアプリケーションの環境構築
◾️rbenvを使えるようにする
⚪︎rbenvをcloneする
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
引数に"~/.rbenv"を指定しているため、~/.rbenvにcloneされる
※ ~ とはホームディレクトリのことで、ホームディレクトリとはユーザーがログインした時のディレクトリのこと
⚪︎rbenvコマンドを使えるようにするため、環境変数(PATH)にパスを追加する
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
⚪︎rbenv初期化スクリプトによって、rbenvコマンドをシェル関数として統合する
https://x.com/dpro_wef2407/status/1816749111212921037
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
<.bashrcについて少々>
Bashシェルの設定ファイルで、ユーザー"固有"の環境設定ができる
ユーザーがログインするたびに.bashrcが自動的に実行される
⚪︎rbenv installコマンドを使えるようにするため、プラグイン(ruby-build)を追加する
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
◾️Rubyをコンパイルするために必要なパッケージ達をインストール
(正直彼らのことはよく知らない。もう少し大人になってからここは学ぶ)
sudo yum install -y gcc-c++ openssl-devel readline-devel zlib-devel libffi-devel libyaml libyaml-devel
◾️Rubyをインストール
rbenv install -v 3.3.0
◾️Node.jsとYarnをインストール
⚪︎nvmをインストール (nvm:Node.js rbenv:Ruby)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash
. ~/.nvm/nvm.sh
⚪︎Node.jsをインストール
nvm install v16.20.2
⚪︎Yarnをインストール (npm:Node.js yum:Linux)
npm install --global yarn
◾️データベースを作成
⚪︎PostgreSQLをインストール
sudo amazon-linux-extras install postgresql14 -y
amazon-linux-extras:Amazon Linux独自のパッケージ管理ツール
yumリポジトリに含まれていないバージョン等を、amazon-linux-extrasを使えば簡単にインストールすることができる
⚪︎PostgreSQLに必要なパッケージをインストール
sudo yum install postgresql-server postgresql-devel -y
⚪︎bundlerをインストール
gem install bundler (bundlerはgemである)
⚪︎Gemfileに記載されているgemをインストール
bundle install
⚪︎package.jsonに記載されているJSパッケージをインストール
yarn install --check-files
⚪︎PostgreSQLの初期化と起動
sudo /sbin/service postgresql initdb
sudo /sbin/service postgresql start
/sbin/serviceとは、Linuxのサービス管理コマンド
サービスを起動したり、停止したり、初期化したりできる
(最近のLinuxでは、systemctlコマンドに置き換わっている)
<PostgreSQL接続認証設定ファイルについて少々>
/var/lib/pgsql/data/pg_hba.conf
localはサーバ内部での接続、hostは外部からの接続っていう理解でとりまOK
peerはログインユーザ名とPostgreSQLユーザ名が一致しているか認証
127.0.0.1/32 これは「接続を許可するIPアドレス」を指しているが、127.0.0.1/32はローカルホストを指すため、外部からの接続は許可しないということになる
⚪︎ec2-userでPostgreSQLを操作するため、PostgreSQL内にec2-userを作成
sudo -u postgres createuser -s ec2-user
-uは、sudoコマンドのオプションでユーザを指定できる
PostgreSQLはデフォルトでpostgresというユーザが存在する
createuserは、PostgreSQLコマンドでPostgreSQL内に新しいユーザを作成する
-s は作成するユーザにスーパー権限を与える
<Rails config/database.ymlファイルについて少々>
データベース設定ファイル
この場合、本番環境で使用するデータベース名はMakina_production
データベースに接続する際のユーザ名はMakinaとなる
⚪︎今回、local接続の認証方法はpeerのため、usernameをec2-userへ変更
⚪︎データベースを作成
まず、postgresユーザでpostgresデータベースへ接続する
sudo -u postgres psql
ちなみにsudo -u ec2-user psqlでエラーになる原因は、データベース名を指定せずpsql(データベース接続)を実行すると、ユーザ名のデータベースへ接続しようとするからec2-userデータベースがなくエラーとなる (postgresデータベースはデフォルトである)
次に、オーナーec2-userでMakina_productionデータベースを作成する
CREATE DATABASE "Makina_production" WITH OWNER "ec2-user";
<credentials.yml.encとmaster.keyについて少々>
credentials.yml.encとは、APIキーやデータベースパスワードなどの機密情報を暗号化して格納しておくファイル
master.keyとは、credentials.yml.encファイルの暗号化を復号化するための鍵
例えば、データベース接続にデータベースパスワード認証が必要な場合などは、master.keyを使ってcredentials.yml.encを復号化、復号化された内容からデータベースパスワードを取得し、データベースへ接続する
通常、アプリケーションをクローンした場合にmaster.keyはない
よって、master.keyを入手する必要がある
今回の場合、secret_key_baseがcredentials.yml.enc内に暗号化されているため、master.keyが必要
※secret_key_baseとは、Railsアプリケーション内での暗号化に使用される
アプリケーション内で暗号化処理を行う場合、このキーが使用される
例えば、ユーザーパスワードってアプリケーション内でも暗号化されてますよね、そんな感じでアプリケーション内でも暗号化させたいデータとかを暗号化する時に使う乱数
bin/rails credentials:edit
master.keyを使ってcredentials.yml.encを復号化した後の内容を編集することができる
(master.keyとcredentials.yml.encがない場合、新たに作成する)
⚪︎データベースを反映
RAILS_ENV=production bundle exec rails db:migrate
RAILS_ENV=とは、環境変数でRailsアプリケーションがどの環境で動作するかを指定できる(デフォルト(指定なし)では、development)
通常アプリケーションサーバとデータベースサーバは分ける
外部から直接通信が届かないようにデータベースはプライベートサブネットのサーバに配置する
アプリケーションサーバ(Unicorn)の導入
◾️Unicornをインストール
Unicornはgemなので、Gemfileにgem 'unicorn'を追記しbundle installする
◾️Unicornの設定ファイル(config/unicorn.rb)を作成
・worker_processes 2
ワーカープロセスとは、リクエストを受け取り、アプリケーションを実行し、レスポンスを返すプロセスのこと
ワーカープロセス数が2の場合、並行して2つのリクエストを受け取り、処理できる
・working_directory "/var/www/MAKINA"
・stderr_path "log/unicorn.stderr.log"
・stdout_path "log/unicorn.stdout.log"
エラーログ / 標準出力ログの出力先
・timeout 30
ワーカープロセスがリクエストを処理するための最大許容時間(秒)
30秒を超えた場合、Unicornは強制的にそのプロセスを終了しエラー処理をする
・listen "/var/www/MAKINA/tmp/sockets/unicorn.sock"
(listen 80にすると、Unicornが直接80番ポートでリクエストを受け取り、処理する)
ソケットとは、通信の出入り口となるドアみたいなもの
・pid "/var/www/MAKINA/tmp/pids/unicorn.pid"
Process IDの出力先
・preload_app true
Unicorn起動時にアプリケーションのコードがメモリにロードされ、各プロセスはそのコードを共有し、処理される
preload_appが無効の場合、各プロセスが起動する度に独立してアプリケーションのコードがメモリにロードされ、処理される
before_fork do |server, worker|
defined?(ActiveRecord::Base) and
ActiveRecord::Base.connection.disconnect!
end
after_fork do |server, worker|
defined?(ActiveRecord::Base) and
ActiveRecord::Base.establish_connection
end
Unicornが起動すると、まず親プロセスが起動する
次に、親プロセスによって、アプリケーションが初期化される
(config/application.rbなどが読み込まれ、Railsの設定や、データベース接続等が行われる)
次に、親プロセスは、指定されたワーカープロセス数だけワーカープロセスを生成する
(親プロセスは、受け取ったリクエストをワーカープロセスへ分配する役割も持つ)
上記コードについて、
親プロセスがワーカープロセスを生成する時、自分自身をコピーするように生成する
よって、親プロセスのデータベース接続もコピーされてしまい、親プロセスとワーカープロセスが同じデータベース接続を共有してしまうことになる
そこで、上記コードによって親プロセスがワーカープロセスを生成する前に、一度データベース接続を切断し、ワーカープロセスを生成した後、再度データベース接続を行うことによって、親プロセスとワーカープロセスで独自にデータベース接続を行うことができる
Webサーバ(Nginx)の導入
◾️Nginxをインストール
sudo amazon-linux-extras install -y nginx1
◾️Nginxの設定ファイルを作成
/etc/nginx/conf.d/MAKINA.conf
メインの設定ファイル(nginx.conf)が自動的に/etc/nginx/conf.d/*.confファイルを読み込む
upstream unicorn {
server unix:/var/www/MAKINA/tmp/sockets/unicorn.sock;
}
Nginxがリクエストを受けた場合、/var/www/MAKINA/tmp/sockets/unicorn.sockに転送する
上記Unicornの設定ファイルでUnicornの待機場所を設定しているため、そこに合わせる必要がある
・listen 80; Nginxの待機場所(リクエスト受付場所)
・server_name PublicIPアドレス or ドメイン名;
Nginxは受け取ったリクエストのヘッダー(宛先)を見て、それがserver_nameに指定された値と一致するサーバーブロックを選択する
(例)
server {
listen 80;
server_name MAKINA.com
}
server {
listen 80;
server_name example.com
}
みたいな感じで、複数のアプリケーションを稼働させているサーバの場合、受け取ったリクエストの内容によって、どのサーバーブロックに対応するリクエストなのかserver_nameを参照し選択する
・root /var/www/MAKINA/public;
Nginxはリクエストを受け取ると、rootに設定されているパス先を確認し、リクエスト内容のファイルがそこにあった場合は、そのファイルを返す
・access_log /var/log/nginx/MAKINA_access.log;
・error_log /var/log/nginx/MAKINA_error.log;
アクセスログ / エラーログの出力先を設定する
location ~ ^/assets/ {
}
location ~ ^/packs/ {
}
Nginxはリクエスト内容がassetsまたはpacksから始まる場合は、{ }内の処理をする
assets / packsディレクトリ内に$uriが存在するか確認する
存在した場合、そのファイルを返す
存在しない場合、404エラーを返す
(例) http://example.com/assets/application-abc123.css
この場合$uriは、/assets/application-abc123.css
※本番環境では、手動でプリコンパイルする必要がある
RAILS_ENV=production bundle exec rails assets:precompile
RAILS_ENV=production bundle exec rails webpacker:compile
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_pass http://unicorn;
}
NginxからUnicornへの転送内容を設定している
・proxy_set_header X-Real-IP $remote_addr;
・proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
(例)
クライアント → プロキシ1 → プロキシ2 → Nginx → Unicorn
X-Real-IPは、クライアントIPアドレス
X-Forwarded-Forは、クライアント/プロキシ1/プロキシ2/Nginx のIPアドレス
クライアントIPアドレスとプロキシ経由記録を転送
・proxy_set_header Host $http_host;
クライアントから送られてきたHostヘッダーをそのまま転送
ホストヘッダーとは、クライアントがサーバへリクエストを送る際に、どのドメインまたはIPアドレスにアクセスしたいのかを伝えるためのHTTPヘッダー
この場合、ホストがexample.com、indexはパス
・proxy_pass http://unicorn;
upstream unicornブロックで定義されたサーバーに送信
◾️サーバ(システム)起動時に自動的にNginx(デーモン)も起動するよう設定
sudo systemctl enable nginx
◾️Nginx(デーモン)を起動
sudo systemctl start nginx
systemctlコマンドとは、Linuxコマンドでシステムとサービス(デーモン)を管理するツールである
◾️Unicornを起動
RAILS_ENV=production bundle exec unicorn -c config/unicorn.rb -D
-c config/unicorn.rb config/unicorn.rb の設定内容に基づいて起動する
-D Unicornをデーモン化する
問題なく公開できていることを確認!