İçeriğe geç

Tekton Pipelines ile Docker Image Build ve Registry Push

Tekrardan merhabalar.

Tekton serimizin bu ikinci blgounda, gerçek hayatta sıkça karşılaştığımız bir süreci — Docker image build ve registry’ye push işlemini — adım adım kurgulayacağız.

İlk yazıda Tekton’ın temel bileşenlerini tanımış ve basit örneklerle pratiğe dökmüştük. Bu yazıyla birlikte o temellerin üzerine gerçek bir senaryo inşa edeceğiz. Ayrıca, serinin bir sonraki yazısında ele alacağımız event driven pipeline yapısına zemin hazırlayacağız.

Eğer temel Tekton kavramları hakkında bir tekrar yapmak veya direkt serideki herhangi başka bir yazıya ulaşmak isterseniz;

1) Tekton Pipelines Nedir? Tekton ile CI/CD Süreçlerine Giriş

2) Tekton Pipelines ile Docker Image Build ve Registry Push (Okumakta olduğunuz blog)

3) Tekton ile Event Driven CI/CD Süreci Kurgulama

Blogta kullanılan kaynak kodlara ulaşmak isterseniz Github repository adresi burada.


Docker image build/push.

Bu yazıdaki amacımız, küçük bir uygulamayı container imajı haline getirip versiyonlayıp bir image registry‘ ye pushlamak. Esasen bu işlem günlük hayatta sıkça karşılaştığımız bir CI adımı.

Bu senaryoda bize eşlik etmesi adına kompleks bir uygulamaya ihtiyacımız yok, basit bir Flask uygulaması işimizi görecektir.

# app.py
import os
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
    version = os.environ.get("APP_VERSION")
    return {"version": version}
if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)

Uygulama environment üzerinden gelen APP_VERSION gelen değerini API response olarak geri dönmekte.

Uygulamayı bir imaj haline getirmek içinse aşağıdaki gibi bir Dockerfile oluşturalım.

FROM python:3.12-alpine
WORKDIR /usr/src/app
ARG APP_VERSION
COPY app.py .
RUN pip install flask
ENV APP_VERSION=${APP_VERSION}
EXPOSE 5000
CMD [ "python", "app.py" ]

Build argümanı olarak geçilen değeri ki bunu imaj tagi olarak kullanacağız; environment üstünde oluşturulacak konteynere geçiriyoruz.

İmajı eğer hızlıca lokalde build alıp ondan bir container oluşturup çıktıyı kontrol etmek isterseniz;

$ export APP_VERSION=0.0.1
$ docker build --build-arg APP_VERSION=$APP_VERSION -t tekton-flask-app:$APP_VERSION .
$ docker container run --rm -p 28512:5000 tekton-flask-app:0.0.1

Container 28512 portunda hizmet veriyor olacaktır.

$ curl localhost:28512
{"version":"0.0.1"}

İle çıktıyı kontrol ettiğimizde her şeyin yolunda olduğunu görebiliriz.

Ön hazırlığımızı tamamladığımıza göre, artık Tekton ile bu imajı oluşturmanın otomasyonunu kurgulamaya başlayabiliriz.


Tekton Pipeline’imizi Oluşturalım.

Öncelikle ne yapacağız?

  • İmajı hazırlanacak uygulamanın reposunu Git sağlayıcısından temin etmemiz (klonlamamız) gerekmekte.
  • Repository içerisinde bulunan Dockerfile ve kaynak kod kullanılarak Docker imajı oluşturulmalı.
  • Oluşturulan ve etiketlenen imaj, bir imaj depolama sunucusuna (registry) push edilmeli.

Peki bir Git reposunu Tekton ile nasıl klonlar ve üzerinde işlem yaparız?

İlk akla gelen ve çalışan bir çözüm, Alpine imajı kullanan bir task oluşturarak içine Git yüklemek ve klasör izinlerini ayarlayıp repo’yu klonlamak olabilir. Sonra istenen branch ya da tag’e checkout yapılarak bu adım tamamlanabilir.

Ama fark ettiyseniz, bu örüntü gayet tanıdık. Hatta neredeyse her pipeline’da yer alabilecek kadar standart denilebilir.

