Docker + Rails 6 + PostgreSQL による Web アプリケーション開発の始め方(Windows版)

要旨

  • Docker Desktop for Windowsを用いてRuby on Rails 6.0によるWebアプリケーション開発を行う手順をできるかぎり簡潔にまとめた。
  • Docker Composeを用いてデータベースサーバーのコンテナとWebサーバーのコンテナを管理する方法についても解説する。

この記事は、macOSUbuntuを対象にしたDocker + Rails 6 + PostgreSQL による Web アプリケーション開発の始め方Windows向けに書き直したものです。一部、内容に重複があります。

対象OS

Windows 10 Pro (32bit) および Windows 10 Home では Docker Desktop for Windows が動作しません。

注意事項

この記事は2019年2月16から18日の間に書かれています。Ruby on Rails 6.0のリリースが予定されているのは2019年4月末です。現時点での最新版である Rails 6.0.0.beta1 に基づいています。今後、内容に訂正が必要となる可能性が十分にあります。

はじめに

この記事ではDocker Desktop for Windowsと呼ばれるソフトウェア(以下、「Docker」と呼ぶ)を用いてRuby on Rails 6の開発環境を構築する手順を紹介します。Dockerをインストールする対象のOS(ホストOS)はWindows 10 Pro (64bit)とします。

ゲストOSにはAlpine Linuxを採用しました。インストールに必要なストレージ容量が非常に小さいことが特長です。

Dockerを用いて構築されたゲストOSの環境はコンテナと呼ばれます。この記事では、データベースサーバー(PostgreSQLサーバー)のためのコンテナとWebサーバー(Railsアプリケーション)のためのコンテナ、計2個のコンテナを作ります。これらのコンテナをうまく組み合わせるためにDocker Composeと呼ばれるソフトウェアを使用します。

コンテナを実行するのに必要なファイルシステムディレクトリ、ファイル、メタ情報の集合体)をイメージと呼びます。イメージはWebサービスDocker Hubを通じて配布されます。

また、この記事では設定ファイル等の取得にGitを使用します。あらかじめGit for Windowsをインストールしておいてください。

Docker Hubアカウントの取得

Docker自体のインストーラやDockerイメージを取得するためには、Docker Hubのアカウントが必要となります。メールを受け取れるアドレスがあれば、無料で取得できます。未取得の方は、次のURLを訪問して登録してください。

https://hub.docker.com/signup 

Docker Hubにログインしてから次の手順に進んでください。

Docker Desktop for Windowsのインストール

https://hub.docker.com/editions/community/docker-ce-desktop-windows から Docker Desktop for Windowsインストーラをダウンロードしてインストールしてください。Dockerと同時にDocker Composeもインストールされます。

@sikkim 氏がQiitaに書いた記事 https://qiita.com/sikkim/items/bfc79561b16b2c13caba に詳しいインストール手順が載っていますので、参考にしてください。

※ DockerToolboxという名前の類似のソフトウェアが存在しますが、別物です。混同しないでください。

【重要な注意】 VirtualBox との共存

  • Docker for WindowsをインストールするとVirtualBoxが動作しなくなるケースがあります。
  • Docker for Windowsを使用する場合、Windows 10の仮想化システム「Hyper-V」を有効にする必要がありますが、バージョン6未満のVirtualBoxはこの「Hyper-V」と共存できません。
  • 2018年末にリリースされたVirtualBox 6.0は「Hyper-V」と共存できるとされていますが、ハードウェアにより動作する場合と動作しない場合あるようです。

【参考】

Dockerへのログイン

Docker Desktop for Windowsのインストールが完了した後、いったんWindowsからサインアウトする必要があります。サインインすると、DockerのWelcomeウィンドウが現れるので、DockerHubのIDとパスワードでログインします。

Docker Compose用の設定ファイル群の取得

コマンドプロンプト(cmd.exe)を開き、以下のコマンドを実行します。

> git clone https://github.com/oiax/rails6-compose.git
> cd rails6-compose
> cp docker-compose.win.yml docker-compose.override.yml

ボリュームの作成

> docker volume create --name pgdata

ボリュームの作成に関する注記

  • 複数の Rails アプリケーション開発プロジェクトを並行して進める場合、上記コマンドの pgdata の部分をそれぞれ別の名前、例えば pgdata1pgdata2 で置き換える必要があります。
  • その場合、テキストエディタdocker-compose.override.yml を開き、pgdata と書かれている箇所を pgdata1 あるいは pgdata2 で書き換えてください。

コンテナ群の構築・起動

