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

# 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ファイルのリファクタリングとモジュール化

---   


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

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


---

# 実践 Step4：Web/DB 2-Tier構成

いよいよ応用編になります。
Terraformの基本的な操作を駆使して実運用に耐えるインフラを構築していきましょう。

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

![servers03.png](https://qiita-image-store.s3.amazonaws.com/0/20668/41304bb4-97e2-4ef1-b2d0-997e486398f8.png)

２台のWebサーバに加えてDNSへの登録も行っていましたね。
現時点でも外部からの接続ができる状態なのですが、
今回は本番運用意識して構成を変更していきます。

#### Web層/DB層の２階層(2-tier)化

スイッチを追加して、DBを配置するプライベートなネットワークを構築します。
以下のリソースを追加します。

  - プライベートネットワーク用にスイッチ(sw01)を追加
  - プライベートネットワーク内にDBサーバー(server_db + disk_db)を追加

DBサーバーではMySQLを稼働させます。

#### 非機能要件対応

本番運用時にはセキュリティ対応や監視/測定、バックアップなどの非機能要件も満たす必要があります。
今回はバックアップは手動、監視は通知だけ来るようにしておき復旧は手動で行うものとして、
以下の対応だけ行います。

  - パケットフィルタを追加してセキュリティ対策
  - [シンプル監視](http://cloud-news.sakura.ad.jp/simplemonitor/)で監視 + Slackへ通知


#### 今回の最終的な構成

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

なんだか一気にリソースが追加されて複雑になってきましたね。

でも大丈夫です。
Terraformを使うのであれば、トライ&エラーで定義ファイル(tfファイル)を
:star:**育てて**:star: いけばいいんです。
tfファイルを作ってテストしておけばその環境の再現は簡単なんですからね。

では早速実践していきましょう。

# 実践

## 第1段階：DBサーバー追加

まずはDBサーバーを追加してみましょう。
この段階では以下のような構成になります。

![server04_part1.png](https://qiita-image-store.s3.amazonaws.com/0/20668/e55d048e-c55d-6143-51f9-b49c8072491a.png)

DBサーバーとWebサーバーとではセキュリティ要件が異なるため、
SSH公開鍵はWeb層(server01と02)とは別のものを使うようにしておきます。
追加するリソースは以下の通りです。

  - サーバー(server_db)
  - ディスク(disk_db)
  - SSH公開鍵(dbkey)

加えて、DBサーバーのプロビジョニングも行っておきます。
さくらのクラウドのパブリックアーカイブからCentOS7.2をインストールした場合、
mysql communityのyumリポジトリがすでに登録されており、インストールが容易なため
MySQL5.6をインストールしていきます。

すでに前回までに出てきたテクニックだけで対応できますね！

### 準備

DBサーバー用にキーペアを作成しましょう。


```bash:SSH用キーペア作成
$ ssh-keygen -C "" -f ./id_rsa_db
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase):  #何も入力せずEnter
Enter same passphrase again:                 #何も入力せずEnter

```

カレントディレクトリに`id_rsa_db`と`id_rsa_db.pub`が作成されているはずです。

### 定義ファイル(tfファイル)編集

それではtfファイルを編集しましょう。前回までのtfファイルは以下の通りです。

```sakura.tf(前回までのtfファイル全体)
provider "sakuracloud" {
    token = "[ACCESS_TOKEN]"
    secret = "[ACCESS_TOKEN_SECRET]"
}

resource "sakuracloud_disk" "disk" {
    name = "${format("disk%02d" , count.index+1)}"
    source_archive_name = "CentOS 7.2 64bit"
    ssh_key_ids = ["${sakuracloud_ssh_key.mykey.id}"]
    disable_pw_auth = true
    count = 2
}

resource "sakuracloud_server" "server" {
    name = "${format("server%02d" , count.index+1)}"
    disks = ["${element(sakuracloud_disk.disk.*.id,count.index)}"]
    count = 2
    # 1: サーバーにはSSHで接続
    connection {
       user = "root"
       host = "${self.base_nw_ipaddress}"
       private_key = "${file("./id_rsa")}"
    }

    # ２： yumでapache+PHPのインストール
　　　　　　　　provisioner "remote-exec" {
        inline = [
          "yum install -y httpd httpd-devel php php-mbstring",
          "chkconfig httpd on"
        ]
   }

   # 3: Webコンテンツをアップロード
   provisioner "file" {
        source = "webapps/"
        destination = "/var/www/html"
   }

}

resource "sakuracloud_ssh_key" "mykey" {
    name = "mykey"
    public_key = "${file("./id_rsa.pub")}"
}

resource "sakuracloud_dns" "dns" {
    zone = "fe-bc.net"
    records = {
        name = "web"
        type = "A"
        value = "${sakuracloud_server.server.0.base_nw_ipaddress}"
    }
    records = {
        name = "web"
        type = "A"
        value = "${sakuracloud_server.server.1.base_nw_ipaddress}"
    }
}

output "global_ip" {
    value = "${join("\n" , formatlist("%s : %s" , sakuracloud_server.server.*.name , sakuracloud_server.server.*.base_nw_ipaddress))}"
}
```

ここに今回のリソースを足していきましょう。
以下を追記します。MySQLのパスワードは任意のものを設定してください。

```sakura.tf(追加分)

variable "mysql_values" {
   default = {
      # MySQLのrootユーザーのパスワード
      root_password = "mysql_password"
      # MySQLデモアプリ用ユーザーの名前
      user_name = "demo"
      # MySQLデモアプリ用ユーザーのパスワード
      user_password = "demo_password"
   }
}

resource "sakuracloud_ssh_key" "dbkey" {
    name = "dbkey"
    public_key = "${file("./id_rsa_db.pub")}"
}

resource "sakuracloud_disk" "disk_db" {
    name = "disk_db"
    source_archive_name = "CentOS 7.2 64bit"
    ssh_key_ids = ["${sakuracloud_ssh_key.dbkey.id}"]
    disable_pw_auth = true
}

resource "sakuracloud_server" "server_db" {
    name = "server_db"
    disks = ["${sakuracloud_disk.disk_db.id}"]
    # サーバーにはSSHで接続
    connection {
       user = "root"
       host = "${self.base_nw_ipaddress}"
       private_key = "${file("./id_rsa_db")}"
    }

    provisioner "remote-exec" {
        inline = [
          "yum install -y mysql-community-server",
          "systemctl start mysql.service",
          "mysql -uroot -e 'GRANT ALL ON *.* TO ${var.mysql_values.user_name}@\"192.168.2.%\" IDENTIFIED BY \"${var.mysql_values.user_password}\"'" ,
          "mysqladmin -u root password '${var.mysql_values.root_password}'",
          "systemctl stop firewalld.service",
          "systemctl disable firewalld.service"
        ]
    }
}
```

#### ポイント:`variable`

`variable`という記述が新たに出てきましたね。
これはtfファイル内で利用出来る変数を定義するものです。
`variable`で定義しておいた値は、`${var.変数名}`で参照できます。
また、variableはコマンドラインから与えたり、別途設定ファイルを用意しておいて
与えることもできます。(tfvarファイル)
`variable`についての詳細な説明は[Terraformドキュメント](https://www.terraform.io/intro/getting-started/variables.html)を参照ください。

ここではMySQLのインストール、MySQLユーザーの追加、MySQLのrootパスワードの設定までを行っています。

**注意**
今回は設定を簡単にするためにmysqlのrootユーザーのパスワードを`mysqladmin`コマンドで
実行するだけの初期化としていますが、実際に運用する際はmysql_secure_instration相当の処理を行うなど、十分にセキュリティに配慮ください。

### outputの追加

このままでは`output`にDBサーバーのグローバルIPが表示されませんので、
DBサーバーのグローバルIP表示用に`output`を定義しましょう。
ついでにSSH接続補助も`output`定義しておきます。

以下を追記してください。

```sakura.tf(SSH接続補助output追記)
output "global_ip_db" {
    value = "${format("%s : %s\n" , sakuracloud_server.server_db.name , sakuracloud_server.server_db.base_nw_ipaddress)}"
}
output "ssh_web01" {
    value = "${format("ssh root@%s -i %s/id_rsa" , sakuracloud_server.server.0.base_nw_ipaddress , path.root)}"
}
output "ssh_web02" {
    value = "${format("ssh root@%s -i %s/id_rsa" , sakuracloud_server.server.1.base_nw_ipaddress , path.root)}"
}
output "ssh_db" {
    value = "${format("ssh root@%s -i %s/id_rsa_db" , sakuracloud_server.server_db.base_nw_ipaddress , path.root)}"
}
```

これで`terraform output ssh_web01`と実行すればsshコマンドが出力されます。
`$(terraform output ssh_web01)`などとすれば実行できますよ!

以後作成したサーバーにSSHする際は以下コマンドでOKです。

  - Webサーバ(server01) : `$(terraform output ssh_web01)`
  - Webサーバ(server02) : `$(terraform output ssh_web02)`
  - DBサーバ(server_db) : `$(terraform output ssh_db)`

### `plan`と`apply`の実行

さて、いつも通り`terraform plan`してから`terraform apply`しましょう。
DBサーバーが追加されましたか？
SSH接続するなどで動作確認もしておきましょう。

## 第2段階：スイッチ追加でプライベートネットワークと接続

では続いてスイッチを追加しましょう。
こんな感じになります。

![server04_part2.png](https://qiita-image-store.s3.amazonaws.com/0/20668/782c4fca-fe4f-0f3e-376e-2876f4c2031d.png)

やることは
 
  - スイッチ用リソース(sakuracloud_switch)の追加
  - スイッチとサーバーの接続
  - プライベートIPの割り振り(プロビジョニングで実施)

です。
また、WebサーバーとDBサーバー間をプライベートネットワークで接続できるようになりますので、
デモ用のWebアプリ(PHP)もDBを使うように変更していきます。


順番にいきましょう。

### スイッチ用リソースの定義

いつも通りtfファイルに追記しましょう。
以下を追記してください。

```sakura.tf(スイッチ追加)
resource "sakuracloud_switch" "sw01"{
    name = "sw01"
}
```

えっ？これだけ？
はい。スイッチは設定項目が少ないのです、、

### スイッチとサーバーの接続

サーバーの定義部分を修正しましょう。

```sakura.tf(サーバーとスイッチの接続)
# Webサーバー
resource "sakuracloud_server" "server" {
    #(中略) 

    # スイッチと接続するインターフェースを定義
    additional_interfaces = ["${sakuracloud_switch.sw01.id}"]
 
   # 以下プロビジョニング(省略)
}
# DBサーバー
resource "sakuracloud_server" "server_db" {
    #(中略) 

    # スイッチと接続するインターフェースを定義
    additional_interfaces = ["${sakuracloud_switch.sw01.id}"]
 
   # 以下プロビジョニング(省略)
}

```

### プライベートIPの割り当て(プロビジョニング)

これで各サーバーに先ほど追加したスイッチに接続した状態のNICが追加されています。(eth1)
eth1にIPアドレスを設定するようにプロビジョニングしましょう。

まずは手元のマシンで以下のスクリプトを作成しておきます。

```bash:IP設定用スクリプト作成
$ vi provision_private_ip.sh
```

```bash:provision_private_ip.sh
#!/bin/sh

# eth1のIP設定
cat << EOS >> /etc/sysconfig/network-scripts/ifcfg-eth1
BOOTPROTO=static
PREFIX0=24
DEVICE=eth1
IPADDR0=$1
ONBOOT=yes
EOS

#反映
ifdown eth1; ifup eth1
```

引数で受け取ったIPアドレスをeth1に設定するスクリプトです。
このスクリプトをTerraformのプロビジョニング機能でアップロード&実行することでIP設定します。

ではtfファイルに記載しましょう。

```sakura.tf(プライベートIP割り振り)

variable "private_ip_addresses" {
    default {
      server01 : "192.168.2.101"
      server02 : "192.168.2.102"
      server_db : "192.168.2.201"
    }
}

# Webサーバー
resource "sakuracloud_server" "server" {
    #(中略) 
        
    # プロビジョニング定義に以下を追記
    # IP設定スクリプトをアップロード
    provisioner "file" {
        source = "provision_private_ip.sh"
        destination = "/tmp/provision_private_ip.sh"
    }

    # IP設定実行
    provisioner "remote-exec" {
        inline = [
          "chmod +x /tmp/provision_private_ip.sh",
          "/tmp/provision_private_ip.sh ${lookup(var.private_ip_addresses , self.name)}"
        ]
    }
}

# DBサーバー
resource "sakuracloud_server" "server" {
    #(中略) 
        
    # プロビジョニング定義に以下を追記
    # IP設定スクリプトをアップロード
    provisioner "file" {
        source = "provision_private_ip.sh"
        destination = "/tmp/provision_private_ip.sh"
    }

    # IP設定実行
    provisioner "remote-exec" {
        inline = [
          "chmod +x /tmp/provision_private_ip.sh",
          "/tmp/provision_private_ip.sh ${lookup(var.private_ip_addresses , self.name)}"
        ]
    }
}

```

#### ポイント:`lookup`

ここでは、サーバー名とプライベートIPの対応関係を`variable`として定義しておき、
`${lookup}`でサーバー名をキーとして持つ値を参照しています。
tfファイルの記述をスッキリさせるのに有効なテクニックです。

### Webアプリの修正

デモ用のWebアプリをDBに繋ぐように修正してみましょう。
今回はデモ目的ですので単純にDBに接続して値が取れていれば十分です。
よって、`index.php`にDB接続処理を直接書いちゃいます。
`webapps/index.php`を以下のように変更しましょう。

**MySQLのユーザー名/パスワードを変更している場合は
`new mysqli`の引数部分も修正してください**

```webapps/index.php
<?php
    date_default_timezone_set('Asia/Tokyo');
    echo "Terraform for さくらのクラウド スタートガイド用デモ". "<br /><br />";
    echo "IPアドレス:" . $_SERVER[ 'SERVER_ADDR' ] . "<br />";
    echo "時刻：" . date("Y/m/d H:i:s"). "<br />";
   
    //DB接続してSQLで時刻取得
    $mysqli = new mysqli('192.168.2.201', 'demo', 'demo_password');
    if ($mysqli->connect_error) {
        echo "fail on connect:".$mysqli->connect_error;
        exit();
    } 
    $mysqli->set_charset("utf8");
    $res = $mysqli->query("SELECT sysdate()");
    echo "DB時刻：" . $res->fetch_row()[0] . "<br />";
    $res->close();
    $mysqli->close();    
```

WebサーバーにPHP-MySQL用のライブラリも追加しておきましょう。
プロビジョニング部分を変更します。

```sakura.tf(Webサーバのプロビジョニング)
resource "sakuracloud_server" "server" {
#中略
    provisioner "remote-exec" {
        inline = [
          # php-mysqlndを追記
          "yum install -y httpd httpd-devel php php-mbstring php-mysqlnd",
          "systemctl restart httpd.service",
          "systemctl enable httpd.service",
          "systemctl stop firewalld.service",
          "systemctl disable firewalld.service"
        ]
    }
#中略
```

### `plan`と`apply`の実行

今回は実行前に`terraform destroy`してから
`terraform plan`と`terraform apply`しましょう。

うまく反映されましたね？
SSH接続した上で`ip addr show`や`ping`を試しておくのも良いでしょう。

この段階でブラウザでWebサーバーにアクセスすると、以下のような表示になるはずです。
![server04_webtest.png](https://qiita-image-store.s3.amazonaws.com/0/20668/ac93145d-d62f-8725-ecf0-7e61c697761d.png)


## 第3段階：パケットフィルタ、シンプル監視の追加

いよいよ最終形です。

![server04_part3.png](https://qiita-image-store.s3.amazonaws.com/0/20668/6fa08f7f-f932-a368-a250-42bb46618946.png)


  - パケットフィルタの追加
  - パケットフィルタをサーバーに適用
  - シンプル監視の追加

を行いましょう。

### パケットフィルタの追加

パケットフィルタを合計2種類定義します。

  - 1: Webサーバー用(pf_web)
  - 2: DBサーバー用(pf_db)

基本的なルールは

  - ICMP応答は許可
  - SSH接続は許可
  - 応答パケットは許可
  - 内部から外部へのパケットは許可
  - 以外はすべて拒否

とし、Webサーバー用については80番、443番ポートを許可します。

これをそれぞれのサーバーのeth0(グローバルIPを持っているNIC)に適用します。
DB接続についてはプライベートネットワーク(192.168.2.0/24)経由でのアクセスのため、
このパケットフィルタの適用対象外となり、サーバー間ではきちんと接続できます。

tfファイルは以下の通りです。

```sakura.tf:パケットフィルタ定義
resource "sakuracloud_packet_filter" "pf_web" {
    name = "pf_web"
    expressions = {
        protocol = "tcp"
        source_nw = "0.0.0.0/0"
        dest_port = "22"
        description = "Allow SSH"
        allow = true
    }
    expressions = {
        protocol = "tcp"
        source_nw = "0.0.0.0/0"
        dest_port = "80"
        description = "Allow www"
        allow = true
    }
    expressions = {
        protocol = "tcp"
        source_nw = "0.0.0.0/0"
        dest_port = "443"
        description = "Allow www(ssl)"
        allow = true
    }
    expressions = {
        protocol = "tcp"
        source_nw = "0.0.0.0/0"
        dest_port = "32768-61000"
        description = "Allow return packet(tcp)"
        allow = true
    }
    expressions = {
        protocol = "udp"
        source_nw = "0.0.0.0/0"
        dest_port = "32768-61000"
        description = "Allow return packet(udp)"
        allow = true
    }
    expressions = {
        protocol = "icmp"
        source_nw = "0.0.0.0"
        allow = true
        description = "Allow all icmp"
    }
    expressions = {
        protocol = "fragment"
        source_nw = "0.0.0.0"
        allow = true
        description = "Allow all fragment"
    }
    expressions = {
        protocol = "ip"
        source_nw = "0.0.0.0"
        allow = false
        description = "Deny all"
    }
}

resource "sakuracloud_packet_filter" "pf_db" {
    name = "pf_db"
    expressions = {
        protocol = "tcp"
        source_nw = "0.0.0.0/0"
        dest_port = "22"
        description = "Allow SSH"
        allow = true
    }
    expressions = {
        protocol = "tcp"
        source_nw = "0.0.0.0/0"
        dest_port = "32768-61000"
        description = "Allow return packet(tcp)"
        allow = true
    }
    expressions = {
        protocol = "udp"
        source_nw = "0.0.0.0/0"
        dest_port = "32768-61000"
        description = "Allow return packet(udp)"
        allow = true
    }
    expressions = {
        protocol = "icmp"
        source_nw = "0.0.0.0"
        allow = true
        description = "Allow all icmp"
    }
    expressions = {
        protocol = "fragment"
        source_nw = "0.0.0.0"
        allow = true
        description = "Allow all fragment"
    }
    expressions = {
        protocol = "ip"
        source_nw = "0.0.0.0"
        allow = false
        description = "Deny all"
    }
}
```

### パケットフィルタをサーバーに接続

tfファイルのサーバー部分を修正しましょう。

```sakura.tf(パケットフィルタ接続)
# webサーバー
resource "sakuracloud_server" "server" {
    # (中略)

    # パケットフィルタ接続
    packet_filter_ids = ["${sakuracloud_packet_filter.pf_web.id}"]
 
   # 以下プロビジョニング(省略)
}

# DBサーバー
resource "sakuracloud_server" "server_db" {
    # (中略)

    # パケットフィルタ接続
    packet_filter_ids = ["${sakuracloud_packet_filter.pf_db.id}"]
 
   # 以下プロビジョニング(省略)
}

```

### シンプル監視の追加

以下のような監視を行います。

  - pingによる死活監視(1分間隔)
  - WebサーバーへのHTTP監視(`/index.php`へリクエスト、1分間隔)
  - 通知はslackに対して行う

事前にslack連携用にwebhookのURLが必要です。
別途slackの設定を行っておいてください。

![slack_webhook.png](https://qiita-image-store.s3.amazonaws.com/0/20668/739ce9ca-a7f3-121b-faed-3eb9ba64d255.png)


tfファイルは以下の通りです。webhookのURLは各自置き換えてください。

```sakura.tf(シンプル監視)
variable "slack_webhook" {
    default = "https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX"
}

# ping監視(DBサーバー)
resource "sakuracloud_simple_monitor" "ping_monitor_db" {
    target = "${sakuracloud_server.server_db.base_nw_ipaddress}"
    health_check = {
        protocol = "ping"
        delay_loop = 60
    }
    notify_email_enabled = false
    notify_slack_enabled = true
    notify_slack_webhook = "${var.slack_webhook}"
}

# ping監視(webサーバー２台分)
resource "sakuracloud_simple_monitor" "ping_monitor_web" {
    count = 2
    target = "${element(sakuracloud_server.server.*.base_nw_ipaddress , count.index)}"
    health_check = {
        protocol = "ping"
        delay_loop = 60
    }
    notify_email_enabled = false
    notify_slack_enabled = true
    notify_slack_webhook = "${var.slack_webhook}"
}
# web監視(webサーバー2台分)
resource "sakuracloud_simple_monitor" "http_monitor_web" {
    count = 2
    target = "${element(sakuracloud_server.server.*.base_nw_ipaddress , count.index)}"
    health_check = {
        protocol = "http"
        delay_loop = 60
        path = "/index.php"
        status = "200"
    }
    notify_email_enabled = false
    notify_slack_enabled = true
    notify_slack_webhook = "${var.slack_webhook}"
}
```

### `plan`と`apply`の実行

いよいよですね！早速実行してみましょう。
うまくいきましたよね？

監視ができているか試しにサーバーを落としてみたりしてください。
slackに通知がきちんと届けばOKです。

## 最終的な定義ファイル(tfファイル)

今回の最終的なtfファイルは以下のようになっているはずです。
(コメントなど入れて整形しています)
うまくいかない方はこちらと比較してみてください。

```sakura.tf(第4回の最終形)
/*********************
 * Provider settings
 *********************/
provider "sakuracloud" {
    token = "[ACCESS_TOKEN]"
    secret = "[ACCESS_TOKEN_SECRET]"
}

/*****************
 * Variables 
 *****************/
variable "slack_webhook" {
    default = "https://hooks.slack.com/services/X0XXX0XXX/X0XXXXXXX/9XXXOxxxxO8XxxX4XX1xxxxx"
}
variable "mysql_values" {
   default = {
      root_password = "mysql_password"
      user_name = "demo"
      user_password = "demo_password"
   }
}
variable "private_ip_addresses" {
    default = {
      server01 = "192.168.2.101"
      server02 = "192.168.2.102"
      server_db = "192.168.2.201"
    }
}

/*****************
 * Disk
 *****************/
resource "sakuracloud_disk" "disk" {
    name = "${format("disk%02d" , count.index+1)}"
    source_archive_name = "CentOS 7.2 64bit"
    ssh_key_ids = ["${sakuracloud_ssh_key.mykey.id}"]
    disable_pw_auth = true
    count = 2
}
resource "sakuracloud_disk" "disk_db" {
    name = "disk_db"
    source_archive_name = "CentOS 7.2 64bit"
    ssh_key_ids = ["${sakuracloud_ssh_key.dbkey.id}"]
    disable_pw_auth = true
}

/*****************
 * Server 
 *****************/
resource "sakuracloud_server" "server" {
    name = "${format("server%02d" , count.index+1)}"
    disks = ["${element(sakuracloud_disk.disk.*.id,count.index)}"]
    additional_interfaces = ["${sakuracloud_switch.sw01.id}"]
    packet_filter_ids = ["${sakuracloud_packet_filter.pf_web.id}"]
    count = 2
    # サーバーにはSSHで接続
    connection {
       user = "root"
       host = "${self.base_nw_ipaddress}"
       private_key = "${file("./id_rsa")}"
    }

    # yumでapache+PHPのインストール
    provisioner "remote-exec" {
        inline = [
          "yum install -y httpd httpd-devel php php-mbstring php-mysqlnd",
          "systemctl restart httpd.service",
          "systemctl enable httpd.service",
          "systemctl stop firewalld.service",
          "systemctl disable firewalld.service"
        ]
    }

    # Webコンテンツをアップロード
    provisioner "file" {
        source = "webapps/"
        destination = "/var/www/html"
    }

    # IP設定スクリプトをアップロード
    provisioner "file" {
        source = "provision_private_ip.sh"
        destination = "/tmp/provision_private_ip.sh"
    }

    # IP設定実行
    provisioner "remote-exec" {
        inline = [
          "chmod +x /tmp/provision_private_ip.sh",
          "/tmp/provision_private_ip.sh ${lookup(var.private_ip_addresses , self.name)}"
        ]
    }


}

resource "sakuracloud_server" "server_db" {
    name = "server_db"
    disks = ["${sakuracloud_disk.disk_db.id}"]
    additional_interfaces = ["${sakuracloud_switch.sw01.id}"]
    packet_filter_ids = ["${sakuracloud_packet_filter.pf_db.id}"]

    # サーバーにはSSHで接続
    connection {
       user = "root"
       host = "${self.base_nw_ipaddress}"
       private_key = "${file("./id_rsa_db")}"
    }

    # yumでmysqlのインストール
    provisioner "remote-exec" {
        inline = [
          "yum install -y mysql-community-server",
          "systemctl start mysql.service",
          "mysql -uroot -e 'GRANT ALL ON *.* TO ${var.mysql_values.user_name}@\"192.168.2.%\" IDENTIFIED BY \"${var.mysql_values.user_password}\"'" ,
          "mysqladmin -u root password '${var.mysql_values.root_password}'",
          "systemctl stop firewalld.service",
          "systemctl disable firewalld.service"
        ]
    }

    # IP設定スクリプトをアップロード
    provisioner "file" {
        source = "provision_private_ip.sh"
        destination = "/tmp/provision_private_ip.sh"
    }

    # IP設定実行
    provisioner "remote-exec" {
        inline = [
          "chmod +x /tmp/provision_private_ip.sh",
          "/tmp/provision_private_ip.sh ${lookup(var.private_ip_addresses , self.name)}"
        ]
    }

}

/*****************
 * SSH key 
 *****************/
resource "sakuracloud_ssh_key" "mykey" {
    name = "mykey"
    public_key = "${file("./id_rsa.pub")}"
}

resource "sakuracloud_ssh_key" "dbkey" {
    name = "dbkey"
    public_key = "${file("./id_rsa_db.pub")}"
}

/*****************
 * Switch
 *****************/
resource "sakuracloud_switch" "sw01" {
  name = "sw01"
}

/*****************
 * PacketFilter
 *****************/
resource "sakuracloud_packet_filter" "pf_web" {
    name = "pf_web"
    expressions = {
        protocol = "tcp"
        source_nw = "0.0.0.0/0"
        dest_port = "22"
        description = "Allow SSH"
        allow = true
    }
    expressions = {
        protocol = "tcp"
        source_nw = "0.0.0.0/0"
        dest_port = "80"
        description = "Allow www"
        allow = true
    }
    expressions = {
        protocol = "tcp"
        source_nw = "0.0.0.0/0"
        dest_port = "443"
        description = "Allow www(ssl)"
        allow = true
    }
    expressions = {
        protocol = "tcp"
        source_nw = "0.0.0.0/0"
        dest_port = "32768-61000"
        description = "Allow return packet(tcp)"
        allow = true
    }
    expressions = {
        protocol = "udp"
        source_nw = "0.0.0.0/0"
        dest_port = "32768-61000"
        description = "Allow return packet(udp)"
        allow = true
    }
    expressions = {
        protocol = "icmp"
        source_nw = "0.0.0.0"
        allow = true
        description = "Allow all icmp"
    }
    expressions = {
        protocol = "fragment"
        source_nw = "0.0.0.0"
        allow = true
        description = "Allow all fragment"
    }
    expressions = {
        protocol = "ip"
        source_nw = "0.0.0.0"
        allow = false
        description = "Deny all"
    }
}
resource "sakuracloud_packet_filter" "pf_db" {
    name = "pf_db"
    expressions = {
        protocol = "tcp"
        source_nw = "0.0.0.0/0"
        dest_port = "22"
        description = "Allow SSH"
        allow = true
    }
   expressions = {
        protocol = "tcp"
        source_nw = "0.0.0.0/0"
        dest_port = "32768-61000"
        description = "Allow return packet(tcp)"
        allow = true
    }
    expressions = {
        protocol = "udp"
        source_nw = "0.0.0.0/0"
        dest_port = "32768-61000"
        description = "Allow return packet(udp)"
        allow = true
    }
    expressions = {
        protocol = "icmp"
        source_nw = "0.0.0.0"
        allow = true
        description = "Allow all icmp"
    }
    expressions = {
        protocol = "fragment"
        source_nw = "0.0.0.0"
        allow = true
        description = "Allow all fragment"
    }
    expressions = {
        protocol = "ip"
        source_nw = "0.0.0.0"
        allow = false
        description = "Deny all"
    }
}

/*****************
 * SimpleMonitor
 *****************/

# ping監視(DBサーバー)
resource "sakuracloud_simple_monitor" "ping_monitor_db" {
    target = "${sakuracloud_server.server_db.base_nw_ipaddress}"
    health_check = {
        protocol = "ping"
        delay_loop = 60
    }
    notify_email_enabled = false
    notify_slack_enabled = true
    notify_slack_webhook = "${var.slack_webhook}"
}

# ping監視(webサーバー２台分)
resource "sakuracloud_simple_monitor" "ping_monitor_web" {
    count = 2
    target = "${element(sakuracloud_server.server.*.base_nw_ipaddress , count.index)}"
    health_check = {
        protocol = "ping"
        delay_loop = 60
    }
    notify_email_enabled = false
    notify_slack_enabled = true
    notify_slack_webhook = "${var.slack_webhook}"
}
# web監視(webサーバー2台分)
resource "sakuracloud_simple_monitor" "http_monitor_web" {
    count = 2
    target = "${element(sakuracloud_server.server.*.base_nw_ipaddress , count.index)}"
    health_check = {
        protocol = "http"
        delay_loop = 60
        path = "/index.php"
        status = "200"
    }
    notify_email_enabled = false
    notify_slack_enabled = true
    notify_slack_webhook = "${var.slack_webhook}"
}


/*****************
 * DNS
 *****************/
resource "sakuracloud_dns" "dns" {
    zone = "fe-bc.net"
    records = {
        name = "web"
        type = "A"
        value = "${sakuracloud_server.server.1.base_nw_ipaddress}"
    }
    records = {
        name = "web"
        type = "A"
        value = "${sakuracloud_server.server.0.base_nw_ipaddress}"
    }
}

/*****************
 * Output
 *****************/
output "global_ip" {
    value = "${join("\n" , formatlist("%s : %s" , sakuracloud_server.server.*.name , sakuracloud_server.server.*.base_nw_ipaddress))}"
}
output "global_ip_db" {
    value = "${format("%s : %s\n" , sakuracloud_server.server_db.name , sakuracloud_server.server_db.base_nw_ipaddress)}"
}
output "ssh_web01" {
    value = "${format("ssh root@%s -i %s/id_rsa" , sakuracloud_server.server.0.base_nw_ipaddress , path.root)}"
}
output "ssh_web02" {
    value = "${format("ssh root@%s -i %s/id_rsa" , sakuracloud_server.server.1.base_nw_ipaddress , path.root)}"
}
output "ssh_db" {
    value = "${format("ssh root@%s -i %s/id_rsa_db" , sakuracloud_server.server_db.base_nw_ipaddress , path.root)}"
}
```


## まとめ

今回は盛りだくさんでしたね！

  - DBサーバーの追加
  - スイッチの追加 + プライベートネットワークの構築
  - パケットフィルタの追加
  - シンプル監視の追加

シンプルな構成のシステムなら十分に実用できるレベルまで近づきましたね！

次回はいよいよ仕上げとして

  - MySQL レプリケーション + PHP(mysqlnd_ms)によるクラスタリング
  - 東京と石狩でマルチゾーン構成
  - GSLBによるゾーン間HA構成

を行います。お楽しみに :bangbang: 

次回：[実践 Step5：東京/石狩 マルチゾーン構成](http://qiita.com/yamamoto-febc/items/4b774404e041fa05688a)

## おまけ：さくらのクラウドで503エラー

`terraform destroy`実行時に以下のようなエラーが出ることがあります。

```bash:さくらのクラウド503エラー
Error applying plan:

1 error(s) occurred:

* sakuracloud_server.server.0: 
Error deleting SakuraCloud Server resource: Error in response:
&sacloud.ResultErrorValue{
    IsFatal:true, 
    Serial:"c843d04c9fc1caaa26e4b1eba649245d", 
    Status:"503 Service Unavailable", 
    ErrorCode:"busy", 
    ErrorMessage:"サービスが利用できません。サーバが混雑しています。しばらく時間をおいてから再度お試しください。"
}

Terraform does not automatically rollback in the face of errors.
Instead, your Terraform state file has been partially updated with
any resources that successfully completed. Please address the error
above and apply again to incrementally change your infrastructure.
```

たくさんのリソースを一気にdestroyした場合などに出ることがあります。
慌てず騒がずちょっと時間をおいて`terraform destroy`を再実行しましょう。