Kubernetes-REST
view release on metacpan or search on metacpan
# ============================================================
# 14. Rolling Update
# ============================================================
say "\n" . "=" x 60;
say " 14. ROLLING UPDATE";
say "=" x 60;
say "\n--- Updating image to nginx:1.27-bookworm ---";
my $rolled;
for my $attempt (1..5) {
my $dep = $api->get('Deployment', 'demo-nginx', namespace => $NS);
my $old_image = $dep->spec->template->spec->containers->[0]->image;
say " Old image: $old_image" if $attempt == 1;
# Update the container image
$dep->spec->template->spec->containers->[0]->image('nginx:1.27-bookworm');
# Add annotation to track the change
my $tpl_annotations = $dep->spec->template->metadata->annotations // {};
$tpl_annotations->{'kubernetes.io/change-cause'} = 'Updated nginx to bookworm variant via Perl';
$dep->spec->template->metadata->annotations($tpl_annotations);
$rolled = eval { $api->update($dep) };
last if $rolled;
say " Attempt $attempt conflict, retrying...";
sleep 1;
}
die "Failed to update deployment after 5 attempts\n" unless $rolled;
say " New image: " . $rolled->spec->template->spec->containers->[0]->image;
wait_for("rolling update to complete", sub {
my $d = $api->get('Deployment', 'demo-nginx', namespace => $NS);
my $updated = $d->status->updatedReplicas // 0;
my $ready = $d->status->readyReplicas // 0;
my $desired = $d->spec->replicas // 3;
return ($updated == $desired && $ready == $desired) ? 1 : 0;
}, 90);
# ============================================================
# 15. Job
# ============================================================
say "\n" . "=" x 60;
say " 15. JOB (run to completion)";
say "=" x 60;
say "\n--- Creating Job ---";
my $job = create_or_get($api, 'Job', 'demo-job',
$api->new_object(Job =>
metadata => {
name => 'demo-job',
namespace => $NS,
labels => { app => 'demo-app', type => 'batch' },
},
spec => {
backoffLimit => 2,
ttlSecondsAfterFinished => 300,
template => {
spec => {
restartPolicy => 'Never',
containers => [{
name => 'worker',
image => 'busybox:latest',
command => ['sh', '-c',
'echo "Job started at $(date)"; '
. 'echo "Processing items..."; '
. 'for i in 1 2 3 4 5; do echo " Item $i done"; sleep 1; done; '
. 'echo "Job completed at $(date)"'
],
resources => {
requests => { cpu => '25m', memory => '16Mi' },
limits => { cpu => '50m', memory => '32Mi' },
},
}],
},
},
},
),
namespace => $NS,
);
say " Job: " . $job->metadata->name;
wait_for("job to complete", sub {
my $j = $api->get('Job', 'demo-job', namespace => $NS);
return ($j->status->succeeded // 0) >= 1 ? 1 : 0;
}, 60);
my $j = $api->get('Job', 'demo-job', namespace => $NS);
say " Succeeded: " . ($j->status->succeeded // 0);
say " Completion: " . ($j->status->completionTime // 'pending');
# ============================================================
# 16. CronJob
# ============================================================
say "\n" . "=" x 60;
say " 16. CRONJOB";
say "=" x 60;
say "\n--- Creating CronJob (every minute) ---";
my $cron = create_or_get($api, 'CronJob', 'demo-cronjob',
$api->new_object(CronJob =>
metadata => {
name => 'demo-cronjob',
namespace => $NS,
labels => { app => 'demo-app', type => 'scheduled' },
},
spec => {
schedule => '*/1 * * * *',
successfulJobsHistoryLimit => 2,
failedJobsHistoryLimit => 1,
jobTemplate => {
spec => {
template => {
spec => {
restartPolicy => 'OnFailure',
containers => [{
name => 'cron-worker',
image => 'busybox:latest',
command => ['sh', '-c', 'echo "Cron tick at $(date)"'],
resources => {
requests => { cpu => '10m', memory => '8Mi' },
limits => { cpu => '25m', memory => '16Mi' },
},
}],
},
},
},
},
},
),
namespace => $NS,
);
say " CronJob: " . $cron->metadata->name;
say " Schedule: " . $cron->spec->schedule;
# ============================================================
# 17. Utility Pod
# ============================================================
say "\n" . "=" x 60;
say " 17. UTILITY POD";
say "=" x 60;
say "\n--- Creating utility pod with all config ---";
my $util_pod = create_or_get($api, 'Pod', 'demo-utility',
$api->new_object(Pod =>
metadata => {
name => 'demo-utility',
namespace => $NS,
labels => { app => 'demo-app', component => 'utility' },
annotations => {
'purpose' => 'Demonstrates ConfigMap + Secret access from a pod',
},
},
spec => {
serviceAccountName => 'demo-sa',
restartPolicy => 'Never',
containers => [{
name => 'util',
image => 'busybox:latest',
command => ['sh', '-c', join('; ',
'echo "=== Environment ==="',
'echo "APP_ENV=$APP_ENV"',
'echo "LOG_LEVEL=$LOG_LEVEL"',
'echo "DB_HOST=$DB_HOST"',
'echo ""',
'echo "=== Config Files ==="',
'echo "nginx.conf:"',
'cat /config/nginx.conf',
'echo ""',
'echo "=== Persistent Storage ==="',
'echo "Hello from Perl" > /data/test.txt',
'cat /data/test.txt',
'echo ""',
'echo "=== Service Account ==="',
'ls /var/run/secrets/kubernetes.io/serviceaccount/',
'sleep 120',
)],
env => [{
name => 'APP_ENV',
valueFrom => {
configMapKeyRef => { name => 'app-config', key => 'app.env' },
},
}, {
name => 'LOG_LEVEL',
valueFrom => {
configMapKeyRef => { name => 'app-config', key => 'app.log_level' },
},
}, {
name => 'DB_HOST',
valueFrom => {
secretKeyRef => { name => 'db-credentials', key => 'host' },
},
}],
volumeMounts => [{
name => 'config',
mountPath => '/config',
}, {
name => 'data',
mountPath => '/data',
}],
resources => {
requests => { cpu => '10m', memory => '8Mi' },
limits => { cpu => '25m', memory => '16Mi' },
},
}],
volumes => [{
name => 'config',
configMap => { name => 'app-config' },
}, {
name => 'data',
persistentVolumeClaim => { claimName => 'demo-storage' },
}],
},
),
namespace => $NS,
);
say " Utility Pod: " . $util_pod->metadata->name;
wait_for("utility pod to be running", sub {
( run in 0.540 second using v1.01-cache-2.11-cpan-39bf76dae61 )