Corredor

ウェブ、プログラミングの勉強メモ。

ラズパイ4のマウスの動きがモッサリしているのを直す

ラズパイ4をイジり始めてから気になっていたこと。なんだかマウスの動作がモッサリしている。設定画面で速度をイジったりしても直らない。

調べてみたら、以下のような対処法が出てきた。

$ sudo vi /boot/cmdline.txt

cmdline.txt にはアレコレ設定が書かれているのだが、最後尾に

usbhid.mousepoll=0

と追記して再起動する。ちょっとマシになったみたい。

Raspbian OS 標準の Vim は機能が少ないので入れ直す

ラズパイ4を触っていたら、Vim の動作が微妙だった。調べてみたところ、機能が少ない Tiny 版が入っているようだったので、クリップボード共有などが有効なモノを入れ直す。

以下のサイトを参考に。

# 今インストールされている Vim を確認する
$ dpkg -l | grep vim
# 恐らく vim-tiny がインストールされているはず

# 今インストールされている Vim をアンインストールする
$ sudo apt remove -y --purge vim-common vim-tiny

# 通常の Vim をインストールする
$ sudo apt install -y vim

コレで OK。

nginx のリバースプロキシを Docker-Compose で試してみる

nginx のリバースプロキシ機能を使って、複数の Node.js サーバへの通信を、単一ドメインで受けてみたい。

リバースプロキシとは

リバースプロキシとは、クライアント側に用意するプロキシと違って、サーバ側に存在してリクエストを仲介するプロキシのこと。

何がリバースなんや?というと、通常クライアント側にいるプロキシは「内々から外へ出ていく」通信を仲介するが、コチラは「外から内々に入っていく」通信を仲介するので、逆だよねってことでリバースらしい。

リバースプロキシで何ができるの?

リバースプロキシによってどういう通信経路ができるかというと、以下のような感じ。

前提条件として、

  • http://localhost:3001/
  • http://localhost:3002/

の2つのポートで、それぞれ何らかのサーバを動かしていることとする。Node.js・Express サーバでも、Python・Flask サーバでもなんでも良い。

これらのウェブアプリにアクセスするには、上のようにそれぞれポート番号を指定してアクセスしても良いが、ドメインは1つに集約したい、ポート番号をイチイチ指定したくない、という時に、リバースプロキシが前段に立ってやる。

リバースプロキシサーバ (= 今回は nginx が担う) は

  • http://localhost/

と80番ポートで待ち構えている。そして、

  • http://localhost/app-1/ へのアクセス → http://localhost:3001/ に転送
  • http://localhost/app-2/ へのアクセス → http://localhost:3002/ に転送

という風にリクエストをハンドリングしてくれるようになる。この時、app-1/ とか app-2/ とかいう階層指定が転送先では削られていることに留意。

  • http://localhost/app-1/examples/test.html へのアクセス → http://localhost:3001/examples/test.html に転送
    • (転送先の3001番ポートの方は app-1/ という階層が削られている)
    • (パスを削らずに転送する設定も可能ではある)

Docker-Compose で開発環境を作る

ふむふむなるほど、nginx でリバプロを立てるとこんなことが出来るのね、じゃあやってみよう。

nginx は Homebrew なんかでもインストールできたりするが、裏に用意するアプリケーションサーバ含めて開発環境・実行環境を構築していくのはちょっと面倒だ。そこで今回は、Docker-Compose を利用して、nginx および Node.js 環境をそれぞれ立ち上げることにしたい。

デモプロジェクトとコード全量

今回作ったデモプロジェクトは、以下の GitHub リポジトリで公開している。細かなコードはコチラを参考にしてほしい。

github.com

プロジェクト構成

プロジェクト構成は次のようにする。

【プロジェクトルート】
├ docker-compose.yml
├ nginx/
│ ├ Dockerfile-nginx
│ ├ default.conf
│ └ html/
│    └ index.html
├ app-1/
│ ├ Dockerfile-app-1
│ └ src/
│    ├ index.js
│    └ example.html
└ app-2/
   ├ Dockerfile-app-2
   └ src/
      ├ index.js
      └ example.html

ルートの docker-compose.yml が、nginx/app-1/app-2/ それぞれのディレクトリを3つのコンテナとして立ち上げる。

