Kubernetes Services in India
Cluster design, container orchestration, autoscaling, upgrades and cost control, run by an engineering team in India for CTOs and platform leads in the US, UK, Canada, Australia and New Zealand. We will also tell you when you do not need Kubernetes at all, which is more often than anyone selling it likes to admit.
Do You Actually Need Kubernetes?
Start here, because the answer is no more often than the industry pretends. Kubernetes is a distributed system that you operate in order to run your distributed system. It has its own failure modes, its own upgrade cadence, its own security surface and its own vocabulary. All of that is a cost you pay every month whether or not you are getting anything back.
The rule we use is blunt. Kubernetes costs you roughly one engineer's continuous attention. Not a project, not a quarter. Continuous. If you cannot dedicate that, or buy it, you are going to end up with a cluster that nobody has upgraded in a year, running an ingress controller with a known CVE, sized by whoever set the first Helm values file and never reviewed since. We get called into that cluster a lot.
So when is it wrong? A single web application with a background worker and a database does not need an orchestrator. Six services owned by one team of eight, deployed together, scaling together, on one cloud, do not need one either. If your traffic is predictable, if your deploys are weekly rather than hourly, if nobody is asking for per team resource isolation, then ECS Fargate, Google Cloud Run, Azure Container Apps or AWS App Runner will run your containers with a config file instead of a control plane. A boring autoscaling group of VMs with systemd units still works, and it still wakes nobody up.
Now the other side. Kubernetes earns its keep when several teams need to ship independently onto shared infrastructure without filing tickets against each other. It earns it when your workloads have genuinely different shapes, so bin packing thirty services of varying size onto fewer machines saves real money. It earns it when you need the same deployment contract across AWS and Azure because a customer contract says so, or across cloud and on premise because a factory floor cannot depend on the internet. It earns it when you are running jobs, cron work, GPU batch and long lived services side by side and want one scheduler deciding where things land. And it earns it when the ecosystem is the point: you want cert-manager, external-dns, Argo CD, Strimzi, KEDA and the rest to be one apply away instead of a hand built integration each.
There is a middle path people forget. Managed node pools plus a small cluster and no service mesh, no custom operators and no multi tenancy is a perfectly reasonable Kubernetes. Most of the pain in this field is self inflicted through complexity that was adopted before it was needed. When we scope Kubernetes services in India for a client, the first deliverable is often a shorter architecture than the one they came in with.
If after that conversation the answer is that you should not be running Kubernetes, we will say it in writing. It is a worse quarter for us and a better decade for you.
What a Kubernetes Engagement Covers
No engagement includes all of this. The assessment sets the order, and the order matters more than the list. Installing a service mesh before you have working resource requests is a common and expensive mistake. Here is the full surface area, roughly in the sequence we build it.
Cluster assessment and architecture
Two weeks, usually. We read your Terraform or CloudFormation, your Helm charts or Kustomize overlays, your ingress config and your RBAC bindings, then we run the cluster through kube-bench for CIS benchmark drift, pluto for deprecated APIs, and a resource audit that compares every request against real usage from your metrics. What comes back is a written architecture note: what the cluster looks like today, which decisions are load bearing, which are accidents, and the order in which we would change them. That note is yours whether or not you continue.
Cluster build or rebuild
Control plane choice, node pool design, CNI selection, storage classes, ingress, DNS, certificates, secrets, and the bootstrap path that gets a fresh cluster from empty to serving. Everything is defined in code. If a cluster cannot be destroyed and rebuilt from a repository, it is a pet, and pets die badly. Related work on the provisioning layer itself sits with our infrastructure and platform engineers rather than in this scope.
Workload onboarding and deployment
Helm charts or Kustomize bases for your services, probes that reflect how your application actually starts and dies, graceful shutdown that respects endpoint propagation, resource requests set from measurement rather than guesswork, and a GitOps controller such as Argo CD or Flux so the cluster state matches a git commit you can point at. Anyone who deploys by running kubectl apply from a laptop will keep having incidents nobody can explain.
Autoscaling across all three axes
Pod count, pod size and node count are three different problems with three different tools and one shared failure mode: they fight each other when configured carelessly. We wire HPA against the metric that actually reflects load for your workload, use the VPA recommender to size containers honestly, and put Karpenter or the Cluster Autoscaler underneath so capacity follows demand instead of standing by.
Networking, ingress and traffic policy
CNI choice and its consequences, ingress controller selection and hardening, TLS through cert-manager, DNS through external-dns, and NetworkPolicy that starts from default deny rather than being retrofitted after an audit finding. Service mesh only when there is a concrete requirement it satisfies.
Stateful workloads and storage
Storage classes with the right binding mode, CSI driver configuration, snapshot and restore paths that someone has actually tested, and operators for databases and message brokers where in cluster state is the right call. Also the conversation about which state should not be in the cluster at all.
Security posture
RBAC scoped to jobs rather than to convenience, Pod Security Admission at the right level per namespace, image provenance and scanning in the pipeline, admission policy through Kyverno or Gatekeeper, secrets kept out of git through External Secrets Operator or Sealed Secrets, and workload identity so pods stop carrying long lived cloud keys.
Upgrades and lifecycle
A repeatable upgrade runbook covering control plane, node pools, CNI, CSI drivers and every controller you run, plus the API deprecation scan that has to happen first. This becomes a scheduled quarterly job, not a heroic weekend.
Observability
Prometheus with kube-state-metrics and node-exporter, dashboards that answer questions rather than display everything, alerts wired to conditions a human can act on, logs through Loki or your existing stack, and traces through OpenTelemetry. The test is whether an on-call engineer can go from a page to a cause in under ten minutes using only what is on the screen.
Cost control
Tagging, OpenCost or Kubecost for per namespace and per team attribution, right sizing from real percentiles, spot capacity where interruption is tolerable, and scale to zero for anything that does not need to exist at night. Cost work is continuous, because a cluster that was efficient in March is not efficient in September.
Handover
Infrastructure as code, chart repositories, runbooks that name the first three things to check for each alert, an architecture decision record for every non obvious choice, and a working session where your engineers break something on purpose in staging and fix it themselves. If they cannot recover the cluster without calling India, the handover is not done.
Managed or Self-Hosted: EKS, GKE, AKS and the Bare Metal Case
The control plane is etcd plus the API server plus the scheduler plus the controller manager, and keeping that quorum healthy under disk pressure is a specialist job. Almost nobody should be doing it themselves in 2026. The interesting question is not managed versus self hosted. It is which managed offering, and how much of its opinion you accept.
Amazon EKS
EKS gives you the least opinion and the most assembly. You pick the CNI, the CSI drivers, the ingress, the autoscaler, the logging agent. That flexibility is genuine, and it is also why an EKS cluster built by three different people in three different years looks like three different products. AWS bills an hourly fee per cluster control plane and raises it once a version enters extended support, so a cluster left on an old minor release gets more expensive rather than merely less safe. Check the current pricing page before you model it.
The specific EKS trap worth knowing is the VPC CNI. Pods get real VPC IP addresses, which is excellent for network visibility and for talking to other AWS services, and it means the number of pods you can fit on a node is capped by how many elastic network interfaces and secondary IPs that instance type allows. Teams discover this when a node with plenty of free CPU refuses to schedule anything. Prefix delegation raises the ceiling substantially and is worth enabling early. The other option is running Cilium or Calico in overlay mode instead, which decouples pod density from instance type at the cost of losing native VPC routing.
EKS Auto Mode and managed node groups have removed a lot of the older toil. Fargate profiles on EKS remain a mixed experience: no daemonsets, higher per pod cost, and cold start behaviour that surprises people who assumed it was just a smaller node.
Google GKE
GKE is the most complete of the three, which is unsurprising given where Kubernetes came from. Release channels do the version management for you, node auto provisioning works well, and the networking model is coherent rather than assembled. If your data is in BigQuery and your team is small, GKE will get you further with fewer decisions.
Autopilot mode deserves a specific note because it changes the economics. You stop paying for nodes and start paying for the resources your pods request, which sounds like a saving and behaves like one only if your requests are accurate. On a fleet with inflated requests, Autopilot turns waste that was previously hidden inside idle node capacity into a line item you get billed for directly. That is arguably the correct incentive. It is also a nasty surprise if you migrate without doing the right sizing work first. Autopilot also restricts privileged workloads and certain daemonsets, so check your agents before committing.
Azure AKS
AKS makes sense when the organisation is already committed to Azure, and Entra ID integration for cluster authentication is the cleanest identity story of the three. Azure has been folding Karpenter into node auto provisioning, which closes the gap that used to exist against AWS on fast, bin packed scaling. The tiers differ in what uptime commitment you get on the control plane, so read the current service terms rather than assuming the free tier is fine for production.
When bare metal or self hosted is the right answer
Three cases. First, hardware reasons: GPU fleets where you own the cards, or workloads where the hypervisor tax on latency actually matters. Second, environments with no internet path, whether that is a defence context, a manufacturing site or an air gapped regulated system. Third, a regulator or a customer contract that will not accept a cloud managed control plane, which still happens in parts of finance and healthcare.
If you land in one of those, do not assemble a cluster from upstream components by hand. Use a distribution. Talos Linux gives you an immutable, API driven node OS with no SSH, which removes an entire category of configuration drift. RKE2 is the pragmatic choice where FIPS or CIS hardening is a requirement out of the box. k3s is right for edge and small footprint installs. Cluster API is how you manage a fleet of these rather than one at a time. Assembling kubeadm plus your own etcd backups plus your own certificate rotation is a project that never finishes.
The multi-cloud question
People adopt Kubernetes for portability and then use EKS with the VPC CNI, IRSA, ALB ingress and EBS volumes, which is about as portable as a CloudFormation stack. That is fine. Just be honest about it, because paying the complexity cost of portability while not being portable is the worst of both. If genuine multi cloud matters, the constraint has to be enforced from day one: overlay networking, a cloud neutral ingress, object storage abstracted behind an interface, and no managed database calls in application code. It is a real tax, and it is worth paying only when a contract or a regulator requires it.
Requests, Limits, OOMKills and the Throttling Nobody Sees
This is where most clusters quietly lose money and reliability at the same time, so it gets a long section. Requests and limits look like two numbers in a YAML file. They are actually the scheduler's contract, the kernel's enforcement policy and your capacity plan, all wearing the same costume.
What the two numbers actually do
A request is what the scheduler reserves. It decides which node your pod lands on and how much of that node is considered spoken for. A limit is what the kernel enforces at runtime, and the enforcement differs completely between the two resources. CPU is compressible: exceed the limit and the CFS scheduler throttles you, which shows up as latency. Memory is not compressible: exceed the limit and the cgroup OOM killer terminates the process, which shows up as exit code 137 and a restart.
That asymmetry drives our default. Set memory request equal to memory limit. Set a CPU request that reflects real usage, and think hard before setting a CPU limit at all.
Why CPU limits often make things worse
The Linux CFS bandwidth controller enforces a CPU limit over a 100 millisecond period by default. A container limited to one CPU gets 100 milliseconds of CPU time per period across all its threads. A multi threaded JVM or Go service with a thread pool sized to the host's core count can burn that entire quota in the first 12 milliseconds and then sit still for 88, on a node that is otherwise idle. Your average CPU graph shows 30 percent utilisation. Your p99 latency shows a cliff.
A kernel fix in the 5.4 series removed a bug that made this considerably worse than intended, and plenty of teams are still hitting the remaining behaviour because their thread pools are sized against the node rather than the cgroup. The metric to look at is container_cpu_cfs_throttled_seconds_total from cAdvisor. If it is climbing on a latency sensitive service, you have found your problem, and it is not capacity.
Our position: set CPU requests everywhere, because that is what protects the scheduler's arithmetic, and leave CPU limits off for latency sensitive services on nodes you control. Apply CPU limits in shared or multi tenant clusters where one team's runaway loop cannot be allowed to starve another. Reasonable engineers disagree with us on this, and the disagreement is usually about how much you trust your neighbours.
Separately, make the runtime aware of its cgroup. Go services should have GOMAXPROCS set from the CPU limit, either manually or through automaxprocs. A JVM on any recent version reads container limits by default, but check it, because a JVM that thinks it has 64 cores on a container limited to two will size its garbage collection threads and fork join pool accordingly.
The OOMKill you cannot see in your heap graph
Every second Kubernetes engagement includes an OOMKill investigation, and the pattern repeats. A Java service has a 4 GiB container limit and Xmx set to 4 GiB. It dies every few days. The heap dashboard shows 2.8 GiB peak, so the team concludes there is a mysterious infrastructure problem.
There is no mystery. The cgroup counts heap plus metaspace plus compressed class space plus thread stacks plus the JIT code cache plus direct byte buffers plus whatever the native layer allocated plus the page cache attributed to the container. Netty based services in particular allocate large amounts of off heap memory that never appears in a heap graph. Set MaxRAMPercentage to something in the region of 70 or 75 instead of a fixed Xmx, so the heap scales with the limit and leaves room for everything else.
Node has the same shape of problem in a different costume: max-old-space-size governs the V8 old space, and buffers, native modules and worker threads live outside it. Python processes get killed for a copied dataframe nobody accounted for. In every case the number to watch is peak container RSS at the ninety ninth percentile over a fortnight, not the average, because the OOM killer only cares about the worst moment.
When a pod dies this way, the evidence is in kubectl describe under last state, with reason OOMKilled and exit code 137. Alert on kube_pod_container_status_last_terminated_reason with reason OOMKilled rather than on restart counts, because a restart count tells you something happened and this tells you what.
Quality of service and who gets evicted first
Kubernetes assigns each pod a QoS class from its requests and limits. Guaranteed means every container has requests equal to limits for both CPU and memory. Burstable means requests are set but lower than limits. BestEffort means nothing is set. Under node memory pressure the kubelet evicts BestEffort pods first, then Burstable pods that are exceeding their requests, and Guaranteed last.
Which means your BestEffort pods are the first thing to die on a bad node, and pods with no requests at all are invisible to the scheduler, so it will happily pack more of them onto a node that is already struggling. A LimitRange per namespace that applies default requests catches the team that forgot, and it is fifteen lines of YAML.
Getting the numbers right
Guessing produces one of two outcomes: a cluster at 15 percent utilisation, or pods that die under load. Do it from data. The VPA recommender running in Off mode gives you sizing recommendations without touching anything, which is the safest way to start. Goldilocks puts a dashboard on top of that. Prometheus percentiles over two weeks of representative traffic tell you the truth, and two weeks matters because a week misses your monthly batch job.
Set memory request and limit to the ninety ninth percentile peak with a margin. Set the CPU request near the ninety fifth percentile of actual usage. Then revisit quarterly, because the number that was right for last year's traffic is not right for this year's. In place pod resizing has been maturing in recent Kubernetes releases and will eventually let you change these without a restart. Check whether your cluster version has it before you plan around it.
Autoscaling: HPA, VPA, Cluster Autoscaler and Karpenter
Three axes, four tools, and a set of interactions that are not obvious until they bite you at 4pm on a Friday.
Horizontal Pod Autoscaler
HPA changes replica count against a metric. The default choice is CPU utilisation as a percentage of the request, which works fine for CPU bound services and badly for everything else. An IO bound API that spends its time waiting on a database will sit at 20 percent CPU while its queue backs up and its p99 goes through the roof, and HPA will do nothing, because nothing it can see has changed.
For those workloads, scale on the metric that reflects the actual constraint: requests per second per pod, queue depth, or p95 latency, pulled in through the Prometheus Adapter or through KEDA. KEDA is usually the better answer for anything event driven, because it understands scalers for Kafka consumer lag, SQS queue length, RabbitMQ, cron schedules and dozens of others, and it can scale to zero, which plain HPA cannot.
The tuning detail people miss is the stabilisation window. Scale up has no stabilisation delay by default, so it reacts immediately. Scale down has a five minute window, which exists to stop the flapping that would otherwise happen when a metric hovers around a threshold. If your traffic is spiky, lengthen the scale down window rather than shortening it. Thrashing pods are worse than a few extra minutes of capacity.
Vertical Pod Autoscaler
VPA changes container size rather than count. We use it in Off mode almost universally, because the recommendations are the most useful part and applying them automatically means evicting and recreating pods, which is disruptive on anything with long lived connections.
The critical constraint: VPA and HPA must not both act on the same resource metric for the same workload. If HPA scales on CPU and VPA also adjusts CPU requests, they enter a feedback loop where each one changes the input the other is reading. The supported combination is HPA on a custom metric with VPA managing memory, or VPA in recommendation mode alongside HPA on CPU with a human applying the sizing changes.
Cluster Autoscaler versus Karpenter
The Cluster Autoscaler works at the level of node groups. You define autoscaling groups with fixed instance types, and it adds or removes nodes within them when pods cannot be scheduled or when nodes sit underutilised. It is stable, it is well understood, it respects pod disruption budgets, and it is limited by the node group abstraction. If your groups are all m5.2xlarge and a pod needs 30 GiB of memory, the autoscaler cannot invent a more suitable instance type.
Karpenter throws away the node group. It reads the requirements of pending pods and provisions the instance that fits them, choosing across families, sizes, architectures and capacity types from a NodePool definition you write once. Scale up is faster because there is no ASG round trip. More usefully, consolidation continuously repacks the cluster, replacing three half empty nodes with one appropriately sized node, which is where most of the saving comes from. Karpenter reached a stable v1 API and has been extending beyond AWS, with Azure using it under node auto provisioning.
Karpenter's honest cost is that it will disrupt pods to consolidate. If your pod disruption budgets are wrong, or absent, you will find out. Workloads that must not move get the do-not-disrupt annotation, and everything else needs a PDB that reflects what it can actually tolerate. Consolidation on a cluster with no PDBs is how a well meaning cost optimisation becomes an incident.
Our default on AWS is Karpenter for stateless workloads with a small on demand node group for the control plane adjacent components that should never move. On GKE, node auto provisioning covers most of the same ground and we usually leave it there.
Spot capacity
Spot instances are the largest single lever on cluster cost and they are only safe for workloads that can be interrupted. AWS gives a two minute interruption notice, and Karpenter or the Node Termination Handler will cordon and drain on that signal, which is enough time for a stateless HTTP service with proper graceful shutdown and nowhere near enough for a database primary or a long running job with no checkpointing.
Split your workloads deliberately. Stateless services and CI runners go to spot with diversified instance types across several availability zones so a single capacity pool cannot take everything at once. Anything stateful, anything with a long startup, and anything holding a lock stays on demand. Then verify by draining a node in business hours and watching what actually happens, because a graceful shutdown path that has never been tested is a graceful shutdown path that does not work.
Networking, Ingress and the Service Mesh Question
Networking is where Kubernetes hides most of its sharp edges, partly because the abstraction is genuinely good and partly because the implementation underneath it is invisible until it fails.
Choosing a CNI
Calico is the safe default: mature, widely deployed, good NetworkPolicy support, and it works in overlay or native routing mode. Cilium is where the interesting work is happening, using eBPF to replace kube-proxy entirely, which removes the iptables rule growth that makes large service counts slow. Cilium also gives you identity aware policy, Hubble for flow visibility, and a mesh option without sidecars. It asks more of the team in return, and a kernel that is new enough.
The AWS VPC CNI is the EKS default and gives pods routable VPC addresses, which makes security groups and flow logs work the way your network team expects. The pod density ceiling per instance type is the cost, as described earlier. Flannel is simple and does not implement NetworkPolicy at all, which people discover during a security review rather than during setup.
On kube-proxy: iptables mode rebuilds rule chains as services change, and at several thousand services that rebuild becomes measurable. IPVS mode handles scale better. The nftables backend has been progressing through the release cycle and eBPF replacement through Cilium sidesteps the question entirely. Unless you are running thousands of services, the default is fine and this is not where your problem is.
Ingress controllers
ingress-nginx has been the default for most of Kubernetes' life, and its position has been shifting. The project has signalled a wind down of maintenance and pointed users toward Gateway API implementations, and it has had serious CVEs in its admission webhook path. If you are standing up a new cluster, check its current support status before you build around it, and if you already run it, keep the admission controller off any broadly reachable network path and stay current on patches.
The alternatives are real. Envoy Gateway and Contour are Envoy based and implement Gateway API properly. Traefik is pleasant to configure and popular with smaller teams. HAProxy Ingress is the choice when you want a proven load balancer with predictable behaviour under load. Cloud native options such as the AWS Load Balancer Controller move ingress out of the cluster into an ALB or NLB, which reduces what you operate and ties you to the cloud.
Gateway API reached GA and is the direction of travel. Its main practical advantage is role separation: a platform team owns the Gateway resource, application teams own HTTPRoutes, and the two do not need to share a single annotation soup of an Ingress object. For a new cluster we default to Gateway API. For an existing one with two hundred working Ingress resources, migration is a project to schedule rather than a switch to flip.
Do you need a service mesh?
Usually not, and this is the most common piece of unnecessary complexity we remove. A mesh is justified by three things: mutual TLS between every service because a compliance requirement says so, fine grained traffic policy such as canary routing by header or request level retries and circuit breaking, and per request telemetry across a service graph too large to reason about otherwise. Two of those three can be met without a mesh in a lot of cases.
If you do need one, Linkerd is the smaller, simpler option with lower resource overhead and a much shorter path to a working install. Istio is more capable, and its ambient mode removed the per pod sidecar, which was the largest objection to it. Cilium's mesh functionality is worth a look if you are already running Cilium. Whatever you pick, the sidecar or node proxy sits in the request path of every call in your system, and every upgrade is therefore a change to the request path of every call in your system. That is the cost to weigh.
The DNS problem everyone eventually meets
CoreDNS resolution latency causes some of the strangest incidents in Kubernetes, because the symptom appears in the application as an intermittent connection failure. The classic cause is ndots set to 5 in the pod DNS config, which means a lookup for an external hostname first tries several cluster local search domains and fails through each one. Under load that multiplies query volume enormously. Setting ndots to 2 for services that mostly call external endpoints, or using fully qualified names with a trailing dot, fixes it. Node local DNS caching removes another class of the same problem. Scale CoreDNS with cluster size rather than leaving it at the install default.
Stateful Workloads: Databases, Volumes and the Things That Do Not Move
Kubernetes was built for stateless workloads and grew support for state afterwards. That history still shows. Running data in a cluster is entirely possible now. It is also the area where a mistake costs you data rather than uptime.
The storage decisions that matter
Block storage from a cloud provider, such as EBS or Persistent Disk, is ReadWriteOnce: one node at a time. That is right for a database and wrong for anything expecting a shared filesystem. If several pods need the same volume you need ReadWriteMany, which means EFS, Filestore, Azure Files or something like Ceph, and those come with different latency characteristics that will surprise an application tuned for local disk.
Set volumeBindingMode to WaitForFirstConsumer on your storage classes. With immediate binding, a volume can be provisioned in one availability zone before the scheduler has decided where the pod goes, and then the pod cannot be scheduled anywhere except that zone. This produces a pod stuck in Pending with an error message that does not obviously say what happened.
The zonal nature of cloud block storage has a bigger consequence. A StatefulSet pod with an EBS volume is pinned to the availability zone that volume lives in, forever. If that zone has a problem, that pod does not move. Multi zone resilience for stateful workloads comes from replication at the application layer, not from the storage layer, and anyone who tells you otherwise is selling something.
Snapshots through the CSI snapshot API are a point in time copy, not a backup, and they are not a backup until someone has restored one into a fresh cluster and checked the data. Velero handles cluster level backup including volume snapshots and namespace scoped restores. Schedule a restore test quarterly. An untested backup is a belief, not a control.
Should the database live in the cluster?
Our default answer is no for a team's first production cluster. Managed RDS, Cloud SQL or Azure Database removes failover, patching, backup verification and point in time recovery from your plate, and those are exactly the things that go wrong at the worst moment. The premium you pay is real and it is usually cheaper than the incident.
The case for in cluster is stronger than it used to be, and it rests on operators rather than on StatefulSets. CloudNativePG is genuinely good: it handles streaming replication, automated failover, backup to object storage with WAL archiving, and connection routing so applications follow the primary after a promotion. Strimzi does the equivalent for Kafka, including rebalancing through Cruise Control. Vitess handles sharded MySQL at a scale most companies never reach. If you are running multi cloud or on premise, or if you have dozens of small databases where per instance managed pricing hurts, an operator is a reasonable choice.
What is not reasonable is a bare StatefulSet with a Postgres image and a PVC. That gives you a database with no failover, no tested backup and no connection routing, running on infrastructure that will drain the node it is on during the next upgrade. We have been called in after exactly that, more than once.
Protecting state during ordinary cluster operations
Node upgrades, spot interruptions and Karpenter consolidation all drain nodes. A stateful pod being drained has to shut down cleanly, and something has to prevent two replicas going at once. Pod disruption budgets are the mechanism, and getting them wrong breaks things in both directions. Set maxUnavailable to zero and a drain will hang forever, blocking your upgrade. Leave the PDB out and a rolling node replacement can take your database quorum.
Also check terminationGracePeriodSeconds against how long your workload actually takes to shut down. The default is 30 seconds. A Kafka broker or a JVM service with in flight work may need considerably more, and a pod that gets SIGKILLed halfway through a flush is how corruption stories start. Note that StatefulSet PVCs are retained when you scale down, by design, so your storage bill keeps the volumes of pods that no longer exist until someone removes them or sets a retention policy.
How Hard Is Your Multi-Tenancy, Really?
Ask a team how they isolate tenants and the answer is usually one namespace per tenant. Ask what that actually stops, and the conversation gets quieter. A namespace is a naming scope and an RBAC boundary. It is not a security boundary, and by default it is not a resource boundary either.
Soft multi-tenancy
Soft isolation is the right model for internal teams who are not hostile to each other, and it is built from four things. ResourceQuota per namespace caps aggregate CPU, memory, storage and object counts so one team cannot consume the cluster. LimitRange sets default requests and limits so pods without them do not become invisible to the scheduler. NetworkPolicy with a default deny ingress rule per namespace stops the flat network where anything can reach anything. RBAC scopes each team to its own namespace with no cluster wide read.
Add priority classes so platform components outrank application workloads under pressure, and topology spread constraints so a single team's pods do not all land on the same node. That combination handles the noisy neighbour problem well. It does not handle a malicious neighbour.
Hard multi-tenancy
If your tenants are external customers, or if one tenant's compromise must not touch another's data, namespaces are not enough. Containers share a kernel. A container escape through a kernel vulnerability crosses every namespace on that node in one step.
Options, in increasing order of separation. Dedicated node pools per tenant with taints and tolerations, so at least tenants do not share a kernel with each other. Sandboxed runtimes such as gVisor or Kata Containers, which put a barrier between the container and the host kernel at some performance cost. Virtual clusters through vcluster, which gives each tenant its own API server view while sharing the underlying nodes, and which is a genuinely useful middle ground for development environments. And at the far end, a cluster per tenant, which is the only answer that is easy to explain to an auditor.
Cluster per tenant used to be prohibitive. With Cluster API or Crossplane and a well factored Terraform module it is a template rather than a project, and for regulated customers it is frequently what we recommend. Be honest about the operational arithmetic though: forty clusters means forty upgrade cycles, and that only works if the upgrade is fully automated.
The question to ask first
What is the actual consequence if tenant A can read tenant B's data? If the answer is a difficult conversation, soft isolation with good NetworkPolicy is proportionate. If the answer is a regulatory notification and a lost contract, you need kernel level separation and you should stop optimising cluster density. Most teams we meet have picked their isolation model without ever asking that question, which is how a healthcare product ends up on shared nodes with a marketing site.
RBAC, Pod Security and Supply Chain
Cluster security fails in boring ways. Nobody is writing a novel kernel exploit against you. Someone gave a CI service account cluster-admin two years ago because a deploy was failing at 6pm, and that binding is still there.
RBAC that reflects jobs
Start from what each role needs to do rather than from what is convenient. Developers usually need read across their namespaces, log and exec access in non production, and no write path to production outside the GitOps pipeline. CI needs write to specific namespaces and nothing else. On-call needs broader read and a defined break glass path that is time limited and logged.
Wildcards in a Role are how privilege escalation happens quietly. The permission to create pods in a namespace is effectively the permission to use any service account in that namespace, which is a detail people miss. Audit with rbac-tool or kubectl-who-can, and audit periodically rather than once, because bindings accumulate.
Human access should come from your identity provider through OIDC, mapped to Kubernetes groups. Certificate based kubeconfigs handed out individually cannot be revoked without rotating the cluster CA, and they outlive employment. Every kubectl call lands in the API server audit log; make sure that log goes somewhere with retention and that someone occasionally reads it.
Pod Security Admission
PodSecurityPolicy was removed in Kubernetes 1.25 and Pod Security Admission replaced it. Three levels: privileged, baseline and restricted. Restricted requires non root execution, a read only root filesystem, dropped capabilities and a seccomp profile, and it is the target for application namespaces.
Roll it out in stages. Label namespaces with the warn and audit modes first, watch what fails for a couple of weeks, fix the workloads, then switch to enforce. Going straight to enforce on a live cluster breaks whatever was quietly running as root, and something always is. Namespaces that genuinely need privileged access, such as monitoring agents and CNI components, get their own labels and their own justification written down.
For anything PSA cannot express, Kyverno or Gatekeeper add policy as code: block images from unapproved registries, require resource requests, require specific labels for cost attribution, mutate pods to add default security contexts. Kyverno's YAML policies are easier for most teams than Gatekeeper's Rego. Run new policies in audit mode first for the same reason as above.
Secrets and workload identity
Kubernetes Secrets are base64 encoded, not encrypted, and are readable by anyone with get access on the namespace unless you have configured encryption at rest and scoped RBAC properly. Enable etcd encryption at rest, or use a KMS provider so the encryption key is not sitting next to the data.
Better: keep secrets out of the cluster. External Secrets Operator syncs from AWS Secrets Manager, GCP Secret Manager, Azure Key Vault or Vault. Sealed Secrets lets you commit encrypted material to git safely if you want secrets in the GitOps repository. And for cloud API access, use workload identity, IRSA on EKS, Workload Identity on GKE, so pods get short lived credentials from the cloud provider instead of carrying static access keys.
Images and supply chain
Scan images in the pipeline with Trivy or Grype and fail the build on fixable high severity findings, not on everything, or the team learns to ignore the report. Use distroless or Chainguard base images to cut the attack surface down to what your application needs. Pin images by digest rather than by tag, because a tag is a mutable pointer and latest is not a version. Sign images with cosign and verify signatures at admission if your threat model includes registry compromise. Runtime detection through Falco catches the things that get through, and it needs someone to triage its alerts or it becomes noise.
Upgrades, Version Skew and the Job Nobody Books Time For
Kubernetes ships three minor releases a year, and each minor release gets patch support for roughly fourteen months. Do the arithmetic: falling two releases behind gives you months of runway, and falling four behind means you are out of support and paying extended support rates on a cluster that no longer receives security patches from upstream. We routinely find clusters eighteen months behind, and the reason is always the same. Nobody owns it, so it never gets scheduled.
Version skew, briefly
The API server is the reference point. Control plane components may run behind it by a limited margin, kubelets may run behind it by a few minor versions on recent releases, and kubectl is supported within one minor version either side. The practical rules that follow: upgrade the control plane before the nodes, never the reverse, and never skip a minor version on the control plane. The exact skew allowances have widened over recent releases, so read the version skew policy for the version you are moving to instead of trusting a number you remember.
The upgrade runbook
Scan first. Pluto and kubent find manifests using APIs that the target version removes, and this is where most upgrade breakage lives. Every major upgrade cycle in recent years has retired something that a Helm chart in your cluster still uses. Read the changelog properly, including the notes for the controllers and operators you run, because your CSI driver and your ingress controller have their own compatibility matrices.
Then upgrade staging, and leave it there for a week under real load rather than upgrading production the same afternoon. Control plane goes first. Node pools follow with a surge strategy, adding new nodes on the new version before draining old ones so capacity never dips. Then the add ons: CNI, CSI drivers, ingress, metrics server, autoscaler, each with its own compatibility check.
Two things block drains, and both are worth checking before you start. Pod disruption budgets that permit zero unavailable will stall the drain indefinitely, and a single replica deployment with a PDB will do the same. Fix those first and the upgrade is uneventful. Skip that check and you will be debugging a stuck drain with half the cluster on the new version.
Where we can, we prefer blue green node pools for major jumps: stand up a new pool on the new version, shift workloads across with topology aware scheduling, keep the old pool until you are confident, then delete it. It costs more for a day and gives you a rollback that actually works.
Observability and Cluster Cost Control
These two belong together because you cannot control what you cannot see, and because the same telemetry answers both questions.
What to watch
Prometheus with kube-state-metrics and node-exporter is the baseline, and the interesting part is what you alert on. Node level: memory pressure, disk pressure, PID pressure, not ready conditions. Workload level: crash loop backoff, OOMKilled terminations, pods pending longer than a few minutes, failed jobs. Capacity level: unschedulable pods, node group at its ceiling, persistent volume nearing full. And the two that most clusters miss: CPU throttling on latency sensitive services, and certificate expiry, because a wildcard certificate that lapses at 2am takes everything down at once.
Control plane health matters even on a managed cluster. API server request latency and error rates tell you when something is hammering the API, which is usually a badly written controller in a retry loop. etcd disk write latency is the number that predicts a bad day on self hosted clusters.
Alerts should be few and actionable. A cluster with 200 alert rules has zero effective alert rules, because nobody reads them. Each alert needs a runbook naming the first three checks. If you cannot write that runbook, you do not understand the alert well enough to page someone with it.
For long term storage, Prometheus alone will not hold a year of history at cluster scale. Thanos, Mimir or VictoriaMetrics take remote writes and handle retention. Watch label cardinality: a metric labelled with pod name in a cluster with high pod churn will quietly destroy your Prometheus, and the failure mode is memory exhaustion in the monitoring stack during the incident you needed it for.
Where the money goes
Four causes account for nearly all cluster overspend, in this order.
Over requested resources come first by a wide margin. Nodes get bought against requests, so a fleet of services asking for 2 CPU and using 0.2 will keep the cluster at low utilisation no matter how clever your instance selection is. Fixing requests pays back more than anything else on this list, and it is unglamorous work that nobody volunteers for.
Idle capacity comes second. Development and staging namespaces running at full size at 3am on a Sunday. Over provisioned node groups with a minimum size set during a launch and never lowered. Load balancers left behind by deleted services. Orphaned persistent volumes from StatefulSets that were scaled down months ago.
Instance selection is third. Graviton and other ARM instances cost less per unit of compute, and most Go, Java, Node, Python and Rust workloads run on them without changes once your images are multi architecture. The migration cost is a build pipeline change and a testing cycle.
Data transfer is fourth and it is the one that hides. Cross availability zone traffic between pods is billed, and a service mesh or a chatty microservice graph can generate a surprising amount of it. Topology aware routing keeps traffic within a zone where the workload allows it. NAT gateway charges for pulling images and calling external APIs add up in a way that never appears in any Kubernetes dashboard.
Make it visible with OpenCost, which is the CNCF project underneath Kubecost, so every namespace and every team sees its own number. Shared costs get allocated by an agreed rule rather than argued about. Once the numbers exist, the conversation stops being about opinions.
Four Situations We Get Called Into
These are patterns rather than named clients. If one of them describes your week, the diagnosis usually holds.
The cluster bill that tripled after a migration
A team moves from a handful of VMs to EKS, keeps the resource requests they copied from an old Docker Compose file, and watches the cloud bill go up rather than down. Every service requests 2 CPU and 4 GiB because that was the VM size, actual usage sits nearer 0.2 CPU, and the cluster runs at under a fifth of its capacity while the autoscaler dutifully adds nodes to satisfy requests nobody is using.
The work here is boring and effective. Deploy the VPA recommender in Off mode, collect two weeks of data covering at least one monthly batch cycle, reset requests to measured percentiles service by service with the owning team in the room, then let Karpenter consolidate the now oversized fleet. Move CI runners and stateless services to spot. Add OpenCost so the next drift is visible within a week rather than a quarter. Usually the requests work alone changes the picture before anything else is touched.
The service that dies every few days for no visible reason
A Java API restarts two or three times a week, always outside deploys. The team has added memory twice. Heap graphs look calm. Somebody has started blaming the cloud provider.
The kubectl describe output shows OOMKilled and exit code 137, which means the container crossed its cgroup memory limit and the kernel ended it. Xmx was set equal to the container limit, so metaspace, thread stacks, the code cache and Netty's direct buffers had nowhere to live. The fix is to set the heap as a percentage of the container limit, measure peak RSS at the ninety ninth percentile over two weeks, and size the limit against that with headroom. While we are there we usually find CPU limits causing throttling on the same service, visible in container_cpu_cfs_throttled_seconds_total, which explains the latency complaints nobody had connected to the restarts.
The database that went down during a routine upgrade
A single Postgres pod in a StatefulSet on an EBS volume, no operator, no replica, no pod disruption budget. A node group upgrade drains the node, the pod is evicted, the volume detaches and reattaches, and the application is down for the length of a cold start plus WAL replay. Nobody planned an outage.
The immediate fix is a pod disruption budget and a tested backup, in that order. The real fix is a decision. Either move to a managed database and stop carrying this class of risk, or adopt CloudNativePG with streaming replication, automated failover, WAL archiving to object storage and connection routing that follows the primary. We will make a recommendation based on who is going to be awake when it fails, and we will write down the trade off either way.
The upgrade that has been deferred four times
The cluster is several minor versions behind, the last person who understood the Helm charts has left, and everyone is afraid to touch it. The cloud bill has an extended support line item on it now, which is what finally forced the conversation.
We start with a read only scan: pluto and kubent for removed APIs, a compatibility matrix for every controller and CSI driver in the cluster, and a list of every PDB and single replica deployment that will block a drain. That produces a sequenced plan with a rehearsal in staging first. Then one minor version at a time, control plane then nodes, with blue green node pools where the jump is large. It is a series of small, dull, reversible steps, and after the first one the fear usually goes away.
How the Engagement Runs, Week by Week
Timelines vary with cluster size and how much of the estate is undocumented. This is the shape of a typical first engagement.
Weeks one and two: assessment
Read access to the cluster and the infrastructure repositories, plus a few hours with whoever built it. We run the security and deprecation scans, pull two weeks of resource data, map the workloads and their dependencies, and interview the people who get paged. The deliverable is an architecture note with findings ranked by risk and effort, and a proposed sequence. This is where we tell you if we think you should not be running Kubernetes, and it happens often enough to be worth saying twice.
Weeks three and four: stabilise
Fix what is dangerous before improving anything. That usually means resource requests on workloads that have none, pod disruption budgets where drains would hurt, default deny NetworkPolicy in the namespaces that need it most, alerting on the conditions that are currently silent, and a backup that has been restored at least once. Nothing here is impressive. All of it is what stops the next incident.
Weeks five to eight: build
The substantive work, in the order the assessment set. GitOps if deployments are manual. Autoscaling if capacity is static. Ingress migration if the current controller is a liability. Right sizing and consolidation if cost is the driver. Each change goes to staging first, gets reviewed by your engineers, and ships with the runbook update in the same pull request.
Weeks nine onward: operate and hand over
Either we run the cluster with your team, or we hand it over and step back. Handover means documentation, runbooks, decision records, and a session where your engineers deliberately break things in staging and recover them without us typing. The exit criterion is that your on-call can handle a node failure, a bad deploy and a full disk without opening a chat with us.
How Does a Kubernetes Team in India Actually Work With Your Timezone?
Here is the arithmetic without the marketing. India is UTC plus five and a half hours. A standard Indian working day of 09:30 to 18:30 IST is 04:00 to 13:00 UTC.
For a US Eastern team, that day ends at 08:00 your time. On a default schedule you get about an hour of overlap if your people start early, and that is not enough to run a project on. So we do not run US engagements on a default schedule. A shifted day of roughly 13:30 to 22:30 IST puts the team on your desk from 09:00 to 12:00 Eastern, which is three hours of real overlap covering your morning. US Pacific needs a further shift or an acceptance that overlap happens at the very start of your day.
The UK is the easy case: four to five hours of overlap on a standard Indian day, more in British Summer Time. Sydney and Auckland are the reverse of the US problem, with the overlap landing in your afternoon and the Indian morning. In every case we agree the window with you before work starts, and we write it down rather than leaving it to habit.
Now the part that is specific to this service, and it is the strongest honest argument for running cluster operations from India. Kubernetes incidents do not respect business hours. A node group exhausting its IP pool, an ingress certificate expiring, a memory leak that reaches the limit at 3am: these are the pages that ruin a US team's week. Indian business hours cover the US night almost exactly. The person picking up that alert is at a desk with coffee, not sitting on the edge of a bed with a laptop.
That advantage is real and it is not free. Follow the sun coverage needs written runbooks rather than tribal knowledge, a shared incident channel with a documented severity scale, an escalation path that says explicitly which decisions an on-call engineer can make alone and which need you woken up, and a handover note at the end of every shift that the next person actually reads. Teams that skip the handover discipline get the worst of both: an engineer awake in the wrong timezone with no authority to act. We set the escalation rules with you before anyone gets production access, and the specifics go in the engagement terms rather than being assumed.
Written first culture makes the rest work. Decisions land in pull requests and architecture decision records, not in a call that only four people heard. Every change is reviewed by someone on your side. Standups are asynchronous and in writing where the overlap is short, synchronous where it is long enough to be worth the hour.
The talent pool for this specific skill
Kubernetes hiring in India is deep, because the large captive centres in Bengaluru, Pune, Hyderabad and Chennai have been running production Kubernetes for years and the people who built those platforms are in the market. The CNCF certifications, CKA, CKAD and CKS, are widely held here, and we treat them as a filter rather than a signal. What we actually test is a broken cluster: a candidate gets a cluster with a real fault in it and a terminal, and we watch how they work. Nobody who cannot read a kubectl describe output under mild time pressure gets through, whatever their certificate says.
English assessment is part of the same session and it is not a grammar test. The question is whether this person can explain to your engineering manager, clearly and without hiding anything, what broke and what they did about it. That is the skill that makes remote operations work.
Access, IP and data handling
The default arrangement keeps everything in your account. Engineers get named identities in your identity provider, mapped to Kubernetes groups through OIDC, with RBAC scoped to what they need. Access goes through your bastion or zero trust proxy, and every API call is in the audit log with a name attached. Production data is not copied to local machines. Your workloads and volumes stay in the region you chose, so your data residency position does not change because the operators are in India. Ownership of the code and the infrastructure definitions, along with confidentiality and data processing terms, is set in the agreement before work starts, and you should have your own counsel review it rather than taking a web page's word for it.
What Goes Wrong, and How We Handle It
Every offshore engagement has failure modes. Pretending otherwise wastes your time and ours.
The knowledge stays with us
The most common bad outcome in platform work is a cluster that runs beautifully and that only the vendor understands. We treat that as a defect. Two engineers touch every part of the platform, the runbook for a component is written by whoever did not build it, and everything lives in your repositories from day one. The handover test is behavioural rather than documentary: your engineer breaks something in staging and fixes it without our help.
Cluster access has to be earned in stages
Nobody sensible hands production cluster-admin to a new vendor in week one. We start with read only access and the assessment, move to write access in staging, and take production write access only after your team has reviewed a few changes and is comfortable. If you would rather we never hold production write access and instead raise pull requests your engineers merge, that works too, and for regulated clients it is often the right shape.
The overlap is shorter than the work needs
Some weeks need more synchronous time than a three hour window allows. Migration cutovers, incident post mortems and architecture decisions with several stakeholders are the usual ones. We schedule those deliberately, with the Indian team shifting for the day, rather than trying to compress them into a standing call. The alternative, running everything asynchronously, adds a day of latency to every decision and it shows up as a slipped date three weeks later.
Your team resists the platform
Platform work fails politically more often than technically. If developers experience the new cluster as a set of rules that stop them shipping, they will route around it, and eventually somebody will run a container on a VM outside the cluster because it was faster. We work with the application teams from the start, keep the developer facing surface small, and make the paved path genuinely the easiest option. If Argo CD makes deploys slower than the shell script it replaced, that is our problem to fix, not the developers' problem to accept.
An engineer moves on
People change jobs, in India as everywhere. Our mitigation is structural rather than contractual: no single owner for any component, documentation as a deliverable rather than an afterthought, and infrastructure defined in code that you hold. Handover and notice arrangements are set out in the engagement agreement before work begins, so the terms are yours to review rather than ours to announce.
The scope grows
Kubernetes engagements attract adjacent work. Someone notices the CI pipeline is the real bottleneck, or that the Terraform is unmaintainable, or that the monitoring stack needs replacing. That may all be true. We flag it, scope it separately and let you decide, rather than quietly absorbing it and delivering the original thing late.
Engagement Models
Three shapes, and the right one depends on whether you are buying a change or buying capacity.
Scoped project
A defined outcome with a defined end. A cluster build, a migration from self managed to EKS, an ingress controller replacement, a cost reduction programme, or an upgrade from a version that is out of support. Fixed scope, agreed deliverables, and a handover at the end. This suits organisations with a platform team who need a specific piece of work done without pulling their own people off the roadmap.
Dedicated team
Engineers who work only on your platform, in your tools, in your standups, as an extension of your team. This is the model for organisations without an internal platform capability who need one continuously rather than once. You direct the work. The overlap window, the reporting cadence and the review process get agreed before the first sprint.
Operations retainer
Ongoing cluster operations: upgrades on a quarterly cadence, monitoring and alert response, cost review, security patching, and support during your night hours. This is the model where the timezone difference works hardest in your favour, and it is usually what follows a project engagement once the platform is stable.
Commercial terms, coverage hours and notice arrangements are agreed in the engagement documents before work starts. We will put the specifics in writing for your situation rather than publishing numbers that would not apply to it.
Where This Sits Alongside Our Other Work
Kubernetes rarely arrives alone. The pipeline that builds and promotes what runs on the cluster, along with the infrastructure code underneath it, sits with our DevOps engineering team, and the two engagements are usually staffed together. If the reason you are looking at Kubernetes is a monolith you want to break apart, the service boundaries matter more than the orchestrator and our microservices work covers that decision first.
Where the question is broader than the cluster, cloud platform selection and account structure fall under cloud services. Cluster hardening overlaps with our cybersecurity practice for anything involving an audit, a penetration test or a compliance programme. For staffing rather than a scoped build, you can hire DevOps developers in India for platform and cluster work, or hire Go developers in India where the work is writing controllers and operators against the Kubernetes API rather than operating the cluster.
Frequently Asked Questions About Kubernetes Services in India
Do we actually need Kubernetes, or is it overkill for us?
If you run under about ten services, one deployment target, no bursty scaling and no platform team, Kubernetes will cost you more in engineering attention than it returns. ECS Fargate, Cloud Run, App Runner or a pair of autoscaled VMs behind a load balancer will do the same job with a fraction of the operational surface. Kubernetes starts paying when you have many teams shipping independently, when workload shapes differ enough that bin packing matters, or when you need the same deployment model across more than one cloud.
Should we use EKS, GKE or AKS, or run our own control plane?
Use the managed control plane of whichever cloud already holds your data. Running your own etcd quorum is a real job with real 3am consequences, and the hourly control plane fee is cheaper than the engineer you would need to cover it. Self hosted only makes sense on bare metal for hardware reasons, in an air gapped environment, or where a regulator will not accept a managed control plane. Even then, use a distribution such as k3s, RKE2 or Talos rather than assembling one by hand.
Why do our pods get OOMKilled when the memory graph looks fine?
Because the graph is probably showing heap or average RSS, and the kernel kills on peak total memory in the cgroup. For a JVM that means heap plus metaspace plus thread stacks plus direct byte buffers plus the JIT code cache. For Node it means the old space limit plus buffers plus native modules. Exit code 137 with reason OOMKilled tells you the container crossed its limit, not that your application leaked. The fix is usually to set the runtime heap as a percentage of the container limit rather than a fixed number, then re-measure peak RSS at the ninety ninth percentile.
How do we stop paying for a cluster that is twenty percent utilised?
Start with requests, not with instance types. Nodes are provisioned against what pods request, so a fleet full of services asking for two CPUs and using a tenth of that will keep buying machines nobody needs. Pull ninety fifth percentile actual usage from Prometheus, reset requests against it, then let a consolidating provisioner such as Karpenter repack the nodes. After that comes spot for anything interruptible, scale to zero for dev namespaces, and attribution through OpenCost so each team sees its own number.
Can you run Postgres or Kafka inside the cluster?
Yes, through a mature operator such as CloudNativePG for Postgres or Strimzi for Kafka, and only when someone will own it. StatefulSet plus a block volume is not a database. What makes in cluster data safe is the operator logic around it: failover, backup verification, connection routing after promotion, and a pod disruption budget that stops a node drain from taking the primary. If nobody on your side wants that responsibility, a managed service is the honest answer and we will say so.
How do you handle Kubernetes upgrades without downtime?
Kubernetes ships three minor releases a year and each one is supported for roughly fourteen months, so upgrades are a standing quarterly job rather than a project. We scan for removed APIs with pluto or kubent, upgrade the control plane first, then roll node groups with surge nodes so capacity never dips. The two things that actually block a drain are pod disruption budgets set to zero unavailable and single replica deployments, and both get fixed before the first node is touched.
Does an India based team make on-call better or worse?
For cluster operations the timezone gap works in your favour, which is not true of most offshore work. Indian business hours cover the window when your US team is asleep, so the pages that would have woken someone at 2am land on a desk where people are already at work. What it costs you is handover discipline: written runbooks, a shared incident channel, and an agreed escalation path for the decisions an on-call engineer should not make alone. We set the overlap window and the escalation rules with you before anyone gets cluster access.
Can your engineers work in our cluster without our data leaving our region?
Yes, and it is the arrangement we push for. Engineers get named identities in your identity provider mapped to Kubernetes groups through OIDC, with RBAC scoped to the namespaces they work in. Cluster access goes through your bastion or your zero trust proxy, every kubectl call lands in the audit log, and production data is never copied to a local machine. Your workloads and volumes stay in the region you chose, so your GDPR position is unchanged. Confirm the specifics with your own counsel before you sign anything.