Open Service Broker for さくらのクラウドでKubernetes + Service Catalog出来るようになりました

Open Service Broker for さくらのクラウド

f:id:febc_yamamoto:20180309230517p:plain

Cloud Foundryやkubernetes-incubatorのservice catalogでおなじみ?のOpen Service Broker APIさくらのクラウド向けに実装しましたのでご紹介させていただきます。

Open Service Broker for SAKURA Cloud

Open Service Broker APIってなに?

Open Service Broker APIとは、Cloud Foundry由来のService Brokerの仕組みをkubernetesなどのCloud Foundry以外の環境でも利用できるようにHTTP APIの仕様を定めたものです。

その前にService Brokerってなに?

Service Broker(サービスブローカー)とは、プラットフォーム(Cloud FoundryやKubernetes)が外部のサービスやコンポーネントを使いやすくするための仕組み、、、です。
と言われてもこれではよくわかりませんね、、、ということで具体的な例を見ながらService Brokerとは何かを追っていきます。

例えばkubernetesでデータベースを使うには?

DockerやKubernetesなどを利用する場合、データベースやKVS、ストレージなどのステートフルなリソースの扱いは悩みどころです。
Kubernetesではこのようなステートフルな運用のためにStateful Setsというものが用意されていますが、通常のステートレスなコンテナと比べると若干デプロイや運用が面倒です。

f:id:febc_yamamoto:20180309173701p:plain

ということで、無理にKubernetes内で動かさずにRDSなどの外部のサービスの利用をすることも多いかと思います。

RDSなどの外部のサービスを利用する場合

データベースをk8s内で運用せず外部のサービスを利用する場合、概ね以下のような作業が必要となります。

  • 利用するサービスの有効化(プロビジョニング)
  • 払い出されたログイン情報などをKubernetes上のSecretsなどに登録
  • ログイン情報を環境変数などでPodに渡す

この辺の作業はスクリプトなどである程度自動化することも可能ですが、利用するサービスごとに手順が異なったりしてなかなか大変です。

f:id:febc_yamamoto:20180309225047p:plain

サービスごとに手順手順が異なるといった煩雑さに対し、統一したインターフェースを提供し手軽に扱えるようにすることがService Brokerの役割(のひとつ)です。
次にService Brokerがこれらの問題をどう解決するのか見ていきます。

Service Broker = 外部のサービスなどを仲介してくれる仲買人

イメージとしてはこんな感じです。

f:id:febc_yamamoto:20180309225105p:plain

利用者と外部サービスとの間にブローカー(仲買人)が入ることで、利用者はブローカーにお願いするだけで済みます。

ブローカーも役割分担することで実装が簡単に

さらに、ブローカーは利用者からの依頼を管理する部分と実際のサービスの調達を行う部分に役割分担されています。

f:id:febc_yamamoto:20180309225109p:plain

役割分担することで、サービスが増えても追加となったサービスの分だけを実装すればよく、多様なサービスに柔軟に対応できるようになります。

f:id:febc_yamamoto:20180309225111p:plain

ここでもう一度: Open Service Broker APIとは?

そして、利用者からの依頼を管理する部分と実際のサービスの調達を行う部分についてのインターフェースを仕様として定めたものがOpen Service Broker APIです。

f:id:febc_yamamoto:20180309225114p:plain

元々はCloud Foundryから始まった仕組みらしいのですが、Service Brokerが非常に便利な仕組みだったために
Kubernetesなど他のプラットフォームでも利用できるようにCF依存でないオープンな仕様としてOpen Service Broker APIが策定されたようです。
(Cloud Foundry周りにあまり詳しくないのでこの辺は後述の参考サイトなどを参照ください…)

Open Service Broker APIの策定はCloud Foundry Foundation (CFF)によってホストされており、現在のAPIバージョンは2.13となっています。
このCFFにはFujitsu, Google, IBM, Pivotal, RedHat, SAPといったメンバーが参加しているとのことです。

Open Service Broker API

ここには載っていませんが、Microsoft(Azure)についてもOpen Service Broker for Azure(OSBA)が活発に開発されています。

KubernetesとOpen Service Broker APIの関係は?

KubernetesにおいてもOpen Service Broker APIを利用するための仕組みが開発されています。
それがkubernetes-incubatorのService Catalogというものです。

参考: kubernetes.io -> Service Catalog

Service Catalogは先ほどの図で言うと、利用者からの依頼を受け付ける仲介者の役割を担っています。

f:id:febc_yamamoto:20180309225117p:plain

具体的にはService CatalogはKubernetesのカスタムリソースとしてServiceInstanceServiceBindingを追加し、それらを管理するコントローラー+APIサーバを提供してくれます。

つまり、Kubernetes利用者からは以下のようにkubectlコマンドでリソースを追加することで、外部のサービスを利用できるようになります。

# こんな感じのyamlを用意(この例ではAzure上のMySQLを利用)
$ cat example_service.yaml

apiVersion: servicecatalog.k8s.io/v1beta1
kind: ServiceInstance
metadata:
  name: example-mysql-instance
  namespace: default
spec:
  clusterServiceClassExternalName: azure-mysql
  clusterServicePlanExternalName: basic50
  parameters:
    location: eastus
    resourceGroup: demo
    firewallRules:
    - startIPAddress: "0.0.0.0"
      endIPAddress: "255.255.255.255"
      name: "AllowAll"

# kubectlで投入
$ kubectl create -f example_service.yaml

これでAzure上のMySQL(Azure Database for MySQL)のインスタンスが作成されます。
このMySQLを各Podなどから利用できるようにするためのクレデンシャルの発行を以下のように行うことができます。
(なお、このインスタンス作成のことをProvisioning、クレデンシャルの発行(仕様上は以外も出来ますが)をBindingと呼びます)

# Azure Database for MySQLへのアクセス用クレデンシャル発行 & Kubernetesのsecretに格納
$ cat example_binding.yaml

apiVersion: servicecatalog.k8s.io/v1beta1
kind: ServiceBinding
metadata:
  name: example-mysql-binding
  namespace: default
spec:
  instanceRef:
    name: example-mysql-instance    # 先ほど作成したインスタンスを指定
  secretName: example-mysql-secret  # クレデンシャルの格納先となるsecret名を指定

# こちらもkubectl経由で投入
$ kubectl create -f example_binding.yaml

各PodではSecretを参照することでデータベースのホスト名/データベース名/ユーザー名/パスワードなどを知ることができます。
kubernetes上で完結できるので、例えばHelm chartとしてきちんと永続化したいデータを持つアプリなどの配布を行う際に非常に便利に使えます。

本題: Open Service Broker for さくらのクラウド

