TT Lab
Get started
Learn Learning paths Courses

CNPE — Cloud Native Platform Engineer

The request succeeded, so why is the app not ready?

Continue in TT Lab

Goal

On a real Crossplane 2.4, you create a namespaced App and verify a wrong readiness condition, team permissions, change convergence, and deletion delay. You do not presume success from a declaration or a single status display; you check the real Pod, HTTP, and the owner UID together.

Why it matters

Self-service is not work that automates only the create button. You must restrict the request's input and permissions, observe the created app, and be responsible for changes and deletion too. A new object with the same name or another team's response must not get mixed in as success of this request. You use k3s, Crossplane, a function, and a real container running inside a personal VM. These are not KWOK or fake Ready Pods. The installation can take several minutes. The estimated lab time is 55 minutes, and if you need more, extend the time before it expires. When the session ends, the VM and files are reclaimed. Download any records you need first, and do not apply anything to a production cluster outside the VM.

What is prepared and the tools

act applies the file for that step or performs the specified experiment. observe queries once. capture observes for up to 75 seconds without fixing the current configuration and then saves the specified JSON. grade does not modify files or change resources. If the state is still being prepared, do not reinstall; query the same run again. Previously verified observation files are preserved even if the same state disappears in later steps. Do not hand-edit a file into a success JSON.

Steps

  1. In /root/cnpe-api/initial.json, write apiVersion=platform.labhub.local/v1alpha1, kind=App, metadata.name=parcel, metadata.namespace=team-a, spec.replicas=1, and spec.channel=stable. act 1 applies this request with the developer account. Save accepted.json with capture 1, and check together a request that is Synced=True but Ready=False and the actual HTTP stable response.
  2. Copy /opt/fixtures/cnpe-crossplane-composition.json to /root/cnpe-api/composition.json. Change only the Ready in spec.pipeline[0].input.resources[0].readinessChecks[0].matchCondition.type to Available. Keep the remaining templates, selectors, image, and permissions. Apply it with act 2 and record the actual readiness in ready.json with capture 2.
  3. Query whether team-a's developer can create its own team's App but cannot create another team's App or a Deployment directly. With act 3, test the two actual permission rejections and the replicas=4 schema rejection. Save boundaries.json with capture 3. Do not use an administrator's success or a failed permission query as evidence of a rejection.
  4. In /root/cnpe-api/update.json, write a request for the same App, name, and team with replicas=2 and channel=preview. Apply it with act 4 and save updated.json with capture 4. Do not create a new request but keep the same UID, and check the current generation, updated replica count, available count, Pod ownership relationships, and the actual preview response.
  5. With act 5, perform the experiment that changes only the child Deployment to 3 replicas. This helper keeps the replica count, generation, and UID of the actual patch response. The App request stays at 2. With capture 5, save in reconciled.json the evidence that it returned to 2 in a generation later than that. Do not judge that you ran the experiment by looking only at the final number 2.
  6. With act 6, insert the educational finalizer labhub.io/handoff-hold and request deletion of parcel. Save deleting.json with capture 6. It is a state where a deletionTimestamp exists but the same UID can still be queried. Do not interpret this marker as guaranteeing the survival of all child resources.
  7. With act 7, remove only the one educational hold marker. Do not forcibly remove other finalizers. Save absent.json with capture 7 and check that the list queries for App, Deployment, ReplicaSet, Service, and Pod all succeed and the targets are gone. team-b/sentinel must keep the same UID and an actual healthy HTTP state.
  8. In /root/cnpe-api/diagnose.py, write diagnose(e). Following the diagnostic contract below, it returns a single string. Leave a missing required field, a number or string instead of a bool, an observation failure, and a state contradiction as unknown. This step is an independent coding task that does not recreate the earlier resources.

Step 8 diagnostic contract

The input e must have all six fields observed, accepted, synced, ready, deleting, and absent, and the values are real bools. This function receives a summary of the current state that has already been compared. accepted means the current request object exists.

  1. If there is a type or required field error or observed=False, it is unknown.
  2. If absent=True, it is absent only when accepted, synced, ready, and deleting are all False, otherwise unknown.
  3. If synced=True but accepted=False, or ready=True but synced=False, it is unknown.
  4. If there is no earlier contradiction and deleting=True, it is deleting when accepted=True, otherwise unknown.
  5. For the rest, if ready=True it is ready, next if synced=True it is synced, next if accepted=True it is accepted, and if all are False it is not_accepted.

