diff --git a/package.json b/package.json index 23aa363f4f4..9768df67e65 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,6 @@ "author": "Government Digital Service", "license": "MIT", "dependencies": { - "mermaid": "^11.16.1" + "mermaid": "^11.17.2" } } diff --git a/source/kubernetes/job-requests/architecture/index.html.md b/source/kubernetes/job-requests/architecture/index.html.md index 11013a669a0..0425cd1b782 100644 --- a/source/kubernetes/job-requests/architecture/index.html.md +++ b/source/kubernetes/job-requests/architecture/index.html.md @@ -1,9 +1,278 @@ --- -title: Job Request Architecture +title: JobRequest Architecture weight: 10 layout: multipage_layout --- -# Job Request Architecture +# JobRequest Architecture -Coming soon... +## System Components + +* [govuk-job-request-operator](/repos/govuk-job-request-operator.html): + This includes: + * [Custom Resource Defitinitions + (CRDs)](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/) + for JobRequest (short name `jr`) and JobRequestReview (short name `jrr`) + resources + * [Kubernetes + Operator](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/) + which is responsible for reconcilling the JobRequest and JobRequestReview + resources + * [Kubernetes Mutating Admission + Policy](https://kubernetes.io/docs/reference/access-authn-authz/mutating-admission-policy/) + which is responsible for validating, and adding the AWS ARN of the + requesting user into annotations on the JobRequest and JobRequestReview + during admission + * [Garbage + collection](https://kubernetes.io/docs/concepts/architecture/garbage-collection/) + to clean up JobRequest and JobRequestReview resoureces more than 30 days old +* [OPA + Gatekeeper](https://kubernetes.io/blog/2019/08/06/opa-gatekeeper-policy-and-governance-for-kubernetes/) + [policies](https://github.com/alphagov/govuk-infrastructure/tree/main/terraform/deployments/cluster-services/modules/gatekeeper) + responsible for validating API requests to create/update JobRequest and + JobRequestReview resources, and providing useful error messages to the end user + if the request is denied. +* [govuk-cli](/repos/govuk-job-request-operator.html): A command line interface + with a jobrequest subcommand which is the primary interface developers are + expected to use to create and review JobRequests. This also does validation + prior to making create requests to the Kubernetes API to give the most friendly + error messages possible. + +## Expected usage + +The following describes the intended happy path from creation of a JobRequest to Job completion. + +1. User A uses `govuk-cli` to create a JobRequest, it prints out the CLI command which can be used to review the JobRequest, and waits to show the logs from the Job that will be created later. +2. User A sends User B the printed out CLI command to run in order to review the request. +3. User B runs the CLI command, reads and reviews the command intended to run, chooses to approve or reject it, then `govuk-cli` creates a JobRequestReview. +4. Review outcome: + a. If user B rejects the JobRequest, assuming user A is still following the logs, user A sees a message telling them the request was rejected. This flow ends here. + b. If user B approves the JobRequest then continue this process +5. The JobRequest operator changes the Status of the JobRequest to approved. +6. The JobRequest operator reads the pod spec it needs to create from the target resource specified in the JobRequest +7. The JobRequest operator creates a Kubernetes Job from the pod spec retrieved in the previous step, overriding the command to be the one approved in the JobRequest +8. The JobRequest operator updates the Status of the JobRequest to include the name of the Job that was created +9. The govuk-cli command that was run in step 1. sees the Job has been created, informs user A, and starts printing out the logs of the Job as they are produced. +9. The JobRequest operator watches the status of the Job and updates the JobRequest status to include the current state of the Job. +10. When the status of the Job reaches a terminal state, the govuk-cli stops following the logs and informs User A the Job has completed. + +### Sequence Diagram for an Approved JobRequest + +
+
+
+sequenceDiagram
+    actor Requester
+    actor Reviewer
+    participant k8sAPI as Kubernetes API
+    participant Operator
+
+    Requester->>k8sAPI: `govuk-cli create jobrequest... --follow`
+    k8sAPI->>Operator: Job Request Created
+    Requester->>Reviewer: Please review my request
+    Reviewer->>k8sAPI: `govuk-cli jobrequest review...` Approved
+    k8sAPI->>Operator: JobRequestReview Created
+    Operator->>k8sAPI: Create Job
+    k8sAPI->>Requester: Logs
+    k8sAPI->>Requester: More Logs
+    k8sAPI->>Requester: Job Complete
+
+
+ +### Sequence Diagram for a Rejected JobRequest + +
+
+
+sequenceDiagram
+    actor Requester
+    actor Reviewer
+    participant k8sAPI as Kubernetes API
+    participant Operator
+
+    Requester->>k8sAPI: `govuk-cli create jobrequest... --follow`
+    k8sAPI->>Operator: JobRequest Created
+    Requester->>Reviewer: Please review my request
+    Reviewer->>k8sAPI: `govuk-cli jobrequest review...` Rejected
+    k8sAPI->>Operator: Rejected JobRequestReview Created
+    Operator->>k8sAPI: Update JobRequest to Rejected
+    k8sAPI->>Requester: JobRequest Rejected
+
+
+ +## Garbage Collection + +Any time a JobRequest or JobRequestReview resource is presented for +reconcilliation, if it has lived longer than the TTL duration (which is set +for 720 hours (30 days)), it will be deleted. + +The [Kubernetes Controller Runtime +configuration](https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/cache#Config) +includes a SyncPeriod, any resources managed by the controller runtime will be +presented to the operator every time the `SyncPeriod` has elapsed. + +The SyncPeriod in the [is configurable in the +govuk-job-request-operator](https://github.com/alphagov/govuk-job-request-operator#configuration) +by setting the `--resource-ttl` flag. + +## Architecture Diagrams + +### JobRequest state diagram + +
+
+
+stateDiagram
+    state "''" as NoState
+
+    [*] --> NoState
+    NoState --> Malformed
+    NoState --> Pending
+    Pending --> Rejected
+    Pending --> Approved
+    Approved --> Started
+    Approved --> Malformed
+    Started --> Complete
+    Started --> Failed
+    Started --> Malformed
+    Malformed --> [*]
+    Complete --> [*]
+    Failed --> [*]
+
+
+ +### JobRequestReview state diagram + +
+
+
+stateDiagram
+    [*] --> Approved
+    [*] --> Rejected
+
+    Approved --> JobRequestNotFound
+    Approved --> JobRequestMalformed
+    Approved --> Conflict
+
+    Rejected --> JobRequestNotFound
+    Rejected --> JobRequestMalformed
+    Rejected --> Conflict
+
+    Approved --> [*]
+    Rejected --> [*]
+    JobRequestNotFound --> [*]
+    JobRequestMalformed --> [*]
+    Conflict --> [*]
+
+
+ +### Creating a JobRequest Flowchart + +
+
+
+flowchart TD
+    RequesterA@{ shape: person }
+    RequesterB@{ shape: person }
+    govuk-cli
+
+    RequesterA--govuk-cli jobrequest create ...-->govuk-cli
+    govuk-cli--Create JobRequest-->k8sAPI
+
+    RequesterB--Create JobRequest-->k8sAPI
+
+    subgraph k8s[Kubernetes]
+        direction TD
+
+        k8sAPI[Kubernetes API]
+        MutatingAdmissionPolicy
+        gatekeeper[OPA Gatekeeper]
+        govuk-job-request-operator[Operator JobRequest controller]
+        etcd[(Etcd)]
+
+        k8sAPI-- Create JobRequest -->MutatingAdmissionPolicy
+        MutatingAdmissionPolicy--Create JobRequest-->gatekeeper
+        gatekeeper--Create JobRequest-->etcd
+
+        etcd--Created JobRequest-->govuk-job-request-operator
+
+        govuk-job-request-operator-->validateJobRequest{Validate}
+        validateJobRequest--valid\n\nSet JobRequest Pending-->k8sAPI
+        validateJobRequest--invalid\n\nSet JobRequest Malformed-->k8sAPI
+    end
+
+
+ +### Reviewing a JobRequest Flowchart + +
+
+
+flowchart TD
+    RequesterA@{ shape: person }
+    RequesterB@{ shape: person }
+    govuk-cli
+
+    RequesterA--govuk-cli jobrequest review ...-->govuk-cli
+    govuk-cli--Create JobRequestReview-->k8sAPI
+
+    RequesterB--Create JobRequestReview-->k8sAPI
+
+    subgraph k8s[Kubernetes]
+        direction TD
+
+        k8sAPI[Kubernetes API]
+        MutatingAdmissionPolicy
+        gatekeeper[OPA Gatekeeper]
+        govuk-job-request-operator[Operator JobRequestReview controller]
+        etcd[(Etcd)]
+
+        k8sAPI-- Create JobRequestReview -->MutatingAdmissionPolicy
+        MutatingAdmissionPolicy-- Create JobRequestReview -->gatekeeper
+        gatekeeper-- Create JobRequestReview -->etcd
+
+        etcd-- Created JobRequestReview -->govuk-job-request-operator
+
+        govuk-job-request-operator-->validateJobRequestReview{Validate}
+        validateJobRequestReview-- invalid\n\nSet JobRequestReview Malformed -->k8sAPI
+
+        validateJobRequestReview-- valid -->jobRequestFound{JobRequest Exists?}
+        jobRequestFound-- found\n\nSet state of JobRequestReview to Approved/Rejected\n\nSet state of JobRequest to Approved/Rejected -->k8sAPI
+        jobRequestFound-- not-found\n\nSet state of JobRequestReview toJobRequestNotFound -->k8sAPI
+    end
+
+
+ +### After a JobRequest has been Approved Flowchart + +
+
+
+flowchart TD
+    subgraph k8s[Kubernetes]
+        direction TD
+
+        k8sAPI[Kubernetes API]
+        MutatingAdmissionPolicy
+        gatekeeper[OPA Gatekeeper]
+        govuk-job-request-operator[Operator JobRequest controller]
+        etcd[(Etcd)]
+
+        etcd-- 1. JobRequest Updated -->govuk-job-request-operator
+        govuk-job-request-operator<-- 2. Get Pod/Deployment -->k8sAPI
+
+        govuk-job-request-operator-- 3. Create Job -->k8sAPI
+        k8sAPI-- 3. Create Job -->etcd
+
+        govuk-job-request-operator-- 4. Watch Job -->k8sAPI
+
+        etcd-- 5. Job State Change --> govuk-job-request-operator
+        govuk-job-request-operator-- 5. Update JobRequest with Job State -->k8sAPI
+        k8sAPI-- 5. Update JobRequest -->MutatingAdmissionPolicy
+        MutatingAdmissionPolicy-- 5. Update JobRequest -->gatekeeper
+        gatekeeper-- 5. Update JobRequest -->etcd
+
+    end
+
+
+
diff --git a/source/kubernetes/job-requests/index.html.md b/source/kubernetes/job-requests/index.html.md index a38d913752e..48a15693546 100644 --- a/source/kubernetes/job-requests/index.html.md +++ b/source/kubernetes/job-requests/index.html.md @@ -9,10 +9,33 @@ layout: multipage_layout If you are looking for how to create or review JobRequests, see the [JobRequests User Guide](/kubernetes/job-requests/user-guide/) +If you are looking to understand how the JobRequest system works, see [the +JobRequest architecture guide](/kubernetes/job-requests/architecture). + ## What is the JobRequest system -Coming soon... +The JobRequest system allows for a user to run commands within containers in +Kubernetes in a way which is: + +* Peer reviewed +* Audited +* Has isolated logs not mixed with regular application logs +* Easy to use +* Easy to see the logs for an executed command +* Does not require the user to create and understand kubernetes manifests +* Is garbage collected after a month + +## What is the process for executing a Job using JobRequests + +1. A user creates a JobRequest to run a command in Kubernetes, which will be + run in a Pod that is a copy of an existing Pod or Deployment. +2. A different user reviews that request (either approving or rejecting). +3. If approved a Kubernetes Job will be created which: + * Has its Pod spec copied from the requested Pod/Deployment + * Has the command of the Pod overridden with the command the user requested ## Why might you use it -Coming soon... +* To execute a Rake task in a Kubernetes cluster +* To receive a peer review for a command you are going to run in a Kubernetes cluster +* To have logs produced so you can see what your command did diff --git a/yarn.lock b/yarn.lock index 614346eef49..b02b930ff34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -34,10 +34,10 @@ "@iconify/types" "^2.0.0" mlly "^1.8.0" -"@mermaid-js/parser@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@mermaid-js/parser/-/parser-1.2.0.tgz#266d728c54d2d4034d270f8b31d790e26296a5fa" - integrity sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA== +"@mermaid-js/parser@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@mermaid-js/parser/-/parser-1.2.1.tgz#94cc40416137bb10d2bc1b0a49c623d716e76233" + integrity sha512-n12NohV3mrUyUL2o93IgG/ifeW9FTyeJn3zDxkhwa8MJ9Fxg3HQMlA3RiGmD/3UnJvheztkjjQAjA2T4LmUcpw== dependencies: "@chevrotain/types" "~11.1.2" @@ -322,10 +322,10 @@ cytoscape-fcose@^2.2.0: dependencies: cose-base "^2.2.0" -cytoscape@^3.33.3: - version "3.34.0" - resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.34.0.tgz#5fbe2eb1cf76b070a8ecd5647c35f65aa097c9c6" - integrity sha512-62rNSrioXw93uliKFBwjukeQyeWwH2PqDrTac31r2P6464u3AUvTk0xS4LVvT251g7IgkFunrI48ZEZGjywSOg== +cytoscape@^3.34.0: + version "3.34.2" + resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.34.2.tgz#295edfc876b87bd971de3ac7a9d65c6be18f4ad6" + integrity sha512-Cm2jaj1X/PBNlzV9yH8zcfGOxO7U+CJ/+mxSBVPSchLaugdp4jtlGx5qaHtPRZ6tgiZ5P+o1XoRfJA+ba6KM3g== "d3-array@1 - 2": version "2.12.1" @@ -606,10 +606,10 @@ dagre-d3-es@7.0.14: d3 "^7.9.0" lodash-es "^4.17.21" -dayjs@^1.11.20: - version "1.11.21" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.21.tgz#57f87562e62de76f3c704bd2b8d522fc33068eb2" - integrity sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA== +dayjs@^1.11.21: + version "1.11.23" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.23.tgz#b0a363506dde5f36cf5075e42ebe8115165a8c79" + integrity sha512-QDTCU0M0MxR3hQfnlDJfwekQiaanm1ubOD231u73WBckQ/fsamwRLiE2GBz6D3a/xF1NgfiDLJjXBa1hYOYTtQ== delaunator@5: version "5.0.1" @@ -630,6 +630,13 @@ es-toolkit@^1.45.1: resolved "https://registry.yarnpkg.com/es-toolkit/-/es-toolkit-1.46.1.tgz#38ca27191a98a867fc544b81cf1477a68947fb06" integrity sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ== +fastdom@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastdom/-/fastdom-1.0.12.tgz#ae43d55af017252ae499b2e186511ab97412de39" + integrity sha512-LB+xjSTEbjHE1cWsxu+tN2Xqr1kpi+V9aADI7sVM5ZMaXyYGPHULQMzpJMYqOTULK/73pUkWVzzObFRBkPr+hg== + dependencies: + strictdom "^1.0.1" + hachure-fill@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/hachure-fill/-/hachure-fill-0.5.2.tgz#d19bc4cc8750a5962b47fb1300557a85fcf934cc" @@ -652,7 +659,7 @@ internmap@^1.0.0: resolved "https://registry.yarnpkg.com/internmap/-/internmap-1.0.1.tgz#0017cc8a3b99605f0302f2b198d272e015e5df95" integrity sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw== -katex@^0.16.45: +katex@^0.16.47: version "0.16.47" resolved "https://registry.yarnpkg.com/katex/-/katex-0.16.47.tgz#0a13a42c2deb4f74e61f162d440b9165a548030f" integrity sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg== @@ -684,26 +691,27 @@ marked@^16.3.0: resolved "https://registry.yarnpkg.com/marked/-/marked-16.4.2.tgz#4959a64be6c486f0db7467ead7ce288de54290a3" integrity sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA== -mermaid@^11.16.1: - version "11.16.1" - resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-11.16.1.tgz#57ae2342f6c45b967113b04c9258430bdd057ee8" - integrity sha512-TQsq6u22fAn3rek5VOubrhKPo1g5hwC3FXUN9hiyupTckcYiGuuKGkNQrKYwGJkXUxZdojwRG46gsSCFZMDp4g== +mermaid@^11.17.2: + version "11.17.2" + resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-11.17.2.tgz#e3caf3717582c0e44e5d04cff043e025caec7cba" + integrity sha512-V6K3C8EBdEsPFZXSKMJe6ppQOENxuHARr9GvHX4hh47lAbhMRD9qf4oEK7LoaRQxULMa80/qt5gHO73aCleBBg== dependencies: "@braintree/sanitize-url" "^7.1.2" "@iconify/utils" "^3.0.2" - "@mermaid-js/parser" "^1.2.0" + "@mermaid-js/parser" "^1.2.1" "@types/d3" "^7.4.3" "@upsetjs/venn.js" "^2.0.0" - cytoscape "^3.33.3" + cytoscape "^3.34.0" cytoscape-cose-bilkent "^4.1.0" cytoscape-fcose "^2.2.0" d3 "^7.9.0" d3-sankey "^0.12.3" dagre-d3-es "7.0.14" - dayjs "^1.11.20" + dayjs "^1.11.21" dompurify "^3.3.3" es-toolkit "^1.45.1" - katex "^0.16.45" + fastdom "1.0.12" + katex "^0.16.47" khroma "^2.1.0" marked "^16.3.0" roughjs "^4.6.6" @@ -793,6 +801,11 @@ rw@1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +strictdom@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strictdom/-/strictdom-1.0.1.tgz#189de91649f73d44d59b8432efa68ef9d2659460" + integrity sha512-cEmp9QeXXRmjj/rVp9oyiqcvyocWab/HaoN4+bwFeZ7QzykJD6L3yD4v12K1x0tHpqRqVpJevN3gW7kyM39Bqg== + stylis@^4.3.6: version "4.3.6" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.6.tgz#7c7b97191cb4f195f03ecab7d52f7902ed378320"