【モダンTerraform】意外と便利!? Miscプロバイダーたち(概要編&Nullプロバイダー編)

f:id:febc_yamamoto:20180130182943p:plain

モダンTerraformシリーズです。

今回はTerraformのプロバイダーのうち、最近充実してきているMiscプロバイダーについてご紹介します。

Miscプロバイダーってなに?

最近Terraformのプロダイバーが増えてきたため、ドキュメントプロダイバーの分類が行われました。
現在は以下のような分類がなされています。

分類 概要
Major Cloud いわゆる3大クラウド(AWS/Azure/GCP)+α
Cloud ↑以外のパブリック/プライベートクラウドやHerokuなどのPaaSなど
Infrastructure Software KubernetesやRancherなどの基盤系ソフトウェア(ConsulやRabbitMQなども)
Network CDNDNSなどネットワーク系
VCS バージョン管理システム系(Bitbucket/GitHub/GitLab)
Monitor & System Management モニタリングやシステム管理系(OpsGenieやPagerDutyなども)
Database データベース(InfluxDB/MySQL/PostgreSQL)
Misc. その他諸々
Community サードパーティ製プロバイダー

Misc.プロダイバーはこの中でも分類しきれなかったその他のものですね。

Miscプロバイダーにはどんなものが含まれてるの?

現在は以下のプロバイダーが含まれています。

分類 概要
Archive ファイルやディレクトリのアーカイブを行う(現在はzipのみ対応)
Cobbler Linuxのネットワークインストール環境を構築/管理してくれるツールCobbler用プロバイダー
External 任意のプログラムをData Sourceとして利用する
Ignition ContainerLinux(CoreOS)で利用されているプロビジョニングユーティリティIgnition用の構成ファイルの出力
Local ローカルファイルの作成/参照を行う
Null 特殊な空リソース
Random ランダム値の生成を行う
Template テンプレート機能の提供
TLS キーペアの生成やCSRの生成、自己署名証明書の発行など

この中でも特によく使われるのは以下2つだと思います。

今回はこの中からNullプロバイダーについて紹介し、Templateプロバイダーやそれ以外のプロバイダーについては次回以降に紹介します。

Nullプロバイダー

NullプロバイダーはTerraformのリソースライフサイクルにおいて何も処理を行わないという特殊なリソースを提供します。
と言われてもわからないですよね。

これは通常のTerraformのリソースだけでは実現が難しいいくつかのケースに対応するためのものです。

Terraformは待ち合わせ処理が苦手

Terraformではtfファイルで定義されているリソース間の依存関係を検出し、自動的に構築順序を制御してくれます。
例えば以下のtfファイルの場合、リソースの構築順序はどうなるでしょうか?

# ディスクの定義
resource sakuracloud_disk "disk" {
  name = "foobar"
}

# サーバの定義
resource sakuracloud_server "server" {
  name  = "foobar"
  # 先程定義したディスクのIDを参照
  disks = ["${sakuracloud_disk.disk.id}"]
}

この場合、sakuracloud_server.serverリソースはsakuracloud_disk.diskリソースのIDを参照しているため、

sakuracloud_server.serverリソースがsakuracloud_disk.diskに依存している

状態となります。

Terraformはサーバを作成するにはディスクが必要という状態を検出し、

  • 1) ディスク
  • 2) サーバ

の順に作成してくれます。
(なお、依存関係はterraform graphコマンドで以下例のように表示できるGraphViz形式でグラフを出力してくれます) f:id:febc_yamamoto:20180204163928p:plain

では以下の場合はどうなるでしょうか?

# ディスクの定義(1本目)
resource sakuracloud_disk "disk01" {
  name  = "disk01"
}
# ディスクの定義(2本目)
resource sakuracloud_disk "disk02" {
  name  = "disk02"
}

# サーバの定義(1台目)
resource sakuracloud_server "server01" {
  name = "server01"
  # 先程定義したディスクのIDを参照
  disks = ["${sakuracloud_disk.disk01.id}"]
}
# サーバの定義(2台目)
resource sakuracloud_server "server02" {
  name = "server02"
  # 先程定義したディスクのIDを参照
  disks = ["${sakuracloud_disk.disk02.id}"]
}

先程の例にディスク/サーバを追加しそれぞれ2つずつリソースを作成する例となっています。 依存関係は以下のようになります。 f:id:febc_yamamoto:20180204165407p:plain

この場合、ディスク -> サーバの組み合わせの作成順序はこれまで通りですが、2つのディスク/サーバの作成順序はどうなるのでしょうか?

この辺りはTerraformの賢いところで、互いに関連しない(依存しない)リソースについては並列で処理が行われます。
先程の図の①の2つのディスクについては互いに関連しないので並列で作成が行われます。

しかし、中には並列で作成されると困る場面もあります。
例えばクラスタの構築などでは、先にマスターとなるノードをプロビジョニングしてからスレーブ側のノードをプロビジョニングする必要がある場合があります。
並列でサーバ作成されるとこの順序を守ることができません。
また、全てのノードがクラスタに参加した後でプロビジョニングを別途実行したいというような場合もあるでしょう。

そこでTerraformではこの問題を解決するいくつかの方法を提供しており、そのうちの一つがNullリソースです。

以下の例では2台のサーバの作成が完了してからプロビジョニングを行う例となっています。
2台のサーバの作成後というタイミング指定のためにNullリソースを利用しています。

# ...ディスクやサーバの定義(省略)...

# 2台のサーバ作成後に実行するプロビジョニング
locals {
    server_ids = ["${sakuracloud_server.server01.id}", "${sakuracloud_server.server02.id}"]
}

resource null_resource "provisioning" {
  triggers = {
    depends = "${join(",", local.server_ids)}" # サーバ01と02のIDを指定
  }

  provisioner "local-exec" {
    command = "run_initialize_cluster_playbook.sh"
  }
}

この例の場合の依存関係は以下のようになります。 f:id:febc_yamamoto:20180204172137p:plain

Nullリソースのtriggersに(localsを通じて)サーバ01/02のIDを指定することで依存関係を示しています。
依存される側が先に作成されますので、この例では結果的にサーバ2台の作成後にNullリソースに設定されたプロビジョニング処理が実行されることになります。

