Než se pustíme do Istio, opět připomenu, že jsem nejprve psal o tom, proč Service Mesh ano, ale také proč ne. Nemyslím si, že je pro každého a jsou i jiné, v některých případech lepší, varianty. Také připomínám svůj hodně zjednodušený pohled na tři nejznámější zástupce:
Dnes se vrhneme na ten funkčně nejbohatší - Istio. Protože už máme za sebou Linkerd, nebudu nutně opakovat všechny scénáře, které umí Istio taky, ale víc se zaměřím na speciality, které Istio posouvají před ostatní. Nezapomínejme ale na overhead v podobě spotřeby zdrojů a přidané latence - zkrátka jako vždy je tu něco za něco.
Všechny soubory použité v tomto článku najdete na mém GitHubu
Istio je výrazně složitější, než Linkerd nebo Consul, ale pokud nechceme nic speciálního a všechno jde dobře, není instalace samotná nijak komplikovaná. Podobně jako u mnoha dalších nadstaveb Kubernetu si stáhneme speciální CLI (Linkerd, DAPR nebo Azure Functions na to jdou stejně).
cd ./istio
wget https://github.com/istio/istio/releases/download/1.4.3/istioctl-1.4.3-linux.tar.gz
tar -xvf istioctl-1.4.3-linux.tar.gz
sudo mv ./istioctl /usr/local/bin/
rm -rf istioctl-1.4.3-linux.tar.gz
Pro Istio si vytvořím namespace a pro instalaci GUI potřebujeme Secret s loginem. Pro zjednodušení použiji soubory ze svého GitHubu s loginem user/Azure12345678.
kubectl create namespace istio-system --save-config
kubectl apply -f grafanaSecret.yaml
kubectl apply -f kialiSecret.yaml
Istiu předhodíme konfigurační soubor. V něm říkám, že chci nasadit i GUI komponenty, že pro ně chci ověřování a prozatím nebudu vyžadovat mTLS mezi službami. Co je ale pro moje zkoušení také důležité je snížit výchozí nároky Istio na zdroje, jinak se mi do levného testovacího clusteru nevejde. Na produkci to nedělejte, Istio je skutečně známé tím, že potřebuje hodně.
apiVersion: install.istio.io/v1alpha2
kind: IstioControlPlane
spec:
# Use the default profile as the base
# More details at: https://istio.io/docs/setup/additional-setup/config-profiles/
profile: default
traffic_management:
components:
pilot:
k8s:
resources:
limits:
cpu: 800m
memory: 500Mi
requests:
cpu: 500m
memory: 250Mi
values:
global:
# Ensure that the Istio pods are only scheduled to run on Linux nodes
defaultNodeSelector:
beta.kubernetes.io/os: linux
# Enable mutual TLS for the control plane
controlPlaneSecurityEnabled: true
mtls:
# Require all service to service communication to have mtls
enabled: false
grafana:
# Enable Grafana deployment for analytics and monitoring dashboards
enabled: true
security:
# Enable authentication for Grafana
enabled: true
kiali:
# Enable the Kiali deployment for a service mesh observability dashboard
enabled: true
tracing:
# Enable the Jaeger deployment for tracing
enabled: true
Pošleme do clusteru.
istioctl manifest apply -f istioConfig.yaml
Istio si nahodí několik CRD a svoje komponenty ať už pro Istio samotné (Citadel, gateway, Pilot, Policy, Sidecar Injector, …) nebo volitelný monitoring a GUI (Prometheus, Grafana, Kiali).
$ kubectl get crd
NAME CREATED AT
adapters.config.istio.io 2020-01-14T19:18:25Z
attributemanifests.config.istio.io 2020-01-14T19:18:25Z
authorizationpolicies.security.istio.io 2020-01-14T19:18:25Z
clusterrbacconfigs.rbac.istio.io 2020-01-14T19:18:25Z
destinationrules.networking.istio.io 2020-01-14T19:18:26Z
envoyfilters.networking.istio.io 2020-01-14T19:18:26Z
gateways.networking.istio.io 2020-01-14T19:18:26Z
handlers.config.istio.io 2020-01-14T19:18:26Z
healthstates.azmon.container.insights 2020-01-14T19:00:59Z
httpapispecbindings.config.istio.io 2020-01-14T19:18:26Z
httpapispecs.config.istio.io 2020-01-14T19:18:26Z
instances.config.istio.io 2020-01-14T19:18:26Z
serviceentries.networking.istio.io 2020-01-14T19:18:27Z
servicerolebindings.rbac.istio.io 2020-01-14T19:18:27Z
serviceroles.rbac.istio.io 2020-01-14T19:18:27Z
sidecars.networking.istio.io 2020-01-14T19:18:27Z
templates.config.istio.io 2020-01-14T19:18:27Z
virtualservices.networking.istio.io 2020-01-14T19:18:28Z
$ kubectl get pods -n istio-system
NAME READY STATUS RESTARTS AGE
grafana-6bc97ff99-768pl 1/1 Running 0 12h
istio-citadel-655bcc5ff-zsv87 1/1 Running 0 12h
istio-galley-864c4dfb4f-fjrlt 2/2 Running 0 12h
istio-ingressgateway-7cd748f5d7-cpcbr 1/1 Running 0 12h
istio-pilot-5fd5cd748f-8vqxf 2/2 Running 0 12h
istio-policy-b8597df85-72jhz 2/2 Running 2 12h
istio-sidecar-injector-f6c874dd9-gdlzk 1/1 Running 0 12h
istio-telemetry-8676d96986-qf4g4 2/2 Running 2 12h
istio-tracing-557f5dcd8c-vs8cz 1/1 Running 0 12h
kiali-59b7fd7f68-p7tvf 1/1 Running 0 12h
prometheus-7c7cf9dbd6-d6w79 1/1 Running 0 12h
Ke GUI se můžeme protunelovat přes Istio CLI, například Kiali (GUI pro Istio), Grafana (vizualizace telemetrie), Jaeger (distribuovaný tracing), Prometheus (monitoring) nebo GUI jednotlivých Envoy proxy v sidecar.
istioctl dashboard grafana
istioctl dashboard prometheus
istioctl dashboard jaeger
istioctl dashboard kiali
istioctl dashboard envoy <pod-name>.<namespace>
V rámci nastavení uděláme ještě jednu věc - zapneme si automatické vstřikování sidecar v namespace default (totéž můžete nastavovat i na úrovni jednotlivých objektů).
kubectl label namespace default istio-injection=enabled
Podobně jako u Linkerd nabízí Istio implementace retry logiky tak, že ji nemusíme dělat v aplikaci. Nasaďme si dvě služby - jednu client (z té to budeme zkoušet) a jednu jako backend.
kubectl apply -f client.yaml
kubectl apply -f retryBackend.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: client-deployment
spec:
replicas: 1
selector:
matchLabels:
app: client
template:
metadata:
labels:
app: client
spec:
containers:
- name: client
image: tkubica/mybox
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: retry-backend
spec:
replicas: 3
selector:
matchLabels:
app: retry
template:
metadata:
labels:
app: retry
spec:
containers:
- name: retry-backend
image: tkubica/retry-backend
ports:
- containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
name: retry-service
spec:
selector:
app: retry
ports:
- protocol: TCP
name: http
port: 80
targetPort: 80
Všimněte si, že po nasazení mám kromě hlavního kontejneru vytvořenu i sidecar s proxy. Stejně jako u Linkerd je nejdřív spuštěn privilegovanější Init kontejner, který připraví síťařinu tak, aby aplikace vůbec nepoznala, že komunikuje přes proxy (Istio má i CNI implementaci, která to řeší jinak).
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
client-deployment-7dcf76fc94-qzgk5 0/2 Pending 0 4s
retry-backend-8bc48b6fd-9vc79 0/2 Init:0/1 0 3s
retry-backend-8bc48b6fd-nsfhd 0/2 Init:0/1 0 3s
retry-backend-8bc48b6fd-sx74k 0/2 Pending 0 3s
kubectl get pod retry-backend-8bc48b6fd-9vc79 -o yaml
spec:
containers:
- image: tkubica/retry-backend
imagePullPolicy: Always
name: retry-backend
...
- name: istio-proxy
args:
...
env:
...
image: docker.io/istio/proxyv2:1.4.3
imagePullPolicy: IfNotPresent
ports:
- containerPort: 15090
name: http-envoy-prom
protocol: TCP
resources:
limits:
cpu: "2"
memory: 1Gi
requests:
cpu: 100m
memory: 128Mi
securityContext:
readOnlyRootFilesystem: true
runAsUser: 1337
...
volumeMounts:
- mountPath: /etc/istio/proxy
name: istio-envoy
- mountPath: /etc/certs/
name: istio-certs
readOnly: true
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: default-token-x8q7k
readOnly: true
initContainers:
- command:
- istio-iptables
- -p
- "15001"
- -z
- "15006"
- -u
- "1337"
- -m
- REDIRECT
- -i
- '*'
- -x
- ""
- -b
- '*'
- -d
- "15020"
image: docker.io/istio/proxyv2:1.4.3
imagePullPolicy: IfNotPresent
name: istio-init
...
securityContext:
capabilities:
add:
- NET_ADMIN
runAsNonRoot: false
runAsUser: 0
Istio má výchozí retry politiku, ale já si ji raději rovnou trochu přizpůsobím.
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: retry-vs
spec:
hosts:
- retry-service
http:
- route:
- destination:
host: retry-service
timeout: 15s
retries:
attempts: 15
perTryTimeout: 1s
retryOn: gateway-error,reset,5xx
kubectl apply -f retryVirtualService.yaml
Moje retry aplikace nechá Pod umřít (navíc s dvouvteřinovým zaseknutím) s pravděpodobností, kterou jí předáme.
export clientPod=$(kubectl get pods -l app=client -o jsonpath="{.items[0].metadata.name}")
kubectl exec $clientPod -c client -- curl -vs -m 30 "retry-service?failRate=50&mode=crash"
Někdy máme odpověď rychle, někdy trochu pomaleji. To je tím, že nám na pozadí jeden z Podů umřel a Istio to zkoušelo znova a znova, dokud nějaký z Podů neodpověděl. Všimněte si, že některé Pody byly restartovány.
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
client-deployment-7dcf76fc94-qzgk5 2/2 Running 0 18m
retry-backend-8bc48b6fd-9vc79 2/2 Running 0 18m
retry-backend-8bc48b6fd-nsfhd 2/2 Running 1 18m
retry-backend-8bc48b6fd-sx74k 2/2 Running 1 18m
Uděláme ještě jeden pokus. Služba teď nebude končit brutální smrtí, ale v rámci dané pravděpodobnosti odpoví chybou 503. Zvýšíme chybovost na 90%, ale necháme fail v režimu vrácení 503 (místo crash). Pošleme request a v druhém okně sledujte logy ze všech retry-backend podů. Měli bychom vidět několik pokusů, než se konečně podaří Istiu dostat rozumnou odpověď.
$ kubectl exec $clientPod -c client -- curl -vs -m 30 "retry-service?failRate=90&mode=busy"
$ kubectl logs -l app=retry -c retry-backend -f
127.0.0.1 - - [15/Jan/2020:09:05:24] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:05:24] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:05:24] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:05:24] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:05:25] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:05:25] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:05:25] "GET /?failRate=90&mode=busy HTTP/1.1" 200 2 "" "curl/7.58.0"
Podívejme se na pokročilejší funkci, kterou jednodušší service mesh technologie často neumožňují. V předchozím případě máme službu, která má pravděpodobnost selhání 90% a to je hodně. Možná bychom mohli mít následující myšlenky:
Vyzkoušíme si následující. Nastavíme politiku, že pokud Pod selže dvakrát během 10 vteřin, přestaneme na něj po dobu jedné minuty posílat provoz. Je pro nás lepší řízená smrt služby, než kulhání, tak dovolíme Istio odebrat klidně Pody všechny a rovnou vracet 503 bez dalšího zkoušení. Takhle taková definice vypadá a pošleme ji do clusteru.
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: retry-vs
spec:
hosts:
- retry-service
http:
- route:
- destination:
host: retry-service
timeout: 30s
retries:
attempts: 30
perTryTimeout: 1s
retryOn: gateway-error,reset,5xx
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: retry-service
spec:
host: retry-service
trafficPolicy:
connectionPool:
tcp:
maxConnections: 1
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
outlierDetection:
consecutiveErrors: 2
interval: 10s
baseEjectionTime: 1m
maxEjectionPercent: 100
kubectl apply -f circuitBreaker.yaml
V jednom okně si opět otevřeme logy ze všech backendů.
kubectl logs -l app=retry -c retry-backend -f
[15/Jan/2020:09:04:33] ENGINE Bus STARTED
127.0.0.1 - - [15/Jan/2020:09:05:24] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:21:21] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:21:21] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:21:21] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:21:21] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:21:22] "GET /?failRate=90&mode=busy HTTP/1.1" 200 2 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:21:27] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:21:27] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:21:27] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:21:27] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:21:27] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:21:27] "GET /?failRate=90&mode=busy HTTP/1.1" 200 2 "" "curl/7.58.0"
... tady nic není, pokusy končí na Istiu, obvod je rozpojen ....
... po minutě se přepínač vrátil do polohy otevřeno ....
127.0.0.1 - - [15/Jan/2020:09:23:06] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:23:06] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:23:06] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:23:06] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:23:06] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
127.0.0.1 - - [15/Jan/2020:09:23:06] "GET /?failRate=90&mode=busy HTTP/1.1" 503 1461 "" "curl/7.58.0"
Pošleme dvákrát či třikrát dotaz s 90% šancí neúspěchu.
kubectl exec $clientPod -c client -- curl -vs -m 30 "retry-service?failRate=90&mode=busy"
Co vidíme z předchozích logů? Nejdřív se doboucháme na odpověď 200, podruhé ještě taky, ale to už každý Pod selhal dvakrát během 10 vteřin a byl odpojen. Další requesty už do Podů nepřichází a dostávám 503 přímo od Istia rovnou. Když počkám minutu, traffic zase bude chodit i na backend. To je příklad circuit breaker.
Stejně jako v případě Linkerd nám může Istio pomoci s lepší balancingem provozu v rámci clusteru. Potíž standardního balancingu v Kubernetes je, že je postavena na L4 implementaci, takže pro dobré rozdělení zátěže potřebujete dobrou entropii v IP/TCP/UDP hlavičkách - hodně adres, hodně TCP spojení. Ne vždy je ale jednoduché toho dosáhnout, například pokud máte na jedné straně singleton (službu, která má jen jednu aktivní instanci) a z ní chcete balancovat provoz na backend službu nebo ta první služba není singleton, ale má málo instancí a dělá proxy (například NGINX Igress). Tím máte malou entropii v IP adresách. Teď do toho přidejte optimalizaci na straně klientů jako je použití HTTP/2 u gRPC komunikace (to efektivně snižuje množství TCP spojení, tedy různorodost TCP hlaviček v paketech) a máte problém s rozumným balancováním. Istio vám ho pomůže vyřešit, protože funguje na L7 a session si rozbaluje, takže dokáže rozhazovat zátěž.
Nasadíme si Deployment a Service. Neděste se, jsou to dva Deploymenty v různých verzích, což je příprava na příště, až budeme řešit traffic split - prozatím ignorujme.
apiVersion: apps/v1beta2
kind: Deployment
metadata:
name: myweb-deployment-v1
spec:
replicas: 3
selector:
matchLabels:
app: myweb
version: v1
template:
metadata:
labels:
app: myweb
version: v1
spec:
containers:
- name: myweb
image: tkubica/web:1
env:
- name: PORT
value: "80"
ports:
- containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myweb-deployment-v2
spec:
replicas: 3
selector:
matchLabels:
app: myweb
version: v2
template:
metadata:
labels:
app: myweb
version: v2
spec:
containers:
- name: myweb
image: tkubica/web:2
env:
- name: PORT
value: "80"
ports:
- containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
name: myweb-service
labels:
app: myweb
spec:
selector:
app: myweb
ports:
- protocol: TCP
name: http
port: 80
targetPort: 80
kubectl apply -f canary.yaml
Nastavme si Istio na jednoduchý balancing podle náhody. K dispozici jsou ale i další metody jako je podle počtu spojení, sekvenčně v round robin a tak podobně.
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio-lb
spec:
hosts:
- myweb-service
http:
- route:
- destination:
host: myweb-service
subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: istio-lb
spec:
host: myweb-service
trafficPolicy:
loadBalancer:
simple: RANDOM
subsets:
- name: v1
labels:
version: v1
kubectl apply -f lbRandom.yaml
Vyzkoušíme provoz a měli bychom dostávat odpověď od různých backendů.
$ kubectl exec $clientPod -c client -- bash -c 'while true; do curl -s myweb-service; echo; sleep 0.2; done'
Version 1: server id 2b1960d8-bf7d-47cb-bf4a-eb3ca464a6dd
Version 1: server id e307911d-5f12-47ec-a158-cb828fbe418a
Version 1: server id b47cb71c-6fda-47d7-8a51-5648b9572179
Version 2: server id d4f8f350-543d-4d82-af84-da75766931dc
Version 2: server id d4f8f350-543d-4d82-af84-da75766931dc
Version 2: server id c285cc5d-f18d-4403-8149-42328f16af08
Version 1: server id 2b1960d8-bf7d-47cb-bf4a-eb3ca464a6dd
Version 2: server id c2e141cb-ae05-41e0-bcf8-c250e83c351d
Představme si teď, že v Kubernetu provozuje například nějakou starší komponentu, která není bezestavová a state si drží v paměti (nebo to dělá protože má extrémní požadavky na výkon a držet state externě je pomalejší). Potřebujeme tedy zajistit, abychom si sice vybrali nějaký backend, ale následná komunikace probíhala stála přes něj a neskákali jsme na různé Pody. Pokud jsem správně četl, tak to Envoy (potažmo Istio) zatím nepodporuje přes Cookie, ale dá se použít Header. V zásadě nějaké políčko na základě kterého se použije konzistentní hash ring, takže příště vyjde stejně (jednoduchý příklad na vysvětlení by byl převod řetězce na číslo a následné modulo na počet živých backend serverů a tím dostaneme pořadové číslo serveru … reálný použitý mechanismus je jiný, ale konceptuálně podobný).
Nastavení změníme takhle:
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: istio-lb
spec:
hosts:
- myweb-service
http:
- route:
- destination:
host: myweb-service
subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: istio-lb
spec:
host: myweb-service
trafficPolicy:
loadBalancer:
consistentHash:
httpHeaderName: User
subsets:
- name: v1
labels:
version: v1
Pošleme do clusteru. Nejdřív zkusíme bez headeru, pak s headerem.
$ kubectl apply -f lbHeaderHash.yaml
$ kubectl exec $clientPod -c client -- bash -c 'while true; do curl -s myweb-service; echo; sleep 0.2; done'
Version 1: server id 2b1960d8-bf7d-47cb-bf4a-eb3ca464a6dd
Version 1: server id e307911d-5f12-47ec-a158-cb828fbe418a
Version 1: server id b47cb71c-6fda-47d7-8a51-5648b9572179
$ kubectl exec $clientPod -c client -- bash -c 'while true; do curl -H "User: tomas" -s myweb-service; echo; sleep 0.2; done'
Version 1: server id 2b1960d8-bf7d-47cb-bf4a-eb3ca464a6dd
Version 1: server id 2b1960d8-bf7d-47cb-bf4a-eb3ca464a6dd
Version 1: server id 2b1960d8-bf7d-47cb-bf4a-eb3ca464a6dd
Na závěr si ukážeme ještě jedno kouzlo. Někdy se může objevit problém, který se vám nedaří replikovat v testovacím prostředí. Zkoušet si to na produkci (například tam zapnout kompletní trasování requestů a debug logy dost možná způsobí omezení služby) nemusí být vhodné, ale zkopírování databází, sjednocení verzí, generování zátěže - nic z toho nevedlo k cíli. Musí tam být něco ve způsobu komunikace služeb mezi sebou nebo něco v requestu uživatele, těžko říct. Bylo by možná užitečné zachytit kopii tohoto provozu ale tak, abychom nijak nesahali do produkčního kontejneru (ve světě VM bychom mohli vzlézt na server a pustit tcpdump, ale i to může být výkonnostně riskantní a v kontejneru to je ještě horší, protože tcpdump asi nemáme v kontejnerovém obrazu a pokud ano, neměli bychom, když není pro provoz potřeba a i pokud tam je, potřebuje root nebo NET_ADMIN/NET_RAW capability a takový Pod byste rozhodně preferovat neměli). Pro situace typu nečekaná odpověď, nějaký neošetřený vstup, který službu sestřeluje nebo jiné než očekávané síťové chování (leze to z jiné adresy nebo to neleze vůbec) se může hodit.
Založím si službu sniffer, což je jednoduchý kontejner se spuštěným tcpdump.
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: sniffer
spec:
replicas: 1
selector:
matchLabels:
app: sniffer
template:
metadata:
labels:
app: sniffer
spec:
containers:
- name: sniffer
image: tkubica/sniffer
env:
- name: args
value: "tcp port 80"
ports:
- containerPort: 80
---
kind: Service
apiVersion: v1
metadata:
name: sniffer
spec:
selector:
app: sniffer
ports:
- protocol: TCP
name: http
port: 80
targetPort: 80
Změníme si teď VirtualService pro službu retry, kterou jsme dnes využívali. Přidáme mirror a kopie provozu tak půjde do snifferu.
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: retry-vs
spec:
hosts:
- retry-service
http:
- route:
- destination:
host: retry-service
mirror:
host: sniffer
Nahodíme a pošleme z client 20 dotazů na retry službu. Očekáváme, že budeme dostávat kopii provozu do snifferu, což uvidíme v jeho logu.
kubectl apply -f sniffer.yaml
kubectl apply -f copyVirtualService.yaml
export clientPod=$(kubectl get pods -l app=client -o jsonpath="{.items[0].metadata.name}")
kubectl exec $clientPod -c client -- bash -c 'for x in {0..20}; do curl -s retry-service?failRate=1&mode=busy; done'
export snifferPod=$(kubectl get pods -l app=sniffer -o jsonpath="{.items[0].metadata.name}")
kubectl logs $snifferPod -c sniffer
$ kubectl logs $snifferPod -c sniffer
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
11:06:28.703034 IP sniffer-86f57f7576-5fmt2.37916 > 169.254.169.254.80: Flags [S], seq 1214248727, win 29200, options [mss
1460,sackOK,TS val 1772460481 ecr 0,nop,wscale 7], length 0
11:06:28.704283 IP 169.254.169.254.80 > sniffer-86f57f7576-5fmt2.37916: Flags [S.], seq 1221788184, ack 1214248728, win 8192, options [mss 1460,nop,wscale 8,sackOK,TS val 2737481901 ecr 1772460481], length 0
11:06:28.704301 IP sniffer-86f57f7576-5fmt2.37916 > 169.254.169.254.80: Flags [.], ack 1, win 229, options [nop,nop,TS val
1772460482 ecr 2737481901], length 0
11:06:28.704417 IP sniffer-86f57f7576-5fmt2.37916 > 169.254.169.254.80: Flags [P.], seq 1:96, ack 1, win 229, options [nop,nop,TS val 1772460482 ecr 2737481901], length 95: HTTP: GET / HTTP/1.1
11:06:28.706250 IP 169.254.169.254.80 > sniffer-86f57f7576-5fmt2.37916: Flags [P.], seq 1:150, ack 96, win 8211, options [nop,nop,TS val 2737481903 ecr 1772460482], length 149: HTTP: HTTP/1.1 400 Bad Request
11:06:28.706268 IP sniffer-86f57f7576-5fmt2.37916 > 169.254.169.254.80: Flags [.], ack 150, win 237, options [nop,nop,TS val 1772460484 ecr 2737481903], length 0
11:06:28.706342 IP sniffer-86f57f7576-5fmt2.37916 > 169.254.169.254.80: Flags [F.], seq 96, ack 150, win 237, options [nop,nop,TS val 1772460484 ecr 2737481903], length 0
11:06:28.706719 IP 169.254.169.254.80 > sniffer-86f57f7576-5fmt2.37916: Flags [F.], seq 150, ack 97, win 8211, options [nop,nop,TS val 2737481903 ecr 1772460484], length 0
11:06:28.706726 IP sniffer-86f57f7576-5fmt2.37916 > 169.254.169.254.80: Flags [.], ack 151, win 237, options [nop,nop,TS val 1772460485 ecr 2737481903], length 0
11:06:57.171776 IP 10.240.0.232.54628 > sniffer-86f57f7576-5fmt2.80: Flags [S], seq 3851720429, win 29200, options [mss 1460,sackOK,TS val 2671513308 ecr 0,nop,wscale 7], length 0
11:06:57.171799 IP sniffer-86f57f7576-5fmt2.80 > 10.240.0.232.54628: Flags [S.], seq 4269784310, ack 3851720430, win 28960, options [mss 1460,sackOK,TS val 2882202838 ecr 2671513308,nop,wscale 7], length 0
11:06:57.171868 IP 10.240.0.232.54628 > sniffer-86f57f7576-5fmt2.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 2671513308 ecr 2882202838], length 0
11:06:57.171967 IP 10.240.0.232.54628 > sniffer-86f57f7576-5fmt2.80: Flags [P.], seq 1:844, ack 1, win 229, options [nop,nop,TS val 2671513308 ecr 2882202838], length 843: HTTP: GET /?failRate=1 HTTP/1.1
11:06:57.171973 IP sniffer-86f57f7576-5fmt2.80 > 10.240.0.232.54628: Flags [.], ack 844, win 240, options [nop,nop,TS val 2882202838 ecr 2671513308], length 0
11:06:57.173172 IP sniffer-86f57f7576-5fmt2.80 > 10.240.0.232.54628: Flags [P.], seq 1:232, ack 844, win 240, options [nop,nop,TS val 2882202839 ecr 2671513308], length 231: HTTP: HTTP/1.1 503 Service Unavailable
11:06:57.173190 IP 10.240.0.232.54628 > sniffer-86f57f7576-5fmt2.80: Flags [.], ack 232, win 237, options [nop,nop,TS val 2671513309 ecr 2882202839], length 0
11:06:57.173821 IP 10.240.0.232.54628 > sniffer-86f57f7576-5fmt2.80: Flags [P.], seq 844:1687, ack 232, win 237, options [nop,nop,TS val 2671513310 ecr 2882202839], length 843: HTTP: GET /?failRate=1 HTTP/1.1
11:06:57.176912 IP sniffer-86f57f7576-5fmt2.80 > 10.240.0.232.54628: Flags [P.], seq 232:463, ack 1687, win 253, options [nop,nop,TS val 2882202843 ecr 2671513310], length 231: HTTP: HTTP/1.1 503 Service Unavailable
11:06:57.178624 IP 10.240.0.232.54628 > sniffer-86f57f7576-5fmt2.80: Flags [P.], seq 1687:2530, ack 463, win 245, options [nop,nop,TS val 2671513314 ecr 2882202843], length 843: HTTP: GET /?failRate=1 HTTP/1.1
11:06:57.178995 IP sniffer-86f57f7576-5fmt2.80 > 10.240.0.232.54628: Flags [P.], seq 463:694, ack 2530, win 266, options [nop,nop,TS val 2882202845 ecr 2671513314], length 231: HTTP: HTTP/1.1 503 Service Unavailable
11:06:57.185819 IP 10.240.0.232.54650 > sniffer-86f57f7576-5fmt2.80: Flags [S], seq 2212653296, win 29200, options [mss 1460,sackOK,TS val 2671513322 ecr 0,nop,wscale 7], length 0
11:06:57.185833 IP sniffer-86f57f7576-5fmt2.80 > 10.240.0.232.54650: Flags [S.], seq 2692512746, ack 2212653297, win 28960, options [mss 1460,sackOK,TS val 2882202852 ecr 2671513322,nop,wscale 7], length 0
11:06:57.185848 IP 10.240.0.232.54650 > sniffer-86f57f7576-5fmt2.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 2671513322 ecr 2882202852], length 0
Dnes jsme si ukázali první část zajímavých funkcí Istio, konkrétně retry, circuit breaker, balancing a kopírování provozu. Už z toho je vidět, že Istio je funkčně hodně vpředu, ale je to vyváženo složitostí a spotřebou zdrojů. Příště si ukážeme traffic split a také to, že Istio může nahrazovat Ingress (Linkerd to záměrně nedělá a s Ingress se kombinuje, Istio jde cestou umožňit alternativu přímo v sobě). Někdy po tom všem se zaměříme i na exporty a vizibilitu jako je distribuovaný tracing možná s napojením na Application Insights přes OpenTelemetry standard. Určitě bychom se měli pustit i do tématu služeb v ruzných clusterech, cloudech nebo technologiích, což některé Service Mesh implementace umožňují.