AWS DynamoDB Terraform Unit Testing (Terratest)
Selamlar Herkese,
Infrastructure as Code (IaC) yaklaşımı ile birlikte, gün geçtikçe hacmi ve karmaşıklığı (complexity) artan alt yapılarda (infrastructure) ‘sürdürülebilir’, ‘yalın’ ve ‘çevik’ bir alt yapı kurgulamak, bulut teknolojilerinin de yaygınlaşması ile birlikte en önemli konulardan birisi haline gelmektedir.
Bu yazıda; ‘sürdürülebilir’ altyapılarda hata oranını en aza indirebilmek, karmaşıklaşan ve hacim olarak genişleyen infrastructure’ımız da yapılan her minor değişiklikle birlikte veya güncellemeler nedeniyle infrastructure’da çalışan modüllerin zaman içerisinde giderek birbirinden farklılaşan ve konfigürasyonunu zorlaştıran süreçleri (configuration drift) minimum seviyeye indirgeyebilmek adına development süreçlerine dahil edilen altyapımızı daha kararlı hale getirebilmek için sürdürülebilir altyapılarda ‘unit testing’ yaklaşımına değinmeye çalışacağız…
İlk olarak AWS DynamoDB
DynamoDB; MongoDB, RavenDB, Couchbase gibi bir NoSQL veritabanıdır. DynamoDB, tam olarak yönetilebilen bir veritabanıdır, sorunsuz ölçeklenebilmesiyle hızlı ve öngörülebilir bir performans sağlar.
DynamoDB, serverless veritabanı olduğu için donanımsal ayarlamalar, kurulum ve konfigürasyonlar, replikasyon gibi işlemlerin operasyonel tarafını düşünmeden dağıtık bir veritabanı kullanmayı sağlar. Ayrıca hassas verilerin korunması amacıyla şifreleme çözümü sunmaktadır. Bu şifreleme yaparken AWS KMS’teki encrypting keyleri kullanır. Şifrelemeyle alakalı daha fazla detaya buradan ulaşabilirsiniz.
DynamoDB ile herhangi bir kesinti veya performans düşüşü yaşamayı dert etmeden, düşük ya da yüksek trafiğe sahip uygulamaların verilerini saklayabilir ve kullanabiliriz. Veritabanını uygun şekilde tasarladığımız takdirde milyonlarca veriyi milisaniyeler düzeyinde okuma performansı vaadediyor. Bunun yanında talebe göre uzun süreli saklama veya arşivleme amacıyla yedekleme imkanı da var, tek bir API call ile yedekleme veya geri çağırma işlemleri gerçekleştirilebiliyor ve bu işlemin performansa etkisi sıfır. TTL(Time To Live) kullanarak tablo içerisinde belirlenen süre boyunca kullanılmayan verileri silerek depolama maliyetini de azaltma imkanı mevcut. Tüm veriler SSD’lerde depolanır ve AWS bölgesindeki alanlara otomatik olarak çoğaltılarak kullanılabilirliği artırır. Ayrıca Global Tables kullanarak farklı AWS bölgeleri arasında da dağıtarak senkron halde tutmayı sağlayabiliyoruz.
DynamoDB’de key-value veya belge(document) olarak tutma şansına sahibiz. İlişkisel veritabanlarından ayrılan yönü sunucusuz çalışmasının yanında diğer NoSQL veritabanları gibi doküman tipinde veri saklayabilmesi diyebiliriz. DynamoDB’nin tablo oluşturma konusunda bazı kuralları mevcut. Bu sistemlere uyarak veriyi saklama ve kullanma konusunda kolaylıklar sağlanabildiğini söyleyebilirim. İsimlendirme hakkındaki kurallardan bazıları şöyle;
Tablolar, attribute ve diğer objeler isme sahip olmalı. Bu isimler de Movies, Actors, Directors örnekleri gibi olabildiğince kısa ve kendini anlatacak şekilde olmalı. Tüm isimler UTF-8 uyumlu ve büyük/küçük harf duyarlı olmalı. Tablo isimleri en az 3, en fazla 255 karakter uzunluğunda ve yalnızca “_(underscore), -(dash), .(dot), a-z, A-Z, 0–9” kullanılmalı.
DynamoDB’de 3 adet veri tipi mevcut.
- Sayısal Tipler(Scalar Types) — Bu tipler tek bir değeri temsil ederler. Number, string, binary, Boolean veya null olabilir.
- Belge Tipleri(Document Types) — Doküman tipleri daha karmaşık yapıları temsil ediyor. JSON dokümanlar gibi iç içe yapılar bunun örneğidir. Belge tipleri, list ve map’tir.
- Set Tipler (Set Types) — Bu tipler çoklu sayısal değerleri temsil ederler. Set tipleri string set, number set veya binary set olabilir.
DynamoDB, verileri daha önce görmediğim bir şekilde tutmakta.
Örnek olarak aşağıdaki gibi bir getItem sorgusu atmak istediğimizde;
“ForumName” : “Amazon DynamoDB” yerine
“ForumName”: {
“S”: “Amazon DynamoDB”
}
şeklinde bir yazım mevcut ancak bu tabii ki böyle yazmak zorunda olduğumuz anlamına gelmiyor.
DynamoDB Document Client kullanarak;
“ForumName”: “Amazon DynamoDB” şeklinde istek atmamız mümkün hale geliyor. Aşağıdaki ekran görüntüsünde Document Client kullanılarak “key” lerin sorguda nasıl kullanıldığı hakkında bilgi alabilirsiniz.
IaC ve AWS DynamoDB Unit Test
Geçmişte ve günümüzde olduğu gibi gelecekte de her zaman ön planda olması gereken şeylerden birisi günün sonunda ortaya çıkan veya çıkacak olan kurguladığımız üründür.Planlanan ürünün başarısı ise yalın, çevik ve sürdürülebilir bir altyapı kurgulamaktan geçmektedir.Canlıdaki ürününüz ile birlikte günün sonunda farklılaşan dinamik haldeki altyapınızın , yapılacak en küçük/minor değişiklere karşı verdiği reaksiyonu öncesinde simulate etmeniz gerekebilir…Aksi takdirde, development ortamından canlıdaki ürününüzün sunulduğu production ortamınıza kadar; kurguladığınız alt yapılarda büyük bir refactoring’e gitmeniz gerekebilir…Bu durum günün sonunda, ne yazık ki maliyet olarak sizlere geri dönecektir.
Zaman açısından maliyeti en aza indirebilmek, kararlı bir alt yapı oluşturabilmek adına, Infrastructure as Code yaklaşımı ile birlikte; kodladığımız alt yapının, aslında günün sonunda ‘gerçekten deklare ettiğimiz region’da mı çalışacak ?’ veya ‘provizyonladığım resource’um gerçekten dışarıdan deklare edilen parametre ile mi oluşturuluyor ? gibi sorulara erken aşamada cevap verebilmek adına kurguladığımız altyapımızı provizyonlamadan önce her bir resource’unun veya module’nün her birinin birbirinden bağımsız bir şekilde beklendiği gibi çalıştığını doğrulayabilmek ve development cycle’ının daha başlarında tespit edebildiğimiz hataları fixlemeye ve kodladığımız altyapımızı yeniden kullanılabilir olmasına olanak sağlayacaktır.
Terratest’e değinebilmek gerekirse; Terratest, Gruntwork tarafından unit-test case’lerimizi oluşturabilmek adına GoLang ile yazılan bir kütüphanedir.Terraform’un yanı sıra, DockerFile,Packer,Kubernetes gibi bir çok yaygın infrastructure tool’larını da desteklemektedir.
…
Terratest’i neden kullanmalıyız ?
Terratest’i kullanmadan önce aslına bakarsanız; her development projesinde ‘ideal’ de kurgulandığı gibi ‘Test-Driven-Development’ yaklaşımı ile bir test case’i oluşturarak başlanılmalı, test case’inizin başarısız olduğunu kanıtlamak; testinizin başarılı olabilmesi için günün sonunda gerekli olan business’ın yazılması; kurgulanan test case’ini yeniden başlatmak ve önceki testlerinde yanı sıra başarılı bir şekilde geçtiğini doğrulayabilmek adına, yeni bir cycle’a başlamadan önce code’unuzu belki de refactoring ile yeniden düzenleyebilmek için size sunduğu bir avantaj olacaktır.
Kısacası; Infrastructure as Code yaklaşımıyla kodlanarak oluşturulan altyapının, yukarıda sözünü ettiğimiz yaklaşımla birlikte development cycle’ının daha henüz başlarındayken karmaşıklığı (‘complexity’) artan alt yapılarda erken safhalarda fixlemeyi ve daha iyi yaklaşımlarla birlikte ideal (‘clean code’) kod tasarlamayı mümkün kılar.
Terratest’in sağladığı avantajlar neler ?
- Kurgulacağımız test case’leri ile birlikte günün sonunda deploy edeceğiniz infrastructure’ınızı ‘real’ componentler ile deploy etmenizi sağlayacaktır.(‘Deploy Infrastructure’)
- Infrastructure’ınız da sub-method olarak kullandığınız yardımcı componentlerin validasyonunu sağlayacaktır(‘Validate Infrastructure’)
- Test için oluşturduğunuz alt yapıyı ve alt yapı ile alakalı tüm componentleri case tamamlandığında sizin için destroy edecektir.(‘Undeploy’)
- Karmaşıklığı artan altyapınızı erken aşamada, deploy edildiği sırada ki davranışlarını gözlemleme imkanı sunacaktır.
- ‘Infrastructure as Code Quality’ yaklaşımı ile planlanan alt yapınızın kod kalitesini ölçebilme imkanınız olacaktır.
NOT: Kullandığınız bilgisayar Mac ise; Homebrew paket yöneticisi kullanarak aşağıdaki gereksinimleri unit-test case’i oluşturmadan önce kurabilirsiniz;
Gereksinimler
- Go (required version ≥ 1.13)
- Terraform
brew install go
brew install terraform
Örnek olabilmesi için bir DynamoDB tablosunu oluşturabilmek adına; DynamoDB resource’unun test’ini yazmaya çalışacağız.
Örnek DynamoDB Resource’u için (dynamodb.tf)
resource “aws_dynamodb_table” “example” {name = var.table_namehash_key = “userId”range_key = “department”billing_mode = “PAY_PER_REQUEST”server_side_encryption {enabled = true}point_in_time_recovery {enabled = true}attribute {name = “userId”type = “S”}attribute {name = “department”type = “S”}ttl {enabled = trueattribute_name = “expires”}tags = {Environment = “production”}}
Değişkenlerimizi tuttuğumuz variable.tf’i de aşağıdaki gibi olabilir
variable "aws_region" {default = "eu-central-1"}variable "table_name" {description = "The name to set for the dynamoDB table."type = stringdefault = "terratest-example"}
Provizyonlacağımız resource’u ilgili provider’da kullanabilmek için (AWS,Google Cloud,Azure…) provider.tf aşağıdaki gibi olabilir
provider "aws" {region = "${var.aws_region}"profile = "demo"}
Lokalinizde AWS Credentiallar’ı ile oluşturacağınız AWS Profile’ını terminal ekranından şu şekilde çağırabilirsiniz;
export AWS_PROFILE=demo
Bir sonraki aşamada; kodladığınız infrastructure’ınızı deploy etmeden önce; unit-test case’ini oluşturarak, lokalinizde (veya ideal’de bu bir CI/CD süreçleri içerisinde yer alabilir…Alması gerekir) unit-test’inizi koşturabilirsiniz
NOT: import ettiğiniz modülleri unit-test’inizi koşmadan önce; go get <MODULE_NAME> ile lokalinize çekmelisiniz.
package testimport ("fmt""testing"awsSDK "github.com/aws/aws-sdk-go/aws""github.com/aws/aws-sdk-go/service/dynamodb""github.com/gruntwork-io/terratest/modules/aws""github.com/gruntwork-io/terratest/modules/random""github.com/gruntwork-io/terratest/modules/terraform""github.com/stretchr/testify/assert")func Test_ShouldBeCreateDynamoDBTable(t *testing.T) {t.Parallel()awsRegion := "eu-central-1"expectedTableName := fmt.Sprintf("terratest-aws-dynamodb-example-table-%s", random.UniqueId())expectedKmsKeyArn := aws.GetCmkArn(t, awsRegion, "alias/aws/dynamodb")expectedKeySchema := []*dynamodb.KeySchemaElement{{AttributeName: awsSDK.String("userId"), KeyType: awsSDK.String("HASH")},{AttributeName: awsSDK.String("department"), KeyType: awsSDK.String("RANGE")},}expectedTags := []*dynamodb.Tag{{Key: awsSDK.String("Environment"), Value: awsSDK.String("production")},}terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{TerraformDir: "../examples/dynamodb/",Vars: map[string]interface{}{"table_name": expectedTableName,"aws_region": awsRegion,},})defer terraform.Destroy(t, terraformOptions)terraform.InitAndApply(t, terraformOptions)table := aws.GetDynamoDBTable(t, awsRegion, expectedTableName)assert.Equal(t, "ACTIVE", awsSDK.StringValue(table.TableStatus))assert.ElementsMatch(t, expectedKeySchema, table.KeySchema)assert.Equal(t, expectedKmsKeyArn, awsSDK.StringValue(table.SSEDescription.KMSMasterKeyArn))assert.Equal(t, "ENABLED", awsSDK.StringValue(table.SSEDescription.Status))assert.Equal(t, "KMS", awsSDK.StringValue(table.SSEDescription.SSEType))ttl := aws.GetDynamoDBTableTimeToLive(t, awsRegion, expectedTableName)assert.Equal(t, "expires", awsSDK.StringValue(ttl.AttributeName))assert.Equal(t, "ENABLED", awsSDK.StringValue(ttl.TimeToLiveStatus))tags := aws.GetDynamoDbTableTags(t, awsRegion, expectedTableName)assert.ElementsMatch(t, expectedTags, tags)}
Finalde son olarak, unit-test case’imizi koşturmak için ;
go test -v dynamodbtable.go
komutu ile yazmış olduğumuz unit-test case’ini lokalimizde koşturabiliriz.
Yukarıdaki gibi goLang ile oluşturduğunuz unit-test case’inizi koşturduğunuzda terminal ekranında sırası ile aslında terratest’in sizin için init,plan,apply,destroy gibi terraform’un ana yaşam döngüsünde yer alan komutları koşturduğunu ve kurguladığınız infrastructure’ınızın istenilen componentler/parametreler ile oluşturulup oluşturulmadığını görebilmeniz mümkün.
Keyifli okumalar