İşte burada ilk blog yazısında da bahsettiğimiz üzere, Tekton ortak bileşenler oluşturup (task) farklı akışlarda(pipeline) kullanılmasına izin veren modüler bir yapıya sahip. Dolayısıyla bu işlemi yapabilecek bir task’i tekerliği yeniden keşfetmeden; Tekton Hub üzerinde arayabiliriz.

Bir task’in Hub üzerinde bulunması ve incelenmesi.

Hub üzerinde yapmak istediğimiz işlemin anahtar kelimeleriyle arama yaptığımızda (clone gibi bir kelime olabilir) listelenen sonuçlardan git-clone taskine ulaşabiliriz.

Detay sayfasında Task ile alakalı meta bilgileri, manifest YAML’ını ve çalıştırılabilmesi için gerekli ve opsiyonel parametreler hakkında bilgi alabiliriz.

Bunun yanında eğer bu işlemleri UI olmaksızın Tkn CLI ile de gerçekleştirebilirsiniz.

Arama yapmak için;

$ tkn hub search <task-name>
$ tkn hub search git-clone
NAME              KIND   CATALOG   DESCRIPTION
git-clone (0.9)   Task   Tekton    These Tasks are Git tasks to work with ...

Bilgi almak için;

$ tkn hub info task <task-name> --version (optional) 
$ tkn hub info task git-clone --version 0.9 
📦 Name: git-clone 
🗂 Display Name: git clone 
📌 Version: 0.9


Task yüklemek.

Taski kullanmadan önce yüklememiz gerekmekte. Bunun için Tkn CLI yada direkt kubectl kullanabiliriz.

$ kubectl apply -f https://api.hub.tekton.dev/v1/resource/tekton/task/git-clone/0.9/raw
# or 
$ tkn hub install task git-clone

Bu adımın ardından artık clusterımız üstünde koşacak pipelinelara git-clone taski dahil edilebilir.

Ufak bir not; eğer -n ile namespace parametresi belirtilmezse tkn cli varsayılan olarak üzerinde çalışılmakta olan namespace altına bu taski yükleyecektir. Biz örnek olması açısından ilk yazıdan beri herhangi bir spesifik namespacede çalışmadık. default namespace’ini kullanmaktayız.


Pipeline’i oluşturmaya başlayalım.

git-clone taski iki adet zorunlu parametreye sahip. Bunlardan ilki repo’nun hangi adresten klonlanacağı, ikincisiyse klonlanan dosyaların nereye kaydedileceği.

Bu bilgilerin ışığında, Pipeline tanımını aşağıdaki gibi yapabiliriz. Tanımın ardından 2 yeni kavram olan; workspaces ve params hakkında konuşacak ve tabiri caizse elimizi kirleterek öğrenmiş olacağız.

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: image-build-and-push
spec:
  params:
    - name: repo-url
      type: string
    - name: revision
      type: string
  workspaces:
    - name: pipeline-workspace
  tasks:
    - name: fetch-repository
      params:
        - name: url
          value: "$(params.repo-url)"
        - name: revision
          value: "$(params.revision)"
      taskRef:
        name: git-clone
      workspaces:
        - name: output
          workspace: pipeline-workspace

Öncelikle params ile başlayalım.

Tekton’da taskler parameterize edilebilir. Yani bir task sağlanan farklı girdileri alarak aynı işlemi yürütüp sonuçlar üretebilir. İşlem aynı kalır fakat her yürütme farklı girdilerle farklı ve tutarlı sonuçlar üretir. git-clone dökümanında klonlanmak istenen reponun adresinin task’e  url ismiyle sağlanmasının gerektiği açıkça belirtilmiştir.

Bunun yanında zorunlu olmayan fakat kullanıldığında akışı yönlendiren yada destekleyen parametrelerde bulunur.  revision parametresi klonlanan reponun hangi head’inde işlem yapılacağını belirtir ve taski repoyu klonladıktan sonra eğer bu değer sağlanmışsa o heade geçmesi için yönlendirir. Bu bir git revision’ı olmalıdır. commit id, branch ismi veya git tagi.

Pipeline’lar, yürüttükleri task’lerin ihtiyaç duyduğu parametreleri içermeli ve bu parametreleri uygun şekilde task’lere aktarmalıdır.