nginx/ ディレクトリはそのものズバリ、nginx サーバの設定を持っている。Dockerfile-nginx という Dockerfile で、nginx ベースのイメージを用意している。

app-1/app-2/ ディレクトリは、それぞれ Node.js サーバというテイ。今回は簡単にするため package.json を省いている。

  • src/index.js は、http モジュールを使って簡易サーバを立てている
  • リクエストすると src/example.html の内容をレスポンスする作り

実際は package.json があって、Dockerfile で npm install しておいて npm start でサーバを起動するような作りになるだろうか。

それぞれの Dockerfile や docker-compose.yml の設定はひとまず後にして、まずは本命である nginx の設定を作っていこう。

nginx の設定を書いていく

nginx の設定ファイル、default.conf を用意して、リバースプロキシ設定を書いていく。

server {
  listen       80;
  server_name  localhost;
  root         /var/www/html;
  
  location /app-1/ {
    proxy_set_header  Host                $host;
    proxy_set_header  X-Real-IP           $remote_addr;
    proxy_set_header  X-Forwarded-Host    $host;
    proxy_set_header  X-Forwarded-Server  $host;
    proxy_set_header  X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_pass        http://app-1:3001/;
  }
  
  location /app-2/ {
    proxy_set_header  Host                $host;
    proxy_set_header  X-Real-IP           $remote_addr;
    proxy_set_header  X-Forwarded-Host    $host;
    proxy_set_header  X-Forwarded-Server  $host;
    proxy_set_header  X-Forwarded-For     $proxy_add_x_forwarded_for;
    proxy_pass        http://app-2:3002/;
  }
}

唐突だが、リバプロの設定はこんな感じ。location /app-1/ {} という一つのブロックで、一つのアプリサーバに対するリバプロ設定が書かれている。

  • location のパスはスラッシュ / を末尾に書くこと。でないと //hoge といったようにスラッシュが2重に付いて転送されてしまう
  • 一連の proxy_set_header はほぼお決まりなのでそのまま書く
  • proxy_pass が転送先 URL を指定する場所。URL の末尾にスラッシュを書くかどうかで転送のされ方が変わるので注意
    • URL の末尾に / を付けると、/app-1/hoge へのリクエストは :3001/hoge に転送される
    • URL の末尾に / を付けないと、/app-1/hoge へのリクエストが :3001/app-1/hoge に転送される

nginx のルートパスは root プロパティを指定して、/var/www/html/ ディレクトリ内のファイルを返すようにしておく。サーバにインストールした nginx の場合、/usr/share/nginx/html/ をデフォルトで参照する場合もあったりする。パスだけ知っておくと良いかも。

あと、server_name 部分は、実行環境によっては以下のように書いたりもするかな。

server_name  $hostname '' 【グローバル IP を書いたり】;

認識してほしいサーバ名を複数書いたり、'' と空白を書いたり。

HTTPS に対応する際は、location 内に CORS 設定を書く場合もあるかな。転送先の Node.js サーバ側でヘッダを付与しても良い。どちらかで指定しておけば良いようだ。Allow-Origin がダブらないよう注意。

location /app-1/ {
  add_header  Access-Control-Allow-Origin       '*'  always;
  add_header  Access-Control-Allow-Methods      'GET, POST, PUT, DELETE, OPTIONS';
  add_header  Access-Control-Allow-Headers      '*';
  add_header  Access-Control-Allow-Credentials  true;
}
// Express サーバに書くイメージ。nginx 側とどちらかで指定すれば良い
app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin'     , '*');
  res.header('Access-Control-Allow-Methods'    , 'GET, POST, PUT, DELETE, OPTIONS');
  res.header('Access-Control-Allow-Headers'    , '*');
  res.header('Access-Control-Allow-Credentials', true);
  next();
});

CORS 設定をしたとき、なぜかルートパス /app-1/ への Ajax リクエストだけ Allow-Origin が効かなくて困ったのだが、直し方が分からず諦めた。普通に遷移するとアクセスはできるので、CORS だけ上手く効かないみたい。よく分からん。

nginx の設定ファイルの書き方はイマイチ分からなくて、それでいて自由度もあって難しいな…。

今回は Docker で動かすので以下の操作は実際には行わないが、どこかのサーバ上にインストールした nginx を使っている場合は、以下のように nginx を再起動したりして、設定ファイルの変更を反映したりする。