おまけ: 別解としてdepends_on

今回みたいに単純な例ならNullリソースでなくてもMeta-Parametersの中のdepends_onを利用することでも順序の制御が可能です。

参考記事: 【モダンTerraform】v0.11以降でdynamicとfor_eachが実装されるかも

各リソースにdepends_onパラメータを指定することで依存関係が指定できますので、これを利用して順序の制御を行います。

# サーバの定義(2台目)
resource sakuracloud_server "server02" {
  # ...

  # 1台目のサーバに依存することを明示
  # (結果的に1台目のサーバ作成後に2台目の作成が行われる)
  depends_on = ["sakuracloud_server.server01"]

  provisioner "local-exec" {
    command = "run_initialize_cluster_playbook.sh"
  }
}

f:id:febc_yamamoto:20180204173231p:plain

この方法は単純な反面、関連するリソース数が増えてくるとdepends_onを各所に書かないといけないというデメリットもありますので適材適所で使い分けましょう。

Null データソース(今ではほぼ出番なし)

NullプロバイダーにはNullリソースのデータソース版であるNullデータソースというものも存在します。

これは以下のようにOutput用に値を参照する部分を一箇所にまとめたり、再利用するために利用します。

data "null_data_source" "values" {
  inputs = {
    all_server_ids = "${concat(aws_instance.green.*.id, aws_instance.blue.*.id)}"
    all_server_ips = "${concat(aws_instance.green.*.private_ip, aws_instance.blue.*.private_ip)}"
  }
}

resource "aws_elb" "main" {
  # ...

  instances = "${data.null_data_source.values.outputs["all_server_ids"]}"
}

output "all_server_ids" {
  value = "${data.null_data_source.values.outputs["all_server_ids"]}"
}

output "all_server_ips" {
  value = "${data.null_data_source.values.outputs["all_server_ips"]}"
}

が、現在ではLocal Valuesの仕組みがあるためほぼ使うことはないでしょう。

参考: 【モダンTerraform】VariableとLocal Valuesの使い分けについて

Local Valuesとの違いとしてはterraform graphへの表示有無があります(Local Valuesはgraph上表示されない)が、実用上これが問題になるケースはほとんどないと思います。

ここでもやっぱりLocal Valuesを積極的に使いましょう。

終わりに

ということで今回はNullプロバイダーを扱いました。
うまく使わないと見通しの悪いtfファイルになってしまいますが、順序や依存関係の細かな調整を行うのにはNullリソースが便利ですので状況に応じて適宜使っていきましょう。

以上です。

【モダンTerraform】v0.11以降でdynamicとfor_eachが実装されるかも

f:id:febc_yamamoto:20180130182943p:plain

モダンTerraformシリーズです。

今回は最近のhashicorp/terraformでの開発状況から、現時点での最新版であるv0.11.3で未実装な機能の中で個人的にかなり期待している機能について紹介します。

countパラメータとその限界

全てのリソースにはMeta-parametersが指定できる

Terraformには全てのリソースに対しMeta-parametersという属性を指定できます。
これはData Sourcesに対しても指定可能となっています。

参考: Terraformドキュメント - Meta-parameters

具体的には以下のようなものがあります。(詳細はドキュメントを参照)

  • count (int)
    リソースの作成数を指定 ※現時点ではモジュールには指定不可
  • depends_on (list of strings)
    リソースが依存するリソースを指定 -> 具体的には作成順序などが制御される
  • provider (string)
    マルチプロバイダーの定義をしている場合に利用するプロバイダーを指定できる
    (例: AWSのリージョンごとにプロバイダー定義している場合にaws.westみたいに指定)
  • lifecycle (configuration block)
    リソースの作成/更新/破棄の挙動を調整する

それぞれに興味深いトピックはあるのですが、今日はcountについて扱います。

countパラメータの基本的な使い方

countパラメータは以下のように利用します。 以下の例ではディスクリソースを2つ作成しています。

resource sakuracloud_disk "disks" {
  name = "${format("disk-%02d", count.index+1)}"
  count = 2
}

countパラメータを指定すると、そのリソースの定義内でcount.indexという属性が利用できます。
これは自身のインデックスを参照するもので、0から始まる数値型の値となっています。
これを利用することで、localsなどとの併用で各リソースに固有の値を指定しておくことが可能です。

locals {
  # 監視先のドメインとパスを指定
  targets = [
    {
      domain = "example.com"
      path   = "/"
    },
    {
      domain = "example.jp"
      path   = "/foo/"
    },
  ]
}

resource sakuracloud_simple_monitor "monitors" {
  # 自身のインデックスでマップ(targetsの各要素)を取得、キーがdomainの値を参照する
  target = "${lookup(locals.target[count.index], "domain"}"

  health_check = {
    protocol   = "https"
    delay_loop = 60
    path       = "${lookup(locals.target[count.index], "path"}"
    status     = "200"
  }

  count = "${length(locals.target)}"
}

countが利用できない場面

上手く使えばtfファイルを非常にシンプルにしてくれるcountですが、利用できない場面というのがあります。

  • リソースの属性
  • モジュール

例えば以下の例ではリソースにsettingという属性があるのですが、これに対してのcount指定はできません。

resource "aws_elastic_beanstalk_environment" "myEnv" {
  name = "test_environment"
  application = "testing"

  setting {
    # !ここではcountが使えない!
    namespace = "aws:elasticbeanstalk:application:environment"
    name      = "HTTP_PROXY"
    value     = "10.1.2.1:8080"
  }
  setting {
    namespace = "aws:elasticbeanstalk:application:environment"
    name      = "TMPDIR"
    value     = "/var/myapp/tmp"
  }
}

議論中: dynanicとfor_each

この問題はGitHub上でも解決に向けた議論が行われています。

参考: GitHub(hashicorp/terraform) - Support count in resource fields #7034

このIssueの中で提案されているのがdynamicブロックとfor_eachです。 これを利用して先ほどの例を書き直すと以下のようになります。

# DRAFT: まだ提案段階でv0.11時点ではこの書き方は利用できません
resource "aws_elastic_beanstalk_environment" "myEnv" {
  name = "test_environment"
  application = "testing"

  dynamic "setting" {
    for_each = var.environment_variables # map型の変数を指定
    content {
      namespace = "aws:elasticbeanstalk:application:environment"
      name      = setting.foreach.key # 現在のイテレーションの要素は`foreach`キーで参照できる
      value     = setting.foreach.value
    }
  }
}

