Docker’a Göç

Merhabalar,

Bu yazıda deploy sürecimizi docker ortamına taşıma ve uygulamamızı dockerize etme süreçlerinden bahsedeceğim. Başlamadan önce Docker, Docker Compose, Bitbucket Pipelines gibi birkaç terimi kısaca açıklayalım.

Docker Nedir?

Docker, işletim sistemi seviyesinde sanallaştırma yapan, Linux Containers(LXC) üzerine kurulmuş bir teknolojidir. LXC 2008 yılında Linux Kernel’e eklenmiştir. LXC işletim sistemi içinde birbirinden izole olmuş şeklinde çalışan Container’lar sağlamaktadır. LXC containerlara işletim sistemi üzerinde sadece kendisi çalışıyormuş gibi soyutlaştırılmış ortam hazırlar.

Docker

LXC 2008 de çıkmasına rağmen popüler olmamıştır. Docker, bu teknolojinin kullanımı kolaylaştırıp detaylardan arındırarak geniş kitlelere ulaşmasını sağlamıştır. Ayrıca docker geliştirmesinin başlarında LXC bağımlılığını ortadan kaldırmak amacıyla LXC teknolojisini libcontainer adında kendi içerisinde tekrardan yazmıştır.

Docker’da temel olarak iki yapı bulunmaktadır. Biri linux kernel ile iletişim kuran Docker Daemon diğeri Docker Daemon ile iletişim kurmamızı sağlayan Docker CLI. Docker CLI, Docker Daemon ile TCP protokolü üzerinden haberleşir. TCP protokolü ile bir Docker CLI ile herhangi bir Docker Daemon’u haberleştirmek mümkündür. Bu özelliğe deploy kısmında ayrıca da değineceğiz.

Container

Birbirinden izole olmuş host işletim sistemi üzerinde çalışan her bir process’e container denir. Containerların yönetimi kolaydır. Durdurma, duraklatma, başlatma işlemleri çok kısa sürede gerçekleşir. Containerler katmanlar halinde image’lerden oluşur.

Image

Container’ın baz alacağı işletim sistemini veya başka image’i, çalıştırılacak uygulamaları içeren dosyalardır. İçeriği Dockerfile adındaki ayar dosyasıyla belirlenir. Katmanlı bir yapısı bulunmaktadır ve güncellerken veya indirirken sadece gerekli katmanlar değişir.

Docker Daemon

Görevi containerları birbirinden izole bir şekilde çalıştırıp containerların gerekli duyduğu ortamı sağlamaktır.

Docker CLI

Docker Daemon ile komutlar ile iletişim kurmamızı sağlar. Container’ın ayağa kaldırılması, image’lerin indirilmesi, push edilmesi gibi işlmeleri Docker Daemon’a iletir.

Docker Compose CLI

Birlikte çalışan containerları ortak bir yerden organize edip kontrol etmek için kullanılan araçtır. Docker-compose.yml dosyası ile ayarlamalar yapılır. Tüm containerlar tek komutla çalıştırılıp, kaldırılabilir.

DockerHub

Image paylaşım platformu diyebiliriz. DockerHub’da topluluğun ürettiği herkese açık(public) image’leri ücretsiz ve sınırsız sayıda indirilebilir. Ayrıca bir şirkete ait veya kişisel olarak gizli image de barındırılabilir ama belli sınırları vardır fazlası için ücret ödemek gerekir.

Bitbucket Pipelines

Bitbucket Pipelines, Bitbucket Cloud’un üzerinde geliştirilmiş, test, build ve deploy (CI/CD) işlemlerini gerçekleştirmemize olanak sağlayan bir üründür.

Bitbucket Pipelines, repostoryimizde yapılan push işleminden sonra içinde kodlarımızın olduğu bir docker containerı oluşturur. Ardından önceden tasarlanmış adımları gerçekleştirir. Tüm adımlar repomuzun ana dizininde bulunan bitbucket-pipelines.yml dosyasından alır.

Deploy Mimarimiz

Uygulamamız backend .net core uygulaması ve frontend ise react uygulaması olan bir sistem. Aynı uygulamayı birden fazla sunucuda barındırıyoruz ve ortak bir bitbucket pipeline ile sunuculara dağıtıp ayağa kaldırıyoruz. (Her bir sunucu farklı müşteri)

Mevcut senaryomuz bitbucket pipeline’ı ile uygulamayı build edip sunuculara ayrı ayrı dosyaları kopyalayıp ayağa kaldırmaktı. Windows işletim sisteminde IIS üzerinde çalıştırıyorduk.

