Istio Basics
1. Download Istio CLI
curl -L https://istio.io/downloadIstio | sh -
cd istio-*
export PATH=$PWD/bin:$PATH
2. Install Istio control plane
istioctl install --set profile=demo -y
kubectl get pods -n istio-system
3. Enable auto sidecar injection on prod
kubectl create namespace prod
kubectl label namespace prod istio-injection=enabled
# Confirm:
kubectl get ns --show-labels
kubectl get namespace -L istio-injection
4. Open Kiali UI
# Kiali's UI
kubectl port-forward --address=0.0.0.0 svc/kiali 3000:20001
# Then open: http://localhost:3000
5. Verify sidecar injection (pods should be 2/2 Ready)
kubectl -n prod create deploy node-app --image siddharth67/node-service:v1
kubectl describe pod node-app-786cb44f85-wkq99 -n prod | grep -A2 "Containers:"
❯❯❯ kubectl describe pod node-app-786cb44f85-wkq99 -n prod | grep -A2 "Containers:" ⎈ (kind-kind/prod)
Init Containers:
istio-init:
Container ID: containerd://2b5ae904579ca1739120831e123b29d23ee3649c6e615f9b4fd1b24f07bbfea7
--
Containers:
node-service:
Container ID: containerd://97484d714ff2c66bf40408aa674b75c049f32f0f9bfbd8d76b7a17f784bbb53b
Istio Ingress Gateway
The Istio Ingress Gateway is a specialized Envoy proxy that handles inbound traffic from outside the service mesh into your cluster.
What it does:
- Acts as the entry point for external traffic
- Provides load balancing, TLS termination, and protocol handling
- Replaces traditional ingress controllers in Istio environments
- Runs as a deployment (not a sidecar)
Virtual Service
Virtual Service defines routing rules - it tells the gateway WHERE to send the traffic once it enters the mesh.
What it does:
- Defines traffic routing logic
- Handles path-based routing, header matching, traffic splitting
- Works with both ingress traffic (via Gateway) and internal traffic
- Provides advanced routing features like retries, timeouts, fault injection
6. Bring traffic through Istio ingress (so Kiali can see it)
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: devsecops-gateway
namespace: prod
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: devsecops-vs
namespace: prod
spec:
hosts:
- "*"
gateways:
- devsecops-gateway
http:
- match:
- uri:
prefix: /plusone
route:
- destination:
host: devsecops-svc
port:
number: 5000
End-to-end path:
Client → Istio ingressgateway (port 80) → VirtualService match /plusone → ClusterIP Service devsecops-svc:5000 → Pod.
kubectl -n prod apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: devsecops
name: devsecops
spec:
replicas: 3
selector:
matchLabels:
app: devsecops
template:
metadata:
labels:
app: devsecops
spec:
serviceAccountName: default
volumes:
- name: vol
emptyDir: {}
containers:
- name: devsecops-container
image: siddharth67/node-service:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
volumeMounts:
- mountPath: /tmp
name: vol
securityContext:
capabilities:
drop: [ "NET_RAW" ]
runAsUser: 100
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: false
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
labels:
app: devsecops
name: devsecops-svc
spec:
selector:
app: devsecops
ports:
- port: 8080
targetPort: 8080
protocol: TCP
type: ClusterIP
EOF
7. Send traffic through the mesh
kubectl -n istio-system port-forward svc/istio-ingressgateway 8080:80
while true; do curl -s http://localhost:8080/plusone/99; echo; sleep .2; done
Kiali will not see the traffic if you're not routing through the Istio gateway, so if we want to see the workload through the Kiali UI, we should create IngressGateway and VirtualService objects.
Here's why: Kiali visualizes service mesh traffic by reading telemetry data that Istio's sidecars (Envoy proxies) collect. For this to work, your traffic needs to flow through Istio's data plane components.
Istio mTLS - Auto mutual TLS
mTLS (mutual TLS) with Istio provides automatic encryption and authentication between services in your mesh. Here's how it works:
Default Behavior:
Istio enables mTLS automatically when sidecars are injected:
- Traffic between services with sidecars is automatically encrypted
- Uses permissive mode by default (accepts both mTLS and plain text)
- No configuration needed for basic mTLS
App A ──(plaintext)──► Sidecar A ──(mTLS)──► Sidecar B ──(plaintext)──► App B
PeerAuthentication modes
1. PERMISSIVE (Default)
Traffic accepted:
✅ mTLS from other sidecars
✅ Plain text from non-mesh services
✅ Plain text from services without sidecars
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: prod
spec:
mtls:
mode: PERMISSIVE # Accepts both mTLS and plaintext
2. STRICT
Traffic accepted:
✅ mTLS from other sidecars
❌ Plain text traffic (rejected with connection errors)
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: enforce-mtls
namespace: prod
spec:
mtls:
mode: STRICT # Only accepts mTLS traffic
3. DISABLE
Traffic accepted:
❌ mTLS traffic (rejected)
✅ Plain text traffic only
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: disable-mtls
namespace: prod
spec:
mtls:
mode: DISABLE
❯❯❯ k get crd
NAME CREATED AT
authorizationpolicies.security.istio.io 2025-09-05T13:29:53Z
destinationrules.networking.istio.io 2025-09-05T13:29:52Z
envoyfilters.networking.istio.io 2025-09-05T13:29:52Z
gateways.networking.istio.io 2025-09-05T13:29:52Z
peerauthentications.security.istio.io 2025-09-05T13:29:54Z #<<##########
proxyconfigs.networking.istio.io 2025-09-05T13:29:52Z
requestauthentications.security.istio.io 2025-09-05T13:29:54Z
serviceentries.networking.istio.io 2025-09-05T13:29:52Z
sidecars.networking.istio.io 2025-09-05T13:29:53Z
telemetries.telemetry.istio.io 2025-09-05T13:29:54Z
virtualservices.networking.istio.io 2025-09-05T13:29:53Z
wasmplugins.extensions.istio.io 2025-09-05T13:29:51Z
workloadentries.networking.istio.io 2025-09-05T13:29:53Z
workloadgroups.networking.istio.io 2025-09-05T13:29:53Z
❯❯❯ k get peerauthentications
No resources found in prod namespace.
❯❯❯ k get pa # peerauthentications
No resources found in prod namespace.
8. MTLS Modes:
kubectl apply <<'EOF'
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: mtls
namespace: prod
spec:
mtls:
mode: PERMISSIVE
EOF
Apply the Deployment below for testing:
apiVersion: apps/v1
kind: Deployment
metadata:
name: devsecops-client
namespace: prod
labels:
app: devsecops-client
spec:
replicas: 1
selector:
matchLabels:
app: devsecops-client
template:
metadata:
labels:
app: devsecops-client
spec:
containers:
- name: curl
image: curlimages/curl:latest
args:
- sh
- -c
- |
while true; do
echo "$(date) -> $(curl -sS -o /dev/null -w '%{http_code}' http://devsecops-svc.prod.svc.cluster.local:5000/health || echo 'curl-failed')";
sleep 5;
done
securityContext:
runAsNonRoot: true
runAsUser: 1000
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
kubectl apply -f deploy.yaml
❯❯❯ k get pa
NAME MODE AGE
mtls PERMISSIVE 103s
❯❯❯ k describe pa mtls
Name: mtls
Namespace: prod
Labels: <none>
Annotations: <none>
API Version: security.istio.io/v1
Kind: PeerAuthentication
Metadata:
Creation Timestamp: 2025-09-06T13:04:22Z
Generation: 1
Resource Version: 48804
UID: 1c9f7fbd-a1a3-4135-9a8a-94e7efb65e9e
Spec:
Mtls:
Mode: PERMISSIVE
Events: <none>
❯❯❯ cat pa.yaml
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: mtls
namespace: prod
spec:
mtls:
mode: DISABLE
❯❯❯ k apply -f pa.yaml
peerauthentication.security.istio.io/mtls configured
❯❯❯ k get pa
NAME MODE AGE
mtls DISABLE 10m
❯❯❯ k describe pa mtls
Name: mtls
Namespace: prod
Labels: <none>
Annotations: <none>
API Version: security.istio.io/v1
Kind: PeerAuthentication
Metadata:
Creation Timestamp: 2025-09-06T13:14:56Z
Generation: 2
Resource Version: 51468
UID: 17008123-2ca7-414c-9b37-136fdc9a67ad
Spec:
Mtls:
Mode: DISABLE
Events: <none>
Top comments (0)