マップ型変数や他で定義したリソースの属性などを利用して、各要素をイテレーションしながら値の参照ができるようです。 この例だとsettingブロック内のfor_eachイテレーションされる各要素はsetting.foreachで参照でき、それぞれkeyvalueのように参照できるようです。

以下のようにイテレーターに名前をつけることでネストも可能なようです。

# DRAFT: Not yet integrated into Terraform, and details may change before final release

  dynamic "example" {
    for_each = var.example_items
    iterator = parent # 外側のループのイテレータの名前を指定
    content {
      dynamic "example" {
        for_each = parent.children 
        iterator = child # 内側のループのイテレータの名前を指定
        content {
          value        = child.value
          parent_value = parent.value
        }
      }
    }
  }

これは嬉しいですね!
これまでこれを行うにはプロバイダー側で親リソース/子リソースに分割して実装する必要があり、 プロバイダーの実装者としてはなかなか辛い思いをしていました。
(リソースを親子に分割すると作成順序とかロックとか色々考慮が増えるのです…)

現在の実装状況は?

本日の時点では@apparentlymart氏が実験的に実装を進めているようです。
(前出のIssue内のコメントを参照)

期待して待ちましょう!!!

以上です。

【モダンTerraform】ベストプラクティスはTerraform Module Registryを参照しよう

f:id:febc_yamamoto:20180201085942p:plain

今回は小ネタです。

Terraformでのベストプラクティス?

Terraformでのベストプラクティスは従来GitHubにて専用のリポジトリで公開されていました。

GitHub: hashicorp/best-practices

が、このリポジトリ、すでに「 Deprecated 」です。

じゃあどこ見ればいいのよ?

best-practicesリポジトリでも言及されていますが今後はTerraform Module Registryを参照すべしとのことです。

Terraform Module Registry

Twitterでも@mitchellh氏らが質問に答える形で言及していました。

f:id:febc_yamamoto:20180201085128p:plain

ただし、RegistryにはHashiCorpの方が作成したもの以外も含まれるため、 参考にする際は「HashiCorp Verified Modules」を見るのがオススメです。

Registryでモジュールを検索する時に条件として「Verified」を指定できるのでそれを利用しましょう。

Terraform Module Registry 検索ページ

f:id:febc_yamamoto:20180201085621p:plain

ということで

Terraform Module Registryを使いましょう。 以上です。

【モダンTerraform】VariableとLocal Valuesの使い分けについて

f:id:febc_yamamoto:20180130182943p:plain

はじめに

ナウでイケてるヤングな皆様におかれましてはTerraformを使うのはもはや当たり前ですよね?

このTerraformですが日々バージョンが上がっており、ネット上で公開されているtfファイルの書き方が若干古いものもちょいちょい見受けられます。

特にTerraform v0.10.3(2017/8/30リリース)で導入されたLocal Valuesについては利用している例が少ないように思いますので今回通常のvariableとの違いなどについてまとめてみます。

TL; DR 今北産業

  • tfファイル内の変数は基本的にLocal Valuesを使おう
  • 特に判定処理はLocal Valuesで明確な名前をつけよう
  • Variableを使うのは外部からのインプットにする場合だけ

そもそもLocal Valuesってなによ?

Local Valuesとはモジュール内に閉じて使える変数です。モジュール内でのローカル変数のようなものですね。

参考: Terraform ドキュメント - Local Values

なお通常の(これまでもあった)variableについてのドキュメントはこちらです。

参考: Terraform ドキュメント - Input Variables

Local Valuesは以下のように使います。

# Local Valuesとして変数定義
locals {
   switch_name = "my-switch-name"
}

# Local Valuesを使う
resource sakuracloud_switch "sw" {
  # "local." というプレフィックスで参照できる
  name = "${local.switch_name}"
}

localsブロックは複数記述可能

localsブロックはモジュール内に複数記述できます。もちろん変数名はモジュール内で一意である必要があります。

# サーバ関連の変数を定義
locals {
  server_name   = "foobar"
  server_core   = 2
  server_memory = 4
}

# ディスク関連の変数を定義
locals {
  disk_name = "foobar"
  disk_size = "20"
}

様々なデータ型が使える

Terraformで使える様々なデータ型を指定することが可能です。

locals {
  # bool型
  enabled = true

  # 数値型(10進数)
  num10 = 10

  # 数値型(16進数)
  num16 = 0x16

  # 文字列
  strvar = "example"

  # リスト
  listvar = ["item1", "item2", "item3"]

  # マップ
  mapvar = {
    item1 = "foo"
    item2 = "bar"
    item3 = "baz"
  }

  # 複合型(map/リスト/文字列など)
  compvar = {
    tags = ["tag1", "tag2"]
    metadata = {
      foo    = "1"
      bar    = "2"
      nested = ["foo", "bar"]
    }
  }
}

Variableとの違い

関数や他リソースの参照が使える

Local Valuesには関数や他リソースの参照などが書けます。
例えば任意の変数が設定されているか(空文字以外が指定されているか)を判定した結果を変数として保持しておけます。

variableを利用する場合、三項演算子などでvariableの値を判定して分岐させるというような処理を行っていました。

variable use_load_balancer {}

resource sakuracloud_load_balancer "lb" {
  # use_load_balancer変数が設定されていたらcountを1に、以外の場合は0にしてリソース作成しない
  count = "${var.use_load_balancer == "" ? 1 : 0}"
}

resource sakuracloud_switch "sw" {
  # use_load_balancer変数が設定されていたらcountを1に、以外の場合は0にしてリソース作成しない
  count = "${var.use_load_balancer == "" ? 1 : 0}"
}

同じ判定を行なっている箇所が複数あってDRYじゃないですね。

これをLocal Valuesを使って書き直すと以下のようになります。

variable use_load_balancer {}

# 判定処理をlocalsブロック内に局所化
locals {
  load_balancer_count = "${var.use_load_balancer == "" ? 1 : 0}"
  switch_count        = "${local.load_balancer_count}"
}

