Michael Irwin�Engineering Manager�Docker
Creating A Landlord for Multi-tenant K8s Using Flux, Gatekeeper, Helm, and Friends
17 May 2022 I Valencia, Spain
Introduction
Outside of Tech…
Session Outline
All source code is at:�github.com/mikesir87/gitopscon-eu-2022-demo
1. Goals of the Effort
The Common Application Platform
Initiative
Establish an organizational unit that provides application technology infrastructure and shared services for all application development teams.
Anticipated Outcomes
In a Nutshell…
“You Build a Container, We’ll Run It!”
Separation of Concerns
Cloud Infrastructure
Cluster Infrastructure
Node/Compute Resources
Application Definitions
Core Cluster Services
Application Services
Application Code
2. Multi-tenancy 101
Definition of a Tenant
What Could Possibly Go Wrong?
The Tenancy Spectrum
Soft
Hard
Virtual Cluster
microVM per pod
Namespace per tenant
Node grouping/ isolation
Additional policy enforcement
Separate RBAC per tenant
Least Secure
Cheapest
Most Secure
Expensive
3. Creating a Landlord
https://unsplash.com/photos/s34TlUTPIf4
The Goals of the Landlord
Why Helm?
First Things First… Namespaces!
Virtual Cluster
microVM per pod
Namespace per tenant
Node grouping/ isolation
Additional policy enforcement
Separate Flux per tenant
Define Namespaces
tenants:
team-awesome:
team-cats:
team-dogs:
{{- range $name, $tenant := .Values.tenants }}
apiVersion: v1
kind: Namespace
metadata:
name: {{ $name }}
labels:
{{- include "landlord.labels" $ | nindent 4 }}
---
{{- end }}
templates/namespace.yaml
values.sample.yaml
How do tenants deploy stuff?
The Manifest Repo
What the Landlord Needs to Do
Define Flux Config
tenants:
team-awesome:
flux:
path: ./manifests/...
team-cats:
flux:
path: ./manifests/...
team-dogs:
flux:
path: ./manifests/...
{{- range $name, $tenant := .Values.tenants }}
{{- $flux := $tenant.flux | default dict -}}
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: GitRepository
metadata:
name: {{ $name }}
namespace: flux-tenant-config
spec:
interval: 30m
url: github.com/mikesir87/{{ $name }}
secretRef:
name: flux-ssh-credentials
ref:
branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
...
values.sample.yaml
Locking Things Down a Little Bit…
Virtual Cluster
microVM per pod
Namespace per tenant
Node grouping/ isolation
Additional policy enforcement
Separate RBAC per tenant
Using Gatekeeper
Define Shared GK Policies
apiVersion: constraints.gatekeeper.sh/v1
kind: K8sPSSBaselinePrivilegedContainer
metadata:
name: {{ .Release.Name }}
spec:
match:
namespaces:
{{- range $name, $tenant := .Values.tenants }}
- {{ $name }}
{{- end }}
kinds:
- apiGroups: [""]
kinds: ["Pod"]
---
apiVersion: constraints.gatekeeper.sh/v1
kind: K8sPSSBaselineCapabilities
metadata:
name: {{ .Release.Name }}
spec:
match:
…
templates/gk-pss.yaml
Define Parameterized GK Policies
{{- range $name, $tenant := .Values.tenants }}
{{- $tenantConfig := $tenant | default dict -}}
---
apiVersion: constraints.gatekeeper.sh/v1
kind: AuthorizedDomainPolicy
metadata:
name: {{ $name }}
spec:
match:
namespaces:
- {{ $name }}
parameters:
domains:
- "{{ $name }}.{{ $.Values.config.tenantHostPrefix }}"
- "*.{{ $name }}.{{ $.Values.config.tenantHostPrefix }}"
{{- if $tenantConfig.domains }}
{{- $tenantConfig.domains | toYaml | nindent 6 }}
{{- end }}
...
templates/gk-authorized-domains.yaml
config:
tenantHostPrefix: tenants.local.mikesir87.training
tenants:
team-awesome:
flux:
path: ./manifests/team-awesome
team-cats:
domains:
- cats.local.mikesir87.training
team-dogs:
Breaking up workloads!
Virtual Cluster
microVM per pod
Namespace per tenant
Node grouping/ isolation
Additional policy enforcement
Separate RBAC per tenant
Defining Node Pools
Node Pools Graphically!
taints:
- platform/node-pool=teamA:NoSchedule
labels:
platform/node-pool: teamA
taints:
- platform/node-pool=teamB:NoSchedule
labels:
platform/node-pool: teamB
…
Node
Pod
toleration:
- platform/node-pool=teamA:NoSchedule
nodeSelector:
platform/node-pool: teamA
toleration:
- platform/node-pool=teamB:NoSchedule
nodeSelector:
platform/node-pool: teamB
Defining Node Pools
nodePools:
demos:
costCode: demos
tenants:
...
{{- range $name, $poolDetails := .Values.nodePools }}
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: {{ $name }}
spec:
taints:
- key: platform/node-pool
value: {{ $name }}
effect: NoSchedule
...
Forcing Pods into Node Pools
nodePools:
demos:
costCode: demos
tenants:
team-awesome:
flux:
path: ./manifests/team-awesome
nodePool: demos
{{- range $name, $tenant := .Values.tenants }}
{{- if $tenant.nodePool }}
apiVersion: mutations.gatekeeper.sh/v1beta1
kind: Assign
metadata:
name: {{ $name }}-nodepool-selector
namespace: gatekeeper-system
spec:
applyTo:
- groups: [""]
kinds: ["Pod"]
versions: ["v1"]
match:
scope: Namespaced
kinds:
- apiGroups: ["*"]
kinds: ["Pod"]
namespaces: ["{{ $name }}"]
location: "spec.nodeSelector"
parameters:
assign:
value:
platform/node-pool: {{ $tenant.nodePool }}
...
Other Things for the Landlord
tenants:
team-awesome:
flux:
path: ./manifests/team-awesome
nodePool: demos
splunkIndex: awesome-team
rbac:
- groupName: awesome-team.devs
- groupName: awesome-team.ops
exec: true
One last thing!
Deploying the Landlord
Looping back… why?!?
Thanks! Questions?