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』の原稿を書き直す方向で検討したいと思います。

macOS/Homebrewで複数バージョンのPostgreSQLを共存させる方法(2018年版)

複数の PostgreSQL クラスタ(インスタンス)を起動するで書いたように Ubuntu 環境では、普通に apt-get でインストールするだけで複数バージョンのPostgresqlを共存させる状況が整います。

しかし、macOS/Homebrew 環境ではちょっとした準備作業が必要となります。以下、その概略を説明します。

【重要な注意】 以下の手順では、いずれのコマンドも sudo を付けずに実行してください。

【参考資料】 Macで複数のバージョンのPostgresqlを共存させる -- itmammoth 氏による 2016 年 3 月の記事。

インストール済みの PostgreSQL を無効化

まず、現在の環境に PostgreSQL がインストールされているかどうかを確認します。

$ brew search postgresql

リストの中にある postgresql の右に ✔ アイコンが付いていればインストール済みです。次のコマンドで無効化します。

$ brew unlink postgresql

リポジトリの追加

リポジトリ petere/homebrew-postgresql を追加します。

$ brew tap petere/homebrew-postgresql

postgresql-common のインストール

$ brew install postgresql-common

PostgreSQL のインストール

インストール可能な PostgreSQL のバージョン番号は brew search postgresql コマンドで調べられます。表示されたリストの中に petere/postgresql/postgresql@X という項目があれば、バージョン X の PostgreSQL がインストール可能です。

例として、バージョン 9.6 と 10 をインストールしてみましょう。

$ brew install postgresql-9.6
$ brew install postgresql-10

もう一度 brew search postgresql コマンドを実行し、petere/postgresql/postgresql@9.6 および petere/postgresql/postgresql@10 の右側に ✔ アイコンが付いていることを確認してください。

クラスタの作成

ここから先は Ubuntu 環境の場合とほぼ同じです。ただし、データディレクトリ、設定ファイルの置き場所、データベース postgres の owner など、細かな違いがあります。繰り返しになりますが、すべてのコマンドを sudo なしで実行してください(重要)。

さて、PostgreSQL 用語の「クラスタ」とは、ひとつの PostgreSQL サーバーインスタンスによって管理される複数のデータベースの集合体を意味します。ひとつの macOS 上で複数個のクラスタを同時に稼働させることができます。

PostgreSQL 9.6 のクラスタ mainbackup を作成します。

$ pg_createcluster 9.6 main
$ pg_createcluster 9.6 backup

さらに、PostgreSQL 10 のクラスタ main を作成します。

$ pg_createcluster 10 main

合計で 3 個のクラスタが作られたことになります。クラスタの状態を確認します。

$ pg_lsclusters

すると、次のような結果が表示されます(一行が長いので一部省略しています)。

Ver Cluster Port Status Owner  Data directory...
9.6 main    5433 down   kuroda /usr/local/var...
9.6 backup  5434 down   kuroda /usr/local/var...
10  main    5435 down   kuroda /usr/local/var...

3番目の Port 列に着目してください。それぞれのクラスタに対応するサーバーインスタンスがどのポートを listen するかが書かれています。なお、5番目の Owner 列には、現在 macOS にログイン中のユーザー名が表示されます。

クラスタの起動

PostgreSQL 9.6 のクラスタ main を起動します。

$ pg_ctlcluster 9.6 main start

クラスタの状態を確認します。

$ pg_lsclusters

すると、次のような結果が表示されます。

Ver Cluster Port Status Owner  Data directory...
9.6 main    5433 online kuroda /usr/local/var...
9.6 backup  5434 down   kuroda /usr/local/var...
10  main    5435 down   kuroda /usr/local/var...

1 行目の Satus 列の値が down から online に変化しています。

同様に、他の 2 個のクラスタも起動します。

$ pg_ctlcluster 9.6 backup start
$ pg_ctlcluster 10 main start

psql で接続確認

PostgreSQL 10 のクラスタ mainpsql で接続してみましょう。

$ psql --port=5435 postgres

--port オプションでポート番号を、接続先データベースとして postgres を指定しています。初期状態で存在する唯一のデータベースが postgres です。オーナーは pg_createcluster を実行したユーザーです。

次のように出力されれば、成功です。

