Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add k8s-ts-wait #1444

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions k8s-ts-wait/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: k8s-ts-wait
runtime: nodejs
description: A TypeScript program which waits on jobs during a Kubernetes deployment.
69 changes: 69 additions & 0 deletions k8s-ts-wait/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Kubernetes Wait for Dependent Jobs

This program creates two Kubernetes Jobs that run the "hello-world" image. It uses an async waitForJob function to wait for the first Job to complete, then creates a second Job with a data dependency on the first Job's completion. The program exports the IDs and status details of both Jobs.

## Deploying the Example

### Prerequisites

To follow this example, you will need:

1. [Install Pulumi](https://www.pulumi.com/docs/get-started/install/)
1. A local [kubeconfig](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/) if available, or one can be passed as a [provider argument](https://www.pulumi.com/registry/packages/kubernetes/api-docs/provider#inputs) in the request.

### Steps

After cloning this repo, from this working directory, run these commands:

1. Install the required Node.js packages:

This installs the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program.

```bash
$ npm install
```

1. Create a new Pulumi stack, which is an isolated deployment target for this example:

```bash
$ pulumi stack init dev
```

1. Deploy the kubernetes jobs by running `pulumi up`.

This command shows a preview of the resources that will be created and asks you
whether to proceed with the deployment. Select "yes" to perform the deployment.

```bash
$ pulumi up
Updating (dev):

View in Browser (Ctrl+O): https://app.pulumi.com/../k8s-ts-wait/dev/updates/1

Type Name Status Info
+ pulumi:pulumi:Stack k8s-ts-wait-dev creating (8s).
+ ├─ kubernetes:batch/v1:Job job created (4s) Waiting for Job "job-b002d656" to succeed (Active: 0 | Succeeded: 1 | Failed: 0)
+ └─ kubernetes:batch/v1:Job job2 creating (2s)... Waiting for Job "job2-5f7b1d45" to succeed (Active: 1 | Succeeded: 0 | Failed: 0)

Outputs:
job2DoneDetails: {..}
jobDoneDetails : {..}
jobId : {..}
jobStatus : {..}

Resources:
+ 3 created

Duration: 12s

```

As part of the update, you'll see the outputs of the program, showing the Job IDs and status details. The second job will not be created until the first job has completed.

1. From here, feel free to experiment a little bit. Once you've finished experimenting,
tear down your stack's resources by destroying and removing it:

```bash
$ pulumi destroy --yes
$ pulumi stack rm --yes
```
86 changes: 86 additions & 0 deletions k8s-ts-wait/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2016-2023, Pulumi Corporation. All rights reserved.

import * as k8sapi from "@kubernetes/client-node";
import * as k8s from "@pulumi/kubernetes";
import * as k8sOutput from "@pulumi/kubernetes/types/output";
import * as pulumi from "@pulumi/pulumi";
import { unknownValue } from "@pulumi/pulumi/runtime";

// Create a Kubernetes Job with a single container running the "hello-world" image
const job = new k8s.batch.v1.Job("job", {
spec: {
template: {
spec: {
containers: [{
name: "helloworld",
image: "hello-world",
}],
restartPolicy: "Never",
},
},
},
});

// Define an async function to wait for a Kubernetes Job to complete
async function waitForJob(jobMetadata: k8sOutput.meta.v1.ObjectMeta): Promise<any> {
// Only run the waitForJob function during a non-dryRun
if (!pulumi.runtime.isDryRun()) {

// Load the default KubeConfig from the local environment
const kc = new k8sapi.KubeConfig();
kc.loadFromDefault();

// Initialize a Kubernetes API client using the loaded KubeConfig
const client = kc.makeApiClient(k8sapi.BatchV1Api);

// Poll the Kubernetes Job status every 10 seconds for up to 10 minutes
for (let i = 0; i <60; i++) {
const jobDetails = (await client.readNamespacedJob(jobMetadata.name, jobMetadata.namespace)).response;
if (jobDetails.body && jobDetails.body.status && jobDetails.body.status.succeeded > 0) {
// Return the Job details once completed successfully
return jobDetails.body;
}
pulumi.log.info(`Waiting for Job to finish (${i})`, job);
// Wait for 10s between polls
await new Promise(r => setTimeout(r, 10000));
}
// Throw an error if the Job did not complete within the 10-minute timeout
throw new Error("timed out waiting for Job to complete");
}

const unknown = (pulumi as any).unknown;
return { status: { completionTime: unknown} };
}

// WaitForJob for the first Kubernetes Job to complete
const jobDone = job.metadata.apply(metadata => waitForJob(metadata));

// Create a second Kubernetes Job with data dependency on the completion of the first Kubernetes Job
const job2 = new k8s.batch.v1.Job("job2", {
metadata: {
annotations: {
// Assign the completionTime of the first Job to an annotation on the second Job
// This enforces a dependency relationship between the jobs
"pulumi-waited-on-completion": jobDone.apply(j => j.status.completionTime),
},
},
spec: {
template: {
spec: {
containers: [{
name: "helloworld",
image: "hello-world",
}],
restartPolicy: "Never",
},
},
},
});
// WaitForJob for the second Kubernetes Job to complete
const job2Done = job2.metadata.apply(metadata => waitForJob(metadata));

// Export resource IDs and status details
export const jobId = job.id;
export const jobStatus = job.status;
export const jobDoneDetails = jobDone;
export const job2DoneDetails = job2Done;
10 changes: 10 additions & 0 deletions k8s-ts-wait/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "k8s-ts-wait",
"main": "index.ts",
"dependencies": {
"@kubernetes/client-node": "^0.20.0",
"@pulumi/kubernetes": "^4.5.4",
"@pulumi/pulumi": "^3.93.0",
"node": "^21.1.0"
}
}
Loading