そして今回実装したのがService Catalogから依頼を受けて実際にサービスを調達する部分です。
これを利用することでkubernetesからさくらのクラウド上のマネージドなサービスを利用することが容易になります。

f:id:febc_yamamoto:20180309225120p:plain

先ほどのAzureの例のように、kubectlコマンドからさくらのクラウド上のマネージドDBであるデータベースアプライアンスを作成できます。

ということで早速使い方を紹介します。

Open Service Broker for さくらのクラウドの使い方

準備

まずはさくらのクラウド上のVPC内にKubernetesクラスタを構築する必要があります。

f:id:febc_yamamoto:20180309225123p:plain

なぜVPC内にKubernetesクラスタが必要かと言うと、さくらのクラウドのデータベースアプライアンスVPC内(正確にはスイッチさえ繋げばOK)に置く必要があるためです。
Kubernetes(内のPod)から繋ぎたいため、必然的にVPC内にクラスタを置く必要があります。

クラスタを持っていない方はDocker for WindowsまたはDocker for MacでもOKです。
その場合、L2TP/IPSecなどでVPCルータに対してVPN接続を行っておきましょう。

f:id:febc_yamamoto:20180309225126p:plain

インストール

Helmのインストール

まずはKubernetesのパッケージ管理ツールであるHelmをインストールします。
macOSの場合はhomebrew、Windowsの場合はChocolateyでインストール可能です

# macの例
$ brew install kubernetes-helm

インストールしたらhelm initを実行しておきます。

$ helm init

Service Catalogのインストール

続いてHelmでService Catalogのインストールを行います。

# HelmにService Catalog用のリポジトリを追加
$ helm repo add svc-cat https://svc-catalog-charts.storage.googleapis.com

$ Service Catalogのインストール(ネームスペースは"catalog"としておく)
helm install svc-cat/catalog --name catalog --namespace catalog

(オプション) Service Catalog CLIのインストール

必須ではないですが、Service Catalog専用のCLI(svcat)をインストールしておくと詳細な情報が参照できて便利です。 CLIを試してみたい方はドキュメントを参考にインストールしてみてください。

参考: Service Catalog: Install guide

Open Service Broker for さくらのクラウドのインストール

続いてHelmでOpen Service Broker for さくらのクラウドのインストールを行います。
さくらのクラウドAPIキーが必要となりますので、発行していない方はコントロールパネルなどから発行しておいてください。

APIキーは以下のように環境変数に設定しておきます(インストール時の入力を楽にするためです)。

$ export SAKURACLOUD_ACCESS_TOKEN=<your-api-token>
$ export SAKURACLOUD_ACCESS_TOKEN_SECRET=<your-api-secret>
$ export SAKURACLOUD_ZONE=<your-zone>

続いてHelmでインストールを行います。

# HelmにOpen Service Broker for さくらのクラウド用のリポジトリを追加
$ helm repo add sacloud https://sacloud.github.io/helm-charts/

# Open Service Broker for さくらのクラウドのインストール(ネームスペースは"osbs"としておく)
$ helm install sacloud/open-service-broker-sacloud --name osbs --namespace osbs \
  --set sacloud.accessToken=$SAKURACLOUD_ACCESS_TOKEN \
  --set sacloud.accessTokenSecret=$SAKURACLOUD_ACCESS_TOKEN_SECRET \
  --set sacloud.zone=$SAKURACLOUD_ZONE

インストール後はkubectl get pod --namespace=osbsを実行して動いているか確認しましょう。

# STATUSがRUNNING、かつREADYが1/1になっていることを確認しておく
$ kubectl get pod --namespace=osbs
NAME                                                READY     STATUS    RESTARTS   AGE
osbs-open-service-broker-sacloud-xxxxxxxx-xxxxx   1/1       Running   0          7h

kubectlコマンドでデータベースアプライアンスを作成してみる

それでは早速データベースアプライアンスを作成してみましょう。
現時点では以下2種類をサポートしています。

今回は例としてMariaDBを作成してみます。

まず以下のようなyamlファイルを用意します。

apiVersion: servicecatalog.k8s.io/v1beta1
kind: ServiceInstance
metadata:
  name: my-mariadb-instance
  namespace: default
spec:
  clusterServiceClassExternalName: sacloud-mariadb
  clusterServicePlanExternalName: db-10g
  parameters:
    switchID: <VPC内のスイッチのID>
    ipaddress: "<データベースに割り当てるIPアドレス>"
    maskLen: <データベースのネットワークマスク長>
    defaultRoute: "<データベースのデフォルトルートIPアドレス>"

さくらのクラウド上のスイッチのIDとデータベースに割り当てるIPアドレス関連の記入が必要です。
記入したらkubectlコマンドを実行します。

kubectl create -f <作成したyamlファイル>

うまくいきましたね? しばらく待つとさくらのクラウド上に新しいデータベースアプライアンスが作成されているはずです。
数分かかりますので気長に待ちましょう。

svcatコマンドをインストールしている場合は以下のコマンドで作成状況を確認できます。

$ svcat get instances

作成したデータベース上にアカウントを作成してみる

次に、Podなどから利用できるようにデータベース上に新たなアカウントを発行し、Kubernetesのsecretにクレデンシャルを登録します。
こちらも先ほどと同じくyamlファイルを作成してkubectlコマンドを実行することで行えます。

apiVersion: servicecatalog.k8s.io/v1beta1
kind: ServiceBinding
metadata:
  name: my-mariadb-binding
  namespace: default
spec:
  instanceRef:
    name: my-mariadb-instance
  secretName: my-mariadb-secret
$ kubectl create -f <作成したyamlファイル>

kubectlを実行すると、my-mariadb-secretという名前でデータベースへのログイン情報(クレデンシャル)が登録されているはずです。

# secretに登録されたクレデンシャルの確認
$ kubectl get secret my-mariadb-secret

なおsecretはBASE64エンコードされていますので、復号して生データを見たい場合は以下のコマンドを実行しましょう。

$ echo <BASE64エンコードされた文字列> | base64 -D

あとは各Podからsecretの値を参照すればOKです。マネージドなデータベースがこれだけで使えるようになるのは便利ですよね!!!

応用: 作成したデータベースを利用する例

応用例としてService Broker for さくらのクラウドを利用するhelm chartを作成してみました。 イケてるデータ可視化ツールであるMetabaseをインストールする例となっています。

応用例: Sacloud Helm Chart : Metabase

Metabaseのバックエンドとしてさくらのクラウドのデータベースアプライアンスを利用、データベースアプライアンスはOpen Service Broker for さくらのクラウドが管理します。

# 応用例のインストール
$ helm install --name my-release sacloud/metabase \
               --set database.switchID=<your-switch-id> \
               --set database.ipaddress=<your-db-ip> \
               --set database.maskLen=<your-db-network-mask-len> \
               --set database.defaultRoute=<your-db-default-route-ip>

