browsertrix/backend/btrixcloud/swarm/base_job.py
Ilya Kreymer 418c07bf0d
Local swarm + podman support (#261)
* backend: refactor swarm support to also support podman (#260)
- implement podman support as subclass of swarm deployment
- podman is used when 'RUNTIME=podman' env var is set
- podman socket is mapped instead of docker socket
- podman-compose is used instead of docker-compose (though docker-compose works with podman, it does not support secrets, but podman-compose does)
- separate cli utils into SwarmRunner and PodmanRunner which extends it
- using config.yaml and config.env, both copied from sample versions
- work on simplifying config: add docker-compose.podman.yml and docker-compose.swarm.yml and signing and debug configs in ./configs
- add {build,run,stop}-{swarm,podman}.sh in scripts dir
- add init-configs, only copy if configs don't exist
- build local image use current version of podman, to support both podman 3.x and 4.x
- additional fixes for after testing podman on centos
- docs: update Deployment.md to cover swarm, podman, k8s deployment
2022-06-14 00:13:49 -07:00

111 lines
3.3 KiB
Python

""" base k8s job driver """
import os
import asyncio
import signal
import sys
import yaml
from fastapi.templating import Jinja2Templates
from .utils import get_templates_dir, get_runner
from ..utils import random_suffix
runner = get_runner()
# =============================================================================
# pylint: disable=too-many-instance-attributes,bare-except,broad-except
class SwarmJobMixin:
""" Crawl Job State """
def __init__(self):
self.secrets_prefix = "/var/run/secrets/"
self.shared_config_file = os.environ.get("SHARED_JOB_CONFIG")
self.custom_config_file = os.environ.get("CUSTOM_JOB_CONFIG")
self.curr_storage = {}
self.job_id = os.environ.get("JOB_ID")
# in case id is modified below, should be able to delete self
self.orig_job_id = self.job_id
self.remove_schedule = False
self.is_scheduled = os.environ.get("RUN_MANUAL") == "0"
if self.is_scheduled:
self.job_id += "-" + random_suffix()
self.prefix = os.environ.get("STACK_PREFIX", "stack-")
if self.custom_config_file:
self._populate_env(self.secrets_prefix + self.custom_config_file)
self.templates = Jinja2Templates(directory=get_templates_dir())
super().__init__()
def _populate_env(self, filename):
with open(filename, encoding="utf-8") as fh_config:
params = yaml.safe_load(fh_config)
for key in params:
val = params[key]
if isinstance(val, str):
os.environ[key] = val
async def init_job_objects(self, template, extra_params=None):
""" init swarm objects from specified template with given extra_params """
loop = asyncio.get_running_loop()
loop.add_signal_handler(signal.SIGUSR1, self.unschedule_job)
if self.shared_config_file:
with open(
self.secrets_prefix + self.shared_config_file, encoding="utf-8"
) as fh_config:
params = yaml.safe_load(fh_config)
else:
params = {}
params["id"] = self.job_id
if extra_params:
params.update(extra_params)
params["storage_name"] = os.environ.get("STORAGE_NAME", "default")
await self._do_create(loop, template, params)
async def delete_job_objects(self, _):
""" remove swarm service stack """
loop = asyncio.get_running_loop()
await self._do_delete(loop)
if not self.is_scheduled or self.remove_schedule:
print("Removed other objects, removing ourselves", flush=True)
await loop.run_in_executor(
None, runner.delete_service_stack, f"job-{self.orig_job_id}"
)
else:
sys.exit(0)
return True
def unschedule_job(self):
""" mark job as unscheduled """
print("Unscheduled, will delete when finished", flush=True)
self.remove_schedule = True
async def _do_create(self, loop, template, params):
data = self.templates.env.get_template(template).render(params)
return await loop.run_in_executor(
None, runner.run_service_stack, self.prefix + self.job_id, data
)
async def _do_delete(self, loop):
await loop.run_in_executor(
None, runner.delete_service_stack, self.prefix + self.job_id
)