はじめに
今回やること
CloudStack(IDCFクラウド)に対して以下をTerraformコマンド一発で行えるようにすること。
- VM作成
- PublicIPアドレスの取得
- PortForwardingの設定
- Firewallの設定
上記を行うことでVMに対してssh
でログインできるようになります。
モチベーション
仕事柄CloudStack上でVMを作成して簡単なツールの検証環境などを構築したりすることが多いです。
今までは自分GUI上やAPI上から、操作していたが、検証環境のマシンスペックやOSは大体同じなので、コマンド一発でやりたいなというモチベーションです。
備考
HashiCorp社が開発している Terraform
を 使った使おうとしたのですが、そのままではIDCFクラウドの CloudStack
では動かなかったで、今回はterraformとそこで利用しているCloudStackのライブラリをforkして利用しました。
terraformの方の修正の一部は試験を書いたらPullRequestを出すつもりです。
ただCloudStackのライブラリについてはCloudStackの開発元とVersionによっては既存の実装のままで正しく動作する、というやり取りがGithub上でされており、マージされる可能性が低いのでPullRequestは出さない予定です。
前提
- IDCFクラウドの以下の情報を取得していること
- APIKey
- SecretKey
- Goのビルド環境があること
Terraformとは
ここを見るとインフラをコードで管理して状態を管理するためのツールらしい。
状態
を管理するので、例えばインフラの設定を変更したらその変更部分だけを適用してくれる感じかな。
手順
環境
- OSX: 10.11.1
- Go: 1.7.3
インストール
GOPATHの確認
$ env | grep GOPATH
GOPATH=/Users/aueno/.go
$
ソースの配置
以下のコマンドでビルド対象のソースを配置するディクレトリを作成しておく。
$ mkdir -p $GOPATH/src/github.com/akito1986
以下でビルド対象のソースを git clone
してくる。
$ cd $GOPATH/src/github.com/akito1986
$ git clone https://github.com/akito1986/terraform.git
ビルド
ビルドする前に依存するライブラリを取得しておく。
$ cd terraform/
$ go get -u
$ ls -1 $GOPATH/src/github.com
akito1986/
hashicorp/
$
ライブラリを取得後は以下のコマンドでビルドする。
ビルド後は-o
で指定されたファイル名を実行するとコマンドシンタックスが表示されます。
$ go build -o /usr/local/bin/terraform
$ /usr/local/bin/terraform
Usage: terraform [--version] [--help] <command> [args]
The available commands for execution are listed below.
The most common, useful commands are shown first, followed by
less common or more advanced commands. If you're just getting
started with Terraform, stick with the common commands. For the
other commands, please read the help and docs before usage.
Common commands:
apply Builds or changes infrastructure
console Interactive console for Terraform interpolations
destroy Destroy Terraform-managed infrastructure
fmt Rewrites config files to canonical format
get Download and install modules for the configuration
graph Create a visual graph of Terraform resources
import Import existing infrastructure into Terraform
init Initializes Terraform configuration from a module
output Read an output from a state file
plan Generate and show an execution plan
push Upload this Terraform module to Atlas to run
refresh Update local state file against real resources
remote Configure remote state storage
show Inspect Terraform state or plan
taint Manually mark a resource for recreation
untaint Manually unmark a resource as tainted
validate Validates the Terraform files
version Prints the Terraform version
All other commands:
state Advanced state management
$
上記のように表示されれば正常にビルドされています。
設定
TerraformはHCL
かJson
でインフラを管理するらしいので、ここではHCL
設定することにしました。
HCL(Hashicorp Configuration Language)
書き方は以下が参考になると思います。
http://dev.classmethod.jp/cloud/terraform-getting-started-with-aws/
以下のように記載できるらしいです。
// comment_1
# comment_2
variable "uri" {
default = "https://compute.jp-east.idcfcloud.com/client/api"
}
variable "api_key" {} // TF_VAR_xxxの形式で環境変数を参照しようとする。
variable "secret_key" {} // 値はTF_VAR_secret_keyという環境変数を参照する。
provider "cloudstack" {
api_url = "${var.uri}" // 変数uriの参照
api_key = "${var.api_key}"
secret_key = "${var.secret_key}"
}
/* A multi
line comment. */
resource "cloudstack_instance" "vm_1" {
name = "terraform-test-1"
service_offering= "light.S1" // nameで指定できるのが良いですよね。
network_id = "${var.network_id}"
template = "CentOS 7.2 64-bit"
zone = "pascal"
keypair = "aueno"
}
resource "cloudstack_port_forward" "pf_ssh" {
ip_address_id = "${cloudstack_ipaddress.public_ipaddress.id}"
forward {
protocol = "tcp"
private_port = 22
public_port = 10022
virtual_machine_id = "${cloudstack_instance.vm.id}" // 他のリソースの変数は「resource種別.resource名.属性名」で取得できるようです。
}
...
以下変数についての自分なりの理解です。
- provider: どのプラグインを利用するのかを記載する。
CloudStack
のリソースを操作する場合はcloudstack
と記載 - resource: 操作対象のリソースを記載。第一引数に操作対象のリソース種別、第二引数にそのリソースの名前を記載します。
CloudStackの設定
https://www.terraform.io/docs/providers/cloudstack/index.html
を参考に以下のように記載する。
今回やりたいことは以下なので、
- VM作成
- Firewall設定
- PortForwarding設定
なので必要なcloudstack
のリソースは以下となります。
- instance
- ipaddress
- port_forward
- firewall
設定ファイル作成
以下のようなファイルを適当なディレクトリに保存します。
terraformはカレントディレクトリの.tf
という拡張子を見て設定ファイルかを判断するらしいので末尾は .tf
である必要があります。
variable myip {}
variable api_key {}
variable secret_key {}
variable network_id { default = "957483e1-7e8f-4b0a-849b-84425e582130" }
provider "cloudstack" {
api_url = "https://compute.jp-east.idcfcloud.com/client/api"
api_key = "${var.api_key}"
secret_key = "${var.secret_key}"
}
resource "cloudstack_instance" "vm" {
name = "terraform-test-1"
service_offering= "light.S1"
network_id = "${var.network_id}"
template = "CentOS 7.2 64-bit"
zone = "pascal"
keypair = "aueno"
}
resource "cloudstack_ipaddress" "public_ipaddress" {
network_id = "${var.network_id}"
zone = "pascal"
}
resource "cloudstack_port_forward" "pf_ssh" {
ip_address_id = "${cloudstack_ipaddress.public_ipaddress.id}"
forward {
protocol = "tcp"
private_port = 22
public_port = 22
virtual_machine_id = "${cloudstack_instance.vm.id}"
}
}
resource "cloudstack_firewall" "myip" {
ip_address_id = "${cloudstack_ipaddress.public_ipaddress.id}"
rule {
cidr_list = ["${var.myip}/32"]
protocol = "tcp"
ports = ["1-65535"]
}
}
output "public_ipaddress" {
value = "${cloudstack_ipaddress.public_ipaddress.ip_address}"
}
上記はterraform-test-1
(VM)を作成して、public_ipaddress
(public ipaddress)を取得して、public_ipaddress
の22番portをterraform-test-1
の22番portにforwardingして、最後にpublic_ipaddress
のfirewallについて指定したmyip
アドレスから1-65535ポートまで接続を許可する設定となります。
実行
以下のコマンドでTerraformで何をするのかを事前に確認することができます。
api_keyとsecret_key、myipについてはそれぞれ、IDCFクラウドのAPIKey、SecretKey、許可したIPアドレスに読み替えてください。
$ TF_VAR_api_key=xxxx TF_VAR_secret_key=yyyy TF_VAR_myip=zzz.zzz.zzz.zzz 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.myip
ip_address_id: "<computed>"
managed: "false"
parallelism: "2"
rule.#: "1"
rule.1438695593.cidr_list.#: "1"
rule.1438695593.cidr_list.3084607623: "126.112.54.50/32"
rule.1438695593.icmp_code: "<computed>"
rule.1438695593.icmp_type: "<computed>"
rule.1438695593.ports.#: "1"
rule.1438695593.ports.1101440173: "1-65535"
rule.1438695593.protocol: "tcp"
rule.1438695593.uuids.%: "<computed>"
+ cloudstack_instance.vm
display_name: "<computed>"
expunge: "false"
group: "<computed>"
ip_address: "<computed>"
keypair: "aueno"
name: "terraform-test-1"
network_id: "957483e1-7e8f-4b0a-849b-84425e582130"
project: "<computed>"
service_offering: "light.S1"
template: "CentOS 7.2 64-bit"
zone: "pascal"
+ cloudstack_ipaddress.public_ipaddress
ip_address: "<computed>"
network_id: "957483e1-7e8f-4b0a-849b-84425e582130"
project: "<computed>"
zone: "pascal"
+ cloudstack_port_forward.pf_ssh
forward.#: "1"
forward.~1715957168.private_port: "22"
forward.~1715957168.protocol: "tcp"
forward.~1715957168.public_port: "22"
forward.~1715957168.uuid: "<computed>"
forward.~1715957168.virtual_machine_id: "<computed>"
ip_address_id: "<computed>"
managed: "false"
Plan: 4 to add, 0 to change, 0 to destroy.
$
4つのリソースが追加されることがわかります。
apply
コマンドで実際に適用します。
$ TF_VAR_api_key=xxxx TF_VAR_secret_key=yyyy TF_VAR_myip=zzz.zzz.zzz.zzz terraform apply
cloudstack_ipaddress.public_ipaddress: Refreshing state... (ID: fb5fc420-0cbb-4599-be01-feb9766f0587)
cloudstack_firewall.myip: Refreshing state... (ID: fb5fc420-0cbb-4599-be01-feb9766f0587)
cloudstack_instance.vm: Creating...
display_name: "" => "<computed>"
expunge: "" => "false"
group: "" => "<computed>"
ip_address: "" => "<computed>"
keypair: "" => "aueno"
name: "" => "terraform-test-1"
network_id: "" => "957483e1-7e8f-4b0a-849b-84425e582130"
project: "" => "<computed>"
service_offering: "" => "light.S1"
template: "" => "CentOS 7.2 64-bit"
zone: "" => "pascal"
.....
cloudstack_instance.vm: Creation complete
cloudstack_port_forward.pf_ssh: Creating...
forward.#: "" => "1"
forward.2467112059.private_port: "" => "22"
forward.2467112059.protocol: "" => "tcp"
forward.2467112059.public_port: "" => "22"
forward.2467112059.uuid: "" => "<computed>"
forward.2467112059.virtual_machine_id: "" => "zzzzzzzzzzz"
ip_address_id: "" => "fb5fc420-0cbb-4599-be01-feb9766f0587"
managed: "" => "false"
cloudstack_port_forward.pf_ssh: Creation complete
Apply complete! Resources: 2 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 = sss.sss.sss.sss // 実際のpublic ipaddressが表示される
$ ssh root@sss.sss.sss.sss
The authenticity of host 'sss.sss.sss.sss (sss.sss.sss.sss)' can't be established.
ECDSA key fingerprint is SHA256:BN7g57iY4sfhMyiDOMbmSi+fBS19/vxRH8rd36X2h0M.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'sss.sss.sss.sss' (ECDSA) to the list of known hosts.
________ ______ ______ __ _
/ _/ __ \/ ____/ / ____/________ ____ / /_(_)__ _____
/ // / / / / / /_ / ___/ __ \/ __ \/ __/ / _ \/ ___/
_/ // /_/ / /___ / __/ / / / /_/ / / / / /_/ / __/ /
/___/_____/\____/ /_/ /_/ \____/_/ /_/\__/_/\___/_/
[root@terraform-test-1 ~]#
ログインできるところまで設定されていることがわかります。
これでいちいちクライアントツールでちまちま設定したり、ブラウザ立ち上げてポチポチしなくても良いですね
より自分がやりたいことに専念できそうです。
補足
現在の設定の確認
show
コマンドで確認することができます。
$ TF_VAR_api_key=xxxx TF_VAR_secret_key=yyyy TF_VAR_myip=zzz.zzz.zzz.zzz
cloudstack_firewall.myip:
id = fb5fc420-0cbb-4599-be01-feb9766f0587
ip_address_id = fb5fc420-0cbb-4599-be01-feb9766f0587
managed = false
parallelism = 2
rule.# = 1
rule.1438695593.cidr_list.# = 1
rule.1438695593.cidr_list.3084607623 = zzz.zzz.zzz.zzz/32
rule.1438695593.icmp_code = 0
rule.1438695593.icmp_type = 0
rule.1438695593.ports.# = 1
rule.1438695593.ports.1101440173 = 1-65535
rule.1438695593.protocol = tcp
rule.1438695593.uuids.% = 1
rule.1438695593.uuids.1-65535 = 67f70c34-d1de-4b49-99f5-e3df7ffbf2b7
cloudstack_instance.vm:
id = c67aa47e-e928-4302-b665-b50edd455720
display_name = terraform-test-1
expunge = false
group =
ip_address = 10.6.1.5
keypair = aueno
name = terraform-test-1
network_id = 957483e1-7e8f-4b0a-849b-84425e582130
project =
service_offering = light.S1
template = CentOS 7.2 64-bit
zone = pascal
cloudstack_ipaddress.public_ipaddress:
id = fb5fc420-0cbb-4599-be01-feb9766f0587
ip_address = 210.140.78.47
network_id = 957483e1-7e8f-4b0a-849b-84425e582130
project =
zone = pascal
cloudstack_port_forward.pf_ssh:
id = fb5fc420-0cbb-4599-be01-feb9766f0587
forward.# = 1
forward.2467112059.private_port = 22
forward.2467112059.protocol = tcp
forward.2467112059.public_port = 22
forward.2467112059.uuid = 41792d57-e6ea-40cc-8fef-8506ad54375b
forward.2467112059.virtual_machine_id = c67aa47e-e928-4302-b665-b50edd455720
ip_address_id = fb5fc420-0cbb-4599-be01-feb9766f0587
managed = false
Outputs:
public_ipaddress = sss.sss.sss.sss
再実行のときの動作
plan
コマンドで確認してみます。
$ TF_VAR_api_key=xxxx TF_VAR_secret_key=yyyy TF_VAR_myip=zzz.zzz.zzz.zzz 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.
cloudstack_instance.vm: Refreshing state... (ID: c67aa47e-e928-4302-b665-b50edd455720)
cloudstack_ipaddress.public_ipaddress: Refreshing state... (ID: fb5fc420-0cbb-4599-be01-feb9766f0587)
cloudstack_firewall.myip: Refreshing state... (ID: fb5fc420-0cbb-4599-be01-feb9766f0587)
cloudstack_port_forward.pf_ssh: Refreshing state... (ID: fb5fc420-0cbb-4599-be01-feb9766f0587)
No changes. Infrastructure is up-to-date. This means that Terraform
could not detect any differences between your configuration and
the real physical resources that exist. As a result, Terraform
doesn't need to do anything.
$
設定がされている状態ではterraformは何もしないことがわかります。
おわりに
おしまい。