def __init__( self, scope: Construct, id: str, cluster: ICluster, ecs_security_group: SecurityGroup, ecs_source_security_group: SecurityGroup, vpc: IVpc, **kwargs, ) -> None: super().__init__(scope, id, **kwargs) global g_nlb Tags.of(self).add("Stack", "Common-Nlb") # TODO -- You need to do some manual actions: # TODO -- 1) enable auto-assign IPv6 address on public subnets # TODO -- 2) add to the Outbound rules of "Live-Common-Nlb/ASG/InstanceSecurityGroup" the destination "::/0" self.private_zone = HostedZone.from_lookup( self, "PrivateZone", domain_name="openttd.internal", private_zone=True, ) user_data = UserData.for_linux(shebang="#!/bin/bash -ex") asset = Asset(self, "NLB", path="user_data/nlb/") user_data.add_commands( "echo 'Extracting user-data files'", "mkdir /nlb", "cd /nlb", ) user_data.add_s3_download_command( bucket=asset.bucket, bucket_key=asset.s3_object_key, local_file="/nlb/files.zip", ) user_data.add_commands("unzip files.zip", ) user_data.add_commands( "echo 'Setting up configuration'", f"echo '{self.region}' > /etc/.region", f"echo '{cluster.cluster_name}' > /etc/.cluster", ) user_data.add_commands( "echo 'Installing nginx'", "amazon-linux-extras install epel", "yum install nginx -y", "cp /nlb/nginx.conf /etc/nginx/nginx.conf", "mkdir /etc/nginx/nlb.d", ) user_data.add_commands( "echo 'Installing Python3'", "yum install python3 -y", "python3 -m venv /venv", "/venv/bin/pip install -r /nlb/requirements.txt", ) user_data.add_commands( "echo 'Generating nginx configuration'", "cd /etc/nginx/nlb.d", "/venv/bin/python /nlb/nginx.py", "systemctl start nginx", ) user_data.add_commands( "echo 'Setting up SOCKS proxy'", "useradd pproxy", "cp /nlb/pproxy.service /etc/systemd/system/", "systemctl daemon-reload", "systemctl enable pproxy.service", "systemctl start pproxy.service", ) asg = AutoScalingGroup( self, "ASG", vpc=vpc, instance_type=InstanceType("t3a.nano"), machine_image=MachineImage.latest_amazon_linux( generation=AmazonLinuxGeneration.AMAZON_LINUX_2), min_capacity=2, vpc_subnets=SubnetSelection(subnet_type=SubnetType.PUBLIC, one_per_az=True), user_data=user_data, health_check=HealthCheck.elb(grace=Duration.seconds(0)), ) asg.add_security_group(ecs_security_group) asg.role.add_managed_policy( ManagedPolicy.from_aws_managed_policy_name( "AmazonSSMManagedInstanceCore")) asset.grant_read(asg.role) policy = ManagedPolicy(self, "Policy") policy_statement = PolicyStatement( actions=[ "ec2:DescribeInstances", "ecs:DescribeContainerInstances", "ecs:DescribeTasks", "ecs:ListContainerInstances", "ecs:ListServices", "ecs:ListTagsForResource", "ecs:ListTasks", ], resources=["*"], ) policy.add_statements(policy_statement) asg.role.add_managed_policy(policy) # We could also make an additional security-group and add that to # the ASG, but it keeps adding up. This makes it a tiny bit # easier to get an overview what traffic is allowed from the # console on AWS. assert isinstance(asg.node.children[0], SecurityGroup) self.security_group = asg.node.children[0] listener_https.add_targets( subdomain_name=self.admin_subdomain_name, port=80, target=asg, priority=2, ) # Create a Security Group so the lambdas can access the EC2. # This is needed to check if the EC2 instance is fully booted. lambda_security_group = SecurityGroup( self, "LambdaSG", vpc=vpc, ) self.security_group.add_ingress_rule( peer=lambda_security_group, connection=Port.tcp(80), description="Lambda to target", ) self.security_group.add_ingress_rule( peer=ecs_source_security_group, connection=Port.udp(8080), description="ECS to target", ) self.create_ecs_lambda( cluster=cluster, auto_scaling_group=asg, ) self.create_asg_lambda( lifecycle_transition=LifecycleTransition.INSTANCE_LAUNCHING, timeout=Duration.seconds(180), vpc=vpc, security_group=lambda_security_group, auto_scaling_group=asg, ) self.create_asg_lambda( lifecycle_transition=LifecycleTransition.INSTANCE_TERMINATING, timeout=Duration.seconds(30), vpc=vpc, security_group=lambda_security_group, auto_scaling_group=asg, ) # Initialize the NLB record on localhost, as we need to be able to # reference it for other entries to work correctly. ARecord( self, "ARecord", target=RecordTarget.from_ip_addresses("127.0.0.1"), zone=dns.get_hosted_zone(), record_name=self.subdomain_name, ttl=Duration.seconds(60), ) AaaaRecord( self, "AAAARecord", target=RecordTarget.from_ip_addresses("::1"), zone=dns.get_hosted_zone(), record_name=self.subdomain_name, ttl=Duration.seconds(60), ) # To make things a bit easier, also alias to staging. self.create_alias(self, "nlb.staging") # Create a record for the internal DNS ARecord( self, "APrivateRecord", target=RecordTarget.from_ip_addresses("127.0.0.1"), zone=self.private_zone, record_name=self.subdomain_name, ttl=Duration.seconds(60), ) if g_nlb is not None: raise Exception("Only a single NlbStack instance can exist") g_nlb = self
def __init__(self, scope: Construct, id: str, *, deployment: Deployment, policy: Policy, cluster: ICluster, **kwargs) -> None: super().__init__(scope, id, **kwargs) Tags.of(self).add("Application", self.application_name) Tags.of(self).add("Deployment", deployment.value) policy.add_stack(self) if deployment == Deployment.PRODUCTION: desired_count = 2 priority = 61 master_port = 3978 dynamodb_prefix = "P-" else: desired_count = 1 priority = 161 master_port = 4978 dynamodb_prefix = "S-" sentry_dsn = parameter_store.add_secure_string( f"/MasterServer/{deployment.value}/SentryDSN").parameter self.container = ECSHTTPSContainer( self, self.application_name, subdomain_name=self.subdomain_name, deployment=deployment, policy=policy, application_name=self.application_name, image_name="ghcr.io/openttd/master-server", port=80, memory_limit_mib=64, desired_count=desired_count, cluster=cluster, priority=priority, command=[ "--app", "master_server", "--bind", "0.0.0.0", "--msu-port", str(master_port), "--db", "dynamodb", "--dynamodb-region", self.region, "--dynamodb-prefix", dynamodb_prefix, "--proxy-protocol", "--socks-proxy", "socks5://nlb.openttd.internal:8080", ], environment={ "MASTER_SERVER_SENTRY_ENVIRONMENT": deployment.value.lower(), }, secrets={ "MASTER_SERVER_SENTRY_DSN": Secret.from_ssm_parameter(sentry_dsn), }, ) table_and_index = [] for table in ("S-MSU-ip-port", "S-MSU-server", "P-MSU-ip-port", "P-MSU-server"): table_and_index.extend([ f"arn:aws:dynamodb:{self.region}:{self.account}:table/{table}", f"arn:aws:dynamodb:{self.region}:{self.account}:table/{table}/index/online_view", f"arn:aws:dynamodb:{self.region}:{self.account}:table/{table}/index/time_last_seen_view", ]) self.container.task_role.add_to_policy( PolicyStatement( actions=[ "dynamodb:CreateTable", "dynamodb:UpdateTimeToLive", "dynamodb:PutItem", "dynamodb:DescribeTable", "dynamodb:ListTables", "dynamodb:GetItem", "dynamodb:Query", "dynamodb:UpdateItem", ], resources=table_and_index, )) self.container.add_udp_port(master_port) nlb.add_nlb(self, self.container.service, Port.udp(master_port), self.nlb_subdomain_name, "Master Server")