SSH経由でMySQLの準同期レプリケーションを行う方法

SSHで暗号化された通信経路でMySQLの準同期レプリケーションを行うための方法を紹介します。今回使用したMySQLのバージョンは5.5.30です。

 

1. マスタとスレーブと間でSSH経由で通信ができるようにする

レプリケーションの設定に入る前に、マスタDBがあるサーバー(以降、マスタと表記)とそのレプリケーション先のDBがあるサーバー(以降、スレーブと表記)の間の通信がちゃんとできるか確認しましよう。

 

------- スレーブでやること ----------
まず、以下のコマンドを実行してSSHポートフォワーディングによってスレーブの10000ポートからマスタの3306ポートにアクセスできるようにします。
ssh -f -N -C -L 10000:localhost:3306 user01@master.tamurasouko.com -p 22

上記のコマンドで下図のようなポートフォワーディングを実現しています。このコマンドの詳細についてはこちらのメモを参考にしてください。
ssh_master_and_slave

 

-------マスタでやること ----------
次に、マスタDBにMySQLクライアントで接続し、以下のコマンドでレプリケーション用のユーザを作成します。
mysql> GRANT REPLICATION SLAVE ON *.* TO slave_user@'localhost' IDENTIFIED BY 'slave_password';

上記のコマンドにはちょっとしたポイントがあります。それはユーザ(slave_user)のホストlocalhostにしている点です。

変だとおもいませんか?マスタに接続してレプリケーションを行うのは外部サーバーのスレーブなのに、マスタへの接続に使用するユーザのホストをスレーブではなく、localhost、つまりマスタ自身にしているところが一見おかしいように思えます。

このような設定にしているのはSSHのポートフォワーディングを利用しているためです。ポートフォワーディングはスレーブのlocalhostから通信を「転送」しているため、その通信を受け取ったマスタもlocalhostからの通信だな、と判断するわけです。(たぶん。このあたりの理解はあやしいです。)

レプリケーション用のユーザの作成が完了したら、以下のコマンドをMySQLクライアントで実行して作成したユーザをDBMSに反映させます。
mysql> FLUSH PRIVILEGES;

 

------- スレーブでやること ----------
ここまで設定が終わったら、スレーブからマスタにポートフォワーディングで接続できるか以下のコマンドを実行して確認します。
mysql -u slave_user -p -h 127.0.0.1 --port=10000

問題なく実行できればOKです。

 

2. 準同期レプリケーションのための設定をする

マスタ・スレーブ間の通信ができることを確認したところで、レプリケーションの設定をMySQLの設定ファイル(/etc/my.cnf)に記述していきます。設定はマスタ、スレーブそれぞれ行う必要があります。以下の設定を追記したらMySQLサーバーを両方とも再起動してください。

マスタのmy.cnf

# サーバーID
server-id=10

# バイナリーログファイルを生成
log-bin
# ログの保持を保証する期間
expire_logs_days=14

# レプリケーションプラグイン(マスタ用)を読み込む
plugin-load=rpl_semi_sync_master=semisync_master.so

# このサーバーをレプリケーションのマスタに指定
rpl_semi_sync_master_enabled=1
# スレーブから応答が帰ってこない場合の待ち時間を指定(ミリ秒)
rpl_semi_sync_master_timeout=1000

 

スレーブのmy.cnf

# サーバーID(※マスタより大きな値を設定)
server-id = 11

# レプリケーションプラグイン(スレーブ用)を読み込む
plugin-load=rpl_semi_sync_slave=semisync_slave.so

# このサーバーをレプリケーションのスレーブに指定
rpl_semi_sync_slave_enabled=1

 

3. データのコピーとログ位置を確認する

レプリケーションを始める前にマスタとスレーブのデータが同一である必要があります。そのためにマスタDBの全データをダンプ(別ファイルに書き出すこと)し、そのダンプデータをスレーブにコピーします。

 

------- マスタでやること ----------
まず、マスタのDBがこのコピー作業中に変更されないようにMySQLクライアントから以下のコマンドを実行し、ロックします。
mysql> FLUSH TABLES WITH READ LOCK;

この状態で以下のコマンドをシェルから実行し、マスタDBの全データをダンプします。
mysqldump -u root --password='root_password' --all-databases --master-data --events > all_dump.db

また、このときのバイナリーログの位置を以下のコマンドをMySQLクライアント上で実行して取得します。このログ位置はスレーブにてレプリケーションマスタを設定するときに使用します。(後述します)
mysql> SHOW MASTER STATUS;

