標題記事の最終回。各回記事の内容は以下のようになります。
導入編:サンプルを動かす
探究編:Blazorの仕組みを理解する
実践編:Chart.jsでグラフを描く
Tips編:BlazorやChart.jsのTipsなど
番外編:Ubuntuサーバで公開する(当記事)
今回の内容は、第1回の「作業の流れ」で提示した項目のうち「9. 発展編(Ubuntuサーバでアプリを公開する)」となります。今回の記事には Blazor や Chart.js の話はほとんど出てきませんので「番外編」としました。あらかじめご承知おきください。
公開までの作業の流れ
まず外部に Ubuntu サーバが用意できていることを前提とします。またそのサーバマシンにはグローバルIPが割り当てられており、適切な FQDN で DNS により正引きできることが必要です。本稿では、AWS EC2 で作成した Ubuntu サーバ[^aws-ubuntu]での実行結果を基に記述することにします。
[^aws-ubuntu]: 筆者は今回の記事を書くにあたり、AWS EC2 で Ubuntu 18.04 を利用しました。お試し用ならば t2.nano のオンデマンドで十分であると思います。あとは使い捨てるつもりで当初使用価格が格安のドメインを購入すれば、都合500円くらいの費用で試してみることができると思います。DNS についてはドメイン業者がサービスを提供しているし、あるいは AWS Route53 を使ってもよいでしょう。
サーバマシンは、ポート 80(HTTP) と 443(HTTPS) が開いていることを確認しておいてください。昨今の Web 環境は SSL/TLS化が当たり前になっています。また、ローカル環境からは ssh および scp でアクセスできることも前提とします。
Blazor で作成した Webアプリは、デフォルトだと HTTP を 5000番ポートで、HTTPS を 5001番ポートで LISTEN します。そして HTTP (5000) にきたリクエストは HTTPS (5001) にリダイレクトされます[^UseHttpsRedirection]。
[^UseHttpsRedirection]: Startup.cs の Configure()
で app.UseHttpsRedirection();
を呼んでいる場合。ただしサーバに「開発証明書」がインストールされていないと HTTPS(5001)のほうは LISTEN されず、したがってリダイレクトもされません(警告が表示される)。
Blazor アプリの Web サーバ部分は Kestrel というプログラムが担っています。以下、「リバース プロキシ サーバーを利用する」から引用します。
Kestrel は、ASP.NET Core から動的なコンテンツを提供するのに役立ちます。 ただし、Web サーバーとしての機能は、IIS、Apache、Nginx などのサーバーと比べると制限されます。リバース プロキシ サーバーは、静的コンテンツ サービス、要求のキャッシュ、要求の圧縮、HTTP サーバーからの HTTPS 終了などの作業の負荷を軽減します。
そういうわけで、 Blazor アプリ自体はデフォルトの設定でバックエンドとして稼動させ、フロントエンドに別のリバースプロキシを立てて、80/443 へのリクエストをバックエンドの kestrel に流すような仕組みが推奨されています。本記事では、リバースプロキシとして nginx を用いることにします。
これをふまえて、以下のような流れで作業することにします。
- 外部 Ubuntu サーバへの .NET Core のインストール
- ローカル環境で製品版をビルドしリモ一トサーバにコピー
- テスト実行
- nginx のインストール
- nginx でリダイレクトとリバースプロキシの設定
- SSL化を自己署名証明書でテスト
- Let's Encrypt からSSLサーバ証明書を取得して本番稼動
Ubuntu サーバへの .NET Core のインストール
Microsoft の「Ubuntu に .NET Core SDK または .NET Core ランタイムをインストールする」を参照しながら作業を進めます。今回は 18.04 (LTS)を対象としますが、実際には自身の環境に合ったパッケージを選んでください。基本的にMSのドキュメント通りに作業すれば問題なくインストールできると思います。
(1)信頼されたキーの一覧に Microsoft パッケージ署名キーを追加し、パッケージ リポジトリを追加
wget https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
sudo dpkg -i packages-microsoft-prod.deb
(2)SDK のインストール
sudo apt-get update; \
sudo apt-get install -y apt-transport-https && \
sudo apt-get update && \
sudo apt-get install -y dotnet-sdk-3.1
(3)確認
dotnet --version
何か問題が発生したら、上記 Microsoft のドキュメントページを参照してください。
ローカル環境で製品版をビルドしリモ一トサーバにコピー
(1)外部 Ubuntu サーバに、Blazor アプリ一式を格納するディレクトリを作成しておきます。任意の場所で構いませんが、本記事では、~/dotnet
とします。
mkdir -p ~/dotnet
(2)ローカルの開発環境で製品版(Release版)をビルド
ローカルPCのプロジェクトフォルダで dotnet publish -c release
を実行し、製品版(Release版)をビルドして、実行に必要なファイルを publish
フォルダにまとめます。
dotnet publish -c release
(3)publish
フォルダを remote Ubuntu の ~/dotnet
にコピー
実行に必要なファイルは bin/Release/netcoreapp3.1/publish
にまとまっているので、下記コマンドを実行して、リモートサーバの ~/dotnet/MyBlazorApp
にコピーします。ここで $PRIVATE_PEM
にはリモートサーバにSSHアクセスするための秘密鍵のパスが、また $REMOTE_HOST
にはリモートサーバーのドメイン名またはグローバルIPが設定されているものとします[^aws-ubuntu-user]。
[^aws-ubuntu-user]: AWS EC2 の Ubuntu インスタンスでは、ログインユーザ名が ubuntu
になるようです。
scp -rp -i $PRIVATE_PEM bin/Release/netcoreapp3.1/publish ubuntu@$REMOTE_HOST:~/dotnet/MyBlazorApp
(4)リモートサーバでファイルを確認
リモートサーバで ls -l ~/dotnet/MyBlazorApp
を実行して確認します。
ls -l ~/dotnet/MyBlazorApp
テスト実行
発行されたアプリを実行するには dotnet MyBlazorApp.dll
[^change_workdir] を実行します。リモートサーバで、まずはそのまま実行してみましょう。
[^change_workdir]: カレントディレクトリを変更してから実行します。Blazor はカレントディレクトリにある wwwroot を見るようです。
cd ~/dotnet/MyBlazorApp
dotnet MyBlazorApp.dll
警告が出ていますが、初回起動時は DataProtection のためのキーが存在しないので、平文のままキーファイルを作成するよ、ということのようです。2回目からはこの警告は出なくなります。
メッセージを見ると HTTP(5000番ポート)をLISTENしていることが分かります。Ubuntu用の ASP.NET Core には「HTTPS開発証明書」(HTTPS developer certificate)が含まれていないようです。Kestrel は HTTPS開発証明書が存在しない場合は HTTPS(5001) をLISTEN しません。
もし Kestrel に HTTPS(5001)でも LISTEN させたい場合は、次のコマンドを実行して開発証明書をインストールしてください。
dotnet dev-certs https
本稿では nginx をリバースプロキシとして用い Kestrel の 5000 ポートに接続するような構成を取るので、このままの状態で先に進みます。
ここで別のターミナルを開いてリモートサーバに接続[^use-screen]し、wget http://localhost:5000/
を実行してみます[^wget-https]。
[^use-screen]: screen
をご存じの方は、いったん ^C でアプリを停止させ、コマンドラインに戻ってから screen
で複数のスクリーンを開いたほうが便利かもしれません。
[^wget-https]: HTTPS(5001)も有効にしている場合は、 wget --no-check-certificate https://localhost:5001/
を実行してください。
wget http://localhost:5000/
HTTP(5000)に接続してルートページがダウンロードされました。ダウンロードされた index.html ファイルを vim で開いてみます。
vim index.html
まさしく作成した Blazor アプリのルートページがダウンロードされています。
nginx のインストール
リモート Ubuntu サーバで以下のコマンドを実行してください。
sudo apt-get update
sudo apt-get install -y nginx
sudo service nginx status
status が active (running) になっていれば OK です。
nginx でリダイレクトとリバースプロキシの設定
以下の2つの設定を行います。詳細については「リバース プロキシ サーバーを構成する」も参照ください。
- HTTP(80) へのリクエストはすべて HTTPS(443) にリダイレクトする
- HTTPS(443) へのリクエストはすべてバックエンドの HTTP(5000) へリバースプロキシで転送する
/etc/nginx/sites-available/default を修正します。修正する前にオリジナルの default ファイルを別名で保存しておくとよいでしょう。
cd /etc/nginx/sites-available
sudo cp -p default default.orig
default をエディタで開いて内容をいったん全削除します[^vim-delete-all]。
sudo vim default
[^vim-delete-all]: vim で :1,$d
<Enter> と打てば全削除できます。ggVGd
でもOK。
以下の内容をコピペしてください[^vim-paste]。その後 YOUR.SERVER.FQDN のところを Blazor アプリを公開するホストの FQDN(小文字)で置き換えてください。
[^vim-paste]: vim を使っている場合は、:set paste
を実行してからペーストしてください。
server {
server_name _;
listen 80;
if ($host = YOUR.SERVER.FQDN) {
return 301 https://$host$request_uri;
}
return 404;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
server_name _;
listen 443 ssl;
ssl_certificate ssl/server.crt;
ssl_certificate_key ssl/private.key;
if ($host != YOUR.SERVER.FQDN) {
return 404;
}
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
location / {
proxy_pass http://localhost:5000;
}
}
これは以下のことを設定しています。
1~10行:ポート80 に対する設定
- 2行:任意の名前のバーチャルホスト名を受け付ける
- 3行:ポート 80 を LISTEN する
- 5~7行: リクエストされたホスト名が、用意したFQDNに一致したら https にリダイレクトする
- 9行: 上記FQDN以外のバーチャルホストに対する要求には 404 を返す
12~15行: 下記 31行の Conection
設定で必要となる変数の用意
17~36行: ポート 443 に対する設定
- 18行: 任意の名前のバーチャルホスト名を受け付ける
- 19行: ポート 443 を SSL で LISTEN する
- 21行: サーバ証明書のパス
- 22行: サーバ秘密鍵のパス
- 24~26行: リクエストされたホスト名が、用意したFQDNに一致しなかったら 404 を返す
- 28~29行: リバースプロキシの際のHTTPバージョンとホスト名の設定
- 30~31行: リバースプロキシで WebSocket を通すために必要な設定[^websocket]
- 33~35行: すべてのパスに対する要求を http://localhost:5000 に転送する[^ssl-5001]
[^websocket]: 「Nginx を構成する」では proxy_set_header Connection keep-alive;
になっていますが、その設定では WebSocket がリバースプロキシを通らないようです。http://nginx.org/en/docs/http/websocket.html を参照してください。Qiitaの記事では「NginxのリバースプロキシでWebソケットを通す際の設定」が参考になりました。
[^ssl-5001]: もし HTTPS開発証明書をインストールしてあり、HTTPSとして5001ポートもLISTENしている場合は、転送先を https://localhost:5001 に変更してください。
上記設定ファイルでは、サーバ証明書として、テスト用の自己署名証明書へのパスを設定してあります。次節でその自己署名証明書を作成し、SSL化のテストを行います。
SSL化を自己署名証明書でテスト
SSL化、リダイレクト、リバースプロキシの設定がうまくいっているかどうかを確認するために、まずは自己署名証明書でテストしてみましょう[^ssl-certificate]。
[^ssl-certificate]: nginx で HTTPS をサポートする場合は、サーバ証明書がないと起動してくれません。
自己署名証明書は通称「オレオレ証明書」とも呼ばれています^ore-ore。本来サーバ証明書というのは公的な認証局によって署名してもらう必要があるのですが、その署名作業自体を自分でやってしまうからです。当然どこからもお墨付きをもらっていないので、自己署名証明書を使っているサイトに https でアクセスすると、ブラウザに「接続がプライベートではありません」というような警告が出てしまいます。それでも https でアクセスするテストにはなるので、まずはこれを試してみましょう。
通常、サーバ証明書は次の3ステップで作成します。
- サーバ秘密鍵の作成
- 認証局に対する署名要求書の作成
- 署名要求書に基づいた認証局による署名
なのですが、世の中にはこの作業を一回のコマンドでやってしまう「オレオレ証明書ワンライナー」という匠の技があります。ググるとたくさん出てくるのですが、今回は「karakaram-blog」というサイトで紹介されている下記コマンドを利用することにします。
ちなみに上記サイトでは3ステップによる証明書作成手順についても説明してくれているので、興味のある方は一読されるとよいかと思います。
具体的な手順は次のようになります。
sudo mkdir -p /etc/nginx/ssl
cd /etc/nginx/ssl
sudo openssl req -x509 -sha256 -nodes -days 3650 -newkey rsa:2048 -subj /CN=localhost -keyout private.key -out server.crt
/etc/nginx/ssl ディレクトリを作成して、そこで openssl コマンドを実行するわけですが、環境によっては上図のように MARKDOWN_HASHfa49ccde9ac6f09f1aa77e8200f799f2MARKDOWNHASH
というようなエラーが出ることがあります。この場合は、 /etc/ssl/openssl.cnf_ の MARKDOWN_HASH7afd45c563e7830f0102dc0970412a3bMARKDOWNHASH
のところをコメントアウトしてから再実行してやればよいようです。
を
に修正してから openssl コマンドを再実行。
今度はうまく行きました。/etc/nginx/sites-available/default_ に設定しておいた名前でファイルも作成されています。
sudo nginx -t
で nginx の設定ファイルが正しく記述されているかテストします。
ここで syntax is ok
, test is successful
にならずに、下図のように test failed
となったら、エラー内容を確認し、ドキュメントを参照して、エラーをつぶしてください。
エラーが無くなったら、 sudo service nginx reload
で設定ファイルをリロードします。
ブラウザから http://YOUR.SERVER.FQDN/ (ドメイン名は正式なものに置き換えてください)にアクセスしてみます。
このように、https にリダイレクトされて警告が表示されたら、「詳細設定」をクリックし、「{ドメイン名}に進む」をクリックします。
その後、いつもの下図画面が表示されればOKです。
もし、「リダイレクトされない」「404 Not found になる」などの現象が発生したら、まずは設定ファイルの YOUR.SERVER.FQDN のところが正しいドメイン名(FQDN)になっているか確認してください。とくに英大文字が混じっているとエラーになるようです[^fqdn-error]。
[^fqdn-error]: nginx の設定ファイルの if
文の条件のところで使っている =
演算子は、大文字・小文字を区別して比較しているためです。~*
という演算子を使うと、大文字・小文字を無視した正規表現で比較してくれます。 http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if を参照。
Let's Encrypt からSSLサーバ証明書を取得して本番稼動
「オレオレ証明書」によるテストは通ったので、いよいよ正式なサーバ証明書を取得して Webアプリを全世界に向けて公開してみましょう。
これまではテストだったので、80ポートや443ポートは自分のローカル環境に付与されているグローバルIPだけが通る状態でもよかったのですが、これから説明する「Let's Encrypt」で証明書を取得するためにはすべてのIPからのアクセスを受け付けるようにしておく必要があります[^all-ip]。あらためて、リモートサーバのファイアウォールあるいはセキュリティの設定が、80 と 443 については 0.0.0.0/0
を受け付けるようになっていることを確認しておいてください。
[^all-ip]: 原理的には Lets' Encrypt からのアクセスだけを通せばよいのですが、そんな面倒なことはやってられません。
Let's Encrypt とは
下図は https://letsencrypt.org/ja/ のトップ画像です。
これがすべてを語っています。公式には https://letsencrypt.org/ja/about/ に説明がありますが、
- フリー: 公式なサーバ証明書を無料で署名してくれます。
- 自動化: 後述の certbot というツールで自動的にサーバ証明書を作成し、 nginx などの設定ファイルに登録してくれます。
- オープン: 「自動的な発行・更新のプロトコルを、今後オープンスタンダードとして発行し、誰でも採用できるようにする」らしいです。
すばらしい組織と取り組みだと思います。ただいろいろと制限はあって、たとえば署名の有効期間は3か月となっており、それを超えて使用する場合は、都度、期間の更新処理を行う必要があります。とは言え、この更新処理自体も自動化されているので、たいした制限というわけではありません。
Let' Encrypt の仕組みに興味がある方は、 https://letsencrypt.org/ja/how-it-works/ を参照してください。
certbot を使って証明書を自動取得
Let's Encrypt は certbot というツールを提供しています。certbot は、証明書を発行してもらおうとしているサーバ内で実行することで、Let's Encrypt 側に対するリモートエージェントとして機能します。Let' Encrypt 側は、この certbot から送られてきた FQDN を使って対象ドメインをホストする Webサーバにアクセスします[^acme-challenge]。そして certbot と情報をやり取りすることによって、
[^acme-challenge]: http://your.server.fqdn/.well-known/acme-challenge/C-JNXCFGg4QsQvTlVjx6V_...
というようなファイルを取得しにきます。このファイルは、certbot によって作成されます。内容は、Let's Encrypt が送ってきたチャレンジ情報にサーバの秘密鍵で署名したものになります。Let's Encrypt側はこのファイルを取得した後、対象サーバの公開鍵を使って署名を検証します。
- DNSに登録されている FQDN が正しくそのサーバに割り当てられていること
- つまり certbot を実行している人がそのドメインの正当な所有者であること
を確認します。なので、あらかじめ DNS にサーバの FQDN を登録しておく必要があるわけですね。
背景がわかったところで、作業に入ります。まず https://certbot.eff.org/ にアクセスします。
このような画面が表示されるので、
- Software: 利用する Webサーバ(われわれの場合は nginx)
- System: 利用するサーバOS(われわれの場合は Ubuntu 18.04)
を選択します。するとその下に選択した環境でのインストール手順が表示されます。
snap
を使ってインストールするようです。ではこの手順に従ってやっていきましょう。
1. サーバにSSHでログインする
かつ、 sudo 権限を持っている必要があります。sudo についてはすでに nginx のインストールで確認済みですね。
2. snapd をインストールする
Ubuntu 18.04 には snap(d) がデフォルトでインストールされています。もし未インストールであれば、「install snapd」の指示に従ってインストールしてください。
3. snapd をアップデートする
下記コマンドを実行し、 snapd をアップデートします。
sudo snap install core; sudo snap refresh core
OKのようです。
4. 古い Certbot の削除
以前に apt などを使って OSパッケージとしてインストール済みの Certbot があれば、あらかじめそれを削除しておきます。これから snap で導入するバージョンとの競合を避けるためですね。今回は初めてのインストールなので、ここはスキップします。(以前にOSバッケージでインストールしたことがあれば、ここの指示に従って削除しておいてください。)
5. Certbot のインストール
下記コマンドを実行し、 certbot をインストールします。
sudo snap install --classic certbot
OKのようです。
6. certbot コマンドの準備
certbot コマンドを shell から呼び出せるようにしかるべき場所にシンボリックリンクを張ります。
sudo ln -s /snap/bin/certbot /usr/bin/certbot
OKです。
7. certbot を実行する
一番重要な箇所に差しかかりました。ここでは、
- 証明書の取得と nginx の設定ファイルへの登録をやる
- 証明書の取得だけをやる
のどちらかを実行します。ここでは前者のほうを実行して、nginx の設定ファイルも書き換えてもらうことにします。
まず、念のため現状の設定ファイル /etc/nginx/sites-available/default を別の名前で保存しておきましょう。
sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.back
そして次のコマンドを実行します。
sudo certbot --nginx
注: この後、設定ファイルの書き換えでいったん失敗するシナリオになっています。もしそれを避けたい場合は、代わりに sudo certbot certonly --nginx
を実行してください。証明書の取得だけを行うので、その後自分で設定ファイルを修正することになります^certbot-failure。
まず、連絡用のメールアドレスを聞いてきますので、自身のメールアドレスを入力します。これは gmail アドレスでも何でもOKです。
「https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf の利用規約をお読みください。ACMEサーバ (https://acme-v02.api.letsencrypt.org/directory) に登録するには、同意が必要です。」
と言っています(by DeepL)。A
を入力して同意します。
最初の証明書の発行に成功したら、電子フロンティア財団とメールアドレスを共有していただけますか?
Certbotを開発していますか?ウェブの暗号化、EFFのニュース、キャンペーン、デジタルの自由をサポートする方法についてのメールをお送りしたいと思います。
と言っています(by DeepL)。とくに情報を必要としないのであれば MARKDOWN_HASH8d9c307cb7f3c4a32822a51922d1ceaaMARKDOWNHASH
を入力します。
おや、ちょっと様子が変です。設定ファイルに名前が見つからないと言っています。とりあえずドメイン名を入力します。不安が募ります。
失敗してしまいました。。。どうやら、/etc/nginx/sites-available/default_ の MARKDOWN_HASHcf1e8c14e54505f60aa10ceb8d5d8ab3MARKDOWNHASH
ブロックに明示的に対象ドメイン名を記述しておく必要があるようです。 /etc/nginx/sites-available/default_ をエディタで開いて、ssl 用の server ブロックのところの server_name _
を server_name YOUR.SERVER.FQDN
に修正してください[^default-host]。YOUR.SERVER.FQDN のところは実際のドメイン名に置換します。以下、 diff の出力です。
気をとりなおして、もう一度実行します。
[^default-host]: 「6.1 名前によるバーチャルサーバ」によると、「もしこれらの server_name と Hostフィールドの名前が一致 しなかった場合には、デフォルトのサーバでリクエストは処理されます。 デフォルトのサーバは、最初に定義されているサーバになります。」ということなので、YOUR.SERVER.FQDN 以外のホスト名でアクセスされてもこの server ブロックが参照されることになります。
sudo certbot --nginx
nginx の設定ファイルに記述したドメイン名が表示されました(ここでは黄色の大文字で表現しています)。設定ファイルに複数のドメイン名が記述されていれば、ここにも複数の候補が表示されるものと思われます。今回は1つしか記述していないので、それだけが候補になっています。その番号1
を入力します。
前回失敗したときに、すでにドメイン名を入力しているので、証明書自体は作成されているようです。作成したばかりなので、当然まだ更新の時期ではありません。「で、どうするよ?」と聞かれているのですが、ここでは 1
を選択[^cert^rate-limit]して作成済みの証明書を nginx の設定ファイルに登録してもらうことにします。
Congratulations! You have successfully enabled ということで今度は成功したようです。
[^cert^rate-limit]: 2
を選択すると証明書の再作成だけを行うことになるのですが、Let's Encrypt には、ある一定期間は同じFQDNの証明書の再作成ができない、という制限があります。今回は作成したばかりなので、2
を選択した場合は、何らかの警告が出てそのまま処理が終了すると思われます。
書き換えられた /etc/nginx/sites-available/default を見てみます。
# managed by Certbot
というのが certbot によって書き換えられた部分となります[^managed-by-certbot]。秘密鍵およびサーバ証明書へのパスが書き換えられています。
[^managed-by-certbot]: certbot はこのコメントを手がかりにしてロールバックすることができるようです。
8. nginx のリロード
sudo nginx -t
で確認します。
OKなので、sudo service nginx reload
します。
最後のテスト
nginx も問題なく設定ファイルをリロードしてくれました。
ブラウザから http://YOUR.SERVER.FQDN/ にアクセスします。
「セキュリティ保護」などは表示されずに正しく「Chartの世界にようこそ!」の画面が表示されました。つまりインストールした証明書が公的な認証機関によって署名されたものであると認識されたわけです。
前述したように、今回作成した証明書の有効期間は3か月です。期限切れが近づくと、登録したメールアドレスにお知らせが届くかと思います。その場合は、まず sudo certbot renew --dry-run
でテスト実行してみてください^renew-dry-run。これで問題がなければ sudo certbot renew
を実行して本当に更新します。
今回は証明書の作成と同時に nginx の設定ファイルの修正も行いましたが、設定ファイルの変更部分を見ると、実質的に ssl_certificate
と ssl_certificate_key
のパスを修正しているだけのようです。なので、次に certbot を使用する際は証明書の取得だけを行って、 nginx の設定ファイルへの登録は自分でやったほうが失敗する可能性が減ってよいでしょう。
おわりに
以上で本連載記事を終わります。
今回の記事では Blazor の話はほとんど出てきませんでしたが、いずれは Blazor の WebAssembly 版についても調査したいと思っています。というか、Blazor ってむしろそっちが本命?ある程度まとまったらまた記事にしたいですね。