Kubernetes-REST
view release on metacpan or search on metacpan
lib/Kubernetes/REST/Example.pm view on Meta::CPAN
=head1 NAME
Kubernetes::REST::Example - Working examples for Kubernetes::REST with Minikube, K3s, and other clusters
=head1 CLUSTER SETUP
L<Kubernetes::REST> works with any Kubernetes cluster. Below are setup
instructions for common local development environments. All of them write
a kubeconfig to C<~/.kube/config> that L<Kubernetes::REST::Kubeconfig> picks
up automatically.
=head2 Minikube
L<Minikube|https://minikube.sigs.k8s.io/> creates a single-node cluster
inside a Docker container or VM.
# Install (Debian/Ubuntu)
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
# Start with Docker driver
minikube start --driver=docker
# Verify
minikube status
Minikube uses self-signed certificates and token authentication. The
kubeconfig context is named C<minikube>.
B<NodePort access:>
minikube service <service-name> -n <namespace>
=head2 K3s
L<K3s|https://k3s.io/> is a lightweight Kubernetes distribution from Rancher.
It installs as a single binary and runs directly on the host (no Docker
required).
# Install
curl -sfL https://get.k3s.io | sh -
# K3s writes its own kubeconfig to a different path
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
# Or copy it so Kubernetes::REST::Kubeconfig finds it automatically
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $USER ~/.kube/config
chmod 600 ~/.kube/config
# Verify
kubectl get nodes
B<Key differences from Minikube:>
=over 4
=item * Kubeconfig lives at C</etc/rancher/k3s/k3s.yaml> (not C<~/.kube/config>)
=item * The server endpoint is C<https://127.0.0.1:6443>
=item * K3s includes Traefik as its default ingress controller
=item * K3s uses C<containerd> instead of Docker
=item * NodePort services are accessible directly on the host IP
=back
B<Connecting from Perl with K3s:>
use Kubernetes::REST::Kubeconfig;
# If you copied the kubeconfig to ~/.kube/config:
my $api = Kubernetes::REST::Kubeconfig->new->api;
# If using the K3s path directly:
my $api = Kubernetes::REST::Kubeconfig->new(
kubeconfig_path => '/etc/rancher/k3s/k3s.yaml',
)->api;
=head2 Kind (Kubernetes in Docker)
L<Kind|https://kind.sigs.k8s.io/> runs Kubernetes nodes as Docker containers.
Good for CI and testing.
# Install
go install sigs.k8s.io/kind@latest
# Create cluster
kind create cluster --name perl-test
# Context is named: kind-perl-test
my $api = Kubernetes::REST::Kubeconfig->new(
context_name => 'kind-perl-test',
)->api;
=head2 Any cluster with a kubeconfig
If you have a kubeconfig (EKS, GKE, AKS, self-managed, etc.):
use Kubernetes::REST::Kubeconfig;
# Uses current context from ~/.kube/config
my $api = Kubernetes::REST::Kubeconfig->new->api;
# Or specify a context
my $api = Kubernetes::REST::Kubeconfig->new(
context_name => 'my-production-cluster',
)->api;
=head1 INSTALL PERL DEPENDENCIES
cpanm Kubernetes::REST
=head1 CONNECTING TO THE CLUSTER
=head2 Using the kubeconfig (recommended)
L<Kubernetes::REST::Kubeconfig> parses your kubeconfig and sets up
authentication, SSL certificates, and the server endpoint automatically:
use Kubernetes::REST::Kubeconfig;
my $kc = Kubernetes::REST::Kubeconfig->new;
my $api = $kc->api;
say "Cluster version: " . $api->cluster_version;
If you have multiple contexts:
my $contexts = $kc->contexts;
say "Available: @$contexts";
my $api = $kc->api('minikube'); # or 'default', 'kind-test', etc.
=head2 Manual configuration
use Kubernetes::REST;
my $api = Kubernetes::REST->new(
server => {
endpoint => 'https://127.0.0.1:6443',
ssl_verify_server => 0,
},
credentials => { token => $token },
resource_map_from_cluster => 0,
);
=head1 LISTING RESOURCES
=head2 Namespaces
my $list = $api->list('Namespace');
for my $ns (@{ $list->items }) {
printf "%-20s %s\n",
$ns->metadata->name,
$ns->status->phase // 'Unknown';
}
=head2 Pods in a namespace
my $pods = $api->list('Pod', namespace => 'kube-system');
for my $pod (@{ $pods->items }) {
my $name = $pod->metadata->name;
my $phase = $pod->status->phase // 'Unknown';
my $ip = $pod->status->podIP // 'pending';
printf "%-45s %-10s %s\n", $name, $phase, $ip;
}
=head2 Services
my $services = $api->list('Service', namespace => 'default');
for my $svc (@{ $services->items }) {
my $name = $svc->metadata->name;
my $type = $svc->spec->type // 'ClusterIP';
my $ports = join ', ', map {
$_->port . '/' . ($_->protocol // 'TCP')
} @{ $svc->spec->ports // [] };
printf "%-20s %-12s %s\n", $name, $type, $ports;
}
=head2 Nodes
my $nodes = $api->list('Node');
for my $node (@{ $nodes->items }) {
my $info = $node->status->nodeInfo;
printf "%-20s OS=%-8s kubelet=%s\n",
$node->metadata->name,
$info->operatingSystem // '?',
$info->kubeletVersion // '?';
}
=head1 GETTING A SINGLE RESOURCE
C<get()> returns a fully typed L<IO::K8s> object:
my $pod = $api->get('Pod', 'my-pod', namespace => 'default');
lib/Kubernetes/REST/Example.pm view on Meta::CPAN
properties:
domain: { type: string }
image: { type: string }
replicas: { type: integer, default: 1 }
tls: { type: boolean, default: false }
status:
type: object
properties:
readyReplicas: { type: integer }
url: { type: string }
scope: Namespaced
names:
plural: staticwebsites
singular: staticwebsite
kind: StaticWebSite
shortNames: [sw]
$ kubectl apply -f staticwebsite-crd.yaml
=head2 Writing a CRD class
Create a Perl class using C<IO::K8s::APIObject> with import parameters
for C<api_version> and C<resource_plural>:
package My::StaticWebSite;
use IO::K8s::APIObject
api_version => 'homelab.example.com/v1',
resource_plural => 'staticwebsites';
with 'IO::K8s::Role::Namespaced';
k8s spec => { Str => 1 };
k8s status => { Str => 1 };
1;
Key points:
=over 4
=item * C<api_version> - the CRD's C<group/version> (e.g. C<homelab.example.com/v1>)
=item * C<resource_plural> - must match the CRD's C<spec.names.plural>
=item * C<kind> is derived automatically from the class name (last C<::> segment)
=item * Apply C<IO::K8s::Role::Namespaced> for namespace-scoped CRDs
=item * C<spec> and C<status> are opaque hashrefs (C<< { Str => 1 } >>)
=back
=head2 Registering the CRD class
Register your class with the C<+> prefix in the resource map:
my $api = Kubernetes::REST::Kubeconfig->new->api;
$api->resource_map->{StaticWebSite} = '+My::StaticWebSite';
Or pass it at construction time:
my $api = Kubernetes::REST->new(
server => { endpoint => '...' },
credentials => { token => '...' },
resource_map => {
%{ IO::K8s->default_resource_map },
StaticWebSite => '+My::StaticWebSite',
},
);
=head2 CRUD on Custom Resources
Once registered, CRDs work exactly like built-in resources:
use JSON::PP ();
# Create
my $site = $api->create($api->new_object(StaticWebSite =>
metadata => {
name => 'my-blog',
namespace => 'default',
},
spec => {
domain => 'blog.example.com',
image => 'nginx:1.27-alpine',
replicas => 2,
tls => JSON::PP::true,
},
));
# Get
my $site = $api->get('StaticWebSite', 'my-blog', namespace => 'default');
say $site->spec->{domain}; # blog.example.com
# List
my $list = $api->list('StaticWebSite', namespace => 'default');
for my $s (@{ $list->items }) {
say $s->metadata->name . ": " . $s->spec->{domain};
}
# Update
$site->spec->{replicas} = 3;
$api->update($site);
# Delete
$api->delete('StaticWebSite', 'my-blog', namespace => 'default');
B<Note:> For boolean CRD fields, use C<JSON::PP::true> and C<JSON::PP::false>
instead of C<1> and C<0>. The Kubernetes API validates JSON types strictly
for custom resources.
=head2 Serialization
CRD objects support the same serialization as built-in resources:
say $site->to_yaml;
# ---
# apiVersion: homelab.example.com/v1
# kind: StaticWebSite
# metadata:
# name: my-blog
# namespace: default
# spec:
( run in 0.837 second using v1.01-cache-2.11-cpan-524268b4103 )