├── .envrc ├── .flake8 ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── README.md ├── apps ├── argo_events.py ├── argo_workflows.py ├── argocd.py ├── cert_manager.py ├── cilium.py ├── contour.py ├── coredns.py ├── fission.py ├── gvisor.py ├── harbor.py ├── keda.py ├── keycloak.py ├── kubevirt.py ├── metallb.py ├── metrics_server.py ├── ocf_io.py ├── postgres_operator.py ├── prometheus.py ├── rabbitmq.py ├── rbac.py ├── rook.py ├── snapshot_controller.py ├── teleport.py ├── vault.py ├── vault_secrets_operator.py ├── velero.py └── versions.toml ├── bootstrap.sh ├── cluster.toml ├── flake.lock └── flake.nix /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | extend-ignore = E203,E501 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: ["https://ocf.io/donate"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | transpire-ci: 10 | uses: ocf/transpire-ci/.github/workflows/trigger.yml@master 11 | secrets: 12 | TRANSPIRE_CI_PAT: ${{ secrets.TRANSPIRE_CI_PAT }} 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | .direnv/ 3 | 4 | # Python stuff 5 | __pycache__ 6 | *.pyc 7 | # Coverage 8 | .coverage 9 | .coverage.* 10 | coverage.xml 11 | htmlcov/ 12 | # VSCode lol 13 | .vscode 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ocf/kubernetes 2 | 3 | This is the git repository for the [Open Computing Facility](https://ocf.berkeley.edu/)'s [Kubernetes](https://k8s.io/) cluster. The repository is a [transpire](https://github.com/ocf/transpire) toplevel repository built through GitHub Actions CI and pushed to [ocf/cluster](https://github.com/ocf/cluster), which is then deployed by ArgoCD. The goal is for every non-dynamic Kubernetes object to be generated by a transpire module. 4 | 5 | ## Deploying Software 6 | 7 | 1. Create a new Python file in `apps/` with a function `objects()` that yields Kubernetes objects (dicts or `python-kubernetes` objects are both accepted). See the other Python files in that folder for examples. Helper functions are provided for Helm charts, although not all software will use helm charts. 8 | a. If you need to build your own container image, do so in another git repository. Instructions in [our internal docs](https://docs.ocf.berkeley.edu/doc/deploying-services-to-kubernetes-NrroInWHyc) (going public soon). 9 | 2. (Root Required) Go to [ArgoCD](https://argo.ocf.berkeley.edu/) and run a sync. Before you click sync, look at the diff to sanity check what will change. We do not automatically sync configuration for safety reasons. 10 | 11 | ### Code-Test Loop 12 | 13 | Install the latest version of [transpire](https://github.com/ocf/transpire). `transpire` is alpha-level software, so you may need to track the `main` branch. There are a few ways to install it (pip, nix), but the quickest way to get started is `pip install git+https://github.com/ocf/transpire.git@main`. 14 | 15 | Then, make changes and use some subset of the following commands to test your changes... 16 | 17 | ```bash 18 | # print all YAML objects to stdout (may want to pipe to less, bat, etc.) 19 | transpire object print $APP_NAME 20 | 21 | # apply it to the current cluster your kubectl points to 22 | transpire object apply $APP_NAME 23 | ``` 24 | 25 | ## Folder Structure 26 | 27 | Our deployment follows the following directory structure... 28 | 29 | ``` 30 | - apps 31 | - cilium 32 | - notes 33 | - ... (any software) 34 | - versions.toml (all software versions) 35 | - cluster.toml (transpire configuration) 36 | ``` 37 | 38 | ## Bootstrapping 39 | 40 | A bootstrap script is provided to help bring up new clusters when needed. It installs the bare minimum required to run ArgoCD, provided Kubernetes is already running without a CNI, and `KUBECONFIG` is pointed at the right place. 41 | 42 | ```bash 43 | ./bootstrap.sh 44 | ``` 45 | 46 | ## Contributing 47 | 48 | The [Open Computing Facility](https://ocf.berkeley.edu/) (OCF) is an all-volunteer student organization dedicated to free computing for all University of California, Berkeley students, faculty, and staff. Students from all backgrounds are encouraged to [join as staff](https://ocf.io/getinvolved)! If you're not a student, or just want to make a one time contribution, please use the standard GitHub pull request workflow. Thanks for helping out :) 49 | 50 | -------------------------------------------------------------------------------- /apps/argo_events.py: -------------------------------------------------------------------------------- 1 | from typing import Generator 2 | 3 | from transpire import helm 4 | from transpire.utils import get_versions 5 | 6 | name = "argo-events" 7 | 8 | 9 | def objects() -> Generator[dict, None, None]: 10 | yield from helm.build_chart_from_versions( 11 | name="argo-events", 12 | versions=get_versions(__file__), 13 | values={ 14 | "controller": { 15 | "metrics": { 16 | "enabled": True, 17 | "serviceMonitor": {"enabled": True} 18 | }, 19 | }, 20 | }, 21 | ) 22 | -------------------------------------------------------------------------------- /apps/argo_workflows.py: -------------------------------------------------------------------------------- 1 | from typing import Generator 2 | 3 | from transpire import helm 4 | from transpire.utils import get_versions 5 | 6 | name = "argo-workflows" 7 | 8 | 9 | def objects() -> Generator[dict, None, None]: 10 | # TODO: Create argo-events namespace. 11 | 12 | yield from helm.build_chart_from_versions( 13 | name="argo-workflows", 14 | versions=get_versions(__file__), 15 | values={ 16 | "controller": { 17 | "serviceMonitor": { 18 | "enabled": True, 19 | }, 20 | "metricsConfig":{ 21 | "enabled": True, 22 | }, 23 | }, 24 | }, 25 | ) 26 | -------------------------------------------------------------------------------- /apps/argocd.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import yaml 4 | from transpire import helm 5 | from transpire.utils import get_versions 6 | 7 | name = "argocd" 8 | 9 | 10 | def objects(): 11 | yield from helm.build_chart_from_versions( 12 | name="argocd", 13 | versions=get_versions(__file__), 14 | values={ 15 | "redis-ha": {"enabled": True}, 16 | "global": { 17 | "domain": "argo.ocf.berkeley.edu", 18 | }, 19 | "controller": { 20 | "replicas": 1, 21 | "metrics": { 22 | "enabled": True, 23 | "serviceMonitor": { 24 | "enabled": True, 25 | }, 26 | }, 27 | }, 28 | "server": { 29 | "replicas": 2, 30 | "ingress": { 31 | "enabled": True, 32 | "ingressClassName": "contour", 33 | "hosts": ["argo.ocf.berkeley.edu"], 34 | "annotations": { 35 | "cert-manager.io/cluster-issuer": "letsencrypt", 36 | "projectcontour.io/websocket-routes": "/", 37 | "ingress.kubernetes.io/force-ssl-redirect": "true", 38 | "kubernetes.io/tls-acme": "true", 39 | }, 40 | "tls": [ 41 | { 42 | "secretName": "argocd-server-tls", 43 | "hosts": ["argo.ocf.berkeley.edu"], 44 | }, 45 | ], 46 | }, 47 | }, 48 | "repoServer": {"replicas": 2}, 49 | "applicationSet": {"replicaCount": 2}, 50 | "configs": { 51 | "cm": { 52 | "url": "https://argo.ocf.berkeley.edu", 53 | "oidc.config": yaml.dump( 54 | { 55 | "name": "Keycloak", 56 | "issuer": "https://idm.ocf.berkeley.edu/realms/ocf", 57 | "clientID": "argocd", 58 | "clientSecret": "$oidc.keycloak.clientSecret", 59 | "requestedScopes": [ 60 | "openid", 61 | "profile", 62 | "email", 63 | "groups", 64 | ], 65 | } 66 | ), 67 | "resource.customizations.ignoreDifferences.admissionregistration.k8s.io_MutatingWebhookConfiguration": json.dumps( 68 | { 69 | "jqPathExpressions": [ 70 | ".webhooks[]?.clientConfig.caBundle", 71 | ] 72 | } 73 | ), 74 | "resource.customizations.ignoreDifferences.apiextensions.k8s.io_CustomResourceDefinition": json.dumps( 75 | { 76 | "jqPathExpressions": [ 77 | ".spec.conversion.webhook.clientConfig.caBundle", 78 | ] 79 | } 80 | ), 81 | }, 82 | "params": { 83 | "server.insecure": True, 84 | }, 85 | "repositories": { 86 | "cluster": { 87 | "url": "https://github.com/ocf/cluster", 88 | }, 89 | }, 90 | "rbac": {"policy.csv": "g, ocfroot, role:admin"}, 91 | }, 92 | }, 93 | ) 94 | 95 | yield { 96 | "apiVersion": "argoproj.io/v1alpha1", 97 | "kind": "Application", 98 | "metadata": {"name": "bootstrap", "namespace": "argocd"}, 99 | "spec": { 100 | "project": "default", 101 | "destination": {"server": "https://kubernetes.default.svc"}, 102 | "source": { 103 | "repoURL": "https://github.com/ocf/cluster", 104 | "path": "base", 105 | }, 106 | }, 107 | } 108 | -------------------------------------------------------------------------------- /apps/cert_manager.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.resources import Secret 3 | from transpire.utils import get_versions 4 | 5 | name = "cert-manager" 6 | 7 | 8 | def objects(): 9 | # Secret to allow cert-manager to create DNS01 entries in BIND. 10 | tsig_secret = "ocf-tsig" 11 | tsig_key = "key" 12 | yield Secret( 13 | name="ocf-tsig", 14 | string_data={ 15 | tsig_key: "", 16 | }, 17 | ).build() 18 | 19 | def make_le_issuer(name: str, endpoint: str) -> dict: 20 | return { 21 | "apiVersion": "cert-manager.io/v1", 22 | "kind": "ClusterIssuer", 23 | "metadata": { 24 | "name": name, 25 | }, 26 | "spec": { 27 | "acme": { 28 | "email": "root@ocf.berkeley.edu", 29 | "server": endpoint, 30 | "privateKeySecretRef": { 31 | "name": name, 32 | }, 33 | "solvers": [ 34 | { 35 | "dns01": { 36 | "cnameStrategy": "Follow", 37 | "rfc2136": { 38 | "nameserver": "169.229.226.22", 39 | "tsigAlgorithm": "HMACSHA512", 40 | "tsigKeyName": "letsencrypt.ocf.io", 41 | "tsigSecretSecretRef": { 42 | "key": tsig_key, 43 | "name": tsig_secret, 44 | }, 45 | }, 46 | } 47 | } 48 | ], 49 | } 50 | }, 51 | } 52 | 53 | yield from helm.build_chart_from_versions( 54 | name="cert-manager", 55 | versions=get_versions(__file__), 56 | values={ 57 | "installCRDs": True, 58 | "prometheus": { 59 | "enabled": True, 60 | "servicemonitor": { 61 | "enabled": True, 62 | }, 63 | }, 64 | }, 65 | ) 66 | 67 | yield make_le_issuer( 68 | "letsencrypt", "https://acme-v02.api.letsencrypt.org/directory" 69 | ) 70 | 71 | yield make_le_issuer( 72 | "letsencrypt-staging", "https://acme-staging-v02.api.letsencrypt.org/directory" 73 | ) 74 | -------------------------------------------------------------------------------- /apps/cilium.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.utils import get_versions 3 | 4 | name = "cilium" 5 | 6 | 7 | def objects(): 8 | yield from helm.build_chart_from_versions( 9 | name="cilium", 10 | versions=get_versions(__file__), 11 | values={ 12 | "kubeProxyReplacement": "strict", 13 | "k8sServiceHost": "dna.ocf.io", 14 | "k8sServicePort": "6443", 15 | "hubble": { 16 | "tls": {"auto": {"method": "cronJob"}}, 17 | "listenAddress": ":4244", 18 | "relay": {"enabled": True}, 19 | "ui": {"enabled": True}, 20 | }, 21 | "ipam": { 22 | "mode": "kubernetes", 23 | "requireIPv4PodCIDR": True, 24 | "requireIPv6PodCIDR": True, 25 | }, 26 | "bpf": { 27 | "masquerade": True, 28 | }, 29 | "routingMode": "native", 30 | "autoDirectNodeRoutes": True, 31 | "loadBalancer": { 32 | "mode": "hybrid", 33 | "acceleration": "native", 34 | }, 35 | "endpointRoutes": { 36 | "enabled": True, 37 | }, 38 | "ipv4NativeRoutingCIDR": "10.244.0.0/16", 39 | "ipv6NativeRoutingCIDR": "2607:f140:8801:1::/112", 40 | "ipv6": { 41 | "enabled": True, 42 | }, 43 | "ipv4": { 44 | "enabled": True, 45 | }, 46 | "k8s": { 47 | "requireIPv6PodCIDR": True, 48 | "requireIPv4PodCIDR": True, 49 | }, 50 | }, 51 | ) 52 | -------------------------------------------------------------------------------- /apps/contour.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.utils import get_versions 3 | 4 | name = "contour" 5 | 6 | 7 | def objects(): 8 | yield from helm.build_chart_from_versions( 9 | name=name, 10 | versions=get_versions(__file__), 11 | values={ 12 | "envoy": { 13 | "service": { 14 | "annotations": { 15 | "metallb.universe.tf/loadBalancerIPs": "169.229.226.81,2607:f140:8801::1:81", 16 | }, 17 | "ipFamilyPolicy": "PreferDualStack", 18 | }, 19 | "resourcesPreset": "large", # otherwise it OOMs 20 | }, 21 | "metrics": { 22 | "serviceMonitor": { 23 | "enabled": True, 24 | } 25 | }, 26 | }, 27 | ) 28 | -------------------------------------------------------------------------------- /apps/coredns.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.utils import get_versions 3 | 4 | values = { 5 | "serviceAccount": {"create": True}, 6 | # TODO: This is a hardcoded variable from the kubelet configuration. 7 | # Consider automatically grabbing this value. 8 | "service": { 9 | "clusterIP": "10.96.0.10", 10 | }, 11 | "prometheus":{ 12 | "service": { 13 | "enabled": True, 14 | }, 15 | "monitor": { 16 | "enabled": True, 17 | }, 18 | }, 19 | } 20 | 21 | name = "coredns" 22 | namespace = "kube-system" 23 | 24 | 25 | def objects(): 26 | yield from helm.build_chart_from_versions( 27 | name="coredns", 28 | versions=get_versions(__file__), 29 | values=values, 30 | ) 31 | -------------------------------------------------------------------------------- /apps/fission.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.resources import Secret 3 | from transpire.surgery import edit_manifests, make_edit_manifest 4 | from transpire.utils import get_versions 5 | 6 | name = "fission" 7 | 8 | 9 | def objects(): 10 | # TODO: The helm chart doesn't come with CRDs. 11 | # kubectl create -k "github.com/fission/fission/crds/v1?ref=v1.17.0" 12 | 13 | s3_secret = "storage-s3-key" 14 | yield Secret( 15 | name=s3_secret, 16 | string_data={ 17 | "STORAGE_S3_ACCESS_KEY_ID": "", 18 | "STORAGE_S3_SECRET_ACCESS_KEY": "", 19 | }, 20 | ).build() 21 | 22 | def inject_secrets(storagesvc: dict) -> dict: 23 | envs = storagesvc["spec"]["template"]["spec"]["containers"][0]["env"] 24 | storagesvc["spec"]["template"]["spec"]["containers"][0]["env"] = list( 25 | filter( 26 | lambda x: not ( 27 | x["name"] == "STORAGE_S3_ACCESS_KEY_ID" 28 | or x["name"] == "STORAGE_S3_SECRET_ACCESS_KEY" 29 | ), 30 | envs, 31 | ) 32 | ) 33 | storagesvc["spec"]["template"]["spec"]["containers"][0]["env"].extend( 34 | [ 35 | { 36 | "name": "STORAGE_S3_ACCESS_KEY_ID", 37 | "valueFrom": { 38 | "secretKeyRef": { 39 | "name": s3_secret, 40 | "key": "STORAGE_S3_ACCESS_KEY_ID", 41 | } 42 | }, 43 | }, 44 | { 45 | "name": "STORAGE_S3_SECRET_ACCESS_KEY", 46 | "valueFrom": { 47 | "secretKeyRef": { 48 | "name": s3_secret, 49 | "key": "STORAGE_S3_SECRET_ACCESS_KEY", 50 | } 51 | }, 52 | }, 53 | ] 54 | ) 55 | return storagesvc 56 | 57 | yield from edit_manifests( 58 | manifests=helm.build_chart_from_versions( 59 | name=name, 60 | versions=get_versions(__file__), 61 | values={ 62 | "routerServiceType": "ClusterIP", 63 | "persistence": { 64 | "enabled": True, 65 | "storageType": "s3", 66 | "s3": { 67 | "bucketName": "ocf-fission", 68 | # These get injected into the Deployment directly, do not use! 69 | # "accessKeyId": "replaced-in-vault", 70 | # "secretAccessKey": "replaced-in-vault", 71 | "endPoint": "https://o3.ocf.berkeley.edu", 72 | }, 73 | }, 74 | # TODO: Configure OpenTelemetry... 75 | # "openTelemetry": {}, 76 | "serviceMonitor": {"enabled": True}, 77 | "podMonitor": {"enabled": True}, 78 | # RabbitMQ Connector 79 | "mqt_keda": {"enabled": True}, 80 | # I have no idea how this works, but neat. 81 | "grafana": { 82 | "namespace": "prometheus", 83 | "dashboards": {"enable": True}, 84 | }, 85 | }, 86 | ), 87 | edits={ 88 | ("Deployment", "storagesvc"): inject_secrets, 89 | }, 90 | ) 91 | -------------------------------------------------------------------------------- /apps/gvisor.py: -------------------------------------------------------------------------------- 1 | name = "gvisor" 2 | 3 | 4 | def objects(): 5 | yield { 6 | "apiVersion": "node.k8s.io/v1", 7 | "kind": "RuntimeClass", 8 | "metadata": {"name": "gvisor"}, 9 | "handler": "runsc", 10 | } 11 | 12 | # Test pod 13 | yield { 14 | "apiVersion": "v1", 15 | "kind": "Pod", 16 | "metadata": {"name": "nginx-gvisor"}, 17 | "spec": { 18 | "runtimeClassName": "gvisor", 19 | "containers": [{"name": "nginx", "image": "docker.io/nginx"}], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /apps/harbor.py: -------------------------------------------------------------------------------- 1 | import secrets 2 | 3 | from transpire import helm, surgery 4 | from transpire.utils import get_versions 5 | 6 | name = "harbor" 7 | 8 | harbor_registry_password = secrets.token_urlsafe(16) 9 | values = { 10 | "exposureType": "ingress", 11 | "ingress": { 12 | "core": { 13 | "ingressClassName": "contour", 14 | "hostname": "harbor.ocf.berkeley.edu", 15 | "annotations": { 16 | "cert-manager.io/cluster-issuer": "letsencrypt", 17 | "kubernetes.io/tls-acme": "true", 18 | }, 19 | "tls": True, 20 | # Default is ImplementationSpecific, which does regex match for Cilium. 21 | # Regex match does not work because it takes the path as a literal regex, 22 | # and the regex `/foo` does not match `/`. 23 | "pathType": "Prefix", 24 | }, 25 | "notary": { 26 | "ingressClassName": "contour", 27 | "hostname": "harbor-notary.ocf.berkeley.edu", 28 | "annotations": { 29 | "cert-manager.io/cluster-issuer": "letsencrypt", 30 | "kubernetes.io/tls-acme": "true", 31 | }, 32 | "tls": True, 33 | # See above. 34 | "pathType": "Prefix", 35 | }, 36 | }, 37 | "externalURL": "https://harbor.ocf.berkeley.edu", 38 | "forcePassword": True, 39 | # This helm chart has default passwords like "not-secure-database-password" and "registry_password" so... 40 | # I am pretty sure most harbor instances in the wild are vulnerable as a result, which is great and fantastic. 41 | "harborAdminPassword": secrets.token_urlsafe(24), 42 | # "core": {"secretKey": "aaaaaaaaaaaaaaaa", "secret": "aaaaaaaaaaaaaaaa"}, 43 | "registry": { 44 | "credentials": { 45 | "username": "harbor_registry_user", 46 | "password": harbor_registry_password, 47 | # TODO: This doesn't work, you need to bcrypt.hashpw(password=b'password', salt=bcrypt.gensalt()) 48 | # ... but we can't depend on bcrypt because it's not in stdlib, so it has to be added to transpire! 49 | "htpasswd": f"harbor_registry_user:{harbor_registry_password}", 50 | }, 51 | }, 52 | "postgresql": {"auth": {"postgresPassword": secrets.token_urlsafe(24)}}, 53 | "metrics": { 54 | "enabled": True, 55 | "serviceMonitor": { 56 | "enabled": True, 57 | }, 58 | }, 59 | "persistence": { 60 | "persistentVolumeClaim": { 61 | "registry": { 62 | "size": "256Gi", 63 | } 64 | } 65 | }, 66 | } 67 | 68 | 69 | def strip_secret_checksum(m): 70 | """ 71 | Bludgeon all checksum annotations for secrets since they are managed in 72 | vault, and the chart autogenerates certificates which change each run 73 | """ 74 | # spec.template.metadata.annotations['checksum/secret*'] 75 | annotations = surgery.delve(m, ("spec", "template", "metadata", "annotations")) 76 | if annotations is not None: 77 | for key, value in list(annotations.items()): 78 | if key.startswith("checksum/secret"): 79 | del annotations[key] 80 | return m 81 | 82 | 83 | def objects(): 84 | yield from [ 85 | strip_secret_checksum(m) 86 | for m in helm.build_chart_from_versions( 87 | name="harbor", 88 | versions=get_versions(__file__), 89 | values=values, 90 | ) 91 | ] 92 | -------------------------------------------------------------------------------- /apps/keda.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.utils import get_versions 3 | 4 | name = "keda" 5 | 6 | 7 | def objects(): 8 | yield from helm.build_chart_from_versions( 9 | name=name, 10 | versions=get_versions(__file__), 11 | values={ 12 | "prometheus": { 13 | "metricServer": { 14 | "enabled": True, 15 | "podMonitor": {"enabled": True}, 16 | }, 17 | "operator": { 18 | "enabled": True, 19 | "podMonitor": {"enabled": True}, 20 | }, 21 | }, 22 | }, 23 | ) 24 | -------------------------------------------------------------------------------- /apps/keycloak.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from transpire import helm 4 | from transpire.resources import ConfigMap 5 | from transpire.utils import get_versions 6 | 7 | name = "keycloak" 8 | 9 | 10 | def oidc_client( 11 | client_id: str, 12 | *, 13 | name: str, 14 | description: str, 15 | home_url: str, 16 | redirect_uris: list[str], 17 | extra: dict[str, str] = {}, 18 | ): 19 | return { 20 | "clientId": client_id, 21 | "name": name, 22 | "description": description, 23 | "baseUrl": home_url, 24 | "clientAuthenticatorType": "client-secret", 25 | "redirectUris": redirect_uris, 26 | "webOrigins": ["+"], 27 | "publicClient": False, 28 | "protocol": "openid-connect", 29 | "enabled": True, 30 | **extra, 31 | } 32 | 33 | 34 | clients = [ 35 | oidc_client( 36 | "rt", 37 | name="rt", 38 | description="Request Ticket tracker", 39 | home_url="https://rt.ocf.berkeley.edu", 40 | redirect_uris=["https://rt.ocf.berkeley.edu/oauth2callback"] 41 | ), 42 | oidc_client( 43 | "argocd", 44 | name="ArgoCD", 45 | description="Declarative GitOps CD for Kubernetes", 46 | home_url="https://argo.ocf.berkeley.edu/", 47 | redirect_uris=["https://argo.ocf.berkeley.edu/auth/callback"], 48 | ), 49 | oidc_client( 50 | "hedgedoc", 51 | name="HedgeDoc", 52 | description="Collaborative markdown notes", 53 | home_url="https://notes.ocf.berkeley.edu/", 54 | redirect_uris=["https://notes.ocf.berkeley.edu/auth/oauth2/callback"], 55 | ), 56 | oidc_client( 57 | "outline", 58 | name="Outline", 59 | description="Team knowledge base & wiki", 60 | home_url="https://docs.ocf.berkeley.edu/", 61 | redirect_uris=["https://docs.ocf.berkeley.edu/auth/oidc.callback"], 62 | ), 63 | oidc_client( 64 | "id6", 65 | name="id6", 66 | description="Identification linking engine", 67 | home_url="https://github.com/ocf/id6", 68 | redirect_uris=["https://discord.ocf.berkeley.edu/*"], 69 | extra={ 70 | "protocolMappers": [ 71 | { 72 | "name": "audience", 73 | "protocol": "openid-connect", 74 | "protocolMapper": "oidc-audience-mapper", 75 | "config": { 76 | "included.client.audience": "id6", 77 | "id.token.claim": "false", 78 | "access.token.claim": "true", 79 | }, 80 | } 81 | ] 82 | }, 83 | ), 84 | oidc_client( 85 | "macwiitl", 86 | name="MacWiitl", 87 | description="WIITL Laptop Extension", 88 | home_url="https://macwiitl.ocf.berkeley.edu/", 89 | redirect_uris=["https://macwiitl.ocf.berkeley.edu/oauth2-callback"], 90 | ), 91 | oidc_client( 92 | "jenkins", 93 | name="Jenkins", 94 | description="Open source automation server", 95 | home_url="https://jenkins.ocf.berkeley.edu/", 96 | redirect_uris=["https://jenkins.ocf.berkeley.edu/securityRealm/finishLogin"], 97 | ), 98 | oidc_client( 99 | "ergo", 100 | name="Ergo", 101 | description="IRC server", 102 | home_url="https://irc.ocf.berkeley.edu/", 103 | redirect_uris=["https://irc.ocf.berkeley.edu/*"], 104 | extra={ 105 | "implicitFlowEnabled": True, 106 | }, 107 | ), 108 | ] 109 | 110 | keycloak_config_cli = { 111 | "id": "ocf", 112 | "realm": "ocf", 113 | "displayName": "OCF", 114 | "sslRequired": "all", 115 | "enabled": True, 116 | "ssoSessionIdleTimeout": 2592000, 117 | "ssoSessionMaxLifespan": 2592000, 118 | "offlineSessionIdleTimeout": 2592000, 119 | "registrationAllowed": False, 120 | "loginWithEmailAllowed": False, 121 | "loginTheme": "keywind", 122 | "accountTheme": "keycloak.v2", 123 | "adminTheme": "keycloak.v2", 124 | "emailTheme": "keycloak", 125 | "roles": { 126 | "realm": [ 127 | {"name": "ocfstaff", "composite": False, "clientRole": False}, 128 | {"name": "ocfroot", "composite": False, "clientRole": False}, 129 | {"name": "opstaff", "composite": False, "clientRole": False}, 130 | ] 131 | }, 132 | "groups": [ 133 | {"name": "ocf"}, 134 | {"name": "ocfalumni"}, 135 | {"name": "ocfapphost"}, 136 | {"name": "ocfhpc"}, 137 | {"name": "ocfofficers"}, 138 | { 139 | "name": "ocfroot", 140 | "realmRoles": ["ocfroot"], 141 | "clientRoles": { 142 | "realm-management": [ 143 | "manage-clients", 144 | "manage-events", 145 | "manage-identity-providers", 146 | "manage-realm", 147 | "manage-users", 148 | "manage-authorization", 149 | "realm-admin", 150 | ] 151 | }, 152 | }, 153 | {"name": "ocfstaff", "realmRoles": ["ocfstaff"]}, 154 | {"name": "opstaff", "realmRoles": ["opstaff"]}, 155 | {"name": "sorry"}, 156 | ], 157 | "clients": clients, 158 | "clientScopes": [ 159 | { 160 | "name": "groups", 161 | "protocol": "openid-connect", 162 | "attributes": { 163 | "include.in.token.scope": "true", 164 | "display.on.consent.screen": "true", 165 | }, 166 | "protocolMappers": [ 167 | { 168 | "name": "groups", 169 | "protocol": "openid-connect", 170 | "protocolMapper": "oidc-group-membership-mapper", 171 | "consentRequired": False, 172 | "config": { 173 | "full.path": "false", 174 | "id.token.claim": "true", 175 | "access.token.claim": "true", 176 | "claim.name": "groups", 177 | "userinfo.token.claim": "true", 178 | }, 179 | } 180 | ], 181 | } 182 | ], 183 | "defaultDefaultClientScopes": [ 184 | "acr", 185 | "email", 186 | "profile", 187 | "roles", 188 | "web-origins", 189 | "groups", 190 | ], 191 | "components": { 192 | "org.keycloak.storage.UserStorageProvider": [ 193 | { 194 | "name": "ldap", 195 | "providerId": "ldap", 196 | "config": { 197 | "enabled": ["true"], 198 | "vendor": ["other"], 199 | "connectionUrl": ["ldaps://ldap.ocf.berkeley.edu"], 200 | "useTruststoreSpi": ["ldapsOnly"], 201 | "connectionPooling": ["true"], 202 | "authType": ["none"], 203 | "editMode": ["READ_ONLY"], 204 | "usersDn": ["ou=People,dc=OCF,dc=Berkeley,dc=EDU"], 205 | "usernameLDAPAttribute": ["uid"], 206 | "rdnLDAPAttribute": ["uid"], 207 | "uuidLDAPAttribute": ["uid"], 208 | "userObjectClasses": ["ocfAccount,account,posixAccount"], 209 | "customUserSearchFilter": [ 210 | "(!(loginShell=/opt/share/utils/bin/sorried))" 211 | ], 212 | "searchScope": ["1"], 213 | "pagination": ["true"], 214 | "importEnabled": ["true"], 215 | "batchSizeForSync": ["1000"], 216 | "fullSyncPeriod": ["604800"], 217 | "changedSyncPeriod": ["86400"], 218 | "allowKerberosAuthentication": ["true"], 219 | "kerberosRealm": ["OCF.BERKELEY.EDU"], 220 | "serverPrincipal": ["HTTP/lb-81.ocf.berkeley.edu@OCF.BERKELEY.EDU"], 221 | "keyTab": ["/vault/secrets/keytab"], 222 | "debug": ["true"], 223 | "useKerberosForPasswordAuthentication": ["true"], 224 | "cachePolicy": ["DEFAULT"], 225 | "syncRegistrations": ["false"], 226 | "validatePasswordPolicy": ["false"], 227 | "trustEmail": ["true"], 228 | "priority": ["0"], 229 | }, 230 | "subComponents": { 231 | "org.keycloak.storage.ldap.mappers.LDAPStorageMapper": [ 232 | { 233 | "name": "username", 234 | "providerId": "user-attribute-ldap-mapper", 235 | "subComponents": {}, 236 | "config": { 237 | "ldap.attribute": ["uid"], 238 | "is.mandatory.in.ldap": ["true"], 239 | "always.read.value.from.ldap": ["false"], 240 | "read.only": ["true"], 241 | "user.model.attribute": ["username"], 242 | }, 243 | }, 244 | { 245 | "name": "email", 246 | "providerId": "user-attribute-ldap-mapper", 247 | "subComponents": {}, 248 | "config": { 249 | "ldap.attribute": ["ocfEmail"], 250 | "is.mandatory.in.ldap": ["true"], 251 | "is.binary.attribute": ["false"], 252 | "always.read.value.from.ldap": ["false"], 253 | "read.only": ["true"], 254 | "user.model.attribute": ["email"], 255 | }, 256 | }, 257 | { 258 | "name": "first name", 259 | "providerId": "user-attribute-ldap-mapper", 260 | "subComponents": {}, 261 | "config": { 262 | "ldap.attribute": ["cn"], 263 | "is.mandatory.in.ldap": ["true"], 264 | "read.only": ["true"], 265 | "always.read.value.from.ldap": ["true"], 266 | "user.model.attribute": ["firstName"], 267 | }, 268 | }, 269 | { 270 | "name": "creation date", 271 | "providerId": "user-attribute-ldap-mapper", 272 | "subComponents": {}, 273 | "config": { 274 | "ldap.attribute": ["createTimestamp"], 275 | "is.mandatory.in.ldap": ["false"], 276 | "read.only": ["true"], 277 | "always.read.value.from.ldap": ["true"], 278 | "user.model.attribute": ["createTimestamp"], 279 | }, 280 | }, 281 | { 282 | "name": "modify date", 283 | "providerId": "user-attribute-ldap-mapper", 284 | "subComponents": {}, 285 | "config": { 286 | "ldap.attribute": ["modifyTimestamp"], 287 | "is.mandatory.in.ldap": ["false"], 288 | "read.only": ["true"], 289 | "always.read.value.from.ldap": ["true"], 290 | "user.model.attribute": ["modifyTimestamp"], 291 | }, 292 | }, 293 | { 294 | "name": "group", 295 | "providerId": "group-ldap-mapper", 296 | "subComponents": {}, 297 | "config": { 298 | "mode": ["READ_ONLY"], 299 | "membership.attribute.type": ["UID"], 300 | "user.roles.retrieve.strategy": [ 301 | "LOAD_GROUPS_BY_MEMBER_ATTRIBUTE" 302 | ], 303 | "group.name.ldap.attribute": ["cn"], 304 | "preserve.group.inheritance": ["false"], 305 | "ignore.missing.groups": ["false"], 306 | "membership.user.ldap.attribute": ["uid"], 307 | "membership.ldap.attribute": ["memberUid"], 308 | "group.object.classes": ["posixGroup"], 309 | "groups.dn": ["ou=Group,dc=ocf,dc=Berkeley,dc=EDU"], 310 | "memberof.ldap.attribute": ["memberOf"], 311 | "drop.non.existing.groups.during.sync": ["false"], 312 | }, 313 | }, 314 | ] 315 | }, 316 | } 317 | ], 318 | }, 319 | } 320 | 321 | 322 | krb5_conf = """ 323 | [libdefaults] 324 | default_realm = OCF.BERKELEY.EDU 325 | 326 | # The following krb5.conf variables are only for MIT Kerberos. 327 | krb4_config = /etc/krb.conf 328 | krb4_realms = /etc/krb.realms 329 | kdc_timesync = 1 330 | ccache_type = 4 331 | forwardable = true 332 | proxiable = true 333 | 334 | # The following libdefaults parameters are only for Heimdal Kerberos. 335 | v4_instance_resolve = false 336 | v4_name_convert = { 337 | host = { 338 | rcmd = host 339 | ftp = ftp 340 | } 341 | plain = { 342 | something = something-else 343 | } 344 | } 345 | fcc-mit-ticketflags = true 346 | 347 | [realms] 348 | OCF.BERKELEY.EDU = { 349 | kdc = kerberos.ocf.berkeley.edu 350 | admin_server = kerberos.ocf.berkeley.edu 351 | } 352 | 353 | [domain_realm] 354 | .ocf.berkeley.edu = OCF.BERKELEY.EDU 355 | ocf.berkeley.edu = OCF.BERKELEY.EDU 356 | 357 | [login] 358 | krb4_convert = true 359 | krb4_get_tickets = false 360 | """ 361 | 362 | helm_values = { 363 | "auth": {"existingSecret": "keycloak", "existingSecretKey": "admin-password"}, 364 | "production": True, 365 | "proxy": "edge", 366 | "httpRelativePath": "/", 367 | "replicaCount": 1, 368 | "resources": { 369 | "requests": { 370 | "cpu": "100m", 371 | "memory": "2048Mi", 372 | }, 373 | "limits": { 374 | "cpu": "500m", 375 | "memory": "4096Mi", 376 | }, 377 | }, 378 | "podAnnotations": { 379 | "vault.hashicorp.com/agent-inject": "true", 380 | "vault.hashicorp.com/role": "keycloak", 381 | "vault.hashicorp.com/service": "https://vault.ocf.berkeley.edu/", 382 | "vault.hashicorp.com/agent-inject-secret-keytab": "kvv2/data/keycloak/keycloak", 383 | "vault.hashicorp.com/agent-inject-template-keytab": ( 384 | "{{`" 385 | '{{- with secret "kvv2/data/keycloak/keycloak" -}}' 386 | "{{ base64Decode .Data.data.keytab }}" 387 | "{{- end}}" 388 | "`}}" 389 | ), 390 | }, 391 | "initContainers": [ 392 | { 393 | "name": "download-resources", 394 | "image": "alpine/git", 395 | "command": ["/bin/sh", "-c"], 396 | "args": [ 397 | "mkdir -p /keycloak/themes/keywind && (git -C /keycloak/themes/keywind pull || git clone https://github.com/lukin/keywind /keycloak/themes/keywind)" 398 | ], 399 | "volumeMounts": [{"name": "keycloak", "mountPath": "/keycloak"}], 400 | } 401 | ], 402 | "ingress": { 403 | "enabled": True, 404 | "ingressClassName": "contour", 405 | "annotations": { 406 | "cert-manager.io/cluster-issuer": "letsencrypt", 407 | "ingress.kubernetes.io/force-ssl-redirect": "true", 408 | "kubernetes.io/tls-acme": "true", 409 | }, 410 | "hostname": "idm.ocf.berkeley.edu", 411 | "tls": True, 412 | }, 413 | "metrics": {"enabled": True}, 414 | "keycloakConfigCli": { 415 | "enabled": True, 416 | "existingConfigmap": "keycloak-config-cli", 417 | "cleanupAfterFinished": { 418 | "enabled": False, 419 | "seconds": 0, 420 | }, 421 | }, 422 | "postgresql": {"enabled": False}, 423 | "externalDatabase": { 424 | "host": "ocf-keycloak", 425 | "port": 5432, 426 | "user": "keycloak", 427 | "database": "keycloak", 428 | "existingSecret": "keycloak.ocf-keycloak.credentials.postgresql.acid.zalan.do", 429 | "existingSecretPasswordKey": "password", 430 | }, 431 | "extraVolumes": [ 432 | {"name": "krb5-conf", "configMap": {"name": "krb5-conf"}}, 433 | {"name": "keycloak", "persistentVolumeClaim": {"claimName": "keycloak"}}, 434 | ], 435 | "extraVolumeMounts": [ 436 | { 437 | "name": "krb5-conf", 438 | "mountPath": "/etc/krb5.conf", 439 | "subPath": "krb5.conf", 440 | }, 441 | { 442 | "mountPath": "/opt/bitnami/keycloak/themes/keywind", 443 | "name": "keycloak", 444 | "subPath": "themes/keywind/theme/keywind", 445 | }, 446 | ], 447 | } 448 | 449 | 450 | def objects(): 451 | yield ConfigMap("krb5-conf", data={"krb5.conf": krb5_conf}).build() 452 | yield ConfigMap( 453 | "keycloak-config-cli", 454 | data={"configuration": json.dumps(keycloak_config_cli)}, 455 | ).build() 456 | 457 | yield { 458 | "apiVersion": "v1", 459 | "kind": "PersistentVolumeClaim", 460 | "metadata": {"name": "keycloak"}, 461 | "spec": { 462 | "storageClassName": "rbd-nvme", 463 | "accessModes": ["ReadWriteOnce"], 464 | "resources": {"requests": {"storage": "2Gi"}}, 465 | }, 466 | } 467 | 468 | yield { 469 | "apiVersion": "acid.zalan.do/v1", 470 | "kind": "postgresql", 471 | "metadata": {"name": "ocf-keycloak"}, 472 | "spec": { 473 | "teamId": "ocf", 474 | "volume": { 475 | "size": "32Gi", 476 | "storageClass": "rbd-nvme", 477 | }, 478 | "numberOfInstances": 1, 479 | "users": {"keycloak": ["superuser", "createdb"]}, 480 | "databases": {"keycloak": "keycloak"}, 481 | "postgresql": {"version": "15"}, 482 | }, 483 | } 484 | 485 | yield from helm.build_chart_from_versions( 486 | name=name, 487 | versions=get_versions(__file__), 488 | values=helm_values, 489 | ) 490 | -------------------------------------------------------------------------------- /apps/kubevirt.py: -------------------------------------------------------------------------------- 1 | from typing import Generator 2 | 3 | import requests 4 | import yaml 5 | from transpire.utils import get_versions 6 | 7 | name = "kubevirt" 8 | 9 | 10 | def objects() -> Generator[dict, None, None]: 11 | version = get_versions(__file__)[name]["version"] 12 | yield from yaml.safe_load_all( 13 | requests.get( 14 | f"https://github.com/kubevirt/kubevirt/releases/download/{version}/kubevirt-operator.yaml" 15 | ).text 16 | ) 17 | 18 | yield { 19 | "apiVersion": "kubevirt.io/v1", 20 | "kind": "KubeVirt", 21 | "metadata": {"name": "kubevirt", "namespace": "kubevirt"}, 22 | "spec": { 23 | "certificateRotateStrategy": {}, 24 | "configuration": {"developerConfiguration": {"featureGates": []}}, 25 | "customizeComponents": {}, 26 | "imagePullPolicy": "IfNotPresent", 27 | "workloadUpdateStrategy": {}, 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /apps/metallb.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.utils import get_versions 3 | 4 | name = "metallb" 5 | 6 | pool = { 7 | "apiVersion": "metallb.io/v1beta1", 8 | "kind": "IPAddressPool", 9 | "metadata": {"name": "pool-1"}, 10 | "spec": { 11 | "addresses": [ 12 | "169.229.226.81-169.229.226.89", 13 | "2607:f140:8801::1:81-2607:f140:8801::1:89", 14 | ] 15 | }, 16 | } 17 | 18 | method = { 19 | "apiVersion": "metallb.io/v1beta1", 20 | "kind": "L2Advertisement", 21 | "metadata": {"name": "pool-1"}, 22 | "spec": {"ipAddressPools": ["pool-1"]}, 23 | } 24 | 25 | 26 | def objects(): 27 | yield from helm.build_chart_from_versions( 28 | name="metallb", 29 | versions=get_versions(__file__), 30 | values={ 31 | "controller": { 32 | "metrics": {"enabled": True, "serviceMonitor": {"enabled": True}} 33 | } 34 | }, 35 | ) 36 | 37 | yield pool 38 | yield method 39 | -------------------------------------------------------------------------------- /apps/metrics_server.py: -------------------------------------------------------------------------------- 1 | from typing import Generator 2 | 3 | from transpire import helm 4 | from transpire.utils import get_versions 5 | 6 | name = "metrics-server" 7 | 8 | 9 | def objects() -> Generator[dict, None, None]: 10 | # Default values seem fine for now, except no Prometheus scraping, but that seems okay? 11 | # We don't really use autoscaling (at least not yet), so we only need `kubectl top`. 12 | # 13 | yield from helm.build_chart_from_versions( 14 | name=name, 15 | versions=get_versions(__file__), 16 | values={ 17 | "metrics": { 18 | "enabled": True, 19 | }, 20 | "serviceMonitor": { 21 | "enabled": True, 22 | }, 23 | } 24 | ) 25 | -------------------------------------------------------------------------------- /apps/ocf_io.py: -------------------------------------------------------------------------------- 1 | from transpire.resources import ConfigMap, Deployment, Ingress, Service 2 | 3 | name = "ocf-io" 4 | 5 | NGINX_CONF = """ 6 | user nginx; 7 | worker_processes auto; 8 | pid /var/run/nginx.pid; 9 | 10 | events { 11 | worker_connections 1024; 12 | } 13 | 14 | http { 15 | server { 16 | listen 80; 17 | server_name ~^(.+).ocf.io$; 18 | 19 | return 302 $scheme://$1.ocf.berkeley.edu$request_uri; 20 | } 21 | } 22 | """ 23 | 24 | 25 | def objects(): 26 | cm = ConfigMap( 27 | name="ocf-io-nginx", 28 | data={"nginx.conf": NGINX_CONF}, 29 | ) 30 | 31 | dep = Deployment( 32 | name="ocf-io", 33 | image="nginx:1.23.4", 34 | ports=[80], 35 | ) 36 | 37 | dep.obj.spec.template.spec.volumes = [ 38 | { 39 | "name": "nginx-conf", 40 | "configMap": {"name": cm.obj.metadata.name}, 41 | }, 42 | ] 43 | 44 | dep.obj.spec.template.spec.containers[0].volume_mounts = [ 45 | { 46 | "name": "nginx-conf", 47 | "mountPath": "/etc/nginx/nginx.conf", 48 | "subPath": "nginx.conf", 49 | } 50 | ] 51 | 52 | svc = Service( 53 | name="ocf-io", 54 | selector=dep.get_selector(), 55 | port_on_pod=80, 56 | port_on_svc=80, 57 | ) 58 | 59 | ing = Ingress.from_svc( 60 | svc, 61 | host="*.ocf.io", 62 | path_prefix="/", 63 | ) 64 | 65 | yield cm.build() 66 | yield dep.build() 67 | yield svc.build() 68 | yield ing.build() 69 | -------------------------------------------------------------------------------- /apps/postgres_operator.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.utils import get_versions 3 | 4 | name = "postgres-operator" 5 | 6 | 7 | def objects(): 8 | yield from helm.build_chart_from_versions( 9 | name=name, 10 | versions=get_versions(__file__), 11 | ) 12 | 13 | yield { 14 | "apiVersion": "acid.zalan.do/v1", 15 | "kind": "postgresql", 16 | # name must be in {TEAM}-{NAME} format 17 | "metadata": {"name": "ocf-postgres-nvme"}, 18 | "spec": { 19 | "teamId": "ocf", 20 | "volume": { 21 | "size": "512Gi", 22 | "storageClass": "rbd-nvme", 23 | }, 24 | "numberOfInstances": 1, 25 | "postgresql": {"version": "14"}, 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /apps/prometheus.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.utils import get_versions 3 | 4 | name = "prometheus" 5 | values = { 6 | "defaultRules": { 7 | "rules": { 8 | # We don't run kube-proxy, Cilium handles that. 9 | "kubeProxy": False, 10 | }, 11 | }, 12 | "nodeExporter": {"enabled": True}, 13 | # TODO: Setup alertmanager. 14 | "alertmanager": { 15 | "enabled": False, 16 | "ingress": { 17 | "enabled": False, 18 | "ingressClassName": "contour", 19 | "annotations": { 20 | "cert-manager.io/cluster-issuer": "letsencrypt", 21 | "ingress.kubernetes.io/force-ssl-redirect": "true", 22 | "kubernetes.io/tls-acme": "true", 23 | }, 24 | "hosts": ["alerts.ocf.berkeley.edu"], 25 | "tls": [{"hosts": ["alerts.ocf.berkeley.edu"], "secretName": "alerts-tls"}], 26 | }, 27 | }, 28 | "prometheus": { 29 | "ingress": { 30 | "enabled": True, 31 | "ingressClassName": "contour", 32 | "annotations": { 33 | "cert-manager.io/cluster-issuer": "letsencrypt", 34 | "ingress.kubernetes.io/force-ssl-redirect": "true", 35 | "kubernetes.io/tls-acme": "true", 36 | }, 37 | "hosts": ["prom.ocf.berkeley.edu"], 38 | "tls": [{"hosts": ["prom.ocf.berkeley.edu"], "secretName": "prom-tls"}], 39 | }, 40 | "prometheusSpec": {"serviceMonitorSelectorNilUsesHelmValues": False}, 41 | }, 42 | "grafana": { 43 | "ingress": { 44 | "enabled": True, 45 | "ingressClassName": "contour", 46 | "annotations": { 47 | "cert-manager.io/cluster-issuer": "letsencrypt", 48 | "ingress.kubernetes.io/force-ssl-redirect": "true", 49 | "kubernetes.io/tls-acme": "true", 50 | }, 51 | "hosts": ["graf.ocf.berkeley.edu"], 52 | "tls": [{"hosts": ["graf.ocf.berkeley.edu"], "secretName": "graf-tls"}], 53 | }, 54 | }, 55 | } 56 | 57 | 58 | def objects(): 59 | yield from helm.build_chart_from_versions( 60 | name="prometheus", versions=get_versions(__file__), values=values, 61 | ) 62 | -------------------------------------------------------------------------------- /apps/rabbitmq.py: -------------------------------------------------------------------------------- 1 | from typing import Generator 2 | 3 | from transpire import helm 4 | from transpire.utils import get_versions 5 | 6 | name = "rabbitmq" 7 | 8 | 9 | def objects() -> Generator[dict, None, None]: 10 | # 11 | values = { 12 | "clusterOperator": { 13 | "metrics": { 14 | "enabled": True, 15 | "serviceMonitor": { 16 | "enabled": True, 17 | }, 18 | }, 19 | }, 20 | "msgTopologyOperator": { 21 | "metrics": { 22 | "enabled": True, 23 | "serviceMonitor": { 24 | "enabled": True, 25 | }, 26 | }, 27 | }, 28 | "useCertManager": True, 29 | "metrics": { 30 | "enabled": True, 31 | }, 32 | "serviceMonitor": { 33 | "enabled": True, 34 | }, 35 | } 36 | yield from helm.build_chart_from_versions( 37 | name=name, 38 | versions=get_versions(__file__), 39 | values=values, 40 | ) 41 | 42 | # 43 | yield { 44 | "apiVersion": "rabbitmq.com/v1beta1", 45 | "kind": "RabbitmqCluster", 46 | "metadata": {"name": "rabbitmq"}, 47 | "spec": { 48 | # 49 | "secretBackend": { 50 | "vault": { 51 | "role": "rabbitmq", 52 | "annotations": { 53 | "vault.hashicorp.com/template-static-secret-render-interval": "60s", 54 | "vault.hashicorp.com/service": "https://vault.ocf.berkeley.edu/", 55 | }, 56 | "defaultUserPath": "kvv2/data/rabbitmq/rabbitmq-admin-userpass", 57 | "tls": { 58 | "pkiIssuerPath": "pki/issue/rabbitmq", 59 | }, 60 | }, 61 | }, 62 | }, 63 | } 64 | -------------------------------------------------------------------------------- /apps/rbac.py: -------------------------------------------------------------------------------- 1 | name = "ocf-rbac" 2 | 3 | 4 | def objects(): 5 | yield { 6 | "apiVersion": "rbac.authorization.k8s.io/v1", 7 | "kind": "ClusterRoleBinding", 8 | "metadata": { 9 | "name": "ocf-viewer", 10 | }, 11 | "subjects": [ 12 | { 13 | "apiGroup": "rbac.authorization.k8s.io", 14 | "kind": "Group", 15 | "name": "ocf:viewer", 16 | } 17 | ], 18 | "roleRef": { 19 | "apiGroup": "rbac.authorization.k8s.io", 20 | "kind": "ClusterRole", 21 | "name": "view", 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /apps/rook.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.utils import get_versions 3 | 4 | name = "rook" 5 | values = { 6 | "image": { 7 | "tag": "v1.10.6", 8 | }, 9 | # 10 | "network": { 11 | "dualStack": True, 12 | }, 13 | # 14 | "dashboard": { 15 | "enabled": True, 16 | }, 17 | "enableDiscoveryDaemon": True, 18 | # 19 | # 20 | "csi": { 21 | "csiRBDPluginVolume": [ 22 | { 23 | "name": "lib-modules", 24 | "hostPath": {"path": "/run/booted-system/kernel-modules/lib/modules/"}, 25 | }, 26 | {"name": "host-nix", "hostPath": {"path": "/nix"}}, 27 | ], 28 | "csiRBDPluginVolumeMount": [ 29 | {"name": "host-nix", "mountPath": "/nix", "readOnly": True} 30 | ], 31 | "csiCephFSPluginVolume": [ 32 | { 33 | "name": "lib-modules", 34 | "hostPath": {"path": "/run/booted-system/kernel-modules/lib/modules/"}, 35 | }, 36 | {"name": "host-nix", "hostPath": {"path": "/nix"}}, 37 | ], 38 | "csiCephFSPluginVolumeMount": [ 39 | {"name": "host-nix", "mountPath": "/nix", "readOnly": True} 40 | ], 41 | }, 42 | } 43 | 44 | nucleusDevices = [ 45 | { 46 | "config": {"deviceClass": "nvme"}, 47 | "name": "/dev/disk/by-path/pci-0000:02:00.0-nvme-1", 48 | }, 49 | { 50 | "config": {"deviceClass": "nvme"}, 51 | "name": "/dev/disk/by-path/pci-0000:41:00.0-nvme-1", 52 | }, 53 | { 54 | "config": {"deviceClass": "hdd"}, 55 | "name": "/dev/disk/by-path/pci-0000:84:00.0-ata-2", 56 | }, 57 | { 58 | "config": {"deviceClass": "hdd"}, 59 | "name": "/dev/disk/by-path/pci-0000:84:00.0-ata-3", 60 | }, 61 | ] 62 | 63 | 64 | def objects(): 65 | yield from helm.build_chart_from_versions( 66 | name="rook", 67 | versions=get_versions(__file__), 68 | values=values, 69 | ) 70 | 71 | yield { 72 | "apiVersion": "ceph.rook.io/v1", 73 | "kind": "CephCluster", 74 | "metadata": { 75 | "name": "ceph", 76 | "namespace": "rook", 77 | "annotations": {"argocd.argoproj.io/compare-options": "IgnoreExtraneous"}, 78 | }, 79 | "spec": { 80 | "cephVersion": {"image": "quay.io/ceph/ceph:v17.2.5"}, 81 | "dataDirHostPath": "/var/lib/rook", 82 | "mon": {"count": 3, "allowMultiplePerNode": False}, 83 | # Re-enable this when the issue that blocks it is merged and available in a released Ceph version. 84 | # 85 | # This is needed to show physical devices in the Ceph dashboard. 86 | # "mgr": {'modules': [{'name': 'rook', 'enabled': True}]}, 87 | "dashboard": { 88 | "enabled": True, 89 | }, 90 | "storage": { 91 | "nodes": [ 92 | { 93 | "devices": nucleusDevices, 94 | "name": "adenine", 95 | }, 96 | { 97 | "devices": nucleusDevices, 98 | "name": "guanine", 99 | }, 100 | { 101 | "devices": nucleusDevices, 102 | "name": "cytosine", 103 | }, 104 | { 105 | "devices": nucleusDevices, 106 | "name": "thymine", 107 | }, 108 | ], 109 | "useAllDevices": False, 110 | "useAllNodes": False, 111 | }, 112 | }, 113 | } 114 | 115 | ################# 116 | # Block Devices # 117 | ################# 118 | yield { 119 | "apiVersion": "ceph.rook.io/v1", 120 | "kind": "CephBlockPool", 121 | "metadata": {"name": "rbd-nvme", "namespace": "rook"}, 122 | "spec": { 123 | "failureDomain": "host", 124 | "replicated": {"size": 3, "requireSafeReplicaSize": True}, 125 | "deviceClass": "nvme", 126 | }, 127 | } 128 | 129 | yield { 130 | "apiVersion": "storage.k8s.io/v1", 131 | "kind": "StorageClass", 132 | "metadata": { 133 | "name": "rbd-nvme", 134 | "annotations": {"storageclass.kubernetes.io/is-default-class": "true"}, 135 | }, 136 | "provisioner": "rook.rbd.csi.ceph.com", 137 | "parameters": { 138 | "clusterID": "rook", 139 | "pool": "rbd-nvme", 140 | "imageFormat": "2", 141 | "csi.storage.k8s.io/provisioner-secret-name": "rook-csi-rbd-provisioner", 142 | "csi.storage.k8s.io/provisioner-secret-namespace": "rook", 143 | "csi.storage.k8s.io/controller-expand-secret-name": "rook-csi-rbd-provisioner", 144 | "csi.storage.k8s.io/controller-expand-secret-namespace": "rook", 145 | "csi.storage.k8s.io/node-stage-secret-name": "rook-csi-rbd-node", 146 | "csi.storage.k8s.io/node-stage-secret-namespace": "rook", 147 | "csi.storage.k8s.io/fstype": "ext4", 148 | # 149 | "imageFeatures": "layering,fast-diff,object-map,deep-flatten,exclusive-lock", 150 | }, 151 | "allowVolumeExpansion": True, 152 | "reclaimPolicy": "Delete", 153 | } 154 | 155 | yield { 156 | "apiVersion": "snapshot.storage.k8s.io/v1", 157 | "kind": "VolumeSnapshotClass", 158 | "metadata": {"name": "csi-rbdplugin-snapclass"}, 159 | "driver": "rook.rbd.csi.ceph.com", 160 | "parameters": { 161 | "clusterID": "rook", 162 | "csi.storage.k8s.io/snapshotter-secret-name": "rook-csi-rbd-provisioner", 163 | "csi.storage.k8s.io/snapshotter-secret-namespace": "rook", 164 | }, 165 | "deletionPolicy": "Delete", 166 | } 167 | 168 | ############### 169 | # Filesystems # 170 | ############### 171 | yield { 172 | "apiVersion": "ceph.rook.io/v1", 173 | "kind": "CephFilesystem", 174 | "metadata": {"name": "cephfs-hybrid"}, 175 | "spec": { 176 | "metadataPool": { 177 | "failureDomain": "host", 178 | "replicated": {"size": 3}, 179 | "deviceClass": "nvme", 180 | }, 181 | "dataPools": [ 182 | { 183 | "name": "nvme", 184 | "failureDomain": "host", 185 | "replicated": {"size": 3}, 186 | "deviceClass": "nvme", 187 | }, 188 | { 189 | "name": "hdd", 190 | "failureDomain": "host", 191 | "replicated": {"size": 3}, 192 | "deviceClass": "hdd", 193 | }, 194 | ], 195 | "preserveFilesystemOnDelete": True, 196 | "metadataServer": {"activeCount": 1, "activeStandby": True}, 197 | }, 198 | } 199 | 200 | yield { 201 | "apiVersion": "storage.k8s.io/v1", 202 | "kind": "StorageClass", 203 | "metadata": {"name": "cephfs-nvme"}, 204 | "provisioner": "rook.cephfs.csi.ceph.com", 205 | "parameters": { 206 | "clusterID": "rook", 207 | "fsName": "cephfs-hybrid", 208 | "pool": "cephfs-hybrid-nvme", 209 | "csi.storage.k8s.io/provisioner-secret-name": "rook-csi-cephfs-provisioner", 210 | "csi.storage.k8s.io/provisioner-secret-namespace": "rook", 211 | "csi.storage.k8s.io/controller-expand-secret-name": "rook-csi-cephfs-provisioner", 212 | "csi.storage.k8s.io/controller-expand-secret-namespace": "rook", 213 | "csi.storage.k8s.io/node-stage-secret-name": "rook-csi-cephfs-node", 214 | "csi.storage.k8s.io/node-stage-secret-namespace": "rook", 215 | }, 216 | "reclaimPolicy": "Delete", 217 | } 218 | 219 | yield { 220 | "apiVersion": "storage.k8s.io/v1", 221 | "kind": "StorageClass", 222 | "metadata": {"name": "cephfs-hdd"}, 223 | "provisioner": "rook.cephfs.csi.ceph.com", 224 | "parameters": { 225 | "clusterID": "rook", 226 | "fsName": "cephfs-hybrid", 227 | "pool": "cephfs-hybrid-hdd", 228 | "csi.storage.k8s.io/provisioner-secret-name": "rook-csi-cephfs-provisioner", 229 | "csi.storage.k8s.io/provisioner-secret-namespace": "rook", 230 | "csi.storage.k8s.io/controller-expand-secret-name": "rook-csi-cephfs-provisioner", 231 | "csi.storage.k8s.io/controller-expand-secret-namespace": "rook", 232 | "csi.storage.k8s.io/node-stage-secret-name": "rook-csi-cephfs-node", 233 | "csi.storage.k8s.io/node-stage-secret-namespace": "rook", 234 | }, 235 | "reclaimPolicy": "Delete", 236 | } 237 | 238 | yield { 239 | "apiVersion": "snapshot.storage.k8s.io/v1", 240 | "kind": "VolumeSnapshotClass", 241 | "metadata": {"name": "csi-cephfsplugin-snapclass"}, 242 | "driver": "rook.cephfs.csi.ceph.com", 243 | "parameters": { 244 | "clusterID": "rook", 245 | "csi.storage.k8s.io/snapshotter-secret-name": "rook-csi-cephfs-provisioner", 246 | "csi.storage.k8s.io/snapshotter-secret-namespace": "rook", 247 | }, 248 | "deletionPolicy": "Delete", 249 | } 250 | 251 | ################## 252 | # Object Storage # 253 | ################## 254 | crtname = "o3-ocf-crt" 255 | yield { 256 | "apiVersion": "cert-manager.io/v1", 257 | "kind": "Certificate", 258 | "metadata": {"name": crtname}, 259 | "spec": { 260 | "secretName": crtname, 261 | "dnsNames": ["o3.ocf.io", "o3.ocf.berkeley.edu"], 262 | "issuerRef": { 263 | "name": "letsencrypt", 264 | "kind": "ClusterIssuer", 265 | "group": "cert-manager.io", 266 | }, 267 | }, 268 | } 269 | 270 | yield { 271 | "apiVersion": "networking.k8s.io/v1", 272 | "kind": "Ingress", 273 | "metadata": { 274 | "name": "o3-ingress", 275 | }, 276 | "spec": { 277 | "ingressClassName": "contour", 278 | "tls": [ 279 | { 280 | "hosts": ["o3.ocf.io", "o3.ocf.berkeley.edu"], 281 | "secretName": crtname, 282 | }, 283 | ], 284 | "rules": [ 285 | { 286 | "host": "o3.ocf.io", 287 | "http": { 288 | "paths": [ 289 | { 290 | "path": "/", 291 | "pathType": "Prefix", 292 | "backend": { 293 | "service": { 294 | "name": "rook-ceph-rgw-rgw-hdd", 295 | "port": {"number": 80}, 296 | } 297 | }, 298 | } 299 | ] 300 | }, 301 | }, 302 | { 303 | "host": "o3.ocf.berkeley.edu", 304 | "http": { 305 | "paths": [ 306 | { 307 | "path": "/", 308 | "pathType": "Prefix", 309 | "backend": { 310 | "service": { 311 | "name": "rook-ceph-rgw-rgw-hdd", 312 | "port": {"number": 80}, 313 | } 314 | }, 315 | } 316 | ] 317 | }, 318 | }, 319 | ], 320 | }, 321 | } 322 | 323 | yield { 324 | "apiVersion": "ceph.rook.io/v1", 325 | "kind": "CephObjectStore", 326 | "metadata": {"name": "rgw-hdd"}, 327 | "spec": { 328 | "metadataPool": { 329 | "failureDomain": "host", 330 | "replicated": {"size": 3}, 331 | "deviceClass": "nvme", 332 | }, 333 | "dataPool": { 334 | "failureDomain": "host", 335 | "replicated": {"size": 3}, 336 | "deviceClass": "hdd", 337 | }, 338 | "preservePoolsOnDelete": True, 339 | "gateway": { 340 | "securePort": 443, 341 | "port": 80, 342 | "instances": 1, 343 | }, 344 | }, 345 | } 346 | 347 | yield { 348 | "apiVersion": "storage.k8s.io/v1", 349 | "kind": "StorageClass", 350 | "metadata": {"name": "rgw-hdd"}, 351 | "provisioner": "rook.ceph.rook.io/bucket", 352 | "reclaimPolicy": "Delete", 353 | "parameters": { 354 | "objectStoreName": "rgw-hdd", 355 | "objectStoreNamespace": "rook-ceph", 356 | }, 357 | } 358 | -------------------------------------------------------------------------------- /apps/snapshot_controller.py: -------------------------------------------------------------------------------- 1 | from transpire import kustomize 2 | from transpire.utils import get_versions 3 | 4 | name = "snapshot-controller" 5 | namespace = "kube-system" 6 | 7 | def objects(): 8 | yield from kustomize.build_kustomization_from_versions( 9 | name="snapshot-controller-crds", 10 | versions=get_versions(__file__), 11 | ) 12 | 13 | yield from kustomize.build_kustomization_from_versions( 14 | name="snapshot-controller", 15 | versions=get_versions(__file__), 16 | ) 17 | -------------------------------------------------------------------------------- /apps/teleport.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.surgery import edit_manifests 3 | from transpire.utils import get_versions 4 | 5 | values = { 6 | "clusterName": "tele.ocf.io", 7 | "kubeClusterName": "dna.ocf.io", 8 | "enterprise": True, 9 | "highAvailability": { 10 | "replicaCount": 1, 11 | "certManager": { 12 | "enabled": True, 13 | "issuerName": "letsencrypt", 14 | "issuerKind": "ClusterIssuer", 15 | }, 16 | }, 17 | "proxy": { 18 | "highAvailability": { 19 | "replicaCount": 1, 20 | }, 21 | }, 22 | "service": { 23 | "spec": { 24 | "loadBalancerIP": "169.229.226.82", 25 | }, 26 | }, 27 | } 28 | 29 | name = "teleport" 30 | 31 | 32 | def objects(): 33 | yield from helm.build_chart_from_versions( 34 | name="teleport", 35 | versions=get_versions(__file__), 36 | values=values, 37 | ) 38 | 39 | yield { 40 | "apiVersion": "ricoberger.de/v1alpha1", 41 | "kind": "VaultSecret", 42 | "metadata": {"name": "license"}, 43 | "spec": { 44 | "keys": ["license.pem"], 45 | "path": "kvv2/teleport/license", 46 | "type": "Opaque", 47 | }, 48 | } 49 | -------------------------------------------------------------------------------- /apps/vault.py: -------------------------------------------------------------------------------- 1 | import textwrap 2 | 3 | from transpire import helm 4 | from transpire.utils import get_versions 5 | 6 | name = "vault" 7 | 8 | 9 | def objects(): 10 | yield { 11 | "apiVersion": "v1", 12 | "kind": "ConfigMap", 13 | "metadata": {"name": "ocf-rootca"}, 14 | "data": { 15 | "root.crt": textwrap.dedent( 16 | """ 17 | -----BEGIN CERTIFICATE----- 18 | MIIC7jCCAk+gAwIBAgIUZPQZpVG1GfVIKkcvKb5vwky5RTAwCgYIKoZIzj0EAwQw 19 | gY8xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQHEwhC 20 | ZXJrZWxleTEgMB4GA1UEChMXT3BlbiBDb21wdXRpbmcgRmFjaWxpdHkxDDAKBgNV 21 | BAsTA0FsbDEoMCYGA1UEAxMfT3BlbiBDb21wdXRpbmcgRmFjaWxpdHkgUm9vdCBY 22 | MTAgFw0yMzAxMTYyMzM5MDBaGA8yMTIzMDExNjIzMzkwMFowgY8xCzAJBgNVBAYT 23 | AlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQHEwhCZXJrZWxleTEgMB4G 24 | A1UEChMXT3BlbiBDb21wdXRpbmcgRmFjaWxpdHkxDDAKBgNVBAsTA0FsbDEoMCYG 25 | A1UEAxMfT3BlbiBDb21wdXRpbmcgRmFjaWxpdHkgUm9vdCBYMTCBmzAQBgcqhkjO 26 | PQIBBgUrgQQAIwOBhgAEAaXbYJW1MmFY27rALUopMUWDNC4DS+A4trLyTOqf8M+x 27 | OrIDDkaEZHjhD5ofAcsyCKQf4tMNGqsj4RKAYhOxJLLDAHJLcpV63S+EojFkUJpr 28 | PtH81lf9toed/yi16f+V159qQ+PF+cGSXkHSyzUHPcqhuVrbuH37/AJNohgDPGmN 29 | rKWDo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E 30 | FgQUYk/x6J54THpb1Xv9lNNqZWvbEtMwCgYIKoZIzj0EAwQDgYwAMIGIAkIBzWa7 31 | +3IgvnGLPv5UaU1tQVOGfAfvW3LYtZSDZ543bAIFVNLxpdhozZAeAfjBuPzSY/yh 32 | T1O56toa7dMv4tILfGsCQgGSQ3VEVnuqwUTGnchcZYsHZtsRSQ/AglekXrphZCxa 33 | xqg2jrBElQrI7xM3NcqlerzdvSzMgVJA3XyqXQJ7uAC9ag== 34 | -----END CERTIFICATE----- 35 | """ 36 | ) 37 | }, 38 | } 39 | 40 | yield from helm.build_chart_from_versions( 41 | name="vault", 42 | versions=get_versions(__file__), 43 | values={ 44 | "global": {"enabled": True, "tlsDisable": False}, 45 | "server": { 46 | "readinessProbe": {"enabled": False}, 47 | "livenessProbe": {"enabled": False, "initialDelaySeconds": 60}, 48 | "auditStorage": {"enabled": True, "storageClass": "rbd-nvme"}, 49 | "dataStorage": {"storageClass": "rbd-nvme"}, 50 | # "image": {"tag": "1.12.2"}, 51 | "ingress": { 52 | "enabled": True, 53 | "annotations": { 54 | "cert-manager.io/cluster-issuer": "letsencrypt", 55 | "ingress.kubernetes.io/force-ssl-redirect": "true", 56 | "kubernetes.io/ingress.class": "contour", 57 | "kubernetes.io/tls-acme": "true", 58 | }, 59 | "hosts": [{"host": "vault.ocf.berkeley.edu", "paths": []}], 60 | "tls": [ 61 | { 62 | "hosts": ["vault.ocf.berkeley.edu"], 63 | "secretName": "vault-ingress-tls", 64 | }, 65 | ], 66 | }, 67 | "volumes": [ 68 | {"name": "ocf-rootca", "configMap": {"name": "ocf-rootca"}} 69 | ], 70 | "volumeMounts": [ 71 | { 72 | "mountPath": "/etc/ssl/certs/ocfroot.pem", 73 | "name": "ocf-rootca", 74 | "subPath": "ocfroot.pem", 75 | } 76 | ], 77 | "standalone": {"enabled": False}, 78 | "ha": { 79 | "enabled": True, 80 | "replicas": 3, 81 | "raft": { 82 | "enabled": True, 83 | "setNodeId": True, 84 | }, 85 | }, 86 | }, 87 | "ui": {"enabled": True}, 88 | "injector": {"enabled": True}, 89 | "serverTelemetry": { 90 | "serviceMonitor": { 91 | "enabled": True, 92 | }, 93 | }, 94 | }, 95 | ) 96 | -------------------------------------------------------------------------------- /apps/vault_secrets_operator.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.utils import get_versions 3 | 4 | name = "vault-secrets-operator" 5 | values = { 6 | "vault": { 7 | "authMethod": "kubernetes", 8 | "address": "http://vault.vault.svc.cluster.local:8200", 9 | }, 10 | } 11 | 12 | 13 | def objects(): 14 | yield from helm.build_chart_from_versions( 15 | name="vault-secrets-operator", 16 | versions=get_versions(__file__), 17 | values=values, 18 | ) 19 | -------------------------------------------------------------------------------- /apps/velero.py: -------------------------------------------------------------------------------- 1 | from transpire import helm 2 | from transpire.utils import get_versions 3 | 4 | name = "velero" 5 | 6 | values = { 7 | # At least one plugin provider image is required. 8 | "initContainers": [ 9 | { 10 | "name": "velero-plugin-for-csi", 11 | "image": "velero/velero-plugin-for-csi:v0.7.0", 12 | "imagePullPolicy": "IfNotPresent", 13 | "volumeMounts": [ 14 | { 15 | "mountPath": "/target", 16 | "name": "plugins", 17 | }, 18 | ], 19 | }, 20 | { 21 | "name": "velero-plugin-for-aws", 22 | # for S3-compatible API 23 | "image": "velero/velero-plugin-for-aws:v1.9.0", 24 | "imagePullPolicy": "IfNotPresent", 25 | "volumeMounts": [ 26 | { 27 | "mountPath": "/target", 28 | "name": "plugins", 29 | }, 30 | ], 31 | }, 32 | ], 33 | "deployNodeAgent": "true", 34 | "configuration": { 35 | "backupStorageLocation": [ 36 | { 37 | "name": "default", 38 | "provider": "velero.io/aws", 39 | "bucket": "velero", 40 | "credential": {"key": "aws-config", "name": "minio-credentials"}, 41 | "config": { 42 | "region": "minio", 43 | "s3ForcePathStyle": "true", 44 | "s3Url": "http://hal.ocf.berkeley.edu:9000", 45 | }, 46 | } 47 | ], 48 | "volumeSnapshotLocation": [ 49 | { 50 | "name": "default", 51 | "provider": "csi", 52 | } 53 | ], 54 | "features": "EnableCSI", 55 | }, 56 | "schedules": { 57 | "daily": { 58 | "disabled": False, 59 | # We run our servers with daylight savings time 60 | # and 4 AM never happens twice 61 | # TODO: This might not be California time? It could be UTC. 62 | "schedule": "0 4 * * *", 63 | # If this is true, deleting this schedule will 64 | # delete all the backups, which is bad! 65 | "useOwnerReferencesInBackup": False, 66 | "template": { 67 | # Specify nothing else so it backs up everything. 68 | # We can also exclude some namespaces if needed. 69 | "ttl": "168h", # 7 days 70 | "snapshotMoveData": True, 71 | }, 72 | } 73 | }, 74 | "nodeAgent": { 75 | # The defaults are way too low, they cause OOM Kills 76 | # when backing up larger PVs. These are probably(?) 77 | # too high but we have a lot of spare compute so I'm 78 | # just going to leave it as is. 79 | "resources": { 80 | "requests": { 81 | "cpu": "2000m", 82 | "memory": "8Gi", 83 | }, 84 | "limits": { 85 | "cpu": "4000m", 86 | "memory": "16Gi", 87 | }, 88 | }, 89 | }, 90 | } 91 | 92 | 93 | def objects(): 94 | yield { 95 | "apiVersion": "ricoberger.de/v1alpha1", 96 | "kind": "VaultSecret", 97 | "metadata": {"name": "minio-credentials"}, 98 | "spec": { 99 | "keys": ["aws-config"], 100 | "path": f"kvv2/{name}/minio-credentials", 101 | "type": "Opaque", 102 | }, 103 | } 104 | 105 | yield from helm.build_chart_from_versions( 106 | name="velero", 107 | versions=get_versions(__file__), 108 | values=values, 109 | ) 110 | -------------------------------------------------------------------------------- /apps/versions.toml: -------------------------------------------------------------------------------- 1 | # This file contains version numbers for software in the cluster. 2 | # It is formatted as best as possible to avoid merge conflicts from 3 | # automated tooling. 4 | 5 | 6 | # Cluster Services & Core Applications 7 | 8 | [argocd] 9 | version = "7.8.13" 10 | helm = "https://argoproj.github.io/argo-helm" 11 | chart = "argo-cd" 12 | 13 | [argo-workflows] 14 | version = "0.45.11" 15 | helm = "https://argoproj.github.io/argo-helm" 16 | chart = "argo-workflows" 17 | 18 | [argo-events] 19 | version = "2.4.4" 20 | helm = "https://argoproj.github.io/argo-helm" 21 | chart = "argo-events" 22 | 23 | [cert-manager] 24 | version = "1.14.5" 25 | helm = "https://charts.jetstack.io" 26 | 27 | [cilium] 28 | version = "1.15.4" 29 | helm = "https://helm.cilium.io/" 30 | 31 | [contour] 32 | version = "17.0.7" 33 | helm = "https://charts.bitnami.com/bitnami" 34 | 35 | [coredns] 36 | version = "1.29.0" 37 | helm = "https://coredns.github.io/helm" 38 | 39 | [fission] 40 | version = "1.20.1" 41 | helm = "https://fission.github.io/fission-charts/" 42 | chart = "fission-all" 43 | 44 | [harbor] 45 | version = "24.0.2" 46 | helm = "https://charts.bitnami.com/bitnami" 47 | 48 | [keda] 49 | version = "2.14.0" 50 | helm = "https://kedacore.github.io/charts" 51 | 52 | [keycloak] 53 | version = "24.4.11" 54 | helm = "https://charts.bitnami.com/bitnami" 55 | 56 | [kubevirt] 57 | version = "v0.59.0-alpha.2" 58 | github = "https://github.com/kubevirt/kubevirt" 59 | 60 | [metallb] 61 | version = "6.1.2" 62 | helm = "https://charts.bitnami.com/bitnami" 63 | 64 | [metrics-server] 65 | version = "3.12.1" 66 | helm = "https://kubernetes-sigs.github.io/metrics-server/" 67 | 68 | [postgres-operator] 69 | version = "1.11.0" 70 | helm = "https://opensource.zalando.com/postgres-operator/charts/postgres-operator" 71 | 72 | [prometheus] 73 | version = "70.2.1" 74 | helm = "https://prometheus-community.github.io/helm-charts" 75 | chart = "kube-prometheus-stack" 76 | 77 | [rabbitmq] 78 | version = "4.2.5" 79 | helm = "https://charts.bitnami.com/bitnami" 80 | chart = "rabbitmq-cluster-operator" 81 | 82 | [rook] 83 | version = "1.14.2" 84 | helm = "https://charts.rook.io/release" 85 | chart = "rook-ceph" 86 | 87 | [snapshot-controller] 88 | version = "v7.0.1" 89 | repo_url = "https://github.com/kubernetes-csi/external-snapshotter.git/" 90 | path = "deploy/kubernetes/snapshot-controller" 91 | 92 | [snapshot-controller-crds] 93 | version = "v7.0.1" 94 | repo_url = "https://github.com/kubernetes-csi/external-snapshotter.git/" 95 | path = "client/config/crd" 96 | 97 | [teleport] 98 | version = "15.4.32" 99 | helm = "https://charts.releases.teleport.dev" 100 | chart = "teleport-cluster" 101 | 102 | [vault] 103 | version = "0.28.0" 104 | helm = "https://helm.releases.hashicorp.com" 105 | 106 | [vault-secrets-operator] 107 | version = "2.7.0" 108 | helm = "https://ricoberger.github.io/helm-charts" 109 | 110 | [velero] 111 | version = "6.0.0" 112 | helm = "https://vmware-tanzu.github.io/helm-charts/" 113 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | echo "OCF Kubernetes Bootstrap Script" 5 | echo "This script will install the bare minimum needed to run ArgoCD onto your currently configured kubernetes cluster." 6 | echo It has been $((($(date +%s)-$(date +%s --date "2021-07-18"))/(3600*24))) days since this script was last updated. Knowing the OCF, this is probably a lot of days. Consider modernizing this script before you run it. 7 | 8 | # TODO: Automatically detect when services are healthy and it's ok to move on. 9 | 10 | echo 11 | read -p "Continue? [y/N] " -n 1 -r 12 | echo 13 | if [[ $REPLY =~ ^[Yy]$ ]] 14 | then 15 | # Check dependencies... 16 | command -v helm >/dev/null 2>&1 || { echo >&2 "Please install helm before running this script."; exit 1; } 17 | command -v kubectl >/dev/null 2>&1 || { echo >&2 "Please install kubectl before running this script."; exit 1; } 18 | 19 | echo "Installing Cilium..." 20 | kubectl create namespace cilium --dry-run=client -o yaml | kubectl apply -f - 21 | python3 -m ocfkube cilium | kubectl apply -n cilium -f - 22 | read -p "Now wait for Cilium to come up, press enter once it's healthy... " -n 1 -r 23 | 24 | echo "Installing CoreDNS..." 25 | kubectl create namespace coredns --dry-run=client -o yaml | kubectl apply -f - 26 | python3 -m ocfkube coredns | kubectl apply -n coredns -f - 27 | read -p "Now wait for CoreDNS to come up, press enter once it's healthy... " -n 1 -r 28 | 29 | echo "Installing ArgoCD..." 30 | kubectl create namespace argocd --dry-run=client -o yaml | kubectl apply -f - 31 | python3 -m ocfkube argocd | kubectl apply -n argocd -f - 32 | read -p "Now wait for ArgoCD to come up, press enter once it's healthy... " -n 1 -r 33 | 34 | echo -n "USERNAME: admin, PASSWORD: " 35 | kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d 36 | kubectl -n argocd get pods 37 | 38 | echo "To finish bootstrapping the cluster, visit http://localhost:8000, log in, and press sync. When you're done, Ctrl+C this script. ArgoCD should now redeploy itself, cilium, and everything else in this repo." 39 | kubectl -n argocd port-forward service/argocd-server 8000:80 40 | fi 41 | -------------------------------------------------------------------------------- /cluster.toml: -------------------------------------------------------------------------------- 1 | apiVersion = "v1" 2 | 3 | [defaults] 4 | certManagerIssuer = "letsencrypt" 5 | ingressClass = "contour" 6 | 7 | [secrets] 8 | provider = "vault" 9 | 10 | [secrets.vault] 11 | kvstore = "kvv2" 12 | 13 | [ci] 14 | webhook_url = "https://ocf.io/wtf" 15 | 16 | [modules] 17 | argo-events = { path = "apps/argo_events.py" } 18 | argo-workflows = { path = "apps/argo_workflows.py" } 19 | argocd = { path = "apps/argocd.py" } 20 | cert-manager = { path = "apps/cert_manager.py" } 21 | cilium = { path = "apps/cilium.py" } 22 | coredns = { path = "apps/coredns.py" } 23 | contour = { path = "apps/contour.py" } 24 | fission = { path = "apps/fission.py" } 25 | gvisor = { path = "apps/gvisor.py" } 26 | harbor = { path = "apps/harbor.py" } 27 | keda = { path = "apps/keda.py" } 28 | keycloak = { path = "apps/keycloak.py" } 29 | kubevirt = { path = "apps/kubevirt.py" } 30 | metallb = { path = "apps/metallb.py" } 31 | metrics-server = { path = "apps/metrics_server.py" } 32 | prometheus = { path = "apps/prometheus.py" } 33 | postgres-operator = { path = "apps/postgres_operator.py" } 34 | ocf-io = { path = "apps/ocf_io.py" } 35 | rabbitmq = { path = "apps/rabbitmq.py" } 36 | rook = { path = "apps/rook.py" } 37 | snapshot-controller = { path = "apps/snapshot_controller.py" } 38 | teleport = { path = "apps/teleport.py" } 39 | vault = { path = "apps/vault.py" } 40 | vault-secrets-operator = { path = "apps/vault_secrets_operator.py" } 41 | velero = { path = "apps/velero.py" } 42 | 43 | create = { git = "https://github.com/ocf/create" } 44 | etc = { git = "https://github.com/ocf/etc" } 45 | ergo = { git = "https://github.com/ocf/irc" } 46 | id6 = { git = "https://github.com/ocf/id6" } 47 | jukebox = { git = "https://github.com/ocf/jukebox" } 48 | labmap = { git = "https://github.com/ocf/labmap" } 49 | notes = { git = "https://github.com/ocf/notes" } 50 | ocfstatic = { git = "https://github.com/ocf/ocfstatic" } 51 | ocfweb = { git = "https://github.com/ocf/ocfweb" } 52 | outline = { git = "https://github.com/ocf/outline" } 53 | templates = { git = "https://github.com/ocf/templates" } 54 | transpire = { git = "https://github.com/ocf/transpire" } 55 | ocfdocs = { git = "https://github.com/ocf/ocfdocs" } 56 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1694529238, 9 | "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "flake-utils_2": { 22 | "inputs": { 23 | "systems": "systems_2" 24 | }, 25 | "locked": { 26 | "lastModified": 1681037374, 27 | "narHash": "sha256-XL6X3VGbEFJZDUouv2xpKg2Aljzu/etPLv5e1FPt1q0=", 28 | "owner": "numtide", 29 | "repo": "flake-utils", 30 | "rev": "033b9f258ca96a10e543d4442071f614dc3f8412", 31 | "type": "github" 32 | }, 33 | "original": { 34 | "owner": "numtide", 35 | "repo": "flake-utils", 36 | "type": "github" 37 | } 38 | }, 39 | "flake-utils_3": { 40 | "inputs": { 41 | "systems": "systems_3" 42 | }, 43 | "locked": { 44 | "lastModified": 1681037374, 45 | "narHash": "sha256-XL6X3VGbEFJZDUouv2xpKg2Aljzu/etPLv5e1FPt1q0=", 46 | "owner": "numtide", 47 | "repo": "flake-utils", 48 | "rev": "033b9f258ca96a10e543d4442071f614dc3f8412", 49 | "type": "github" 50 | }, 51 | "original": { 52 | "owner": "numtide", 53 | "repo": "flake-utils", 54 | "type": "github" 55 | } 56 | }, 57 | "nixpkgs": { 58 | "locked": { 59 | "lastModified": 1697226376, 60 | "narHash": "sha256-cumLLb1QOUtWieUnLGqo+ylNt3+fU8Lcv5Zl+tYbRUE=", 61 | "owner": "NixOS", 62 | "repo": "nixpkgs", 63 | "rev": "898cb2064b6e98b8c5499f37e81adbdf2925f7c5", 64 | "type": "github" 65 | }, 66 | "original": { 67 | "owner": "NixOS", 68 | "ref": "nixos-23.05", 69 | "repo": "nixpkgs", 70 | "type": "github" 71 | } 72 | }, 73 | "poetry2nix": { 74 | "inputs": { 75 | "flake-utils": "flake-utils_3", 76 | "nixpkgs": [ 77 | "transpire", 78 | "nixpkgs" 79 | ] 80 | }, 81 | "locked": { 82 | "lastModified": 1680741011, 83 | "narHash": "sha256-yngUYI0IzwlvPq0pydX9B781munOueoSyzR69nlQQjY=", 84 | "owner": "nix-community", 85 | "repo": "poetry2nix", 86 | "rev": "585f19cce38a7f75d5bc567b17060ec45bc63ed0", 87 | "type": "github" 88 | }, 89 | "original": { 90 | "owner": "nix-community", 91 | "repo": "poetry2nix", 92 | "type": "github" 93 | } 94 | }, 95 | "root": { 96 | "inputs": { 97 | "flake-utils": "flake-utils", 98 | "nixpkgs": "nixpkgs", 99 | "transpire": "transpire" 100 | } 101 | }, 102 | "systems": { 103 | "locked": { 104 | "lastModified": 1681028828, 105 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 106 | "owner": "nix-systems", 107 | "repo": "default", 108 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 109 | "type": "github" 110 | }, 111 | "original": { 112 | "owner": "nix-systems", 113 | "repo": "default", 114 | "type": "github" 115 | } 116 | }, 117 | "systems_2": { 118 | "locked": { 119 | "lastModified": 1681028828, 120 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 121 | "owner": "nix-systems", 122 | "repo": "default", 123 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 124 | "type": "github" 125 | }, 126 | "original": { 127 | "owner": "nix-systems", 128 | "repo": "default", 129 | "type": "github" 130 | } 131 | }, 132 | "systems_3": { 133 | "locked": { 134 | "lastModified": 1681028828, 135 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 136 | "owner": "nix-systems", 137 | "repo": "default", 138 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 139 | "type": "github" 140 | }, 141 | "original": { 142 | "owner": "nix-systems", 143 | "repo": "default", 144 | "type": "github" 145 | } 146 | }, 147 | "transpire": { 148 | "inputs": { 149 | "flake-utils": "flake-utils_2", 150 | "nixpkgs": [ 151 | "nixpkgs" 152 | ], 153 | "poetry2nix": "poetry2nix" 154 | }, 155 | "locked": { 156 | "lastModified": 1697415752, 157 | "narHash": "sha256-g5a3PYyx/s7vt1sXSa8tcYDq1Kj9kwoXJ9y4sBF8Cyc=", 158 | "owner": "ocf", 159 | "repo": "transpire", 160 | "rev": "dbeecaf566b790764181f370361a971a14510bba", 161 | "type": "github" 162 | }, 163 | "original": { 164 | "owner": "ocf", 165 | "repo": "transpire", 166 | "type": "github" 167 | } 168 | } 169 | }, 170 | "root": "root", 171 | "version": 7 172 | } 173 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "A very basic flake"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | transpire = { 8 | url = "github:ocf/transpire"; 9 | inputs.nixpkgs.follows = "nixpkgs"; 10 | }; 11 | }; 12 | 13 | outputs = { self, nixpkgs, flake-utils, transpire }: 14 | flake-utils.lib.eachDefaultSystem (system: 15 | let 16 | pkgs = import nixpkgs { inherit system; }; 17 | in 18 | { 19 | devShells.default = pkgs.mkShell { 20 | packages = [ transpire.packages.${system}.transpire ]; 21 | }; 22 | }); 23 | } 24 | --------------------------------------------------------------------------------