Terraform Module Nedir? Resuable Altyapı Oluşturmak
Terraform Module; yeniden kullanılabilir, bakımı yapılabilir ve test edilebilir Terraform kodu yazmanın temel bileşenidir. Modül ile yaptığınız ortak işleri tek bir method altında toplayabilir ve kod tekrarından kurtularak daha clean, bakımı kolay ve efektif kod yazmış olursunuz.
Terraform State Nedir? Nasıl Yönetilir? başlıklı yazımızın sonunda, Şekil 1'de gösterilen mimariyi devreye almıştınız.
Bu mimari, tek ortamınız olduğunda harika çalışır, ancak genellikle en az iki ortama ihtiyacınız vardır. Şekil 2'de gösterildiği gibi, biri ekibinizin testleri gerçekleştirdiği "stagging" ve diğeri de gerçek kullanıcıların erişebileceği "production" ortamıdır. İdeal olarak, iki ortam neredeyse aynıdır, ancak paradan tasarruf etmek için hazırlamada biraz daha az/daha küçük sunucu çalıştırabilirsiniz.
Staging ortamındaki tüm kodu terraform kodunuzu kopyala yapıştır yapmak zorunda kalmadan production ortamına nasıl eklersiniz? Örneğin, stage/services/webserver-cluster
içindeki tüm kodu prod/services/webserver-cluster
'a ve stage/data-stores/mysql
içindeki tüm kodu prod/data-stores/mysql
içine kopyalayıp yapıştırmaktan nasıl kaçınırsınız?
Programlama dillerinde, aynı kodu birkaç yere kopyalayıp yapıştırdıysanız, bu kodu bir methodun içine koyabilir ve bu methodu her yerde yeniden kullanabilirsiniz:
# Tek bir yerde methodu tanımlıyoruz
def example_function():
print("Hello, World")
# İstediğiniz her yerde bu methodu çağırabiliyoruz
example_function()
Benzer şekilde, Terraform kodunuzu bir Terraform modülünün içine koyabilir ve bu modülü kodunuz boyunca birden çok yerde yeniden kullanabilirsiniz. Staging ve production ortamlarında aynı kodu kopyalayıp yapıştırmak yerine, Şekil 3'te gösterildiği gibi her iki ortamın da aynı modülden gelen kodu yeniden kullanmasını sağlayabilirsiniz.
Modüller, yeniden kullanılabilir, bakımı yapılabilir ve test edilebilir Terraform kodu yazmanın temel bileşenidir. Onları kullanmaya başladığınızda, bir daha bırakamayacaksınız. Her şeyi bir modül olarak oluşturmaya, şirketinizde paylaşmak için bir modül kütüphanesi oluşturmaya, çevrimiçi bulduğunuz modülleri kullanarak ve tüm altyapınızı yeniden kullanılabilir modüller koleksiyonu olarak düşünmeye başlayacaksınız.
Bu bölümde, aşağıdaki konuları ele alarak Terraform modüllerinin nasıl oluşturulacağını ve kullanılacağını göstereceğiz:
- Modül temelleri
- Modül girdi değişkenleri - parametreler
- Modül yerel değişkenleri - locals
- Modül çıktı değişkenleri - output
- Modül hatalar
- Modül versiyonlama
1. Modül Temelleri
Bir Terraform modülü çok basittir ve bir klasördeki herhangi bir Terraform konfigürasyon dosyası bir modüldür. Şimdiye kadar yazdığınız tüm konfigürasyonlar, onları doğrudan dağıttığınız için özellikle ilginç olmasalar da teknik olarak modül olarak adlandırılırlar. Doğrudan bir modül üzerinde apply
komutunu çalıştırırsanız, buna root modül denir. Modüllerin gerçekten neler yapabildiğini görmek için, diğer modüllerde kullanılması amaçlanan bir modül olan yeniden kullanılabilir bir modül oluşturmanız gerekir.
Örnek olarak, bir Auto Scaling Group (ASG), Application Load Balancer (ALB), güvenlik grupları ve daha birçok kaynağı içeren stage/services/webserver-cluster
içindeki kodu yeniden kullanılabilir bir modüle çevirelim.
İlk adım olarak, daha önce oluşturduğunuz kaynakları temizlemek için stage/services/webserver-cluster
da terraform destroy
çalıştırın. Ardından, modules
adlı yeni bir üst düzey klasör oluşturun ve tüm dosyaları stage/services/webserver-cluster
dan modules/services/webserver-cluster
a taşıyın. Şekil 4'e benzeyen bir klasör yapısı elde etmelisiniz.
main.tf
dosyasını modules/services/webserver-cluster
da açın ve provider tanımını kaldırın. Sağlayıcılar, modülün kendisi tarafından değil, modülün kullanıcısı tarafından yapılandırılmalıdır (sağlayıcıların nasıl ele alınacağı hakkında daha fazla bilgiyi ilerideki yazılarımızda edineceksiniz).
Artık bu modülü staging ortamında kullanabilirsiniz. Modül kullanmanın sözdizimi şu şekildedir:
module "<NAME>" {
source = "<SOURCE>"
[CONFIG ...]
}
NAME
, bu modüle başvurmak için Terraform kodu boyunca kullanabileceğiniz bir tanımlayıcıdır (ör.web-server
),-
SOURCE
modül kodunun bulunabileceği yoldur (ör.modules/services/webserver-cluster
) CONFIG
o modüle özgü bir veya daha fazla argümandan oluşur.
Örneğin, stage/services/webserver-cluster/main.tf
'de yeni bir dosya oluşturabilir ve içindeki webserver-cluster
modülünü aşağıdaki gibi kullanabilirsiniz:
provider "aws" {
region = "us-east-2"
}
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
}
Ardından, aşağıdaki içeriklere sahip yeni bir prod/services/webserver-cluster/main.tf
dosyası oluşturarak aynı modülü production ortamında yeniden kullanabilirsiniz:
provider "aws" {
region = "us-east-2"
}
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
}
Ve işte karşınızda, minimum kopyalama / yapıştırma içeren ve birden çok ortamda aynı kodun yeniden kullanımını sağlayan modül yeteneği. Terraform yapılandırmalarınıza bir modül eklediğinizde veya bir modülün kaynak parametresini değiştirdiğinizde, plan
veya apply
komutlarını çalıştırmadan önce init
komutunu çalıştırmanız gerektiğini unutmayın:
$ terraform init
Initializing modules...
- webserver_cluster in ../../../modules/services/webserver-cluster
Initializing the backend...
Initializing provider plugins...
Terraform has been successfully initialized!
Artık init
komutunun içerdiği tüm hileleri gördünüz: tek bir kullanışlı komutta sağlayıcıları kurar, arka uçlarınızı yapılandırır ve modülleri indirir.
Bu kodda apply
komutunu çalıştırmadan önce, webserver-cluster
modülünde bir sorun olduğunu unutmayın: Tüm adlar sabit kodlanmıştır. Yani güvenlik gruplarının adı, ALB ve diğer kaynakların tümü sabit kodlanmıştır, bu nedenle bu modülü aynı AWS hesabında birden fazla kullanırsanız, ad çakışması hataları alırsınız. modules/services/webserver-cluster
içine kopyaladığınız main.tf
dosyası, veritabanı adresini ve bağlantı noktasını bulmak için bir terraform_remote_state
veri kaynağı kullandığından ve terraform_remote_state
'e bakmak için kodlanmış olduğundan, veritabanı durumunun nasıl okunacağına ilişkin ayrıntılar bile sabit kodlanmıştır.
Bu sorunları gidermek ve farklı ortamlarda farklı davranabilmesi için webserver-cluster
modülünü yapılandırılabilir girdiler eklemeniz gerekir.
2. Modül Girdi Değişkenleri - Parametreler
Python gibi genel amaçlı bir programlama dilinde bir methodu yapılandırılabilir hale getirmek için bu methoda parametreler ekleyebilirsiniz:
# İki parametre alan bir method
def example_function(param1, param2)
print("Hello", param1, param2")
# Methoda iki parametre değeri geçiyoruz
example_function("foo", "bar")
Terraform'da modüller de benzer şekilde parametrelere sahip olabilir. Bu bölümde, daha önceki "Terraform İle AWS Kullanımı ve Load Balancer Deploy" başlıklı yazımızda yaptığımız örneği modüle çevireceğiz.
Örneğimize başlamak için modules/services/webserver-cluster/variables.tf
dosyasını açın ve üç yeni değişkeni ekleyin:
variable "cluster_name" {
description = "The name to use for all the cluster resources"
type = string
}
variable "db_remote_state_bucket" {
description = "The name of the S3 bucket for the database's remote state"
type = string
}
variable "db_remote_state_key" {
description = "The path for the database's remote state in S3"
type = string
}
Ardından, modules/services/webserver-cluster/main.tf
'e gidin ve sabit kodlanmış adlar yerine var.cluster_name
kullanın (örneğin, "terraform-asg-example" yerine). Örneğin, ALB güvenlik grubu için bunu nasıl yapacağınız aşağıda gösterilmiştir:
resource "aws_security_group" "alb" {
name = "${var.cluster_name}-alb"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
name
parametresinin nasıl "${var.cluster_name}-alb"
olarak ayarlandığına dikkat edin. Diğer aws_security_group
kaynağında (ör. ona "${var.cluster_name}-instance"
adını verin), aws_alb
kaynağında ve aws_autoscaling_group
kaynağının etiket bölümünde benzer bir değişiklik yapmanız gerekir.
State dosyasını doğru ortamdan okuduğunuzdan emin olmak için, sırasıyla db_remote_state_bucket
ve db_remote_state_key
'i bucket ve key parametresi olarak kullanmak için terraform_remote_state
veri kaynağını da güncellemelisiniz:
data "terraform_remote_state" "db" {
backend = "s3"
config = {
bucket = var.db_remote_state_bucket
key = var.db_remote_state_key
region = "us-east-2"
}
}
Şimdi, staging ortamında, stage/services/webserver-cluster/main.tf
'de, bu yeni değişkenlerini buna göre ayarlayabilirsiniz:
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
cluster_name = "webservers-stage"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "stage/data-stores/mysql/terraform.tfstate"
}
Bu değişkenleri ayrıca prod/services/webserver-cluster/main.tf
içindeki production ortamında, ancak o ortama karşılık gelen farklı değerlere ayarlamalısınız:
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
cluster_name = "webservers-prod"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "prod/data-stores/mysql/terraform.tfstate"
}
Gördüğünüz gibi, bir kaynak için argümanları ayarlamakla aynı sözdizimini kullanarak bir modül için değişkenlerini ayarlarsınız. Değişkenler, modülün farklı ortamlarda nasıl davranacağını kontrol eden API'dir.
Şimdiye kadar name
ve database remote state
için girdi değişkenleri eklediniz, ancak diğer parametreleri de modülünüzde yapılandırılabilir hale getirmek isteyebilirsiniz. Örneğin, staging ortamında paradan tasarruf etmek için küçük bir web sunucusu kümesi çalıştırmak isteyebilirsiniz, ancak üretimde çok fazla trafiği yönetmek için daha büyük bir küme çalıştırmak isteyebilirsiniz. Bunu yapmak için module/services/webserver-cluster/variables.tf
dosyasına üç girdi değişkeni daha ekleyebilirsiniz:
variable "instance_type" {
description = "The type of EC2 Instances to run (e.g. t2.micro)"
type = string
}
variable "min_size" {
description = "The minimum number of EC2 Instances in the ASG"
type = number
}
variable "max_size" {
description = "The maximum number of EC2 Instances in the ASG"
type = number
}
Ardından, instance_type
parametresini yeni var.instance_type
giriş değişkenine ayarlamak için module/services/webserver-cluster/main.tf
içindeki başlatma yapılandırmasını güncelleyin:
resource "aws_launch_configuration" "example" {
image_id = "ami-0fb653ca2d3203ac1"
instance_type = var.instance_type
security_groups = [aws_security_group.instance.id]
user_data = templatefile("user-data.sh", {
server_port = var.server_port
db_address = data.terraform_remote_state.db.outputs.address
db_port = data.terraform_remote_state.db.outputs.port
})
# Required when using a launch configuration with an auto scaling group.
lifecycle {
create_before_destroy = true
}
}
Benzer şekilde, min_size
ve max_size
parametrelerini sırasıyla yeni var.min_size
ve var.max_size
girdi değişkenlerine ayarlamak için aynı dosyadaki ASG tanımını güncellemelisiniz:
resource "aws_autoscaling_group" "example" {
launch_configuration = aws_launch_configuration.example.name
vpc_zone_identifier = data.aws_subnets.default.ids
target_group_arns = [aws_lb_target_group.asg.arn]
health_check_type = "ELB"
min_size = var.min_size
max_size = var.max_size
tag {
key = "Name"
value = var.cluster_name
propagate_at_launch = true
}
}
Şimdi, staging ortamında (stage/services/webserver-cluster/main.tf
), instance_type
öğesini "t2.micro" ve min_size
ve max_size
öğesini 2 olarak ayarlayarak kümeyi küçük ve ucuz tutabilirsiniz:
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
cluster_name = "webservers-stage"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "stage/data-stores/mysql/terraform.tfstate"
instance_type = "t2.micro"
min_size = 2
max_size = 2
}
Öte yandan, prodcution ortamında, m4.large gibi daha fazla CPU ve belleğe sahip daha büyük bir instance_type
kullanabilirsiniz (bu Instance türünün AWS ücretsiz katmanının bir parçası olmadığını unutmayın. Bu nedenle yalnızca öğrenmek için kullanıyor ve ücretlendirilmek istemiyorsanız, instance_type
için "t2.micro" ya bağlı kalın) ve kümenin yüke bağlı olarak küçülmesine veya büyümesine izin vermek için max_size
değerini 10 olarak ayarlayabilirsiniz (endişelenmeyin, küme başlangıçta iki Instance ile başlatılır):
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
cluster_name = "webservers-prod"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "prod/data-stores/mysql/terraform.tfstate"
instance_type = "m4.large"
min_size = 2
max_size = 10
}
3. Modül Yerel Değişkenler - Locals
Modülünüzün girdilerini tanımlamak için giriş değişkenlerini kullanmak harikadır, ancak bazı ara hesaplamalar yapmak veya yalnızca kodunuzu DRY tutmak için modülünüzde bir değişken tanımlamanın bir yoluna ihtiyacınız varsa, ancak bu değişkeni yapılandırılabilir bir giriş olarak göstermek istemiyorsanız ne olur? Örneğin, module/services/webserver-cluster/main.tf
'deki webserver-cluster
modülündeki yük dengeleyici, HTTP için varsayılan bağlantı noktası olan 80 numaralı bağlantı noktasını dinler. Bu bağlantı noktası numarası şu anda yük dengeleyici dinleyicisi de dahil olmak üzere birden çok yere kopyalanıp yapıştırılmıştır:
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.example.arn
port = 80
protocol = "HTTP"
# By default, return a simple 404 page
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: page not found"
status_code = 404
}
}
}
Ve yük dengeleyici güvenlik grubu:
resource "aws_security_group" "alb" {
name = "${var.cluster_name}-alb"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
"Tüm IP'ler" CIDR bloğu 0.0.0.0/0
, "any port" değeri 0
ve "any protokol" değeri "-1"
de dahil olmak üzere güvenlik grubundaki değerler modül boyunca çeşitli yerlere kopyalanır ve yapıştırılır. Bu sihirli değerlerin birden çok yerde sabit kodlanmış olması, kodun okunmasını ve korunmasını zorlaştırır. Değerleri giriş değişkenler olarak ayarlayabilirsiniz, ancak daha sonra modülünüzün kullanıcıları (yanlışlıkla) bu değerleri geçersiz kılabilir ve bu durumu istemeyebilirsiniz. Giriş değişkenlerini kullanmak yerine, bunları bir locals
bloğunda yerel değişkenler olarak tanımlayabilirsiniz:
locals {
http_port = 80
any_port = 0
any_protocol = "-1"
tcp_protocol = "tcp"
all_ips = ["0.0.0.0/0"]
}
Yerel değerler, herhangi bir Terraform ifadesine bir ad atamanıza ve bu adı modül boyunca kullanmanıza olanak tanır. Bu adlar yalnızca modül içinde görünür, bu nedenle diğer modüller üzerinde hiçbir etkileri olmaz ve bu değerleri modülün dışından geçersiz kılamazsınız. Bir yerelin değerini okumak için aşağıdaki sözdizimini kullanan bir yerel başvuru kullanmanız gerekir:
local.<NAME>
Load balancer listener bağlantı noktası parametresini güncellemek için artık bu söz dizimini kullanabilirsiniz:
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.example.arn
port = local.http_port
protocol = "HTTP"
# By default, return a simple 404 page
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: page not found"
status_code = 404
}
}
}
Yük dengeleyici güvenlik grubu da dahil olmak üzere modüldeki güvenlik gruplarındaki hemen hemen tüm parametreleri güncelleyebilirsiniz:
resource "aws_security_group" "alb" {
name = "${var.cluster_name}-alb"
ingress {
from_port = local.http_port
to_port = local.http_port
protocol = local.tcp_protocol
cidr_blocks = local.all_ips
}
egress {
from_port = local.any_port
to_port = local.any_port
protocol = local.any_protocol
cidr_blocks = local.all_ips
}
}
Locals, kodunuzun okunmasını ve bakımını kolaylaştırır, bu nedenle bunları sık sık kullanın.
4. Modül Çıktı Değişkenleri - Output
ASG'lerin güçlü bir özelliği, onları yüke yanıt olarak çalıştırdığınız sunucuların sayısını artıracak veya azaltacak şekilde yapılandırabilmenizdir. Bunu yapmanın bir yolu, gün içinde planlanan bir zamanda kümenin boyutunu değiştirebilen zamanlanmış bir eylem kullanmaktır. Örneğin, normal iş saatlerinde kümenize gelen trafik çok daha yüksekse, sunucu sayısını sabah 9'da artırmak ve akşam 5'te azaltmak için zamanlanmış bir eylem kullanabilirsiniz.
webserver-cluster
modülünde zamanlanmış eylemi tanımlarsanız, hem staging hem de prdocution için geçerli olacaktır. Staging ortamınızda bu tür bir ölçekleme yapmanız gerekmediğinden, şimdilik, otomatik ölçeklendirme çizelgesini doğrudan production konfigürasyonlarında tanımlayabilirsiniz.
Zamanlanmış bir eylem tanımlamak için aşağıdaki iki aws_autoscaling_schedule
kaynağını prod/services/webserver-cluster/main.tf
dosyasına ekleyin:
resource "aws_autoscaling_schedule" "scale_out_during_business_hours" {
scheduled_action_name = "scale-out-during-business-hours"
min_size = 2
max_size = 10
desired_capacity = 10
recurrence = "0 9 * * *"
}
resource "aws_autoscaling_schedule" "scale_in_at_night" {
scheduled_action_name = "scale-in-at-night"
min_size = 2
max_size = 10
desired_capacity = 2
recurrence = "0 17 * * *"
}
Bu kod, sabah saatlerinde sunucu sayısını 10'a çıkarmak için bir aws_autoscaling_schedule
kaynağı (recurrence
parametresi cron sözdizimini kullanır, dolayısıyla "0 9 * * *"
, "her gün sabah 9" anlamına gelir) ve gece sunucu sayısını azaltmak için ikinci bir aws_autoscaling_schedule
kaynağı ("0 17 * * *"
, "her gün 17:00" anlamına gelir) kullanır. Ancak, her iki aws_autoscaling_schedule
kullanımında, ASG'nin adını belirten autoscaling_group_name
adlı gerekli bir parametre eksik. ASG'nin kendisi webserver-cluster
modülü içinde tanımlanır, peki onun adına nasıl erişirsiniz? Python gibi genel amaçlı bir programlama dilinde fonksiyonlar değerler döndürebilir:
# A function that returns a value
def example_function(param1, param2):
return "Hello," param1, param2
# Call the function and get the return value
return_value = example_function("foo", "bar")
Terraform'da bir modül de değerler döndürebilir. Yine, bunu zaten bildiğiniz bir mekanizma kullanarak yaparsınız: çıktı değişkenleri. ASG adını /modules/services/webserver-cluster/outputs.tf
dosyasına aşağıdaki gibi bir çıktı değişkeni olarak ekleyebilirsiniz:
output "asg_name" {
value = aws_autoscaling_group.example.name
description = "The name of the Auto Scaling Group"
}
Modül çıktı değişkenlerine aşağıdaki sözdizimini kullanarak erişebilirsiniz:
module.<MODULE_NAME>.<OUTPUT_NAME>
Örneğin:
module.frontend.asg_name
prod/services/webserver-cluster/main.tf
dosyasında, aws_autoscaling_schedule
kaynaklarının her birinde autoscaling_group_name
parametresini ayarlamak için bu söz dizimini kullanabilirsiniz:
resource "aws_autoscaling_schedule" "scale_out_during_business_hours" {
scheduled_action_name = "scale-out-during-business-hours"
min_size = 2
max_size = 10
desired_capacity = 10
recurrence = "0 9 * * *"
autoscaling_group_name = module.webserver_cluster.asg_name
}
resource "aws_autoscaling_schedule" "scale_in_at_night" {
scheduled_action_name = "scale-in-at-night"
min_size = 2
max_size = 10
desired_capacity = 2
recurrence = "0 17 * * *"
autoscaling_group_name = module.webserver_cluster.asg_name
}
webserver-cluster
modülünde başka bir çıktı olan ALB'nin DNS adını çıktı olarak eklemek isteyebilirsiniz. Böylece küme dağıtıldığında hangi URL'nin test edileceğini bilirsiniz. Bunu yapmak için /modules/services/webserver-cluster/outputs.tf
dosyasına tekrar bir çıktı değişkeni eklersiniz:
output "alb_dns_name" {
value = aws_lb.example.dns_name
description = "The domain name of the load balancer"
}
Daha sonra bu çıktıyı stage/services/webserver-cluster/outputs.tf
ve prod/services/webserver-cluster/outputs.tf
dosyasında aşağıdaki gibi geçebilirsiniz:
output "alb_dns_name" {
value = module.webserver_cluster.alb_dns_name
description = "The domain name of the load balancer"
}
Web sunucusu kümeniz dağıtıma neredeyse hazır. Geriye kalan tek şey, birkaç yanlışı hesaba katmak.
5. Modül Hatalar
Modülleri oluştururken şu hatalara dikkat etmelisiniz:
- Dosya yolları
- Satıriçi Bloklar
5.1. Dosya Yolları
Terraform State Nedir? Nasıl Yönetilir? başlıklı yazımızda, User Data komut dosyasını user-data.sh
adlı harici bir dosyaya taşıdınız ve bu dosyayı diskten okumak için templatefile
işlevini kullandınız. templatefile
işleviyle ilgili önemli olan, kullandığınız dosya yolunun relative bir yol olması gerektiğidir (Terraform kodunuz her biri farklı bir disk düzenine sahip birçok farklı bilgisayarda çalışabileceğinden absolute dosya yollarını kullanmak istemezsiniz)—ama ne relative path nedir?
Varsayılan olarak, Terraform yolu mevcut çalışma dizinine göre yorumlar. Bu, terraform apply
'ı çalıştırdığınız dizinle aynı dizinde bulunan bir Terraform yapılandırma dosyasında templatefile
işlevini kullanıyorsanız (yani, kök modülde templatefile
işlevini kullanıyorsanız) işe yarar, ancak ayrı bir klasörde (yeniden kullanılabilir bir modül) tanımlanan bir modülde templatefile
kullanırken çalışmaz.
Bu sorunu çözmek için, path referansı olarak bilinen ve path.<TYPE>
biçimindeki bir ifadeyi kullanabilirsiniz. Terraform, aşağıdaki yol referansı türlerini destekler:
path.module
: İfadenin tanımlandığı modülün dosya sistemi yolunu döndürür.path.root
: Kök modülün dosya sistemi yolunu döndürür.path.cwd
: Geçerli çalışma dizininin dosya sistemi yolunu döndürür. Terraform'un normal kullanımında bu,path.root
ile aynıdır, ancak Terraform'un bazı gelişmiş kullanımları, onu kök modül dizini dışında bir dizinden çalıştırarak bu yolların farklı olmasına neden olur.
User Data script dosyası için, modülün kendisine göre bir yola ihtiyacınız vardır, bu nedenle modules/services/webserver-cluster/main.tf
'de templatefile
işlevini çağırırken path.module
kullanmalısınız:
user_data = templatefile("${path.module}/user-data.sh", {
server_port = var.server_port
db_address = data.terraform_remote_state.db.outputs.address
db_port = data.terraform_remote_state.db.outputs.port
})
5.2. Satıriçi Bloklar
Bazı Terraform kaynakları için yapılandırma, satır içi bloklar veya ayrı kaynaklar olarak tanımlanabilir. Satır içi blok, şu biçimdeki bir kaynak içinde ayarladığınız bir argümandır:
resource "xxx" "yyy" {
<NAME> {
[CONFIG...]
}
}
burada NAME
, satır içi bloğun adıdır (örneğin, ingress
) ve CONFIG
, o satır içi bloğa özgü bir veya daha fazla bağımsız değişkenden oluşur (örneğin, from_port
ve to_port
). Örneğin, aws_security_group_resource
ile, satır içi blokları (ör. ingress { … }
) veya ayrı aws_security_group_rule
kaynaklarını kullanarak giriş ve çıkış kurallarını tanımlayabilirsiniz.
Terraform'un nasıl tasarlandığından dolayı hem satır içi blokların hem de ayrı kaynakların bir karışımını kullanmaya çalışırsanız, yapılandırmaların çakıştığı ve birbirinin üzerine yazdığı yerlerde hatalar alırsınız. Bu nedenle, birini veya diğerini kullanmalısınız. Tavsiyemi sorarsanız: Modül oluştururken her zaman ayrı kaynaklar kullanmayı tercih etmelisiniz.
Ayrı kaynaklar kullanmanın avantajı, bunların herhangi bir yere eklenebilmesidir, oysa satır içi blok yalnızca kaynak oluşturan modül içinde eklenebilir. Bu nedenle, yalnızca ayrı kaynakları kullanmak, modülünüzü daha esnek ve yapılandırılabilir hale getirir.
Örneğin, webserver-cluster
modülünde (modules/services/webserver-cluster/main.tf
), ingress
ve egress
kurallarını tanımlamak için satır içi blokları kullandık:
resource "aws_security_group" "alb" {
name = "${var.cluster_name}-alb"
ingress {
from_port = local.http_port
to_port = local.http_port
protocol = local.tcp_protocol
cidr_blocks = local.all_ips
}
egress {
from_port = local.any_port
to_port = local.any_port
protocol = local.any_protocol
cidr_blocks = local.all_ips
}
}
Bu satır içi bloklarla, bu modülün bir kullanıcısının modülün dışından ek ingress
ve egress
kuralları ekleme yolu yoktur. Modülünüzü daha esnek hale getirmek için, ayrı aws_security_group_rule
kaynakları kullanarak tam olarak aynı ingress
ve egress
kurallarını tanımlayacak şekilde değiştirmelisiniz (bunu modüldeki her iki güvenlik grubu için yaptığınızdan emin olun):
resource "aws_security_group" "alb" {
name = "${var.cluster_name}-alb"
}
resource "aws_security_group_rule" "allow_http_inbound" {
type = "ingress"
security_group_id = aws_security_group.alb.id
from_port = local.http_port
to_port = local.http_port
protocol = local.tcp_protocol
cidr_blocks = local.all_ips
}
resource "aws_security_group_rule" "allow_all_outbound" {
type = "egress"
security_group_id = aws_security_group.alb.id
from_port = local.any_port
to_port = local.any_port
protocol = local.any_protocol
cidr_blocks = local.all_ips
}
Ayrıca aws_security_group
kimliğini modules/services/webserver-cluster/outputs.tf
'de bir çıktı değişkeni olarak dışa aktarmalısınız:
output "alb_security_group_id" {
value = aws_security_group.alb.id
description = "The ID of the Security Group attached to the load balancer"
}
Şimdi, yalnızca staging ortamında (örneğin, test için) fazladan bir bağlantı noktası göstermeniz gerekiyorsa, bunu stage/services/webserver-cluster/main.tf
'ye bir aws_security_group_rule
kaynağı ekleyerek yapabilirsiniz:
module "webserver_cluster" {
source = "../../../modules/services/webserver-cluster"
# (parameters hidden for clarity)
}
resource "aws_security_group_rule" "allow_testing_inbound" {
type = "ingress"
security_group_id = module.webserver_cluster.alb_security_group_id
from_port = 12345
to_port = 12345
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
Satır içi blok olarak tek bir ingress
veya egress
kuralı tanımlamış olsaydınız, bu kod çalışmazdı. Bu aynı tür sorunun aşağıdakiler gibi bir dizi Terraform kaynağını etkilediğini unutmayın:
aws_security_group
veaws_security_group_rule
aws_route_table
veaws_route
aws_network_acl
veaws_network_acl_rule
Bu noktada, web sunucusu kümenizi hem staging hem de production ortamında dağıtmaya nihayet hazırsınız. Terraform uygulamasını her zamanki gibi çalıştırın ve altyapınızın iki ayrı kopyasını kullanmanın keyfini çıkarın.
6. Modül Versiyonlama
Hem staging hem de production ortamınız aynı modül klasörünü işaret ediyorsa, o klasörde değişiklik yaptığınız anda bu, bir sonraki dağıtımda her iki ortamı da etkiler. Bu tür bir bağlantı, production'ı etkileme şansı olmadan bir değişikliği test etmeyi daha da zorlaştırır. Daha iyi bir yaklaşım olarak, Şekil 5'te gösterildiği gibi, staging'de bir sürümü (örn., v0.0.2) ve production'da farklı bir sürümü (örn. v0.0.1) kullanabilmeniz için sürümlü modüller oluşturmaktır.
Şimdiye kadar gördüğünüz tüm modül örneklerinde, ne zaman bir modül kullansanız, modülün kaynak parametresini yerel bir dosya yoluna ayarlarsınız. Dosya yollarına ek olarak, Terraform Git URL'leri, Mercurial URL'leri ve isteğe bağlı HTTP URL'leri gibi diğer modül kaynakları türlerini destekler. Sürümlü bir modül oluşturmanın en kolay yolu, modülün kodunu ayrı bir Git deposuna koymak ve kaynak parametreyi bu reponun URL'sine ayarlamaktır. Bu, Terraform kodunuzun (en az) iki repoya yayılacağı anlamına gelir:
- modules: Bu repo, yeniden kullanılabilir modülleri tanımlar. Her modülü, altyapınızın belirli bir bölümünü tanımlayan bir "blueprint" olarak düşünün.
- live: Bu repo, her ortamda (stage, prod, mgmt, vb.) çalıştırdığınız canlı altyapıyı tanımlar. Bunu, modül deposundaki “blueprints” lerden inşa ettiğiniz “evler” olarak düşünün.
Terraform kodunuz için güncellenmiş klasör yapısı şimdi Şekil 6'ya benziyor.
Bu klasör yapısını kurmak için önce stage, prod ve global klasörlerini live adlı bir klasöre taşımanız gerekir. Ardından, live ve modules klasörlerini ayrı Git repoları olarak yapılandırın. Modüller klasörü için bunun nasıl yapılacağına dair bir örnek:
$ cd modules
$ git init
$ git add .
$ git commit -m "Initial commit of modules repo"
$ git remote add origin "(URL OF REMOTE GIT REPOSITORY)"
$ git push origin main
Sürüm numarası olarak kullanmak için modül deposuna bir etiket de ekleyebilirsiniz. GitHub kullanıyorsanız, başlık altında bir etiket oluşturacak bir sürüm oluşturmak için GitHub kullanıcı arayüzünü kullanabilirsiniz. GitHub kullanmıyorsanız Git CLI'yi kullanabilirsiniz:
$ git tag -a "v0.0.1" -m "First release of webserver-cluster module"
$ git push --follow-tags
Artık bu sürümlü modülü, kaynak parametrede bir Git URL'si belirterek hem staging hem de production ortamda kullanabilirsiniz. Modül reponuz GitHub deposunda olsaydı, live/stage/services/webserver-cluster/main.tf
dosyasında bu şöyle görünürdü (aşağıdaki Git URL'sindeki çift slash gereklidir):
module "webserver_cluster" {
source = "github.com/foo/modules//webserver-cluster?ref=v0.0.1"
cluster_name = "webservers-stage"
db_remote_state_bucket = "(YOUR_BUCKET_NAME)"
db_remote_state_key = "stage/data-stores/mysql/terraform.tfstate"
instance_type = "t2.micro"
min_size = 2
max_size = 2
}
ref
parametresi, sha1 hash, branch adı veya bu örnekte olduğu gibi belirli bir Git etiketi aracılığıyla belirli bir Git commiti belirtmenize olanak tanır. Modüllerin sürüm numaraları olarak genellikle Git etiketlerini kullanmanızı öneririm. Branch adları sabit değildir, çünkü bir branch'te her zaman en son commiti alırsınız. Bu init
komutunu her çalıştırdığınızda değişebilir ve sha1 hashleri ise pek insan dostu değildir. Git etiketleri bir commit kadar kararlıdır (aslında, bir etiket yalnızca bir commit için bir işaretçidir), ancak kolay, okunabilir bir ad kullanmanıza izin verir.
Etiketler için özellikle yararlı bir adlandırma şeması semantik sürüm oluşturmadır. Bu, sürüm numarasının her bir bölümünü ne zaman artırmanız gerektiğine ilişkin belirli kurallarla birlikte MAJOR.MINOR.PATCH
(örn. 1.0.4) biçiminin bir sürüm oluşturma şemasıdır. Özellikle, şunları artırmalısınız:
MAJOR
sürümü, uyumsuz API değişiklikleri yaptığınızda,MINOR
sürüm, geriye dönük uyumlu bir şekilde işlevsellik eklediğinizde,PATCH
sürümü, geriye dönük uyumlu hata düzeltmeleri yaptığınızda
Semantik sürüm oluşturma, modülünüzün kullanıcılarına ne tür değişiklikler yaptığınızı ve yükseltmenin sonuçlarını bildirmeniz için bir yol sağlar.
Terraform kodunuzu sürümlü bir modül URL'si kullanacak şekilde güncellediğiniz için, Terraform'a terraform init
'i yeniden çalıştırarak modül kodunu indirmesi için talimat vermeniz gerekir:
$ terraform init
Initializing modules...
Downloading [email protected]:brikis98/terraform-up-and-running-code.git?ref=v0.2.0
for webserver_cluster...
(...)
Bu sefer Terraform'un modül kodunu yerel dosya sisteminizden ziyade Git'ten indirdiğini görebilirsiniz. Modül kodu indirildikten sonra, apply
komutunu her zamanki gibi çalıştırabilirsiniz.
7. ÖZET
Modüllerde altyapıyı kod olarak tanımlayarak, çeşitli yazılım mühendisliği best practiceleri altyapınıza uygulayabilirsiniz. Kod reviewler ve otomatik testler yoluyla bir modülde yapılan her değişikliği doğrulayabilirsiniz. Her modülün anlamsal olarak sürümlendirilmiş sürümlerini oluşturabilirsiniz ve bir modülün farklı sürümlerini farklı ortamlarda güvenle deneyebilir ve bir sorunla karşılaşırsanız önceki sürümlere geri dönebilirsiniz.
Tüm bunlar, altyapıyı hızlı ve güvenilir bir şekilde oluşturma yeteneğinizi önemli ölçüde artırabilir, çünkü geliştiriciler kanıtlanmış, test edilmiş ve belgelenmiş altyapının tüm parçalarını yeniden kullanabilecektir. Örneğin, bir kümenin nasıl çalıştırılacağı, kümenin yüke yanıt olarak nasıl ölçeklendirileceği ve trafik isteklerinin küme genelinde nasıl dağıtılacağı dahil olmak üzere tek bir mikro hizmetin nasıl dağıtılacağını tanımlayan kurallı bir modül oluşturabilirsiniz ve her ekip bunu kullanabilir. Bu modül, kendi mikro hizmetlerini yalnızca birkaç satır kodla yönetir.
Böyle bir modülün birden fazla ekip için çalışmasını sağlamak için, o modüldeki Terraform kodunun esnek ve yapılandırılabilir olması gerekir. Örneğin, bir ekip, yük dengeleyici olmadan mikro hizmetlerinin tek bir Instance dağıtmak için modülünüzü kullanmak isteyebilirken, bir başka ekip, trafiği bu Instance'lar arasında dağıtmak için bir yük dengeleyici ile mikro hizmetlerinin bir düzine Instance'ını isteyebilir.