resource sakuracloud_load_balancer "lb" {
  count = "${local.load_balancer_count}"
}

resource sakuracloud_switch "sw" {
  count = "${local.switch_count}"
}

判定処理についてはわかりやすい名前をつけておくことで可読性も上がりますし、どういう判定をしているのか追いやすい(定義を見ればよい)ですね。

Local Valuesは外部からの値の設定ができない

variableは以下のように様々な方法で値の設定を行うことができます。

  • apply実行時に対話的に入力
  • コマンドラインから-varオプションや-ver-fileオプションで指定
  • terraform.tfvarsファイルで指定
  • 環境変数(TF_VAR_xxxなど)で指定
  • variableの定義時にデフォルト値を明示

このため、variableをtfファイルの簡易化といった目的で利用していた場合は意図しない値が入力される可能性もあったりします。
例えばcount構文と組み合わせる場合に以下のような書き方をすることがありました。

#
# Local Valuesがない時代の書き方
#

# サーバに割り当てるIPアドレスのリスト
variable ip_list {
  default = ["192.2.0.1", "192.2.0.2", "192.2.0.3"]
}

resource sakuracloud_server "servers" {
  # ip_listの要素数分のサーバを作成
  count = "${length(var.ip_list)}"

  # 自身のインデックスでIPアドレスリストを参照
  ipaddress = "${var.ip_list[count.index]}"
}

ip_listはvariableなため、外部から意図しない値が入力される可能性があります。 Local Valuesであればこの辺りを気にせずに使用可能です。

#
# Local Valuesを利用した書き方
#

# サーバに割り当てるIPアドレスのリスト
locals {
 ip_list = ["192.2.0.1", "192.2.0.2", "192.2.0.3"]
}

resource sakuracloud_server "servers" {
  # ip_listの要素数分のサーバを作成
  count = "${length(local.ip_list)}"

  # 自身のインデックスでIPアドレスリストを参照
  ipaddress = "${local.ip_list[count.index]}"
}

ということで、意図しない値の設定を防ぐためにもtfファイル上で変数を扱う際はまずLocal Valuesを利用し、外部から値の入力が必要な場合のみvariableを利用するのがオススメです。

まとめ

ということでLocal Valuesを積極的に使いましょう。 以上です。

続・さくらのクラウド上にMetabaseを構築する【HTTPS対応版】

前回はMetabase環境構築を行いました。

しかし、実運用の際はHTTPS対応は必須だと思いますので対応版を作りました。

Metabase環境構築 on さくらのクラウド(HTTPS版)

今回はLet's encryptにてHTTPS対応を行うバージョンとなっています。 Let's encrypt対応にはsteveltn/https-portalコンテナを利用します。

構築手順は前回とほぼ同じですが、あらかじめさくらのクラウド上にDNSゾーンの登録を行っておく必要があります。

テンプレート

前回との差分は以下の通りです。

  • コメントを追加
  • Let's encrypt用にDNS関連の記述を追加
  • RancherOSのcloud-configにLet's encrypt用のコンテナを記述
### 概要
#
# データベースアプライアンス(PostgreSQL)とRancherOSでMetabase実行環境を構築するテンプレート
#
# このテンプレートはRancherOS上のDockerでMetabaseを実行する構成となっています。
# Metabaseのバックエンドとしてデータベースアプライアンス(PostgreSQL)を利用します。
#
# MetabaseサーバのHTTPS対応としてLet's encryptでの証明書取得も行います。
#
# <事前準備>
#
# 1) さくらのクラウド上にSSH用の公開鍵を登録します。
# 2) さくらのクラウド上にDNSゾーンを登録しネームサーバの設定などを行っておきます。
#    (すでに登録済みのゾーンがあればそれを利用可能です。)
#
# <構築手順>
# 1) リソースマネージャーにて新しいテンプレートを作成し、このtffileの内容を貼り付けます。
# 2) tffile編集画面の"変数定義"タブにて以下の値を編集します。
#    - サーバ管理者のパスワード(server_password)
#    - データベース接続ユーザーのパスワード(database_password)
#    - さくらのクラウドに登録済みの公開鍵の名称(ssh_public_key_name)
#    - さくらのクラウドに登録済みのDNSゾーン名(dns_zone_name)
# 3) リソースマネージャー画面にて"計画/反映"を実行
#
# <動作確認>
#
# ブラウザから以下のURLにアクセスするとMetabaseの画面が開きます。
#    https://<MetabaseサーバのFQDN>/
#
# MetabaseサーバのFQDNは以下の形式です。
#    ${server_name}.${dns_zone_name}
#
# FQDNの例:
#    - server_name: "metabase"
#    - dns_zone_name: "example.com"
# この場合FQDNは以下のようになります。
#    FQDN: metabase.example.com
#
# <サーバへのSSH接続>
#
# サーバへのSSH接続は、指定した公開鍵による公開鍵認証のみ許可されるようになっています。
# SSH接続の際は秘密鍵を指定して接続してください。
#
# > usacloudでのSSH接続例
# $ usacloud server ssh -i <your-private-key-file> <your-server-name>
#
# SSH接続後はdocker logsコマンドなどでMetabaseコンテナのログを確認可能です。
#
### 変数定義
locals {
  #*********************************************
  # パスワード/公開鍵関連(要変更)
  #*********************************************
  # サーバ管理者のパスワード
  server_password = "<put-your-password-here>"
  
  # データベース接続ユーザーのパスワード
  database_password = "<put-your-password-here>"
 
  # さくらのクラウドに登録済みの公開鍵の名称
  ssh_public_key_name = "<put-your-public-key-name>"

  # さくらのクラウドに登録済みのDNSゾーン名
  dns_zone_name = "<put-your-zone-name>"

  #*********************************************
  # サーバ/ディスク
  #*********************************************
  # サーバ名
  server_name = "metabase"

  # サーバホスト名
  host_name = "${local.server_name}"

  # サーバ コア数
  server_core = 2

  # サーバ メモリサイズ(GB)
  server_memory = 4

  # ディスクサイズ
  disk_size = 20

  #*********************************************
  # ネットワーク(スイッチ/パケットフィルタ)
  #*********************************************
  # スイッチ名
  switch_name = "metabase-internal"

  # パケットフィルタ名
  packet_filter_name = "metabase-filter"

  #*********************************************
  # データベースアプライアンス
  #*********************************************
  # データベースアプライアンス名
  database_name = "metabase-db"

  # プラン
  database_plan = "30g" # 10g/30g/90g/240g

  # 接続ユーザー名
  database_user_name = "metabase"

  # バックアップ時刻
  database_backup_time = "01:00"
}