詳細はChartの内容をみていただければと思いますが、ちゃんと永続化したいデータを伴う構成でもHelmで手軽に配布できるようになっています。

終わりに

ということで今回はOpen Service Broker for さくらのクラウドについて紹介しました。
Open Service Broker/Service Catalogは非常に便利ですのでぜひ使ってみてください!!

以上です。

参考記事

この記事を書くのに以下の記事/スライドを大いに参考にさせていただきました。
Service Brokerの仕組みについてはこちらの資料を読むのがおすすめです。

Arukasで任意のコマンドを実行する「Rarukas」 - 雲の向こうの使い捨てコンテナ

f:id:febc_yamamoto:20180225174323p:plain

2018/2/20にArukasのβサービスが再開しましたね!!待ってました!!!

Arukas βサービス提供再開に関するお知らせ

ということで早速使ってみました。

何が変わったの?

GitHubアカウントでのサインイン/サインアップは廃止

先ほどのお知らせにも書かれていましたが、GitHubアカウントでのサインアップが廃止されていました。
以前のArukasでアカウントを持っていた場合でも再作成となるようです。
以下ページからサインアップを行いました。

現在は予約制となっており、サインアップ後に登録可能な状態になったら招待メールが届きます。
(私の時は30分程度で届きました)

Arukas サインアップ

4つのプランの中から選択が可能に

課金体系についても公開されていました。
スペックやインスタンス数の制限などの違いで以下4つのプランがあるとのことです。

名称 vCPU RAM 価格 概要
Free 0.1 128MB 無料 同時に起動できるコンテナーインスタンスは一つだけ
Hobby 0.1 512MB 0.74円/h FreeプランとCPU性能は同じ、RAMサイズ増
Standard x1 0.5 512MB 1.48円/h HobbyプランからvCPU増
Standard x2 0.5 1024MB 2.96円/h Standard x1プランからRAMサイズ増

なお、Freeプラン以外を一度でも起動した月は基本料金として月額50円上乗せされるとのことです。

APIも結構変わった

また、サービス再開に合わせてAPIについても更新されていました。

Arukas API Reference

認証や必要なヘッダなどについては変更ないようですが、リソース名やリソースの内容についてはだいぶ変わっています。
例: app_setsとかは無くなりました。

旧Arukas APIを利用していた場合は変更が必要でしょう。
取り急ぎGo言語についてAPIライブラリを作成しておきましたのでぜひご利用ください。

Go言語向け Arukas APIライブラリ go-arukas

APIライブラリを作ったらアプリも欲しくなりましたのでとりあえず作っておきました。

Arukasで任意のコマンドを実行する「Rarukas」コマンド

shell(sh)に対するremote-shell(rsh)みたいに、Arukasでもっと手軽にコマンド実行したいなーということでremote-arukas = rarukasというコマンド作りました。

GitHub: rarukas - CLI for running one-off command(s) on Arukas

Go言語で書いてますので、インストールはバイナリをGitHubからダウンロードして実行権付与するだけです。
ダウンロードは以下のGitHub Releasesページから行えます。

GitHub: rarukas - releases

以下のような感じで任意のコマンドをArukas上のコンテナで実行可能です。
コンテナはオンデマンドに作成され、コマンド実行が終了すると破棄されます。エコですね!!

# 事前にAPIキーを環境変数に設定(コマンドラインオプションでも指定可)
$ export ARUKAS_JSON_API_TOKEN=<APIトークン>
$ export ARUKAS_JSON_API_SECRET=<APIシークレット>

# Arukas上でcurlコマンドを実行
$ rarukas curl -L https://arukas.io/

動きとしてはこんな感じです。

f:id:febc_yamamoto:20180225163229p:plain

  • SSH用のキーペアを生成(オプション指定でキーの指定もできる)
  • Arukas APIでコンテナ作成 & 起動、SSH用の公開鍵をArukas API経由でコンテナに渡す
    (起動するコンテナはRarukas側で用意したSSH/SCP可能なイメージを利用してます)
  • (任意)コンテナが起動したらSCPでローカルのファイルをコンテナのworkdirへアップロード
  • コンテナ上で指定のコマンド実行
  • (任意)コンテナ上のworkdirをローカルにダウンロード
  • Arukas APIでコンテナ破棄

実行したいコマンドはファイルでの指定もできる

実行したいコマンドはファイルでの指定も可能になっています。
例えばローカルマシンにrun_on_arukas.shというスクリプトを用意しておいて、以下のようにオプション指定すればOKです。

お好きなシェルスクリプトが利用できます!

# 実行したいコマンドを別ファイルで指定
rarukas -c run_on_arukas.sh

ローカルマシンのファイルをアップロード/コマンド実行後にコンテナからダウンロード

さらにはSCP経由でファイル/ディレクトリのやりとりができるため、

  • ローカルの処理したいデータをアップロード
  • Arukas上で加工
  • 加工したデータをローカルにダウンロード

ということもできます。

このために--sync-dirというオプションを用意しており、これで指定したディレクトリをコマンド実行前にアップロード、コマンド実行後にダウンロードしてくれるようになります。

# カレントディレクトリの内容をアップロード/コマンド実行で生成されたファイルをダウンロード
$ rarukas --sync-dir . "echo from-arukas > result.txt"

# ローカルマシンにコンテナ上で生成されたファイルがダウンロードされている
$ ls
result.txt

ベースイメージの選択ができる

Arukas上で起動するコンテナのベースイメージはデフォルトでは軽量なalpineベースですが、実行したいコマンドに応じて以下のようなものを指定できます。

  • alpine : デフォルト、alpineベースの軽量イメージ
  • centos : CentOS7ベース
  • ubuntu: Ubuntu 16.04ベース
  • ansible: Ansible実行用のalpineベースイメージ
  • sacloud : さくらのクラウド系ツール(usacloudterraformpackerなど)の実行用イメージ、alpineベース

ベースイメージはオプション指定することで変更できます。

# ubuntuベースのイメージを利用する場合
rarukas --type ubuntu <your-command>

どんな時に使うの?

バッチ処理のオフロードや多少時間がかかってもいいから自分のマシンの負荷を上げたくない時などに便利です。
rarukasコマンドでArukasコンテナを起動するのには早くて10〜20秒、負荷状況によってはもう少しかかることもあるため速度を求める処理には向きませんが、 複数のコンテナに分散して並列に処理できるような場面では真価を発揮します。

利用例: 10コンテナでコマンドを分散/並列実行

以下のように xargsなどと組み合わせることで任意の処理を複数のコンテナに分散/並列実行できます。
この例ではecho Hello World from Rarukas -(連番)というコマンドを10台に分散/並列実行しています。
(複数インスタンスの利用となるため、--planオプションでhobbyプランを利用するように指定)