"$(params.variable_name)" ile Pipeline üstündeki parametre tanımı task’in params tanımı altında bağlanır ve aktarılmış olur.

workspaces kavramıyla devam edelim.

IT dünyasında genellikle işlemlerimizi “on the fly”, tamamen bağımsız, izole bir şekilde gerçekleştirmeyiz.

Bir konfigürasyon dosyasına ya da bir veri çıktısına ihtiyaç duymamız çoğu zaman olasıdır.

İşlemler sistematik ve birbirine bağımlı şekilde ilerler. Bir girdi bir başka bir alt işlemde kullanılır, bu işlemin çıktısı bir başka alt işleme girdi olur. Bu yapı n kez tekrar edebilir ve sonunda nihai bir sonuç ortaya çıkar.

Bu akış tekrarlanırken, süreçlerin ihtiyaç duyduğu bilgileri çoğunlukla konfigürasyonlarla sağlarız. (Örneğin private uygulamanının access bilgileri.)

Tekton workspaces ile bu işlemleri yapmamıza salık verir.

Veriyi task içindeki stepler veya pipeline içindeki taskler arasında taşıyabilir, akışı destekleme adına konfigürasyon dosyalarını saklayabilir yada önemli verileri pipeline yada task yaşam döngüsünün dışında da saklanabilmesini sağlar. Docker veya K8S volume’lerine çok benzer değil mi 🙂‍↔️

Workspaces kavramı hakkında bir kaç bilinmesi ve dikkat edilmesi gereken noktayı da belirttikten sonra diğer adıma geçebiliriz.

  • Tekton bir Task içerisinde workspace tanımı varsa ve aksi belirtilmemişse boş geçici bir klasörü pod üzerinde oluşturarak, bu taskin yürütmüş olduğu step(container) lar arasında kullanımı için mount eder. Böylelikle bir step kendinden önceki step’in yazmış olduğu veriyi alıp okuyabilir, işleyip, kayıt edebilir ve veriyi bir sonraki step’e geçirebilir. Task işlemlerini tamamladığında bu workspace kaldırılır. Eğer anlatılan bu durum istenilmiyorsa doğrudan bir volume oluşturulup mount edilebilir.
  • Fakat Pipeline bağlamında çalışan Task’ler doğrudan ortak podlar ile çalışmazlar. Bu durumda verinin pipeline içindeki taskler arasında doğrudan taşınabilmesini engeller. Bir K8S PVC’si oluşturulup pipeline kullanımına sunulursa, farklı task’ler bu volume’i kullanarak birbirlerine veri paslayabilirler.

git-clone taski çıktısını kayıt etmek için output isimli bir workspace tanımını bekler. pipeline-workspace ile oluşturulan workspace’i task’e mount ederek klonlanan reponun yazılacağı pathi belirtiyoruz.

taskRef Pipeline içinde çalışacak olan tasklerin hangi referans üstünden yürütüleceğini belirtmekte. Dolayısıyla,  pipeline üstünde klon işlemini yapacak bir task tanımı yapıyor ve parametre, workspace gibi ilişkilerini belirtiyoruz, ama aslında işi referans göstermiş olduğumuz git-clone taski yapacak.

Build&push adımına geçmeden önce pipeline’i bu haliyle yükleyelim ve koşturalım. Her şeyin çalıştığına emin olduktan sonra, build&push adımına devam edebiliriz.

git-clone task’inin yürütülmesi.

Öncelikle yukarıda oluşturmuş olduğumuz manifesti apply ederek pipeline tanımını cluster üstünde gerçekleştirelim.

$ kubectl apply -f <your_pipeline_file_name>.yaml

Başarıyla yüklendiğini gördükten sonra Tekton CLI ile pipeline’i koşturabiliriz.

$ tkn pipeline start image-build-and-push \
  --param repo-url=https://github.com/pallets/flask.git \
  --param revision=main \
  --workspace name=pipeline-workspace,emptyDir="" \
  --showlog
PipelineRun started: image-build-and-push-run-f88h2
Waiting for logs to be available...

PipelineRun oluşturmadık? Tekton CLI bizim için bu görevi üstlendi ☺️