### サーバ/ディスク

# パブリックアーカイブ(OS)のID参照用のデータソース(RancherOS)
data sakuracloud_archive "rancheros" {
  os_type = "rancheros"
}

# 公開鍵のID参照用のデータソース
data "sakuracloud_ssh_key" "ssh_public_key" {
  name_selectors = ["${local.ssh_public_key_name}"]
}

# ディスク
resource "sakuracloud_disk" "disk" {
  name              = "${local.server_name}"
  source_archive_id = "${data.sakuracloud_archive.rancheros.id}"
  hostname          = "${local.host_name}"
  password          = "${local.server_password}"
  note_ids          = ["${sakuracloud_note.provisioning.id}"]
  ssh_key_ids       = ["${data.sakuracloud_ssh_key.ssh_public_key.id}"]
  disable_pw_auth   = true

  lifecycle {
    ignore_changes = ["source_archive_id"]
  }
}

# サーバ
resource "sakuracloud_server" "server" {
  name              = "${local.server_name}"
  disks             = ["${sakuracloud_disk.disk.id}"]
  core              = "${local.server_core}"
  memory            = "${local.server_memory}"
  packet_filter_ids = ["${sakuracloud_packet_filter.filter.id}"]
  additional_nics   = ["${sakuracloud_switch.sw.id}"]
}

# スタートアップスクリプト(IP設定、metabaseコンテナ起動)
locals {
  fqdn = "${local.server_name}.${local.dns_zone_name}"
}

resource "sakuracloud_note" "provisioning" {
  name  = "provisioning-metabase"
  class = "yaml_cloud_config"

  content = <<EOF
#cloud-config
rancher:
  console: default
  docker:
    engine: docker-17.09.1-ce
  network:
    interfaces:
      eth1:
        address: 192.168.100.10/28
        dhcp: false
  services:
    https-portal:
      image: sacloud/https-portal
      ports:
        - "80:80"
        - "443:443"
      volumes:
        - https-portal:/var/lib/https-portal
      environment:
        DOMAINS: "${local.fqdn} -> http://192.168.100.10:3000"
        STAGE: production
      restart: always
    metabase:
      image: metabase/metabase:latest
      ports:
        - "3000:3000"
      environment:
        MB_DB_TYPE: postgres
        MB_DB_DBNAME: ${local.database_user_name}
        MB_DB_PORT: 5432
        MB_DB_USER: ${local.database_user_name}
        MB_DB_PASS: ${local.database_password}
        MB_DB_HOST: 192.168.100.2
      restart: always
EOF
}

### データベースアプライアンス
resource "sakuracloud_database" "db" {
  name = "${local.database_name}"

  database_type = "postgresql"
  plan          = "${local.database_plan}"

  user_name     = "${local.database_user_name}"
  user_password = "${local.database_password}"

  allow_networks = ["192.168.100.0/28"]
  port           = 5432
  backup_time    = "${local.database_backup_time}"

  switch_id     = "${sakuracloud_switch.sw.id}"
  ipaddress1    = "192.168.100.2"
  nw_mask_len   = 28
  default_route = "192.168.100.1"
}

### パケットフィルタ
resource "sakuracloud_packet_filter" "filter" {
  name = "${local.packet_filter_name}"

  expressions = {
    protocol    = "tcp"
    dest_port   = "22"
    description = "Allow external:SSH"
  }

  expressions = {
    protocol    = "tcp"
    dest_port   = "80"
    description = "Allow external:HTTP(for Let's encrypt)"
  }

  expressions = {
    protocol    = "tcp"
    dest_port   = "443"
    description = "Allow external:HTTPS"
  }

  expressions = {
    protocol = "icmp"
  }

  expressions = {
    protocol = "fragment"
  }

  expressions = {
    protocol    = "udp"
    source_port = "123"
  }

  expressions = {
    protocol    = "tcp"
    dest_port   = "32768-61000"
    description = "Allow from server"
  }

  expressions = {
    protocol    = "udp"
    dest_port   = "32768-61000"
    description = "Allow from server"
  }

  expressions = {
    protocol    = "ip"
    allow       = false
    description = "Deny ALL"
  }
}

### スイッチ
resource sakuracloud_switch "sw" {
  name = "${local.switch_name}"
}

### DNS

data sakuracloud_dns "zone" {
  filter = {
    name   = "Name"
    values = ["${local.dns_zone_name}"]
  }
}

#DNSレコード
resource sakuracloud_dns_record "records" {
  dns_id = "${data.sakuracloud_dns.zone.id}"
  name   = "${local.server_name}"
  type   = "A"
  value  = "${sakuracloud_server.server.ipaddress}"
}

後はリソースマネージャーで展開するだけでOKです。

以上です。

【リソースマネージャー対応】さくらのクラウド上にMetabaseを構築する

Metabaseが流行ってきてますね。記事もちらほら見かけるようになりました。

ということでさくらのクラウド上にMetabaseを構築してみました。

===>【2018/1/30追記】HTTPS対応版について記事書きました。

続・さくらのクラウド上にMetabaseを構築する【HTTPS対応版】

ドメインをお持ちの方はこちらも是非お試しください。

<=== 追記ここまで

Metabase環境構築 on さくらのクラウド

今回はさくらのクラウドリソースマネージャーを利用して環境構築してみました。

今回の環境

今回MetabaseはDockerで動かします。Docker用のホストはRancherOSを使用します。 Metabase用のデータベースとしてPostgreSQL(さくらのクラウド上のデータベースアプライアンス)を利用します。

構築手順は以下の通りです。

  • 1) サーバへのSSH用公開鍵をコンパネから登録
  • 2) リソースマネージャでテンプレート作成&反映

1) サーバへのSSH用公開鍵をコンパネから登録

