Zum Inhalt springen

Istio Service Mesh

Istio ist ein Service Mesh für Kubernetes. Diese Anleitung beschreibt die Installation in einem Gardener-Shoot-Cluster der noris Sovereign Cloud (nSC). Die konkrete Konfiguration, etwa für Traffic Management, mTLS, Tracing oder Canary Releases, obliegt Ihnen als Kundin oder Kunde.

Die Anleitung orientiert sich am Upstream-Tutorial Gardener yourself a Shoot with Istio, custom Domains, and Certificates.

Damit Istio mit dem Cilium-CNI zusammenarbeitet, sind zwei Anpassungen an der Netzwerkkonfiguration des Shoots erforderlich. Fügen Sie Folgendes zur Shoot-Deklaration hinzu:

apiVersion: core.gardener.cloud/v1beta1
kind: Shoot
...
spec:
networking:
type: cilium
providerConfig:
apiVersion: cilium.networking.extensions.gardener.cloud/v1alpha1
kind: NetworkConfig
bpfSocketLBHostnsOnly:
enabled: false
cni:
exclusive: false

Für eingehenden Traffic kann Istio als Ingress-Gateway dienen. Alternativ lässt sich auch die Kubernetes Gateway API einsetzen, die Istio ebenfalls unterstützt. Beide Varianten profitieren von TLS-Zertifikaten und DNS-Einträgen, die Gardener über die Erweiterungen shoot-cert-service und shoot-dns-service verwaltet. Diese sind im Abschnitt Zertifikate der Gardener-Dokumentation beschrieben. Die Ausstellung über Let’s Encrypt macht die Nutzung komfortabel und wird empfohlen, sie ist jedoch nicht zwingend erforderlich. Richten Sie beide Erweiterungen gemäß jener Anleitung ein, bevor Sie mit der Istio-Installation fortfahren.

Der dort vergebene Issuer-Name (im Beispiel custom-issuer) wird im nächsten Schritt bei der Istio-Installation referenziert.

Istio bietet zahlreiche Konfigurationsmöglichkeiten. Hier wird exemplarisch nur die Ingress-Gateway-Funktion konfiguriert. Weitere Optionen beschreibt die Istio-Dokumentation.

Installieren Sie istioctl gemäß der offiziellen Installationsanleitung.

Legen Sie anschließend die Umgebungsvariablen für Ihre Domain und den Issuer fest. Die Domain entspricht einer Subdomain Ihres Shoot-Clusters:

export domainname="istio-mesh.g5lxqfthcn.k8s.nsc02.noris.cloud"
export issuer="custom-issuer"

Installieren Sie Istio mit dem Default-Profil und annotieren Sie den Ingress-Gateway-Service, sodass Gardener DNS und Zertifikate übernimmt:

cat <<EOF | istioctl install -y -f -
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
profile: default
components:
ingressGateways:
- name: istio-ingressgateway
enabled: true
k8s:
serviceAnnotations:
cert.gardener.cloud/issuer: "${issuer}"
cert.gardener.cloud/secretname: wildcard-tls
dns.gardener.cloud/class: garden
dns.gardener.cloud/dnsnames: "${domainname}"
dns.gardener.cloud/ttl: "120"
EOF

Prüfen Sie, ob DNS-Einträge und Zertifikate erfolgreich erstellt wurden:

kubectl -n istio-system describe service istio-ingressgateway

Im Ereignisbereich sollten Meldungen wie certificate ready und dns entry active erscheinen. Die Ausstellung der Zertifikate über Let’s Encrypt dauert etwa 70 Sekunden, warten Sie diese Wartezeit ab, bevor Sie die Verifikation durchführen.

Verifizieren Sie den DNS-Eintrag:

dig ${domainname}

Weitere Schritte wie das Freigeben einer Anwendung über Istio Gateway- und VirtualService-Ressourcen beschreibt das Upstream-Tutorial im Abschnitt Ingress at Your Service.

Bei Konfigurationsproblemen hilft istioctl analyze.

Um Istio wieder zu entfernen:

istioctl uninstall --purge

Das folgende Beispiel demonstriert mTLS zwischen Namespaces. Es legt drei Namespaces an: foo und bar mit Istio-Injection, legacy ohne Sidecar. In foo wird PeerAuthentication mit STRICT mTLS erzwungen.

Speichern Sie das Manifest als mtls-demo.yaml und wenden Sie es an:

kubectl apply -f mtls-demo.yaml
---
# Namespace: foo (mit Istio injection)
apiVersion: v1
kind: Namespace
metadata:
name: foo
labels:
istio-injection: enabled
---
# Namespace: bar (mit Istio injection)
apiVersion: v1
kind: Namespace
metadata:
name: bar
labels:
istio-injection: enabled
---
# Namespace: legacy (ohne Istio injection)
apiVersion: v1
kind: Namespace
metadata:
name: legacy
---
# web in foo
apiVersion: v1
kind: ServiceAccount
metadata:
name: web
namespace: foo
---
apiVersion: v1
kind: Service
metadata:
name: web
namespace: foo
spec:
ports:
- port: 80
name: http
selector:
app: web
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: foo
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
serviceAccountName: web
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
---
# sleep in foo
apiVersion: v1
kind: ServiceAccount
metadata:
name: sleep
namespace: foo
---
apiVersion: v1
kind: Service
metadata:
name: sleep
namespace: foo
spec:
ports:
- port: 80
name: http
selector:
app: sleep
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
namespace: foo
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
serviceAccountName: sleep
containers:
- name: sleep
image: curlimages/curl:latest
command: ["/bin/sleep", "infinity"]
---
# sleep in bar
apiVersion: v1
kind: ServiceAccount
metadata:
name: sleep
namespace: bar
---
apiVersion: v1
kind: Service
metadata:
name: sleep
namespace: bar
spec:
ports:
- port: 80
name: http
selector:
app: sleep
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
namespace: bar
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
serviceAccountName: sleep
containers:
- name: sleep
image: curlimages/curl:latest
command: ["/bin/sleep", "infinity"]
---
# sleep in legacy (kein Sidecar)
apiVersion: v1
kind: ServiceAccount
metadata:
name: sleep
namespace: legacy
---
apiVersion: v1
kind: Service
metadata:
name: sleep
namespace: legacy
spec:
ports:
- port: 80
name: http
selector:
app: sleep
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
namespace: legacy
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
serviceAccountName: sleep
containers:
- name: sleep
image: curlimages/curl:latest
command: ["/bin/sleep", "infinity"]
---
# PeerAuthentication: STRICT mTLS im Namespace foo
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: foo
spec:
mtls:
mode: STRICT

Test 1: mTLS von bar nach foo, erwartet 200:

kubectl exec -n bar deploy/sleep -- curl -s -o /dev/null -w "%{http_code}" http://web.foo.svc.cluster.local/

Test 2: Plaintext von legacy nach foo wird durch STRICT mTLS geblockt, erwartet 000:

kubectl exec -n legacy deploy/sleep -- curl -s -o /dev/null -w "%{http_code}" http://web.foo.svc.cluster.local/

Test 3: Auf PERMISSIVE umstellen, dann geht Plaintext auch:

kubectl patch peerauthentication default -n foo --type=json -p='[{"op":"replace","path":"/spec/mtls/mode","value":"PERMISSIVE"}]'

Der Curl-Befehl aus Test 2 müsste jetzt 200 statt 000 zurückgeben.