# 10コンテナに分散してコマンド実行
$ seq 10 | xargs -P10 -n1 rarukas --plan hobby echo Hello from Rarukas -$1 

これを実行すると以下のような表示になるはずです。
ただし、並列実行のため順序は保証されませんので多少出力順が異なる可能性があります。

# 見やすくするために標準エラー出力は除いています
Hello from Rarukas - 2
Hello from Rarukas - 9
Hello from Rarukas - 4
Hello from Rarukas - 1
Hello from Rarukas - 8
Hello from Rarukas - 3
Hello from Rarukas - 7
Hello from Rarukas - 10
Hello from Rarukas - 5
Hello from Rarukas - 6

利用例: Ansibleで複数マシンのプロビジョニングやレポート作成を分散/並列実行

先ほども出てきましたが、ansibleコマンドを実行できるイメージを用意しています。
これを利用すれば自マシンに負荷をかけることなく大量のサーバのプロビジョニングも行えますね。

まずはインベントリファイルやSSH用の秘密鍵などを準備しておきます。
この例ではプロビジョニング対象の各サーバ毎にインベントリファイル&秘密鍵を用意するようにしています。

# 対象のサーバ毎にインベントリファイルと秘密鍵を準備
$ tree .
.
├── host1
│   ├── ansible.cfg
│   ├── hosts
│   └── private_key
├── host2
│   ├── ansible.cfg
│   ├── hosts
│   └── private_key
├── host3
│   ├── ansible.cfg
│   ├── hosts
│   └── private_key
├── host4
│   ├── ansible.cfg
│   ├── hosts
│   └── private_key
└── host5
    ├── ansible.cfg
    ├── hosts
    └── private_key

# バックグラウンドでrarukasコマンド起動(5台分)
$ rarukas --type ansible --plan hobby --sync-dir host1 ansible host1 -i hosts -m setup --tree ./ &
$ rarukas --type ansible --plan hobby --sync-dir host2 ansible host2 -i hosts -m setup --tree ./ &
$ rarukas --type ansible --plan hobby --sync-dir host3 ansible host3 -i hosts -m setup --tree ./ &
$ rarukas --type ansible --plan hobby --sync-dir host4 ansible host4 -i hosts -m setup --tree ./ &
$ rarukas --type ansible --plan hobby --sync-dir host5 ansible host5 -i hosts -m setup --tree ./ &

他にも大容量のファイルのファイルのダウンロード -> 他のサーバへ送信といったように自マシンやネットワークの負荷を軽減するような使い方もありますね。 色々応用できると思います。

でもお高いんでしょ?

Rarukasを使うことで手軽にコンテナを使い捨てできますが、その際に心配なのがやっぱり料金ですね。

例えば1台あたり10分かかる処理を10台で行なったとすると、1実行あたり100分、毎日1回実行とすると30日で3,000分 = 50時間ですね。
Hobbyプランだと時間あたり 0.74円ですので、月額利用料としては

@0.74円 × 50時間 + 基本料金50円 = 87円

なんと、87円!!!

87円ですよ!!これはお安いですね!!!

ちなみに、公式サイトの料金の部分に「課金は秒単位」と記載があるので、ちょっとだけ起動して破棄する = 使い捨てる運用にもってこいですね!!

f:id:febc_yamamoto:20180225180033p:plain

ということでコンテナ使い捨てにRarukasは非常にお手軽です。
是非ご利用くださいー!!

終わりに

Arukasは3/19までは無料で利用できるとのことです。
この機会に一度試してみてはいかがでしょうか?
その際は是非Rarukasコマンドもよろしくお願いします!!

以上です。

【さくらのクラウド】baserCMS用のスタートアップスクリプトを使ってみた

f:id:febc_yamamoto:20180215161713p:plain

baserCMSがさくらのクラウドと連携し、baserCMS環境を簡単に構築できるスタートアップスクリプトが追加されたとのニュースが出ていました。

baserCMSニュース: IaaS型クラウド「さくらのクラウド」に、baserCMSのスタートアップスクリプトが搭載

baserCMSはオープンソースカンファレンス(OSC)などで度々見かけてはいたものの、なかなか手を出せていなかったのでこの機会に試してみました。

baserCMSってなに?

この辺はやはり公式サイトにバッチリ書いていました。

公式サイト: baserCMSとは

公式サイトの紹介文には以下のような特徴が挙げられています。

  • 日本人が日本人の為に開発している国産CMS(コンテンツマネージメントシステム)
  • CakePHPをベースとしているので、カスタマイズ性、メンテナンス性が高い
  • メールフォームや新着ブログなどのプラグインや管理画面の枠組みを最初から装備
  • スマートフォーンや携帯サイトにも対応
  • マニュアルやソースコードのコメントが日本語

ライセンスはMITだそうです。

ざっと公式サイトを見ましたが、ドキュメントが非常に充実しているなーという印象です。

baserCMSのインストール

スタートアップスクリプトの前に、通常のbaserCMSのインストール手順を確認してみました。
インストール手順は公式サイトのはじめてガイドの中に導入マニュアルがまとめられています。

公式サイト: baserCMS 導入マニュアル

通常のインストール手順としては

  • (1) baserCMSのソース一式をダウンロード
  • (2) サーバにアップロード
  • (3) ブラウザでインストーラーページを開く
    • 必要に応じてWebサーバの設定変更など
  • (4) データベースの選択(MySQL/PostgreSQL/SQLite3が選べる)
  • (5) 管理ユーザーの登録

という流れです。多くのCMSと同じ流れですね。
慣れている方であればすぐに設置可能でしょう。

baserCMS用のスタートアップスクリプトは何をしてくれるの?

スクリプトを眺めると、先ほどのインストール手順を一気に行ってくれるもののようです。
割と面倒なWebサーバや関連ライブラリのインストール、DBのセットアップ、意外と忘れがちなサーバのタイムゾーンの設定などを行っています。
これを利用すればインストールが非常に楽になりそうです。

ということで早速スタートアップスクリプトを利用してbaserCMSをインストールしてみます。

さくらのクラウドでbaserCMS用スタートアップを利用

手順としては以下の通りです。

まずはさくらのクラウドのコントロールパネルにアクセスします。

さくらのクラウド コントロールパネル

アカウントをお持ちでない場合はこちらからアカウント開設を行っておきます。

1) IaaS画面へ遷移

ログイン後表示される画面でIaaSを選択します。

f:id:febc_yamamoto:20180215145218p:plain

2) ゾーン選択 -> サーバ作成画面へ

次に対象のゾーンを選択します(以下図の①)。ゾーンによって価格が若干異なりますのでお好みのゾーンを選択してください。