# default.conf ではなく nginx.conf がデフォルトの設定ファイルな場合もある (バージョンによるもの?)
$ vi /etc/nginx/default.conf

# 設定を再読込したり
$ service nginx reload
# nginx を再起動したり
$ service nginx restart
# 稼動状況を確認したり
$ service nginx status

nginx コンテナを立ち上げるための設定

設定ファイルができたので、コレを nginx コンテナとして立ち上げるための設定を書いていく。

  • ./nginx/Dockerfile-nginx
FROM nginx:1.17

COPY ./default.conf /etc/nginx/conf.d/default.conf
COPY ./html/        /var/www/html/

Dockerfile がやることは、ベースイメージの指定と設定ファイルのコピーだけ。

html/ ディレクトリはサーバルートにアクセスした時に index.html を返せるように、コピーしている。

この Dockerfile により、予めファイルをコピーして Docker イメージをビルドして nginx サーバを起動するので、設定ファイルをココで書き換えても、起動中の Docker コンテナの設定は変わらない点に注意。変更の度に Docker イメージの再ビルド (COPY) と再起動が必要になる。まぁ頻繁にイジらないし良いか。

  • ./docker-compose.yml
version: '3'
services:
  nginx:
    build:
      # Dockerfile の保存場所を指定する
      context   : ./nginx/
      # Dockerfile 名を指定する
      dockerfile: Dockerfile-nginx
    # イメージ名
    image         : practice-nginx
    # コンテナ名
    container_name: practice-nginx
    # ポートフォワード
    ports:
      - 80:80

Docker-Compose の設定ファイルはこんな感じ。build プロパティで ./nginx/Dockerfile-nginx を参照できるようにしておく。imagecontainer_name は任意に。最後に ports で80番ポートをホスト側に公開するようにしておく。

コレで $ docker-compose up --build コマンドを叩けば、nginx サーバが起動し http://localhost/ にアクセスすると index.html の内容が返ることが確認できる。まだ /app-1//app-2/ を用意していないので、リバプロの動作確認はできていない。

適当な Node.js サーバを作る

続いて、リバプロの転送先となる適当な Node.js サーバを作っておく。今回は本筋ではないので、雑にコーディングする。

  • ./app-1/src/index.js
require('http').createServer((_req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
  res.end(require('fs').readFileSync(require('path').join(__dirname, './example.html'), 'utf-8'));
}).listen(3001);

最後に指定しているように、3001番ポートで公開する設定。app-2 の方はこのポート番号だけ変えていて、3002番ポートで公開するようになっている。

レスポンスに使用する example.html は、区別がつくようにサーバ名が書いてあるだけの適当なファイル。なんでも良い。

  • ./app-1/Dockerfile-app-1
FROM node:14.3

COPY ./src/ /src/
WORKDIR /src/
CMD ["node", "/src/index.js"]

Dockerfile は、Node.js ベースのイメージに資材を配置して起動しているだけ。package.json を用意した場合は npm install などを事前に行っておく感じかな。./app-2/Dockerfile-app-2 も全く同じ内容で良い。

  • docker-compose.yml
version: '3'
services:
  nginx:
    # 前述のとおり・省略
  
  # app-1 : ビルド済のコンテナを公開する。ファイルの変更は検知できない
  app-1:
    build:
      context   : ./app-1/
      dockerfile: Dockerfile-app-1
    image         : practice-app-1
    container_name: practice-app-1
  
  # app-2 : ボリュームマウントすることで静的ファイルの変更を反映できるようにする
  app-2:
    build:
      context   : ./app-2/
      dockerfile: Dockerfile-app-2
    image         : practice-app-2
    container_name: practice-app-2
    # ボリュームマウントして実行する
    volumes:
      - ./app-2/src:/src
    working_dir: /src/
    # node コマンドのパスを認識させるため sh -c が必要
    command: [sh, -c, node /src/index.js]
    # 直接公開する場合は以下を書く
    #ports:
    #  - 3002:3002

Docker-Compose 設定ファイルには app-1app-2 という services が追加されている。この名前が、nginx の default.conf 内に指定した http://app-1:3001/ などのサーバ名に対応することになる。

シンプルにビルドした Docker イメージを立ち上げるだけなら、app-1 の書き方で app-2 も書いてやれば良い。