ここまでの作業が終わったら、以下のコマンドでロックを解除します。
mysql> UNLOCK TABLES;

ここまでがマスタ側での作業です。

 

------- スレーブでやること ----------
次に、マスタDBのダンプデータをSFTPなどを用いてスレーブにもってきます。そして以下のコマンドを実行してスレーブDBにマスタDBのデータを投入します。
mysql -u root ---password='root_password' < all_dump.db

 

4. スレーブにレプリケーション先を設定する

------- スレーブでやること ----------
マスタとスレーブのデータが同一になったところで、スレーブにレプリケーション元のマスタを設定します。スレーブで以下のコマンドを実行します。
mysql> CHANGE MASTER TO master_host='localhost', master_port=10000, master_user='slave_user', master_password='slave_password', master_log_file='mysqld-bin.000001', master_log_pos=2530

SSHポートフォワーディングによってマスタに接続するため、コマンドのmaster_hostやmaster_portの値は上記のようになります。また、前節においてマスタでSHOW MASTER STATUSコマンドを実行して得られたログ位置をmaster_log_posの値として設定しています。

 

5. レプリケーション開始

------- スレーブでやること ----------
スレーブで以下のコマンドを実行すれば準同期レプリケーションが開始されます。
mysql> START SLAVE;

 

6. レプリケーションが行われているか確認

------- スレーブでやること ----------
実際にマスタを更新してデータがスレーブにも更新されるか見て確認してもいいですが、以下のコマンドをスレーブ側で実行して確認することもできます。(両方やっておけば確実です)
mysql> SHOW SLAVE STATUS\G

上記のコマンドを実行すると、Slave_IO_RunningSlave_SQL_Runningという項目が表示されます。これらがどちらもYesになっていればOKです。

 

環境

CentOS 6.4
MySQL 5.5.30
openSSL 1.0.0

 

 

SSHのポートフォワーディングでMySQLに接続する方法

SSHポートフォワーディング(トンネリング)を利用して、外部サーバーで稼働しているMySQLデータベースに接続する方法を紹介します。

 

SSHポートフォワーディングとは

ssh_portforwarding

SSHポートフォワーディングの仕組みがわかってしまえば、今回の話は簡単です。要は上図のように、SSHによって確立された通信経路を利用してクライアントの10000ポートをサーバーの3306ポートにマッピングするわけです。

つまり、このポートフォワーディングが行われているときにクライアントのローカルの10000ポートに接続すれば、サーバーの3306ポートに接続していると変わらないというわけです。

しかも、このとき通信経路はSSHによって暗号化されおり、また接続先のポートがファイアウォール等で閉じられていたとしてもSSHのポートさえ開いていれば利用可能という非常に使い勝手がよい仕組みになっています。

 

ポートフォワーディングを実行する

では、さっそくSSHポートフォワーディングをやってみましょう。以下のコマンドを実行してください。
ssh -f -N -C -L 10000:localhost:3306 user01@xxx.tamurasouko.com -p 22

上記のコマンドを解説します。まずの"-L 10000:localhost:3306"の部分ですが、これはローカルの10000ポートを接続先サーバーの3306ポートにマッピングする、という指定です。次に、"user01@xxx.tamurasouko.com -p 22"の部分では、xxx.tamurasouko.comというサーバーの22ポートにユーザ名user01で接続する、という指定です。この22というポートはSSHの接続を待ち受けているサーバーのポート番号になります。
また、オプションの意味はそれぞれ以下の通りです。