参考: さくらのクラウド 価格表(サーバ)

ゾーン選択後にサーバ管理画面を開き(図の②)、右上の方にある「追加」ボタン(図の③)をクリックします。

f:id:febc_yamamoto:20180215145223p:plain

3) サーバ作成画面でシンプルモードのチェックを外す

デフォルトではシンプルモードという、簡易選択画面になっていると思います。
スタートアップスクリプトはシンプルモードでは選択できませんのでチェックを外しておきます。

f:id:febc_yamamoto:20180215145230p:plain

4) サーバのスペック選択(CPU/メモリ/ディスクなど)

続いて作成するサーバのスペックを選択します。

CPUとメモリについては後から変更可能ですので最初は適当に選べば良いと思います。
(baserCMSのドキュメントを確認しましたが、特に推奨スペックは記載がなさそうでした…)

また、baserCMSのスタートアップスクリプトはCentOS7のみ対応ということですので、ディスクについては

  • ディスクソースはアーカイブを選択
  • アーカイブ選択ではCentOS7を選択

としてください。

ディスクサイズについてですが、こちらは先ほどのCPU/メモリと異なり、増設するのに若干の作業が必要となります。
(別ディスクを増設する or より大きいサイズのディスクにコピーして移行など)
なので、ある程度のディスク利用が見込める場合は最初から大きめにしておくのがオススメです。

f:id:febc_yamamoto:20180215145234p:plain

5) ディスクの修正

続いて「ディスクの修正」という欄に入力を行なっていきます。
まずサーバのrootユーザのパスワードを入力します。

本番運用するサーバであればここで公開鍵を登録しておくべきなのですが、今回は省略しています。
なるべく登録をお勧めします。

そしていよいよスタートアップスクリプトの設定を行います。

スタートアップスクリプトとしてshellを選択すると、スクリプト一覧のリストが表示されます。
この中からbaserCMSを選択します。

f:id:febc_yamamoto:20180215145240p:plain

6) baserCMS関連の設定

baserCMSスタートアップスクリプトを選択すると、その下部にbaserCMS関連の設定項目を入力する欄が表示されます。
以下3つは必須となっていますので忘れずに入力してください。

  • baserCMS 管理ユーザー名
  • baserCMS 管理ユーザーのパスワード
  • baserCMS 管理ユーザのメールアドレス

ここで入力したユーザー名/パスワードは、後ほどbaserCMSの管理画面にログインする際に利用します。

f:id:febc_yamamoto:20180215145244p:plain

サーバ作成画面にはその他入力項目もありますが、適当に入力してください。

最後に画面下部にある「作成」ボタンを押せばサーバ作成が行われます。 作成完了まで数分かかることもありますので少し待ちましょう。

f:id:febc_yamamoto:20180215151506p:plain

動作確認!!

サーバ作成はうまくいきましたね?
それでは早速baserCMSの画面を開いてみましょう。

まずはさくらのクラウドのコントロールパネルから、作成されたサーバのグローバルIPアドレスを確認します。

サーバ一覧画面にサーバのグローバルIPが表示されていますので控えておきます。
(右クリックするとIPアドレスのコピーができますよ!)

f:id:febc_yamamoto:20180215151852p:plain

あとはブラウザから以下URLをひらけばbaserCMSのトップページが表示されるはずです。

  • 公開画面: http://<サーバのグローバルIP>/
  • 管理者画面: http://<サーバのグローバルIP>/admin

公開画面トップページ

f:id:febc_yamamoto:20180215152907p:plain

管理者ログイン画面

f:id:febc_yamamoto:20180215152910p:plain

管理者ダッシュボード

f:id:febc_yamamoto:20180215152933p:plain


ということでbaserCMS環境が非常に簡単に構築できました。

あとは公式サイトの導入マニュアルを参照しながらカスタマイズしていくだけですね。
導入マニュアルには「インストールあとのはじめの一歩」というページにどのような設定を行えば良いのかについてまとめられていますので、インストール後はこちらもご参照ください。

公式サイト: baserCMS 導入マニュアル -> インストールあとのはじめの一歩

以上です。Enjoy!!

【さくらのクラウド】TerraformでWindowsの展開を自動化する

f:id:febc_yamamoto:20180130182943p:plain

はじめに

今回はTerraform for さくらのクラウドを用いてWindows Serverの展開を自動化する方法をご紹介します。
さくらのクラウドではWindows Serverのセットアップを行うにはコントロールパネルからコンソール接続を行う必要がありましたが、
この方法を使えばterraformコマンドだけで一気に展開可能です。

なお、今回の方法は残念ながらリソースマネージャーには非対応です。 terraformコマンドを直接使う方向けとなっています。

背景

まずはさくらのクラウドでのWindows Serverプランの扱いについて確認しておきます。

さくらのクラウドWindows Serverプラン

さくらのクラウドではWindows Serverプランが提供されています。

さくらのクラウド マニュアル - Windows Serverプラン

Datacenter EditionやRDS、さらにはOffice付きのものやSQLServerインストール済みのものまで様々なプランがあります。

さくらのクラウドWindows Serverプランの仕様

便利なWindows Serverプランなのですが、その他のLinux系OSなどとは異なる仕様がいくつかあります。
例えばディスクサイズは100GB以上が要求されることなどがあります。

その中でも構築の自動化の際に大きく関わってくる仕様として「ディスクの修正が一部項目のみ対応」というものがあります。

「ディスクの修正」とは

さくらのクラウドでは各サーバ固有の設定(グローバルIPアドレスやホスト名、管理者パスワードなど)はディスクの修正という機能で各サーバ(のディスク)に設定されます。

SSH用の公開鍵の登録やスタートアップスクリプトなどもこのディスクの修正で反映できるようになっています。

設定できる項目は以下の通りです。

しかしWindows Serverプランではこのディスクの修正機能が一部のみしか利用できないという仕様になっています。

Windows Serverプランでのディスクの修正機能

Windows Serverプランの場合、ディスクの修正機能で設定可能な項目は以下のみとなっています。

参考: さくらのクラウド マニュアル - Windows ディスク修正

また、パブリックアーカイブからの新規作成時のみディスク修正が行えるという制限もあります。

つまり、Windows Serverプランを利用する場合はサーバ作成後にコントロールパネルからサーバのコンソールを利用して管理者パスワードの設定などの初期設定を手作業で行う必要があります。
(一応VNCも使えます。usacloudからだとusacloud server vnc <your-server-name>でOK)

これが数台程度であれば良いですが、数十〜数百台となってくるとブラウザから手作業で構築するのは非常に大変な作業となります。

そこで何か自動化する手段は無いか、、、ということで自動化する方法を考えました。

