Bu makalede hedefimiz Asp.Net Core ile geliştirilmiş uygulamamızı Github sunucusuna gönderdikten sonra otomatik olarak deploy edilmesini sağlamak. Bu makalede basit bir demo yapacağız. DevOps hakkında genel bir bilgi kazanmak için şuradaki podcast’i dinleyebilirsiniz. DevOps, CI, CD gibi yöntemleri gördükçe “Biz Filezilla ile FTP üzerinden Deploy yapan delikanlı yazılımcılardık ne ara bu noktaya geldik” diyesim geliyor. 🙂

Sürecimiz yukarıdaki resim gibi olacak. Kendi makinemde geliştirmiş olduğum uygulamanın kodlarını Github sunucusuna  göndereceğim daha sonra bir Hook yardımı ile Ubuntu makinemdeki Jenkins’i tetikleyeceğim. Jenkins tarafında Github’dan kodlar çekilecek, testler yürütülecek daha sonra Docker Compose yardımı ile bir Docker image oluşturulacak ve bunlar Container olarak ilgili portlar da çalıştırılacak. Makinemizde 80 portunu Nginx dinleyecek ve yönlendirme yapacak. Bu bir demo olduğu için test, build ve deploy aşamalarını aynı makine üzerinde yapacağım. Bu süreç her firmanın Deployment stratejisine göre değişebilir.

Makalede ilgili teknolojilerin ve araçların ne olduğunu çok detaylı bahsetmeyeceğim çünkü makalenin konusu CI (Continuous Integration) sürecini gerçekleştirmek. Başlamadan önce aşağıdakilerin bilinmesinde fayda var:

-Docker için şuradaki seriyi okumanızı tavsiye ederim

-DevOps için video, Continuous Integration,Continuous Delivery, Jenkins

-Nginx ve Reverse Proxy

-Asp.Net Core

1)Basit bir Asp.Net Core uygulaması oluşturmak

Ben MacOs’da cli kullanarak uygulamayı oluşturacağım, Windows’da Visual Studio ile cli kullanmadan da projeyi oluşturabilirsiniz.

Yukarıda cli yardımı ile projemizi oluşturduk daha sonra git klasörümüzü oluşturduk, .gitignore dosyasının içeriğini projenin github repo’sunda bulabilirsiniz. Core.Web içersinde  dotnet run komutu ile projemizin çalışmasını test ettikten sonra ufak bir unit test yazalım.

dotnet add reference ../CoreApp.Web/CoreApp.Web.csproj  komutu ile projemizi test projesine referans olarak ekleyelim.

dotnet add package Microsoft.AspNetCore.All paketini eklemeyi unutmayalım 🙂

Testimizde Index metodumuzun döndürdüğü değerin tipini kontrol ediyoruz.  dotnet test ile testimizi çalıştırabiliriz.

Şimdi Dockerfile’ı oluşturuyoruz, Makale konusunun dışına çıkmamak için, linkini verdiğim Docker yazı serisini okuduğunuzu düşünerek Docker ile ilgili kısımları açıklamıyorum.

.dockerignore dosyasını oluşturup aşağıdaki satırları eklemeyi de unutmayalım.

docker-compose.yml dosyamız

Docker compose ile bir önceki adımda oluşturduğumuz Docker image’i 3 ayrı portu dinlemek üzere Container olarak ayağa kadırıyoruz. Bunu yapmamızda ki amaç siteye gelen yükü dengelemek.

2)Sunucuda Docker, Jenkins ve Nginx Ayarları

Sunucu makinem için Amazon Web Services’de bir ubuntu makine ayağa kaldıracağım.  Yalnız makineyi oluştururken aşağıdaki gibi 80 portu ve 8080 portuna izin verelim. Bu arada 8080 portu için altdaki uyarıyı dikkate alıyoruz ve sadece kendi ip adresimize izin veriyoruz. 🙂 Bu ayar daha sonradan Security Group kısmından da yapabilirsiniz. Not: Tam bu makaleyi yazdığım sırada Feedly’de Nginx ile alakalı taze taze dumanı üstünde yeni bir yazı yazıldığını fark ettim, isteyenler şu linkden yazıyı okuyabilir.

