やったこと
Terrafformを利用して複数VMの作成。
背景
以前の記事に書かせてもらっているが、検証用のインフラ構築の際にブラウザやら単純なClientツールで設定するのが面倒。
Terraformなら簡単に構築できそうなので今検証中という感じ。
環境
- OSX: 10.11.1
- Go: 1.7.3
前提条件
- IDCFクラウドの以下の情報を取得していること
- APIKey
- SecretKey
- Goのビルド環境があること
手順
Terraformのビルド
以前の記事に記載の方法でforkしたterraformをビルドする。
Terraformの設定
Terraformでは共通化可能な設定をモジュールという単位で保持することができる。
今回は、VMの設定とポートフォワーディングの設定をモジュール化して、通常の設定ファイルから参照するようにした。
以下設定ファイルが置かれているディクレトリから見たときのディクレクトリ構造。
$ tree .
.
├── cloudstack.tf // 設定ファイル
├── terraform.log // log file
├── terraform.tfstatef
├── terraform.tfstate.backup
├── terraform.tfvars
└── vm_instances
└── terraform_test // モジュールディレクトリ
└── test.tf
2 directories, 6 files
$
Moduleの作成
以下のように通常の設定ファイルの形式で設定する。
variable counter {}
variable public_ipaddress_id {}
resource "cloudstack_instance" "vm" {
name = "${format("terraform-test-0%s", var.counter)}"
service_offering = "light.S1"
network_id = "26a035ad-3802-4622-8657-b0df6e33cb0a"
template = "CentOS 7.2 64-bit"
zone = "weber"
keypair = "aueno"
group = "terraform-test"
expunge = true
}
resource "cloudstack_port_forward" "pf_ssh" {
ip_address_id = "${var.public_ipaddress_id}"
forward {
protocol = "tcp"
private_port = 22
public_port = "${10022 + var.counter - 1}"
virtual_machine_id = "${cloudstack_instance.vm.id}"
}
}
変数定義されているものは、呼び出す際に渡される変数である。
counter
はVMの名前に利用しており、public_ipaddress_id
はポートフォワーディング設定するときに指定するものである。
設定ファイルの作成
今回は3台のサーバーを構築してみる。以下のような設定となる。
Moduleに渡すべき引数を設定していることがわかると思う。
またここでは先日リリースしたGPUが使えるIDCFクラウド東日本リージョン2に対して設定を行っている。
variable api_key {}
variable secret_key {}
variable zone {}
variable network_id {}
variable myips {
type = "list"
}
provider "cloudstack" {
api_url = "https://compute.jp-east-2.idcfcloud.com/client/api"
api_key = "${var.api_key}"
secret_key = "${var.secret_key}"
}
module "vm_instance_1" {
source = "./vm_instances/terraform_test"
counter = 1
public_ipaddress_id = "${cloudstack_ipaddress.public_ipaddress.id}"
}
module "vm_instance_2" {
source = "./vm_instances/terraform_test"
counter = 2
public_ipaddress_id = "${cloudstack_ipaddress.public_ipaddress.id}"
}
module "vm_instance_3" {
source = "./vm_instances/terraform_test"
counter = 3
public_ipaddress_id = "${cloudstack_ipaddress.public_ipaddress.id}"
}
resource "cloudstack_ipaddress" "public_ipaddress" {
network_id = "${var.network_id}"
zone = "${var.zone}"
}
resource "cloudstack_firewall" "myips" {
ip_address_id = "${cloudstack_ipaddress.public_ipaddress.id}"
rule {
cidr_list = "${var.myips}"
protocol = "tcp"
ports = ["1-65535"]
}
}
output "public_ipaddress" {
value = "${cloudstack_ipaddress.public_ipaddress.ip_address}"
}
変数ファイルの作成
設定ファイルに変数の定義もすることは出来るが、少し汚くなるかなと思い今回は変数ファイルを作成した。
変数ファイルの内容は以下である。
network_id = "26a035ad-3802-4622-8657-b0df6e33cb0a"
zone = "weber"
myips = [
"sss.sss.sss.ss1/32",
"sss.sss.sss.ss2/32",
]
myipsに接続を許可させたいcidrを指定する。
大元の設定ファイルの同一ディレクトリに terraform.tfvars
というファイルで作成すればオプション指定せずに読み込んでくれます。
VM作成
Moduleのインポート
ローカル環境のモジュールの場合もあらかじめ、terraform get
でモジュールをインポートする必要がある。
$ terraform get
Get: file:///Users/aueno/workspace/test/vm_instances/terraform_test
Get: file:///Users/aueno/workspace/test/vm_instances/terraform_test
Get: file:///Users/aueno/workspace/test/vm_instances/terraform_test
$
事前の状態
VM一覧
IPアドレス
Apply実行
さてここまでで、CloudStackを弄る準備ができました。
まずはplan
で何をするのか確認しておきます。
APIKEY, SECRET_KEYはご自身のIDCFクラウドのAPIKey, SecretKeyを指定してください。
$ TF_VAR_api_key=$APIKEY TF_VAR_secret_key=$SECRET_KEY terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but
will not be persisted to local or remote state storage.
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.
Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.
+ cloudstack_firewall.myips
ip_address_id: "<computed>"
managed: "false"
parallelism: "2"
rule.#: "1"
rule.2149869111.cidr_list.#: "1"
rule.2149869111.cidr_list.1098773020: "zzz.zzz.zzz.zz/32"
rule.2149869111.icmp_code: "<computed>"
rule.2149869111.icmp_type: "<computed>"
rule.2149869111.ports.#: "1"
rule.2149869111.ports.1101440173: "1-65535"
rule.2149869111.protocol: "tcp"
rule.2149869111.uuids.%: "<computed>"
+ cloudstack_ipaddress.public_ipaddress
ip_address: "<computed>"
network_id: "26a035ad-3802-4622-8657-b0df6e33cb0a"
project: "<computed>"
zone: "weber"
+ module.vm_instance_1.cloudstack_instance.vm
display_name: "<computed>"
expunge: "true"
group: "terraform-test"
ip_address: "<computed>"
keypair: "aueno"
name: "terraform-test-01"
network_id: "26a035ad-3802-4622-8657-b0df6e33cb0a"
project: "<computed>"
service_offering: "light.S1"
template: "CentOS 7.2 64-bit"
zone: "weber"
+ module.vm_instance_1.cloudstack_port_forward.pf_ssh
forward.#: "1"
forward.~3923475130.private_port: "22"
forward.~3923475130.protocol: "tcp"
forward.~3923475130.public_port: "10022"
forward.~3923475130.uuid: "<computed>"
forward.~3923475130.virtual_machine_id: "<computed>"
ip_address_id: "<computed>"
managed: "false"
+ module.vm_instance_2.cloudstack_instance.vm
display_name: "<computed>"
expunge: "true"
group: "terraform-test"
ip_address: "<computed>"
keypair: "aueno"
name: "terraform-test-02"
network_id: "26a035ad-3802-4622-8657-b0df6e33cb0a"
project: "<computed>"
service_offering: "light.S1"
template: "CentOS 7.2 64-bit"
zone: "weber"
+ module.vm_instance_2.cloudstack_port_forward.pf_ssh
forward.#: "1"
forward.~1844077387.private_port: "22"
forward.~1844077387.protocol: "tcp"
forward.~1844077387.public_port: "10023"
forward.~1844077387.uuid: "<computed>"
forward.~1844077387.virtual_machine_id: "<computed>"
ip_address_id: "<computed>"
managed: "false"
+ module.vm_instance_3.cloudstack_instance.vm
display_name: "<computed>"
expunge: "true"
group: "terraform-test"
ip_address: "<computed>"
keypair: "aueno"
name: "terraform-test-03"
network_id: "26a035ad-3802-4622-8657-b0df6e33cb0a"
project: "<computed>"
service_offering: "light.S1"
template: "CentOS 7.2 64-bit"
zone: "weber"
+ module.vm_instance_3.cloudstack_port_forward.pf_ssh
forward.#: "1"
forward.~1201611294.private_port: "22"
forward.~1201611294.protocol: "tcp"
forward.~1201611294.public_port: "10024"
forward.~1201611294.uuid: "<computed>"
forward.~1201611294.virtual_machine_id: "<computed>"
ip_address_id: "<computed>"
managed: "false"
Plan: 8 to add, 0 to change, 0 to destroy.
8つのリソースが追加されることがわかると思います。
以下でApply実行。
$ TF_VAR_api_key=$APIKEY TF_VAR_secret_key=$SECRET_KEY terraform apply
cloudstack_ipaddress.public_ipaddress: Creating...
ip_address: "" => "<computed>"
network_id: "" => "26a035ad-3802-4622-8657-b0df6e33cb0a"
project: "" => "<computed>"
zone: "" => "weber"
module.vm_instance_2.cloudstack_instance.vm: Creating...
display_name: "" => "<computed>"
expunge: "" => "true"
group: "" => "terraform-test"
ip_address: "" => "<computed>"
keypair: "" => "aueno"
name: "" => "terraform-test-02"
network_id: "" => "26a035ad-3802-4622-8657-b0df6e33cb0a"
project: "" => "<computed>"
service_offering: "" => "light.S1"
template: "" => "CentOS 7.2 64-bit"
zone: "" => "weber"
module.vm_instance_1.cloudstack_instance.vm: Creating...
display_name: "" => "<computed>"
expunge: "" => "true"
group: "" => "terraform-test"
ip_address: "" => "<computed>"
keypair: "" => "aueno"
name: "" => "terraform-test-01"
network_id: "" => "26a035ad-3802-4622-8657-b0df6e33cb0a"
project: "" => "<computed>"
service_offering: "" => "light.S1"
template: "" => "CentOS 7.2 64-bit"
zone: "" => "weber"
module.vm_instance_3.cloudstack_instance.vm: Creating...
display_name: "" => "<computed>"
expunge: "" => "true"
group: "" => "terraform-test"
ip_address: "" => "<computed>"
keypair: "" => "aueno"
name: "" => "terraform-test-03"
network_id: "" => "26a035ad-3802-4622-8657-b0df6e33cb0a"
project: "" => "<computed>"
service_offering: "" => "light.S1"
template: "" => "CentOS 7.2 64-bit"
zone: "" => "weber"
cloudstack_ipaddress.public_ipaddress: Creation complete
cloudstack_firewall.myips: Creating...
ip_address_id: "" => "9692c7c9-f3ca-445a-b375-3a1ba488d886"
managed: "" => "false"
parallelism: "" => "2"
rule.#: "" => "1"
rule.2149869111.cidr_list.#: "" => "1"
rule.2149869111.cidr_list.1098773020: "" => "sss.sss.sss.sss/32"
rule.2149869111.icmp_code: "" => "<computed>"
rule.2149869111.icmp_type: "" => "<computed>"
rule.2149869111.ports.#: "" => "1"
rule.2149869111.ports.1101440173: "" => "1-65535"
rule.2149869111.protocol: "" => "tcp"
rule.2149869111.uuids.%: "" => "<computed>"
cloudstack_firewall.myips: Creation complete
module.vm_instance_1.cloudstack_instance.vm: Still creating... (10s elapsed)
module.vm_instance_2.cloudstack_instance.vm: Still creating... (10s elapsed)
module.vm_instance_3.cloudstack_instance.vm: Still creating... (10s elapsed)
module.vm_instance_2.cloudstack_instance.vm: Still creating... (20s elapsed)
module.vm_instance_1.cloudstack_instance.vm: Still creating... (20s elapsed)
module.vm_instance_3.cloudstack_instance.vm: Still creating... (20s elapsed)
module.vm_instance_3.cloudstack_instance.vm: Creation complete
module.vm_instance_3.cloudstack_port_forward.pf_ssh: Creating...
forward.#: "" => "1"
forward.1057937353.private_port: "" => "22"
forward.1057937353.protocol: "" => "tcp"
forward.1057937353.public_port: "" => "10024"
forward.1057937353.uuid: "" => "<computed>"
forward.1057937353.virtual_machine_id: "" => "597c88c6-9bcf-44bf-8166-5412af937abb"
ip_address_id: "" => "9692c7c9-f3ca-445a-b375-3a1ba488d886"
managed: "" => "false"
module.vm_instance_1.cloudstack_instance.vm: Creation complete
module.vm_instance_1.cloudstack_port_forward.pf_ssh: Creating...
forward.#: "" => "1"
forward.3480124144.private_port: "" => "22"
forward.3480124144.protocol: "" => "tcp"
forward.3480124144.public_port: "" => "10022"
forward.3480124144.uuid: "" => "<computed>"
forward.3480124144.virtual_machine_id: "" => "b1e8ad78-9e3c-4798-9b3d-672cf3eb4363"
ip_address_id: "" => "9692c7c9-f3ca-445a-b375-3a1ba488d886"
managed: "" => "false"
module.vm_instance_2.cloudstack_instance.vm: Creation complete
module.vm_instance_2.cloudstack_port_forward.pf_ssh: Creating...
forward.#: "" => "1"
forward.1508310808.private_port: "" => "22"
forward.1508310808.protocol: "" => "tcp"
forward.1508310808.public_port: "" => "10023"
forward.1508310808.uuid: "" => "<computed>"
forward.1508310808.virtual_machine_id: "" => "5a7858c1-f84a-47b0-837f-4639530f6ef9"
ip_address_id: "" => "9692c7c9-f3ca-445a-b375-3a1ba488d886"
managed: "" => "false"
module.vm_instance_2.cloudstack_port_forward.pf_ssh: Creation complete
module.vm_instance_3.cloudstack_port_forward.pf_ssh: Creation complete
module.vm_instance_1.cloudstack_port_forward.pf_ssh: Creation complete
Apply complete! Resources: 8 added, 0 changed, 0 destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path: terraform.tfstate
Outputs:
public_ipaddress = zzz.zzz.zzz.zzz
$
並列でVM作成などが行なわれていることがわかると思います。
Apply後の状態
VM一覧
見事にVMが作成されている!
画面で見るとやった感が出ます。
IPアドレス
新しいPublicIPアドレスが取得されて、Firewallやポートフォワーディングが設定されていることがわかります。
おわりに
Terraformは状態
を管理するものなのでそこが良いと思うし、GO製のツールなのでタスクが普通に並列化されるところが良いと思う。
ただIDCFクラウドで名前を管理するために利用しているTagの設定ができたらもっと良いかなと思うところ。
自分で実装しろよ、という話だけでも