Planladığmız senaryo ise Bitbucket Pipeline’da uygulamayı build edip DockerHub’a pushlamak ve sunuculara, pushladığımız image dosyalarını pull edip ayağa kaldırmasını söylemekti. Bunu Docker CLI ve Docker Daemon ile sağlayacağız.

Hatırlarsanız Docker CLI ile Docker Daemon birbirleriyle TCP protokolü ile iletişim kurduğunu ve Docker CLI ile herhangi bir Docker Daemonun haberleşmesinin mümkün olduğunu söylemiştik. Bunun için Docker CLI olan makinede DOCKER_HOST ortam değişkenine uzak sunucunun bağlantı bilgilerini vermemiz yeterli. Verdikten sonra Docker CLI aldığı tüm komutları uzak sunucudaki Docker Daemon’a iletecektir.

Özet olarak Bitbucket Pipeline bizim Docker CLI olacak ve farklı adımlarda uzak sunucudaki Docker Daemon’lara bağlanıp gerekli komutları ileterek uygullamamızı ayağa kaldıracağız.

Deploy Yapısı

Senaryomuz bu şekilde. İlk yapmamız gereken mevcut uygulamamızı dockerize etmek. Yani docker ortamında çalışır hale getirmek.

Uygulamanın Dockerize Edilmesi

