4. Istio ile Canary Deployment
← Önceki: 3. Kiali ile Istio Ağ Trafiği
İlk 3 yazıda Istio’dan bahsettik ve Istio ile çalışan üzerinden haberleşen uygulamalar çalıştırdık ardından Kiali ile ağ trafiğimizi ve uygulama trafiğimizi inceledik. Bu yazıda ise Canary Deployment uygulayacağız.
Canary Deployment nedir?
Canary Deployment uygulamanızın yeni versiyonunu servis sağladığınız tüm kaynaklara uygulamak yerine daha küçük bir kısmına uygulayarak kademeli olarak yeni versiyona geçiş yapılan bir stratejidir.
Kısaca örnek verelim. Aşağıda Load Balancer’a bağlı 4 pod bulunuyor ve bu trafik 4 poda dağıtılıyor

Bu çalışan podların içerisindeki uygulamarın versiyonlarının hepsi stabil, sorunsuz versiyonlar.
Şimdi uygulamada köklü bazı değişiklikler yaptık, stage ortamında denedik varsayalım.
Yeni versiyon yayına almaya hazır ama olası bir downtime durumuna karşı önlem almak istiyoruz. Elimizde 4 stabil versiyona sahip pod vardı, 1 tanesini silelim ve riskli/yeni versiyona sahip bir pod koyalım.
Görüntümüz şöyle olacaktır:

