diff --git a/ansible/group_vars/all/stackspin.yml b/ansible/group_vars/all/stackspin.yml index dd8767f1fd784cb33dad0bfceb955244caeb5a3d..fd2f694e032ca62bc91e27a5e12b26c718943f4d 100644 --- a/ansible/group_vars/all/stackspin.yml +++ b/ansible/group_vars/all/stackspin.yml @@ -13,7 +13,7 @@ flux: k3s: # https://github.com/k3s-io/k3s/releases - version: 'v1.23.1+k3s2' + version: 'v1.23.3+k3s1' # args to start the k3s server with # https://rancher.com/docs/k3s/latest/en/installation/install-options/server-config/ # kubelet arguments can be passed with `--kubelet-arg` diff --git a/flux2/apps/nextcloud/nextcloud-values-configmap.yaml b/flux2/apps/nextcloud/nextcloud-values-configmap.yaml index 0fea5d02bb0e6ef2c534681e641912544947a637..ae60f00430ce79570ee3f8449a9d40d1dd759ff8 100644 --- a/flux2/apps/nextcloud/nextcloud-values-configmap.yaml +++ b/flux2/apps/nextcloud/nextcloud-values-configmap.yaml @@ -179,10 +179,10 @@ data: enabled: false resources: limits: - cpu: 750m + cpu: 500m memory: 512Mi requests: - cpu: 300m + cpu: 200m memory: 256Mi livenessProbe: initialDelaySeconds: 180 diff --git a/flux2/apps/wordpress/release.yaml b/flux2/apps/wordpress/release.yaml index 00a784226fd02d4d4bfe331fb1e2e5c3ede67c75..32e737a32e4b4cfa646927abd417bceb43d93cda 100644 --- a/flux2/apps/wordpress/release.yaml +++ b/flux2/apps/wordpress/release.yaml @@ -10,7 +10,7 @@ spec: spec: # renovate: registryUrl=https://open.greenhost.net/api/v4/projects/27/packages/helm/stable chart: wordpress - version: 0.5.3 + version: 0.5.4 sourceRef: kind: HelmRepository name: wordpress-helm diff --git a/flux2/apps/wordpress/wordpress-values-configmap.yaml b/flux2/apps/wordpress/wordpress-values-configmap.yaml index 986cbf0093df78f0318ae9f275a224705d1fac4d..396c20259786e0bd18b946b9f61cbb4fe45cd434 100644 --- a/flux2/apps/wordpress/wordpress-values-configmap.yaml +++ b/flux2/apps/wordpress/wordpress-values-configmap.yaml @@ -84,12 +84,14 @@ data: commonLabels: stackspin.net/backupSet: "wordpress" + # It's advisable to set resource limits to prevent your K8s cluster from + # crashing resources: limits: - cpu: 1000m + cpu: 500m memory: 512Mi requests: - cpu: 250m + cpu: 100m memory: 256Mi ingress: diff --git a/install/generate_secrets.py b/install/generate_secrets.py index 04aed3ff53ed53ddc6b7226068fa0ce780b1bcd2..f1eb31606192fcc0b46867bb66026083a70640bc 100644 --- a/install/generate_secrets.py +++ b/install/generate_secrets.py @@ -1,5 +1,4 @@ -""" -Generates Kubernetes secrets based on a provided app name. +"""Generates Kubernetes secrets based on a provided app name. If the `templates` directory contains a secret called `stackspin-{app}-variables`, it will check if that secret already exists in the cluster, and if not: generate @@ -19,9 +18,9 @@ import string import sys import jinja2 -import jinja2_base64_filters # pylint: disable=unused-import import yaml from kubernetes import client, config +from kubernetes.client import api_client from kubernetes.client.exceptions import ApiException from kubernetes.utils import create_from_yaml from kubernetes.utils.create_from_yaml import FailToCreateError @@ -35,10 +34,12 @@ APPS_WITHOUT_OAUTH = [ "alertmanager", ] + def main(): - """Run everything""" + """Run everything.""" # Add jinja filters we want to use - env = jinja2.Environment(extensions=["jinja2_base64_filters.Base64Filters"]) + env = jinja2.Environment( + extensions=["jinja2_base64_filters.Base64Filters"]) env.filters["generate_password"] = generate_password if len(sys.argv) < 2: @@ -47,99 +48,113 @@ def main(): app_name = sys.argv[1] # Create app variables secret - create_variables_secret(app_name, f"stackspin-{app_name}-variables.yaml.jinja", env) + create_variables_secret( + app_name, f"stackspin-{app_name}-variables.yaml.jinja", env) # Create a secret that contains the oauth variables for Hydra Maester if app_name not in APPS_WITHOUT_OAUTH: - create_variables_secret(app_name, "stackspin-oauth-variables.yaml.jinja", env) + create_variables_secret( + app_name, "stackspin-oauth-variables.yaml.jinja", env) create_basic_auth_secret(app_name, env) def get_templates_dir(): - """Returns directory that contains the Jinja templates used to create app - secrets""" - return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'templates') + """Returns directory that contains the Jinja templates used to create app secrets.""" + return os.path.join(os.path.dirname(os.path.realpath(__file__)), "templates") def create_variables_secret(app_name, variables_filename, env): - """Checks if a variables secret for app_name already exists, generates it if necessary""" - variables_filepath = \ - os.path.join(get_templates_dir(), variables_filename) + """Checks if a variables secret for app_name already exists, generates it if necessary.""" + variables_filepath = os.path.join(get_templates_dir(), variables_filename) if os.path.exists(variables_filepath): # Check if k8s secret already exists, if not, generate it - with open(variables_filepath) as template_file: + with open(variables_filepath, encoding="UTF-8") as template_file: lines = template_file.read() secret_name, secret_namespace = get_secret_metadata(lines) new_secret_dict = yaml.safe_load( - env.from_string( - lines, - globals={"app": app_name} - ).render()) - current_secret_data = get_kubernetes_secret_data(secret_name, - secret_namespace) + env.from_string(lines, globals={"app": app_name}).render() + ) + current_secret_data = get_kubernetes_secret_data( + secret_name, secret_namespace + ) if current_secret_data is None: # Create new secret update_secret = False - elif current_secret_data.keys() != new_secret_dict['data'].keys(): + elif current_secret_data.keys() != new_secret_dict["data"].keys(): # Update current secret with new keys update_secret = True - print(f"Secret {secret_name} in namespace {secret_namespace}" - " already exists. Merging...") + print( + f"Secret {secret_name} in namespace {secret_namespace}" + " already exists. Merging..." + ) # Merge dicts. Values from current_secret_data take precedence - new_secret_dict['data'] |= current_secret_data + new_secret_dict["data"] |= current_secret_data else: # Do Nothing - print(f"Secret {secret_name} in namespace {secret_namespace}" - " is already in a good state, doing nothing.") + print( + f"Secret {secret_name} in namespace {secret_namespace}" + " is already in a good state, doing nothing." + ) return - print(f"Storing secret {secret_name} in namespace" - f" {secret_namespace} in cluster.") - store_kubernetes_secret(new_secret_dict, secret_namespace, - update=update_secret) + print( + f"Storing secret {secret_name} in namespace" + f" {secret_namespace} in cluster." + ) + store_kubernetes_secret( + new_secret_dict, secret_namespace, update=update_secret + ) else: - print(f'Template {variables_filename} does not exist, no action needed') + print( + f"Template {variables_filename} does not exist, no action needed") def create_basic_auth_secret(app_name, env): - """Checks if a basic auth secret for app_name already exists, generates it if necessary""" - basic_auth_filename = \ - os.path.join(get_templates_dir(), f"stackspin-{app_name}-basic-auth.yaml.jinja") + """Checks if a basic auth secret for app_name already exists, generates it if necessary.""" + basic_auth_filename = os.path.join( + get_templates_dir(), f"stackspin-{app_name}-basic-auth.yaml.jinja" + ) if os.path.exists(basic_auth_filename): - with open(basic_auth_filename) as template_file: + with open(basic_auth_filename, encoding="UTF-8") as template_file: lines = template_file.read() secret_name, secret_namespace = get_secret_metadata(lines) if get_kubernetes_secret_data(secret_name, secret_namespace) is None: - basic_auth_username = 'admin' + basic_auth_username = "admin" basic_auth_password = generate_password(32) basic_auth_htpasswd = gen_htpasswd( - basic_auth_username, - basic_auth_password) - print(f"Adding secret {secret_name} in namespace" - f" {secret_namespace} to cluster.") + basic_auth_username, basic_auth_password + ) + print( + f"Adding secret {secret_name} in namespace" + f" {secret_namespace} to cluster." + ) template = env.from_string( - lines, - globals={ - 'pass': basic_auth_password, - 'htpasswd': basic_auth_htpasswd - }) + lines, + globals={ + "pass": basic_auth_password, + "htpasswd": basic_auth_htpasswd, + }, + ) secret_dict = yaml.safe_load(template.render()) store_kubernetes_secret(secret_dict, secret_namespace) else: - print(f"Secret {secret_name} in namespace {secret_namespace}" - " already exists. Not generating new secrets.") + print( + f"Secret {secret_name} in namespace {secret_namespace}" + " already exists. Not generating new secrets." + ) else: - print(f'File {basic_auth_filename} does not exist, no action needed') + print(f"File {basic_auth_filename} does not exist, no action needed") + def get_secret_metadata(yaml_string): - """Returns secret name and namespace from metadata field in a yaml string""" + """Returns secret name and namespace from metadata field in a yaml string.""" secret_dict = yaml.safe_load(yaml_string) - secret_name = secret_dict['metadata']['name'] + secret_name = secret_dict["metadata"]["name"] # default namespace is flux-system, but other namespace can be # provided in secret metadata - if 'namespace' in secret_dict['metadata']: - secret_namespace = secret_dict['metadata']['namespace'] + if "namespace" in secret_dict["metadata"]: + secret_namespace = secret_dict["metadata"]["namespace"] else: - secret_namespace = 'flux-system' + secret_namespace = "flux-system" return secret_name, secret_namespace @@ -154,9 +169,10 @@ def get_kubernetes_secret_data(secret_name, namespace): return None return secret + def store_kubernetes_secret(secret_dict, namespace, update=False): - """Stores either a new secret in the cluster, or updates an existing one""" - api_client = client.api_client.ApiClient() + """Stores either a new secret in the cluster, or updates an existing one.""" + api_client_instance = api_client.ApiClient() if update: verb = "updated" api_response = patch_kubernetes_secret(secret_dict, namespace) @@ -164,37 +180,38 @@ def store_kubernetes_secret(secret_dict, namespace, update=False): verb = "created" try: api_response = create_from_yaml( - api_client, - yaml_objects=[secret_dict], - namespace=namespace) + api_client_instance, + yaml_objects=[secret_dict], + namespace=namespace + ) except FailToCreateError as ex: print(f"Secret not {verb} because of exception {ex}") return - print(f"Secret {verb} with api response: {api_response}") + def patch_kubernetes_secret(secret_dict, namespace): - """Patches secret in the cluster with new data""" - api_client = client.api_client.ApiClient() - api_instance = client.CoreV1Api(api_client) - name = secret_dict['metadata']['name'] + """Patches secret in the cluster with new data.""" + api_client_instance = api_client.ApiClient() + api_instance = client.CoreV1Api(apiclient) + name = secret_dict["metadata"]["name"] body = {} - body['data'] = secret_dict['data'] + body["data"] = secret_dict["data"] return api_instance.patch_namespaced_secret(name, namespace, body) + def generate_password(length): - """Generates a password of "length" characters""" + """Generates a password of "length" characters.""" length = int(length) - password = ''.join((secrets.choice(string.ascii_letters) for i in range(length))) + password = "".join((secrets.choice(string.ascii_letters) + for i in range(length))) return password def gen_htpasswd(user, password): - """generate htpasswd entry for user with password""" - return "{}:{}".format(user, crypt.crypt( - password, crypt.mksalt(crypt.METHOD_SHA512) - ), - ) + """Generate htpasswd entry for user with password.""" + return f"{user}:{crypt.crypt(password, crypt.mksalt(crypt.METHOD_SHA512))}" + if __name__ == "__main__": config.load_kube_config()