Terraform tuzakları

Terraform tuzakları
Döngüler, if ifadeleri ve dağıtım teknikleriyle ilgili olanlar ve ayrıca Terraform'u genel olarak etkileyen daha genel sorunlar da dahil olmak üzere birkaç tuzağı vurgulayalım:

  • count ve for_each parametrelerinin sınırlamaları vardır;
  • sıfır kesinti süreli dağıtımları sınırlayın;
  • iyi bir plan bile başarısız olabilir;
  • Yeniden düzenlemenin tuzakları olabilir;
  • ertelenmiş tutarlılık... ertelemeyle tutarlıdır.

Count ve for_each parametrelerinin sınırlamaları vardır

Bu bölümdeki örneklerde döngülerde ve koşullu mantıkta count parametresi ve for_each ifadesi kapsamlı bir şekilde kullanılmaktadır. İyi performans gösteriyorlar ancak bilmeniz gereken iki önemli sınırlamaları var.

  • Count ve for_each hiçbir kaynak çıktı değişkenine referans veremez.
  • count ve for_each modül yapılandırmasında kullanılamaz.

count ve for_each hiçbir kaynak çıktı değişkenine referans veremez

Birkaç EC2 sunucusunu dağıtmanız gerektiğini ve herhangi bir nedenle ASG'yi kullanmak istemediğinizi düşünün. Kodunuz şu şekilde olabilir:

