“Kod” yazmak aslında çok eskiye dayanan bir iletişim yöntemi. Mağaralara, duvarlara, kitaplara belli işaretler ile bazı kavramları, bazı insanlara anlatmak için insanoğlunun çok eskiden beri çabaladığı bir iletişim yöntemi. Artık bilgisayar denen cihazı yarattığımız ve yaşantımızda önemli noktalara yerleştirdiğimiz için onunla iletişime girebilmemiz için de “kod” yazmak daha da önemli hale geldi. Bilgisayarların anlayabildiği diller ile onların çözümleyebileceği kompleks problemleri çözmek ve bazı ihtiyaçları gidermek artık insan hayatının önemli bir parçası haline geliyor…
Offff yeter, bu kadar felsefe yeter, konumuza geçelim. 😜 Son bir kaç zamandır “Infrastructure as Code(IaC)” kavramı ile ilgili ellerimi kirletiyorum. Daha önce sadece okuduklarım kadar bir hakimiyetim vardı kavrama. Artık biraz dokunmanın da zamanı geldi sanırım. Açıkcası IaC ile ilgili çok uzun uzun bir şeyler anlatacağım bir yazı olmayacak. Biraz daha, anahtar kelimelerin bol olacağı, “Infrastructure as Code(IaC) nedir?”, “Neden gerekir?” ve “Avantajları nedir?” gibi sorulara cevap olabilecek ve IaC kavramı ile tanışmak isteyenlere yol gösterebilecek bir yazı olacak. Hadi bakalım…
Nedir bu Infrastructure as Code(IaC)?
“Kod olarak altyapı” diye direkt chicken translate tadında çevirince çok çirkin oluyor. Ama anlamaya başlamak için birkaç ip ucu veriyor. Yazılım çözümlerinin çalıştığı altyapılar artık sadece elektronik birer kutu değil. Günümüzde hele hiç değil. Altyapılar da ihtiyaç çerçevesinde karmaşık bir hal alabiliyor. Yönetmesi zorlaşıyor. Yönetmeye gelene kadar kurması ve altyapıları ayağa kaldırmak zorlaşıyor. Bu zorlukların, zaman ve maddi açılardan götürüleri de oluyor. Günümüzde de önemli konular. Artık “kod” yazarak belli problemleri çözmeye başladığımız için bu problemleri de kod yazarak çözebilir miyiz diye ortaya çıkan bir kavram IaC. Özetle ve basitçe; IaC için altyapıların oluşturulması, kurulması, güncellenmesi, kopyalanması ve ayağı kaldırılması konularını güvenilr ve hızlı bir şekilde yapabilmek için reçete hazırlama diyebilirim. En azından ben bu şekilde bakarak biraz daha kolaylaştırmaya çalışıyorum. Bu reçetenin belli bir standart içinde oluşturulması için de Chef, Terraform, Puppet, Ansible, Google Cloud Deployment Manager, Azure Resource Manager, AWS CloudFormation gibi farklı teknolojiler mevcut.
Peki neden?
İşletim sistemi kurulumu, işletim sisteminde ayarlar, sunucu yaklaşımı ile alakalı ek kurulumlar, network ayaları gibi birçok işlemi tek tek yapmak gerekiyor ki, çözüm sağladığımız yazılımlar çalışabilsin. Bir çözüm için yaptığımız kurulum ve ayarları, çözümü çokladığımız zaman tekrar olarak karşımıza çıkar ve tekrar uğraşırız. Bu sefer atladığımız bir ayar ikinci çözümün farklı ya da hatalı çalışmasına sebep olur. Uğraş dur sonra… 🙄
ABC firması olarak çözümümüzün kurulumlarını ve ayarlarını 2-3 günde yapabiliyor diyelim. Ama rakip XYZ firmasının kendi çözümünü 1 günde yapabilmesi, çözümümüzün tercih edilmesini olumsuz bir şekilde etkileyebilir. Hızlı ve değişime kolay adapte olan balıklar artık okyanusta daha havalı.
Bu yaklaşımlardan dolayı belli bir reçeteyle güvenilir bir altyapıyı ve çözümü hızlıca ayağa kaldırabilmek artık önemli.
Altyapı, altyapı dedin durdun da altyapı nerede?
Yazılım çözümlerinin altyapısı dediğimiz olay, günümüzde artık yavaş yavaş “cloud” platformlarda oluşmaya başlıyor ya da taşınmaya çalışılıyor; hepimizin bir şekilde aşina olduğu üzere. Belli kural ve regülasyonlardan dolayı “cloud” platformlarda olamayan altyapılar da belli bir düzen, güvenilirlik içinde hızlıca oluşturulmaya çalışılıyor. Bu noktada IaC teknolojileri, altyapıların hem “cloud” platformlarda, hem de yerinde oluşmasına fayda sağlıyor. Ama “cloud” platformlar için daha fonksiyonel ve anlamlı olduğunu düşünüyorum. Ki zaten yerinde çalışan bir çözümün “cloud” platformlarda çalışabilmesi için yapılması gerekenler ile ilgili bir senaryo ile devam edeceğim.
Infrastructure as Code(IaC) olaylarını daha iyi anlamak için bir senaryomuz olsun. ProductX adında bir web uygulaması şeklinde bir ürünümüz olsun. Bu ürünü müşterilerin kendi sunucularına gidip tek tek kurmak, kurumlardaki riskler, kendi sunucusu olmayan müşteri gibi farklı durumlar karşımıza çıksın. Bu durumlar baş ağrısı şeklinde bizi çok yoruyor olsun. Ya da şirket iş modeli artık “cloud” platformlarda çözüm sağlamak şeklinde değişsin ve “on-premise” şeklinde sunulan çözümler “cloud” plaftormlara taşınsın. Benim senaryo diye sıktığım bu olay, eminim bir çoğumuzun karşısına çıkmakta.
Şimdi bu basit senaryo için; ProductX adındaki ürünümüz bir ASP.NET Core uygulaması olsun. IaC yaklaşımı ile Terraform kullanarak, uygulamamızım Azure üzerinde altyapısını oluşturalım. Bu altyapıyı Azure DevOps ile müşterilere özel bir şekilde yapalım. Ürünümüzü yine Azure DevOps ile altyapımıza kuralım ve gerektiğinde ürünümüzün versiyonunu güncelleyelim.
Açıkçası tek tek bütün bunların ayrıntısına girmeyeceğim. Yukarıda da dediğim gibi belli anahtar kelimeler ve örnekler ile sadece bir yol haritası çizmeye çalışıyorum. Ellerinizi kirletmeyeceğim, nasıl kirletebileceğinizi göstereceğim. 😃
Terraform ile altyapımızı oluşturalım…
Terraform, IaC yaklaşımı ile altyapılarımızı oluşturmamızı sağlayan bir araç. Kendi json/yaml benzeri bir konfigürasyon şeması/dili(HCL) var. HCL ile oluşturduğumuz reçeteleri belli “resoruce” yapıları ile ilgili “provider” ile çalıştırıp altyapıların oluşturulması ve ayarlanmasını sağlıyor. Nasıl kuruluyor, nasıl çalıştırılıyor ve daha fazlasını kendi sitesinden ayrıntılı bir şekilde öğrenebilirsiniz. Aşağıda ProductX ürünümüzün Azure’da altyapısını App Services ve SQL Server şeklinde oluşması ve ayarlanması için reçetemizi oluşturuyoruz.
provider "azurerm" { version = "=1.38.0" subscription_id = "__SUBSCRIPTION-ID__" client_id = "__CLIENT-ID__" client_secret = "__CLIENT-SECRET__" tenant_id = "__TENANT-ID__" } locals { customer ="__CUSTOMER__" apikey ="__APIKEY__" version ="__VERSION__" } # Rastgele şifre oluşturmak için bir modül resource "random_password" "dbpassword" { length = 16 special = true override_special = "_%@" } # Azure üzerinde önce "resource" oluşturuyoruz. resource "azurerm_resource_group" "someproduct" { name = "${local.customer}-someproduct-resources" location = "West Europe" } # Azure üzerinde bir SQL Server oluşturuyoruz # İlgili ayarlar yapıp şifre belirliyoruz resource "azurerm_sql_server" "someproduct" { name = "${lower(local.customer)}-someproduct-sqlserver" resource_group_name = "${azurerm_resource_group.someproduct.name}" location = "${azurerm_resource_group.someproduct.location}" version = "12.0" administrator_login = "4dm1n157r470r" administrator_login_password = "${random_password.dbpassword.result}" } # Oluşan SQL Server'a veri tabanı özellikleri ile veri tabanı oluşturuyoruz resource "azurerm_sql_database" "someproduct" { name = "${lower(local.customer)}-someproductdb" resource_group_name = "${azurerm_resource_group.someproduct.name}" location = "${azurerm_resource_group.someproduct.location}" server_name = "${azurerm_sql_server.someproduct.name}" edition = "Basic" collation = "SQL_Latin1_General_CP1_CI_AS" create_mode = "Default" requested_service_objective_name = "Basic" } # SQL Server için firewall ayarları resource "azurerm_sql_firewall_rule" "someproduct" { name = "${local.customer}-allow-azure-services" resource_group_name = "${azurerm_resource_group.someproduct.name}" server_name = "${azurerm_sql_server.someproduct.name}" start_ip_address = "0.0.0.0" end_ip_address = "0.0.0.0" } # Azure App Service için bir plan tanımı resource "azurerm_app_service_plan" "someproduct" { name = "${local.customer}-someproduct-appserviceplan" location = "${azurerm_resource_group.someproduct.location}" resource_group_name = "${azurerm_resource_group.someproduct.name}" kind = "Linux" reserved = true sku { tier = "Basic" size = "B1" } } #Azure App Service uygulaması ve özellikleri resource "azurerm_app_service" "someproduct" { name = "${local.customer}-someproduct-app" location = "${azurerm_resource_group.someproduct.location}" resource_group_name = "${azurerm_resource_group.someproduct.name}" app_service_plan_id = "${azurerm_app_service_plan.someproduct.id}" site_config { linux_fx_version = "DOTNETCORE|3.1" } app_settings = { "APIKey" = "${local.apikey}" "CustomerName" = "${local.customer}" "Version" = "${local.version}" } connection_string { name = "DefaultConnection" type = "SQLAzure" value = "Server=tcp:${azurerm_sql_server.someproduct.fully_qualified_domain_name}.database.windows.net,1433;Initial Catalog=${azurerm_sql_database.someproduct.name};Persist Security Info=False;User ID=${azurerm_sql_server.someproduct.administrator_login};Password=${random_password.dbpassword.result};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;" } }
Terraform ile bir yazılım çözümünün çalışabilmesi için gerekli altyapıyu ve konfigürasyonları bu şekilde tanımlayabiliyoruz. Bu örnekte Terraform’un Azure bileşenini(azurerm) kullanarak, Azure üzerinde bir uygulama için gerekli olabilecek kaynakları(resource) yaratmak için reçetemizi oluşturuyoruz.
Terraform‘un sağladığı özellikler ile, reçeteleri yazarken bir çok modül, bileşen özelliği, bağımlılık yönetimi, temel gerekli fonksiyonlar kullanmak mümkün olabiliyor. Yukarıdaki örnekte, SQL Server için gelişigüzel bir şifreyi üretip ayarlıyor, reçete de kullanmak için değişken tanımlayıp kullanmak gibi basit örnekleri görebilirsiniz.
- Terraform ile ayrıntılı bilgiler ve örnekler için buradan başlayabilirsiniz.
- Terraform reçetelerini Azure, Google gibi farklı platformlar için oluşturmak için “provider” ve “resource”‘lara da GitHub üzerinden ulaşabilirsiniz.
ProductX
Şimdi de ProductX ürünümüzü derleyelim ve paketleyelim ki, oluşturacağımız altyapıya kolayca kurabilelim. Burada uygulamamızı derlemek için Azure DevOps Pipelines üzerinde çalışması için bir *.yaml dosyası oluşturuyoruz.
trigger: - master pool: vmImage: 'ubuntu-latest' variables: buildConfiguration: 'Release' steps: - task: UseDotNet@2 displayName: 'Use .Net Core' inputs: version: '3.1.x' - script: dotnet build --configuration $(buildConfiguration) displayName: 'dotnet build $(buildConfiguration)' - task: DotNetCoreCLI@2 displayName: 'dotnet publish' inputs: command: 'publish' publishWebProjects: true arguments: '--configuration $(BuildConfiguration) --output $(Build.ArtifactStagingDirectory)' zipAfterPublish: True - task: CopyFiles@2 displayName: 'Copy files' inputs: SourceFolder: 'Infrastructure' Contents: '**' TargetFolder: '$(build.artifactstagingdirectory)/Infrastructure' CleanTargetFolder: true OverWrite: true - task: PublishBuildArtifacts@1 displayName: 'Publish artifact' inputs: PathtoPublish: '$(Build.ArtifactStagingDirectory)' ArtifactName: 'drop' publishLocation: 'Container'
GitHub’dan kodları alıyoruz, derliyor ve “publish” edip üretim ortamı için hazır hale getirip paketliyoruz. Altyapımız için oluşturduğumuz reçeteyi de Azure DevOps üzerinde çalıştıracağımız için “CopyFiles” görevi ile reçete dosyalarını da paketimize kopyalıyoruz. Paketimizi kurulum için hazırlıyoruz.
- Azure DevOps Pipelines ile ilgili hem UI hem de *.yaml olarak konfigürasyonlar hakkında tüm bilgileri öğrenmek için buradan başlayabilirsiniz.
Altyapı reçetemizi çalıştırıyoruz…
Altyapı için oluşturduğumuz reçeteyi yine Azure DevOps üzerinde çalıştırıp, altyapıyı ilgili müşteri için oluşturuyoruz. Bunun için Azure DevOps Releases‘de ilgili Terraform görevlerini kullanıyoruz. Reçetemiz çalıştıktan sonra da yine Azure DevOps Releases’deki görev ile kurulum için oluşturduğumuz paketi, Azure üzerindeki ilgili servise kuruyoruz.
- Yukarıdaki görsel de Terraform reçetemizi çalıştırmak için Terraform’un init, plan, apply komutlarını kullanıyoruz.
- Replace Tokens görevi ile Terraform reçetemiz içindeki __CLIENT-ID__ gibi değişkenleri Azure DevOps Variables ile değişiyoruz. Bu sayede konfigürasyon dosyalarımız içine gömülü kritik bilgileri yönetebiliyoruz.
- Azure DevOps Releases ile ilgili daha fazla ayrıntı için buradan başlayabilirsiniz.
Bütün bunları yine *.yaml olarak da kodla yapabiliyoruz. Ama açıkçası yönetim biraz daha çeşitli ve kolay olsun diye Azure DevOps önyüzünü bu senaryoda da göstermek istedim. Yukarıdaki önyüzlerin *.yaml karşılıklarını da birazdan aşağıda bahsedeceğim GitHub projesinden görebilirsiniz.
Bu şekilde bir yaklaşım ile, aynı ürünü farklı müşteriler için, tamamen müşteriye özel bir altyapıda, müşteriye özel versiyon ve konfigürasyonlar ile “cloud” bir platformda hızlı ve güvenilir bir şekilde sunabiliyoruz.
Bu senaryomuz için ürettiğimiz çözümde, yukarıdaki görseldeki gibi ProductX ürünümüz TEST müşterisi için ayrı ve farklı bir kaynak olarak kurulabiliyor. CustomerXYZ için de aynı şekilde ona özel bir altyapı ile kullanılabiliyor. CI/CD yaklaşımı ile de birine v0.21 versiyonu olurken diğerine v0.22 versiyonu olabiliyor.
Çok basit bu senaryonun, gerçek varyasyonları ciddi anlamda çok değer katan çözümler oluyor. “cloud” platformların avantajları ile güvenilir bir altyapıda hızlıca ürünleri müşterilere sunabilmek günümüzde oldukça değerli. IaC yaklaşımı yanında yazılım çözümlerinin de DevOps bakış açısı ile dağıtılabilir olması, içeride üretilen yazılım çözümlerinin kalitesine de değer katıyor.
Everything As Code 😃
Yukarıdaki örnekleri ve örnek ProductX projesini GitHub‘a ekledim, isterseniz oradan alıp üzerinde oynayabilirsiniz.
Dilim döndüğünce “Infrastructure as Code(IaC)” konusunda ellerinizi nasıl kirletebilirsiniz konusuna biraz giriş olması için bir şeyler karalamaya çalıştım. Umarım faydalı olmuştur. İlerleyen yazılarda IaC kavramına, hatta belki bazı teknolojilere daha da dokunabilirim. Takipte kalın… 🙄