app-2 の書き方の方は、./app-2/Dockerfile-app-2 で指定した COPY・WORKDIR・CMD 設定を上書きしていて、ボリュームマウントして動かしている。コレにより、./app-2/src/example.html の内容を書き換えた時に即座に反映されるようになる。index.js 側は書き換えても反映されないので、完全なライブリロード開発ではないが。

こんな感じでボリュームマウントを使ったり、起動コマンドを

  • command: [sh, -c, npm run dev]

などと開発用コマンドに変えてやったりすれば、もう少し開発がしやすくなると思われる。

なお、app-1 が公開している3001番ポート、app-2 が公開している3002番ポートは、ホスト側から直接アクセスはできない。直接アクセスしたい場合は ports プロパティをそれぞれに書いてやり、ポートフォワードしてやること。今回は nginx が転送するので、基本的には3001・3002番ポートをホストに公開する必要はない。

動かしてみる

それでは実際に動かしてみよう。

$ docker-compose up --build

常に上のコマンドを使用し、Dockerfile を都度ビルドすることにする。コレにより設定ファイルの変更が反映された状態でコンテナ群が起動する。

起動したら、ホスト側で次の URL にアクセスして動作確認してみよう。

  1. http://localhost/
    • ./nginx/html/index.html の内容が返ること
    • default.confroot プロパティが効いていることが確認できる
  2. http://localhost/app-1/
    • ./app-1/src/example.html の内容が返ること
    • アプリケーションサーバ app-1 が3001番ポートで起動しており、nginx がリバースプロキシしていることが確認できる
  3. http://localhost/app-2/
    • ./app-2/src/example.html の内容が返ること
    • アプリケーションサーバ app-2 が3002番ポートで起動しており、nginx がリバースプロキシしていることが確認できる

それぞれちゃんと動作すれば OK。

502 Bad Gateway が出た場合は、アプリサーバがリスンしているポートが何になっているか、default.conf で指定しているポート番号が合っているか、などを確認してみよう。

以上

こんな感じで、nginx によるリバースプロキシの設定方法を確認できた。

Docker を使えばホストマシンへのインストール作業が要らなくなるし、Docker-Compose を使うことで複数のサーバを起動したりする手間が省ける。

仲介する・ラップする要素が増えると理解するのが難しくなるところもあるが、マシンに直接インストールして設定したりしていると、何がどうなっているのか整理が付かなくなる場合もあるので、こうしてクリーンな環境で試せるのは良いことだろう。

nginx実践入門 WEB+DB PRESS plus

nginx実践入門 WEB+DB PRESS plus

VSCode + SSH 開発。Remote SSH 拡張機能を使ってみた

VSCode Remote Development Extension Pack 概説第3弾。

  • 開発用の Docker コンテナを立ち上げる Remote Containers
  • WSL 環境を VSCode で開く Remote WSL

そして今回は、SSH 接続先サーバのディレクトリをホスト側の VSCode で開けるという前代未聞の拡張機能、Remote SSH を紹介する。

Remote SSH を使うと何が良いのか

Remote SSH 拡張機能は、SSH で接続したサーバの、任意のディレクトリを、ホスト側の VSCode で開けるというモノ。

SSH 接続先サーバが GUI を持たず、VSCode をインストールしていない状況でも、ホスト側の慣れ親しんだ VSCode 環境を使って、SSH 接続先を直接編集できるというワケだ。

この拡張機能により、AWS EC2 上で直接開発をしてみたり、本番稼動しているソースコードを読みやすい環境で読んだりすることができる。ホストマシンにアレコレインストールする必要がなくなるという意味でも便利だ。

Windows で使おうとしたらエラー発生。アクセス権を編集する

早速使ってみるが、SSH 接続しようとしたところでエラーが発生した。事象の再現手順と解消方法を紹介していく。

f:id:neos21:20200605190317p:plain

まず VSCode のウィンドウ左下の青いアイコンを押下し、

  • Remote-SSH: Connect to Host...

を選択する。

f:id:neos21:20200605190322p:plain

~/.ssh/config に定義した接続先のホスト名がリスト表示されるので、接続したいサーバを選択する。

f:id:neos21:20200605190326p:plain