Windows展開の自動化といえばsysprep+応答ファイル

Windows環境構築の自動化といえばsysprep+応答ファイルという方法がありますね。

参考: MSDN - Windows セットアップの自動化の概要

実はさくらのクラウドで提供されているWindowsパブリックアーカイブはISOイメージからのインストール直後の状態ではなく、NIC用のVirtIOドライバなどがインストールされた状態となっており、起動直後はわずかな入力を行うだけで直ぐに利用できる状態となります。 (おそらくさくらのクラウド側であらかじめある程度構築後にsysprepされた状態になっているのでしょう)

なので、応答ファイルもごく一部の項目のみ作成すればOKとなっています。 具体的には最低限以下の設定をすれば直ぐにリモートデスクトップ接続ができる状態に持っていけます。

  • EULA(ソフトウェア仕様許諾)ヘの同意
  • ロケール/キーボードの設定
  • Administratorのパスワードの設定

これにchocolateyなどをインストールするスクリプトを仕込めば一気に環境構築できそうです。

応答ファイルはサーバにどうやって渡す? -> ISOイメージを使う

応答ファイルを作成しておけば構築の自動化はできそうですが、サーバ作成時にどうやって応答ファイルを渡せば良いでしょうか? いくつか方法はありますが、さくらのクラウドではISOイメージのアップロードに対応していますのでこれを利用してあげれば良いでしょう。

以下のドキュメントにも記載がありますが、応答ファイルをUnattend.xmlという名前でリムーバブルメディアのドライブのルートにおいておけば初回起動時に参照してくれます。

参考: Windows セットアップの実行方法

なので、応答ファイルを格納したISOイメージを作成し、サーバ作成時にISOイメージを挿入した状態で起動すれば良さそうですね。

Terraform for さくらのクラウドならISOイメージも一発構築

Terraform for さくらのクラウドISOイメージの作成もサポートしています。
ISOイメージの作成処理はGo言語のみで実装していますのでTerraformを実行するのがWindowsの場合でも問題なくISOイメージの作成が行えます。

また、ISOイメージに格納する応答ファイルUnattend.xmlについてはTerraformの変数やテンプレート機能を活用すれば作成できますね。

ということで実際にTerraform for さくらのクラウドで自動化を行なってみます。

構築

Windows Server向け環境構築用のtfファイル

tfファイルは以下のようになります。

### 概要
# Windows Serverの初期設定を行うテンプレート
#
# このテンプレートはWindows Serverの管理者パスワードの設定や任意のスクリプトの実行を行うテンプレートです。
# 初期設定は応答ファイル(Unattend.xml)を格納したISOイメージをサーバにアタッチすることで行なっています。
#

### 変数定義
# サーバ管理者(Administrator)のパスワード
variable password {}

locals {
  #*********************************************
  # プロビジョニング
  #*********************************************
  # サーバ上で実行したいコマンドを指定
  run_commands = [
    # パッケージマネージャー"chocolatey"のインストール
    "powershell -Command \"Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))\"",

    # chocolateyでChromeをインストール
    "C:\\ProgramData\\chocolatey\\bin\\choco.exe install -y googlechrome",
  ]

  #*********************************************
  # サーバ/ディスク
  #*********************************************
  # 利用するアーカイブ種別: 指定できる値についてはTerraform for さくらのクラウドのマニュアルを参照してください
  os_type = "windows2016"

  # サーバ名
  server_name = "windows-server"

  # ホスト名(コンピューター名)
  host_name = "windows-server"

  # サーバ コア数
  server_core = 2

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

  # ディスクサイズ(100GB以上)
  disk_size = 100
}

### 構成構築
# 利用するアーカイブ
data sakuracloud_archive "windows" {
  os_type = "${local.os_type}"
}

# ディスク
resource "sakuracloud_disk" "disk" {
  name              = "${local.server_name}"
  size              = "${local.disk_size}"
  source_archive_id = "${data.sakuracloud_archive.windows.id}"

  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}"
  cdrom_id = "${sakuracloud_cdrom.settings.id}"
}

# ISOイメージ(応答ファイル用)
resource sakuracloud_cdrom "settings" {
  name = "${local.server_name}"

  # 単一ファイルを内包するISOイメージを作成する
  content           = "${local.unattend_body}"
  content_file_name = "Unattend.xml"
}

# 応答ファイルの組み立て
locals {
  command_elm_format = <<EOL
                <SynchronousCommand wcm:action="add">
                    <Order>$${index}</Order>
                    <CommandLine>$${body}</CommandLine>
                </SynchronousCommand>
EOL

  commands_format = <<EOL
            <FirstLogonCommands>
                %s
            </FirstLogonCommands>
EOL

  commands_body = "${length(local.run_commands) > 0 ? format(local.commands_format, join("", data.template_file.run_commands.*.rendered)) : "" }"
}

data template_file "run_commands" {
  count    = "${length(local.run_commands)}"
  template = "${local.command_elm_format}"

  vars = {
    index = "${count.index + 1}"
    body  = "${local.run_commands[count.index]}"
  }
}

locals {
  unattend_body = <<EOL
<?xml version="1.0" encoding="utf-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
    <settings pass="oobeSystem">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <OOBE>
                <HideEULAPage>true</HideEULAPage>
            </OOBE>
            <UserAccounts>
                <AdministratorPassword>
                    <Value>${var.password}</Value>
                    <PlainText>true</PlainText>
                </AdministratorPassword>
            </UserAccounts>
            <AutoLogon>
                <Password>
                    <Value>${var.password}</Value>
                    <PlainText>true</PlainText>
                </Password>
                <Enabled>true</Enabled>
                <LogonCount>1</LogonCount>
                <Username>Administrator</Username>
            </AutoLogon>
${local.commands_body}
        </component>
        <component name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <InputLocale>0411:E0010411</InputLocale>
            <SystemLocale>ja-JP</SystemLocale>
            <UILanguage>ja-JP</UILanguage>
            <UILanguageFallback>ja-JP</UILanguageFallback>
            <UserLocale>ja-JP</UserLocale>
        </component>
    </settings>
    <settings pass="specialize">
        <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <ComputerName>${local.host_name}</ComputerName>
        </component>
    </settings>
</unattend>
EOL
}

あとはterraform applyを実行すればプロビジョニング済みのWindows Serverの出来上がりです。

各要素の解説

一応各要素について解説しておきます。

ISOイメージと応答ファイルの作成

ISOイメージは以下のように定義することで応答ファイルを格納したイメージの作成が可能です。

# ISOイメージ(応答ファイル用)
resource sakuracloud_cdrom "settings" {
  name = "${local.server_name}"

  # 単一ファイルを内包するISOイメージを作成する場合
  content           = "${local.unattend_body}"
  content_file_name = "Unattend.xml"
}

