İçeriğe geç

Tekton Pipelines ile Event Driven CI/CD Süreci Kurgulama

Tekton Pipelines blog serisinin 3. yazısından selamlar.

Bu blog, serinin son halkası olmakla birlikte; önceki yazılarda adım adım inşa ettiğimiz yapıların üzerine artık gerçek bir otomasyon katmanı ekliyoruz.

İlk yazımızda Tekton’ın temel kavramlarını incelemiş, ikinci yazıda ise bu temelleri kullanarak bir uygulamanın container imajını build edip, image registry’ye pushlayan bir pipeline kurgulamıştık.

Şimdi bu pipeline’ı bir adım ileri taşıyacak, manuel müdahale gerektirmeden; bir olay gerçekleştiğinde (örneğin bir Git push) otomatik olarak tetiklenmesini sağlayacağız.

Yani artık olaylara tepki veren (event-driven) bir CI/CD mimarisi kurma zamanı.

Burada bu yazıyı okumadan önce serinin diğer yazılarına bir göz atmak veya sırasıyla takip etmek isterseniz aşağıdaki yönergeyi kullanabilirsiniz ☺️

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

2. Tekton Pipelines ile Docker Image Build ve Registry Push

3. Tekton Pipelines ile Event Driven CI/CD Süreci Kurgulama (okumakta olduğunuz blog)


Tekton Triggers nedir?

Tekton Triggers dış kaynaklarda (örneğin bir Git push veya webhook çağrısı gibi) oluşan event’leri tanımlayıp yakalayarak, ilgili Pipeline veya Task’lerin otomatik olarak tetiklenmesini sağlayan bir Tekton bileşenidir.

Dış kaynakta oluşan ve Trigger tarafından yakalanan event’teki veriler, tetiklenecek olan objeye parametre olarak aktarılabilir. Bu sayede pipeline ya da task’ler dinamik şekilde parameterize edilebilir.

Tekton Triggers nasıl çalışır ve hangi bileşenlerden oluşur?

Triggers, bir dizi custom Tekton tabanlı K8S CRD (Custom Resource Definition) objesinden oluşur.

🎧 EventListener

Tekton Triggers yapısının dış dünya ile iletişim kuran bileşenidir. Belirli bir port üzerinden HTTP isteklerini dinler ve gelen event’leri yakalar. Cluster içinde birden fazla EventListener tanımlanabilir. Event’i gönderen dış sistem, bu EventListener bileşenini hedef alacak şekilde yapılandırılır (örneğin bir Git webhook’u).

⚙️ Trigger

EventListener tarafından yakalanan event’e nasıl tepki verileceğini belirler. Yani hangi pipeline ya da task’in, hangi parametrelerle tetikleneceğini tanımlar. Bir Trigger objesi mutlaka TriggerTemplate ve TriggerBinding objesi refere etmelidir.

🏛️ TriggerTemplate

Event listener’ objesinin yakaladığı event sonucu yürütümü sağlanacak kaynakların tanımını içerir. Event sonucu pipeline veya task objelerinin yürütümü için PipelineRun ve TaskRun objelerini tanımlandığı şekilde oluşturmakla yükümlüdür.

💿 TriggerBinding

Dış kaynaktan listenere gelen event payloadından veriyi çıkartmak için kullanılır. Payload üzerinden çıkarılan veri TriggerTemplate üzerine aktarılır.

🚔 Interceptor

Interceptor, bir event gerçekleştiğinde, bu event henüz Trigger’a ulaşmadan önce devreye girerek çeşitli kontrollerin ve işlemlerin yapılmasına olanak sağlar. Bu sayede event’in içeriği doğrulanabilir, belirli koşullara göre filtrelenebilir veya payload üzerinde dönüşümler uygulanabilir. Uygun eventler Triggerobjesine iletilirken, uygun olmayanlar bu aşamada elenir. Tanımlanması ve kullanılması ise zorunlu değildir.

