I use saltstack to manage deployment of docker containers in my homelab. For two of my cloud servers, I am running them in memory constrained environments for cost reasons, and typically only run the minimum required there.
Since I wanted to wrap the docker_container
module, I started by creating my own state in _states/docker_customized. I can do a simple naive version, just by using setdefault
# _states/docker_customized.py
def running(name, **kwargs):
kwargs.setdefault("mem_limit", "1024M")
kwargs.setdefault("oom_score_adj", 100)
return __states__["docker_container.running"](name, **kwargs)
If I then do a find replace on s/docker_container.running/docker_customized.running/g, then when I use state.apply on a target, it will call my method, update the values I want, before calling the original with the updated parameters (I may also need to update any watch or requires entires as needed).
I can then take this further, and do more specific logic, like basing the limit on the server grains.
def running(name, **kwargs):
if "mem_limit" not in kwargs:
# if for whatever reason the grain is missing, we'll default to 1G
total_mem = __grains__.get("mem_total", 1024)
# Then we want to ensure our container limits to only 10% of total memory or a max of 1G
default_limit = min(int(total_mem * 0.10), 1024)
kwargs["mem_limit"] = f"{default_limit}M"
# I just set a default value here without extra logic
kwargs.setdefault("oom_score_adj", 100)
# Call the parent state with our updated values
return __states__["docker_container.running"](name, **kwargs)
I could also use this to validate the data before calling it, if I did some kind of check and returned a state dictionary on failure.
if not some_condition:
return {
"name": name,
"changes": {},
"result": False,
"comment": "Failed some condition check"
}
With this new change, hopefully I can keep the host system more responsive, even if a container misbehaves. Now that I know how easy it is to wrap some of these states, there may be some more I handle in the same way in the future.