{"id":22138487,"url":"https://github.com/wabscale/petal-interview","last_synced_at":"2026-05-02T15:34:38.591Z","repository":{"id":100129399,"uuid":"394794529","full_name":"wabscale/petal-interview","owner":"wabscale","description":null,"archived":false,"fork":false,"pushed_at":"2021-08-11T03:37:22.000Z","size":260,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-29T15:50:48.701Z","etag":null,"topics":["flask","interview","interview-test","python"],"latest_commit_sha":null,"homepage":"https://petal-interview.johncunniff.dev/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wabscale.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-08-10T22:20:15.000Z","updated_at":"2023-03-04T02:19:57.000Z","dependencies_parsed_at":"2023-03-10T08:15:32.807Z","dependency_job_id":null,"html_url":"https://github.com/wabscale/petal-interview","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wabscale%2Fpetal-interview","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wabscale%2Fpetal-interview/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wabscale%2Fpetal-interview/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wabscale%2Fpetal-interview/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wabscale","download_url":"https://codeload.github.com/wabscale/petal-interview/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245251364,"owners_count":20584866,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["flask","interview","interview-test","python"],"created_at":"2024-12-01T20:10:05.899Z","updated_at":"2026-05-02T15:34:38.557Z","avatar_url":"https://github.com/wabscale.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Petal Interview\n\n## Overview\n\nThe purpose of this assignment is to provide a simple API that reverses and capitalizes strings provided.\nThere is the added requirements that api.shoutcloud.io be used for capitalizing the string, and that the\ndeployment is automated (with CD/CI).\n\nThere are a bunch of things in the Makefile for things like debugging, provisioning, deploying and testing.\n\n```text\nAvailable make targets:\nmake debug              # Debug application (run app.py locally on port 5000)\nmake test               # Run application test (have the app running in another window)\nmake build-docker       # Build docker image\nmake debug-docker       # Debug docker image by running it locally\nmake debug-docker       # Debug docker image by running it locally\nmake lint               # Lint app.py with black\nmake requirements.txt   # pip-compile requirements.txt dependencies\nmake venv               # Generate a virtual environment for local debugging\nmake provision-k8s      # Provision the initial cluster with traefik and metric-server\nmake deploy             # Manually deploy things to the k8s cluster\nmake clean              # Clean local debug environment of artifacts\n\n```\n\n## Try it out\n\n```shell\ncurl -XPOST --data '{\"data\": \"test\"}' -H 'Content-Type: application/json'  https://petal-interview.johncunniff.dev/\n```\n\n## Local Debugging \u0026 Testing\n\nTo debug this system locally I have a few targets in the makefile to get you up and started quickly.\nAssuming you have virtualenv installed, you can get the app started quickly by running the debug make \ntarget with:\n\n```shell\n# Quickly get the app up and running in debug mode\nmake debug\n```\n\nThat will create a virtualenv for you and start the api on [https://localhost:5000/](https://localhost:5000/).\nYou can then run the tests against your running api with the test target:\n\n```shell\n# Run the tests against the app running on port 5000\nmake test\n```\n\nThe tests cover both making sure that good requests work, and that broken requests do not.\n\n## The deployment\n\nI decided to go with what I know which is kubernetes. This project is probably the perfect example\nof an unnecessary k8s deployment, but here we are.\n\nThe k8s cluster is hosted on digital ocean using their managed kubernetes, and container registry.\n\nThe deployment itself is relatively simple. I use traefik with a load balancer for the ingress,\nand have a k8s deployment for the api.\n\n[![](https://mermaid.ink/img/eyJjb2RlIjoiZ3JhcGggTFJcbiAgICB1c2VyW1VzZXJdIC0tPiB8dXNlciByZXF1ZXN0fCBsYihMb2FkIEJhbGFuY2VyKVxuICAgIGxiIC0uLT4gdHJhZWZpazFbVHJhZWZpa11cbiAgICBsYiAtLi0-IHRyYWVmaWsyW1RyYWVmaWtdXG5cbiAgICBzdWJncmFwaCBrOHMgY2x1c3RlclxuICAgIHRyYWVmaWsxIC0tPiBhcGkxW0FQSTFdICYgYXBpMltBUEkyXVxuICAgIHN1YmdyYXBoIG5vZGUxXG4gICAgICAgIHRyYWVmaWsxXG4gICAgICAgIGFwaTFcbiAgICAgICAgYXBpMlxuICAgIGVuZFxuICAgIHRyYWVmaWsyIC0tPiBhcGkzW0FQSTNdXG4gICAgc3ViZ3JhcGggbm9kZTJcbiAgICAgICAgdHJhZWZpazJcbiAgICAgICAgYXBpM1xuICAgIGVuZFxuICAgIGVuZFxuICAiLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlLCJhdXRvU3luYyI6dHJ1ZSwidXBkYXRlRGlhZ3JhbSI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/edit##eyJjb2RlIjoiZ3JhcGggTFJcbiAgICB1c2VyW1VzZXJdIC0tPiB8dXNlciByZXF1ZXN0fCBsYihMb2FkIEJhbGFuY2VyKVxuICAgIGxiIC0uLT4gdHJhZWZpazFbVHJhZWZpa11cbiAgICBsYiAtLi0-IHRyYWVmaWsyW1RyYWVmaWtdXG5cbiAgICBzdWJncmFwaCBrOHMgY2x1c3RlclxuICAgIHRyYWVmaWsxIC0tPiBhcGkxW0FQSTFdICYgYXBpMltBUEkyXVxuICAgIHN1YmdyYXBoIG5vZGUxXG4gICAgICAgIHRyYWVmaWsxXG4gICAgICAgIGFwaTFcbiAgICAgICAgYXBpMlxuICAgIGVuZFxuXG4gICAgdHJhZWZpazIgLS0-IGFwaTNbQVBJM11cbiAgICBzdWJncmFwaCBub2RlMlxuICAgICAgICB0cmFlZmlrMlxuICAgICAgICBhcGkzXG4gICAgZW5kXG4gICAgZW5kXG4gICIsIm1lcm1haWQiOiJ7XG4gIFwidGhlbWVcIjogXCJkZWZhdWx0XCJcbn0iLCJ1cGRhdGVFZGl0b3IiOmZhbHNlLCJhdXRvU3luYyI6dHJ1ZSwidXBkYXRlRGlhZ3JhbSI6ZmFsc2V9)\n\n## The CD/CI\n\nGithub actions seemed to be the reasonable choice for this situation.\n\n![alt petal-github-actions](./img/petal-github-actions.png)\n\n[The actions workflow is very simple](.github/workflows/test-build-deploy.yaml). \nThere are test, build and deploy steps. They are run sequentially so that\nif one step fails, the others will not be run. This ensures that things are only deployed when the tests\npass.\n\n#### Test step\n\nIn the test step, I start the webserver in the background and run the tests file using pytest.\n\n#### Build step\n\nIn the build step, I log into the digital ocean container registry, build the image and push it off.\n\n#### Deploy step\n\nIn the deploy step, I download and configure kubectl using a digital ocean action, then apply and rollout\na new deployment of the api.\n\n## Reliability \u0026 Scaling\n\nThere are a few things in this deployment that increase its reliability.\n\nOn the kubernetes side of things I have:\n- [rolling updates to reduce service degradation while deploying](k8s-deployment.yaml#L22)\n- [Horizontal Pod Autoscalers to respond to automatically add new api pods under load](k8s-deployment.yaml#L149)\n- [startup and readiness probes on api pods to ensure that requests are only routed to healthy pods](k8s-deployment.yaml#L67) \n\nAlong with these I have the [traefik ingress configured to retry failed requests a certain number of \ntimes before failing](k8s-deployment.yaml#L114). The retry ensures that one off failed requests to shoutcloud do not cause a \nfailed request.\n\nI also use flask-caching as a super simple [cache in between the api and shoutcloud](app.py#L64). The caching ensures\nthat quick sequential requests are cached. It also removes some reliance on shoutcloud.\n\nIn digital ocean I also have the node pool for the kubernetes cluster set to scale up \nif there are resource pressure conditions. The resource pressure conditions may be things like\nrunning out of memory or cpu.\n\n![alt digital-ocean-nodes](./img/petal-k8s-nodes.png)\n \n\n## More to be done...\n\nThere are several things that could be done to improve this deployment. \n\n#### The cache\nThe cache could be greatly improved by moving from a non-persistent per pod filesystem cache \nto something with more global state like redis. flask-caching supports this backend, so really \nI would just need to put a redis on the cluster, and point the cache at it.\n\n#### Helm chart\nRaw k8s is only good up to a certain point. Putting the k8s yaml into a helm chart would be the\nnext thing I would want to do. Helm allows for the yaml to be templated, and configurable through a\nvalues.yaml file. A chart would then make this project \"helm installable\", and therefore much more \nportable in the event that the system needed to be installed somewhere else.\n\n#### Documentation Server / Generation\nIt would be good to provide clients of the api some kind of website that highlights the endpoints\nprovided. I would probably want to use something like flask-swagger as I have used it in the past.\nThat would make the API much more user friendly.\n\n#### Monitoring\nIf this was something important, I would add something like status cake to send me notifications when\nthings went down.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwabscale%2Fpetal-interview","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwabscale%2Fpetal-interview","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwabscale%2Fpetal-interview/lists"}