-
Notifications
You must be signed in to change notification settings - Fork 184
feat(operator): gate readiness on installation completion #3464
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
base: main
Are you sure you want to change the base?
Changes from all commits
1777729
7c18bd1
03329f5
7778ddc
0b288c7
6b03bca
e6a621b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,15 @@ | ||
| The App Platform Operator has been successfully deployed on the cluster. | ||
|
|
||
| Please inspect the output of the apl-operator deployment (apl-operator/{{ include "apl-operator.fullname" . }}) for any feedback or errors. | ||
| {{ if ne ((.Values.operator.readiness | default dict).gateOnInstallationComplete) false }} | ||
| Installing the platform takes 10-15 minutes. The operator reports Ready only once | ||
| installation has completed, so you can wait for it: | ||
|
|
||
| kubectl wait --for=condition=Available deployment/{{ include "apl-operator.fullname" . }} -n apl-operator --timeout=30m | ||
|
|
||
| Progress is readable at any time from: | ||
|
|
||
| kubectl get cm apl-installation-status -n apl-operator -o jsonpath='{.data.status}' | ||
| {{ end }} | ||
|
|
||
| Also visit https://techdocs.akamai.com/app-platform/ for further instructions and reference documentation. | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,8 @@ | ||||||
| {{- $kms := .Values.kms | default dict }} | ||||||
| {{- $version := .Values.otomi.version | default .Chart.AppVersion }} | ||||||
| {{- $skipDeployment := .Values.installation.skipOperatorDeployment }} | ||||||
| {{- $readiness := .Values.operator.readiness | default dict }} | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is already a default in the values.yaml
Suggested change
|
||||||
| {{- $gateOnInstall := ne $readiness.gateOnInstallationComplete false }} | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This flag is not necessary. If we have the marker in code which will always run why not always use it? |
||||||
| {{- if not $skipDeployment }} | ||||||
| apiVersion: apps/v1 | ||||||
| kind: Deployment | ||||||
|
|
@@ -10,6 +12,11 @@ metadata: | |||||
| labels: {{- include "apl-operator.labels" . | nindent 4 }} | ||||||
| spec: | ||||||
| replicas: 1 | ||||||
| # Installing the platform takes considerably longer than the 600s default, and | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment is cluttering. One line on why we increased the limit would be better. |
||||||
| # with the readinessProbe gated on convergence the rollout stays Progressing | ||||||
| # for that whole window. Without this, `kubectl rollout status` reports | ||||||
| # ProgressDeadlineExceeded on a perfectly healthy first install. | ||||||
| progressDeadlineSeconds: {{ $readiness.progressDeadlineSeconds | default 3600 }} | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is already a default in the values.yaml
Suggested change
|
||||||
| selector: | ||||||
| matchLabels: {{- include "apl-operator.selectorLabels" . | nindent 6 }} | ||||||
| strategy: | ||||||
|
|
@@ -90,11 +97,25 @@ spec: | |||||
| failureThreshold: 3 | ||||||
| readinessProbe: | ||||||
| exec: | ||||||
| {{- if $gateOnInstall }} | ||||||
| # Convergence signal: /tmp/ready is written by the operator once the | ||||||
| # platform installation reaches the 'completed' state, so the | ||||||
| # Deployment only goes Available when the helmfile pipeline has | ||||||
| # actually converged. This is what makes `helm install --wait` and | ||||||
| # `kubectl wait --for=condition=Available` usable as bootstrap gates. | ||||||
| # NOTE: a first install takes 10-15 minutes β size --timeout accordingly. | ||||||
| command: ["/bin/sh", "-c", "test -f /tmp/ready"] | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lets remove the if else block and keep this command. Additionally we can remove the comment. It's pretty clear what it does. |
||||||
| {{- else }} | ||||||
| # Liveness-equivalent readiness: reports Ready as soon as the operator | ||||||
| # process is up, long before the platform has converged. | ||||||
| command: ["/bin/sh", "-c", "pgrep -f 'apl-operator' > /dev/null"] | ||||||
| {{- end }} | ||||||
| initialDelaySeconds: 30 | ||||||
| periodSeconds: 10 | ||||||
| timeoutSeconds: 5 | ||||||
| failureThreshold: 3 | ||||||
| # Readiness latches once installation completes; a transient probe | ||||||
| # failure should not take the Deployment out of Available. | ||||||
| failureThreshold: {{ $readiness.failureThreshold | default 3 }} | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was already set to 3. Why does it need to be configurable? |
||||||
| volumes: | ||||||
| - name: values-secret | ||||||
| secret: | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -129,6 +129,20 @@ operator: | |
| installRetries: 1000 | ||
| installMaxTimeoutMs: 10000 | ||
|
|
||
| readiness: | ||
| # When true (default), the operator only reports Ready once the platform | ||
| # installation has completed, making the apl-operator Deployment a truthful | ||
| # "platform converged" signal for `helm install --wait` and `kubectl wait`. | ||
| # A first install takes 10-15 minutes, so size `helm --timeout` accordingly | ||
| # (e.g. `--wait --timeout 30m`). | ||
| # Set to false to restore the previous process-liveness readiness, which | ||
| # reports Ready within a minute regardless of installation progress. | ||
| gateOnInstallationComplete: true | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This flag can be removed with the comment |
||
| # Rollout budget for the install window. Only relevant when the readiness | ||
| # gate is on β `kubectl rollout status` fails once it is exceeded. | ||
| progressDeadlineSeconds: 3600 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's set this to 30 minutes 1800 seconds. If the operator did not install within 30 minutes something is off. |
||
| failureThreshold: 3 | ||
|
|
||
| image: | ||
| repository: "mirror.registry.linodelke.net/docker/linode/apl-core" | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -73,6 +73,22 @@ spec: | |
| periodSeconds: 60 | ||
| failureThreshold: 3 | ||
| timeoutSeconds: 10 | ||
| {{- if ne (.Values.operator.readiness | default dict).gateOnInstallationComplete false }} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If block can be removed same goes for the comment. |
||
| # /tmp/ready is written by the operator once the platform installation | ||
| # reaches the 'completed' state β the Deployment reports Available only | ||
| # after the helmfile pipeline has converged, not merely once the process | ||
| # is up. Readiness latches, so steady-state reconciles do not flap it. | ||
| readinessProbe: | ||
| exec: | ||
| command: | ||
| - /bin/sh | ||
| - -c | ||
| - "test -f /tmp/ready" | ||
| initialDelaySeconds: 30 | ||
| periodSeconds: 10 | ||
| failureThreshold: 3 | ||
| timeoutSeconds: 5 | ||
| {{- end }} | ||
| resources: | ||
| {{- toYaml .Values.resources | nindent 12 }} | ||
| volumeMounts: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,6 +60,10 @@ operator: | |
| gitOpTimeoutMs: 10000 | ||
| installRetries: 1000 | ||
| installMaxTimeoutMs: 10000 | ||
| readiness: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be removed when we remove the flag |
||
| # When true (default), the operator reports Ready only after the platform | ||
| # installation has completed. Set to false to drop the readinessProbe. | ||
| gateOnInstallationComplete: true | ||
|
|
||
| nodeSelector: {} | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -473,6 +473,71 @@ Shared by both loops with trigger-specific variations: | |
| 8. Update apply state to 'succeeded' or 'failed' | ||
| 9. Release lock (`isApplying = false`) | ||
|
|
||
| ## Readiness and Convergence Contract | ||
|
|
||
| Bootstrap automation needs a machine-checkable answer to "is the platform installed | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not entirely true. When apl-operator is done installing the platform is not completly ready. We still have ArgoCD that is applying stuff. It only means that the operator is finished and started is reconcile loop. |
||
| yet?". The operator exposes it through the readiness of its own Deployment. | ||
|
|
||
| ### The gate | ||
|
|
||
| The operator writes `/tmp/ready` (`markInstallationComplete()`) at exactly one point: | ||
| after the installation phase resolves to `completed` β whether that came from a fresh | ||
| install, a recovery install, or a restart on an already-installed cluster. The | ||
| `readinessProbe` on the apl-operator Deployment tests for that file, so: | ||
|
|
||
| ```bash | ||
| # blocks until the helmfile pipeline has actually converged | ||
| kubectl wait --for=condition=Available deployment/apl-operator -n apl-operator --timeout=30m | ||
|
|
||
| # same signal, via helm | ||
| helm install apl β¦ --wait --timeout 30m | ||
| ``` | ||
|
|
||
| Three properties are deliberate: | ||
|
|
||
| - **It latches.** Readiness is never cleared while a later apply runs. The reconcile | ||
| loop applies every ~5 minutes in steady state; flipping the Deployment out of | ||
| `Available` on each pass would make the condition useless as a gate. Per-apply | ||
| status is reported through the `apl-operator-state` ConfigMap instead (below). | ||
| - **It fails closed.** If the marker cannot be written, or installation keeps | ||
| retrying, the pod stays NotReady. The signal never claims a convergence that did | ||
| not happen β `--wait` times out loudly rather than returning early. | ||
| - **A first install takes 10-15 minutes.** Size `--timeout` accordingly; the | ||
| Deployment's `progressDeadlineSeconds` is raised to 3600 so `kubectl rollout | ||
| status` does not report `ProgressDeadlineExceeded` on a healthy install. | ||
|
|
||
| Set `operator.readiness.gateOnInstallationComplete=false` to restore the previous | ||
| behaviour, where readiness only reflected that the operator process was running. | ||
|
|
||
| ### Introspection | ||
|
|
||
| For phase detail rather than a binary gate, read the ConfigMaps in the table below: | ||
|
|
||
| ```bash | ||
| # installation phase: pending | in-progress | completed | failed (+ attempt, timestamp) | ||
| kubectl get cm apl-installation-status -n apl-operator -o jsonpath='{.data.status}' | ||
|
|
||
| # last apply: commitHash, status, timestamp, trigger, errorMessage | ||
| kubectl get cm apl-operator-state -n apl-operator -o jsonpath='{.data.state}' | ||
| ``` | ||
|
|
||
| `apl-operator-state.commitHash` is the answer to "did the operator apply *my* commit | ||
| yet?" β poll for `status: succeeded` at the revision you pushed. | ||
|
|
||
| ### What this is not | ||
|
|
||
| The Deployment gate covers the operator's own pipeline: essential manifests, CRDs, | ||
| `stage=prep`, `app=core`, and the ArgoCD Applications for the remaining apps. Apps | ||
| that ArgoCD syncs afterwards report health through ArgoCD, not through this gate. | ||
|
|
||
| An end-to-end smoke check that the platform is externally serving is | ||
| `https://auth.<domainSuffix>/ready` (oauth2-proxy behind the ingress). It exercises | ||
| DNS, ingress-nginx, the TLS certificate and the auth chain, which the in-cluster | ||
| gate does not. It is complementary, not a substitute: it needs public DNS and a | ||
| trusted certificate, it cannot tell you *which* revision of your values converged, | ||
| and a non-200 cannot distinguish "platform not ready" from a DNS or certificate | ||
| problem. | ||
|
|
||
| ## Kubernetes Resources | ||
|
|
||
| ### ConfigMaps | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -39,6 +39,40 @@ export function updateHeartbeatFile(): void { | |||||
| writeFileSync('/tmp/heartbeat', '') | ||||||
| } | ||||||
|
|
||||||
| /** | ||||||
| * Marker file that signals platform installation has completed. The operator | ||||||
| * readinessProbe gates on its existence, so the apl-operator Deployment only | ||||||
| * becomes Available once the helmfile pipeline has actually converged β not | ||||||
| * merely once the operator process is up. That makes `helm install --wait` and | ||||||
| * `kubectl wait --for=condition=Available deployment/apl-operator` meaningful | ||||||
| * gates for bootstrap automation. | ||||||
| * | ||||||
| * The marker lives on the pod's /tmp emptyDir, so it is cleared on every | ||||||
| * restart and re-created as soon as the operator re-confirms the installation | ||||||
| * status from the apl-installation-status ConfigMap. | ||||||
| */ | ||||||
| export const READINESS_FILE = '/tmp/ready' | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The name speaks for itself, we can remove the above comment |
||||||
|
|
||||||
| /** | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment can be removed if we rename the function. Then from the function name it is clear what this function does. That is the clean code we want. |
||||||
| * Writes the readiness marker. Called once installation has reached the | ||||||
| * 'completed' state β including on restarts of an already-installed cluster. | ||||||
| * Readiness latches: it is intentionally NOT cleared while a subsequent apply | ||||||
| * runs, because steady-state reconcile loops must not flap the Deployment's | ||||||
| * Available condition. Per-apply status lives in the apl-operator-state | ||||||
| * ConfigMap instead. | ||||||
| */ | ||||||
| export function markInstallationComplete(filePath: string = READINESS_FILE): void { | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would rename this function so it states what it actually does:
Suggested change
|
||||||
| const d = terminal('operator:k8s:markInstallationComplete') | ||||||
| try { | ||||||
| writeFileSync(filePath, new Date().toISOString()) | ||||||
| d.info(`Installation complete, wrote readiness marker ${filePath}`) | ||||||
| } catch (error) { | ||||||
| // Deliberately non-fatal: a missing marker keeps the pod NotReady, which is | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment can be removed. It does not tell much. |
||||||
| // the safe direction β it never reports convergence that did not happen. | ||||||
| d.warn(`Failed to write readiness marker ${filePath}:`, getErrorMessage(error)) | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| export async function updateApplyState( | ||||||
| state: ApplyState, | ||||||
| namespace: string = APL_OPERATOR_NS, | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If block can be removed