サーバへのSSH接続時に利用する公開鍵をコンパネなどから登録しておきます。 登録時に指定した名前を控えておいてください。

参考: 公開鍵の登録方法 - さくらのクラウド マニュアル

2) リソースマネージャーでテンプレート作成&反映

続いてリソースマネージャーにてテンプレートを作成します。 以下のtfファイルをコピペで登録してください。 なおtfファイルの最初の方にパスワードや先ほど登録した公開鍵の名称を指定している部分があります。 忘れずに各自で置き換えてください。

### 概要
# データベースアプライアンス(PostgreSQL)とRancherOSでMetabase実行環境を構築するテンプレート
#
# このテンプレートはRancherOS上のDockerでMetabaseを実行する構成となっています。
# Metabaseのバックエンドとしてデータベースアプライアンス(PostgreSQL)を利用します。
#
### 変数定義
locals {
  #*********************************************
  # パスワード/公開鍵関連(!!!要変更!!!)
  #*********************************************
  # サーバ管理者のパスワード
  server_password = "<put-your-password-here>"

  # データベース接続ユーザーのパスワード
  database_password = "<put-your-password-here>"

  # さくらのクラウドに登録済みの公開鍵の名称
  ssh_public_key_name = "<put-your-public-key-name>"

  #*********************************************
  # サーバ/ディスク
  #*********************************************
  # サーバ名
  server_name = "metabase-server"

  # サーバホスト名
  host_name = "${local.server_name}"

  # サーバ コア数
  server_core = 2

  # サーバ メモリサイズ(GB)
  server_memory = 4

  # ディスクサイズ
  disk_size = 20

  #*********************************************
  # ネットワーク(スイッチ/パケットフィルタ)
  #*********************************************
  # スイッチ名
  switch_name = "metabase-internal"

  # パケットフィルタ名
  packet_filter_name = "metabase-filter"

  #*********************************************
  # データベースアプライアンス
  #*********************************************
  # データベースアプライアンス名
  database_name = "metabase-db"

  # プラン
  database_plan = "30g" # 10g/30g/90g/240g

  # 接続ユーザー名
  database_user_name = "metabase"

  # バックアップ時刻
  database_backup_time = "01:00"
}

### サーバ/ディスク

# パブリックアーカイブ(OS)のID参照用のデータソース(RancherOS)
data sakuracloud_archive "rancheros" {
  os_type = "rancheros"
}

# 公開鍵のID参照用のデータソース
data "sakuracloud_ssh_key" "ssh_public_key" {
  name_selectors = ["${local.ssh_public_key_name}"]
}

# ディスク
resource "sakuracloud_disk" "disk" {
  name              = "${local.server_name}"
  source_archive_id = "${data.sakuracloud_archive.rancheros.id}"
  hostname          = "${local.host_name}"
  password          = "${local.server_password}"
  note_ids          = ["${sakuracloud_note.provisioning.id}"]
  ssh_key_ids       = ["${data.sakuracloud_ssh_key.ssh_public_key.id}"]
  disable_pw_auth   = true

  lifecycle {
    ignore_changes = ["source_archive_id"]
  }
}

# サーバ
resource "sakuracloud_server" "server" {
  name              = "${local.server_name}"
  disks             = ["${sakuracloud_disk.disk.id}"]
  core              = "${local.server_core}"
  memory            = "${local.server_memory}"
  packet_filter_ids = ["${sakuracloud_packet_filter.filter.id}"]
  additional_nics   = ["${sakuracloud_switch.sw.id}"]
}

# スタートアップスクリプト(IP設定、metabaseコンテナ起動)
resource "sakuracloud_note" "provisioning" {
  name  = "provisioning-metabase"
  class = "yaml_cloud_config"

  content = <<EOF
#cloud-config
rancher:
  console: default
  docker:
    engine: docker-17.09.1-ce
  network:
    interfaces:
      eth1:
        address: 192.168.100.10/28
        dhcp: false
  services:
    metabase:
      image: metabase/metabase:latest
      ports:
       - "80:3000"
      environment:
        MB_DB_TYPE: postgres
        MB_DB_DBNAME: metabase
        MB_DB_PORT: 5432
        MB_DB_USER: ${local.database_user_name}
        MB_DB_PASS: ${local.database_password}
        MB_DB_HOST: 192.168.100.2
      restart: always
EOF
}

### データベースアプライアンス
resource "sakuracloud_database" "db" {
  name = "${local.database_name}"

  database_type = "postgresql"
  plan          = "${local.database_plan}"

  user_name     = "${local.database_user_name}"
  user_password = "${local.database_password}"

  allow_networks = ["192.168.100.0/28"]
  port           = 5432
  backup_time    = "${local.database_backup_time}"

  switch_id     = "${sakuracloud_switch.sw.id}"
  ipaddress1    = "192.168.100.2"
  nw_mask_len   = 28
  default_route = "192.168.100.1"
}

### ネットワーク(パケットフィルタ)
resource "sakuracloud_packet_filter" "filter" {
  name = "${local.packet_filter_name}"

  expressions = {
    protocol    = "tcp"
    dest_port   = "22"
    description = "Allow external:SSH"
  }

  expressions = {
    protocol    = "tcp"
    dest_port   = "80"
    description = "Allow external:HTTP"
  }

  expressions = {
    protocol = "icmp"
  }

  expressions = {
    protocol = "fragment"
  }

  expressions = {
    protocol    = "udp"
    source_port = "123"
  }

  expressions = {
    protocol    = "tcp"
    dest_port   = "32768-61000"
    description = "Allow from server"
  }

  expressions = {
    protocol    = "udp"
    dest_port   = "32768-61000"
    description = "Allow from server"
  }

  expressions = {
    protocol    = "ip"
    allow       = false
    description = "Deny ALL"
  }
}

### ネットワーク(スイッチ)
resource sakuracloud_switch "sw" {
  name = "${local.switch_name}"
}

tfファイル編集画面で「タブ分割/統合」ボタンを押すと以下のようにtfファイルをタブで分割してわかりやすく表示してくれます。 変更すべき内容は「変数定義」タブにまとめていますので目を通しておくのがオススメです。

f:id:febc_yamamoto:20180129175951p:plain