> docker pull oiax/rails6-deps:latest
> docker-compose up -d

オプション -d により、コンテナ群が detached mode で起動します。すなわち、コンテナ群がバックグランドで動き始めます。

ターミナルに次のようなメッセージが表示されれば OK です。

Starting rails6compose_db_1 ... 
Starting rails6compose_db_1 ... done
Starting rails6compose_web_1 ... 
Starting rails6compose_web_1 ... done

Web コンテナにログイン

> docker-compose exec web bash

※ ログアウトするには exit コマンドを実行するか、Ctrl-D キーを入力してください。

コンテナ群の停止

> docker-compose stop

コンテナ群の破棄

> docker-compose down

ボリュームの破棄

> docker volume rm pgdata

Docker + Rails 6 + PostgreSQL による Web アプリケーション開発の始め方

要旨

  • Dockerを用いてRuby on Rails 6.0によるWebアプリケーション開発を行う手順をできるかぎり簡潔にまとめた。
  • Docker Composeを用いてデータベースサーバーのコンテナとWebサーバーのコンテナを管理する方法についても解説する。

対象OS

Windows 10 ProでもDockerを用いてRails開発を行うことができますが、用語や手順で相違点が多いので今回は対象外とします。

【更新】 Windows編を書きました。

注意事項

この記事は2019年2月16から18日の間に書かれています。Ruby on Rails 6.0のリリースが予定されているのは2019年4月末です。現時点での最新版である Rails 6.0.0.beta1 に基づいています。今後、内容に訂正が必要となる可能性が十分にあります。

はじめに

この記事ではDockerと呼ばれるソフトウェアを用いてRuby on Rails 6の開発環境を構築する手順を紹介します。Dockerをインストールする対象のOS(ホストOS)は、macOSUbuntu 18.04 (64bit)とします。

ゲストOSにはAlpine Linuxを採用しました。インストールに必要なストレージ容量が非常に小さいことが特長です。

Dockerを用いて構築されたゲストOSの環境はコンテナと呼ばれます。この記事では、データベースサーバー(PostgreSQLサーバー)のためのコンテナとWebサーバー(Railsアプリケーション)のためのコンテナ、計2個のコンテナを作ります。これらのコンテナをうまく組み合わせるためにDocker Composeと呼ばれるソフトウェアを使用します。

コンテナを実行するのに必要なファイルシステムディレクトリ、ファイル、メタ情報の集合体)をイメージと呼びます。イメージはWebサービスDocker Hubを通じて配布されます。

また、この記事では設定ファイル等の取得にGitを使用します。ホストOSに合わせて、最新のGitをインストールしてください。

Docker Hubアカウントの取得

Docker自体のインストーラやDockerイメージを取得するためには、Docker Hubのアカウントが必要となります。メールを受け取れるアドレスがあれば、無料で取得できます。未取得の方は、次のURLを訪問して登録してください。

https://hub.docker.com/signup 

Docker Hubにログインしてから次の手順に進んでください。

Docker/Docker Composeのインストール

macOSの場合

https://hub.docker.com/editions/community/docker-ce-desktop-mac から Docker Desktop for Macインストーラをダウンロードしてインストールしてください。Dockerと同時にDocker Composeもインストールされます。

Ubuntuの場合

ターミナルで以下のコマンド群を順に実行し、Dockerをインストールします。

$ sudo apt-get update
$ sudo apt-get install -y \
    apt-transport-https \
    ca-certificates \
    curl \
    gnupg-agent \
    software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
$ sudo apt-get update
$ sudo apt-get install -y docker-ce docker-ce-cli containerd.io

詳しくは https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-using-the-repository を参照してください。

さらに、以下のコマンド群を順に実行し、Docker Composeをインストールします。

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" \
    -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

1.23.2 の部分は、Docker Composeのバージョン番号です。

詳しくは、https://docs.docker.com/compose/install/#install-compose を参照してください。

$ sudo gpasswd -a $USER docker

Ubuntuからログアウトし、ログインし直します。

docker login

ターミナルで次のコマンドを実行し、Docker Hubにログインします。

$ docker login

Docker Compose用の設定ファイル群の取得

$ git clone https://github.com/oiax/rails6-compose.git
$ cd rails6-compose

コンテナ群の構築・起動

$ setup.sh
$ docker-compose up -d

オプション -d により、コンテナ群が detached mode で起動します。すなわち、コンテナ群がバックグランドで動き始めます。

ターミナルに次のようなメッセージが表示されれば OK です。

