OK-11’s blog

駆け出したいエンジニア

Webアプリケーションを世の中に出してみる

勉強がてら、自分で制作した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によって、アプリケーションを配置

SSHはできないため、HTTPSによって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"

Unicornを稼働させるディレクト

 

・stderr_path "log/unicorn.stderr.log"

・stdout_path "log/unicorn.stdout.log"

エラーログ / 標準出力ログの出力先

 

・timeout 30

ワーカープロセスがリクエストを処理するための最大許容時間(秒)

30秒を超えた場合、Unicornは強制的にそのプロセスを終了しエラー処理をする

 

・listen "/var/www/MAKINA/tmp/sockets/unicorn.sock"

Unicornがリクエストを受け付ける場所を指定する

(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/ {

    try_files $uri $uri/ =404;

}

location ~ ^/packs/ {

    try_files $uri $uri/ =404;

}

Nginxはリクエスト内容がassetsまたはpacksから始まる場合は、{ }内の処理をする

try_files $uri $uri/ =404;

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ヘッダー

http://example.com/index

この場合、ホストが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をデーモン化する

NginxとUnicorn

問題なく公開できていることを確認!