psql (10.2 (Homebrew petere/postgresql))
Type "help" for help.

postgres=#

クラスタの停止

PostgreSQL 9.6 のクラスタ main を停止するには、次のコマンドを実行します。

$ pg_ctlcluster 9.6 main stop

ポート番号などの変更

クラスタの設定ファイルは /usr/local/etc/postgresql ディレクトリの下にあります。PostgreSQL 10 のクラスタ main の設定ファイルは、/usr/local/etc/postgresql/10/main ディレクトリにあります。

ポート番号を変更したい場合は、エディタで postgresql.conf を開き、port = で始まる行を探して値を書き換えてください。設定を反映させるには、クラスタの再起動が必要です。

PostgreSQL: template1 のエンコーディングを UTF-8 に変更する

PostgreSQL におけるデータベース作成とは、既存のデータベースの複製を作ることです。デフォルトでは template1 という名前のデータベースが複製元となります。

さて、PostgreSQLのインストール直後に template1エンコーディングSQL_ASCII になっていることがあります。

postgres=# \l
                                    List of databases
     Name     |  Owner   | Encoding  |   Collate   |    Ctype    |   Access privileges   
--------------+----------+-----------+-------------+-------------+-----------------------
 postgres     | postgres | SQL_ASCII | C           | C           | 
 template0    | postgres | SQL_ASCII | C           | C           | =c/postgres          +
              |          |           |             |             | postgres=CTc/postgres
 template1    | postgres | SQL_ASCII | C           | C           | 

Web アプリケーションのバックエンドとして使う場合、エンコーディングとして UTF-8 を採用することが多いので、このままだと不便です。

psql のコンソールで次のステートメントを発行すれば、template1エンコーディングUTF-8 に(ついでに、ロケールをに ja_JP に)変更できます。

UPDATE pg_database SET datistemplate = FALSE WHERE datname = 'template1';
DROP DATABASE template1;
CREATE DATABASE template1 WITH TEMPLATE = template0 ENCODING = 'UTF8'
  LC_COLLATE = 'ja_JP.UTF-8' LC_CTYPE = 'ja_JP.UTF-8';
UPDATE pg_database SET datistemplate = TRUE WHERE datname = 'template1';

結果は、この通り:

postgres=# \l
                                    List of databases
     Name     |  Owner   | Encoding  |   Collate   |    Ctype    |   Access privileges   
--------------+----------+-----------+-------------+-------------+-----------------------
 postgres     | postgres | SQL_ASCII | C           | C           | 
 template0    | postgres | SQL_ASCII | C           | C           | =c/postgres          +
              |          |           |             |             | postgres=CTc/postgres
 template1    | postgres | UTF8      | ja_JP.UTF-8 | ja_JP.UTF-8 | 

Ubuntu: lsyncd によるディレクトリのリアルタイム同期(追記あり)

目標

  • Ubuntu 16.04 Server がインストールされた 2 台のサーバー host1host2 がある。
  • host1host2 の間で /home/foo/share ディレクトリの内容を常に同期する。
  • 一般ユーザー(root 以外のユーザー)で rsync コマンドを実行する。

準備作業

RSA鍵ペアの作成

foo ユーザーで host1host2ssh でログインし、それぞれ次のコマンドを実行する。

$ ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa.rsync

RSA鍵公開鍵を設置

  • host1~/.ssh/id_rsa.rsync.pub の中身を host2~/.ssh/authorized_keys の末尾に貼り付ける。
  • host2~/.ssh/id_rsa.rsync.pub の中身を host1~/.ssh/authorized_keys の末尾に貼り付ける。

注意: ~/.sshパーミッションが 700 であり、~/.ssh/authorized_keysパーミッションが 400 または 600 であることを確認すること。

接続確認

$ ssh -i ~/.ssh/id_rsa.rsync host2 # host1 から
$ ssh -i ~/.ssh/id_rsa.rsync host1 # host2 から

rsync コマンドの動作確認

host1 で次のコマンドを群を実行する。

$ mkdir ~/share
$ echo TEST ~/share/test.txt
$ rsync -av -e ssh ~/share/ foo@host2:/home/foo/share

~/share/ の末尾のスラッシュ(/)は必須である。