Starting rails6compose_db_1 ... 
Starting rails6compose_db_1 ... done
Starting rails6compose_web_1 ... 
Starting rails6compose_web_1 ... done

【参考】 setup.sh の中身

setup.sh は次のような bash スクリプトです。

#!/bin/bash
set -eu

docker pull oiax/rails6-deps:latest

case "$OSTYPE" in
  darwin*)
    docker-compose build web
    ;;
  linux*)
    docker-compose build --build-arg UID=$(id -u) --build-arg GID=$(id -g) web
    ;;
  *)
    echo "Unknown OS Type: $OSTYPE"
    ;;
esac

まず、docker pull コマンドでベースとなるイメージの最新版を取得しています。

続いて、docker-compose build コマンドで Web コンテナを構築します。このとき、ホスト OS とコンテナの間でソースコード等を共有するときに、ファイルやディレクトリの所有権に関する問題が発生しないように --build-arg オプションを不可しています。この問題は Linux 版の Docker で発生しますが、macOS 版では発生しません。

参考資料:

コンテナ群の状態の確認

$ docker-compose ps

コンテナ群が正常に起動していれば次のような結果が出力されます。

       Name                      Command              State           Ports
------------------------------------------------------------------------------------
rails6compose_db_1    docker-entrypoint.sh postgres   Up      5432/tcp
rails6compose_web_1   /bin/sh                         Up      0.0.0.0:3000->3000/tcp 

Webコンテナへのログイン

$ docker-compose exec web bash

develユーザーとしてAlpine Linuxにログインします。パスワードなしでsudoを実行する権限が与えてあります。

Rails 開発に最低限必要と思われるパッケージ群は入っていますが、もし足りないパッケージがあれば apk コマンドでインストールしてください。

Railsアプリケーションの開発の要点

  • rails コマンドや bundler コマンドを用いた各種作業は、Webコンテナにログインして行います。
  • ソースコードの編集はホストOS側のテキストエディタで行います。

https://github.com/oiax/rails6-compose/blob/master/RAILS.md に具体的な操作例がありますので、参考にしてください。

Rails アプリケーションの起動

ホスト OS のターミナルで、次のコマンドを実行します。

$ docker-compose exec web bash -c 'cd /apps/myapp; bin/webpack-dev-server'

さらに別のターミナルを開き、次のコマンドを実行します。

$ docker-compose exec web bash -c 'cd /apps/myapp; bin/rails s'

コンテナの停止

$ docker-compose stop

コンテナの破棄

$ docker-compose down

おわりに

私がこの記事を書こうと思ったきっかけは、2014年刊行の拙著『実践Ruby on Rails 4 現場のプロから学ぶ本格Webプログラミング』の改訂版を出したいという打診をインプレスの編集者から受けたことです。

この本ではVagrantを使って開発環境を構築しましたが、改訂版ではDockerを利用したいと考えました。原稿を書き始める前にブログ記事を書いて知識をアップデートしておこう、ということです。あわよくば読者の方からフィードバックをもらって間違いを潰しておきたい、という意図もあります。

この記事を書くにあたっては、Dockerの公式ドキュメントにある「Quickstart: Compose and Rails」(以下、「Quickstart」と呼びます)を参考にしました。

https://docs.docker.com/compose/rails/

ただし、両者の間には重要な相違点があります。Quickstartでは docker-compose up コマンドの結果、自動的にRailsアプリケーションが起動しますが、私の記事では手動で起動しなければなりません。私の手順の方が少し面倒ですが、わかりやすさを優先しました。

この記事で紹介しているのは、あくまでDockerを用いて手早くRails開発環境を構築する方法です。構築した開発環境の設定をチームで共有したり、実運用(本番)環境にRailsアプリケーションをデプロイしたりするのは、また別の話になります。

この記事についての質問は、ブログ上のコメント欄の他にメールでも受け付けます。hermes@oiax.jp までご連絡ください。

Elixir/Phoenix: Distillery を利用した tarball 作成と配備

『Elixir/Phoenix 初級④: バリデーション』のサンプルアプリ NanoPlanner を本番環境(Ubuntu 18.04 Server LTS)にセットアップして、公開する手順を紹介します。

デプロイメントツールとしてDistilleryを使用します。

前提条件

  • Ubuntu 18.04 Server LTS
  • kuroda ユーザー (sudo 権限あり)
  • phoenix ユーザー (sudo 権限あり)

Erlang と Elixir のインストール

kuroda ユーザーで Ubuntu Server にログインして、次のコマンド群を順に実行。

$ wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
$ sudo dpkg -i erlang-solutions_1.0_all.deb
$ sudo apt-get update
$ sudo apt-get -y install esl-erlang
$ sudo apt-get -y install elixir
$ elixir --version

次のような結果が出ることを確認する。

Erlang/OTP 21 [erts-10.1.3] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [hipe]

Elixir 1.7.4 (compiled with Erlang/OTP 20)

Node.js のインストール

引き続き、kuroda ユーザーとして次のコマンドを実行。

$ sudo apt install nodejs npm

PostgreSQL のインストールとセットアップ

引き続き、kuroda ユーザーとして次のコマンド群を順に実行。

$ sudo apt-get -y install postgresql
$ sudo -u postgres createuser -d phoenix -P

パスワードの入力を求められるので、phoenix と2回入力。

さらに次のコマンドを実行。

$ sudo -u postgres createdb --owner phoenix nano_planner_prod

Nginx のインストールと設定

kuroda ユーザーとして次のコマンドを実行。

$ sudo apt-get -y install nginx

テキストエディタ/etc/nginx/sites-available ディレクトリに新規ファイル nano_planner を作成

upstream phoenix {
  server 127.0.0.1:4000;
}

server {
  listen 80;
  server_name example.com;

  proxy_http_version 1.1;
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header Host $http_host;
  proxy_set_header X-Cluster-Client-Ip $remote_addr;

  location / {
    proxy_pass  http://phoenix;
    allow all;
  }
}

ただし、example.com の部分は、Ubuntu Server に割り当てられた実際のホスト名で置き換える。

kuroda ユーザーとして次のコマンド群を順に実行。

$ cd /etc/nginx/sites-enabled
$ sudo ln -s ../sites-available/nano_planner .
$ sudo systemctl reload nginx

NanoPlanner のセットアップ

ここから先は、phoenix ユーザーで Ubuntu Server にログインして、次のコマンド群を順に実行。

ソースコードの取得から npm run deploy まで

$ git clone -b master4-deploy https://github.com/oiax/nano_planner.git
$ cd nano_planner
$ cp config/skel/prod.secret.exs config/
$ cd assets
$ npm install
$ npm run deploy
$ cd ..

tarballの作成

$ mix deps.get --only prod
$ MIX_ENV=prod mix compile
$ MIX_ENV=prod mix phx.digest
$ MIX_ENV=prod mix release

リリースの配備

$ mkdir -p ~/app/releases/0.1.0
$ cd _build/prod/rel/nano_planner/releases/0.1.0
$ cp nano_planner.tar.gz ~/app/releases/0.1.0
$ cd ~/app/releases/0.1.0
$ tar -xf nano_planner.tar.gz -C ~/app

データベースの初期化

$ cd ~/nano_planner
$ MIX_ENV=prod mix ecto.migrate
$ MIX_ENV=prod mix run priv/repo/seeds.exs

NanoPlanner の起動

$ cd ~/app
$ bin/nano_planner start

4000番ポートで待ち受けるプロセスの存在を確認。

$ lsof -i:4000

次のような結果が出ることを確認する。

COMMAND    PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
beam.smp 16931 phoenix   15u  IPv6  52028      0t0  TCP *:4000 (LISTEN)

以上で作業完了です。ブラウザで http://example.com にアクセスすれば、NanoPlanner の画面が出てくるはずです。ただし、example.com の部分は、Ubuntu Server に割り当てられた実際のホスト名で読み替えてください。

GCP/Ubuntu: Cloud SQL Proxy のセットアップ(追記あり)

先々月にGCP: 自動スケーリング時における Google Cloud SQL ホワイトリストの管理という記事を書きました。その続きです。

GCPVMインスタンスからCloud SQLインスタンスへ接続するのにCloud SQL Proxyを使えばうまく行くんじゃないか、という考察で終わっていました。

結論から言えばその通りでした。


Ubuntu 18.04(64bit版)におけるセットアップ手順の概要は、次の通りです。

  1. VMインスタンスの設定で「すべての Cloud API に完全アクセス権を許可」を選ぶ。
  2. VMインスタンスsshでログインして、cloud_sql_proxy をインストールする。
  3. cloud_sql_proxy のためのSystemd起動スクリプトを設置する。

これで、Cloud SQL インスタンスPostgreSQL なら localhost:5432 で接続できるようになります。とても簡単です。

なお、GCPドキュメントには「下位互換性のない方法で変更される可能性があり、 は、SLA または非推奨ポリシーの対象ではありません。」と書いてあります(2018年9月9日現在)。採用は自己責任でお願いします。


以下、詳しい説明です。

VMインスタンスの設定

