Terraform for さくらのクラウド スタートガイド(第5回)

# 2017/05追記

当記事の情報はすでに古くなっています。
新しい記事を投稿していますのでこれからTerraform for さくらのクラウドを利用される方はこちらを参照ください。

[Terraform for さくらのクラウド スタートガイド(全5回)](http://knowledge.sakura.ad.jp/knowledge/7230/)

こちらの記事は一応残しておきます。

## 連載目次

#### [第1回：導入編](http://qiita.com/yamamoto-febc/items/ae92cd258cf040957487)
  - 概要
  - セットアップ
  - 実践 Step1：サーバー１台構成

#### [第2回：実践編](http://qiita.com/yamamoto-febc/items/2480b11c9e6a8b64f78d)
  - 実践 Step2：構成/設定の変更
    - リソースの追加
    - リソースの変更
    - count構文
    - output機能

#### [第3回：実践編2](http://qiita.com/yamamoto-febc/items/fe954e2d4a92b864cfef)
  - 実践 Step3：プロビジョニング
    - プロビジョニング接続設定
    - fileプロビジョニング
    - remote-execプロビジョニング
    - さくらのクラウドDNSリソースの利用

#### [第4回：応用編](http://qiita.com/yamamoto-febc/items/a9795cb909bd9b69f729)
  - 実践 Step4：Web/DB 2-Tier構成
    - MySQLの利用
    - スイッチによるプライベートネットワークの構築
    - パケットフィルタ/シンプル管理:Slack通知の利用

#### **[第5回：応用編2](http://qiita.com/yamamoto-febc/items/4b774404e041fa05688a)(当記事)**
  - 実践 Step5：東京/石狩 マルチゾーン構成
    - 東京と石狩でマルチゾーン構成
    - GSLBによるゾーン間HA構成
    - MySQL レプリケーション + PHP(mysqlnd_ms)によるDBのクラスタリング
    - `null_resource`や`template_file`などの特殊なリソース
    - tfファイルのリファクタリングとモジュール化 

---   

連載第5回です。
[第1回](http://qiita.com/yamamoto-febc/items/ae92cd258cf040957487)から順番にお読みください。

[第5回サンプルコード](https://github.com/yamamoto-febc/terraform-for-sakuracloud-start-guide/tree/no5) / [第4回との差分表示](https://github.com/yamamoto-febc/terraform-for-sakuracloud-start-guide/compare/no4...no5)


---

# 実践 Step5：東京/石狩 マルチゾーン構成

前回の応用編で実運用ができる状態にだいぶ近づきました。
今回はさらに可用性を高める仕組みを導入し、
シンプル・安価ながら安定したサービス提供ができる環境を構築します。

前回(第4回)は以下のような構成でした。

#### 第4回の構成
![server04.png](https://qiita-image-store.s3.amazonaws.com/0/20668/26ccbdc7-4147-7b01-4dc4-94e65457a6a7.png)

この構成に対し以下の対応を行います。

  - 対応1:東京/石狩マルチゾーン化/ブリッジ接続
  - 対応2:WebサーバーのロードバランシングにGSLBを利用
  - 対応3:DBサーバーを冗長化、双方向レプリケーションを行う

これらの対応を行った後の構成は以下のようになります。

#### 今回の構成
![server05.png](https://qiita-image-store.s3.amazonaws.com/0/20668/b1c83070-ccc5-d0d5-3e4c-968e9fa57416.png)

それぞれの対応をもう少し詳しく見ておきましょう。

### 対応1:東京/石狩マルチゾーン化/ブリッジ接続

これまでは石狩第1ゾーン(tfファイルでゾーンを明示しなかった場合のデフォルト)にのみサーバーを設置していましたが、東京にもサーバーを設置することでより効果的な負荷分散や地理的な災害対策が行えます。
しかもさくらのクラウドには[ブリッジ接続](http://cloud-news.sakura.ad.jp/bridge/)というサービスがあります。
異なるリージョン・ゾーンにあるスイッチをL2で接続してくれるという便利なものです。

引用元：[さくらクラウドニュース「ブリッジ接続」](http://cloud-news.sakura.ad.jp/bridge/)
![TOKYO-Brige.png](https://qiita-image-store.s3.amazonaws.com/0/20668/1cbabb5c-80fb-8e47-da0e-63cef8362285.png)

今回はこのブリッジ接続を用いて、各サーバーが東京/石狩をまたいで同一セグメントにいるかのような構成とします。

### 対応2:Webサーバのロードバランシング

WebサーバーのロードバランシングをDNSラウンドロビンからGSLBヘ変更します。
これまでは`さくらのクラウドDNS`にWebサーバーのAレコードを台数分登録することでDNSラウンドロビンしていたのですが、
ダウンしているWebサーバーのレコードもDNS応答してしまうという問題がありました。

そこで、さくらのクラウドの提供する[GSLB](http://cloud-news.sakura.ad.jp/gslb/)機能を利用します。
GSLBとは「**G**lobal **S**erver **L**oad **B**alancing」の略です。
ロードバランシングしたいFQDNの代理DNSサーバーとして動作してくれます。
配下のサーバーの死活監視(ping/http/https/tcp)を行い、生きているサーバーのDNSレコードを返すという動きです。

引用元：[さくらのクラウドニュース「GSLB」](http://cloud-news.sakura.ad.jp/gslb/)
![gslb1-1024x417.png](https://qiita-image-store.s3.amazonaws.com/0/20668/efa69138-4c46-1227-6065-ef3d31bb0e75.png)


今回は東京/石狩の計4台のWebサーバーを配下に設定します。

### 対応3:DBサーバーを冗長化、双方向レプリケーションを行う

対応1により、ゾーン間をL2で結んだ状態となっています。
このネットワークを使い、DBサーバーを東京/石狩において双方向でMySQLレプリケーションを行います。
また、Webサーバー(PHPアプリ)からのDB接続には[mysqlnd_ms](http://php.net/manual/ja/book.mysqlnd-ms.php)を利用します。
まだまだ日本語情報が少ないですが、PHP側からは透過的に複数のMySQLサーバーを使えるというものです。

**注意**
稼働させるシステムによっては双方向レプリケーションがそぐわない場合もあります。
システム要件などよく確認の上で採用してください。

# 実践

前回の最終的な定義ファイル(tfファイル)は[こちら](https://github.com/yamamoto-febc/terraform-for-sakuracloud-start-guide/tree/no4)です。
(長いのでgithubへのリンクとしています)

これを元に追加/変更していきます。
なお、今回は定義ファイル(tfファイル)について、変更内容やポイントだけ説明し、細部は省略します。
ここまで連載読んでいただいた方であれば十分に読み解いていけるとおもいます。

最終的な定義ファイル類は[こちら](https://github.com/yamamoto-febc/terraform-for-sakuracloud-start-guide/tree/no5)に置きました。

今回のポイントは以下の通りです。

  - 1) 東京/石狩ゾーン指定方法
  - 2) ブリッジ接続方法
  - 3) GSLB利用方法
  - 4) MySQLレプリケーション対応
  - 5) モジュール(`module`)でのtfファイルのリファクタリング

順番に見ていきましょう。

## 1) 東京/石狩ゾーン指定方法

現在はtfファイルで明示していないため、すべてのリソースがデフォルトの石狩第1ゾーン(is1a)に置かれています。
これをリソースごとに設置するゾーンを明示してあげることで複数ゾーンへの設置ができるようになります。

### ゾーンの指定方法

プロバイダ設定、またはリソース設定にて指定可能です。
両方を指定することも可能です。

設定する値は、ゾーンごとに以下の文字列です。
 
  - 石狩第1ゾーン : `is1a`
  - 石狩第2ゾーン : `is1b`
  - 東京第1ゾーン : `tk1a`
  - サンドボックス  : `tk1v` **(注意：サンドボックスではプロビジョニングが動かせません)**

#### プロバイダ設定で指定する場合

プロバイダ設定で指定する場合は以下のように記載します。
この場合、各リソースで上書きしない限り、ここで指定したゾーンに設置されるようになります。

```sakura.tf(プロバイダ設定でのゾーン指定)
provider "sakuracloud" {
    token = "[ACCESS_TOKEN]"
    secret = "[ACCESS_TOKEN_SECRET]"

    #ゾーン指定
    zone = "is1a" 
}
```

#### 各リソースで指定する場合

プロバイダ設定で指定する場合は以下のように記載します。
リソース設定にてゾーンが指定された場合はプロバイダ設定で指定したゾーンを上書きします。

```sakura.tf(リソース設定でのゾーン指定)
resource "sakuracloud_switch" "sw01" {
  name = "sw01"

  #ゾーン指定
  zone = "is1a"
}
```

なお、以下のリソースは「グローバルリソース」と呼ばれる、全ゾーンで共通のリソースです。
全ゾーンに跨るリソースのため、ゾーンの指定はできません。

  - SSH鍵
  - スタートアップスクリプト
  - DNS
  - GSLB
  - シンプル監視


## 2) ブリッジ接続方法

今回の対応の目玉のひとつ、東京と石狩をL2で結んでくれるブリッジの使い方です。
非常に簡単なのでtfファイルを示します。

```sakura.tf(ブリッジ接続)
# Bridge
resource "sakuracloud_bridge" "br01" {
    name = "br01"
}

# Switch(石狩)
resource "sakuracloud_switch" "sw01" {
    name = "sw01"
    zone = "is1a"
    bridge_id = "${sakuracloud_bridge.br01.id}"
}
# Switch(東京)
resource "sakuracloud_switch" "sw02" {
    name = "sw02"
    zone = "tk1a"
    bridge_id = "${sakuracloud_bridge.br01.id}"
}
```

ブリッジ`sakuracloud_bridge`リソースを定義し、各ゾーンのスイッチに`bridge_id`を設定すればOKです。
簡単ですね:bangbang:

## 3) GSLB利用方法

GSLBはさくらのクラウドDNSとの合わせ技で設定します。
以下のような定義になります。

```sakura.tf(GSLB定義サンプル)
/****************************************
 GSLB(合計4台を配下に登録)
*****************************************/
resource "sakuracloud_gslb" "gslb" {
    name = "gslb01"
    health_check = {
        protocol = "http"
        delay_loop = 10
        host_header = "web.fe-bc.net"
        path = "/index.php"
        status = "200"
    }
    servers = {
      ipaddress = "${element(split("," , module.is1a_web_servers.ip_addresses),0)}"
    }
    servers = {
      ipaddress = "${element(split("," , module.is1a_web_servers.ip_addresses),1)}"
    }
    servers = {
      ipaddress = "${element(split("," , module.tk1a_web_servers.ip_addresses),0)}"
    }
    servers = {
      ipaddress = "${element(split("," , module.tk1a_web_servers.ip_addresses),1)}"
    }
}

/****************************************
 さくらのクラウドDNS
*****************************************/
resource "sakuracloud_dns" "dns" {
    zone = "fe-bc.net"
    records = {
        name = "web" 
        type = "CNAME"
        #GSLBのFQDNをweb.fe-bc.netのCNAMEとして登録
        value = "${sakuracloud_gslb.gslb.FQDN}." #注：末尾にピリオドつける!!
    }
}
```

GSLBの`servers`に監視したいサーバーの実IPアドレスを登録します。
今回はWebサーバー4台のグローバルIPを指定します。

また、DNSにCNAMEレコードを登録しています。
こうすることで、対象のFQDNへのDNSクエリがGSLBに対して行われるようになります。

#### 参考：DNSクエリの流れ

1) クライアントがDNSサーバーへ`web.fe-bc.net`のIPアドレス問い合わせ
2) DNSサーバーがCNAMEレコード(GSLBのFQDN)を返却
3) クライアントがDNSサーバーヘGSLBのFQDNのIPアドレス問い合わせ
4) GSLBは死活監視を行っている配下のサーバーから生きているサーバーのIPアドレスを返す


## 4) MySQLレプリケーション対応

今回の本筋とはあまり関係ありませんのでさらっと流します。

やっていることは以下の通りです。

  - レプリケーションを有効にしたmy.cnfをあらかじめ作成しておく
  - mysqlnd_ms用の設定ファイルをあらかじめ作成しておく
  - 設定ファイル類をfileプロビジョニングでアップロード
  - 必要なライブラリはremote-execプロビジョニング時にyumでインストール

今回は簡単なデモ用Webアプリからの利用しかしていないですが、
wordpressなどの本格的なWebアプリでも十分利用出来る構成です。

各リソースの定義を見れば大体読み解けると思いますが、以下の2点がポイントです。

 - 1: レプリケーションの開始を制御するため`null_resource`を利用
 - 2: プロビジョニングのコマンドの組み立てに`template_file`を利用

いづれもTerraformに組み込みのリソースで、tfファイルの制御に用いるものとなっています。
それぞれ見ていきましょう。

### `null_resource`について

`null_resource`とは、その名の通り実体を持たないリソースで、
複数のリソースに依存したリソースを定義するために利用します。
例えば、以下のような`null_resource`を定義した場合、
サーバーAとサーバーBの作成が完了してから`null_resource`のプロビジョニングを実行するという
タイミングの制御のようなことができます。

```null_resourceのサンプル　
resource sakuracloud_server "serverA"{
    # (省略)
}
resource sakuracloud_server "serverB"{
    # (省略)
}

resource "null_resource" "db_replication" {
    triggers{
        # 依存を定義
        server_ids = "${sakuracloud_server.serverA.id},${sakuracloud_server.serverB.id}"
    }
 
    # MySQLレプリケーション開始
    provisioner "remote-exec" {
       connection {
         user = "root"
         host = "${var.server01_ip}"
         private_key = "${file("${path.root}/${var.ssh_keyfile}")}"
       }
       inline = [
          "cat << EOF | mysql -u root --password='${var.mysql_root_password}'\n${template_file.start_replication_sql01.rendered}\nEOF\n"
        ]
    }
} 

```

今回はMySQLレプリケーションの開始をこの`null_resource`を使うことで、
2台のMySQLサーバーの構築が終わってから`CHANGE MASTER`や`START SLAVE`といった
SQLを実行するようにタイミング調整しています。

### `template_file`について

今回プロビジョニング時に実行するコマンドを組み立てるのに`template_file`というリソースを使いました。
これは、その名の通りテンプレート機能を提供するもので、以下のように利用します。

```template_fileのサンプル
resource "template_file" "sample01" {
    template = "${file(./sample.txt)}"
    vars {
        target_name = "hoge"
    }
}
resource "template_file" "sample02" {
    template = "${file(./sample.txt)}"
    vars {
        target_name = "fuga"
    }
}

```

```上記のsample.txtの中身
echo "Hello ${target_name}!"
```

このテンプレートを使う側は以下のようになります。

```テンプレート利用側のサンプル

    # プロビジョニング
    provisioner "remote-exec" {
       inline = [
          "${template_file.sample01.rendered}"
          "${template_file.sample02.rendered}"
        ]
    }
```

`${template_file.[リソースのID].rendered}`とすればテンプレート内の
変数を展開した文字列を参照できます。
今回はmy.cnfの組み立てや`CHANGE MASTER`SQL文の組み立てに利用しています。

## 5) モジュール(`module`)でのtfファイルのリファクタリング

今回の構成では石狩/東京ゾーンに同じ構成のリソース(サーバー、ディスク、パケットフィルタなど)が必要になります。
tfファイルの該当部分をコピペしてもいいのですが、Terraformにはリソース定義を共通化できる`module`という仕組みがあります。
今回はこれを使ってtfファイルをリファクタリングしています。

#### `module`について

Terraformには`module`という仕組みがあります。
リソースの定義をひとまとめにしておいて、再利用するための仕組みです。
この`module`のポイントは**外部から変数を受け取れる**ということです。

簡単な`module`の例を見てみましょう。
"./modules/sample_module"というディレクトリ配下に以下のファイルを作成します。

```module.tf(モジュール定義)
variable "disk_name" {
    default = "diskname"
}
resource "sakuracloud_disk" "disk" {
    name = "${var.disk_name}"
    size = 100
    source_archive_name = "Ubuntu 16.04 LTS 64bit"
}
```

通常のtfファイルですね。サイズが100GB、ソースとしてUbuntuを使うディスクの定義です。
variableもこれまで出てきたものと同様です。

今度は`module`を利用する側のtfファイルを見てみましょう。
以下のようになります。

```sakura.tf(モジュール利用)
module "sample_disk01" {
    source = "./modules/sample_module"
    disk_name = "test01"
}
module "sample_disk02" {
    source = "./modules/sample_module"
    disk_name = "test02"
}
```

なんとなく読み取れると思いますが、`source`でモジュールの在り処を指定し、
モジュール側で定義された　`variable`を指定するという形で利用します。
こうしておけば一つの定義を再利用できますね!!

なお、今回はモジュールの在り処として"./modules/sample_module"のようにローカルファイルパスを指定しましたが、他にも以下のようなものが指定できます。

 - ローカルファイルパス
 - GitHubのURL
 - BitBucketのURL
 - GitやMercurialのリポジトリ
 - HTTP URLs

モジュールを使う場合の注意点としては、`terraform plan`などの実行の前に
あらかじめ`terraform get`というコマンドを実行する必要があるということです。
(未実行の場合はその旨警告が表示されます)
ローカルファイルパスの場合でも`terraform get`の実行は必要ですので忘れずに実行しましょう。

## 実行

さて、今回も盛りだくさんです。
今回の最終的なtfファイル類は[こちら](https://github.com/yamamoto-febc/terraform-for-sakuracloud-start-guide/tree/no5)にありますので、実行する場合はこちらを参考に必要なtfファイル類を準備してください。

さて、準備できたら`terraform plan`と`terraform apply`するだけです。
(`terraform get`していない場合は実行してから!!)

、、、出来ましたよね？
今回リソースが多いですので多少時間がかかると思います。気長に待ちましょう。

### 確認

GSLBの確認として各Webサーバーを落としてみたり、
DBの確認としてDBサーバーを落としてみたりしてみてください。
Webサーバー/DBサーバーともにいずれかがダウンしてもサービス継続できていますよね？
あまりデータ更新頻度の高くない参照中心のシステムであれば十分に実用に耐える可用性を持っています。
あとはシステムごとの性能要件に合わせてチューニングしたりサーバースペックを調整したり、
Webサーバーをスケールアウトさせてみたりしてみてください。

## まとめ

ここまで全5回にわたって「Terraform for さくらのクラウド」でインフラ構築を行ってきました。
tfファイル = Code を中心としたインフラ構築、いわゆる「Infrastructure as code」の世界を
多少覗けましたでしょうか？

コード化することにより、インフラが「可視化」されただけでなく、これまでDevs特有のテクニックであった
バージョン管理やリファクタリングなどが使えるようになること、さらに「テスト〜コーディング〜レビュー〜デプロイ」というフローが作れることや、そのフローを自動化(例えばGithub + TravisCI)するなんてことも出来るようになりますね！

さくらのクラウドでは、イベントなどで配布されている2万円クーポンを活用すれば
10台を超える大量の分散サーバーを試す、なんてことも完全無料で出来ちゃいます。

これからTerraformを触ってみようとしている方は
Terraform for さくらのクラウドではじめの一歩を踏み出してみてはいかがでしょうか？


## あとがき

もし分からないところ、読み解けないところがあればTwitterなどで(@yamamoto-febc)あてに
お気軽に質問ください。

ここまでお読みくださりありがとうございました。

以上です。