Webサーバーに対する知識は、
Webエンジニアの必須教養のはずですが、
意外と実務で触れる機会は少なかったりします。
特にnginxなどは、
慣れていないとかなり戸惑うと思いますし、
苦手意識をいだきやすいかもしれません(私がそうでした。)
しかし、なんだかんだで、
本番環境のWebサーバとして、
Nginxが使われている現場は多い印象があり、
場合によっては、非インフラエンジニアも、
このNginxの知識が必要になったりする場面もあるかと思います。
そこで本記事では、
今更聞けないNginxの基礎から、SSL(自己署名証明書)設定をして、
Hello worldを表示するWebサーバー構築を行いたいと思います。
Nginxは、インフラ設定をする際には、
かなりの確率で触ることになると思うので、
ぜひとも習熟したいところですよね。
Nginxとは
Nginxは、高性能かつ軽量なウェブサーバーで、
特に大規模なウェブアプリケーションや高トラフィック環境での使用に適しています。
その特徴の一つが、イベント駆動型アーキテクチャです。
Nginxのイベント駆動型アーキテクチャは、高性能かつ効率的なWebサーバーとしての性能を実現するための重要な要素です。
このアーキテクチャを理解するために、いくつかの基本的な概念を明確にする必要があります。
イベント駆動型アーキテクチャ
Nginxの核心となるのは、イベント駆動型アーキテクチャです。
この設計により、Nginxは多数のリクエストを同時に効率的に処理できます。
従来のスレッドベースのアプローチと異なり、リソースの消費が少なく、スケーラビリティが高いのが特徴です。
通常のプログラムは、main関数から開始され、特定の順序で関数が呼び出されます。
しかし、イベント駆動アーキテクチャでは、プログラムの主要な流れはイベント
(例えば、クライアントからの接続要求やファイルへのアクセス要求など)によって駆動されます。
JavaScriptとかでのクリックイベントとかをイメージするとわかりやすいと思います。
JavaScriptでクリックすると、そのclickイベントによってコールバック関数が呼ばれると思いますが、
Nginxでもそういったアーキテクチャが採用されているという感じです。
Nginxでのイベントは、
上記でも触れましたが、クライアントからの接続要求などがあたります。
Nginxでは、これらのイベントが発生すると、
それに対応するコールバック関数(イベントリスナー)が呼び出されることによって処理が進行します。
これにより、非常に効率的でスケーラブルな処理が可能になります。
ノンブロッキングI/O:
ブロッキングI/Oでは、ファイルやネットワークリソースへのアクセスが完了するまで
プログラムの実行が停止(ブロック)されます。
これに対して、ノンブロッキングI/Oでは、リソースが利用可能になるまで待たずに、
即座にエラーを返すなどして、プログラムの実行を続けることができます。
Nginxでは、このノンブロッキングI/Oを利用することで、プログラムの並行性を高め、効率的なリソース利用が可能になります。
I/Oとは?
そもそも、I/Oって何かっていうと、
I/O(Input/Output、入出力)のことで、
コンピューターや電子機器において、データや信号の入力と出力のプロセスを指しています。
具体的には、コンピューターシステムが外部の装置や他のシステムからデータを受け取る(入力)ことと、
それらにデータを送る(出力)ことを意味します。
I/Oとして同期I/Oと非同期I/Oの二つがあり、
同期I/O(ブロッキングI/O)は、
データの送受信が完了するまで他の処理は待機します。
I/O処理が始まると、他の処理は一時的に停止し、送受信が完了してから他の処理が再開されます。
これに対して、非同期I/O(ノンブロッキングI/O)は、
データの送受信が完了するのを待たずに他の処理を進めることができます。
I/O処理と並行して他の処理を進めることができ、送受信が必要な処理にたどり着いた時点で、その完了を待ちます。
Nginxの場合、ノンブロッキングI/Oとして、
即座にエラーを返してブロックされるのを防ぐため、
厳密には非同期I/OとノンブロッキングI/Oは、
同義ではないかなと思います。
https://e-words.jp/w/I-O.html#:~:text=
非同期I/Oは、データの入出力が可能になった時点で通知されるI/Oモデルです。
これにより、通知が来るまでアプリケーションは他の処理を進めることができます。
これに対して、ノンブロッキングI/Oでは、I/O対象のファイルディスクリプタが準備完了していない場合、
アプリケーションに即座にエラーが返されます。
https://ryuichi1208.hateblo.jp/entry/2020/02/19/215155#:~:text=
これにより、プロセスがI/O操作の完了を待つことなく、他のタスクに切り替えられるということです。
正直、この辺の厳密な違いは、かなり細かいかつ、
とりあえずNginxを使うレベルであれば、
不要な知識のような気もしますが念のため...
まあ、何が言いたいかというと、
Nginxは、こういったノンブロッキングI/Oや非同期I/Oを採用しているので、
並行性が高く、効率的だということです。
マスタープロセスとワーカープロセスのマルチプロセス構成
Nginxは、一つのマスタープロセスと複数のワーカープロセスで構成されています。
マスタープロセスは設定の読み込みや全体の管理を担い、ワーカープロセスが実際のリクエストの処理を行います。
この構成により、複数のプロセスが並行して動作し、高いパフォーマンスと信頼性を実現します。
これについては、
下記の記事がわかりやすかったです。
Nginx以外のWebサーバー
さて、ここまでNginxの特徴を見てきましたが、
抽象的な話が続いたので、結局Nginxってすごいの?ってなるかと思います。
Webサーバとしては、
他にもApache、IIS、Lighttpdなどのウェブサーバーが存在します。
試しに、有名なApacheと軽く比較考察してみます。
Apacheのアーキテクチャとして、主に「Multi Processing Module (MPM)」が採用されています。
これは、Apacheの処理モデルを定義するもので、ApacheにはいくつかのMPMが存在します。
主要なMPMとして「prefork」「worker」「event」があります。
例えば、Prefork MPMは、Apacheが古くから使用している方式で、
基本的には1つのプロセスが1つのスレッドを持つシングルスレッドプロセスモデルを採用しています。
この方式では、各httpdプロセスが1つのCPUコアを使用してリクエストを処理します。
トラフィックが増加すると、システムは新しいプロセスを生成(fork)してリクエストを処理します。
他のWorker MPMでは、1つのプロセスが複数のスレッドを持つ「マルチスレッドプロセス」方式を採用しています。
これにより、より少ないプロセス数で多くのリクエストを同時に処理することが可能になります。
本記事は、Apacheの記事ではないので、
Apacheの詳細はこれぐらいにしておきます。
MPMの解説は、下記の記事がわかりやすかったです。
https://milestone-of-se.nesuke.com/sv-basic/linux-basic/apache-mpm-prefork-worker-event/
Apacheと比較したNginxの強み
Nginxは、その高いパフォーマンス、低リソース消費、設定のしやすさにより、
多くの開発者に選ばれています。
また、静的コンテンツの配信、ロードバランシング、キャッシュ機能、
リバースプロキシとしての使用など、様々な機能を提供します。
Apacheの先述のアーキテクチャとNginxを比較すると、
Nginxのイベント駆動型およびノンブロッキングI/Oアーキテクチャが、
特に同時大量アクセスの処理において優れていることがわかります。
Prefork MPMのようなシングルスレッドプロセスモデルでは、
大量の同時リクエストに対応するために多くのプロセスを生成する必要があり、これはメモリとCPUリソースの大きな消費につながります。
一方で、Nginxのモデルでは、少数のワーカープロセスで多数の接続を非同期に処理することができ、
リソース消費を大幅に抑えることが可能です。
その結果、Nginxは高トラフィックの環境においてApacheよりも高いスケーラビリティと効率を提供します。
また、静的コンテンツの配信においてもApacheより高速なレスポンスが期待できます。
NginxとDockerでWEBサーバーを構築
Dockerを使わずに、
nginxのソースコードをダウンロードして、
zlibとか必要ライブラリをインストールして、
./configureして、
makeして、make installという、
あの例の儀式を行って環境構築するのも勉強になって、
それはそれでいいのですが、今回は大変なので
Dockerで済ませます...
Dockerを利用することで、環境に依存せず、
一貫した方法でNginxサーバーを構築できますよね。
DockerでNginxコンテナを作成する
以下は、Dockerを用いてNginxコンテナを作成し起動する基本的なコマンドです。
nginxのコンテナを引っ張って、立ち上げているだけですね。
(Dockerはインストールされているものとする)
docker pull nginx
docker run --name my-nginx -p 80:80 -d nginx
というか、runの実行時に、
ローカルにnginxイメージがなければ、
取得してくれるので、ぶっちゃけ、
docker run --name my-nginx -p 8080:80 -d nginx
だけでも大丈夫です。
--name my-nginxは、起動するコンテナに「my-nginx」という名前を割り当てていて、
-p 8080:80は、ローカルマシンの8080ポートをコンテナの80ポートにマッピングしています。
つまり、ローカルマシンの8080ポートへのリクエストは、コンテナの80ポートに転送されるという感じです。
-dはコンテナをデタッチモード(バックグラウンドモード)で実行し、
最後のnginxは、イメージの名前です。Docker Hubにあるnginx公式イメージですね。
Dockerじゃなくて、
普通にnginxをインストールしたら、
systemctl start nginx的な感じで、
startさせる必要がありますが、
Dockerコンテナの場合、コンテナを起動すれば、
立ち上がっています。
なので、この状態で、localhost:8080に
アクセスすれば、下記のようなページになっているはずです。
ちなみに、Dockerの基本的な操作とか概念については、
こちらの記事で解説しているので、
よろしければご参考ください。
-
参考Dockerとは何か?概要を完全解説 簡単なWebサーバー構築で基礎を学ぶ
Dockerとは? Dockerは、コンテナ型仮想環境を作成するソフトウェアの一種で、隔離された実行環境を用意することが特徴です。 https://aws.amazon.com/jp/docker/ ...
続きを見る
hello world画面を表示する
さて、DockerでNginxを起動したので、簡単なHTMLページを表示する手順を説明します。
とりあえず、何でもいいのですが、適当なHTMLファイルを作ります。ローカルに保存して大丈夫です
<!DOCTYPE html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
さて、ローカルにこんな感じで、
適当なHTMLファイルを作ったら、
それをNginxコンテナに渡して上げる必要があるので、
ここではdocker cpを使います。
docker cp index.html my-nginx:/usr/share/nginx/html/index.html
このコマンドは、index.htmlをmy-nginxコンテナの、
/usr/share/nginx/html/index.htmlにコピーするコマンドです。
一応、これでもうHello Worldは表示されているはずです。
ただ、なぜこのコマンドで表示されるのかが、
まだ不可解なので解説します。
まず、Nginxの設定ファイルは、デフォルトだと、
/etc/nginx/nginx.confになります。
このファイルの中身を見てみましょう。
まずコンテナに入りましょう。
docker exec -it my-nginx /bin/bash
コンテナに入ったら、/etc/nginx/nginx.confを見てみましょう。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
ここで注目したいのは、一番最後の、
include /etc/nginx/conf.d/*.confの部分ですね。
これは、conf.d以下の末尾が.confの設定を読み込むという記述です。
試しに、conf.d以下をlsしてみると、
default.confというファイルがあるはずです。
そこの設定が読み込まれているというわけです。
server {
listen 80;
listen [::]:80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
以下省略
}
ここの設定を見ると、
root /usr/share/nginx/htmlという部分があると思います。
すなわち、ここで表示するhtmlのディレクトリを設定していたという感じです。
これにより、先程のdocker cpコマンドで
hello worldが表示されるようになったという感じです。
ちなみに、docker cpのようなホストPCとコンテナのデータ共有(主にボリューム周り)
については、下記の記事で詳しく解説しているので、
良ければご参考ください。
-
参考【Docker volume/cp】ホストとコンテナのデータをマウントする
Dockerを使用するにあたって、必ず直面するものとして、ホスト(自分のローカルPC)とコンテナのデータの共有及びそれらの永続化があると思います。 本記事では、それを実現する、Dockerの重要概念で ...
続きを見る
SSL設定
※書く順番を間違えてしまったのですが、
OS再起動が挟まるかもしれない関係で、先に最後のホストファイルの編集をやってから、
こちらのSSL設定をしたほうが、余計な修正が出なくていいかもしれません。
Nginxを使うときは、
SSLの設定をすることも多いと思うので、
それについても紹介します。
ちなみにこのあとからは、
ポートが80ではなく、443になるので、
dockerのポートも変更しておきます。
(私はこれを忘れて結構はまりました)
docker run --name my-nginx -p 443:443 -d nginx
SSL証明書とは
SSL証明書は、ウェブサーバーの身元を認証し、データの暗号化を可能にするデジタル証明書です。
証明書は、信頼できる第三者機関(CA)によって発行されます。
開発環境やテスト環境なら、opensslで生成した自己署名証明書で良いかと思います。
これは公的な認証局(CA)によって発行された証明書ではないため、
一般のウェブサイトでの使用には適していません。
ブラウザはこれらの証明書を信頼せず、訪問者にセキュリティ警告を表示します。
なので、あくまで開発環境など向けにはなります。
OpenSSLとは
OpenSSLは、主に暗号化およびセキュリティ関連の機能を提供するもので、
SSLおよびTLSプロトコルの実装で最も広く使用され、
インターネット上でのデータ通信のセキュリティを強化するために重要な役割を果たしています。
主な機能としては、
ポイント
- SSL/TLSプロトコルの実装
- 暗号化アルゴリズムの提供
- デジタル証明書の管理
- 暗号化キーの生成と管理
などがあげられます。
一番、よく使うのが、
やはり本記事での使用のような、
SSL設定ですが、文書の暗号化とか、
SSHの鍵生成みたいな使い方もできるので、
割となんでもツールみたいな側面はあるかもしれません。
OpenSSLでの自己署名証明書の生成
最初に秘密鍵を生成します。これはSSL証明書の暗号化と復号に使用されます。
下記のコマンドは、2048ビットのRSA秘密鍵を生成し、その鍵を "sample-nginx.key" という名前のファイルに保存します。
genrsaは、RSA秘密鍵を生成するという感じです。
openssl genrsa -out sample-nginx.key 2048
次に、CSR(証明書署名リクエスト)の作成を行います。
CSRは、証明書の発行を認証局にリクエストする際に使用されるファイルです。
自己署名証明書の場合、このステップでは証明書の詳細(ドメイン名、組織名など)を含めます。
openssl req -new -key sample-nginx.key -out sample-nginx.csr
既存の秘密鍵(ここでは「sample-nginx.key」というファイル)を基にして行われ、
生成されたCSRは「sample-nginx.csr」として保存されます。
これを実行すると、色々聞かれますが、
開発環境なので、多少情報は適当でも大丈夫かと思います。
Common Nameの部分は、
ドメイン名なので、ここだけちゃんと入力しましょう。
これらが終わったら、最後に自己署名証明書の作成します
秘密鍵とCSRを使用してSSL証明書を自己署名します。
openssl x509 -req -days 365 -in sample-nginx.csr -signkey sample-nginx.key -out sample-nginx.crt
このコマンドは、証明書署名要求(CSR)から自己署名されたSSL/TLS証明書を生成します。
x509はX.509証明書を処理するためのオプションです。
X.509は、SSL/TLSなどで使用されるデジタル証明書の標準フォーマットです。
-reqは、CSRから証明書を生成することを意味します。
-days 365は、証明書の有効期限を設定します。この例では、365日間となっています。
-in sample-nginx.csrで、入力として使用するCSRファイルを指定します。
ここでは「sample-nginx.csr」というファイルです。
-signkey sample-nginx.keyは、CSRに署名するために使用する秘密鍵を指定します。
この例では「sample-nginx.key」という秘密鍵を使用します。
-out sample-nginx.crtで生成された証明書を保存するファイル名を指定します。
ここでは「sample-nginx.crt」という名前のファイルに保存されます。
そうして、コマンドの結果は、自己署名されたSSL/TLS証明書です。
これで、sample-nginx.crt(自己署名証明書)とかが生成されているはずです。
NginxでのSSL設定
次は、生成した、鍵と証明書をNginxコンテナに配置します。
docker cp sample-nginx.crt my-nginx:/etc/ssl/certs/
docker cp sample-nginx.key my-nginx:/etc/ssl/private/
次にコンテナのnginxの設定ファイルを編集する必要があるので、
もう一度コンテナに入ります。
コンテナに入ったらVimで編集!って行きたいところなんですが、
ちょっと困ったことが。
なに、まさかのviが使えんとは。
さっきみたいに、ローカルでnginxの設定ファイルを使って、
docker cpするみたいな感じでもいいのですが、
やっぱvimmerとしてはvimが使えない環境は見過ごせない。
ということでvimを使えるようにします。
コンテナの中で、
apt update && apt install -y vim
これでvimが使えるようになるはず。
たまにnginxイメージとかのversionが古かったりすると、
apt updateが失敗するときもあるので、
その際は、下記の記事にようにしたら修正できました。
https://qiita.com/teriyakisan/items/e5cf7d5228dc02500562
さて、vimをインストールしたら、
/etc/nginx/conf.d/default.conを開いて、下記の内容に修正します。
server {
listen 443 ssl;
server_name sample-nginx.com;
ssl_certificate /etc/ssl/certs/sample-nginx.crt;
ssl_certificate_key /etc/ssl/private/sample-nginx.key;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
これが何をしているか、
順に見ていきます。
まず、serverブロックですが、
これはNginxの設定ファイルにおける基本的な構造であり、
ここに特定のサーバーに関する設定を記述します。
ブロックっていうものが何かというと、
特定の設定ディレクティブのグループを囲むための構文要素でで、
これらを使用することで、Nginxの挙動を柔軟に制御し、
特定のコンテキストや条件に基づいてさまざまな設定を適用できます。
ブロックは波括弧({})で囲まれたセクションです。
ブロック内には複数のディレクティブが含まれることがあり、
入れ子にすることも可能です。
たとえば、httpブロック内に複数のserverブロックを、
serverブロック内に複数のlocationブロックをというふうに。
Nginxの主なブロックの種類
httpブロック
Webサーバー全体の設定を定義するためのブロックです。
ここには、リバースプロキシ設定、ロギング、MIMEタイプの定義など、
すべてのサーバーインスタンスに共通する設定が含まれます。
serverブロック
個別のWebサーバーまたは仮想ホストの設定を行うブロックです。
ディレクティブでポートを指定し、server_nameでドメインを指定することで、
特定のホストやポートへのリクエストに応じた設定を行います。
locationブロック
特定のURLパス(例:/images、/apiなど)に適用される設定を定義します。
これにより、異なるパスに異なる挙動(例えば、特定のパスへのアクセス制御やコンテンツの提供方法の変更)を設定できます。
いわゆる、一般的なエンドポイントはこのブロックで指定します
ifブロック
条件に基づいて設定を適用する場合に使用しますが、ifディレクティブは使用に注意が必要です。
なぜなら、特定の条件下でのみ実行される設定は、思わぬ挙動を引き起こすことがあるからです。
upstreamブロック
ロードバランシングやリバースプロキシ設定で使用され、
複数のサーバーアドレスをグループ化して管理します。
RailsのPumaとかでのソケット通信などで使ったりしました。
さて、少し脱線してしまいましたが、
先程のコードの説明に戻ります。
listen 443 sslはは、Nginxがリクエストを受け付けるポートを指定します。
ここでは443ポートを使用しており、これはHTTPS通信の標準ポートです。
sslキーワードは、このポートでSSL/TLSを使用することを指示します。
server_name sample-nginx.comは、サーバーが応答すべきホスト名またはドメイン名を指定します。
この例ではsample-nginx.comが使われています。
ssl_certificate と ssl_certificate_keyの2つは、
ssl_certificateディレクティブは、SSL証明書のファイルの場所を指定します。
ssl_certificate_keyディレクティブは、証明書に対応する秘密鍵のファイルの場所を指定します。
これらはサーバーの身元を確認し、クライアントとの安全な通信を確立するために必要です。
rootディレクティブは、リクエストされたURIに対するファイルが保存されているサーバー上のディレクトリを指定します。
indexディレクティブは、ディレクトリインデックスとして使用されるファイル名を指定します。
さて、これでsslの設定をしたので、
次はhostファイルの設定をします。
ホストファイルの編集
本番環境とかであれば、
面倒なDNS設定が必要ですが、
開発環境なので、ホストファイルの編集だけで済ませてしまおうと思います。
開発環境でsample-nginx.comというドメイン名でNginxの画面を表示するので、
ローカル環境のホストファイルを編集して、sample-nginx.comをローカルIPアドレス(通常は127.0.0.1)にマッピングすることで、
このドメイン名をローカルで解決できるようにしたいと思います。
設定ファイルは、macやlinuxなら
/etc/hostsが該当します。
ファイルの末尾に、以下を追加します。
127.0.0.1 sample-nginx.com
このあと、OSの再起動またはブラウザの再起動が場合によっては必要かもしれません。
再起動後にhttps://sample-nginx.com/にアクセスして、
表示されればうまくいっています。
自己署名証明書なので、ブラウザから警告が出ていると思いますが。
Nginxを更に極めるために
Nginxを更に極めるために、
下記のような書籍はおすすめです。
特に一冊目は、
公式ドキュメントではなかなかわかりにくいところも
広く解説しているので、おすすめです。