ConfigMaps, secrets & networking
ConfigMaps
$cluster->configMaps()
->setNamespace('shop')
->setName('checkout-config')
->setData([
'APP_ENV' => 'production',
'nginx.conf' => $nginxConfig,
])
->create();
$config = $cluster->configMaps()->setNamespace('shop')->withName('checkout-config')->find();
$config->getData(); // the whole map
$config->getData('nginx.conf'); // one key
$config->getData('MISSING', 'n/a'); // with a default
$config->addData('LOG_LEVEL', 'debug')
->removeData('APP_ENV')
->update();Data keys are file names — nginx.conf, app.env — so getData(), addData() and removeData() always treat them as flat keys, never as dotted attribute paths. Removing the last key drops the data field altogether, because an empty map would encode as the JSON list [], which the apiserver refuses.
Secrets
Secret::setData() and addData() base64-encode values for you and getData() decodes them, so you only ever handle plain strings. Keys such as tls.crt or .dockerconfigjson stay flat:
$cluster->secrets()
->setNamespace('shop')
->setName('checkout-db')
->setData(['username' => 'checkout', 'password' => $password]) // base64-encoded for you
->create();
$secret = $cluster->secrets()->setNamespace('shop')->withName('checkout-db')->find();
$secret->getData('password'); // the decoded plain string
$secret->getData(); // every key, decoded
$secret->addData('host', 'db.shop.svc.cluster.local')->update();TLS certificate secrets
Secret supports a typed type plus a TLS convenience that base64-encodes a PEM certificate and key into a kubernetes.io/tls Secret — ready for an ingress or a Traefik TLSStore:
$secret = $cluster->secrets()
->setNamespace('default')
->setName('wildcard-tls')
->asTlsCertificate($pemCertificate, $pemPrivateKey) // sets type + data['tls.crt'] / data['tls.key']
->create();
$secret->getType(); // "kubernetes.io/tls"
$secret->getData('tls.crt'); // the decoded PEM certificate
// Or set an arbitrary Secret type explicitly:
$cluster->secrets()->setName('dockercfg')->setType('kubernetes.io/dockerconfigjson');Services
use RoundlyConsulting\KubernetesApi\Resources\Types\Port;
$cluster->services()
->setNamespace('shop')
->setName('checkout')
->setType('ClusterIP')
->setSelectors(['app' => 'checkout'])
->addPort(Port::http(8080)->setName('http')) // port 80 -> targetPort 8080, TCP
->addPort(Port::make()->setName('metrics')->setProtocol('TCP')->setPort(9090)->setTargetPort(9090))
->create();
$service = $cluster->services()->setNamespace('shop')->withName('checkout')->find();
$service->getClusterDns(); // checkout.shop.svc.cluster.local
$service->getType(); // 'ClusterIP'
$service->getPorts(); // Port[]
$service->getSelectors(); // ['app' => 'checkout']
// A LoadBalancer that keeps client source IPs
$cluster->services()
->setNamespace('shop')
->setName('checkout-public')
->setType('LoadBalancer')
->useLocalTrafficPolicy() // externalTrafficPolicy: Local
->setSelectors(['app' => 'checkout'])
->addPort(Port::http(8080)->setName('http'))
->create();Ingresses
addRule() writes a host, a path and a backend service in one call; addTls() attaches a certificate secret to a list of hosts:
$cluster->ingresses()
->setNamespace('shop')
->setName('checkout')
->setIngressClassName('nginx')
->addRule('shop.example.com', '/', 'checkout', 80) // pathType defaults to Prefix
->addRule('shop.example.com', '/api', 'checkout-api', 8080, 'Prefix')
->addTls(['shop.example.com'], 'wildcard-tls')
->create();
$ingress = $cluster->ingresses()->setNamespace('shop')->withName('checkout')->find();
$ingress->getRules();
$ingress->getTls();
$ingress->getLoadBalancerIngress(); // status.loadBalancer.ingressNetwork policies and select-all
An empty label selector means “select all” in Kubernetes and must be sent as the empty object {}, not [] — which the apiserver rejects. The selector setters handle this for you, so passing an empty array serialises correctly:
// Default-deny / select-all: an empty podSelector matches every pod in the namespace.
$cluster->networkPolicies()
->setNamespace('shop')
->setName('default-deny')
->setPodSelector([]) // serialises to "podSelector": {}
->setPolicyTypes(['Ingress'])
->create();
// Then allow the ingress namespace to reach checkout on 8080.
$cluster->networkPolicies()
->setNamespace('shop')
->setName('allow-ingress-to-checkout')
->setPodSelector(['app' => 'checkout']) // podSelector.matchLabels
->setPolicyTypes(['Ingress'])
->addIngressRule([
'from' => [['namespaceSelector' => ['matchLabels' => ['kubernetes.io/metadata.name' => 'ingress']]]],
'ports' => [['protocol' => 'TCP', 'port' => 8080]],
])
->create();Service::setSelectors([]) serialises the same way. Workload pod selectors are different: the apiserver refuses an empty selector on apps/v1 workloads (Deployment, ReplicaSet, StatefulSet, DaemonSet), so always give them at least one label, as in setPodsSelectors(['app' => 'checkout']). For any other node that must be an empty object, set it to a RoundlyConsulting\KubernetesApi\Resources\Types\EmptyObject — it always serialises to {}.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.