diff --git a/kubernetes-ts-ssa/README.md b/kubernetes-ts-ssa/README.md new file mode 100644 index 000000000..56386831a --- /dev/null +++ b/kubernetes-ts-ssa/README.md @@ -0,0 +1,270 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new?template=https://github.com/pulumi/examples/blob/master/kubernetes-ts-ssa/README.md) + +# Kubernetes SSA (Multi-Party Authoring) + +An example of using Kubernetes Server-Side Apply (SSA) to manage a deployment using +Pulumi and `@pulumi/kubernetes`. This example highlights the separation-of-concerns aspect of SSA, +by splitting the management of an nginx deployment across two apps (`deploy` and `scale`). + +The `deploy` app is concerned with deploying and servicing the nginx server. It sets an initial value +for `spec.replicas` and then ignores any changes to the field. + +The `scale` app is concerned with scaling the nginx server. It uses Pulumi's support for SSA to manage +the replicas field of the deployment. + +## Deploying the App + +Follow the steps in [Pulumi Installation and Setup](https://www.pulumi.com/docs/get-started/install/) and [Configuring Pulumi +Kubernetes](https://www.pulumi.com/docs/intro/cloud-providers/kubernetes/setup/) to get setup with Pulumi and Kubernetes. + +Change to the `deploy` directory: + +```sh +cd deploy/ +``` + +Install dependencies: + +```sh +cd deploy/ +npm install +``` + +Create a new stack: + +```sh +$ pulumi stack init +Enter a stack name: testing +``` + +Perform the deployment, and take note of the outputted name of the deployment (e.g. `nginx-a3e12ea6`): + +```sh +$ pulumi up +Updating (testing) + + Type Name Status + + pulumi:pulumi:Stack kubernetes-ts-ssa-deploy-testing created (3s) + + └─ kubernetes:apps/v1:Deployment nginx created (2s) + +Outputs: + name: "nginx-a3e12ea6" + +Resources: + + 2 created + +Duration: 5s +``` + +The application is now deployed. Use `kubectl` to see the deployment, and observe that replicas is `1`: + +```sh +$ k get deployment +NAME READY UP-TO-DATE AVAILABLE AGE +nginx-a3e12ea6 1/1 1 1 79s +``` + +## Scaling the App + +Change to the `scale` directory: + +```sh +cd scale/ +``` + +Install dependencies: + +```sh +cd scale/ +npm install +``` + +Create a new stack: + +```sh +$ pulumi stack init +Enter a stack name: testing +``` + +Configure the name of the deployment to be scaled by the app: + +```sh +$ pulumi config set name nginx-a3e12ea6 +``` + +Scale the deployment: + +```sh +$ pulumi up +Updating (testing) + + Type Name Status + + pulumi:pulumi:Stack kubernetes-ts-ssa-scale-testing created (0.28s) + + └─ kubernetes:apps/v1:DeploymentPatch nginx created (0.13s) + + +Resources: + + 2 created + +Duration: 2s +``` + +Use `kubectl` to see the updated deployment, and observe that replicas is `3`: + +```sh +$ k get deployment +NAME READY UP-TO-DATE AVAILABLE AGE +nginx-a3e12ea6 3/3 3 3 11m +``` + +## Observing the Managed Fields +The Kubernetes API tracks field ownership using object metadata (`managedFields`). Observe that the deployment +now has three field managers, representing the deploy and scale applications that are managing the spec, +and the Kubernetes controller that is managing the status. + +```sh +$ k get deployment nginx-a3e12ea6 --show-managed-fields -oyaml +NAME READY UP-TO-DATE AVAILABLE AGE +nginx-a3e12ea6 3/3 3 3 11m +``` + +The results should resemble: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + annotations: + deployment.kubernetes.io/revision: "1" + pulumi.com/autonamed: "true" + pulumi.com/patchFieldManager: kubernetes-ts-ssa-deploy + pulumi.com/patchForce: "true" + creationTimestamp: "2023-09-07T00:18:03Z" + generation: 2 + managedFields: + - apiVersion: apps/v1 + fieldsType: FieldsV1 + fieldsV1: + f:metadata: + f:annotations: + f:pulumi.com/autonamed: {} + f:pulumi.com/patchFieldManager: {} + f:spec: + f:selector: {} + f:template: + f:metadata: + f:labels: + f:app: {} + f:spec: + f:containers: + k:{"name":"nginx"}: + .: {} + f:image: {} + f:name: {} + manager: kubernetes-ts-ssa-deploy + operation: Apply + time: "2023-09-07T00:18:03Z" + - apiVersion: apps/v1 + fieldsType: FieldsV1 + fieldsV1: + f:metadata: + f:annotations: + f:pulumi.com/patchForce: {} + f:spec: + f:replicas: {} + manager: pulumi-kubernetes-31df3088 + operation: Apply + time: "2023-09-07T00:33:54Z" + - apiVersion: apps/v1 + fieldsType: FieldsV1 + fieldsV1: + f:metadata: + f:annotations: + f:deployment.kubernetes.io/revision: {} + f:status: + f:availableReplicas: {} + f:conditions: + .: {} + k:{"type":"Available"}: + .: {} + f:lastTransitionTime: {} + f:lastUpdateTime: {} + f:message: {} + f:reason: {} + f:status: {} + f:type: {} + k:{"type":"Progressing"}: + .: {} + f:lastTransitionTime: {} + f:lastUpdateTime: {} + f:message: {} + f:reason: {} + f:status: {} + f:type: {} + f:observedGeneration: {} + f:readyReplicas: {} + f:replicas: {} + f:updatedReplicas: {} + manager: kube-controller-manager + operation: Update + subresource: status + time: "2023-09-07T00:33:57Z" + name: nginx-a3e12ea6 + namespace: default + resourceVersion: "72206" + uid: 131954f7-83d1-4129-a956-e797dce2dfdd +spec: + progressDeadlineSeconds: 600 + replicas: 3 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: nginx + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + creationTimestamp: null + labels: + app: nginx + spec: + containers: + - image: nginx + imagePullPolicy: Always + name: nginx + resources: {} + terminationMessagePath: /dev/termination-log + terminationMessagePolicy: File + dnsPolicy: ClusterFirst + restartPolicy: Always + schedulerName: default-scheduler + securityContext: {} + terminationGracePeriodSeconds: 30 +``` + +## Cleanup + +Destroy the scale app: + +```sh +$ cd scale/ +$ pulumi destroy -f +``` + +Observe that the deployment was not deleted, and that replicas has reverted to the default value: + +```sh +$ k get deployment +NAME READY UP-TO-DATE AVAILABLE AGE +nginx-a3e12ea6 1/1 1 1 14m +``` + +Destroy the deploy app: + +```sh +$ cd deploy/ +$ pulumi destroy -f +``` diff --git a/kubernetes-ts-ssa/deploy/.gitignore b/kubernetes-ts-ssa/deploy/.gitignore new file mode 100644 index 000000000..c6958891d --- /dev/null +++ b/kubernetes-ts-ssa/deploy/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/node_modules/ diff --git a/kubernetes-ts-ssa/deploy/Pulumi.yaml b/kubernetes-ts-ssa/deploy/Pulumi.yaml new file mode 100644 index 000000000..19c62b091 --- /dev/null +++ b/kubernetes-ts-ssa/deploy/Pulumi.yaml @@ -0,0 +1,3 @@ +name: kubernetes-ts-ssa-deploy +runtime: nodejs +description: Creates a deployment using server-side apply. diff --git a/kubernetes-ts-ssa/deploy/index.ts b/kubernetes-ts-ssa/deploy/index.ts new file mode 100644 index 000000000..0fd8ea958 --- /dev/null +++ b/kubernetes-ts-ssa/deploy/index.ts @@ -0,0 +1,23 @@ +import * as k8s from "@pulumi/kubernetes"; + +const appLabels = { app: "nginx" }; +const deployment = new k8s.apps.v1.Deployment("nginx", { + metadata: { + annotations: { + "pulumi.com/patchFieldManager": "kubernetes-ts-ssa-deploy" + }, + }, + spec: { + selector: { matchLabels: appLabels }, + replicas: 1, + template: { + metadata: { labels: appLabels }, + spec: { containers: [{ name: "nginx", image: "nginx" }] } + } + } +}, { + // this program sets the initial scale and then hands off to other managers. + ignoreChanges: ["spec.replicas"] +}); + +export const name = deployment.metadata.name; diff --git a/kubernetes-ts-ssa/deploy/package.json b/kubernetes-ts-ssa/deploy/package.json new file mode 100644 index 000000000..7894fb6d6 --- /dev/null +++ b/kubernetes-ts-ssa/deploy/package.json @@ -0,0 +1,11 @@ +{ + "name": "kubernetes-ts-ssa-deploy", + "main": "index.ts", + "devDependencies": { + "@types/node": "^16" + }, + "dependencies": { + "@pulumi/pulumi": "^3.0.0", + "@pulumi/kubernetes": "^4.0.0" + } +} diff --git a/kubernetes-ts-ssa/deploy/tsconfig.json b/kubernetes-ts-ssa/deploy/tsconfig.json new file mode 100644 index 000000000..ab65afa61 --- /dev/null +++ b/kubernetes-ts-ssa/deploy/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "strict": true, + "outDir": "bin", + "target": "es2016", + "module": "commonjs", + "moduleResolution": "node", + "sourceMap": true, + "experimentalDecorators": true, + "pretty": true, + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "forceConsistentCasingInFileNames": true + }, + "files": [ + "index.ts" + ] +} diff --git a/kubernetes-ts-ssa/scale/Pulumi.yaml b/kubernetes-ts-ssa/scale/Pulumi.yaml new file mode 100644 index 000000000..c077d79e0 --- /dev/null +++ b/kubernetes-ts-ssa/scale/Pulumi.yaml @@ -0,0 +1,19 @@ +name: kubernetes-ts-ssa-scale +runtime: yaml +description: Scales a deployment using server-side apply. +config: + name: + type: string + replicas: + type: integer + default: 3 +resources: + nginx: + properties: + metadata: + name: ${name} + annotations: + "pulumi.com/patchForce": "true" + spec: + replicas: ${replicas} + type: kubernetes:apps/v1:DeploymentPatch