Kubernetes, canary deployment i Istio

Kubernetes, canary deployment i Istio

Nie ważne czy wdrażasz aplikacje monolityczne czy mikroserwisy. Na pewno przed wypuszczeniem nowej wersji chciałbyś ją przetestować na jakiejś próbce swoich użytkowników. Powiedzmy na 10 procentach. Na czystym Kubernetesie będzie ciężko. Możesz zarządzać liczbą podów w konkretnym deploymencie. Pody podpinasz pod serwis. I to chyba tyle. Pokażę jak zrobić canary deployment w Kubernetes za pomocą Istio.

Deployment

W przykładzie będę używał swojego obrazu dockerowego. Serwis w nim zawarty potrafi między innymi zwracać tekst zdefiniowany w zmiennej środowiskowej RETURN_TEXT. W ten sposób będę mógł łatwo rozróżnić „wersję” aplikacji. Pierwsza z nich będzie zwracała tekst service-1a

Stworzę dwa deploymenty z dwoma wersjami serwisu. Drugi będzie zwracał tekst service-1b

Na początek wrzucimy na klaster nasze zasoby. Dodatkowo tworzymy namespace, które oznaczamy labelką istio-injection: enabled.

apiVersion: v1
kind: Namespace
metadata:
  name: srv
  labels:
    istio-injection: enabled

To spowoduje, że do wszystkich podów deployowanych w tej przestrzeni będą automatycznie wstrzykiwane komponenty Istio.

Pierwszy deployment wygląda następująco:

apiVersion: apps/v1
kind: Deployment
metadata:
  namespace: srv
  labels:
    app: mt1
  name: mt1
spec:
  replicas: 2
  selector:
    matchLabels:
      app: mt1
      ver: "1"
  template:
    metadata:
      labels:
        app: mt1
        ver: "1"
    spec:
      containers:
      - image: przemekmalak/multitool
        name: multitool
        env:
          - name: RETURN_TEXT
            value: service-1a

W drugim zmienimy tylko wartość RETURN_TEXT na service-1b oraz labelkę opisującą wersję na 2

 selector:
    matchLabels:
      app: mt1
      ver: "2"
  template:
    metadata:
      labels:
        app: mt1
        ver: "2"

Serwis

Serwis będzie szukał wszystkich podów spełniających warunek:

  selector:
    app: mt1

W ten sposób, oba deploymenty „ubierzemy” w jeden serwis. Ruch będzie rozrzucany pomiędzy obie wersje.

Cały plik yaml z definicją serwisu wygląda tak:

apiVersion: v1
kind: Service
metadata:
  namespace: srv
  labels:
    app: mt1
  name: mt1
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: mt1

Testy

Jeżeli wszystko już mamy w naszym klastrze gotowe możemy przetestować jak wygląda dystrybucja ruchu. W tym celu uruchomię nowego poda, w którym będę miał curla.

kubectl run test --image=przemekmalak/tools --rm -it -- sh

i z wewnątrz tego poda spróbuję kilka razy podłączyć się do naszego serwisu:

for i in `seq 1 10`
do
	curl -m 3 mt1.srv:8080
done

Jak zapewnie zauważyliście ruch został rozłożony po wszystkich podach z obu deploymentów. Ale, tak jak pisałem, ilość requestów przekazywanych do poszczególnych wersji możemy regulować za pomocą ilości podów w poszczególnych deploymentach.

Istio

Zakładam, że samo Istio masz zainstalowane. Jeżeli nie to popatrz tutaj jak to zrobić. To naprawdę tylko kilka kroków. Zainstaluj z profilem demo. To pozwoli potem na zabawę z kilkoma innymi usługami.

Istio dodaje do klastra kilka własnych obiektów, które pozwalają na zarządzanie ruchem. Jednym z kich jest DestinationRule. Za pomocą tego obiektu zdefiniujemy sobie tak zwane subsety. Dwa. Po jednym dla każdej wersji naszej aplikacji. Znowu skorzystamy z labelek w poszczególnych deploymentach.

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  namespace: srv
  name: mt1
spec:
  host: mt1.srv.svc.cluster.local
  subsets:
  - name: mt1
    labels:
      app: mt1
      ver: "1"
  - name: mt2
    labels:
      app: mt1
      ver: "2"

W Destination rule definiujemy między innymi to, co ma się stać z ruchem, który zostanie skierowany w konkretne miejsce. A w te miejsca ruch będzie kierował kolejny obiekt Istio, czyli Virtual Service. To właśnie tutaj zdefiniujemy zasady routingu. Nasze nie są zbyt skomplikowane, mamy tylko jedną ścieżkę. Ale za jego pomocą możemy także zdefiniować jaka część ruchu ma być przesyłana do poszczególnych wersji. W naszym przypadku, prześlemy 80% ruchu do wersji pierwszej i 20% do wersji drugiej.

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: mt1
  namespace: srv
spec:
  hosts:
  - mt1.srv.svc.cluster.local
  http:
  - match:
    - uri:
        prefix: /
    route:
      - destination:
          host: mt1.srv.svc.cluster.local
          subset: mt1
          port:
            number: 8080
        weight: 80
      - destination:
          host: mt1.srv.svc.cluster.local
          subset: mt2
          port:
            number: 8080
        weight: 20

Dodajemy oba obiekty do klastra i spróbujmy ponownie sprawdzić jak kierowany jest ruch. Tym razem już przez Istio. Ponownie tworzymy dodatkowego poda kubectl run test --image=przemekmalak/tools --rm -it -n srv -- sh

Zwróćcie uwagę, że tworzę go w przestrzeni nazw srv. I ponownie, wewnątrz poda wykonujemy:

for i in `seq 1 10`
do
	curl -m 3 mt1.srv.svc.cluster.local
done

I w efekcie powinniśmy dostać coś zbliżonego do

20% ruchu poszło do wersji 2, a 80% do wersji pierwszej.

Dla leniwych

Dołączam dwa pliki, które możecie wykorzystać. Po rozpakowaniu wystarczy wykonać dwa polecenia: kubectl apply -f 1_services.yaml oraz kubectl apply -f 2_virtual_services.yaml

Po chwili na klastrze powinniście mieć nasze serwisy i dodatkowy pod o nazwie debug , z którego możecie sprawdzić jak rozrzucany jest ruch.

Comments are closed.