VMインスタンスの「アクセス範囲」に関する設定を「デフォルトのアクセスを許可」から「すべての Cloud API に完全アクセス権を許可」に変更します。

cloud_sql_proxy のインストール

VMインスタンスsshでログインして、cloud_sql_proxy をインストールします。

$ wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 -O cloud_sql_proxy
$ sudo chown root:root cloud_sql_proxy
$ sudo chmod +x cloud_sql_proxy
$ sudo mv cloud_sql_proxy /usr/local/bin

起動スクリプトの設置

テキストエディタで新規ファイル /etc/systemd/system/cloud-sql-proxy.service を以下の内容で作成します。

[Unit]
Description=Cloud SQL Proxy
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/cloud_sql_proxy -dir=sqlproxy -instances=<ID>=tcp:5432
TimeoutSec=300
Restart=always

[Install]
WantedBy=multi-user.target

ただし、<ID> の部分は Cloud SQL インスタンスの接続名で置き換えてください。GCP コンソールで Cloud SQL インスタンスの「概要」タブを見れば、「このインスタンスに接続」と書いてあるボックスの中に記載されています。具体的には

foo:asia-northeast1:bar

のような形式の文字列です。foo がプロジェクト ID で、bar が Cloud SQL インスタンス ID です。

なお、5432 の部分は接続に使用するポート番号です。適宜変更しても構いません。

サービスの起動

VM インスタンスのターミナルで次のコマンドを実行し、Cloud SQL Proxy サービスを起動します。

$ sudo systemctl start cloud-sql-proxy.service

サービスの状態を確認します。

$ sudo systemctl status cloud-sql-proxy.service

正常に動いていれば、次のようなログが表示されます。

Started Cloud SQL Proxy.
Listening on sqlproxy/...
Ready for new connections

失敗した場合、例えば次のようなログが表示されます。

Failed to start Cloud SQL Proxy.

テキストエディタ/etc/systemd/system/cloud-sql-proxy.service を開き、インスタンスの接続名などが誤っていないかどうかを確認し、訂正してください。

次のコマンドを順に実行し、Cloud SQL Proxy サービスを起動し直します。

$ sudo systemctl daemon-reload
$ sudo systemctl start cloud-sql-proxy.service

接続の確認

psql コマンドで接続してみます。

$ psql -h localhost --port 5432 -U postgres postgres

サービスの有効化

正常にサービスが起動できるようになったら Cloud SQL Proxy サービスを有効化します。

$ sudo systemctl enable cloud-sql-proxy.service

直接 PostgreSQL に接続する場合とのパフォーマンスの違い(追記)

私の環境で実測したところ、直接 PostgreSQL に接続した場合に比べて Cloud SQL Proxy 経由で接続した場合、Rails アプリケーションのパフォーマンスが1割〜2割程度落ちる、という現象を観察しました。

ここで「パフォーマンス」は、レスポンス時間を指します。具体的に言えば、平均0.63秒が平均0.69秒になりました。いろいろと条件を変えて負荷試験を繰り返しましたが、レスポンス時間が1割〜2割増加するという事実は変化しませんでした。

Cloud SQL Proxy は設定を楽にしてくれますが、2018年9月の段階では、多少のデメリットもあるようです。

GCP: 自動スケーリング時における Google Cloud SQL ホワイトリストの管理

次のような条件でRuby on Railsアプリケーションを運用することにした。

セットアップの過程で少し困ったのが、PostgreSQL インスタンスに接続できるIPアドレスのリスト(ホワイトリスト)をどう管理するか、という点である。

なぜ困るか。次の3点に集約される。

  1. マネージドインスタンスグループに属する VM インスタンスPostgreSQL インスタンスは別のネットワークにある。
  2. 自動スケーリングによって起動される VM インスタンスの IP アドレスは事前にわからない。
  3. 自動スケーリングによって起動された VM インスタンスに割り当てられた IP アドレスは、インスタンス停止後に他のインスタンスにより再利用される。

アプリケーションサーバーと同じネットワーク内に PostgreSQL サーバーを自前で立てて動かす場合、そのネットワーク(例えば、192.168.0.0/24)を pg_hba.conf に記載してやればいいのだが、その方法は使えない。

VM インスタンスの起動時にその IP アドレスをホワイトリストに登録し、停止時に抹消する必要がある。

この状況を打開する方法はふたつある。ひとつは、VM gcloud コマンドで VM インスタンスの起動時にその IP アドレスをホワイトリストに登録し、停止時に抹消するという方法、もうひとつは、Cloud SQL Proxyを利用する方法である。