absent is merely an absence confirmed by a normal query and does not mean a history of successful reclamation of a past request. Reclamation is checked separately in step 7.

Reference

Compare the App→Deployment→ReplicaSet→Pod ownership relationships with the currently responding Pod. Reject records of another object that merely has the same name. The observation files are material for learning the past state of the same lab run and are not a cryptographic remote attestation or an anti-cheating device. Grading from step 2 onward also checks the current Composition, and step 3 also checks the current XRD and permissions. For steps 4 and 5, if it is before deletion, it also checks the current app. After deletion, you need the actual reclamation evidence of step 7 to recheck the past records. Preparing a step does not overwrite existing files or later progress and fills in only the earlier steps it needs. Step 8 does not roll back the cluster state. A single educational finalizer does not preserve all child resources. Separating namespace permissions is not proof of network policy or quota as a whole. Official documentation: Composition · Finalizers.

Create the request and observe the readiness failure

In /root/cnpe-api/initial.json, write apiVersion=platform.labhub.local/v1alpha1, kind=App, metadata.name=parcel, metadata.namespace=team-a, spec.replicas=1, and spec.channel=stable. act 1 applies this request with the developer account. Save accepted.json with capture 1, and check together a request that is Synced=True but Ready=False and the actual HTTP stable response.

Request acceptance is not execution completion. Look separately at the App conditions and the HTTP response of the relevant Pod.

Restore a readiness condition that fits the app

Copy /opt/fixtures/cnpe-crossplane-composition.json to /root/cnpe-api/composition.json. Change only the Ready in spec.pipeline[0].input.resources[0].readinessChecks[0].matchCondition.type to Available. Keep the remaining templates, selectors, image, and permissions. Apply it with act 2 and record the actual readiness in ready.json with capture 2.

Query the condition name the Deployment actually provides. Removing the readiness check by setting it to None is not a recovery.

A real rejection test with a restricted account

Query whether team-a's developer can create its own team's App but cannot create another team's App or a Deployment directly. With act 3, test the two actual permission rejections and the replicas=4 schema rejection. Save boundaries.json with capture 3. Do not use an administrator's success or a failed permission query as evidence of a rejection.

Use --as=system:serviceaccount:team-a:developer with kubectl auth can-i. After the rejection, also confirm from the administrator's list that nothing was created.

Change the same request to a new channel

In /root/cnpe-api/update.json, write a request for the same App, name, and team with replicas=2 and channel=preview. Apply it with act 4 and save updated.json with capture 4. Do not create a new request but keep the same UID, and check the current generation, updated replica count, available count, Pod ownership relationships, and the actual preview response.

Look at metadata.generation and status.observedGeneration together. Old Pods that are terminating may remain.

Why a change to a child resource comes back

With act 5, perform the experiment that changes only the child Deployment to 3 replicas. This helper keeps the replica count, generation, and UID of the actual patch response. The App request stays at 2. With capture 5, save in reconciled.json the evidence that it returned to 2 in a generation later than that. Do not judge that you ran the experiment by looking only at the final number 2.

The source of a change you want to keep is the App. A manual change to a child resource can be overwritten at the next convergence.

Distinguish a deletion request from actual absence

With act 6, insert the educational finalizer labhub.io/handoff-hold and request deletion of parcel. Save deleting.json with capture 6. It is a state where a deletionTimestamp exists but the same UID can still be queried. Do not interpret this marker as guaranteeing the survival of all child resources.

The success of delete --wait=false is the success of the request. Query the finalizer and deletionTimestamp directly.

Confirm that only my resources were reclaimed

With act 7, remove only the one educational hold marker. Do not forcibly remove other finalizers. Save absent.json with capture 7 and check that the list queries for App, Deployment, ReplicaSet, Service, and Pod all succeed and the targets are gone. team-b/sentinel must keep the same UID and an actual healthy HTTP state.

A failed query and an empty list are different. An answer that removes my resources by deleting another team's as well is also a failure.

A diagnostic tool that leaves unknown states as unknown

In /root/cnpe-api/diagnose.py, write diagnose(e). Following the diagnostic contract below, it returns a single string. Leave a missing required field, a number or string instead of a bool, an observation failure, and a state contradiction as unknown. This step is an independent coding task that does not recreate the earlier resources.

First check the types, required fields, and whether it could be observed, and after ruling out contradictions, decide the deleting and ready states.