Skip to content
This repository was archived by the owner on Apr 20, 2023. It is now read-only.

Commit bccdbee

Browse files
committed
Metaflow UI Cloudformation nested stack support.
1 parent 4b31681 commit bccdbee

File tree

3 files changed

+352
-0
lines changed

3 files changed

+352
-0
lines changed

aws/cloudformation/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,37 @@ Did you choose to enable "APIBasicAuth" and/or "CustomRole" and are wondering ho
4949
2. From the AWS Console, navigate to "Services" and select "API Gateway" from "Networking & Content Delivery" (or search for it in the search bar). Click your API, select "API Keys" from the left side, select the API that corresponds to your Stack name, and click "show" next to "API Key".
5050

5151
- **CustomRole** - This template can create an optional role that can be assumed by users (or applications) that includes limited permissions to only the resources required by Metaflow, including access only to the Amazon S3 bucket, AWS Batch Compute Environment, and Amazon Sagemaker Notebook Instance created by this template. You will, however, need to modify the trust policy for the role to grant access to the principals (users/roles/accounts) who will assume it, and you'll also need to have your users configure an appropriate role-assumption profile. The ARN of the Custom Role can be found in the "Output" tab of the CloudFormation stack under `MetaflowUserRoleArn`. To modify the trust policy to allow new principals, follow the directions [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/roles-managingrole-editing-console.html#roles-managingrole_edit-trust-policy). Once you've granted access to the principals of your choice, have your users create a new Profile for the AWS CLI that assumes the role ARN by following the directions [here](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-role.html).
52+
53+
### Optional Metaflow User Interface (`EnableUI` -parameter)
54+
55+
Please note: This section can be ignored if `EnableUI` -parameter is disabled.
56+
57+
User Interface is provided as a CloudFormation Nested Stack, which means that the UI components are separated to its own template and are deployed to separate CloudFormation Stack. All Nested Stack operations should be initiated from the root stack.
58+
59+
While working with Nested Stacks, templates need to be packaged before deployment. For this AWS Command Line Interface can be used. Packaging step is only required when deploying Metaflow User Interface Nested Stack.
60+
61+
Prerequisites for User Interface enabled Nested Stack deployment:
62+
63+
* **AWS Command Line Interface** - Installed and configured.
64+
* **S3 bucket to upload CloudFormation templates to** - Nested Stacks require templates referred by absolute S3 paths.
65+
66+
Packaging step is required in order to deploy templates with Nested Stacks:
67+
68+
```bash
69+
$ aws cloudformation package \
70+
--s3-bucket manually-created-s3-bucket \
71+
--template-file metaflow-cfn-template.yml \
72+
--output-template-file metaflow-cfn-template.yml.package
73+
```
74+
75+
Above package command does the following:
76+
77+
1. Nested Stack template is uploaded to S3 bucket
78+
1. Defined by `--s3-bucket` parameter (this bucket should already exist and needs to be created manually)
79+
2. Root template is transformed so that all local paths are converted to absolute S3 paths
80+
1. Input file is defined by `--template-file` parameter
81+
3. Transformed and packaged root template is placed in local file system defined by `--output-template-file` parameter
82+
83+
Once this package step is completed, you can follow the [AWS CloudFormation Deployment](https://admin-docs.metaflow.org/metaflow-on-aws/deployment-guide/aws-cloudformation-deployment#steps-for-aws-cloudformation-deployment). Please note: instead of using the non-packaged template, you should use the packaged template `metaflow-cfn-template.yml.package` you just created.
84+
85+
Read more about Cloudformation and working with nested stacks [here](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-nested-stacks.html).

aws/cloudformation/metaflow-cfn-template.yml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ Parameters:
6464
Default: 'aws'
6565
AllowedValues: ['aws', 'aws-us-gov']
6666
Description: 'IAM Partition (Select aws-us-gov for AWS GovCloud, otherwise leave as is)'
67+
EnableUI:
68+
Type: String
69+
Default: 'false'
70+
AllowedValues: ['false', 'true']
71+
Description: 'Enable Metaflow UI'
6772

6873
Mappings:
6974
ServiceInfo:
@@ -92,6 +97,7 @@ Conditions:
9297
EnableRole: !Equals [ !Ref CustomRole, 'true']
9398
EnableSagemaker: !Equals [ !Ref EnableSagemaker, 'true']
9499
IsGov: !Equals [ !Ref IAMPartition, 'aws-us-gov']
100+
EnableUI: !Equals [ !Ref EnableUI, 'true']
95101
Resources:
96102
VPC:
97103
Type: AWS::EC2::VPC
@@ -1330,6 +1336,38 @@ Resources:
13301336
TimeToLiveSpecification:
13311337
AttributeName: ttl
13321338
Enabled: true
1339+
1340+
# Metaflow UI Nested stack
1341+
1342+
MetaflowUI:
1343+
Condition: 'EnableUI'
1344+
DependsOn:
1345+
- VPC
1346+
- Subnet1
1347+
- Subnet2
1348+
- FargateSecurityGroup
1349+
- ECSCluster
1350+
- ECSTaskExecutionRole
1351+
- MetadataSvcECSTaskRole
1352+
- RDSMasterInstance
1353+
- MyRDSSecret
1354+
Type: AWS::CloudFormation::Stack
1355+
Properties:
1356+
TemplateURL: ui/metaflow-ui-cfn-template.yml
1357+
Parameters:
1358+
VPCId: !Ref VPC
1359+
VPCCidrBlock: !GetAtt 'VPC.CidrBlock'
1360+
SubnetIds: !Join [',', [ !Ref Subnet1, !Ref Subnet2 ]]
1361+
SecurityGroup: !Ref FargateSecurityGroup
1362+
ECSClusterId: !Ref ECSCluster
1363+
ECSTaskExecutionRoleId: !Ref ECSTaskExecutionRole
1364+
MetadataSvcECSTaskRoleArn: !GetAtt 'MetadataSvcECSTaskRole.Arn'
1365+
DatabaseHost: !GetAtt 'RDSMasterInstance.Endpoint.Address'
1366+
DatabasePort: "5432"
1367+
DatabaseUser: "master"
1368+
DatabasePassword: !Join ['', ['{{resolve:secretsmanager:', !Ref MyRDSSecret, ':SecretString:password}}' ]]
1369+
DatabaseName: "metaflow"
1370+
13331371
Outputs:
13341372
MetaflowDataStoreS3Url:
13351373
Description: Amazon S3 URL for Metaflow DataStore [METAFLOW_DATASTORE_SYSROOT_S3]
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
AWSTemplateFormatVersion: "2010-09-09"
2+
Description: Nested stack for deployment of Metaflow UI (last-updated-date 09/24/2021)
3+
4+
Parameters:
5+
VPCId:
6+
Type: AWS::EC2::VPC::Id
7+
Description: "VPC Id"
8+
VPCCidrBlock:
9+
Type: String
10+
Description: "VPC CIDR block"
11+
SubnetIds:
12+
Type: List<AWS::EC2::Subnet::Id>
13+
Description: "List of VPC subnet Ids"
14+
SecurityGroup:
15+
Type: AWS::EC2::SecurityGroup::Id
16+
Description: "Security group"
17+
ECSClusterId:
18+
Type: String
19+
Description: "ECS cluster Id"
20+
ECSTaskExecutionRoleId:
21+
Type: String
22+
Description: "ECS task execution IAM role Id"
23+
MetadataSvcECSTaskRoleArn:
24+
Type: String
25+
Description: "ECS service IAM role ARN"
26+
DatabaseHost:
27+
Type: String
28+
Description: "Database hostname"
29+
DatabasePort:
30+
Type: String
31+
Default: "5432"
32+
Description: "Database port"
33+
DatabaseUser:
34+
Type: String
35+
Default: "master"
36+
Description: "Database user"
37+
DatabasePassword:
38+
Type: String
39+
Description: "Database password"
40+
DatabaseName:
41+
Type: String
42+
Default: "metaflow"
43+
Description: "Database name"
44+
45+
Mappings:
46+
ServiceInfoUI:
47+
ServiceName:
48+
value: "metaflow-ui-service"
49+
ImageUrl:
50+
value: "netflixoss/metaflow_metadata_service"
51+
ContainerPort:
52+
value: 8083
53+
ContainerCpu:
54+
value: 512
55+
ContainerMemory:
56+
value: 1024
57+
DesiredCount:
58+
value: 1
59+
60+
Resources:
61+
# Ingress rules
62+
63+
IngressRuleUIService:
64+
Type: "AWS::EC2::SecurityGroupIngress"
65+
Properties:
66+
Description: "Allow API Calls Internally (UI service)"
67+
GroupId: !Ref SecurityGroup
68+
IpProtocol: tcp
69+
FromPort: !FindInMap ["ServiceInfoUI", "ContainerPort", "value"]
70+
ToPort: !FindInMap ["ServiceInfoUI", "ContainerPort", "value"]
71+
CidrIp: !Ref VPCCidrBlock
72+
73+
# Load balancer
74+
75+
ALBUISecurityGroup:
76+
Type: AWS::EC2::SecurityGroup
77+
Properties:
78+
GroupDescription: Access to the public facing load balancer
79+
VpcId: !Ref VPCId
80+
SecurityGroupIngress:
81+
# Allow access to ALB from anywhere on the internet
82+
- CidrIp: 0.0.0.0/0
83+
IpProtocol: -1
84+
ALBUI:
85+
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
86+
Properties:
87+
Scheme: internet-facing
88+
Type: application
89+
Subnets: !Ref SubnetIds
90+
SecurityGroups: [!Ref ALBUISecurityGroup]
91+
ALBUIListener:
92+
Type: AWS::ElasticLoadBalancingV2::Listener
93+
DependsOn:
94+
- ALBUI
95+
Properties:
96+
DefaultActions:
97+
- TargetGroupArn: !Ref ALBUITargetGroupUIService
98+
Type: "forward"
99+
LoadBalancerArn: !Ref ALBUI
100+
Port: 80
101+
Protocol: HTTP
102+
ALBUITargetGroupUIService:
103+
Type: AWS::ElasticLoadBalancingV2::TargetGroup
104+
Properties:
105+
HealthCheckIntervalSeconds: 6
106+
HealthCheckPath: /ping
107+
HealthCheckProtocol: HTTP
108+
HealthCheckTimeoutSeconds: 5
109+
HealthyThresholdCount: 2
110+
TargetType: ip
111+
Port: !FindInMap ["ServiceInfoUI", "ContainerPort", "value"]
112+
Protocol: HTTP
113+
UnhealthyThresholdCount: 2
114+
VpcId: !Ref VPCId
115+
116+
# Coudfront distribution
117+
118+
CloudFrontDistribution:
119+
Type: AWS::CloudFront::Distribution
120+
DependsOn:
121+
- ALBUI
122+
Properties:
123+
DistributionConfig:
124+
Origins:
125+
- DomainName: !GetAtt "ALBUI.DNSName"
126+
Id: !Ref ALBUI
127+
CustomOriginConfig:
128+
HTTPPort: 80
129+
OriginProtocolPolicy: http-only
130+
OriginKeepaliveTimeout: 60
131+
OriginReadTimeout: 30
132+
OriginSSLProtocols:
133+
- TLSv1
134+
- TLSv1.1
135+
- TLSv1.2
136+
- SSLv3
137+
Enabled: true
138+
HttpVersion: http2
139+
DefaultCacheBehavior:
140+
AllowedMethods:
141+
- GET
142+
- HEAD
143+
- DELETE
144+
- OPTIONS
145+
- PATCH
146+
- POST
147+
- PUT
148+
Compress: true
149+
DefaultTTL: 0
150+
MinTTL: 0
151+
MaxTTL: 0
152+
SmoothStreaming: false
153+
TargetOriginId: !Ref ALBUI
154+
ForwardedValues:
155+
QueryString: true
156+
Cookies:
157+
Forward: none
158+
ViewerProtocolPolicy: redirect-to-https
159+
CacheBehaviors:
160+
- AllowedMethods:
161+
- GET
162+
- HEAD
163+
- DELETE
164+
- OPTIONS
165+
- PATCH
166+
- POST
167+
- PUT
168+
Compress: false
169+
PathPattern: /api/*
170+
DefaultTTL: 0
171+
MinTTL: 0
172+
MaxTTL: 0
173+
SmoothStreaming: false
174+
TargetOriginId: !Ref ALBUI
175+
ForwardedValues:
176+
QueryString: true
177+
Cookies:
178+
Forward: none
179+
ViewerProtocolPolicy: redirect-to-https
180+
PriceClass: PriceClass_All
181+
IPV6Enabled: true
182+
183+
# ESC Cluster task definition & service
184+
185+
TaskDefinitionUIService:
186+
Type: AWS::ECS::TaskDefinition
187+
Properties:
188+
Family: !FindInMap ["ServiceInfoUI", "ServiceName", "value"]
189+
Cpu: !FindInMap ["ServiceInfoUI", "ContainerCpu", "value"]
190+
Memory: !FindInMap ["ServiceInfoUI", "ContainerMemory", "value"]
191+
NetworkMode: awsvpc
192+
RequiresCompatibilities:
193+
- FARGATE
194+
ExecutionRoleArn: !Ref ECSTaskExecutionRoleId
195+
TaskRoleArn: !Ref MetadataSvcECSTaskRoleArn
196+
ContainerDefinitions:
197+
- Name: !FindInMap ["ServiceInfoUI", "ServiceName", "value"]
198+
Environment:
199+
- Name: "MF_METADATA_DB_HOST"
200+
Value: !Ref DatabaseHost
201+
- Name: "MF_METADATA_DB_PORT"
202+
Value: !Ref DatabasePort
203+
- Name: "MF_METADATA_DB_USER"
204+
Value: !Ref DatabaseUser
205+
- Name: "MF_METADATA_DB_PSWD"
206+
Value: !Ref DatabasePassword
207+
- Name: "MF_METADATA_DB_NAME"
208+
Value: !Ref DatabaseName
209+
- Name: "UI_ENABLED"
210+
Value: "1"
211+
Cpu: !FindInMap ["ServiceInfoUI", "ContainerCpu", "value"]
212+
Memory: !FindInMap ["ServiceInfoUI", "ContainerMemory", "value"]
213+
Image: !FindInMap ["ServiceInfoUI", "ImageUrl", "value"]
214+
Command:
215+
[
216+
"/opt/latest/bin/python3",
217+
"-m",
218+
"services.ui_backend_service.ui_server",
219+
]
220+
PortMappings:
221+
- ContainerPort:
222+
!FindInMap ["ServiceInfoUI", "ContainerPort", "value"]
223+
LogConfiguration:
224+
LogDriver: awslogs
225+
Options:
226+
awslogs-group:
227+
!Join [
228+
"",
229+
[
230+
"/ecs/",
231+
!Ref "AWS::StackName",
232+
"-",
233+
!FindInMap ["ServiceInfoUI", "ServiceName", "value"],
234+
],
235+
]
236+
awslogs-region: !Ref "AWS::Region"
237+
awslogs-stream-prefix: "ecs"
238+
ServiceLogGroupUIService:
239+
Type: AWS::Logs::LogGroup
240+
Properties:
241+
LogGroupName:
242+
!Join [
243+
"",
244+
[
245+
"/ecs/",
246+
!Ref "AWS::StackName",
247+
"-",
248+
!FindInMap ["ServiceInfoUI", "ServiceName", "value"],
249+
],
250+
]
251+
ECSFargateServiceUIService:
252+
Type: AWS::ECS::Service
253+
DependsOn: ALBUIListener
254+
Properties:
255+
ServiceName: !FindInMap ["ServiceInfoUI", "ServiceName", "value"]
256+
Cluster: !Ref ECSClusterId
257+
LaunchType: FARGATE
258+
DeploymentConfiguration:
259+
MaximumPercent: 200
260+
MinimumHealthyPercent: 75
261+
DesiredCount: !FindInMap ["ServiceInfoUI", "DesiredCount", "value"]
262+
NetworkConfiguration:
263+
AwsvpcConfiguration:
264+
AssignPublicIp: ENABLED
265+
SecurityGroups:
266+
- !Ref SecurityGroup
267+
Subnets: !Ref SubnetIds
268+
TaskDefinition: !Ref TaskDefinitionUIService
269+
LoadBalancers:
270+
- ContainerName: !FindInMap ["ServiceInfoUI", "ServiceName", "value"]
271+
ContainerPort: !FindInMap ["ServiceInfoUI", "ContainerPort", "value"]
272+
TargetGroupArn: !Ref ALBUITargetGroupUIService
273+
274+
Outputs:
275+
ServiceUrl:
276+
Description: "ALB URL for Metadata UI (Open to Public Access)"
277+
Value: !Sub "http://${ALBUI.DNSName}/"
278+
ServiceCloudfrontUrl:
279+
Description: "Cloudfront URL for Metadata UI (Open to Public Access)"
280+
Value: !Sub "https://${CloudFrontDistribution.DomainName}/"

0 commit comments

Comments
 (0)