今回は、前者の方法について解説しよう。


前提条件

対象となる GCP プロジェクトに関して、以下の準備作業を済ませる。

  • GCP コンソールの[APIとサービス]→[ライブラリ]から「Cloud SQL Admin API」が有効化してある。
  • 「Cloud SQL Admin」権限を持つ GCP のサービスアカウントを作成してある。

以下の条件を満たす VM インスタンスを用意する。

  • サーバー OS は Ubuntu 18.04
  • Ruby 2.4 以上がインストール済み
  • 上記サービスアカウントの資格情報(JSONデータ)を /root/gcp_sa_credentials.json に保存してある。

VM インスタンスの起動・停止時に実行する Ruby スクリプトの作成

/root/bin/ ディレクトリに次のような内容のファイル vm-instance-startup.rb を作成する。ただし、5 行目の pg-instance-0 の部分は、自分の Google Cloud SQL インスタンスの名前(id)で置き換えること。

#!/usr/bin/env ruby

require "json"

SQL_INSTANCE_ID = "pg-instalnce-0"
GCLOUD_COMMAND = "/snap/bin/gcloud sql instances"
KEY_FILE_PATH = "/root/gcp_sa_credentials.json"

system("/snap/bin/gcloud auth activate-service-account --key-file=#{KEY_FILE_PATH}")

api_url = "http://metadata.google.internal/computeMetadata/v1" +
  "/instance/network-interfaces/0/access-configs/0/external-ip"

external_ip = `/usr/bin/curl -s #{api_url} -H "Metadata-Flavor: Google"`

sql_instance_description = `#{GCLOUD_COMMAND} describe #{SQL_INSTANCE_ID} --format=json`
description_hash = JSON.parse(sql_instance_description)

networks = description_hash["settings"]["ipConfiguration"]["authorizedNetworks"].map do |e|
  e["value"]
end

unless networks.include?(external_ip)
  networks << external_ip
  joined = networks.join(",")

  system("#{GCLOUD_COMMAND} patch #{SQL_INSTANCE_ID} --quiet --authorized-networks=#{joined}")
end

また、同じディレクトリに次のような内容のファイル vm-instance-shutdown.rb を作成する。

#!/usr/bin/env ruby

require "json"

SQL_INSTANCE_ID = "pg-00"
GCLOUD_COMMAND = "/snap/bin/gcloud sql instances"
KEY_FILE_PATH = "/root/gcp_sa_credentials.json"

system("/snap/bin/gcloud auth activate-service-account --key-file=#{KEY_FILE_PATH}")

api_url = "http://metadata.google.internal/computeMetadata/v1" +
  "/instance/network-interfaces/0/access-configs/0/external-ip"

external_ip = `/usr/bin/curl -s #{api_url} -H "Metadata-Flavor: Google"`

sql_instance_description = `#{GCLOUD_COMMAND} describe #{SQL_INSTANCE_ID} --format=json`
description_hash = JSON.parse(sql_instance_description)

networks = description_hash["settings"]["ipConfiguration"]["authorizedNetworks"].map do |e|
  e["value"]
end

if networks.include?(external_ip)
  networks.delete(external_ip)
  joined = networks.join(",")

  system("#{GCLOUD_COMMAND} patch #{SQL_INSTANCE_ID} --quiet --authorized-networks=#{joined}")
end

ふたつの Ruby スクリプトの内容はほぼ同じである。末尾の6行だけが異なっている。

そして、これらの Ruby ファイルの実行可能フラグを立てておく。

