docs: grab workload version in k8s tutorial#2559
Conversation
|
While working on this task, I noticed that in Chapter 5, we introduced |
Oops, you're quite right. Well spotted! In the tutorial we say "Then import |
Thanks for the confirmation David. I added the step to run |
dwilding
left a comment
There was a problem hiding this comment.
This is a serious chunk of work - thanks for working through it so thoroughly! It's looking great. I'm sending a first batch of comments and will hold off reviewing further until you've had a chance to think about them.
| 1. The same Juju controller injects Pebble -- a lightweight, API-driven process supervisor -- into each workload container and overrides the container entrypoint so that Pebble starts when the container is ready. | ||
| 1. When the Kubernetes API reports that a workload container is ready, the Juju controller informs the charm that the instance of Pebble in that container is ready. At that point, the charm knows that it can start communicating with Pebble. | ||
| 1. Typically, at this point the charm will make calls to Pebble so that Pebble can configure and start the workload and begin operations. | ||
| 1. Besides Pebble exec, the charm can communicate to the workload using HTTP requests. This is possible because they share the same pod (Kubernetes charms are deployed following sidecar pattern). |
There was a problem hiding this comment.
I think it's good to make this feel more sequential, following on from "begin operations" in the previous item. Also I don't think we need to mention the sidecar pattern, as item 1 essentially explains it.
| 1. Besides Pebble exec, the charm can communicate to the workload using HTTP requests. This is possible because they share the same pod (Kubernetes charms are deployed following sidecar pattern). | |
| 1. During operations, the charm may need to directly communicate with the workload using HTTP requests. This is possible because the charm container and workload container share the same pod. |
|
|
||
| ### Write a helper module | ||
|
|
||
| Your charm will interact with our workload application `api_demo_server`. It’s a good idea to write a helper module that wraps `api_demo_server`. Charmcraft created `src/fastapi_demo.py` as a placeholder helper module. |
There was a problem hiding this comment.
| Your charm will interact with our workload application `api_demo_server`. It’s a good idea to write a helper module that wraps `api_demo_server`. Charmcraft created `src/fastapi_demo.py` as a placeholder helper module. | |
| Your charm will interact with our workload application `api_demo_server`. It's a good idea to write a helper module that wraps `api_demo_server`. Charmcraft created `src/fastapi_demo.py` as a placeholder helper module. |
| The helper module will be independent of the main logic of your charm. This will make it easier to test your charm. In this tutorial, the helper module only contains the logic to get the version of `api_demo_server`. The server has an endpoint at `/version` that returns a JSON payload containing the version number (see {ref}`study-your-application`). | ||
|
|
||
| This is called the workload version. To make things easier for Juju admins, our charm should expose the workload version to Juju. It will be visible in `juju status`. For more information, see {ref}`how-to-set-the-workload-version`. |
There was a problem hiding this comment.
I'm OK to drop the reference link because people typically follow in sequence and there's not much be gained by jumping back at this point.
A couple of other suggestions: It's best to use the term "Juju users" instead of admins (although we still say "admins" in a bunch of places for historical reasons). I recommend changing juju status because we haven't introduced this command yet.
| The helper module will be independent of the main logic of your charm. This will make it easier to test your charm. In this tutorial, the helper module only contains the logic to get the version of `api_demo_server`. The server has an endpoint at `/version` that returns a JSON payload containing the version number (see {ref}`study-your-application`). | |
| This is called the workload version. To make things easier for Juju admins, our charm should expose the workload version to Juju. It will be visible in `juju status`. For more information, see {ref}`how-to-set-the-workload-version`. | |
| The helper module will be independent of the main logic of your charm. This will make it easier to test your charm. In this tutorial, the helper module only contains the logic to get the version of `api_demo_server`. The server has an endpoint at `/version` that returns a JSON payload containing the version number. This is called the workload version. | |
| To make things easier for Juju users, your charm should expose the workload version to Juju. It will be visible in Juju's status output. For more information, see {ref}`how-to-set-the-workload-version`. |
|
|
||
| This is called the workload version. To make things easier for Juju admins, our charm should expose the workload version to Juju. It will be visible in `juju status`. For more information, see {ref}`how-to-set-the-workload-version`. | ||
|
|
||
| We first define a function to get the workload version. Replace the content of `src/fastapi_demo.py` with: |
There was a problem hiding this comment.
I think the first sentence can be dropped because we've already said what what we're going to do (and the code is short and clear enough)
| We first define a function to get the workload version. Replace the content of `src/fastapi_demo.py` with: | |
| Replace the content of `src/fastapi_demo.py` with: |
|
|
||
|
|
||
| def get_version(port: int) -> str: | ||
| """Get the version of fastapi_demo installed. |
There was a problem hiding this comment.
How about this instead?
| """Get the version of fastapi_demo installed. | |
| """Get the version of fastapi_demo that is running. |
| "Failed to get version from the server: %s. Please double check your port config", | ||
| version_e, | ||
| ) | ||
| self.unit.status = ops.BlockedStatus(str(version_e)) |
There was a problem hiding this comment.
I think it's best to use a hardcoded status string here. Blocked status implies that the Juju user should be able to unblock the charm by themself, and ideally the message should give some indication of how.
As I write this, now I'm wondering whether blocked is really right. I'd like to hear other opinions - but here's what I'm thinking:
- If getting the version fails because we can't connect, it might be that the workload container is temporarily unavailable. The charm should go into Maintenance status with an appropriate message. The user needs to wait.
- If we got the version, but we couldn't decode it, there's nothing the user can do. We shouldn't catch the error; we should let the charm go into Error state. (The user needs to bug the charm developer about it.)
There was a problem hiding this comment.
I discussed this a bit with Nhan, and a couple of things:
- Agreed it's better to use a hard-coded message, more like the log message, for example: "Failed to get version from server: check port config"
- I don't think Maintenance status would be good here. That means we the charm is doing some maintenance and we know it's going to come right in a minute. But here it might never come right until they fix the port. So I'd recommend keeping BlockedStatus
- Agreed re decoding being a hard error -- I think we should just omit the JSONDecodeError and let that one bubble up
|
|
||
| When the config is loaded as part of creating the Pebble layer, if the config is invalid (in our case, if the port is set to 22), then a `ValueError` will be raised. The `_replan_workload` method handles that by logging the error and setting the status of the unit to blocked, letting the Juju user know that they need to take action. | ||
|
|
||
| We also add error handling for getting the workload version. If the charm fails to obtain a valid version number, we set the unit status to blocked. This lets the Juju user know that either the charm code or the workload application behaved unexpectedly. |
There was a problem hiding this comment.
This will need updating if the error handling strategy changes (which I think it should)
|
|
||
| ```python | ||
| def test_config_changed(): | ||
| def test_config_changed(mock_version): |
There was a problem hiding this comment.
[Continuing discussion from another comment]
I agree that we need mock_version here. Because _on_config_changed calls _replan_workload, which gets the workload version.
I think it would be worth adding a brief sentence or two immediately after the code block, to explain why mock_version is needed in this instance. Then don't mention it again for the rest of the tutorial.
| And define a method that does the various checks, adding appropriate statuses. The library will take care of selecting the 'most significant' status for you. | ||
|
|
||
| ```python | ||
| def _on_collect_status(self, event: ops.CollectStatusEvent) -> None: |
There was a problem hiding this comment.
Arguably we should get the workload version at the end of this method, as a way of confirming that the workload has started correctly. I'd class that as icing on the cake, not a must-have for this PR.
|
|
||
| ```python | ||
| def test_get_db_info_action(): | ||
| def test_get_db_info_action(mock_version): |
There was a problem hiding this comment.
This test shouldn't need mock_version, because _on_get_db_info_action doesn't end up getting the workload version. But if we change _on_collect_status so that it gets the workload version, then we will need mock_version here.
james-garner-canonical
left a comment
There was a problem hiding this comment.
This is looking good. I agree with David's suggestions.
|
Thanks everyone for the reviews. I really appreciate it ! I will address the feedback |
Closes #2282.
This PR modify the
From zero to hero: Write your first Kubernetes charmtutorial:The PR reviewer can focus on the Markdown docs. We can leave the code changes in
examples/at the end, when we have finalized the tutorial, as the changes inexamples/are plenty and mostly reflect the tutorial.