Var Olan Altyapıyı Terraform'a Almak: import Block, terraform query ve list, moved ve removed Block'ları
Elle kurulmuş bir kaynağı Terraform'a almanın yolu üzerine apply etmek değil, import block'uyla hangi kaynağın hangi adrese ait olduğunu söylemek ve ilk plan'daki her replace satırını tek tek karara bağlamaktır. Elle kurulmuş bir staging'i for_each'li import block'larıyla alıyor, -generate-config-out ve terraform query'nin ne verdiğini görüyor, moved block'larının ne zaman silineceğini, count'tan for_each'e geçişi ve removed ile kaynağı silmeden başka bir state'e devretmeyi deniyoruz.
Sürüm notu: Bu yazıdaki örnekler Terraform 1.17.2, kreuzwerker/docker provider 4.7.0, hashicorp/http 3.6.3, pg backend için PostgreSQL 18 ve Docker Engine 29 ile denenmiştir. AzureRM 5.14.0 kodu için init ve validate çalıştırıldı; terraform query Azure hesabı olmadan kimlik doğrulamada durdu, Azure'a plan/apply yapılmadı.
Önce Tahmin Et
7. yazıda Bookstore'un (sitedeki serilerin ortak örnek uygulaması, bir online kitapçı) staging ortamı için envs/staging klasörünü yazmış ama apply etmemiştik. Bu arada Emre, test ekibi beklemesin diye staging'i elle kurdu: network, iki volume ve API container'ı, hepsi Terraform'un üreteceği adlarla. Şimdi sen envs/staging'de terraform apply çalıştırıyorsun. Terraform bu kaynakların var olduğunu bilmiyor. Ne olur: her şey hata mı verir, yoksa bir kısmı sessizce "oluşturuldu" mu der?
Elle Kurulmuş Bir Staging
Azure'da bu iş portal'da tıklayarak yapılır; yerel örnekte karşılığı Docker CLI. Emre'nin çalıştırdığı komutlar:
$ docker network create tfseries-staging
$ docker volume create tfseries-staging-covers
$ docker volume create tfseries-staging-exports
$ docker run -d --name tfseries-staging-api --network tfseries-staging \
-p 18481:8080 --restart unless-stopped \
-v tfseries-staging-covers:/data/covers -v tfseries-staging-exports:/data/exports \
-e Bookstore__Environment=staging -e Bookstore__Payments__ApiKey="$PAYMENTS_API_KEY" \
bookstore-api:1.1.0
$ curl -s localhost:18481/health
{"status":"Healthy","environment":"staging","version":"1.1.0"}
Container yalnız tfseries-staging network'üne bağlı; Terraform'un kodu bir de paylaşılan tfseries-shared network'ünü ekliyor. Bu farkı birazdan plan'da göreceğiz. Önce tahmin sorusunu cevaplayalım; apply'ı ayrı bir state ile, bir denemede çalıştırdım:
$ terraform apply
...
module.bookstore.module.api.docker_volume.data["exports"]: Creating...
module.bookstore.docker_network.bookstore: Creating...
module.bookstore.module.api.docker_volume.data["covers"]: Creating...
module.bookstore.module.api.docker_volume.data["covers"]: Creation complete after 0s [id=tfseries-staging-covers]
module.bookstore.module.api.docker_volume.data["exports"]: Creation complete after 0s [id=tfseries-staging-exports]
Error: Unable to create network: Error response from daemon: network with name tfseries-staging already exists
Sonuç karışık. Network için Docker "bu ad zaten var" diyor ve apply duruyor; container'a hiç sıra gelmiyor. Volume'ler ise "oluşturuldu". Gerçekte hiçbir şey oluşmadı: docker volume create var olan bir adla çağrılınca hata vermeden mevcut volume'ü döndürüyor ve provider bunu yeni kaynak sanıyor. Terraform artık Emre'nin volume'lerini kendi oluşturduğunu düşünüyor ve state'e label'larıyla birlikte yazıyor. Bir sonraki plan -refresh-only label'ların aslında olmadığını görüyor:
$ terraform plan -refresh-only
...
Note: Objects have changed outside of Terraform
...
- labels {
Docker'da volume label'ı değiştirmek replace demek; bir sonraki normal plan bu volume'leri silip yeniden yaratmak isteyecekti. Var olan altyapıyı koda almanın yolu "üzerine apply etmek" değil, Terraform'a hangi kaynağın hangi adrese ait olduğunu açıkça söylemek: import block'u. Denemenin state'ini sildim; Emre'nin kaynakları yerinde.
import Block'ları
4. yazıda tek bir kaynak için terraform import komutunu görmüştük. Komut state'i hemen değiştirir ve review'dan geçmez. Terraform 1.5'ten beri aynı işi kodda yapan import block'u var: plan'da görünür, pull request'te review edilir, apply ile uygulanır. Block'un iki parçası var: to kaynağın koddaki adresi, id de provider'ın o kaynağı gerçek dünyada bulmak için kullandığı kimlik. Docker provider'ı network ve container için Docker'ın ID'sini, volume için adını istiyor:
$ docker network inspect -f '{{.Id}}' tfseries-staging
26b2d9567429a440a7330adba171c447bc0d3cfc1a1891fe37fab513f63e8455
$ docker inspect -f '{{.Id}}' tfseries-staging-api
b356423113bbc5023fe6bb4ab5668be64e5b4dd37486cfe2685ed7fb7ff7d5fd
# Staging Docker CLI ile elle kuruldu; bu block'lar onu sahipleniyor
import {
to = module.bookstore.docker_network.bookstore
id = "26b2d9567429a440a7330adba171c447bc0d3cfc1a1891fe37fab513f63e8455"
}
import {
for_each = toset(["covers", "exports"])
to = module.bookstore.module.api.docker_volume.data[each.key]
id = "tfseries-staging-${each.key}"
}
import {
to = module.bookstore.module.api.docker_container.this
id = "b356423113bbc5023fe6bb4ab5668be64e5b4dd37486cfe2685ed7fb7ff7d5fd"
}
İki ayrıntı var. to bir module'ün içindeki adresi gösterebiliyor; kaynakları bookstore module'ünden çıkarmamıza gerek yok. Volume'ler için tek block yetti: Terraform 1.7'den beri import block'u for_each alabiliyor ve to'daki key'i each.key ile kurabiliyorsun.
İlk Plan'ı Okumak
Import'un en önemli adımı ilk plan. Terraform kaynağı state'e alıyor, sonra kodla karşılaştırıp farkları kapatmak için ne yapacağını söylüyor:
$ terraform plan
...
# module.bookstore.docker_network.bookstore must be replaced
# (imported from "26b2d9567429a440a7330adba171c447bc0d3cfc1a1891fe37fab513f63e8455")
# Warning: this will destroy the imported resource
...
+ labels { # forces replacement
...
# module.bookstore.module.api.docker_volume.data["covers"] must be replaced
# (imported from "tfseries-staging-covers")
# Warning: this will destroy the imported resource
...
+ labels { # forces replacement
...
Plan: 3 to import, 4 to add, 0 to change, 3 to destroy.
...
Error: Instance cannot be destroyed
"Warning: this will destroy the imported resource" satırı ciddiye alınacak bir uyarı: import edilen kaynak, apply'ın ilk dakikasında silinecek. Sebebi label'lar: modülümüz network ve volume'lere app, environment, managed_by label'larını koyuyor, elle kurulanlarda label yok ve Docker label'ı yerinde değiştiremiyor. Network için bu kabul edilebilir, içinde veri yok. Volume'ler için değil: staging'deki kapak resimleri silinirdi. Bu sefer prevent_destroy korudu, ama her kaynakta o koruma yok.
Import sonrası her "değiştireceğim" ya da "replace edeceğim" satırı için iki seçenek var: kodu gerçeğe uydurmak ya da değişikliği bilerek kabul etmek. Volume'lerde kodu değiştirdim. Docker volume label'larını zaten yerinde güncelleyemediği için, bir label değişikliği yüzünden veri volume'ünü yeniden yaratmak hiçbir ortamda istenecek bir şey değil. bookstore-service module'ünde:
resource "docker_volume" "data" {
# ...
lifecycle {
prevent_destroy = true
# Docker bir volume'ün label'larını yerinde değiştiremez; bir label yüzünden veri volume'ü asla replace edilmesin
ignore_changes = [labels]
}
}
5. yazıda ignore_changes'in drift'i saklamak için kullanılmaması gerektiğini söylemiştik; buradaki kullanım farklı: değişmesi mümkün olmayan bir alanı bilerek dışarıda bırakıyoruz. İkinci plan:
$ terraform plan
...
# module.bookstore.module.api.docker_container.this must be replaced
# (imported from "b356423113bbc5023fe6bb4ab5668be64e5b4dd37486cfe2685ed7fb7ff7d5fd")
# Warning: this will destroy the imported resource
...
+ env = (sensitive value) # forces replacement
...
~ network_mode = "tfseries-staging" -> "bridge" # forces replacement
...
+ networks_advanced { # forces replacement
...
+ ports { # forces replacement
...
+ volumes { # forces replacement
...
# module.bookstore.module.api.docker_volume.data["covers"] will be imported
...
# module.bookstore.module.api.docker_volume.data["exports"] will be imported
...
Plan: 4 to import, 3 to add, 0 to change, 2 to destroy.
Volume'ler artık değişmeden import ediliyor. Container için plan replace diyor ve listede beklemediğim alanlar var: env, ports, volumes. Docker provider'ı container import ederken bu alanları state'e okumuyor, bu yüzden koddakiyle karşılaştırınca hepsi "yeni" görünüyor. Gerçek farklar ise networks_advanced (paylaşılan network eksik) ve label'lar. Container stateless, veri volume'lerde; replace'i kabul ettim. Network de label'lar yüzünden yeniden oluşturulacak, o da kabul. Apply:
$ terraform apply
...
module.bookstore.module.api.docker_volume.data["covers"]: Import complete [id=tfseries-staging-covers]
module.bookstore.module.api.docker_volume.data["exports"]: Import complete [id=tfseries-staging-exports]
module.bookstore.docker_network.bookstore: Import complete [id=26b2d9567429...]
module.bookstore.module.api.docker_container.this: Import complete [id=b356423113bb...]
module.bookstore.docker_network.bookstore: Destroying... [id=26b2d9567429...]
...
module.bookstore.module.api.docker_container.this: Destroying... [id=b356423113bb...]
...
module.bookstore.module.api.docker_container.this: Creation complete after 0s [id=4fa8d12de663...]
Apply complete! Resources: 4 imported, 3 added, 0 changed, 2 destroyed.
$ curl -s localhost:18481/health
{"status":"Healthy","environment":"staging","version":"1.1.0"}
$ terraform plan
...
Plan: 0 to add, 0 to change, 0 to destroy.
Önce dört kaynak state'e alındı, sonra plan'daki replace'ler uygulandı. Volume'ler yerinde, içlerindeki veri de. Apply'dan sonra import block'ları artık bir şey yapmıyor; doküman onları silmeyi ya da kaynağın nereden geldiğinin kaydı olarak bırakmayı serbest bırakıyor. Bizdeki ID'ler, network ve container yeniden oluşturulduğu için artık geçersiz; imports.tf'i sildim.
-generate-config-out ile İskelet Üretmek
Bizim durumumuzda kod hazırdı. Kodu olmayan bir kaynağı import ederken Terraform, block'larını senin için yazabilir. Root module'de iki import block'u olan boş bir klasörde:
import {
to = docker_network.staging
id = "26b2d9567429a440a7330adba171c447bc0d3cfc1a1891fe37fab513f63e8455"
}
import {
to = docker_container.staging_api
id = "b356423113bbc5023fe6bb4ab5668be64e5b4dd37486cfe2685ed7fb7ff7d5fd"
}
$ terraform plan -generate-config-out=generated.tf
...
# docker_container.staging_api must be replaced
...
+ env = (known after apply) # forces replacement
...
Plan: 2 to import, 1 to add, 0 to change, 1 to destroy.
Warning: Config generation is experimental
Generating configuration during import is currently experimental, and the
generated configuration format may change in future versions.
Üretilen dosyanın container kısmından bir bölüm:
# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.
resource "docker_container" "staging_api" {
...
entrypoint = ["dotnet", "Bookstore.Api.dll"]
...
hostname = "b356423113bb"
image = "bookstore-api:1.1.0"
...
name = "tfseries-staging-api"
network_mode = "tfseries-staging"
...
restart = "unless-stopped"
...
user = "1654"
...
working_dir = "/app"
}
84 satırlık bir dosya geldi ve plan yine container'ı replace etmek istiyor, çünkü üretilen kodda env, port ve volume tanımları yok; hostname gibi Docker'ın kendi atadığı değerler ise var. Bu çıktı bir iskelet: hangi argument'ların olduğunu gösterir, ama olduğu gibi commit'lenecek kod değildir. Gereksiz alanları sil, eksikleri ekle, değerleri variable ve local'lere bağla, mümkünse var olan bir module'ün çağrısına çevir. Config generation Terraform 1.5'ten beri var ve hâlâ "experimental" uyarısıyla geliyor; üretilen dosyanın biçimi sürümden sürüme değişebilir.
terraform query ve list Block'ları
Import için kaynağın ID'sini bilmen gerekir. Portal'da yıllar içinde birikmiş bir subscription'da ne olduğunu bilmiyorsan, Terraform 1.14 ile gelen terraform query keşif adımını yapıyor. .tfquery.hcl uzantılı dosyalara list block'ları yazılır; her block bir kaynak tipini sorgular ve provider'ın döndürdüğü sonuçları listeler. -generate-config-out ile her sonuç için import ve resource block'ları da üretilir.
Bu özellik provider desteğine bağlı. Kurduğum provider'ların şemalarına baktım: AzureRM 5.14'te 127 kaynak tipi için list desteği var; resource group, storage account, storage container, SQL Server ve database, service plan, user-assigned identity ve Key Vault bunların arasında, azurerm_linux_web_app değil. kreuzwerker/docker 4.7'de hiç yok, bu yüzden yerel örnekte deneyemedim. Azure tarafında staging için yazdığım sorgu:
# Staging için portal'da elle kurulanları listeler
list "azurerm_resource_group" "all" {
provider = azurerm
config {}
}
list "azurerm_storage_account" "staging" {
provider = azurerm
config {
resource_group_name = "rg-bookstore-staging-weu"
}
}
list "azurerm_mssql_server" "staging" {
provider = azurerm
config {
resource_group_name = "rg-bookstore-staging-weu"
}
}
Resource group listesi tag'e göre filtrelenebiliyor, ama elle kurulmuş kaynaklarda tag olmadığını varsayıp hepsini listeliyorum. Backend'i lokal bir kopyada devre dışı bırakıp çalıştırdım; Azure CLI ve hesap olmadığı için provider kimlik doğrulamada durdu:
$ terraform query
Error: unable to build authorizer for Resource Manager API: could not configure AzureCli Authorizer: could not parse Azure CLI version: launching Azure CLI: exec: "az": executable file not found in $PATH
with provider["registry.terraform.io/hashicorp/azurerm"],
on providers.tf line 1, in provider "azurerm":
1: provider "azurerm" {
Hesabın varsa çıktı, her kaynak için bir satır ve kaynağın kimliği olur. Terraform 1.12 ile gelen resource identity sayesinde import block'ları da uzun Azure ID'leri yerine anlamlı alanlarla yazılabiliyor. Query'nin üreteceği block'lar bu biçimde; elle yazınca validate geçti:
import {
to = module.bookstore.azurerm_resource_group.bookstore
identity = {
subscription_id = "00000000-0000-0000-0000-000000000000"
name = "rg-bookstore-staging-weu"
}
}
import {
to = module.bookstore.azurerm_storage_account.bookstore
identity = {
subscription_id = "00000000-0000-0000-0000-000000000000"
resource_group_name = "rg-bookstore-staging-weu"
name = "stbookstorestagingweu"
}
}
import {
to = module.bookstore.azurerm_mssql_server.bookstore
id = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-bookstore-staging-weu/providers/Microsoft.Sql/servers/sql-bookstore-staging-weu"
}
İlk iki block identity ile, sonuncusu klasik id ile. Hangi alanların identity'ye girdiğini provider şeması söylüyor: resource group için subscription ve ad, storage account için bir de resource group adı. 1.17 ile terraform query, HCP (HashiCorp Cloud Platform; HashiCorp'un yönetilen bulut platformu) Terraform'da uzaktan çalışırken bulunan kaynakları policy'lerle değerlendirebiliyor; lokal CLI'da bu adım yok.
moved Block'larını Ne Zaman Silmeli?
6. ve 7. yazıda iki moved.tf bıraktık. Bir moved block'u, state'inde eski adres olan herkesin apply etmesi için duruyor. HashiCorp'un refactoring dokümanı block'u silmeyi breaking change sayıyor: eski adresle duran bir state, block olmadan plan alınca taşımak yerine silip yeniden yaratmaya çalışır. Yayınlanan, başkalarının kullandığı module'lerde doküman eski moved block'larını hiç silmemeyi öneriyor. Ekip içindeki module'lerde silmek güvenli, ama yalnız bütün state'lerin yeni sürümle apply edildiğinden eminsen.
Yerel örnekte eski adresleri taşıyan tek state dev'inki ve dev iki değişikliği de apply etti; staging ise doğrudan yeni adreslerle başladı. İki dosyayı da sildim:
$ rm infra/local/bookstore/moved.tf infra/local/envs/dev/moved.tf
$ cd infra/local/envs/dev
$ terraform plan
...
Plan: 0 to add, 0 to change, 0 to destroy.
Azure tarafındaki moved.tf'leri ise tutuyorum: bu kodu kendi subscription'ında 6. yazıdaki hâliyle apply etmiş biri, ancak onlar sayesinde güvenle güncelleyebilir. Silme kararını "en son apply ne zaman yapıldı" sorusuna göre ver, "kod temiz görünsün" isteğine göre değil.
3. yazıdaki tuzağa da burada dönelim: count ile açılmış volume'leri for_each'e çevirmek. Ayrı bir deneme klasöründe 3. yazının ilk hâlini kurdum:
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 4.7"
}
}
}
provider "docker" {}
variable "volume_names" {
type = list(string)
default = ["covers", "exports", "invoices"]
}
resource "docker_volume" "data" {
count = length(var.volume_names)
name = "tfseries-lab-${var.volume_names[count.index]}"
}
Variable'ı set(string)'e, kaynağı for_each'e çevirince plan:
$ terraform plan
...
# docker_volume.data[0] will be destroyed
# (because resource does not use count)
# docker_volume.data[1] will be destroyed
# (because resource does not use count)
# docker_volume.data[2] will be destroyed
# (because resource does not use count)
# docker_volume.data["covers"] will be created
# docker_volume.data["exports"] will be created
# docker_volume.data["invoices"] will be created
Plan: 3 to add, 0 to change, 3 to destroy.
Adresler [0]'dan ["covers"]'a değiştiği için Terraform üç volume'ü silip yeniden yaratmak istiyor; içlerindeki veri giderdi. Her sıra numarasını bir key'e eşleyen moved block'larıyla:
# count sıra numarası kullanıyordu; for_each key kullanıyor
moved {
from = docker_volume.data[0]
to = docker_volume.data["covers"]
}
moved {
from = docker_volume.data[1]
to = docker_volume.data["exports"]
}
moved {
from = docker_volume.data[2]
to = docker_volume.data["invoices"]
}
$ terraform plan
...
# docker_volume.data[0] has moved to docker_volume.data["covers"]
# docker_volume.data[1] has moved to docker_volume.data["exports"]
# docker_volume.data[2] has moved to docker_volume.data["invoices"]
Plan: 0 to add, 0 to change, 0 to destroy.
moved block'u for_each almadığı için her eleman için ayrı block yazılıyor. Listeyi okuyup bu block'ları üreten küçük bir script de yazılabilir, ama sıra numarası ile key'in eşleşmesini bir kez gözle kontrol etmek şart: count.index'in sırası listenin sırası, set'in sırası değil.
removed Block'u: Silmeden Bırakmak
Bazen bir kaynağı Terraform'un yönetiminden çıkarmak istersin, ama silmek istemezsin: kaynak başka bir ekibe ya da 7. yazıdaki gibi başka bir katmanın state'ine geçiyor. Kaynak block'unu koddan silmek bunu yapmaz; 5. yazıda gördüğümüz gibi block kalkınca Terraform kaynağı siler. Doğru araç removed block'u. Aynı deneme klasöründe volume'leri bir "data" klasörüne devretmek istedim ve önce yalnız invoices'ı çıkarmayı denedim:
removed {
from = docker_volume.data["invoices"]
lifecycle {
destroy = false
}
}
$ terraform validate
Error: Resource instance keys not allowed
on removed.tf line 2, in removed:
2: from = docker_volume.data["invoices"]
Resource address must be a resource (e.g. "test_instance.foo"), not a
resource instance (e.g. "test_instance.foo[1]").
removed tek bir instance'ı değil, kaynağın tamamını hedefler. Tek bir key'i ayırman gerekiyorsa önce moved ile onu ayrı bir kaynak adresine taşır, ikinci adımda o adresi removed ile bırakırsın. Ben üç volume'ü birlikte devrettim: resource block'unu silip yerine şunu koydum:
# Volume'ler artık data katmanının; silme, yalnız burada yönetmeyi bırak
removed {
from = docker_volume.data
lifecycle {
destroy = false
}
}
$ terraform plan
...
# docker_volume.data["covers"] will no longer be managed by Terraform, but will not be destroyed
# (destroy = false is set in the configuration)
. resource "docker_volume" "data" {
id = "tfseries-lab-covers"
name = "tfseries-lab-covers"
# (3 unchanged attributes hidden)
}
...
Plan: 0 to add, 0 to change, 0 to destroy.
Warning: Some objects will no longer be managed by Terraform
If you apply this plan, Terraform will discard its tracking information for
the following objects, but it will not delete them:
- docker_volume.data["exports"]
- docker_volume.data["covers"]
- docker_volume.data["invoices"]
After applying this plan, Terraform will no longer manage these objects. You
will need to import them into Terraform to manage them again.
Plan'daki nokta işareti ve uyarı, kaynağın silinmeyeceğini ama state'ten çıkacağını açıkça söylüyor. Apply'dan sonra volume'ler Docker'da duruyor, app klasörünün state'i boş. Devralan data klasörü onları for_each'li bir import ile alıyor:
terraform {
required_providers {
docker = {
source = "kreuzwerker/docker"
version = "~> 4.7"
}
}
}
provider "docker" {}
locals {
data_sets = toset(["covers", "exports", "invoices"])
}
import {
for_each = local.data_sets
to = docker_volume.data[each.key]
id = "tfseries-lab-${each.key}"
}
resource "docker_volume" "data" {
for_each = local.data_sets
name = "tfseries-lab-${each.key}"
lifecycle {
prevent_destroy = true
}
}
$ terraform plan
...
# docker_volume.data["covers"] will be imported
# docker_volume.data["exports"] will be imported
# docker_volume.data["invoices"] will be imported
Plan: 3 to import, 0 to add, 0 to change, 0 to destroy.
$ terraform apply
...
Apply complete! Resources: 3 imported, 0 added, 0 changed, 0 destroyed.
$ cd ../app && terraform plan
No changes. Your infrastructure matches the configuration.
Bu iki adım, eskiden terraform state rm ve terraform import komutlarıyla elle yapılan "kaynağı bir state'ten diğerine taşıma" işinin kodla, review edilebilir hâli. removed block'u da apply'dan sonra silinebilir; kaynak artık state'te olmadığı için block'un bir işi kalmıyor. Ama 5. yazıdaki destroy = false uyarısı burada da geçerli: apply'dan önce block'u silersen ve resource block'u da yoksa, plan kaynağı silmeye döner.
Üretimde Dikkat
Import'u production'a uygulamadan önce aynı akışı bir kopya üzerinde dene: plan'ı al, her replace ve update satırının sebebini yaz, kabul ettiklerini pull request açıklamasına koy. Azure'da tag farkları genelde yerinde update olur, ama SKU (kaynağın boyut ve fiyat katmanı), region, ad gibi alanlar replace demek; bir SQL database'in replace edilmesi veri kaybıdır. Import edeceğin data kaynaklarına önce prevent_destroy koymak, bizdeki gibi beklenmeyen bir replace'i plan aşamasında durdurur.
Import bir kerelik bir iş değil, drift kontrolünün devamı. Koda alınmamış ya da koddan farklılaşmış kaynakları görmek için plan -refresh-only (4. yazı) ve zamanlanmış bir drift job'u (11. yazı) gerekir; Azure'da terraform query ile düzenli olarak "Terraform'un bilmediği resource group'lar" listesi almak da iyi bir sinyal.
Elle kurulmuş kaynakların secret'larına dikkat et. Import ettiğin kaynağın ayarlarında secret varsa (Web App'in app setting'leri gibi) bu değerler state'e okunur; bizim container'da da replace'ten sonra ödeme API key'i state'te. State'in erişimi kısıtlı değilse import, secret'ı daha fazla kişiye açmak demek. Bunun çözümü bir sonraki yazının konusu.
Sık Düşülen Tuzaklar
Import sonrası ilk plan'ı körü körüne apply etmek. Plan "0 to change" demiyorsa, aradaki her satır gerçek altyapıda bir değişiklik; "Warning: this will destroy the imported resource" varsa import ettiğin kaynak silinecek. Önce kodu gerçeğe yaklaştır, kalan farkları tek tek kabul et.
Var olan kaynakların üzerine apply etmek. Yukarıda gördüğümüz gibi bazı API'ler "zaten var" hatası verir, bazıları sessizce var olanı döndürür. İkinci durumda Terraform kaynağı kendi oluşturduğunu sanar; o kaynağı silebilecek bir state'in sahibi olursun.
moved block'larını erken silmek ya da kaynak block'unu removed olmadan silmek. İkisinin sonucu da aynı: plan, taşımak ya da bırakmak yerine silmeye döner. Silme kararını verdiğin her plan'da son satırdaki to destroy sayısına bak.
Küçük Bir Deneme
★☆☆ tfseries-staging-api'yi elle durdur (docker stop) ve envs/staging'de terraform plan -refresh-only al. Terraform hangi alanın değiştiğini söylüyor?
★★☆ Docker CLI ile tfseries-staging-reports adlı bir volume aç ve bookstore-service'in data_volumes listesine reports'u ekleyerek import et. İlk plan'da replace görmemek için neyi değiştirmen gerekti?
★★★ count'tan for_each'e geçişteki moved block'larını, listedeki sırayı okuyup üreten bir for expression ile yazmayı dene. Terraform buna izin veriyor mu? Vermiyorsa block'ları üretmek için hangi aracı kullanırdın?
Bir sonraki yazıda secret'lara geçiyoruz: sensitive'in neyi gizleyip neyi gizlemediği, ephemeral value'lar, Azure SQL şifresini state'e hiç yazmayan write-only argument'lar ve Key Vault.