host2/home/foo/share ディレクトリに test.txt というファイルができていればOK。

lsyncd のセットアップ

ソフトウェアのインストール

$ sudo apt-get install -y lsyncd

設定ファイルの作成

/etc/lsyncd/lsyncd.conf.lua を作成。

settings {
        logfile    = "/var/log/lsyncd.log",
        statusFile = "/tmp/lsyncd.stat",
        delay        = 1
}

sync {
        default.rsync,
        source="/home/foo/share/",
        target="foo@host2:/home/foo/share/",
        delete=false,
        rsync = {
          rsh = "/usr/bin/ssh -i /home/foo/.ssh/id_rsa.rsync -o UserKnownHostsFile=/home/foo/.ssh/known_hosts"
        }
}

注意事項:

  • delay の値(デフォルト: 15)は、ファイル更新イベント発生から rsync 実行までの遅延時間(秒単位)である。リアルタイム性を追求するなら値は小さいほうがよいが、ファイル更新イベントが頻発するサーバーでこの値を小さくし過ぎると、かえって更新に時間がかかるかもしれない。
  • source の末尾のスラッシュ(/)は必須である。
  • host2 側の lsyncd.conf.lua では、target の値を "foo@host1:/home/foo/share/" とする。
  • delete=false については本稿末尾の「備考」を参照。
  • -o UserKnownHostsFile-... の記述を省略すると、Host Key Verification Failed というエラーが発生する。

lsyncd の起動

lsyncd を起動。

$ sudo systemctl restart lsyncd

動作確認

  • host1/home/foo/shared ディレクトリに新しいファイルを追加し、host2/home/foo/shared ディレクトリにそのファイルが転送されることを確認。
  • host2/home/foo/shared ディレクトリに新しいファイルを追加し、host1/home/foo/shared ディレクトリにそのファイルが転送されることを確認。

うまく行かない場合は、/var/log/lsyncd.log を見て調べる。

備考

本稿で使用した lsyncd の設定では delete=false オプションを使用しているため、host1 または host2 でファイルが削除されても、もう一方の側でそのファイルは削除されない。

ファイルが削除される可能性のある環境で lsyncd による「双方向同期」を使用する場合の注意点については、lsyncdで双方向同期するなら、delete='running' がいいを参照せよ。


追記

「備考」で触れたが、lsyncd で双方向同期を行う場合、ファイル削除の扱いが難しい。

host1 がダウンしている間に host2 に追加されたファイル X があるとき、host1 の再起動時に X が削除される可能性がある、ということだ。

この問題を回避するため、参照したブログ記事では「lsyncd の起動時にファイルの同期をしない(delete="running")」という設定を勧めている。

しかし、そもそもなぜファイルを削除するのかを考えると、この回避策でよいだろうか。

host1 がダウンしている間に host2 でファイル Y が削除されたとする。「lsyncd の起動時にファイルの同期をしない」設定の場合、host1 の再起動後も host1 上でファイル Y が残り続けることになる。

おそらく、ファイル Y を削除する目的は、ディスクスペースの節約かファイル Y の内容を残したくない(内容がまずい)かのどちらかだろう。

つまり、「lsyncd の起動時にファイルの同期をしない」設定では、この目的が達成されない。

とすれば、正しい回避策は、「lsyncd の起動時にファイルの同期をするが、ファイルの削除はしない(delete=false)」設定にしておいて、ファイル Y を削除したいときにはファイル Y を空にする(ファイルサイズを 0 にする)運用をすることではないだろうか。これならば、host1 が復活したときに、host1 上のファイル Y が消える。

ちなみに、サイズが 0 のファイルも一定のディスクスペースを占めるし、あまりに数が多いと「inode枯渇」という別の問題を引き起こす。

だから、ファイル削除が頻繁に行われる環境では、サイズが 0 のファイルを削除する仕組みを別途考えたほうがいいだろう。

例えば、毎朝 4:01 に host1host2 の両方が動いていることを確かめた上で、4:00 以前に書き換えられたサイズ 0 のファイルをすべて削除するような処理を cron にやらせればいい。

なお、この「追記」の内容は筆者が頭の中で考えたことに過ぎず、実績があるわけではない。参考にされる方は、この点に留意していただきたい。