Uygulamamız .net 5 ile çalışmaktadır. Docker ile çalışması için sadece birkaç ayar ekleyip Dockerfile dosyasını oluşturmamız yeterli oldu.

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base`WORKDIR /appFROM mcr.microsoft.com/dotnet/sdk:5.0 AS buildWORKDIR /appCOPY *.sln .COPY App.Web/*.csproj ./App.Web/COPY App.Domain/*.csproj ./App.Domain/COPY App.Data/*.csproj ./App.Data/RUN dotnet restore COPY App.Web/. ./App.Web/COPY App.Domain/. ./App.Domain/COPY App.Data/. ./App.Data/RUN dotnet test — no-restoreFROM build AS publishWORKDIR “/app/App.Web”RUN dotnet publish -c Release -o /app/publish /p:EnvironmentName=ProductionFROM base AS finalWORKDIR /appCOPY — from=publish /app/publish .ENV ASPNETCORE_URLS http://+:5000EXPOSE 5000ENTRYPOINT [“dotnet”, “App.Web.dll”]

Dockerfile da ilk olarak runtime için dotnet aspnet image’ini ve build için dotnet sdk image’ini ekliyoruz. Daha sonra gerekli dosyaları kopyalıyoruz.

Ardından dotnet restore komutunu çalıştırarak kullandığımız paketleri indiriyoruz.

dotnet test ile uygulamamızdaki test projesini çalıştırıyoruz.

Test başarılı bir şekilde tamamlanırsa uygulamayı dotnet publish -c Release -o /app/publishkomutu ile release modunda belirli bir klasöre publish ediyoruz.

EXPOSE 5000 komutu ile hangi port ile container’a ulaşılacağını söylüyoruz.

ENTRYPOINT ["dotnet","App.Web.dll"] komutu ile container çalıştığında ilk çalıştırılacak komutu belirtiyoruz. Yani dotnet uygulamamızı çalıştırıyoruz.

Uygulamamızı docker ortamında çalışır hale getirdikten sonra sıra geldi istekleri karşılamak üzere bir nginx container’ı oluşturmaya.

NGINX

Başlangıçta mail sunucusu olarak yazılmıştır ama sonradan web sunucusu olarak güncellenmiştir. Linux platformunda az kaynak kullanımı, yüksek trafikte iyi performans göstermesi gibi özelliklerinden dolayı popülerdir.

Nginx container’ı için kısa Dockerfile yeterli oldu.

FROM nginx
COPY nginx.conf /etc/nginx/nginx.conf

Baz olarak nginx image i dikkate alınıyor. Ek olarak ayar dosyası vermemiz gerekiyor.

docker-compose.yml

Gerekli containerları oluşturduktan sonra bu containerları birlikte yönetebilmemiz için Docker Compose aracını kullanacağız. Bunun için docker-compose.yml dosyası oluşturuyoruz.

services: nginx:  build: ./nginx  ports:   - "80:80"   - "443:443"  volumes:   - "/certs:/etc/certs"  restart: alwayskestrel: image: company/app:latest environment:  - EnvironmentName=Production
- ConncetionStrings__AbcContext
expose: - "5000" volumes: - "/logs:/app/Logs" restart: always

Sistemimiz biri nginx diğeri kestrel isminde iki servisten oluşuyor. Bu iki servis için gerekli ayarlamaları yapmamız gerekiyor.

NGINX Servisi

build: ./nginx Nginx container’ının yolunu gösterdiğimiz dockerfile dosyasından build ediyoruz.

ports: Bu kısmına port yönlendirmelerini ekliyoruz. Ana bilgisayarın 80 portunu containerın 80 portuna, 443 portunu ise containerın 443 portuna yönlendiriyoruz. Nginx configuration dosyasında ise bu portlara gelen istekleri kestrel 5000 portuna yönlendiriyoruz yani uygulamamıza.Ayrıca conf dosyasındaki kısa ayarlama ile 80(http) portuna gelen istekler 443(https) portuna yönlendiriliyor.

volumes: Bu alanında ise ana bilgisayardaki sertifika dosyalarını koyduğumuz /certs klasörünü container’daki /etc/certs klasörü ile eşleştirdik. Böylece serfitika dosyalarını sunucudaki /certs klasörüne atmamız yeterli oluyor, container içinden /etc/certs yolundan bu sertifikalara ulaşabiliyoruz.

Kestrel Servisi

image:company/app:latest Kestrel yani uygulamamıza ise image kısmında belirtiğimiz isim ile DockerHub üzerinden ulaşıyoruz.

expose: Expose kısmında ise container’ın 5000 portunu sadece diğer servislere açmış oluyoruz.

volumes:Volume alanında logların kalıcı olmasını istediğimizden logs klasörünü eşleştiriyoruz. Yani container durdurulsa veya silinse dahi log dosyaları kalıcı olarak muhafaza ediliyor.

environment: Gerekli ortam değişkenlerinin containera aktarılmasını sağlıyoruz. Bu kısımda değişken değerini bu şekilde name=value verebiliyoruz. Sadece değişken ismi verildiğinde ise ayağa kalkarken bulunduğu ortamın, ortam değişkenlerinden değeri alınıp container’a aktarılacaktır.

Docker compose dosyasını oluşturduktan sonra sıra uygulamamızı ayağa kaldırmaya geldi.

Uygulamayı ayağa kaldırmak için ilk olarak nginx servisini build etmemiz ardından kestrel servisini DockerHub’dan pull etmemiz gerekiyor. Servisleri hazırladıktan sonra docker-compose up komutu ile uygulamamızı çalıştırabiliriz. Bu ayağa kaldırma işlemlerini Bitbucket Pipeline aracılığıyla uzaktaki sunucularda yürüteceğiz.

Bitbucket Pipeline Ayarları

Deploy adımına geçmeden her sunucu için bazı Bitbucket Pipeline ayarları yapmamız gerekiyor. Bitbucket Pipeline ile bağlantı kurabilmek için ssh ayarlamaları ve Bitbucket ayarlarından ortam(deployment environment) oluşturmamız gerekiyor.

Bitbucket Pipeline ve SSH

SSH, uzak sunucuları kontrol etmeye yarayan bir protokoldür. Deploy planımızda Bitbucket Pipeline’ından sunculardaki Docker Daemon’a komut ileteceğimizi ve uygulamamızı ayağa kaldıracağımızdan bahsetmiştik. Bu bağlantıyı SSH ile sağlayacağız. Aşağıdaki komutlar ile sunucumuzda kurulumu yapıp gerekli klasör ve dosyaları oluşturuyoruz.

  • sudo apt update
  • sudo apt install openssh-server
  • mkdir ~/.ssh
  • touch ~/.ssh/authorized_keys

Kurulum yaptıktan sonra Bitbucket ayarlarından aldığımız ssh keyi sunucudaki authorized_keys dosyasına eklememiz gerekiyor.

  • SSH key Bitbucket > Repository Settings > SSH Keys sayfasından kopyalanır.
  • Bitbucket ten alınan ssh key i aşağıdaki komut ile sunucuda authorized_keys dosyasına eklenebilir.
    echo “ssh-key” >> ~/.ssh/authorized_keys

Bitbucket Pipeline’ından uzak sunucuya ssh bağlantısı için gerekli ayarlar bu kadar artık bağlantı kurulup komutlar iletilebilir. Daha fazla bilgiye Bitbucket dökümanlarından ulaşabilirsiniz.

Bitbucket Deployment Environment

Her bir sunucu için Bitbucket > Repository Settings > Deployments sayfasından ortam oluşturmamız ve gerekli değişkenleri eklememiz gerekiyor. Bu değişkenler veritabanı bağlantısı gibi bazı ayarlar.

Bitbucket Pipeline’ındaki herhangi bir adımda, deployment parametresine ortam adını vererek ortam değişkenlerine ulaşabiliyoruz.

Deployment Environments

Örnek olarak aşağıdaki adımda deployment parametresinde Test Ubuntu denilerek bu ortamı kullanılacağı belirtilmiş. Böylelikle Test Ubuntu adındaki ortama eklenen değişkenlere bu adımda ulaşabiliriz.

Step Deployment Parametresi

Uygulama için gerekli değişkenleri her sunucu için ayrı oluşturulmuş ortama ekliyoruz. Ardından bu değişkenleri docker-compose.yml dosyasındaki servisin environment kısmına da ekleyerek containera aktarıyoruz.

DOCKER_HOST

Son olarak oluşturduğumuz her ortama(deployment environment) DOCKER_HOST değişkenini eklememiz gerekiyor. Docker veya Docker Compose CLI varsayılan olarak aynı makinedeği Docker Daemon soketine bağlanır. Başka bir Daemona bağlanmak istiyorsak bağlanacağımız Daemon bilgisini DOCKER_HOST değişkenine vermemiz gerekiyor. Biz de ssh kullanacağımızdan DOCKER_HOST değişkenine ssh://user@ip_adresi bilgisini vererek ayarlarımızı tamamlamış oluyoruz.

Bu ayarlar sayesinde aşağıda bahsedeceğimiz deploy adımındaki komutlar uzak sunuculara iletilecektir. Uygulamamız artık hazır. Sıra bitbucket pipeline’ını tasarlamaya geldi.

Docker Compose ve Bitbucket Pipeline ile Sürüm Geçişi

Bitbucket Pipeline ayarları repomuzun ana sizininde bulunan bitbucket-pipeline.yml dosyası aracığıyla yapılmaktadır.

Bizim bitbucket-pipeline.yml dosyamız bu şekilde

definitions: 
steps:
- step: &deploy
image: abisteknoloji/docker-compose:latest
script:
- docker login --username $USERNAME --password $PASSWORD
- docker-compose pull kestrel
- docker-compose build nginx
- docker-compose up -d
pipelines:
branches:
develop:
- step:
name: React - Build Client
image: 'node:12.14.1'
caches:
- node
script:
- cd App.Web/Client
- npm install --production
- npm install webpack@4.44.2
- npm run webpack-prod
artifacts:
- App.Web/wwwroot/webpack/**
- step:
name: Build docker image and push to Docker Hub
script:
- docker login --username $USERNAME --password $PASSWORD
- docker build -f App.Web/Dockerfile.production -t company/app:latest .
- docker push company/app:latest
services:
- docker
- parallel:
- step:
<<: *deploy
name: Deploy to ubuntu
deployment: Test Ubuntu
- step:
<<: *deploy
name: Deploy to production1
deployment: Test Production 1
- step:
<<: *deploy
name: Deploy to production 2
deployment: Test Production 2
  • Pipeline’da ilk olarak react uygulamasını build ediyoruz.
  • Ardından uygulamamızın Dockerfile dosyası ile build edip image’ini oluşturduktan sonra DockerHub’a pushluyoruz.
  • DockerHub’a ilettikten sonra sıra sunuculara gerekli komutları ileterek uygulamaları ayağa kaldırmasını söyleyeceğiz.

Deploy adımı tekrar kullanılan bir adım olduğundan “yaml anchors” özelliğini kullanarak tanımlayıp tekrar kullanabilir yaptık. Daha fazla bilgi için YAML Anchors.

Deploy adımında docker ve docker-compose araçlarına ihtiyacımız var. Bitbucket Pipeline adımında kısa bir ayar ile docker kullanılabiliyor ama docker-compose kullanımı için kurulum yapmak gerekiyor. Script kısmında bu kurulumu yapmamız gerekiyordu. Biz bunun yerine docker ve docker-compose kurulu abisteknoloji/docker-compose:latest adında image oluşturup DockerHub’a yükledik ve bu adımda kullandık.

  • docker-compose pull kestrel komutu ile docker-compose.yml dosyasında belirtiğimiz kestrel servisini yani uygulamamızı DockerHub’dan indiriyoruz.
  • docker-compose build nginx komutu ile nginx servisini build ediyoruz.
  • docker-compose up -d komutu ile servisleri ayağa kaldırıyoruz. 🎉

Deploy süreci her sunucu için ayrı ve paralel bir şekilde yürütülerek tamamlanıyor. Ve tüm sunuculara uygulamamız dağıtılmış oluyor. Yeni bir sunucuyu basit ayarlar ile deploy sürecimize dahil etmek oldukça kolay olacak 👌

Böylece Docker’a geçiş sürecimizin sonuna geldik. Başka yazılarda görüşmek üzere 👋

--

--