-Nginx, Ubuntu’da yeni bir kullanıcı oluşturup yetki işlemleri ile uğraşmadan  sudo su  ile root üzerinden işlemlerinizi yapabilirsiniz. Nginx kurulumu için Digital Ocean’da ki  şu  tutorial ile kurulumu yaptıktan sonra makinenin public dns veya public ip adresi ile tarayıcı üzerinden test yapabilirsiniz.

cd etc/nginx/sites-enabled/ dizinindeki default dosyamızı aşağıdaki gibi yapılandırıyoruz.  service nginx restart Nginx’i yeniden başlatmayı unutmayalım.

Bu aşamada sitemizde portları dinleyen bir uygulama olmadığı için Nginx hata verecektir.

-Docker kurulumu için link  docker info   ile kurulumun başarılı olduğunu test ettikten sonra  apt install docker-compose  ile docker compose yükleyelim.

-Jenkins kurulumu için link daha sonra yeni bir job oluşturalım.

 

Aşağıdaki iki resimde şu işlemi yaptık, Github proje linkimizi belirttik ve Master dalına kodlar gönderildiğinde Jenkins’i tetiklenmesini sağladık. Bu bir örnek olduğu için çok detaylı bir ayar yapmayacağız ama gerçekdeki bir senaryo da şöyle olabilir: Projede yeni bir özellik için açılmış olan Branch’e kodlar gönderildiğinde Jenkins tetiklenir, Build ve test işlemleri yapılır daha sonra Jenkins release Branch’ine kodları gönderilir ve ilgili sunuculara Deploy yapılır.

Execute Shell kısmına aşağıdaki komutları ekleyelim, komutları çalıştırmak için Jenkins’de yetki hatası alırsanız,  sudo visudo dosyasına(/etc/sudoers),  jenkins ALL=(ALL) NOPASSWD: ALL ekleyelim ve bu dosyayı temp olarak değil üzerine yazarak kayıt edelim.  service jenkins restart ile Jenkins’i yeniden başlatalım.

Yukarıdaki shell komutları ile şu işlemler gerçekleşicek; İlk önce testlerimizi çalıştırılacak ve bir hata almazsak devam edip önceki docker container’lar durdurulacak ve tekrar yeni bir image oluşturacak. Daha sonra ilgili portları dinlemek üzere container’larımız ayağa kaldırılacak. Tabi burada şöyle bir sıkıntı ortaya çıkıyor oda şudur ki; Docker’ın çalışma prensibi gereği bu işlemler sırasında birçok untagged images oluşturuluyor.  docker rmi $(docker images | grep "^<none>" | awk "{print $3}") Bu komut ile ister manuel ister otomatik olarak çalıştırıp gereksiz Images’leri silmek de fayda var. 🙂

Şimdi Github Repo’muza Jenkins’i tetikleyecek bir Hook ekleyelim.

Artık sürecemiz tamamlanmıştır, videoda demoyu izleyebilirsiniz.

Sonuç

Videoda bahsettiğim gibi bu süreci production seviyesinde kullanmak o kadar kolay değil, öncesi ve sonrasını iyi düşünmek gerekiyor. Örneğin ilk aklıma gelen problem resimler, kullanıcıdan aldığımız resimleri nereye kayit edeceğiz. Container da olamaz. Çünkü bir sonraki release de o container silinecek. Bu yüzden yüklenen resimler başka bir klasörde saklanmalı; hatta statik dosyalar için gelen istekleri Nginx’e bırakılması daha iyi olur, çünkü Nginx’in Cache’leme mekanizması çok daha iyi. Aynı şekilde kullanıcı oturum bilgilerinin nerede saklanacağı da düşünülmesi lazım, Çünkü Container’lar birbirinden bağımsız çalışıyor. Bir sonraki isteğinde başka bir Container’a yönlendirildiğinde, oturum bilgileri bir önceki Container’da kalmış olacak bu yüzden bu bilgiler Container da değil ortak bir yerde tutulması gerekiyor. Bu gibi sıkıntıların çözümleri ile ilgili makaleler yazmayı hedefliyorum.

 

Kaynak Kodlar : https://github.com/enesdalga/CoreApp