Öncelikli olarak uzun bir sürenin ardından blog yazısı yazdığım için büyük ihtimalle cümleler çok düşük ve imla hatalarıyla dolu olacaktır eğer bu konuda takıntılıysanız okumamanızı tavsiye ederim. Bu yazıda sizlerle Gitlab da ki deponuz üzerinde Gitlab CI yapılandırmayı ele alacağız. Bu yazı bünyesinde CD sürecinden yani uygulamayı yayına alma kısmına değinmeyeceğim fakat başka bir yazıda bunu ele almak planlarım içinde bulunmaktadır. Bu yazıyı en iyi şekilde anlayabilmeniz için şu konularda bilginiz olması daha iyi olacaktır; git, docker ve CI (continuous integration). Şimdi hazırsanız var olan bir depomuza Gitlab CI yapılandırması nasıl yapılır bakalım.
Ön Bilgiler
İlk olarak Gitlab nedir ondan kısaca bir bahsedelim. Gitlab, sizlere uzak bir git deposu hizmeti sunan ve bunu bir ara yüze de aktaran bir servistir. Daha çok bilinen alternatifi Github’dır. Gitlab’ın hem açık kaynak hem de ücretli versiyonu bulunmaktadır. Gitlab’ı isterseniz kendi sunucunuza isterseniz de gitlab.com üzerinden kullanabilirsiniz.
Gitlab CI, Gitlab’ın geliştirdiği Gitlab Runner ile Gitlab’ın size sunduğu bir CI hizmetidir. Gitlab Runner’ın temel görevi Gitlab’a bağlanıp komut dinlemektir. Gitlab ilgili bir CI süreci olunca kuyruğa ekler ve Runner bunu kuyruktan alarak konfigürasyonunuza göre uygulamayı çalıştırır ve sonucunun çıktısıyla birlikte Gitlab’a iletir ve Gitlab’da bunları size gösterir. Gitlab’ı kendi sunucunuza kurduysanız Gitlab Runner’ı da kendiniz kurmanız gerekmektedir. Oldukça basit bir kurulum betiği bulunmaktadır. Bu betiği çalıştırarak Gitlab Runner’ı ayağa kaldırabilirsiniz. Gitlab Runner’ı isterseniz docker üzerinden çalıştırması için yapılandırabilirsiniz isterseniz kurduğunuz fiziksel makine üzerinden hizmet vermesini isteyebilirsiniz. Fiziksel makine üzerinden hizmet vermesini neden isteyeyim diyebilirsiniz hemen bir örnek vermek gerekirse; eğer fiziksel olarak makinaya bağlı bir cihaza kod gönderip çıktısını almanız gerekiyor ise bu durumda hayatınızı kurtaracaktır. Gitlab Runner’ın kurulumu ve yapılandırması için burayı inceleyebilirsiniz.
Gitlab üzerinde bulunan deponuzun kök dizininde eğer .gitlab-ci.yml adında bir dosya bulunuyorsa Gitlab bu dosyayı okur ve anlamlandırır. Depoya yaptığınız hep push işleminde Gitlab kök dizinde varsa .gitlab-ci.yml dosyasını okur ve size ait bir ardışık düzen(pipeline) oluşturur. Ardışık düzen içinde varsa sizin tanımladığınız aşamalar(stage) ve bu aşamaların adımları(step) bulunmaktadır. Tüm bu ön bilgilerin ardından artık asıl konumuz olan Gitlab CI’ı yapılandırma işlemine geçebiliriz.
Gitlab CI’ın uygulamamızı bir docker container’i içinde çalıştırdığını yukarıda size belirtmiştim. Bu sebep ile ilk olarak eğer varsayılan bir docker imajı işinizi görmüyorsa uygulamanıza ait bir docker imajı oluşturmanız gerekmektedir. Docker Hub’da bulunan bir imaj ihtiyacınızı karşılıyorsa “Docker İmajı Oluşturma” adımını görmezden gelebilirsiniz eğer bulunan imajlar ihtiyacınızı karşılamıyor ise “Docker İmajı Oluşturma” adımını adım adım yapıp Gitlab’ın Container Registry servisine göndereceğiz.
Docker İmajı Oluşturma
Docker imajı oluşturmak için projemizin kök dizininde Dockerfile adında bir dosya oluşturmamız gerekmektedir. Ardından temel imajımızı seçmemiz gerekiyor. Ben Gitlab CI’ı bir rails uygulaması için yapılandıracağım sizde buradaki yapılandırmayı kendi uygulamanıza göre yapmanız gerekmektedir. Kendinize uygun olan imajı Docker Hub üzerinde arayarak bulabilirsiniz. Ben ruby 2.7.0 versiyonunun alpine dağıtımı üzerinde olanını temel imaj olarak kullanacağım. Bunu belirtmek için de FROM ruby:2.7.0-alpine
şeklinde bir direktifi Dockerfile içine yazıyorum. Ardından uygulamamızın ihtiyaç duyduğu bağımlılık varsa bunları da kurmak için alpine dağıtımının paket yöneticisi olan apk’yı kullanarak bu paketleri kuruyoruz. Rails uygulamalarında nokogiri ve tzdata bağımlılığı bulunmaktadır buna ek olarak veri tabanınıza bağlanmak için kullandığınız veri tabanının geliştirme kütüphanelerine ihtiyaç duyulduğu için bunlarda sistemde bulunmalıdır. PostgreSQL veri tabanı olan bir rails uygulaması için şu paketlerin olması gerekmektedir; build-base libxml2-dev libxslt-dev postgresql-dev tzdata tüm bunları Dockerfile içine şu şekilde ekliyoruz; RUN apk add –update build-base libxml2-dev libxslt-dev postgresql-dev tzdata && rm -rf /var/cache/apk/*
burada kurulumlardan sonra /var/cache/apk altını temizlememizin amacı imajımızda gereksiz dosyaları silerek boyutu düşürmeye çalışmaktır. Temel bir rails uygulaması için bu kadar bir Dockerfile işimizi görecektir. Dockerfile’ımızın son hali şöyle olmalıdır;
FROM ruby:2.7.0-alpine
RUN apk add --update \
build-base \
libxml2-dev \
libxslt-dev \
postgresql-dev \
tzdata \
&& rm -rf /var/cache/apk/*
Şimdi sırada Dockerfile’ı inşa etmek ve bunu Gitlab Container Registry’e kaydetmek var. Bunun için deponuzda bulunan Packages’da Container Registry’e tıkladığınız zaman aşağıda ki şekilde bir ekran göreceksiniz.
Bu ekranda üç adet komut gözükmektedir sırası ile bunlar;
- Gitlab’ın Container Registry servisinde oturum açmanızı sağlar. Eğer hesabınızda iki adımlı doğrulama aktif ise Access Token oluşturmanız için bir hata mesajı alacaksınız hata mesajında ki linke tıklayarak bir Personel Access Token oluşturup parola kısmına yazmanız gerekecektir.
- Hazırladığımız Dockerfile’ı yerelimizde inşa etmemizi sağlar.
- Yerelimizde hazırladığımız imajı Gitlab’ın Container Registry servisine göndermemizi sağlar.
Bu üç aşamayı da tamamladıktan sonra Gitlab’da deponuza gidip Packages altında Container Registry’e tıklayınca pushladığımız imajı görebilmemiz gerekiyordur şu şekilde bir görüntü oluşacaktır;
Bu şekilde görüntü aldıktan sonra artık uygulamamızın ihtiyaç duyduğu özelleştirilmiş docker imajı hazır demektir ve sırada Gitlab CI’ın yapılandırması kaldı demektir.
Gitlab CI Yapılandırması
Gitlab CI’ı depomuzda yapılandırmak için proje kök dizininde .gitlab-ci.yml adında bir dosya oluşturuyoruz ve yapılandırma ayarlarmızı bu dosya içine yazıyoruz. Dosyamızın uzantısından da anlaşılacağı üzere yaml formatlı bir yapılandırma direktifleri yazacağız. Uygulamamız bir docker container içinde ayağa kaldırılacağı için ilk olarak bir docker imajı seçmemiz gerekmektedir. Bunun için Docker Hub’da ki imajlardan birini veya depomuzun container register’ına attığımız bir imajı kullanabiliriz. Kullanmak istediğimiz imajı image anahtarına değer olarak yazıyoruz. Bunun için yapılandırma dosyamıza aşağıda ki gibi bir satır ekliyoruz;
image: 'registry.gitlab.com/PROJECT_SCOPE/PROJECT'
Yukarıda ki satırda docker container’ımıza Gitlab Container Registry’den bir imaj kullanmak istediğimizi belirttik. Şimdi sırada her adımda çalıştırılması gereken komutlarımızı belirtmek var. Örnek olarak her adımda uygulama bağımlılıklarımızı indirip kurmamız gerekmektedir. Bunu rails’de bundle install
komutu ile yapmaktayız. Her adımda çalıştırılmasını istediğimiz komutları before_script
anahtarının altında bir dizi olarak yazıyoruz.
before_script:
- 'bundle install --jobs 4'
Bu deklarasyon ile artık her adımdan önce container içinde bundle install komutunun çalıştırılmasını sağladık eğer sizin istediğiniz farklı komutlar varsa buraya onları yazabilirsiniz. Eğer birden fazla komut çalıştırmak istiyorsanız diğer komutları alt alta ekleyebilirsiniz.
Şimdi yukarıda yaptığımız işlem her adımdan önce çalıştırıldığı için haliyle bir performans kaybına neden olacaktır. Bunun için bu kütüphanelerin bulunduğu dizini cache’e ekleyerek gitlab ci’a bu dizini belirtebilir ve her adımdan önce container’a bu dizinin bağlanmasını sağlayarak performans kazanımı sağlayabiliriz. Cache’e eklemek istediğimiz dizinleri cache anahtarının altında paths’e dizi olarak veriyoruz.
cache:
paths:
- 'vendor/bundle'
Yukarıda ki deklarasyon da vendor/bundle
dizinini cache’e ekledik böylece artık bu dizin cache’e kaydedilecek ve container ayağa kalkmadan önce container’e bağlanacak. Artık cache kullandığımıza göre her seferinde bundle install yapmak yerine gerektiğinde bundle install yapmak için komutumuzu aşağıda ki şekli ile güncelliyoruz.
before_script:
- 'bundle check || bundle install --jobs 4'
Artık uygulamamız gitlab-ci tarafından çekiliyor ve bağımlılık kütüphaneleri indirilip cacheleniyor. Şimdi sırada aşamalarımızı tanımlayarak bu aşamalara adımlar bağlamak var. Bunun için de stages
‘i kullanıyoruz. Bizim için şuanda Rubocop’u Lint’e Brakeman’i Security’e Rspec’i ise Test aşaması altına ekleyeceğim. Burada ki isimler tamamen size bağlıdır. Yukarı da belirttiğim aşamaları şu şekilde ekliyorum;
stages:
- 'Lint'
- 'Test'
- 'Security'
Artık aşamalarımızı oluşturduk şimdi bu aşamalara adım eklemek kaldı. İlk olarak rubocop’u sonra brakeman’ı sonrada rspec’i ekleyeceğim. Burada her adımın adını vererek çalıştırılacak komutu ve bulunacağı aşamayı belirtiyoruz. Aşağıda ki şekilde dediğim 3 adımıda kendi aşamalarına ekliyoruz;
rubocop:
stage: 'Lint'
script:
- 'bundle exec rubocop'
rspec:
stage: 'Test'
script:
- 'bundle exec rails db:setup'
- 'bundle exec rspec'
brakeman:
stage: 'Security'
script:
- 'bundle exec brakeman -z -q'
Uygulamamızda bazı adımların çalışması için veri tabanı gibi harici servisler gerekebilmektedir bunları ise services anahtarının altına dizi olarak ekleyebiliyoruz. Bizim uygulamamız da postgresql gerektiği için burada postgresql’i aşağıda ki şekilde yapılandırma dosyamıza ekliyoruz;
services:
- 'postgres:11.5'
Şimdi sırada ortam değişkenlerini tanımlanması var bunun için variables anahtarına bir obje olarak anahtarları ve değerlerini veriyoruz. Bu uygulama kapsamında bizim ihtiyaç duyduğumuz ortam değişkenleri RAILS_ENV, BUNDLE_PATH, DATABASE_HOST, DATABASE_USER ve DATABASE_PASS bunları aşağıda ki şekilde tanımlıyoruz;
variables:
RAILS_ENV: 'test'
BUNDLE_PATH: 'vendor/bundle'
DATABASE_HOST: 'postgres'
DATABASE_USER: 'postgres'
DATABASE_PASS: 'postgres'
Tüm bu direktiflerin olduğu .gitlab-ci.yml dosyasının içeriği aşağıda ki şekliyle olacaktır;
image: 'registry.gitlab.com/PROJECT_SCOPE/PROJECT'
cache:
paths:
- 'vendor/bundle'
before_script:
- 'bundle check || bundle install --jobs 4'
stages:
- 'Lint'
- 'Test'
- 'Security'
rubocop:
stage: 'Lint'
script:
- 'bundle exec rubocop'
rspec:
stage: 'Test'
script:
- 'bundle exec rails db:setup'
- 'bundle exec rspec'
brakeman:
stage: 'Security'
script:
- 'bundle exec brakeman -z -q'
services:
- 'postgres:11.5'
variables:
RAILS_ENV: 'test'
BUNDLE_PATH: 'vendor/bundle'
DATABASE_HOST: 'postgres'
DATABASE_USER: 'postgres'
DATABASE_PASS: 'postgres'
Artık uygulamamız ve gitlab ci hazır dosyamızı kaydederek pushladığımız zaman gitlab tarafından ardışık düzenin oluşturulduğunu ve sırası ile tüm aşamaların ve aşamaların adımlarının çalıştırıldığını aşağıda ki şekillerde olduğu gibi göreceksiniz.
Üstte ki görselde tüm ardışık düzenin başarıyla tamamlandığını görüyorsunuz.
Üstte ki görselde ardışık düzende Test aşamasında rspec adımının hata verdiğini ve ardışık düzenin bitirildiğini görüyorsunuz.
Üstte ki görselde rubocop adımının başarıyla tamanlandığını ve çıktısını görüyorsunuz.
Üstte ki görselde rspec adımının hata fırlattığını ve çıktısını görüyorsunuz.
Zaman ayırıp okuduğunuz için teşekkürler.
Önerilerinizi ve sorularınızı issue olarak buradan sorabilirsiniz.