diff --git a/config/sequencer/config.json b/config/sequencer/config.json new file mode 100644 index 0000000000..5d3a456571 --- /dev/null +++ b/config/sequencer/config.json @@ -0,0 +1,18 @@ +{ + "chain_id": "0x5", + "eth_fee_token_address": "0x6", + "strk_fee_token_address": "0x7", + "components.batcher.execution_mode": "Disabled", + "components.batcher.local_server_config.#is_none": true, + "components.consensus_manager.execution_mode": "Disabled", + "components.gateway.execution_mode": "Disabled", + "components.http_server.execution_mode": "Disabled", + "components.mempool.execution_mode": "Disabled", + "components.mempool_p2p.execution_mode": "Disabled", + "components.consensus_manager.local_server_config.#is_none": true, + "components.gateway.local_server_config.#is_none": true, + "components.http_server.local_server_config.#is_none": true, + "components.mempool.local_server_config.#is_none": true, + "components.mempool_p2p.local_server_config.#is_none": true, + "components.http_server.remote_server_config.#is_none": true +} \ No newline at end of file diff --git a/deployments/images/sequencer/Dockerfile b/deployments/images/sequencer/Dockerfile new file mode 100644 index 0000000000..93f59a18db --- /dev/null +++ b/deployments/images/sequencer/Dockerfile @@ -0,0 +1,31 @@ +# syntax = devthefuture/dockerfile-x + +INCLUDE deployments/images/base/Dockerfile + + +# Compile the sequencer_node crate in release mode, ensuring dependencies are locked. +FROM base AS builder +COPY . . +RUN cargo build --release --package starknet_sequencer_node + +FROM base AS sequencer + +ENV ID=1000 +WORKDIR /app +COPY --from=builder /target/release/starknet_sequencer_node /app/target/release/starknet_sequencer_node +COPY config/ config/ + +# Create a new user "sequencer". +RUN set -ex; \ + addgroup --gid ${ID} sequencer; \ + adduser --ingroup $(getent group ${ID} | cut -d: -f1) --uid ${ID} --gecos "" --disabled-password --home /app sequencer; \ + chown -R sequencer:sequencer /app + +# Expose RPC and monitoring ports. +EXPOSE 8080 8081 8082 + +# Switch to the new user. +USER ${ID} + +# Set the entrypoint to use tini to manage the process. +ENTRYPOINT ["tini", "--", "/app/target/release/starknet_sequencer_node"] diff --git a/deployments/sequencer/Pipfile b/deployments/sequencer/Pipfile index 785709042e..0f2c87ba18 100644 --- a/deployments/sequencer/Pipfile +++ b/deployments/sequencer/Pipfile @@ -6,9 +6,10 @@ verify_ssl = true [dev-packages] [packages] -cdk8s = "~=2.66.2" constructs = "~=10.2.70" jsonschema = "~=4.23.0" +mypy = "*" +cdk8s = "*" [requires] python_version = "3.10" diff --git a/deployments/sequencer/Pipfile.lock b/deployments/sequencer/Pipfile.lock index 73a3e10ec7..929845ea91 100644 --- a/deployments/sequencer/Pipfile.lock +++ b/deployments/sequencer/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "fe6063e14d8160d1576b69892abd3113c32cde648637a35a066ed416048e7ace" + "sha256": "4b91fda4ef8bd983ad69375ba1abdb7384516464b63a87c710a6da60f281867f" }, "pipfile-spec": 6, "requires": { @@ -34,12 +34,12 @@ }, "cdk8s": { "hashes": [ - "sha256:bd3bd68cdcd807a6bb91fa541a4b4e201d18198da61e57aceab0e53d661447d2", - "sha256:c09c35cce4af3d1a56afc62f30ce7a058d8f3de967343e44f4d88db5489e89be" + "sha256:ba4d63e78fd29a98dbf2af739087d686d7dc553e37d002d732a7254e03f69935", + "sha256:c1a262e26b2b0b2a2289d987b8cde2ad659e6b9d3a81b176380d8ae4d5259126" ], "index": "pypi", - "markers": "python_version ~= '3.7'", - "version": "==2.66.13" + "markers": "python_version ~= '3.8'", + "version": "==2.69.15" }, "constructs": { "hashes": [ @@ -91,6 +91,53 @@ "markers": "python_version >= '3.9'", "version": "==2024.10.1" }, + "mypy": { + "hashes": [ + "sha256:0246bcb1b5de7f08f2826451abd947bf656945209b140d16ed317f65a17dc7dc", + "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", + "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f", + "sha256:07de989f89786f62b937851295ed62e51774722e5444a27cecca993fc3f9cd74", + "sha256:100fac22ce82925f676a734af0db922ecfea991e1d7ec0ceb1e115ebe501301a", + "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", + "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b", + "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73", + "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", + "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d", + "sha256:3e38b980e5681f28f033f3be86b099a247b13c491f14bb8b1e1e134d23bb599d", + "sha256:4bde84334fbe19bad704b3f5b78c4abd35ff1026f8ba72b29de70dda0916beb6", + "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca", + "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d", + "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", + "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", + "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a", + "sha256:7029881ec6ffb8bc233a4fa364736789582c738217b133f1b55967115288a2bc", + "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7", + "sha256:7bcb0bb7f42a978bb323a7c88f1081d1b5dee77ca86f4100735a6f541299d8fb", + "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", + "sha256:7f5b7deae912cf8b77e990b9280f170381fdfbddf61b4ef80927edd813163732", + "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80", + "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", + "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", + "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", + "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", + "sha256:a6789be98a2017c912ae6ccb77ea553bbaf13d27605d2ca20a76dfbced631b24", + "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", + "sha256:bde31fc887c213e223bbfc34328070996061b0833b0a4cfec53745ed61f3519b", + "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372", + "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.13.0" + }, + "mypy-extensions": { + "hashes": [ + "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", + "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782" + ], + "markers": "python_version >= '3.5'", + "version": "==1.0.0" + }, "publication": { "hashes": [ "sha256:0248885351febc11d8a1098d5c8e3ab2dabcf3e8c0c96db1e17ecd12b53afbe6", @@ -231,6 +278,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.16.0" }, + "tomli": { + "hashes": [ + "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38", + "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed" + ], + "markers": "python_version >= '3.8'", + "version": "==2.0.2" + }, "typeguard": { "hashes": [ "sha256:00edaa8da3a133674796cf5ea87d9f4b4c367d77476e185e80251cc13dfbb8c4", diff --git a/deployments/sequencer/config/sequencer.py b/deployments/sequencer/config/sequencer.py index 2b2efa5992..d61d3da18f 100644 --- a/deployments/sequencer/config/sequencer.py +++ b/deployments/sequencer/config/sequencer.py @@ -1,30 +1,21 @@ -from typing import Dict, Any import os import json import jsonschema -ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../../') -CONFIG_DIR = os.path.join(ROOT_DIR, 'config/papyrus/') - - -class Config(): - def __init__(self, schema: Dict[Any, Any], config: Dict[Any, Any]): - self.schema = schema - self.config = config +from services.objects import Config - def get(self): - return self.config - def validate(self): - pass +ROOT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), '../../../') +CONFIG_DIR = os.path.join(ROOT_DIR, 'config/papyrus/') class SequencerDevConfig(Config): - def __init__(self): + def __init__(self, mount_path: str): super().__init__( schema=json.loads(open(os.path.join(CONFIG_DIR, 'default_config.json'), 'r').read()), - config=json.loads(open(os.path.join(CONFIG_DIR, 'presets', 'sepolia_testnet.json'), 'r').read()) - ) + config=json.loads(open(os.path.join(CONFIG_DIR, 'presets', 'sepolia_testnet.json'), 'r').read()), + mount_path = mount_path + ) def validate(self): jsonschema.validate(self.config, schema=self.schema) diff --git a/deployments/sequencer/main.py b/deployments/sequencer/main.py index 1a16ae9124..f0f71d8c51 100644 --- a/deployments/sequencer/main.py +++ b/deployments/sequencer/main.py @@ -1,12 +1,15 @@ #!/usr/bin/env python3 -from constructs import Construct -from cdk8s import App, Chart -from typing import Dict, Any -from services.service import Service import dataclasses +from constructs import Construct # type: ignore +from cdk8s import App, Chart, YamlOutputType # type: ignore +from typing import Dict, Any, Optional + +from services.service import Service from config.sequencer import Config, SequencerDevConfig +from services.objects import * +from services import defaults @dataclasses.dataclass @@ -14,48 +17,82 @@ class SystemStructure: topology: str = "mesh" replicas: str = "2" size: str = "small" - config: Config = SequencerDevConfig() + config: Optional[Config] = None def __post_init__(self): self.config.validate() -class SequencerSystem(Chart): +class SequencerNode(Chart): def __init__( self, scope: Construct, name: str, - namespace: str, - system_structure: Dict[str, Dict[str, Any]], + namespace: str ): super().__init__( scope, name, disable_resource_name_hashes=True, namespace=namespace ) - self.mempool = Service( + self.service = Service( self, - "mempool", - image="paulbouwer/hello-kubernetes:1.7", - replicas=2, - config=system_structure.config.get(), - ) - self.batcher = Service(self, "batcher", image="ghost", container_port=2368) - self.sequencer_node = Service( - self, - "sequencer", - image="", - container_port=8082, - startup_probe_path="/monitoring/nodeVersion", - readiness_probe_path="/monitoring/ready", - liveness_probe_path="/monitoring/alive" + "sequencer-node", + image="us.gcr.io/starkware-dev/sequencer-node-test:0.0.1-dev.1", + args=defaults.sequencer.args, + port_mappings=defaults.sequencer.port_mappings, + service_type=defaults.sequencer.service_type, + replicas=defaults.sequencer.replicas, + config=defaults.sequencer.config, + health_check=defaults.sequencer.health_check, + pvc=defaults.sequencer.pvc, + ingress=defaults.sequencer.ingress ) -app = App() -a = SequencerSystem( +# class SequencerSystem(Chart): +# def __init__( +# self, +# scope: Construct, +# name: str, +# namespace: str, +# system_structure: Dict[str, Dict[str, Any]], +# ): +# super().__init__( +# scope, name, disable_resource_name_hashes=True, namespace=namespace +# ) +# self.mempool = Service( +# self, +# "mempool", +# image="paulbouwer/hello-kubernetes:1.7", +# replicas=2, +# config=system_structure.config, +# health_check=defaults.health_check +# ) +# self.batcher = Service( +# self, +# "batcher", +# image="ghost", +# port_mappings=[ +# PortMapping(name="http", port=80, container_port=2368) +# ], +# health_check=defaults.health_check +# ) + + +app = App( + yaml_output_type=YamlOutputType.FOLDER_PER_CHART_FILE_PER_RESOURCE +) + +sequencer_node = SequencerNode( scope=app, - name="sequencer-system", - namespace="test-namespace", - system_structure=SystemStructure(), + name="sequencer-node", + namespace="sequencer-node-test" ) +# a = SequencerSystem( +# scope=app, +# name="sequencer-system", +# namespace="test-namespace", +# system_structure=SystemStructure(config=SequencerDevConfig(mount_path="/app/config")), +# ) + app.synth() diff --git a/deployments/sequencer/references/sequencer-system.k8s.yaml b/deployments/sequencer/references/sequencer-system.k8s.yaml index 4f2aad70bc..f45df3203e 100644 --- a/deployments/sequencer/references/sequencer-system.k8s.yaml +++ b/deployments/sequencer/references/sequencer-system.k8s.yaml @@ -6,10 +6,9 @@ metadata: spec: ports: - port: 80 - targetPort: 8082 + targetPort: 8080 selector: app: sequencer-system-mempool - type: LoadBalancer --- apiVersion: v1 kind: ConfigMap @@ -37,7 +36,7 @@ spec: containers: - image: paulbouwer/hello-kubernetes:1.7 livenessProbe: - failureThreshold: 5 + failureThreshold: 10 httpGet: path: / port: http @@ -45,20 +44,20 @@ spec: timeoutSeconds: 5 name: web ports: - - containerPort: 8082 + - containerPort: 8080 readinessProbe: - failureThreshold: 3 + failureThreshold: 10 httpGet: path: / port: http - periodSeconds: 10 + periodSeconds: 5 timeoutSeconds: 5 startupProbe: - failureThreshold: 12 + failureThreshold: 10 httpGet: path: / port: http - periodSeconds: 10 + periodSeconds: 5 timeoutSeconds: 5 volumes: - configMap: @@ -76,7 +75,6 @@ spec: targetPort: 2368 selector: app: sequencer-system-batcher - type: LoadBalancer --- apiVersion: apps/v1 kind: Deployment @@ -96,7 +94,7 @@ spec: containers: - image: ghost livenessProbe: - failureThreshold: 5 + failureThreshold: 10 httpGet: path: / port: http @@ -106,47 +104,55 @@ spec: ports: - containerPort: 2368 readinessProbe: - failureThreshold: 3 + failureThreshold: 10 httpGet: path: / port: http - periodSeconds: 10 + periodSeconds: 5 timeoutSeconds: 5 startupProbe: - failureThreshold: 12 + failureThreshold: 10 httpGet: path: / port: http - periodSeconds: 10 + periodSeconds: 5 timeoutSeconds: 5 --- apiVersion: v1 kind: Service metadata: - name: sequencer-system-sequencer-service + name: sequencer-system-sequencer-node-service namespace: test-namespace spec: ports: - port: 80 targetPort: 8082 selector: - app: sequencer-system-sequencer - type: LoadBalancer + app: sequencer-system-sequencer-node + type: ClusterIP +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: sequencer-system-sequencer-node-config + namespace: test-namespace +data: + config: '{"chain_id": "SN_SEPOLIA", "starknet_url": "https://alpha-sepolia.starknet.io/", "base_layer.starknet_contract_address": "0xe2bb56ee936fd6433dc0f6e7e3b8365c906aa057"}' --- apiVersion: apps/v1 kind: Deployment metadata: - name: sequencer-system-sequencer-deployment + name: sequencer-system-sequencer-node-deployment namespace: test-namespace spec: replicas: 1 selector: matchLabels: - app: sequencer-system-sequencer + app: sequencer-system-sequencer-node template: metadata: labels: - app: sequencer-system-sequencer + app: sequencer-system-sequencer-node spec: containers: - image: "" @@ -155,22 +161,26 @@ spec: httpGet: path: /monitoring/alive port: http - periodSeconds: 5 + periodSeconds: 10 timeoutSeconds: 5 name: web ports: - containerPort: 8082 readinessProbe: - failureThreshold: 3 + failureThreshold: 5 httpGet: path: /monitoring/ready port: http periodSeconds: 10 timeoutSeconds: 5 startupProbe: - failureThreshold: 12 + failureThreshold: 10 httpGet: - path: /monitoring/nodeVersion + path: /monitoring/NodeVersion port: http periodSeconds: 10 timeoutSeconds: 5 + volumes: + - configMap: + name: sequencer-system-sequencer-node-config + name: sequencer-system-sequencer-node-config diff --git a/deployments/sequencer/services/defaults.py b/deployments/sequencer/services/defaults.py new file mode 100644 index 0000000000..9349e6a406 --- /dev/null +++ b/deployments/sequencer/services/defaults.py @@ -0,0 +1,78 @@ +import dataclasses + +from typing import Sequence, Optional, List +from services.objects import * +from config.sequencer import * + + +@dataclasses.dataclass +class ServiceDefaults: + image: Optional[str] | None = None + replicas: Optional[int] = 1 + service_type: Optional[ServiceType] | None = None + port_mappings: Optional[Sequence[PortMapping]] | None = None + health_check: Optional[HealthCheck] | None = None + pvc: Optional[PersistentVolumeClaim] | None = None + ingress: Optional[Ingress] | None = None + config: Optional[Config] | None = None + args: Optional[List[str]] | None = None + + +sequencer = ServiceDefaults( + replicas=1, + config=SequencerDevConfig(mount_path="/app/config"), + service_type=ServiceType.CLUSTER_IP, + args=["--config_file", "/app/config/sequencer/config.json"], + port_mappings=[ + PortMapping(name="http", port=80, container_port=8080), + PortMapping(name="rpc", port=8081, container_port=8081), + PortMapping(name="monitoring", port=8082, container_port=8082) + ], + health_check=HealthCheck( + startup_probe=Probe(port=8082, path="/monitoring/nodeVersion", period_seconds=10, failure_threshold=10, timeout_seconds=5), + readiness_probe=Probe(port=8082, path="/monitoring/ready", period_seconds=10, failure_threshold=5, timeout_seconds=5), + liveness_probe=Probe(port=8082, path="/monitoring/alive", period_seconds=10, failure_threshold=5, timeout_seconds=5) + ), + pvc=PersistentVolumeClaim( + access_modes=["ReadWriteOnce"], + storage_class_name="premium-rwo", + volume_mode="Filesystem", + storage="256Gi", + mount_path="/data", + read_only=False + ), + ingress=Ingress( + annotations={ + "kubernetes.io/tls-acme": "true", + "cert-manager.io/common-name": "sequencer.gcp-integration.sw-dev.io", + "cert-manager.io/issue-temporary-certificate": "true", + "cert-manager.io/issuer": "letsencrypt-prod", + "acme.cert-manager.io/http01-edit-in-place": "true" + }, + class_name="gce", + rules=[ + IngressRule( + host="sequencer.gcp-integration.sw-dev.io", + paths=[ + IngressRuleHttpPath( + path="/monitoring/", + path_type="Prefix", + backend_service_name="sequencer-node-service", + backend_service_port_name="monitoring", + backend_service_port_number=8082 + ) + ] + ) + ], + tls=[ + IngressTls( + hosts=[ + "sequencer.gcp-integration.sw-dev.io" + ], + secret_name="sequencer-tls" + ) + ] + ) +) + + diff --git a/deployments/sequencer/services/objects.py b/deployments/sequencer/services/objects.py new file mode 100644 index 0000000000..5db99dcd15 --- /dev/null +++ b/deployments/sequencer/services/objects.py @@ -0,0 +1,90 @@ +import dataclasses + +from typing import Optional, Dict, Any, Mapping, Sequence +from enum import Enum + + +@dataclasses.dataclass +class Probe: + port: int | str + path: str + period_seconds: int + failure_threshold: int + timeout_seconds: int + + def __post_init__(self): + assert not isinstance(self.port, (bool)), \ + "Port must be of type int or str, not bool." + + +@dataclasses.dataclass +class HealthCheck: + startup_probe: Optional[Probe] | None = None + readiness_probe: Optional[Probe] | None = None + liveness_probe: Optional[Probe] | None = None + + +@dataclasses.dataclass +class ServiceType(Enum): + CLUSTER_IP = "ClusterIP" + LOAD_BALANCER = "LoadBalancer" + NODE_PORT = "NodePort" + + +@dataclasses.dataclass +class PersistentVolumeClaim: + storage_class_name: str | None + access_modes: list[str] | None + volume_mode: str | None + storage: str | None + read_only: bool | None + mount_path: str | None + + +@dataclasses.dataclass +class Config: + schema: Dict[Any, Any] + config: Dict[Any, Any] + mount_path: str + + def get(self): + return self.config + + def validate(self): + pass + + +@dataclasses.dataclass +class PortMapping: + name: str + port: int + container_port: int + + +@dataclasses.dataclass +class IngressRuleHttpPath: + path: Optional[str] + path_type: str + backend_service_name: str + backend_service_port_number: int + backend_service_port_name: Optional[str] + + +@dataclasses.dataclass +class IngressRule: + host: str + paths: Sequence[IngressRuleHttpPath] + + +@dataclasses.dataclass +class IngressTls: + hosts: Sequence[str] | None = None + secret_name: str | None = None + + +@dataclasses.dataclass +class Ingress: + annotations: Mapping[str, str] | None + class_name: str | None + rules: Sequence[IngressRule] | None + tls: Sequence[IngressTls] | None diff --git a/deployments/sequencer/services/service.py b/deployments/sequencer/services/service.py index 04e4f29303..5c8f9d618b 100644 --- a/deployments/sequencer/services/service.py +++ b/deployments/sequencer/services/service.py @@ -1,10 +1,12 @@ import json -from typing import Optional, Dict, Union +from typing import Optional, List from constructs import Construct -from cdk8s import Names - +from cdk8s import Names, ApiObjectMetadata from imports import k8s +from imports.com.google import cloud as google + +from services.objects import * class Service(Construct): @@ -15,37 +17,40 @@ def __init__( *, image: str, replicas: int = 1, - port: Optional[int] = 80, - container_port: int = 8082, - config: Optional[Dict[str, str]] = None, - startup_probe_path: Optional[str] = "/", - readiness_probe_path: Optional[str] = "/", - liveness_probe_path: Optional[str] = "/" + service_type: Optional[ServiceType] = None, + port_mappings: Optional[Sequence[PortMapping]] = None, + config: Optional[Config] = None, + health_check: Optional[HealthCheck] = None, + pvc: Optional[PersistentVolumeClaim] = None, + ingress: Optional[Ingress] = None, + args: Optional[List[str]] = None ): super().__init__(scope, id) label = {"app": Names.to_label_value(self, include_hash=False)} - if port is not None: + + if port_mappings is not None: k8s.KubeService( self, "service", spec=k8s.ServiceSpec( - type="LoadBalancer", + type=service_type.value if service_type is not None else None, ports=[ k8s.ServicePort( - port=port, - target_port=k8s.IntOrString.from_number(container_port), - ) + name=port_map.name, + port=port_map.port, + target_port=k8s.IntOrString.from_number(port_map.container_port), + ) for port_map in port_mappings ], - selector=label, + selector=label ), ) if config is not None: - service_config = k8s.KubeConfigMap( + k8s.KubeConfigMap( self, "config", - data=dict(config=json.dumps(config)), + data=dict(config=json.dumps(config.get())), ) k8s.KubeDeployment( @@ -59,53 +64,165 @@ def __init__( spec=k8s.PodSpec( containers=[ k8s.Container( - name="web", + name="sequencer", image=image, - ports=[ - k8s.ContainerPort(container_port=container_port) - ], + args=args or [], + ports=[k8s.ContainerPort(container_port=port_map.container_port) for port_map in port_mappings or []], startup_probe=k8s.Probe( http_get=k8s.HttpGetAction( - port=k8s.IntOrString.from_string("http"), - path=startup_probe_path, + path=health_check.startup_probe.path, + port=k8s.IntOrString.from_string(health_check.startup_probe.port) + if isinstance(health_check.startup_probe.port, str) + else k8s.IntOrString.from_number(health_check.startup_probe.port) ), - period_seconds=10, - failure_threshold=12, - timeout_seconds=5, - ), + period_seconds=health_check.startup_probe.period_seconds, + failure_threshold=health_check.startup_probe.failure_threshold, + timeout_seconds=health_check.startup_probe.timeout_seconds + ) if health_check.startup_probe is not None else None, + readiness_probe=k8s.Probe( http_get=k8s.HttpGetAction( - port=k8s.IntOrString.from_string("http"), - path=readiness_probe_path, + path=health_check.readiness_probe.path, + port=k8s.IntOrString.from_string(health_check.readiness_probe.port) + if isinstance(health_check.readiness_probe.port, str) + else k8s.IntOrString.from_number(health_check.readiness_probe.port) ), - period_seconds=10, - failure_threshold=3, - timeout_seconds=5, - ), + period_seconds=health_check.readiness_probe.period_seconds, + failure_threshold=health_check.readiness_probe.failure_threshold, + timeout_seconds=health_check.readiness_probe.timeout_seconds + ) if health_check.readiness_probe is not None else None, + liveness_probe=k8s.Probe( http_get=k8s.HttpGetAction( - port=k8s.IntOrString.from_string("http"), - path=liveness_probe_path, + path=health_check.liveness_probe.path, + port=k8s.IntOrString.from_string(health_check.liveness_probe.port) + if isinstance(health_check.liveness_probe.port, str) + else k8s.IntOrString.from_number(health_check.liveness_probe.port) ), - period_seconds=5, - failure_threshold=5, - timeout_seconds=5, - ), + period_seconds=health_check.liveness_probe.period_seconds, + failure_threshold=health_check.liveness_probe.failure_threshold, + timeout_seconds=health_check.liveness_probe.timeout_seconds + ) if health_check.liveness_probe is not None else None, + + volume_mounts=[ + mount for mount in [ + k8s.VolumeMount( + name=f"{self.node.id}-config", + mount_path=config.mount_path, + read_only=True + ) if config is not None else None, + + k8s.VolumeMount( + name=f"{self.node.id}-data", + mount_path=pvc.mount_path, + read_only=True + ) if pvc is not None else None + ] if mount is not None + ] ) ], - volumes=( - [ + volumes=[ + vol for vol in [ k8s.Volume( - name=service_config.name, - config_map=k8s.ConfigMapVolumeSource( - name=service_config.name - ), - ) - ] - if config is not None - else None - ), + name=f"{self.node.id}-config", + config_map=k8s.ConfigMapVolumeSource(name=f"{self.node.id}-config") + ) if config is not None else None, + + k8s.Volume( + name=f"{self.node.id}-data", + persistent_volume_claim=k8s.PersistentVolumeClaimVolumeSource(claim_name=f"{self.node.id}-data", read_only=pvc.read_only) + ) if pvc is not None else None + ] if vol is not None + ] ), ), ), ) + + google.BackendConfig( + self, + "backendconfig", + metadata=ApiObjectMetadata( + name=f"{self.node.id}-backendconfig", + labels=label + ), + spec=google.BackendConfigSpec( + health_check=google.BackendConfigSpecHealthCheck( + check_interval_sec=5, + healthy_threshold=10, + unhealthy_threshold=5, + timeout_sec=5, + request_path="/", + type="http" + ), + iap=google.BackendConfigSpecIap( + enabled=True, + oauthclient_credentials=google.BackendConfigSpecIapOauthclientCredentials( + secret_name="" + ) + ) + ) + ) + + if ingress is not None: + k8s.KubeIngress( + self, + "ingress", + metadata=k8s.ObjectMeta( + name=f"{self.node.id}-ingress", + labels=label, + annotations=ingress.annotations + ), + spec=k8s.IngressSpec( + ingress_class_name=ingress.class_name, + tls=[ + k8s.IngressTls( + hosts=tls.hosts, + secret_name=tls.secret_name + ) + for tls in ingress.tls or [] + ], + rules=[ + k8s.IngressRule( + host=rule.host, + http=k8s.HttpIngressRuleValue( + paths=[ + k8s.HttpIngressPath( + path=path.path, + path_type=path.path_type, + backend=k8s.IngressBackend( + service=k8s.IngressServiceBackend( + name=path.backend_service_name, + port=k8s.ServiceBackendPort( + name=path.backend_service_port_name, + number=path.backend_service_port_number + ) + ) + ) + ) + for path in rule.paths or [] + ] + ) + ) + for rule in ingress.rules or [] + ] + ) + ) + + if pvc is not None: + k8s.KubePersistentVolumeClaim( + self, + "pvc", + metadata=k8s.ObjectMeta( + name=f"{self.node.id}-data", + labels=label + ), + spec=k8s.PersistentVolumeClaimSpec( + storage_class_name=pvc.storage_class_name, + access_modes=pvc.access_modes, + volume_mode=pvc.volume_mode, + resources=k8s.ResourceRequirements( + requests={"storage": k8s.Quantity.from_string(pvc.storage)} + ) + ) + )