OCI에 프로비저닝 리소스들은 다음과 같습니다.
테라폼의 Module을 사용하여 각 리소스를 모듈화 하였고, Workspace를 사용해서 Multi Oracle Tenancy 프로비저닝이 가능하도록 구현했습니다.
테라폼 프로젝트는 아래 깃헙 저장소를 통해서 제공합니다. https://github.com/the-team-oasis/Infrastructure-as-Code/tree/main/terraform
다운로드 받은 OCI용 테라폼 프로젝트의 디렉토리 구조는 다음과 같습니다.
.
├── terraform
│ ├── evn
│ │ ├── {tenancy}.{region}.tfvars
│ │ └── danoci.ap-seoul-1.tfvars
│ ├── provider.tf
│ ├── main.tf
│ ├── vars.tf
│ ├── terraform.tfstate.d
│ ├── modules
│ │ ├── adb
│ │ │ ├── main.tf
│ │ │ ├── datasources.tf
│ │ │ ├── vars.tf
│ │ │ └── outputs.tf
│ │ ├── compartment
│ │ │ ├── main.tf
│ │ │ ├── datasources.tf
│ │ │ ├── vars.tf
│ │ │ └── outputs.tf
│ │ ├── compute
│ │ │ ├── main.tf
│ │ │ ├── datasources.tf
│ │ │ ├── vars.tf
│ │ │ └── outputs.tf
│ │ ├── container
│ │ │ ├── main.tf
│ │ │ ├── datasources.tf
│ │ │ ├── vars.tf
│ │ │ └── outputs.tf
│ │ ├── dbsystem
│ │ │ ├── main.tf
│ │ │ ├── datasources.tf
│ │ │ ├── vars.tf
│ │ │ └── outputs.tf
│ │ ├── functions
│ │ │ ├── main.tf
│ │ │ ├── datasources.tf
│ │ │ ├── vars.tf
│ │ │ └── outputs.tf
│ │ ├── group
│ │ │ ├── main.tf
│ │ │ ├── datasources.tf
│ │ │ ├── vars.tf
│ │ │ └── outputs.tf
│ │ ├── policy
│ │ │ ├── main.tf
│ │ │ ├── datasources.tf
│ │ │ ├── vars.tf
│ │ │ └── outputs.tf
│ │ └── vcn
│ │ ├── main.tf
│ │ ├── datasources.tf
│ │ ├── vars.tf
│ │ └── outputs.tf
│ └── run_tf.sh
└──
현재 oci 프로젝트 하위 모듈들의 공통 구성 파일인 provider.tf, main.tf, vars.tf 파일이 있고, modules 하위에 생성할 리소스 별로 모듈을 구성했습니다. 각 모듈은 main.tf, datasources.tf, vars.tf, outputs.tf 파일로 구성되어 있습니다. env 폴더안에는 OCI Tenancy 및 Region별로 tfvars 환경파일을 작성했습니다. 이외 terraform.tfstate.d, workspace는 테라폼 workspace를 구성할 경우 생성되는데, terraform.tfstate.d 에는 테라폼 실행 시 각 workspace별로 상태파일(tfstate)이 저장됩니다. run_tf.sh 파일은 workspace를 각 Tenancy 혹은 Region별로 구분한 후 이를 동시에 프로비저닝하기 위해 만든 쉘 스크립트 파일입니다.
먼저 workspace를 생성해보겠습니다. 여기서 workspace 이름은 환경 구성파일 (tfvars)의 이름과 동일하게 구성할 것이며, {tenancy}-{region} 형태로 구성하겠습니다. OCI에 Free Trial 계정을 하나 생성해서 진행해 봅니다. 환경은 다음과 같습니다.
테라폼 설치가 되어 있어야 합니다. 테라폼 설치는 여기를 참고합니다.
이제 다운로드 받은 프로젝트의 oci 폴더에서 workspace를 생성합니다.
$ terraform workspace new myocitenancy-ap-seoul-1
생성한 workspace를 확인합니다. *가 붙은것은 현재 선택된 workspace를 의미합니다. 다른 workspace를 선택할 경우에는 terraform workspace select {workspace} 형태로 변경할 수 있습니다.
$ terraform workspace list
default
* myocitenancy-ap-seoul-1
env 폴더안에 workspace 이름과 동일하게 tfvars 파일을 만들었습니다. 환경 변수로 등록해서 사용할 수 있지만, 여기서는 tfvars를 사용했고, workspace 이름과 동일하게 만든 이유는 쉘 스크립트 안에서 여러개의 workspace별로 동시에 실행할 수 있도록 구성하기 위해서입니다. (이 부분은 run_tf.sh를 설명하는 곳에서 다시 설명)
env 폴더안에 danoci.ap-seoul-1.tfvars 파일의 내용을 보면 다음과 같습니다.
tenancy_ocid = "ocid1.tenancy.oc1......"
compartment_ocid = ""
user_ocid = "ocid1.user.oc1......"
fingerprint = "48:1a:98:8c:cd:f6:63:4b:f...."
private_key_path = "~/.oci/oci_api_key.pem"
region = "ap-seoul-1"
home_region = "ap-seoul-1"
ssh_public_key = "~/.ssh/id_rsa.pub
tenancy_ocid, user_ocid, fingerprint, private_key, ssh_public_key, region등의 정보는 아래 포스트를 참고합니다.
생성할 리소스들을 모듈화 했는데, 각 모듈에서 사용할 공통 구성 파일을 provider.tf, main.tf, vars.tf 3개의 파일로 구성했습니다.
먼저 provider.tf에는 oci provider를 다음과 같이 정의합니다.
provider.tf
provider "oci" {
tenancy_ocid = "${var.tenancy_ocid}"
user_ocid = "${var.user_ocid}"
fingerprint = "${var.fingerprint}"
private_key_path = "${var.private_key_path}"
region = "${var.region}"
}
vars.tf 파일에는 프로바이더와 각 모듈에서 사용하는 변수들을 정의합니다. tfvars에 있는 값들은 vars.tf의 각 변수에 자동으로 매핑됩니다. 그 외에 각 모듈별로 사용할 변수와 값들이 포함되어 있습니다. vars.tf
# Variables Exported from env.sh
# Uses Default Value
variable "tenancy_ocid" {}
variable "user_ocid" {}
variable "oci_user_ocid" {}
variable "fingerprint" {}
variable "private_key_path" {}
variable "compartment_ocid" {}
variable "region" {}
variable "home_region" {}
variable "ssh_public_key" {}
variable "availability_domain" {
default = 1
}
variable "name_prefix" {
default = "dan"
}
variable "freeform_tags" {
type = "map"
default = {
freeform_tags = "Freeform Tags"
}
}
variable "defined_tags" {
type = "map"
default = {
"KRSET02.ET" = "ET_TEAM:donghu.kim@oracle.com"
}
}
# for compute module
variable "compute" {
type = "map"
default = {
num_nodes = 2
instance_shape = "VM.Standard2.1"
}
}
# for vm database module
variable "dbsystem" {
type = "map"
default = {
"database_edition" = "ENTERPRISE_EDITION"
"db_version" = "12.2.0.1"
"admin_password" = "WelCome123##"
"character_set" = "AL32UTF8"
"ncharacter_set" = "AL16UTF16"
"db_workload" = "OLTP"
"licence_model" = "LICENSE_INCLUDED"
"node_count" = 1
"shape" = "VM.Standard2.1"
"source" = "NONE"
"time_zone" = "Asia/Seoul"
}
}
# for container module
variable "container" {
type = "map"
default = {
"kubernetes_version" = "LATEST"
"is_kubernetes_dashboard_enabled" = true
"is_tiller_enabled" = true
"node_image_name" = "Oracle-Linux-7.6"
"node_shape" = "VM.Standard2.1"
"num_nodes" = 1
"kubecfg_expiration" = 10
"kubecfg_token_version" = "2.0.0"
}
}
# for autonomous database module
variable "adb" {
type = "map"
default = {
"cpu_core_count" = 1
"data_storage_size_in_tbs" = 1
"db_workload" = "DW" #DW (ADW), OLTP (ATP)
"is_auto_scaling_enabled" = false
"is_dedicated" = false
"is_preview_version_with_service_terms_accepted" = false
}
}
main.tf에는 각 모듈을 실행하기 위해 해당 모듈이 있는 폴더의 위치를 지정하고, 넘겨줄 변수를 정의합니다.
# Creates a group
module "group" {
source = "./modules/group"
tenancy_ocid = "${var.tenancy_ocid}"
user_ocid = "${var.oci_user_ocid}" // IDCS에서 관리하는 사용자를 추가할 경우 ${var.user_ocid}
name_prefix = "${var.name_prefix}"
freeform_tags = "${var.freeform_tags}"
}
# Creates a compartment
module "compartment" {
source = "./modules/compartment"
tenancy_ocid = "${var.tenancy_ocid}"
name_prefix = "${var.name_prefix}"
freeform_tags = "${var.freeform_tags}"
# group_name = "${module.group.group_name}"
}
# Create a Virtual Cloud Network
module "vcn" {
source = "./modules/vcn"
tenancy_ocid = "${var.tenancy_ocid}"
compartment_ocid = "${module.compartment.compartment_id}"
#compartment_ocid = "${var.compartment_ocid}"
availability_domain = "${var.availability_domain}"
name_prefix = "${var.name_prefix}"
freeform_tags = "${var.freeform_tags}"
}
# Creates Compute Instance
module "compute" {
source = "./modules/compute"
tenancy_ocid = "${var.tenancy_ocid}"
region = "${var.region}"
compartment_ocid = "${module.compartment.compartment_id}"
#compartment_ocid = "${var.compartment_ocid}"
availability_domain = "${var.availability_domain}"
compute = "${var.compute}"
name_prefix = "${var.name_prefix}"
ssh_public_key = "${file(var.ssh_public_key)}"
public_subnet_ocid = "${module.vcn.public_subnet_ocid}"
freeform_tags = "${var.freeform_tags}"
}
# Creates Autonomous Database
module "dbsystem" {
source = "./modules/dbsystem"
tenancy_ocid = "${var.tenancy_ocid}"
availability_domain = "${var.availability_domain}"
compartment_ocid = "${module.compartment.compartment_id}"
#compartment_ocid = "${var.compartment_ocid}"
dbsystem = "${var.dbsystem}"
public_subnet_ocid = "${module.vcn.public_subnet_ocid}"
name_prefix = "${var.name_prefix}"
ssh_public_key = "${file(var.ssh_public_key)}"
freeform_tags = "${var.freeform_tags}"
}
# Creates Autonomous Data Warehouse (ADW), Autonomous Transaction Processing (ATP)
module "adb" {
source = "./modules/adb"
compartment_ocid = "${module.compartment.compartment_id}"
#compartment_ocid = "${var.compartment_ocid}"
adb = "${var.adb}"
freeform_tags = "${var.freeform_tags}"
}
# Create Kubernetes Engine (OKE)
module "container" {
source = "./modules/container"
tenancy_ocid = "${var.tenancy_ocid}"
compartment_ocid = "${module.compartment.compartment_id}"
#compartment_ocid = "${var.compartment_ocid}"
availability_domain = "${var.availability_domain}"
container = "${var.container}"
private_subnet_ocid = "${module.vcn.private_subnet_ocid}"
vcn_id = "${module.vcn.vcn_id}"
name_prefix = "${var.name_prefix}"
}
# Create Funtions (Serverless)
module "functions" {
source = "./modules/functions"
#compartment_ocid = "${var.compartment_ocid}"
compartment_ocid = "${module.compartment.compartment_id}"
public_subnet_ocid = "${module.vcn.public_subnet_ocid}"
name_prefix = "${var.name_prefix}"
freeform_tags = "${var.freeform_tags}"
}
# Create Policy
module "policy" {
source = "./modules/policy"
#compartment_ocid = "${var.compartment_ocid}"
compartment_ocid = "${var.tenancy_ocid}"
statements = "${local.statements}"
name_prefix = "${var.name_prefix}"
# providers = {
# oci = "oci.home"
# }
}
locals {
# statements = [
# "Allow service OKE to manage all-resources in compartment ${module.compartment.compartment_name}", "Allow service FaaS to manage all-resources in compartment ${module.compartment.compartment_name}"
# ]
statements = [
"Allow service OKE to manage all-resources in tenancy", "Allow service FaaS to manage all-resources in compartment ${module.compartment.compartment_name}"
]
}
위에서 module.compartment.compartment_id 이런 내용이 있는데 Compartment 모듈을 실행하고 Compartment의 Outputs.tf에서 나온 결과를 다른 모듈의 매개변수로 사용한 것입니다. 아래는 Compartment Module의 outputs.tf인데, output으로 compartment_id가 반환되는 것을 확인할 수 있습니다.
# Output variables from created compartment
output "compartment_name" {
value = "${oci_identity_compartment.compartment.name}"
}
output "compartment_id" {
value = "${oci_identity_compartment.compartment.id}"
}
main.tf 맨 아래 locals가 보입니다. 변수의 일종이지만, variable과는 다르게 일종의 지역변수와 같은 역할을 합니다. main.tf 안에서만 사용하면서 여기서 사용한 것과 같이 특정 모듈의 output의 값을 할당해서 동적으로 값을 지정할 수 있습니다. 본 예제의 Function의 경우 위에서 생성한 Compartment에서만 관리되도록 Policy 구문을 사용했고, OKE의 경우는 전체 Tenancy 레벨에서 괸리되는 구문을 사용했는데, locals에 해당 구문들을 정의했습니다.
OKE 관련 Policy
Allow service OKE to manage all-resources in tenancy
FaaS(Function) 관련 Policy
Allow service FaaS to manage all-resources in compartment ${module.compartment.compartment_name}
우선 provider.tf 파일이 있는 위치에서 terraform init을 통해 모듈 초기화 및 OCI Provider Plugin을 내려 받습니다.
$ terraform init
그럼 생성한 workspace를 선택하고 테라폼을 실행해보겠습니다. 먼저 workspace를 선택합니다.
$ terraform workspace select myocitenancy-ap-seoul-1
현재 선택된 workspace를 보려면 다음과 같이 show 명령어를 사용합니다.
$ terraform workspace show
myocitenancy-ap-seoul-1
테라폼 적용(apply)전에 plan을 실행해서 수행될 작업에 대한 계획을 확인합니다. 구성 파일에 대한 유효성 검사도 같이 진행하게 됩니다. tfvars 환경 구성파일의 경우 현재 workspace 이름과 같기 때문에 아래와 같이 실행하면 해당 tfvars 파일을 참조하게 됩니다.
$ terraform plan -var-file="env/$(terraform workspace show).tfvars"
plan 결과가 예상한 계획과 일치하면, apply를 통해서 실제 인프라에 반영합니다. 다음과 같이 apply를 실행합니다.
$ terraform apply -var-file="env/$(terraform workspace show).tfvars"
오류없이 완료되면 아래와 같은 메시지를 볼 수 있습니다.
Apply complete! Resources: 30 added, 0 changed, 0 destroyed.
OCI Console에 접속해서 실제 리소스가 생성된 것을 확인할 수 있습니다.
Group
Group
Compartment
Policy
Compute (2nodes, VM-Standard-2.1)
VCN
Function
Container (1node, VM-Standard-E2.1)
Autonomous Database
Autonomous Data Warehouse
등록된 테라폼 workspace들을 순차적으로 실행하기 위한 스크립트를 작성해봤습니다.
보통 workspace를 선택하고 실행하면 해당 workspace에 lock을 걸기 때문에 다른 workspace를 동시에 실행하기는 어렵습니다. 이럴땐 -lock=false 옵션을 주면 됩니다.
사용 방식은 다음과 같습니다.
1개의 workspace만 실행할 경우 (action은 init, plan, apply)
$ ./run_tf.sh {action} {workspace명}
등록된 모든 workspace를 실행할 경우 (action은 init, plan, apply)
$ ./run_tf.sh {action}
백그라운드로 실행하게 해서 병렬로 실행해봤더니, TLS handshake timeout이 발생합니다. 각 OCI Tenancy별로 API 통신을 할 때 핸드쉐이크하는 과정이 있어서 그런거 같은데, wait을 주면 해결될 것 같긴 하지만, 하여튼 병렬로 실행하는 것은 좀 더 확인해봐야 할 거 같습니다.
이 글은 개인적으로 얻은 지식과 경험을 작성한 글로 내용에 오류가 있을 수 있습니다. 또한 글 속의 의견은 개인적인 의견으로 특정 회사를 대변하지 않습니다.
Donghu Kim CLOUDNATIVE
oci terraform resource manager