Eğer riskli/yeni versiyona sahip poddaki uygulama belli bir süre başarılı devam eder ise yeni versiyonu 2,3 ve son olarak 4 pod yapıp tamamen yeni versiyona geçebiliriz.
Istio Canary Deployment yapabilmek için gerekenlere sahip bir araç.
Istio üzerinde Canary Deployment uygulayabilmek için ilk başta uygulama deploy ettiğimizden farklı bir şey yapacağız.
Server tarafına ait Kubernetes objelerini zaten serinin 2. yazısında gerçekleştirmiştik. Şimdi ise Istio özelinde bazı nesneler oluşturmamız gerekecek. Bunları da detaylıca anlatacağım.
Canary Deployment yapabilmemiz için bizim elimizde 2 adet server versiyonu olmalı zaten daha önce v1 adındaki Docker imajı ile bir versiyonumuz vardı öncelikle server uygulamasının 2. ve riskli/hatalı versiyonunu yazacağız ve yeni bir isim ile deploy edeceğiz.
Sonrasında Istio nesnelerini yaratacağız Gateway, VirtualService ve DestinationRule
Aslında konsept olarak ihtiyacımız olan sadece 2 nesne var VirtualService ve Destination rule trafiği yönlendirmek için yeterli fakat VirtualService objesinin bir Gateway ile çalışması gerekiyor bu nedenle Gateway objesi de yaratacağız.
Gateway ile dışardan gelen istekleri karşılayan ilk kısmı oluşturmuş oluyoruz. Bu nesne ile bir hostname gireceğiz ve bu hostname için gelen istekleri bizim VirtualService objemiz ile birlikte ilgili servislere yönlendirecek. DestinationRule objesi ile versiyonlarımızı tanımlayacağız ve eski/yeni versiyonlara isteklerin nasıl dağılacağını VirtualService nesnesi ile configure edeceğiz.
Öncelikle server tarafı için hata içeren bir versiyon oluşturalım.
serverv2/main.go
package main
import "net/http"
func main() {
http.HandleFunc("/test", test)
http.ListenAndServe(":8080", nil)
}
func test(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(500)
w.Write([]byte("something went wrong"))
}
Burası oldukça basit, :8080/test adresine gelen isteklerin tamamı 500 alacak.
Uygulamamızı Dockerize etmek için:
serverv2/Dockerfile
FROM golang:1.20
WORKDIR /app
COPY . .
RUN go build -o main main.go
CMD ["./main"]
EXPOSE 8080
Şimdi bir imaj yaratabiliriz.
docker build . -t server:v2 && kind load docker-image server:v2
Yeni/hatalı versiyon için server:v2 adında bir imaj oluşturduk ve kind registry’sine yükledik.
Şimdi bu versiyona özel deployment objemizi yazalım ve server için hatalı versiyonu bitirelim.
Deployment objesi ilk yazdığımız server:v1 için olan deployment objesinden biraz daha farklı olacak, en azından ismi version label’ı farklı olmalı.
apiVersion: apps/v1
kind: Deployment
metadata:
name: server-deployment-v2
labels:
name: server
app: server
version: v2
spec:
replicas: 1
selector:
matchLabels:
name: server
template:
metadata:
labels:
name: server
app: server
version: v2
spec:
containers:
- name: server
image: server:v2
ports:
- containerPort: 8080
İsim değişti “server-deployment” -> “server-deployment-v2” oldu.
“version” label’ı değişti (v1 -> v2). Bunun amacı ise DestinationRule objeleri bu label’a bakarak versiyonlamayı yapıyor. Dolayısı ile biz trafiğin %90'ını eski/stabil versiyona ilet, %10'unu yeni/hatalı versiyona iletmek istediğimizde VirtualService DestinationRule objesindeki v1 versiyonuna ve v2 versiyona göre bunu yapacak.
Gateway objesini yazarak başlayalım.
gateway.yaml
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: test-gateway
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 8080
name: http
protocol: HTTP
hosts:
- "*"
kubectl apply -f gateway.yaml
Bu Gateway objesi ile 8080 portuna gelen tüm hostlara ait istekleri Istio’nun dağıtacağını belirtmiş olduk.
Şimdi DestinationRule objesini yazalım.
destination-rule.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: server-dr
spec:
host: server-service
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
Kısaca DestinationRule objesindeki Istio özelinde olan ifadeleri açıklayalım.
“host” bu objenin hangi Kubernetes servisi ile çalışacağını, hangi servise yönlendirme yapacağını belirtiyor. Buradaki host != hostname.
Buradaki “host” Kubernetes service objelerini ifade eder ve buraya girilen isim ilgili namespace içerisinde bulunmalıdır.
Biz zaten daha önce “server-service” adındaki service objesini server deploy ederken eklemiştik.
Şimdi gelelim “subsets” adlı bölüme. Burada 2 farklı versiyon tanımlıyoruz v1 ve v2.
Subsets kısmı ile server-service adlı servise gidecek isteklerin version: v1 ve version: v2 labellarına sahip podlara yönlendirileceğini belirtiyoruz.
DestinationRule dosyamız hazır.
kubectl apply -f destination-rule.yaml
Şimdi son olarak VirtualService dosyamızı hazırlayalım.
virtual-service.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: server-virtual-service
spec:
hosts:
- server-service.default.svc.cluster.local
http:
- route:
- destination:
host: server-service
subset: v1
port:
number: 8080
weight: 90
- destination:
host: server-service
subset: v2
port:
number: 8080
weight: 10
kubectl apply -f virtual-service.yaml
Buradaki Istio özelindeki field’larıda kısaca açıklayalım.
spec.host kısmında aşağıdaki konfigürasyonların uygulanacağı host adını bildiriyoruz. Buradaki host adı bir Kubernetes servis objesi değil gelen request’teki host kısmını ifade ediyor. Daha önce Gateway objesinde * olarak tanımlamıştık, burada spesifik bir host belirtiyoruz, zaten client’ın da buraya istek atacağını biliyoruz.
http.route altında 2 adet destination bulunuyor bunlar görüldüğü üzere yük dağılımını ifade ediyor.
v1 → %90 yük alacak v2 → %10 yük alacak şekilde tanımlamamızı yaptık.
Ve artık hazırız sanıyorum ki VirtualService objesini apply ettiğimizden beri trafik %90 — — %10 şeklinde dağılmaya başlamıştır.
Hemen Kiali arayüzüne dönüyorum.

Burada Workloads menüsünden server-deployment-v2 workload’ına baktığımda bir hata olduğunu görüyorum, incelemeye devam edelim.
Workload’a tıkladım ve Traffic tab’ine geçtim, açılan ekranda başarı oranının 0 olduğunu gördüm.

Biraz daha uzaktan bakmak için Graph menüsüne geçiyorum.
Burada başarı oranının yaklaşık %90'a düştüğünü ve hangi versiyonun başarılı hangisinin başarısız olduğunu görebiliyoruz.

Ben burada hatalı senaryoyu ele aldım. Eğer v2 versiyonum başarılı olsaydı mevcut versiyonumdaki imaj v1'i → v2 yapıp yeni versiyonu tamamen devreye alabilirdim fakat olmadı.