ポイントはcontentcontent_file_nameの部分ですね。
Terraform for さくらのクラウドのISOイメージリソースはすでに存在するISOイメージのアップロードも可能ですが、文字列を指定して単一のファイルを内包するISOイメージの作成が行えるようになっています。

contentに作成するファイルの内容を、content_file_nameに作成するファイル名を指定すればOKです。
(なお、content_file_nameに指定した値はボリュームラベルとしても利用されます。)

ここではファイル名にUnattend.xmlを指定してますね。 contentには応答ファイルのXMLを組み立てた結果を格納した変数が指定されています。

応答ファイルには以下の内容を記載しています。

  • EULA(ソフトウェア仕様許諾)ヘの同意
  • ロケール/キーボードの設定
  • Administratorのパスワードの設定
  • コンピューター名の変更
  • 指定のスクリプトを初回ログオン時に実行
  • サーバ作成後に1回だけ自動ログイン

実行したいスクリプトはLocalValuesのrun_commandsに指定しています。
この例ではChocolateyをインストールし、chocolateyを用いてChromeをインストールしています。
好みに応じてこの辺りを編集してみてください。

サーバへのISOイメージの挿入

これはサーバリソースの定義にISOイメージのIDを指定するだけでOKです。 例では以下のように指定しています。

# サーバ
resource "sakuracloud_server" "server" {
  # ...

  cdrom_id = "${sakuracloud_cdrom.settings.id}"
}

注意点

ISOイメージは安価とはいえお金がかかります(現時点では月額108円)。
サーバ構築後はISOイメージは利用しませんので削除してしまってもOKです。
削除する場合はtfファイルからISOイメージ関連の記述をコメントアウト & applyなどしておきましょう。

応用: WinRMを有効化してAnsibleやTerraformのプロビジョナーを利用する

簡単なスクリプト程度なら先程のrun_commandsに実行したいコマンドを書いていけばいいですが、 少し複雑なことをやる場合はやはりAnsibleなどのツールを利用するのが楽だと思います。

そのためにはWinRMを有効にする必要があります。

この場合、以下のようにtfファイルに記載すれば良いでしょう。

  #*********************************************
  # プロビジョニング
  #*********************************************
  # サーバ上で実行したいコマンドを指定
  run_commands = [
    #*****************************************************************
    # WinRMを有効化(5985:httpと5986:httpsポートが解放される)
    #*****************************************************************
    "powershell -Command \"Invoke-WebRequest -Uri https://raw.githubusercontent.com/ansible/ansible/devel/examples/scripts/ConfigureRemotingForAnsible.ps1 -OutFile ConfigureRemotingForAnsible.ps1\"",
    "powershell -Command \"powershell ConfigureRemotingForAnsible.ps1\"",
    "powershell -Command \"Remove-Item -path ConfigureRemotingForAnsible.ps1 -force\"",
  ]

この記述をしておけば、Ansibleが提供しているスクリプト(powershell)を利用してWinRMを有効化できます。
WinRMさえ有効になってしまえば以下のようにTerraformのプロビジョナーも利用できます。

# サーバ
resource "sakuracloud_server" "server" {
  name     = "${local.server_name}"
  disks    = ["${sakuracloud_disk.disk.id}"]
  core     = "${local.server_core}"
  memory   = "${local.server_memory}"
  cdrom_id = "${sakuracloud_cdrom.settings.id}"

  #  > WinRMを有効化しておけばプロビジョナーも利用可能
  provisioner "remote-exec" {
      # 接続情報の指定
      connection {
        type     = "winrm"
        host     = "${sakuracloud_server.server.ipaddress}"
        port     = 5986
        https    = true
        insecure = true
  
        user     = "Administrator"
        password = "${var.password}"
      }
  
      # サーバ上で実行するコマンド
      inline = [
        "echo foobar",
      ]
  }
}

Ansibleを利用する場合はfileリソースなどで以下のようにインベントリファイルを作成しておくと楽でしょう。

参考: Windows Serverもansibleで自動構築してServerspecでテストしよう

resource "local_file" "foo" {
  filename = "${path.module}/hosts"
  content  = <<EOL
[windows]
${sakuracloud_server.server.ipaddress}

[windows:vars]
ansible_user=Administrator
ansible_password=${var.password}
ansible_port=5986
ansible_connection=winrm
ansible_winrm_server_cert_validation=ignore
EOL
}

あとはansible -i hosts windows -m win_pingみたいに実行できるはずです。
(これをlocal_execプロビジョナーで実行してもいいですね)

終わりに

これでWindows環境の展開が楽になりますね!! 是非ご活用ください!!!

以上です。

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

f:id:febc_yamamoto:20180130182943p:plain

モダンTerraformシリーズです。

今回は前回の続きとしてMiscプロバイダーの中からTemplateプロバイダーについて扱います。

Templateプロバイダーとは

その名の通りテンプレート機能を提供してくれるプロバイダーです。
以下のリソースとデータソースが含まれます。

分類 名称 概要
データソース template_file 単一のファイル(など)用のテンプレート機能
データソース template_cloudinit_config cloud-initのconfigファイル用テンプレート機能
リソース template_dir ディレクトリ内のテンプレートファイルに対し一括処理を行う機能

template_dirだけリソースになっている点が注意ですね。
詳しくは後述しますが、template_dirのみファイルが作成されるという作用があるためです。

それでは各リソース/データソースについて見ていきます。

template_fileデータソース

単一のファイル(など)用のテンプレート機能を提供してくれます。

基本的な使い方

まず次のようなテンプレートを用意しておきます。

#!/bin/bash

echo "CONSUL_ADDRESS = ${consul_address}" > /tmp/iplist

このテンプレートを描画するには以下のようにデータソースを定義します。

data "template_file" "init" {
  # テンプレートとして<モジュールのディレクトリ>/init.tpl を指定
  template = "${file("${path.module}/init.tpl")}"

  # テンプレートに渡す変数の定義
  vars {
    consul_address = "${sakuracloud_server.consul.ipaddress}"
  }
}

あとはこのリソースのrenderedという属性を参照することで描画済み文字列が取得可能です。

# 例: スタートアップスクリプトとして利用する
resource sakuracloud_note "init-script" {
  name     = "init"
  content = "${data.template_file.init.rendered}" # rendered属性から描画済み文字列を参照
}

# 例: プロビジョナーに渡す
resource sakuracloud_server "server" {
  name = "server"

  # 描画した内容をサーバ上の/tmp/init.shに書き込み
  provisioner "file" {
    destination = "/tmp/init.sh"
    content     = "${data.template_file.init.rendered}"
  }
}

変数の指定

テンプレートに渡す変数は先程の例の通りvarsブロックで指定します。