Yukarıda bahsettiğimiz yapının görselleştirilmiş bir çıktısını aşağıdaki çizimden inceleyebilirsiniz.

TriggerFlow

 


Triggers’i yükleyelim ve EventListener ile event driven yapıyı oluşturalım.

Triggers’i ve arından interceptorları aşağıdaki şekilde hızlıca clusterınıza kurabilirsiniz.

$ kubectl apply --filename \
https://storage.googleapis.com/tekton-releases/triggers/latest/release.yaml
$ kubectl apply --filename \
https://storage.googleapis.com/tekton-releases/triggers/latest/interceptors.yaml

Aşağıdaki komutun çıktısındaki tüm podlar Running statüsüne geçmisse kurulum başarılı şekilde tamamlanmıştır.

$ kubectl get pods -n tekton-pipelines --watch

Bir önceki blog yazısında oluşturmuş olduğumuz build/push pipeline’ini kullanacak ve bu pipeline’i event-driven hale getireceğiz.

Öncelikle pipeline tanımımızı burada tekrar edelim;

# pipeline.yaml
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

Bu blog ve önceki bloglarda kullanılan tüm kaynak koduna burada linkli olan Github repository’sinden erişebilirsiniz.

TriggerBinding oluşturulması.

Öncelikle veriyi dışarıdan karşılayacak olan TriggerBindingobjesini aşağıdaki gibi oluşturup üstüne konuşalım.

# trigger-binding.yaml
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerBinding
metadata:
  name: image-build-and-push-trigger-binding
spec:
  params:
    - name: repository_url
      value: $(body.repository_url)
    - name: git_revision
      value: $(body.git_revision)
    - name: app_version
      value: $(body.app_version)
    - name: image_push_addr
      value: $(body.image_push_addr)
    - name: image_build_context
      value: $(body.image_build_context)
    - name: dockerfile_path
      value: $(body.dockerfile_path)

Parametre tanımında, $body context’inin kullanıldığına dikkat edelim. Gelen HTTP isteğinin taşıdığı body, Tekton tarafında kullanılmak istenen değişken isimleriyle maplenerek payload karşılanmış oluyor.

Burada tanımlamış olduğumuz parametreler interceptor ile bir kontrolde yapmadığımız için doğrudan bu binding ile bağlı olan TriggerTemplate objesine aktarılacaktır.

⚠️ Bizim örnek senaryomuz için şu anda verinin validate edilmemesi pek fazla problem teşkil etmiyor fakat dış kaynaklara tamamiyle açık bir EventListener kullanımında binding üstünden  geçebilecek potansiyel zararlı bir verinin interceptor‘lar yardımıyla validate edilmesi iyi bir yaklaşım olacaktır.


TriggerTemplate oluşturulması.

Hatırlarsanız, bir Pipeline veya Task yürüteceğimizde objeye uygun örnegin PipelineRun, TaskRun tanımını da gerçekleştirip bunu cluster üzerinde apply ediyorduk. TriggerTemplate’de aslında event handle edileceğinde nasıl bir objenin hangi parametreleri kullanılarak yaratılacağının bir kalıbı. Tanımı üzerindeki kalıpla işlemi başlatacak olan nesneyi oluşturuyor.

# trigger-template.yaml
apiVersion: triggers.tekton.dev/v1beta1
kind: TriggerTemplate
metadata:
  name: build-push-trigger-template
spec:
  params:
    - name: repository_url
    - name: git_revision
    - name: image_push_addr
    - name: app_version
    - name: dockerfile_path
    - name: image_build_context
  resourcetemplates:
    - 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: "$(tt.params.repository_url)"
          - name: revision
            value: "$(tt.params.git_revision)"
          - name: IMAGE
            value: "$(tt.params.image_push_addr)"
          - name: DOCKERFILE
            value: "$(tt.params.dockerfile_path)"
          - name: CONTEXT
            value: "$(tt.params.image_build_context)"
          - name: APP_VERSION
            value: "$(tt.params.app_version)"

