Merge pull request #141 from sbidoul/add-start-stop-modes

Add start and stop deployment modes
This commit is contained in:
Stéphane Bidoul 2025-11-29 20:23:42 +01:00 committed by GitHub
commit ab984e1da6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 50 additions and 11 deletions

View file

@ -104,15 +104,21 @@ kubernetes cluster, or even a different one, depending on your taste.
All resources to be deployed in kubernetes for a build are in All resources to be deployed in kubernetes for a build are in
[src/runboat/kubefiles](./src/runboat/kubefiles). They are gathered together from a [src/runboat/kubefiles](./src/runboat/kubefiles). They are gathered together from a
`kustomization.yaml` jinja template that leads to three possible resource groups `kustomization.yaml` jinja template that leads to 5 possible resource groups
depending on a `mode` variable in the jinja rendering context: depending on a `mode` variable in the jinja rendering context:
- `deployment` creates a kubernetes deployment with its associated resources (pvc, - `deployment` creates a kubernetes deployment with its associated resources (pvc,
service, ingress, ...); service, ingress, ...).
- `initialization` creates a job that creates the database; - `initialization` creates a job that performs installation and initializes the database;
- `cleanup` creates a job that drops the database; - `start` updates the resources before scaling up the deployment to 1. This can be
useful to scale up other resources that must only be present while the main
deployment is running.
- `stop` updates the resources after initialization and before scaling down the
deployment to 0. This can be useful to scale down other resources that must
only be present while the main deployment is running.
- `cleanup` creates a job that perform cleanup before tearing down all resources.
Besides the three modes, the controller has limited knowledge of what the kubefiles Besides the 5 modes, the controller has limited knowledge of what the kubefiles
actually deploy. It expects the following to hold true: actually deploy. It expects the following to hold true:
- the `runboat/build` label is set on all resources, with the unique build name as - the `runboat/build` label is set on all resources, with the unique build name as

View file

@ -138,6 +138,8 @@ class DeploymentMode(str, Enum):
deployment = "deployment" deployment = "deployment"
initialize = "initialize" initialize = "initialize"
cleanup = "cleanup" cleanup = "cleanup"
start = "start"
stop = "stop"
class DeploymentVars(BaseModel): class DeploymentVars(BaseModel):
@ -260,6 +262,7 @@ async def delete_deployment_resources(build_name: str) -> None:
@sync_to_async @sync_to_async
def kill_job(build_name: str, job_kind: DeploymentMode) -> None: def kill_job(build_name: str, job_kind: DeploymentMode) -> None:
# TODO delete all resources with runboat/build and runboat/job-kind label # TODO delete all resources with runboat/build and runboat/job-kind label
assert job_kind in (DeploymentMode.initialize, DeploymentMode.cleanup)
batchv1 = client.BatchV1Api() batchv1 = client.BatchV1Api()
batchv1.delete_collection_namespaced_job( batchv1.delete_collection_namespaced_job(
namespace=settings.build_namespace, namespace=settings.build_namespace,

View file

@ -195,12 +195,6 @@ class Build(BaseModel):
build_settings, build_settings,
) )
await k8s.deploy(kubefiles_path, deployment_vars) await k8s.deploy(kubefiles_path, deployment_vars)
await github.notify_status(
commit_info.repo,
commit_info.git_commit,
GitHubStatusState.pending,
target_url=None,
)
@classmethod @classmethod
async def deploy(cls, commit_info: CommitInfo) -> None: async def deploy(cls, commit_info: CommitInfo) -> None:
@ -211,6 +205,12 @@ class Build(BaseModel):
await cls._deploy( await cls._deploy(
commit_info, name, slug, job_kind=k8s.DeploymentMode.deployment commit_info, name, slug, job_kind=k8s.DeploymentMode.deployment
) )
await github.notify_status(
commit_info.repo,
commit_info.git_commit,
GitHubStatusState.pending,
target_url=None,
)
async def start(self) -> None: async def start(self) -> None:
"""Start build if init succeeded, or reinitialize if failed.""" """Start build if init succeeded, or reinitialize if failed."""
@ -218,6 +218,12 @@ class Build(BaseModel):
_logger.info(f"Ignoring start command for {self} that is {self.status}.") _logger.info(f"Ignoring start command for {self} that is {self.status}.")
return return
_logger.info(f"Starting {self} that was last scaled on {self.last_scaled}.") _logger.info(f"Starting {self} that was last scaled on {self.last_scaled}.")
await self._deploy(
self.commit_info,
self.name,
self.slug,
job_kind=k8s.DeploymentMode.start,
)
await self._patch(desired_replicas=1) await self._patch(desired_replicas=1)
async def stop(self) -> None: async def stop(self) -> None:
@ -226,6 +232,12 @@ class Build(BaseModel):
return return
_logger.info(f"Stopping {self} that was last scaled on {self.last_scaled}.") _logger.info(f"Stopping {self} that was last scaled on {self.last_scaled}.")
await self._patch(desired_replicas=0) await self._patch(desired_replicas=0)
await self._deploy(
self.commit_info,
self.name,
self.slug,
job_kind=k8s.DeploymentMode.stop,
)
async def undeploy(self) -> None: async def undeploy(self) -> None:
# To undeploy, we delete the deployment. Due to the finalizer, the deletion # To undeploy, we delete the deployment. Due to the finalizer, the deletion
@ -246,6 +258,12 @@ class Build(BaseModel):
self.slug, self.slug,
job_kind=k8s.DeploymentMode.deployment, job_kind=k8s.DeploymentMode.deployment,
) )
await github.notify_status(
self.commit_info.repo,
self.commit_info.git_commit,
GitHubStatusState.pending,
target_url=None,
)
async def initialize(self) -> None: async def initialize(self) -> None:
"""Launch the initialization job.""" """Launch the initialization job."""
@ -300,6 +318,12 @@ class Build(BaseModel):
# is restarting, and is notified of existing initialization jobs. # is restarting, and is notified of existing initialization jobs.
return return
_logger.info(f"Initialization job succeded for {self}, ready to start.") _logger.info(f"Initialization job succeded for {self}, ready to start.")
await self._deploy(
self.commit_info,
self.name,
self.slug,
job_kind=k8s.DeploymentMode.stop,
)
if await self._patch(init_status=BuildInitStatus.succeeded): if await self._patch(init_status=BuildInitStatus.succeeded):
await github.notify_status( await github.notify_status(
self.commit_info.repo, self.commit_info.repo,
@ -314,6 +338,12 @@ class Build(BaseModel):
# restarting, and is notified of existing initialization jobs. # restarting, and is notified of existing initialization jobs.
return return
_logger.info(f"Initialization job failed for {self}.") _logger.info(f"Initialization job failed for {self}.")
await self._deploy(
self.commit_info,
self.name,
self.slug,
job_kind=k8s.DeploymentMode.stop,
)
if await self._patch(init_status=BuildInitStatus.failed, desired_replicas=0): if await self._patch(init_status=BuildInitStatus.failed, desired_replicas=0):
await github.notify_status( await github.notify_status(
self.commit_info.repo, self.commit_info.repo,