登録後はリソースマネージャーのコマンドから計画/反映を実行するだけです。 なお、手元の環境では2分ほどで構築完了しました。

動作確認

構築が完了したらhttp://<サーバのグローバルIP>にアクセスするとmetabaseの画面が開くはずです。

f:id:febc_yamamoto:20180129175312p:plain

後は画面に従って初期ユーザーの作成などを行うだけです。

終わりに

Metabaseいいですね! 以上です。

moby / linuxkit をさくらのクラウド対応させました

f:id:febc_yamamoto:20171023183107p:plain

Linuxコンテナを実行できるコンテナプラットフォームを簡単に構築/展開できるmobylinuxkitさくらのクラウドに対応させてみましたのでご紹介します。
(2017/10/23追記): システム要件としてDockerとGNU Makeのインストールが必要な旨を追記しました

TL; DR

以下のようにすればmobylinuxkitさくらのクラウド上に簡単にコンテナプラットフォームを構築できます。
※あらかじめDockerGNU Makeをインストールしておく必要があります。

# sacloud/linuxkitのインストール
$ brew tap sacloud/linuxkit
$ brew install --HEAD moby
$ brew install --HEAD linuxkit

# さくらのクラウドAPIキーを環境変数に設定
$ export SAKURACLOUD_ACCESS_TOKEN="your-token"
$ export SAKURACLOUD_ACCESS_TOKEN_SECRET="your-secret"
$ export SAKURACLOUD_ZONE="tk1a"

# mobyコマンドでraw形式のイメージ作成
$ moby build -format raw -size 256M sakuracloud.yml

# linuxkit pushでさくらのクラウド上にアップロード
$ linuxkit push sakuracloud sakuracloud.raw

# linuxkit runでさくらのクラウド上にサーバ作成/起動
$ linuxkit run sakuracloud sakuracloud

moby/linuxkitとは?

mobyとlinuxkitについてはPublickeyの以下の記事にわかりやすくまとめられています。

Publickey: Docker、「LinuxKit」を発表。コンテナランタイムのためだけにゼロから開発されたセキュアなLinux Subsystem。DockerCon 2017

全てがコンテナで実行される軽量でimmutableなLinuxイメージを作成できるツールとなっています。

どうやって使うの?

LinuxKitを使ってLinuxイメージを作成するためにmobyコマンドが提供されています。
mobyコマンドは、使用するカーネルやinitプロセス、動かしたいコンテナといった構成情報をyaml形式のファイルで定義し、定義に沿ったLinuxイメージを作成してくれます。

出力形式は以下のようなものがサポートされており、AWS/Azure/GCPといったクラウド上で利用できるイメージだけでなくOpenStackやオンプレのベアメタルサーバなどに対応できる形式のイメージが作成できます。

mobyコマンドで作成できるイメージの形式

  • docker
  • dynamic-vhd
  • gcp
  • iso-bios
  • iso-efi
  • kernel+initrd
  • qcow2
  • raw
  • rpi3
  • tar
  • tar-kernel-initrd
  • vhd
  • vmdk

作成したイメージはブータブルとなっており、自分でクラウド上にアップロードしたりオンプレの仮想化基盤に登録したりすることでイメージを利用したサーバを起動できるようになっています。
(ISOイメージで出力してCD/DVDなどのメディアを用意する方法も可能)

自分で(各クラウドの)コントロールパネルなどからアップロードしても良いですしCLIなどを利用してもよいですが、これらを簡単に行えるようにlinuxkitコマンドが用意されています。

linuxkitコマンドは何をするもの?

linuxkitコマンドは主にmobyコマンドで作成したイメージをクラウド(など)へアップロードし、そのイメージを用いて起動するサーバの作成を行ってくれます。
メタデータ用ISOイメージ作成や構成要素として利用できるパッケージ作成などの補助機能もあります。

イメージ作成〜サーバ起動までの利用イメージは以下のようになります。

# mobyコマンドでイメージ作成(example.rawファイルが作成される)
$ moby build -format raw example.yml

# linuxkit pushでアップロード
$ linuxkit push aws -bucket bucketname example.raw

# linuxkit runでアップロードしたイメージを利用したサーバを作成/起動
$ linuxkit run aws example

定義ファイル(yaml)の作り方についてはこちらのドキュメントに詳しく記載されています。

linuxkitドキュメント: Yaml Configuration Document

現時点では以下のクラウド(など)に対応しています。

イメージのアップロード(linuxkit push)対応先の一覧

  • aws
  • azure
  • gcp
  • openstack
  • vcenter

イメージからのサーバ作成/起動(linuxkit run)対応先の一覧

標準ではさくらのクラウドに対応していませんので今回対応させてみました。

linuxkitのさくらのクラウド対応

以下のリポジトリにlinuxkitをforkしてさくらのクラウド対応を行っています。

Github: sacloud/linuxkit

このリポジトリを利用することでlinuxkitさくらのクラウド上にイメージのアップロードを行いサーバ起動を行うことが可能となります。

sacloud/linuxkitのインストール

まずはforkしたsacloud/linuxkitのインストールを行う必要があります。 以下2つの方法があります。

  • 方法1) homebrewを利用してインストール
  • 方法2) 上記リポジトリをクローンして自分でビルド

方法1) homebrewでsacloud/linuxkitをインストール

本家linuxkitと同じくhomebrewでのインストールを行えるようにしています。

# まずはtap
$ brew tap sacloud/linuxkit

# インストール実施
$ brew install --HEAD moby
$ brew install --HEAD linuxkit

もしすでにlinuxkit/linuxkitをtapしている場合は名前が衝突しますのでbrew install時に以下のように完全名で指定する必要があります。

$ brew install --HEAD sacloud/linuxkit/moby
$ brew install --HEAD sacloud/linuxkit/linuxkit

方法2) sacloud/linuxkitをクローンして自分でビルド

ビルドにはGo言語の開発環境が必要です。$GOPATHの設定なども行っておく必要があります。

$GOPATH/src/github.com/linuxkit/linuxkitディレクトリにsacloud/linuxkitをクローンしてmakeを実行すればOKです。

# ディレクトリ作成
$ mkdir -p $GOPATH/src/github.com/linuxkit

# クローン
$ git clone https://github.com/sacloud/linuxkit.git $GOPATH/src/github.com/linuxkit/linuxkit

