Skip to main content

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

  1. Check istiod pods are running with HA replicas:
kubectl get pods -n istio-system -l app=istiod
  1. Verify gateway deployment:
kubectl get pods -n istio-ingress
  1. Check service and LoadBalancer IP (if applicable):
kubectl get svc -n istio-ingress
  1. 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

ComponentRequestsLimits
Istiod200m CPU, 256Mi500m CPU, 1Gi
Gateway100m CPU, 128Mi500m CPU, 512Mi
Sidecar Proxy50m CPU, 64Mi200m CPU, 256Mi
Cloudflared50m CPU, 64Mi200m 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