data "template_file" "init" {
  ...

  # テンプレートに渡す変数の定義
  vars {
    consul_address = "${sakuracloud_server.consul.ipaddress}"
  }
}

この例ではconsul_addressというキーを用いていますが、任意のキーを複数指定可能です。

  vars {
    ipaddress   = "192.2.0.1"
    nw_mask_len = 24
  }

注意点としては値の指定にはリスト型/マップ型を指定できないということです。

# !バリデーションエラーとなります!

locals {
  # リスト型変数
  iplist = ["192.2.0.1", "192.2.0.2"]

  # マップ型変数
  rules = {
    allow = "80,443"
    deny  = "all"
  }
}

data "template_file" "init" {
  template = "foobar"

  # テンプレートに渡す変数の定義
  vars {
    target_hosts = "${local.iplist}" # リスト型は指定不可 
    target_rules = "${local.rules}"  # マップ型は指定不可
  }
}

この場合は何らかの形で文字列に変換して渡してあげましょう。

# 文字列にする例

  # テンプレートに渡す変数の定義
  vars {
    target_hosts = "${join(",", local.iplist)}"  # joinで文字列化
    target_rules = "${jsonencode(local.rules)}"  # jsonencodeでJSON化
  }

テンプレートをインラインで指定

テンプレートはファイルとして用意しておかなくてもtfファイルの中にインライン指定可能です。

data "template_file" "init" {
  # テンプレートをインライン指定
  template = "$${consul_address}:1234"

  vars {
    consul_address = "${sakuracloud_server.consul.ipaddress}"
  }
}

この場合、$エスケープして$$として書く必要があることに注意してください。
エスケープを忘れて$ひとつだけにした場合はテンプレート描画前にterraformが参照の解決をしようとしてしまいます。

使い所

基本的には以下のような場合に利用することになると思います。

  • tfファイル中にインラインで書くには長すぎる内容を扱う場合
  • 複数のリソースに展開する場合

ちょっと長い程度の文字列なら以下のようにヒアドキュメントで書くことも可能ですので、状況に応じて使い分けましょう。

resource sakuracloud_server "server" {
  name = "server"

  # 描画した内容をサーバ上の/tmp/init.shに書き込み
  provisioner "file" {
    destination = "/tmp/init.sh"
    # ちょっと長い程度ならヒアドキュメントで指定もあり
    content     = <<EOL

#!/bin/bash

echo "CONSUL_ADDRESS = ${sakuracloud_server.consul.ipaddress}" > /tmp/iplist
EOL
  }
}

template_cloudinit_configデータソース

template_fileデータソースの機能に加え、cloud-initで利用するconfigに特化した機能が追加されたデータソースです。
write-mime-multipartコマンドなどを利用せずともマルチパートなconfigを組み立てることが可能です。

以下のように利用します。

# Render a part using a `template_file`
data "template_file" "script" {
  template = "${file("${path.module}/init.tpl")}"

  vars {
    consul_address = "${aws_instance.consul.private_ip}"
  }
}

# Render a multi-part cloudinit config making use of the part
# above, and other source files
data "template_cloudinit_config" "config" {
  gzip          = true
  base64_encode = true

  # Setup hello world script to be called by the cloud-config
  part {
    filename     = "init.cfg"
    content_type = "text/part-handler"
    content      = "${data.template_file.script.rendered}"
  }

  part {
    content_type = "text/x-shellscript"
    content      = "baz"
  }

  part {
    content_type = "text/x-shellscript"
    content      = "ffbaz"
  }
}

# Start an AWS instance with the cloudinit config as user data
resource "aws_instance" "web" {
  ami           = "ami-d05e75b8"
  instance_type = "t2.micro"
  user_data     = "${data.template_cloudinit_config.config.rendered}"
}

これを描画すると以下のようになります(gzip/base64エンコードはoffの状態)

Content-Type: multipart/mixed; boundary="MIMEBOUNDARY"
MIME-Version: 1.0

--MIMEBOUNDARY
Content-Disposition: attachment; filename="init.cfg"
Content-Transfer-Encoding: 7bit
Content-Type: text/part-handler
Mime-Version: 1.0

#!/bin/bash

echo "CONSUL_ADDRESS = 192.2.0.1" > /tmp/iplist
--MIMEBOUNDARY
Content-Transfer-Encoding: 7bit
Content-Type: text/x-shellscript
Mime-Version: 1.0

baz
--MIMEBOUNDARY
Content-Transfer-Encoding: 7bit
Content-Type: text/x-shellscript
Mime-Version: 1.0

ffbaz
--MIMEBOUNDARY--

template_fileと比較すると以下の属性が追加で指定可能となっています。

  • gzip: 出力をgzip圧縮するか(デフォルトtrue)
  • base64_encode : 出力をBASE64エンコードするか(デフォルトtrue)
  • part : 各パートの内容(指定可能な属性は以下参照)
    • file_name: ファイル名
    • content_type: コンテントタイプ
    • content: ボディ
    • merge_type: マージタイプ(詳細はドキュメントを参照)

user_dataの組み立てに便利ですね。

template_dirリソース

template_fileデータソースのディレクトリ版です。
指定のディレクトリ配下のファイルをテンプレートとみなし、一括して描画してくれます。 実際にファイル(ディレクトリも)が作成されることに注意してください。

使い方は以下の通りです。

resource "template_dir" "config" {
  source_dir      = "${path.cwd}/source"
  destination_dir = "${path.cwd}/dest"

  vars {
    consul_addr = "${var.consul_addr}"
  }
}

この例だとterraform applyを実行するとsourceディレクトリ配下の各ファイルをテンプレートとして読み込み、描画したものをdestディレクトリ配下に同名のファイルとして作成してくれます。

ちなみにtemplate_dirリソースはIDにdestination_dirのコンテンツから作成されたハッシュ値が用いられており、ファイル/ディレクトリの変更やリネーム、削除が行われた場合は次回のapply時に再作成されるようになっています。

template_dirの注意点

前述の通りこのリソースはIDが宛先ディレクトリのコンテンツから作成されたハッシュとなっているため、ステートを複数人で共有していてもterraformコマンドを実行するマシンに宛先ディレクトリ(と描画後のコンテンツ)が存在しない場合はapply時に再作成が行われることとなります。

例えばステートレスなCIでterraform planに差分があったら通知するような仕組みを取っている場合は問題となるケースもありますので注意が必要です。

終わりに

今回はTemplateプロバイダーを紹介しました。
ちょっとしたプロビジョニングをTerraformで行う際の強力な武器となりますので是非使いこなしましょう。

次回はMiscプロバイダーの中からRandomかTLSを扱う予定です。

以上です。Enjoy!!

【モダン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内のコメントを参照)

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

以上です。