# 移動
$ cd $GOPATH/src/github.com/linuxkit/linuxkit

# ビルド(binディレクトリ配下にlinuxkit/mobyコマンドが作成される)
$ make

必要に応じて$PATHの設定を行ってください。

sacloud/linuxkitの実行

あとは通常のmobylinuxkitと同様の手順でOKです。
システム要件もmobylinuxkitと同じく以下がインストールされていること、となっています。

  • GNU Make(BSD Makeは不可)
  • Docker
  • qemu(オプション)

なお、linuxkitでAzureを利用する場合などと同じくログイン情報(APIキー)を環境変数に登録しておく必要があります。
sacloud/linuxkitでは以下の環境変数の設定を行っておく必要があります。
さくらのクラウドのコントロールパネルでAPIキーを発行しておいてください。
(環境変数usacloudTerraform for さくらのクラウドと共通となっています)

# APIキー(アクセストークン)
$ export SAKURACLOUD_ACCESS_TOKEN="your-access-token"

# APIキー(アクセスシークレット)
$ export SAKURACLOUD_ACCESS_TOKEN_SECRET="your-access-secret"

# 対象ゾーン(石狩第1: is1a / 石狩第2: is1b / 東京第1: tk1a / サンドボックス: tk1v)
$ export SAKURACLOUD_ZONE="tk1a"

イメージのビルド

定義ファイルを用意した上でmobyコマンドでビルドを行います。

定義ファイルの例をGitHub上で公開していますのでそれを元に作成してください。

さくらのクラウドでの定義ファイルの例: GitHub: sacloud/linuxkit/examples/sakuracloud.yml

kernel:
  image: linuxkit/kernel:4.9.56
  cmdline: "console=tty0 console=ttyS0 console=ttyAMA0"
init:
  - linuxkit/init:6b3755e47f00d6027321d3fca99a19af6504be75
  - linuxkit/runc:52f92cb577879ce4cfe4e89be2d63af82523fc92
  - linuxkit/containerd:ed8e8f92e24dd4b94260cf147594ae3fd13a2182
  - linuxkit/ca-certificates:ea3c4c120f929f4f07ac8535d75933365b5e9582
onboot:
  - name: sysctl
    image: linuxkit/sysctl:1644bf07edbcaf5ce0bb764fa925b544183547f9
  - name: rngd1
    image: linuxkit/rngd:45ed7759dd927f4cce3863073ea2e0da1d52a427
    command: ["/sbin/rngd", "-1"]
services:
  - name: getty
    image: linuxkit/getty:7abaf7b276c59f80891d92e9279e3e3ee8e2f512
    env:
     - INSECURE=true
  - name: rngd
    image: linuxkit/rngd:45ed7759dd927f4cce3863073ea2e0da1d52a427
  - name: dhcpcd
    image: linuxkit/dhcpcd:aa685261ceb2557990dcfe9dd8824c6b9ec416e2
  - name: sshd
    image: linuxkit/sshd:4a2fc7be31fa57dcade391de6173e0af55296e7f
files:
  - path: root/.ssh/authorized_keys
    source: ~/.ssh/id_rsa.pub
    mode: "0600"
    optional: true
trust:
  org:
    - linuxkit

この例はSSHDを起動するイメージの例となっています。
SSH用のキーペアをssh-keygenなどで作成し定義ファイルに公開鍵のファイルパスを記載してください。(デフォルトでは~/.ssh/id_rsa.pubが指定されています)

定義ファイルを作成したらmobyコマンドを実行します。
さくらのクラウドではAWSなどと同じく-formatオプションにrawを指定する必要があります。

# mobyコマンドでraw形式のイメージ作成
$ moby build -format raw -size 256M sakuracloud.yml

デフォルトでは定義ファイルの拡張子を除いたもの+.rawというファイル名でイメージが作成されます。
これは-nameオプションで上書き可能です。

また、イメージのサイズはデフォルトで1024M(1GB)となっています。
SSHを実行する程度のイメージであれば256Mもあれば十分ですので-sizeオプションでサイズを明示しています。
定義ファイルの内容によってこの値は調節する必要があります。

うまくいくとsakuracloud.rawというファイルが作成されるはずです。

イメージのアップロード

次にlinuxkit pushコマンドでアップロードを行います。

# 作成したイメージをさくらのクラウドへアップロード
$ linuxkit push sakuracloud sakuracloud.raw

デフォルトではイメージファイル名の拡張子を除いたものがさくらのクラウドアーカイブ名として利用されます。
この例ではsakuracloudという名前でアーカイブが作成されます。

実行!!

いよいよサーバの作成/起動です。
linuxkit runコマンドにアップロードしたアーカイブ名を指定することで起動できます。

# アップロードしたアーカイブを利用してサーバ作成/起動
$ linuxkit run sakuracloud sakuracloud

現在は以下のオプションが利用可能です。

  • -name: 作成されるサーバの名称(デフォルトではアーカイブ名と同じになる)
  • -core : 作成されるサーバのコア数(デフォルト1)
  • -memory : 作成されるサーバのメモリサイズ、単位はGB(デフォルト1)
  • -disk-size : 作成されるサーバのディスクサイズ、単位はGB(デフォルト20)

-core-memoryの組み合わせによってはサーバ作成時にエラーとなりますので、以下のドキュメントを参考にサポートされている組み合わせを指定してください。

さくらのクラウド: サーバプラン一覧

作成されたら以下のコマンドでSSH接続可能です。
(定義ファイルの内容によってはSSH接続できない場合もあります)

$ ssh -i your-private-key-path root@サーバのグローバルIP

もしSSH接続できないイメージを作成した場合、SSH接続の代わりにさくらのクラウドCLIであるusacloudを用いてVNC接続を行うことも可能です。

# usacloudでVNC接続
$ usacloud server vnc [サーバ名 or サーバID]

終わりに

今回はmobylinuxkitを用いてさくらのクラウド上でコンテナプラットフォームを簡単に構築する方法をご紹介しました。
linuxkitを用いれば特定機能に特化したイメージを簡単に作成でき、アップロード/サーバ作成と起動も手軽に行えますね。

ぜひお試しください。以上です。