Két évig én voltam az a fickó, aki adatbázisokat provizionál. Minden hétfő reggel ugyanaz a Slack üzenet: “Hé, kéne egy Postgres a new service-hez.” Megnyitottam a Terraformot, másolás, három változó átírása, plan, approval, apply. Húsz perc az életemből, el. Szorozd meg négy csapattal, és érezni fogod.
Aztán beüzemeltem a Crossplane-t Compositions-szel, és most a fejlesztők maguk csinálják egyetlen YAML fájllal. Íme, hogyan jutottam el idáig, és mi tört el közben.
Miért nem maradtam a Terraformnál?
A Terraform működik. Nem azért írom ezt, hogy leégessem. De önkiszolgálásra van egy alapvető problémája: a fejlesztőknek kell hozzáférés a state-hez, a provider credentialökhoz és a CI pipeline-hoz, ami futtatja a terraform apply-t. Ez rengeteg bizalmi felület valakinek, aki csak egy adatbázist akar.
A Crossplane megfordítja ezt. A Kubernetes klaszterben fut controllerek formájában. A fejlesztő létrehoz egy custom resource-t, a Crossplane összerakja belőle a valódi cloud resource-okat. Ugyanaz a GitOps workflow, amit az appjaikhoz már használnak.
Crossplane telepítés
Helmen keresztül futtatom, mert a marketplace operátornak gondjai voltak az OPA policy-inkkel:
helm repo add crossplane-stable https://charts.crossplane.io/stable
helm repo update
helm install crossplane crossplane-stable/crossplane \
--namespace crossplane-system \
--create-namespace \
--set args='{"--enable-usages"}' \
--version 1.19.0
Az --enable-usages flag fontos. Nélküle egy Composition törlése árván hagyhat cloud resource-okat. Ezt a drága utat tanultam meg, amikor egy gyakornok törölt egy CompositeResourceDefinition-t, és 14 nyomon nem követett RDS instance futott egy hétig.
AWS Provider beállítás
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: provider-aws-rds
spec:
package: xpkg.upbound.io/upbound/provider-aws-rds:v1.18.0
runtimeConfigRef:
name: irsa-config
IRSA-t (IAM Roles for Service Accounts) használok statikus credentialök helyett. A runtime config így néz ki:
apiVersion: pkg.crossplane.io/v1beta1
kind: DeploymentRuntimeConfig
metadata:
name: irsa-config
spec:
deploymentTemplate:
spec:
selector: {}
template:
spec:
serviceAccountName: crossplane-provider-aws
containers:
- name: package-runtime
args:
- --poll=1m
Egy buktató: a provider podoknak kell az eks.amazonaws.com/role-arn annotáció a saját ServiceAccountjukon, nem a Crossplane rendszer SA-ján. Egy délutánt töltöttem “AccessDenied” hibák debugolásával emiatt.
A Composition: RDS becsomagolva
Itt lesz érdekes. A Composition lényegében egy sablon, ami egy egyszerű, fejlesztőknek szánt API-t térképez le a mögöttes komplex cloud resource-ra.
Először definiáljuk, mit lát a fejlesztő (az XRD):
apiVersion: apiextensions.crossplane.io/v1
kind: CompositeResourceDefinition
metadata:
name: xpostgresinstances.database.dedico.hu
spec:
group: database.dedico.hu
names:
kind: XPostgresInstance
plural: xpostgresinstances
claimNames:
kind: PostgresInstance
plural: postgresinstances
versions:
- name: v1alpha1
served: true
referenceable: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
size:
type: string
enum: ["small", "medium", "large"]
description: "small=db.t3.micro, medium=db.t3.small, large=db.t3.medium"
teamName:
type: string
required:
- size
- teamName
A fejlesztők pólóméret alapján választanak és megadják a csapatnevet. Ennyi. Nincs instance class memorizálás, nincs subnet group config, nincs parameter group.
Aztán a Composition leképezi ezeket az egyszerű inputokat valódi AWS resource-okra:
apiVersion: apiextensions.crossplane.io/v1
kind: Composition
metadata:
name: postgres-on-aws
labels:
provider: aws
spec:
compositeTypeRef:
apiVersion: database.dedico.hu/v1alpha1
kind: XPostgresInstance
resources:
- name: rds-instance
base:
apiVersion: rds.aws.upbound.io/v1beta2
kind: Instance
spec:
forProvider:
engine: postgres
engineVersion: "16.4"
dbSubnetGroupName: shared-private
vpcSecurityGroupIds:
- sg-0abc123def456
publiclyAccessible: false
storageEncrypted: true
autoMinorVersionUpgrade: true
backupRetentionPeriod: 7
deletionProtection: true
patches:
- type: FromCompositeFieldPath
fromFieldPath: spec.size
toFieldPath: spec.forProvider.instanceClass
transforms:
- type: map
map:
small: db.t3.micro
medium: db.t3.small
large: db.t3.medium
- type: FromCompositeFieldPath
fromFieldPath: spec.size
toFieldPath: spec.forProvider.allocatedStorage
transforms:
- type: map
map:
small: "20"
medium: "50"
large: "100"
- type: FromCompositeFieldPath
fromFieldPath: spec.teamName
toFieldPath: spec.forProvider.tags.Team
- name: rds-password
base:
apiVersion: secretstores.crossplane.io/v1alpha1
kind: VaultSecret
spec:
forProvider:
path: database/creds
Mit csinálnak a fejlesztők a gyakorlatban?
Egy fejlesztő, aki adatbázist akar, ezt hozza létre az appja GitOps repójában:
apiVersion: database.dedico.hu/v1alpha1
kind: PostgresInstance
metadata:
name: user-service-db
namespace: team-payments
spec:
size: small
teamName: payments
Pusholja, az ArgoCD szinkronizálja, a Crossplane felveszi, és 5 perc múlva fut egy RDS instance, a connection string pedig bekerül egy Kubernetes Secretbe a namespace-ükben.
Nincs Slack üzenet. Nincs ticket. Nincs várakozás rám.
Amik félrementek
1. probléma: A Composition drift detection lassú. A Crossplane intervallumonként pollozza a cloud providereket (alapból 1 perc). Ha valaki módosít egy RDS instance-t az AWS konzolon, akár egy percbe is telhet, mire észreveszi és visszaállítja. Nekünk ez belefért, de ha szorosabb drift detectionre van szükséged, csökkentsd a --poll intervallumot. Csak figyelj az API rate limitekre.
2. probléma: A törlés sorrendje számít. Volt egy Compositionünk, ami RDS instance-t és security groupot is létrehozott. Amikor egy fejlesztő törölte a claimjét, a Crossplane megpróbálta a security groupot az RDS instance előtt törölni. A security group törlés elbukott, mert még használatban volt, és az egész beragadt egy törlési loopba. Megoldás: a usages feature használata a függőségek deklarálásához.
apiVersion: apiextensions.crossplane.io/v1alpha1
kind: Usage
metadata:
name: rds-uses-sg
spec:
of:
apiVersion: ec2.aws.upbound.io/v1beta1
kind: SecurityGroup
resourceRef:
name: my-sg
by:
apiVersion: rds.aws.upbound.io/v1beta2
kind: Instance
resourceRef:
name: my-rds
3. probléma: Provider verziófrissítések CRD-ket törhetnek. Amikor a provider-aws-rds-t v1.14-ről v1.16-ra frissítettem, két mező nevet változtatott. Az összes létező managed resource “field not found” hibákat kezdett dobálni. Azóta mindig staging klaszterben tesztelem a provider frissítéseket, és éles környezetben pontos verziókat pinnelek.
Költségkontroll
A pólóméret modell remek guardrail. Senki sem tud véletlenül egy db.r6g.4xlarge-ot felhúzni, mert nincs benne az enumban. De azért raktunk egy Kyverno policyt is második rétegként:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: limit-postgres-size
spec:
validatingAdmissionPolicy: false
rules:
- name: max-size-per-namespace
match:
any:
- resources:
kinds:
- PostgresInstance
validate:
message: "Nem-production namespace-ek csak 'small' vagy 'medium' méreteket használhatnak"
deny:
conditions:
all:
- key: "{{request.object.spec.size}}"
operator: Equals
value: "large"
- key: "{{request.namespace}}"
operator: AnyNotIn
value:
- prod-*
Megérte?
Három hónap után: heti 15+ infra kérésből lett talán 2 (edge case-ek, ahol valaki a standard méreteken kívül esik). A fejlesztők boldogabbak, mert nem kell várniuk. Én boldogabb vagyok, mert a platformra tudok koncentrálni, ahelyett hogy élő Terraform futtatógép lennék.
A setup nagyjából két hét valódi munkába került. Ennek nagy része az IRSA rendberakása és a Compositionök tesztelése különböző hibaforgatókönyvek ellen. Ha már futtatod a Kubernetest és GitOps-ot használsz, a Crossplane hozzáadása természetes következő lépés.
Egy tanács: kezd egy resource típussal. Csináld meg jól a Compositiont, dokumentáld le, hagyd, hogy a csapatok használják egy hónapig. Aztán bővíts. Az első napon teljes önkiszolgáló katalógust építeni recept a félig kész absztrakciókra, amikben senki nem bízik.