Flask framework’ünün GitHub reposunu repo-url, main branch’ini ise revision olarak parametre geçtik.

git-clone task’inin beklediği output workspace’ini, pipeline içindeki pipeline-workspace ile eşleştirdik.

Herhangi bir veri transferi yapmayacağımız için bu workspace’i emptyDir ile tanımladık; böylece işlem sonrası verinin silinmesi sağlanmış oldu.

Pipeline tamamlanıp komut çıkış yaptığında artık çalışan bir repo klonlama fonksiyonumuz olduğundan eminiz. Pipelinei şimdilik aşağıdaki gibi kaldırıp genişletme zamanı.

$ tkn pipeline delete image-build-and-push

Kaniko ile image build/push.

kaniko Docker daemon’ı bulunmayan ortamlarda (örneğin Kubernetes cluster’larında),

Dockerfile içeriğine göre image build edip bunu bir container registry’ye pushlamayı sağlayan bir araçtır.

Tüm build işlemlerini tamamen userspace içinde gerçekleştirdiği için;

lightweight, daemonsız ve özellikle K8s native senaryolarda oldukça kullanışlıdır.

Tekton Hub’da kaniko için hazır bir Task tanımı da bulunmaktadır.

Bu Task’i kullanarak pipeline’ımıza image build & push yeteneği ekleyebiliriz.

Peki nelere ihtiyacımız var?

Dökümanında da belirtildiği üzere, kaniko task’inin çalışabilmesi için, bir kaç gerekli parametre bulunmakta. Bunları kaniko ‘ya sağlamalı, pipeline tanımımızı genişletmeli ve pipeline taskleri arasındaki veriyi (kaynak kodu) workspaces yardımıyla taşımalıyız.

Yine tam tanımı gördükten sonra açıklayarak devam edelim.

⚠️ Önemli Not

Gerçek hayat senaryolarına daha yakın olması adına bu yazıda DockerHub ‘ı image registry olarak kullanacağız. Bu nedenle, eğer henüz bir Docker Hub hesabınız yoksa, ücretsiz bir hesap oluşturmak isteyebilirsiniz.

Ancak bu zorunlu değil.

Kendi lokal registry’nizi ya da özel bir registry kullanacaksanız, aşağıdaki iki noktayı değiştirmeniz yeterli olacaktır:

1.Image tag’i içinde yer alan host adını, kendi registry adresinizle değiştirin

2.Eğer registry’inizde kimlik doğrulama gerekmiyorsa, birazdan oluşturacağımız Docker credential secret’ını kaldırabilir veya ihtiyaçlarınıza göre uyarlayabilirsiniz

Notun ardından Pipeline’i aşağıdaki şekilde tanımlayalım.

apiVersion: tekton.dev/v1beta1
kind: Pipeline
metadata:
  name: image-build-and-push
spec:
  params:
    - name: repo-url
      type: string
    - name: revision
      type: string
    - name: IMAGE
      type: string
    - name: DOCKERFILE
      type: string
    - name: CONTEXT
      type: string
    - name: APP_VERSION
      type: string
  workspaces:
    - name: pipeline-workspace
    - name: docker-credentials
  tasks:
    - name: fetch-repository
      params:
        - name: url
          value: "$(params.repo-url)"
        - name: revision
          value: "$(params.revision)"
      taskRef:
        name: git-clone
      workspaces:
        - name: output
          workspace: pipeline-workspace
    - name: build-push
      taskRef:
        name: kaniko
      params:
        - name: IMAGE
          value: "$(params.IMAGE):$(params.APP_VERSION)"
        - name: DOCKERFILE
          value: "$(params.DOCKERFILE)"
        - name: CONTEXT
          value: "$(params.CONTEXT)"
        - name: EXTRA_ARGS
          value:
            - "--build-arg=APP_VERSION=$(params.APP_VERSION)"
      runAfter:
        - fetch-repository
      workspaces:
        - name: source
          workspace: pipeline-workspace
        - name: dockerconfig
          workspace: docker-credentials

📦 Parametreler

Pipeline için params tanımını, kaniko’nun ihtiyaç duyduğu parametreleri de içerecek şekilde genişlettik.