オプション 説明
-f このポートフォワーディングをバックグラウンドで実行する。(必須
-N SSHトンネリングのみに使用する。(必須
-C データを圧縮して送る。

尚、ポートフォワーディングを停止したい場合はps aux|grep sshコマンドで当該プロセスを探し、killすればいいだけです。

 

外部サーバーのMySQLに接続する

外部サーバーの3306ポートでMySQLが通信を受け付け、かつ上記のポートフォワーディングが行われている場合、クライアントは次のコマンドで外部サーバーのMySQLに接続できます。
mysql -u root -p -h 127.0.0.1 --port=10000

portオプションでポートフォワーディングの窓口として設定したローカルの10000番ポートを指定すれば外部サーバーで稼働しているMySQLに接続することができます。

ちなみに"-h 127.0.0.1"を指定していないとうまくフォワーディング できませんでした。-hオプションは接続先を指定するオプションですが、上記の例ではローカルホストの10000ポートを窓口としてサーバーの3306ポートに接続しているわけですから、明示的に接続先がローカルホストであることを示す必要があります。

 

以上になります。今回はMySQLサーバーへに接続でしたが、SSHのポートフォワーディングは他にもいろいろ応用ができそうですね。
⇒ 応用例:[SSH経由でMySQLの準同期レプリケーションを行う]

 

環境

CentOS 6.4
MySQL 5.5.30
openSSL 1.0.0

 

rsyncの使い方

SSH経由で同期処理が可能なとっても便利なコマンド「rsync」の使い方のメモです。

 

基本

rsync -r ~/data/ bbb.tamurasouko.com:~/sync_data/
ホームディレクトリのdataディレクトリ配下のファイルを、リモートサーバー(bbb.tamurasouko.com)のホームディレクトリにあるsync_dataディレクトリ配下に同期(コピー)します。-rオプションをつけないとディレクトリ配下のファイルを再帰的に同期してくれないので、ほとんどの場合、このオプションを付けることになります。

 

SSHのポート番号を指定

セキュリティ対策の一環として、SSHのポート番号を変えていることがあると思います。その場合は以下のようにしてリモートサーバーのSSHのポート番号を指定します。
rsync -e "ssh -p 3862" -r ~/data/ bbb.tamurasouko.com:~/sync_data/

 

注意点

rsyncはcronを使って定期的に自動実行する場合が多いと思いますが、SSHの鍵にパスフレーズがついていると、rsync実行時に毎回パスフレーズの入力が求められるため、自動化ができません。この問題への手っ取り早い対策は、SSHの鍵の生成時にパスフレーズを指定しないことです。

えっ?パスフレーズなしで大丈夫なのかって? 大丈夫です。秘密鍵が漏れない限り心配いりません。私もつい最近まで勘違いしていたのですが、パスフレーズはSSHの鍵の生成の「種」として使われるのではなく、別なレイヤーでの保護措置に使われるものだからです。えっ?別なレイヤーって何かって? えーと、それは。。

 

環境

CentOS 6.4
rsync 3.0.6

 

よく使うNginxの設定

Nginxの設定ファイル(*.conf)の書き方をいつも忘れてしまうので、メモを残しておきます。

▼基本設定

http {
    ...

    # "you should increase server_names_hash_bucket_size"というエラーが出た時の対応
    server_names_hash_bucket_size  64;

    # 最大アップロードサイズを設定
    client_max_body_size 32m;

    # serverの設定は個別ファイルから読み込むようにする
    include /opt/nginx/conf/conf.d/*.conf;

    ...
}

 

▼リダイレクト(URL書き換え)

server {
  # 80番ポートにアクセスしてきたらSSL通信用のURLにリダイレクト
  listen 80;
  server_name xxx.tamurasouko.com;
  rewrite ^(.*) https://xxx.tamurasouko.com$1 permanent;
}

 

環境

Nginx 1.2.6

 

yumの使い方

よくyumにはお世話になるのですが、使い方を忘れてしまうのでメモ。

▼インストール済みのパッケージを確認する
yum list installed

▼利用可能なアップデートがあるか確認する
yum list updates

▼レポジトリを指定する
yum install [パッケージ名] --enablerepo=[レポジトリ名]
このオプションはupdateなど他のコマンドでも利用できます。

▼パッケージの詳細を確認する
yum info [パッケージ名]
パッケージをインストールする前にバージョンなどを調べるときに便利です。

▼yumで管理されているパッケージに更新があるかチェック
yum check-update

▼yumで管理されている全てのパッケージの最新化
yum -y update

 

Monitによるサーバー監視

Monitというモニタリングソフトを使ってみました。導入も設定も簡単でした。

 

インストール

yumを使えば簡単です。以下のコマンドを実行すればインストールは完了です。
sudo yum install monit

 

基本設定

/etc/monit.confファイルに以下の設定を記述するだけで、問題なく動作します。

# 120秒ごとに監視を行う
set daemon  120
# アラートメールの送信サーバーを指定(※Gmailを使用する場合の例)
set mailserver smtp.gmail.com port 587 username "あなたのGmailアドレス" password "あなたのパスワード" using tlsv1
# Webブラウザからモニタリング状況を確認できるようにする
set httpd port 2812 and
    allow admin:monit

上記の5行目の設定によって、"http://[Monitが動作しているサーバーのアドレス]:2812"というURLにアクセスすることでモニタリング状況がWebブラウザから確認できます。このときユーザIDをパスワードを求められますが、それぞれ"admin"と"monit"と入力すればログインできます。

 

起動・終了

yumを使ってインストールした場合、Monitをデーモンとして起動するスクリプトが/etc/init.d配下にあるので、このスクリプトを利用します。
起動: sudo /etc/init.d/monit start
終了: sudo /etc/init.d/monit stop

 

監視設定

以下は「www.tamurasouko.comというWebサーバーの80番ポートに接続できるか監視を行い、できなければアラートメールを送信する」という設定の例です。この設定は/etc/monit.confに記述しても動きますが、個別設定用に/etc/monit.dというディレクトリが準備されているので、そこに別ファイルとして保存したほうが管理しやすいと思います。

check host www.tamurasouko.com with address www.tamurasouko.com
      if failed port 80 protocol http
      then alert

 

環境

CentOS 6.3

 

iOS – AppStoreへのアプリの再申請

以前に申請したiOSアプリがrejectされてしまったので、再申請をしました。そのときの手順をこのメモに残しておきます。

iOS Dev CenterからiTune Connectにログインします。「Manage Your Apps」のリンクを開くと以前に申請したアプリのアイコンが表示されるので、そのリンクを開きます。開かれた画面の右下に「View Details」というボタンがあるので、そのボタンのリンクを開きます。

itune_connect_ready_to_upload_binaryそうすると、アプリのタイトルとともに以前申請した内容が表示されます。この画面の右上と右下に「Ready to Upload Binary」というボタンが表示されるので、このボタンを押します。

この操作によって修正後のアプリを再度App Storeにアップロードできるようになります。アプリのアップロードの手順は以前書いたメモ「App Storeへのアプリの登録手順」の「4.アプリのアップロード」と同じです。

後はiTunes ConnectのReview Noteに前回からの変更点などを書いてあげるとレビューアーも助かると思います。

 

 

iOS – UITextFieldやUITextViewで入力文字数を制限する方法

入力制限

UITextFieldやUITextViewで入力できる最大文字数を指定する方法を紹介します。

UITextFieldの場合

UITextFieldのshouldChangeCharactersInRangeデリゲートメソッドを利用します。このメソッドはファーストレスポンダになっているUITextFieldに1文字でも入力があれば呼び出されます。このメソッド内において入力された文字が最大文字数を超えていないかチェックします。

尚、お決まりですが、このデリゲートメソッドを使用するためにはUITextFieldDelegateプロトコルを当該ViewControllerで採用し、デリゲート先をそのViewControllerにする必要があります。

実装例は以下の通りです。

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{    
    // 最大入力文字数
    int maxInputLength = 200;

    // 入力済みのテキストを取得
    NSMutableString *str = [textField.text mutableCopy];

    // 入力済みのテキストと入力が行われたテキストを結合
    [str replaceCharactersInRange:range withString:string];

    if ([str length] > maxInputLength) {
        // ※ここに文字数制限を超えたことを通知する処理を追加

        return NO;
    }

    return YES;
}

上記のコードを解説します。まず、7行目でファーストレスポンダになっているUITextFieldから既に入力済みの文字列を取得します。これにはこのデリゲートが呼び出されるきっかけとなった入力は含まれていません。きっかけとなった入力はreplacementStringに格納されています。

10〜12行目の部分では、これら2つの文字列を結合し、その文字列の長さが許容されている範囲を超えていないかチェックして、返す戻り値を変えています。このデリゲートメソッドがYESを返せば直前の入力がUITextFieldに反映されますが、NOを返した場合は反映されません

 

UITextViewの場合

UITextFieldの場合とほとんど同じですが、デリゲートメソッド名や採用するプロトコルが異なります。UITextViewではshouldChangeTextInRangeデリゲートメソッドを利用し、当該ViewControllerではUITextViewDelegateプロトコルを採用します。

以下、実装例です。

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
    int maxInputLength = 2000;

    // 入力済みのテキストを取得
    NSMutableString *str = [textView.text mutableCopy];

    // 入力済みのテキストと入力が行われたテキストを結合
    [str replaceCharactersInRange:range withString:text];

    if ([str length] > maxInputLength) {
        return NO;
    }

    return YES;
}

 

複数のUITextFieldがある場合はどうするか tag_value_on_attribute_inspector

UITextFieldが複数あり、それぞれ最大入力文字数が異なる場合はUITextFieldに異なるtag値を設定し、そのtag値でどのUITextFieldなのかを判定して処理を行うのが簡単だと思います。tag値は右図のようにStoryboard上のAttribute Inspectorで編集できます。

以下に実装例を載せます。

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    // 最大入力文字数
    int maxInputLength = 50;
    switch (textField.tag) {
        case 1: // 名前
            maxInputLength = 50;
            break;
        case 2: // 住所
            maxInputLength = 200;
            break;
        case 3: // 年齢
            maxInputLength = 3;
            break;

        default:
            break;
    }

    // 入力済みのテキストを取得
    NSMutableString *str = [textField.text mutableCopy];

    // 入力済みのテキストと入力が行われたテキストを結合
    [str replaceCharactersInRange:range withString:string];

    if ([str length] > maxInputLength) {
        return NO;
    }

    return YES;
}

上記のコードは「名前」「住所」「年齢」の3つのUITextFieldにそれぞれtag値として1, 2, 3を割り振った場合の実装例です。

 

環境

XCode 4.6.1

 

Rails – Deviseのコントローラをカスタマイズする方法

Railsの認証エンジン「Devise」のコントローラをカスタマイズする方法を紹介します。

 

コントローラファイルの作成

Deviseをインストールしてもそのコントローラは生成されません。Deviseが内部的に持っているコントローラが使用されます。そのため、Deviseのコントローラの挙動を変えたい場合は、そのDeviseのコントローラクラスを継承した独自のコントローラを作成する必要があります。

認証用のモデルを「User」という名前にした場合、app/controllers/usersディレクトリ配下にsessions_controller.rbとregistrations_controller.rbという2つのコントローラファイルを作成します。前者はユーザのログイン/ログアウト機能のコントローラで、後者はユーザ登録機能のコントローラになります。それぞれソースコードは以下のようにします。

class Users::SessionsController < Devise::SessionsController

  def new
    super
  end

  def create
    super
  end
end
class Users::RegistrationsController < Devise::RegistrationsController

  def new
    super
  end

  def create
    super
  end

end

ポイントはクラスの宣言部分です。 Devise::SessionsControllerとDevise::RegistrationsControllerをそれぞれ継承して、親クラスのnewメソッドとcreateメソッドをオーバーライドしています。独自の処理を加えたい場合は、これらのメソッド内に処理を追加すればよいわけです。

 

Deviseで使用するコントローラファイルの指定

上記で作成したコントロールファイルをDeviseが使用するように設定を記述する必要があります。config/routes.rbファイルのDeviseに関する設定を以下のように変更します。

devise_for :users, :controllers => {
  :sessions => 'users/sessions',
  :registrations => 'users/registrations'
}

:controllersオプションで使用するコントローラを指定しています。ちなみに'users/sessions'のusersはapp/controllers/usersディレクトリのことを表し、sessionsはsessions_controller.rbファイルの先頭を表しています。そのため、実際のディレクトリ構造とファイル名とがこの設定と一致していないとうまく動きません。

 

使用するコントローラが切り替わっていることを確認

最後に以下のコマンドを実行してDeviseが使用するコントローラが今回作成したコントロールになっているか確認しましょう。
> rake routes

devise_controller_changed_routes

 

環境

Rails 3.2.9
Devise 2.1.2

 

iOS – ZBarライブラリのiPhone5対応

バーコード、QRコード読取りでよく使われるライブラリに「ZBar」があります。

 

問題

このZBarライブラリですが、iPhone5で動かそうとすると問題が発生します。配布されているZBarのlibzbar.aarmv7sアーキテクチャを前提にコンパイルされていないため、iPhone5用にアプリをコンパルする際にリンクエラーが発生してしまいます。

リンクエラーの場合、以下のようなエラーメッセージが出力されるはずです。
ld: file is universal (3 slices) but does not contain a(n) armv7s slice: .../libzbar.a for architecture armv7s

 

解決方法

さてどうしたものかと思い、ネットの海をさまよっていたところ、ZBarライブラリをソースコードからコンパイルしてarmv7sに対応したlibzbar.aを作成するという解決方法がありました。

しかしながら、調べてみるとソースコードからのコンパイルもいろいろと難しそうなので、armv7sに対応したコンパイル済みのものがないかと探したところ、ありました。
http://sourceforge.net/p/zbar/discussion/1072195/thread/ba2844b5

コンパイル済みのZBarはこちらから:http://sourceforge.net/projects/zbar/files/iPhoneSDK/beta/

 

上記のWebページから「ZBarSDK-1.3.1.dmg」をダウンロードして古いZBarと置き換えたところ、無事iPhone5でも動作しました。

 

環境

XCode 4.6