resource "aws_instance" "example_1" {
   count             = 3
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Şimdi bunlara tek tek bakalım.

Count parametresi statik bir değere ayarlandığından bu kod sorunsuz çalışacaktır: Apply komutunu çalıştırdığınızda üç EC2 sunucusu oluşturacaktır. Peki ya mevcut AWS bölgenizdeki her Erişilebilirlik Alanına (AZ) bir sunucu dağıtmak istiyorsanız? Kodunuzun aws_availability_zones veri kaynağından bir bölge listesi yüklemesini ve ardından her birinde döngü oluşturmasını ve count parametresini ve dizi indeks erişimini kullanarak içinde bir EC2 sunucusu oluşturmasını sağlayabilirsiniz:

resource "aws_instance" "example_2" {
   count                   = length(data.aws_availability_zones.all.names)
   availability_zone   = data.aws_availability_zones.all.names[count.index]
   ami                     = "ami-0c55b159cbfafe1f0"
   instance_type       = "t2.micro"
}

data "aws_availability_zones" "all" {}

Count parametresi veri kaynaklarına sorunsuz bir şekilde başvurabildiğinden bu kod da düzgün çalışacaktır. Peki, oluşturmanız gereken sunucu sayısı bir kaynağın çıktısına bağlıysa ne olur? Bunu göstermenin en kolay yolu, adından da anlaşılacağı gibi rastgele bir tamsayı döndüren random_integer kaynağını kullanmaktır:

resource "random_integer" "num_instances" {
  min = 1
  max = 3
}

Bu kod 1 ile 3 arasında rastgele bir sayı üretir. Bu kaynağın çıktısını aws_instance kaynağının count parametresinde kullanmaya çalışırsak ne olacağını görelim:

resource "aws_instance" "example_3" {
   count             = random_integer.num_instances.result
   ami                = "ami-0c55b159cbfafe1f0"
   instance_type = "t2.micro"
}

Bu kod üzerinde terraform planını çalıştırırsanız aşağıdaki hatayı alırsınız:

Error: Invalid count argument

   on main.tf line 30, in resource "aws_instance" "example_3":
   30: count = random_integer.num_instances.result

The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.

Terraform, herhangi bir kaynak oluşturulmadan veya değiştirilmeden önce planlama aşamasında count ve for_each'in hesaplanmasını gerektirir. Bu, count ve for_each'in değişmez değerlere, değişkenlere, veri kaynaklarına ve hatta kaynak listelerine (uzunlukları planlama zamanında belirlenebildiği sürece) başvurabileceği, ancak hesaplanan kaynak çıktı değişkenlerine başvuramayacağı anlamına gelir.

count ve for_each modül yapılandırmasında kullanılamaz

Bir gün modül konfigürasyonunuza bir sayım parametresi eklemek isteyebilirsiniz:

module "count_example" {
     source = "../../../../modules/services/webserver-cluster"

     count = 3

     cluster_name = "terraform-up-and-running-example"
     server_port = 8080
     instance_type = "t2.micro"
}

Bu kod, web sunucusu kümesi kaynağının üç kopyasını oluşturmak için bir modül içindeki count'u kullanmaya çalışır. Veya bir modülün count parametresini 0'a ayarlayarak Boole koşuluna dayalı olarak bağlanmayı isteğe bağlı hale getirmek isteyebilirsiniz. Bu makul bir kod gibi görünebilir, ancak terraform planını çalıştırırken şu hatayı alırsınız:

Error: Reserved argument name in module block

   on main.tf line 13, in module "count_example":
   13: count = 3

The name "count" is reserved for use in a future version of Terraform.

Ne yazık ki Terraform 0.12.6'dan itibaren modül kaynağında count veya for_each kullanımı desteklenmiyor. Terraform 0.12 sürüm notlarına (http://bit.ly/3257bv4) göre, HashiCorp bu özelliği gelecekte eklemeyi planlıyor, dolayısıyla bu kitabı ne zaman okuduğunuza bağlı olarak zaten mevcut olabilir. Kesin olarak öğrenmek için, Terraform değişiklik günlüğünü buradan okuyun.

Sıfır Kesinti Süreli Dağıtımların Sınırlamaları

Create_before_destroy bloğunun ASG ile birlikte kullanılması, sıfır kesinti süreli dağıtımlar oluşturmak için harika bir çözümdür; ancak bir uyarı dışında: otomatik ölçeklendirme kuralları desteklenmez. Veya daha kesin bir ifadeyle bu, her dağıtımda ASG boyutunu min_size değerine sıfırlar; bu, çalışan sunucu sayısını artırmak için otomatik ölçeklendirme kuralları kullanıyorsanız sorun olabilir.

Örneğin, web sunucusu kümesi modülü, sabah 9'da kümedeki sunucu sayısını ikiden ona çıkaran bir çift aws_autoscaling_schedule kaynağı içerir. Örneğin sabah 11'de dağıtım yaparsanız, yeni ASG on sunucu yerine yalnızca iki sunucuyla başlatılacak ve ertesi gün sabah 9'a kadar bu şekilde kalacaktır.

Bu sınırlama çeşitli şekillerde aşılabilir.

  • aws_autoscaling_schedule'deki yineleme parametresini 0 9 * * * ("sabah 9'da çalıştır") yerine 0-59 9-17 * * * ("sabah 9'dan akşam 5'e kadar her dakika çalıştır") gibi bir değere değiştirin. ASG'nin zaten on sunucusu varsa, bu otomatik ölçeklendirme kuralını tekrar çalıştırmak hiçbir şeyi değiştirmeyecektir, biz de bunu istiyoruz. Ancak ASG yakın zamanda konuşlandırıldıysa, bu kural maksimum bir dakika içinde sunucu sayısının ona ulaşmasını sağlayacaktır. Bu tamamen şık bir yaklaşım değil ve ondan iki sunucuya ve geriye doğru büyük sıçramalar da kullanıcılar için sorunlara neden olabilir.
  • ASG'deki etkin sunucuların sayısını belirlemek için AWS API'yi kullanan özel bir komut dosyası oluşturun, bunu harici bir veri kaynağı kullanarak çağırın (bkz. "Harici Veri Kaynağı" sayfa 249) ve ASG'nin istenen_capacity parametresini, tarafından döndürülen değere ayarlayın. senaryo. Bu şekilde, her yeni ASG bulut sunucusu her zaman mevcut Terraform koduyla aynı kapasitede çalışacak ve bakımı daha zor hale gelecektir.

Elbette Terraform ideal olarak sıfır kesinti süreli dağıtımlar için yerleşik desteğe sahip olacaktı, ancak Mayıs 2019 itibarıyla HashiCorp ekibinin bu işlevselliği ekleme planı yoktu (ayrıntılar - burada).

Doğru plan başarısızlıkla uygulanabilir

Bazen plan komutu tamamen doğru bir dağıtım planı üretir ancak uygulama komutu bir hata döndürür. Örneğin, Bölüm 2'de daha önce oluşturduğunuz IAM kullanıcısı için kullandığınız aws_iam_user kaynağını eklemeyi deneyin:

resource "aws_iam_user" "existing_user" {
   # Подставьте сюда имя уже существующего пользователя IAM,
   # чтобы попрактиковаться в использовании команды terraform import
   name = "yevgeniy.brikman"
}

Şimdi plan komutunu çalıştırırsanız Terraform makul görünen bir dağıtım planının çıktısını alacaktır:

Terraform will perform the following actions:

   # aws_iam_user.existing_user will be created
   + resource "aws_iam_user" "existing_user" {
         + arn                  = (known after apply)
         + force_destroy   = false
         + id                    = (known after apply)
         + name               = "yevgeniy.brikman"
         + path                 = "/"
         + unique_id         = (known after apply)
      }

Plan: 1 to add, 0 to change, 0 to destroy.

Apply komutunu çalıştırırsanız aşağıdaki hatayı alırsınız:

Error: Error creating IAM User yevgeniy.brikman: EntityAlreadyExists:
User with name yevgeniy.brikman already exists.

   on main.tf line 10, in resource "aws_iam_user" "existing_user":
   10: resource "aws_iam_user" "existing_user" {

Elbette sorun, bu ada sahip bir IAM kullanıcısının zaten mevcut olmasıdır. Ve bu yalnızca IAM kullanıcılarının değil, hemen hemen her kaynağın başına gelebilir. Birisinin bu kaynağı manuel olarak veya komut satırını kullanarak oluşturmuş olması mümkündür, ancak her iki durumda da kimliklerin eşleştirilmesi çakışmalara yol açar. Bu hatanın Terraform'a yeni gelenleri sıklıkla şaşırtan birçok çeşidi vardır.

Önemli olan nokta, terraform plan komutunun yalnızca Terraform durum dosyasında belirtilen kaynakları dikkate almasıdır. Kaynaklar başka bir şekilde oluşturulursa (örneğin, AWS konsolunda manuel olarak tıklatılarak), durum dosyasında yer almazlar ve bu nedenle Terraform, plan komutunu yürütürken bunları dikkate almaz. Sonuç olarak ilk bakışta doğru gibi görünen bir plan başarısızlıkla sonuçlanacaktır.

Bundan çıkarılacak iki ders var.

  • Zaten Terraform ile çalışmaya başladıysanız başka bir şey kullanmayın. Altyapınızın bir kısmı Terraform kullanılarak yönetiliyorsa artık bunu manuel olarak değiştiremezsiniz. Aksi takdirde, yalnızca garip Terraform hataları riskiyle karşı karşıya kalmazsınız, aynı zamanda kod artık altyapınızı doğru bir şekilde temsil etmeyeceği için IaC'nin birçok avantajını da ortadan kaldırırsınız.
  • Zaten bir altyapınız varsa, içe aktarma komutunu kullanın. Eğer Terraform'u mevcut altyapı ile kullanmaya başlıyorsanız terraform import komutunu kullanarak state dosyasına ekleyebilirsiniz. Bu şekilde Terraform hangi altyapının yönetilmesi gerektiğini bilecek. Import komutu iki argüman alır. Birincisi, yapılandırma dosyalarınızdaki kaynak adresidir. Buradaki sözdizimi kaynak bağlantılarıyla aynıdır: _. (aws_iam_user.existing_user gibi). İkinci argüman, içe aktarılacak kaynağın kimliğidir. Aws_iam_user kaynak kimliğinin kullanıcı adı olduğunu (örneğin, yevgeniy.brikman) ve aws_instance kaynak kimliğinin EC2 sunucu kimliği olduğunu (i-190e22e5 gibi) varsayalım. Bir kaynağın nasıl içe aktarılacağı genellikle sayfanın altındaki belgelerde belirtilir.

    Aşağıda, Bölüm 2'deki IAM kullanıcısıyla birlikte Terraform yapılandırmanıza eklediğiniz aws_iam_user kaynağını senkronize eden bir içe aktarma komutu bulunmaktadır (tabii ki adınızı yevgeniy.brikman yerine):

    $ terraform import aws_iam_user.existing_user yevgeniy.brikman

    Terraform, IAM kullanıcınızı bulmak ve bu kullanıcı ile Terraform yapılandırmanızdaki aws_iam_user.existing_user kaynağı arasında bir durum dosyası ilişkilendirmesi oluşturmak için AWS API'yi çağıracaktır. Artık plan komutunu çalıştırdığınızda Terraform IAM kullanıcısının zaten var olduğunu bilecek ve onu tekrar oluşturmaya çalışmayacaktır.

    Zaten Terraform'a aktarmak istediğiniz çok sayıda kaynağınız varsa, kodu manuel olarak yazmanın ve her birini tek tek içe aktarmanın zorluk yaratabileceğini belirtmekte fayda var. Bu nedenle, AWS hesabınızdan kodu ve durumu otomatik olarak içe aktarabilen Terraforming (http://terraforming.dtan4.net/) gibi bir araca bakmaya değer.

    Yeniden düzenlemenin tuzakları olabilir

    Yeniden Düzenleme dış davranışı değiştirmeden kodun iç yapısını değiştirdiğiniz programlamada yaygın bir uygulamadır. Bunun amacı kodu daha net, daha düzenli ve bakımı daha kolay hale getirmektir. Yeniden düzenleme, düzenli kullanılması gereken vazgeçilmez bir tekniktir. Ancak konu Terraform veya başka bir IaC aracı olduğunda, bir kod parçasının "harici davranışı" ile neyi kastettiğinize son derece dikkat etmelisiniz, aksi takdirde beklenmedik sorunlar ortaya çıkacaktır.

    Örneğin, yaygın bir yeniden düzenleme türü, değişkenlerin veya işlevlerin adlarını daha anlaşılır olanlarla değiştirmektir. Birçok IDE, yeniden düzenleme için yerleşik desteğe sahiptir ve proje boyunca değişkenleri ve işlevleri otomatik olarak yeniden adlandırabilir. Genel amaçlı programlama dillerinde bu, aklınıza gelmeyecek kadar basit bir işlemdir ancak Terraform'da bu konuda son derece dikkatli olmanız gerekir, aksi halde kesintiler yaşayabilirsiniz.

    Örneğin, web sunucusu kümesi modülünde küme_adı adlı bir giriş değişkeni bulunur:

    variable "cluster_name" {
       description = "The name to use for all the cluster resources"
       type          = string
    }

    Foo adlı bir mikro hizmeti dağıtmak için bu modülü kullanmaya başladığınızı hayal edin. Daha sonra hizmetinizi bar olarak yeniden adlandırmak istiyorsunuz. Bu değişiklik önemsiz görünebilir ancak gerçekte hizmet kesintilerine neden olabilir.

    Gerçek şu ki, web sunucusu kümesi modülü, iki güvenlik grubunun name parametresi ve ALB dahil olmak üzere bir dizi kaynakta Cluster_name değişkenini kullanır:

    resource "aws_lb" "example" {
       name                    = var.cluster_name
       load_balancer_type = "application"
       subnets = data.aws_subnet_ids.default.ids
       security_groups      = [aws_security_group.alb.id]
    }

    Bir kaynağın name parametresini değiştirirseniz Terraform o kaynağın eski sürümünü silecek ve yerine yenisini oluşturacaktır. Ancak bu kaynak bir ALB ise, onu silmekle yeni bir sürümü indirmek arasında, trafiği web sunucunuza yeniden yönlendirecek bir mekanizmaya sahip olmayacaksınız. Benzer şekilde, bir güvenlik grubu silinirse sunucularınız yeni bir grup oluşturulana kadar her türlü ağ trafiğini reddetmeye başlayacaktır.

    İlginizi çekebilecek başka bir yeniden düzenleme türü de Terraform Kimliğini değiştirmektir. Örnek olarak webserver-cluster modülündeki aws_security_group kaynağını ele alalım:

    resource "aws_security_group" "instance" {
      # (...)
    }

    Bu kaynağın tanımlayıcısına örnek denir. Yeniden düzenleme sırasında onu daha anlaşılır (sizin görüşünüze göre) bir küme_instance adıyla değiştirmeye karar verdiğinizi düşünün:

    resource "aws_security_group" "cluster_instance" {
       # (...)
    }

    Sonunda ne olacak? Bu doğru: bir kesinti.

    Terraform, her kaynak kimliğini bulut sağlayıcı kimliğiyle ilişkilendirir. Örneğin, iam_user, AWS IAM kullanıcı kimliğiyle ilişkilendirilir ve aws_instance, AWS EC2 sunucu kimliğiyle ilişkilendirilir. Kaynak kimliğini değiştirirseniz (örneğin, aws_security_group'ta olduğu gibi örnekten Cluster_instance'a), Terraform'a, eski kaynağı silip yenisini eklemişsiniz gibi görünecektir. Bu değişiklikleri uygularsanız Terraform eski güvenlik grubunu silecek ve yeni bir grup oluşturacak, sunucularınız ise ağ trafiğini reddetmeye başlayacaktır.

    İşte bu tartışmadan çıkarmanız gereken dört önemli ders.

    • Her zaman plan komutunu kullanın. Tüm bu pürüzleri ortaya çıkarabilir. Çıktısını dikkatlice inceleyin ve Terraform'un büyük olasılıkla silinmemesi gereken kaynakları silmeyi planladığı durumlara dikkat edin.
    • Silmeden önce oluşturun. Bir kaynağı değiştirmek istiyorsanız orijinali silmeden önce yenisini oluşturmanız gerekip gerekmediğini dikkatlice düşünün. Cevabınız evet ise create_before_destroy yardımcı olabilir. Aynı sonuca manuel olarak iki adım gerçekleştirilerek de ulaşılabilir: önce yapılandırmaya yeni bir kaynak ekleyin ve uygulama komutunu çalıştırın, ardından eski kaynağı yapılandırmadan kaldırın ve uygulama komutunu yeniden kullanın.
    • Tanımlayıcıların değiştirilmesi durumun değiştirilmesini gerektirir. Kaynağı silmeden ve yeni bir sürümünü oluşturmadan bir kaynakla ilişkili kimliği değiştirmek istiyorsanız (örneğin, aws_security_group'u örnekten Cluster_instance olarak yeniden adlandırın), Terraform durum dosyasını buna göre güncellemeniz gerekir. Bunu asla manuel olarak yapmayın; bunun yerine terraform durumu komutunu kullanın. Tanımlayıcıları yeniden adlandırırken aşağıdaki sözdizimine sahip terraform state mv komutunu çalıştırmalısınız:
      terraform state mv <ORIGINAL_REFERENCE> <NEW_REFERENCE>

      ORIGINAL_REFERENCE, kaynağa mevcut haliyle başvuran bir ifadedir ve NEW_REFERENCE, onu taşımak istediğiniz yerdir. Örneğin, aws_security_group grubunu örnekten Cluster_instance'a yeniden adlandırırken aşağıdaki komutu çalıştırmanız gerekir:

      $ terraform state mv 
         aws_security_group.instance 
         aws_security_group.cluster_instance

      Bu, Terraform'a daha önce aws_security_group.instance ile ilişkilendirilen durumun artık aws_security_group.cluster_instance ile ilişkilendirilmesi gerektiğini bildirir. Bu komutu yeniden adlandırıp çalıştırdıktan sonra terraform planında herhangi bir değişiklik görünmüyorsa, her şeyi doğru yaptınız demektir.

    • Bazı ayarlar değiştirilemez. Birçok kaynağın parametreleri değiştirilemez. Bunları değiştirmeye çalışırsanız Terraform eski kaynağı silecek ve yerine yenisini oluşturacaktır. Her kaynak sayfası genellikle belirli bir ayarı değiştirdiğinizde ne olacağını gösterir; bu nedenle belgeleri kontrol ettiğinizden emin olun. Her zaman plan komutunu kullanın ve create_before_destroy stratejisini kullanmayı düşünün.

    Ertelenen tutarlılık ertelemeyle tutarlıdır

    AWS gibi bazı bulut sağlayıcılarının API'leri eşzamansızdır ve gecikmeli tutarlılığa sahiptir. Eşzamansızlık, arayüzün istenen eylemin tamamlanmasını beklemeden hemen yanıt verebilmesi anlamına gelir. Gecikmiş tutarlılık, değişikliklerin sistem genelinde yayılmasının zaman alabileceği anlamına gelir; bu gerçekleşirken yanıtlarınız tutarsız olabilir ve API çağrılarınıza hangi veri kaynağı replikasının yanıt verdiğine bağlı olabilir.

    Örneğin, AWS'ye bir API çağrısı yaptığınızı ve ondan bir EC2 sunucusu oluşturmasını istediğinizi düşünün. API, sunucunun kendisinin oluşturulmasını beklemeden neredeyse anında "başarılı" bir yanıt (201 Oluşturuldu) döndürecektir. Hemen bağlanmaya çalışırsanız, neredeyse kesinlikle başarısız olacaktır çünkü bu noktada AWS hala kaynakları başlatıyor veya alternatif olarak sunucu henüz başlatılmadı. Üstelik bu sunucu hakkında bilgi almak için tekrar arama yaparsanız (404 Bulunamadı) hatası alabilirsiniz. Mesele şu ki, bu EC2 sunucusuyla ilgili bilgiler, her yerde kullanıma sunulmadan önce AWS genelinde yayılmaya devam edebilir; birkaç saniye beklemeniz gerekecek.

    Tembel tutarlılığa sahip eşzamansız bir API kullandığınızda, eylem tamamlanıp sistemde yayılana kadar isteğinizi düzenli aralıklarla yeniden denemeniz gerekir. Maalesef AWS SDK bunun için iyi bir araç sunmuyor ve Terraform projesi 6813 (https://github.com/hashicorp/terraform/issues/6813) gibi birçok hatadan muzdaripti:

    $ terraform apply
    aws_subnet.private-persistence.2: InvalidSubnetID.NotFound:
    The subnet ID 'subnet-xxxxxxx' does not exist

    Başka bir deyişle, bir kaynak (alt ağ gibi) oluşturursunuz ve ardından bu kaynakla ilgili bazı bilgileri (yeni oluşturulan alt ağın kimliği gibi) almaya çalışırsınız ve Terraform onu ​​bulamaz. Bu hataların çoğu (6813 dahil) düzeltildi, ancak yine de zaman zaman ortaya çıkıyorlar, özellikle de Terraform yeni bir kaynak türü için destek eklediğinde. Bu can sıkıcıdır ancak çoğu durumda herhangi bir zarara neden olmaz. Terraform Apply'ı tekrar çalıştırdığınızda her şey çalışmalıdır, çünkü bu zamana kadar bilgiler zaten sisteme yayılmış olacaktır.

    Bu alıntı Evgeniy Brikman'ın kitabından sunulmuştur. "Terraform: kod düzeyinde altyapı".

Kaynak: habr.com

Yorum ekle