Bu parametreler build-push task’ine geçirilerek, uygulamanın:

  • Hangi Dockerfile
  •   Hangi context dizini
  • Hangi imaj ismi ve tag ile build edilmesi gerektiği

belirtilmiş oldu.


🔗 Bileşik parametre kullanımı

Birden fazla parametreyi aynı string içinde birleştirebiliyoruz:

örneğin: "$(params.IMAGE):$(params.APP_VERSION)".

Bu sayede imaj ismini ve tag’ini tek parametre içinde tanımlayarak kaniko’ya doğrudan verebiliyoruz.


⏱ Sıralama – runAfter

runAfter tanımı ile build-push adımını fetch-repository adımından sonra çalışacak şekilde yapılandırdık.

Yani repo klonlanmadan image build işlemi başlamayacak.


🔐 Docker credentials – dockerconfig workspace’i

kaniko, Docker registry kimlik doğrulamasını dockerconfig adındaki bir workspace’ten bekliyor.

Eğer bu isimle bir workspace bağlanırsa, kaniko bu workspace’e mount edilen config.json içeriğini okuyarak image push işlemini gerçekleştirerek kendini registry’e tanıtmış oluyor. Burada direkt bir dosya dizini kullanılabileceği gibi bir secret objesi de kullanılabilir.

Yaygın kullanım K8S üstünde bir secret tutmaktır. Bu secret’i bir sonraki adımda oluşturacağız.


📂 Kaynak kodun taşınması

pipeline-workspace hem git-clone hem de kaniko task’leri tarafından paylaşılıyor.

git-clone bu workspace’e kodu yazıyor, kaniko ise aynı workspace’i source ismiyle mount ederek çalışacağı kaynak kodu kendi akışına dahil etmiş oluyor.


$ kubectl apply -f pipeline.yamlcluster üstüne tanımlayı unutmayalım.


Docker Authentication Konfigürasyon Secret’ini Oluşturalım.

Dockerhub’ta saklanması için bir imaj pushlamak istiyorsak, kendimizi bu işlem sırasında tanıtmalı ve konfigürasyonu oluşturmalıyız. Docker eğer CLI veya Docker Desktop üstünden login olunmuşsa bu bilgileri;

~/.docker/config.jsonpathinde tanımlar. İçeriğine göz atabilirsiniz fakat doğrudan bu dosyayı base64 ile decode edip bir secret haline getirmeyeceğiz. Bunun sebebi örnegin MacOS üstünde çalışan Docker Daemon’ı credentialleri işletim sisteminin keychaini üzerinde saklamakta ve oradan okumakta.

Standartize ve ortak bir noktada buluşmak için auth konfigürasyonunu shell scripti ile oluşturup, bunu base64 ile encode edip, direkt K8s secreti oluşturmak bize istediğimizi verecektir.

DOCKERHUB_USERNAME="your_username"
DOCKERHUB_PASSWORD="your_password"
CREDENTIALS=$(printf "%s:%s" "$DOCKERHUB_USERNAME" "$DOCKERHUB_PASSWORD" | base64)
kubectl create secret generic docker-credentials \
  --from-literal=config.json="{\"auths\":{\"https://index.docker.io/v1/\":{\"auth\":\"$CREDENTIALS\"}}}"

secret/docker-credentials created çıktısı alınmışsa tebrikler, pipeline’i çalıştırmaya hazırız.


Pipelinerun objesini oluşturalım.

Oluşturduğumuz pipeline’i yürütecek olan PipelineRun objesini aşağıdaki gibi tanımladıktan sonra üstünde konuşabiliriz.

apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  generateName: image-build-and-push-pipeline-run-
spec:
  pipelineRef:
    name: image-build-and-push
  podTemplate:
    securityContext:
      fsGroup: 65532
  workspaces:
    - name: pipeline-workspace
      volumeClaimTemplate:
        spec:
          accessModes:
            - ReadWriteOnce
          resources:
            requests:
              storage: 1Gi
    - name: docker-credentials
      secret:
        secretName: docker-credentials
  
  params:
    - name: repo-url
      value: "https://github.com/EgehanGundogdu/tekton-pipeline-blog-series"
    - name: "revision"
      value: "main"
    - name: IMAGE
      value: "docker.io/egundogdu/tekton-blog-series-flask-app"
    - name: DOCKERFILE
      value: "./Dockerfile"
    - name: CONTEXT
      value: "."
    - name: APP_VERSION
      value: "0.0.1"