$ chmod u+x /root/bin/*.rb

Systemdの設定ファイルを作成

ディレクト/etc/systemd/system/whitelist-registry.service という名前のファイルを次のような内容で作成する(名前は何でもよい)。ただし、このファイルには実行可能フラグを立てないこと。

[Unit]
Description=Google Cloud SQL Whitelist Registry

[Service]
Type=oneshot
ExecStart=/root/bin/vm-instance-startup.rb
ExecStop=/root/bin/vm-instance-shutdown.rb
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

そして、このサービスを有効化する。

$ sudo systemctl enable whitelist-registry.service

動作確認

サービスの起動を試す。

$ sudo systemctl start whitelist-registry.service

GCP コンソールで Cloud SQL インスタンスの「承認」タブで、この VM インスタンスの IP アドレスが「承認済みネットワーク」に加わっていれば OK である。

サービスの停止を試す。

$ sudo systemctl stop whitelist-registry.service

GCP コンソールで Cloud SQL インスタンスの「承認」タブで、この VM インスタンスの IP アドレスが「承認済みネットワーク」から消えていれば OK である。

最後に VM インスタンス自体を再起動したり、停止したりして、動作確認する。

考察

今回解説した方法の懸念点は、Systemd の停止スクリプトが実行されないまま VM インスタンスが異常終了したときに、その IP アドレスがホワイトリストに残ってしまう、ということである。データベース自体にパスワードが設定されていればそれほど危険ではないものの、気持ち悪さは残る。

Cloud SQL Proxy を使えばこの懸念は解消されるだろうが、正直に言えば、私自身がまだ試せていない。近いうちにレポートを書きたいと思う。

『Elixir/Phoenix初級③: フォーム構造体とチェンジセット』(OIAX BOOKS)本日発売開始

拙著『Elixir/Phoenix初級③: フォーム構造体とチェンジセット』(OIAX BOOKS)が本日発売になりました。価格は税込み1,944円。206ページです。

f:id:tkrd:20180404093239j:plain

残念ながら全国の書店には並ばないので、AmazonかOIAX BOOKSのウェブストアからお買い求めください。Kindle版もあります(税込み1,080円)。

前巻(『Elixir/Phoenix初級②)の出版からちょうど1年経過しました。もともとの計画では2017年夏には出そうと思っていたのですが、ソフトウェア開発の仕事が忙しくなってしまって、なかなか執筆時間を確保できませんでした。

前巻まではElixir 1.3とPhoenix 1.2を採用していたのですが、本巻の内容はElixir 1.6とPhoenix 1.3に基づきます。Phoenix 1.3になって、Webアプリケーションのディレクトリ構造と用語法が大きく変わりました。概要を知りたい方は、@shufo 氏のPhoenix 1.3のディレクトリ構造とContext - Qiitaを参照してください。

Phoenixの「チェンジセット」はとても面白い概念です。本巻ではそれほど深いところまでは解説できていないのですが、チェンジセットを使って実際にプログラムを書いてみれば、関数型言語でWebアプリケーションを開発する楽しさの一端が見えてくると思います。

著述スタイルは、前巻を踏襲しています。具体的なWebアプリケーション例として簡易予定表管理システムNanoPlannerを作りつつ、必要に応じてElixirの文法や各種関数の説明を加えていく、というスタイルです。初心者でも迷わないように、コマンドの実行やソースコードの変更については、できるかぎり省略せずに記述しています。プログラミング経験の長い方には、少し冗長に見えるかもしれません。


さて、本書は今月の22日に開かれる技術書典4に出品されます。私の会社(株式会社オイアクス)も参加を申し込んでいたのですが、残念ながら落選してしまいました。そこで、知人のブースを間借りさせていただくことにしました。「き09」の「神Excel(カミエクセル)」です。少し割引があるので、よろしくお願いいたします。

Windows Subsystem for Linux は Rails 入門環境には(多分)向かない【追記あり】

私は、Ruby on Rails の入門書の著者あるいは初心者向け講習会の講師として、Microsoft Windows というプラットフォームの取り扱いに悩まされてきました。

プログラミング入門者・初心者の間では Windows が主流ですが、プロの Rails エンジニアの多くは macOSLinux を使っています。RubyRails が急速に進化を遂げる中で Windows が取り残される、という状況がどうしても起きます。そのため、WindowsRails 開発環境を構築する過程でさまざまなトラブルが発生しがちです。

2014年刊行の実践Ruby on Rails 4 現場のプロから学ぶ本格Webプログラミングでは、VagrantVirtualBox を用いて CentOS または Ubuntu をインストールする方法を採用しました。

しかし、2015年刊行の改訂3版基礎 Ruby on Railsでは、初版・第2版と同様にRubyInstaller for Windowsが配布しているインストーラを使用することにしました。本書はプログラミング自体を初めて体験しようとする人々に読まれているので、Vagrant は少々ハードルが高いと考えられたためです。

さて、この文章を書いているのは2018年3月18日です。まもなく Ruby on Rails 5.2 がリリースされようという時期に当たります。『基礎Ruby on Rails』の改訂作業を始めようとしています。

私は普段の Ruby on Rails の開発業務で UbuntumacOS を主に使っていて、Windows 業界の動きはあまり追いかけていないのですが、Windows 10 上で UbuntuopenSUSE などの Linux ディストリビューションを動かせるという情報は目にしていました。そして、次の『基礎Ruby on Rails』の改訂では、この技術を利用できるといいなと漠然と期待を抱いてきました。

最初の発表当時、この技術は Bash on Windows と呼ばれていましたが、その後 Windows Subsytem for Linux (WSL) が正式名称となりました。

昨晩私は WSL を初めて試してみました。まず、コントロールパネルで WSL を有効にし、Microsoft Store から Ubuntu (無償)をダウンロードし、インストールしました。UbuntuRails の開発環境を構築するのはやや複雑な過程ではありますが、何度もやっていることなので難しくはありませんでした。

しかし、Rails のインストールが終わってから重大な問題に気付きました。Windows 上にインストールされたソフトウェアから WSL 上のファイルを操作できないのです。つまり、Windows 用のテキストエディタRailsソースコードを編集することはできません。Ubuntu 上のテキストエディタである nano とか vim とかを使うのなら問題はありません。でも、私はプログラミング入門者・初心者に nano や vim を勧める気にはなりません。AtomVisual Studio Code などのモダンなテキストエディタを使ってもらいたいです。これらなら初心者でもすぐに使い始められます。書籍の第1章や講習会の初日をテキストエディタの練習に費やしたくはありません。

もう一点、私の WSL に対する印象を悪くしたのは、Ubuntu のセットアップやライブラリ群のインストール・更新に予想外の時間がかかったことです。私のネットワーク環境や実施した時間帯がよくなかったのかもしれませんが、VirtualBoxUbuntu をインストールするのに比べてかなり進みが遅かったのです。

私は、WSL を採用することで環境構築の時間が短縮できると期待していたので、この結果にはかなり失望させられました。

以上のようなわけで、残念ながら『改訂4版基礎Ruby on Rails』で WSL を採用するのは難しそうだという結論になりました。

[追記] 2018-03-19

この文章をポストした翌日、もう少し可能性を探ってみようと思い立ちました。

基本的なアイデアは、「Windows側の特定のフォルダ X とWSL側の特定のフォルダ Y を自動で同期する」というものです。これができるのなら、Windows 側のエディタで X 上のファイルを編集し、WSL上で Rails サーバーを起動し、Windows 側のブラウザでアクセスする、という仕組みが可能になります。

手動でふたつのフォルダを同期するのは難しくありません。WSLのシェル上で

$ rsync -a /mnt/c/Users/kuroda/rails/ ~/rails/

というコマンドを実行すれば、Windows 上の kuroda ユーザーのホームディレクトリにある rails フォルダでの変更内容が WSL 側の ~/rails ディレクトリに反映されます。

しかし、ふたつのフォルダを自動で同期するとなると、話は複雑になります。Linux には lsyncd という便利なツールがあり、これをデーモン(daemon)として常駐させておけば、フォルダ同士が自動で同期されます。

しかし、現行の WSL は「デーモン」に対応していないのです。

Microsoftによる2017年12月4日の発表によれば、デーモンをサポートする方向で WSL の開発が進んでいるようで、2018 年春に予定されている次の Windows 10 のアップデートで実現するかもしれません。

けれども、『改訂4版基礎Ruby on Rails』の刊行も2018年初夏を予定しているので、原稿の準備や動作確認のことを考えるととても間に合わないそうもありません。

やはり、現時点では「Windows Subsystem for LinuxRails 入門環境には(多分)向かない」という結論になってしまいます。

[追記] 2018-04-15

Qiitaで@stkdevさんが書かれた記事RailsをWindows Subsystem for Linuxで動かしてみるを読んで、私が重大な見落としをしていたことに気付きました。

Windows側からWSL側のファイルを操作できないのですが、逆は可能なのです。つまり、RailsアプリのソースコードWindows側の適当なフォルダ上で作成すれば、Windows側のテキストエディタで読み書きできるし、WSL側でサーバーとして起動することもできる、というわけです。

まったく目が曇っていました。

具体的な手順は、こんな感じになります。Windowsユーザーの名前を kuroda とすれば、まず Windows 側のホームディレクトリ直下に rails というフォルダを作成します。それは、WSL側からは /mnt/c/Users/kuroda/rails として見えるので、WSLのシェル上で

$ ln -s /mnt/c/Users/kuroda/rails ~/rails

のようにシンボリックリンクを設定できます。続いて、WSLのシェル上で、Railsアプリを生成します。

$ cd ~/rails
$ rails new foo_bar

多分、以上の考え方で問題ないでしょう。『改訂4版基礎Ruby on Rails』の原稿を書き直す方向で検討したいと思います。

[追記] 2019-02-20

https://forest.watch.impress.co.jp/docs/news/1170221.html によれば、2019年4月に予定されている Windows アップデートにより、WindowsからWSL側のファイルをアクセスできるようになるそうです。