Skip to content

Base Workloads With Istio and Argo CD

First PublishedByAtif Alam

This example shows which files to add for a minimal application + Istio baseline on an already running cluster. It does not create the cluster, install Istio, install Argo CD, or configure public DNS/TLS for a real domain — those are covered in Istio, GitOps, TLS and Certificates, and Ingress Controllers.

Naming: “Base” here means a starting set of workloads and mesh config, not provisioning a new cluster from scratch.

  • Working kubectl and the correct context (kubectl config current-context).
  • Istio control plane already installed — see Install (istioctl).
  • For Argo CD: controller running in the cluster and a Git repo Argo CD can reach; install steps are on GitOps — ArgoCD, not repeated here.
  • If Kubernetes YAML is new, skim Core Objects.
  • Sample app runs in namespace demo.
  • Istio ingress gateway exists in istio-system (default install).
  • The app image is a public image (nginx below); replace with your registry in production.

Use a layout like this in Git (paths are illustrative):

examples/
base-with-istio/ # Argo CD spec.source.path points here (workloads + Istio only)
namespaces.yaml
app/
deployment.yaml
service.yaml
istio/
gateway.yaml
virtualservice.yaml
destinationrule.yaml # optional
peerauthentication.yaml # optional
argocd/
demo-application.yaml # Apply separately; keep outside synced path to avoid recursion
File / folderPurpose
namespaces.yamlCreates demo and enables Istio sidecar injection on that namespace.
app/deployment.yamlRuns the workload (example: nginx).
app/service.yamlClusterIP Service; port name uses an http prefix for Istio protocol detection.
istio/gateway.yamlIstio Gateway on the ingress gateway (usually istio-system).
istio/virtualservice.yamlRoutes from the gateway to the Kubernetes Service.
istio/destinationrule.yamlOptional: subsets, TLS mode to upstream, outlier detection.
istio/peerauthentication.yamlOptional: mesh mTLS mode for a namespace — be careful with STRICT during migration.
examples/argocd/demo-application.yamlArgo CD Application — apply once from outside the synced path (see Path B (Argo CD)).
apiVersion: v1
kind: Namespace
metadata:
name: demo
labels:
istio-injection: enabled

For revision-based Istio installs, replace istio-injection: enabled with the label your platform uses (for example istio.io/rev: <revision>). See Istio.

apiVersion: apps/v1
kind: Deployment
metadata:
name: demo-app
namespace: demo
spec:
replicas: 1
selector:
matchLabels:
app: demo-app
template:
metadata:
labels:
app: demo-app
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80

Name the Service port so Istio infers HTTP (for example http or http-web):

apiVersion: v1
kind: Service
metadata:
name: demo-app
namespace: demo
spec:
selector:
app: demo-app
ports:
- name: http-web
port: 80
targetPort: 80

The Gateway attaches listeners to the ingress gateway workload in istio-system:

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: demo-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"

Bind traffic that enters through the gateway to the Kubernetes Service DNS name (demo-app.demo.svc.cluster.local or short name demo-app inside the same namespace):

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: demo-vs
namespace: demo
spec:
hosts:
- "*"
gateways:
- istio-system/demo-gateway
http:
- route:
- destination:
host: demo-app
port:
number: 80

Hosts on the VirtualService must align with how clients call the app (SNI/Host header vs in-cluster names). Mismatches often surface as 404 or routing to the wrong service.

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: demo-app
namespace: demo
spec:
host: demo-app.demo.svc.cluster.local
trafficPolicy:
tls:
mode: ISTIO_MUTUAL

Using STRICT before every client has a sidecar can break traffic. Prefer PERMISSIVE until the mesh is ready.

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: demo
spec:
mtls:
mode: PERMISSIVE
  1. kubectl apply -f namespaces.yaml
  2. kubectl apply -f app/deployment.yaml -f app/service.yaml
  3. kubectl apply -f istio/gateway.yaml -f istio/virtualservice.yaml
  4. Optional: destinationrule.yaml, peerauthentication.yaml

Apply a directory in one shot if you prefer: kubectl apply -f examples/base-with-istio/ --recursive (only the base-with-istio tree — not the Argo CD Application manifest).

Sanity check without applying: kubectl apply --dry-run=client -f <file>.

Terminal window
kubectl get pods,svc -n demo
kubectl get gateway -n istio-system
kubectl get virtualservice -n demo
istioctl analyze

Use the ingress gateway external address (often from kubectl get svc -n istio-system -l istio=ingressgateway) to curl port 80. See Istio troubleshooting.

If Pods fail, see Troubleshooting and Debugging.

Keep the same YAML under examples/base-with-istio/ in Git. Point spec.source.path at that folder only so Argo CD does not sync an Application that references itself (avoid infinite recursion). Store demo-application.yaml outside that path — for example examples/argocd/demo-application.yaml.

Istio CRDs must already exist on the cluster (from the Istio install); Argo CD only applies your Gateway / VirtualService — not istiod.

Each manifest should set metadata.namespace where needed (demo, istio-system, etc.). For multi-namespace apps, prefer explicit namespaces in YAML; Argo CD applies each object to the namespace in the manifest.

RBAC: The default Argo CD installation can sync to common namespaces; custom AppProjects may need extra roles.

Replace repoURL and targetRevision with your repository. path is only the workload + Istio manifests folder.

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: demo-with-istio
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/your-repo.git
targetRevision: main
path: examples/base-with-istio
destination:
server: https://kubernetes.default.svc
namespace: demo
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
  1. Commit examples/base-with-istio/ (app + Istio YAML only).
  2. Apply the Application once: kubectl apply -f examples/argocd/demo-application.yaml (or manage it from a bootstrap repo).
  3. Watch sync: Argo CD UI, or argocd app get demo-with-istio, argocd app sync demo-with-istio.

Installing Argo CD itself is documented on GitOps. If sync stays OutOfSync or health is Unknown, check namespace existence, CRDs, and project permissions. Ensure the demo namespace can be created (CreateNamespace) or pre-create it.

How this fits next to Helm is covered in Helm vs operators vs GitOps.

  • cert-manager and real TLS hostnames for production ingress.
  • NetworkPolicies, PodDisruptionBudgets, HPA, ResourceQuota / LimitRange.
  • Sealed Secrets or External Secrets for credentials.
  • ServiceMonitor (if you use Prometheus).
  • Kustomize overlays (base/ + overlays/prod) for environment-specific patches.