💼  Workspaces tanımları

dockerconfig olarak kaniko taskinin ihtiyaç duyduğu workspacei bizim senaryomuzda docker-credentials olarak tanımlamıştık. Bu workspace’in içeriğini cluster üzerinde oluşturmuş olduğumuz secret nesnesi üzerinden dolduruyoruz.

pipeline-workspace ile cluster üzerinde PVC aracılığıyla bir volume talebinde bulunuyoruz. Bu talep üstüne açılacak volume dizinine ReadWriteOnce şeklinde erişimimiz olacağını bildiriyoruz.

🏭 Pod erişim ayarı.

 git-clone task’i, root kullanıcı ile çalışmaz. Bunun yerine, UID (user ID) veya GID (group ID) değeri 65532 olan non-root bir kullanıcı ile çalışır. Bu durum, genellikle root kullanıcı tarafından oluşturulmuş volume’larda izin hatalarına neden olur. Çünkü non-root kullanıcı bu volume’a yazma yetkisine sahip değildir.

Bu sorunun çözümü için iki alternatif yaklaşım vardır:

1.Storage tarafında volume’ların 65532 UID ile erişilebilir hale getirilmesi

2.Ya da pod seviyesinde, securityContext kullanılarak UID/GID eşlemesi yapılması

Bizde 2. çözümü uygulayarak pod içinde çalışacak container’ların doğru erişim yetkisine sahip olmalarını sağladık.

💾 Parametre kullanımı.

Burada aslında yeni bir şey yok, bu blog yazısının kaynak materyallerinin bulunduğu repoyu kaynak olarak kullanarak,  tekton-pipeline-blog-series adlı bir imaj oluşturacağımızı ve taginin 0.0.1 olacağını belirtiyoruz.

Buradaki değerleri, farklı bir repo bir için çalışacaksanız onun adresi olacak şekilde ve kendi DockerHub kullanıcı adınıza göre düzenlenmelisiniz.


Pipeline’nin çalıştırılması.

Artık pipelinimiz hazır yukarıdaki pipelinerun.yaml dosyamızı K8S clusterında create edelim.

kubectl create -f pipelinerun.yaml

💡 Not: generateName ile her çağrıda benzersiz bir isim üretilmesi sağlanır. Bu özellik yalnızca kubectl create  komutuyla kullanıldığında geçerlidir.

$ kubectl create -f pipelinerun.yaml
pipelinerun.tekton.dev/image-build-and-push-pipeline-run-jpstv created

Loglarını takip edecek olursak (isimlerin sizde farklı;

$ tkn pr logs image-build-and-push-pipeline-run-jpstv -f
...
[build-push : build-and-push] INFO[0011] Pushing image to docker.io/egundogdu/tekton-blog-series-flask-app:0.0.1
[build-push : build-and-push] INFO[0028] Pushed image to 1 destinations
[build-push : write-url] docker.io/egundogdu/tekton-blog-series-flask-app:0.0.1

Görünen o ki her şey olması gerektiği gibi çalışıyor ☺️

 


🏁 Sonuç.

Bu blogta, Tekton kullanarak sektörde çok yaygın karşılaştığımız bir CI senaryosunu — image build & push sürecini — adım adım kurguladık ve çalıştırdık.

Buraya kadar sabırla geldiyseniz gerçekten tebrikler ve teşekkürler 🙏

Ama tabii ki işimiz burada bitmiyor çünkü şöyle bir soru kafanıza takılmış olabilir; her yeni versiyon oluşturduğumuzda gidip elle bir PipelineRun mı tetikleyeceğiz? Tabi ki hayır.

Bu yüzden bir sonraki blog yazısında bu pipeline’ı event-driven hale getireceğiz ve belirli olaylar gerçekleştiğinde bu pipeline’in otomatik olarak çalışmasını sağlayacağız.

Görüşmek üzere 👋

 

 

Tarih:Blog

İlk Yorumu Siz Yapın

Bir cevap yazın

E-posta hesabınız yayımlanmayacak.

Göster
Gizle