Fark edeceğiniz üzere aslında bir önceki blog serisinde manuel olarak oluşturup apply etmiş olduğumu PipelineRun objesiyle pek bir fark barındırmamakta.

Dikkat edilmesi ilk nokta TriggerTemplate nesnesinin, kendisine TriggerBinding üzerinden geçilmiş olan değişken isimleriyle uyuşan bir parametre listesi kabul etmesi ve resourceTemplates keyi altında PipelineRuntanımının barındırılıyor olması.

Bir diğer önemli nokta ise, bu parametrelerin PipelineRun objesine aktarılırken $(tt.params.X) şeklinde kullanılması. Bunun nedeni de tahmin edebileceğiniz üzere, TriggerTemplate nesnesinin farklı senaryolarda yeniden kullanılabilir olmasını sağlamak. Bu context ayrımı sayesinde template daha modüler hale gelir ve başka trigger’larda da tekrar kullanılabilir.

⚠️ docker-credentials adlı bir önceki yazıda tanımlanan secretin cluster üstünde hala tanımlı olduğundan emin olalım.

Eğer tanımlı değilse bir önceki yazıya ufak bir dönüş yapabilirsiniz.


EventListener oluşturulması.

Buraya kadar listener tanımında bulunması gereken en önemli iki bileşeni oluşturduk ve artık dışarıdan gelecek istekleri karşılayacak olan EventListener tanımını yapabiliriz.

Öncelikle tanımını yapalım ve üzerine konuşalım.

# event-listener.yaml
apiVersion: triggers.tekton.dev/v1beta1
kind: EventListener
metadata:
  name: image-build-and-push-event-listener
spec:
  serviceAccountName: tekton-triggers-admin-sa
  triggers:
    - name: image-build-and-push-trigger
      bindings:
        - ref: image-build-and-push-trigger-binding
      template:
        ref: image-build-and-push-trigger-template

Önce aşina olduklarımızla başlayalım.

Triggers listesinde, biraz önce oluşturduğumuz TriggerTemplate ve TriggerBinding isimlerini referans gösteriyoruz ki Trigger’in tam tanımlı hale gelsin.

Burada son bir kez daha tekrar etmenin kavramları daha da oturtacağını düşünüyorum. Bir başka deyişle diyoruz ki bu event listener dışarıdan bir HTTP isteği alıp bir eventi handle edeceğinde hangi veri taşıyıcısını kullanarak hangi kalıpta bir işlem yürütsün!? 🚀

Son olarak, EventListener‘da her handle ettiği evente karşılık, cluster üstünde objeler oluşturması, listelemsi, gözlemesi vs gerektiğini biliyoruz.

Dolayısıyla -insan olmayan 😄- nesnelerin yetkilendirmesinde kullandığımız bir ServiceAccount‘a ihtiyacı var. O tanımı da gerçekleştiriyoruz.

Artık kurguladığımız yapıyı test etme zamanı!

⚠️ Not.

EventListener’in kullanacağı serviceaccountı kısaca aşağıdaki şekilde oluşturabilirsiniz. Cluster seviyesinde admin yetkisini vermek gerçek hayat senaryolarında pek önerilmesede blogu takip eden herkesle ortak bir taban sağlama adına burada bunu es geçeceğiz. EventListener‘i  doğrudan production seviyesinde bir clusterda kullanacaksanız mutlaka ihtiyaçlarınıza yönelik kısıtlamalar yapmalısınız.

# service-account.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: tekton-triggers-admin-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: tekton-triggers-admin-binding
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
subjects:
  - kind: ServiceAccount
    name: tekton-triggers-admin-sa
    namespace: default  

Test edelim.

Tanımalarını yapmış olduğumuz ServiceAccount, TriggerTemplate, TriggerBinding ve EventListener objelerini cluster üstüne apply edelim.

