AWSTemplateFormatVersion: 2010-09-09 Description: >- Creates VPC and subnets for SageMaker training. It also creates an EFS file-system for SageMaker training input. Finally, it creates a SageMaker notebook instance that mounts the EFS file-system for staging Training data on EFS file-system. Parameters: VpcCIDR: Default: 172.30.0.0/25 Description: Vpc CIDR AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})' Type: String PublicSubnetCIDR: Default: 172.30.0.0/27 Description: Public Subnet CIDR in Vpc CIDR AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})' Type: String PrivateSubnet1CIDR: Default: 172.30.0.32/27 Description: Private Subnet1 CIDR in Vpc CIDR AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})' Type: String PrivateSubnet2CIDR: Default: 172.30.0.64/27 Description: Private Subnet2 CIDR in Vpc CIDR AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})' Type: String PrivateSubnet3CIDR: Default: 172.30.0.96/27 Description: Private Subnet2 CIDR in Vpc CIDR AllowedPattern: '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})' Type: String EbsVolumeSize: Default: '100' Description: Notebook instance EBS volume size Type: String AllowedValues: - '100' - '150' - '200' - '250' - '300' - '350' - '400' - '450' - '500' NotebookInstanceType: Description: SageMaker Notebook instance type. Type: String Default: 'ml.m5.2xlarge' AllowedValues: - 'ml.m5.2xlarge' - 'ml.m4.2xlarge' ConstraintDescription: Subset of supported SageMaker Notebook instance types EFSFileSystemId: Description: >- Existing Amazon EFS File System Id, or leave it blank to create a new EFS File System. Type: String AllowedPattern: '(^fs-[0-9a-f]{8,8})$|()$' Default: '' ConstraintDescription: Should be a Valid EFS File System Id EFSSharedDir: Description: >- EFS shared file-system directory Type: String Default: '/' ConstraintDescription: Should be a valid EFS File System absolute directory path EFSMountPoint: Description: >- EFS mount point Type: String Default: '/home/ec2-user/efs' ConstraintDescription: Should be a valid local file-system absolute directory path GitHubUsername: Description: GitHub username Type: String GitHubToken: Description: GitHub Token Type: String CodeRepoName: Description: Code Repository name Type: String Default: sagemaker-github-repo GitHubRepoUrl: Description: GitHub Repository Url Type: String GitHubRepoBranch: Description: GitHub Repository branch Type: String Default: 'master' NotebookInstanceName: Description: SageMaker Notebook instance name Type: String Default: 'sagemaker-notebook' S3BucketName: Description: SageMaker S3 Bucket Name Type: String Conditions: CreateNewFileSystem: !Equals [ !Ref EFSFileSystemId, ''] CreateCodeRepo: !Not [!Equals [ !Ref GitHubRepoUrl, ''] ] CreateGitHubRepoSecret: !And - !Not [ !Equals [!Ref GitHubToken, ''] ] - !Not [ !Equals [!Ref GitHubUsername, ''] ] Resources: Vpc: Type: 'AWS::EC2::VPC' Properties: CidrBlock: !Ref VpcCIDR EnableDnsSupport: 'true' EnableDnsHostnames: 'true' Tags: - Key: Name Value: !Ref 'AWS::StackName' InternetGateway: Type: 'AWS::EC2::InternetGateway' Properties: Tags: - Key: Network Value: Public - Key: Name Value: !Ref 'AWS::StackName' GatewayToInternet: Type: 'AWS::EC2::VPCGatewayAttachment' Properties: VpcId: !Ref Vpc InternetGatewayId: !Ref InternetGateway PublicSubnet: Type: 'AWS::EC2::Subnet' Properties: VpcId: !Ref Vpc AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" CidrBlock: !Ref PublicSubnetCIDR Tags: - Key: Network Value: Public - Key: Name Value: !Ref 'AWS::StackName' PrivateSubnet1: Type: 'AWS::EC2::Subnet' Properties: VpcId: !Ref Vpc AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" CidrBlock: !Ref PrivateSubnet1CIDR Tags: - Key: Network Value: Private - Key: Name Value: !Ref 'AWS::StackName' PrivateSubnet2: Type: 'AWS::EC2::Subnet' Properties: VpcId: !Ref Vpc AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: "" CidrBlock: !Ref PrivateSubnet2CIDR Tags: - Key: Network Value: Private - Key: Name Value: !Ref 'AWS::StackName' PrivateSubnet3: Type: 'AWS::EC2::Subnet' Properties: VpcId: !Ref Vpc AvailabilityZone: Fn::Select: - 2 - Fn::GetAZs: "" CidrBlock: !Ref PrivateSubnet3CIDR Tags: - Key: Network Value: Private - Key: Name Value: !Ref 'AWS::StackName' NATGatewayEIP: Type: 'AWS::EC2::EIP' Properties: Domain: vpc NATGateway: Type: 'AWS::EC2::NatGateway' DependsOn: GatewayToInternet Properties: AllocationId: !GetAtt - NATGatewayEIP - AllocationId SubnetId: !Ref PublicSubnet PublicRouteTable: Type: 'AWS::EC2::RouteTable' DependsOn: GatewayToInternet Properties: VpcId: !Ref Vpc Tags: - Key: Network Value: Public - Key: Name Value: !Ref 'AWS::StackName' PublicRoute: Type: 'AWS::EC2::Route' Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PublicSubnetRouteAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' DependsOn: [ PublicRoute ] Properties: SubnetId: !Ref PublicSubnet RouteTableId: !Ref PublicRouteTable PrivateRouteTable: Type: 'AWS::EC2::RouteTable' Properties: VpcId: !Ref Vpc Tags: - Key: Network Value: Private - Key: Name Value: !Ref 'AWS::StackName' PrivateRoute: Type: 'AWS::EC2::Route' DependsOn: [ NATGateway ] Properties: RouteTableId: !Ref PrivateRouteTable DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NATGateway PrivateSubnet1RouteAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' DependsOn: [ PrivateRoute ] Properties: SubnetId: !Ref PrivateSubnet1 RouteTableId: !Ref PrivateRouteTable PrivateSubnet2RouteAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' DependsOn: [ PrivateRoute ] Properties: SubnetId: !Ref PrivateSubnet2 RouteTableId: !Ref PrivateRouteTable PrivateSubnet3RouteAssociation: Type: 'AWS::EC2::SubnetRouteTableAssociation' DependsOn: [ PrivateRoute ] Properties: SubnetId: !Ref PrivateSubnet3 RouteTableId: !Ref PrivateRouteTable VpcS3Endpoint: Type: AWS::EC2::VPCEndpoint Properties: PolicyDocument: Version: 2012-10-17 Statement: - Effect: "Allow" Principal: "*" Action: - "s3:*" Resource: "*" RouteTableIds: - !Ref PrivateRouteTable ServiceName: !Sub com.amazonaws.${AWS::Region}.s3 VpcId: !Ref Vpc FileSystem: Type: 'AWS::EFS::FileSystem' Condition: CreateNewFileSystem DeletionPolicy: Retain Properties: PerformanceMode: generalPurpose FileSystemTags: - Key: Name Value: !Ref 'AWS::StackName' VpcSecurityGroup: Type: 'AWS::EC2::SecurityGroup' Properties: GroupDescription: Security group VpcId: !Ref Vpc Tags: - Key: Name Value: !Ref 'AWS::StackName' VpcSecurityGroupIngress: Type: 'AWS::EC2::SecurityGroupIngress' Properties: GroupId: !GetAtt VpcSecurityGroup.GroupId IpProtocol: tcp FromPort: 0 ToPort: 65535 SourceSecurityGroupId: !GetAtt VpcSecurityGroup.GroupId MountTarget1: Type: 'AWS::EFS::MountTarget' DependsOn: [ PrivateSubnet1RouteAssociation, VpcSecurityGroupIngress ] Properties: FileSystemId: !If - CreateNewFileSystem - !Ref FileSystem - !Ref EFSFileSystemId SubnetId: !Ref PrivateSubnet1 SecurityGroups: - !Ref VpcSecurityGroup MountTarget2: Type: 'AWS::EFS::MountTarget' DependsOn: [ PrivateSubnet2RouteAssociation, VpcSecurityGroupIngress ] Properties: FileSystemId: !If - CreateNewFileSystem - !Ref FileSystem - !Ref EFSFileSystemId SubnetId: !Ref PrivateSubnet2 SecurityGroups: - !Ref VpcSecurityGroup MountTarget3: Type: 'AWS::EFS::MountTarget' DependsOn: [ PrivateSubnet3RouteAssociation, VpcSecurityGroupIngress ] Properties: FileSystemId: !If - CreateNewFileSystem - !Ref FileSystem - !Ref EFSFileSystemId SubnetId: !Ref PrivateSubnet3 SecurityGroups: - !Ref VpcSecurityGroup GitHubRepoSecret: Type: 'AWS::SecretsManager::Secret' Condition: CreateGitHubRepoSecret Properties: Description: GitHub Sign In SecretString: Fn::Join: - '' - - '{' - '"username":' - !Ref GitHubUsername - ',' - '"password":' - !Ref GitHubToken - '}' Tags: - Key: Name Value: !Ref 'AWS::StackName' CodeRepo: Type: AWS::SageMaker::CodeRepository Condition: CreateCodeRepo Properties: CodeRepositoryName: !Ref CodeRepoName GitConfig: Branch: !Ref GitHubRepoBranch RepositoryUrl: !Ref GitHubRepoUrl SecretArn: !If - CreateGitHubRepoSecret - !Ref GitHubRepoSecret - !Ref 'AWS::NoValue' NotebookStartConfig: Type: AWS::SageMaker::NotebookInstanceLifecycleConfig Properties: OnStart: - Content: !Base64 'Fn::Join': - '' - - '#!/bin/bash' - |+ - set -e - |+ - mkdir -p - ' ' - !Ref EFSMountPoint - |+ - sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2 - ' ' - !If - CreateNewFileSystem - !Ref FileSystem - !Ref EFSFileSystemId - .efs. - !Ref 'AWS::Region' - '.amazonaws.com:' - !Ref EFSSharedDir - ' ' - !Ref EFSMountPoint NotebookInstance: Type: "AWS::SageMaker::NotebookInstance" DependsOn: [ MountTarget1, MountTarget2, MountTarget3, VpcS3Endpoint ] Properties: NotebookInstanceName: !Ref NotebookInstanceName InstanceType: !Ref NotebookInstanceType RoleArn: !GetAtt ExecutionRole.Arn RootAccess: Enabled SecurityGroupIds: - !GetAtt VpcSecurityGroup.GroupId SubnetId: !Ref PrivateSubnet1 DirectInternetAccess: Disabled AdditionalCodeRepositories: !If - CreateCodeRepo - [!GetAtt CodeRepo.CodeRepositoryName] - !Ref 'AWS::NoValue' LifecycleConfigName: !GetAtt NotebookStartConfig.NotebookInstanceLifecycleConfigName VolumeSizeInGB: !Ref EbsVolumeSize Tags: - Key: Name Value: !Ref 'AWS::StackName' ExecutionRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "sagemaker.amazonaws.com" Action: - "sts:AssumeRole" Path: "/" Policies: - PolicyName: "sagemaker-policy" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "ecr:GetAuthorizationToken" - "ecr:BatchCheckLayerAvailability" - "ecr:GetDownloadUrlForLayer" - "ecr:GetRepositoryPolicy" - "ecr:DescribeRepositories" - "ecr:ListImages" - "ecr:DescribeImages" - "ecr:BatchGetImage" - "ecr:InitiateLayerUpload" - "ecr:UploadLayerPart" - "ecr:CompleteLayerUpload" - "ecr:PutImage" - "ecr:CreateRepository" - "fsx:*" - "sagemaker:*" - "cloudformation:*" - "ec2:CreateNetworkInterface" - "ec2:CreateNetworkInterfacePermission" - "ec2:CreateVpcEndpoint" - "ec2:DeleteNetworkInterface" - "ec2:DeleteNetworkInterfacePermission" - "ec2:DescribeDhcpOptions" - "ec2:DescribeNetworkInterfaces" - "ec2:DescribeRouteTables" - "ec2:DescribeSecurityGroups" - "ec2:DescribeSubnets" - "ec2:DescribeVpcEndpoints" - "ec2:DescribeVpcs" - "logs:CreateLogGroup" - "logs:CreateLogStream" - "logs:DescribeLogStreams" - "logs:GetLogEvents" - "logs:PutLogEvents" - "cloudwatch:DeleteAlarms" - "cloudwatch:DescribeAlarms" - "cloudwatch:GetMetricData" - "cloudwatch:GetMetricStatistics" - "cloudwatch:ListMetrics" - "cloudwatch:PutMetricAlarm" - "cloudwatch:PutMetricData" - "iam:PassRole" - "iam:ListRoles" - "kms:DescribeKey" - "kms:ListAliases" - "elasticfilesystem:DescribeMountTargets" Resource: "*" - Effect: "Allow" Action: - "iam:CreateServiceLinkedRole" - "iam:AttachRolePolicy" - "iam:PutRolePolicy" Resource: "arn:aws:iam::*:role/aws-service-role/*" - Effect: "Allow" Action: - "s3:ListBucket*" - "s3:GetBucket*" Resource: Fn::Join: - '' - - "arn:aws:s3:::" - !Ref S3BucketName - Effect: "Allow" Action: - "s3:AbortMultipartUpload" - "s3:ListMultipartUploadParts" - "s3:PutObject*" - "s3:DeleteObject*" - "s3:GetObject*" Resource: - Fn::Join: - '' - - "arn:aws:s3:::" - !Ref S3BucketName - Fn::Join: - '' - - "arn:aws:s3:::" - !Ref S3BucketName - "/*" Outputs: SageMakerExecutionRoleArn: Value: !GetAtt ExecutionRole.Arn SecurityGroup: Value: Fn::Join: - '' - - '[' - '''' - !GetAtt VpcSecurityGroup.GroupId - '''' - ']' Subnets: Value: Fn::Join: - '' - - '[' - '''' - !Ref PrivateSubnet1 - '''' - ',' - '''' - !Ref PrivateSubnet2 - '''' - ',' - '''' - !Ref PrivateSubnet3 - '''' - ']'