Istio Configuration
This article covers the operational deployment configuration of Istio managed via ArgoCD. For the architectural decisions and role of Istio within the DEUSS platform, see Traffic Management.
ArgoCD Project Structure
The Istio deployment consists of three ArgoCD Applications to maintain proper separation and dependency ordering. All applications should be part of a dedicated ArgoCD Project for access control.
Directory Layout
istio-repo/
├── apps/
│ ├── istio-base.yaml
│ ├── istio-istiod.yaml
│ └── istio-gateway.yaml
├── istio/
│ ├── base/
│ │ └── values.yaml
│ ├── istiod/
│ │ └── values.yaml
│ └── gateway/
│ └── values.yaml
Istio Base Installation
The base chart installs Istio CRDs and cluster-wide resources. This must be deployed before istiod.
ArgoCD Application: istio-base.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: istio-base
namespace: argocd
spec:
project: infrastructure
source:
repoURL: https://istio-release.storage.googleapis.com/charts
chart: base
targetRevision: 1.24.0
helm:
valueFiles:
- $values/istio/base/values.yaml
destination:
server: https://kubernetes.default.svc
namespace: istio-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Values: istio/base/values.yaml
global:
istioNamespace: istio-system
defaultRevision: default
The base chart is minimal. Most configuration happens in istiod values.
Istiod Control Plane Installation
Istiod is the control plane component. This configuration sets up HA deployment with 3 replicas and namespace-scoped mTLS.
ArgoCD Application: istio-istiod.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: istio-istiod
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
spec:
project: infrastructure
source:
repoURL: https://istio-release.storage.googleapis.com/charts
chart: istiod
targetRevision: 1.24.0
helm:
valueFiles:
- $values/istio/istiod/values.yaml
destination:
server: https://kubernetes.default.svc
namespace: istio-system
syncPolicy:
automated:
prune: true
selfHeal: true
Values: istio/istiod/values.yaml
pilot:
autoscaleEnabled: true
autoscaleMin: 3
autoscaleMax: 5
replicaCount: 3
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 500m
memory: 1Gi
podDisruptionBudget:
minAvailable: 2
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: istiod
topologyKey: kubernetes.io/hostname
global:
proxy:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
logging:
level: "default:info"
meshConfig:
enablePrometheusMerge: true
enableTracing: false
accessLogFile: /dev/stdout
defaultConfig:
holdApplicationUntilProxyStarts: true
proxyMetadata: {}
Istio Gateway Installation
The gateway handles ingress traffic. Deploy it in a separate namespace for isolation and easier management.
ArgoCD Application: istio-gateway.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: istio-gateway
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "2"
spec:
project: infrastructure
source:
repoURL: https://istio-release.storage.googleapis.com/charts
chart: gateway
targetRevision: 1.24.0
helm:
valueFiles:
- $values/istio/gateway/values.yaml
destination:
server: https://kubernetes.default.svc
namespace: istio-ingress
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
Option A: LoadBalancer with TLS Termination
Use this configuration when you have LoadBalancer support (MetalLB, cloud provider, or K3S ServiceLB) and want to terminate TLS at the Istio gateway.
Gateway Values: istio/gateway/values.yaml
replicaCount: 2
service:
type: LoadBalancer
loadBalancerIP: "192.168.1.100" # Set your IP here
ports:
- name: http
port: 80
targetPort: 8080
- name: https
port: 443
targetPort: 8443
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 5
podDisruptionBudget:
minAvailable: 1
Option B: Cloudflare Tunnel Integration
Use this when external LoadBalancer isn't available or when you prefer Cloudflare's edge for DDoS protection and CDN. Traffic flows: Internet → Cloudflare → cloudflared pod → Istio Gateway (ClusterIP) → workloads.
Gateway Values for Cloudflared
replicaCount: 2
service:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
Cloudflared Deployment
Deploy cloudflared in the istio-ingress namespace pointing to the gateway service.
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudflared
namespace: istio-ingress
spec:
replicas: 2
selector:
matchLabels:
app: cloudflared
template:
metadata:
labels:
app: cloudflared
spec:
containers:
- name: cloudflared
image: cloudflare/cloudflared:latest
args:
- tunnel
- --config
- /etc/cloudflared/config.yaml
- run
volumeMounts:
- name: config
mountPath: /etc/cloudflared
readOnly: true
- name: credentials
mountPath: /etc/cloudflared/creds
readOnly: true
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
volumes:
- name: config
configMap:
name: cloudflared-config
- name: credentials
secret:
secretName: cloudflared-credentials
Cloudflared ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: cloudflared-config
namespace: istio-ingress
data:
config.yaml: |
tunnel: YOUR_TUNNEL_ID
credentials-file: /etc/cloudflared/creds/credentials.json
ingress:
- hostname: app.example.com
service: http://istio-gateway.istio-ingress.svc.cluster.local:80
- hostname: api.example.com
service: http://istio-gateway.istio-ingress.svc.cluster.local:80
- service: http_status:404
TLS termination happens at Cloudflare edge. Traffic between cloudflared and Istio gateway is HTTP within the cluster. For end-to-end encryption within the cluster, configure Istio Gateway to use mTLS.
Certificate Management
Two approaches for TLS certificate management: automated via cert-manager or manual secret creation.
Option A: cert-manager Integration
Requires cert-manager installed in the cluster with a configured ClusterIssuer.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: app-example-com
namespace: istio-ingress
spec:
secretName: app-example-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
commonName: app.example.com
dnsNames:
- app.example.com
Option B: Manual Certificate
Create TLS secret manually from existing certificate files.
kubectl create secret tls app-example-com-tls \
--cert=fullchain.pem \
--key=privkey.pem \
-n istio-ingress
Or as YAML manifest:
apiVersion: v1
kind: Secret
metadata:
name: app-example-com-tls
namespace: istio-ingress
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded-cert>
tls.key: <base64-encoded-key>
Gateway and VirtualService Configuration
Gateway Resource with TLS
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: main-gateway
namespace: istio-ingress
spec:
selector:
istio: gateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: app-example-com-tls
hosts:
- app.example.com
- port:
number: 80
name: http
protocol: HTTP
tls:
httpsRedirect: true
hosts:
- app.example.com
Gateway Resource for Cloudflared (HTTP only)
apiVersion: networking.istio.io/v1
kind: Gateway
metadata:
name: main-gateway
namespace: istio-ingress
spec:
selector:
istio: gateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- app.example.com
- api.example.com
VirtualService Example
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: app-routing
namespace: production
spec:
hosts:
- app.example.com
gateways:
- istio-ingress/main-gateway
http:
- match:
- uri:
prefix: /api
route:
- destination:
host: api-service
port:
number: 8080
- route:
- destination:
host: frontend-service
port:
number: 80
mTLS Configuration
The setup uses PERMISSIVE mode globally, with STRICT mode enforced per namespace. This allows gradual adoption and supports mixed environments where some services don't have sidecars.
Global PeerAuthentication (PERMISSIVE)
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: default
namespace: istio-system
spec:
mtls:
mode: PERMISSIVE
Namespace-Level STRICT mTLS
Apply this to namespaces requiring strict mTLS enforcement.
apiVersion: security.istio.io/v1
kind: PeerAuthentication
metadata:
name: strict-mtls
namespace: production # Apply per namespace
spec:
mtls:
mode: STRICT
Namespace Configuration
Enabling Sidecar Injection
Label namespaces to enable automatic sidecar injection.
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
istio-injection: enabled
Or add label to existing namespace:
kubectl label namespace production istio-injection=enabled
Cross-Namespace Communication
Services in namespaces without sidecars can still communicate with mTLS-enabled namespaces because PERMISSIVE mode is set globally. The mesh services accept both mTLS and plaintext connections.
For services outside the mesh calling into STRICT namespaces, use DestinationRule to disable mTLS for specific sources:
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: allow-plaintext-from-external
namespace: production
spec:
host: my-service.production.svc.cluster.local
trafficPolicy:
tls:
mode: ISTIO_MUTUAL
Excluding Pods from Injection
Exclude specific pods using annotation:
apiVersion: v1
kind: Pod
metadata:
annotations:
sidecar.istio.io/inject: "false"
Cilium CNI Considerations
Running Istio with Cilium requires attention to potential overlaps in functionality and proper configuration.
Kube-proxy Replacement
If Cilium runs in kube-proxy replacement mode, Istio works without issues. Traffic interception uses iptables rules injected by the sidecar, which operates independently of Cilium's eBPF-based routing.
Network Policy Interaction
Both Cilium and Istio can enforce network policies. Recommended approach: use Cilium for L3/L4 policies (namespace isolation, pod-to-pod restrictions) and Istio AuthorizationPolicy for L7 policies (HTTP path-based rules, JWT validation).
Socket-Level Load Balancing
Cilium's socket-level load balancing can bypass iptables, potentially skipping Istio sidecars. If using this feature, ensure proper configuration or disable it for namespaces with Istio injection. Add to Cilium ConfigMap:
bpf-lb-sock-hostns-only: "true" This restricts socket LB to host namespace, allowing pod traffic to flow through sidecars.
Verification and Testing
Installation Verification
- Check istiod pods are running with HA replicas:
kubectl get pods -n istio-system -l app=istiod
- Verify gateway deployment:
kubectl get pods -n istio-ingress
- Check service and LoadBalancer IP (if applicable):
kubectl get svc -n istio-ingress
- Verify CRDs installed:
kubectl get crds | grep istio
mTLS Verification
Check if mTLS is active between services using istioctl:
istioctl x authz check <pod-name> -n <namespace>
istioctl proxy-config endpoint <pod-name> -n <namespace>
Verify PeerAuthentication policies:
kubectl get peerauthentication -A
Troubleshooting
Common Issues
- Sidecar not injecting: Verify namespace label (istio-injection=enabled) and check if the pod has exclusion annotation.
- 503 errors after enabling STRICT mTLS: Source service may not have sidecar. Check with 'istioctl analyze -n <namespace>'.
- Gateway not receiving traffic: Verify selector labels match, check LoadBalancer status, review gateway logs.
- Certificate issues: Ensure secret exists in istio-ingress namespace and credentialName matches.
- Cross-namespace routing fails: Verify VirtualService gateway reference includes namespace (istio-ingress/main-gateway).
Diagnostic Commands
## Analyze configuration for issues
istioctl analyze -n <namespace>
## Check proxy status
istioctl proxy-status
## View proxy configuration
istioctl proxy-config cluster <pod-name> -n <namespace>
## Debug sidecar injection
kubectl describe pod <pod-name> -n <namespace> | grep -A5 istio
## View gateway logs
kubectl logs -n istio-ingress -l istio=gateway -f
Resource Limits Reference
| Component | Requests | Limits |
|---|---|---|
| Istiod | 200m CPU, 256Mi | 500m CPU, 1Gi |
| Gateway | 100m CPU, 128Mi | 500m CPU, 512Mi |
| Sidecar Proxy | 50m CPU, 64Mi | 200m CPU, 256Mi |
| Cloudflared | 50m CPU, 64Mi | 200m CPU, 256Mi |
Maintenance Notes
- Istio upgrades should follow canary pattern: deploy new control plane, migrate workloads, remove old version
- Monitor certificate expiration for manually managed certificates
- Review ArgoCD sync status after cluster upgrades
- Periodically run 'istioctl analyze' to catch configuration drift