ウィンドウが開き直され、「Select the platform of the remote host」という問が出てくる。接続先サーバは Linux マシンなので、「Linux」を選択する。

f:id:neos21:20200605190330p:plain

すると上のようなエラーが出た。

  • Bad owner or permissions on C:\\Users\\Neo/.ssh/config
  • プロセスが、存在しないパイプに書き込もうとしました。

こんな感じのエラーメッセージが出てしまった。ひとまず「Close Remote」ボタンを押して中止する。

対処法を色々調べたところ、次の記事で紹介されていた権限設定で直せた。

まずはエクスプローラで次の SSH フォルダのプロパティを開く。

  • C:\Users\【ユーザ名】\.ssh

f:id:neos21:20200605190334p:plain

.ssh フォルダのプロパティ」で「セキュリティ」タブを選択し、「詳細設定」ボタンを押す。

f:id:neos21:20200605190339p:plain

そしたら上のスクショのようになるよう、アクセス許可を設定していく。

  1. まずは下部の「継承の無効化」ボタンを押下する。確認ダイアログが表示されるので、「継承されたアクセス許可をこのオブジェクトの明示的なアクセス許可に変換します。」を選ぶ
  2. 「Users」や「Authenticated Users」が存在したら「削除」し、一旦「Administrators」と「System」だけにしておく。これらは「アクセス:フルコントロール」「継承元:なし」となっていること
  3. 「追加」ボタンを押下し、「プリンシパルの選択」を選ぶ
  4. 自分の Windows ユーザ名を入力し、「名前の確認」ボタンを押下し、OK ボタンを押す
  5. 「アクセス許可エントリ」は「フルコントロール」を選択し全てにチェックが入る状態にする

ということで、上のスクショのような状態にする。

続いて、SSH Config ファイルのアクセス権を調整する。

  • C:\Users\【ユーザ名】\.ssh\config

f:id:neos21:20200605190344p:plain

コチラも、上のスクショのように設定を変更していく。~/.ssh/ ディレクトリに設定した権限と一箇所だけ異なり、Windows ユーザに与えた権限は「フルコントロール」ではなく「変更」とすること。

  1. 「継承の無効化」ボタンを押下。確認ダイアログは「継承されたアクセス許可をこのオブジェクトの明示的なアクセス許可に変換します。」を選ぶ
  2. 「Users」や「Authenticated Users」が存在したら「削除」し、一旦「Administrators」と「System」だけにしておく。これらは「アクセス:フルコントロール」「継承元:なし」となっていること
  3. 「追加」ボタンを押下し、「プリンシパルの選択」を選ぶ
  4. 自分の Windows ユーザ名を入力し、「名前の確認」ボタンを押下し、OK ボタンを押す
  5. 「アクセス許可エントリ」で、一旦「フルコントロール」を選択し全てにチェックを入れたら、「フルコントロール」のチェックだけ再度外すことで、アクセス許可が「変更」になるようにする

コレで上のスクショのようになるはず。

ココまでアクセス権を変更したら、今度は正しく SSH 接続できるようになっているはず。

再接続

前述のとおり、VSCode のコマンドパレットより

  • Remote-SSH: Connect to Host...

を選択し、~/.ssh/config に記載されているホストから接続したいホストを選択する。

無事 SSH 接続ができると、次のスクショのようにウィンドウ左下が「SSH: 【ホスト名】」といった表示に切り替わっている。

f:id:neos21:20200605190349p:plain

VSCode ターミナルを開くと SSH 接続先サーバのプロンプトが開いていることが分かるだろう。

フォルダツリーから「フォルダーを開く」を選択すると、以下のように SSH 接続先のディレクトリが選べるようになる。

f:id:neos21:20200605190355p:plain

ディレクトリを選択するとそのディレクトリが VSCode で開かれる。

f:id:neos21:20200605190359p:plain

Remote Containers や Remote WSL 拡張機能と同様に、SSH 接続先サーバと同期した状態になっているので、VSCode 上で「新しいファイル」を作って保存してやると、SSH 接続先サーバにそのファイルが置かれているというワケ。

以上

ホストマシンの VSCode 環境から、任意のリモートサーバを開発環境・実行環境として利用できる Remote SSH。

前回、前々回に紹介した Remote Containers、Remote WSL とともに、リモート開発を促進していける拡張機能なので、今後もキャッチアップしていきたい。