はじめに
Oracle Cloud Infrastrucrure には、Infrastructure as a Code を実現するために、Resource Manager というサービスが存在しています。
これを使うことで、OCI上のインフラ構成をCodeで定義して、テンプレートのように自動プロビジョニングを行うことができます。いわゆる、Infrastructure as a Code を実現するための1つのサービスとなります。
Resource Managerでは、Terraform が使われています。Terraformに馴染みのある方は扱いやすいと思います。
今回の記事では、以下の構成をResource Manager で作成するための構成を確認していきます。
なお、GitHubに公開しているので、適宜ご活用ください。
https://github.com/Sugi275/oci-resourcemanager-sample
ResourceManagerで作成するOCIのリソース
以下の構成を Resource Manager で作成していきます。
- Regional Public Subnet
- Compute x2
- FileStorage x1
用語
Resource Manager で出てくる用語を整理します。
Stack
ResourceManager上で定義する、OCIリソースのまとまりを表しています。Stack単位でApply・Destroyを行うため、管理しやすい粒度で定義すると良いと思います。
他のPublicCloudの同様のサービスだと、定義したまとまりをApply後にStackと表現する場合がありますが、
ResourceManagerの場合は、Applyの有無にかかわらずStackと表現するため混乱しないように注意をする必要があります。
Plan
定義したStackを使用して、OCIのリソースをどのように変更するのか、実行計画を立てます。
以前Stackを実行したときの状態と、Stackで定義したあるべき姿を比較して、何を変更するかを洗い出します。
Stackに定義されているリソースが存在しなければ新規作成を行い、以前作成したものがStackに存在しなければ削除を行います。
Apply
Planで生成した実行計画をもとに、実際のOCI環境に適用を行います。
このタイミングで、OCIリソースの変更処理が発生します。
Destroy
Stackで作成したOCIリソースのまとまりを削除します。
手順
Terraform Install(Optional)
まず、Resource Manager そのものを利用する時にはTerraformは必要ではありません。
ただ、手元のローカルPCで、Terraform用の構成ファイルを作成する際に、terraformコマンドを使用できると便利なので、インストールを行います
Terraform用のバイナリファイルはzipファイルとして公開されています。Terraform用のディレクトリを作成し、バイナリファイルを格納します。
mkdir ~/terraform
cd ~/terraform
wget https://releases.hashicorp.com/terraform/0.11.13/terraform_0.11.13_linux_amd64.zip
zipを解凍します
unzip terraform_0.11.13_linux_amd64.zip
解凍後に生成さえたバイナリファイルを PATH に追加します
以下はfish shellの例
string trim '
set PATH ~/terraform $PATH
' >> ~/.config/fish/config.fish
これでshellを開きなおすと、terraformコマンドを実行可能になります
> terraform -h
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.
...snip...
作業用ディレクトリの作成
適当に作業用ディレクトリを作成します
mkdir ~/resourcemanager/webserver/terraformfiles/
Terraformのファイル群を作成
Terraformで使用するファイル群を作成します。
Linuxのfishでファイルを作成する方法で記載しています。
Bashやその他shellの場合は、適宜変更してファイルを作成してください。
また、「はじめに」で記載したGitHubに同様のファイルが存在するので、そちらをご利用していただいても問題ありません。
OCI Provider
OCI Provider に必要な情報を定義します
string trim '
provider "oci" {
region = "${var.region}"
}
' > ~/resourcemanager/webserver/terraformfiles/provider.tf
Variable
variableを定義します。variableは後述のTerraform用定義ファイルで利用ができます。
また、Resource Manager(OCIコンソール、OCI CLI) でも同様のvariableを定義することができます。
適宜必要に応じて値を変更します。
string trim '
variable "region" {
default = "us-phoenix-1"
}
variable "compartment_ocid" {
default = "ocid1.compartment.oc1..todorename"
}
variable "instance_image_ocid" {
type = "map"
default = {
// See https://docs.us-phoenix-1.oraclecloud.com/images/
// Oracle-provided image "Oracle-Linux-7.6-2019.02.20-0"
us-phoenix-1 = "ocid1.image.oc1.phx.aaaaaaaapxvrtwbpgy3lchk2usn462ekarljwg4zou2acmundxlkzdty4bjq"
us-ashburn-1 = "ocid1.image.oc1.iad.aaaaaaaannaquxy7rrbrbngpaqp427mv426rlalgihxwdjrz3fr2iiaxah5a"
eu-frankfurt-1 = "ocid1.image.oc1.eu-frankfurt-1.aaaaaaaa527xpybx2azyhcz2oyk6f4lsvokyujajo73zuxnnhcnp7p24pgva"
uk-london-1 = "ocid1.image.oc1.uk-london-1.aaaaaaaarruepdlahln5fah4lvm7tsf4was3wdx75vfs6vljdke65imbqnhq"
ca-toronto-1 = "ocid1.image.oc1.ca-toronto-1.aaaaaaaa7ac57wwwhputaufcbf633ojir6scqa4yv6iaqtn3u64wisqd3jjq"
}
}
variable "mount_target_1_display_name" {
default = "tf-mounttarget1"
}
variable "file_system_1_display_name" {
default = "tf-fs1"
}
variable "export_path_fs1_mt1" {
default = "/tf-fs1"
}
variable "ssh_public_key" {
default = "overwrite by resourcemanager"
}
variable "ssh_private_key" {
default = "overwrite by resourcemanager"
}
locals {
mount_target_1_ip_address = "${lookup(data.oci_core_private_ips.ip_mount_target1.private_ips[0], "ip_address")}"
}
data "oci_core_private_ips" ip_mount_target1 {
subnet_id = "${oci_file_storage_mount_target.my_mount_target_1.subnet_id}"
filter {
name = "id"
values = ["${oci_file_storage_mount_target.my_mount_target_1.private_ip_ids.0}"]
}
}
' > ~/resourcemanager/webserver/terraformfiles/variable.tf
Network
ネットワーク全般を定義します。
なお、Regional Subnet を作成したい場合は、Subnetを定義するときに、Availability Domain を指定しない方法となるため、注意が必要です。
基本的にはRegional Subnetで作成すべきです。
string trim '
resource "oci_core_virtual_network" "tfvcn" {
cidr_block = "10.0.0.0/16"
compartment_id = "${var.compartment_ocid}"
display_name = "tfvcn"
dns_label = "tfvcn"
}
resource "oci_core_internet_gateway" "tf-ig1" {
compartment_id = "${var.compartment_ocid}"
display_name = "tf-ig1"
vcn_id = "${oci_core_virtual_network.tfvcn.id}"
}
resource "oci_core_default_route_table" "tf-default-route-table" {
manage_default_resource_id = "${oci_core_virtual_network.tfvcn.default_route_table_id}"
display_name = "tf-default-route-table"
route_rules {
destination = "0.0.0.0/0"
destination_type = "CIDR_BLOCK"
network_entity_id = "${oci_core_internet_gateway.tf-ig1.id}"
}
}
resource "oci_core_security_list" "TFSecurityList" {
compartment_id = "${var.compartment_ocid}"
vcn_id = "${oci_core_virtual_network.tfvcn.id}"
display_name = "TFSecurityList"
// allow outbound tcp traffic on all ports
egress_security_rules {
destination = "0.0.0.0/0"
protocol = "6"
}
// allow outbound udp traffic on a port range
egress_security_rules {
destination = "0.0.0.0/0"
protocol = "17" // udp
stateless = true
udp_options {
// These values correspond to the destination port range.
"min" = 319
"max" = 320
}
}
// allow inbound ssh traffic from a specific port
ingress_security_rules {
protocol = "6" // tcp
source = "0.0.0.0/0"
stateless = false
tcp_options {
// These values correspond to the destination port range.
"min" = 22
"max" = 22
}
}
// allow inbound icmp traffic of a specific type
ingress_security_rules {
protocol = 1
source = "0.0.0.0/0"
stateless = true
icmp_options {
"type" = 3
"code" = 4
}
}
// allow inbound nfs traffic
ingress_security_rules {
protocol = "6" // tcp
source = "10.0.0.0/16"
stateless = false
tcp_options {
"min" = 111
"max" = 111
}
}
// allow inbound nfs traffic
ingress_security_rules {
protocol = "6" // tcp
source = "10.0.0.0/16"
stateless = false
tcp_options {
"min" = 2048
"max" = 2050
}
}
// allow inbound nfs traffic
ingress_security_rules {
protocol = "17" // udp
source = "10.0.0.0/16"
stateless = false
udp_options {
"min" = 111
"max" = 111
}
}
// allow inbound nfs traffic
ingress_security_rules {
protocol = "17" // udp
source = "10.0.0.0/16"
stateless = false
udp_options {
"min" = 2048
"max" = 2048
}
}
// allow inbound nfs traffic
egress_security_rules {
destination = "0.0.0.0/0"
protocol = "17" // udp
stateless = true
udp_options {
// These values correspond to the destination port range.
"min" = 111
"max" = 111
}
}
}
resource "oci_core_subnet" "public01" {
cidr_block = "10.0.1.0/24"
display_name = "public01"
dns_label = "public01"
vcn_id = "${oci_core_virtual_network.tfvcn.id}"
prohibit_public_ip_on_vnic = false
security_list_ids = ["${oci_core_security_list.TFSecurityList.id}"]
route_table_id = "${oci_core_virtual_network.tfvcn.default_route_table_id}"
dhcp_options_id = "${oci_core_virtual_network.tfvcn.default_dhcp_options_id}"
compartment_id = "${var.compartment_ocid}"
}
resource "oci_core_subnet" "private01" {
cidr_block = "10.0.2.0/24"
display_name = "private01"
dns_label = "private01"
vcn_id = "${oci_core_virtual_network.tfvcn.id}"
prohibit_public_ip_on_vnic = true
security_list_ids = ["${oci_core_virtual_network.tfvcn.default_security_list_id}"]
route_table_id = "${oci_core_virtual_network.tfvcn.default_route_table_id}"
dhcp_options_id = "${oci_core_virtual_network.tfvcn.default_dhcp_options_id}"
compartment_id = "${var.compartment_ocid}"
}
' > ~/resourcemanager/webserver/terraformfiles/network.tf
Compute
2個のCompute Instance を定義します。
ポイントは、"remote-exec" Provisioner を使用して Instance 内のコマンドを発行し、File Storage をマウントしている点です。
Resource Manager で、簡単なInstance内のコマンドを発行することができます。
ただ、複雑なものはResource Manager でやるのはつらいので、Ansible などのほかの構成管理ツールと一緒に使っていくと良いと思います。
string trim '
resource "oci_core_instance" "web01" {
count = "1"
availability_domain = "TGjA:PHX-AD-1"
compartment_id = "${var.compartment_ocid}"
display_name = "web01"
shape = "VM.Standard2.2"
create_vnic_details {
subnet_id = "${oci_core_subnet.public01.id}"
display_name = "primaryvnic"
assign_public_ip = true
hostname_label = "web01"
}
source_details {
source_type = "image"
source_id = "${var.instance_image_ocid[var.region]}"
}
metadata {
ssh_authorized_keys = "${var.ssh_public_key}"
}
timeouts {
create = "60m"
}
provisioner "remote-exec" {
connection {
agent = false
timeout = "15m"
host = "${oci_core_instance.web01.public_ip}"
user = "opc"
private_key = "${var.ssh_private_key}"
}
inline = [
"sudo yum -y install nfs-utils > nfs-utils-install.log",
"sudo mkdir -p /mnt/filestorage/fs1",
"sudo mount ${local.mount_target_1_ip_address}:${var.export_path_fs1_mt1} /mnt/filestorage/fs1",
]
}
}
resource "oci_core_instance" "web02" {
count = "1"
availability_domain = "TGjA:PHX-AD-2"
compartment_id = "${var.compartment_ocid}"
display_name = "web02"
shape = "VM.Standard2.2"
create_vnic_details {
subnet_id = "${oci_core_subnet.public01.id}"
display_name = "primaryvnic"
assign_public_ip = true
hostname_label = "web02"
}
source_details {
source_type = "image"
source_id = "${var.instance_image_ocid[var.region]}"
}
metadata {
ssh_authorized_keys = "${var.ssh_public_key}"
}
timeouts {
create = "60m"
}
provisioner "remote-exec" {
connection {
agent = false
timeout = "15m"
host = "${oci_core_instance.web02.public_ip}"
user = "opc"
private_key = "${var.ssh_private_key}"
}
inline = [
"sudo yum -y install nfs-utils > nfs-utils-install.log",
"sudo mkdir -p /mnt/filestorage/fs1",
"sudo mount ${local.mount_target_1_ip_address}:${var.export_path_fs1_mt1} /mnt/filestorage/fs1",
]
}
}
' > ~/resourcemanager/webserver/terraformfiles/compute.tf
File Storage
File Storage を定義します
string trim '
resource "oci_file_storage_mount_target" "my_mount_target_1" {
#Required
availability_domain = "TGjA:PHX-AD-3"
compartment_id = "${var.compartment_ocid}"
subnet_id = "${oci_core_subnet.public01.id}"
#Optional
display_name = "${var.mount_target_1_display_name}"
}
resource "oci_file_storage_export_set" "my_export_set_1" {
# Required
mount_target_id = "${oci_file_storage_mount_target.my_mount_target_1.id}"
# Optional
display_name = "my_export_set_1"
}
resource "oci_file_storage_export" "my_export_fs1_mt1" {
#Required
export_set_id = "${oci_file_storage_export_set.my_export_set_1.id}"
file_system_id = "${oci_file_storage_file_system.my_fs_1.id}"
path = "${var.export_path_fs1_mt1}"
export_options = [
{
source = "0.0.0.0/0"
access = "READ_WRITE"
identity_squash = "NONE"
require_privileged_source_port = true
},
]
}
resource "oci_file_storage_file_system" "my_fs_1" {
#Required
availability_domain = "TGjA:PHX-AD-3"
compartment_id = "${var.compartment_ocid}"
#Optional
display_name = "${var.file_system_1_display_name}"
}
' > ~/resourcemanager/webserver/terraformfiles/filestorage.tf
StackのOutput
Resource Manager を実行したときに実行結果をカスタマイズすることができます。
Defaultで出力される実行結果の下に、インスタンスのGlobalIPを表示させています。
string trim '
output "InstancePublicIPs" {
value = ["${oci_core_instance.web01.*.public_ip}",
"${oci_core_instance.web02.*.public_ip}"]
}
' > ~/resourcemanager/webserver/terraformfiles/output.tf
validate
上記で作成したTerraformのファイル群の、書式・内容に問題がないかを確認します。
確認しないでResource Manager へアップロードしてもよいのですが、Resource Manager 上の各種操作は5分ほど実行時間が必要です。
書式に問題があるのが発覚するまで若干の時間がかかるため、ローカルで確認するのが良いと思います。
まず、ローカルのterraformコマンドで書式を確認するために、init を実行します。
terraformのHCLとして正しい書式か、ローカル環境で検証
cd ~/resourcemanager/webserver/terraformfiles
terraform init
validate コマンドで書式に問題がないか確認します
cd ~/resourcemanager/webserver/terraformfiles
terraform validate
実行例。何もエラーが表示されなければOK
> terraform validate
>
以下のようなエラーが表示された場合、対処を行います。
> terraform validate
Error: Variable 'ssh_public_key': duplicate found. Variable names must be unique.
zip化
terraform用ファイルをzipで固めます。なお、.terraform
ディレクトリを含めてzip化するとエラーになりますが、アスタリスクを指定しても.terraform
ディレクトリは含まれないので問題ありません。
cd ~/resourcemanager/webserver/terraformfiles
zip webserver.zip *
oci cliでstackを作成
zipファイルなどを指定してstackの新規作成を行います
cd ~/resourcemanager/webserver/terraformfiles
oci resource-manager stack create \
--config-source "webserver.zip" \
--display-name "Sample webserver" \
--description "Example: Create a webserver Instance"
stackのocidを変数に格納します
set stackid (oci resource-manager stack list --display-name "Sample webserver" | jq -r ".data[0].id")
なお、zipファイルを更新する場合は、以下の update で更新が可能です
cd ~/resourcemanager/webserver/terraformfiles
oci resource-manager stack update \
--stack-id $stackid \
--config-source "webserver.zip"
Variableを指定
公開鍵と秘密鍵を、ResourceManagerの機能で指定します。
ResourceManagerのvariablesで指定する理由は、Terraformのファイル群の中で記載すると、クレデンシャル情報をGitHubなどにアップロードすることになり、セキュリティに懸念があるためです。
秘密鍵の改行コードは \n に変換して指定すると良いでしょう。
なお、以下の鍵の情報は、中身を適当に書き換えているため使用することはできません。
oci resource-manager stack update \
--stack-id $stackid \
--variables '{"ssh_public_key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAAQC8/ABrmRV/Vc2aGrUEsp15XpxoIelke72QZP1lgCPAxp2AO5EJ7bnER6pANwXPAcuQUv3ZaqtP/wNKH9xQUVUWUmOrvhJ6w9BHz8+zgR0N9WH2JoVTfvdEaQXEfHliRQwvT9dZWAC3GAw5VkwtyO3v87cwmNkDJeUjjQxVL8MRNTFB9MRkNKUFtV72gGtp4vbYEgCYgiBgSpJX5l6QjVNISVHCMbANHLhiGNGrmdKw177xK9POoasjdaopsjWHInskalNco2xqnOKNClqtL/7H7C/QJqbHkqbcj+q5+V63eQ57PdVxMQkR1tu790A8CaTWzTUvehKCO8DITRddeQr sugi@SUSUGIYA-JP",
"ssh_private_key":"-----BEGIN RSA PRIVATE KEY-----\nsecret\nsecret\nsecret\nsecret\nsecret\nsecret\nsecret\nsecret\nsecret\n-----END RSA PRIVATE KEY-----"
}'
Plan
作成したstackを使用して、実行計画を生成します。
oci resource-manager job create \
--stack-id $stackid \
--operation PLAN \
--display-name "Plan Job"
抜粋した実行結果は以下のようになっており、目視で内容を読み解くことができます。
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create <============= + マークの意味が記載されている。
Terraform will perform the following actions:
+ oci_core_default_route_table.tf-default-route-table <============= + マークがついているため、新規作成ということが読み取れる
id: <computed>
display_name: "tf-default-route-table"
freeform_tags.%: <computed>
manage_default_resource_id: "${oci_core_virtual_network.tfvcn.default_route_table_id}"
route_rules.#: "1"
route_rules.~4148037591.cidr_block: <computed>
route_rules.~4148037591.destination: "0.0.0.0/0"
route_rules.~4148037591.destination_type: "CIDR_BLOCK"
route_rules.~4148037591.network_entity_id: "${oci_core_internet_gateway.tf-ig1.id}"
state: <computed>
time_created: <computed>
time_modified: <computed>
+ oci_core_instance.web01 <============= + マークがついているため、新規作成ということが読み取れる
id: <computed>
agent_config.#: <computed>
availability_domain: "TGjA:PHX-AD-1"
boot_volume_id: <computed>
省略
Apply
最新の Plan で生成した実行計画を使用して、OCI上にリソースを生成します。
特に問題が発生しなければ、OCI上のリソースが作成されます。
oci resource-manager job create \
--stack-id $stackid \
--operation APPLY \
--display-name "Apply Job" \
--apply-job-plan-resolution '{"isUseLatestJobId":true}'
実行ログを確認し、CompleteやGlobalIPが表示されていることを確認します
Apply complete! Resources: 12 added, 0 changed, 0 destroyed.
Outputs:
InstancePublicIPs = [
129.146.48.77,
129.146.114.140
]
Instanceにログインして確認
Resource Manager で作成されたインスタンスにログインして、mount情報を確認します。
File Storage が 自動的にNFS マウントされていることを確認できます。
# df -hT
Filesystem Type Size Used Avail Use% Mounted on
devtmpfs devtmpfs 15G 0 15G 0% /dev
tmpfs tmpfs 15G 0 15G 0% /dev/shm
tmpfs tmpfs 15G 8.6M 15G 1% /run
tmpfs tmpfs 15G 0 15G 0% /sys/fs/cgroup
/dev/sda3 xfs 39G 2.1G 37G 6% /
/dev/sda1 vfat 200M 9.7M 191M 5% /boot/efi
10.0.1.3:/tf-fs1 nfs 8.0E 0 8.0E 0% /mnt/filestorage/fs1 <===== File Storageがマウントされている
tmpfs tmpfs 3.0G 0 3.0G 0% /run/user/1000
fstab
注意点として、今回のResource Managerでは、インスタンスのfstabを更新していないので、インスタンスを再起動するとマウントが外れてしまいます。
Resource Manager で fstabを作成してもよいのですが、ファイルの作成は Resource Manager(Terraform)はあまり得意ではないと思うので、AnsibleといったほかのOSSを使うと良いのかなと思います。
操作の注意点
StackをApplyした後、DestroyせずにStackのDeleteをすると
Stackで作成したものが、残ったままとなる。StackをDeleteする前に、かならずDestroyすること
実行時間
Stackに関する、Create, Plan, Apply, Destroy などの操作は、5分くらい実行時間がかかる。少々待つ必要がある。
参考URL
OCI Document
https://docs.cloud.oracle.com/iaas/Content/ResourceManager/Concepts/resourcemanager.htm
GitHub Provider
https://github.com/terraform-providers/terraform-provider-oci
GitHub Example
https://github.com/terraform-providers/terraform-provider-oci/tree/master/examples
Terraformの公式ページ 各種OCIリソースの定義方法が記載されている
https://www.terraform.io/docs/providers/oci/index.html
Terraform の Resource 定義
https://www.terraform.io/docs/configuration/resources.html
Terraform の remote-exec について (InstanceのShell上でコマンドを実行することができる)
https://dev.classmethod.jp/cloud/how-to-provision-remote-servers-with-terraform-by-using-remote-exec/