Bunu en kısa yoldan yapmak isterseniz Github reposundaki post3 klasörü altında

kubectl apply -f . 
# or 
kubectl apply -f trigger-template.yaml
kubectl apply -f trigger-binding.yaml
kubectl apply -f service-account.yaml
kubectl apply -f event-listener.yaml

Default namespaceindeki event listener podu Running statüye geçtiyse, artık event listener podunu veya service’ini host makine üzerine yönlendirdikten sonra test edebiliriz.

EventListener varsayılan olarak ClusterIP tipinde bir servisi önüne konumlandırır. Fakat farklı servis tipleri ve Ingress objeleriyle kullanımda pek tabi ki mümkündür. Biz şu anda sadece test gerçekleştirmek adında service’in yönlendirme yapmış olduğu 8080 portunu port-forward ederek host üstünde kullanıma açalım.

$ kubectl port-forward services/el-image-build-and-push-event-listener 8080:8080

Dilerseniz gerçek zamanlı loglarını da gözlemek ve pipeline’imizin dispatch edildiğini veya olası hataları takip etmek isterseniz;

$ kubectl logs -f services/el-image-build-and-push-event-listener

Listener 8080 portundan artık istekleri kabul etmeye hazır.

Aşağıdaki gibi bir payload ile süreci test edebiliriz. Image adresini kendi Dockerhub repository’iniz ile değiştirmeyi unutmayın ☺️

Örnek request.

$ curl --location 'http://localhost:8080/' \
--header 'Content-Type: application/json' \
--data '{
  "repository_url": "https://github.com/EgehanGundogdu/tekton-pipeline-blog-series",
  "git_revision": "main",
  "image_push_addr": "docker.io/egundogdu/tekton-blog-series-flask-app",
  "dockerfile_path": "Dockerfile",
  "image_build_context": ".",
  "app_version": "0.48"
}'

Örnek response.

{
    "eventListener": "image-build-and-push-event-listener",
    "namespace": "default",
    "eventListenerUID": "1edcccb6-67e4-4c15-9892-505c4fe6cf16",
    "eventID": "aa6f4961-b61c-4d25-bbb0-5ed490977c82"
}

Pushlamıs oldugum 0.48 tag’li Docker imajına aşağıdaki adresten göz atabilir, pullayıp run edebilirsiniz 🚀☺️

https://hub.docker.com/repository/docker/egundogdu/tekton-blog-series-flask-app/


🏁 Sonuç.

Böylece Tekton pipelines serisinin sonuna geldik 🎉 Buraya kadar sabırla takip etmişseniz çok teşekkürler!

İlk yazıda Tekton’ın temel yapı taşlarına göz attık, ikinci yazıda image build & push sürecini kurguladık ve bu son yazıyla birlikte artık dış olaylara tepki veren bir CI/CD sistemine sahip olduk.

Manuel müdahaleye gerek duymayan, olay bazlı (event-driven) ve esnek bir yapı kurduk.

Buradan sonra yapabilecekleriniz ihtiyaçlarınıza ve hayal gücünüze bağlı;

✅ Interceptor’larla güvenliği artırabilir,

✅ Otomatik test/deploy adımları ekleyebilir,

✅ Farklı pipeline’ları bu yapı üzerinden tetikleyebilirsiniz.

Pek tabii ki Tekton’un yetenekleri bu kadarla sınırlı değil, dolayısıyla dökümanın içine dalıp daha da derinlemesine bilgi sahibi olmak isteyebilirsiniz.

Dökümanın linki: https://tekton.dev/docs/

Eğer bu seri size fayda sağladıysa ve sizin gibi Tekton’a adım atmak isteyen başkaları da varsa, blog serisini paylaşarak daha fazla kişiye ulaşamasını sağlayabilirsiniz.

Her türlü geri bildirim, öneri ve sohbet için yorumlara veya iletişim adresime beklerim.

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